Skip to content

test(data): add dataviewer custom hook tests with consolidated infra#606

Open
WilliamBerryiii wants to merge 4 commits intomainfrom
test/214-dataviewer-custom-hook-tests
Open

test(data): add dataviewer custom hook tests with consolidated infra#606
WilliamBerryiii wants to merge 4 commits intomainfrom
test/214-dataviewer-custom-hook-tests

Conversation

@WilliamBerryiii
Copy link
Copy Markdown
Member

Description

Adds Vitest unit test coverage for 13 previously untested dataviewer custom hooks and establishes shared frontend testing infrastructure. Introduces the renderHookWithProviders helper and a consolidated fetch mocking module (installFetchMock, jsonResponse, mockMutationFetch) under src/test-utils/, adds unmount-during-pending coverage (DR-04) for async hooks, and raises Vitest coverage thresholds from 45/35/35/45 to 55/55/40/55 with safety margin. Also documents the --no-file-parallelism requirement for local coverage runs (happy-dom races under parallel workers).

Closes #214

Type of Change

  • 🐛 Bug fix (non-breaking change fixing an issue)
  • ✨ New feature (non-breaking change adding functionality)
  • 💥 Breaking change (fix or feature causing existing functionality to change)
  • 📚 Documentation update
  • 🏗️ Infrastructure change (Terraform/IaC)
  • ♻️ Refactoring (no functional changes)

Component(s) Affected

  • infrastructure/terraform/prerequisites/ - Azure subscription setup
  • infrastructure/terraform/ - Terraform infrastructure
  • infrastructure/setup/ - OSMO control plane / Helm
  • workflows/ - Training and evaluation workflows
  • training/ - Training pipelines and scripts
  • docs/ - Documentation

data-management/viewer/frontend/ (test-only changes; not on the predefined list).

Testing Performed

  • Terraform plan reviewed (no unexpected changes)
  • Terraform apply tested in dev environment
  • Training scripts tested locally with Isaac Sim
  • OSMO workflow submitted successfully
  • Smoke tests passed (smoke_test_azure.py)

cd data-management/viewer/frontend && npm run validate — 832 passing, 1 skipped; coverage 61.08% lines / 46.82% branches / 62.07% functions / 61.41% statements (above the new 55/55/40/55 thresholds).

Documentation Impact

  • No documentation changes needed
  • Documentation updated in this PR
  • Documentation issue filed

Bug Fix Checklist

Skipped — test infrastructure addition, not a bug fix.

Checklist

  • My code follows the project conventions
  • Commit messages follow conventional commit format
  • I have performed a self-review
  • Documentation impact assessed above
  • No new linting warnings introduced

- Add renderHookWithProviders shared test utility
- Add Vitest tests for 13 previously untested hooks
- Swallow ECONNREFUSED unhandledRejections from background refetches
- Document use-object-detection useMemo side-effect bug (follow-up)
- Refs #214

🧪 - Generated by Copilot
- migrate 7 hook test files to shared renderHookWithProviders helper
- consolidate fetch mocks (installFetchMock, jsonResponse, mockMutationFetch) in src/test-utils
- add unmount-during-pending coverage (DR-04) to use-annotations, use-ai-analysis, use-export, use-toast
- raise vitest coverage thresholds 45/35/35/45 -> 55/55/40/55 with safety margin
- document --no-file-parallelism requirement for local coverage (happy-dom races)

🔒 - Generated by Copilot
@WilliamBerryiii WilliamBerryiii requested a review from a team as a code owner May 3, 2026 04:28
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 3, 2026

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Snapshot Warnings

⚠️: No snapshots were found for the head SHA aa284e2.
Ensure that dependencies are being submitted on PR branches and consider enabling retry-on-snapshot-warnings. See the documentation for more information and troubleshooting advice.

Scanned Files

None

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented May 3, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 70.76%. Comparing base (73eb79a) to head (aa284e2).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #606      +/-   ##
==========================================
+ Coverage   65.16%   70.76%   +5.60%     
==========================================
  Files         251      265      +14     
  Lines       15597    16844    +1247     
  Branches     2152     2293     +141     
==========================================
+ Hits        10164    11920    +1756     
+ Misses       5142     4607     -535     
- Partials      291      317      +26     
Flag Coverage Δ *Carryforward flag
pester 83.13% <ø> (ø) Carriedforward from 6bda6fa
pytest-data-pipeline 100.00% <ø> (ø) Carriedforward from 6bda6fa
pytest-dataviewer 66.92% <ø> (ø) Carriedforward from 6bda6fa
pytest-dm-tools 100.00% <ø> (ø) Carriedforward from 6bda6fa
pytest-evaluation 99.83% <ø> (?)
pytest-fuzz 4.90% <ø> (ø) Carriedforward from 6bda6fa
pytest-inference 0.00% <ø> (ø) Carriedforward from 6bda6fa
pytest-training 82.14% <ø> (ø) Carriedforward from 6bda6fa
vitest 61.38% <100.00%> (+8.36%) ⬆️

*This pull request uses carry forward flags. Click here to find out more.

Files with missing lines Coverage Δ
...ment/viewer/frontend/src/test-utils/fetch-mocks.ts 100.00% <100.00%> (ø)
...ent/viewer/frontend/src/test-utils/render-hook.tsx 100.00% <100.00%> (ø)

... and 27 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

- align resolveFetch annotation in use-annotations.test.ts with the JsonResponseLike helper used elsewhere in the file
- resolves CI tsc TS2345 (jsonResponse return not assignable to DOM Response) on PR #606

🩹 - Generated by Copilot
Copy link
Copy Markdown
Collaborator

@katriendg katriendg left a comment

Choose a reason for hiding this comment

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

Good test infrastructure PR — all 15 custom hooks now have coverage, the shared renderHookWithProviders and fetch mock utilities are clean, and the setup.ts ECONNREFUSED handler is well-scoped. The WI-01 documentation for the useMemo anti-pattern is appreciated.

Key findings:

  • 🔒 High: Label mutation hooks (use-labels.ts) missing CSRF tokens — pre-existing production gap faithfully mirrored by tests
  • ⚠️ Medium: cancelExport() sets error state when idle — production bug surfaced by tests
  • 💡 Low: as never casts reduce type safety; setTimeout(0) disabled-query assertions are fragile; some hooks lack error-path and DR-04 unmount coverage

None of these are blockers for the test PR itself — the tests accurately reflect production behavior. The CSRF gap and cancelExport bug should be tracked as follow-ups.

Comment on lines +45 to +56
episodes: { '0': ['SUCCESS'], '1': ['CUSTOM'] },
}),
)

selectDataset('ds-1')

const { result } = renderHookWithProviders(() => useDatasetLabels())

await waitFor(() => expect(result.current.isSuccess).toBe(true))

expect(mockFetch).toHaveBeenCalledTimes(1)
expect(mockFetch.mock.calls[0][0]).toBe('/api/datasets/ds-1/labels')
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔒 Security: Label mutations missing CSRF tokens

setEpisodeLabels, addLabelOption, and removeLabelOption perform PUT/POST/DELETE requests without CSRF tokens. Compare with use-annotations.ts which correctly uses mutationHeaders() from api-client.ts to include X-CSRF-Token.

The tests accurately reflect this gap (no _resetCsrfToken() or mockMutationFetch calls), but the production code needs fixing — these mutation endpoints should include CSRF protection consistent with the rest of the app.

This is not a test issue — it's a pre-existing production gap that the tests faithfully mirror. Consider filing a follow-up to add mutationHeaders() to all three functions.

Comment on lines +170 to +180

expect(mockCancel).not.toHaveBeenCalled()
expect(result.current.error).toBe('Export cancelled')
})

it('fetchPreview populates previewStats on success', async () => {
mockGetExportPreview.mockResolvedValueOnce(samplePreview)
const { result } = renderHook(() => useExport({ datasetId: 'ds-1' }))

await act(async () => {
await result.current.fetchPreview([0, 1], [5])
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

⚠️ Functional: cancelExport sets error when no export is in flight

This test documents a production bug — cancelExport() sets error to 'Export cancelled' even when isExporting is false. The assertion confirms it:

expect(result.current.error).toBe('Export cancelled')

Calling cancelExport() from a clean/idle state should be a true no-op (no error set). Consumers reading error to display alerts will show a spurious "Export cancelled" message. Consider filing a follow-up to guard cancelExport with an isExporting check before setting error state.

Comment on lines +24 to +26
[1, 1, 1],
[2, 2, 2],
]
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

💡 Anti-pattern: pervasive as never type casts

as never casts appear throughout the test suite (here, use-annotation-workflow, use-labels, use-object-detection). This completely disables type checking and can mask breaking interface changes — if a hook's parameter shape changes, these tests still compile but test the wrong shapes silently.

Consider using test-specific factory functions that return the correct type, or targeted as unknown as TrajectoryData casts that at least name the target type so refactoring tools can detect breakage.

Comment on lines +67 to +70

it('is disabled when fewer than 3 positions are provided', async () => {
renderHookWithProviders(() =>
useAISuggestion({
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

💡 Fragile timing: setTimeout(resolve, 0) for disabled-query assertions

Multiple tests across the suite assert disabled-query behavior using:

await new Promise((resolve) => setTimeout(resolve, 0))
expect(mockFetch).not.toHaveBeenCalled()

A zero-delay setTimeout is timing-dependent — a query that fires after one additional microtask could still pass this check. More robust alternative: assert result.current.fetchStatus === 'idle' (TanStack Query's disabled state signal), which is deterministic and doesn't rely on microtask ordering.

Affected files: use-ai-analysis, use-annotations, use-datasets, use-labels, use-episodes.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

💡 Missing error-path and DR-04 unmount coverage

Several query hooks lack error-path tests: useAISuggestion, useTrajectoryAnalysis, useAnomalyDetection, useDatasetLabels, useCapabilities. Since these use handleResponse which throws ApiClientError, verifying error propagation increases confidence.

Also, DR-04 unmount-during-pending coverage is selective — useDashboardStats with its refetchInterval: 60_000 and useOfflineAnnotations.sync() are not covered. The refetch interval case is particularly relevant since a background refetch firing into a dead mount is a common source of test flakiness and "state update on unmounted component" warnings.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

test(dataviewer): add tests for custom hooks

3 participants