Skip to content

Conversation

@PRoIISHAAN
Copy link
Contributor

@PRoIISHAAN PRoIISHAAN commented Dec 29, 2025

Checklist

  • I have read the OpenWISP Contributing Guidelines.
  • I have manually tested the changes proposed in this pull request.
  • I have written new test cases for new code and/or updated existing tests for changes to existing code.
  • I have updated the documentation.

Reference to Existing Issue

Closes #870.

Description of Changes

  1. React 18 Migration
    Upgraded React and React DOM from v17 → v18.3
    Replaced legacy ReactDOM.render with createRoot
    Migrated from react-helmet → react-helmet-async for React 18 compatibility

  2. Migration from Enzyme to RTL
    Partial migration from Enzyme to React Testing Library (RTL)
    Added RTL + Jest DOM setup:
    @testing-library/react
    @testing-library/jest-dom

  3. Test Suite Migration
    Updated snapshot tests to match RTL rendering
    Rewrote tests to use RTL patterns (fireEvent, screen, waitFor)
    Fixed async test stability issues caused by React 18 concurrent rendering
    All test suites now pass, except status.test.js (kept as .skip / backup for future migration)

  4. Server-Side Refactor for Testability
    Extracted Express app into server/app.js
    Simplified server/index.js to only handle startup logic
    Updated API tests to use supertest instead of live HTTP requests

  5. Build & Tooling Updates
    Updated Babel plugins for React 18 compatibility
    Updated Jest to v30
    Updated Webpack config:
    Added eslint-webpack-plugin
    Improved linting behavior (non-blocking)

Updated package.json

@PRoIISHAAN PRoIISHAAN closed this Dec 30, 2025
@PRoIISHAAN PRoIISHAAN reopened this Dec 31, 2025
@PRoIISHAAN PRoIISHAAN force-pushed the fix-reactversion-migration branch from 5045db9 to ade71d3 Compare December 31, 2025 17:19
@coderabbitai
Copy link

coderabbitai bot commented Dec 31, 2025

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

💤 Files selected but had no reviewable changes (1)
  • package.json
⛔ Files ignored due to path filters (14)
  • client/components/404/__snapshots__/404.test.js.snap is excluded by !**/*.snap
  • client/components/contact-box/__snapshots__/contact.test.js.snap is excluded by !**/*.snap
  • client/components/footer/__snapshots__/footer.test.js.snap is excluded by !**/*.snap
  • client/components/header/__snapshots__/header.test.js.snap is excluded by !**/*.snap
  • client/components/login/__snapshots__/login.test.js.snap is excluded by !**/*.snap
  • client/components/mobile-phone-change/__snapshots__/mobile-phone-change.test.js.snap is excluded by !**/*.snap
  • client/components/mobile-phone-verification/__snapshots__/mobile-phone-verification.test.js.snap is excluded by !**/*.snap
  • client/components/organization-wrapper/__snapshots__/organization-wrapper.test.js.snap is excluded by !**/*.snap
  • client/components/password-change/__snapshots__/password-change.test.js.snap is excluded by !**/*.snap
  • client/components/password-confirm/__snapshots__/password-confirm.test.js.snap is excluded by !**/*.snap
  • client/components/password-reset/__snapshots__/password-reset.test.js.snap is excluded by !**/*.snap
  • client/components/registration/__snapshots__/registration.test.js.snap is excluded by !**/*.snap
  • client/components/status/__snapshots__/status.test.js.snap is excluded by !**/*.snap
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

📝 Walkthrough

Walkthrough

This PR updates app bootstrap to use react-dom/client.createRoot and wraps the root with HelmetProvider, CookiesProvider, and Redux Provider; replaces several Babel proposal plugins with transform equivalents (loose: true); enhances ESLint config for testing (jest, testing-library, jest-dom); adds many accessibility/data-testid attributes; introduces mounted/AbortController guards in multiple components; migrates most unit tests from Enzyme to React Testing Library with new test helpers; converts one instance method to static; and adds a browser-test utility waitForToastText used across several e2e tests.

Sequence Diagram(s)

sequenceDiagram
    participant Browser as Browser
    participant AppInit as client/app.js
    participant ReactClient as react-dom/client
    participant Helmet as HelmetProvider
    participant Redux as Redux Provider
    participant Cookies as CookiesProvider
    participant DOM as DOM Container

    Browser->>AppInit: load entry bundle
    AppInit->>ReactClient: call createRoot(container)
    ReactClient->>DOM: obtain container reference
    AppInit->>Helmet: wrap app tree with HelmetProvider
    AppInit->>Cookies: wrap app tree with CookiesProvider
    AppInit->>Redux: wrap app tree with Redux Provider
    AppInit->>ReactClient: root.render(<Helmet><Cookies><Redux><App/></Redux></Cookies></Helmet>)
    ReactClient->>DOM: mount component tree
    DOM-->>Browser: app rendered
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • nemesifier
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title '[deps] Upgraded React to 18.3 #870' clearly identifies the main change as a React dependency upgrade to version 18.3, directly referencing the issue number. It is concise and specific about the primary objective.
Description check ✅ Passed The PR description follows the template structure with checklist items completed (3 of 4), includes issue reference (#870), and provides a detailed description of changes across multiple categories (React migration, Enzyme to RTL, test suite, server refactor, build updates).
Linked Issues check ✅ Passed The PR addresses all primary requirements from issue #870: upgraded to React 18.3, migrated testing from Enzyme to RTL, updated Babel plugins, updated package.json dependencies, and ran test suite. Code changes comprehensively implement these objectives across multiple files.
Out of Scope Changes check ✅ Passed All code changes are directly related to the React 18.3 upgrade and Enzyme-to-RTL migration objectives. Changes include React API updates (createRoot, HelmetProvider), test migrations (RTL setup), build tooling updates (Babel, Jest), and accessibility/testability attributes that support the migration goals.

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


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: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
client/components/contact-box/contact.test.js (1)

63-63: Fix the component name in the describe block.

The describe block incorrectly references <Status /> but should reference <Contact /> to match the component under test.

🔎 Proposed fix
-describe("<Status /> rendering", () => {
+describe("<Contact /> rendering", () => {
client/components/modal/modal.test.js (1)

206-232: Fix event type mismatch in non-Esc key test.

Line 228 fires a keyDown event, but the Modal component listens for keyup events (as noted in the comment on line 198 and confirmed in the component code). This test passes for the wrong reason—the component never receives the event. Use fireEvent.keyUp to properly test the non-Esc key behavior.

🔎 Proposed fix
    // Simulate non-Esc key press
-    fireEvent.keyDown(document, {keyCode: 1});
+    fireEvent.keyUp(document, {keyCode: 1});

    // Should not navigate
    expect(props.navigate).not.toHaveBeenCalled();
client/app.js (1)

27-27: Remove unused history prop from Router component.

In react-router-dom v6, BrowserRouter doesn't accept a history prop—it creates its own history internally and ignores this prop. While the history import is used elsewhere in the application (e.g., in redirect utilities), passing it to Router has no effect and should be removed.

🧹 Nitpick comments (22)
client/components/footer/footer.test.js (1)

72-79: Excellent migration to RTL patterns!

The test assertions correctly use:

  • getByText for elements that must exist (throws if not found)
  • queryByText for elements that should not exist (returns null)
  • queryByRole("link") to verify absence of links (line 78)
  • jest-dom matchers for clear, readable assertions

The added comments clearly document which links should be visible vs. hidden, improving test maintainability.

Optional: Consider role-based queries for better accessibility testing.

While the current text-based queries work correctly, you could enhance semantic clarity and accessibility testing by using getByRole('link', {name: '...'}) for link assertions:

// Instead of:
expect(screen.getByText("about")).toBeInTheDocument();

// Consider:
expect(screen.getByRole('link', {name: 'about'})).toBeInTheDocument();

This explicitly tests that the text appears within a link element and aligns with accessibility best practices. However, the current approach is perfectly valid for this migration.

Also applies to: 81-84, 86-99, 101-114, 116-130

client/components/modal/modal.test.js (1)

36-43: Consider removing redundant mock cleanup.

The afterEach cleanup on lines 41-43 is redundant since beforeEach already clears all mocks before each test. You can safely remove the afterEach hook.

🔎 Suggested cleanup
  beforeEach(() => {
    jest.clearAllMocks();
    axios.mockReset();
  });

-  afterEach(() => {
-    jest.clearAllMocks();
-  });
client/components/registration/registration-subscriptions.test.js (3)

233-233: Typo: lastConsoleOutuput should be lastConsoleOutput.

This typo is repeated throughout the file and affects readability.

🔎 Proposed fix
-  let lastConsoleOutuput;
+  let lastConsoleOutput;

Apply similar fix to all occurrences (lines 239, 241, 319, 320, 321).


317-322: Fragile assertion pattern for act() warnings.

Checking lastConsoleOutuput for act(...) warnings is brittle and couples tests to React's internal warning format. Consider wrapping the render/updates in act() properly instead, or if warnings are expected and harmless, suppress them explicitly in the test setup.


470-505: Consider using RTL queries instead of direct DOM access.

While the eslint-disable comments acknowledge this, using screen.getByRole or screen.getByLabelText would be more idiomatic RTL and less brittle than form.querySelector. The billing info section has labels that could be queried.

🔎 Example improvement
-    // eslint-disable-next-line testing-library/no-node-access
-    const emailInput = form.querySelector("input[name='email']");
+    const emailInput = screen.getByRole('textbox', { name: /email/i });
client/components/registration/registration.test.js (2)

212-212: Same typo: lastConsoleOutuput should be lastConsoleOutput.

For consistency across the test suite, this should be fixed here as well.


664-674: The eslint-disable comment is unnecessary.

The test does have an assertion via expect(container).toMatchSnapshot(), so the jest/expect-expect disable is not needed.

🔎 Proposed fix
-  // eslint-disable-next-line jest/expect-expect
   it("should render modal", () => {
     props = createTestProps();
     const {container} = renderWithProviders(
       <Routes>
         <Route path="*" element={<Registration {...props} />} />
       </Routes>,
     );

     expect(container).toMatchSnapshot();
   });
client/components/organization-wrapper/organization-wrapper.test.js (3)

192-217: Test assertions verify props, not actual behavior.

The assertions on lines 213-216 only confirm that the props contain the expected values, which is tautological since the test itself sets those values. This doesn't verify that Helmet actually processes the CSS files.

Consider using react-helmet-async's HelmetProvider context or Helmet.peek() to verify the actual head elements being rendered.

🔎 Proposed approach
it("should load multiple CSS files", async () => {
  const helmetContext = {};
  props.organization = {
    ...props.organization,
    configuration: {
      ...props.organization.configuration,
      css: ["index.css", "custom.css"],
    },
    exists: true,
  };

  // Render with HelmetProvider that captures helmet state
  render(
    <HelmetProvider context={helmetContext}>
      {/* ... rest of providers ... */}
    </HelmetProvider>
  );

  await waitFor(() => {
    expect(container.querySelector(".app-container")).toBeInTheDocument();
  });

  // Verify helmet captured the link elements
  const { helmet } = helmetContext;
  expect(helmet.link.toComponent()).toHaveLength(2);
});

554-591: Consider consolidating mountComponent with renderWithRouter.

Both helpers create similar mocked stores and wrap components with the same providers. Consider refactoring renderWithRouter to accept an optional initialEntries parameter to reduce duplication.

🔎 Proposed consolidation
-const renderWithRouter = (props) => {
+const renderWithRouter = (props, initialEntries = [props.location?.pathname || "/"]) => {
   const mockedStore = {
     // ... existing store setup
   };

   return render(
     <HelmetProvider>
       <Provider store={mockedStore}>
-        <MemoryRouter initialEntries={[props.location?.pathname || "/"]}>
+        <MemoryRouter initialEntries={initialEntries}>
           <OrganizationWrapper {...props} />
         </MemoryRouter>
       </Provider>
     </HelmetProvider>,
   );
 };

Then replace mountComponent calls with renderWithRouter(props, ["/default/status"]).


146-153: Consider using accessibility-based queries where possible.

The extensive use of container.querySelector with eslint-disable comments works but deviates from RTL best practices. For elements with meaningful text or roles, queries like getByRole, getByText, or findByTestId would be more resilient to refactoring.

For structural assertions (checking if .app-container exists), the current approach is acceptable, but consider adding data-testid attributes for key elements to enable cleaner queries without eslint overrides.

client/components/password-reset/password-reset.test.js (1)

54-85: Consider extracting shared test utilities to reduce duplication.

The createMockStore and renderWithProviders helpers are duplicated across multiple test files in this PR (password-reset, header, payment-status, password-change, etc.). Extracting these to a shared test utility file would improve maintainability.

Example shared test utility
// client/test-utils/render-with-providers.js
import {render} from "@testing-library/react";
import {Provider} from "react-redux";
import {MemoryRouter} from "react-router-dom";
import React from "react";

export const createMockStore = (overrides = {}) => {
  const defaultState = {
    organization: { configuration: { /* defaults */ } },
    language: "en",
  };
  return {
    subscribe: () => {},
    dispatch: () => {},
    getState: () => ({...defaultState, ...overrides}),
  };
};

export const renderWithProviders = (component, {store, ...options} = {}) =>
  render(
    <Provider store={store || createMockStore()}>
      <MemoryRouter>{component}</MemoryRouter>
    </Provider>,
    options
  );
client/components/payment-status/payment-status.test.js (1)

126-143: Fragile mock re-setup pattern in afterEach.

Re-creating the getConfig mock implementation in afterEach after jest.restoreAllMocks() is fragile and duplicates the mock definition from the top of the file. This pattern is repeated across multiple test files.

Consider using jest.spyOn with mockReturnValue that can be reset, or restructuring to avoid needing to re-setup mocks:

Alternative approach
+// Define mock config once at module level
+const mockPaymentStatusConfig = {
+  components: {
+    payment_status_page: {
+      content: { en: { pending: "Payment pending", success: "Payment successful", failed: "Payment failed" } },
+    },
+  },
+};
+
 afterEach(() => {
   jest.clearAllMocks();
   jest.restoreAllMocks();
-  // Re-setup the getConfig mock after clearing
-  getConfig.mockImplementation(() => ({
-    components: {
-      payment_status_page: {
-        content: {
-          en: {
-            pending: "Payment pending",
-            success: "Payment successful",
-            failed: "Payment failed",
-          },
-        },
-      },
-    },
-  }));
+  getConfig.mockImplementation(() => mockPaymentStatusConfig);
 });
client/components/password-change/password-change.test.js (1)

211-221: Complex error detection matcher is pragmatic but brittle.

The custom matcher function checking both text content and class name is a pragmatic solution when error messages may use translation keys. However, it's coupled to implementation details (class names).

Consider adding a data-testid to error elements in the component for more stable test queries, or asserting on user-visible behavior rather than implementation details.

client/components/mobile-phone-change/mobile-phone-change.test.js (1)

134-179: Inconsistent rendering helpers in the same test file.

The file defines two different rendering approaches:

  • renderWithProviders (lines 134-139): Uses MemoryRouter
  • mountComponent (lines 151-179): Uses BrowserRouter with Routes and explicit route paths

This inconsistency makes the test file harder to understand and maintain. Consider consolidating to a single approach.

Suggested consolidation

If routing with specific paths is needed, extend renderWithProviders to accept route configuration:

const renderWithProviders = (component, {initialEntries = ["/"]} = {}) =>
  render(
    <Provider store={createMockStore()}>
      <MemoryRouter initialEntries={initialEntries}>
        <Routes>
          <Route path="/test-org-2/status" element={<StatusMock />} />
          <Route path="*" element={component} />
        </Routes>
      </MemoryRouter>
    </Provider>,
  );
client/components/password-confirm/password-confirm.test.js (2)

8-57: Mock declarations after imports rely on Jest hoisting.

The jest.mock("../../utils/get-config") call at line 53 appears after getConfig is imported at line 8. While Jest hoists mock declarations to the top of the file, this code organization is confusing and doesn't follow the pattern established in other test files in this PR (which use /* eslint-disable import/first */ to explicitly place mocks before imports).

Consider reorganizing for consistency with other test files:

Suggested reorder
 import axios from "axios";
 import {render, screen, waitFor, fireEvent} from "@testing-library/react";
 import "@testing-library/jest-dom";
 import React from "react";
 import {MemoryRouter} from "react-router-dom";
 import {Provider} from "react-redux";
 import {toast} from "react-toastify";
-import getConfig from "../../utils/get-config";
-import loadTranslation from "../../utils/load-translation";
-import PasswordConfirm from "./password-confirm";
-import translation from "../../test-translation.json";
 import tick from "../../utils/tick";

 const mockConfig = { /* ... */ };

+/* eslint-disable import/first */
 jest.mock("axios");
 jest.mock("../../utils/get-config", () => ({
   __esModule: true,
   default: jest.fn(() => mockConfig),
 }));
 jest.mock("../../utils/load-translation");

+import getConfig from "../../utils/get-config";
+import loadTranslation from "../../utils/load-translation";
+import PasswordConfirm from "./password-confirm";
+import translation from "../../test-translation.json";
+/* eslint-enable import/first */

84-111: Different renderWithProviders signature than other files.

This file's renderWithProviders takes props directly and builds the store from props, while other files define createMockStore separately and have renderWithProviders take a component. This inconsistency adds cognitive load when working across test files.

Consider aligning with the pattern used in other files for consistency, or if this approach is preferred, adopt it across all test files.

client/components/login/login.test.js (1)

148-174: Naming inconsistency: renderWithProvider vs renderWithProviders.

This file uses renderWithProvider (singular) while other files in this PR use renderWithProviders (plural). This minor inconsistency could cause confusion when developers work across files.

Consider standardizing on one name across all test files, preferably renderWithProviders to match the majority.

client/components/payment-process/payment-process.test.js (4)

131-141: Test assertion doesn't verify redirect behavior.

The test claims to verify redirect behavior when payment_url is absent, but only asserts that validateToken was called. Consider asserting on the actual redirect mechanism (e.g., verify Navigate component renders or navigate prop is called).


131-131: Inconsistent mock setup for async function.

validateToken is an async function, but tests inconsistently use mockReturnValue (lines 131, 149, 165, 179, etc.) vs mockResolvedValue (lines 111, 364). While both work due to JavaScript's await behavior, using mockResolvedValue consistently is semantically correct for async functions.


255-285: Incomplete event listener restoration could cause test pollution.

This test mocks window.addEventListener but doesn't mock or restore window.removeEventListener, unlike the comprehensive approach in lines 206-245. Consider using the same pattern for consistency:

🔎 Suggested fix
     const events = {};
     const originalAddEventListener = window.addEventListener;
+    const originalRemoveEventListener = window.removeEventListener;

     window.addEventListener = jest.fn((event, callback) => {
       events[event] = callback;
     });
+    window.removeEventListener = jest.fn();

     // ... test body ...

     window.addEventListener = originalAddEventListener;
+    window.removeEventListener = originalRemoveEventListener;

379-381: Weak assertion for redirect behavior.

The assertion queryByText(/payment/i).not.toBeInTheDocument() is too generic. The component returns null when redirecting, but this assertion could pass even if the component renders nothing due to an error. The redirectSpy assertion on line 375 is sufficient to verify the behavior.

.eslintrc.json (1)

2-4: Consider extending recommended configs for new plugins.

The testing-library and jest-dom plugins are added but their rules aren't enabled. Extend their recommended configs to catch common anti-patterns:

Suggested enhancement
-  "extends": ["airbnb", "prettier", "plugin:jest/recommended"],
+  "extends": ["airbnb", "prettier", "plugin:jest/recommended", "plugin:testing-library/react", "plugin:jest-dom/recommended"],
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f030dce and a418cbf.

⛔ Files ignored due to path filters (19)
  • client/components/404/__snapshots__/404.test.js.snap is excluded by !**/*.snap
  • client/components/contact-box/__snapshots__/contact.test.js.snap is excluded by !**/*.snap
  • client/components/footer/__snapshots__/footer.test.js.snap is excluded by !**/*.snap
  • client/components/header/__snapshots__/header.test.js.snap is excluded by !**/*.snap
  • client/components/login/__snapshots__/login.test.js.snap is excluded by !**/*.snap
  • client/components/logout/__snapshots__/logout.test.js.snap is excluded by !**/*.snap
  • client/components/mobile-phone-change/__snapshots__/mobile-phone-change.test.js.snap is excluded by !**/*.snap
  • client/components/mobile-phone-verification/__snapshots__/mobile-phone-verification.test.js.snap is excluded by !**/*.snap
  • client/components/modal/__snapshots__/modal.test.js.snap is excluded by !**/*.snap
  • client/components/organization-wrapper/__snapshots__/organization-wrapper.test.js.snap is excluded by !**/*.snap
  • client/components/password-change/__snapshots__/password-change.test.js.snap is excluded by !**/*.snap
  • client/components/password-confirm/__snapshots__/password-confirm.test.js.snap is excluded by !**/*.snap
  • client/components/password-reset/__snapshots__/password-reset.test.js.snap is excluded by !**/*.snap
  • client/components/payment-process/__snapshots__/payment-process.test.js.snap is excluded by !**/*.snap
  • client/components/payment-status/__snapshots__/payment-status.test.js.snap is excluded by !**/*.snap
  • client/components/registration/__snapshots__/registration.test.js.snap is excluded by !**/*.snap
  • client/components/status/__snapshots__/status.test.js.snap is excluded by !**/*.snap
  • package-lock.json is excluded by !**/package-lock.json
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (54)
  • .eslintrc.json
  • babel.config.js
  • browser-test/create-mobile-configuration.js
  • browser-test/utils.js
  • client/app.js
  • client/components/404/404.test.js
  • client/components/contact-box/contact.test.js
  • client/components/footer/footer.test.js
  • client/components/header/header.test.js
  • client/components/login/login.test.js
  • client/components/logout/logout.test.js
  • client/components/mobile-phone-change/mobile-phone-change.js
  • client/components/mobile-phone-change/mobile-phone-change.test.js
  • client/components/mobile-phone-verification/mobile-phone-verification.js
  • client/components/mobile-phone-verification/mobile-phone-verification.test.js
  • client/components/modal/modal.test.js
  • client/components/organization-wrapper/organization-wrapper.js
  • client/components/organization-wrapper/organization-wrapper.test.js
  • client/components/password-change/password-change.js
  • client/components/password-change/password-change.test.js
  • client/components/password-confirm/password-confirm.js
  • client/components/password-confirm/password-confirm.test.js
  • client/components/password-reset/password-reset.js
  • client/components/password-reset/password-reset.test.js
  • client/components/payment-process/payment-process.test.js
  • client/components/payment-status/payment-status.js
  • client/components/payment-status/payment-status.test.js
  • client/components/registration/registration-subscriptions.test.js
  • client/components/registration/registration.js
  • client/components/registration/registration.test.js
  • client/components/registration/subscriptions.test.js
  • client/components/registration/test-utils.js
  • client/components/status/status.js
  • client/components/status/status.test.js
  • client/components/status/status.test.js.enzyme-backup
  • client/components/status/status.test.js.rtl-backup
  • client/utils/__mocks__/get-config.js
  • client/utils/get-config.js
  • client/utils/get-plan-selection.js
  • client/utils/load-translation.js
  • client/utils/load-translation.test.js
  • client/utils/log-error.js
  • client/utils/needs-verify.js
  • client/utils/tick.js
  • client/utils/utils.test.js
  • client/utils/with-route-props.js
  • config/__tests__/add-org.test.js
  • config/add-org.js
  • config/jest.config.js
  • config/setupTests.js
  • config/webpack.config.js
  • package.json
  • server/app.js
  • server/index.js
🧰 Additional context used
🧬 Code graph analysis (15)
client/components/password-confirm/password-confirm.js (1)
client/utils/submit-on-enter.js (1)
  • form (3-3)
client/components/password-reset/password-reset.js (1)
client/utils/submit-on-enter.js (1)
  • form (3-3)
client/components/modal/modal.test.js (2)
client/components/modal/modal.js (1)
  • Modal (14-97)
client/utils/get-text.js (1)
  • getText (1-5)
client/components/logout/logout.test.js (1)
client/components/logout/logout.js (1)
  • Logout (16-62)
client/components/password-reset/password-reset.test.js (2)
client/components/password-reset/password-reset.js (1)
  • PasswordReset (18-154)
client/utils/tick.js (1)
  • tick (1-5)
client/components/registration/registration-subscriptions.test.js (6)
client/utils/get-config.js (2)
  • config (4-4)
  • getConfig (3-13)
client/utils/__mocks__/get-config.js (1)
  • getConfig (11-16)
client/components/registration/test-utils.js (2)
  • mountComponent (8-38)
  • mockedStore (12-27)
browser-test/create-mobile-configuration.js (1)
  • data (6-6)
client/utils/tick.js (1)
  • tick (1-5)
client/utils/redirect-to-payment.js (1)
  • redirectToPayment (3-4)
client/components/password-change/password-change.test.js (1)
client/components/password-change/password-change.js (1)
  • PasswordChange (22-251)
client/components/payment-process/payment-process.test.js (5)
client/utils/get-payment-status.js (1)
  • getPaymentStatusRedirectUrl (37-71)
client/utils/validate-token.js (1)
  • validateToken (11-87)
client/utils/get-config.js (1)
  • getConfig (3-13)
client/components/payment-process/payment-process.js (1)
  • PaymentProcess (12-133)
client/utils/tick.js (1)
  • tick (1-5)
client/components/mobile-phone-change/mobile-phone-change.test.js (5)
client/utils/__mocks__/get-config.js (1)
  • getConfig (11-16)
client/utils/get-config.js (1)
  • getConfig (3-13)
client/components/organization-wrapper/lazy-import.js (2)
  • MobilePhoneChange (13-18)
  • MobilePhoneChange (13-18)
client/utils/__mocks__/load-translation.js (1)
  • loadTranslation (17-27)
client/utils/tick.js (1)
  • tick (1-5)
client/components/header/header.test.js (2)
client/utils/check-internal-links.js (1)
  • isInternalLink (1-4)
client/components/header/index.js (2)
  • mapDispatchToProps (15-19)
  • mapDispatchToProps (15-19)
client/components/login/login.test.js (3)
client/components/login/login.js (1)
  • Login (36-506)
client/utils/redirect-to-payment.js (1)
  • redirectToPayment (3-4)
client/utils/get-parameter-by-name.js (1)
  • getParameterByName (1-9)
client/components/contact-box/contact.test.js (1)
client/components/contact-box/contact.js (1)
  • Contact (13-75)
client/app.js (1)
client/store/index.js (1)
  • store (6-6)
client/components/password-confirm/password-confirm.test.js (2)
client/components/password-confirm/password-confirm.js (1)
  • PasswordConfirm (20-231)
client/utils/tick.js (1)
  • tick (1-5)
client/components/mobile-phone-verification/mobile-phone-verification.test.js (3)
client/utils/__mocks__/get-config.js (1)
  • getConfig (11-16)
client/utils/validate-token.js (1)
  • validateToken (11-87)
client/utils/tick.js (1)
  • tick (1-5)
🪛 Gitleaks (8.30.0)
client/components/registration/registration-subscriptions.test.js

[high] 104-104: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 105-105: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Tests and Coverage
🔇 Additional comments (44)
client/components/footer/footer.test.js (2)

3-4: LGTM! Correct imports for RTL migration.

The imports are properly updated to use React Testing Library's render and screen utilities, along with jest-dom for extended DOM matchers.


54-55: LGTM! Snapshot tests correctly updated for RTL.

The snapshot tests properly use RTL's render and destructure container for snapshot matching, which is the correct pattern for migrating from Enzyme.

Also applies to: 68-69

client/components/contact-box/contact.test.js (3)

1-22: LGTM! Mock setup follows RTL migration best practices.

The mock placement before imports with scoped ESLint suppression is correct and consistent with the project-wide migration pattern.


78-96: LGTM! RTL query patterns and assertions are correct.

The test correctly uses:

  • getByAltText for asserting element presence (throws if not found)
  • queryByAltText with not.toBeInTheDocument() for asserting element absence
  • toBeInTheDocument() matcher from jest-dom

This follows RTL best practices for the migration.


74-96: No changes needed. The test expectations correctly match the shouldLinkBeShown implementation. The authentication logic is standard: authenticated: true shows the link only to authenticated users, authenticated: false shows it only to non-authenticated users, and undefined always shows it. All test assertions align with the component behavior.

client/components/modal/modal.test.js (1)

311-354: Excellent cleanup verification test.

This test properly verifies that the Modal component registers event listeners on mount and removes them on unmount, which is critical for preventing memory leaks in React 18. The use of spies and proper cleanup with mockRestore() follows RTL best practices.

client/components/registration/registration.js (3)

379-379: LGTM!

Adding data-testid="registration-form" is the correct approach for RTL-based testing. This provides a stable selector that won't break with UI changes.


635-635: LGTM!

The parentClassName="password-toggle" prop provides a consistent styling hook for both password toggle icons, enabling tests to locate these elements reliably.

Also applies to: 663-663


672-675: LGTM!

Good addition of data-testid="billing-info" for conditionally rendered billing section. This enables reliable RTL queries in tests that verify billing info visibility based on plan requirements.

client/components/registration/registration-subscriptions.test.js (2)

103-106: False positive from static analysis - these are test fixture tokens.

The Gitleaks warnings on lines 104-105 are false positives. These are clearly mock/fake tokens used for testing API response handling, not real credentials. The values are arbitrary hex strings that simulate what the API would return, which is standard practice for unit tests.


160-165: LGTM - Well-structured provider wrapper.

The renderWithProviders helper correctly wraps components with Redux Provider and MemoryRouter, which is essential for RTL testing of connected components with routing.

client/components/registration/registration.test.js (5)

4-9: LGTM - Proper RTL import setup.

Good migration to RTL with the correct imports: render, screen, waitFor, fireEvent, and jest-dom matchers.


17-88: LGTM - Mock configuration pattern.

Defining mockConfig before jest.mock and using it in the mock factory is the correct pattern for Jest module mocking. This ensures the mock is properly hoisted.


119-150: LGTM - Well-structured test utilities.

The createMockStore and renderWithProviders helpers provide consistent test setup with Redux and Router context, following RTL best practices for testing connected components.


256-385: Well-structured sequential API mock testing.

Good use of chained mockImplementationOnce calls to test multiple submission scenarios in sequence. The test properly awaits async operations with tick() and waitFor().


703-717: LGTM - Good test for password toggle functionality.

This test properly verifies the password visibility toggle feature by checking the input type changes from 'password' to 'text' after clicking the toggle.

client/components/organization-wrapper/organization-wrapper.js (1)

9-9: LGTM! Correct migration to react-helmet-async.

This import change from react-helmet to react-helmet-async is the appropriate approach for React 18 compatibility, as the async variant supports concurrent rendering. The Helmet component API remains the same, so no further changes are needed in this file.

client/components/organization-wrapper/organization-wrapper.test.js (3)

10-46: LGTM! Correct mock-before-import pattern.

Mocking modules before importing them is the correct approach for Jest, and the eslint-disable comments are justified here.


94-132: Good helper implementation for RTL testing.

The renderWithRouter helper correctly provides all necessary context (HelmetProvider, Redux Provider, MemoryRouter) for testing the OrganizationWrapper component.


1-8: Good migration to React Testing Library with HelmetProvider integration.

The test file has been comprehensively migrated from Enzyme to RTL with proper async handling using waitFor, correct HelmetProvider integration for react-helmet-async, and appropriate mocking patterns. The overall structure supports the React 18 upgrade well.

client/components/password-confirm/password-confirm.js (1)

133-137: Good accessibility improvement.

Adding the aria-label enhances screen reader support for the password confirmation form.

client/components/password-reset/password-reset.js (1)

97-101: Good accessibility improvement.

The aria-label attribute provides better screen reader support for the password reset form.

client/components/payment-status/payment-status.js (1)

20-20: Mounted-flag pattern correctly implemented.

The componentIsMounted flag prevents setState warnings after the component unmounts. This pattern is correctly applied:

  • Initialized in constructor (line 20)
  • Set to true in componentDidMount (line 24)
  • Checked before setState after async operation (lines 41-43)
  • Cleared in componentWillUnmount (lines 70-72)

This is a pragmatic solution for React 18 class component migration. For future refactoring to functional components, consider using cleanup functions in useEffect to cancel async operations instead of relying on mounted flags.

Also applies to: 24-24, 41-43, 70-72

client/components/password-change/password-change.js (2)

38-38: Mounted-flag pattern correctly implemented.

The componentIsMounted flag prevents setState warnings in the error handler. Implementation follows the correct pattern:

  • Initialized in constructor (line 38)
  • Set to true in componentDidMount (line 42)
  • Cleared in componentWillUnmount (lines 63-65)
  • Checked before setState in catch block (lines 117-125)

This is appropriate for the React 18 migration. Consistent with similar guards in payment-status.js and other components in this PR.

Also applies to: 42-42, 63-65, 117-125


182-186: Good accessibility improvement.

The aria-label attribute enhances screen reader support for the password change form.

client/components/mobile-phone-verification/mobile-phone-verification.js (1)

45-45: Mounted-flag pattern correctly implemented.

The componentIsMounted flag prevents setState warnings after unmount. Implementation is correct:

  • Initialized in constructor (line 45)
  • Set to true in componentDidMount (line 49)
  • Checked alongside isValid before setState (line 72)
  • Cleared in componentWillUnmount (lines 86-88)

The dual check isValid && this.componentIsMounted on line 72 is good defensive programming. Consistent with the pattern used in mobile-phone-change.js and other components in this PR.

Also applies to: 49-49, 72-72, 86-88

client/components/mobile-phone-change/mobile-phone-change.js (2)

38-38: Mounted-flag pattern correctly implemented.

The componentIsMounted flag prevents setState warnings after unmount. Implementation follows the correct pattern:

  • Initialized in constructor (line 38)
  • Set to true in componentDidMount (line 42)
  • Checked alongside isValid before setState (line 57)
  • Cleared in componentWillUnmount (lines 65-67)

Consistent with the pattern in mobile-phone-verification.js and other components.

Also applies to: 42-42, 57-57, 65-67


139-139: Good accessibility improvement.

The aria-label attribute provides screen reader support for the mobile phone change form.

client/components/404/404.test.js (1)

2-4: Clean migration to React Testing Library.

The test migration is well-executed:

  • Replaced Enzyme shallow with RTL render (lines 2-4)
  • Added MemoryRouter wrapper for routing context (line 4)
  • Created renderWithRouter helper for DRY testing (lines 21-22)
  • Updated snapshot assertions to use RTL container (lines 27-28, etc.)
  • Props are accessed directly instead of through wrapper instances (lines 51, 60)

This aligns with the broader RTL migration across the test suite and follows React Testing Library best practices.

Also applies to: 21-22, 27-28, 38-39, 44-45, 50-51, 59-60

client/components/logout/logout.test.js (1)

2-3: Comprehensive migration to React Testing Library.

The test migration demonstrates proper RTL patterns:

  • Replaced Enzyme with RTL imports (lines 2-3, 5)
  • Wrapped components with MemoryRouter for routing context (lines 39-44, 54-59, etc.)
  • Added cleanup hooks with jest.clearAllMocks() (lines 67, 71-73)
  • Used screen.getByRole for accessible queries (lines 83, 118)
  • Replaced wrapper interactions with fireEvent.click (lines 86, 119)
  • Updated assertions to DOM-based checks (line 84: toBeInTheDocument)

This migration is consistent with other test file updates in the PR (e.g., 404.test.js) and follows React Testing Library best practices.

Also applies to: 5-5, 39-44, 54-59, 67-73, 77-94, 98-105, 112-120

client/components/password-reset/password-reset.test.js (2)

232-238: Act warning workaround is fragile.

This pattern of allowing act(...) warnings by checking console output is a workaround for async state updates that occur after the test completes. While acceptable for this migration, the root cause should be addressed.

The act warnings typically indicate state updates happening after the component unmounts. Consider:

  1. Adding cleanup with unmount() in tests
  2. Using waitFor more comprehensively to await all state updates
  3. Investigating if the component needs an isMounted guard (which it appears to have based on the broader PR changes)

180-239: Well-structured multi-scenario test with proper async handling.

The test correctly chains multiple axios mock implementations and verifies the component's behavior through success and error scenarios using waitFor and fireEvent. The use of tick() combined with waitFor provides reliable async handling.

client/components/header/header.test.js (2)

194-210: Appropriate use of container queries for class-based assertions.

The eslint-disable comments for testing-library/no-container and testing-library/no-node-access are justified here. When testing component-specific CSS classes like .header-desktop-link and .header-desktop-language-btn, container queries are the pragmatic choice since RTL prioritizes accessibility-based queries that don't directly map to implementation-specific class names.


263-290: Good interaction test with proper state verification.

The sticky message test correctly:

  1. Renders with sticky HTML content
  2. Verifies the announcement is visible
  3. Simulates user interaction (click close button)
  4. Verifies the content is removed from DOM

This is a well-structured RTL interaction test.

client/components/payment-status/payment-status.test.js (1)

272-330: Comprehensive test for payment flow with proper cleanup.

The test correctly handles component lifecycle with view.unmount() before re-rendering with different props. The assertions properly verify the setUserData calls with expected payloads for both payment_requires_internet scenarios.

client/components/login/login.test.js (2)

675-720: Thorough token storage verification.

This test comprehensively verifies the authentication token storage behavior:

  • Correctly sets up axios mock before mounting
  • Verifies checkbox state
  • Checks both sessionStorage and localStorage after authentication
  • Validates no UI errors are shown

This is a well-structured integration test for the remember-me functionality.


976-1031: Good coverage of radius_realms captive portal form.

The test properly verifies:

  1. Hidden form is not present initially
  2. Hidden form appears with correct attributes when radius_realms is enabled
  3. Hidden input fields have correct names and values
  4. Form submission triggers the captive portal form submit

The mock of cpForm.submit is appropriate for testing form submission behavior without actually submitting.

client/components/mobile-phone-verification/mobile-phone-verification.test.js (3)

273-309: Complex but necessary axios mock for multi-call component lifecycle.

The call-count-based axios mock implementation is necessary here because componentDidMount makes multiple API calls (activePhoneToken followed by createPhoneToken). This approach correctly handles the 404 case for activePhoneToken (which should be silent) while allowing subsequent calls to succeed.

The comment on line 282-283 explaining the flow is helpful for future maintainers.


408-458: Well-structured verification flow test.

The test correctly:

  1. Sets up different axios responses for componentDidMount calls vs. the verification call
  2. Waits for the input to render before interacting
  3. Simulates user entering code and submitting
  4. Verifies setUserData is called with the expected user state (is_active, is_verified, mustLogin flags)

561-574: Good test for session-based token caching.

This test verifies that if owPhoneTokenSent is already in sessionStorage, the component doesn't make an unnecessary API call to create a new phone token. This tests an important optimization that prevents duplicate SMS sends.

client/components/payment-process/payment-process.test.js (1)

322-357: Good coverage of iframe height state updates.

This test properly verifies the setHeight postMessage handler updates the iframe's height attribute. The DOM assertion correctly expects the string "800" since React serializes numeric props to strings.

browser-test/utils.js (1)

35-36: LGTM!

The ESLint directive additions are appropriate for intentional console logging in error handlers. The enhanced error output (including page source) will help debug element location failures in browser tests.

Also applies to: 46-47

browser-test/create-mobile-configuration.js (1)

63-64: LGTM!

Appropriate ESLint directive for intentional error logging in the catch block.

client/app.js (1)

49-61: Correct React 18 migration with createRoot API.

The migration from ReactDOM.render to createRoot follows the React 18 upgrade path correctly. The HelmetProvider wrapping at the root level properly supports server-side rendering with react-helmet-async.

@PRoIISHAAN PRoIISHAAN force-pushed the fix-reactversion-migration branch from a418cbf to 67474cb Compare December 31, 2025 17:51
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
client/components/modal/modal.test.js (1)

206-232: Incorrect event type: component listens to keyup, not keydown.

The Modal component registers a keyup event listener (see modal.js line 14: document.addEventListener("keyup", this.handleKeyDown, false)), but this test fires a keyDown event on line 228. This means the test doesn't actually verify the handler behavior.

🔎 Suggested fix
   it("should not navigate on non-Esc key press", async () => {
     axios.mockImplementationOnce(() =>
       Promise.resolve({
         status: 200,
         data: {
           __html: "Modal Content",
         },
       }),
     );
     props = createTestProps();

     render(
       <MemoryRouter>
         <Modal {...props} />
       </MemoryRouter>,
     );

     await waitFor(() => {
       expect(screen.getByText(/modal content/i)).toBeInTheDocument();
     });

     // Simulate non-Esc key press
-    fireEvent.keyDown(document, {keyCode: 1});
+    fireEvent.keyUp(document, {keyCode: 1});

     // Should not navigate
     expect(props.navigate).not.toHaveBeenCalled();
   });
♻️ Duplicate comments (11)
client/components/mobile-phone-change/mobile-phone-change.test.js (1)

404-412: Conditional assertion may mask test failures.

The if (cancelButton) check means this test will silently pass even if the cancel button is not rendered. Since the mock configuration sets cancel: true (line 33), the cancel button should always be present, and the test should fail when it's missing.

🔎 Proposed fix
   const cancelButton = screen.queryByRole("link", {name: /cancel/i});
-
-  if (cancelButton) {
-    fireEvent.click(cancelButton);
-  }
+  expect(cancelButton).toBeInTheDocument();
+  fireEvent.click(cancelButton);

   expect(toast.info).not.toHaveBeenCalled();
   expect(consoleErrorSpy).not.toHaveBeenCalled();
client/components/payment-process/payment-process.test.js (1)

76-81: LoadingContext.Provider still missing from test helper.

As previously noted, PaymentProcess calls this.context.setLoading() but renderWithProviders doesn't include LoadingContext.Provider, which means loading behavior isn't tested. Update the helper to wrap with <LoadingContext.Provider value={{setLoading: jest.fn(), isLoading: false}}> so loading state changes can be verified.

client/components/registration/registration-subscriptions.test.js (2)

601-607: Test assertion doesn't verify the actual payload.

The test claims to verify phone number as username but only asserts JSON.stringify was called. This was already flagged in previous reviews.


663-669: Same issue: Test doesn't verify the username assertion.

As noted in previous reviews, this test only checks JSON.stringify was called but never verifies the actual username value in the payload.

client/components/password-change/password-change.test.js (1)

358-373: Incorrect rerender usage with provider wrapper.

This issue was previously flagged and remains unfixed. The rerender function from RTL should receive only the component, not the full provider tree. Calling rerender with <Provider> and <MemoryRouter> creates nested providers.

Recommended fix: Either use the returned rerender with just the component (rerender(<PasswordChange {...props} />)), or split this into two separate test cases where each calls renderWithProviders fresh.

Suggested fix (Option 1: Use rerender correctly)
  // Test with social_login method
  props.userData.method = "social_login";
- const {rerender} = renderWithProviders(<PasswordChange {...props} />);
-
- rerender(
-   <Provider store={createMockStore()}>
-     <MemoryRouter>
-       <PasswordChange {...props} />
-     </MemoryRouter>
-   </Provider>,
- );
+ const {rerender} = renderWithProviders(<PasswordChange {...props} />);
+ rerender(<PasswordChange {...props} />);

  // Verify redirect behavior for social login
  formElement = screen.queryByRole("form");
  expect(formElement).not.toBeInTheDocument();
Suggested fix (Option 2: Split into separate tests)
+ it("should redirect to status if login method is SAML", async () => {
+   props = createTestProps();
+   props.userData.method = "saml";
+
+   renderWithProviders(<PasswordChange {...props} />);
+
+   const formElement = screen.queryByRole("form");
+   expect(formElement).not.toBeInTheDocument();
+ });
+
+ it("should redirect to status if login method is Social Login", async () => {
+   props = createTestProps();
+   props.userData.method = "social_login";
+
+   renderWithProviders(<PasswordChange {...props} />);
+
+   const formElement = screen.queryByRole("form");
+   expect(formElement).not.toBeInTheDocument();
+ });
babel.config.js (1)

15-19: Complete the plugin naming migration for consistency.

Line 18 correctly updates to @babel/plugin-transform-private-methods, but lines 15 and 19 still use the older proposal naming convention. For consistency and to use the maintained versions, also update:

  • Line 15: @babel/plugin-proposal-class-properties@babel/plugin-transform-class-properties
  • Line 19: @babel/plugin-proposal-private-property-in-object@babel/plugin-transform-private-property-in-object
client/components/modal/modal.test.js (1)

286-309: Misleading test name doesn't match assertions.

The test is named "should close modal on backdrop click" but doesn't test backdrop click functionality—it only verifies modal rendering. Either rename to reflect actual behavior (e.g., "should render modal with backdrop") or remove if redundant.

client/components/registration/registration.test.js (1)

784-789: Test assertion doesn't match description.

The test "should set country when selectedCountry is executed" only verifies that the form exists, but doesn't actually test the country selection behavior. Consider removing this test or implementing proper country selection testing.

client/components/organization-wrapper/organization-wrapper.test.js (3)

250-250: Typo in variable name: lastConsoleOutuputlastConsoleOutput.


322-336: Rerender missing required providers.

The rerender on lines 329-333 only wraps the component with MemoryRouter, but the initial render used HelmetProvider, Provider, and MemoryRouter. This inconsistency may cause the component to fail to render properly or produce misleading test results.

Additionally, line 323 creates a duplicate console.error spy when one already exists from the beforeEach block.


675-684: Incomplete test: missing 404 behavior assertion.

The test verifies that .app-container renders, but the comment on lines 683-684 indicates it should verify 404 page or redirect behavior. Consider adding an assertion for the ConnectedDoesNotExist component or specific 404 content.

🧹 Nitpick comments (20)
client/components/mobile-phone-change/mobile-phone-change.test.js (1)

218-223: Remove duplicate phone number input assertion.

The phone number textbox is queried and asserted twice (lines 218-219 and 222-223). The second check is redundant.

🔎 Proposed fix
   expect(
     screen.getByRole("textbox", {name: /mobile phone number/i}),
   ).toBeInTheDocument();
   expect(screen.getByRole("form")).toBeInTheDocument();
-  expect(
-    screen.getByRole("textbox", {name: /mobile phone number/i}),
-  ).toBeInTheDocument();
   expect(
     screen.getByRole("button", {name: /change your phone number/i}),
   ).toBeInTheDocument();
client/components/mobile-phone-verification/mobile-phone-verification.test.js (6)

113-120: Simplify the mock return value.

The mock returns both data: {active: false} and a top-level active: false property. This redundancy could be confusing.

🔎 Suggested simplification
     axios.mockImplementation(() =>
       Promise.resolve({
         status: 200,
         statusText: "OK",
         data: {active: false},
-        active: false,
       }),
     );

159-176: Remove redundant mock re-setup in afterEach.

The getConfig mock is already defined at the module level (lines 21-40). After jest.restoreAllMocks() completes, the module-level mock is still active. This manual re-implementation in afterEach is redundant and adds maintenance burden.

🔎 Suggested fix
   afterEach(() => {
     axios.mockReset();
     jest.clearAllMocks();
     jest.restoreAllMocks();
-    // Re-setup the getConfig mock after clearing
-    getConfig.mockImplementation(() => ({
-      slug: "default",
-      name: "default name",
-      components: {
-        mobile_phone_verification_form: {
-          input_fields: {
-            code: {
-              type: "text",
-              pattern: "^[0-9]{6}$",
-            },
-          },
-        },
-      },
-      settings: {
-        mobile_phone_verification: true,
-      },
-    }));
     sessionStorage.clear();
   });

278-299: Consider a more maintainable axios mock pattern.

Using a call counter to differentiate between sequential axios calls couples the test tightly to the implementation's call order. If the component's API call sequence changes, this test will break even if the behavior is correct.

💡 Alternative approach

Consider mocking axios by URL or using a library like msw (Mock Service Worker) to define endpoint-specific behavior rather than relying on call order:

axios.get = jest.fn().mockResolvedValueOnce({
  status: 404,
  // ... activePhoneToken response
});

axios.post = jest.fn().mockResolvedValueOnce({
  status: 201,
  // ... createPhoneToken response
});

This makes the test more resilient to refactoring.


413-430: Same counter pattern - consider the earlier refactoring suggestion.

This test uses the same fragile call-order-dependent pattern mentioned in the previous comment (lines 278-299). The same concerns about maintenance and brittleness apply here.


466-486: Same counter pattern - consider the earlier refactoring suggestion.

This test also uses the fragile call-order-dependent counter pattern. Consider the alternative approach suggested in the earlier comment (lines 278-299).


623-641: Remove redundant mock re-setup (same issue as earlier).

This afterEach block has the same redundant getConfig mock re-implementation as the one in the previous test suite (lines 159-176). The module-level mock remains active after jest.restoreAllMocks().

🔎 Suggested fix
   afterEach(() => {
     jest.clearAllMocks();
     jest.restoreAllMocks();
-    // Re-setup the getConfig mock after clearing
-    getConfig.mockImplementation(() => ({
-      slug: "default",
-      name: "default name",
-      components: {
-        mobile_phone_verification_form: {
-          input_fields: {
-            code: {
-              type: "text",
-              pattern: "^[0-9]{6}$",
-            },
-          },
-        },
-      },
-      settings: {
-        mobile_phone_verification: true,
-      },
-    }));
     sessionStorage.clear();
   });
client/components/password-reset/password-reset.test.js (3)

99-106: Optional: Redundant jest.clearAllMocks() calls.

Calling jest.clearAllMocks() in both beforeEach (line 99) and afterEach (line 105) is redundant. Calling it once in beforeEach is typically sufficient to ensure clean test state.

🔎 Suggested simplification
  beforeEach(() => {
    jest.clearAllMocks();
    props = createTestProps();
    loadTranslation("en", "default");
  });

  afterEach(() => {
-   jest.clearAllMocks();
  });

153-165: Optional: Redundant jest.clearAllMocks() calls.

Similar to the previous test suite, jest.clearAllMocks() is called in both beforeEach (line 153) and afterEach (line 163). One call in beforeEach is sufficient.

🔎 Suggested simplification
  beforeEach(() => {
    jest.clearAllMocks();
    axios.mockReset();
    lastConsoleOutuput = null;
    jest.spyOn(global.console, "error").mockImplementation((data) => {
      lastConsoleOutuput = data;
    });
    props = createTestProps();
  });

  afterEach(() => {
-   jest.clearAllMocks();
    jest.restoreAllMocks();
  });

203-208: Optional: Evaluate necessity of tick() before waitFor().

The pattern of calling tick() immediately before waitFor() appears in multiple tests (lines 203, 215, 226, 265, 289). Since RTL's waitFor() already handles async waiting with automatic retries, the tick() call might be unnecessary. Consider testing whether waitFor() alone is sufficient, as this could simplify the test code.

Example simplified pattern
- fireEvent.submit(form);
- await tick();
- 
- await waitFor(() => {
+ fireEvent.submit(form);
+ await waitFor(() => {
    expect(emailInput).toHaveClass("error");
    expect(spyToastError).toHaveBeenCalledTimes(1);
  });
client/components/payment-process/payment-process.test.js (4)

127-159: Optional: Strengthen redirect assertions.

Both tests claim to verify redirect behavior but only assert that validateToken was called. Consider adding assertions on props.navigate to verify the component actually redirects unauthenticated users or users without payment_url.


161-173: Optional: Verify loader is actually displayed.

The test description claims to verify the loader is shown when token is invalid, but only asserts that validateToken was called. Once LoadingContext is added (as per previous comment), add an assertion to verify the loader is visible.


287-320: Optional: Verify loader state change.

The test simulates a showLoader message but only verifies that the event listener was registered. Once LoadingContext is properly provided, consider adding an assertion to verify that the loading state actually changes in response to the message.


199-357: Optional: Extract event listener mocking pattern.

The pattern of mocking addEventListener, storing callbacks in an events object, and restoring the original function is repeated across four tests. Consider extracting this into a test helper to reduce duplication.

Example helper
const mockMessageEvents = () => {
  const events = {};
  const originalAddEventListener = window.addEventListener;
  const originalRemoveEventListener = window.removeEventListener;
  
  window.addEventListener = jest.fn((event, callback) => {
    events[event] = callback;
  });
  window.removeEventListener = jest.fn((event) => {
    delete events[event];
  });
  
  return {
    events,
    restore: () => {
      window.addEventListener = originalAddEventListener;
      window.removeEventListener = originalRemoveEventListener;
    }
  };
};
client/components/registration/registration-subscriptions.test.js (1)

468-505: Prefer RTL queries over querySelector; conditional guards may hide test failures.

Using form.querySelector bypasses RTL's accessibility-focused queries. The if guards are risky—if an input doesn't exist, the test silently passes without filling required fields, potentially masking regressions.

Consider using getByRole or getByLabelText which will fail fast if elements are missing:

🔎 Proposed approach
// Instead of:
const emailInput = form.querySelector("input[name='email']");
if (emailInput) {
  fireEvent.change(emailInput, {...});
}

// Prefer:
const emailInput = screen.getByRole("textbox", {name: /email/i});
fireEvent.change(emailInput, {target: {name: "email", value: "[email protected]"}});

If elements are conditionally rendered, use findByRole with appropriate waits. If the form inputs lack accessible labels, adding aria-label or <label> associations to the component would improve both testability and accessibility.

client/components/password-confirm/password-confirm.test.js (2)

233-321: Consider splitting this mega-test and review the act() workaround.

This test covers five distinct scenarios (password mismatch + 3 API errors + success) in a single test case, which makes it harder to debug failures and violates the single-responsibility principle for tests.

Additionally, lines 315-319 implement a workaround that explicitly allows act(...) warnings by checking if console output contains that string. While this may be acceptable as a temporary measure during React 18 migration, it could hide legitimate issues with unhandled async state updates.

💡 Recommendation

Option 1 (Recommended): Split into separate test cases:

  • it("should show validation error for password mismatch")
  • it("should handle API error with detail field")
  • it("should handle API error with non_field_errors")
  • it("should handle API error with token field")
  • it("should show success message after successful submission")

Option 2 (If keeping combined): Remove the act() warning workaround and properly wrap async operations, or add a TODO comment explaining why this workaround is temporarily necessary.


331-362: Consider more robust toggle button selection and assertion.

The current approach has two fragility points:

  1. Lines 345-349: Finding toggle buttons by filtering all buttons with role="button" is indirect and could break if the component structure changes.
  2. Lines 357-361: The assertion only checks that at least one input changed to type "text", which is a weak guarantee of correct toggle behavior.
💡 Suggestions

For button selection: Consider adding a data-testid attribute to the PasswordToggleIcon component to make selection more explicit:

const toggleButton = screen.getByTestId('password-toggle');

For assertion: Verify the actual expected behavior after toggle:

await waitFor(() => {
  expect(passwordInput).toHaveAttribute("type", "text");
  expect(confirmInput).toHaveAttribute("type", "text");
});
client/components/payment-status/payment-status.test.js (3)

126-143: Consider simplifying the afterEach pattern.

The current pattern calls restoreAllMocks() which removes all mock implementations, then re-implements the getConfig mock. This works but is complex.

Since restoreAllMocks() is likely only needed for the console mocks, consider using console.log.mockRestore() and console.error.mockRestore() instead to avoid needing to re-implement the getConfig mock.

🔎 Simplified approach
  afterEach(() => {
    jest.clearAllMocks();
-   jest.restoreAllMocks();
-   // Re-setup the getConfig mock after clearing
-   getConfig.mockImplementation(() => ({
-     components: {
-       payment_status_page: {
-         content: {
-           en: {
-             pending: "Payment pending",
-             success: "Payment successful",
-             failed: "Payment failed",
-           },
-         },
-       },
-     },
-   }));
+   // Only restore console mocks
+   global.console.log.mockRestore?.();
+   global.console.error.mockRestore?.();
  });

150-150: Use mockResolvedValue for async function mocks.

validateToken is an async function returning Promise<boolean>, but the tests use mockReturnValue(true/false). While this works (await on a plain value resolves immediately), it's more explicit and correct to use mockResolvedValue for async functions.

🔎 Recommended change

Replace all instances of:

-validateToken.mockReturnValue(true);
+validateToken.mockResolvedValue(true);

and:

-validateToken.mockReturnValue(false);
+validateToken.mockResolvedValue(false);

This makes it clear that the mock returns a Promise and follows Jest best practices for mocking async functions.

Also applies to: 180-180, 208-208, 227-227, 253-253, 273-273, 338-338, 360-360, 382-382, 404-404, 425-425, 443-443, 461-461, 479-479, 497-497, 523-523


272-330: Consider splitting this into two separate tests.

This test covers two distinct scenarios (payment_requires_internet true vs false) in a single test function, requiring an unmount() call in the middle. Splitting into two tests would improve readability and make failures easier to debug.

Suggested structure
it("should NOT set proceedToPayment when payment_requires_internet is false", async () => {
  // Test first scenario only
});

it("should set proceedToPayment when payment_requires_internet is true", async () => {
  // Test second scenario only
});
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a418cbf and e89997c.

⛔ Files ignored due to path filters (19)
  • client/components/404/__snapshots__/404.test.js.snap is excluded by !**/*.snap
  • client/components/contact-box/__snapshots__/contact.test.js.snap is excluded by !**/*.snap
  • client/components/footer/__snapshots__/footer.test.js.snap is excluded by !**/*.snap
  • client/components/header/__snapshots__/header.test.js.snap is excluded by !**/*.snap
  • client/components/login/__snapshots__/login.test.js.snap is excluded by !**/*.snap
  • client/components/logout/__snapshots__/logout.test.js.snap is excluded by !**/*.snap
  • client/components/mobile-phone-change/__snapshots__/mobile-phone-change.test.js.snap is excluded by !**/*.snap
  • client/components/mobile-phone-verification/__snapshots__/mobile-phone-verification.test.js.snap is excluded by !**/*.snap
  • client/components/modal/__snapshots__/modal.test.js.snap is excluded by !**/*.snap
  • client/components/organization-wrapper/__snapshots__/organization-wrapper.test.js.snap is excluded by !**/*.snap
  • client/components/password-change/__snapshots__/password-change.test.js.snap is excluded by !**/*.snap
  • client/components/password-confirm/__snapshots__/password-confirm.test.js.snap is excluded by !**/*.snap
  • client/components/password-reset/__snapshots__/password-reset.test.js.snap is excluded by !**/*.snap
  • client/components/payment-process/__snapshots__/payment-process.test.js.snap is excluded by !**/*.snap
  • client/components/payment-status/__snapshots__/payment-status.test.js.snap is excluded by !**/*.snap
  • client/components/registration/__snapshots__/registration.test.js.snap is excluded by !**/*.snap
  • client/components/status/__snapshots__/status.test.js.snap is excluded by !**/*.snap
  • package-lock.json is excluded by !**/package-lock.json
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (55)
  • .eslintrc.json
  • .github/workflows/ci.yml
  • babel.config.js
  • browser-test/create-mobile-configuration.js
  • browser-test/utils.js
  • client/app.js
  • client/components/404/404.test.js
  • client/components/contact-box/contact.test.js
  • client/components/footer/footer.test.js
  • client/components/header/header.test.js
  • client/components/login/login.test.js
  • client/components/logout/logout.test.js
  • client/components/mobile-phone-change/mobile-phone-change.js
  • client/components/mobile-phone-change/mobile-phone-change.test.js
  • client/components/mobile-phone-verification/mobile-phone-verification.js
  • client/components/mobile-phone-verification/mobile-phone-verification.test.js
  • client/components/modal/modal.test.js
  • client/components/organization-wrapper/organization-wrapper.js
  • client/components/organization-wrapper/organization-wrapper.test.js
  • client/components/password-change/password-change.js
  • client/components/password-change/password-change.test.js
  • client/components/password-confirm/password-confirm.js
  • client/components/password-confirm/password-confirm.test.js
  • client/components/password-reset/password-reset.js
  • client/components/password-reset/password-reset.test.js
  • client/components/payment-process/payment-process.test.js
  • client/components/payment-status/payment-status.js
  • client/components/payment-status/payment-status.test.js
  • client/components/registration/registration-subscriptions.test.js
  • client/components/registration/registration.js
  • client/components/registration/registration.test.js
  • client/components/registration/subscriptions.test.js
  • client/components/registration/test-utils.js
  • client/components/status/status.js
  • client/components/status/status.test.js
  • client/components/status/status.test.js.enzyme-backup
  • client/components/status/status.test.js.rtl-backup
  • client/utils/__mocks__/get-config.js
  • client/utils/get-config.js
  • client/utils/get-plan-selection.js
  • client/utils/load-translation.js
  • client/utils/load-translation.test.js
  • client/utils/log-error.js
  • client/utils/needs-verify.js
  • client/utils/tick.js
  • client/utils/utils.test.js
  • client/utils/with-route-props.js
  • config/__tests__/add-org.test.js
  • config/add-org.js
  • config/jest.config.js
  • config/setupTests.js
  • config/webpack.config.js
  • package.json
  • server/app.js
  • server/index.js
🚧 Files skipped from review as they are similar to previous changes (11)
  • client/components/payment-status/payment-status.js
  • client/components/contact-box/contact.test.js
  • client/components/mobile-phone-verification/mobile-phone-verification.js
  • client/app.js
  • client/components/password-reset/password-reset.js
  • client/components/mobile-phone-change/mobile-phone-change.js
  • client/components/registration/registration.js
  • client/components/password-change/password-change.js
  • client/components/password-confirm/password-confirm.js
  • browser-test/create-mobile-configuration.js
  • .eslintrc.json
🧰 Additional context used
🧬 Code graph analysis (10)
client/components/password-change/password-change.test.js (1)
client/components/password-change/password-change.js (1)
  • PasswordChange (22-251)
client/components/404/404.test.js (1)
client/components/404/404.js (1)
  • DoesNotExist (9-47)
client/components/organization-wrapper/organization-wrapper.test.js (3)
client/utils/__mocks__/get-config.js (1)
  • getConfig (11-16)
client/components/organization-wrapper/organization-wrapper.js (1)
  • OrganizationWrapper (38-371)
client/utils/__mocks__/load-translation.js (1)
  • loadTranslation (17-27)
client/components/header/header.test.js (4)
client/components/header/header.js (1)
  • Header (13-268)
client/utils/__mocks__/load-translation.js (1)
  • loadTranslation (17-27)
client/utils/check-internal-links.js (1)
  • isInternalLink (1-4)
client/components/header/index.js (2)
  • mapDispatchToProps (15-19)
  • mapDispatchToProps (15-19)
client/components/registration/registration-subscriptions.test.js (4)
client/utils/__mocks__/get-config.js (1)
  • getConfig (11-16)
client/components/registration/test-utils.js (2)
  • mountComponent (8-38)
  • mockedStore (12-27)
client/utils/tick.js (1)
  • tick (1-5)
client/utils/redirect-to-payment.js (1)
  • redirectToPayment (3-4)
client/components/payment-status/payment-status.test.js (4)
client/components/payment-status/payment-status.js (1)
  • PaymentStatus (13-227)
client/utils/validate-token.js (1)
  • validateToken (11-87)
client/utils/__mocks__/get-config.js (1)
  • getConfig (11-16)
client/utils/get-config.js (1)
  • getConfig (3-13)
client/components/modal/modal.test.js (3)
client/components/modal/modal.js (1)
  • Modal (14-97)
client/utils/get-text.js (1)
  • getText (1-5)
client/utils/log-error.js (1)
  • logError (1-15)
client/components/login/login.test.js (2)
client/components/login/login.js (1)
  • Login (36-506)
client/utils/__mocks__/get-parameter-by-name.js (1)
  • getParameterByName (1-1)
client/components/logout/logout.test.js (1)
client/components/logout/logout.js (1)
  • Logout (16-62)
client/components/password-confirm/password-confirm.test.js (2)
client/components/password-confirm/password-confirm.js (1)
  • PasswordConfirm (20-231)
client/utils/tick.js (1)
  • tick (1-5)
🪛 Gitleaks (8.30.0)
client/components/registration/registration.test.js

[high] 153-153: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 154-154: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Tests and Coverage
🔇 Additional comments (50)
browser-test/utils.js (1)

35-36: LGTM! Appropriate ESLint suppressions for test debugging.

The ESLint disable comments are appropriate for console logging in test utilities. The error logging with CSS selector and page source provides valuable debugging context for Selenium test failures.

Also applies to: 46-47

.github/workflows/ci.yml (1)

81-81: No action needed. The "expect" package is necessary for the test suite.

The expect utility is actively used in config/__tests__/add-org.test.js to automate interactive terminal prompts during configuration testing. The test "creates config via interactive prompts using expect for TTY" uses an expect script to provide a pseudo-TTY environment for the inquirer.js prompts, enabling automated testing of the interactive configuration workflow. This is a legitimate and justified system dependency.

client/components/mobile-phone-verification/mobile-phone-verification.test.js (2)

20-46: LGTM! Clean mock setup.

The module-level mocks are properly configured before the component import, and the default configuration structure is comprehensive.


47-97: LGTM! Well-structured test utilities.

The helper functions properly wrap components with necessary providers (Redux store and Router) for full integration testing.

client/components/password-reset/password-reset.test.js (3)

10-32: LGTM: Correct mock setup pattern.

The mocks are correctly placed before component imports, which is essential for Jest to properly intercept module loading.


80-85: LGTM: Clean provider wrapper pattern.

The renderWithProviders helper is a clean abstraction for wrapping components with necessary context providers.


232-237: Review act() warning handling approach.

The test explicitly allows act() warnings by checking console output. While this prevents test failures, act() warnings typically indicate real timing issues where React state updates occur outside the testing framework's awareness. Consider investigating and addressing the root cause rather than suppressing the warnings, as this could lead to flaky tests or mask actual bugs in the component.

If the component performs state updates after unmounting or has unresolved promises, consider adding cleanup logic or ensuring all async operations complete before the test ends.

client/components/payment-process/payment-process.test.js (5)

1-32: LGTM: Mock setup follows best practices.

The mock-before-import pattern is correctly implemented, and all necessary modules are properly mocked before the actual imports.


103-125: LGTM: Test lifecycle hooks are properly configured.

The beforeEach/afterEach setup correctly manages mock state and console output between tests.


175-246: LGTM: Iframe rendering and event listener tests are well-structured.

These tests properly verify iframe attributes and the postMessage event listener lifecycle.


248-285: LGTM: Payment completion and height adjustment tests verify expected behavior.

Test verifies navigation on payment completion and iframe height updates correctly.

Also applies to: 322-357


359-403: LGTM: Redirect and token validation tests are comprehensive.

Both tests properly verify the redirect flow when payment_iframe is false and token validation with correct parameters.

client/components/registration/registration-subscriptions.test.js (1)

672-696: LGTM!

The rerender approach to test loading state transitions is appropriate, and the assertion verifies the expected axios call behavior.

client/components/password-confirm/password-confirm.test.js (4)

1-12: LGTM! Proper RTL imports.

The imports correctly transition from Enzyme to React Testing Library, including necessary utilities like fireEvent, waitFor, and the tick helper for async handling.


14-50: LGTM! Comprehensive mock configuration.

The mock configuration provides all necessary context for isolated testing, including form input patterns, component configs, and language settings.


84-111: LGTM! Well-structured test helper.

The renderWithProviders helper properly wraps the component with necessary providers (Redux + Router) for isolated testing, following RTL best practices.


389-416: LGTM! Clean error-clearing test.

This test properly verifies that error states are cleared after a successful submission, using appropriate async handling with tick() and waitFor().

client/components/password-change/password-change.test.js (5)

1-44: Good mock setup following RTL best practices.

The mock-before-import pattern is correctly implemented, and the structure is clear. The prefer-promise-reject-errors disable on line 2 is necessary for axios mock rejections with plain objects, which is acceptable in test contexts.


48-95: Well-structured test utilities.

The helper functions follow RTL best practices:

  • createTestProps provides sensible defaults with override capability
  • createMockStore supplies a minimal but sufficient Redux store mock for these tests
  • renderWithProviders correctly wraps components with both Redux and routing context

97-126: Rendering tests look good.

The tests correctly verify component rendering with snapshots and explicit DOM queries. The password expiration test properly uses queryByText with not.toBeInTheDocument() to verify the cancel button's absence.


128-256: Interaction tests are well-structured.

The handleSubmit test correctly covers multiple validation paths:

  • Local validation errors (tests 1-2) that short-circuit before API calls
  • Server error handling (test 3) using the first mocked rejection
  • Successful submission (test 4) using the second mocked resolution

The axios mock chaining aligns properly with the test scenarios, and waitFor is used appropriately for async assertions.


258-331: Thorough testing of field attributes and toggle functionality.

The tests verify essential input attributes (type, id, placeholder, autoComplete, required) and correctly test the password visibility toggle by filtering buttons and waiting for type changes. The approach is defensive and ensures the HTML structure meets accessibility and UX requirements.

client/components/footer/footer.test.js (1)

1-131: LGTM: Clean Enzyme → RTL migration.

The migration to React Testing Library is well-executed:

  • Proper use of render, screen, and jest-dom assertions
  • Container-based snapshots replace Enzyme wrappers
  • DOM queries (getByText, queryByText, queryByRole) correctly verify presence/absence of links
client/components/404/404.test.js (2)

21-22: Good practice: renderWithRouter helper reduces duplication.

The renderWithRouter helper cleanly wraps components with MemoryRouter, providing routing context without repetition across tests.


1-63: LGTM: Successful RTL migration.

The test migration is correct:

  • RTL rendering with MemoryRouter context
  • Container-based snapshots
  • Proper mock access via props.setTitle.mock
client/components/logout/logout.test.js (2)

67-73: Good practice: Proper test lifecycle management.

The beforeEach/afterEach hooks with jest.clearAllMocks() ensure clean test isolation.


75-94: LGTM: Correct interaction testing with RTL.

The test properly:

  • Uses screen.getByRole for accessible queries
  • Simulates user interaction with fireEvent.click
  • Verifies the mustLogin: true payload, which is essential for captive portal login flows
client/components/modal/modal.test.js (1)

36-43: Good practice: Comprehensive test lifecycle management.

The setup/teardown properly:

  • Clears mocks between tests
  • Resets axios
  • Stores and restores event listener methods

This ensures test isolation and prevents cross-test pollution.

Also applies to: 162-175

client/components/header/header.test.js (3)

6-30: Good practice: Hoisting mocks before imports.

Moving jest.mock calls before imports with the /* eslint-disable import/first */ pattern ensures mocks are active when modules load. This is the recommended approach for module mocking.


194-228: Acceptable use of container queries for class-based selectors.

While Testing Library prefers role/text queries, using container.querySelector for specific CSS classes (like .header-desktop-language-btn) is reasonable when testing implementation details like class names and active states. The eslint-disable comments acknowledge this tradeoff.


304-329: Good pattern: Using rerender to test state variations.

The test efficiently verifies link visibility across different userData.method values (saml, social_login, mobile_phone) using RTL's rerender, avoiding redundant component mounting.

client/components/login/login.test.js (7)

16-94: Good practice: Comprehensive pre-import mock configuration.

The extensive mock setup before imports ensures consistent test behavior. The mockConfig object provides complete configuration coverage for login form tests.


148-174: Useful helper: renderWithProvider simplifies test setup.

The helper function cleanly wraps components with both Redux Provider and MemoryRouter, reducing boilerplate across tests while maintaining proper context.


222-269: Excellent: Proper lazy loading verification.

Both tests correctly handle React 18 Suspense behavior:

  • Line 228: findByPlaceholderText waits for lazy-loaded PhoneInput
  • Lines 243-269: Verifies fallback input renders immediately while PhoneInput loads
  • Uses waitFor appropriately for async rendering

374-450: Well-structured: Sequential error scenario testing.

The test methodically verifies multiple error paths:

  1. Field validation errors (username/password)
  2. 500 server error
  3. 504 timeout error
  4. Success case

Each uses waitFor properly and validates both DOM state and mock calls.


675-720: LGTM: Correct token storage verification.

The test properly verifies the authentication flow:

  • Token goes to sessionStorage when remember_me is unchecked
  • rememberMe preference stored in localStorage
  • No race conditions due to proper waitFor usage

862-906: Correct: Sesame token (passwordless auth) flow verification.

The test properly validates automatic authentication when a sesame token is in the URL:

  • Verifies getParameterByName is called for "sesame"
  • Confirms automatic authenticate and setUserData calls
  • Uses waitFor for async authentication

976-1031: Thorough: Captive portal form integration test.

The test comprehensively verifies radius_realms behavior:

  • Checks hidden captive portal form structure and attributes
  • Validates hidden input fields (username, password, zone)
  • Verifies form submission flow
  • Uses DOM queries appropriately for hidden form elements
client/components/organization-wrapper/organization-wrapper.js (1)

9-9: HelmetProvider is correctly configured in the application root.

Verification confirms that HelmetProvider from react-helmet-async is imported in client/app.js (line 5) and properly wraps the entire component tree at the root level (lines 54-60), with all child providers and the App component nested within it. The setup is correct.

client/components/registration/registration.test.js (4)

1-92: LGTM: Well-structured test setup.

The test file correctly migrates to React Testing Library with proper mock setup before imports. The comprehensive mockConfig provides a realistic test environment for the Registration component.


94-183: LGTM: Well-designed test helpers.

The helper functions (createTestProps, createMockStore, renderWithProviders, mountComponent) provide proper context wrapping with Redux Provider and React Router, ensuring components render in a realistic environment.


234-254: LGTM: Proper RTL interaction patterns.

The test correctly uses semantic queries (getByRole, getByLabelText) and fireEvent to simulate user input, following React Testing Library best practices.


256-385: LGTM: Comprehensive form submission test.

This test thoroughly exercises the form submission flow through multiple scenarios (password mismatch, API errors, server errors, success, billing errors) with proper async handling using waitFor and tick.

client/components/organization-wrapper/organization-wrapper.test.js (4)

1-46: LGTM: Proper test setup with react-helmet-async.

The test file correctly imports HelmetProvider from react-helmet-async (line 8) and defines mocks before importing the component under test, following best practices for Jest and React 18 compatibility.


94-132: LGTM: Comprehensive test helper with proper context.

The renderWithRouter helper correctly wraps the component with HelmetProvider, Redux Provider, and MemoryRouter in the proper order, ensuring components render with full app context for realistic testing.


142-154: LGTM: Proper loading state test.

The test correctly verifies the loading state by checking that only the loader is present while the main content and error states are not rendered.


134-699: LGTM: Comprehensive test coverage with proper RTL patterns.

The test suite thoroughly covers the OrganizationWrapper component with tests for rendering states, lifecycle behavior, authentication flows, and routing. Most tests follow React Testing Library best practices with proper async handling and context wrapping.

client/components/payment-status/payment-status.test.js (3)

1-36: Good mock setup and RTL imports.

The mock setup before imports is correct for Jest module mocks, and all necessary RTL utilities are properly imported. The inline getConfig mock with payment_status_page content is well-structured for these tests.


38-83: Excellent test harness setup.

The renderWithProviders helper with Redux Provider and MemoryRouter is a clean, reusable pattern for testing components that depend on both Redux and routing. The mock store structure with organization configuration is appropriate for these tests.


145-200: Excellent RTL migration with proper async handling.

The tests correctly use:

  • RTL queries (screen.getByText, screen.getByRole with accessible names)
  • waitFor for React 18's async rendering
  • fireEvent for user interactions
  • jest-dom matchers (toBeInTheDocument, toHaveAttribute)

The async handling with tick() and waitFor properly accommodates React 18's rendering behavior.

Also applies to: 221-244

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: 6

♻️ Duplicate comments (3)
client/components/registration/registration.test.js (2)

212-212: Typo: lastConsoleOutuput should be lastConsoleOutput.

This typo appears throughout the file and was previously flagged.


784-794: Test doesn't verify country selection behavior.

The test title implies testing selectedCountry execution, but it only asserts DOM structure. This was previously flagged.

client/components/organization-wrapper/organization-wrapper.test.js (1)

250-250: Typo in variable name: lastConsoleOutuputlastConsoleOutput.

This variable name has a typo ("Outuput" instead of "Output"). Please update the declaration here and all usages at lines 255, 259, and 349.

🔎 Proposed fix
-  let lastConsoleOutuput;
+  let lastConsoleOutput;

Also update lines 255, 259, and 349 to use the corrected identifier.

🧹 Nitpick comments (26)
client/components/registration/registration.test.js (4)

85-88: Shared mock object may cause test pollution.

The getConfig mock returns the same mockConfig object reference. Tests that modify props.registration.input_fields (lines 397-398, 412-415) mutate this shared object, potentially affecting subsequent tests.

🔎 Proposed fix: return a deep copy
 jest.mock("../../utils/get-config", () => ({
   __esModule: true,
-  default: jest.fn(() => mockConfig),
+  default: jest.fn(() => JSON.parse(JSON.stringify(mockConfig))),
 }));

Alternatively, reset or clone mockConfig in beforeEach for affected test suites.


119-143: Consolidate duplicate mock store creation logic.

createMockStore (lines 119-143) and the inline store in mountComponent (lines 159-174) duplicate the same pattern. Consider reusing createMockStore with parameters.

🔎 Proposed refactor
-const mountComponent = (passedProps) => {
-  const config = getConfig(passedProps.orgSlug || "test-org-2");
-  const mockedStore = {
-    subscribe: () => {},
-    dispatch: () => {},
-    getState: () => ({
-      organization: {
-        configuration: {
-          ...config,
-          components: {
-            ...config.components,
-            contact_page: config.components.contact_page || {},
-          },
-        },
-      },
-      language: passedProps.language || "en",
-    }),
-  };
-
-  return render(
-    <Provider store={mockedStore}>
+const createMockStore = (configOverride, language = "en") => {
+  const config = configOverride || defaultConfig;
+  const state = {
+    organization: {
+      configuration: {
+        ...config,
+        slug: config.slug || "default",
+        components: {
+          ...config.components,
+          contact_page: config.components.contact_page || {},
+        },
+      },
+    },
+    language,
+  };
+  return {
+    subscribe: () => {},
+    dispatch: () => {},
+    getState: () => state,
+  };
+};
+
+const mountComponent = (passedProps) => {
+  const config = getConfig(passedProps.orgSlug || "test-org-2");
+  return render(
+    <Provider store={createMockStore(config, passedProps.language)}>
       <MemoryRouter>
         <Registration {...passedProps} />
       </MemoryRouter>
     </Provider>,
   );
 };

Also applies to: 157-183


664-674: Remove unnecessary eslint-disable comment.

The test now includes an assertion (expect(container).toMatchSnapshot() on line 673), so the jest/expect-expect disable is no longer needed.

🔎 Proposed fix
-  // eslint-disable-next-line jest/expect-expect
   it("should render modal", () => {

593-598: Consider centralizing mock restoration.

The pattern of re-setting getConfig.mockImplementation after jest.restoreAllMocks() is repeated in multiple afterEach blocks. If this is needed, consider moving it to a shared setup or using jest.clearAllMocks() instead of restoreAllMocks() to preserve the original mock implementation.

🔎 Alternative approach
   afterEach(() => {
     jest.clearAllMocks();
-    jest.restoreAllMocks();
-    // Re-setup the getConfig mock after clearing
-    getConfig.mockImplementation(() => mockConfig);
   });

Or define a shared helper at the top of the file that both afterEach blocks call.

client/components/password-change/password-change.test.js (4)

132-134: Optional: Simplify mock cleanup.

The sequence axios.mockClear() followed immediately by axios.mockReset() is redundant—mockReset() already clears the mock and additionally removes its implementation.

🔎 Suggested simplification
   beforeEach(() => {
     jest.clearAllMocks();
-    axios.mockClear();
     axios.mockReset();
     props = createTestProps();
   });

155-247: Consider splitting this test into separate test cases.

This test validates four distinct scenarios sequentially. Splitting them into individual tests would improve isolation, make failures easier to diagnose, and allow each scenario to run independently.

💡 Rationale
  • If an earlier assertion fails, subsequent scenarios won't run.
  • Error messages will be clearer when a specific scenario fails.
  • Each test can have a descriptive name explaining what it validates.

Consider creating:

  • it("should show error when passwords don't match")
  • it("should show error when new password matches current password")
  • it("should show error on server rejection")
  • it("should navigate to status page on successful password change")

212-221: Optional: Consider more semantic error assertions.

The custom matcher checks className.includes("error"), which couples the test to implementation details (CSS classes).

If feasible, prefer:

  • Using role="alert" on error messages and querying by role
  • Adding data-testid attributes to error containers
  • Using getByText with the exact error message text

The current approach works but may be fragile if styling changes.


294-331: Optional: Make toggle test more specific.

The test verifies that at least one password input changes to type="text" after clicking a toggle button, but doesn't verify the specific input/toggle relationship.

Consider:

  • Using aria-label or data-testid on toggle buttons to identify which input they control
  • Asserting that the correct input toggles (e.g., if clicking the current password toggle, verify currentPasswordInput changes type)

The current test works but could be more precise about the toggle behavior.

client/components/mobile-phone-verification/mobile-phone-verification.test.js (3)

85-90: Minor: subscribe should return an unsubscribe function.

Per the Redux store contract, subscribe() should return a function to unsubscribe. Returning undefined could cause runtime errors if any code (including React-Redux internals) tries to call the unsubscribe function.

🔎 Proposed fix
   return {
-    subscribe: () => {},
+    subscribe: () => () => {},
     dispatch: () => {},
     getState: () => state,
   };

108-133: Missing validateToken mock setup may cause test instability.

The validateToken mock is not configured in this describe block's beforeEach. While global jest.mock handles module replacement, the mock's return value is undefined, which could cause the component's componentDidMount to behave unexpectedly when isValid is checked.

🔎 Proposed fix
   beforeEach(() => {
+    validateToken.mockResolvedValue(true);
     // Mock axios to handle multiple calls during component mount:
     // 1. activePhoneToken (GET) - returns { active: false } so createPhoneToken is called
     // 2. createPhoneToken (POST) - returns success
     axios.mockImplementation(() =>
       Promise.resolve({
         status: 200,
         statusText: "OK",
         data: {active: false}
       }),
     );
   });

237-253: Test assertion is weak and doesn't verify the intended behavior.

This test named "should check if active token is present" only asserts that axios was called, without verifying that the active token response (active: true) was actually handled correctly or that createPhoneToken was skipped. Consider asserting on the expected side effect (e.g., no toast.info for token sent, or axios call count).

🔎 Proposed fix
   it("should check if active token is present", async () => {
     validateToken.mockResolvedValue(true);
+    jest.spyOn(toast, "info");
     axios.mockReset();
     axios.mockImplementation(() =>
       Promise.resolve({
         status: 200,
+        statusText: "OK",
+        data: { active: true },
-        active: true,
       }),
     );
+    props.userData = userData;

     renderWithProviders(<MobilePhoneVerification {...props} />);

     await tick();

-    // Component should check for active token
-    expect(axios).toHaveBeenCalled();
+    // When active token exists, only activePhoneToken GET is called, createPhoneToken POST is skipped
+    expect(axios).toHaveBeenCalledTimes(1);
+    expect(toast.info).not.toHaveBeenCalled(); // No "TOKEN_SENT" toast
   });
client/components/password-confirm/password-confirm.test.js (1)

233-251: Refactor nested describe to avoid variable shadowing.

The nested describe block re-declares props (line 234) which shadows the parent variable (line 195), creating confusion. Additionally, the beforeEach/afterEach blocks duplicate mock clearing from the parent scope.

Consider either:

  • Flattening the structure and removing the nested describe block, or
  • Removing the shadowed variable declarations and relying on parent scope variables
🔎 Proposed refactor (Option 1: Flatten structure)

Remove the nested describe block and move the tests to the parent level, eliminating the duplicate variable declarations:

  describe("<PasswordConfirm /> interactions", () => {
    let props;
-  let lastConsoleOutput;
+  let spyToastError;
+  let spyToastSuccess;
+  let consoleErrorSpy;

    beforeEach(() => {
      jest.clearAllMocks();
      axios.mockReset();
-    lastConsoleOutput = null;
-    jest.spyOn(global.console, "error").mockImplementation((data) => {
-      lastConsoleOutput = data;
-    });
+    consoleErrorSpy = jest.spyOn(global.console, "error").mockImplementation(() => {});
      props = createTestProps();
+    spyToastError = jest.spyOn(toast, "error");
+    spyToastSuccess = jest.spyOn(toast, "success");
    });

    afterEach(() => {
      jest.clearAllMocks();
      jest.restoreAllMocks();
    });

    // ... rest of tests without nested describe
client/components/payment-process/payment-process.test.js (2)

157-163: Consider removing redundant mock re-setup.

The getConfig mock is already defined with mockImplementation at lines 11-20 before imports. Since jest.clearAllMocks() only clears call history (not implementations), re-setting the mock here is likely unnecessary and may add confusion.

🔎 Simplify afterEach
  afterEach(() => {
    jest.clearAllMocks();
    jest.restoreAllMocks();
-    // Re-setup the getConfig mock after clearing
-    getConfig.mockImplementation(() => ({
-      components: {
-        payment_status_page: {
-          content: {en: "Payment processing..."},
-        },
-      },
-    }));
  });

377-408: Use the mockMessageEvents helper for consistency.

This test creates an inline event mock (lines 377-382) while other postMessage tests (lines 262-299, 301-333, 335-371) use the mockMessageEvents helper defined at lines 99-119. Using the helper would improve consistency and maintainability.

🔎 Refactor to use mockMessageEvents
  it("should handle postMessage for setHeight", async () => {
    props = createTestProps({userData: responseData});
-    validateToken.mockReturnValue(true);
+    validateToken.mockResolvedValue(true);

-    const events = {};
-    const originalAddEventListener = window.addEventListener;
-
-    window.addEventListener = jest.fn((event, callback) => {
-      events[event] = callback;
-    });
+    const eventMock = mockMessageEvents();

    renderWithProviders(<PaymentProcess {...props} />);

    await tick();

    // Simulate setHeight message
-    expect(events.message).toBeDefined();
+    expect(eventMock.events.message).toBeDefined();
    await act(async () => {
-      events.message({
+      eventMock.events.message({
        data: {
          type: "setHeight",
          message: 800,
        },
        origin: window.location.origin,
      });
    });

    await tick();

    // Check if iframe height was updated
    const iframe = within(document.body).getByTitle("owisp-payment-iframe");
    expect(iframe).toBeInTheDocument();
    expect(iframe).toHaveAttribute("height", "800");

-    window.addEventListener = originalAddEventListener;
+    eventMock.restore();
  });
client/components/payment-status/payment-status.test.js (2)

117-131: Consider the implications of optional chaining on console mock cleanup.

The optional chaining (mockRestore?.()) on lines 129-130 is defensive but may hide setup issues. If mockRestore is undefined, it means the mock was never created in beforeEach, yet the test silently continues. While this prevents test failures from cleanup, it could mask problems with the mock setup itself.

Consider either:

  1. Removing the optional chaining to surface setup issues, OR
  2. Adding explicit checks in beforeEach to ensure mocks are created successfully

142-205: Consider simplifying the tick() + waitFor async pattern.

Several tests use await tick() followed by waitFor (e.g., lines 200-205). With React Testing Library and React 18's concurrent rendering, waitFor alone is often sufficient as it polls until the condition is met. The tick() call may be redundant in these cases.

Example from lines 196-205:

await tick();
await waitFor(() => {
  expect(validateToken).toHaveBeenCalled();
});

Could be simplified to:

await waitFor(() => {
  expect(validateToken).toHaveBeenCalled();
});

This applies to similar patterns throughout the file (lines 221-229, 247-255, 275-281, 337-339, etc.).

client/components/password-reset/password-reset.test.js (3)

175-232: Consider splitting into three separate test cases.

This test combines three distinct scenarios (error with detail, error with non_field_errors, and success) into a single test. Splitting these into separate test cases would improve clarity, make debugging easier, and ensure each scenario runs independently.

💡 Example structure for separate tests
it("should show error when submit fails with detail message", async () => {
  axios.mockImplementationOnce(() =>
    Promise.reject({response: {data: {detail: "errors"}}}),
  );
  // ... test logic for error scenario
});

it("should show error when submit fails with non_field_errors", async () => {
  axios.mockImplementationOnce(() =>
    Promise.reject({response: {data: {non_field_errors: ["non field errors"]}}},
  );
  // ... test logic for non_field_errors scenario
});

it("should handle successful password reset", async () => {
  axios.mockImplementationOnce(() => Promise.resolve({data: {detail: true}}));
  // ... test logic for success scenario
});

151-151: Remove redundant axios.mockReset().

Since jest.clearAllMocks() on line 149 already resets all mocks including axios, the explicit axios.mockReset() call is redundant.


265-287: Duplicate test coverage with earlier test.

This test verifies the error flow with a detail message, which is already covered in the first scenario of the test at lines 175-232 (specifically lines 196-204). Consider removing this duplication, or if you split the earlier test as suggested, this test would become redundant.

client/components/modal/modal.test.js (3)

36-43: Minor redundancy: jest.clearAllMocks() called in both hooks.

The afterEach at line 42 is redundant since beforeEach already clears mocks before each test. Consider removing one for clarity.

🔎 Suggested simplification
  beforeEach(() => {
    jest.clearAllMocks();
    axios.mockReset();
  });
-
-  afterEach(() => {
-    jest.clearAllMocks();
-  });

159-175: The event listener backup/restore pattern may be unnecessary.

The manual storage of originalAddEventListener and originalRemoveEventListener (lines 159-160, 166-167, 173-174) is defensive but potentially unnecessary. The test at line 311 uses jest.spyOn with mockRestore(), which is the cleaner approach. Consider using spies consistently across tests if you need to verify event listener behavior.


286-309: Test is redundant with earlier rendering tests.

The previous review comment about the misleading name was addressed (renamed to "should render modal with backdrop"). However, this test is now redundant—it only verifies the modal renders, which is already covered by the tests in the <Modal /> rendering describe block. Additionally, line 308 duplicates the assertion from line 304's waitFor.

Consider removing this test or adding a distinct assertion (e.g., verifying specific backdrop CSS classes or structure).

client/components/mobile-phone-change/mobile-phone-change.test.js (4)

10-15: Simplify router imports for clarity.

Both BrowserRouter (aliased as Router) and MemoryRouter are imported, but Router is only used in the mountComponent helper (line 186) while MemoryRouter is used in renderWithProviders (line 151). This dual usage can be confusing. Consider using MemoryRouter consistently or renaming the alias to clarify intent.


213-216: Move console.error spy to specific tests.

The console.error spy is set up in beforeEach for all tests but only explicitly checked in two tests (lines 401, 421). This means console errors in other tests will be silently suppressed, potentially hiding legitimate issues. Consider moving the spy setup to only the tests that expect console errors, or add assertions in other tests to verify console.error wasn't called unexpectedly.

🔎 Example refactor

Move the spy into specific tests:

   beforeEach(() => {
     jest.clearAllMocks();
     axios.mockReset();
     props = createTestProps();
     validateToken.mockClear();
-    // Spy on console.error to track calls
-    consoleErrorSpy = jest
-      .spyOn(global.console, "error")
-      .mockImplementation(() => {});
   });

Then in the specific test:

   it("should render nonField error", async () => {
+    const consoleErrorSpy = jest
+      .spyOn(global.console, "error")
+      .mockImplementation(() => {});
     jest.spyOn(toast, "info");

219-224: Simplify mock cleanup logic.

Calling both jest.clearAllMocks() and jest.restoreAllMocks() followed by manually re-setting the getConfig mock is redundant. Since getConfig is mocked at module level with jest.mock(), calling jest.clearAllMocks() alone should suffice to reset call counts while preserving the implementation. The restoreAllMocks() call removes the mock entirely, making the manual re-setup necessary but unnecessarily complex.

🔎 Suggested simplification
   afterEach(() => {
     jest.clearAllMocks();
-    jest.restoreAllMocks();
-    // Re-setup the getConfig mock after clearing
-    getConfig.mockImplementation(() => mockConfig);
   });

530-538: Strengthen redirect assertions.

The redirect tests (lines 530-538, 540-554) assert that the form is absent when a redirect should occur, but don't verify that the redirect actually happens. The comments indicate that a Navigate component is rendered, which should trigger routing to the status page. Consider asserting that status-mock becomes present to confirm the redirect actually occurs, making the test more robust.

🔎 Suggested enhancement
   it("should redirect if mobile_phone_verification disabled", async () => {
     props.settings.mobile_phone_verification = false;
 
     mountComponent(props);
 
-    // Component renders Navigate component which triggers routing
-    // The form should not be present when redirecting
-    expect(screen.queryByRole("form")).not.toBeInTheDocument();
+    // Verify redirect actually happens
+    await waitFor(() => {
+      expect(screen.getByTestId("status-mock")).toBeInTheDocument();
+    });
   });

Also applies to: 540-554

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e89997c and 5682cc3.

⛔ Files ignored due to path filters (4)
  • client/components/login/__snapshots__/login.test.js.snap is excluded by !**/*.snap
  • client/components/password-change/__snapshots__/password-change.test.js.snap is excluded by !**/*.snap
  • client/components/password-confirm/__snapshots__/password-confirm.test.js.snap is excluded by !**/*.snap
  • client/components/registration/__snapshots__/registration.test.js.snap is excluded by !**/*.snap
📒 Files selected for processing (15)
  • babel.config.js
  • browser-test/mobile-phone-change.test.js
  • client/components/mobile-phone-change/mobile-phone-change.test.js
  • client/components/mobile-phone-verification/mobile-phone-verification.test.js
  • client/components/modal/modal.test.js
  • client/components/organization-wrapper/organization-wrapper.test.js
  • client/components/password-change/password-change.test.js
  • client/components/password-confirm/password-confirm.test.js
  • client/components/password-reset/password-reset.test.js
  • client/components/payment-process/payment-process.test.js
  • client/components/payment-status/payment-status.test.js
  • client/components/registration/registration.js
  • client/components/registration/registration.test.js
  • client/components/registration/subscriptions.test.js
  • client/utils/password-toggle.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • client/components/registration/registration.js
  • babel.config.js
🧰 Additional context used
🧬 Code graph analysis (8)
browser-test/mobile-phone-change.test.js (1)
browser-test/utils.js (4)
  • getElementByCss (30-39)
  • getElementByCss (30-39)
  • successToastSelector (94-94)
  • successToastSelector (94-94)
client/components/password-confirm/password-confirm.test.js (3)
client/components/password-confirm/password-confirm.js (1)
  • PasswordConfirm (20-231)
client/utils/get-error-text.js (1)
  • error (2-2)
client/utils/tick.js (1)
  • tick (1-5)
client/components/payment-process/payment-process.test.js (3)
client/utils/get-payment-status.js (1)
  • getPaymentStatusRedirectUrl (37-71)
client/components/payment-process/payment-process.js (1)
  • PaymentProcess (12-133)
client/utils/tick.js (1)
  • tick (1-5)
client/components/mobile-phone-verification/mobile-phone-verification.test.js (2)
client/components/mobile-phone-verification/mobile-phone-verification.js (1)
  • MobilePhoneVerification (30-347)
client/utils/validate-token.js (1)
  • validateToken (11-87)
client/components/modal/modal.test.js (2)
client/components/modal/modal.js (1)
  • Modal (14-97)
client/utils/get-text.js (1)
  • getText (1-5)
client/components/payment-status/payment-status.test.js (3)
client/utils/__mocks__/load-translation.js (1)
  • loadTranslation (17-27)
client/utils/validate-token.js (1)
  • validateToken (11-87)
client/utils/tick.js (1)
  • tick (1-5)
client/components/organization-wrapper/organization-wrapper.test.js (1)
client/components/organization-wrapper/organization-wrapper.js (1)
  • OrganizationWrapper (38-371)
client/components/password-reset/password-reset.test.js (2)
client/components/password-reset/password-reset.js (1)
  • PasswordReset (18-154)
client/utils/tick.js (1)
  • tick (1-5)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Tests and Coverage
🔇 Additional comments (35)
browser-test/mobile-phone-change.test.js (1)

54-54: LGTM! Good refactor for maintainability.

Centralizing the success toast selector in utils.js improves test maintainability and consistency. The new selector (.Toastify__toast--success div[role=alert]) is more specific than the previous hard-coded div[role=alert], reducing the risk of matching unintended alert elements on the page.

Also applies to: 92-92

client/components/registration/registration.test.js (2)

234-254: Good RTL migration pattern.

The test correctly uses accessible queries (screen.getByRole, screen.getByLabelText) for form interactions. The mix with container.querySelector is acceptable where accessible attributes are unavailable.


343-348: Async test pattern is appropriate for React 18.

The combination of await tick() followed by await waitFor() addresses async stability issues from React 18's concurrent rendering, as noted in the PR objectives.

client/components/password-change/password-change.test.js (1)

347-365: LGTM: Redirect tests correctly structured.

Both SAML and Social Login redirect tests now correctly use independent renderWithProviders calls. The previous rerender misuse has been properly addressed.

client/components/mobile-phone-verification/mobile-phone-verification.test.js (2)

643-684: LGTM!

The corner case tests correctly verify that the component skips API calls when the user is already verified or when the mobile verification feature is disabled. Good coverage of edge cases.


1-18: Good migration to React Testing Library.

The test file has been well-migrated from Enzyme to RTL, following best practices:

  • Uses semantic queries (getByRole, getByText) instead of implementation details
  • Properly wraps components with required context providers
  • Sets up mocks before imports as required by Jest
client/components/password-confirm/password-confirm.test.js (1)

1-521: Well-executed RTL migration overall.

The migration from Enzyme to React Testing Library is well done. The tests properly:

  • Use renderWithProviders helper with Redux store and Router wrappers
  • Query the DOM using semantic selectors (placeholders, labels, roles)
  • Test user interactions with fireEvent
  • Handle async behavior with waitFor and tick()
  • Cover rendering, validation, API error scenarios, and success flows
  • Use proper mocking for axios, toast, and console

The structure issues flagged in separate comments (duplicate test, variable shadowing, unused variable) are minor and can be addressed to improve maintainability.

client/components/organization-wrapper/organization-wrapper.test.js (8)

10-46: LGTM: Well-structured mock setup.

The pre-import mocks follow best practices by being declared before the component imports. The mock implementations provide realistic test data with comprehensive organization configuration.


48-90: LGTM: Comprehensive test props factory.

The createTestProps helper provides well-structured default props with realistic test data and allows for easy customization through the spread operator pattern.


92-132: LGTM: Robust rendering helper.

The renderWithRouter helper correctly wraps the component with all necessary providers (HelmetProvider, Redux Provider, MemoryRouter) and constructs a proper mock Redux store. This centralizes test setup and ensures consistency across tests.


134-245: LGTM: Comprehensive rendering tests.

The rendering test suite properly validates the three main states (loading, not found, exists) and verifies asset configuration. The use of waitFor correctly handles React 18's concurrent rendering, and the approach of verifying configuration props rather than actual DOM asset injection is appropriate given that react-helmet-async handles the actual injection.


310-347: LGTM: Rerenders now include all required providers.

Both rerender calls in this test now properly wrap the component with HelmetProvider, Provider, and MemoryRouter, ensuring consistent context across renders. This addresses the previous review concern about missing providers.


353-478: LGTM: Language and route interaction tests.

The tests properly handle localStorage interactions, language changes with proper rerendering, and authenticated route rendering. The use of waitFor and call count tracking for loadTranslation is appropriate for verifying language loading behavior.


563-717: LGTM: Comprehensive route testing.

The route tests properly cover authenticated, unauthenticated, and unverified user scenarios across various routes. The mountComponent helper appropriately sets up the test context with all required providers. The header/footer test at lines 704-716 correctly verifies their presence across routes.


689-702: No action required on the test.

The selector #not-foud-404 in the test is correct. It matches the actual DOM element in the 404 component implementation (client/components/404/404.js:19), which uses id="not-foud-404". While this ID contains a spelling error ("foud" instead of "found"), the test is accurately checking for the element as it exists in the rendered DOM.

If the ID should be corrected, that change belongs in the implementation file (client/components/404/404.js), not the test.

Likely an incorrect or invalid review comment.

client/components/payment-process/payment-process.test.js (2)

87-97: Excellent addition of LoadingContext.Provider!

This helper now correctly wraps the component with LoadingContext.Provider, addressing the previous review concern about PaymentProcess's calls to this.context.setLoading() being silently ignored. The optional mockSetLoading parameter allows tests to track loading state changes when needed.


138-454: Comprehensive test coverage with good RTL patterns.

The test suite provides thorough coverage of the PaymentProcess component's behaviors:

  • Redirect scenarios (no payment_url, unauthenticated users)
  • Token validation and loading states
  • Iframe rendering and attribute verification
  • PostMessage event handling (paymentClose, showLoader, setHeight)
  • Event listener lifecycle (add/remove)

The migration to React Testing Library is well-executed with proper use of render, waitFor, act, and within utilities. Test isolation and cleanup are handled correctly.

client/components/payment-status/payment-status.test.js (4)

1-36: LGTM: Clean migration to RTL with proper mock setup.

The module mocks are correctly placed before imports, and the inline translation data in the get-config mock provides good test isolation without external dependencies.


260-323: LGTM: Comprehensive coverage of proceedToPayment flag logic.

These two tests effectively cover both branches of the payment_requires_internet setting:

  • Lines 260-294: Verifies that clicking "proceed with payment" does NOT trigger setUserData when the flag is false
  • Lines 296-323: Verifies that clicking "proceed with payment" DOES set proceedToPayment: true when the flag is true

The test isolation (clearing mocks at line 283) and explicit assertions make the expected behavior clear.


466-483: LGTM: Good coverage of token validation failure.

This test correctly verifies that when validateToken returns false, the component doesn't call setUserData (line 482), preventing state updates when authentication has failed. This is important for security and proper error handling.


52-83: The mock store structure correctly matches the PaymentStatus component's Redux requirements.

The mock store's use of ...defaultConfig spread provides the required state shape (userData, settings, isAuthenticated) expected by mapStateToProps. The payment_status_page component configuration is properly included via ...defaultConfig.components. The contact_page addition is unused by this component and does not affect test validity. The renderWithProviders pattern correctly wraps the component with Redux Provider and MemoryRouter for proper integration testing.

client/components/password-reset/password-reset.test.js (3)

10-32: Good mocking pattern for RTL migration.

The module mocks are correctly placed before imports to ensure proper hoisting, and the mock structure appropriately matches the component's requirements.


54-85: Well-structured test helpers for RTL migration.

The createMockStore and renderWithProviders helpers provide clean scaffolding for Redux and Router context, following RTL best practices.


208-208: Remove tick() calls before waitForprocess.nextTick may not flush React 18's concurrent updates.

The project uses React 18.3.1 (concurrent rendering by default) and @testing-library/react 16.3.1. The tick() utility relies on process.nextTick, which executes before microtasks but at a lower priority than React 18's internal scheduler. waitFor from @testing-library/react already handles waiting for DOM updates from async operations, making the explicit tick() calls redundant and potentially masking timing assumptions. Remove await tick() calls at lines 208, 219, 258, and 282, then run tests to confirm they pass with waitFor alone.

client/components/modal/modal.test.js (7)

1-17: Imports and mocks setup looks good.

The migration to RTL is properly set up with appropriate imports (render, screen, waitFor, fireEvent) and jest-dom matchers. The mocks for external dependencies are correctly configured.


45-154: Rendering tests are well-structured.

The tests properly:

  • Use waitFor to handle async content loading
  • Wrap components with MemoryRouter for router context
  • Verify expected behavior including error handling with logError
  • Include snapshot tests for regression detection

177-204: Esc key handling test is correct.

The test properly fires the keyUp event on document (matching the component's event listener), uses the correct keyCode: 27, and verifies navigation occurs.


206-232: Non-Esc key test is appropriate.

The negative assertion (not.toHaveBeenCalled()) immediately after fireEvent is acceptable here since the handler is synchronous. If navigate were called, it would happen immediately.


234-253: mapStateToProps test is straightforward and correct.

This is a clean unit test for the Redux selector function.


311-354: Event listener cleanup test is well-implemented.

Using jest.spyOn with proper cleanup via mockRestore() is the correct approach. The test effectively verifies that componentWillUnmount properly removes the event listener.


255-284: The test is correct as written. toHaveStyle from jest-dom v6.9.1 uses getComputedStyle internally and explicitly ignores the !important priority flag—it only compares property values. The Modal component setting overflow: "hidden" with !important will match the assertion at line 277, and the cleanup assignment at line 31 will properly overwrite it so that the assertion at line 283 passes.

client/components/mobile-phone-change/mobile-phone-change.test.js (4)

226-287: Well-structured core functionality tests.

These tests properly verify the main user flows with good RTL practices: using accessible role queries, waiting for async updates with waitFor, and asserting both DOM state and function calls. The phone number formatting checks (removing spaces/hyphens before comparison) correctly handle the input's formatting behavior.


336-402: Effective error handling validation.

The error tests properly validate both field-specific and non-field errors by asserting that error messages appear in the DOM (user-visible validation) rather than checking internal state. The non-field error test correctly verifies that console.error is called for this error type.


404-422: Cancel flow correctly validates button existence.

The conditional assertion issue from the previous review has been addressed. The test now explicitly asserts that the cancel button exists (line 417) before clicking it, ensuring the test will fail if the button is missing rather than silently passing.


186-191: No changes needed. React Router v6 explicitly supports the location and navigator props on the Router component. This pattern is correct and used consistently across other tests in the codebase (login, registration). The Router with these props works properly in v6.2.1.

Likely an incorrect or invalid review comment.

@PRoIISHAAN PRoIISHAAN force-pushed the fix-reactversion-migration branch from 5682cc3 to a876f80 Compare January 2, 2026 12:52
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

♻️ Duplicate comments (11)
client/components/password-reset/password-reset.test.js (1)

225-230: Act() warning workaround is a symptom of component cleanup issues.

This pattern of allowing act() warnings has already been flagged in a previous review. The root cause is that the PasswordReset component lacks proper cleanup for async state updates (missing componentIsMounted guard or AbortController).

client/components/registration/registration.test.js (1)

784-794: Test doesn't match its description.

This test claims to verify country selection behavior but only checks that the form exists. As noted in a previous review, this test should either be removed, renamed to reflect what it actually tests, or enhanced to test actual country selection functionality.

client/components/payment-process/payment-process.test.js (9)

170-170: Use mockResolvedValue for async function mock.

Line 170 still uses mockReturnValue(true) instead of mockResolvedValue(true) for the async validateToken function. While JavaScript auto-wraps non-promises when awaited, using the correct mock method ensures semantic correctness.

🔎 Quick fix
-    validateToken.mockReturnValue(true);
+    validateToken.mockResolvedValue(true);

192-192: Use mockResolvedValue for async function mock.

Same issue as line 170 - use mockResolvedValue(true) instead of mockReturnValue(true).

🔎 Quick fix
-    validateToken.mockReturnValue(true);
+    validateToken.mockResolvedValue(true);

213-213: Use mockResolvedValue for async function mock.

Use mockResolvedValue(false) instead of mockReturnValue(false).

🔎 Quick fix
-    validateToken.mockReturnValue(false);
+    validateToken.mockResolvedValue(false);

242-242: Use mockResolvedValue for async function mock.

Use mockResolvedValue(true) instead of mockReturnValue(true).

🔎 Quick fix
-    validateToken.mockReturnValue(true);
+    validateToken.mockResolvedValue(true);

266-266: Use mockResolvedValue for async function mock.

Use mockResolvedValue(true) instead of mockReturnValue(true).

🔎 Quick fix
-    validateToken.mockReturnValue(true);
+    validateToken.mockResolvedValue(true);

303-303: Use mockResolvedValue for async function mock.

Use mockResolvedValue(true) instead of mockReturnValue(true).

🔎 Quick fix
-    validateToken.mockReturnValue(true);
+    validateToken.mockResolvedValue(true);

337-337: Use mockResolvedValue for async function mock.

Use mockResolvedValue(true) instead of mockReturnValue(true).

🔎 Quick fix
-    validateToken.mockReturnValue(true);
+    validateToken.mockResolvedValue(true);

374-374: Use mockResolvedValue for async function mock.

Use mockResolvedValue(true) instead of mockReturnValue(true).

🔎 Quick fix
-    validateToken.mockReturnValue(true);
+    validateToken.mockResolvedValue(true);

441-441: Use mockResolvedValue for async function mock.

Use mockResolvedValue(true) instead of mockReturnValue(true).

🔎 Quick fix
-    validateToken.mockReturnValue(true);
+    validateToken.mockResolvedValue(true);
🧹 Nitpick comments (17)
client/components/payment-status/payment-status.test.js (2)

71-76: Consider using redux-mock-store or returning an unsubscribe function.

The subscribe method should return an unsubscribe function per Redux's store contract. While this works for these tests, it could cause issues if the component or any connected child attempts to unsubscribe.

🔎 Suggested fix
  return {
-   subscribe: () => {},
+   subscribe: () => () => {}, // returns unsubscribe function
    dispatch: () => {},
    getState: () => state,
  };

Alternatively, consider using redux-mock-store or @reduxjs/toolkit's configureStore for more realistic store behavior.


190-207: Consider strengthening redirect assertions.

This test verifies validateToken was called but doesn't explicitly assert that navigation occurred. If the component redirects verified users, consider asserting props.navigate was called with the expected path.

    await waitFor(() => {
      expect(validateToken).toHaveBeenCalled();
+     expect(props.navigate).toHaveBeenCalledWith(`/${props.orgSlug}/status`);
    });
client/components/modal/modal.test.js (4)

36-43: Minor redundancy in cleanup hooks.

The afterEach hook only calls jest.clearAllMocks() which is already called at the start of beforeEach. This is harmless but slightly redundant. Consider keeping cleanup in one place for clarity.


102-129: Test assertion could be more specific.

The test "should render nothing on incorrect param name" only verifies that axios was called, but doesn't explicitly assert the empty content state. Consider adding an assertion that verifies the rendered content is empty.

🔎 Suggested improvement
     await waitFor(() => {
       // Component should render but with empty content
       expect(axios).toHaveBeenCalled();
     });
 
+    // Verify empty content was rendered
+    expect(container.querySelector(".message")).toHaveTextContent("");
     expect(container).toMatchSnapshot();

234-253: Consider moving to a separate describe block.

This test for mapStateToProps is a pure function test unrelated to component interactions. Consider moving it to its own describe block like describe("mapStateToProps", () => {...}) for better test organization.


286-309: Redundant assertion within the test.

Lines 307-308 duplicate the assertion already made inside waitFor at lines 303-305. The second assertion adds no additional value.

🔎 Suggested cleanup
     await waitFor(() => {
       expect(screen.getByText(/modal content/i)).toBeInTheDocument();
     });
-
-    // Just verify the modal rendered - the close functionality is tested via Esc key
-    expect(screen.getByText(/modal content/i)).toBeInTheDocument();
   });
client/components/footer/footer.test.js (1)

90-130: Consider role-based queries for enhanced specificity.

While the current text-based queries work well, you could make link queries more specific by using getByRole with name option:

expect(screen.getByRole("link", {name: "about"})).toBeInTheDocument();

This combines semantic querying with specificity and would be more resilient if multiple elements share the same text content.

client/components/password-reset/password-reset.test.js (2)

54-78: Consider using configureStore from Redux Toolkit or redux-mock-store for more robust mocking.

The current mock store implementation is minimal and works for these tests. However, subscribe should return an unsubscribe function, and dispatch typically returns the action (or promise for thunks). If future tests need thunk support or subscription cleanup, this may cause issues.

🔎 Proposed improvement
 const createMockStore = () => {
   const state = {
     organization: {
       configuration: {
         ...defaultConfig,
         slug: "default",
         components: {
           ...defaultConfig.components,
           contact_page: {
             email: "[email protected]",
             helpdesk: "+1234567890",
             social_links: [],
           },
         },
       },
     },
     language: "en",
   };

   return {
-    subscribe: () => {},
-    dispatch: () => {},
+    subscribe: () => () => {}, // Returns unsubscribe function
+    dispatch: (action) => action, // Returns action for compatibility
     getState: () => state,
   };
 };

175-232: Consider splitting the combined handleSubmit test into separate tests for better isolation.

This test combines three scenarios (error with detail, error with non_field_errors, and success) in a single test case. While this works, it makes debugging failures harder and couples the scenarios together. Each scenario depends on the previous mock being consumed.

🔎 Proposed approach

Split into three separate test cases:

it("should show error toast when API returns detail error", async () => { ... });
it("should show error toast when API returns non_field_errors", async () => { ... });
it("should show success message and hide form on successful reset", async () => { ... });

This provides better test isolation and clearer failure messages.

client/components/mobile-phone-verification/mobile-phone-verification.test.js (5)

64-64: Unused second argument to mocked getConfig.

The call getConfig("default", true) passes a second argument, but the mock defined at line 21 only accepts one parameter and ignores it. This is harmless but could confuse future maintainers.

🔎 Proposed fix
-const defaultConfig = getConfig("default", true);
+const defaultConfig = getConfig("default");

126-126: Props defined at describe scope could cause test pollution.

props is created once outside beforeEach and shared across all tests in this describe block. While there's only one test currently, this pattern risks test pollution if more tests are added and any test mutates props. The second describe block correctly initializes props in beforeEach.

🔎 Proposed fix
+  let props;
+
   beforeEach(() => {
     // Mock axios to handle multiple calls during component mount:
     // 1. activePhoneToken (GET) - returns { active: false } so createPhoneToken is called
     // 2. createPhoneToken (POST) - returns success
     axios.mockImplementation(() =>
       Promise.resolve({
         status: 200,
         statusText: "OK",
         data: {active: false},
       }),
     );
+    props = createTestProps();
   });
 
   afterEach(() => {
     axios.mockReset();
   });
 
-  const props = createTestProps();
   it("should render translation placeholder correctly", () => {

163-163: Calling mocked loadTranslation has no effect.

loadTranslation is mocked at line 42, so calling it here does nothing. This appears to be leftover from the Enzyme-based tests. The same pattern appears at lines 410 and 479.

🔎 Proposed fix

Remove the no-op calls:

-    loadTranslation("en", "default");

Apply similarly at lines 410 and 479.


464-475: Potential flakiness: assertions after tick() without waitFor.

After await tick(), the test immediately asserts setUserData was called. If the component's async flow takes slightly longer (e.g., due to concurrent rendering in React 18), this could be flaky. Consider wrapping in waitFor for consistency with other tests.

🔎 Proposed fix
-    await tick();
-
-    expect(props.setUserData).toHaveBeenCalledTimes(1);
+    await waitFor(() => {
+      expect(props.setUserData).toHaveBeenCalledTimes(1);
+    });

596-596: Use mockReset() instead of mockClear() for full isolation.

axios.mockClear() only resets call history but preserves the mock implementation. If a previous test set a custom mockImplementation, it may leak into this test. Use axios.mockReset() for full isolation, consistent with the pattern used elsewhere in this file (e.g., lines 140, 186, 257).

🔎 Proposed fix
   it("should not call API to resend token if one has already sent", async () => {
-    axios.mockClear();
+    axios.mockReset();
     sessionStorage.setItem("owPhoneTokenSent", true);
client/components/payment-process/payment-process.test.js (1)

377-407: Consider using the mockMessageEvents helper for consistency.

This test manually mocks window.addEventListener without try-finally protection, meaning the restoration at line 407 won't occur if the test fails early. Other tests (e.g., lines 262-299, 301-333) use the mockMessageEvents helper which provides cleaner cleanup via the restore() method.

🔎 Refactor suggestion
-    const events = {};
-    const originalAddEventListener = window.addEventListener;
-
-    window.addEventListener = jest.fn((event, callback) => {
-      events[event] = callback;
-    });
+    const eventMock = mockMessageEvents();

     renderWithProviders(<PaymentProcess {...props} />);

     await tick();

     // Simulate setHeight message
-    expect(events.message).toBeDefined();
+    expect(eventMock.events.message).toBeDefined();
     await act(async () => {
-      events.message({
+      eventMock.events.message({
         data: {
           type: "setHeight",
           message: 800,
         },
         origin: window.location.origin,
       });
     });

     await tick();

     // Check if iframe height was updated
     const iframe = within(document.body).getByTitle("owisp-payment-iframe");
     expect(iframe).toBeInTheDocument();
     expect(iframe).toHaveAttribute("height", "800");

-    window.addEventListener = originalAddEventListener;
+    eventMock.restore();
client/components/organization-wrapper/organization-wrapper.test.js (1)

704-716: Inconsistent assertion pattern.

Lines 711-714 use .toBeTruthy() while the rest of the test file consistently uses .toBeInTheDocument() for DOM presence checks. For consistency and clearer intent, consider using .toBeInTheDocument().

🔎 Proposed refactor for consistency
     await waitFor(() => {
       // Header and footer should be present
       // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
       expect(
         container.querySelector(".header-container, .header-mobile"),
-      ).toBeTruthy();
+      ).toBeInTheDocument();
       // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
-      expect(container.querySelector(".footer-container")).toBeTruthy();
+      expect(container.querySelector(".footer-container")).toBeInTheDocument();
     });
client/components/mobile-phone-change/mobile-phone-change.test.js (1)

221-222: Consider simplifying mock reset pattern.

The pattern of calling jest.clearAllMocks() followed by re-establishing the getConfig mock is somewhat redundant. Since getConfig is already mocked at the module level (line 71), you could either:

  1. Use mockClear() instead of clearAllMocks() to preserve the mock implementation, or
  2. Remove the re-setup line if the module-level mock is sufficient
🔎 Option 1: Use mockClear to preserve implementations
   afterEach(() => {
-    jest.clearAllMocks();
+    getConfig.mockClear();
+    validateToken.mockClear();
     jest.restoreAllMocks();
-    // Re-setup the getConfig mock after clearing
-    getConfig.mockImplementation(() => mockConfig);
   });
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5682cc3 and ec9e917.

⛔ Files ignored due to path filters (19)
  • browserTestError.log is excluded by !**/*.log
  • client/components/404/__snapshots__/404.test.js.snap is excluded by !**/*.snap
  • client/components/contact-box/__snapshots__/contact.test.js.snap is excluded by !**/*.snap
  • client/components/footer/__snapshots__/footer.test.js.snap is excluded by !**/*.snap
  • client/components/header/__snapshots__/header.test.js.snap is excluded by !**/*.snap
  • client/components/login/__snapshots__/login.test.js.snap is excluded by !**/*.snap
  • client/components/logout/__snapshots__/logout.test.js.snap is excluded by !**/*.snap
  • client/components/mobile-phone-change/__snapshots__/mobile-phone-change.test.js.snap is excluded by !**/*.snap
  • client/components/mobile-phone-verification/__snapshots__/mobile-phone-verification.test.js.snap is excluded by !**/*.snap
  • client/components/modal/__snapshots__/modal.test.js.snap is excluded by !**/*.snap
  • client/components/organization-wrapper/__snapshots__/organization-wrapper.test.js.snap is excluded by !**/*.snap
  • client/components/password-change/__snapshots__/password-change.test.js.snap is excluded by !**/*.snap
  • client/components/password-confirm/__snapshots__/password-confirm.test.js.snap is excluded by !**/*.snap
  • client/components/password-reset/__snapshots__/password-reset.test.js.snap is excluded by !**/*.snap
  • client/components/payment-process/__snapshots__/payment-process.test.js.snap is excluded by !**/*.snap
  • client/components/payment-status/__snapshots__/payment-status.test.js.snap is excluded by !**/*.snap
  • client/components/registration/__snapshots__/registration.test.js.snap is excluded by !**/*.snap
  • client/components/status/__snapshots__/status.test.js.snap is excluded by !**/*.snap
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (56)
  • .eslintrc.json
  • .github/workflows/ci.yml
  • babel.config.js
  • browser-test/create-mobile-configuration.js
  • browser-test/mobile-phone-change.test.js
  • browser-test/utils.js
  • client/app.js
  • client/components/404/404.test.js
  • client/components/contact-box/contact.test.js
  • client/components/footer/footer.test.js
  • client/components/header/header.test.js
  • client/components/login/login.test.js
  • client/components/logout/logout.test.js
  • client/components/mobile-phone-change/mobile-phone-change.js
  • client/components/mobile-phone-change/mobile-phone-change.test.js
  • client/components/mobile-phone-verification/mobile-phone-verification.js
  • client/components/mobile-phone-verification/mobile-phone-verification.test.js
  • client/components/modal/modal.test.js
  • client/components/organization-wrapper/organization-wrapper.js
  • client/components/organization-wrapper/organization-wrapper.test.js
  • client/components/password-change/password-change.js
  • client/components/password-change/password-change.test.js
  • client/components/password-confirm/password-confirm.js
  • client/components/password-confirm/password-confirm.test.js
  • client/components/password-reset/password-reset.js
  • client/components/password-reset/password-reset.test.js
  • client/components/payment-process/payment-process.test.js
  • client/components/payment-status/payment-status.js
  • client/components/payment-status/payment-status.test.js
  • client/components/registration/registration.js
  • client/components/registration/registration.test.js
  • client/components/registration/subscriptions.test.js
  • client/components/registration/test-utils.js
  • client/components/status/status.js
  • client/components/status/status.test.js
  • client/components/status/status.test.js.enzyme-backup
  • client/components/status/status.test.js.rtl-backup
  • client/utils/__mocks__/get-config.js
  • client/utils/get-config.js
  • client/utils/get-plan-selection.js
  • client/utils/load-translation.js
  • client/utils/load-translation.test.js
  • client/utils/log-error.js
  • client/utils/needs-verify.js
  • client/utils/password-toggle.js
  • client/utils/tick.js
  • client/utils/utils.test.js
  • client/utils/with-route-props.js
  • config/__tests__/add-org.test.js
  • config/add-org.js
  • config/jest.config.js
  • config/setupTests.js
  • config/webpack.config.js
  • package.json
  • server/app.js
  • server/index.js
🚧 Files skipped from review as they are similar to previous changes (13)
  • browser-test/utils.js
  • client/components/mobile-phone-change/mobile-phone-change.js
  • client/components/contact-box/contact.test.js
  • client/components/password-confirm/password-confirm.js
  • browser-test/mobile-phone-change.test.js
  • client/components/logout/logout.test.js
  • client/components/payment-status/payment-status.js
  • client/components/password-change/password-change.js
  • browser-test/create-mobile-configuration.js
  • client/components/mobile-phone-verification/mobile-phone-verification.js
  • .github/workflows/ci.yml
  • client/components/password-reset/password-reset.js
  • babel.config.js
🧰 Additional context used
🧬 Code graph analysis (12)
client/components/404/404.test.js (1)
client/components/404/404.js (1)
  • DoesNotExist (9-47)
client/app.js (1)
client/store/index.js (1)
  • store (6-6)
client/components/registration/registration.test.js (2)
client/components/registration/test-utils.js (1)
  • mountComponent (8-38)
client/components/registration/registration.js (1)
  • Registration (35-854)
client/components/password-change/password-change.test.js (1)
client/components/password-change/password-change.js (1)
  • PasswordChange (22-251)
client/components/login/login.test.js (3)
client/components/login/login.js (1)
  • Login (36-506)
client/utils/__mocks__/get-config.js (1)
  • getConfig (11-16)
client/utils/__mocks__/get-parameter-by-name.js (1)
  • getParameterByName (1-1)
client/components/mobile-phone-change/mobile-phone-change.test.js (5)
client/utils/__mocks__/get-config.js (1)
  • getConfig (11-16)
client/utils/loading-context.js (3)
  • LoadingContext (4-4)
  • loadingContextValue (3-3)
  • loadingContextValue (3-3)
client/components/registration/test-utils.js (3)
  • mountComponent (8-38)
  • historyMock (9-9)
  • mockedStore (12-27)
client/utils/load-translation.js (1)
  • loadTranslation (74-95)
client/utils/tick.js (1)
  • tick (1-12)
client/components/password-reset/password-reset.test.js (3)
client/components/password-reset/password-reset.js (1)
  • PasswordReset (18-154)
client/utils/load-translation.js (1)
  • loadTranslation (74-95)
client/utils/tick.js (1)
  • tick (1-12)
client/components/header/header.test.js (2)
client/utils/check-internal-links.js (1)
  • isInternalLink (1-4)
client/components/header/index.js (2)
  • mapDispatchToProps (15-19)
  • mapDispatchToProps (15-19)
client/components/mobile-phone-verification/mobile-phone-verification.test.js (4)
client/components/mobile-phone-verification/mobile-phone-verification.js (1)
  • MobilePhoneVerification (30-347)
client/utils/validate-token.js (1)
  • validateToken (11-87)
client/utils/tick.js (1)
  • tick (1-12)
client/utils/log-error.js (1)
  • logError (1-15)
client/components/modal/modal.test.js (3)
client/components/modal/modal.js (1)
  • Modal (14-97)
client/utils/get-text.js (1)
  • getText (1-5)
client/utils/log-error.js (1)
  • logError (1-15)
client/components/footer/footer.test.js (1)
client/components/footer/footer.js (1)
  • Footer (12-55)
client/components/password-confirm/password-confirm.test.js (3)
client/components/password-confirm/password-confirm.js (1)
  • PasswordConfirm (20-231)
client/utils/get-error-text.js (1)
  • error (2-2)
client/utils/tick.js (1)
  • tick (1-12)
🪛 Gitleaks (8.30.0)
client/components/registration/registration.test.js

[high] 153-153: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 154-154: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

client/components/login/login.test.js

[high] 685-685: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 871-871: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 872-872: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

client/components/registration/subscriptions.test.js

[high] 104-104: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 105-105: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Tests and Coverage
🔇 Additional comments (59)
.eslintrc.json (2)

17-17: Enable Jest environment for test file linting.

Adding "jest": true to the env block is appropriate, as it enables Jest globals and prevents linting errors for Jest-specific identifiers like describe, it, expect, etc., which are now in use throughout the test suite after the Enzyme-to-RTL migration.


4-4: ESLint plugins for testing-library and jest-dom are properly installed.

The eslint-plugin-testing-library (^7.15.3) and eslint-plugin-jest-dom (^5.5.0) packages are present in package.json as devDependencies, so the plugin configuration in .eslintrc.json is valid.

client/components/payment-status/payment-status.test.js (6)

1-36: LGTM!

The mock setup pattern is correct—mocks are defined before importing the component under test. The eslint-disable import/first is properly scoped with matching eslint-enable.


117-131: LGTM!

The mock setup and teardown pattern is solid. Using optional chaining on mockRestore?.() is good defensive coding for cases where the spy might not have been properly established.


133-160: LGTM!

Good migration to RTL patterns—using getByRole and getByText with regex matchers provides resilient, accessible-focused assertions.


273-281: Verify the intentional use of mustLogin: undefined.

The assertion expects mustLogin: undefined which seems intentional based on the comment, but this is an unusual pattern. Ensure this matches the actual component behavior and isn't masking a bug where mustLogin should be explicitly false or omitted entirely.


285-294: Good test for payment_requires_internet flag behavior.

The test correctly verifies that clicking the payment link does not trigger setUserData when payment_requires_internet is false. This properly validates the conditional behavior.


511-527: LGTM!

Good coverage of the draft state rendering and the setUserData call with mustLogin: true. The snapshot assertion combined with the mock verification provides solid test coverage.

client/components/modal/modal.test.js (6)

1-17: LGTM!

The imports and mocks are well-structured for RTL-based testing. The mocking strategy correctly isolates external dependencies (axios, utilities) while allowing the component behavior to be tested.


45-71: LGTM!

The test correctly awaits async content rendering and verifies that getText is called with proper arguments. The snapshot captures the final rendered state.


159-175: LGTM!

The pattern of storing and restoring original DOM methods provides clean isolation between tests. The actual spying is done per-test with jest.spyOn, which is the correct approach.


177-232: LGTM!

Good test coverage for keyboard interaction. The tests correctly use fireEvent.keyUp to match the component's keyup event listener, and cover both the Esc key (should navigate) and non-Esc key (should not navigate) cases.


255-284: LGTM!

Good test for verifying the scrollbar behavior on mount/unmount. The test correctly uses RTL's unmount() to trigger cleanup and verifies the overflow style changes.


311-354: LGTM!

Excellent test for verifying event listener cleanup on unmount. The test properly spies on addEventListener and removeEventListener, verifies the correct event type and handler are used, and correctly restores the spies afterward.

client/components/footer/footer.test.js (7)

3-4: LGTM! Clean RTL imports.

The migration from Enzyme to React Testing Library is correctly implemented with the standard RTL and jest-dom imports.


54-55: LGTM! Correct snapshot pattern for RTL.

Using container from the render result for snapshot testing is the standard RTL approach.

Also applies to: 68-69


77-78: LGTM! Proper use of queryByRole for negative assertion.

Using queryByRole with not.toBeInTheDocument() is the correct pattern for verifying an element's absence.


82-83: LGTM! Simple and effective RTL assertion.


90-99: LGTM! Well-structured conditional rendering test.

Excellent use of getByText for expected elements and queryByText for elements that should be absent. The comments clearly document test expectations.


105-114: LGTM! Authenticated state correctly verified.

The test properly validates that authenticated-only links appear when isAuthenticated is true.


121-130: LGTM! Verification requirement correctly tested.

The test properly validates that links requiring verification are hidden when is_verified is false, while other authenticated links remain visible.

client/components/password-reset/password-reset.test.js (4)

10-32: Well-structured mock setup with proper hoisting.

The mocks are correctly placed before imports with appropriate ESLint directives. Jest hoists jest.mock() calls, so this pattern ensures mocks are in place before module resolution.


44-52: Translation helper provides useful fallback behavior.

The getTranslationString helper gracefully falls back to the message ID when translation lookup fails, which aids debugging while preventing test crashes.


87-93: Clean snapshot test following RTL patterns.

The placeholder translation snapshot test is concise and properly uses the renderWithProviders helper.


243-287: Good coverage of success and error state transitions.

The tests for clearing errors on success and showing errors for invalid email effectively verify the component's state handling through DOM assertions rather than internal state inspection, which aligns well with RTL philosophy.

client/components/404/404.test.js (1)

1-63: LGTM! Clean RTL migration.

The migration from Enzyme to React Testing Library is well executed. The renderWithRouter helper appropriately wraps components with MemoryRouter, and tests correctly use container-based snapshots and screen queries. Mock access patterns are correct.

client/components/header/header.test.js (1)

1-402: LGTM! Comprehensive RTL migration with proper routing context.

The test suite successfully migrates to React Testing Library with appropriate patterns:

  • Pre-import mocks are correctly placed with eslint-disable directives
  • MemoryRouter wrapper provides necessary routing context
  • Interaction tests properly use fireEvent and screen queries
  • Use of container.querySelector for class-based element counting is acceptable when screen queries are insufficient
  • Test coverage is comprehensive, including visibility toggles, language switching, and conditional rendering
client/components/password-change/password-change.test.js (1)

1-366: LGTM! Well-structured RTL migration with proper provider wrapping.

The test suite is well organized with:

  • Proper pre-import mocks for utilities
  • renderWithProviders helper correctly wraps components with Redux Provider and MemoryRouter
  • Sequential test scenarios are correctly implemented with axios mocking and waitFor
  • Password visibility toggle tests verify DOM attribute changes appropriately
  • The previous rerender issue flagged in past reviews has been properly addressed by using separate test cases
client/components/password-confirm/password-confirm.test.js (1)

1-495: LGTM! Well-organized test suite with comprehensive error handling coverage.

Excellent test structure with:

  • Proper mock configuration including detailed form field patterns
  • renderWithProviders helper correctly provides Redux store and router context
  • Nested describe blocks effectively organize form submission scenarios
  • Comprehensive error handling tests for different API error shapes (detail, non_field_errors, token)
  • Password visibility toggle tests correctly verify shared state between fields
  • Proper cleanup with jest.restoreAllMocks() in afterEach
  • Previous issues from past reviews (typos, duplicates) have been addressed
client/components/login/login.test.js (1)

1-1032: LGTM! Comprehensive test suite with excellent coverage of authentication flows.

This is a thorough migration to RTL with strong test coverage:

  • Two appropriate render helpers: renderWithProvider (MemoryRouter) for simple tests and mountComponent (BrowserRouter with history) for complex navigation scenarios
  • Excellent lazy-loading tests for PhoneInput, including fallback verification
  • Comprehensive authentication flow coverage: mobile phone verification, bank card, SAML, social login, sesame tokens, and radius realms
  • Proper sessionStorage/localStorage verification for token persistence
  • Sequential axios mocking is correctly implemented with waitFor
  • Captive portal form test thoroughly verifies hidden fields and form attributes

Regarding the static analysis warnings (lines 685, 871, 872): These are false positives. The flagged values are test fixtures (mock tokens and keys) used for testing authentication flows, not actual secrets.

client/components/registration/registration.js (1)

379-379: LGTM! Excellent accessibility and testing improvements.

The addition of aria-label attributes to form inputs significantly improves screen reader support, while data-testid attributes enable more robust RTL-based testing. The parentClassName prop for PasswordToggleIcon ensures consistent styling. These changes align perfectly with the PR's objective to migrate to React Testing Library and improve accessibility.

Also applies to: 459-459, 632-632, 637-637, 661-661, 666-666, 675-678, 701-701, 717-717, 732-732, 748-748

client/components/registration/registration.test.js (2)

17-88: Well-structured RTL test setup.

The pre-import mock configuration and helper functions (createMockStore, renderWithProviders, mountComponent) provide a solid foundation for RTL-based testing. The approach of mocking get-config before importing components is the correct pattern for ensuring consistent test environments.

Also applies to: 117-150, 157-183


234-385: Comprehensive test coverage with proper RTL patterns.

The interaction tests effectively use RTL queries (screen.getByRole, screen.getByLabelText), simulate user actions with fireEvent, and handle asynchronous updates with waitFor. The test suite covers multiple scenarios including validation errors, server errors, and successful registration flows.

client/components/registration/subscriptions.test.js (2)

13-94: Solid RTL test infrastructure for subscription flows.

The test setup follows the same robust pattern established in registration.test.js, with appropriate mocks and helper functions. The mock configuration correctly enables subscriptions and provides all necessary test context including Redux store and routing.

Also applies to: 108-165, 203-229


255-696: Excellent subscription flow test coverage.

The test suite comprehensively covers subscription-specific scenarios including plan selection UI, conditional billing info display based on requires_payment and requires_invoice flags, payment redirection, and the nuanced username behavior (phone number vs. email-based username) depending on plan requirements. The tests properly use RTL patterns and handle asynchronous state updates correctly.

client/components/mobile-phone-verification/mobile-phone-verification.test.js (2)

20-45: Well-structured mock setup.

The mocks are correctly defined before importing the component, and the utility mocks (validateToken, loadTranslation, logError, handleLogout, axios) are properly isolated. This follows best practices for RTL test setup.


644-684: LGTM!

The corner case tests are well-isolated with proper mock cleanup in beforeEach/afterEach. The tests correctly verify that no API calls are made when the user is already verified or when mobile verification is disabled.

client/components/payment-process/payment-process.test.js (4)

1-43: LGTM! Solid mock setup for RTL migration.

The module mocks are correctly placed before imports, the Navigate mock provides testable output, and LoadingContext is now properly imported and used in the test helper.


45-119: LGTM! Well-structured test helpers.

The renderWithProviders helper now includes LoadingContext.Provider (addressing the previous review comment), and the mockMessageEvents utility provides proper cleanup via the restore() method.


138-164: LGTM! Proper test lifecycle setup.

The beforeEach correctly uses mockResolvedValue for the async validateToken function, and the afterEach properly clears and restores mocks including the getConfig implementation.


410-435: LGTM! Pragmatic approach to testing redirect behavior.

This test correctly uses mockResolvedValue (line 415) and properly restores the spy. While spying on prototype methods tests implementation details rather than behavior, it's a reasonable approach for verifying redirect behavior in a test environment.

client/components/organization-wrapper/organization-wrapper.js (1)

9-9: LGTM! Clean migration to react-helmet-async.

The import change from react-helmet to react-helmet-async is correct. The rest of the Helmet usage throughout the file remains unchanged, which is appropriate since react-helmet-async is a drop-in replacement with improved async SSR support and React 18 compatibility.

client/app.js (2)

3-3: Correct React 18 imports.

The imports for createRoot and HelmetProvider are appropriate for the React 18 migration and react-helmet-async integration.

Also applies to: 5-5


50-61: Proper React 18 migration with createRoot and context providers.

The migration from legacy render() to createRoot().render() follows React 18 best practices. The provider hierarchy (HelmetProvider → CookiesProvider → Redux Provider → App) is correctly ordered, ensuring all components have access to the necessary contexts.

client/components/organization-wrapper/organization-wrapper.test.js (7)

10-46: Well-structured pre-import mocking pattern.

The pre-import mocking of getConfig, loadTranslation, and needsVerify before importing OrganizationWrapper is the correct approach to ensure mocks are in place before the module loads. The mock implementations provide reasonable default values for testing.


94-132: Excellent test helper for consistent rendering.

The renderWithRouter helper correctly wraps components with all required providers (HelmetProvider, Redux Provider, MemoryRouter) and constructs a properly mocked Redux store. This ensures consistent test setup across all test cases and mirrors the actual application structure.


142-244: Solid rendering tests with proper async handling.

The rendering tests correctly use waitFor to handle React 18's concurrent rendering and async component loading. The approach of verifying props for CSS/JS files (lines 213-216, 240-243) is pragmatic given that react-helmet-async manages the actual DOM injection.


274-351: Comprehensive lifecycle and rerender testing.

The test properly validates componentDidUpdate behavior by rendering with all required providers during rerender operations. The handling of invalid params with console.error mocking is correct, and the typo fix for lastConsoleOutput has been properly addressed.


397-452: Thorough language switching test.

The test correctly captures the initial call count before triggering a language change, then verifies that loadTranslation is called again. The proper cleanup of localStorage prevents test pollution.


568-605: Well-designed mountComponent helper for route testing.

The mountComponent helper provides flexible initialization with custom initialEntries for MemoryRouter, making it ideal for testing different route scenarios. The store configuration properly handles default values and overrides.


700-700: Remove this suggestion—the selector is correct.

The test selector #not-foud-404 is not a typo. The element in client/components/404/404.js (line 19) is defined with id="not-foud-404", so the test assertion accurately reflects the current implementation and will pass. While "foud" appears to be a misspelling within the component itself, the test correctly targets the actual element ID.

Likely an incorrect or invalid review comment.

client/components/mobile-phone-change/mobile-phone-change.test.js (8)

232-247: Well-tested phone number population from userData.

The test properly waits for the phone input to be populated asynchronously from userData using waitFor, and correctly normalizes the formatted phone number for assertion by removing whitespace and hyphens. This accounts for PhoneInput's formatting behavior.


415-417: Cancel button assertion properly addressed.

The test now explicitly asserts that the cancel button exists before clicking it, addressing the past review comment about the conditional check that could mask failures. This ensures the test will fail if the cancel button is unexpectedly missing.


165-165: Good test isolation with per-test history instance.

Creating historyMock inside mountComponent ensures each test gets a fresh history instance, preventing state leakage between tests. This properly addresses the past review comment about shared history objects.


288-312: Comprehensive lazy-loading test coverage.

This test properly verifies that PhoneInput loads asynchronously, checks its attributes (id, placeholder), and validates that the onChange handler works correctly. The value normalization for assertion accounts for formatting.


529-537: Good pattern for testing redirect behavior.

The test correctly verifies redirect behavior by asserting the form is NOT rendered when mobile_phone_verification is disabled. This is the right approach for testing that a component renders a <Navigate> component instead of its normal content.


234-234: > Likely an incorrect or invalid review comment.


185-185: Incorrect router component with invalid props.

BrowserRouter (aliased as Router on line 11) does not accept location or navigator props. These props are specific to MemoryRouter or the low-level Router from react-router. This will cause React to issue warnings and the props will be ignored, potentially breaking routing assertions in tests.

🔎 Proposed fix
-      <Router location={historyMock.location} navigator={historyMock}>
+      <MemoryRouter location={historyMock.location} navigator={historyMock}>
         <Routes>
           <Route path="/test-org-2/status" element={<StatusMock />} />
           <Route path="*" element={<MobilePhoneChange {...props} />} />
         </Routes>
-      </Router>
+      </MemoryRouter>

Likely an incorrect or invalid review comment.


400-400: No action needed — console.error logging is intentional.

The test assertion correctly validates that console.error is called when errors occur. The component deliberately logs errors through the logError() utility function, which explicitly calls console.error() when the error response exists. The eslint-disable comment in that utility confirms this is approved logging behavior, not an unintended side-effect.

Likely an incorrect or invalid review comment.

@PRoIISHAAN PRoIISHAAN force-pushed the fix-reactversion-migration branch from ec9e917 to 80d13ad Compare January 2, 2026 14:34
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: 6

♻️ Duplicate comments (5)
client/components/password-reset/password-reset.test.js (2)

109-121: Test name is misleading - already flagged in previous review.

The test is named "should render 2 inputs" but only verifies 1 input and 1 button. This was already noted in a previous review comment.


225-230: Component needs async cleanup to avoid act() warnings - already flagged.

This workaround for act() warnings was already addressed in a previous review. The underlying issue is that the PasswordReset component lacks unmount guards for async setState calls.

client/components/payment-process/payment-process.test.js (1)

170-170: Use mockResolvedValue for async validateToken.

Since validateToken is async, use mockResolvedValue(true) instead of mockReturnValue(true) for semantic correctness. This issue appears at lines 170, 192, 213, 242, 266, 303, 337, 374, and 441.

client/components/registration/registration.test.js (2)

157-183: Duplicate mountComponent implementation.

This is the same pattern duplicated in subscriptions.test.js and test-utils.js. Consider consolidating into a single shared utility.


784-794: Placeholder test still doesn't verify country selection behavior.

Per previous review feedback, this test is titled "should set country when selectedCountry is executed" but only verifies that a form exists. The comment acknowledges this is tested "implicitly" but provides no actual assertion on country selection.

🔎 Suggested fix

Either implement actual country selection testing or rename the test to reflect what it actually tests:

-  it("should set country when selectedCountry is executed", async () => {
+  it("should render registration form correctly", async () => {
     const {container} = mountComponent(props);
 
     await waitFor(() => {
       expect(container.querySelector("#registration-form")).toBeInTheDocument();
     });
 
-    // The selectedCountry method is tested implicitly through user interaction
-    // This test verifies the component structure is correct
     expect(container.querySelector("form")).toBeInTheDocument();
   });
🧹 Nitpick comments (22)
client/components/password-reset/password-reset.test.js (1)

175-232: Consider splitting multi-scenario test for better isolation.

This test covers 3 distinct scenarios (error with detail, error with non_field_errors, success) in a single test case. While functional, this makes failures harder to diagnose and violates the single-assertion principle. The shared state between scenarios (e.g., cumulative toHaveBeenCalledTimes checks) can mask issues.

Additionally, the await tick() calls before await waitFor() are redundant since waitFor already handles async state updates.

🔎 Suggestion: Remove redundant tick() calls
     // Test 2: Error with non_field_errors
     fireEvent.submit(form);
-    await tick();
-
     await waitFor(() => {
       expect(spyToastError).toHaveBeenCalledTimes(2);
     });

The same applies to other tick() + waitFor() combinations.

client/components/payment-process/payment-process.test.js (2)

87-97: Consider adding isLoading to LoadingContext value.

The LoadingContext.Provider only passes setLoading but the context typically includes an isLoading state as well. If PaymentProcess or any child component reads isLoading from context, tests may behave unexpectedly.

🔎 Suggested fix
 const renderWithProviders = (component, mockSetLoading = null) => {
   const setLoading = mockSetLoading || jest.fn();
 
   return render(
     <Provider store={createMockStore()}>
-      <LoadingContext.Provider value={{setLoading}}>
+      <LoadingContext.Provider value={{setLoading, isLoading: false}}>
         <MemoryRouter>{component}</MemoryRouter>
       </LoadingContext.Provider>
     </Provider>,
   );
 };

373-408: Use mockMessageEvents helper for consistency.

This test duplicates the window event mocking pattern that mockMessageEvents already provides. Additionally, line 407 only restores addEventListener but not removeEventListener, creating an inconsistent state.

🔎 Suggested refactor
   it("should handle postMessage for setHeight", async () => {
     props = createTestProps({userData: responseData});
     validateToken.mockReturnValue(true);
 
-    const events = {};
-    const originalAddEventListener = window.addEventListener;
-
-    window.addEventListener = jest.fn((event, callback) => {
-      events[event] = callback;
-    });
+    const eventMock = mockMessageEvents();
 
     renderWithProviders(<PaymentProcess {...props} />);
 
     await tick();
 
     // Simulate setHeight message
-    expect(events.message).toBeDefined();
+    expect(eventMock.events.message).toBeDefined();
     await act(async () => {
-      events.message({
+      eventMock.events.message({
         data: {
           type: "setHeight",
           message: 800,
         },
         origin: window.location.origin,
       });
     });
 
     await tick();
 
     // Check if iframe height was updated
     const iframe = within(document.body).getByTitle("owisp-payment-iframe");
     expect(iframe).toBeInTheDocument();
     expect(iframe).toHaveAttribute("height", "800");
 
-    window.addEventListener = originalAddEventListener;
+    eventMock.restore();
   });
client/components/registration/subscriptions.test.js (3)

203-229: Consider consolidating with shared mountComponent from test-utils.js.

This mountComponent function duplicates the implementation in client/components/registration/test-utils.js. Consider importing the shared utility to reduce code duplication and ensure consistency across test files.

🔎 Proposed refactor
+import {mountComponent} from "./test-utils";
-
-const mountComponent = (passedProps) => {
-  const config = getConfig(passedProps.orgSlug || "default");
-  const mockedStore = {
-    subscribe: () => {},
-    dispatch: () => {},
-    getState: () => ({
-      organization: {
-        configuration: {
-          ...config,
-          components: {
-            ...config.components,
-            contact_page: config.components.contact_page || {},
-          },
-        },
-      },
-      language: passedProps.language || "en",
-    }),
-  };
-
-  return render(
-    <Provider store={mockedStore}>
-      <MemoryRouter>
-        <Registration {...passedProps} />
-      </MemoryRouter>
-    </Provider>,
-  );
-};

247-253: Mock restoration order may cause issues.

In afterEach, jest.clearAllMocks() is called before re-setting up the getConfig mock. However, jest.restoreAllMocks() also runs, which could undo the mock re-setup if order matters. Consider restructuring:

🔎 Proposed refactor
   afterEach(() => {
-    jest.clearAllMocks();
     jest.restoreAllMocks();
+    jest.clearAllMocks();
     // Re-setup the getConfig mock after clearing
     getConfig.mockImplementation(() => mockConfig);
   });

Alternatively, if the mock re-setup is needed for subsequent tests, ensure it happens in beforeEach rather than afterEach.


317-322: Console warning assertion is fragile.

The test checks for act(...) warnings in console output, but this approach is fragile and couples tests to React's internal warning messages. If warnings are expected during async state updates, consider suppressing them explicitly or restructuring the test to avoid them.

🔎 Proposed improvement
-    // RTL may produce act() warnings which are expected for async state updates
-    const hasOnlyActWarnings =
-      lastConsoleOutuput === null ||
-      (typeof lastConsoleOutuput === "string" &&
-        lastConsoleOutuput.includes("act(...)"));
-    expect(hasOnlyActWarnings).toBe(true);
+    // If act() warnings are expected, the test should focus on
+    // the actual behavior rather than console output

Focus assertions on the actual DOM state and component behavior rather than implementation details of React's warning system.

client/components/password-change/password-change.test.js (3)

131-140: Redundant mock clearing calls.

axios.mockClear() and axios.mockReset() are both called, but mockReset() already clears the mock and also resets the implementation. Only mockReset() is needed here.

🔎 Proposed fix
   beforeEach(() => {
     jest.clearAllMocks();
-    axios.mockClear();
     axios.mockReset();
     props = createTestProps();
   });

155-247: Test "handleSubmit" covers too many scenarios in one test case.

This single test sequentially tests 4 different scenarios (password mismatch, same-as-current, server error, success). This violates the single-assertion-per-test principle and makes debugging failures harder. Consider splitting into separate it blocks for each scenario.

🔎 Proposed structure
describe("handleSubmit", () => {
  it("should show error when passwords don't match", async () => { /* ... */ });
  it("should show error when new password matches current", async () => { /* ... */ });
  it("should handle server error", async () => { /* ... */ });
  it("should navigate to status on success", async () => { /* ... */ });
});

308-331: Password toggle test relies on filtering buttons by type.

The filter logic !btn.type || btn.type === "button" is fragile. Consider adding data-testid attributes to toggle buttons in the component for more reliable selection.

🔎 Proposed improvement
-    // Find all buttons with role="button" (password toggle icons)
-    const allButtons = screen.getAllByRole("button");
-    // Filter out the submit button to get only toggle buttons
-    const toggleButtons = allButtons.filter(
-      (btn) => !btn.type || btn.type === "button",
-    );
+    // Use data-testid for reliable selection
+    const toggleButtons = screen.getAllByTestId("password-toggle-icon");

This would require adding data-testid="password-toggle-icon" to the PasswordToggleIcon component.

client/components/registration/registration.test.js (2)

256-385: Large test "handleSubmit" tests 6 scenarios sequentially.

Similar to the password-change test, this combines multiple distinct scenarios (password mismatch, API field errors, server error, gateway timeout, success, billing error) into one test. This makes failures harder to diagnose and violates test isolation principles.

🔎 Proposed structure
describe("handleSubmit", () => {
  it("should show error for password mismatch", async () => { /* Test 1 */ });
  it("should display API field errors", async () => { /* Test 2 */ });
  it("should handle server error", async () => { /* Test 3 */ });
  it("should handle gateway timeout", async () => { /* Test 4 */ });
  it("should authenticate on success", async () => { /* Test 5 */ });
  it("should display billing errors", async () => { /* Test 6 */ });
});

518-523: Test "handleResponse" only takes a snapshot without meaningful assertions.

The test description suggests testing handleResponse behavior, but the implementation only renders the component and takes a snapshot. Consider adding behavioral assertions or removing this test if it doesn't add value.

client/components/password-confirm/password-confirm.test.js (1)

84-111: renderWithProviders signature differs from other test files.

This implementation takes props as the argument and internally creates the component, while other files (e.g., password-change.test.js, registration.test.js) take a component as the argument. Consider standardizing the pattern across test files for consistency.

🔎 Option A: Keep current pattern but document the difference

The current pattern is valid and slightly more concise for this file since it only tests one component. Add a comment explaining the pattern.

+// Note: This renderWithProviders takes props directly since we only test PasswordConfirm
 const renderWithProviders = (props) => {
🔎 Option B: Standardize with other files
-const renderWithProviders = (props) => {
+const createMockStore = (props) => {
   const state = {
     organization: {
       configuration: {
         ...props.configuration,
         components: {
           ...props.configuration.components,
           contact_page: props.configuration.components.contact_page || {},
         },
       },
     },
     language: props.language,
   };

-  const mockedStore = {
+  return {
     subscribe: () => {},
     dispatch: () => {},
     getState: () => state,
   };
+};

-  return render(
+const renderWithProviders = (component, props) =>
+  render(
-    <Provider store={mockedStore}>
+    <Provider store={createMockStore(props)}>
       <MemoryRouter>
-        <PasswordConfirm {...props} />
+        {component}
       </MemoryRouter>
     </Provider>,
   );
-};
client/components/organization-wrapper/organization-wrapper.test.js (3)

142-244: Consider using RTL queries over container.querySelector.

While the current implementation works and you've acknowledged the linting concerns, using RTL queries like queryByRole, queryByTestId, or queryByText would align better with RTL best practices and improve test maintainability.

Example refactor

Add test IDs to the actual components:

// In the OrganizationWrapper component
<div className="app-container" data-testid="app-container">
<div className="org-wrapper-not-found" data-testid="org-not-found">
<div className="loader-container" data-testid="loader">

Then update tests:

-    // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
-    expect(container.querySelector(".app-container")).not.toBeInTheDocument();
+    expect(queryByTestId("app-container")).not.toBeInTheDocument();

568-605: Consider consolidating render helpers.

You have two similar helpers: renderWithRouter (line 94) and mountComponent (line 568). Both wrap components with providers and create mocked Redux stores. Consolidating these into a single, configurable helper would reduce duplication.

Example consolidation
+const renderWithProviders = (props, options = {}) => {
+  const {initialEntries = [props.location?.pathname || "/"]} = options;
+  
   const mockedStore = {
     subscribe: () => {},
     dispatch: () => {},
     getState: () => ({
       organization: {
         configuration: {
-          ...defaultConfig,
+          ...props.organization?.configuration,
           components: {
-            ...components,
+            ...props.organization?.configuration?.components,
             // ... merge logic
           },
         },
       },
       language: props.language || "en",
     }),
   };

   return render(
     <HelmetProvider>
       <Provider store={mockedStore}>
         <MemoryRouter initialEntries={initialEntries}>
           <OrganizationWrapper {...props} />
         </MemoryRouter>
       </Provider>
     </HelmetProvider>,
   );
 };

704-716: Use .toBeInTheDocument() for consistency.

Lines 712 and 714 use .toBeTruthy() to check element presence. For consistency with the rest of the test file and RTL best practices, use .toBeInTheDocument() instead.

🔎 Proposed change
     await waitFor(() => {
       // Header and footer should be present
       // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
       expect(
         container.querySelector(".header-container, .header-mobile"),
-      ).toBeTruthy();
+      ).toBeInTheDocument();
       // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
-      expect(container.querySelector(".footer-container")).toBeTruthy();
+      expect(container.querySelector(".footer-container")).toBeInTheDocument();
     });
client/components/mobile-phone-change/mobile-phone-change.test.js (5)

69-72: Mock always returns same config regardless of argument.

The getConfig mock at line 71 always returns mockConfig, but createTestProps at line 82 accepts a configName parameter (defaulting to "test-org-2") and calls getConfig(configName). This creates a mismatch: tests may pass different config names expecting different configurations, but they all receive the same mockConfig.

Consider either removing the configName parameter from createTestProps or updating the mock to return different configs based on the argument.

🔎 Proposed fix

Option 1: Remove unused parameter

-const createTestProps = (props, configName = "test-org-2") => {
-  const conf = getConfig(configName);
+const createTestProps = (props) => {
+  const conf = getConfig("default");

Option 2: Make mock aware of different configs (if tests need different configs)

 jest.mock("../../utils/get-config", () => ({
   __esModule: true,
-  default: jest.fn(() => mockConfig),
+  default: jest.fn((slug) => {
+    if (slug === "test-org-2") {
+      return {...mockConfig, slug: "test-org-2"};
+    }
+    return mockConfig;
+  }),
 }));

Also applies to: 82-83


108-132: Inconsistent store creation patterns across helpers.

The test file uses two different approaches to create mock Redux stores:

  • createMockStore() (lines 108-132) used by renderWithProviders
  • Inline mockedStore (lines 166-181) used by mountComponent

While both work, this inconsistency makes the test suite harder to maintain. If the store structure needs to change, you'll need to update multiple locations.

🔎 Proposed fix

Consolidate to use createMockStore() in both helpers:

 const mountComponent = (props) => {
   const historyMock = createMemoryHistory();
-  const mockedStore = {
-    subscribe: () => {},
-    dispatch: () => {},
-    getState: () => ({
-      organization: {
-        configuration: {
-          ...props.configuration,
-          components: {
-            ...props.configuration.components,
-            contact_page: props.configuration.components.contact_page || {},
-          },
-        },
-      },
-      language: props.language,
-    }),
-  };
+  const mockedStore = createMockStore();

   return render(
     <Provider store={mockedStore}>

If mountComponent needs props-specific configuration, extend createMockStore to accept parameters instead of duplicating the logic.

Also applies to: 164-193


207-216: Console error spy is set up but rarely checked.

The consoleErrorSpy is created in beforeEach for every test (lines 213-215), but it's only explicitly checked in one test (line 400). The spy at line 420 checks that console.error was NOT called, which is good, but most tests don't verify console state at all.

This means unexpected console errors in other tests would be silently ignored, potentially hiding issues.

Suggested approach

Option 1: Add a general assertion in afterEach to catch unexpected errors:

   afterEach(() => {
+    // Fail if any test logged unexpected errors
+    if (consoleErrorSpy.mock.calls.length > 0) {
+      console.warn('Unexpected console.error calls:', consoleErrorSpy.mock.calls);
+    }
     jest.clearAllMocks();
     jest.restoreAllMocks();

Option 2: Only create the spy in tests that explicitly need it:

   beforeEach(() => {
     jest.clearAllMocks();
     axios.mockReset();
     props = createTestProps();
     validateToken.mockClear();
-    // Spy on console.error to track calls
-    consoleErrorSpy = jest
-      .spyOn(global.console, "error")
-      .mockImplementation(() => {});
   });

Then add the spy locally in the tests that check it (lines 370, 403).

Also applies to: 400-400, 420-420


218-223: Redundant mock reset in afterEach.

Line 222 manually resets the getConfig mock after jest.clearAllMocks() (line 219) has already been called. Since jest.clearAllMocks() clears all mock calls and instances, the explicit getConfig.mockImplementation() reset appears redundant.

If this manual reset is required for tests to pass, it suggests the mock may not be properly configured or there's an unexpected interaction.

Consider removing the redundant reset:

   afterEach(() => {
     jest.clearAllMocks();
     jest.restoreAllMocks();
-    // Re-setup the getConfig mock after clearing
-    getConfig.mockImplementation(() => mockConfig);
   });

If tests fail after removing it, investigate why the mock state is problematic rather than working around it.


288-333: Consider consolidating similar PhoneInput tests.

The tests at lines 288-312 and 314-333 both verify PhoneInput rendering and onChange behavior with nearly identical assertions. The primary difference is that one waits for lazy loading while the other checks the fallback state.

While both tests are valid, the overlap in assertions (checking attributes, placeholder, onChange behavior) creates maintenance burden.

Suggested approach

Consider either:

  1. Focus each test on its unique aspect:

    • First test: Focus only on lazy loading completion
    • Second test: Focus only on fallback rendering
  2. Or combine into one test that checks both states:

it("should render fallback then lazy-load PhoneInput with correct handlers", async () => {
  renderWithProviders(<MobilePhoneChange {...props} />);

  // Check fallback state
  const fallbackInput = screen.getByRole("textbox", {name: /mobile phone number/i});
  expect(fallbackInput).toHaveClass("form-control", "input");
  
  // Wait for lazy load
  await waitFor(() => {
    const phoneInput = screen.getByRole("textbox", {name: /mobile phone number/i});
    expect(phoneInput).toHaveAttribute("id", "phone-number");
  });
  
  // Test onChange once after loading
  fireEvent.change(phoneInput, {target: {value: "+911234567890", name: "phone_number"}});
  expect(phoneInput.value.replace(/[\s-]/g, "")).toContain("911234567890");
});
client/components/mobile-phone-verification/mobile-phone-verification.test.js (2)

256-295: Consider extracting the sequential mock pattern.

The method-based axios mocking pattern (checking config.method and returning different responses) appears multiple times throughout the file. While the current implementation is clear and well-commented, extracting it into a reusable helper could improve maintainability.

💡 Example helper function
const createSequentialAxiosMock = (responses) => {
  const callCounts = {};
  
  return (config) => {
    const method = config.method?.toUpperCase();
    const key = `${method}`;
    
    callCounts[key] = (callCounts[key] || 0) + 1;
    
    const response = responses[method]?.[callCounts[key] - 1];
    
    if (!response) return undefined;
    
    return response.error 
      ? Promise.reject(response.error) 
      : Promise.resolve(response.success);
  };
};

// Usage:
axios.mockImplementation(createSequentialAxiosMock({
  GET: [
    { error: { response: { status: 404, statusText: "NOT FOUND", data: { non_field_errors: ["Not Found"] } } } }
  ],
  POST: [
    { success: { status: 201, statusText: "CREATED", data: null } }
  ]
}));

412-480: Consider using mockResolvedValueOnce for sequential calls.

The postCallCount closure pattern works correctly, but RTL typically favors mockResolvedValueOnce() chaining for sequential mock responses, which can be more explicit.

💡 Alternative approach example
axios
  .mockResolvedValueOnce({
    status: 200,
    data: { active: false }
  })
  .mockResolvedValueOnce({
    status: 201,
    statusText: "CREATED",
    data: null
  })
  .mockResolvedValueOnce({
    status: 200,
    statusText: "OK",
    data: null
  });

Note: This approach doesn't differentiate by HTTP method, so it may not fit all scenarios in this file where GET/POST need different handling.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ec9e917 and 80d13ad.

⛔ Files ignored due to path filters (2)
  • client/components/registration/__snapshots__/registration.test.js.snap is excluded by !**/*.snap
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (13)
  • client/components/mobile-phone-change/mobile-phone-change.test.js
  • client/components/mobile-phone-verification/mobile-phone-verification.test.js
  • client/components/organization-wrapper/organization-wrapper.test.js
  • client/components/password-change/password-change.test.js
  • client/components/password-confirm/password-confirm.test.js
  • client/components/password-reset/password-reset.test.js
  • client/components/payment-process/payment-process.test.js
  • client/components/payment-status/payment-status.test.js
  • client/components/registration/registration.js
  • client/components/registration/registration.test.js
  • client/components/registration/subscriptions.test.js
  • client/utils/tick.js
  • server/app.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • client/components/registration/registration.js
🧰 Additional context used
🧬 Code graph analysis (3)
client/components/password-reset/password-reset.test.js (3)
client/components/password-reset/password-reset.js (1)
  • PasswordReset (18-154)
client/utils/__mocks__/load-translation.js (1)
  • loadTranslation (17-27)
client/utils/tick.js (1)
  • tick (1-12)
client/components/password-change/password-change.test.js (1)
client/components/password-change/password-change.js (1)
  • PasswordChange (22-251)
client/components/registration/subscriptions.test.js (3)
client/utils/__mocks__/get-config.js (1)
  • getConfig (11-16)
client/components/registration/test-utils.js (2)
  • mountComponent (8-38)
  • mockedStore (12-27)
client/utils/tick.js (1)
  • tick (1-12)
🪛 Gitleaks (8.30.0)
client/components/payment-status/payment-status.test.js

[high] 129-129: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 130-130: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

client/components/registration/subscriptions.test.js

[high] 104-104: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 105-105: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Tests and Coverage
🔇 Additional comments (27)
client/components/password-reset/password-reset.test.js (4)

10-32: Mock setup follows correct pattern.

The mocks are correctly declared before imports, which is essential for Jest's module mocking to work properly. The eslint-disable import/first pragma is appropriately scoped.


54-85: Mock store and render helper are correctly implemented.

The minimal Redux store mock provides the necessary interface. Consider extracting createMockStore and renderWithProviders to a shared test utility file if similar patterns are used across other test files in this PR.


234-241: LGTM!

The setTitle test is clean and correctly verifies the expected behavior.


265-287: LGTM!

The error message test correctly verifies the error state is applied to the input field.

client/components/payment-process/payment-process.test.js (3)

262-299: LGTM!

Good test coverage for the postMessage event listener lifecycle, including proper cleanup verification with unmount() and restore().


410-435: LGTM!

Good test structure with proper spy usage, mockResolvedValue for async validation, and cleanup with mockRestore().


437-455: LGTM!

Good test coverage for verifying token validation is invoked on mount with the correct arguments.

client/components/payment-status/payment-status.test.js (4)

1-36: Excellent migration to React Testing Library.

The mock setup follows Jest best practices by defining all mocks before importing modules. The get-config mock provides the necessary translation structure, and the eslint overrides correctly acknowledge the import ordering requirement.


38-83: Well-structured test helpers.

The renderWithProviders pattern encapsulates Redux and Router context, ensuring consistent test setup. The mock store provides realistic organization configuration and language state.


114-131: Test setup and teardown logic is correct.

The console mocking prevents test output noise, and the optional chaining on mockRestore safely handles cases where the mock might not exist.

Note: The static analysis warnings on lines 129-130 are false positives. These lines invoke Jest's mockRestore method using optional chaining, not API keys.


133-528: Comprehensive test coverage with RTL best practices.

The test suite thoroughly validates:

  • Various payment status flows (failed, draft, success, verified, unverified)
  • Conditional logic for payment_requires_internet
  • Edge cases (invalid tokens, wrong payment methods, unexpected statuses)
  • User interactions (logout, proceed with payment)

The use of accessible queries (getByRole), waitFor for async behavior, and strategic mock resets (line 283) demonstrates solid RTL patterns. The verbose comment on lines 273-274 helpfully documents non-obvious behavior.

client/components/registration/subscriptions.test.js (2)

104-106: Static analysis false positive - test fixture data, not real secrets.

The Gitleaks warnings on lines 104-105 are false positives. These are clearly test fixture values (responseData) used for mocking API responses, not actual API keys or secrets. The values are deterministic test data that doesn't expose any real credentials.


269-298: Well-structured async test with proper RTL patterns.

Good use of waitFor for async assertions and proper DOM queries using getByTestId and getByRole. The test correctly waits for plans to load before making assertions.

client/components/password-change/password-change.test.js (1)

347-365: SAML and Social Login redirect tests are well-structured.

Clean separation of test cases for different authentication methods. The tests correctly verify that the form is not rendered when redirect should occur.

client/components/registration/registration.test.js (1)

85-88: Good practice: Deep cloning mock config for test isolation.

Using JSON.parse(JSON.stringify(mockConfig)) ensures each test gets a fresh copy of the config, preventing mutation side effects between tests.

client/components/password-confirm/password-confirm.test.js (3)

228-248: Well-organized nested describe block for form submission scenarios.

Good use of nested describe with its own beforeEach/afterEach for form submission tests. The consoleErrorSpy setup is properly scoped and cleaned up.


410-465: Excellent password toggle tests using data-testid.

These tests correctly use getAllByTestId("password-toggle-icon") for reliable element selection, which is the recommended pattern. This is a good example for other test files to follow.


467-494: Good coverage for error clearing on successful submit.

The test verifies that error classes are removed after successful submission, which is an important UX behavior to validate.

client/components/organization-wrapper/organization-wrapper.test.js (4)

1-46: LGTM! Clean migration to RTL with proper mock setup.

The imports and mocking structure follows Jest best practices by placing mocks before the actual imports, and the mock implementations provide appropriate test data.


92-132: LGTM! Well-structured render helper.

The renderWithRouter helper properly wraps the component with all required providers (HelmetProvider, Redux Provider, MemoryRouter) and creates a properly structured mocked Redux store with sensible fallbacks to defaultConfig.


247-478: LGTM! Interaction tests properly handle providers and async behavior.

The tests correctly:

  • Set up console error spies with proper cleanup
  • Wrap all rerenders with the same providers used in initial render
  • Handle async state changes with waitFor
  • Test language switching and localStorage interactions appropriately

Past review feedback has been properly addressed.


480-561: LGTM! Comprehensive coverage of authentication states.

The tests properly cover unauthenticated and unverified user scenarios with appropriate mock setup and cleanup.

client/components/mobile-phone-verification/mobile-phone-verification.test.js (5)

20-40: LGTM! Comprehensive mock setup.

The mock configuration properly provides all necessary fields for the test scenarios, and the mock is correctly placed before imports.


108-133: LGTM! Clean snapshot test setup.

The mock setup correctly returns data.active structure, and the snapshot test properly uses RTL rendering patterns.


160-182: LGTM! Excellent use of accessible queries.

The test properly waits for async rendering and validates UI elements using role-based queries, which is a best practice for RTL.


650-691: LGTM! Comprehensive corner case coverage.

The tests properly verify that the component correctly handles edge cases (already verified users and disabled feature) by not making unnecessary API calls.


1-691: Excellent RTL migration with comprehensive test coverage.

This test file demonstrates strong adherence to React Testing Library best practices:

  • Accessible queries prioritized (getByRole, getByText)
  • Proper async handling with waitFor
  • Component integration testing rather than implementation details
  • Good test isolation with proper setup/teardown

The migration successfully addresses the past review comment about mock data structure and provides comprehensive coverage of standard flows and corner cases.

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

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 80d13ad and 4f520b7.

📒 Files selected for processing (1)
  • client/components/mobile-phone-change/mobile-phone-change.js
🧰 Additional context used
🧬 Code graph analysis (1)
client/components/mobile-phone-change/mobile-phone-change.js (4)
client/components/mobile-phone-change/mobile-phone-change.test.js (1)
  • userData (195-201)
client/components/mobile-phone-verification/mobile-phone-verification.test.js (1)
  • userData (99-106)
client/components/organization-wrapper/organization-wrapper.test.js (1)
  • userData (48-61)
client/components/login/login.test.js (1)
  • userData (131-144)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Tests and Coverage
🔇 Additional comments (2)
client/components/mobile-phone-change/mobile-phone-change.js (2)

95-95: LGTM: Appropriate toast channel change.

Changing from toast.info to toast.success better reflects the semantic meaning of successfully sending a token.


139-139: LGTM: Good accessibility enhancement.

Adding aria-label to the form improves screen reader accessibility by clearly identifying the form's purpose.

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

♻️ Duplicate comments (2)
client/components/mobile-phone-change/mobile-phone-change.test.js (2)

134-142: LoadingContext mock shape does not match actual implementation.

The test defines LoadingContext with {setLoading, isLoading}, but the actual implementation in client/utils/loading-context.js uses {setLoading, getLoading}. This mismatch means tests pass with an incorrect provider shape that differs from production.

🔎 Proposed fix
 const createLoadingContextValue = () => ({
   setLoading: jest.fn(),
-  isLoading: false,
+  getLoading: jest.fn(() => false),
 });

 const LoadingContext = React.createContext({
   setLoading: () => {},
-  isLoading: false,
+  getLoading: () => false,
 });

Alternatively, import the actual LoadingContext from client/utils/loading-context.js to ensure consistency.


144-154: renderWithProviders uses a different LoadingContext than the component.

The renderWithProviders helper creates a local LoadingContext (line 150), but the actual MobilePhoneChange component imports and uses LoadingContext from client/utils/loading-context.js. Wrapping with a locally-defined context won't provide the context value to the component since they are different context instances.

If the component needs loading context in tests, you should import and use the actual LoadingContext from the utils module.

🔎 Proposed fix
+import {LoadingContext} from "../../utils/loading-context";

-const LoadingContext = React.createContext({
-  setLoading: () => {},
-  isLoading: false,
-});
-
 const renderWithProviders = (
   component,
   loadingContextValue = createLoadingContextValue(),
 ) =>
   render(
     <Provider store={createMockStore()}>
       <LoadingContext.Provider value={loadingContextValue}>
         <MemoryRouter>{component}</MemoryRouter>
       </LoadingContext.Provider>
     </Provider>,
   );
🧹 Nitpick comments (1)
client/components/mobile-phone-change/mobile-phone-change.test.js (1)

275-277: Consider replacing tick() with RTL's waitFor for consistency.

The test uses a custom tick() utility followed by assertions. With React Testing Library, the idiomatic approach is to wrap assertions in waitFor to handle async state updates. This is especially important with React 18's concurrent rendering.

🔎 Proposed refactor
-    await tick();
-
-    expect(toast.success).toHaveBeenCalledTimes(1);
+    await waitFor(() => {
+      expect(toast.success).toHaveBeenCalledTimes(1);
+    });

This pattern should be applied consistently across all tests that use tick() followed by assertions (lines 275-277, 356-358, 389-391).

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4f520b7 and cfd4dc9.

📒 Files selected for processing (1)
  • client/components/mobile-phone-change/mobile-phone-change.test.js
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Tests and Coverage
🔇 Additional comments (3)
client/components/mobile-phone-change/mobile-phone-change.test.js (3)

288-312: Approve PhoneInput lazy loading test.

This test correctly validates the lazy loading behavior of PhoneInput, checking for element presence with waitFor and verifying input attributes and onChange handling. The use of renderWithProviders is appropriate here since routing is not needed for this specific test.


403-421: Cancel button test now properly asserts element presence.

The test correctly uses expect(cancelButton).toBeInTheDocument() before clicking, addressing the previous review comment about conditional assertions masking failures.


164-165: History mock isolation addressed.

The historyMock is now correctly created inside mountComponent, ensuring each test gets a fresh history instance. This addresses the previous review comment about shared history state leaking between tests.

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 (2)
browser-test/js-file-inject.test.js (2)

23-25: Consider explicit waits instead of arbitrary delays.

The driver.sleep(1000) calls introduce fixed delays that may be too short (causing flakiness) or too long (slowing tests). Selenium best practices recommend explicit waits for specific conditions instead.

🔎 Example of explicit wait alternative
// Wait for a specific condition indicating the page is fully loaded
await driver.wait(
  async () => {
    const scripts = await driver.findElements(By.css('script'));
    return scripts.length > 0;
  },
  5000,
  'Scripts not loaded in time'
);

Alternatively, if React 18 concurrent rendering requires these specific delays during the migration phase, consider documenting this rationale in a comment for future maintainability.

Also applies to: 52-54


50-74: Extract duplicated script collection logic into a helper function.

The script source collection logic (lines 27-44 and 56-73) is duplicated with identical structure. Extracting this into a reusable helper function would improve maintainability and reduce the risk of inconsistent updates.

🔎 Proposed helper function

Add to browser-test/utils.js:

export const getScriptSources = async (driver) => {
  const scripts = await getElementsByCss(driver, "script");
  
  // Get all script sources in a single pass to avoid iterating over potentially stale elements
  const scriptSources = await Promise.all(
    scripts.map(async (script) => {
      try {
        return await script.getAttribute("src");
      } catch (error) {
        // If element becomes stale, return null and filter it out
        return null;
      }
    }),
  );
  
  // Filter out null values from stale elements
  return scriptSources.filter((src) => src !== null);
};

Then simplify the test:

-    let scriptSources = [];
-    // Re-fetch scripts to avoid stale element reference
-    let scripts = await getElementsByCss(driver, "script");
-
-    // Get all script sources in a single pass to avoid iterating over potentially stale elements
-    scriptSources = await Promise.all(
-      scripts.map(async (script) => {
-        try {
-          return await script.getAttribute("src");
-        } catch (error) {
-          // If element becomes stale, return null and filter it out
-          return null;
-        }
-      }),
-    );
-
-    // Filter out null values from stale elements
-    scriptSources = scriptSources.filter((src) => src !== null);
+    let scriptSources = await getScriptSources(driver);
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cfd4dc9 and d2c7499.

📒 Files selected for processing (1)
  • browser-test/js-file-inject.test.js
🧰 Additional context used
🧬 Code graph analysis (1)
browser-test/js-file-inject.test.js (1)
browser-test/utils.js (6)
  • getElementsByCss (41-50)
  • getElementsByCss (41-50)
  • initialData (52-52)
  • initialData (52-52)
  • urls (75-92)
  • urls (75-92)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Tests and Coverage
  • GitHub Check: QA-Checks
🔇 Additional comments (1)
browser-test/js-file-inject.test.js (1)

27-44: Good robustness improvement for handling stale elements.

The refactor to collect all script sources in a single pass using Promise.all with try/catch effectively handles stale element references that can occur during page transitions or React 18's concurrent rendering. The filtering of null values ensures clean results.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
client/app.js (1)

13-13: Remove unused import.

The history import is no longer used after removing the history prop from the Router component at Line 27.

🔎 Proposed fix
-import history from "./utils/history";
♻️ Duplicate comments (2)
client/components/modal/modal.test.js (1)

282-305: Test remains redundant despite past feedback.

This test was previously flagged as not testing backdrop-specific functionality and marked as addressed. However, the current implementation still only verifies that the modal renders with content (line 304), which duplicates coverage from the earlier rendering tests (lines 41-67). Consider removing this test unless you plan to add actual backdrop interaction testing (e.g., verifying CSS classes, testing click-outside behavior if implemented).

client/components/registration/registration.test.js (1)

783-793: Test doesn't verify the behavior described in its name.

The test "should set country when selectedCountry is executed" only verifies that the registration form exists. The comment acknowledges the actual country selection method is "tested implicitly through user interaction," but no such interaction is present. This test should either implement actual country selection testing with proper assertions, or be renamed/removed to avoid confusion.

🔎 Suggestion

Either implement proper country selection testing or remove this placeholder:

   it("should set country when selectedCountry is executed", async () => {
     const {container} = mountComponent(props);
 
     await waitFor(() => {
       expect(container.querySelector("#registration-form")).toBeInTheDocument();
     });
 
-    // The selectedCountry method is tested implicitly through user interaction
-    // This test verifies the component structure is correct
-    expect(container.querySelector("form")).toBeInTheDocument();
+    // If a country selector exists, test it explicitly
+    const countrySelect = container.querySelector("select[name='country']");
+    if (countrySelect) {
+      fireEvent.change(countrySelect, { target: { value: "US" } });
+      await waitFor(() => {
+        // Add assertion verifying country state/prop was updated
+        expect(countrySelect.value).toBe("US");
+      });
+    }
   });

Alternatively, if this test is not needed, consider removing it entirely.

🧹 Nitpick comments (9)
client/components/footer/footer.test.js (1)

93-94: Consider using getByRole consistently for link assertions.

The tests mix getByRole("link", {name: "..."}) (line 93) with getByText() (lines 94, 109-110, 125) for verifying links. Using getByRole consistently offers better test quality:

  • Verifies the element is semantically a link (not just text)
  • Aligns with accessibility best practices
  • More resilient to refactoring
🔎 Example refactor for consistent query patterns
-    expect(screen.getByText("signUp")).toBeInTheDocument();
+    expect(screen.getByRole("link", {name: "signUp"})).toBeInTheDocument();

-    expect(screen.getByText("status")).toBeInTheDocument();
-    expect(screen.getByText("change-password")).toBeInTheDocument();
+    expect(screen.getByRole("link", {name: "status"})).toBeInTheDocument();
+    expect(screen.getByRole("link", {name: "change-password"})).toBeInTheDocument();

-    expect(screen.queryByText("signUp")).not.toBeInTheDocument();
+    expect(screen.queryByRole("link", {name: "signUp"})).not.toBeInTheDocument();

Apply similar changes to lines 125, 128-129.

Also applies to: 109-110, 113-113, 125-125, 128-129

client/components/modal/modal.test.js (2)

155-171: Remove unnecessary event listener save/restore pattern.

The beforeEach/afterEach pattern that saves and restores addEventListener/removeEventListener is unnecessary. Only the test starting at line 307 creates spies, and it already manages its own spy lifecycle (creating and restoring within the test). Jest automatically cleans up mocks between tests, so this manual bookkeeping adds no value and may confuse future maintainers.

🔎 Suggested refactor
 describe("<Modal /> interactions", () => {
   let props;
-  let originalAddEventListener;
-  let originalRemoveEventListener;
 
   beforeEach(() => {
     jest.clearAllMocks();
     axios.mockReset();
-    // Store original methods
-    originalAddEventListener = document.addEventListener;
-    originalRemoveEventListener = document.removeEventListener;
   });
 
-  afterEach(() => {
-    jest.clearAllMocks();
-    // Restore original methods
-    document.addEventListener = originalAddEventListener;
-    document.removeEventListener = originalRemoveEventListener;
-  });
-

330-345: Consider strengthening event listener cleanup assertion.

The test verifies that addEventListener and removeEventListener are called with expect.any(Function), but doesn't confirm they receive the same function reference. If the component mistakenly added and removed different functions, this would pass but leak memory. While the Modal implementation correctly uses a stable arrow function property (handleKeyDown), the test could be more precise by capturing and comparing the actual function reference.

🔎 Optional improvement
   // Wait for component to mount
+  let capturedHandler;
   await waitFor(() => {
-    expect(addEventListenerSpy).toHaveBeenCalledWith(
-      "keyup",
-      expect.any(Function),
-      false,
-    );
+    const calls = addEventListenerSpy.mock.calls;
+    expect(calls.length).toBeGreaterThan(0);
+    const keyupCall = calls.find(call => call[0] === "keyup");
+    expect(keyupCall).toBeDefined();
+    capturedHandler = keyupCall[1];
   });
 
   // Unmount component
   unmount();
 
   // Event listeners should be removed
   expect(removeEventListenerSpy).toHaveBeenCalledWith(
     "keyup",
-    expect.any(Function),
+    capturedHandler,
     false,
   );
client/components/registration/registration.test.js (5)

17-89: Consider extracting the mock configuration to a shared fixture file.

The inline mockConfig object is comprehensive but spans 70+ lines. Extracting it to a separate test fixture file (e.g., __fixtures__/mockConfig.js) would improve maintainability and enable reuse across test files.


145-183: Consider consolidating the two rendering helper functions.

Both renderWithProviders (lines 145-150) and mountComponent (lines 157-183) provide similar functionality—rendering components with Redux store and Router context—but with different implementations. This duplication may confuse maintainers about which helper to use. Consider consolidating them into a single, parameterized helper function.


256-385: Consider splitting this comprehensive test into separate test cases.

This single test covers six different submission scenarios (password mismatch, API errors, server errors, timeout, success, and billing errors). While comprehensive, splitting it into individual test cases would improve clarity, make failures easier to diagnose, and better align with the "one assertion per test" principle.


597-597: Redundant mock setup in afterEach hooks.

Re-setting up the getConfig mock in afterEach (lines 597, 735) appears unnecessary since the mock is already configured at the module level (lines 85-88) and clearAllMocks() should suffice to reset call counts and instances.

Also applies to: 735-735


702-716: Use more robust selectors for password toggle test.

Accessing password toggles via array index (passwordToggles[0]) is fragile. If the DOM structure changes, the test could break or select the wrong element. Consider using more specific selectors like container.querySelector('.row.password .password-toggle') or data-testid attributes.

🔎 Proposed improvement
-    const passwordToggles = container.querySelectorAll(".password-toggle");
+    const passwordToggle = container.querySelector(".row.password .password-toggle");
     const password1Input = container.querySelector(".row.password input");
 
     expect(password1Input).toHaveAttribute("type", "password");
 
-    expect(passwordToggles[0]).toBeDefined();
-    fireEvent.click(passwordToggles[0]);
+    expect(passwordToggle).toBeInTheDocument();
+    fireEvent.click(passwordToggle);
client/app.js (1)

50-61: React 18 migration looks good.

The migration from ReactDOM.render to createRoot is correctly implemented, and the provider nesting (HelmetProvider → CookiesProvider → Redux Provider → App) is appropriate.

💡 Optional: Consider wrapping with React.StrictMode

While not required, wrapping the app in React.StrictMode helps identify potential issues with React 18's concurrent features during development:

  root.render(
+   <React.StrictMode>
      <HelmetProvider>
        <CookiesProvider>
          <Provider store={store}>
            <App />
          </Provider>
        </CookiesProvider>
      </HelmetProvider>,
+   </React.StrictMode>,
  );
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d2c7499 and b13bec3.

⛔ Files ignored due to path filters (1)
  • client/components/contact-box/__snapshots__/contact.test.js.snap is excluded by !**/*.snap
📒 Files selected for processing (5)
  • client/app.js
  • client/components/contact-box/contact.test.js
  • client/components/footer/footer.test.js
  • client/components/modal/modal.test.js
  • client/components/registration/registration.test.js
🧰 Additional context used
🧬 Code graph analysis (4)
client/components/footer/footer.test.js (1)
client/components/footer/footer.js (1)
  • Footer (12-55)
client/app.js (1)
client/store/index.js (1)
  • store (6-6)
client/components/contact-box/contact.test.js (1)
client/components/contact-box/contact.js (1)
  • Contact (13-75)
client/components/modal/modal.test.js (3)
client/components/modal/modal.js (1)
  • Modal (14-97)
client/utils/get-text.js (1)
  • getText (1-5)
client/utils/log-error.js (1)
  • logError (1-15)
🪛 Gitleaks (8.30.0)
client/components/registration/registration.test.js

[high] 153-153: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 154-154: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Tests and Coverage
🔇 Additional comments (7)
client/components/contact-box/contact.test.js (5)

1-2: LGTM! Clean RTL imports.

The imports are correct for the Enzyme-to-RTL migration. render and screen enable querying the rendered DOM, and jest-dom provides semantic matchers like toBeInTheDocument().


5-22: LGTM! Proper mock hoisting pattern.

The mock setup correctly places jest.mock calls before imports with appropriate ESLint suppression. The comment clearly explains why import/first must be disabled.


55-60: LGTM! Snapshot test migrated correctly.

The test properly uses RTL's render and captures snapshots via container, which is the standard approach for snapshot testing with RTL.


74-84: LGTM! Correct RTL query patterns.

The test properly uses getByAltText for elements that must exist (throws if missing) and queryByAltText for elements that must not exist (returns null). The authentication logic is asserted correctly.


86-96: LGTM! Authentication logic tested correctly.

The test verifies that authenticated users see the appropriate social links. The query pattern is consistent with RTL best practices, and the assertions match the expected conditional rendering behavior.

client/components/footer/footer.test.js (1)

3-4: Excellent Enzyme-to-RTL migration!

The migration from Enzyme to React Testing Library is well executed:

  • Proper imports of render and screen from RTL
  • Container-based snapshots following RTL conventions
  • Correct use of queryBy for negative assertions (line 78)
  • Good use of toBeInTheDocument() matcher from jest-dom

The test logic and coverage remain intact while adopting RTL's DOM-centric approach.

Also applies to: 54-55, 68-69, 77-84

client/components/registration/registration.test.js (1)

152-155: Static analysis false positive: these are mock test values, not real secrets.

The static analysis tool flagged lines 153-154 as potential API keys. However, these are clearly mock response data used for testing (responseData constant with hardcoded test values). No security risk exists here.

@PRoIISHAAN PRoIISHAAN force-pushed the fix-reactversion-migration branch from b13bec3 to feab576 Compare January 2, 2026 17:55
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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
client/components/login/login.test.js (1)

401-450: Test logic flaw: Fourth submit succeeds but expects 4 error toasts.

The fourth axios mock (line 410) returns Promise.resolve() indicating success, yet the assertion at line 448 expects spyToast.toHaveBeenCalledTimes(4). If the fourth submission succeeds, toast.error should not be invoked again—the count should remain at 3.

This test may pass coincidentally if the component shows a toast on empty success response, but the intent appears incorrect. Either the fourth mock should reject, or the expected toast count should be 3.

Suggested fix (if fourth submit should fail)
       .mockImplementationOnce(() =>
-        Promise.resolve());
+        Promise.reject({
+          response: {
+            data: {detail: "Another error"},
+          },
+        }),
+      );
Alternative fix (if fourth submit should succeed)
     // Fourth submit - success
     fireEvent.submit(form);
     await waitFor(() => {
       expect(container.querySelectorAll("div.error")).toHaveLength(0);
       expect(container.querySelectorAll("input.error")).toHaveLength(0);
-      expect(props.authenticate).not.toHaveBeenCalled();
       expect(lastConsoleOutuput).toBe(null);
-      expect(spyToast).toHaveBeenCalledTimes(4);
+      expect(spyToast).toHaveBeenCalledTimes(3);
     });
♻️ Duplicate comments (8)
client/components/mobile-phone-change/mobile-phone-change.js (1)

38-38: Mounted guard pattern is incomplete.

The componentIsMounted flag is properly initialized in the constructor and cleaned up in componentWillUnmount, but this issue was already raised in a previous review: the handleSubmit method's async callbacks (lines 90-93, 106-112) still lack guards against setState after unmount. Please address the previous review feedback to complete this pattern.

Also applies to: 65-67

client/components/payment-process/payment-process.test.js (1)

170-170: Still using mockReturnValue for async function in most tests.

A past review requested changing validateToken.mockReturnValue to mockResolvedValue at these lines, but only lines 149 and 415 were updated. Lines 170, 192, 213, 242, 266, 303, 337, 374, and 441 still use mockReturnValue for the async validateToken function.

🔎 Apply the fix across all remaining occurrences
-    validateToken.mockReturnValue(true);
+    validateToken.mockResolvedValue(true);

Or for the false case at line 213:

-    validateToken.mockReturnValue(false);
+    validateToken.mockResolvedValue(false);

Also applies to: 192-192, 213-213, 242-242, 266-266, 303-303, 337-337, 374-374, 441-441

client/components/mobile-phone-verification/mobile-phone-verification.test.js (2)

64-64: Remove the unnecessary second parameter.

The second parameter true has no effect since the mock at lines 21-40 ignores all parameters. This issue was previously flagged.


601-614: Use explicit string value for sessionStorage.

Line 603 passes a boolean true to sessionStorage.setItem, but string values are preferred. This issue was previously flagged.

client/components/mobile-phone-change/mobile-phone-change.test.js (1)

134-142: LoadingContext mock shape mismatch with actual implementation.

The test mock defines isLoading: false but the actual LoadingContext in client/utils/loading-context.js uses getLoading: () => {}. This mismatch means tests pass with an incorrect provider shape that differs from production.

Either import loadingContextValue from client/utils/loading-context.js or update the mock to match the real shape.

🔎 Proposed fix
 const createLoadingContextValue = () => ({
   setLoading: jest.fn(),
-  isLoading: false,
+  getLoading: jest.fn(() => false),
 });

 const LoadingContext = React.createContext({
   setLoading: () => {},
-  isLoading: false,
+  getLoading: () => false,
 });
client/app.js (1)

13-13: Remove unused history import.

The history import is no longer used since React Router v6's BrowserRouter doesn't accept a custom history prop. This was already flagged in a previous review.

🔎 Proposed fix
-import history from "./utils/history";
client/components/organization-wrapper/organization-wrapper.test.js (1)

689-702: Typo in 404 component ID still present.

The test correctly queries for #not-foud-404, but this reflects a typo in the actual component (client/components/404/404.js). This was flagged in a previous review and should be addressed in the component file.

client/components/registration/registration.test.js (1)

783-793: Test description doesn't match implementation.

The test title claims to test country selection behavior, but it only verifies the form's existence. The inline comment states "tested implicitly through user interaction," yet no user interaction or assertion related to country selection is present.

Consider either:

  1. Implementing actual country selection testing if the feature exists
  2. Renaming the test to match what it actually verifies
  3. Removing the test if it's not needed
🧹 Nitpick comments (15)
client/components/footer/footer.test.js (1)

92-129: Consider consistent query strategy for link assertions.

The tests mix two query strategies:

  • Line 93, 108, 124: getByRole("link", {name: "about"}) (verifies element is a link with accessible name)
  • Lines 94, 109-110, 125: getByText("signUp") (only verifies text exists, not that it's in a link)

For consistency and better accessibility testing, consider using getByRole("link", {name: ...}) for all link assertions.

🔎 Proposed refactor for consistency
     // Check visible links
     expect(screen.getByRole("link", {name: "about"})).toBeInTheDocument();
-    expect(screen.getByText("signUp")).toBeInTheDocument();
+    expect(screen.getByRole("link", {name: "signUp"})).toBeInTheDocument();

     // Check links that shouldn't be visible
     expect(screen.queryByText("status")).not.toBeInTheDocument();
     expect(screen.queryByText("change-password")).not.toBeInTheDocument();

Apply similar changes to lines 109-110 and 125.

client/components/payment-process/payment-process.test.js (1)

373-408: Inconsistent message event mocking pattern.

This test manually mocks window.addEventListener (lines 377-382) instead of using the mockMessageEvents helper used in tests at lines 262-299, 301-333, and 335-371. For consistency and maintainability, consider using the helper.

🔎 Refactor to use the mockMessageEvents helper
-    const events = {};
-    const originalAddEventListener = window.addEventListener;
-
-    window.addEventListener = jest.fn((event, callback) => {
-      events[event] = callback;
-    });
+    const eventMock = mockMessageEvents();

     renderWithProviders(<PaymentProcess {...props} />);

     await tick();

     // Simulate setHeight message
-    expect(events.message).toBeDefined();
+    expect(eventMock.events.message).toBeDefined();
     await act(async () => {
-      events.message({
+      eventMock.events.message({
         data: {
           type: "setHeight",
           message: 800,
         },
         origin: window.location.origin,
       });
     });

     await tick();

     // Check if iframe height was updated
     const iframe = within(document.body).getByTitle("owisp-payment-iframe");
     expect(iframe).toBeInTheDocument();
     expect(iframe).toHaveAttribute("height", "800");

-    window.addEventListener = originalAddEventListener;
+    eventMock.restore();
client/components/mobile-phone-change/mobile-phone-change.test.js (1)

273-285: Consider using waitFor instead of custom tick() for async assertions.

Using the custom tick() utility followed by synchronous assertions can be fragile with React 18's concurrent rendering. RTL's waitFor or findBy* queries are more reliable for async behavior.

🔎 Proposed refactor
   fireEvent.submit(form);

-  await tick();
-
-  expect(toast.success).toHaveBeenCalledTimes(1);
-  expect(props.navigate).toHaveBeenCalledWith(
-    `/${props.orgSlug}/mobile-phone-verification`,
-  );
-  expect(props.setUserData).toHaveBeenCalledTimes(1);
-  expect(props.setUserData).toHaveBeenCalledWith({
-    is_verified: false,
-    phone_number: expect.stringContaining("393660011333"),
-  });
+  await waitFor(() => {
+    expect(toast.success).toHaveBeenCalledTimes(1);
+  });
+
+  expect(props.navigate).toHaveBeenCalledWith(
+    `/${props.orgSlug}/mobile-phone-verification`,
+  );
+  expect(props.setUserData).toHaveBeenCalledTimes(1);
+  expect(props.setUserData).toHaveBeenCalledWith({
+    is_verified: false,
+    phone_number: expect.stringContaining("393660011333"),
+  });
client/components/modal/modal.test.js (3)

155-171: Remove unused event listener storage.

The originalAddEventListener and originalRemoveEventListener variables are declared and managed in beforeEach/afterEach but never used. The test at lines 307-350 that spies on these methods creates its own spies and restores them with mockRestore(), making this setup redundant.

🔎 Proposed fix
 describe("<Modal /> interactions", () => {
   let props;
-  let originalAddEventListener;
-  let originalRemoveEventListener;
 
   beforeEach(() => {
     jest.clearAllMocks();
     axios.mockReset();
-    // Store original methods
-    originalAddEventListener = document.addEventListener;
-    originalRemoveEventListener = document.removeEventListener;
   });
 
   afterEach(() => {
     jest.clearAllMocks();
-    // Restore original methods
-    document.addEventListener = originalAddEventListener;
-    document.removeEventListener = originalRemoveEventListener;
   });

230-249: Consider moving to a dedicated suite.

The mapStateToProps test is placed in the "interactions" suite but tests a pure Redux selector function rather than component interactions. While the test itself is correct, it would be better organized in its own suite (e.g., "Redux integration" or "State mapping").

💡 Suggested organization
+describe("<Modal /> state mapping", () => {
   it("should map state to props", () => {
     const result = mapStateToProps(
       {
         organization: {
           configuration: {
             privacy_policy: "# Privacy Policy",
             terms_and_conditions: "# Terms and Conditions",
           },
         },
         language: "en",
       },
       {prevPath: "/default/login"},
     );
     expect(result).toEqual({
       privacyPolicy: "# Privacy Policy",
       termsAndConditions: "# Terms and Conditions",
       language: "en",
       prevPath: "/default/login",
     });
   });
+});

282-305: Test remains redundant despite previous rename.

This test was renamed per previous review feedback, but it still doesn't verify any unique behavior—modal content rendering is already covered by multiple tests in the rendering suite (lines 41-67, 69-96). The test should either:

  1. Actually verify backdrop element presence (check for an element with appropriate class/role), or
  2. Be removed as redundant

Note: Looking at the Modal component implementation, the render returns a div with class "modal is-visible content" containing a "modal-container", but no separate backdrop element is rendered.

client/components/payment-status/payment-status.test.js (1)

110-110: Consider replacing snapshots with specific assertions.

While snapshot tests provide coverage, RTL best practices favor specific assertions that document expected behavior and are less brittle to unrelated changes. For instance, line 144's snapshot could be replaced with focused checks for the specific elements that define the "failed" state (which you've already added in lines 147-159).

Example: Replace snapshot with focused assertions

For the failed state test (line 144), you already have specific assertions following the snapshot. You could remove the snapshot entirely:

-    expect(container).toMatchSnapshot();
-
     // Check for failed payment page content
     expect(screen.getByText(/payment failed/i)).toBeInTheDocument();

This makes the test more maintainable and self-documenting.

Also applies to: 144-144, 522-522

client/components/registration/registration.js (1)

632-632: Redundant aria-label with existing <label> element.

The password input at line 622 already has an associated <label htmlFor="password"> (line 617), which provides the accessible name. The aria-label is redundant here. However, this doesn't cause issues—it's a minor point.

The same applies to other inputs with both <label> and aria-label:

  • Confirm Password (line 661)
  • City (line 701)
  • Street (line 717)
  • Zip Code (line 732)
  • Tax Number (line 748)

Consider removing the aria-label attributes since the visible labels already provide accessibility, or keep them if they serve a specific testing purpose.

client/components/registration/registration.test.js (1)

702-716: Consider more specific assertion for password toggle.

The test verifies that clicking the toggle increases text inputs, but could be more explicit by checking the specific password input's type changed to "text".

🔎 Optional improvement for clarity
   expect(passwordToggles[0]).toBeDefined();
   fireEvent.click(passwordToggles[0]);
   await waitFor(() => {
-    const textInputs = container.querySelectorAll("input[type='text']");
-    expect(textInputs.length).toBeGreaterThan(0);
+    expect(password1Input).toHaveAttribute("type", "text");
   });
client/components/login/login.test.js (3)

8-14: Incorrect use of BrowserRouter with location and navigator props.

BrowserRouter (aliased as Router) is imported here but used later with location and navigator props (line 338). In React Router v6, BrowserRouter does not accept these props—they are meant for the low-level Router component. The props are silently ignored, meaning historyMock has no effect on routing.

For controlled routing in tests, use MemoryRouter with initialEntries, or import the base Router from react-router-dom for custom history support.

Suggested fix
 import {
-  BrowserRouter as Router,
+  MemoryRouter,
   Route,
   Routes,
-  MemoryRouter,
 } from "react-router-dom";
-import {createMemoryHistory} from "history";

Then update mountComponent to use MemoryRouter directly:

-    const historyMock = createMemoryHistory();
-
     return render(
       <Provider store={mockedStore}>
-        <Router location={historyMock.location} navigator={historyMock}>
+        <MemoryRouter>
           <Routes>
             <Route path="/*" element={<Login {...passedProps} />} />
           </Routes>
-        </Router>
+        </MemoryRouter>
       </Provider>,
     );

286-313: Redundant axios.mockReset() in both beforeEach and afterEach.

Calling axios.mockReset() in both lifecycle hooks is unnecessary. The beforeEach reset ensures a clean state before each test runs, making the afterEach reset redundant.

Suggested fix
   afterEach(() => {
     /* eslint-disable no-console */
     console.error = originalError;
     /* eslint-enable no-console */
     jest.clearAllMocks();
-    axios.mockReset(); // Also reset in afterEach for good measure
   });

243-269: Test doesn't effectively verify Suspense fallback vs loaded component.

Both the fallback input and the lazy-loaded PhoneInput share the same placeholder (PHONE_PHOLD), so the final waitFor assertion (lines 266-268) merely confirms an input with that placeholder exists—it doesn't prove a swap from fallback to the real component occurred.

To strengthen this test, consider checking for a distinguishing attribute (e.g., a class or data attribute unique to the loaded PhoneInput) or verify the component type changed.

client/components/password-change/password-change.js (1)

42-42: Mounting guard lifecycle methods implemented correctly.

The flag is set appropriately in componentDidMount and reset in componentWillUnmount, completing the mounting guard pattern.

For React 18, consider using AbortController to cancel in-flight requests on unmount instead of the flag-based pattern:

🔎 AbortController pattern
 constructor(props) {
   super(props);
   // ... existing state ...
-  this.componentIsMounted = false;
+  this.abortController = null;
 }

 async componentDidMount() {
-  this.componentIsMounted = true;
+  this.abortController = new AbortController();
   // ... rest of componentDidMount ...
 }

 componentWillUnmount() {
-  this.componentIsMounted = false;
+  if (this.abortController) {
+    this.abortController.abort();
+  }
 }

 handleSubmit(e) {
   // ... validation logic ...
   return axios({
     method: "post",
     // ... other options ...
+    signal: this.abortController.signal,
   })

This cancels the request itself rather than just preventing side effects.

Also applies to: 63-65

client/components/password-change/password-change.test.js (2)

142-247: Comprehensive test coverage for form interactions.

The test effectively covers multiple scenarios: input changes, password mismatch validation, same-password validation, server errors, and successful submission.

Minor suggestions for robustness:

  1. Lines 212-221: The error detection relies on className and text content, which is fragile:

    const errorElements = screen.queryAllByText((content, element) => {
      const hasText =
        element.textContent.includes("password") ||
        element.textContent.includes("PWD_CURR_ERR");
      const elementHasClass =
        element.className && element.className.includes("error");
      return hasText && elementHasClass;
    });

    Consider using data-testid attributes or more specific role-based queries for better maintainability.

  2. Line 235: The tick() call before waitFor may be redundant, as waitFor already handles async waiting.


258-331: Thorough testing of password input attributes and visibility toggling.

The tests verify all relevant input attributes and successfully test the password visibility toggle functionality.

Lines 308-314: The button filtering assumes structural details about button types:

const toggleButtons = allButtons.filter(
  (btn) => !btn.type || btn.type === "button",
);

For better resilience, consider adding data-testid attributes to password toggle buttons in the component, allowing direct queries like screen.getAllByTestId('password-toggle').

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b13bec3 and 9d493a3.

⛔ Files ignored due to path filters (18)
  • client/components/404/__snapshots__/404.test.js.snap is excluded by !**/*.snap
  • client/components/contact-box/__snapshots__/contact.test.js.snap is excluded by !**/*.snap
  • client/components/footer/__snapshots__/footer.test.js.snap is excluded by !**/*.snap
  • client/components/header/__snapshots__/header.test.js.snap is excluded by !**/*.snap
  • client/components/login/__snapshots__/login.test.js.snap is excluded by !**/*.snap
  • client/components/logout/__snapshots__/logout.test.js.snap is excluded by !**/*.snap
  • client/components/mobile-phone-change/__snapshots__/mobile-phone-change.test.js.snap is excluded by !**/*.snap
  • client/components/mobile-phone-verification/__snapshots__/mobile-phone-verification.test.js.snap is excluded by !**/*.snap
  • client/components/modal/__snapshots__/modal.test.js.snap is excluded by !**/*.snap
  • client/components/organization-wrapper/__snapshots__/organization-wrapper.test.js.snap is excluded by !**/*.snap
  • client/components/password-change/__snapshots__/password-change.test.js.snap is excluded by !**/*.snap
  • client/components/password-confirm/__snapshots__/password-confirm.test.js.snap is excluded by !**/*.snap
  • client/components/password-reset/__snapshots__/password-reset.test.js.snap is excluded by !**/*.snap
  • client/components/payment-process/__snapshots__/payment-process.test.js.snap is excluded by !**/*.snap
  • client/components/payment-status/__snapshots__/payment-status.test.js.snap is excluded by !**/*.snap
  • client/components/registration/__snapshots__/registration.test.js.snap is excluded by !**/*.snap
  • client/components/status/__snapshots__/status.test.js.snap is excluded by !**/*.snap
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (57)
  • .eslintrc.json
  • .github/workflows/ci.yml
  • babel.config.js
  • browser-test/create-mobile-configuration.js
  • browser-test/js-file-inject.test.js
  • browser-test/mobile-phone-change.test.js
  • browser-test/utils.js
  • client/app.js
  • client/components/404/404.test.js
  • client/components/contact-box/contact.test.js
  • client/components/footer/footer.test.js
  • client/components/header/header.test.js
  • client/components/login/login.test.js
  • client/components/logout/logout.test.js
  • client/components/mobile-phone-change/mobile-phone-change.js
  • client/components/mobile-phone-change/mobile-phone-change.test.js
  • client/components/mobile-phone-verification/mobile-phone-verification.js
  • client/components/mobile-phone-verification/mobile-phone-verification.test.js
  • client/components/modal/modal.test.js
  • client/components/organization-wrapper/organization-wrapper.js
  • client/components/organization-wrapper/organization-wrapper.test.js
  • client/components/password-change/password-change.js
  • client/components/password-change/password-change.test.js
  • client/components/password-confirm/password-confirm.js
  • client/components/password-confirm/password-confirm.test.js
  • client/components/password-reset/password-reset.js
  • client/components/password-reset/password-reset.test.js
  • client/components/payment-process/payment-process.test.js
  • client/components/payment-status/payment-status.js
  • client/components/payment-status/payment-status.test.js
  • client/components/registration/registration.js
  • client/components/registration/registration.test.js
  • client/components/registration/subscriptions.test.js
  • client/components/registration/test-utils.js
  • client/components/status/status.js
  • client/components/status/status.test.js
  • client/components/status/status.test.js.enzyme-backup
  • client/components/status/status.test.js.rtl-backup
  • client/utils/__mocks__/get-config.js
  • client/utils/get-config.js
  • client/utils/get-plan-selection.js
  • client/utils/load-translation.js
  • client/utils/load-translation.test.js
  • client/utils/log-error.js
  • client/utils/needs-verify.js
  • client/utils/password-toggle.js
  • client/utils/tick.js
  • client/utils/utils.test.js
  • client/utils/with-route-props.js
  • config/__tests__/add-org.test.js
  • config/add-org.js
  • config/jest.config.js
  • config/setupTests.js
  • config/webpack.config.js
  • package.json
  • server/app.js
  • server/index.js
🚧 Files skipped from review as they are similar to previous changes (13)
  • client/components/mobile-phone-verification/mobile-phone-verification.js
  • .github/workflows/ci.yml
  • browser-test/js-file-inject.test.js
  • client/components/organization-wrapper/organization-wrapper.js
  • client/components/payment-status/payment-status.js
  • babel.config.js
  • client/components/contact-box/contact.test.js
  • client/components/404/404.test.js
  • browser-test/create-mobile-configuration.js
  • browser-test/mobile-phone-change.test.js
  • client/components/password-reset/password-reset.js
  • browser-test/utils.js
  • .eslintrc.json
🧰 Additional context used
🧬 Code graph analysis (11)
client/components/footer/footer.test.js (1)
client/components/footer/footer.js (1)
  • Footer (12-55)
client/components/organization-wrapper/organization-wrapper.test.js (3)
client/utils/__mocks__/get-config.js (1)
  • getConfig (11-16)
client/components/organization-wrapper/organization-wrapper.js (1)
  • OrganizationWrapper (38-371)
client/utils/__mocks__/load-translation.js (1)
  • loadTranslation (17-27)
client/components/password-confirm/password-confirm.js (1)
client/utils/submit-on-enter.js (1)
  • form (3-3)
client/components/password-confirm/password-confirm.test.js (2)
client/utils/get-error-text.js (1)
  • error (2-2)
client/utils/tick.js (1)
  • tick (1-12)
client/components/registration/registration.test.js (3)
client/utils/__mocks__/get-config.js (1)
  • getConfig (11-16)
client/components/registration/test-utils.js (2)
  • mountComponent (8-38)
  • mockedStore (12-27)
client/components/registration/registration.js (1)
  • Registration (35-854)
client/components/login/login.test.js (10)
client/components/registration/registration.test.js (7)
  • state (120-136)
  • defaultConfig (117-117)
  • props (186-186)
  • props (195-195)
  • props (210-210)
  • props (578-578)
  • props (720-720)
client/components/mobile-phone-verification/mobile-phone-verification.test.js (2)
  • state (67-83)
  • defaultConfig (64-64)
client/components/organization-wrapper/organization-wrapper.test.js (7)
  • defaultConfig (92-92)
  • defaultConfig (565-566)
  • props (135-135)
  • props (248-248)
  • props (481-481)
  • props (524-524)
  • props (564-564)
client/components/login/login.js (1)
  • Login (36-506)
client/utils/load-translation.js (1)
  • loadTranslation (74-95)
client/utils/__mocks__/get-config.js (1)
  • getConfig (11-16)
client/utils/get-config.js (1)
  • getConfig (3-13)
client/utils/redirect-to-payment.js (1)
  • redirectToPayment (3-4)
client/utils/get-parameter-by-name.js (1)
  • getParameterByName (1-9)
config/build-translations.js (1)
  • result (28-28)
client/components/password-change/password-change.test.js (1)
client/components/password-change/password-change.js (1)
  • PasswordChange (22-251)
client/components/modal/modal.test.js (2)
client/components/modal/modal.js (1)
  • Modal (14-97)
client/utils/get-text.js (1)
  • getText (1-5)
client/components/header/header.test.js (2)
client/utils/check-internal-links.js (1)
  • isInternalLink (1-4)
client/components/header/index.js (2)
  • mapDispatchToProps (15-19)
  • mapDispatchToProps (15-19)
client/app.js (1)
client/store/index.js (1)
  • store (6-6)
client/components/logout/logout.test.js (1)
client/components/logout/logout.js (1)
  • Logout (16-62)
🪛 Gitleaks (8.30.0)
client/components/registration/registration.test.js

[high] 153-153: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 154-154: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Tests and Coverage
🔇 Additional comments (62)
client/components/mobile-phone-change/mobile-phone-change.js (3)

57-61: LGTM!

The mounted guard correctly prevents setState after unmount for the async validateToken operation in componentDidMount.


95-95: LGTM!

Using toast.success for a successful phone number change submission is semantically appropriate and provides clearer visual feedback to the user.


139-139: LGTM!

Adding the aria-label improves accessibility for screen readers and aligns with the broader testing improvements in this PR.

client/components/footer/footer.test.js (2)

3-4: Excellent migration to React Testing Library!

The imports are correct and the snapshot pattern using container from the render result follows RTL best practices.

Also applies to: 54-55, 68-69


77-83: Correct use of RTL query variants!

Properly uses queryByRole for negative assertions (line 78) and getByText for positive assertions (line 83).

client/components/password-reset/password-reset.test.js (1)

54-85: The review comment's premise is incorrect. The shared test utilities referenced in the file dependencies do not actually exist—there is no client/test-utils/ directory in the repository, and renderWithProviders.js and createMockStore.js are not located there.

However, the underlying observation about code duplication is valid: these utilities are defined inline in multiple test files across the codebase (registration, status, payment-process, password-reset, and others). Each test file independently defines its own createMockStore and renderWithProviders functions, with slight variations in signatures and implementations.

Rather than importing from non-existent shared modules, consider extracting these utilities into a shared test-utils module as part of a broader refactoring effort across all test files.

Likely an incorrect or invalid review comment.

client/components/mobile-phone-verification/mobile-phone-verification.test.js (8)

1-62: LGTM! Clean RTL migration setup.

The imports, mock configuration, and test helper setup follow RTL best practices. The getConfig mock is correctly placed before component import, and the returned config structure matches the component's expectations.


66-97: LGTM! Well-structured test helpers.

The createMockStore and renderWithProviders utilities follow common RTL patterns for testing Redux-connected components with routing. The mock store provides the minimal required state shape.


108-133: LGTM! Correct mock structure.

The axios mock now correctly returns data: { active: false } with the active field properly nested inside the data object, which matches how the component accesses response.data.active.


135-254: LGTM! Solid async test patterns.

The test suite properly manages axios mocks between tests and uses waitFor correctly for async assertions. The mock response structures correctly nest data (e.g., data: { active: true }), addressing previous concerns.


256-356: LGTM! Sophisticated mock handling.

The method-based axios mock routing (lines 260-284) is an elegant solution for handling sequential GET/POST calls with different responses. The error-handling assertions correctly distinguish between expected 404s (silent) and actual errors (logged).


358-480: LGTM! Clever sequential call handling.

The postCallCount pattern (lines 420-451) elegantly differentiates between the first POST (createPhoneToken) and second POST (verifyToken). The test correctly simulates user input and verifies the expected data structure passed to setUserData.


482-599: LGTM! Comprehensive interaction tests.

The error display test correctly validates both the UI feedback (screen.getByText(/invalid code/i)) and the logging behavior. Logout and title tests properly verify the expected side effects.


616-691: LGTM! Complete edge case coverage.

The rejection test correctly uses mockImplementationOnce for isolated error simulation. The corner cases suite properly validates that no API calls are made when the user is already verified or the feature is disabled.

client/components/mobile-phone-change/mobile-phone-change.test.js (5)

1-22: LGTM!

Imports are correctly structured for the RTL migration with appropriate testing utilities.


24-77: LGTM!

Mock configuration is well-structured with proper hoisting of jest.mock calls.


288-333: LGTM!

Good coverage for lazy-loaded PhoneInput component and its fallback behavior. The use of waitFor for async component loading is appropriate.


335-401: LGTM!

Error handling tests are well-structured with proper use of waitFor for async error display.


433-553: LGTM!

Corner case tests provide good coverage for redirect conditions and edge cases with proper test isolation.

client/components/modal/modal.test.js (6)

1-31: LGTM: Clean test setup.

The RTL migration is properly configured with correct imports, mocks, and test scaffolding. The createTestProps helper provides good test flexibility.


33-150: LGTM: Rendering tests follow RTL best practices.

The test suite properly:

  • Clears mocks in beforeEach
  • Wraps all renders in MemoryRouter
  • Uses async/await with waitFor for async operations
  • Queries DOM via screen and uses jest-dom matchers
  • Captures snapshots via container
  • Verifies error handling and utility calls

173-200: LGTM: Esc key handling test is correct.

The test properly simulates the keyup event (not keyDown) and verifies navigation. The clarifying comment on line 195 is helpful.


202-228: LGTM: Non-Esc key test provides good negative coverage.

This test correctly verifies that non-Esc keys don't trigger navigation, complementing the positive Esc key test.


251-280: LGTM: Scrollbar lifecycle test is thorough.

The test properly verifies that the modal hides the scrollbar on mount and restores it on unmount, using jest-dom's toHaveStyle matcher appropriately.


307-350: LGTM: Event listener cleanup test is thorough.

The test properly verifies that event listeners are added on mount and removed on unmount, creating and restoring spies appropriately.

client/components/payment-status/payment-status.test.js (5)

9-36: LGTM: Excellent mock setup pattern.

The module mocking strategy is correct—placing jest.mock() calls before imports prevents hoisting issues, and the ESLint disable/enable comments appropriately document the deviation from typical import ordering. The mock structure for get-config properly mirrors the expected component configuration.


52-83: LGTM: Well-structured test helpers.

The createMockStore and renderWithProviders helpers follow RTL best practices for testing Redux-connected, router-enabled components. The mock store provides exactly the state slices the component needs (organization config, language), and the rendering wrapper correctly composes the necessary providers.


119-131: LGTM: Defensive console mocking.

The optional chaining on mockRestore?.() (lines 129-130) is a thoughtful pattern that prevents errors if the spy wasn't created or was already restored—particularly valuable with React 18's concurrent rendering where test lifecycle can be less predictable.


1-528: Excellent Enzyme-to-RTL migration.

This test file demonstrates a high-quality migration from Enzyme to React Testing Library:

✅ Proper module mock hoisting
✅ Clean provider wrappers for Redux and Router
✅ Accessible queries (getByRole with name patterns)
✅ Appropriate async handling (waitFor, tick())
✅ Comprehensive coverage of payment status scenarios
✅ Defensive cleanup patterns for React 18 compatibility

The migration successfully maintains test coverage while adopting RTL patterns that better reflect user interactions and accessibility.


273-281: The explicit mustLogin: undefined is intentional and correctly tested.

The component code (line 65 of payment-status.js) explicitly sets mustLogin: settings.payment_requires_internet ? true : undefined. When payment_requires_internet is false, the ternary operator evaluates to undefined. The test correctly verifies this behavior—it's not testing a hypothetical scenario, but the actual component logic. Including mustLogin: undefined in the expectation is necessary because the component deliberately outputs this value.

client/components/password-confirm/password-confirm.js (1)

133-137: Good accessibility improvement.

Adding aria-label to the form element improves screen reader support. The form structure is clean with proper onSubmit handler.

client/app.js (1)

49-61: React 18 migration implemented correctly.

The createRoot API is used properly, and the provider hierarchy (HelmetProvider → CookiesProvider → Redux Provider) is well-structured. This correctly replaces the legacy ReactDOM.render pattern.

client/components/logout/logout.test.js (2)

36-46: Clean RTL migration with proper router context.

The test correctly wraps the component in MemoryRouter and uses RTL's container-based snapshots. This aligns with the broader test migration pattern in this PR.


75-94: Well-structured interaction test using RTL best practices.

Using getByRole with a name matcher is the preferred RTL approach for finding elements. The test properly verifies that setUserData is called with the correct mustLogin: true flag.

client/components/header/header.test.js (3)

6-30: Pre-import mocks correctly structured.

The jest.mock calls are properly placed before the imports they mock, with appropriate eslint-disable comments. This ensures the mocks are hoisted correctly by Jest.


188-197: Acceptable use of direct DOM queries for CSS class assertions.

While RTL prefers role-based queries, using container.querySelectorAll is appropriate here for verifying specific CSS classes. The eslint-disable comments acknowledge this intentional deviation.


372-388: Good keyboard interaction coverage.

Testing keyUp events for the hamburger menu ensures keyboard accessibility. The test correctly differentiates between non-Enter keys (no action) and Enter key (toggle menu).

client/components/organization-wrapper/organization-wrapper.test.js (2)

94-132: Well-designed renderWithRouter helper.

The helper correctly wraps the component with all required providers (HelmetProvider, Redux Provider, MemoryRouter) and constructs a proper mocked store state. This pattern promotes test consistency and reduces boilerplate.


192-217: Pragmatic approach to testing Helmet integration.

Since react-helmet-async manages head elements internally, verifying the configuration props rather than actual DOM injection is a reasonable testing strategy. The component renders without errors, confirming the integration works.

client/components/password-confirm/password-confirm.test.js (3)

84-111: Consistent renderWithProviders helper pattern.

The helper follows the same structure used across other test files in this PR, wrapping the component with Redux Provider and MemoryRouter. This promotes consistency in the test suite.


277-305: Comprehensive API error handling coverage.

The tests cover multiple error response structures (detail, non_field_errors, token), ensuring the component handles various backend error formats correctly. The tick() + waitFor pattern handles React 18's concurrent rendering timing.


410-443: Thorough password visibility toggle testing.

The test correctly verifies that clicking either toggle button affects both password fields due to the shared hidePassword state. Using data-testid for toggle icons is appropriate here.

client/components/registration/registration.js (2)

379-379: Good addition of data-testid for form testing.

Adding data-testid="registration-form" enables reliable form selection in RTL tests, complementing the existing id attribute.


674-678: Helpful data-testid for conditional section testing.

Adding data-testid="billing-info" to the billing container enables RTL tests to verify the section renders only when doesPlanRequireInvoice() returns true.

client/components/registration/registration.test.js (8)

153-154: Static analysis false positive: test fixtures, not secrets.

The static analysis tool flagged these as potential API keys, but they are clearly mock response data used throughout the test suite. No action needed.


17-88: LGTM: Comprehensive mock configuration.

The mock configuration is well-structured and returns a deep clone to prevent test pollution. This follows RTL best practices for pre-import mocking.


94-183: LGTM: Well-structured test utilities.

The helper functions (createTestProps, createMockStore, renderWithProviders, mountComponent) are well-implemented and follow RTL patterns. The distinction between renderWithProviders and mountComponent serves different testing needs appropriately.


234-254: LGTM: Excellent RTL migration with semantic queries.

The test correctly uses semantic queries (getByRole, getByLabelText) and proper event handling. This improves test maintainability and reflects actual user interactions.


256-385: LGTM: Comprehensive form submission testing.

This test thoroughly covers multiple submission scenarios (validation errors, API errors, success) using sequential mocks. The async handling with waitFor and proper assertions make this a robust test.


387-431: LGTM: Thorough optional field configuration testing.

These tests properly verify conditional rendering and accessibility (label text) based on field settings. Good coverage of the various configuration states.


468-551: LGTM: Comprehensive modal interaction testing.

These tests properly cover various conflict scenarios (409 status) and verify modal behavior. Good use of waitFor for async DOM updates.


577-717: LGTM: Thorough mobile phone verification testing.

This suite comprehensively tests the mobile phone verification flow, including lazy loading of PhoneInput (via Suspense), form submission, and field presence based on configuration.

client/components/login/login.test.js (4)

18-94: Mock configuration and setup look good.

The mock setup is correctly placed before imports, and mockConfig includes all necessary configuration fields for comprehensive test coverage. The jest.mock calls properly isolate dependencies.


148-174: Clean helper implementation for rendering tests.

The renderWithProvider helper is appropriately minimal, providing the Redux and Router context needed for rendering tests without overcomplicating the setup.


675-720: Good coverage for token storage behavior.

This test correctly verifies that when remember_me is unchecked, the auth token is stored in sessionStorage rather than localStorage, and rememberMe is set to "false" in localStorage.


976-1031: Comprehensive radius_realms form submission test.

The test effectively verifies the captive portal form structure, hidden field values, and submission flow when radius_realms is enabled. The approach of mocking form.submit to verify the call is appropriate for this scenario.

client/components/password-change/password-change.js (2)

38-38: Mounting guard initialized.

The componentIsMounted flag is part of a valid defensive pattern to prevent setState warnings after unmount.


182-186: Good accessibility enhancement.

Adding aria-label="password change form" improves screen reader navigation and makes the form easily identifiable in tests.

client/components/password-change/password-change.test.js (4)

11-44: Well-structured mock setup.

Mocking modules before imports follows Jest best practices. The detailed get-config mock provides realistic test data for password field configuration.


64-95: Excellent test utilities for RTL migration.

The createMockStore and renderWithProviders helpers centralize provider setup and ensure consistent test context across all test cases.


97-126: Rendering tests migrated successfully to RTL.

The tests combine snapshot testing with specific assertions (e.g., checking cancel button absence when password_expired is true), providing both regression protection and targeted verification.


333-366: Token validation and redirect tests implemented correctly.

The tests properly verify token validation calls and check that the form is not rendered for SAML and Social Login methods, confirming the redirect behavior.

Note: The previous review concern about incorrect rerender usage has been properly addressed by splitting into separate test cases.

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: 3

Fix all issues with AI Agents 🤖
In @client/app.js:
- Around line 49-50: document.getElementById("root") can return null, so guard
the container before passing it to createRoot: check the variable container
after const container = document.getElementById("root") and if it's null, log an
error (or throw) and abort initialization (or use a safe fallback) instead of
calling createRoot(null); only call createRoot(container) and proceed to use
root when container is non-null.
- Around line 48-61: The app() function unconditionally calls createRoot on the
same DOM container causing multiple React roots; fix by caching the root
instance at module scope and reusing it: add a module-level variable (e.g., let
root = null), in app() check for the container and if root is null assign root =
createRoot(container), otherwise reuse the cached root, then call
root.render(...); also guard against missing container before creating the root.

In @client/components/header/header.js:
- Line 80: Duplicate data-testid="header" is used for both desktop and mobile
header elements; update the attributes so each header has a unique test id
(e.g., data-testid="header-desktop" for the element with className
"header-container header-desktop" and data-testid="header-mobile" for the mobile
header) and update any tests that query "header" to use the new specific ids.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
client/components/mobile-phone-change/mobile-phone-change.js (1)

100-115: Handle abort errors in the catch block.

When the component unmounts and abortController.abort() is called, axios will throw an AbortError that will be caught here. The current code attempts to access error.response.data which will be undefined for abort errors, causing a runtime error. Additionally, setState will still be called on the unmounted component.

🔎 Proposed fix
      .catch((error) => {
+       // Ignore abort errors when component unmounts
+       if (error.name === 'AbortError' || error.name === 'CanceledError') {
+         return;
+       }
+       if (!error.response) {
+         setLoading(false);
+         return;
+       }
        const {data} = error.response;
        const errorText = getErrorText(error);
        if (errorText) {
          logError(error, errorText);
          toast.error(errorText);
        }
        setLoading(false);
        this.setState({
          errors: {
            ...errors,
            ...(data.phoneNumber ? {phoneNumber: data.phoneNumber} : null),
            ...(errorText ? {nonField: errorText} : {nonField: ""}),
          },
        });
      });
♻️ Duplicate comments (4)
client/components/password-reset/password-reset.test.js (2)

106-118: Test name still doesn't match assertions.

The test is named "should render 2 inputs" but only verifies 1 input (emailInput) and 1 button (submitButton). This was flagged previously but the fix doesn't appear to be applied in the current code.


237-257: Assertion may false-positive if form is removed after success.

After a successful password reset, if the component replaces the form with a success message (as mentioned in previous review context), the emailInput queried at line 242 may no longer be in the DOM when line 255 executes. The assertion expect(emailInput).not.toHaveClass("error") could then pass incorrectly because the element is unmounted rather than because it lacks the error class.

This was flagged previously but the fix doesn't appear to be applied in the current code.

client/components/payment-process/payment-process.test.js (1)

208-235: Use mockResolvedValue(false) for consistency with async function mock.

Line 212 uses mockReturnValue(false) while other tests (lines 148, 169, 191, etc.) correctly use mockResolvedValue. Since validateToken is async, use mockResolvedValue(false) for semantic correctness.

🔎 Proposed fix
-    validateToken.mockReturnValue(false);
+    validateToken.mockResolvedValue(false);
client/components/login/login.test.js (1)

281-297: Typo: lastConsoleoutput should be lastConsoleOutput.

The variable name has inconsistent casing - should be lastConsoleOutput with capital 'O' in 'Output' for consistency with camelCase conventions.

🔎 Proposed fix
-  let lastConsoleoutput;
+  let lastConsoleOutput;

Update all usages throughout the file accordingly.

🧹 Nitpick comments (17)
client/components/contact-box/contact.js (1)

84-88: Consider more specific PropTypes for better type safety.

The PropTypes definitions could be more specific to improve type checking and documentation:

  • socialLinks could use PropTypes.arrayOf(PropTypes.shape({...})) to define the link object structure
  • preHtml and afterHtml could be more specific if their structure is known
🔎 Suggested PropTypes refinement
 contactPage: PropTypes.shape({
-  socialLinks: PropTypes.array,
+  socialLinks: PropTypes.arrayOf(
+    PropTypes.shape({
+      url: PropTypes.string.isRequired,
+      icon: PropTypes.string,
+      alt: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
+      text: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
+      css: PropTypes.string,
+      authenticated: PropTypes.bool,
+      verified: PropTypes.bool,
+    })
+  ),
   email: PropTypes.string,
   helpdesk: PropTypes.string,
-  preHtml: PropTypes.object,
-  afterHtml: PropTypes.object,
+  preHtml: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
+  afterHtml: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
 }).isRequired,
client/components/contact-box/contact.test.js (1)

9-21: Consider removing orphaned eslint-enable comment.

The eslint-enable comment on line 21 appears without a corresponding eslint-disable. Since jest.mock calls are hoisted by Jest and don't require eslint rule suppression, this comment can likely be removed.

🔎 Suggested cleanup
 }));
 jest.mock("../../utils/load-translation");
-/* eslint-enable import/first */
client/components/payment-status/payment-status.js (1)

19-19: Consider using AbortController instead of the componentIsMounted flag.

While the componentIsMounted flag prevents the "setState on unmounted component" warning, the React team recommends using AbortController or cleanup functions for managing async operations, especially with React 18's concurrent features.

🔎 Alternative approach using AbortController
  constructor(props) {
    super(props);
    this.state = {
      isTokenValid: null,
    };
    this.paymentProceedHandler = this.paymentProceedHandler.bind(this);
-   this.componentIsMounted = false;
+   this.abortController = new AbortController();
  }

Then update componentDidMount:

async componentDidMount() {
  const {cookies, orgSlug, setUserData, logout, params, settings, language} =
    this.props;
  const {status} = params;
  let {userData} = this.props;
  const {setLoading} = this.context;

  setLoading(true);
  
  try {
    const isTokenValid = await validateToken(
      cookies,
      orgSlug,
      setUserData,
      userData,
      logout,
      language,
      this.abortController.signal, // Pass signal if validateToken supports it
    );
    setLoading(false);
    
    if (!this.abortController.signal.aborted) {
      this.setState({isTokenValid});
    }
    // ... rest of logic
  } catch (error) {
    if (error.name === 'AbortError') {
      return; // Component unmounted, ignore
    }
    throw error;
  }
}

And update componentWillUnmount:

  componentWillUnmount() {
-   this.componentIsMounted = false;
+   this.abortController.abort();
  }
client/components/password-reset/password-reset.test.js (4)

77-82: Consider accepting render options in renderWithProviders.

The helper works well but doesn't forward options like container or baseElement from RTL's render function. For most tests this is fine, but accepting and spreading options would improve flexibility for edge cases.

🔎 Optional enhancement
-const renderWithProviders = (component) =>
+const renderWithProviders = (component, options = {}) =>
   render(
     <Provider store={createMockStore()}>
       <MemoryRouter>{component}</MemoryRouter>
-    </Provider>,
+    </Provider>,
+    options,
   );

172-226: Consider splitting this test into three separate tests.

This test covers three distinct scenarios (error with detail, error with non_field_errors, success) in a single test function with shared axios mock setup. While this works, it reduces test isolation and makes debugging failures harder—if the first scenario fails, the subsequent scenarios won't run.

Suggested approach

Split into three focused tests:

  • "should show error toast and add error class when detail error occurs"
  • "should show error toast when non_field_errors occur"
  • "should show success toast and hide form when password reset succeeds"

Each test would have its own axios mock and be independently runnable.


166-166: Minor: Input name attribute is generic.

The input's name attribute is set to "input" rather than "email", which is less semantically clear. While this doesn't affect functionality in the test, using "email" would better match the field's purpose.


145-151: Console error mock could hide unexpected errors.

The console.error mock captures all console errors during tests. While this is useful for verifying expected warnings (like React act() warnings), it could also silently suppress unexpected errors that should fail the test. Consider adding an assertion in afterEach to verify console.error was only called when expected.

client/components/password-confirm/password-confirm.test.js (1)

466-493: Consider using consistent axios mocking approach.

Line 467 uses mockImplementationOnce(() => Promise.resolve(...)) while line 367 uses the more concise mockResolvedValueOnce(...). For consistency and simplicity, consider using mockResolvedValueOnce throughout.

🔎 Optional refactor for consistency
-    axios.mockImplementationOnce(() => Promise.resolve({data: {detail: true}}));
+    axios.mockResolvedValueOnce({data: {detail: true}});
client/components/password-confirm/password-confirm.js (1)

109-109: Consider refactoring login_page_link to loginPageLink for consistency, but this requires updating the configuration layer.

The prop login_page_link is defined in configuration files (internals/config/default.yml, internals/generators/organization/config.yml.hbs) and used in both password-confirm and password-reset components. The current approach destructures it with a camelCase alias for internal use, which works but creates a naming mismatch between PropTypes (snake_case) and usage (camelCase).

To complete the camelCase refactor across this component chain, you'd need to update the YAML configuration files and ensure downstream code handles the renamed property. This is a larger structural change beyond the component level, so treat it as an optional improvement rather than a required fix.

client/components/payment-process/payment-process.js (1)

145-148: Remove redundant defaultProps.

Explicitly setting defaultProps to undefined is redundant, as this is already the default behavior when props are not provided. Consider removing these default props or providing meaningful default values.

🔎 Proposed refactor
-PaymentProcess.defaultProps = {
-  orgSlug: undefined,
-  isAuthenticated: undefined,
-};

If you need these props to be explicitly optional in PropTypes, consider marking them as optional without defaultProps:

PaymentProcess.propTypes = {
  orgSlug: PropTypes.string,  // already marked optional (no .isRequired)
  // ...
  isAuthenticated: PropTypes.bool,  // already marked optional
};
client/components/mobile-phone-verification/mobile-phone-verification.js (1)

85-87: Lifecycle guard pattern is correctly implemented, but async handlers lack protection.

The componentIsMounted guard is properly set up in constructor/mount/unmount. However, several async callbacks still perform unguarded state updates:

  • handleSubmit success callback (lines 114-123)
  • handleSubmit error callback (lines 131-137)
  • createPhoneToken callbacks (lines 166-188)
  • resendPhoneToken setState (lines 227-230)

While the componentDidMount flow is guarded, form submissions and resend actions could still trigger state updates after unmount if the user navigates away mid-request.

🔎 Example guard for handleSubmit
       .then(() => {
+        if (!this.componentIsMounted) return;
         this.setState({
           errors: {},
         });
client/components/password-change/password-change.test.js (1)

87-92: Consider adding LoadingContext.Provider to test helper.

The PasswordChange component uses LoadingContext (line 43 in the component calls this.context.setLoading). Without the provider, loading state changes are silently ignored. Consider wrapping with LoadingContext.Provider like in payment-process.test.js.

🔎 Proposed enhancement
+import LoadingContext from "../../utils/loading-context";
+
 const renderWithProviders = (component) =>
   render(
     <Provider store={createMockStore()}>
-      <MemoryRouter>{component}</MemoryRouter>
+      <LoadingContext.Provider value={{setLoading: jest.fn()}}>
+        <MemoryRouter>{component}</MemoryRouter>
+      </LoadingContext.Provider>
     </Provider>,
   );
client/components/mobile-phone-verification/mobile-phone-verification.test.js (1)

90-95: Consider adding LoadingContext.Provider for complete test coverage.

Similar to password-change.test.js, the MobilePhoneVerification component uses LoadingContext. Adding the provider would ensure loading state changes are testable.

client/components/mobile-phone-change/mobile-phone-change.js (1)

235-238: Inconsistent naming in PropTypes.

change_phoneNumber mixes snake_case with camelCase, which is inconsistent with the camelCase convention used elsewhere in this refactor (e.g., phoneNumber, mobilePhoneVerification).

🔎 Proposed fix
     buttons: PropTypes.shape({
-      change_phoneNumber: PropTypes.bool,
+      changePhoneNumber: PropTypes.bool,
       cancel: PropTypes.bool,
     }).isRequired,
client/components/mobile-phone-change/index.js (1)

8-13: Mixed naming convention in config key.

phoneNumberChange_form uses an underscore before form, which is inconsistent with full camelCase (would be phoneNumberChangeForm). If this matches a backend/config key that can't be changed, consider adding a comment explaining the naming.

client/components/login/login.js (1)

54-56: Inconsistent naming: passwordless_authToken_name mixes conventions.

This property uses snake_case (passwordless_) combined with camelCase (authToken). If this must match a backend/config key, consider adding a comment. Otherwise, align with the camelCase convention used elsewhere.

Also applies to: 224-226, 569-569

client/components/mobile-phone-verification/index.js (1)

9-9: Consistent with other components but mixed naming convention.

mobilePhoneVerification_form follows the same pattern as phoneNumberChange_form in the sibling component. This is internally consistent but uses a mixed convention (camelCase + underscore). If this maps to a configuration key structure, it's acceptable; otherwise, consider mobilePhoneVerificationForm for full consistency.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9d493a3 and be42fc1.

⛔ Files ignored due to path filters (7)
  • client/components/404/__snapshots__/404.test.js.snap is excluded by !**/*.snap
  • client/components/footer/__snapshots__/footer.test.js.snap is excluded by !**/*.snap
  • client/components/header/__snapshots__/header.test.js.snap is excluded by !**/*.snap
  • client/components/login/__snapshots__/login.test.js.snap is excluded by !**/*.snap
  • client/components/mobile-phone-change/__snapshots__/mobile-phone-change.test.js.snap is excluded by !**/*.snap
  • client/components/organization-wrapper/__snapshots__/organization-wrapper.test.js.snap is excluded by !**/*.snap
  • client/components/registration/__snapshots__/registration.test.js.snap is excluded by !**/*.snap
📒 Files selected for processing (92)
  • .eslintrc.json
  • CHANGES.md
  • browser-test/create-mobile-configuration.js
  • browser-test/initialize_data.py
  • browser-test/mobile-phone-change.test.js
  • client/actions/actions.test.js
  • client/actions/logout.js
  • client/app.js
  • client/components/404/404.js
  • client/components/404/index.css
  • client/components/contact-box/contact.js
  • client/components/contact-box/contact.test.js
  • client/components/footer/footer.js
  • client/components/footer/footer.test.js
  • client/components/header/header.js
  • client/components/header/header.test.js
  • client/components/login/index.css
  • client/components/login/index.js
  • client/components/login/login.js
  • client/components/login/login.test.js
  • client/components/logout/logout.js
  • client/components/logout/logout.test.js
  • client/components/mobile-phone-change/index.js
  • client/components/mobile-phone-change/mobile-phone-change.js
  • client/components/mobile-phone-change/mobile-phone-change.test.js
  • client/components/mobile-phone-verification/index.js
  • client/components/mobile-phone-verification/mobile-phone-verification.js
  • client/components/mobile-phone-verification/mobile-phone-verification.test.js
  • client/components/modal/index.js
  • client/components/modal/modal.js
  • client/components/modal/modal.test.js
  • client/components/organization-wrapper/organization-wrapper.js
  • client/components/organization-wrapper/organization-wrapper.test.js
  • client/components/password-change/password-change.js
  • client/components/password-change/password-change.test.js
  • client/components/password-confirm/password-confirm.js
  • client/components/password-confirm/password-confirm.test.js
  • client/components/password-reset/password-reset.js
  • client/components/password-reset/password-reset.test.js
  • client/components/payment-process/payment-process.js
  • client/components/payment-process/payment-process.test.js
  • client/components/payment-status/payment-status.js
  • client/components/payment-status/payment-status.test.js
  • client/components/registration/index.js
  • client/components/registration/registration.js
  • client/components/registration/registration.test.js
  • client/components/registration/subscriptions.test.js
  • client/components/status/status.js
  • client/components/status/status.test.js
  • client/components/status/status.test.js.enzyme-backup
  • client/components/status/status.test.js.rtl-backup
  • client/reducers/organization.js
  • client/reducers/reducers.test.js
  • client/test-config.json
  • client/test-translation.json
  • client/utils/__mocks__/get-config.js
  • client/utils/authenticate.js
  • client/utils/get-error-text.js
  • client/utils/get-html.js
  • client/utils/get-payment-status.js
  • client/utils/handle-logout.js
  • client/utils/load-translation.test.js
  • client/utils/loader.js
  • client/utils/modal.js
  • client/utils/needs-verify.js
  • client/utils/render-additional-info.js
  • client/utils/session.js
  • client/utils/should-link-be-shown.js
  • client/utils/utils.test.js
  • client/utils/validate-token.js
  • config/__tests__/add-org.test.js
  • config/add-org.js
  • config/setup.js
  • docs/user/intro.rst
  • docs/user/settings.rst
  • docs/user/setup.rst
  • i18n/de.po
  • i18n/en.po
  • i18n/es.po
  • i18n/fur.po
  • i18n/it.po
  • i18n/ru.po
  • i18n/sl.po
  • internals/config/default.yml
  • internals/generators/organization/config.yml.hbs
  • server/controllers/mobile-phone-number-change-controller.js
  • server/controllers/mobile-phone-token-controller.js
  • server/controllers/obtain-token-controller.js
  • server/controllers/registration-controller.js
  • server/controllers/validate-token-controller.js
  • server/utils/openwisp-urls.js
  • server/utils/send-cookies.js
💤 Files with no reviewable changes (1)
  • client/components/logout/logout.js
🚧 Files skipped from review as they are similar to previous changes (3)
  • browser-test/mobile-phone-change.test.js
  • client/components/password-reset/password-reset.js
  • .eslintrc.json
🧰 Additional context used
🧬 Code graph analysis (24)
client/app.js (1)
client/store/index.js (1)
  • store (6-6)
client/components/mobile-phone-change/mobile-phone-change.test.js (2)
client/utils/__mocks__/get-config.js (1)
  • getConfig (10-15)
client/utils/loading-context.js (3)
  • loadingContextValue (3-3)
  • loadingContextValue (3-3)
  • LoadingContext (4-4)
client/components/footer/footer.js (2)
client/utils/get-html.js (1)
  • getHtml (4-13)
client/reducers/language.js (1)
  • language (3-10)
client/components/payment-status/payment-status.js (3)
client/components/login/login.test.js (1)
  • userData (129-142)
client/components/logout/logout.test.js (1)
  • userData (17-23)
client/components/organization-wrapper/organization-wrapper.test.js (1)
  • userData (46-59)
client/components/login/index.js (3)
client/components/login/login.test.js (1)
  • loginForm (95-95)
client/components/mobile-phone-change/index.js (1)
  • conf (7-7)
client/components/registration/index.js (1)
  • conf (7-7)
client/components/modal/modal.test.js (4)
client/components/modal/modal.js (1)
  • Modal (14-93)
client/utils/get-text.js (1)
  • getText (1-5)
client/utils/log-error.js (1)
  • logError (1-13)
client/components/modal/index.js (2)
  • mapStateToProps (6-12)
  • mapStateToProps (6-12)
client/components/contact-box/contact.test.js (1)
client/components/contact-box/contact.js (1)
  • Contact (12-74)
client/components/organization-wrapper/organization-wrapper.test.js (4)
client/utils/__mocks__/get-config.js (1)
  • getConfig (10-15)
client/components/organization-wrapper/organization-wrapper.js (1)
  • OrganizationWrapper (36-391)
client/utils/needs-verify.js (1)
  • needsVerify (5-28)
client/utils/__mocks__/load-translation.js (1)
  • loadTranslation (17-27)
client/components/password-confirm/password-confirm.test.js (1)
client/components/password-confirm/password-confirm.js (1)
  • PasswordConfirm (20-230)
client/components/contact-box/contact.js (2)
client/utils/get-html.js (1)
  • getHtml (4-13)
client/reducers/language.js (1)
  • language (3-10)
client/components/mobile-phone-change/index.js (12)
client/components/mobile-phone-change/mobile-phone-change.test.js (2)
  • componentConf (76-76)
  • conf (75-75)
client/components/login/index.js (1)
  • conf (7-7)
client/components/mobile-phone-verification/index.js (1)
  • conf (7-7)
client/components/registration/index.js (1)
  • conf (7-7)
client/components/status/index.js (1)
  • conf (12-12)
client/components/payment-status/index.js (1)
  • conf (7-7)
client/components/password-confirm/index.js (1)
  • conf (8-8)
client/components/password-change/index.js (1)
  • conf (7-7)
client/components/footer/index.js (1)
  • conf (6-6)
client/components/password-reset/index.js (1)
  • conf (7-7)
client/components/logout/index.js (1)
  • conf (6-6)
client/components/contact-box/index.js (1)
  • conf (6-6)
client/components/organization-wrapper/organization-wrapper.js (3)
client/utils/needs-verify.js (1)
  • needsVerify (5-28)
client/components/organization-wrapper/lazy-import.js (6)
  • Registration (4-6)
  • Registration (4-6)
  • MobilePhoneVerification (25-30)
  • MobilePhoneVerification (25-30)
  • Status (7-9)
  • Status (7-9)
client/utils/loading-context.js (1)
  • LoadingContext (4-4)
client/components/payment-process/payment-process.js (1)
client/components/login/login.test.js (1)
  • userData (129-142)
client/components/header/header.test.js (3)
client/components/header/header.js (1)
  • Header (12-274)
client/utils/check-internal-links.js (1)
  • isInternalLink (1-4)
client/components/header/index.js (2)
  • mapDispatchToProps (15-19)
  • mapDispatchToProps (15-19)
client/components/password-change/password-change.js (2)
client/components/login/login.test.js (1)
  • userData (129-142)
server/controllers/password-change-controller.js (1)
  • passwordChange (10-73)
client/components/password-change/password-change.test.js (1)
client/components/password-change/password-change.js (1)
  • PasswordChange (22-251)
client/components/login/login.test.js (4)
client/utils/get-config.js (1)
  • getConfig (3-13)
client/components/login/login.js (1)
  • Login (35-510)
client/utils/redirect-to-payment.js (1)
  • redirectToPayment (3-4)
client/utils/get-parameter-by-name.js (1)
  • getParameterByName (1-9)
browser-test/initialize_data.py (1)
browser-test/create-mobile-configuration.js (1)
  • data (6-6)
client/components/mobile-phone-verification/index.js (11)
client/components/login/index.js (1)
  • conf (7-7)
client/components/mobile-phone-change/index.js (1)
  • conf (7-7)
client/components/registration/index.js (1)
  • conf (7-7)
client/components/status/index.js (1)
  • conf (12-12)
client/components/payment-status/index.js (1)
  • conf (7-7)
client/components/password-confirm/index.js (1)
  • conf (8-8)
client/components/password-change/index.js (1)
  • conf (7-7)
client/components/footer/index.js (1)
  • conf (6-6)
client/components/password-reset/index.js (1)
  • conf (7-7)
client/components/logout/index.js (1)
  • conf (6-6)
client/components/contact-box/index.js (1)
  • conf (6-6)
client/components/payment-process/payment-process.test.js (6)
client/utils/loading-context.js (1)
  • LoadingContext (4-4)
client/utils/get-payment-status.js (1)
  • getPaymentStatusRedirectUrl (37-71)
client/utils/validate-token.js (1)
  • validateToken (11-87)
client/utils/get-config.js (1)
  • getConfig (3-13)
client/components/payment-process/payment-process.js (1)
  • PaymentProcess (11-131)
client/utils/tick.js (1)
  • tick (1-12)
client/components/logout/logout.test.js (1)
client/components/logout/logout.js (1)
  • Logout (15-61)
client/components/mobile-phone-verification/mobile-phone-verification.js (6)
client/components/login/login.test.js (1)
  • userData (129-142)
client/components/mobile-phone-change/mobile-phone-change.test.js (1)
  • userData (173-179)
client/components/mobile-phone-verification/mobile-phone-verification.test.js (1)
  • userData (97-104)
client/components/organization-wrapper/organization-wrapper.test.js (1)
  • userData (46-59)
client/constants/index.js (2)
  • createMobilePhoneTokenUrl (15-16)
  • createMobilePhoneTokenUrl (15-16)
client/utils/get-language-headers.js (1)
  • getLanguageHeaders (1-5)
client/components/login/login.js (4)
client/utils/get-parameter-by-name.js (1)
  • getParameterByName (1-9)
client/components/login/index.js (1)
  • loginForm (8-8)
client/utils/get-text.js (1)
  • getText (1-5)
client/utils/authenticate.js (1)
  • authenticate (3-13)
client/components/modal/modal.js (1)
config/setup.js (1)
  • content (90-90)
🪛 Biome (2.1.2)
client/components/modal/modal.js

[error] 87-87: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

🪛 Gitleaks (8.30.0)
client/components/mobile-phone-change/mobile-phone-change.test.js

[high] 175-175: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 419-419: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

client/components/organization-wrapper/organization-wrapper.test.js

[high] 53-53: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 54-54: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

client/components/payment-status/payment-status.test.js

[high] 92-92: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 93-93: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

client/components/login/login.test.js

[high] 136-136: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 137-137: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 696-696: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 887-887: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 888-888: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

client/components/payment-process/payment-process.test.js

[high] 128-128: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 129-129: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

client/components/mobile-phone-verification/mobile-phone-verification.test.js

[high] 99-99: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Tests and Coverage

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

Fix all issues with AI Agents 🤖
In @client/components/organization-wrapper/organization-wrapper.test.js:
- Line 469: Change the test's localStorage call to store the string "true"
instead of the boolean true for the "userAutoLogin" key to match project
conventions; update the line using localStorage.setItem("userAutoLogin", ...) in
organization-wrapper.test.js so it writes the string "true" (consistent with
usages like String(userAutoLogin) and "false" elsewhere).

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
client/components/modal/modal.test.js (1)

224-243: Incomplete test coverage: missing orgSlug verification.

The test doesn't verify the orgSlug property that mapStateToProps maps from state.organization.configuration.slug. The input state should include slug and the assertion should verify orgSlug in the result.

🔎 Proposed fix to include orgSlug verification
  it("should map state to props", () => {
    const result = mapStateToProps(
      {
        organization: {
          configuration: {
+           slug: "default",
            privacyPolicy: "# Privacy Policy",
            termsAndConditions: "# Terms and Conditions",
          },
        },
        language: "en",
      },
      {prevPath: "/default/login"},
    );
    expect(result).toEqual({
+     orgSlug: "default",
      privacyPolicy: "# Privacy Policy",
      termsAndConditions: "# Terms and Conditions",
      language: "en",
      prevPath: "/default/login",
    });
  });
client/app.js (1)

73-81: Add HMR callback to re-trigger re-renders on module updates.

The entry point correctly calls app() from client/index.js, so no changes needed there. However, the HMR setup is incomplete:

The module.hot.accept() at line 74 accepts module updates but doesn't re-trigger the app function. With the cached root pattern you have (lines 48–70), subsequent app() calls correctly re-render. To enable hot reloading in development, pass a callback to accept():

if (module && module.hot) {
  module.hot.accept(() => {
    app();
  });
}

This ensures changes to app.js trigger a re-render during development.

🧹 Nitpick comments (5)
client/components/login/login.test.js (1)

370-446: Well-structured comprehensive error handling test.

This test effectively validates multiple error scenarios (field errors, 500, 504, and success) in sequence using axios mock chains. While lengthy, the sequential approach ensures proper state transitions between scenarios.

Optional: Consider splitting into separate tests for improved isolation

If you want more granular test isolation, you could split this into four separate tests:

  • should show field errors on validation failure
  • should show toast on 500 server error
  • should show toast on 504 gateway timeout
  • should clear errors on successful submission

However, the current approach is also valid and ensures sequential error state handling.

client/components/organization-wrapper/organization-wrapper.test.js (4)

44-44: Orphaned ESLint comment.

The /* eslint-enable import/first */ comment has no matching /* eslint-disable */ in the visible code. This appears to be leftover from a previous structure and can be removed.

🔎 Proposed fix
-/* eslint-enable import/first */

178-228: CSS/JS loading tests only verify prop values, not actual Helmet behavior.

The tests at lines 198-201 and 224-227 assert that props.organization.configuration.css and .js contain expected values, which is essentially testing the test setup rather than the component behavior. While the comments acknowledge this limitation, consider adding assertions that verify Helmet was called or that the component rendered without errors in these specific configurations.

That said, this is acceptable given HelmetProvider's testing limitations.


529-537: Consider strengthening redirect assertion.

The test comment says "Unverified users trying to access status should be redirected," but the assertion only checks that app-container is present. Consider verifying that the redirect actually occurred by checking for verification-specific content or asserting that the status route content is not present.


554-558: Redundant fallback for header/footer in mountComponent.

Lines 556-557 fall back to defaultConfig.components.header/footer, but components is already destructured from defaultConfig at line 542-543. The fallback components.header || defaultConfig.components.header is always the same value since components === defaultConfig.components.

🔎 Proposed fix
               contact_page: components.contact_page || {},
-              header: components.header || defaultConfig.components.header,
-              footer: components.footer || defaultConfig.components.footer,
+              header: components.header,
+              footer: components.footer,
             },
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between be42fc1 and adba120.

⛔ Files ignored due to path filters (4)
  • client/components/footer/__snapshots__/footer.test.js.snap is excluded by !**/*.snap
  • client/components/header/__snapshots__/header.test.js.snap is excluded by !**/*.snap
  • client/components/login/__snapshots__/login.test.js.snap is excluded by !**/*.snap
  • client/components/organization-wrapper/__snapshots__/organization-wrapper.test.js.snap is excluded by !**/*.snap
📒 Files selected for processing (19)
  • README.rst
  • client/app.js
  • client/components/header/header.js
  • client/components/login/login.test.js
  • client/components/modal/modal.test.js
  • client/components/organization-wrapper/organization-wrapper.test.js
  • client/test-config.json
  • client/utils/__mocks__/get-config.js
  • docs/developer/index.rst
  • docs/developer/installation.rst
  • docs/developer/usage.rst
  • docs/index.rst
  • docs/user/extra-js.rst
  • docs/user/handling-radius-errors.rst
  • docs/user/internet-mode.rst
  • docs/user/intro.rst
  • docs/user/settings.rst
  • docs/user/setup.rst
  • docs/user/translations.rst
🚧 Files skipped from review as they are similar to previous changes (1)
  • client/components/header/header.js
🧰 Additional context used
🧬 Code graph analysis (4)
client/components/organization-wrapper/organization-wrapper.test.js (3)
client/utils/__mocks__/get-config.js (2)
  • defaultConfig (3-3)
  • getConfig (12-17)
client/components/organization-wrapper/organization-wrapper.js (1)
  • OrganizationWrapper (36-391)
client/utils/__mocks__/load-translation.js (1)
  • loadTranslation (17-27)
client/components/login/login.test.js (3)
client/utils/get-config.js (1)
  • getConfig (3-13)
client/utils/__mocks__/get-parameter-by-name.js (1)
  • getParameterByName (1-1)
client/utils/get-parameter-by-name.js (1)
  • getParameterByName (1-9)
client/app.js (1)
client/store/index.js (1)
  • store (6-6)
client/components/modal/modal.test.js (3)
client/components/modal/modal.js (1)
  • Modal (14-93)
client/utils/get-text.js (1)
  • getText (1-5)
client/components/modal/index.js (2)
  • mapStateToProps (6-12)
  • mapStateToProps (6-12)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Tests and Coverage
🔇 Additional comments (23)
README.rst (1)

1-3: Clarify scope: Why are header style changes included if documentation updates were deferred?

The header styles have been reformatted from simple underlines to decorative asterisk borders, which is syntactically valid reStructuredText. However, this change appears unrelated to the React 18.3 upgrade objectives. Additionally, the PR checklist indicates "documentation not updated," yet the README has been modified.

Consider:

  1. Are these header style changes intentional as part of this PR, or should they be reverted to keep the scope tightly focused on the React upgrade?
  2. If documentation updates are in scope, consider adding substantive upgrade notes (e.g., React 18.3 release highlights, breaking changes for contributors, testing framework migration details) to help users and developers understand the modernization effort.

Also applies to: 51-53, 60-62, 67-69, 74-76, 81-83

client/components/modal/modal.test.js (5)

1-29: LGTM! Clean RTL migration and proper test setup.

The migration from Enzyme to React Testing Library is well-executed with appropriate imports, mocking, and test utilities. The updated prop names (privacyPolicy, termsAndConditions) correctly align with the camelCase refactoring across the codebase.


31-145: LGTM! Comprehensive rendering test coverage.

The rendering tests properly cover success paths (terms-and-conditions, privacy-policy), edge cases (incorrect param), and error handling (bad request). The RTL patterns are correctly applied with proper async handling and snapshot testing.


147-222: LGTM! Thorough keyboard interaction testing.

The keyboard tests properly verify both Esc key navigation and that non-Esc keys don't trigger navigation. The use of fireEvent.keyUp correctly matches the component's keyup event listener, and the setup/teardown prevents test pollution.


245-274: LGTM! Excellent side-effect testing.

This test properly verifies the scrollbar hiding behavior: overflow: hidden is set when the modal mounts and overflow: auto is restored on unmount. The use of unmount() and toHaveStyle() matcher demonstrates good RTL testing practices.


301-344: LGTM! Robust event listener cleanup verification.

Excellent test that verifies event listeners are properly added on mount and removed on unmount, preventing memory leaks. The spy setup and cleanup is handled correctly.

client/app.js (4)

3-5: LGTM! Correct React 18 and react-helmet-async imports.

The imports correctly use createRoot from react-dom/client for React 18 and HelmetProvider from react-helmet-async, aligning with the migration objectives.


26-26: LGTM! React Router v6 migration correctly removes history prop.

The Router component correctly omits the history prop, which is the proper pattern for React Router v6. The past review concern about the unused history import appears to have been resolved, as no such import is present in the current code.


48-60: LGTM! Root caching and container guard properly implemented.

The implementation correctly addresses the previous review concerns:

  • The module-level root variable with the if (root === null) check prevents multiple React 18 root creation.
  • The container null check with a descriptive error message prevents runtime failures.

This follows React 18 best practices for application mounting.


62-70: LGTM! Provider hierarchy correctly structured.

The provider wrapping is properly implemented with the correct nesting order:

  1. HelmetProvider (required at top level for react-helmet-async)
  2. CookiesProvider
  3. Redux Provider
  4. App component

This structure ensures all context providers are available throughout the application.

client/components/login/login.test.js (7)

22-145: LGTM! Well-structured mock setup and test data.

The mock configuration and test data are comprehensive and properly aligned with the React 18 migration:

  • mockConfig uses camelCase properties matching the PR's refactoring objectives
  • Mock functions are set up correctly with proper jest.fn() usage
  • responseData correctly transforms authToken to key to match API response shape
  • createTestProps provides sensible defaults for all required props

146-172: LGTM! Clean test helper implementation.

The renderWithProvider helper properly wraps components with Redux Provider and MemoryRouter for simpler rendering tests. The minimal mock store implementation is appropriate for unit testing.


278-309: LGTM! Thorough test lifecycle management.

The beforeEach and afterEach hooks properly clean up mocks, reset axios, clear storage, and manage console.error capture. This prevents test pollution and ensures test isolation.

Note: The typo mentioned in past review comments (lastConsoleOutuputlastConsoleOutput) appears to be already corrected throughout the file.


311-341: LGTM! Comprehensive mounting helper for full integration testing.

The mountComponent helper properly sets up the full React Router v6 context with Provider, Router, and Routes. This is appropriate for tests that need to verify routing behavior and full component integration.


220-267: LGTM! Excellent coverage of lazy-loaded PhoneInput behavior.

These tests properly verify both the Suspense fallback and the actual lazy-loaded PhoneInput component. Good use of findByPlaceholderText and waitFor to handle async loading.


686-736: LGTM! Proper token storage verification.

The test correctly sets up the axios mock before mounting (Line 704 comment is helpful), verifies checkbox state, and confirms tokens are stored in the correct storage location based on the rememberMe setting.


990-1045: LGTM! Thorough radius realms form testing.

Good pattern of unmounting and remounting to test different configurations. The use of toContainHTML for hidden inputs (Lines 1014-1018) is pragmatic since hidden inputs lack accessible roles. The mock of form.submit() properly verifies captive portal behavior.

client/components/organization-wrapper/organization-wrapper.test.js (6)

1-13: LGTM!

The imports are correctly structured for React Testing Library-based tests with proper provider support (HelmetProvider, Redux Provider, MemoryRouter) needed for the migrated React 18 environment.


92-130: LGTM!

The renderWithRouter helper is well-structured, providing consistent provider wrapping (HelmetProvider, Redux Provider, MemoryRouter) with a properly configured mock store. This aligns with RTL best practices for testing components that depend on context.


258-335: LGTM! Past review concerns addressed.

The componentDidUpdate lifecycle test now correctly:

  1. Wraps rerender calls with all required providers (HelmetProvider, Provider, MemoryRouter)
  2. Creates separate mocked stores for each rerender scenario
  3. Uses the corrected variable name lastConsoleOutput
  4. Reuses the consoleErrorSpy from beforeEach instead of creating duplicates

673-681: LGTM!

The header/footer presence test correctly verifies both desktop and mobile header variants along with the footer, ensuring consistent layout rendering across routes.


660-671: Manual verification required: Confirm the 404 component ID spelling correction.

The test at lines 660-671 correctly queries for not-found-404. Verify that the component file was updated to use this correct ID spelling and that no instances of the misspelled not-foud-404 remain in the codebase.


74-77: No action needed.

passwordless_authToken_name is used consistently as a backend API field name throughout the codebase (login.js, test files) and must remain in snake_case to match the backend. The mixed naming conventions in the settings object are intentional: keys like default_language and passwordless_authToken_name are backend API fields (snake_case), while keys like mobilePhoneVerification and subscriptions are internal configuration options (camelCase).

Likely an incorrect or invalid review comment.

@PRoIISHAAN PRoIISHAAN force-pushed the fix-reactversion-migration branch from 227b756 to e01a08f Compare January 7, 2026 22:48
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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
client/components/payment-status/payment-status.js (1)

40-66: Mount guard is incomplete for subsequent state updates.

The componentIsMounted guard protects only the first setState (line 41), but setUserData calls at lines 51-56 and 62-65 execute outside the guard. If the component unmounts after validateToken resolves but before processing completes, these calls still run.

Consider wrapping the entire post-validation logic:

Proposed fix
     if (this.componentIsMounted) {
       this.setState({isTokenValid});
-    }
-    if (isTokenValid === false) {
-      return;
-    }
+      if (isTokenValid === false) {
+        return;
+      }

-    ({userData} = this.props);
-    const {method, is_verified: isVerified} = userData;
-    // flag user to repeat login in order to restart session with new radius group
-    if (status === "success" && method === "bank_card" && isVerified === true) {
-      setUserData({
-        ...userData,
-        mustLogin: !settings.payment_requires_internet,
-        mustLogout: settings.payment_requires_internet,
-        repeatLogin: settings.payment_requires_internet,
-      });
-    } else if (
-      status === "draft" &&
-      method === "bank_card" &&
-      isVerified === false
-    ) {
-      setUserData({
-        ...userData,
-        mustLogin: settings.payment_requires_internet ? true : undefined,
-      });
+      ({userData} = this.props);
+      const {method, is_verified: isVerified} = userData;
+      // flag user to repeat login in order to restart session with new radius group
+      if (status === "success" && method === "bank_card" && isVerified === true) {
+        setUserData({
+          ...userData,
+          mustLogin: !settings.payment_requires_internet,
+          mustLogout: settings.payment_requires_internet,
+          repeatLogin: settings.payment_requires_internet,
+        });
+      } else if (
+        status === "draft" &&
+        method === "bank_card" &&
+        isVerified === false
+      ) {
+        setUserData({
+          ...userData,
+          mustLogin: settings.payment_requires_internet ? true : undefined,
+        });
+      }
     }
🤖 Fix all issues with AI agents
In @client/components/payment-status/payment-status.test.js:
- Around line 17-32: The test mock for get-config does not include the timeout
and max_attempts expected by the PaymentStatus component (it destructures
timeout and max_attempts from the page prop in the component), so update the
jest.mock for default get-config to include payment_status_page.timeout and
payment_status_page.max_attempts (e.g., numeric values like 5 and 3) alongside
the existing content translations so the mock accurately reflects the shape the
component expects.

In @client/components/registration/registration.js:
- Line 737: The label uses a mis-cased translation key taxNumber_LBL; replace it
with the consistent UPPER_SNAKE_CASE key TAX_NUMBER_LBL in the component where
<label htmlFor="tax_number"> is rendered (registration.js) and update any
translation resource files to define/rename the corresponding TAX_NUMBER_LBL
entry so the lookup succeeds.
- Line 173: Change the API field name sent in registration: replace uses of
postData.planPricing with postData.plan_pricing so the payload matches the API
convention (e.g., where plan_pricing.id is assigned) and will align with other
snake_case fields like requires_payment and phone_number; also fix the
translation key: replace the incorrect taxNumber_LBL with the existing
TAX_NUMBER_LBL so the lookup resolves to the defined translation entry.
🧹 Nitpick comments (11)
client/components/mobile-phone-verification/mobile-phone-verification.js (1)

44-44: Good addition of mounting guard, but consider extending to all async operations.

The componentIsMounted flag correctly prevents state updates after unmount in the componentDidMount flow. However, other async methods (handleSubmit, createPhoneToken, resendPhoneToken) still call setState without checking the mounted state, which could cause the same memory leak warnings.

Consider either:

  1. Adding the same this.componentIsMounted check before all setState calls in async callbacks, or
  2. Using AbortController to cancel pending requests in componentWillUnmount (modern pattern for React 18+).
💡 Example using AbortController pattern

In constructor:

this.abortController = new AbortController();

In componentWillUnmount:

 componentWillUnmount() {
-  this.componentIsMounted = false;
+  this.abortController.abort();
 }

In axios calls:

axios({
  method: "post",
  url,
  signal: this.abortController.signal,
  // ... other options
})

This automatically cancels pending requests on unmount.

Also applies to: 48-48, 71-71, 85-87

client/components/password-confirm/password-confirm.test.js (2)

227-399: Excellent test coverage for form submission scenarios.

The nested test suite comprehensively covers validation errors, API error responses (detail, non_field_errors, token fields), and success paths. The setup with toast and console spies is well-organized.

Optional: Consider whether tick() calls before waitFor() are necessary.

The pattern of calling await tick() followed by await waitFor() appears on lines 299, 329, 359, and 389. Since waitFor() already handles async updates and promise resolution, the tick() call might be redundant. However, this could be intentional to ensure axios promises resolve before assertions in React 18's concurrent rendering.

🔍 Verification script

Test whether the tick() calls can be safely removed:

#!/bin/bash
# Check if other migrated test files use tick() before waitFor()
echo "=== Pattern: tick() followed by waitFor() ==="
rg -n "await tick\(\);" client/components/**/*.test.js -A 2 | rg -A 1 "waitFor"

echo -e "\n=== Check tick utility documentation ==="
cat client/utils/tick.js

466-493: Test logic is correct, but consider consistent axios mocking style.

The test properly verifies that error classes are cleared after successful submission.

♻️ Consistent axios mocking style

Line 467 uses axios.mockImplementationOnce(() => Promise.resolve(...)) while other tests in this file use axios.mockResolvedValueOnce(...) (line 367) and axios.mockRejectedValueOnce(...) (lines 279, 309, 339). Both styles are functionally equivalent, but consistency improves readability.

-    axios.mockImplementationOnce(() => Promise.resolve({data: {detail: true}}));
+    axios.mockResolvedValueOnce({data: {detail: true}});
client/components/contact-box/contact.test.js (1)

21-21: Orphaned ESLint directive.

The eslint-enable import/first comment has no corresponding eslint-disable. This is likely a leftover from refactoring. Remove it.

Proposed fix
 jest.mock("../../utils/load-translation");
-/* eslint-enable import/first */
client/components/password-change/password-change.test.js (2)

41-41: Orphaned ESLint directive.

The eslint-enable import/first comment has no corresponding eslint-disable. Remove it.

Proposed fix
 jest.mock("../../utils/handle-logout");
-/* eslint-enable import/first */

128-133: Redundant mock clearing.

axios.mockReset() already clears mock data (includes mockClear() functionality), making axios.mockClear() redundant.

Proposed fix
   beforeEach(() => {
     jest.clearAllMocks();
-    axios.mockClear();
     axios.mockReset();
     props = createTestProps();
   });
client/components/organization-wrapper/organization-wrapper.test.js (1)

469-469: Use string literal for localStorage consistency.

The codebase uses string values for userAutoLogin elsewhere (e.g., String(userAutoLogin) in status.js, "false" in logout.js). Use "true" for consistency.

Suggested fix
-    localStorage.setItem("userAutoLogin", true);
+    localStorage.setItem("userAutoLogin", "true");
client/components/payment-process/payment-process.test.js (1)

212-212: Use mockResolvedValue(false) for async function.

Since validateToken is an async function (awaited in the component), use mockResolvedValue(false) instead of mockReturnValue(false) for semantic correctness.

Suggested fix
-    validateToken.mockReturnValue(false);
+    validateToken.mockResolvedValue(false);
client/components/login/login.test.js (1)

330-341: Consider using MemoryRouter instead of BrowserRouter with history props.

BrowserRouter doesn't accept location and navigator props—those are for the low-level Router component. The historyMock won't actually control navigation here. Consider using MemoryRouter with initialEntries for consistency with other test files.

Suggested fix
-import {createMemoryHistory} from "history";
...
   const mountComponent = function mountComponent(passedProps) {
     const mockedStore = {
       // ... store setup
     };

-    const historyMock = createMemoryHistory();
-
     return render(
       <Provider store={mockedStore}>
-        <Router location={historyMock.location} navigator={historyMock}>
+        <MemoryRouter initialEntries={["/"]}>
           <Routes>
             <Route path="/*" element={<Login {...passedProps} />} />
           </Routes>
-        </Router>
+        </MemoryRouter>
       </Provider>,
     );
   };
client/components/password-reset/password-reset.test.js (1)

106-118: Test name doesn't match assertions.

The test is named "should render 2 inputs" but only verifies 1 input (emailInput) and 1 button (submitButton). Consider renaming for clarity.

Suggested fix
-  it("should render 2 inputs", () => {
+  it("should render email input and submit button", () => {
client/components/payment-status/payment-status.test.js (1)

274-280: Clarify the mustLogin: undefined assertion.

The test asserts that setUserData is called with mustLogin: undefined when payment_requires_internet is false and status is draft. Explicitly setting a property to undefined is unusual and might indicate unclear component behavior. Consider whether this should be omitted from the object or set to a boolean value.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between adba120 and 227b756.

⛔ Files ignored due to path filters (6)
  • client/components/login/__snapshots__/login.test.js.snap is excluded by !**/*.snap
  • client/components/mobile-phone-change/__snapshots__/mobile-phone-change.test.js.snap is excluded by !**/*.snap
  • client/components/mobile-phone-verification/__snapshots__/mobile-phone-verification.test.js.snap is excluded by !**/*.snap
  • client/components/password-change/__snapshots__/password-change.test.js.snap is excluded by !**/*.snap
  • client/components/password-reset/__snapshots__/password-reset.test.js.snap is excluded by !**/*.snap
  • client/components/registration/__snapshots__/registration.test.js.snap is excluded by !**/*.snap
📒 Files selected for processing (38)
  • .eslintrc.json
  • browser-test/create-mobile-configuration.js
  • browser-test/mobile-phone-change.test.js
  • client/components/contact-box/contact.js
  • client/components/contact-box/contact.test.js
  • client/components/footer/footer.js
  • client/components/footer/footer.test.js
  • client/components/header/header.test.js
  • client/components/login/login.js
  • client/components/login/login.test.js
  • client/components/logout/logout.test.js
  • client/components/mobile-phone-change/mobile-phone-change.js
  • client/components/mobile-phone-change/mobile-phone-change.test.js
  • client/components/mobile-phone-verification/mobile-phone-verification.js
  • client/components/mobile-phone-verification/mobile-phone-verification.test.js
  • client/components/modal/modal.test.js
  • client/components/organization-wrapper/organization-wrapper.js
  • client/components/organization-wrapper/organization-wrapper.test.js
  • client/components/password-change/password-change.js
  • client/components/password-change/password-change.test.js
  • client/components/password-confirm/password-confirm.js
  • client/components/password-confirm/password-confirm.test.js
  • client/components/password-reset/password-reset.test.js
  • client/components/payment-process/payment-process.js
  • client/components/payment-process/payment-process.test.js
  • client/components/payment-status/payment-status.js
  • client/components/payment-status/payment-status.test.js
  • client/components/registration/registration.js
  • client/components/registration/registration.test.js
  • client/components/registration/subscriptions.test.js
  • client/components/status/status.js
  • client/components/status/status.test.js
  • client/test-config.json
  • client/utils/needs-verify.js
  • client/utils/should-link-be-shown.js
  • client/utils/utils.test.js
  • config/__tests__/add-org.test.js
  • config/add-org.js
💤 Files with no reviewable changes (1)
  • client/components/contact-box/contact.js
🚧 Files skipped from review as they are similar to previous changes (8)
  • client/components/logout/logout.test.js
  • client/components/password-confirm/password-confirm.js
  • browser-test/create-mobile-configuration.js
  • client/components/modal/modal.test.js
  • client/components/mobile-phone-change/mobile-phone-change.js
  • client/components/password-change/password-change.js
  • client/components/login/login.js
  • .eslintrc.json
🧰 Additional context used
🧬 Code graph analysis (10)
client/components/contact-box/contact.test.js (2)
client/components/contact-box/contact.js (1)
  • Contact (12-74)
client/utils/__mocks__/load-translation.js (1)
  • loadTranslation (17-27)
client/components/password-confirm/password-confirm.test.js (4)
client/components/password-confirm/password-confirm.js (1)
  • PasswordConfirm (20-231)
client/utils/load-translation.js (1)
  • loadTranslation (73-93)
client/utils/get-error-text.js (1)
  • error (2-2)
client/utils/tick.js (1)
  • tick (1-12)
client/components/password-change/password-change.test.js (1)
client/components/password-change/password-change.js (1)
  • PasswordChange (22-251)
client/components/organization-wrapper/organization-wrapper.test.js (4)
client/components/registration/subscriptions.test.js (1)
  • defaultConfig (129-129)
client/utils/__mocks__/get-config.js (1)
  • getConfig (12-17)
client/components/organization-wrapper/organization-wrapper.js (1)
  • OrganizationWrapper (36-391)
client/utils/load-translation.js (1)
  • loadTranslation (73-93)
client/components/payment-status/payment-status.js (1)
client/components/organization-wrapper/lazy-import.js (2)
  • PaymentStatus (31-33)
  • PaymentStatus (31-33)
client/components/header/header.test.js (3)
client/components/header/header.js (1)
  • Header (12-277)
client/utils/check-internal-links.js (1)
  • isInternalLink (1-4)
client/components/header/index.js (2)
  • mapDispatchToProps (15-19)
  • mapDispatchToProps (15-19)
browser-test/mobile-phone-change.test.js (1)
browser-test/utils.js (4)
  • getElementByCss (30-39)
  • getElementByCss (30-39)
  • successToastSelector (94-94)
  • successToastSelector (94-94)
client/components/mobile-phone-change/mobile-phone-change.test.js (3)
client/components/password-confirm/password-confirm.test.js (2)
  • defaultConfig (59-59)
  • renderWithProviders (83-110)
client/utils/__mocks__/get-config.js (1)
  • getConfig (12-17)
client/utils/loading-context.js (2)
  • loadingContextValue (3-3)
  • loadingContextValue (3-3)
client/components/footer/footer.test.js (1)
client/components/footer/footer.js (1)
  • Footer (11-54)
client/components/password-reset/password-reset.test.js (4)
client/components/password-reset/password-reset.js (1)
  • PasswordReset (18-164)
client/utils/load-translation.js (1)
  • loadTranslation (73-93)
client/utils/tick.js (1)
  • tick (1-12)
client/utils/get-error-text.js (1)
  • error (2-2)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Tests and Coverage
🔇 Additional comments (39)
browser-test/mobile-phone-change.test.js (1)

92-92: LGTM! Excellent refactoring to use centralized selector.

Replacing the hard-coded selector with successToastSelector from utils improves maintainability and consistency across test files. This aligns well with the PR's goal of modernizing test infrastructure.

client/components/mobile-phone-verification/mobile-phone-verification.test.js (3)

18-38: LGTM: Mock configuration structure is correct.

The getConfig mock properly uses ESModule format and returns a well-structured configuration object that matches the expected schema for the component tests.


45-60: LGTM: Test utilities are well-structured.

The test utilities (createTestProps, createMockStore, renderWithProviders) provide clean abstractions for test setup and follow React Testing Library best practices by wrapping components with necessary providers.

Also applies to: 62-62, 64-95


106-689: LGTM: Comprehensive RTL migration with good test coverage.

The test suite has been successfully migrated from Enzyme to React Testing Library with:

  • Proper async handling using waitFor
  • Appropriate RTL queries (getByRole, getByText)
  • Good coverage of success, error, and edge cases
  • Correct mock implementations for sequential axios calls

All past review comments have been addressed.

client/components/password-confirm/password-confirm.test.js (5)

1-58: LGTM! Clean test setup and comprehensive mock configuration.

The imports, mock configuration, and Jest mocks are well-structured. The mockConfig object provides all necessary component props, and the mocking strategy appropriately isolates external dependencies.


120-191: LGTM! Thorough rendering tests with proper RTL patterns.

The tests correctly verify form field rendering using RTL queries (screen.getByPlaceholderText, screen.getByText) and check all relevant attributes (placeholder, title, type). The use of getTranslationString ensures translations are properly tested.


207-225: LGTM! Proper DOM-driven interaction testing.

The test correctly uses fireEvent.change and verifies behavior through DOM element values rather than internal component state, which aligns with RTL best practices.


401-464: LGTM! Thorough testing of title setting and password visibility toggling.

The tests properly verify:

  • Title is set on component mount (line 403)
  • Both password fields share hidePassword state and toggle together (lines 420-441)
  • Either toggle button works correctly (lines 444-464)

The use of getAllByTestId("password-toggle-icon") and waitFor() for state updates demonstrates good RTL practices.


83-110: Provide LoadingContext in the test helper and production app.

The component imports and declares PasswordConfirm.contextType = LoadingContext and calls setLoading(true/false) in the form submission handler (lines 64, 84, 94). However, renderWithProviders does not wrap the component with a LoadingContext provider. Additionally, app.js also lacks a LoadingContext.Provider wrapper.

Currently, the component receives the default context value with no-op functions ({setLoading: () => {}, getLoading: () => {}}), so loading state calls are silently ignored. When LoadingContext is properly implemented in the app, the test helper must also be updated to provide the context, otherwise tests will fail if the context value becomes meaningful.

renderWithProviders needs LoadingContext provider
const renderWithProviders = (props) => {
  // ... existing state setup ...
  return render(
    <Provider store={mockedStore}>
      <MemoryRouter>
        <LoadingContext.Provider value={loadingContextValue}>
          <PasswordConfirm {...props} />
        </LoadingContext.Provider>
      </MemoryRouter>
    </Provider>,
  );
};
client/components/footer/footer.js (1)

16-16: LGTM!

Adding data-testid="footer" aligns with the RTL migration pattern and provides a stable selector for tests without affecting runtime behavior.

client/components/payment-status/payment-status.js (1)

247-252: LGTM!

The defaultProps additions correctly specify undefined as defaults for the optional props that lack .isRequired in propTypes.

client/components/footer/footer.test.js (1)

1-130: LGTM!

Clean migration to React Testing Library with proper use of:

  • screen.queryBy* for non-existent elements
  • screen.getByRole/getByText for presence assertions
  • Container-based snapshots for regression testing

The test coverage for authenticated/non-authenticated link visibility and verified status is thorough.

client/components/contact-box/contact.test.js (1)

73-95: LGTM!

Tests correctly verify authentication-driven visibility of social links using getByAltText/queryByAltText, matching the component's conditional rendering based on shouldLinkBeShown.

client/components/registration/registration.js (1)

378-378: LGTM on accessibility enhancements!

The additions of aria-label attributes, data-testid hooks, and ariaLabel="dialog" for the modal improve both accessibility and testability.

Also applies to: 458-458, 631-642, 661-672, 676-679, 702-702, 718-718, 733-733, 749-749, 832-832

client/components/payment-process/payment-process.js (2)

91-93: LGTM!

Converting redirectToPaymentUrl to a static method is appropriate since it doesn't access instance state—it only calls window.location.assign(). The call site update to PaymentProcess.redirectToPaymentUrl(...) is correct.

Also applies to: 111-111


145-148: LGTM!

The defaultProps correctly match the optional props in propTypes that lack .isRequired.

client/components/password-change/password-change.test.js (1)

87-361: LGTM on RTL migration!

The test suite is well-structured with:

  • Proper Provider/Router wrapping via renderWithProviders
  • Comprehensive coverage of form interactions, validation errors, and redirect behavior
  • Correct use of screen.queryBy* for elements that shouldn't exist
  • Separate test cases for SAML/social_login redirect scenarios (addressing the previous review feedback)
client/components/organization-wrapper/organization-wrapper.js (3)

47-55: LGTM! Clean extraction of loading context value.

The getLoading and getLoadingContextValue methods properly encapsulate the loading state management, making the context value creation more maintainable and testable.


108-126: LGTM! Route rendering helpers improve code organization.

Extracting route rendering logic into dedicated methods (renderRegistrationRoute, etc.) reduces complexity in the main render method and makes the routing logic easier to test and maintain.


214-218: LGTM! data-testid attributes enable reliable RTL testing.

Adding data-testid="app-container" provides a stable selector for tests that won't break with UI changes.

client/components/organization-wrapper/organization-wrapper.test.js (3)

14-44: LGTM! Well-structured mock configuration.

The mock setup correctly uses jest.mock before imports and provides a comprehensive default config structure that mirrors the actual configuration shape.


92-130: LGTM! Comprehensive test rendering helper.

The renderWithRouter helper properly wraps the component with all required providers (HelmetProvider, Redux Provider, MemoryRouter) and creates a realistic store state for testing.


660-671: No action needed—404 component ID is correctly implemented.

The component uses the correct ID not-found-404 and matches the test's expectations. The misspelled #not-foud-404 is not present in the codebase, confirming the past issue has already been resolved.

client/components/mobile-phone-change/mobile-phone-change.test.js (3)

14-14: LGTM! Correct LoadingContext usage.

The test now correctly imports loadingContextValue from the actual module, ensuring the mock matches the production context shape ({setLoading, getLoading}).

Also applies to: 126-133


143-171: LGTM! Proper MemoryRouter usage.

The mountComponent helper correctly uses MemoryRouter with initialEntries instead of the previous BrowserRouter with history props, addressing the past review feedback.


392-397: LGTM! Cancel button assertion fixed.

The test now properly asserts that the cancel button exists before clicking it, addressing the previous concern about conditional assertions masking failures.

client/components/payment-process/payment-process.test.js (2)

86-96: LGTM! LoadingContext.Provider now included.

The renderWithProviders helper correctly wraps the component with LoadingContext.Provider, addressing the past review feedback about missing context.


98-118: LGTM! Clean window event mocking helper.

The mockMessageEvents helper properly saves and restores the original event listeners, ensuring test isolation.

client/components/header/header.test.js (3)

12-29: LGTM! Clean mock configuration.

The mock setup correctly uses jest.mock with __esModule: true pattern and provides the expected config structure for header tests.


187-196: LGTM! Scoped queries using within().

Using within(desktopNav).getAllByRole("link") properly scopes the query to the desktop navigation, avoiding false matches from mobile navigation links.


344-361: LGTM! Proper hamburger menu interaction test.

The test correctly verifies the actual DOM state change (class toggle from display-none to display-flex) rather than internal component state.

client/components/login/login.test.js (2)

22-82: LGTM! Comprehensive mock configuration.

The mockConfig object provides a complete configuration structure that covers login form fields, social login, registration, header, footer, and other required components.


146-172: LGTM! Simple provider helper for basic tests.

The renderWithProvider helper is appropriately simple for tests that don't need full routing, using MemoryRouter correctly.

client/components/password-reset/password-reset.test.js (3)

51-82: LGTM! Well-structured test utilities.

The createMockStore and renderWithProviders helpers follow the established pattern across the test suite, providing consistent Redux and Router context for tests.


172-226: LGTM! Comprehensive form submission test.

The test properly covers multiple error scenarios and success case, using waitFor for async assertions and verifying both UI state and toast notifications.


237-257: LGTM! Proper success state assertion.

The test correctly waits for the form to be removed from the DOM after successful submission, addressing the previous concern about asserting on potentially unmounted elements.

client/components/payment-status/payment-status.test.js (3)

51-82: LGTM: Well-structured test utilities.

The createMockStore and renderWithProviders utilities properly set up Redux and Router context for testing. This is a good pattern for migrating to RTL.


116-130: LGTM: Proper test setup and cleanup.

The selective console mock restoration using optional chaining (lines 128-129) is a good defensive pattern that prevents errors if mocks are already restored.


259-322: These tests correctly verify the payment link behavior.

The PaymentStatus component implements an onClick handler on the Link element (line 170) that's properly bound in the constructor. When fireEvent.click() is triggered in the tests, it correctly invokes paymentProceedHandler, which conditionally calls setUserData based on the payment_requires_internet flag. The test expectations match the actual implementation:

  • When payment_requires_internet is false, setUserData is not called
  • When payment_requires_internet is true, setUserData is called with proceedToPayment: true

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
client/components/mobile-phone-change/mobile-phone-change.js (1)

100-115: Handle abort errors and guard setState in error handler.

Two issues in the error handler:

  1. Line 101: Accessing error.response.data without checking if error.response exists. When a request is aborted via AbortController, axios throws an error without a response property, causing a TypeError.

  2. Lines 108-114: The setState call lacks an abort check, potentially updating state on an unmounted component.

🔧 Recommended fix
      .catch((error) => {
+       if (this.abortController.signal.aborted) return;
+       if (!error.response) {
+         // Handle abort or network errors without response
+         setLoading(false);
+         return;
+       }
        const {data} = error.response;
        const errorText = getErrorText(error);
        if (errorText) {
          logError(error, errorText);
          toast.error(errorText);
        }
        setLoading(false);
        this.setState({
          errors: {
            ...errors,
            ...(data.phone_number ? {phone_number: data.phone_number} : null),
            ...(errorText ? {nonField: errorText} : {nonField: ""}),
          },
        });
      });
🤖 Fix all issues with AI agents
In @client/components/registration/registration.js:
- Line 737: The translation key used in the label for tax number is mixedCase
and won't match the translations: update the template tag usage t`taxNumber_LBL`
to the UPPER_SNAKE_CASE key t`TAX_NUMBER_LBL` so it matches the translation
file; locate the label with htmlFor="tax_number" in the registration component
and replace the key accordingly.
- Line 173: The assignment uses camelCase property postData.planPricing but the
API expects snake_case; change the property to postData.plan_pricing and assign
the same value (replace the usage of postData.planPricing with
postData.plan_pricing wherever it’s set), ensuring consistency with other fields
like requires_payment, phone_number, and billing_info so the backend receives
plan_pricing = plan_pricing.id.
🧹 Nitpick comments (17)
client/components/payment-status/payment-status.js (1)

19-19: Consider extending the mounting guard to cover all post-validation operations.

The componentIsMounted flag correctly prevents setState after unmount (lines 40-42), but the subsequent setUserData calls (lines 51-56, 62-65) can still execute if the component unmounts between line 42 and those operations. While Redux actions won't trigger React warnings, executing state updates for an unmounted component may cause unintended side effects.

🛡️ Proposed enhancement to guard all operations after validateToken
   setLoading(false);
   if (this.componentIsMounted) {
     this.setState({isTokenValid});
   }
-  if (isTokenValid === false) {
+  if (!this.componentIsMounted || isTokenValid === false) {
     return;
   }

   ({userData} = this.props);

Also applies to: 23-23, 40-42, 69-71

client/components/logout/logout.test.js (2)

66-72: Minor: Redundant mock clearing.

Calling jest.clearAllMocks() in both beforeEach and afterEach is redundant—clearing in beforeEach alone ensures each test starts with clean mocks. However, this is harmless and doesn't affect test correctness.

♻️ Simplify mock management
  beforeEach(() => {
    jest.clearAllMocks();
    loadTranslation("en", "default");
  });

  afterEach(() => {
-   jest.clearAllMocks();
  });

107-124: Consider restoring the toast spy.

While jest.clearAllMocks() may handle cleanup, it's good practice to explicitly restore spies after use to avoid potential side effects in other tests.

♻️ Add explicit spy restoration
  it("should login if user is already authenticated and clicks log in again", () => {
    const spyToast = jest.spyOn(toastify.toast, "success");
    props = createTestProps();
    props.isAuthenticated = true;

    render(
      <MemoryRouter>
        <Logout {...props} />
      </MemoryRouter>,
    );

    const loginButton = screen.getByRole("link", {name: /login again/i});
    fireEvent.click(loginButton);

    expect(spyToast).toHaveBeenCalled();
    expect(spyToast).toHaveBeenCalledWith("Login successful", {
      toastId: "main_toast_id",
    });
+   
+   spyToast.mockRestore();
  });
client/components/password-confirm/password-confirm.test.js (1)

129-131: Optional: Remove redundant afterEach.

The outer afterEach at lines 129-131 appears redundant since the nested describe blocks (<PasswordConfirm /> interactions starting at line 193) have their own cleanup in afterEach at lines 202-205. The outer cleanup won't add value unless there are tests at the outer scope that need it.

♻️ Proposed cleanup
   beforeEach(() => {
     jest.clearAllMocks();
     props = createTestProps();
     loadTranslation("en", "default");
   });
-
-  afterEach(() => {
-    jest.clearAllMocks();
-  });
client/components/mobile-phone-change/mobile-phone-change.test.js (2)

61-64: Consider making the getConfig mock handle arguments.

The mock always returns mockConfig regardless of arguments, but createTestProps (line 74) defaults configName to "test-org-2" and line 98 tries to pass a second parameter. This creates misleading test code where parameters appear functional but are ignored.

Either update the mock to handle different slugs, or document that all calls return the same config.

♻️ Option 1: Update mock to accept slug parameter
 jest.mock("../../utils/get-config", () => ({
   __esModule: true,
-  default: jest.fn(() => mockConfig),
+  default: jest.fn((slug) => {
+    // Return mockConfig for all slugs, but you could add different configs
+    return mockConfig;
+  }),
 }));

199-200: Optional: Remove redundant mock re-setup.

The getConfig.mockImplementation(() => mockConfig) calls in afterEach are redundant. Module-level jest.mock() is hoisted and not affected by restoreAllMocks(), so the mock remains in place. These lines can be safely removed.

♻️ Simplify afterEach
 afterEach(() => {
   jest.clearAllMocks();
   jest.restoreAllMocks();
-  // Re-setup the getConfig mock after clearing
-  getConfig.mockImplementation(() => mockConfig);
 });

Also applies to: 438-439

client/components/modal/modal.test.js (3)

96-123: Weak assertion for edge case handling.

The test verifies axios was called but doesn't assert the expected behavior when an incorrect param name is provided. The comment on line 118 states "Component should render but with empty content," but the test doesn't verify the content is actually empty or that the component handles this gracefully.

💡 Consider strengthening the assertion
  await waitFor(() => {
-    // Component should render but with empty content
-    expect(axios).toHaveBeenCalled();
+    expect(axios).toHaveBeenCalled();
  });

+  // Verify the modal content is empty or not rendered
+  expect(container.querySelector('.modal-content')).toBeEmptyDOMElement();
  expect(container).toMatchSnapshot();

Note: Adjust the selector based on the actual Modal component's DOM structure.


276-299: Redundant test: Consider removal.

This test only verifies that the modal renders with content, which is already thoroughly covered by the rendering test suite (lines 39-94). The test doesn't add any new assertions about backdrop behavior or functionality. The comment on line 297 acknowledges this limitation.

Consider either:

  1. Removing this test as redundant, or
  2. Adding specific assertions about the backdrop element (e.g., verifying backdrop DOM presence, CSS classes, or other backdrop-specific behavior)
🗑️ Option 1: Remove redundant test
-  it("should render modal with backdrop", async () => {
-    axios.mockImplementationOnce(() =>
-      Promise.resolve({
-        status: 200,
-        data: {
-          __html: "Modal Content",
-        },
-      }),
-    );
-    props = createTestProps();
-
-    render(
-      <MemoryRouter>
-        <Modal {...props} />
-      </MemoryRouter>,
-    );
-
-    await waitFor(() => {
-      expect(screen.getByText(/modal content/i)).toBeInTheDocument();
-    });
-
-    // Just verify the modal rendered - the close functionality is tested via Esc key
-    expect(screen.getByText(/modal content/i)).toBeInTheDocument();
-  });
-

188-189: Modernize keyboard event handling from deprecated keyCode to key property.

The component registers a keyup event listener (despite the method name handleKeyDown) that checks the deprecated keyCode property. Both the component and tests should be modernized to use the standard key property for better maintainability:

In client/components/modal/modal.js:

  handleKeyDown = (event) => {
    const {prevPath, navigate} = this.props;
-   switch (event.keyCode) {
-     case 27:
+   if (event.key === "Escape") {
      navigate(prevPath);
-     break;
-     default:
-       break;
-   }
+   }
  };

In client/components/modal/modal.test.js:

-  fireEvent.keyUp(document, {keyCode: 27});
+  fireEvent.keyUp(document, {key: "Escape"});
-  fireEvent.keyUp(document, {keyCode: 1});
+  fireEvent.keyUp(document, {key: "a"});
client/components/password-change/password-change.test.js (3)

129-137: Optional: Simplify mock cleanup.

The beforeEach calls jest.clearAllMocks(), axios.mockClear(), and axios.mockReset() sequentially. Since jest.clearAllMocks() already clears all mocks (including axios), the additional axios-specific calls are redundant.

♻️ Optional simplification
  beforeEach(() => {
    jest.clearAllMocks();
-   axios.mockClear();
-   axios.mockReset();
    props = createTestProps();
  });

152-243: Consider splitting this test into separate cases.

This comprehensive test covers four distinct scenarios (mismatched passwords, same password validation, server error, successful change). While functional, splitting into separate it blocks would improve readability and make failures easier to diagnose.

That said, the current implementation works correctly—validation failures short-circuit before axios calls, so the two mocked responses align with the two actual network requests.

Example structure for splitting
it("should show error when passwords don't match", async () => {
  // Test 1 content
});

it("should show error when new password matches current", async () => {
  // Test 2 content
});

it("should handle server errors gracefully", async () => {
  // Test 3 content
});

it("should navigate to status on successful password change", async () => {
  // Test 4 content
});

290-327: Password toggle test works, but could be more robust.

The test filters buttons by checking !btn.type || btn.type === "button" to distinguish toggle buttons from the submit button. This works but is somewhat fragile—if the DOM structure changes, the test might break.

💡 Optional improvement: use data-testid

Consider adding data-testid="password-toggle" to the toggle buttons in the component and querying them directly:

const toggleButton = screen.getByTestId('password-toggle');
fireEvent.click(toggleButton);

This makes the test more resilient to DOM changes.

client/components/contact-box/contact.test.js (1)

21-21: Remove orphaned ESLint directive.

The /* eslint-enable import/first */ comment has no matching eslint-disable directive above it. This appears to be a leftover from previous refactoring.

🧹 Proposed fix
 jest.mock("../../utils/load-translation");
-/* eslint-enable import/first */
client/components/organization-wrapper/organization-wrapper.test.js (2)

44-44: Remove orphaned ESLint directive.

Similar to contact.test.js, this /* eslint-enable import/first */ has no matching disable directive.

🧹 Proposed fix
 );
 jest.mock("../../utils/needs-verify");
-/* eslint-enable import/first */

469-469: Use string literal for localStorage consistency.

While localStorage.setItem("userAutoLogin", true) works (localStorage coerces to "true"), other parts of the codebase use explicit strings: String(userAutoLogin) in status.js and "false" in logout.js. Use "true" for consistency.

🔧 Proposed fix
-    localStorage.setItem("userAutoLogin", true);
+    localStorage.setItem("userAutoLogin", "true");
client/components/organization-wrapper/organization-wrapper.js (1)

47-55: Consider memoizing context value to prevent unnecessary re-renders.

getLoadingContextValue() creates a new object on every render, which can cause unnecessary re-renders of all context consumers. Since this is called in the render method (line 214), every render creates a new object reference.

For a class component, you can cache the context value and only update it when loading state changes.

♻️ Proposed optimization
   constructor(props) {
     super(props);
     this.state = {
       loading: false,
       translationLoaded: true,
       configLoaded: false,
     };
     this.loadLanguage = this.loadLanguage.bind(this);
+    this.loadingContextValue = {
+      setLoading: this.setLoading,
+      getLoading: this.getLoading,
+    };
   }

-  getLoadingContextValue = () => ({
-    setLoading: this.setLoading,
-    getLoading: this.getLoading,
-  });
+  getLoadingContextValue = () => this.loadingContextValue;

Since setLoading and getLoading are arrow functions bound to the instance, they're stable references, so caching the object containing them is safe.

client/components/header/header.test.js (1)

29-29: Remove orphaned ESLint directive.

Same issue as other test files - no matching eslint-disable above.

🧹 Proposed fix
 jest.mock("../../utils/check-internal-links");
-/* eslint-enable import/first */
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 227b756 and e01a08f.

⛔ Files ignored due to path filters (6)
  • client/components/login/__snapshots__/login.test.js.snap is excluded by !**/*.snap
  • client/components/mobile-phone-change/__snapshots__/mobile-phone-change.test.js.snap is excluded by !**/*.snap
  • client/components/mobile-phone-verification/__snapshots__/mobile-phone-verification.test.js.snap is excluded by !**/*.snap
  • client/components/password-change/__snapshots__/password-change.test.js.snap is excluded by !**/*.snap
  • client/components/password-reset/__snapshots__/password-reset.test.js.snap is excluded by !**/*.snap
  • client/components/registration/__snapshots__/registration.test.js.snap is excluded by !**/*.snap
📒 Files selected for processing (38)
  • .eslintrc.json
  • browser-test/create-mobile-configuration.js
  • browser-test/mobile-phone-change.test.js
  • client/components/contact-box/contact.js
  • client/components/contact-box/contact.test.js
  • client/components/footer/footer.js
  • client/components/footer/footer.test.js
  • client/components/header/header.test.js
  • client/components/login/login.js
  • client/components/login/login.test.js
  • client/components/logout/logout.test.js
  • client/components/mobile-phone-change/mobile-phone-change.js
  • client/components/mobile-phone-change/mobile-phone-change.test.js
  • client/components/mobile-phone-verification/mobile-phone-verification.js
  • client/components/mobile-phone-verification/mobile-phone-verification.test.js
  • client/components/modal/modal.test.js
  • client/components/organization-wrapper/organization-wrapper.js
  • client/components/organization-wrapper/organization-wrapper.test.js
  • client/components/password-change/password-change.js
  • client/components/password-change/password-change.test.js
  • client/components/password-confirm/password-confirm.js
  • client/components/password-confirm/password-confirm.test.js
  • client/components/password-reset/password-reset.test.js
  • client/components/payment-process/payment-process.js
  • client/components/payment-process/payment-process.test.js
  • client/components/payment-status/payment-status.js
  • client/components/payment-status/payment-status.test.js
  • client/components/registration/registration.js
  • client/components/registration/registration.test.js
  • client/components/registration/subscriptions.test.js
  • client/components/status/status.js
  • client/components/status/status.test.js
  • client/test-config.json
  • client/utils/needs-verify.js
  • client/utils/should-link-be-shown.js
  • client/utils/utils.test.js
  • config/__tests__/add-org.test.js
  • config/add-org.js
💤 Files with no reviewable changes (1)
  • client/components/contact-box/contact.js
✅ Files skipped from review due to trivial changes (1)
  • browser-test/create-mobile-configuration.js
🚧 Files skipped from review as they are similar to previous changes (9)
  • client/components/password-reset/password-reset.test.js
  • client/components/mobile-phone-verification/mobile-phone-verification.js
  • client/components/payment-process/payment-process.test.js
  • .eslintrc.json
  • client/components/password-change/password-change.js
  • client/components/payment-process/payment-process.js
  • client/components/login/login.test.js
  • browser-test/mobile-phone-change.test.js
  • client/components/login/login.js
🧰 Additional context used
🧬 Code graph analysis (9)
client/components/password-change/password-change.test.js (1)
client/components/password-change/password-change.js (1)
  • PasswordChange (22-251)
client/components/header/header.test.js (2)
client/utils/check-internal-links.js (1)
  • isInternalLink (1-4)
client/components/header/index.js (2)
  • mapDispatchToProps (15-19)
  • mapDispatchToProps (15-19)
client/components/mobile-phone-verification/mobile-phone-verification.test.js (3)
client/utils/get-config.js (2)
  • getConfig (3-13)
  • config (4-4)
client/components/mobile-phone-verification/mobile-phone-verification.js (1)
  • MobilePhoneVerification (29-346)
client/utils/load-translation.js (1)
  • loadTranslation (73-93)
client/components/mobile-phone-change/mobile-phone-change.test.js (9)
client/components/password-confirm/password-confirm.test.js (4)
  • mockConfig (14-50)
  • defaultConfig (59-59)
  • renderWithProviders (83-110)
  • mockedStore (97-101)
client/utils/__mocks__/get-config.js (1)
  • getConfig (12-17)
client/utils/get-config.js (1)
  • getConfig (3-13)
client/utils/loading-context.js (3)
  • loadingContextValue (3-3)
  • loadingContextValue (3-3)
  • LoadingContext (4-4)
client/components/organization-wrapper/lazy-import.js (2)
  • MobilePhoneChange (13-18)
  • MobilePhoneChange (13-18)
client/components/registration/test-utils.js (1)
  • mockedStore (12-27)
client/utils/load-translation.js (1)
  • loadTranslation (73-93)
client/utils/__mocks__/load-translation.js (1)
  • loadTranslation (17-27)
client/utils/tick.js (1)
  • tick (1-12)
client/components/logout/logout.test.js (1)
client/components/logout/logout.js (1)
  • Logout (15-61)
client/components/password-confirm/password-confirm.test.js (2)
client/components/password-confirm/password-confirm.js (1)
  • PasswordConfirm (20-231)
client/utils/tick.js (1)
  • tick (1-12)
client/components/payment-status/payment-status.test.js (3)
client/components/payment-status/payment-status.js (1)
  • PaymentStatus (12-226)
client/utils/validate-token.js (1)
  • validateToken (11-87)
client/utils/tick.js (1)
  • tick (1-12)
client/components/footer/footer.test.js (1)
client/components/footer/footer.js (1)
  • Footer (11-54)
client/components/organization-wrapper/organization-wrapper.js (3)
client/utils/needs-verify.js (1)
  • needsVerify (5-28)
client/components/organization-wrapper/lazy-import.js (6)
  • Registration (4-6)
  • Registration (4-6)
  • MobilePhoneVerification (25-30)
  • MobilePhoneVerification (25-30)
  • Status (7-9)
  • Status (7-9)
client/utils/loading-context.js (1)
  • LoadingContext (4-4)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Tests and Coverage
🔇 Additional comments (49)
client/components/payment-status/payment-status.js (1)

247-252: LGTM!

The addition of defaultProps improves the component's API clarity and aligns with React best practices.

client/components/payment-status/payment-status.test.js (3)

1-2: LGTM! RTL migration follows established patterns.

The test infrastructure correctly uses RTL with Redux Provider and MemoryRouter context, consistent with the broader test migration across this PR.

Also applies to: 5-8, 77-82


137-159: LGTM! Async test patterns are correct.

The tests properly use validateToken.mockResolvedValue, await tick(), and await waitFor() to handle React 18's concurrent rendering, and RTL queries (screen.getByRole, screen.getByText) correctly replace Enzyme assertions.

Also applies to: 169-187, 197-206


259-293: Good coverage of payment flow conditional logic.

These tests verify the critical distinction between payment_requires_internet true/false paths, ensuring proceedToPayment is set correctly. The explicit assertions on setUserData calls validate the component's state management behavior.

Also applies to: 295-322

client/components/logout/logout.test.js (5)

1-9: LGTM: Clean RTL migration imports.

The imports are well-organized and include all necessary RTL utilities (render, screen, fireEvent) and jest-dom matchers. The MemoryRouter import is correctly added since the Logout component uses Link from react-router-dom.


38-44: LGTM: Proper RTL snapshot test.

The test correctly wraps the component with MemoryRouter and uses container.toMatchSnapshot(), which is the appropriate pattern for RTL snapshot testing.


53-59: LGTM: Consistent RTL rendering pattern.

The rendering pattern is consistent with the first test suite and properly loads translations before rendering.


76-93: LGTM: Well-structured RTL interaction test.

The test properly uses screen.getByRole with an accessible name to find the button, which follows RTL best practices. The assertion correctly verifies that setUserData is called with mustLogin: true, matching the component's behavior when !isAuthenticated.


97-104: LGTM: Lifecycle hook verification.

The test correctly verifies that setTitle is called during componentDidMount with the translated title and organization name.

client/components/password-confirm/password-confirm.js (2)

109-110: LGTM: Cleaner destructuring pattern.

Consolidating the destructuring into a single statement improves readability while maintaining the same renaming convention.


133-137: LGTM: Accessibility improvement.

The aria-label attribute improves accessibility and enables the test suite to query the form using getByRole("form"), which aligns with the RTL migration in this PR.

client/components/password-confirm/password-confirm.test.js (6)

2-12: LGTM: RTL imports and test utilities.

The migration to React Testing Library with proper imports of render, screen, waitFor, and fireEvent aligns with the PR objectives. The tick utility import for handling async operations is appropriate for React 18's concurrent rendering.


14-56: LGTM: Comprehensive mock configuration.

The mockConfig object and get-config mock setup provide a complete test environment. The ES module mocking syntax is correct and consistent with modern Jest practices.


83-110: LGTM: Well-structured test helper.

The renderWithProviders helper correctly wraps the component with Redux Provider and MemoryRouter, providing the necessary context for testing. The mock store implementation is appropriate for these component tests.


138-225: LGTM: RTL-based component rendering tests.

The tests correctly use RTL queries (getByPlaceholderText, getByText) and DOM assertions (toBeInTheDocument, toHaveAttribute) to verify component rendering and user interactions. The use of fireEvent.change to test input handling is appropriate.


227-399: LGTM: Comprehensive form submission test coverage.

The nested describe block provides excellent coverage of form submission scenarios:

  • Password mismatch validation
  • Various API error responses (detail, non_field_errors, token)
  • Success flow with toast notifications
  • Error state verification

The use of tick() followed by waitFor() for async assertions is correct for React 18. The spy setup for toast and console.error enables proper verification of side effects.


409-464: LGTM: Password visibility toggle tests.

These tests verify that the password visibility toggle correctly affects both password fields (shared hidePassword state) using either toggle button. The use of getAllByTestId and waitFor for attribute changes is appropriate.

client/components/registration/registration.js (1)

378-378: LGTM! Excellent accessibility and testability improvements.

The additions of data-testid attributes (for React Testing Library) and aria-label attributes (for screen readers) significantly improve both test reliability and accessibility compliance. These changes align well with the PR's objectives to migrate to RTL and enhance the codebase.

Also applies to: 458-458, 631-638, 661-668, 676-679, 702-702, 718-718, 733-733, 749-749, 832-832

client/components/mobile-phone-change/mobile-phone-change.js (4)

37-37: AbortController setup is correct, but async handlers need abort guards.

The AbortController initialization, lifecycle integration, and signal passing to axios are implemented correctly. This properly addresses the previous review's recommendation to use AbortController instead of a mounted flag.

However, the .then() and .catch() callbacks still lack abort checks before calling setState() (see separate comments below).

Based on previous review comments.

Also applies to: 41-41, 64-68, 89-89


56-56: LGTM!

The abort signal check properly guards the setState call after the async validateToken operation.


97-97: LGTM!

Changing the toast notification from info to success better reflects the successful completion of the token send operation.


141-141: LGTM!

The aria-label addition improves accessibility by providing a descriptive label for screen readers.

client/components/mobile-phone-change/mobile-phone-change.test.js (5)

1-15: LGTM: Imports are correct for RTL migration.

The imports properly include React Testing Library utilities and the correct LoadingContext exports.


74-133: LGTM: Helper functions correctly implement test utilities.

The renderWithProviders helper properly uses the actual loadingContextValue from the module (imported on line 14), addressing the previous review concern about incorrect mock shape. The helper correctly wraps components with Redux Provider, LoadingContext, and MemoryRouter.


143-171: LGTM: Router setup correctly uses MemoryRouter.

The mountComponent helper now properly uses MemoryRouter with initialEntries (line 163), addressing the previous review concern about incorrect Router/BrowserRouter usage with custom history props.


380-398: LGTM: Cancel button test properly asserts existence.

The test now includes an explicit assertion expect(cancelButton).toBeInTheDocument() (line 393) before clicking, properly addressing the previous review concern about silent test passage if the button is missing.


203-531: LGTM: Test suite correctly handles async behavior and maintains isolation.

The tests properly use waitFor for async assertions (e.g., lines 219-225, 271-275, 339-345) and handle React 18's concurrent rendering. Mock cleanup in afterEach ensures test isolation.

client/components/modal/modal.test.js (3)

1-37: LGTM: Clean RTL migration setup.

The imports, mocks, and test utilities are correctly configured for React Testing Library. The beforeEach cleanup and createTestProps factory provide good test isolation and consistency.


245-274: LGTM: Proper side-effect testing.

The test correctly verifies that the modal hides the scrollbar when open and restores it on unmount. The assumption that the default overflow is "auto" is reasonable for testing purposes.


301-344: LGTM: Thorough cleanup verification.

The test properly verifies that event listeners are added on mount and removed on unmount using spies, with appropriate cleanup of the spies themselves. This ensures no memory leaks from lingering event listeners.

client/components/mobile-phone-verification/mobile-phone-verification.test.js (1)

19-38: The review comment is incorrect. The inline jest.mock correctly provides a synchronous mock of getConfig because createTestProps is a test-only utility function that doesn't need to replicate the async behavior of production code.

Here's why the original concern doesn't apply:

  1. The component doesn't use getConfig directly. MobilePhoneVerification receives pre-computed configuration via props (e.g., mobile_phone_verification, settings), not by calling getConfig.

  2. createTestProps is a test utility, not production code. It only exists in the test file to build mock props for component testing.

  3. The async nature of getConfig is tested elsewhere. The actual async handling occurs in client/actions/set-organization.js (line 16: const orgConfig = await getConfig(slug);), which is tested separately.

  4. Synchronous mocks for test utilities are standard practice. Other test files in the codebase (e.g., password-change.test.js) similarly use the synchronous getConfig from __mocks__ for convenience.

The inline mock is appropriate as-is; no changes are needed.

client/components/password-change/password-change.test.js (7)

1-44: LGTM! Clean RTL setup and comprehensive mocks.

The imports and mock setup are well-structured for the React Testing Library migration. The get-config mock provides a realistic password_change_form schema that properly supports the component's rendering and validation logic.


45-92: LGTM! Well-structured test helpers.

The helper functions follow RTL best practices:

  • createTestProps provides a clean prop factory
  • createMockStore supplies necessary Redux state (organization config, language)
  • renderWithProviders correctly wraps components with both Provider and MemoryRouter

94-123: LGTM! Rendering tests correctly migrated to RTL.

The tests properly verify:

  • Component rendering with translation placeholders
  • Rendered output with loaded translations
  • Conditional rendering of the cancel button based on password_expired state

The use of queryByText to assert the cancel button's absence is the correct RTL pattern.


139-150: LGTM! Clean handleChange test.

The test correctly uses RTL queries and fireEvent to verify input handling. The assertions confirm both the element's presence and the updated value.


245-252: LGTM! setTitle test is clear and correct.


254-288: LGTM! Thorough input validation test.

Excellent coverage of input attributes (type, id, placeholder, autocomplete, required). This ensures the form fields are properly configured for both functionality and accessibility.


329-361: LGTM! Token validation and redirect tests are correct.

The tests properly verify:

  • validateToken is called with correct arguments
  • Components redirect (don't render form) for SAML and social_login methods
  • Use of queryByRole to assert form absence is the correct RTL pattern

The social_login and SAML redirect tests correctly replace the problematic rerender logic flagged in previous review comments.

client/components/contact-box/contact.test.js (1)

54-95: RTL migration looks good.

The test migration from Enzyme to RTL follows best practices:

  • Uses render and screen from RTL
  • Uses getByAltText/queryByAltText for presence checks based on authentication state
  • Container-based snapshots are appropriate for this use case
client/components/organization-wrapper/organization-wrapper.test.js (2)

92-130: Well-structured test utility with proper provider wrapping.

The renderWithRouter helper correctly wraps components with all required providers (HelmetProvider, Provider, MemoryRouter) and creates a consistent mocked store. This addresses the provider consistency issues and provides a clean pattern for all tests in this file.


660-671: The 404 component ID has been correctly updated.

Verification confirms the test and component are properly aligned. The component file (client/components/404/404.js) correctly uses not-found-404 for both the id attribute (line 20) and data-testid attribute (line 21), matching the test expectation at line 669.

client/components/footer/footer.js (1)

16-16: LGTM!

Adding data-testid="footer" enables reliable RTL-based test selection. This is a clean, non-breaking change that aligns with the testing strategy used across other components in this PR.

client/components/footer/footer.test.js (1)

50-129: Clean RTL migration with comprehensive test coverage.

The tests properly cover:

  • Snapshot rendering
  • Empty links state
  • Authentication-based link visibility
  • Verification-based link visibility

Good use of RTL query variants (getByRole, getByText, queryByText) for presence/absence assertions.

client/components/organization-wrapper/organization-wrapper.js (3)

7-7: Correct migration to react-helmet-async.

Using react-helmet-async instead of react-helmet is the correct choice for React 18, as it properly supports concurrent rendering and avoids the deprecated side-effect patterns.


108-188: Route helper methods improve code organization.

Extracting renderRegistrationRoute, renderMobilePhoneVerificationRoute, renderStatusRoute, and renderLogoutRoute consolidates the routing logic and makes the render method more readable. The conditional navigation logic is clear and follows a consistent pattern.


215-218: LGTM - data-testid enables reliable test selection.

Adding data-testid="app-container" allows tests to reliably query for the main application container regardless of class name changes.

client/components/header/header.test.js (3)

187-208: Excellent use of scoped queries with within().

Using within() to scope queries to specific containers (desktop-navigation, desktop-language-selector) makes tests more precise and resilient to DOM structure changes. This is a best practice in RTL.


344-378: Good coverage of interaction and accessibility tests.

The tests properly verify:

  • Hamburger menu toggle behavior with click
  • Keyboard navigation with Enter key (keyCode 13)
  • CSS class changes (display-nonedisplay-flex)

This ensures the mobile menu remains accessible via keyboard.


380-391: LGTM - Redux dispatch mapping test is correctly preserved.

The mapDispatchToProps test verifies the dispatch behavior independently of component rendering, which is appropriate for testing the Redux integration.

Copy link
Member

@nemesifier nemesifier left a comment

Choose a reason for hiding this comment

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

Please follow up with all the reviews from coderabbit, if you don't agree to something just reply to it.

@PRoIISHAAN PRoIISHAAN force-pushed the fix-reactversion-migration branch from 8189240 to b1734ab Compare January 9, 2026 10:51
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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
client/components/registration/registration.js (4)

618-642: Avoid hardcoded English aria-label on inputs that already have translated <label>s (aria-label overrides the label). If you want explicit aria hooks, use the existing translated label strings (or drop aria-label entirely and rely on <label htmlFor>).

Proposed fix (keep aria-label but localize it)
<input
  ...
- aria-label="Password"
+ aria-label={t`PWD_LBL`}
 />

<input
  ...
- aria-label="Confirm Password"
+ aria-label={t`CONFIRM_PWD_LBL`}
 />

Also applies to: 648-672


633-672: Remove unused ariaLabel prop or implement aria-label attribute in the component. The parentClassName prop is consumed (line 66), but ariaLabel is passed but never destructured or used in PasswordToggleIcon (client/utils/password-toggle.js). The component renders a <div role="button"> rather than an actual button, so form submission isn't triggered; however, if aria-label is intended, the component must apply it to the toggle element.


401-471: Add missing id to fallback input and move aria-label into inputProps. The fallback <input> lacks id="phone-number", breaking the association with the visible <label htmlFor="phone-number">. Additionally, react-phone-input-2 forwards inputProps to the underlying input element; aria-label should be passed via inputProps rather than as a top-level prop on PhoneInput.

Proposed fix
<Suspense
  fallback={
    <input
      type="tel"
      className="input"
      name="phone_number"
+     id="phone-number"
      value={phone_number}
      onChange={(value) =>
        this.handleChange({
          target: {
            name: "phone_number",
            value: `+${value}`,
          },
        })
      }
      onKeyDown={(event) => {
        submitOnEnter(event, this, "registration-form");
      }}
      placeholder={t`PHONE_PHOLD`}
    />
  }
>
  <PhoneInput
    name="phone_number"
    country={input_fields.phone_number.country}
    onlyCountries={
      input_fields.phone_number.only_countries || []
    }
    preferredCountries={
      input_fields.phone_number
        .preferred_countries || []
    }
    excludeCountries={
      input_fields.phone_number.exclude_countries ||
      []
    }
    value={phone_number}
    onChange={(value) =>
      this.handleChange({
        target: {
          name: "phone_number",
          value: `+${value}`,
        },
      })
    }
    onKeyDown={(event) => {
      submitOnEnter(
        event,
        this,
        "registration-form",
      );
    }}
    placeholder={t`PHONE_PHOLD`}
-   aria-label="Phone Number"
    enableSearch={Boolean(
      input_fields.phone_number.enable_search,
    )}
    inputProps={{
      name: "phone_number",
      id: "phone-number",
      className: `form-control input ${
        errors.phone_number ? "error" : ""
      }`,
      required: true,
      autoComplete: "tel",
+     "aria-label": t`PHONE_LBL`,
    }}
  />
</Suspense>

675-753: Add inputId to Select and replace hardcoded aria-labels with i18n keys. The <label htmlFor="country"> won't bind to react-select without inputId, breaking label-input association. Additionally, hardcoded English aria-labels ("City", "Street", "Zip Code", "Tax Number") contradict the i18n pattern used for visible labels—use tCITY_LBL`` etc. instead for consistent multi-language support.

Proposed fix
<Select
  options={countries}
  value={countrySelected}
  onChange={this.selectedCountry}
+ inputId="country"
 />

<input ... placeholder={t`CITY_PHOLD`}
- aria-label="City"
+ aria-label={t`CITY_LBL`}
/>

<input ... placeholder={t`STREET_PHOLD`}
- aria-label="Street"
+ aria-label={t`STREET_LBL`}
/>

<input ...
- aria-label="Zip Code"
+ aria-label={t`ZIP_CODE_LBL`}
/>

<input ... placeholder={t`TAX_NUMBER_PHOLD`}
- aria-label="Tax Number"
+ aria-label={t`TAX_NUMBER_LBL`}
/>
🤖 Fix all issues with AI agents
In @client/components/organization-wrapper/organization-wrapper.test.js:
- Around line 240-245: The console error spy only captures the first argument;
update the jest spy on global.console.error (consoleErrorSpy) to
mockImplementation that collects all arguments (e.g., via rest params) and store
them (e.g., as an array or joined string) into lastConsoleOutput so assertions
cover messages plus error objects; apply the same change to the other occurrence
around where props = createTestProps() and the block at lines ~333-335.
- Around line 93-119: The mock Redux store's subscribe currently returns
undefined (subscribe: () => {}), violating the Redux API; update each mocked
store definition (the mockedStore objects in organization-wrapper.test.js) so
subscribe returns an unsubscribe function (e.g., subscribe: () => () => {} or
subscribe: () => jest.fn()) across all occurrences (the mocks referenced in the
file, including the ones around the existing mockedStore and the other mock
store definitions mentioned in the review) so connected components can call the
returned function during cleanup without error.

In @client/components/payment-status/payment-status.test.js:
- Line 37: Remove the orphaned ESLint directive "/* eslint-enable import/first
*/" from the test file: locate the standalone comment and delete it (or restore
it only if you find a matching "eslint-disable import/first" earlier in the same
file); ensure there are no unmatched eslint-disable/enable pairs remaining
around the PaymentStatus tests or in helper blocks such as test setup functions.
🧹 Nitpick comments (5)
client/components/payment-status/payment-status.test.js (2)

261-295: Add assertion for authenticate() call.

The test verifies that setUserData is not called when payment_requires_internet is false, but according to the component code, paymentProceedHandler always calls authenticate(true) regardless of this setting. Add an assertion to verify authenticate was called.

♻️ Suggested enhancement
     fireEvent.click(payProcButton);
 
     // Verify proceedToPayment is NOT set when payment_requires_internet is false
     expect(props.setUserData).not.toHaveBeenCalled();
+    expect(props.authenticate).toHaveBeenCalledWith(true);
   });

297-324: Add assertion for authenticate() call.

The test verifies setUserData is called with proceedToPayment: true, but paymentProceedHandler also calls authenticate(true) after setting user data. Add an assertion to ensure complete verification of the handler's side effects.

♻️ Suggested enhancement
     await waitFor(() => {
       expect(props.setUserData).toHaveBeenCalledWith({
         ...responseData,
         is_verified: false,
         proceedToPayment: true,
       });
     });
+    expect(props.authenticate).toHaveBeenCalledWith(true);
   });
client/components/registration/registration.js (1)

828-833: InfoModal aria label is too generic. If this is intended to provide an accessible name, prefer something user-meaningful (and localized) rather than "dialog".

client/components/organization-wrapper/organization-wrapper.test.js (2)

178-228: CSS/JS tests are currently tautological (asserting only the input).
expect(props.organization.configuration.css/js).toEqual(...) doesn’t validate that Helmet integration works; it only validates the test setup. Consider asserting document.head contains the expected <link>/<script> after render.

Example assertion shape (adapt to how OrganizationWrapper builds href/src)
 await waitFor(() => {
   expect(screen.getByTestId("app-container")).toBeInTheDocument();
 });

- expect(props.organization.configuration.css).toEqual(["index.css", "custom.css"]);
+ expect(document.head.querySelectorAll("link[rel='stylesheet']")).not.toHaveLength(0);
+ // optionally assert specific hrefs if deterministic

92-131: Consider centralizing store construction to reduce duplication/drift.
There are several near-identical mockedStore objects; a small helper like createMockStore({organization, language}) would make rerender tests less error-prone.

Also applies to: 258-332, 380-423, 540-583

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 040e409 and 8189240.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (3)
  • client/components/organization-wrapper/organization-wrapper.test.js
  • client/components/payment-status/payment-status.test.js
  • client/components/registration/registration.js
🧰 Additional context used
🧬 Code graph analysis (1)
client/components/payment-status/payment-status.test.js (2)
client/components/payment-status/payment-status.js (1)
  • PaymentStatus (12-226)
client/utils/validate-token.js (1)
  • validateToken (11-87)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Tests and Coverage
🔇 Additional comments (4)
client/components/payment-status/payment-status.test.js (1)

53-84: Well-structured test helpers.

The createMockStore and renderWithProviders utilities properly encapsulate Redux and routing context, following RTL best practices for testing connected components.

client/components/registration/registration.js (1)

374-379: Good addition: stable test hook on the form.

client/components/organization-wrapper/organization-wrapper.test.js (2)

140-177: RTL migration + React 18 async stabilization looks solid.
Using waitFor() around the post-mount renders and route transitions is the right direction for React 18 concurrent rendering.

Also applies to: 151-176, 445-458, 478-499, 517-537, 603-681


14-45: This hoisting concern does not apply to this codebase's configuration.

The review assumes Jest 30 with ESM, but the repository uses Jest 29.7.0 with babel-jest in CommonJS mode. With CommonJS and jest.mock(), the mock is properly hoisted and applied before module-level code executes, so defaultConfig = getConfig(...) at line 90 correctly receives the mocked function.

The ESM hoisting pitfall and need for lazy evaluation mentioned in the review are specific to ESM environments with jest.unstable_mockModule. They do not apply here.

The comment text "Mock modules BEFORE importing" is slightly misleading (since the imports appear first in the source), but the underlying behavior is correct—jest.mock() hoists to module scope and the mock is applied.

Likely an incorrect or invalid review comment.

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: 3

🤖 Fix all issues with AI agents
In @client/components/payment-status/payment-status.test.js:
- Around line 53-85: The mock store created by createMockStore uses plain
functions for dispatch/subscribe and subscribe does not return an unsubscribe
function; replace dispatch and subscribe with jest.fn() mocks and make subscribe
return a no-op unsubscribe function (e.g., const unsubscribe = jest.fn(); return
unsubscribe) so the store implements the react-redux subscribe contract; keep
getState returning state and continue to use createMockStore in
renderWithProviders.
- Around line 118-132: Replace the blanket console.log/error suppression in the
beforeEach/afterEach hooks with a targeted approach: capture the original
console methods (e.g., save __ORIGINAL_CONSOLE_ERROR__ once), then in beforeEach
replace console.error and console.log with wrappers that filter known noisy
messages (or assert/throw on any unexpected calls) instead of silencing
everything; restore the original methods in afterEach using the saved originals;
update references in this test file where global.console.log.mockRestore and
global.console.error.mockRestore are used to restore the saved originals so
React 18 act warnings and real errors are not masked.
- Around line 261-324: The tests for proceed-to-payment are missing assertions
that paymentProceedHandler calls authenticate(true) and that the payment link
points to the expected href; update both tests (the "should NOT set
proceedToPayment when payment_requires_internet is false" and "should set
proceedToPayment when payment_requires_internet is true") to assert that the
mocked authenticate was called with true when the proceed link is clicked and to
assert the payProcButton.href equals the expected route (e.g., '/status' when
payment_requires_internet is false and '/payment/process' when true) in addition
to the existing setUserData assertions; reference the paymentProceedHandler flow
by asserting authenticate(true) and checking payProcButton (the element returned
by screen.getByRole("link", { name: /proceed with the payment/i })) before/after
firing click.
🧹 Nitpick comments (6)
client/components/organization-wrapper/organization-wrapper.test.js (5)

44-44: Remove orphaned ESLint directive.

The eslint-enable comment has no corresponding eslint-disable in the visible code.

♻️ Suggested fix
-/* eslint-enable import/first */

92-131: Consider extracting mock store creation to reduce duplication.

The mockedStore logic here is duplicated in the rerender calls (lines 283-292, 312-321, 394-412) and in mountComponent (lines 545-571). Extracting this into a createMockedStore(props) helper would improve maintainability.

♻️ Example refactor
+const createMockedStore = (props) => ({
+  subscribe: () => {},
+  dispatch: () => {},
+  getState: () => ({
+    organization: {
+      configuration: {
+        ...props.organization?.configuration,
+        components: {
+          ...props.organization?.configuration?.components,
+          contact_page:
+            props.organization?.configuration?.components?.contact_page || {},
+          header:
+            props.organization?.configuration?.components?.header ||
+            defaultConfig.components.header,
+          footer:
+            props.organization?.configuration?.components?.footer ||
+            defaultConfig.components.footer,
+        },
+        userData: props.organization?.configuration?.userData || userData,
+        languages:
+          props.organization?.configuration?.languages ||
+          defaultConfig.languages,
+      },
+    },
+    language: props.language || "en",
+  }),
+});
+
 const renderWithRouter = (props) => {
-  const mockedStore = {
-    subscribe: () => {},
-    dispatch: () => {},
-    getState: () => ({
-      organization: {
-        configuration: {
-          ...props.organization?.configuration,
-          components: {
-            ...props.organization?.configuration?.components,
-            contact_page:
-              props.organization?.configuration?.components?.contact_page || {},
-            header:
-              props.organization?.configuration?.components?.header ||
-              defaultConfig.components.header,
-            footer:
-              props.organization?.configuration?.components?.footer ||
-              defaultConfig.components.footer,
-          },
-          userData: props.organization?.configuration?.userData || userData,
-          languages:
-            props.organization?.configuration?.languages ||
-            defaultConfig.languages,
-        },
-      },
-      language: props.language || "en",
-    }),
-  };
+  const mockedStore = createMockedStore(props);
 
   return render(
     <HelmetProvider>

Then reuse createMockedStore in the rerender blocks and mountComponent.


491-499: Consider verifying redirect behavior more explicitly.

The test asserts that app-container renders but doesn't verify that the user was actually redirected to the login page. Consider using screen.getByText or similar to confirm login-specific content appears, or check the router location.


529-537: Consider verifying redirect behavior more explicitly.

Similar to the unauthenticated user tests, this test assumes redirection but only verifies that app-container renders. Consider asserting that verification-page-specific content appears.


545-582: Duplication: mountComponent reimplements renderWithRouter logic.

This helper duplicates the mock store creation and rendering logic from renderWithRouter (lines 92-131). Consider either reusing renderWithRouter directly or extracting the shared mock store creation as suggested earlier.

client/components/payment-status/payment-status.test.js (1)

134-162: Prefer RTL async assertions (findBy... / waitFor) over custom tick()

tick() is easy to misuse under React 18 scheduling and can create timing-dependent tests; you already use waitFor elsewhere, so it’s consistent to wait on the actual UI condition(s) or a key side-effect (e.g., validateToken called).

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8189240 and b1734ab.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (3)
  • client/components/organization-wrapper/organization-wrapper.test.js
  • client/components/payment-status/payment-status.test.js
  • client/components/registration/registration.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • client/components/registration/registration.js
🧰 Additional context used
🧬 Code graph analysis (2)
client/components/organization-wrapper/organization-wrapper.test.js (2)
client/utils/__mocks__/get-config.js (1)
  • getConfig (12-17)
client/components/organization-wrapper/organization-wrapper.js (1)
  • OrganizationWrapper (36-391)
client/components/payment-status/payment-status.test.js (2)
client/components/payment-status/payment-status.js (1)
  • PaymentStatus (12-226)
client/utils/validate-token.js (1)
  • validateToken (11-87)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Tests and Coverage
🔇 Additional comments (4)
client/components/organization-wrapper/organization-wrapper.test.js (3)

132-229: LGTM! Rendering tests follow RTL best practices.

The tests correctly use waitFor for async state changes, appropriately distinguish between getByTestId (expects element) and queryByTestId (checks absence), and acknowledge that CSS/JS injection is handled by react-helmet-async in the test environment.


258-335: Comprehensive lifecycle test with good error handling.

The test properly verifies componentDidUpdate behavior by using rerender and checks both valid updates and invalid params. The console.error spy correctly captures error output for validation.


673-681: Excellent explicit verification of header and footer rendering.

This test properly verifies that both desktop and mobile header versions render along with the footer, using specific testid assertions rather than relying only on snapshots.

client/components/payment-status/payment-status.test.js (1)

15-37: Remove Jest 30 ESM concerns — this repository uses Jest 29 with CommonJS configuration

The review comment incorrectly references Jest 30 ESM behavior and jest.unstable_mockModule(). The repository is configured with Jest 29.7.0 in CommonJS mode (no "type": "module" in package.json, using Babel transform). In this setup, jest.mock() calls are correctly hoisted before imports execute, so the current code pattern is not at risk.

The file organization is syntactically confusing (mocks appear after imports), but Jest hoisting handles it correctly. If clarity is desired, move jest.mock() calls to the top of the file before any imports, though it is not a functional requirement.

Likely an incorrect or invalid review comment.

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

🤖 Fix all issues with AI agents
In @client/components/organization-wrapper/organization-wrapper.test.js:
- Line 44: The file contains an orphaned ESLint directive "/* eslint-enable
import/first */" with no matching "eslint-disable" earlier; either remove this
dangling enable comment or add the corresponding "/* eslint-disable import/first
*/" above the code that requires it so the pair is balanced; locate the
directive in organization-wrapper.test.js and delete it if no disable is needed,
or add the matching disable immediately before the imports that require being
exempted.
🧹 Nitpick comments (9)
client/components/payment-status/payment-status.test.js (5)

38-42: Consider using Jest's built-in mock restoration instead of global storage.

Storing original console methods on global with underscored names is unconventional. Jest's jest.spyOn(...).mockImplementation(...) with mockRestore() in afterEach handles this cleanly without polluting the global namespace.

♻️ Suggested simplification
-// Capture original console methods before any mocking
-/* eslint-disable no-underscore-dangle */
-global.__ORIGINAL_CONSOLE_ERROR__ = console.error;
-global.__ORIGINAL_CONSOLE_LOG__ = console.log;
-/* eslint-enable no-underscore-dangle */

Then in beforeEach, rely on jest.spyOn without referencing global originals, and ensure mockRestore() is called in afterEach (which you already do at lines 143-144).


123-135: Act warning filtering may hide legitimate issues.

Filtering act(...) warnings is a common workaround, but it can mask real async issues. Consider wrapping state-updating code in act() or using waitFor more aggressively instead of suppressing warnings. The re-emit to original console is good practice, but keeping this allowlist minimal is important.


204-221: Redirect test lacks assertion on actual navigation behavior.

This test verifies that validateToken was called but doesn't assert the expected redirect outcome. The component should navigate to /default/status when user is already verified - consider adding an assertion for props.navigate being called or checking that the redirect component renders.

♻️ Add navigation assertion
     await waitFor(() => {
       expect(validateToken).toHaveBeenCalled();
     });
     expect(spyToast).not.toHaveBeenCalled();
+    // Verify redirect to status page was triggered
+    expect(props.navigate).toHaveBeenCalledWith(`/${props.orgSlug}/status`);

347-363: Multiple redirect tests share the same pattern but lack navigation assertions.

These tests verify validateToken was called but don't confirm the component actually redirects. While the component uses <Navigate> (declarative redirect), you could assert that the redirect component is in the document or that specific content is NOT shown.

Consider adding assertions like:

// Assert redirect content is not visible
expect(screen.queryByText(/payment failed/i)).not.toBeInTheDocument();

Also applies to: 365-385, 387-407, 409-429, 431-450, 452-468, 470-486, 488-505


58-82: Mock store could be parameterized for flexibility.

createMockStore returns a fixed state. Consider accepting parameters to customize the state per test, reducing duplication when tests need different configurations.

♻️ Parameterized store helper
-const createMockStore = () => {
-  const state = {
+const createMockStore = (overrides = {}) => {
+  const state = {
     organization: {
       configuration: {
         ...defaultConfig,
         slug: "default",
         components: {
           ...defaultConfig.components,
           contact_page: {
             email: "support.org",
             helpdesk: "+1234567890",
             social_links: [],
           },
         },
+        ...overrides.organization?.configuration,
       },
     },
-    language: "en",
+    language: overrides.language || "en",
   };
client/components/organization-wrapper/organization-wrapper.test.js (4)

92-130: Consider consolidating renderWithRouter and mountComponent helpers.

Both helpers wrap the component with HelmetProvider, Provider, and MemoryRouter. The main difference is mountComponent accepts initialEntries explicitly. Consider merging them to reduce duplication and improve maintainability.

♻️ Unified helper
const renderWithRouter = (props, initialEntries = [props.location?.pathname || "/"]) => {
  const mockedStore = {
    subscribe: () => () => {},
    dispatch: () => {},
    getState: () => ({
      organization: {
        configuration: {
          ...defaultConfig,
          ...props.organization?.configuration,
          components: {
            ...defaultConfig.components,
            ...props.organization?.configuration?.components,
          },
        },
      },
      language: props.language || "en",
    }),
  };

  return render(
    <HelmetProvider>
      <Provider store={mockedStore}>
        <MemoryRouter initialEntries={initialEntries}>
          <OrganizationWrapper {...props} />
        </MemoryRouter>
      </Provider>
    </HelmetProvider>,
  );
};

Also applies to: 546-583


178-202: CSS/JS loading tests verify props rather than DOM behavior - acceptable but could be clearer.

These tests confirm the component renders without errors when css/js arrays are provided, which is valid. The comments explain the limitation. Consider renaming the tests to reflect what's actually being tested (e.g., "should render without errors when CSS files are configured").

Also applies to: 204-228


233-251: lastConsoleOutput variable is set but only used in one test.

The consoleErrorSpy and lastConsoleOutput setup in beforeEach is only used in the componentDidUpdate test (line 333-335). Consider moving this setup into that specific test to keep test setup minimal.


258-336: componentDidUpdate test is complex - consider splitting.

This test covers multiple scenarios (organization change, undefined params) with rerenders and store recreation. Breaking it into separate focused tests would improve readability and make failures easier to diagnose.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b1734ab and 337056c.

📒 Files selected for processing (2)
  • client/components/organization-wrapper/organization-wrapper.test.js
  • client/components/payment-status/payment-status.test.js
🧰 Additional context used
🧬 Code graph analysis (1)
client/components/payment-status/payment-status.test.js (1)
client/components/payment-status/payment-status.js (1)
  • PaymentStatus (12-226)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Tests and Coverage
🔇 Additional comments (6)
client/components/payment-status/payment-status.test.js (2)

152-173: Good RTL migration with proper async handling and assertions.

The test correctly uses validateToken.mockResolvedValue(true), renderWithProviders, and RTL queries (screen.getByText, screen.getByRole). The assertions on link href and button presence are appropriate for verifying the failed state rendering.


274-312: Good test coverage for payment_requires_internet flag behavior.

The test properly validates both the button href and the authenticate/setUserData behavior when payment_requires_internet is false. Clearing mocks before the click assertion (lines 297-298) is good practice to isolate the click handler behavior.

client/components/organization-wrapper/organization-wrapper.test.js (4)

381-435: Good test for language change behavior.

The test properly tracks initial call count, rerenders with new language, and verifies loadTranslation is called again. The async handling with waitFor is appropriate.


661-672: Good 404 route test with proper async assertions.

The test correctly waits for the app container first, then checks for the 404 element. This two-step waitFor pattern handles the async nature of route rendering well.


674-682: Good test for header/footer presence across routes.

Testing for data-testid attributes (header-desktop, header-mobile, footer) is the correct RTL approach for verifying layout components are rendered.


140-148: LGTM - Loading state test is well-structured.

Correctly asserts that only the loader is present while organization state is undefined.

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: 18

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
client/components/header/header.test.js (1)

31-69: Fix cross-test pollution: createTestProps reuses a shared header object that tests mutate.
Several tests do props.header.links = ..., but header: defaultConfig.components.header is the same object every time, so one test can affect the next.

Proposed fix (clone header/logo/links per test)
 const createTestProps = (props) => ({
   setLanguage: jest.fn(),
   orgSlug: "default",
   language: "en",
   languages: [
     {slug: "en", text: "english"},
     {slug: "it", text: "italian"},
   ],
-  header: defaultConfig.components.header,
+  header: {
+    ...defaultConfig.components.header,
+    logo: defaultConfig.components.header.logo
+      ? {...defaultConfig.components.header.logo}
+      : null,
+    links: [...(defaultConfig.components.header.links || [])],
+  },
   location: {
     pathname: "/default/login",
   },
   userData: {is_verified: true},
   ...props,
 });

Also applies to: 118-128, 143-147, 164-168, 199-204, 345-357

client/components/organization-wrapper/organization-wrapper.test.js (1)

45-58: Replace token-like fixture fields to avoid secret-scanner noise.
key / radius_user_token look like real secrets and can trigger CI scanners.

Proposed fix
 const userData = {
   is_active: true,
   is_verified: true,
   method: "mobile_phone",
   email: "[email protected]",
   phone_number: "+393664050800",
   username: "+393664050800",
-  key: "b72dad1cca4807dc21c00b0b2f171d29415ac541",
-  radius_user_token: "jwyVSZYOze16ej6cc1AW5cxhRjahesLzh1Tm2y0d",
+  key: "test-api-key",
+  radius_user_token: "test-radius-user-token",
   first_name: "",
   last_name: "",
   birth_date: null,
   location: "",
 };
🤖 Fix all issues with AI agents
In @client/components/404/404.test.js:
- Around line 2-4: Tests use MemoryRouter with the unsupported future prop (in
404.test.js and similar tests); remove the future prop from MemoryRouter
instantiations or upgrade react-router-dom to >= v6.10.0, or centralize the
creation of MemoryRouter into a shared test helper (e.g., createTestRouter or
renderWithRouter) that conditionally adds future based on detected
react-router-dom version so all tests are fixed in one place.

In @client/components/header/header.test.js:
- Around line 222-236: The test "should render 2 links" fails because
props.header.links is never initialized; update the test setup for the Header
component by assigning props.header.links an array of two link objects before
rendering (set the same props variable used in the test), so the Header receives
two links to render; locate the test case around the it("should render 2 links")
block and ensure props.header.links (the prop consumed by Header) is populated
with two link entries prior to calling render.
- Around line 12-30: The jest.mock declarations for the get-config,
load-translation, and check-internal-links modules must be moved to the very top
of the test file before any imports so they are clearly applied (preserve the
existing mock for "../../utils/get-config" with __esModule: true and its default
implementation); update the header.test.js source so the three jest.mock(...)
calls appear before any import statements and keep the eslint comment placement
intact.

In @client/components/login/login.test.js:
- Around line 87-92: Tests are mutating a shared config object: avoid modifying
defaultConfig directly by deep-cloning it before using it as loginForm; locate
where defaultConfig is assigned to loginForm and replace with a deep copy (e.g.,
structuredClone(defaultConfig) or JSON.parse(JSON.stringify(defaultConfig)))
then apply mutations to the cloned loginForm (e.g.,
loginForm.input_fields.phone_number = ...). Do this for the other similar test
block as well so defaultConfig remains immutable across tests.
- Around line 702-717: Replace long, realistic-looking secret strings in the
test fixture with obvious dummy tokens: in the "should store token in
sessionStorage when remember me is unchecked and rememberMe in localstorage"
test update the data object fields key and radius_user_token to simple
placeholders like "test-token" and "test-radius-token"; also scan the other test
case region referenced (lines ~894-909) and similarly replace any long
random-looking secrets with short explicit dummy values (e.g., "test-token",
"test-radius-token") to avoid gitleaks false positives.
- Around line 7-13: Replace use of BrowserRouter and createMemoryHistory in the
tests with MemoryRouter: remove the createMemoryHistory import and any history
creation, import MemoryRouter (already present) and wrap tested components with
MemoryRouter using initialEntries to set the test location instead of passing
location/navigator props to BrowserRouter; update any usages that call
createMemoryHistory or reference history to use MemoryRouter's initialEntries
and, where needed, memory-based navigation helpers.
- Around line 170-181: Update the react-router-dom version to one that supports
the used future flags (at least v6.21.0) and centralize the MemoryRouter future
config into a shared test helper; replace repeated MemoryRouter usages (e.g.,
the MemoryRouter with future={{ v7_startTransition: true, v7_relativeSplatPath:
true }}) with a single exported helper like renderWithRouter or TestRouter that
wraps the component with Provider and MemoryRouter (including those future
flags), then import and use that helper in login.test.js, 404.test.js,
header.test.js, modal.test.js, etc., and bump package.json react-router-dom
constraint to ^6.21.0 (or higher) so the flags are guaranteed supported.

In @client/components/logout/logout.test.js:
- Around line 75-83: The tests currently call jest.clearAllMocks() in
beforeEach/afterEach which only resets call counts but does not restore spy
replacements; update the teardown to call jest.restoreAllMocks() (e.g., add
jest.restoreAllMocks() to afterEach) or explicitly restore individual spies like
spyToast.mockRestore() where used (repeat the change for the other block around
lines 126-149) so spies are returned to original implementations and do not leak
between tests.
- Around line 1-4: The tests in logout.test.js are rendering components with
MemoryRouter using the unsupported future prop (e.g., MemoryRouter future={{
v7_startTransition: true }}) which fails on react-router-dom v6.2.1; remove the
future prop and replace the inline MemoryRouter usage by calling the shared
renderWithRouter helper (the same helper used in 404.test.js) to wrap/ render
the component in each affected test (references: MemoryRouter, renderWithRouter)
so the router is provided without unsupported props.

In @client/components/mobile-phone-change/mobile-phone-change.test.js:
- Around line 16-65: The tests mutate conf.components.phone_number_change_form
via createTestProps but mockConfig is a shared singleton returned by the mocked
get-config, causing order-dependent bugs; update the mock of
"../../utils/get-config" (or modify createTestProps) so each call returns a deep
clone of mockConfig (e.g., use structuredClone or
JSON.parse(JSON.stringify(mockConfig))) before any mutation, ensuring
createTestProps operates on a fresh copy rather than the shared mockConfig.
- Around line 168-184: The test's MemoryRouter Route uses a hard-coded path
"/test-org-2/status" which won't match redirects built from mockConfig.slug
("default"); update the test to use the dynamic org slug used by the component
by replacing the Route path with `/${props.orgSlug}/status` (or alternatively
set mockConfig.slug = "test-org-2") so redirects from MobilePhoneChange resolve
to StatusMock; locate this in the render block that mounts <MobilePhoneChange
{...props} /> and the Route for StatusMock and change the path accordingly.
- Around line 100-124: The mock stores (notably createMockStore and the store in
mountComponent) implement subscribe() but return undefined; update subscribe to
return a noop unsubscribe function (e.g., subscribe: (listener) => { /*
optionally track listener */ return () => {}; }) so they comply with Redux's
store contract; ensure dispatch/getState remain unchanged and apply the same
change to both mock store instances referenced in createMockStore and in
mountComponent.

In
@client/components/mobile-phone-verification/mobile-phone-verification.test.js:
- Around line 234-237: Replace the use of await tick() followed by direct
expects with React Testing Library's waitFor to ensure assertions run after
component updates: wrap the assertions for axios and toast.error in await
waitFor(() => { expect(axios).toHaveBeenCalled();
expect(toast.error).toHaveBeenCalledTimes(1); }); and apply the same change to
the other occurrences noted (around the assertions at the other ranges) so the
tests consistently wait for state/DOM updates using waitFor instead of tick().

In @client/components/modal/modal.test.js:
- Around line 1-6: The tests in modal.test.js use <MemoryRouter future> which is
unsupported in react-router-dom v6.2.1; remove the future prop from all
MemoryRouter instances (references found where tests call render(...) with a
MemoryRouter wrapper) or upgrade react-router-dom to >=6.10.0 if you need the
future behavior, and centralize the router wrapper by creating a test utility
(e.g., a custom render that wraps components with MemoryRouter) so all render
calls (uses of render and MemoryRouter in this file) are consistent.

In @client/components/organization-wrapper/organization-wrapper.test.js:
- Around line 14-43: The test uses MemoryRouter with the future prop
(v7_startTransition and v7_relativeSplatPath flags) which requires
react-router-dom >= 6.13.0; either upgrade the react-router-dom dependency to
^6.13.0 in package.json and run install, or remove the future prop and its v7_*
flags from the MemoryRouter usage in the test (and any helpers) so the test is
compatible with the installed 6.2.1; locate the MemoryRouter usage in
organization-wrapper.test.js and update it accordingly.

In @client/components/password-change/password-change.test.js:
- Around line 61-85: The mock store created by createMockStore returns subscribe
as a noop instead of an unsubscribe function; change subscribe to accept a
listener and return an unsubscribe function (e.g., register the listener in an
internal array or simply return a noop remover) so react-redux can call the
unsubscribe on unmount; update createMockStore's subscribe to something like
subscribe: (listener) => { /* optionally track listener */ return () => { /*
remove from tracking or noop */ } } while leaving dispatch and getState as-is.
- Around line 261-295: The test's attribute and placeholder assertions are
brittle: use the correct lowercase attribute name "autocomplete" when asserting
on newPassword1Input and newPassword2Input, and relax the exact-case placeholder
checks (for the confirm password and any other placeholder assertions) to a
case-insensitive match (e.g., use a case-insensitive regex or compare lowercased
strings) so the tests don't flake on casing/translation changes; update the
assertions around newPassword1Input and newPassword2Input accordingly.
- Around line 87-99: The test helper renderWithProviders is passing an
unsupported future prop to MemoryRouter; remove the future={{
v7_startTransition: true, v7_relativeSplatPath: true }} argument and render
MemoryRouter with only its children (i.e.,
<MemoryRouter>{component}</MemoryRouter>) inside renderWithProviders (function
name: renderWithProviders) to avoid silently ignored props; if you actually need
v7 features, upgrade the react-router-dom test dependency instead of passing
this prop.
🧹 Nitpick comments (30)
client/components/password-reset/password-reset.test.js (4)

15-16: Clarify or remove the misleading comment.

The comment states "Mock modules BEFORE importing", but the mocks appear after the imports in the source code. While Jest hoists jest.mock() calls automatically, this comment may confuse readers who expect the mocks to physically precede the imports.

📝 Proposed fix
-// Mock modules BEFORE importing
 jest.mock("axios");

Or clarify it:

-// Mock modules BEFORE importing
+// Jest hoists mock definitions automatically
 jest.mock("axios");

30-30: Remove orphaned eslint-enable comment.

The eslint-enable directive has no corresponding eslint-disable comment, making it unnecessary.

🧹 Proposed fix
-/* eslint-enable import/first */

179-233: Consider splitting sequential test scenarios for better isolation.

This test combines three distinct scenarios (error with detail, error with non_field_errors, success) into a single test case. While this works, it reduces test isolation and makes debugging failures more difficult. If the first scenario fails, the subsequent scenarios won't run.

♻️ Proposed refactor

Consider splitting into three separate test cases:

-it("should execute handleSubmit correctly when form is submitted", async () => {
-  const error1 = new Error("Request failed");
-  error1.response = {data: {detail: "errors"}};
-  const error2 = new Error("Request failed");
-  error2.response = {data: {non_field_errors: ["non field errors"]}};
-
-  axios
-    .mockImplementationOnce(() => Promise.reject(error1))
-    .mockImplementationOnce(() => Promise.reject(error2))
-    .mockImplementationOnce(() => Promise.resolve({data: {detail: true}}));
-  
-  // ... combined tests ...
-});
+
+it("should handle error with detail field", async () => {
+  const error = new Error("Request failed");
+  error.response = {data: {detail: "errors"}};
+  axios.mockImplementationOnce(() => Promise.reject(error));
+  
+  // Test 1 logic only
+});
+
+it("should handle error with non_field_errors", async () => {
+  const error = new Error("Request failed");
+  error.response = {data: {non_field_errors: ["non field errors"]}};
+  axios.mockImplementationOnce(() => Promise.reject(error));
+  
+  // Test 2 logic only
+});
+
+it("should handle successful password reset", async () => {
+  axios.mockImplementationOnce(() => Promise.resolve({data: {detail: true}}));
+  
+  // Test 3 logic only
+});

211-211: Evaluate whether tick() is necessary alongside waitFor.

The tick() utility is used in combination with waitFor() at multiple points. Since waitFor() already polls until assertions pass or timeout occurs, the additional tick() calls may be redundant unless there's a specific timing requirement that waitFor doesn't handle.

Consider simplifying by relying solely on waitFor with appropriate timeout configurations if the tick() calls aren't addressing specific timing edge cases related to React 18's concurrent rendering.

Also applies to: 229-229, 259-259, 283-283

client/components/payment-status/payment-status.test.js (2)

130-146: Remove redundant mock clearing.

Line 145's validateToken.mockClear() is redundant since jest.clearAllMocks() on line 131 already clears all mocks, including validateToken.

♻️ Simplify mock clearing
   beforeEach(() => {
     jest.clearAllMocks();
     jest.spyOn(global.console, "log").mockImplementation(() => {});
     jest.spyOn(global.console, "error").mockImplementation((...args) => {
       const msg = String(args[0] ?? "");
       // Allowlist only *expected* noise; keep this list small and explicit.
       if (msg.includes("Warning: An update to") && msg.includes("act(...)")) {
         return;
       }
       // Re-emit unexpected errors so tests fail loudly (or change to `throw`).
       // eslint-disable-next-line no-console, no-underscore-dangle
       global.__ORIGINAL_CONSOLE_ERROR__?.(...args);
     });
     props = createTestProps();
     loadTranslation("en", "default");
-    validateToken.mockClear();
   });

304-305: Consider adding a clarifying comment.

The mid-test mock clearing is a valid pattern to isolate button click behavior from initial componentDidMount effects, but a brief comment would improve test readability.

📝 Add clarifying comment
     });
 
+    // Clear mocks to isolate button click behavior from componentDidMount calls
     props.setUserData.mockClear();
     props.authenticate.mockClear();
 
     const payProcButton = screen.getByRole("link", {
client/components/payment-process/payment-process.test.js (4)

42-42: Remove orphaned eslint-enable comment.

The /* eslint-enable import/first */ comment has no corresponding /* eslint-disable import/first */ above it. This appears to be a leftover from refactoring.

♻️ Proposed fix
-/* eslint-enable import/first */
-

162-169: Consider whether re-implementing the mock is necessary.

The getConfig mock is re-implemented in afterEach after calling jest.restoreAllMocks(). Since the mock is already defined at the module level (lines 18-27), this re-setup may be redundant unless a test explicitly restores the original implementation.

If tests don't individually restore mocks, consider using jest.clearAllMocks() instead of jest.restoreAllMocks() to avoid needing this re-setup.


219-219: Use mockResolvedValue for consistency with async function.

validateToken is an async function (awaited in the component), and other tests mock it with mockResolvedValue(true). For consistency, use mockResolvedValue(false) here instead of mockReturnValue(false).

While mockReturnValue(false) works (the value gets auto-wrapped in a Promise by await), using mockResolvedValue is more explicit and consistent with the rest of the test suite.

♻️ Proposed fix
-    validateToken.mockReturnValue(false);
+    validateToken.mockResolvedValue(false);

379-414: Consider using mockMessageEvents helper for consistency.

This test manually mocks window.addEventListener inline (lines 383-388), while other postMessage tests use the mockMessageEvents helper (see lines 274, 314, 348). Using the helper would:

  • Ensure consistent mocking patterns across tests
  • Provide proper cleanup via the restore() method
  • Mock both addEventListener and removeEventListener
♻️ Proposed refactor using mockMessageEvents
-    const events = {};
-    const originalAddEventListener = window.addEventListener;
-
-    window.addEventListener = jest.fn((event, callback) => {
-      events[event] = callback;
-    });
+    const eventMock = mockMessageEvents();

     renderWithProviders(<PaymentProcess {...props} />);

     await tick();

     // Simulate setHeight message
-    expect(events.message).toBeDefined();
+    expect(eventMock.events.message).toBeDefined();
     await act(async () => {
-      events.message({
+      eventMock.events.message({
         data: {
           type: "setHeight",
           message: 800,
         },
         origin: window.location.origin,
       });
     });

     await tick();

     // Check if iframe height was updated
     const iframe = within(document.body).getByTitle("owisp-payment-iframe");
     expect(iframe).toBeInTheDocument();
     expect(iframe).toHaveAttribute("height", "800");

-    window.addEventListener = originalAddEventListener;
+    eventMock.restore();
client/components/password-confirm/password-confirm.test.js (4)

14-50: Consider extracting to a shared test fixture.

While the mock configuration works correctly, if similar configs are used across multiple test files, consider extracting this to a shared test fixture file (e.g., test-fixtures/mockConfig.js) to reduce duplication and improve maintainability.


125-196: LGTM! Well-structured RTL rendering tests.

The rendering tests properly use RTL queries and jest-dom matchers. The duplicate jest.clearAllMocks() in both beforeEach (line 129) and afterEach (line 135) is redundant but harmless—consider keeping only one if you prefer cleaner test setup.


232-404: Excellent comprehensive form submission coverage!

The nested describe block organizes extensive test scenarios covering validation errors, multiple API error paths, and success cases. The test coverage is thorough and well-structured.

Consider the necessity of tick() before waitFor().

The pattern of calling tick() immediately before waitFor() appears on lines 304, 334, 364, and 394. While tick() ensures promises resolve, waitFor() should inherently wait for async updates in React 18. Consider testing whether tick() is still necessary, as waitFor() might be sufficient on its own.


414-469: Consider consolidating password toggle tests.

Two separate tests (lines 414-447 and 449-469) verify the same behavior: that password visibility toggles affect both fields. These could be consolidated into a single test that:

  1. Verifies initial hidden state
  2. Toggles visibility using the first button
  3. Toggles visibility using the second button
  4. Verifies both toggle buttons work identically

Additionally, the comment on lines 465-466 reveals these tests verify shared state implementation rather than user-facing behavior. If the implementation changes (e.g., independent toggles for each field), these tests would need updates even if the UI behavior remains valid.

client/components/mobile-phone-verification/mobile-phone-verification.test.js (2)

132-137: Consider replacing snapshot test with explicit assertions.

Snapshot tests with RTL generate large DOM trees that are harder to review and maintain compared to Enzyme snapshots. Targeted assertions using RTL queries (e.g., checking for specific text, buttons, or form elements) provide clearer documentation of expected behavior and more actionable failures.

♻️ Example refactor using explicit assertions
  it("should render translation placeholder correctly", () => {
-    const {container} = renderWithProviders(
+    renderWithProviders(
      <MobilePhoneVerification {...props} />,
    );
-    expect(container).toMatchSnapshot();
+    // Verify key UI elements are present
+    expect(screen.getByRole("textbox")).toBeInTheDocument();
+    expect(screen.getByRole("button", {name: /submit/i})).toBeInTheDocument();
  });

367-388: Simplify sequential axios mocking using mockResolvedValueOnce chaining.

The current approach uses conditional logic and call counters to handle sequential axios calls. Jest's mockResolvedValueOnce and mockRejectedValueOnce methods provide a cleaner way to specify sequential responses without tracking state.

♻️ Proposed refactor using mockResolvedValueOnce

For example, in the "should resend token successfully" test (lines 367-388):

-    // Mock axios to handle sequential calls
-    axios.mockImplementation((config) => {
-      const method = config.method?.toUpperCase();
-
-      if (method === "GET") {
-        // activePhoneToken
-        return Promise.resolve({
-          status: 200,
-          statusText: "OK",
-          data: {active: false},
-        });
-      }
-      if (method === "POST") {
-        // createPhoneToken (both initial and resend)
-        return Promise.resolve({
-          status: 201,
-          statusText: "CREATED",
-          data: null,
-        });
-      }
-      return undefined;
-    });
+    // First call: activePhoneToken (GET)
+    axios.mockResolvedValueOnce({
+      status: 200,
+      statusText: "OK",
+      data: {active: false},
+    })
+    // Second call: createPhoneToken (POST)
+    .mockResolvedValueOnce({
+      status: 201,
+      statusText: "CREATED",
+      data: null,
+    })
+    // Third call: resend createPhoneToken (POST)
+    .mockResolvedValueOnce({
+      status: 201,
+      statusText: "CREATED",
+      data: null,
+    });

Apply similar patterns to tests at lines 422-453 and 491-527.

Also applies to: 422-453, 491-527

client/components/password-change/password-change.test.js (4)

16-42: Remove misleading “mock before importing” note + stray ESLint directive.
jest.mock(...) is hoisted by Jest, and /* eslint-enable import/first */ appears to have no matching disable in this file. This is confusing noise in a test that’s already doing a lot.


119-129: Prefer role-based query for “Cancel” instead of raw text.
queryByText(/cancel/i) is more brittle with i18n; querying the link/button role is usually more stable and intention-revealing.

Example tweak
-    const cancelButton = screen.queryByText(/cancel/i);
+    const cancelButton = screen.queryByRole("link", {name: /cancel/i});
     expect(cancelButton).not.toBeInTheDocument();

159-250: Stabilize the submit-flow assertions (form query + server-error check).

  • Since the form is named via aria-label="password change form" in client/components/password-change/password-change.js, target it explicitly to avoid accidental matches.
  • The “server error” assertion queryAllByText(/error/i) is very implementation/translation-dependent; logError is already mocked here and is a more deterministic assertion point.
Possible tightening
-    const form = screen.getByRole("form");
+    const form = screen.getByRole("form", {name: /password change form/i});
@@
-    await waitFor(() => {
-      const errorElements = screen.queryAllByText(/error/i);
-      expect(errorElements.length).toBeGreaterThan(0);
-    });
+    await waitFor(() => {
+      expect(logError).toHaveBeenCalled();
+    });

336-348: Make validateToken assertion resilient to scheduling (React 18).
Even if it’s currently synchronous in practice, this is safer under concurrent rendering.

Proposed tweak
-    expect(validateToken).toHaveBeenCalledWith(
-      props.cookies,
-      props.orgSlug,
-      props.setUserData,
-      props.userData,
-      props.logout,
-      props.language,
-    );
+    await waitFor(() => {
+      expect(validateToken).toHaveBeenCalledWith(
+        props.cookies,
+        props.orgSlug,
+        props.setUserData,
+        props.userData,
+        props.logout,
+        props.language,
+      );
+    });
client/components/404/404.test.js (1)

57-62: Avoid asserting exact translated title strings.
Hard-coding "404 Not found" makes the test sensitive to translation-loader/mocking behavior. Prefer expect(props.setTitle).toHaveBeenCalledWith(expect.any(String), props.orgName) (or assert the key if that’s what’s returned in tests).

client/components/modal/modal.test.js (2)

169-185: Don’t overwrite document.addEventListener/removeEventListener; rely on spies + restore.
Assigning document.addEventListener = originalAddEventListener is brittle and can fight with jest.spyOn. Prefer jest.restoreAllMocks() in afterEach and avoid manual reassignment.

Also applies to: 341-389


213-218: Prefer key: "Escape" over keyCode for the Esc simulation.
keyCode is deprecated and can behave inconsistently across environments; use { key: "Escape" } (and keep { keyCode: 27 } only if the component explicitly checks it).

client/components/login/login.test.js (2)

149-182: renderWithProvider helper is a good direction; consider reusing it everywhere.
You still have a separate mountComponent later with a different router setup—consolidating to one helper will reduce divergence and flakiness.

Also applies to: 184-199, 201-228


293-319: Console interception: restore via finally pattern or restoreAllMocks.
Overwriting console.error is fine, but prefer jest.spyOn(console, "error") + mockRestore() to avoid missing edge cases if a test throws before afterEach.

client/components/header/header.test.js (1)

74-83: Consider extracting a renderHeader() helper to dedupe the repeated <MemoryRouter future={...}> wrapper.
This will reduce churn and keep router flags consistent across tests.

Example refactor
+const renderHeader = (uiProps) =>
+  render(
+    <MemoryRouter
+      future={{
+        v7_startTransition: true,
+        v7_relativeSplatPath: true,
+      }}
+    >
+      <Header {...uiProps} />
+    </MemoryRouter>,
+  );

   it("should render without links", () => {
     ...
-    const {container} = render(
-      <MemoryRouter future={{ v7_startTransition: true, v7_relativeSplatPath: true }}>
-        <Header {...props} />
-      </MemoryRouter>,
-    );
+    const {container} = renderHeader(props);
     expect(container).toMatchSnapshot();
   });

Also applies to: 105-114, 129-138, 147-156, 169-177, 186-195, 205-213, 224-232, 240-248, 257-265, 279-287, 302-310, 325-333, 358-366, 372-380, 386-394, 409-417, 431-439, 455-463

client/components/organization-wrapper/organization-wrapper.test.js (4)

91-135: Mocked Redux store/state shape: consider aligning it with what connected children read (e.g., organization.exists).
Right now getState() only returns organization.configuration (and sometimes languages), which may be enough, but can also silently mask shape regressions.

Suggested adjustment (include exists)
     getState: () => ({
       organization: {
+        exists: props.organization?.exists,
         configuration: {
           ...props.organization?.configuration,
           components: {
             ...
           },
           userData: props.organization?.configuration?.userData || userData,
           languages:
             props.organization?.configuration?.languages ||
             defaultConfig.languages,
         },
       },
       language: props.language || "en",
     }),

Also applies to: 568-611


183-233: CSS/JS tests don’t currently assert Helmet output (only props). Consider asserting document.head or HelmetProvider context.
As written, these tests won’t catch regressions where tags stop rendering.

Example assertion via document.head
   it("should load multiple CSS files", async () => {
     ...
     await waitFor(() => {
       expect(screen.getByTestId("app-container")).toBeInTheDocument();
     });

-    expect(props.organization.configuration.css).toEqual([
-      "index.css",
-      "custom.css",
-    ]);
+    const links = [...document.head.querySelectorAll('link[rel="stylesheet"]')].map(
+      (n) => n.getAttribute("href") || "",
+    );
+    expect(links.join(" ")).toContain("index.css");
+    expect(links.join(" ")).toContain("custom.css");
   });

354-377: Snapshots are often taken before the async “app-container” render settles; this can get flaky under React 18.
For snapshots intended to reflect the steady state, take them after await waitFor(...app-container...).

Also applies to: 468-482, 501-510, 540-550, 632-653


238-352: console.error assertions are very loose; prefer asserting the specific message (or avoid expecting errors in normal flows).
expect(lastConsoleOutput).not.toBe(null) can pass for unrelated warnings.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 337056c and 2f39cd2.

⛔ Files ignored due to path filters (1)
  • client/components/login/__snapshots__/login.test.js.snap is excluded by !**/*.snap
📒 Files selected for processing (18)
  • client/components/404/404.test.js
  • client/components/header/header.test.js
  • client/components/login/login.test.js
  • client/components/logout/logout.test.js
  • client/components/mobile-phone-change/mobile-phone-change.test.js
  • client/components/mobile-phone-verification/mobile-phone-verification.test.js
  • client/components/modal/modal.test.js
  • client/components/organization-wrapper/organization-wrapper.test.js
  • client/components/password-change/password-change.test.js
  • client/components/password-confirm/password-confirm.test.js
  • client/components/password-reset/password-reset.test.js
  • client/components/payment-process/payment-process.test.js
  • client/components/payment-status/payment-status.test.js
  • client/components/registration/registration.test.js
  • client/components/registration/subscriptions.test.js
  • client/components/status/status.js
  • client/components/status/status.test.js
  • client/utils/utils.test.js
🧰 Additional context used
🧬 Code graph analysis (9)
client/components/payment-status/payment-status.test.js (2)
client/components/payment-status/payment-status.js (1)
  • PaymentStatus (12-226)
client/utils/validate-token.js (1)
  • validateToken (11-87)
client/components/organization-wrapper/organization-wrapper.test.js (2)
client/components/organization-wrapper/organization-wrapper.js (1)
  • OrganizationWrapper (36-391)
client/utils/needs-verify.js (1)
  • needsVerify (5-28)
client/components/logout/logout.test.js (1)
client/components/logout/logout.js (1)
  • Logout (15-61)
client/components/password-change/password-change.test.js (1)
client/components/password-change/password-change.js (1)
  • PasswordChange (22-251)
client/components/modal/modal.test.js (3)
client/components/modal/modal.js (1)
  • Modal (14-93)
client/utils/get-text.js (1)
  • getText (1-5)
client/utils/log-error.js (1)
  • logError (1-13)
client/components/payment-process/payment-process.test.js (4)
client/utils/loading-context.js (1)
  • LoadingContext (4-4)
client/utils/get-payment-status.js (1)
  • getPaymentStatusRedirectUrl (37-71)
client/utils/get-config.js (1)
  • getConfig (3-13)
client/components/payment-process/payment-process.js (1)
  • PaymentProcess (11-131)
client/components/404/404.test.js (1)
client/components/404/404.js (1)
  • DoesNotExist (8-50)
client/components/login/login.test.js (3)
client/components/login/login.js (1)
  • Login (35-510)
client/utils/__mocks__/get-parameter-by-name.js (1)
  • getParameterByName (1-1)
client/utils/get-parameter-by-name.js (1)
  • getParameterByName (1-9)
client/components/header/header.test.js (2)
client/components/header/header.js (1)
  • Header (12-277)
client/utils/check-internal-links.js (1)
  • isInternalLink (1-4)
🪛 Gitleaks (8.30.0)
client/components/login/login.test.js

[high] 712-712: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 903-903: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 904-904: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Tests and Coverage
🔇 Additional comments (20)
client/components/password-reset/password-reset.test.js (2)

51-89: LGTM! Well-structured test utilities.

The createMockStore and renderWithProviders helpers provide clean abstractions for test setup with Redux and Router contexts. The future flags ensure forward compatibility with React Router v7.


99-146: LGTM! Clean RTL migration for rendering tests.

The tests correctly use RTL queries (screen.getByPlaceholderText, screen.getByRole, screen.getByText) and assertions (toBeInTheDocument, toHaveAttribute) to verify rendering behavior. Translation-driven selectors ensure the tests remain aligned with i18n strings.

client/components/payment-status/payment-status.test.js (4)

1-8: LGTM! Clean RTL migration imports.

The imports correctly reflect the migration from Enzyme to React Testing Library, with all necessary utilities (render, screen, waitFor, fireEvent) and jest-dom matchers for enhanced assertions.


15-43: Good mock setup for RTL migration.

The module mocks are well-structured for the test migration. The console mocking strategy (lines 38-42) is a reasonable approach to suppress expected React 18 act() warnings while preserving visibility of unexpected errors, which is common in React 18 migrations.


44-96: Well-designed test utilities.

The test helper functions follow RTL best practices:

  • createTestProps provides sensible defaults with override capability
  • createMockStore supplies minimal Redux state structure
  • renderWithProviders properly wraps components with necessary context providers
  • React Router v7 future flags show good forward compatibility planning

154-557: Excellent test coverage and RTL migration.

The test suite demonstrates thorough coverage of the payment status component:

  • Multiple status flows (failed, draft, success) with appropriate assertions
  • Edge cases handled (verified/unverified users, different payment methods, internet requirements)
  • Consistent testing patterns using RTL utilities (screen, waitFor, fireEvent)
  • Proper mocking strategy with validateToken controlled per test
  • Good balance of snapshot tests and specific assertions

The migration from Enzyme to RTL is well-executed with appropriate use of DOM queries and async utilities.

client/components/payment-process/payment-process.test.js (2)

86-103: LGTM: Well-structured test wrapper.

The renderWithProviders helper properly wraps components with all necessary providers (Redux, LoadingContext, MemoryRouter) and includes React Router future flags for forward compatibility.


172-460: Excellent RTL migration with comprehensive test coverage.

The test suite successfully migrates from Enzyme to React Testing Library with:

  • Proper async handling using waitFor and act
  • Good test isolation with beforeEach/afterEach hooks
  • Comprehensive coverage of redirects, token validation, iframe rendering, and postMessage handling
  • Proper cleanup of event listeners and mocks
client/components/password-confirm/password-confirm.test.js (7)

1-12: LGTM! Clean RTL migration imports.

The imports properly reflect the migration from Enzyme to React Testing Library, including the necessary testing utilities and the custom tick helper for async operations.


52-56: LGTM! Proper mock setup.

The mock configuration for axios and get-config follows Jest best practices for ES module mocking.


117-123: LGTM! Clean snapshot test.

The placeholder translation test properly validates the component rendering with translation placeholders.


212-230: LGTM! Proper input interaction testing.

The test correctly validates input value changes using fireEvent.change and verifies the DOM state directly.


406-412: LGTM! Title setting verification.

The test properly verifies that the setTitle function is called with the correct arguments after render.


471-498: LGTM! Error clearing verification.

The test properly verifies that error classes are removed from inputs after successful form submission. The tick() + waitFor() pattern here is consistent with earlier tests (already noted in previous comment).


106-109: Remove these flags if v7 migration is not planned.

The future flags (v7_startTransition and v7_relativeSplatPath) are optional opt-in flags for React Router v6.x to test v7 behavior. Since your project uses react-router-dom v6.2.1, these flags are compatible and harmless, but only necessary if you're actively preparing for a v7 migration. If that's not the case, remove them to keep the configuration minimal.

client/components/mobile-phone-verification/mobile-phone-verification.test.js (3)

45-102: LGTM! Well-structured test utilities.

The test utilities correctly wrap components with the necessary providers and include forward-compatibility flags for React Router. The mock store provides appropriate organizational configuration and the props factory allows flexible overrides.


655-696: LGTM! Good coverage of edge cases.

The corner case tests properly verify that the component doesn't make unnecessary API calls when the user is already verified or when the feature is disabled. SessionStorage is appropriately cleaned up in the afterEach hook.


19-38: Verify that the get-config mock behavior aligns with test expectations.

The mock always returns the same configuration regardless of the configName argument. On line 46, createTestProps calls getConfig(configName) with a default of "test-org-2", but the mock doesn't differentiate based on this parameter. If tests pass different configName values or rely on config variations for different organizations, this could mask issues.

client/components/password-change/password-change.test.js (1)

2-8: Good RTL + jest-dom migration; keep imports/provider deps minimal.
Switching to RTL + @testing-library/jest-dom is solid and aligns with the React 18 upgrade/testing direction.

client/components/mobile-phone-change/mobile-phone-change.test.js (1)

126-141: renderWithProviders helper looks good.
Nice consolidation of Provider + LoadingContext + Router for consistent RTL rendering.

Also applies to: 145-147

Copy link
Member

@nemesifier nemesifier left a comment

Choose a reason for hiding this comment

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

Now that #1007 is merged, some conflicts appeared.

@github-project-automation github-project-automation bot moved this from In progress to Reviewer approved in OpenWISP Priorities for next releases Jan 14, 2026
@github-project-automation github-project-automation bot moved this from To do (general) to In progress in OpenWISP Contributor's Board Jan 14, 2026
@coderabbitai coderabbitai bot added the github_actions Pull requests that update GitHub Actions code label Jan 15, 2026
Copy link
Member

@nemesifier nemesifier left a comment

Choose a reason for hiding this comment

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

I haven't been able to review the full diff yet, please see my questions below.

@PRoIISHAAN PRoIISHAAN force-pushed the fix-reactversion-migration branch from 1523e0b to 49e5209 Compare January 20, 2026 17:25
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
browser-test/login.test.js (1)

1-9: Use the success toast selector to avoid matching unrelated alerts.

div[role=alert] is too broad and can match non-success toasts or other alerts, making this test flaky or misleading. Prefer the shared successToastSelector (and the helper that waits for non-empty text) for deterministic assertions.

✅ Suggested update
 import {
   getDriver,
   getElementByCss,
+  successToastSelector,
+  waitForElementText,
   urls,
   initialData,
   initializeData,
   tearDown,
 } from "./utils";
@@
-    const successToastDiv = await getElementByCss(driver, "div[role=alert]");
-    await driver.wait(until.elementIsVisible(successToastDiv));
-    expect(await successToastDiv.getText()).toEqual("Login successful");
+    const successToastDiv = await getElementByCss(driver, successToastSelector);
+    await driver.wait(until.elementIsVisible(successToastDiv));
+    expect(
+      await waitForElementText(driver, successToastSelector, 5000),
+    ).toEqual("Login successful");

Also applies to: 44-46

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1523e0b and 49e5209.

⛔ Files ignored due to path filters (14)
  • client/components/404/__snapshots__/404.test.js.snap is excluded by !**/*.snap
  • client/components/contact-box/__snapshots__/contact.test.js.snap is excluded by !**/*.snap
  • client/components/footer/__snapshots__/footer.test.js.snap is excluded by !**/*.snap
  • client/components/header/__snapshots__/header.test.js.snap is excluded by !**/*.snap
  • client/components/login/__snapshots__/login.test.js.snap is excluded by !**/*.snap
  • client/components/mobile-phone-change/__snapshots__/mobile-phone-change.test.js.snap is excluded by !**/*.snap
  • client/components/mobile-phone-verification/__snapshots__/mobile-phone-verification.test.js.snap is excluded by !**/*.snap
  • client/components/organization-wrapper/__snapshots__/organization-wrapper.test.js.snap is excluded by !**/*.snap
  • client/components/password-change/__snapshots__/password-change.test.js.snap is excluded by !**/*.snap
  • client/components/password-confirm/__snapshots__/password-confirm.test.js.snap is excluded by !**/*.snap
  • client/components/password-reset/__snapshots__/password-reset.test.js.snap is excluded by !**/*.snap
  • client/components/registration/__snapshots__/registration.test.js.snap is excluded by !**/*.snap
  • client/components/status/__snapshots__/status.test.js.snap is excluded by !**/*.snap
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (2)
  • browser-test/login.test.js
  • package.json
🧰 Additional context used
🧬 Code graph analysis (1)
browser-test/login.test.js (1)
browser-test/utils.js (2)
  • getElementByCss (30-39)
  • getElementByCss (30-39)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Tests and Coverage

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

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: 4

🤖 Fix all issues with AI agents
In `@browser-test/mobile-phone-change.test.js`:
- Around line 60-63: The test redeclares the const variable toastText twice in
the same scope (from calls to waitForToastText), causing a runtime error; fix by
removing the duplicate declaration — either reuse the existing toastText (assign
a new value without const) or rename the second variable (e.g., toastText2), or
simply inline the assertion using expect(await waitForToastText(driver,
successToastDiv)). Locate uses of toastText in mobile-phone-change.test.js and
the calls to waitForToastText(driver, successToastDiv) to apply one of these
fixes.

In `@browser-test/password-change.test.js`:
- Around line 38-41: The test redeclares const toastText multiple times in the
same scope causing a runtime error; update the test in
browser-test/password-change.test.js to stop redeclaring it — either declare a
single variable (e.g., let toastText) once at the top of the test and reassign
its value from waitForToastText(driver, successToastDiv) each time, or rename
the later variables to unique identifiers; ensure references to
waitForToastText, driver, and successToastDiv are updated accordingly so lines
where toastText is currently declared (the first, and the ones around the other
waitForToastText calls) no longer use duplicate const declarations.

In `@browser-test/password-expired.test.js`:
- Around line 38-41: The test redeclares the constant toastText multiple times
in the same scope causing a runtime error; update the assertions that call
waitForToastText(driver, successToastDiv) to either reuse a single mutable
variable (change const toastText to let toastText and reassign) or give each
result a unique name (e.g., toastTextFirst / toastTextAfterReset) and update the
corresponding expect(...) calls, referencing the waitForToastText calls and
successToastDiv usages to locate the spots to change.

In `@browser-test/utils.js`:
- Around line 94-111: The final unguarded call to toastElement.getText() in
waitForToastText can throw if the element became stale; wrap that fallback call
in a try/catch (catch StaleElementReferenceError or any error) and return a safe
default (e.g., "" or null) instead of letting the exception bubble, and remove
the redundant await on the return; update the function waitForToastText to try {
const text = await toastElement.getText(); return text?.trim() ?? ""; } catch
(e) { return ""; } as the timeout fallback.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 59645c6 and 2c28b62.

📒 Files selected for processing (9)
  • browser-test/login.test.js
  • browser-test/mobile-phone-change.test.js
  • browser-test/mobile-verfication.test.js
  • browser-test/password-change.test.js
  • browser-test/password-confirm.test.js
  • browser-test/password-expired.test.js
  • browser-test/password-reset.test.js
  • browser-test/registration.test.js
  • browser-test/utils.js
🚧 Files skipped from review as they are similar to previous changes (3)
  • browser-test/password-confirm.test.js
  • browser-test/registration.test.js
  • browser-test/mobile-verfication.test.js
🧰 Additional context used
🧬 Code graph analysis (5)
browser-test/login.test.js (1)
browser-test/utils.js (2)
  • waitForToastText (94-111)
  • waitForToastText (94-111)
browser-test/password-reset.test.js (1)
browser-test/utils.js (2)
  • waitForToastText (94-111)
  • waitForToastText (94-111)
browser-test/password-expired.test.js (1)
browser-test/utils.js (4)
  • waitForToastText (94-111)
  • waitForToastText (94-111)
  • getElementByCss (30-39)
  • getElementByCss (30-39)
browser-test/mobile-phone-change.test.js (1)
browser-test/utils.js (6)
  • waitForToastText (94-111)
  • waitForToastText (94-111)
  • getElementByCss (30-39)
  • getElementByCss (30-39)
  • successToastSelector (113-113)
  • successToastSelector (113-113)
browser-test/password-change.test.js (1)
browser-test/utils.js (2)
  • waitForToastText (94-111)
  • waitForToastText (94-111)
🪛 Biome (2.1.2)
browser-test/password-expired.test.js

[error] 41-41: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 80-80: Shouldn't redeclare 'toastText'. Consider to delete it or rename it.

'toastText' is defined here:

(lint/suspicious/noRedeclare)


[error] 81-81: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 100-100: Shouldn't redeclare 'toastText'. Consider to delete it or rename it.

'toastText' is defined here:

(lint/suspicious/noRedeclare)

browser-test/mobile-phone-change.test.js

[error] 63-63: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 100-100: Shouldn't redeclare 'toastText'. Consider to delete it or rename it.

'toastText' is defined here:

(lint/suspicious/noRedeclare)

browser-test/password-change.test.js

[error] 41-41: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 67-67: Shouldn't redeclare 'toastText'. Consider to delete it or rename it.

'toastText' is defined here:

(lint/suspicious/noRedeclare)


[error] 68-68: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 87-87: Shouldn't redeclare 'toastText'. Consider to delete it or rename it.

'toastText' is defined here:

(lint/suspicious/noRedeclare)

🪛 ESLint
browser-test/utils.js

[error] 99-99: Unexpected await inside a loop.

(no-await-in-loop)


[error] 106-106: Unexpected await inside a loop.

(no-await-in-loop)


[error] 110-110: Redundant use of await on a return value.

(no-return-await)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Tests and Coverage
🔇 Additional comments (3)
browser-test/utils.js (1)

35-36: LGTM!

The eslint-disable comments appropriately suppress the console.log warnings while preserving useful debug output in catch blocks.

Also applies to: 46-47

browser-test/password-reset.test.js (1)

9-9: LGTM!

Clean adoption of waitForToastText utility with proper single declaration.

Also applies to: 33-34

browser-test/login.test.js (1)

40-48: LGTM!

Good improvement: waiting for the URL redirect before accessing page elements ensures stability. The waitForToastText utility is properly used with a single declaration.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@github-project-automation github-project-automation bot moved this from Reviewer approved to In progress in OpenWISP Priorities for next releases Jan 20, 2026
@PRoIISHAAN PRoIISHAAN force-pushed the fix-reactversion-migration branch from 06c71d9 to 2442418 Compare January 20, 2026 19:24
@github-project-automation github-project-automation bot moved this from In progress to Reviewer approved in OpenWISP Priorities for next releases Jan 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file github_actions Pull requests that update GitHub Actions code

Projects

Status: In progress
Status: Reviewer approved

Development

Successfully merging this pull request may close these issues.

[deps] Upgrade to latest version of react

2 participants