From acb08f7e149507d3fa802ef039860e9bcb700f3a Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Wed, 1 Jul 2026 15:17:41 +0000 Subject: [PATCH] feat: make notes keep open when suddenly closed (#7) ## What - New feature allowing open notes to be restore, if the tab is closed ## Why - Making users' life easier ## Mood Reviewed-on: https://lightroasted.vps-kinghost.net/rmcampos/tasknote/pulls/7 --- client/src/__test__/views/Home.test.tsx | 96 ++++++++++++++++++++++++- client/src/views/Home/index.tsx | 24 ++++++- 2 files changed, 117 insertions(+), 3 deletions(-) diff --git a/client/src/__test__/views/Home.test.tsx b/client/src/__test__/views/Home.test.tsx index 20ea28b..52dd5e9 100644 --- a/client/src/__test__/views/Home.test.tsx +++ b/client/src/__test__/views/Home.test.tsx @@ -55,7 +55,19 @@ vi.mock('../../components/AlertError', () => ({ })); vi.mock('../../components/ModalMarkdown', () => ({ - default: (props: any) =>
{props.show ? 'Modal Open' : ''}
+ default: (props: any) => ( +
+ {props.show ? ( +
+
{props.title}
+
{props.markdownText}
+ +
+ ) : ( + '' + )} +
+ ) })); vi.mock('../../components/TaskTitle', () => ({ @@ -67,7 +79,14 @@ vi.mock('../../components/TaskTimeLeft', () => ({ })); vi.mock('../../components/TaskTag', () => ({ - default: (props: any) =>
{props.tag}
+ default: (props: any) => ( +
+ {props.tag} + {props.taskOrNote === 'note' && props.onClick && ( + Open it + )} +
+ ) })); vi.mock('../../components/NoteTitle', () => ({ @@ -545,6 +564,79 @@ describe('Home Component', () => { expect(screen.getAllByTestId('task-title')[0].textContent).toBe('Task 1'); }); }); + + test('saves note ID to localStorage when opening modal', async () => { + await act(async () => { + renderHome(); + }); + + await waitFor(() => { + expect(screen.getAllByTestId('open-it').length).toBe(2); + }); + + const openItLinks = screen.getAllByTestId('open-it'); + await act(async () => { + fireEvent.click(openItLinks[1]); + }); + + expect(localStorage.getItem('OPEN_NOTE_ID')).toBe('1'); + expect(screen.getByTestId('modal-title').textContent).toBe('Note 1'); + expect(screen.getByTestId('modal-content').textContent).toBe('Line 1\nLine 2\nLine 3'); + }); + + test('restores open note modal from localStorage on reload', async () => { + localStorage.setItem('OPEN_NOTE_ID', '2'); + + await act(async () => { + renderHome(); + }); + + await waitFor(() => { + expect(screen.getByTestId('modal-title').textContent).toBe('Note 2'); + }); + + expect(screen.getByTestId('modal-content').textContent).toBe('This is a sample\nnote content'); + }); + + test('does not restore modal if localStorage note ID not found', async () => { + localStorage.setItem('OPEN_NOTE_ID', '999'); + + await act(async () => { + renderHome(); + }); + + await waitFor(() => { + expect(screen.getAllByTestId('note-title').length).toBe(2); + }); + + const modal = screen.getByTestId('modal-markdown'); + expect(modal.textContent).toBe(''); + expect(localStorage.getItem('OPEN_NOTE_ID')).toBeNull(); + }); + + test('clears localStorage when closing modal', async () => { + await act(async () => { + renderHome(); + }); + + await waitFor(() => { + expect(screen.getAllByTestId('open-it').length).toBe(2); + }); + + const openItLinks = screen.getAllByTestId('open-it'); + await act(async () => { + fireEvent.click(openItLinks[1]); + }); + + expect(localStorage.getItem('OPEN_NOTE_ID')).toBe('1'); + + const closeButton = screen.getByTestId('modal-close'); + await act(async () => { + fireEvent.click(closeButton); + }); + + expect(localStorage.getItem('OPEN_NOTE_ID')).toBeNull(); + }); /* test('getFirstRows properly formats note preview', async () => { await act(async () => { diff --git a/client/src/views/Home/index.tsx b/client/src/views/Home/index.tsx index 2e526b8..b2222ca 100644 --- a/client/src/views/Home/index.tsx +++ b/client/src/views/Home/index.tsx @@ -28,6 +28,8 @@ import TaskTimeLeft from '../../components/TaskTimeLeft'; import TaskTag from '../../components/TaskTag'; import NoteTitle from '../../components/NoteTitle'; +const OPEN_NOTE_ID_KEY = 'OPEN_NOTE_ID'; + /** * Home page component. * @@ -299,7 +301,10 @@ function Home(): React.ReactNode { return preview.join('\n'); }; - const handleCloseModal = () => setShowMarkdownView(false); + const handleCloseModal = () => { + setShowMarkdownView(false); + localStorage.removeItem(OPEN_NOTE_ID_KEY); + }; const getSelectedLabel = (): string => { if (selectedOption === 'everything') return t('home_radio_everything'); @@ -345,6 +350,22 @@ function Home(): React.ReactNode { applyFilter(filterText, selectedOption, savedTasks, savedNotes); }, [savedTasks, savedNotes, filterText, selectedOption]); + useEffect(() => { + const openNoteId = localStorage.getItem(OPEN_NOTE_ID_KEY); + if (openNoteId && notes.length > 0) { + const noteId = Number(openNoteId); + const foundNote = notes.find(n => n.id === noteId); + if (foundNote) { + setModalTitle(foundNote.title); + setModalContent(foundNote.description); + setShowMarkdownView(true); + } + else { + localStorage.removeItem(OPEN_NOTE_ID_KEY); + } + } + }, [notes]); + return (