mirror of
https://github.com/suitenumerique/docs.git
synced 2026-09-10 11:47:52 +02:00
🐛(frontend) preserve title when adding an emoji
The emoji action could reuse a stale document title while a rename was being submitted, replacing the new title with the emoji alone. Keep the latest submitted title in the header and cover the interaction with a regression test. Signed-off-by: fch-aa <21101725+fch-aa@users.noreply.github.com>
This commit is contained in:
@@ -6,6 +6,10 @@ and this project adheres to
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- 🐛(frontend) preserve page titles when adding an emoji #2586
|
||||
|
||||
## [v5.6.1] - 2026-09-04
|
||||
|
||||
### Added
|
||||
|
||||
+25
-1
@@ -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,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -33,6 +37,7 @@ describe('DocHeader - Add emoji (April Fools easter egg)', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
mockUpdateDocEmoji.mockClear();
|
||||
mockUpdateDocTitle.mockClear();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -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',
|
||||
'📄',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Button } from '@gouvfr-lasuite/ui-components';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { css } from 'styled-components';
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
@@ -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 ? '🐟' : '📄',
|
||||
);
|
||||
}}
|
||||
@@ -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' }} />
|
||||
|
||||
@@ -21,9 +21,10 @@ 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;
|
||||
|
||||
@@ -31,7 +32,7 @@ export const DocTitle = ({ doc }: DocTitleProps) => {
|
||||
return <DocTitleText />;
|
||||
}
|
||||
|
||||
return <DocTitleInput doc={doc} />;
|
||||
return <DocTitleInput doc={doc} onTitleUpdate={onTitleUpdate} />;
|
||||
};
|
||||
|
||||
export const DocTitleText = () => {
|
||||
@@ -103,7 +104,7 @@ const DocTitleEmojiPicker = ({ doc }: DocTitleProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
const DocTitleInput = ({ doc }: DocTitleProps) => {
|
||||
const DocTitleInput = ({ doc, onTitleUpdate }: DocTitleProps) => {
|
||||
const { isSmallMobile } = useResponsiveStore();
|
||||
const { t } = useTranslation();
|
||||
const { isTopRoot } = useDocUtils(doc);
|
||||
@@ -121,6 +122,8 @@ const DocTitleInput = ({ doc }: DocTitleProps) => {
|
||||
if (isTopRoot) {
|
||||
const sanitizedTitle = updateDocTitle(doc, inputText);
|
||||
setTitleDisplay(sanitizedTitle);
|
||||
onTitleUpdate?.(sanitizedTitle);
|
||||
|
||||
return sanitizedTitle;
|
||||
} else {
|
||||
const { emoji: pastedEmoji } = getEmojiAndTitle(inputText);
|
||||
@@ -136,9 +139,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) => {
|
||||
|
||||
Reference in New Issue
Block a user