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/App.coverage.test.tsx b/frontend/src/App.coverage.test.tsx index 0b9a20aa8..061d0ea48 100644 --- a/frontend/src/App.coverage.test.tsx +++ b/frontend/src/App.coverage.test.tsx @@ -260,6 +260,9 @@ const snapshots = [ beforeEach(() => { 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/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({ 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