Skip to content

Conversation

@gpailler
Copy link
Collaborator

@gpailler gpailler commented Nov 26, 2025

Summary by CodeRabbit

  • Bug Fixes

    • Search now resets to the first page when the query changes, keeping pagination consistent.
  • Chores

    • Major test infrastructure upgrade: new test runner configuration, many unit/integration tests, test utilities, mocks, polyfills, and a mock API server to improve reliability.
    • CI now runs tests, collects coverage, and uploads coverage artifacts.
    • Removed legacy global DOM matcher setup to streamline test behavior.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Nov 26, 2025

Walkthrough

Adds Vitest testing infrastructure, MSW mock server, polyfills, many new unit/integration tests and fixtures, CI steps to run tests and upload coverage, package.json test scripts/deps, a Vitest config, test utilities/setup, and a small Search URL pagination behavior change.

Changes

Cohort / File(s) Change Summary
CI / Coverage
\.github/workflows/build-deploy.yml
Adds steps to run tests (pnpm test:coverage) and upload the coverage artifact via actions/upload-artifact@v4.
Test runner & config
vitest.config.ts, package.json
New vitest.config.ts (default export) merging Vite config; adds globals/jsdom, setup file, CSS handling, v8 coverage reporters, include/exclude patterns, forced coverage and 80% thresholds. package.json adds test scripts (test, test:ui, test:run, test:coverage, test:watch) and test-related deps/devDeps (vitest, @vitest/*, jsdom, msw, testing-library, @dr.pogodin/react-helmet repositioned, removed @types/jest).
Test setup & utilities
src/__tests__/setup.ts, src/__tests__/test-utils.tsx, src/__tests__/polyfills.ts
Adds global Vitest setup (start/stop/reset MSW, env stubs), custom render wrapper exposing ColorSchemeProvider + MemoryRouter, and browser API polyfills/mocks (matchMedia, localStorage, navigator.clipboard).
MSW server & fixtures
src/__tests__/server.ts, src/__tests__/mocks/fixtures.ts
New MSW server and SEARCH_API_URL constant; default POST /search handler returning fixtures or error. New extensive fixtures and helper factories (mockSearchResponse, mockManifest, mockBucketsResponse, createMockManifest, createMockSearchResponse, etc.).
Test entry & harness
src/App.test.tsx, src/setupTests.ts
Adds src/App.test.tsx to assert top-level app layout; removes @testing-library/jest-dom import from src/setupTests.ts (jest-dom matchers no longer globally imported).
Component/provider tests
src/colorscheme/*, src/components/*, src/serialization/*, src/serialization/*, src/utils.test.ts
Adds many new test files across providers, components, and serialization: ColorSchemeProvider.test.tsx, BucketTypeIcon.test.tsx, Buckets.test.tsx, CopyToClipboardButton.test.tsx, Footer.test.tsx, Home.test.tsx, NavBar.test.tsx, Root.test.tsx, Search.test.tsx, SearchBar.test.tsx, SearchPagination.test.tsx, SearchProcessor.test.tsx, SearchResult.test.tsx, SearchStatus.test.tsx, multiple src/serialization/*.test.ts and src/utils.test.ts. Tests cover rendering, interactions, API flows, sorting, pagination, link generation, deserialization, and utility behavior.
Component logic tweaks
src/components/Search.tsx, src/components/SearchProcessor.tsx, src/components/SearchResult.tsx, src/colorscheme/ColorSchemeProvider.tsx
Search: when query changes, reset page URL param to 1. SearchResult: rename prop onCopyToClipbardonCopyToClipboard and update usages. SearchProcessor: replace ref-held AbortController with per-effect local AbortController. ColorSchemeProvider: use named handleChange with MediaQueryListEvent and add/remove via addEventListener/removeEventListener.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Runner as Test runner (Vitest)
    participant Setup as Test setup (src/__tests__/setup.ts)
    participant MSW as MSW server (`server`)
    participant Component as Component under test
    participant API as Search API (mock /search)

    Runner->>Setup: beforeAll -> start server, stub env
    Setup->>MSW: server.listen()
    Runner->>Component: render(component)
    Component->>API: fetch POST /search
    API->>MSW: request intercepted
    MSW->>Component: respond with `mockSearchResponse` or error
    Component->>Component: update state, render results
    Runner->>Component: user interactions / assertions
    Runner->>Setup: afterEach -> resetHandlers, cleanup, clear storage
    Runner->>Setup: afterAll -> server.close()
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

  • Rationale: many new tests (+20 files), config changes, MSW handlers, fixtures, and several small component logic changes requiring cross-file validation.
  • Focus areas:
    • src/components/Search.test.tsx and src/components/SearchProcessor.test.tsx (debounce, URL params, abort logic)
    • vitest.config.ts (coverage rules/thresholds)
    • MSW handlers and fixtures (src/__tests__/server.ts, src/__tests__/mocks/fixtures.ts)
    • Prop rename in SearchResult.tsx and usages across tests/components

Possibly related PRs

Poem

🐇 I hopped through fixtures, mocks, and test light,

Vitest drummed softly through the night,
MSW served stories, coverage in tow,
Components awakened, their truths on show,
A tiny rabbit applauds — ready to go!

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding comprehensive unit tests and test infrastructure across the codebase, which is the primary focus of this PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch unit-tests

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (13)
src/serialization/DateConverter.test.ts (1)

17-25: Consider using UTC methods for consistency.

When parsing date strings without a timezone, JavaScript's behavior is implementation-specific. While this test should pass (both parsing and extraction use local time), using UTC methods would make the test more robust and explicit.

Consider this alternative approach:

-    it('should deserialize date string without timezone', () => {
-      const dateString = '2024-01-15T10:30:00';
+    it('should deserialize date string and handle as local time', () => {
+      const dateString = '2024-01-15T10:30:00Z';  // Use explicit UTC
       const result = converter.deserialize(dateString);
 
       expect(result).toBeInstanceOf(Date);
-      expect(result.getFullYear()).toBe(2024);
-      expect(result.getMonth()).toBe(0); // January = 0
-      expect(result.getDate()).toBe(15);
+      expect(result.getUTCFullYear()).toBe(2024);
+      expect(result.getUTCMonth()).toBe(0); // January = 0
+      expect(result.getUTCDate()).toBe(15);
     });
.github/workflows/build-deploy.yml (1)

41-45: Consider removing redundant test execution.

The workflow runs tests twice:

  1. Line 42: pnpm test:run
  2. Line 45: pnpm test:coverage

Since test:coverage already executes all tests while collecting coverage, the separate test:run step is redundant and increases CI time unnecessarily.

Apply this diff to remove the redundant step:

-    - name: Run tests
-      run: pnpm test:run
-
     - name: Generate coverage report
       run: pnpm test:coverage

If you want to fail fast without coverage overhead, you could keep test:run and make coverage optional, but for most cases, running coverage alone is sufficient.

src/App.test.tsx (1)

1-33: App test is clear; consider reusing shared test render utilities

The test correctly verifies that App renders the Root layout under a HelmetProvider. For consistency with the rest of the suite, you might consider importing the shared render helper from your src/__tests__/test-utils.tsx so all component tests are wrapped with the same providers by default, but the current approach is perfectly fine functionally.

src/components/NavBar.test.tsx (1)

12-29: Type contextOverrides to avoid implicit any and strengthen checks

renderNavBar currently takes contextOverrides = {} without a type, which will become any under noImplicitAny: false and a compiler error under noImplicitAny: true. You can tighten this and get better autocomplete by typing it against the context value:

// Assuming you have something like IColorSchemeContext exported:
const defaultContextValue: IColorSchemeContext = {
  preferedColorScheme: ColorSchemeType.Auto,
  browserColorScheme: ColorSchemeType.Light,
  isDarkMode: false,
  toggleColorScheme: mockToggleColorScheme,
};

const renderNavBar = (contextOverrides: Partial<IColorSchemeContext> = {}) => {
  const contextValue = { ...defaultContextValue, ...contextOverrides };
  // ...
};

(or derive the type via React.ContextType<typeof ColorSchemeContext>).

src/components/SearchBar.test.tsx (1)

27-76: Debounce tests are accurate; consider hardening fake timer teardown

The debounce and cleanup behavior are well covered and correctly wrapped in act. To make the suite more robust, you might centralize timer management so they’re always restored even if an assertion fails, e.g.:

describe('SearchBar', () => {
  beforeEach(() => {
    vi.useFakeTimers();
  });

  afterEach(() => {
    vi.useRealTimers();
  });

  // tests can now omit the per-test useFakeTimers/useRealTimers calls
});

(or use try/finally inside each test). Not required, but it reduces the risk of leaking fake timers into other tests.

src/components/Footer.test.tsx (1)

28-32: Version/commit assertion is broad; tighten if the UI text is known

getByText(/\//) will pass on any text node containing /, not necessarily the version/commit label if other content with slashes appears. If the footer renders a more specific pattern (e.g. v1.2.3/abcdef), you could assert on a more constrained regex or role/text combination. If the exact format is intentionally loose, this is fine to keep.

src/components/Buckets.test.tsx (1)

41-56: Consider more explicit filter checking for maintainability.

The MSW handler distinguishes official vs community buckets using body.filter?.includes('1') on line 48. While functional, this magic string check is fragile and may break if the component's filter format changes.

Consider a more explicit check, such as:

-        if (body.filter?.includes('1')) {
+        if (body.filter?.includes('OfficialRepository eq true')) {
           return HttpResponse.json(mockOfficialBucketsResponse);
         }

Or add a comment explaining the '1' check.

vitest.config.ts (1)

1-41: Solid Vitest configuration with comprehensive coverage settings!

The configuration properly:

  • Merges with existing Vite config to preserve build settings
  • Sets up jsdom environment with necessary resources for React component testing
  • Configures appropriate coverage reporting (text, json, html, lcov) for CI integration
  • Excludes tests, configs, and build artifacts from coverage
  • Uses setup file for global test initialization

The 80% coverage thresholds across all metrics are ambitious. If you encounter difficulties meeting these thresholds initially, consider starting with lower values (e.g., 70%) and gradually increasing them:

       thresholds: {
-        lines: 80,
-        functions: 80,
-        branches: 80,
-        statements: 80,
+        lines: 70,
+        functions: 70,
+        branches: 70,
+        statements: 70,
       },
src/colorscheme/ColorSchemeProvider.test.tsx (1)

407-427: Consider adding cleanup verification.

The test verifies addEventListener is called but doesn't verify that removeEventListener is called on unmount. This could help catch memory leaks.

     it('should register media query change listener', () => {
       const mockAddEventListener = vi.fn();
+      const mockRemoveEventListener = vi.fn();
 
       mockMatchMedia.mockReturnValue({
         matches: false,
         media: '(prefers-color-scheme: dark)',
         addEventListener: mockAddEventListener,
-        removeEventListener: vi.fn(),
+        removeEventListener: mockRemoveEventListener,
         dispatchEvent: vi.fn(),
       });
 
-      renderWithoutProviders(
+      const { unmount } = renderWithoutProviders(
         <ColorSchemeProvider>
           <TestComponent />
         </ColorSchemeProvider>
       );
 
       expect(mockMatchMedia).toHaveBeenCalledWith('(prefers-color-scheme: dark)');
       expect(mockAddEventListener).toHaveBeenCalledWith('change', expect.any(Function));
+
+      unmount();
+      expect(mockRemoveEventListener).toHaveBeenCalledWith('change', expect.any(Function));
     });
src/components/SearchProcessor.test.tsx (1)

340-357: Abort controller test doesn't verify abort behavior.

The test name suggests it verifies that previous requests are aborted, but it only confirms that onResultsChange is called again after the query changes. This doesn't prove the previous request was actually aborted.

To properly test abort behavior, consider verifying the abort signal:

   describe('abort controller', () => {
     it('should abort previous request when query changes', async () => {
+      let abortSignals: AbortSignal[] = [];
+
+      server.use(
+        http.post(`${SEARCH_API_URL}/search`, async ({ request }) => {
+          abortSignals.push(request.signal);
+          return HttpResponse.json(mockSearchResponse);
+        })
+      );
+
       const { rerender } = render(<SearchProcessor {...defaultProps} query="git" />);
 
       await waitFor(() => {
         expect(mockOnResultsChange).toHaveBeenCalled();
       });
 
       mockOnResultsChange.mockClear();
 
       // Change query
       rerender(<SearchProcessor {...defaultProps} query="nodejs" />);
 
       await waitFor(() => {
         expect(mockOnResultsChange).toHaveBeenCalled();
+        // First request should have been aborted
+        expect(abortSignals[0]?.aborted).toBe(true);
       });
     });
   });
src/components/Home.test.tsx (1)

117-131: Color scheme tests don't verify style application.

The test names "applies light style in light mode" and "applies dark style in dark mode" are misleading - they only verify the component renders without error, not that any specific styles are applied. The comments acknowledge this, but the test names should reflect actual behavior.

Consider renaming for clarity:

   describe('color scheme', () => {
-    it('applies light style in light mode', () => {
+    it('renders without error in light mode', () => {
       renderHome({ isDarkMode: false });
 
-      // Component should render without error in light mode
       expect(screen.getByText('Scoop')).toBeInTheDocument();
     });
 
-    it('applies dark style in dark mode', () => {
+    it('renders without error in dark mode', () => {
       renderHome({ isDarkMode: true });
 
-      // Component should render without error in dark mode
       expect(screen.getByText('Scoop')).toBeInTheDocument();
     });
   });
src/components/Search.test.tsx (2)

95-113: Consider centralizing renderSearch wiring into shared test utilities

renderSearch wires HelmetProvider, MemoryRouter, routes, and LocationDisplay. If other tests need a similar environment, consider moving this into your shared test-utils to avoid duplication and keep setup consistent. This is optional but can simplify future test maintenance.


132-250: Tests cover key behaviors well; a couple of small cleanups are possible

The three tests exercise URL/state initialization, query updates with debounced submit, and result → modal flow very thoroughly. A couple of minor tidy-ups you might consider later:

  • In the last test, vi.useRealTimers() is called twice (Lines 219 and 249); the second call is redundant and can be dropped.
  • If other tests in the suite start relying on localStorage, adding a localStorage.clear() in a global beforeEach/afterEach could help avoid subtle cross-test interactions.

These are non-blocking polish items; the current tests are otherwise in good shape.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dbaf208 and 5c08104.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (32)
  • .github/workflows/build-deploy.yml (1 hunks)
  • package.json (3 hunks)
  • src/App.test.tsx (1 hunks)
  • src/__tests__/mocks/fixtures.ts (1 hunks)
  • src/__tests__/polyfills.ts (1 hunks)
  • src/__tests__/server.ts (1 hunks)
  • src/__tests__/setup.ts (1 hunks)
  • src/__tests__/test-utils.tsx (1 hunks)
  • src/colorscheme/ColorSchemeProvider.test.tsx (1 hunks)
  • src/components/BucketTypeIcon.test.tsx (1 hunks)
  • src/components/Buckets.test.tsx (1 hunks)
  • src/components/CopyToClipboardButton.test.tsx (1 hunks)
  • src/components/Footer.test.tsx (1 hunks)
  • src/components/Home.test.tsx (1 hunks)
  • src/components/NavBar.test.tsx (1 hunks)
  • src/components/Root.test.tsx (1 hunks)
  • src/components/Search.test.tsx (1 hunks)
  • src/components/Search.tsx (1 hunks)
  • src/components/SearchBar.test.tsx (1 hunks)
  • src/components/SearchPagination.test.tsx (1 hunks)
  • src/components/SearchProcessor.test.tsx (1 hunks)
  • src/components/SearchResult.test.tsx (1 hunks)
  • src/components/SearchStatus.test.tsx (1 hunks)
  • src/serialization/BucketsResultsFacetJson.test.ts (1 hunks)
  • src/serialization/BucketsResultsJson.test.ts (1 hunks)
  • src/serialization/DateConverter.test.ts (1 hunks)
  • src/serialization/ManifestJson.test.ts (1 hunks)
  • src/serialization/MetadataJson.test.ts (1 hunks)
  • src/serialization/SearchResultsJson.test.ts (1 hunks)
  • src/setupTests.ts (0 hunks)
  • src/utils.test.ts (1 hunks)
  • vitest.config.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • src/setupTests.ts
🧰 Additional context used
🧬 Code graph analysis (11)
src/components/Buckets.test.tsx (1)
src/__tests__/server.ts (2)
  • server (21-21)
  • SEARCH_API_URL (5-5)
src/serialization/BucketsResultsJson.test.ts (1)
src/__tests__/mocks/fixtures.ts (1)
  • mockBucketsResponse (90-118)
src/serialization/SearchResultsJson.test.ts (1)
src/__tests__/mocks/fixtures.ts (3)
  • mockSearchResponse (6-58)
  • mockEmptySearchResponse (61-64)
  • createMockSearchResponse (155-160)
src/utils.test.ts (1)
src/utils.ts (1)
  • extractPathFromUrl (1-3)
src/components/SearchResult.test.tsx (1)
src/__tests__/mocks/fixtures.ts (1)
  • mockManifest (67-87)
src/__tests__/server.ts (1)
src/__tests__/mocks/fixtures.ts (1)
  • mockSearchResponse (6-58)
src/components/SearchProcessor.test.tsx (3)
src/__tests__/server.ts (2)
  • server (21-21)
  • SEARCH_API_URL (5-5)
src/__tests__/mocks/fixtures.ts (1)
  • mockSearchResponse (6-58)
src/components/SearchProcessor.tsx (1)
  • sortModes (37-80)
src/components/NavBar.test.tsx (1)
src/colorscheme/ColorSchemeContext.ts (1)
  • ColorSchemeContext (19-19)
src/__tests__/setup.ts (1)
src/__tests__/server.ts (2)
  • server (21-21)
  • SEARCH_API_URL (5-5)
src/colorscheme/ColorSchemeProvider.test.tsx (1)
src/colorscheme/ColorSchemeContext.ts (1)
  • ColorSchemeContext (19-19)
src/components/Home.test.tsx (1)
src/colorscheme/ColorSchemeContext.ts (1)
  • ColorSchemeContext (19-19)
🪛 GitHub Actions: Build and deploy Website
src/components/Search.test.tsx

[error] 125-125: Cannot find namespace 'vi'.

🪛 GitHub Check: Build ⚙️📦
src/components/Search.test.tsx

[failure] 125-125:
Cannot find namespace 'vi'.

🔇 Additional comments (33)
src/serialization/DateConverter.test.ts (4)

1-5: LGTM!

Clean test setup with proper imports and converter instance initialization.


8-15: LGTM!

Test correctly handles ISO 8601 format with explicit UTC timezone, ensuring timezone-independent behavior.


37-51: LGTM!

Both tests are well-designed:

  • The various formats test appropriately validates multiple date string formats with explicit timezone information.
  • The invalid date test correctly verifies error handling behavior.

54-59: LGTM!

Correctly tests that serialization throws an appropriate error with a clear message.

src/__tests__/polyfills.ts (1)

1-53: LGTM! Comprehensive test polyfills.

The polyfills provide complete implementations of browser APIs needed for testing:

  • matchMedia with all standard listener methods
  • localStorage with proper IIFE encapsulation and complete API surface
  • clipboard.writeText mock

These will ensure tests run reliably in the jsdom environment.

src/serialization/BucketsResultsFacetJson.test.ts (1)

1-73: LGTM! Comprehensive test coverage for facet deserialization.

The test suite thoroughly covers:

  • Standard value types (boolean, string)
  • Edge cases (zero count, large count, empty string)
  • Proper json2typescript integration

All assertions are clear and verify both count and value properties correctly.

src/serialization/BucketsResultsJson.test.ts (2)

1-132: LGTM! Comprehensive test suite for BucketsResultsJson.

The tests thoroughly cover:

  • Basic deserialization with facets
  • Facet result structure and ordering
  • Empty responses
  • Multiple facet types
  • Single facet values
  • Edge cases (zero counts, large counts, special characters)

All test assertions are clear and validate the expected behavior correctly.


30-40: No inconsistency found. The codebase consistently uses the correct OData property name.

The verification confirms:

  • mockBucketsResponse in fixtures.ts uses '@odata.count'
  • BucketsResultsJson implementation uses @JsonProperty('@odata.count', Number)
  • The test snippet uses '@odata.count': 0
  • No usage of '@data.count' exists anywhere in the codebase

The original review comment's concern about inconsistent property names is unfounded. All code and tests correctly use the Azure Search API standard '@odata.count' property name.

src/__tests__/mocks/fixtures.ts (1)

1-160: LGTM! Well-structured test fixtures and helpers.

The fixtures provide comprehensive mock data for testing:

  • Complete search responses with realistic Azure Search structure
  • Empty response handling
  • Detailed manifest examples
  • Buckets responses with facets
  • Error responses
  • Helper functions for customizing fixtures

The createMockManifest and createMockSearchResponse helpers are particularly useful for test scenarios requiring variations.

.github/workflows/build-deploy.yml (1)

47-51: LGTM! Coverage artifact upload configured correctly.

The coverage report upload will provide visibility into test coverage metrics in CI, which is valuable for maintaining code quality.

src/components/Search.tsx (1)

201-209: LGTM! Correct UX behavior for query changes.

Resetting the page parameter to 1 when the query changes ensures users see results from the beginning of the new search, which is the expected behavior. This prevents showing an empty or incorrect page when the new query has fewer results.

src/__tests__/setup.ts (1)

1-23: LGTM! Proper test lifecycle management.

The test setup provides:

  • MSW server initialization with strict unhandled request handling
  • Environment variable stubbing for Azure Search credentials
  • Comprehensive cleanup after each test (handlers, DOM, storage, mocks)
  • Proper teardown on completion

This ensures tests run in isolation with a clean, predictable environment.

package.json (1)

49-65: All specified test dependency versions are current stable releases with no known security advisories.

The packages you listed are at their latest versions as of November 2025:

No known security vulnerabilities or advisories have been reported for these versions.

src/components/CopyToClipboardButton.test.tsx (1)

1-55: CopyToClipboardButton timer behavior tests look solid

The tests thoroughly cover the disabled window and subsequent clicks with fake timers, and the helper pattern with ComponentProps<typeof CopyToClipboardButton> keeps props flexible. No changes needed from my side.

src/serialization/MetadataJson.test.ts (1)

5-134: MetadataJson deserialization tests provide good coverage

The tests exercise full vs partial metadata, date conversion, official flag, and star count edge cases using JsonConvert, which should catch most mapping regressions in MetadataJson. Looks good as-is.

src/components/SearchStatus.test.tsx (1)

5-104: SearchStatus test coverage is comprehensive and focused

These tests nicely cover searching, results, no-results (with and without query), officialOnly behavior, and query formatting edge cases. The expectations are clear and map well to the component props; I don’t see any changes needed here.

src/components/BucketTypeIcon.test.tsx (1)

1-91: Well-structured test suite with comprehensive coverage!

The test organization is clear and covers all major rendering paths:

  • Icon type variations based on official flag and star counts
  • Tooltip configuration with wrapper verification
  • Props spreading validation

The tests effectively verify color attributes, SVG presence, and prop forwarding.

src/serialization/SearchResultsJson.test.ts (1)

1-170: Comprehensive deserialization test coverage!

The test suite thoroughly validates SearchResultsJson.Create across multiple scenarios:

  • Standard and empty responses
  • Single and multiple results
  • Count vs results length mismatches (pagination scenario)
  • Nested property preservation including Date conversion
  • Edge cases with missing optional fields and large datasets

The use of mock fixtures and helper functions promotes maintainability.

src/__tests__/server.ts (1)

1-21: Clean MSW test server setup!

The server configuration is well-structured with:

  • Reusable URL constant for consistency across tests
  • Default handler supporting both success and error scenarios
  • Clear separation between test conditions (search === 'error')

This provides a solid foundation for API mocking across the test suite.

src/utils.test.ts (1)

1-167: Excellent comprehensive test coverage for the utility function!

The test suite thoroughly documents extractPathFromUrl behavior across:

  • Basic URL and path extraction
  • Custom separator support
  • Edge cases including trailing slashes, empty strings, encoded characters
  • Real-world scenarios from GitHub and Azure Search

The edge case tests (lines 72-136) are particularly valuable for documenting the function's actual behavior with unusual inputs.

src/__tests__/test-utils.tsx (1)

1-33: Excellent test utility following React Testing Library best practices!

The custom render function provides:

  • Consistent provider wrapping (ColorSchemeProvider, MemoryRouter) across all tests
  • Flexible withProviders flag for edge cases where providers aren't needed
  • Full re-export of Testing Library utilities for convenience

This pattern significantly reduces boilerplate in test files while ensuring consistent context setup.

src/components/SearchResult.test.tsx (1)

27-299: Comprehensive test coverage for SearchResult component!

The test suite thoroughly validates:

  • Core rendering (name, description, repository, version, homepage, notes)
  • License handling (SPDX links vs plain text)
  • Command generation for official and community repositories
  • Copy-to-clipboard interactions including "Copy all" functionality
  • Result selection behavior
  • Homepage formatting (protocol removal, trailing slash handling)
  • Official vs community bucket differences
  • Date formatting and commit links

The test organization and assertions are clear and maintainable, pending resolution of the prop name typo.

src/components/SearchPagination.test.tsx (1)

1-34: Well-structured visibility tests.

Good coverage of edge cases: single page, zero results, and multiple pages. The test organization with describe blocks is clean.

src/colorscheme/ColorSchemeProvider.test.tsx (2)

26-77: Thorough browser API mocking setup.

The mocks for document.getElementsByTagName, window.matchMedia, and localStorage are well-structured. The beforeEach/afterEach lifecycle properly clears state between tests.


233-251: <function_calls>

#!/bin/bash

Read the test file to understand structure and the toggle test in question

cat -n src/colorscheme/ColorSchemeProvider.test.tsx | sed -n '220,260p'



</function_calls>

src/components/SearchProcessor.test.tsx (1)

50-146: Comprehensive API interaction tests.

Good coverage of query parameters, filters, and error handling using MSW. The pattern of capturing request bodies for verification is effective.

src/serialization/ManifestJson.test.ts (3)

1-62: Solid deserialization test coverage.

The tests properly verify JSON deserialization including optional fields and metadata mapping. Good use of the mockManifest fixture with spread operator for creating test variants.


64-159: Thorough highlight getter testing.

Excellent coverage of highlight scenarios including fallbacks, multiple highlight sources, and joining multiple values. Tests align well with the expected behavior described in the summary.


161-200: Good favicon edge case coverage.

Tests cover homepage with subpaths, ports, and missing homepage scenarios. The URL origin extraction is properly validated.

src/components/Home.test.tsx (2)

9-21: Good mocking strategy for complex dependencies.

Mocking AsciinemaCasts to avoid complex dependencies and useNavigate for navigation verification is appropriate. The mock preserves the rest of react-router-dom's exports correctly.


92-106: Search navigation test is well-structured.

Properly simulates user typing and form submission, then verifies navigation call with correct pathname and search params.

src/components/Search.test.tsx (2)

15-60: Mocking child components via vi.mock and capturing props looks solid

Using lightweight mocks for SearchBar, SearchProcessor, SearchResult, and SearchPagination while forwarding props into mockSearch* fns gives you good introspection without over-coupling to implementation details. This is a clean pattern for keeping the tests focused on Search’s behavior.


115-130: The review comment is incorrect – the code already uses the proper Mock type import

The review claims line 125 contains (globalThis.fetch as unknown as vi.Mock) causing a "Cannot find namespace 'vi'" error, but the actual code on line 125 is:

(globalThis.fetch as unknown as Mock) = vi.fn().mockResolvedValue({

The Mock type is correctly imported as a type import from vitest on line 5:

import { beforeAll, beforeEach, describe, expect, it, type Mock, vi } from 'vitest';

The current code is already fixed and does not have the issue described. The suggested change to remove the type assertion entirely would actually reduce type safety unnecessarily.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
src/components/Search.test.tsx (3)

115-130: Globals setup is fine; consider centralizing and adding cleanup

Stubbing cancelIdleCallback, fetch, and scrollTo, plus clearing all component mocks in beforeEach, is reasonable here, but you may want to (a) move shared polyfills like cancelIdleCallback into your global test setup and (b) add a matching afterEach that restores or clears global state (e.g., localStorage.clear(), vi.restoreAllMocks()) to avoid subtle cross‑test coupling as the suite grows.


198-250: Result rendering + modal flow looks good; drop duplicate useRealTimers

The test cleanly simulates a results payload, verifies list and pagination updates, and drives the modal open/close cycle including URL id changes. There’s a minor nit: vi.useRealTimers() is called twice (once after runAllTimers and again at the end); the second call is redundant and can be removed for clarity.

If you update this, just ensure no other parts of the test rely on fake timers after the first useRealTimers() call.


62-88: Perfect! I've completed the verification. Here are my findings:

Verification Results:

  1. Current Modal Mock Limitations Confirmed: The mock only accepts show, onHide, and children, but the actual Search component passes additional props: restoreFocus, size, centered, and className.

  2. Modal.Header/Footer Usage: Not currently used anywhere in the codebase, confirming the "future-proofing" aspect of the suggestion.

  3. Props Not Being Forwarded: Unlike the MockWrapper components that properly forward extra props with ...rest, the Modal mock component ignores all undestructured props.

  4. Tests Still Pass: The tests only verify visibility and the close button, so the missing props don't cause test failures—but they could matter if future tests need to assert on modal styling or behavior.

The review comment's suggestion is valid and appropriately scoped as optional. The recommendation to forward extra props and add stub Modal.Header/Modal.Footer components would indeed make the mock more resilient to future changes without adding significant complexity.


Consider slightly future-proofing the Modal mock

The mock currently only supports show, onHide, children, and Modal.Body. However, the actual Search component passes additional props (restoreFocus, size, centered, className) that the mock silently ignores. While this doesn't break current tests, you might optionally forward extra props to the Modal wrapper and add stubbed Modal.Header / Modal.Footer components to make the mock more resilient to future changes.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5c08104 and 77bd5be.

📒 Files selected for processing (1)
  • src/components/Search.test.tsx (1 hunks)
🔇 Additional comments (8)
src/components/Search.test.tsx (8)

1-14: Imports and typing are consistent and minimal

The import set and use of type-only imports keep runtime surface small and typings clear; nothing problematic here.


15-23: SearchBar mock pattern is clear and type‑safe

Capturing props via ComponentProps and a dedicated mockSearchBar gives you good visibility into interactions while keeping the rendered output trivial.


24-36: SearchProcessor mock correctly preserves named exports

Re‑exporting SortDirection and sortModes while stubbing only the default component is a good balance between isolation and realism.


38-51: SearchResult mock nicely distinguishes list vs modal context

Deriving a stable test id from result.id and onResultSelected and wiring onClick through onResultSelected gives you expressive, robust selectors for the tests.


53-60: SearchPagination mock is sufficient for prop inspection

Lightweight markup plus mockSearchPagination captures all the behavior the tests need without coupling to the real pagination UI.


90-113: Test harness around Search is well‑structured

The renderSearch helper that wraps Search with routing and a LocationDisplay gives each test a clean, reusable way to assert on URL search params.


132-157: Initialization test covers URL + stored preferences well

This test exercises the interaction between query params, stored sort settings, and pagination props; the assertions on bar, processor, and pagination props look consistent with the intended behavior.


158-197: Query‑change behavior and debounced submit are exercised thoroughly

The test validates URL updates, page reset, pagination sync, and the debounced onSubmit path via fake timers; the sequencing with waitFor and timer toggling looks sound.

@gpailler gpailler changed the title Unit tests Add comprehensive unit tests and test infrastructure Nov 27, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (5)
src/components/Footer.test.tsx (1)

5-26: Footer creator and link tests are clear and robust

These tests cover the key visible text and link targets the footer exposes and use RTL queries appropriately. If you want to reduce a bit of repetition, you could move render(<Footer />); into a small helper or beforeEach, but it’s not required.

src/colorscheme/ColorSchemeProvider.test.tsx (2)

183-221: Strengthen localStorage persistence assertions to cover write/remove behavior

Both localStorage tests currently start from a state that makes them pass even if the persistence logic were partially missing:

  • “save user preference” pre-populates preferred-color-scheme with Dark and only checks it remains Dark, which would still pass if setItem weren’t called or the key changed.
  • “remove localStorage item when set to Auto” starts from empty storage and only asserts getItem is null, which would also be true if removeItem were never called.

To better exercise persistence, consider:

  • Starting from an empty localStorage and asserting that localStorage.setItem is called with the expected key/value when a non‑Auto preference is active (spying on localStorage.setItem).
  • Starting from a non‑empty value and asserting that localStorage.removeItem is invoked and that the key is cleared.

This would make these tests more effective at catching regressions in the persistence logic.


223-405: Toggle and media-query behavior tests are thorough; minor simplification possible

The toggle and media-query suites cover the full matrix of Auto/Light/Dark vs. browser preference and validate both context values and isDarkMode, plus verifying listener registration and update on change. From a behavior standpoint this is very solid.

You could simplify a bit by relying on React Testing Library’s normal rerendering after state updates (the rerender calls after button clicks aren’t strictly necessary), but that’s optional and doesn’t affect correctness.

src/components/SearchProcessor.test.tsx (1)

9-30: Clear shared mocks between tests for stronger isolation

The shared mocks (mockOnResultsChange, mockOnSortChange, etc.) are defined once at the top of the suite and (apart from the abort tests) never explicitly cleared. If clearMocks isn’t enabled in the Vitest config, earlier calls could satisfy later toHaveBeenCalledWith assertions and slightly weaken test isolation.

Consider adding a beforeEach that calls mockOnResultsChange.mockClear() (and similarly for the other shared mocks) so each test only observes its own interactions.

src/components/SearchResult.test.tsx (1)

9-25: Consider clearing shared mocks between tests for stricter isolation

mockOnCopyToClipboard and mockOnResultSelected are shared across the suite and aren’t reset between tests. Since the expectations only use toHaveBeenCalledWith, earlier calls could satisfy later tests if Vitest isn’t configured with clearMocks: true.

It’s not breaking, but adding a simple beforeEach that calls mockOnCopyToClipboard.mockClear() and mockOnResultSelected.mockClear() would make each copy/selection test self‑contained.

Also applies to: 165-208

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 77bd5be and fb7e3ed.

📒 Files selected for processing (16)
  • .github/workflows/build-deploy.yml (1 hunks)
  • src/colorscheme/ColorSchemeProvider.test.tsx (1 hunks)
  • src/colorscheme/ColorSchemeProvider.tsx (1 hunks)
  • src/components/Buckets.test.tsx (1 hunks)
  • src/components/Footer.test.tsx (1 hunks)
  • src/components/Home.test.tsx (1 hunks)
  • src/components/Root.test.tsx (1 hunks)
  • src/components/Search.test.tsx (1 hunks)
  • src/components/Search.tsx (3 hunks)
  • src/components/SearchBar.test.tsx (1 hunks)
  • src/components/SearchPagination.test.tsx (1 hunks)
  • src/components/SearchProcessor.test.tsx (1 hunks)
  • src/components/SearchProcessor.tsx (3 hunks)
  • src/components/SearchResult.test.tsx (1 hunks)
  • src/components/SearchResult.tsx (1 hunks)
  • src/serialization/DateConverter.test.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/components/SearchBar.test.tsx
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/components/Buckets.test.tsx
  • src/components/SearchPagination.test.tsx
  • src/serialization/DateConverter.test.ts
  • src/components/Home.test.tsx
  • .github/workflows/build-deploy.yml
🧰 Additional context used
🧬 Code graph analysis (3)
src/components/SearchProcessor.test.tsx (3)
src/__tests__/server.ts (2)
  • server (21-21)
  • SEARCH_API_URL (5-5)
src/__tests__/mocks/fixtures.ts (1)
  • mockSearchResponse (6-58)
src/components/SearchProcessor.tsx (1)
  • sortModes (37-80)
src/colorscheme/ColorSchemeProvider.test.tsx (1)
src/colorscheme/ColorSchemeContext.ts (1)
  • ColorSchemeContext (19-19)
src/components/SearchResult.test.tsx (1)
src/__tests__/mocks/fixtures.ts (1)
  • mockManifest (67-87)
🔇 Additional comments (10)
src/components/Footer.test.tsx (1)

28-38: Version/commit test is valuable; ensure globals are wired into the Vitest env

The checks for APP_VERSION / APP_COMMIT_HASH being defined and rendered are useful to catch build/config issues, and the substring getByText usage is reasonable. Just make sure these globals are also defined in the Vitest environment (e.g., via define/globals in your test config), otherwise this test will throw a ReferenceError when run in isolation.

src/colorscheme/ColorSchemeProvider.tsx (1)

87-101: Media query listener wiring and cleanup look correct

Using a named handleChange closure and addEventListener/removeEventListener with the same function reference ensures updates and cleanup behave correctly, and the effect’s empty dependency array keeps the handler identity stable. No issues from this change.

src/components/Root.test.tsx (1)

1-58: Root composition tests are clear and well‑isolated

The mocking strategy for NavBar, Footer, and ColorSchemeProvider plus wrapping Root in MemoryRouter gives focused coverage of Root’s wiring without over-coupling to child internals. Type-only ReactNode usage keeps the mock typed without pulling in a runtime React import. This file looks good as-is.

src/colorscheme/ColorSchemeProvider.test.tsx (1)

31-73: I'll begin by verifying the concerns raised in the review comment. Let me start by examining the full test file and understanding the actual test implementation, particularly around mock restoration and localStorage assertions.
<function_calls>

#!/bin/bash

First, find and read the test file to understand full context

fd "ColorSchemeProvider.test.tsx" --type f


</function_calls>
<function_calls>

#!/bin/bash

Read the full test file to understand structure and mock restoration

cat src/colorscheme/ColorSchemeProvider.test.tsx


</function_calls>

src/components/SearchProcessor.tsx (1)

147-242: AbortController refactor correctly scopes cancellation to each effect run

Creating the AbortController inside the effect and aborting it in the cleanup neatly ties each request to a single render cycle and preserves the previous cancellation and error‑handling semantics without introducing new races or leaks. Looks good.

src/components/SearchResult.tsx (1)

20-39: Copy‑to‑clipboard prop rename is consistent and correctly wired

The onCopyToClipboard prop name, handler, and hook dependencies are now aligned across the component and callers; no behavioral regressions here.

src/components/SearchProcessor.test.tsx (1)

336-378: Abort controller behavior is well‑specified in tests

The abort‑controller tests correctly distinguish initial render (no abort on mount) from subsequent query changes (cleanup abort invoked), and the comments acknowledge the extra abort calls from undici’s internal propagation. This gives good coverage over the new per‑effect AbortController logic.

src/components/Search.tsx (2)

201-207: Resetting page to 1 on query change is correct and consistent

Updating both the p search param and currentPage to 1 when the query changes keeps URL state and component state in sync and avoids “stale page” scenarios when starting a new search. This matches the tests in Search.test.tsx and is a sensible UX.


257-263: Copy‑to‑clipboard plumbing between Search and SearchResult is aligned

handleCopyToClipboard writes to the clipboard and is passed as onCopyToClipboard in both the list and modal usages of SearchResult, matching the renamed prop and the SearchResult tests. No issues here.

Also applies to: 323-357

src/components/Search.test.tsx (1)

95-113: Search tests provide good coverage of URL state, debouncing, and modal flow

The mocked child components, router wrapper, and LocationDisplay helper work well together to assert URL/search‑param behavior, debounced query submission, pagination updates, and the selected‑result modal lifecycle. Timer usage (useFakeTimers/useRealTimers) is scoped and correctly drives the debounced query and requestIdleCallback path. This looks solid.

Also applies to: 134-251

Comment on lines +53 to +64
it('should render version with "v" prefix for numeric versions', () => {
const numericVersionManifest = {
...mockManifest,
Version: '1.0.0',
};
const result = jsonConvert.deserializeObject(numericVersionManifest, ManifestJsonClass);

render(<SearchResult {...defaultProps} result={result} />);

// Check that version is displayed
expect(screen.getByText('1.0.0')).toBeInTheDocument();
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Align test assertion with the “v”‑prefix behavior

The test name says “should render version with 'v' prefix for numeric versions”, but the assertion only checks that 1.0.0 is rendered, not that it is actually prefixed with v (i.e., v1.0.0).

Either:

  • Strengthen the assertion to check for the full prefixed string, or
  • Relax the test name to reflect that it only verifies that the version text is rendered.

Right now, the test wouldn’t fail if the prefix behavior regressed.

🤖 Prompt for AI Agents
In src/components/SearchResult.test.tsx around lines 53 to 64, the test title
expects the version to be rendered with a "v" prefix but the assertion only
checks for "1.0.0"; update the test to assert the prefixed string by replacing
the expectation to look for "v1.0.0" (or a regex that includes the leading "v")
so the test will fail if the "v" prefix is not rendered. Ensure the rendered
component still receives the same numeric Version value and adjust the assertion
only.

@gpailler gpailler merged commit 297b913 into main Dec 1, 2025
4 checks passed
@gpailler gpailler deleted the unit-tests branch December 1, 2025 01:43
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.

2 participants