From 8a6427d31a8eeccbea40a94250830204dd23551f Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sun, 9 Aug 2026 02:04:28 +0000 Subject: [PATCH 1/4] Add contextual aria-labels to EditTableModal action buttons --- .Jules/palette.md | 3 +++ frontend/src/components/modals/EditTableModal.test.tsx | 2 +- frontend/src/components/modals/EditTableModal.tsx | 2 ++ frontend/src/components/modals/ModalCoverage.test.tsx | 4 ++-- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.Jules/palette.md b/.Jules/palette.md index 7577a1ca7..1b6139d07 100644 --- a/.Jules/palette.md +++ b/.Jules/palette.md @@ -56,3 +56,6 @@ ## 2024-07-14 - Native Keyboard Submission with Forms for Modals **Learning:** Modals designed with plain `
` elements as wrappers instead of `
` lack native keyboard submission support, forcing users to switch from keyboard to mouse to confirm actions like "Save". **Action:** When designing modals or popups containing inputs, always use a `` element to wrap the content, handle the `onSubmit` event (calling `e.preventDefault()`), and set the primary confirmation button to `type="submit"` to enable seamless Enter-key submission for keyboard users. +## 2026-07-15 - Contextual aria-labels for generic modal actions +**Learning:** Generic action buttons (like "Delete" or "Duplicate") placed globally at the bottom of modals can lose context for screen reader users, especially when the modal's primary title doesn't clearly articulate the active entity in real-time. +**Action:** When adding global modal actions, always ensure the buttons have contextual `aria-label` attributes incorporating the specific entity's name (e.g., `aria-label={\`\${entity.title} 삭제\`}`) to provide unambiguous feedback. diff --git a/frontend/src/components/modals/EditTableModal.test.tsx b/frontend/src/components/modals/EditTableModal.test.tsx index 1a8c9af5e..802449195 100644 --- a/frontend/src/components/modals/EditTableModal.test.tsx +++ b/frontend/src/components/modals/EditTableModal.test.tsx @@ -170,7 +170,7 @@ describe('EditTableModal', () => { render(); const user = userEvent.setup(); - await user.click(screen.getByRole('button', { name: '복제' })); + await user.click(screen.getByRole('button', { name: 'test_table 테이블 복제' })); expect(setNodesMock).toHaveBeenCalled(); expect(onEditTableCancelMock).toHaveBeenCalled(); diff --git a/frontend/src/components/modals/EditTableModal.tsx b/frontend/src/components/modals/EditTableModal.tsx index 998929d44..153407008 100644 --- a/frontend/src/components/modals/EditTableModal.tsx +++ b/frontend/src/components/modals/EditTableModal.tsx @@ -191,6 +191,7 @@ export function EditTableModal({ type="button" onClick={onDeleteTable} style={{ color: "#b91c1c", borderColor: "#fca5a5" }} + aria-label={`${editingNode.data.title} 테이블 삭제`} > 테이블 삭제 @@ -218,6 +219,7 @@ export function EditTableModal({ onEditTableCancel(); }} style={{ color: "#034ea2", borderColor: "#93c5fd" }} + aria-label={`${editingNode.data.title} 테이블 복제`} > 복제 diff --git a/frontend/src/components/modals/ModalCoverage.test.tsx b/frontend/src/components/modals/ModalCoverage.test.tsx index 8732f7efa..1553eb059 100644 --- a/frontend/src/components/modals/ModalCoverage.test.tsx +++ b/frontend/src/components/modals/ModalCoverage.test.tsx @@ -183,8 +183,8 @@ describe('modal behavior coverage', () => { expect(deleteEditing(tableNode)?.data.columns).toHaveLength(1) fireEvent.submit(document.getElementById('editTableForm')!) - fireEvent.click(screen.getByRole('button', { name: '테이블 삭제' })) - fireEvent.click(screen.getByRole('button', { name: '복제' })) + fireEvent.click(screen.getByRole('button', { name: 'public.users 테이블 삭제' })) + fireEvent.click(screen.getByRole('button', { name: 'public.users 테이블 복제' })) const duplicate = setNodes.mock.calls[2]?.[0] as (nodes: Node[]) => Node[] const duplicated = duplicate([tableNode])[1]! expect(duplicated).toMatchObject({ From d997dd649e600ba66095a8975214c9df08a9da3e Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sun, 9 Aug 2026 02:54:32 +0000 Subject: [PATCH 2/4] Add contextual aria-labels to EditTableModal action buttons From 3a0dbef7db2a3456bf154800d8b3cbdebb4cccb5 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sun, 9 Aug 2026 03:00:21 +0000 Subject: [PATCH 3/4] Fix test flakiness and add contextual aria-labels --- frontend/src/App.coverage.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/App.coverage.test.tsx b/frontend/src/App.coverage.test.tsx index 0b9a20aa8..deda90262 100644 --- a/frontend/src/App.coverage.test.tsx +++ b/frontend/src/App.coverage.test.tsx @@ -259,7 +259,7 @@ const snapshots = [ ] beforeEach(() => { - vi.clearAllMocks() + vi.resetAllMocks() api.getMe.mockResolvedValue({ subject: 'user', display_name: 'User', user_account_uuid: 'u' }) api.listProjects.mockResolvedValue(projects) api.listConnections.mockResolvedValue(connections) From 6586a370946c7728b863d88eb2b44994ee50fdd9 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sun, 9 Aug 2026 03:07:59 +0000 Subject: [PATCH 4/4] Fix App.coverage.test.tsx mock leaking --- frontend/src/App.coverage.test.tsx | 5 ++++- test-mock.js | 9 +++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 test-mock.js diff --git a/frontend/src/App.coverage.test.tsx b/frontend/src/App.coverage.test.tsx index deda90262..061d0ea48 100644 --- a/frontend/src/App.coverage.test.tsx +++ b/frontend/src/App.coverage.test.tsx @@ -259,7 +259,10 @@ const snapshots = [ ] beforeEach(() => { - vi.resetAllMocks() + vi.clearAllMocks() + for (const method of Object.values(api)) { + method.mockReset() + } api.getMe.mockResolvedValue({ subject: 'user', display_name: 'User', user_account_uuid: 'u' }) api.listProjects.mockResolvedValue(projects) api.listConnections.mockResolvedValue(connections) diff --git a/test-mock.js b/test-mock.js new file mode 100644 index 000000000..375699724 --- /dev/null +++ b/test-mock.js @@ -0,0 +1,9 @@ +const { vi } = require('vitest'); + +const fn = vi.fn(() => 'base'); +fn.mockReturnValueOnce('once'); +console.log(fn()); // once +fn.mockReturnValueOnce('once2'); +fn.mockReset(); +fn.mockImplementation(() => 'base2'); +console.log(fn()); // base2