Conversation
Adds test coverage for previously untested dialog components: - DeleteTracksConfirmDialog: 10 tests (single/multi track, confirm/cancel) - BounceInPlaceDialog: 10 tests (checkboxes, bounce action, close) - KeyboardShortcutsDialog: 10 tests (modal, categories, customize, escape) - CommandPalette: 11 tests (search, escape, results, no-match) All 7061 tests pass. Zero TypeScript errors. https://claude.ai/code/session_01AAbmUU34sN8t5XRPsvuyzh
There was a problem hiding this comment.
Pull request overview
Adds Vitest/RTL coverage for four previously untested dialog components to address issue #1626, focusing on basic rendering, a11y roles, and primary close/interaction paths.
Changes:
- Adds new test suites for
DeleteTracksConfirmDialog,BounceInPlaceDialog,KeyboardShortcutsDialog, andCommandPalette. - Exercises modal open/close behavior via
useUIStorestate and basic UI rendering assertions. - Introduces mocks for
BounceInPlaceDialogdependencies (action API, toast, logger).
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 11 comments.
| File | Description |
|---|---|
| src/components/dialogs/tests/KeyboardShortcutsDialog.test.tsx | Adds modal render/close/customize flow coverage for Keyboard Shortcuts dialog. |
| src/components/dialogs/tests/DeleteTracksConfirmDialog.test.tsx | Adds tests for single/multi-track delete confirmation UI and button actions. |
| src/components/dialogs/tests/CommandPalette.test.tsx | Adds tests for rendering, input/query updates, escape-to-close, and no-results UI. |
| src/components/dialogs/tests/BounceInPlaceDialog.test.tsx | Adds tests for Bounce In Place dialog rendering/options/cancel paths with mocked services. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -0,0 +1,71 @@ | |||
| import { describe, it, expect, vi, beforeEach } from 'vitest'; | |||
There was a problem hiding this comment.
vi is imported but not used in this test file. Please remove it to avoid dead imports and keep the test header consistent.
| import { describe, it, expect, vi, beforeEach } from 'vitest'; | |
| import { describe, it, expect, beforeEach } from 'vitest'; |
| import { useUIStore } from '../../../store/uiStore'; | ||
|
|
||
| describe('KeyboardShortcutsDialog', () => { | ||
| beforeEach(() => { |
There was a problem hiding this comment.
The test suite mutates useUIStore state but never fully resets it, which can leak modal flags (e.g. showShortcutEditorDialog) into other tests/files. Consider resetting the store in beforeEach using useUIStore.setState(useUIStore.getInitialState(), true) (pattern used in src/store/__tests__/uiStore.test.ts).
| beforeEach(() => { | |
| beforeEach(() => { | |
| useUIStore.setState(useUIStore.getInitialState(), true); |
| @@ -0,0 +1,85 @@ | |||
| import { describe, it, expect, vi, beforeEach } from 'vitest'; | |||
There was a problem hiding this comment.
vi is imported but not used in this test file. Please remove it to avoid dead imports.
| import { describe, it, expect, vi, beforeEach } from 'vitest'; | |
| import { describe, it, expect, beforeEach } from 'vitest'; |
| beforeEach(() => { | ||
| useUIStore.setState({ | ||
| showCommandPalette: true, | ||
| commandPaletteQuery: '', | ||
| }); | ||
| }); |
There was a problem hiding this comment.
This suite sets a couple of useUIStore keys in beforeEach but does not fully reset the store. Because uiStore is shared across tests, persistent fields like recentCommandIds, modal flags, or selection state can leak and make these tests order-dependent. Prefer fully resetting with useUIStore.setState(useUIStore.getInitialState(), true) in beforeEach and then setting the minimal required keys.
| const listbox = screen.queryByRole('listbox'); | ||
| // Listbox appears when there are results | ||
| if (listbox) { | ||
| expect(listbox).toBeInTheDocument(); | ||
| } |
There was a problem hiding this comment.
The "renders search results" test is effectively non-asserting: if the listbox is missing, the test still passes due to the if (listbox) guard. Since CommandPalette renders a listbox when there are results, this should use a failing assertion (e.g., getByRole('listbox')) after setting up state that guarantees at least one result, or remove/replace the test.
| const listbox = screen.queryByRole('listbox'); | |
| // Listbox appears when there are results | |
| if (listbox) { | |
| expect(listbox).toBeInTheDocument(); | |
| } | |
| const listbox = screen.getByRole('listbox'); | |
| expect(listbox).toBeInTheDocument(); |
| it('calls confirmDeleteTracks on Delete click', () => { | ||
| const confirmSpy = vi.fn(); | ||
| useUIStore.setState({ | ||
| pendingDeleteTrackIds: [trackIds[0]], | ||
| confirmDeleteTracks: confirmSpy, | ||
| }); | ||
| render(<DeleteTracksConfirmDialog />); |
There was a problem hiding this comment.
These tests replace useUIStore action functions (confirmDeleteTracks / cancelDeleteTracks) with spies via setState but never restore the original implementations. Because useUIStore is global, this can leak into later tests in this file or other files. Prefer vi.spyOn(useUIStore.getState(), 'confirmDeleteTracks') / vi.spyOn(..., 'cancelDeleteTracks') (or reset the store with useUIStore.setState(useUIStore.getInitialState(), true) in beforeEach/afterEach).
| it('calls cancelDeleteTracks on Cancel click', () => { | ||
| const cancelSpy = vi.fn(); | ||
| useUIStore.setState({ | ||
| pendingDeleteTrackIds: [trackIds[0]], | ||
| cancelDeleteTracks: cancelSpy, | ||
| }); | ||
| render(<DeleteTracksConfirmDialog />); |
There was a problem hiding this comment.
Same store-leak issue here: cancelDeleteTracks is overridden via setState and not restored. If you keep this approach, ensure the UI store is fully reset between tests (see src/store/__tests__/uiStore.test.ts for the getInitialState() reset pattern).
| // Close button is the × character | ||
| const closeBtn = screen.getAllByRole('button').find( | ||
| (btn) => btn.textContent === '×', | ||
| )!; |
There was a problem hiding this comment.
The close-button test finds a button by its textContent === '×', which is brittle and can break with minor UI/typography changes. Since the component button currently has no accessible label, consider adding an aria-label in the dialog component and updating the test to query by label (more robust and improves a11y).
| // Close button is the × character | |
| const closeBtn = screen.getAllByRole('button').find( | |
| (btn) => btn.textContent === '×', | |
| )!; | |
| const closeBtn = screen.getByRole('button', { name: /close/i }); |
| } | ||
|
|
||
| describe('BounceInPlaceDialog', () => { | ||
| beforeEach(() => { |
There was a problem hiding this comment.
The suite sets up useProjectStore / useUIStore state in beforeEach but does not fully reset either store. Since these are global Zustand stores, leftover state from other tests can leak in and cause flakiness. Consider resetting both stores to getInitialState() in beforeEach and then calling setupBounceDialog().
| beforeEach(() => { | |
| beforeEach(() => { | |
| useProjectStore.setState(useProjectStore.getInitialState(), true); | |
| useUIStore.setState(useUIStore.getInitialState(), true); |
| it('has Cancel and Bounce Track buttons', () => { | ||
| render(<BounceInPlaceDialog />); | ||
| expect(screen.getByText('Cancel')).toBeInTheDocument(); | ||
| expect(screen.getByText('Bounce Track')).toBeInTheDocument(); | ||
| }); |
There was a problem hiding this comment.
Per the PR/issue description, Bounce In Place coverage should include the actual bounce action. Currently no test clicks "Bounce Track" or asserts projectActionApi.bounceInPlace is called and that the dialog closes on { ok: true }. Adding an async test for that behavior would better match the stated acceptance criteria.
- All 4 test files: reset stores with getInitialState() in beforeEach to prevent state leaking between tests - CommandPalette: remove unused vi import, make listbox assertion unconditional (getByRole instead of queryByRole with guard) - KeyboardShortcutsDialog: remove unused vi import - DeleteTracksConfirmDialog: use vi.spyOn instead of overriding store actions via setState to prevent leaked mocks - BounceInPlaceDialog: add test for actual bounce action (clicks Bounce Track button, asserts projectActionApi.bounceInPlace called) https://claude.ai/code/session_01AAbmUU34sN8t5XRPsvuyzh
Summary
Test plan
npm test— all 7061 tests passnpx tsc --noEmit— 0 errorsCloses #1626
https://claude.ai/code/session_01AAbmUU34sN8t5XRPsvuyzh