Skip to content

CI/CD: Refresh Frontend Test Suites#1056

Open
rohan-pandeyy wants to merge 6 commits intoAOSSIE-Org:mainfrom
rohan-pandeyy:ci-cd/frontend-test-suites
Open

CI/CD: Refresh Frontend Test Suites#1056
rohan-pandeyy wants to merge 6 commits intoAOSSIE-Org:mainfrom
rohan-pandeyy:ci-cd/frontend-test-suites

Conversation

@rohan-pandeyy
Copy link
Contributor

@rohan-pandeyy rohan-pandeyy commented Jan 21, 2026

closes #889

Description

Addressing the need for a reliable, modern testing baseline for the frontend. This PR introduces a layered frontend testing strategy and incrementally expands it with navigation interaction sanity tests.

The intent is to provide fast, stable and high-signal checks.

Changes

Infrastructure

  • Test Utils: src/test-utils.tsx with a customRender function that wraps components in Redux, Router, and Theme providers.
  • Fresh Store: src/app/store.ts and test utils to create a fresh Redux store for every test run. prevents state leakage.

New Tests

  • Page Sanity Tests

    • Home: Confirms the page renders a valid core state (gallery, empty state, or loading).
    • Settings: Verifies presence of key sections:
      • Folder Management
      • User Preferences
      • Application Controls
  • Sidebar Navigation

    • Verifies all primary navigation links render correctly.
    • Validates route changes when sidebar links are clicked:
      • Home
      • AI Tagging
      • Favourites
      • Videos
      • Albums
      • Memories
      • Settings

Checklist

  • Sanity Tests:
    • Home Page Structure
    • Settings Page Sections
  • Sidebar navigation structure & interactions

Summary by CodeRabbit

  • Tests

    • Expanded test infrastructure with console suppression, extensive platform and HTTP mocks, and shared test utilities/providers.
    • Added many unit and interaction tests: pages, sidebar navigation, theme toggle, settings, dialogs, empty states, loader, utilities, and date grouping.
    • Removed a previous matchMedia polyfill.
  • Refactor

    • Simplified store setup by extracting a root reducer.
  • Chores

    • Added a browser-mapping package dependency.

@github-actions github-actions bot added CI/CD enhancement New feature or request test labels Jan 21, 2026
@rohan-pandeyy rohan-pandeyy changed the title CI/CD: Refresh Frontend Sanity Tests & Infrastructure CI/CD: Refresh Frontend Test Suites Jan 21, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 21, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds extensive frontend test scaffolding and deterministic mocks (Tauri, Axios, window.matchMedia), shared React test utilities/providers, many new unit/integration tests for pages and components, exports a combined Redux rootReducer, and adds baseline-browser-mapping to package.json.

Changes

Cohort / File(s) Summary
Jest setup & runtime mocks
frontend/jest.setup.ts
Suppresses test console output; adds window.matchMedia handling and broad Jest mocks for Tauri core/plugins and a comprehensive Axios mock (default export, create, HTTP methods with preset responses).
Test utilities & providers
frontend/src/test-utils.tsx
New test harness: AllTheProviders, CustomRenderOptions, and customRender exported as render to wrap components with Redux Provider, ThemeProvider, QueryClientProvider, and MemoryRouter.
New frontend tests
frontend/src/pages/__tests__/PageSanity.test.tsx, frontend/src/pages/__tests__/SettingsPage.test.tsx, frontend/src/pages/__tests__/allPages.test.tsx, frontend/src/components/__tests__/Sidebar.test.tsx, frontend/src/components/__tests__/ThemeToggle.test.tsx, frontend/src/components/__tests__/DeleteImageDialog.test.tsx, frontend/src/components/__tests__/EmptyStates.test.tsx, frontend/src/components/__tests__/GlobalLoader.test.tsx, frontend/src/components/__tests__/InfoDialog.test.tsx, frontend/src/lib/__tests__/utils.test.ts, frontend/src/utils/__tests__/dateUtils.test.ts
Adds numerous unit and interaction tests covering pages, navigation, theme toggle, dialogs, empty states, global loader, utils, and date helpers. One test file removes a previous global matchMedia polyfill.
Store export change
frontend/src/app/store.ts
Exports a new rootReducer = combineReducers(...) and updates store configuration to use reducer: rootReducer. No behavioral changes to individual reducers.
Dependency update
frontend/package.json
Adds baseline-browser-mapping to dependencies and devDependencies.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

TypeScript/JavaScript

Suggested reviewers

  • rahulharpal1603

Poem

🐰 I hopped through mocks and routes tonight,

Wrapped providers snug and tests polite,
Reducers joined in tidy rows,
Clicks and toggles, passing shows,
A carrot for each green test light 🥕

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'CI/CD: Refresh Frontend Test Suites' clearly and concisely summarizes the main objective of adding and updating frontend test infrastructure and test suites.
Linked Issues check ✅ Passed The PR fully addresses objectives from issue #889: refreshes test suites with modern provider-wrapped renders, adds high-value navigation and page sanity tests, establishes fresh Redux store per test to prevent state leakage, and increases CI confidence in UI stability.
Out of Scope Changes check ✅ Passed All changes are directly related to refreshing frontend test infrastructure and adding comprehensive test coverage for pages, components, and utilities, with no unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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
Contributor

@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

🤖 Fix all issues with AI agents
In `@frontend/src/components/__tests__/Sidebar.test.tsx`:
- Around line 21-28: Update the test assertions in Sidebar.test.tsx to use the
ROUTES constants instead of hard-coded string paths: replace assertions that
expect literal paths like "/settings", "/home", "/ai-tagging", "/favourites",
"/videos", "/albums", "/memories" with assertions that build the expected path
from ROUTES (e.g., `"/" + ROUTES.HOME` or `"/" + ROUTES.SETTINGS` as
appropriate) so the tests reference the same source-of-truth ROUTES constants
used by the Route definitions; update every navigation assertion in the file
that currently uses string literals to use the ROUTES.* symbols to avoid
coupling to hard-coded paths.
🧹 Nitpick comments (1)
frontend/src/components/__tests__/Sidebar.test.tsx (1)

54-149: Consider table‑driven navigation tests to reduce repetition.

The interaction tests follow the same pattern; a data‑driven approach trims boilerplate and makes it easier to add new routes.

♻️ Example refactor using test.each
+  const navCases = [
+    { label: 'Settings', start: '/', target: ROUTES.SETTINGS, page: 'Settings Page' },
+    { label: 'Home', start: ROUTES.SETTINGS, target: ROUTES.HOME, page: 'Home Page' },
+    { label: 'AI Tagging', start: '/', target: ROUTES.AI, page: 'AI Tagging Page' },
+    { label: 'Favourites', start: '/', target: ROUTES.FAVOURITES, page: 'Favourites Page' },
+    { label: 'Videos', start: '/', target: ROUTES.VIDEOS, page: 'Videos Page' },
+    { label: 'Albums', start: '/', target: ROUTES.ALBUMS, page: 'Albums Page' },
+    { label: 'Memories', start: '/', target: ROUTES.MEMORIES, page: 'Memories Page' },
+  ];
+
-  test('clicking Settings link navigates to /settings', async () => {
-    const user = userEvent.setup();
-    render(<SidebarWithRoutes />, { initialRoutes: ['/'] });
-    expect(screen.getByTestId('location-display')).toHaveTextContent('/'); // start location
-    await user.click(screen.getByText('Settings')); // click settings
-    expect(screen.getByTestId('location-display')).toHaveTextContent('/settings');
-    expect(screen.getByText('Settings Page')).toBeInTheDocument();
-  });
+  test.each(navCases)(
+    'clicking %s navigates to %s',
+    async ({ label, start, target, page }) => {
+      const user = userEvent.setup();
+      render(<SidebarWithRoutes />, { initialRoutes: [start] });
+      await user.click(screen.getByText(label));
+      expect(screen.getByTestId('location-display')).toHaveTextContent(target);
+      expect(screen.getByText(page)).toBeInTheDocument();
+    },
+  );

@rohan-pandeyy
Copy link
Contributor Author

Updated PR description.
The total test count is now at 16 (7 navigation + 1 structure + 8 page tests)
Total test suites: 3

Copy link
Contributor

@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: 2

🤖 Fix all issues with AI agents
In `@frontend/package.json`:
- Line 91: Remove the unnecessary direct devDependency
"baseline-browser-mapping" from package.json: open package.json, locate the
dependency entry "baseline-browser-mapping": "^2.9.19" and delete that line so
it is no longer listed under devDependencies; rely on the transitive dependency
provided by browserslist and run a quick npm/yarn install and lockfile update
(npm install or yarn install) to ensure the lockfile is updated accordingly.

In `@frontend/src/components/__tests__/ThemeToggle.test.tsx`:
- Around line 1-2: Tests in ThemeToggle.test.tsx are racing against Radix UI
portal mounting and exit animations; update the test to use async-safe queries
and waits: replace immediate synchronous queries (e.g., getByText / queryByText)
with await findByText to wait for elements to appear and wrap presence/absence
assertions in waitFor to wait for state/animation transitions after
userEvent.click on the ThemeToggle component; target the ThemeToggle test
utilities and any assertions that check the dropdown/menu text to ensure they
use findByText and waitFor around visibility checks.

Consolidates and stabilizes frontend tests: suppress console.log in jest.setup to reduce noise; replace repetitive Sidebar navigation specs with a parameterized test.each suite and fix route usage; refactor ThemeToggle tests to use parameterized cases, findByText/waitFor assertions for more reliability. Adds comprehensive SettingsPage unit tests covering interactions and state changes. Removes a redundant matchMedia mock from allPages tests. Updates test-utils to adjust react-query defaults (disable retries, set gc/stale times) and return the test store and queryClient alongside the render result for easier test introspection.
Copy link
Contributor

@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

🤖 Fix all issues with AI agents
In `@frontend/src/pages/__tests__/SettingsPage.test.tsx`:
- Around line 119-126: A lint violation is caused by the "TODO" comment in
SettingsPage.test.tsx; remove or modify it: either (a) add an inline suppression
by placing "// eslint-disable-next-line no-warning-comments" directly above the
TODO comment, (b) move the note into an external tracker (create a GitHub issue
or README entry) and delete the comment, or (c) reword the comment to avoid the
TODO keyword (e.g., change to "NOTE" or "FUTURE") so the lint rule no longer
triggers; target the comment block in
frontend/src/pages/__tests__/SettingsPage.test.tsx that lists "System
integrations (future scope)".

Add comprehensive Jest tests for several frontend pieces: new tests for DeleteImageDialog (rendering and yes/no interactions), EmptyStates (gallery and AI tagging), GlobalLoader (conditional rendering and messages), and InfoDialog (rendering, variants, and close action updating store). Also add lib/utils tests for cn and getErrorMessage (including Axios-like errors) and dateUtils tests for getTimeAgo (uses fake timers) and groupImagesByYearMonthFromMetadata (grouping and edge cases). Minor comment wording tweak in SettingsPage.test.tsx.
Copy link
Contributor

@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: 2

🧹 Nitpick comments (3)
frontend/src/components/__tests__/DeleteImageDialog.test.tsx (1)

23-37: Extract duplicate confirmation text to a constant.

The confirmation text string is duplicated across tests. Extract it to reduce duplication and simplify future maintenance if the text changes.

♻️ Proposed fix
 describe('DeleteImagesDialog', () => {
   const mockSetIsOpen = jest.fn();
   const mockExecuteDeleteImages = jest.fn();
+  const confirmationText = 'Do you also want to delete these images from Device ?';

   const defaultProps = {
     isOpen: true,
     setIsOpen: mockSetIsOpen,
     executeDeleteImages: mockExecuteDeleteImages,
   };

   // ...

   describe('Rendering', () => {
     test('renders confirmation text when open', () => {
       render(<DeleteImagesDialog {...defaultProps} />);

-      expect(
-        screen.getByText(
-          'Do you also want to delete these images from Device ?',
-        ),
-      ).toBeInTheDocument();
+      expect(screen.getByText(confirmationText)).toBeInTheDocument();
     });

     test('does not render content when closed', () => {
       render(<DeleteImagesDialog {...defaultProps} isOpen={false} />);

-      expect(
-        screen.queryByText(
-          'Do you also want to delete these images from Device ?',
-        ),
-      ).not.toBeInTheDocument();
+      expect(screen.queryByText(confirmationText)).not.toBeInTheDocument();
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/__tests__/DeleteImageDialog.test.tsx` around lines 23
- 37, Extract the duplicated confirmation string used in the tests into a shared
constant (e.g., CONFIRMATION_TEXT) at the top of the DeleteImageDialog.test.tsx
file and replace the two inline occurrences inside the expect/getByText and
queryByText calls with that constant; update any other tests in this file that
use the same literal to reference CONFIRMATION_TEXT so all assertions use the
single source of truth for the message shown by the DeleteImagesDialog
component.
frontend/src/components/__tests__/GlobalLoader.test.tsx (1)

17-18: Remove the redundant inline comment in this assertion block.

The comment on Line 17 restates exactly what Line 18 already asserts, so it adds noise without clarifying intent.

As per coding guidelines, "Point out redundant obvious comments that do not add clarity to the code."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/__tests__/GlobalLoader.test.tsx` around lines 17 -
18, The inline comment that duplicates the assertion is redundant; remove the
comment line that says "renders an empty div" above the assertion
expect(container.firstChild).toBeEmptyDOMElement() in the GlobalLoader test so
the test reads only the assertion (or replace it with a brief intent-focused
comment if you want to add clarifying context).
frontend/src/lib/__tests__/utils.test.ts (1)

54-75: Add single-field Axios payload cases to close a branch-coverage gap.

Current cases validate { error, message } together, but not payloads containing only one of them. Adding those two cases would lock in expected formatting behavior.

📌 Suggested test additions
+  test.each([
+    [{ error: 'Not Found' }, 'Not Found'],
+    [{ message: 'Image does not exist' }, 'Image does not exist'],
+  ])(
+    'extracts single-field Axios response data correctly',
+    (responseData, expected) => {
+      const axiosErr = fakeAxiosError('Request failed', 'ERR_BAD_REQUEST', responseData);
+      expect(getErrorMessage(axiosErr)).toBe(expected);
+    },
+  );

As per coding guidelines, "Ensure that test code is automated, comprehensive, and follows testing best practices."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/lib/__tests__/utils.test.ts` around lines 54 - 75, Add two unit
tests in frontend/src/lib/__tests__/utils.test.ts using the existing
fakeAxiosError and getErrorMessage helpers to cover single-field response
payloads: one where the response data contains only { error: 'Not Found' } and
assert getErrorMessage(...) returns 'Not Found', and one where the response data
contains only { message: 'Image does not exist' } and assert
getErrorMessage(...) returns 'Image does not exist'; place these alongside the
existing AxiosError tests so the branch where only one field is present is
exercised.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/components/__tests__/InfoDialog.test.tsx`:
- Around line 59-68: Replace the unsafe cast by importing and using the explicit
RootState type from your store and type the retrieved state (e.g. const state =
store.getState() as RootState) instead of using any; also stop inspecting the
implementation detail data-slot attribute—locate the close button via a
user-facing query by finding the dialog element (e.g. the element returned by
screen.getByRole('dialog') or the component wrapper) and then using
within(dialog).getByRole('button', { name: /close/i }) (or an equivalent
user-facing selector) before calling user.click on that button; update
references to store.getState and infoDialog accordingly.

In `@frontend/src/test-utils.tsx`:
- Around line 10-13: Replace weak ReturnType<typeof configureStore> usage with
an explicit Redux store type to preserve RootState typing: change the
CustomRenderOptions property "store?: ReturnType<typeof configureStore>" to
"store?: EnhancedStore<RootState, UnknownAction>" (and update the same typing at
the other occurrences and the test render return type). Also add imports for
EnhancedStore and UnknownAction from `@reduxjs/toolkit` and ensure any factory
function that creates the test store returns EnhancedStore<RootState,
UnknownAction> so callers can use store.getState() without casting.

---

Nitpick comments:
In `@frontend/src/components/__tests__/DeleteImageDialog.test.tsx`:
- Around line 23-37: Extract the duplicated confirmation string used in the
tests into a shared constant (e.g., CONFIRMATION_TEXT) at the top of the
DeleteImageDialog.test.tsx file and replace the two inline occurrences inside
the expect/getByText and queryByText calls with that constant; update any other
tests in this file that use the same literal to reference CONFIRMATION_TEXT so
all assertions use the single source of truth for the message shown by the
DeleteImagesDialog component.

In `@frontend/src/components/__tests__/GlobalLoader.test.tsx`:
- Around line 17-18: The inline comment that duplicates the assertion is
redundant; remove the comment line that says "renders an empty div" above the
assertion expect(container.firstChild).toBeEmptyDOMElement() in the GlobalLoader
test so the test reads only the assertion (or replace it with a brief
intent-focused comment if you want to add clarifying context).

In `@frontend/src/lib/__tests__/utils.test.ts`:
- Around line 54-75: Add two unit tests in
frontend/src/lib/__tests__/utils.test.ts using the existing fakeAxiosError and
getErrorMessage helpers to cover single-field response payloads: one where the
response data contains only { error: 'Not Found' } and assert
getErrorMessage(...) returns 'Not Found', and one where the response data
contains only { message: 'Image does not exist' } and assert
getErrorMessage(...) returns 'Image does not exist'; place these alongside the
existing AxiosError tests so the branch where only one field is present is
exercised.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 38b1f33 and 96bede0.

📒 Files selected for processing (8)
  • frontend/src/components/__tests__/DeleteImageDialog.test.tsx
  • frontend/src/components/__tests__/EmptyStates.test.tsx
  • frontend/src/components/__tests__/GlobalLoader.test.tsx
  • frontend/src/components/__tests__/InfoDialog.test.tsx
  • frontend/src/lib/__tests__/utils.test.ts
  • frontend/src/pages/__tests__/SettingsPage.test.tsx
  • frontend/src/test-utils.tsx
  • frontend/src/utils/__tests__/dateUtils.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/pages/tests/SettingsPage.test.tsx

Comment on lines +59 to +68
// Target the explicit "Close" action button (data-slot="button"),
// not the dialog's built-in X close button (data-slot="dialog-close")
const actionButton = screen
.getAllByRole('button', { name: /close/i })
.find((btn) => btn.getAttribute('data-slot') === 'button')!;
await user.click(actionButton);

// verify store updated - dialog should be closed
const state = (store.getState() as any).infoDialog;
expect(state.isOpen).toBe(false);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Check if the test file exists and view the specific lines
wc -l frontend/src/components/__tests__/InfoDialog.test.tsx

Repository: AOSSIE-Org/PictoPy

Length of output: 118


🏁 Script executed:

# View the specific lines mentioned in the review (59-68)
sed -n '59,68p' frontend/src/components/__tests__/InfoDialog.test.tsx

Repository: AOSSIE-Org/PictoPy

Length of output: 559


🏁 Script executed:

# Check for data-slot and any casts in the test file
rg -n "data-slot|as any" frontend/src/components/__tests__/InfoDialog.test.tsx

Repository: AOSSIE-Org/PictoPy

Length of output: 350


🏁 Script executed:

# Check if RootState is exported from the store
rg -n "export.*type RootState|export interface RootState" frontend/src/app/store.ts

Repository: AOSSIE-Org/PictoPy

Length of output: 123


🏁 Script executed:

# Check the full test file to understand context better
cat -n frontend/src/components/__tests__/InfoDialog.test.tsx

Repository: AOSSIE-Org/PictoPy

Length of output: 2946


🏁 Script executed:

# Find and examine the InfoDialog component
find frontend -name "InfoDialog*" -type f | grep -v test

Repository: AOSSIE-Org/PictoPy

Length of output: 107


🏁 Script executed:

# View the InfoDialog component to understand button structure
cat frontend/src/components/Dialog/InfoDialog.tsx

Repository: AOSSIE-Org/PictoPy

Length of output: 2092


Remove as any cast and use explicit RootState typing for state assertions.

Line 67 bypasses type safety with the as any cast, violating the coding guideline to "Avoid 'any', use explicit types." The RootState type is already exported from the store and should be used directly.

Additionally, the selector on data-slot (line 63) is an implementation-detail approach. Consider using a user-facing selector instead:

Suggested fix
+import type { RootState } from '@/app/store';
@@
-      const actionButton = screen
-        .getAllByRole('button', { name: /close/i })
-        .find((btn) => btn.getAttribute('data-slot') === 'button')!;
+      const actionButton = screen.getByRole('button', { name: /^close$/i });
       await user.click(actionButton);
@@
-      const state = (store.getState() as any).infoDialog;
-      expect(state.isOpen).toBe(false);
+      const state = store.getState() as RootState;
+      expect(state.infoDialog.isOpen).toBe(false);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Target the explicit "Close" action button (data-slot="button"),
// not the dialog's built-in X close button (data-slot="dialog-close")
const actionButton = screen
.getAllByRole('button', { name: /close/i })
.find((btn) => btn.getAttribute('data-slot') === 'button')!;
await user.click(actionButton);
// verify store updated - dialog should be closed
const state = (store.getState() as any).infoDialog;
expect(state.isOpen).toBe(false);
const actionButton = screen.getByRole('button', { name: /^close$/i });
await user.click(actionButton);
// verify store updated - dialog should be closed
const state = store.getState() as RootState;
expect(state.infoDialog.isOpen).toBe(false);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/__tests__/InfoDialog.test.tsx` around lines 59 - 68,
Replace the unsafe cast by importing and using the explicit RootState type from
your store and type the retrieved state (e.g. const state = store.getState() as
RootState) instead of using any; also stop inspecting the implementation detail
data-slot attribute—locate the close button via a user-facing query by finding
the dialog element (e.g. the element returned by screen.getByRole('dialog') or
the component wrapper) and then using within(dialog).getByRole('button', { name:
/close/i }) (or an equivalent user-facing selector) before calling user.click on
that button; update references to store.getState and infoDialog accordingly.

Comment on lines +10 to +13
interface CustomRenderOptions extends Omit<RenderOptions, 'wrapper'> {
preloadedState?: Partial<RootState>;
store?: ReturnType<typeof configureStore>;
initialRoutes?: string[];
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, check the structure and read the test-utils.tsx file
git ls-files frontend/src/ | head -20

Repository: AOSSIE-Org/PictoPy

Length of output: 926


🏁 Script executed:

# Check file sizes before reading
wc -l frontend/src/test-utils.tsx
wc -l frontend/src/app/store.ts

Repository: AOSSIE-Org/PictoPy

Length of output: 121


🏁 Script executed:

# Read the test-utils.tsx file
cat -n frontend/src/test-utils.tsx

Repository: AOSSIE-Org/PictoPy

Length of output: 2923


🏁 Script executed:

# Check for RootState export in store.ts
rg -n "export.*RootState" frontend/src/app/store.ts

Repository: AOSSIE-Org/PictoPy

Length of output: 123


🏁 Script executed:

# Check for ReturnType<typeof configureStore> usage in test-utils.tsx
rg -n "ReturnType<typeof configureStore>" frontend/src/test-utils.tsx

Repository: AOSSIE-Org/PictoPy

Length of output: 156


🏁 Script executed:

# Search for 'as any' casts in test files
rg -n "as any" frontend/src/components/__tests__/ --type ts --type tsx

Repository: AOSSIE-Org/PictoPy

Length of output: 89


🏁 Script executed:

# Check if EnhancedStore is available from `@reduxjs/toolkit`
rg -n "EnhancedStore" frontend/src/ --type ts --type tsx

Repository: AOSSIE-Org/PictoPy

Length of output: 89


🏁 Script executed:

# Try searching for 'as any' without the type filter
rg -n "as any" frontend/src/components/__tests__/

Repository: AOSSIE-Org/PictoPy

Length of output: 176


🏁 Script executed:

# Check if InfoDialog.test.tsx exists and examine it
cat -n frontend/src/components/__tests__/InfoDialog.test.tsx

Repository: AOSSIE-Org/PictoPy

Length of output: 2946


🏁 Script executed:

# Check store.ts to understand the full context
cat -n frontend/src/app/store.ts

Repository: AOSSIE-Org/PictoPy

Length of output: 1294


🏁 Script executed:

# Search for 'as any' more broadly in all test files
rg -n "as any" frontend/src/ -g "*.test.*"

Repository: AOSSIE-Org/PictoPy

Length of output: 176


🏁 Script executed:

# Check `@reduxjs/toolkit` exports to verify if EnhancedStore is available
# by looking at imports in the codebase
rg -n "from '@reduxjs/toolkit'" frontend/src/ | head -20

Repository: AOSSIE-Org/PictoPy

Length of output: 1152


🏁 Script executed:

# Check if EnhancedStore or similar types are mentioned anywhere in the codebase
rg -n "EnhancedStore|UnknownAction" frontend/src/

Repository: AOSSIE-Org/PictoPy

Length of output: 44


🏁 Script executed:

# Check package.json to see `@reduxjs/toolkit` version
cat -n package.json | grep -A 5 -B 5 "reduxjs"

Repository: AOSSIE-Org/PictoPy

Length of output: 44


🏁 Script executed:

# Search for Redux Toolkit type documentation patterns in the codebase
rg -n "ReturnType.*store\." frontend/src/

Repository: AOSSIE-Org/PictoPy

Length of output: 149


🌐 Web query:

@reduxjs/toolkit EnhancedStore UnknownAction types exports

💡 Result:

  • EnhancedStore is exported by RTK as a type from @reduxjs/toolkit (it’s re-exported from RTK’s configureStore module). (app.unpkg.com)

    import type { EnhancedStore } from '@reduxjs/toolkit'
  • In RTK v2.x, the EnhancedStore / configureStore generics default the action type to UnknownAction (A extends Action = UnknownAction), and that UnknownAction type is imported from redux. (app.unpkg.com)

  • Where to import UnknownAction:

    • From redux (the canonical source):
      import type { UnknownAction } from 'redux'
      (redux.js.org)
    • Or from @reduxjs/toolkit, because RTK does export * from 'redux' (so Redux exports like UnknownAction are re-exported by RTK):
      import type { UnknownAction } from '@reduxjs/toolkit'
      (app.unpkg.com)

Citations:


Tighten test store typing to RootState to avoid downstream any casts.

Using ReturnType<typeof configureStore> weakens store.getState() typing. This forces test consumers to cast to any, as seen in InfoDialog.test.tsx:67 where store.getState() as any is required to access the typed state.

Replace with explicit EnhancedStore<RootState, UnknownAction> type for proper typing preservation across all tests.

🔧 Suggested typing refinement
 import { configureStore } from '@reduxjs/toolkit';
+import type { EnhancedStore, UnknownAction } from '@reduxjs/toolkit';
 import { rootReducer, RootState } from '@/app/store';

+type TestStore = EnhancedStore<RootState, UnknownAction>;
+
 interface CustomRenderOptions extends Omit<RenderOptions, 'wrapper'> {
   preloadedState?: Partial<RootState>;
-  store?: ReturnType<typeof configureStore>;
+  store?: TestStore;
   initialRoutes?: string[];
 }
@@
   children: React.ReactNode;
-  store: ReturnType<typeof configureStore>;
+  store: TestStore;
   queryClient: QueryClient;
   initialRoutes?: string[];
 }) => {
@@
-  const testStore =
+  const testStore: TestStore =
     store ??
     configureStore({
       reducer: rootReducer,
       preloadedState,
     });

Applies to lines 12, 23, 48, and the return statement (lines 80-84). Aligns with coding guideline: "Avoid 'any', use explicit types."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/test-utils.tsx` around lines 10 - 13, Replace weak
ReturnType<typeof configureStore> usage with an explicit Redux store type to
preserve RootState typing: change the CustomRenderOptions property "store?:
ReturnType<typeof configureStore>" to "store?: EnhancedStore<RootState,
UnknownAction>" (and update the same typing at the other occurrences and the
test render return type). Also add imports for EnhancedStore and UnknownAction
from `@reduxjs/toolkit` and ensure any factory function that creates the test
store returns EnhancedStore<RootState, UnknownAction> so callers can use
store.getState() without casting.

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

Labels

CI/CD enhancement New feature or request test

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CI/CD: Outdated Frontend Test Suites

1 participant