From 3e191f4c2c3753d807bebe1b94f32d469a1d37ab Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Mon, 27 Jul 2026 02:23:38 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20=EB=AA=A8=EB=8B=AC=20?= =?UTF-8?q?=EB=82=B4=20=EC=9D=BC=EB=B0=98=20=EB=B2=84=ED=8A=BC=EC=9D=98=20?= =?UTF-8?q?=EC=A0=91=EA=B7=BC=EC=84=B1(ARIA)=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 모달(`EditEdgeModal`, `EditTableModal`) 내 일반적인 목적의 버튼(삭제, 취소, 저장, 복제)에 동적인 `aria-label` 추가 * 스크린 리더 사용자가 해당 버튼이 어떤 컨텍스트에서 동작하는지 명확히 인지할 수 있도록 컨텍스트 정보 포함 * 관련 프론트엔드 테스트 코드 업데이트 및 통과 확인 --- .jules/palette.md | 3 +++ frontend/src/components/modals/EditEdgeModal.tsx | 4 +++- .../src/components/modals/EditTableModal.test.tsx | 2 +- frontend/src/components/modals/EditTableModal.tsx | 5 ++++- .../src/components/modals/ModalCoverage.test.tsx | 12 ++++++------ 5 files changed, 17 insertions(+), 9 deletions(-) diff --git a/.jules/palette.md b/.jules/palette.md index d892e2f9..ed7f6bc2 100644 --- a/.jules/palette.md +++ b/.jules/palette.md @@ -54,3 +54,6 @@ ## 2024-07-13 - [Table Node Accessibility] **Learning:** Adding `aria-hidden="true"` inside `abbr` elements with `aria-label` prevents screen readers from redundantly announcing short abbreviations like "PK" or "NN" along with their full label. **Action:** When creating short, domain-specific abbreviations with tooltips, use `aria-label` on the wrapper and hide the visual text from screen readers using `aria-hidden="true"` to create a cleaner auditory experience. +## 2026-07-27 - [Descriptive ARIA labels for modal buttons] +**Learning:** Adding dynamic, contextual `aria-label` attributes to generic buttons (like Save, Cancel, Delete) inside modals significantly improves screen reader navigation and clarity, ensuring users understand exactly what action they are taking. +**Action:** Always provide contextual `aria-label`s for generic action buttons in dialogs using dynamic data like node or edge names. diff --git a/frontend/src/components/modals/EditEdgeModal.tsx b/frontend/src/components/modals/EditEdgeModal.tsx index c34b7e55..b3a3ae65 100644 --- a/frontend/src/components/modals/EditEdgeModal.tsx +++ b/frontend/src/components/modals/EditEdgeModal.tsx @@ -83,14 +83,16 @@ export function EditEdgeModal({ type="button" onClick={onRelDelete} style={{ color: "#b91c1c", borderColor: "#fca5a5" }} + aria-label={`관계 삭제 (${editingEdge.source}에서 ${editingEdge.target})`} > 삭제
- + diff --git a/frontend/src/components/modals/EditTableModal.test.tsx b/frontend/src/components/modals/EditTableModal.test.tsx index 1a8c9af5..80244919 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 998929d4..e3f58758 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,16 +219,18 @@ 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 aa9dac53..9d1229b1 100644 --- a/frontend/src/components/modals/ModalCoverage.test.tsx +++ b/frontend/src/components/modals/ModalCoverage.test.tsx @@ -116,9 +116,9 @@ describe('modal behavior coverage', () => { fireEvent.change(screen.getByLabelText('제약조건 이름 (Label)'), { target: { value: 'fk_changed' }, }) - fireEvent.click(screen.getByRole('button', { name: '삭제' })) - fireEvent.click(screen.getByRole('button', { name: '취소' })) - fireEvent.click(screen.getByRole('button', { name: '저장' })) + fireEvent.click(screen.getByRole('button', { name: '관계 삭제 (a에서 b)' })) + fireEvent.click(screen.getByRole('button', { name: '관계 편집 취소' })) + fireEvent.click(screen.getByRole('button', { name: '관계 저장 (a에서 b)' })) expect(setRelLabel).toHaveBeenCalledWith('fk_changed') expect(onDelete).toHaveBeenCalledOnce() expect(onCancel).toHaveBeenCalledOnce() @@ -181,8 +181,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({ @@ -191,7 +191,7 @@ describe('modal behavior coverage', () => { data: { title: 'public.users_copy' }, }) expect(duplicated.data.columns).not.toBe(tableNode.data.columns) - fireEvent.click(screen.getByRole('button', { name: '취소' })) + fireEvent.click(screen.getByRole('button', { name: '테이블 편집 취소' })) fireEvent.click(screen.getByRole('button', { name: '닫기' })) expect(onSubmit).toHaveBeenCalledOnce() expect(onDeleteTable).toHaveBeenCalledOnce()