Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ and this project adheres to

### Fixed

- 🐛(frontend) preserve page titles when adding an emoji
- 🐛(frontend) refresh pins after document deletion and restoration
- 🐛(frontend) redirect homepage to login when homepage feat is disabled #2521
- 🐛(backend) ignore CSPs for API docs in development
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
import { AppWrapper } from '@/tests/utils';

const mockUpdateDocEmoji = vi.fn();
const mockUpdateDocTitle = vi.fn((_doc: unknown, title: string) => title);

vi.mock('@/docs/doc-management', async () => {
const actual = await vi.importActual('@/docs/doc-management');
return {
...actual,
useDocTitleUpdate: () => ({ updateDocEmoji: mockUpdateDocEmoji }),
useDocTitleUpdate: () => ({
updateDocEmoji: mockUpdateDocEmoji,
updateDocTitle: mockUpdateDocTitle,
}),
};
});

Expand All @@ -33,6 +37,7 @@ describe('DocHeader - Add emoji (April Fools easter egg)', () => {
beforeEach(() => {
vi.useFakeTimers();
mockUpdateDocEmoji.mockClear();
mockUpdateDocTitle.mockClear();
});

afterEach(() => {
Expand All @@ -58,4 +63,23 @@ describe('DocHeader - Add emoji (April Fools easter egg)', () => {
);
});
});

test('preserves a title changed immediately before adding an emoji', () => {
vi.setSystemTime(new Date('2026-03-30'));

render(<DocHeader doc={{ ...doc, title: '' }} />, {
wrapper: AppWrapper,
});

const titleInput = screen.getByRole('textbox', { name: 'Document title' });
titleInput.textContent = 'My new document';
fireEvent.blur(titleInput);
fireEvent.click(screen.getByRole('button', { name: 'Add icon' }));

expect(mockUpdateDocEmoji).toHaveBeenCalledWith(
'doc-1',
'My new document',
'📄',
);
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Button } from '@gouvfr-lasuite/cunningham-react';
import { useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { css } from 'styled-components';

Expand Down Expand Up @@ -31,6 +32,11 @@ export const DocHeader = ({ doc }: DocHeaderProps) => {
const { updateDocEmoji } = useDocTitleUpdate();
const { isTopRoot } = useDocUtils(doc);
const displayEmojiButton = doc.abilities.partial_update && !isTopRoot;
const latestTitleRef = useRef(doc.title ?? '');

useEffect(() => {
latestTitleRef.current = doc.title ?? '';
}, [doc.title]);

return (
<>
Expand Down Expand Up @@ -74,10 +80,10 @@ export const DocHeader = ({ doc }: DocHeaderProps) => {
const isAprilFools =
today.getMonth() === 3 && today.getDate() === 1;
emoji
? updateDocEmoji(doc.id, doc.title ?? '', '')
? updateDocEmoji(doc.id, latestTitleRef.current, '')
: updateDocEmoji(
doc.id,
doc.title ?? '',
latestTitleRef.current,
isAprilFools ? '🐟' : '📄',
);
}}
Expand All @@ -97,7 +103,12 @@ export const DocHeader = ({ doc }: DocHeaderProps) => {
</Button>
)}
</Box>
<DocTitle doc={doc} />
<DocTitle
doc={doc}
onTitleUpdate={(title) => {
latestTitleRef.current = title;
}}
/>
<DocHeaderInfo doc={doc} />
</Box>
<HorizontalSeparator $margin={{ top: '24px' }} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,18 @@ export const CLASS_DOC_TITLE = '--docs--doc-title';

interface DocTitleProps {
doc: Doc;
onTitleUpdate?: (title: string) => void;
}

export const DocTitle = ({ doc }: DocTitleProps) => {
export const DocTitle = ({ doc, onTitleUpdate }: DocTitleProps) => {
const { isEditable, isLoading } = useIsCollaborativeEditable(doc);
const readOnly = !doc.abilities.partial_update || !isEditable || isLoading;

if (readOnly) {
return <DocTitleText />;
}

return <DocTitleInput doc={doc} />;
return <DocTitleInput doc={doc} onTitleUpdate={onTitleUpdate} />;
};

export const DocTitleText = () => {
Expand Down Expand Up @@ -98,7 +99,7 @@ const DocTitleEmojiPicker = ({ doc }: DocTitleProps) => {
);
};

const DocTitleInput = ({ doc }: DocTitleProps) => {
const DocTitleInput = ({ doc, onTitleUpdate }: DocTitleProps) => {
const { isSmallMobile } = useResponsiveStore();
const { t } = useTranslation();
const { isTopRoot } = useDocUtils(doc);
Expand All @@ -116,6 +117,8 @@ const DocTitleInput = ({ doc }: DocTitleProps) => {
if (isTopRoot) {
const sanitizedTitle = updateDocTitle(doc, inputText);
setTitleDisplay(sanitizedTitle);
onTitleUpdate?.(sanitizedTitle);

return sanitizedTitle;
} else {
const { emoji: pastedEmoji } = getEmojiAndTitle(inputText);
Expand All @@ -131,9 +134,10 @@ const DocTitleInput = ({ doc }: DocTitleProps) => {
getEmojiAndTitle(sanitizedTitle);

setTitleDisplay(sanitizedTitleWithoutEmoji);
onTitleUpdate?.(sanitizedTitle);
}
},
[updateDocTitle, doc, emoji, isTopRoot],
[updateDocTitle, doc, emoji, isTopRoot, onTitleUpdate],
);

const handleKeyDown = (e: React.KeyboardEvent) => {
Expand Down