Skip to content

test: frontend view and component coverage gaps (#716)#826

Open
Chris0Jeky wants to merge 9 commits intomainfrom
test/frontend-view-component-coverage
Open

test: frontend view and component coverage gaps (#716)#826
Chris0Jeky wants to merge 9 commits intomainfrom
test/frontend-view-component-coverage

Conversation

@Chris0Jeky
Copy link
Copy Markdown
Owner

Summary

  • Adds 107 new test cases across 8 new test files for previously untested or under-tested frontend views and components
  • ArchiveView (11 tests): item restore flow (success, cancel, error, partial failure), empty/loading states, refresh action, entity type badges
  • MetricsView (16 tests): retry button, CSV export (enabled/disabled/error), forecast section (loading, error, data, confidence band, caveats, null date), ARIA accessibility attributes
  • BoardView (12 tests): loading/error states, column creation flow (submit, empty name, cancel), column rendering order, fetchBoard and realtime lifecycle, unmount cleanup
  • ReviewView (10 tests): approve action, apply-to-board with confirmation, reject with reason, error toasts, summary card counts, two-step flow indicator, risk level badges
  • AutomationChatView (16 tests): message sending (success, empty, error), message display and role labels, session creation/list/switching, loading state, LLM health states
  • CardItem (21 tests): context move menu (show/hide/select/escape), blocked badge, label rendering with colors, selection/aria state, click/keyboard events, description display
  • BoardCanvas (12 tests): empty state, column rendering with card counts, drag state CSS classes, drag event emissions, ARIA attributes
  • BoardActionRail (9 tests): all button emissions, review-first trust hint, data attribute, primary button distinction

Closes #716

Test plan

  • All 107 new test cases pass
  • Full existing frontend test suite still passes (2349 total tests, 198 files)
  • TypeScript typecheck passes

Adds 11 test cases covering item restore flow (success, cancel,
error, partial failure), empty states for items and boards, refresh
action, and entity type badge display.

Closes part of #716
…lity

Adds 16 test cases covering retry button, CSV export (enabled/disabled/
error), forecast section (loading, error, data, confidence band, caveats,
assumptions, null date), and ARIA accessibility attributes.

Closes part of #716
…anup

Adds 12 test cases covering loading/error states, column creation
flow (submit, empty name, cancel), column rendering order, fetchBoard
and realtime lifecycle, and unmount cleanup.

Closes part of #716
Adds 10 test cases covering approve action, apply-to-board action with
confirmation, reject with reason, error toasts for failures, summary
card counts, two-step flow indicator, and risk level badge rendering.

Closes part of #716
…alth

Adds 16 test cases covering message sending (success, empty, error),
message display and role labels, session creation (with/without board,
error), session list and switching, loading state, and LLM health
states (unavailable, verify flow).

Closes part of #716
…a11y

Adds 21 test cases covering move-to column menu (show/hide, column
listing, selection, escape close), blocked badge, label rendering
with colors, selection state and aria-selected, click/keyboard
events, tabindex, role, and description display.

Closes part of #716
… a11y

Adds 12 test cases covering empty state display, column rendering
with card counts, drag state CSS classes (opacity, scale), drag event
emissions, and ARIA role/label attributes on column wrappers.

Closes part of #716
Adds 9 test cases covering all five action button emissions (capture,
chat, review, inbox, addCard), review-first trust hint text,
data-board-action-rail attribute, and primary button distinction.

Closes part of #716
@Chris0Jeky
Copy link
Copy Markdown
Owner Author

Adversarial Self-Review

Findings

1. Good: Meaningful interaction testing

  • Tests consistently use trigger('click'), check emitted() events, and verify toHaveBeenCalledWith arguments rather than just checking static text rendering
  • The CardItem move menu tests verify the full open/select/close lifecycle, not just initial state
  • ReviewView tests cover the confirm() → API call → toast chain

2. Good: Error path coverage

  • ArchiveView: restore failure shows error toast AND keeps item in list
  • MetricsView: CSV export error, forecast error with retry button
  • ReviewView: approve/apply failure toasts
  • AutomationChatView: session creation failure, message send failure

3. Good: Accessibility assertions

  • BoardCanvas: role="group" and aria-label on column wrappers
  • CardItem: tabindex, role="option", aria-selected, aria-haspopup/aria-expanded on move menu
  • MetricsView: role="status" with aria-live on loading, role="alert" on error

4. Acceptable: Some tests check text content

  • Tests like "shows empty state" and "renders all action buttons" use toContain() for text. This is appropriate for view-level tests where the contract IS the user-visible text.

5. Minor gap: No negative flow on BoardCanvas drag

  • BoardCanvas tests check that drag events are emitted but don't test that columnDragStart is NOT emitted from the wrapper div itself (column DnD is handle-initiated in the actual ColumnLane). This is acceptable because ColumnLane tests already cover handle-initiated drag guardrails.

6. No remaining issues requiring fixes.
All tests pass, typecheck clean, patterns match existing test conventions.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a comprehensive suite of unit and integration tests for various frontend components and views, including BoardActionRail, BoardCanvas, CardItem, ArchiveView, AutomationChatView, BoardView, MetricsView, and ReviewView. The feedback focuses on improving test reliability and robustness. Specifically, it recommends consistently unmounting wrappers when using attachTo: document.body to prevent DOM pollution, replacing brittle Promise.resolve() chains with flushPromises() for handling asynchronous UI updates, and adding explicit assertions before using non-null operators to ensure clearer failure messages.

Comment on lines +123 to +141
function mountView() {
return mount(BoardView, {
attachTo: document.body,
global: {
stubs: {
ColumnLane: {
props: ['column'],
template: '<div :data-column-id="column.id"><span>{{ column.name }}</span></div>',
},
BoardSettingsModal: { template: '<div />' },
LabelManagerModal: { template: '<div />' },
StarterPackCatalogModal: { template: '<div />' },
KeyboardShortcutsHelp: { template: '<div />' },
FilterPanel: { template: '<div />' },
CaptureModal: { template: '<div />' },
},
},
})
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The mountView helper uses attachTo: document.body, but the tests do not consistently unmount the wrapper. This leads to DOM pollution where components from previous tests remain in the document, which can cause side effects (like duplicate IDs or roles) or slow down the test suite. It is recommended to store the wrapper and unmount it in an afterEach block, or avoid attachTo if not strictly necessary for these tests.

Comment on lines +39 to +42
async function waitForAsyncUi() {
await Promise.resolve()
await Promise.resolve()
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using multiple await Promise.resolve() calls to wait for asynchronous UI updates is brittle and relies on internal implementation details of the microtask queue. It is better to use flushPromises() from @vue/test-utils (after importing it), which waits for all pending promises to resolve, ensuring the component has finished its async lifecycle before assertions run.

async function waitForAsyncUi() {
  await flushPromises()
}

Comment on lines +20 to +21
const btn = wrapper.findAll('button').find((b) => b.text().trim() === 'Capture here')
await btn!.trigger('click')
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using the non-null assertion operator (!) on the result of find() is an anti-pattern in tests. If the button is missing due to a regression, the test will fail with a generic TypeError instead of a clear assertion failure. It is better to explicitly expect the element to be defined before interacting with it.

Suggested change
const btn = wrapper.findAll('button').find((b) => b.text().trim() === 'Capture here')
await btn!.trigger('click')
const btn = wrapper.findAll('button').find((b) => b.text().trim() === 'Capture here')
expect(btn).toBeDefined()
await btn!.trigger('click')

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds targeted frontend unit tests to close coverage gaps for several high-impact views and board-related components, improving confidence in common user flows and UI states.

Changes:

  • Added new coverage-focused specs for Review, Metrics, Board, Archive, and Automation Chat views.
  • Added new coverage-focused specs for key board UI components (CardItem, BoardCanvas, BoardActionRail).
  • Expanded assertions around loading/error/empty states and some accessibility attributes (ARIA roles/labels).

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
frontend/taskdeck-web/src/tests/views/ReviewView.coverage.spec.ts Adds tests for proposal review flows (approve/apply/reject), summary cards, risk badges, and error handling.
frontend/taskdeck-web/src/tests/views/MetricsView.coverage.spec.ts Adds tests for retry/export behaviors, forecast rendering states, and ARIA attributes.
frontend/taskdeck-web/src/tests/views/BoardView.coverage.spec.ts Adds tests for board loading/error, column creation, column ordering, realtime lifecycle, and help callout.
frontend/taskdeck-web/src/tests/views/AutomationChatView.coverage.spec.ts Adds tests for chat send flow, session creation/list/switching, loading state, and LLM health banners.
frontend/taskdeck-web/src/tests/views/ArchiveView.coverage.spec.ts Adds tests for archive restore flows, loading/empty/error states, refresh behavior, and entity type badges.
frontend/taskdeck-web/src/tests/components/CardItem.coverage.spec.ts Adds tests for card move menu behavior, blocked/labels rendering, selection/accessibility, and interactions.
frontend/taskdeck-web/src/tests/components/BoardCanvas.coverage.spec.ts Adds tests for empty state, column lane rendering, DnD CSS classes and emitted events, and ARIA labeling.
frontend/taskdeck-web/src/tests/components/BoardActionRail.coverage.spec.ts Adds tests for action button rendering/emissions and a few UI hints/attributes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

})

it('shows sessions loading indicator when sessions are being fetched', async () => {
const deferred = createDeferred<typeof buildSession[]>()
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createDeferred<typeof buildSession[]>() is typed as an array of the buildSession function type, not an array of session objects. This will fail TypeScript typechecking (and makes the deferred’s resolve signature wrong). Use createDeferred<ReturnType<typeof buildSession>[]>() (or a dedicated session type) instead.

Suggested change
const deferred = createDeferred<typeof buildSession[]>()
const deferred = createDeferred<ReturnType<typeof buildSession>[]>()

Copilot uses AI. Check for mistakes.
expect(restoreButton!.attributes('disabled')).toBeDefined()
})

it('does not cancel board restore when confirm is cancelled', async () => {
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test name is misleading: it asserts that a board restore is not performed when the confirm dialog is cancelled (i.e., updateBoard is not called). Consider renaming to something like “does not restore board when confirm is cancelled” so the intent matches the assertions.

Suggested change
it('does not cancel board restore when confirm is cancelled', async () => {
it('does not restore board when confirm is cancelled', async () => {

Copilot uses AI. Check for mistakes.
Comment on lines +123 to +126
function mountView() {
return mount(BoardView, {
attachTo: document.body,
global: {
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mountView() uses attachTo: document.body, but the mounted wrappers in these tests aren’t unmounted afterward. That leaves DOM nodes/component instances around across tests and can cause flaky behavior (event listeners, timers, Teleports). Consider either removing attachTo (if not needed) or adding an afterEach that unmounts the wrapper(s).

Copilot uses AI. Check for mistakes.
@Chris0Jeky
Copy link
Copy Markdown
Owner Author

Round 2 Adversarial Review

Critical (CI-blocking)

1. Lint failure: unused vi import in CardItem.coverage.spec.ts breaks CI
CardItem.coverage.spec.ts line 1 imports { describe, it, expect, vi } but vi is never used in the file. ESLint's @typescript-eslint/no-unused-vars fires as an error, which fails the Frontend Unit CI check on both Ubuntu and Windows. This is currently blocking the PR from merging.

Fix: Remove vi from the import.


Important

2. BoardView tests: DOM pollution from attachTo: document.body without cleanup
mountView() in BoardView.coverage.spec.ts mounts the component with attachTo: document.body but has no afterEach hook that unmounts the wrapper. Only one test (clears presence and stops realtime on unmount) explicitly calls wrapper.unmount(). The other 11 tests leak mounted components into the DOM across test runs, which can cause:

  • Accumulated DOM nodes affecting query selectors in later tests
  • Event listener leaks (SignalR stubs, watchers)
  • Flaky failures when tests interact with stale DOM

Compare with ReviewView.coverage.spec.ts which correctly stores the wrapper in mountedWrapper and unmounts in afterEach.

Fix: Add let mountedWrapper + afterEach(() => { mountedWrapper?.unmount(); mountedWrapper = null }) and assign in each mountView() call, following the ReviewView pattern.

3. AutomationChatView: createDeferred<typeof buildSession[]>() has incorrect generic type
On the line:

const deferred = createDeferred<typeof buildSession[]>()

typeof buildSession resolves to the function type (overrides?: Record<string, unknown>) => {...}, not the return type. typeof buildSession[] is parsed as (typeof buildSession)[] -- an array of function types. The intended type is ReturnType<typeof buildSession>[].

This doesn't blow up at runtime because deferred.resolve() is never called in the test, but it's a type-safety hole and would fail if someone later tried to resolve the deferred with actual session objects.

Fix: createDeferred<ReturnType<typeof buildSession>[]>()

4. waitForAsyncUi() uses chained Promise.resolve() -- brittle async flushing
Multiple test files (ArchiveView, AutomationChatView, BoardView) use:

async function waitForAsyncUi() {
  await Promise.resolve()
  await Promise.resolve()
}

This is a fragile microtask tick hack. If the component adds another async step (e.g., a .then() chain), these tests will break silently. MetricsView.coverage.spec.ts already uses the correct approach: flushPromises() from @vue/test-utils.

Not blocking, but inconsistency within a single PR is a maintenance risk.


Minor

5. Non-null assertions (!) on find() results hide root-cause failures
Throughout all test files, button lookups use wrapper.findAll('button').find(...) followed by btn!.trigger('click'). If the button text changes due to a product update, the test fails with a cryptic TypeError: Cannot read properties of undefined instead of a clear "button not found" assertion. At minimum, adding expect(btn).toBeDefined() before the ! call (as gemini already suggested) would improve debuggability.

6. ArchiveView test name: "does not cancel board restore when confirm is cancelled"
This test name is confusing -- it reads as "does NOT cancel" which suggests the restore proceeds, but the test actually asserts that updateBoard is NOT called (i.e., the restore IS cancelled). A clearer name: "does not restore board when confirm is cancelled".

7. ReviewView: originalPrompt saved but window.prompt only overridden in one test
The beforeEach saves originalPrompt = window.prompt and afterEach would ideally restore it, but the test that uses window.prompt = vi.fn(...) sets it inline. The beforeEach/afterEach save/restore is good practice, but adding window.prompt = originalPrompt in the afterEach would be more defensive.


What Looks Good

  • Interaction depth: Tests cover full user flows (open menu -> select -> verify emission -> verify menu closes), not just render checks.
  • Error paths: Every view has explicit error-state tests (API rejection -> toast -> item stays in list).
  • Accessibility coverage: ARIA roles, aria-selected, aria-expanded, aria-haspopup, role="status" with aria-live, keyboard navigation (Enter, Space, Escape) -- this is above-average for a test coverage PR.
  • ReviewView cleanup: Proper afterEach unmounting, mountedWrapper pattern, real Vue Router with createMemoryHistory.
  • ArchiveView restore flow: Tests cover success, cancel, API error, and partial failure (success=false) -- all four branches of the restore logic.

- Remove unused `vi` import from CardItem.coverage.spec.ts (CI-blocking lint error)
- Add afterEach wrapper cleanup to BoardView.coverage.spec.ts (prevents DOM pollution from attachTo: document.body)
- Fix createDeferred generic type in AutomationChatView.coverage.spec.ts (ReturnType<typeof buildSession>[] instead of typeof buildSession[])
- Rename misleading test name in ArchiveView.coverage.spec.ts
Chris0Jeky added a commit that referenced this pull request Apr 13, 2026
Update STATUS.md, IMPLEMENTATION_MASTERPLAN.md, TESTING_GUIDE.md,
and MANUAL_TEST_CHECKLIST.md for the supplementary test depth wave.

- Add delivery entry 134 to masterplan for 6 parallel worktree PRs
- Update test counts: backend ~4,479+, frontend ~2,454+, combined ~6,950+
- Add new TESTING_GUIDE section with run commands and test breakdowns
- Add Z22 manual validation checklist for new test categories
- Update date stamps to 2026-04-13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Pending

Development

Successfully merging this pull request may close these issues.

TST-49: Frontend view and component coverage gaps — untested views and critical components

2 participants