refactor(v032): 탭 ARIA canonical + loadExpired dead-code 제거 (#14, #18)

- App.tsx 탭 button 의 aria-pressed → role="tab" + aria-selected
  (canonical pattern, a11y audit 정정)
- store.ts loadExpired action + test 제거 (App.tsx 호출 0건,
  loadInitial/refreshMeta 가 inline fetch — dead code)
This commit is contained in:
altair823
2026-05-10 14:12:37 +09:00
parent 302bbd4ce0
commit 9073e78169
4 changed files with 20 additions and 26 deletions

View File

@@ -106,8 +106,10 @@ export function App(): React.ReactElement {
).map((t) => (
<button
key={t.key}
type="button"
role="tab"
aria-selected={view === t.key}
onClick={() => setView(t.key)}
aria-pressed={view === t.key}
style={tabBtnStyle(view === t.key)}
>
{t.label}({t.count})

View File

@@ -58,7 +58,6 @@ interface InboxState {
restoreNote: (id: string) => Promise<void>;
permanentDeleteNote: (id: string) => Promise<void>;
emptyTrash: () => Promise<void>;
loadExpired: () => Promise<void>;
trashExpiredBatch: (ids: string[]) => Promise<void>;
snoozeExpired: () => void;
recheckOllama: () => Promise<void>;
@@ -235,10 +234,6 @@ export const useInbox = create<InboxState>((set, get) => ({
set({ trashNotes: [], trashCount: 0 });
}
},
async loadExpired() {
const expiredCandidates = await inboxApi.listExpired();
set({ expiredCandidates });
},
async trashExpiredBatch(ids: string[]) {
const r = await inboxApi.trashExpiredBatch(ids);
if (!r.confirmed) return;

View File

@@ -130,25 +130,31 @@ describe('App header — 4 tabs', () => {
it('renders 4 tabs with counts', async () => {
render(<App />);
expect(await screen.findByRole('button', { name: /Inbox\(5\)/ })).toBeInTheDocument();
expect(screen.getByRole('button', { name: /완료\(3\)/ })).toBeInTheDocument();
expect(screen.getByRole('button', { name: /보관\(2\)/ })).toBeInTheDocument();
expect(screen.getByRole('button', { name: /휴지통\(1\)/ })).toBeInTheDocument();
expect(await screen.findByRole('tab', { name: /Inbox\(5\)/ })).toBeInTheDocument();
expect(screen.getByRole('tab', { name: /완료\(3\)/ })).toBeInTheDocument();
expect(screen.getByRole('tab', { name: /보관\(2\)/ })).toBeInTheDocument();
expect(screen.getByRole('tab', { name: /휴지통\(1\)/ })).toBeInTheDocument();
});
it('clicking 완료 tab sets view=completed', async () => {
render(<App />);
fireEvent.click(await screen.findByRole('button', { name: /완료/ }));
fireEvent.click(await screen.findByRole('tab', { name: /완료/ }));
expect(useInbox.getState().view).toBe('completed');
});
it('aria-pressed reflects current view', async () => {
it('aria-selected reflects current view', async () => {
useInbox.setState({ view: 'archived' });
render(<App />);
const archivedBtn = await screen.findByRole('button', { name: /보관/ });
expect(archivedBtn.getAttribute('aria-pressed')).toBe('true');
const inboxBtn = screen.getByRole('button', { name: /Inbox/ });
expect(inboxBtn.getAttribute('aria-pressed')).toBe('false');
const archivedBtn = await screen.findByRole('tab', { name: /보관/ });
expect(archivedBtn.getAttribute('aria-selected')).toBe('true');
const inboxBtn = screen.getByRole('tab', { name: /Inbox/ });
expect(inboxBtn.getAttribute('aria-selected')).toBe('false');
});
it('inbox tab has aria-selected="true" when active', async () => {
render(<App />);
const inboxTab = await screen.findByRole('tab', { name: /Inbox/ });
expect(inboxTab).toHaveAttribute('aria-selected', 'true');
});
});
@@ -175,7 +181,7 @@ describe('App — onboarding wizard', () => {
it('does not render OnboardingWizard when onboarding_completed=true', async () => {
vi.mocked(inboxApi.getSettings).mockResolvedValue({ onboarding_completed: true });
render(<App />);
await screen.findByRole('button', { name: /Inbox/ });
await screen.findByRole('tab', { name: /Inbox/ });
expect(screen.queryByText(/Inkling 사용 시작/)).toBeNull();
});
});

View File

@@ -52,15 +52,6 @@ describe('useInbox — expired state (v0.2.3 #5)', () => {
afterEach(() => { vi.restoreAllMocks(); });
it('loadExpired sets expiredCandidates from inboxApi', async () => {
mockApi.listExpired.mockResolvedValueOnce([noteStub('n1')]);
const { useInbox } = await import('../../src/renderer/inbox/store.js');
await useInbox.getState().loadExpired();
const s = useInbox.getState();
expect(s.expiredCandidates).toHaveLength(1);
expect(s.expiredCandidates[0]!.id).toBe('n1');
});
it('trashExpiredBatch removes ids and increments trashCount when confirmed', async () => {
mockApi.trashExpiredBatch.mockResolvedValueOnce({ trashedCount: 2, confirmed: true });
const { useInbox } = await import('../../src/renderer/inbox/store.js');