Skip to content

TST-44: Frontend store integration tests#816

Merged
Chris0Jeky merged 10 commits intomainfrom
test/frontend-store-integration
Apr 12, 2026
Merged

TST-44: Frontend store integration tests#816
Chris0Jeky merged 10 commits intomainfrom
test/frontend-store-integration

Conversation

@Chris0Jeky
Copy link
Copy Markdown
Owner

Summary

  • Extends captureStore integration tests with API 500 error handling, multi-param query forwarding, cancelItem action, and triage polling lifecycle coverage
  • Adds boardStore integration tests for card snap-back on 409 conflict, deleteCard, and column CRUD (create/delete)
  • Adds notificationStore integration tests for unread count derivation, markAllRead count behavior, single-read decrement, real-time notification injection, and board-scoped unread filtering
  • Adds sessionStore integration tests for changePassword action and token expiry detection via isAuthenticated computed
  • Adds queueStore integration tests for loading state transitions and cancel-item isolation
  • Creates archiveApi integration tests covering listing with filters, restore operations, URL encoding for special characters, error propagation, and conflict handling
  • Covers regression scenarios for BUG: Fresh user sees other users' automation queue data (data isolation failure) #508 (data isolation) and BUG: Board auto-switches between boards every few seconds without user input #509 (board auto-switching)

Closes #711

Test plan

  • All new store integration tests pass with vitest (1952 tests, 162 files, 0 failures)
  • npm run typecheck passes
  • No existing tests broken

@Chris0Jeky
Copy link
Copy Markdown
Owner Author

Self-review findings

Reviewed all 6 changed files against the adversarial checklist:

Mock level

All tests mock at the HTTP layer (vi.mock('../../api/http')), never at the store or API module level. This is correct -- it tests the full store -> API module -> http chain and catches shape mismatches.

State transition verification

Every test verifies both:

  1. The HTTP call shape (URL, method, payload)
  2. The resulting store state after the action completes

No test just calls an action without assertions.

Error path coverage

Each store action tested has both success and failure paths:

  • captureStore: 500 error, cancel failure, triage failure
  • boardStore: 409 snap-back, delete failure
  • notificationStore: fetch error, mark-read error
  • sessionStore: changePassword failure, expired token detection
  • queueStore: fetch error, loading state consistency

Test isolation

  • beforeEach creates fresh Pinia + clears mocks in every suite
  • afterEach restores real timers where fake timers are used
  • No shared mutable state between tests

Boundary testing accuracy

  • archiveApi tests live in tests/store/ since there is no archiveStore; the archive functionality goes directly from ArchiveView -> archiveApi -> http
  • Notification unread count is derived via filter(!isRead) since the store does not expose a computed property -- this correctly tests the underlying data mutation

Verdict: No issues found. All tests are clean.

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 adds comprehensive integration tests for several stores and API modules, including archiveApi, boardStore, captureStore, notificationStore, queueStore, and sessionStore. The review feedback suggests using more idiomatic Vitest assertions, such as toHaveBeenCalledWith with expect.stringContaining instead of manual mock call extraction, and recommends verifying the exact number of API calls in polling tests to ensure logic correctness.

Comment on lines +67 to +68
const calledUrl = vi.mocked(http.get).mock.calls[0][0] as string
expect(calledUrl).toContain('entityType=card')
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

Instead of manually extracting the URL from the mock calls, use expect(http.get).toHaveBeenCalledWith(expect.stringContaining(...)). This is more idiomatic in Vitest and avoids brittle index-based access to mock calls.

Suggested change
const calledUrl = vi.mocked(http.get).mock.calls[0][0] as string
expect(calledUrl).toContain('entityType=card')
expect(http.get).toHaveBeenCalledWith(expect.stringContaining('entityType=card'))

Comment on lines +94 to +97
const calledUrl = vi.mocked(http.get).mock.calls[0][0] as string
expect(calledUrl).toContain('entityType=card')
expect(calledUrl).toContain('boardId=board-1')
expect(calledUrl).toContain('limit=50')
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

When testing multiple query parameters, you can use multiple toHaveBeenCalledWith assertions to verify the presence of all required parameters in the URL. This is cleaner than manual string extraction.

Suggested change
const calledUrl = vi.mocked(http.get).mock.calls[0][0] as string
expect(calledUrl).toContain('entityType=card')
expect(calledUrl).toContain('boardId=board-1')
expect(calledUrl).toContain('limit=50')
expect(http.get).toHaveBeenCalledWith(expect.stringContaining('entityType=card'))
expect(http.get).toHaveBeenCalledWith(expect.stringContaining('boardId=board-1'))
expect(http.get).toHaveBeenCalledWith(expect.stringContaining('limit=50'))

Comment on lines +361 to +363
// After reaching 'Triaged' (terminal), polling should have stopped
expect(store.triagePollingItemId).toBeNull()
expect(store.detailById['c-poll']?.status).toBe('Triaged')
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

It is recommended to verify the number of API calls made during polling. This confirms that the polling logic correctly executed the expected number of attempts before reaching the terminal status.

Suggested change
// After reaching 'Triaged' (terminal), polling should have stopped
expect(store.triagePollingItemId).toBeNull()
expect(store.detailById['c-poll']?.status).toBe('Triaged')
// After reaching 'Triaged' (terminal), polling should have stopped
expect(http.get).toHaveBeenCalledTimes(3)
expect(store.triagePollingItemId).toBeNull()
expect(store.detailById['c-poll']?.status).toBe('Triaged')

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/extends Vitest + Pinia integration tests that exercise the real store→API-module→mocked-HTTP boundary to improve coverage for common success/error paths and regressions.

Changes:

  • Extend store integration suites (capture/board/notification/session/queue) with additional actions and error/edge-case scenarios.
  • Add a new archiveApi integration test suite validating query building, URL encoding, and error propagation.
  • Add coverage for lifecycle behaviors like triage polling and loading-state transitions.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
frontend/taskdeck-web/src/tests/store/sessionStore.integration.spec.ts Adds integration coverage for changePassword and isAuthenticated behavior with expired vs valid tokens.
frontend/taskdeck-web/src/tests/store/queueStore.integration.spec.ts Adds tests for loading-state transitions and cancel-request isolation behavior.
frontend/taskdeck-web/src/tests/store/notificationStore.integration.spec.ts Adds unread-count behavior checks and board-scoped mark-all-read expectations.
frontend/taskdeck-web/src/tests/store/captureStore.integration.spec.ts Adds server-error coverage for create, query param forwarding, cancel action, and triage polling lifecycle tests.
frontend/taskdeck-web/src/tests/store/boardStore.integration.spec.ts Adds tests for 409 handling on move, deleteCard behavior, and column create/delete flows.
frontend/taskdeck-web/src/tests/store/archiveApi.integration.spec.ts New integration suite for archiveApi.getItems and archiveApi.restoreItem including encoding + error propagation.

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

Comment on lines +301 to +315
it('retains original column and position when move API rejects with 409 (snap-back)', async () => {
const store = useBoardStore()
const card = makeCardPayload({ id: 'card-snap', columnId: 'col-1', position: 0 })
store.currentBoardCards = [card]

vi.mocked(http.post).mockRejectedValue({ response: { status: 409, data: { message: 'Stale position' } } })

await expect(store.moveCard('board-1', 'card-snap', 'col-2', 3)).rejects.toBeDefined()

// The card should still be in the store (not removed) and in the original column
const storedCard = store.currentBoardCards.find(c => c.id === 'card-snap')
expect(storedCard).toBeDefined()
expect(storedCard?.columnId).toBe('col-1')
expect(storedCard?.position).toBe(0)
})
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

This test is labeled as a 409 “snap-back” regression, but moveCard in the store does not perform an optimistic local move before awaiting the API response (it only mutates state after a successful cardsApi.moveCard). As written, the assertion will pass even if no rollback/snap-back behavior exists. Either rename the test to reflect the actual contract (no local mutation on failure) or simulate the optimistic move in state first and then assert it is reverted on 409 (if that’s the intended behavior).

Copilot uses AI. Check for mistakes.
Comment on lines +281 to +283
expect(store.items).toHaveLength(0)
// getErrorDisplay mock returns the fallback when error lacks .message
expect(store.actionError).toBe('Failed to capture item')
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

The inline comment about the 500 case is misleading: this rejection payload includes a message (response.data.message), and in this suite getErrorDisplay is mocked to always return the fallback regardless. Consider updating/removing the comment so it accurately reflects why the fallback is expected here.

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

@Chris0Jeky Chris0Jeky left a comment

Choose a reason for hiding this comment

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

Post-bot review and fixes

After reading all 6 test files and cross-referencing against the actual store/API implementations, here are the issues I found and am fixing:

Bot comment fixes

  1. archiveApi: manual URL extraction pattern (Gemini) — Lines 67-68, 76-77, 85-86, 94-97, and similar patterns in captureStore. Replaced mock.calls[0][0] extraction with idiomatic expect(http.get).toHaveBeenCalledWith(expect.stringContaining(...)).

  2. archiveApi restoreItem URL encoding test — Same pattern for http.post mock call extraction on line 144-146.

  3. captureStore pollTriageCompletion: missing call count assertion (Gemini) — Added assertion that http.get was called the expected number of times (3) to verify polling actually executed the right number of attempts.

  4. boardStore 409 snap-back test (Copilot) — The test IS valid. After reading cardStore.ts, moveCard calls the API first and only updates local state after success. On 409, the card genuinely stays in its original column. Renamed the comment from "snap-back" to clarify there's no optimistic mutation to roll back — state was never changed.

  5. captureStore line 282 misleading comment (Copilot) — The getErrorDisplay mock always returns the fallback, unconditionally. Fixed the comment to reflect reality.

Additional issues found in adversarial review

  1. captureStore query parameter tests partly duplicate existing coverage — The fetchItems describe block already tests status forwarding (line 97-105). The new query parameter tests add boardId and combined params, which are valuable, but noted overlap.

  2. notificationStore unread count tests test Array.filter, not a store getter — These document expected behavior but the store doesn't expose an unreadCount computed. The "adding a new unread notification locally" test exercises pure array manipulation, not a store action. Added a comment clarifying the intent.

  3. captureStore fetchItems query parameter tests — Applied the same toHaveBeenCalledWith(expect.stringContaining(...)) idiom.

Replace manual mock.calls[0][0] URL extraction with
expect(http.get).toHaveBeenCalledWith(expect.stringContaining(...))
for query param and URL encoding assertions. More idiomatic Vitest
and less brittle.
…c matchers

- Fix misleading comment on 500 error test: getErrorDisplay mock always
  returns fallback, not conditionally
- Add callCount assertion to pollTriageCompletion to verify expected
  number of poll attempts
- Replace manual mock.calls URL extraction with
  toHaveBeenCalledWith(expect.stringContaining(...)) for query param tests
…back

The store calls the API before updating local state, so on rejection
the card is never modified. Updated test name and comment to reflect
this — there is no snap-back because there was no optimistic mutation.
The store does not expose an unreadCount computed, so these tests
verify the expected derivation from the notifications array after
store actions rather than testing a store getter.
@Chris0Jeky Chris0Jeky merged commit 20586f3 into main Apr 12, 2026
24 checks passed
@github-project-automation github-project-automation bot moved this from Pending to Done in Taskdeck Execution Apr 12, 2026
@Chris0Jeky Chris0Jeky deleted the test/frontend-store-integration branch April 12, 2026 00:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

TST-44: Frontend store integration tests — stores + real API module interaction

2 participants