From 1f126cf62941cd9f32cd291b96712a7fdcb7c9fe Mon Sep 17 00:00:00 2001 From: fch-aa <21101725+fch-aa@users.noreply.github.com> Date: Fri, 14 Aug 2026 13:37:35 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B(frontend)=20preserve=20title=20whe?= =?UTF-8?q?n=20adding=20an=20emoji?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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> --- CHANGELOG.md | 4 +++ .../__tests__/DocHeaderEmoji.spec.tsx | 26 ++++++++++++++++++- .../docs/doc-header/components/DocHeader.tsx | 17 +++++++++--- .../docs/doc-header/components/DocTitle.tsx | 12 ++++++--- 4 files changed, 51 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 357ad8bb1..50d2ddbb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocHeaderEmoji.spec.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocHeaderEmoji.spec.tsx index caa82a8da..636ff918e 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocHeaderEmoji.spec.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocHeaderEmoji.spec.tsx @@ -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(, { + 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', + '📄', + ); + }); }); diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeader.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeader.tsx index 7a2ac462b..d7790a76a 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeader.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeader.tsx @@ -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) => { )} - + { + latestTitleRef.current = title; + }} + /> diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocTitle.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocTitle.tsx index 36b1cd063..68923f65b 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocTitle.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocTitle.tsx @@ -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 ; } - return ; + return ; }; 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) => {