-
-
Notifications
You must be signed in to change notification settings - Fork 118
[deps] Upgraded React to 18.3 #870 #1006
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
[deps] Upgraded React to 18.3 #870 #1006
Conversation
5045db9 to
ade71d3
Compare
|
Important Review skippedReview was skipped as selected files did not have any reviewable changes. 💤 Files selected but had no reviewable changes (1)
⛔ Files ignored due to path filters (14)
You can disable this status message by setting the 📝 WalkthroughWalkthroughThis 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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ 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. Comment |
There was a problem hiding this 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
keyDownevent, but the Modal component listens forkeyupevents (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. UsefireEvent.keyUpto 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 unusedhistoryprop fromRoutercomponent.In react-router-dom v6,
BrowserRouterdoesn't accept ahistoryprop—it creates its own history internally and ignores this prop. While thehistoryimport is used elsewhere in the application (e.g., in redirect utilities), passing it toRouterhas 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:
getByTextfor elements that must exist (throws if not found)queryByTextfor 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
afterEachcleanup on lines 41-43 is redundant sincebeforeEachalready clears all mocks before each test. You can safely remove theafterEachhook.🔎 Suggested cleanup
beforeEach(() => { jest.clearAllMocks(); axios.mockReset(); }); - afterEach(() => { - jest.clearAllMocks(); - });client/components/registration/registration-subscriptions.test.js (3)
233-233: Typo:lastConsoleOutuputshould belastConsoleOutput.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
lastConsoleOutuputforact(...)warnings is brittle and couples tests to React's internal warning format. Consider wrapping the render/updates inact()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.getByRoleorscreen.getByLabelTextwould be more idiomatic RTL and less brittle thanform.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:lastConsoleOutuputshould belastConsoleOutput.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 thejest/expect-expectdisable 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'sHelmetProvidercontext orHelmet.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 consolidatingmountComponentwithrenderWithRouter.Both helpers create similar mocked stores and wrap components with the same providers. Consider refactoring
renderWithRouterto accept an optionalinitialEntriesparameter 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
mountComponentcalls withrenderWithRouter(props, ["/default/status"]).
146-153: Consider using accessibility-based queries where possible.The extensive use of
container.querySelectorwith eslint-disable comments works but deviates from RTL best practices. For elements with meaningful text or roles, queries likegetByRole,getByText, orfindByTestIdwould be more resilient to refactoring.For structural assertions (checking if
.app-containerexists), the current approach is acceptable, but consider addingdata-testidattributes 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
createMockStoreandrenderWithProvidershelpers 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
getConfigmock implementation inafterEachafterjest.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.spyOnwithmockReturnValuethat 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-testidto 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): UsesMemoryRoutermountComponent(lines 151-179): UsesBrowserRouterwithRoutesand explicit route pathsThis 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
renderWithProvidersto 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 aftergetConfigis 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
renderWithProviderstakespropsdirectly and builds the store from props, while other files definecreateMockStoreseparately and haverenderWithProviderstake 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:renderWithProvidervsrenderWithProviders.This file uses
renderWithProvider(singular) while other files in this PR userenderWithProviders(plural). This minor inconsistency could cause confusion when developers work across files.Consider standardizing on one name across all test files, preferably
renderWithProvidersto 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_urlis absent, but only asserts thatvalidateTokenwas called. Consider asserting on the actual redirect mechanism (e.g., verifyNavigatecomponent renders ornavigateprop is called).
131-131: Inconsistent mock setup for async function.
validateTokenis an async function, but tests inconsistently usemockReturnValue(lines 131, 149, 165, 179, etc.) vsmockResolvedValue(lines 111, 364). While both work due to JavaScript's await behavior, usingmockResolvedValueconsistently is semantically correct for async functions.
255-285: Incomplete event listener restoration could cause test pollution.This test mocks
window.addEventListenerbut doesn't mock or restorewindow.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 returnsnullwhen redirecting, but this assertion could pass even if the component renders nothing due to an error. TheredirectSpyassertion on line 375 is sufficient to verify the behavior..eslintrc.json (1)
2-4: Consider extending recommended configs for new plugins.The
testing-libraryandjest-domplugins 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
⛔ Files ignored due to path filters (19)
client/components/404/__snapshots__/404.test.js.snapis excluded by!**/*.snapclient/components/contact-box/__snapshots__/contact.test.js.snapis excluded by!**/*.snapclient/components/footer/__snapshots__/footer.test.js.snapis excluded by!**/*.snapclient/components/header/__snapshots__/header.test.js.snapis excluded by!**/*.snapclient/components/login/__snapshots__/login.test.js.snapis excluded by!**/*.snapclient/components/logout/__snapshots__/logout.test.js.snapis excluded by!**/*.snapclient/components/mobile-phone-change/__snapshots__/mobile-phone-change.test.js.snapis excluded by!**/*.snapclient/components/mobile-phone-verification/__snapshots__/mobile-phone-verification.test.js.snapis excluded by!**/*.snapclient/components/modal/__snapshots__/modal.test.js.snapis excluded by!**/*.snapclient/components/organization-wrapper/__snapshots__/organization-wrapper.test.js.snapis excluded by!**/*.snapclient/components/password-change/__snapshots__/password-change.test.js.snapis excluded by!**/*.snapclient/components/password-confirm/__snapshots__/password-confirm.test.js.snapis excluded by!**/*.snapclient/components/password-reset/__snapshots__/password-reset.test.js.snapis excluded by!**/*.snapclient/components/payment-process/__snapshots__/payment-process.test.js.snapis excluded by!**/*.snapclient/components/payment-status/__snapshots__/payment-status.test.js.snapis excluded by!**/*.snapclient/components/registration/__snapshots__/registration.test.js.snapis excluded by!**/*.snapclient/components/status/__snapshots__/status.test.js.snapis excluded by!**/*.snappackage-lock.jsonis excluded by!**/package-lock.jsonyarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (54)
.eslintrc.jsonbabel.config.jsbrowser-test/create-mobile-configuration.jsbrowser-test/utils.jsclient/app.jsclient/components/404/404.test.jsclient/components/contact-box/contact.test.jsclient/components/footer/footer.test.jsclient/components/header/header.test.jsclient/components/login/login.test.jsclient/components/logout/logout.test.jsclient/components/mobile-phone-change/mobile-phone-change.jsclient/components/mobile-phone-change/mobile-phone-change.test.jsclient/components/mobile-phone-verification/mobile-phone-verification.jsclient/components/mobile-phone-verification/mobile-phone-verification.test.jsclient/components/modal/modal.test.jsclient/components/organization-wrapper/organization-wrapper.jsclient/components/organization-wrapper/organization-wrapper.test.jsclient/components/password-change/password-change.jsclient/components/password-change/password-change.test.jsclient/components/password-confirm/password-confirm.jsclient/components/password-confirm/password-confirm.test.jsclient/components/password-reset/password-reset.jsclient/components/password-reset/password-reset.test.jsclient/components/payment-process/payment-process.test.jsclient/components/payment-status/payment-status.jsclient/components/payment-status/payment-status.test.jsclient/components/registration/registration-subscriptions.test.jsclient/components/registration/registration.jsclient/components/registration/registration.test.jsclient/components/registration/subscriptions.test.jsclient/components/registration/test-utils.jsclient/components/status/status.jsclient/components/status/status.test.jsclient/components/status/status.test.js.enzyme-backupclient/components/status/status.test.js.rtl-backupclient/utils/__mocks__/get-config.jsclient/utils/get-config.jsclient/utils/get-plan-selection.jsclient/utils/load-translation.jsclient/utils/load-translation.test.jsclient/utils/log-error.jsclient/utils/needs-verify.jsclient/utils/tick.jsclient/utils/utils.test.jsclient/utils/with-route-props.jsconfig/__tests__/add-org.test.jsconfig/add-org.jsconfig/jest.config.jsconfig/setupTests.jsconfig/webpack.config.jspackage.jsonserver/app.jsserver/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
renderandscreenutilities, along with jest-dom for extended DOM matchers.
54-55: LGTM! Snapshot tests correctly updated for RTL.The snapshot tests properly use RTL's
renderand destructurecontainerfor 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:
getByAltTextfor asserting element presence (throws if not found)queryByAltTextwithnot.toBeInTheDocument()for asserting element absencetoBeInTheDocument()matcher from jest-domThis follows RTL best practices for the migration.
74-96: No changes needed. The test expectations correctly match theshouldLinkBeShownimplementation. The authentication logic is standard:authenticated: trueshows the link only to authenticated users,authenticated: falseshows 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
renderWithProvidershelper 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, andjest-dommatchers.
17-88: LGTM - Mock configuration pattern.Defining
mockConfigbeforejest.mockand 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
createMockStoreandrenderWithProvidershelpers 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
mockImplementationOncecalls to test multiple submission scenarios in sequence. The test properly awaits async operations withtick()andwaitFor().
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-helmettoreact-helmet-asyncis 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
renderWithRouterhelper 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 forreact-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-labelenhances screen reader support for the password confirmation form.client/components/password-reset/password-reset.js (1)
97-101: Good accessibility improvement.The
aria-labelattribute 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
componentIsMountedflag 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
useEffectto 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
componentIsMountedflag 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-labelattribute 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
componentIsMountedflag prevents setState warnings after unmount. Implementation is correct:
- Initialized in constructor (line 45)
- Set to true in componentDidMount (line 49)
- Checked alongside
isValidbefore setState (line 72)- Cleared in componentWillUnmount (lines 86-88)
The dual check
isValid && this.componentIsMountedon 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
componentIsMountedflag prevents setState warnings after unmount. Implementation follows the correct pattern:
- Initialized in constructor (line 38)
- Set to true in componentDidMount (line 42)
- Checked alongside
isValidbefore 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-labelattribute 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
shallowwith RTLrender(lines 2-4)- Added
MemoryRouterwrapper for routing context (line 4)- Created
renderWithRouterhelper 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
MemoryRouterfor routing context (lines 39-44, 54-59, etc.)- Added cleanup hooks with
jest.clearAllMocks()(lines 67, 71-73)- Used
screen.getByRolefor 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:
- Adding cleanup with
unmount()in tests- Using
waitFormore comprehensively to await all state updates- Investigating if the component needs an
isMountedguard (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
waitForandfireEvent. The use oftick()combined withwaitForprovides 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-containerandtesting-library/no-node-accessare justified here. When testing component-specific CSS classes like.header-desktop-linkand.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:
- Renders with sticky HTML content
- Verifies the announcement is visible
- Simulates user interaction (click close button)
- 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 thesetUserDatacalls with expected payloads for bothpayment_requires_internetscenarios.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
sessionStorageandlocalStorageafter 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:
- Hidden form is not present initially
- Hidden form appears with correct attributes when
radius_realmsis enabled- Hidden input fields have correct names and values
- Form submission triggers the captive portal form submit
The mock of
cpForm.submitis 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
componentDidMountmakes multiple API calls (activePhoneTokenfollowed bycreatePhoneToken). This approach correctly handles the 404 case foractivePhoneToken(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:
- Sets up different axios responses for componentDidMount calls vs. the verification call
- Waits for the input to render before interacting
- Simulates user entering code and submitting
- Verifies
setUserDatais 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
owPhoneTokenSentis 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
setHeightpostMessage 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.rendertocreateRootfollows the React 18 upgrade path correctly. TheHelmetProviderwrapping at the root level properly supports server-side rendering withreact-helmet-async.
client/components/organization-wrapper/organization-wrapper.test.js
Outdated
Show resolved
Hide resolved
client/components/organization-wrapper/organization-wrapper.test.js
Outdated
Show resolved
Hide resolved
client/components/registration/registration-subscriptions.test.js
Outdated
Show resolved
Hide resolved
client/components/registration/registration-subscriptions.test.js
Outdated
Show resolved
Hide resolved
a418cbf to
67474cb
Compare
There was a problem hiding this 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 tokeyup, notkeydown.The Modal component registers a
keyupevent listener (seemodal.jsline 14:document.addEventListener("keyup", this.handleKeyDown, false)), but this test fires akeyDownevent 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 setscancel: 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,
PaymentProcesscallsthis.context.setLoading()butrenderWithProvidersdoesn't includeLoadingContext.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.stringifywas 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.stringifywas 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
rerenderfunction from RTL should receive only the component, not the full provider tree. Callingrerenderwith<Provider>and<MemoryRouter>creates nested providers.Recommended fix: Either use the returned
rerenderwith just the component (rerender(<PasswordChange {...props} />)), or split this into two separate test cases where each callsrenderWithProvidersfresh.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 olderproposalnaming 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-objectclient/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:lastConsoleOutuput→lastConsoleOutput.
322-336: Rerender missing required providers.The rerender on lines 329-333 only wraps the component with
MemoryRouter, but the initial render usedHelmetProvider,Provider, andMemoryRouter. This inconsistency may cause the component to fail to render properly or produce misleading test results.Additionally, line 323 creates a duplicate
console.errorspy when one already exists from thebeforeEachblock.
675-684: Incomplete test: missing 404 behavior assertion.The test verifies that
.app-containerrenders, but the comment on lines 683-684 indicates it should verify 404 page or redirect behavior. Consider adding an assertion for theConnectedDoesNotExistcomponent 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-levelactive: falseproperty. 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
getConfigmock is already defined at the module level (lines 21-40). Afterjest.restoreAllMocks()completes, the module-level mock is still active. This manual re-implementation inafterEachis 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
afterEachblock has the same redundantgetConfigmock re-implementation as the one in the previous test suite (lines 159-176). The module-level mock remains active afterjest.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: Redundantjest.clearAllMocks()calls.Calling
jest.clearAllMocks()in bothbeforeEach(line 99) andafterEach(line 105) is redundant. Calling it once inbeforeEachis typically sufficient to ensure clean test state.🔎 Suggested simplification
beforeEach(() => { jest.clearAllMocks(); props = createTestProps(); loadTranslation("en", "default"); }); afterEach(() => { - jest.clearAllMocks(); });
153-165: Optional: Redundantjest.clearAllMocks()calls.Similar to the previous test suite,
jest.clearAllMocks()is called in bothbeforeEach(line 153) andafterEach(line 163). One call inbeforeEachis 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 oftick()beforewaitFor().The pattern of calling
tick()immediately beforewaitFor()appears in multiple tests (lines 203, 215, 226, 265, 289). Since RTL'swaitFor()already handles async waiting with automatic retries, thetick()call might be unnecessary. Consider testing whetherwaitFor()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
validateTokenwas called. Consider adding assertions onprops.navigateto 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
validateTokenwas 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
showLoadermessage 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 aneventsobject, 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 overquerySelector; conditional guards may hide test failures.Using
form.querySelectorbypasses RTL's accessibility-focused queries. Theifguards are risky—if an input doesn't exist, the test silently passes without filling required fields, potentially masking regressions.Consider using
getByRoleorgetByLabelTextwhich 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
findByRolewith appropriate waits. If the form inputs lack accessible labels, addingaria-labelor<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:
- Lines 345-349: Finding toggle buttons by filtering all buttons with
role="button"is indirect and could break if the component structure changes.- 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-testidattribute 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 thegetConfigmock. This works but is complex.Since
restoreAllMocks()is likely only needed for the console mocks, consider usingconsole.log.mockRestore()andconsole.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: UsemockResolvedValuefor async function mocks.
validateTokenis an async function returningPromise<boolean>, but the tests usemockReturnValue(true/false). While this works (await on a plain value resolves immediately), it's more explicit and correct to usemockResolvedValuefor 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
⛔ Files ignored due to path filters (19)
client/components/404/__snapshots__/404.test.js.snapis excluded by!**/*.snapclient/components/contact-box/__snapshots__/contact.test.js.snapis excluded by!**/*.snapclient/components/footer/__snapshots__/footer.test.js.snapis excluded by!**/*.snapclient/components/header/__snapshots__/header.test.js.snapis excluded by!**/*.snapclient/components/login/__snapshots__/login.test.js.snapis excluded by!**/*.snapclient/components/logout/__snapshots__/logout.test.js.snapis excluded by!**/*.snapclient/components/mobile-phone-change/__snapshots__/mobile-phone-change.test.js.snapis excluded by!**/*.snapclient/components/mobile-phone-verification/__snapshots__/mobile-phone-verification.test.js.snapis excluded by!**/*.snapclient/components/modal/__snapshots__/modal.test.js.snapis excluded by!**/*.snapclient/components/organization-wrapper/__snapshots__/organization-wrapper.test.js.snapis excluded by!**/*.snapclient/components/password-change/__snapshots__/password-change.test.js.snapis excluded by!**/*.snapclient/components/password-confirm/__snapshots__/password-confirm.test.js.snapis excluded by!**/*.snapclient/components/password-reset/__snapshots__/password-reset.test.js.snapis excluded by!**/*.snapclient/components/payment-process/__snapshots__/payment-process.test.js.snapis excluded by!**/*.snapclient/components/payment-status/__snapshots__/payment-status.test.js.snapis excluded by!**/*.snapclient/components/registration/__snapshots__/registration.test.js.snapis excluded by!**/*.snapclient/components/status/__snapshots__/status.test.js.snapis excluded by!**/*.snappackage-lock.jsonis excluded by!**/package-lock.jsonyarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (55)
.eslintrc.json.github/workflows/ci.ymlbabel.config.jsbrowser-test/create-mobile-configuration.jsbrowser-test/utils.jsclient/app.jsclient/components/404/404.test.jsclient/components/contact-box/contact.test.jsclient/components/footer/footer.test.jsclient/components/header/header.test.jsclient/components/login/login.test.jsclient/components/logout/logout.test.jsclient/components/mobile-phone-change/mobile-phone-change.jsclient/components/mobile-phone-change/mobile-phone-change.test.jsclient/components/mobile-phone-verification/mobile-phone-verification.jsclient/components/mobile-phone-verification/mobile-phone-verification.test.jsclient/components/modal/modal.test.jsclient/components/organization-wrapper/organization-wrapper.jsclient/components/organization-wrapper/organization-wrapper.test.jsclient/components/password-change/password-change.jsclient/components/password-change/password-change.test.jsclient/components/password-confirm/password-confirm.jsclient/components/password-confirm/password-confirm.test.jsclient/components/password-reset/password-reset.jsclient/components/password-reset/password-reset.test.jsclient/components/payment-process/payment-process.test.jsclient/components/payment-status/payment-status.jsclient/components/payment-status/payment-status.test.jsclient/components/registration/registration-subscriptions.test.jsclient/components/registration/registration.jsclient/components/registration/registration.test.jsclient/components/registration/subscriptions.test.jsclient/components/registration/test-utils.jsclient/components/status/status.jsclient/components/status/status.test.jsclient/components/status/status.test.js.enzyme-backupclient/components/status/status.test.js.rtl-backupclient/utils/__mocks__/get-config.jsclient/utils/get-config.jsclient/utils/get-plan-selection.jsclient/utils/load-translation.jsclient/utils/load-translation.test.jsclient/utils/log-error.jsclient/utils/needs-verify.jsclient/utils/tick.jsclient/utils/utils.test.jsclient/utils/with-route-props.jsconfig/__tests__/add-org.test.jsconfig/add-org.jsconfig/jest.config.jsconfig/setupTests.jsconfig/webpack.config.jspackage.jsonserver/app.jsserver/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
expectutility is actively used inconfig/__tests__/add-org.test.jsto 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
renderWithProvidershelper 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_iframeis 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 thetickhelper 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
renderWithProvidershelper 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()andwaitFor().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-errorsdisable 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:
createTestPropsprovides sensible defaults with override capabilitycreateMockStoresupplies a minimal but sufficient Redux store mock for these testsrenderWithProviderscorrectly 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
queryByTextwithnot.toBeInTheDocument()to verify the cancel button's absence.
128-256: Interaction tests are well-structured.The
handleSubmittest 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
waitForis 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 linksclient/components/404/404.test.js (2)
21-22: Good practice: renderWithRouter helper reduces duplication.The
renderWithRouterhelper cleanly wraps components withMemoryRouter, 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.mockclient/components/logout/logout.test.js (2)
67-73: Good practice: Proper test lifecycle management.The
beforeEach/afterEachhooks withjest.clearAllMocks()ensure clean test isolation.
75-94: LGTM: Correct interaction testing with RTL.The test properly:
- Uses
screen.getByRolefor accessible queries- Simulates user interaction with
fireEvent.click- Verifies the
mustLogin: truepayload, which is essential for captive portal login flowsclient/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.mockcalls 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.querySelectorfor 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: Usingrerenderto test state variations.The test efficiently verifies link visibility across different
userData.methodvalues (saml, social_login, mobile_phone) using RTL'srerender, 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
mockConfigobject 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:
findByPlaceholderTextwaits for lazy-loaded PhoneInput- Lines 243-269: Verifies fallback input renders immediately while PhoneInput loads
- Uses
waitForappropriately for async rendering
374-450: Well-structured: Sequential error scenario testing.The test methodically verifies multiple error paths:
- Field validation errors (username/password)
- 500 server error
- 504 timeout error
- Success case
Each uses
waitForproperly 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
sessionStoragewhen remember_me is uncheckedrememberMepreference stored inlocalStorage- No race conditions due to proper
waitForusage
862-906: Correct: Sesame token (passwordless auth) flow verification.The test properly validates automatic authentication when a sesame token is in the URL:
- Verifies
getParameterByNameis called for "sesame"- Confirms automatic
authenticateandsetUserDatacalls- Uses
waitForfor 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
HelmetProviderfromreact-helmet-asyncis imported inclient/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
mockConfigprovides 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) andfireEventto 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
waitForandtick.client/components/organization-wrapper/organization-wrapper.test.js (4)
1-46: LGTM: Proper test setup with react-helmet-async.The test file correctly imports
HelmetProviderfromreact-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
renderWithRouterhelper correctly wraps the component withHelmetProvider, ReduxProvider, andMemoryRouterin 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
renderWithProvidershelper 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.getByRolewith accessible names)waitForfor React 18's async renderingfireEventfor user interactions- jest-dom matchers (
toBeInTheDocument,toHaveAttribute)The async handling with
tick()andwaitForproperly accommodates React 18's rendering behavior.Also applies to: 221-244
client/components/registration/registration-subscriptions.test.js
Outdated
Show resolved
Hide resolved
client/components/registration/registration-subscriptions.test.js
Outdated
Show resolved
Hide resolved
client/components/registration/registration-subscriptions.test.js
Outdated
Show resolved
Hide resolved
There was a problem hiding this 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:lastConsoleOutuputshould belastConsoleOutput.This typo appears throughout the file and was previously flagged.
784-794: Test doesn't verify country selection behavior.The test title implies testing
selectedCountryexecution, 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:lastConsoleOutuput→lastConsoleOutput.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
getConfigmock returns the samemockConfigobject reference. Tests that modifyprops.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
mockConfiginbeforeEachfor affected test suites.
119-143: Consolidate duplicate mock store creation logic.
createMockStore(lines 119-143) and the inline store inmountComponent(lines 159-174) duplicate the same pattern. Consider reusingcreateMockStorewith 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 thejest/expect-expectdisable 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.mockImplementationafterjest.restoreAllMocks()is repeated in multipleafterEachblocks. If this is needed, consider moving it to a shared setup or usingjest.clearAllMocks()instead ofrestoreAllMocks()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
afterEachblocks call.client/components/password-change/password-change.test.js (4)
132-134: Optional: Simplify mock cleanup.The sequence
axios.mockClear()followed immediately byaxios.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-testidattributes to error containers- Using
getByTextwith the exact error message textThe 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-labelordata-testidon toggle buttons to identify which input they control- Asserting that the correct input toggles (e.g., if clicking the current password toggle, verify
currentPasswordInputchanges 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:subscribeshould return an unsubscribe function.Per the Redux store contract,
subscribe()should return a function to unsubscribe. Returningundefinedcould 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: MissingvalidateTokenmock setup may cause test instability.The
validateTokenmock is not configured in this describe block'sbeforeEach. While globaljest.mockhandles module replacement, the mock's return value is undefined, which could cause the component'scomponentDidMountto behave unexpectedly whenisValidis 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
axioswas called, without verifying that the active token response (active: true) was actually handled correctly or thatcreatePhoneTokenwas 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 describeclient/components/payment-process/payment-process.test.js (2)
157-163: Consider removing redundant mock re-setup.The
getConfigmock is already defined withmockImplementationat lines 11-20 before imports. Sincejest.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 themockMessageEventshelper 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
mockMessageEventshelper 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. IfmockRestoreis undefined, it means the mock was never created inbeforeEach, yet the test silently continues. While this prevents test failures from cleanup, it could mask problems with the mock setup itself.Consider either:
- Removing the optional chaining to surface setup issues, OR
- Adding explicit checks in
beforeEachto ensure mocks are created successfully
142-205: Consider simplifying thetick() + waitForasync pattern.Several tests use
await tick()followed bywaitFor(e.g., lines 200-205). With React Testing Library and React 18's concurrent rendering,waitForalone is often sufficient as it polls until the condition is met. Thetick()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 redundantaxios.mockReset().Since
jest.clearAllMocks()on line 149 already resets all mocks including axios, the explicitaxios.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
afterEachat line 42 is redundant sincebeforeEachalready 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
originalAddEventListenerandoriginalRemoveEventListener(lines 159-160, 166-167, 173-174) is defensive but potentially unnecessary. The test at line 311 usesjest.spyOnwithmockRestore(), 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 /> renderingdescribe block. Additionally, line 308 duplicates the assertion from line 304'swaitFor.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 asRouter) andMemoryRouterare imported, butRouteris only used in themountComponenthelper (line 186) whileMemoryRouteris used inrenderWithProviders(line 151). This dual usage can be confusing. Consider usingMemoryRouterconsistently or renaming the alias to clarify intent.
213-216: Move console.error spy to specific tests.The
console.errorspy is set up inbeforeEachfor 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()andjest.restoreAllMocks()followed by manually re-setting thegetConfigmock is redundant. SincegetConfigis mocked at module level withjest.mock(), callingjest.clearAllMocks()alone should suffice to reset call counts while preserving the implementation. TherestoreAllMocks()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
Navigatecomponent is rendered, which should trigger routing to the status page. Consider asserting thatstatus-mockbecomes 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
⛔ Files ignored due to path filters (4)
client/components/login/__snapshots__/login.test.js.snapis excluded by!**/*.snapclient/components/password-change/__snapshots__/password-change.test.js.snapis excluded by!**/*.snapclient/components/password-confirm/__snapshots__/password-confirm.test.js.snapis excluded by!**/*.snapclient/components/registration/__snapshots__/registration.test.js.snapis excluded by!**/*.snap
📒 Files selected for processing (15)
babel.config.jsbrowser-test/mobile-phone-change.test.jsclient/components/mobile-phone-change/mobile-phone-change.test.jsclient/components/mobile-phone-verification/mobile-phone-verification.test.jsclient/components/modal/modal.test.jsclient/components/organization-wrapper/organization-wrapper.test.jsclient/components/password-change/password-change.test.jsclient/components/password-confirm/password-confirm.test.jsclient/components/password-reset/password-reset.test.jsclient/components/payment-process/payment-process.test.jsclient/components/payment-status/payment-status.test.jsclient/components/registration/registration.jsclient/components/registration/registration.test.jsclient/components/registration/subscriptions.test.jsclient/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.jsimproves test maintainability and consistency. The new selector (.Toastify__toast--success div[role=alert]) is more specific than the previous hard-codeddiv[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 withcontainer.querySelectoris acceptable where accessible attributes are unavailable.
343-348: Async test pattern is appropriate for React 18.The combination of
await tick()followed byawait 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
renderWithProviderscalls. 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
renderWithProvidershelper with Redux store and Router wrappers- Query the DOM using semantic selectors (placeholders, labels, roles)
- Test user interactions with
fireEvent- Handle async behavior with
waitForandtick()- 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
createTestPropshelper 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
renderWithRouterhelper 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
waitForcorrectly 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
rerendercalls in this test now properly wrap the component withHelmetProvider,Provider, andMemoryRouter, 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
waitForand call count tracking forloadTranslationis 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
mountComponenthelper 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-404in the test is correct. It matches the actual DOM element in the 404 component implementation (client/components/404/404.js:19), which usesid="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 aboutPaymentProcess's calls tothis.context.setLoading()being silently ignored. The optionalmockSetLoadingparameter 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
PaymentProcesscomponent'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, andwithinutilities. 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-configmock provides good test isolation without external dependencies.
260-323: LGTM: Comprehensive coverage ofproceedToPaymentflag logic.These two tests effectively cover both branches of the
payment_requires_internetsetting:
- Lines 260-294: Verifies that clicking "proceed with payment" does NOT trigger
setUserDatawhen the flag is false- Lines 296-323: Verifies that clicking "proceed with payment" DOES set
proceedToPayment: truewhen the flag is trueThe 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
validateTokenreturnsfalse, the component doesn't callsetUserData(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
...defaultConfigspread provides the required state shape (userData,settings,isAuthenticated) expected bymapStateToProps. Thepayment_status_pagecomponent configuration is properly included via...defaultConfig.components. Thecontact_pageaddition is unused by this component and does not affect test validity. TherenderWithProviderspattern 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
createMockStoreandrenderWithProvidershelpers provide clean scaffolding for Redux and Router context, following RTL best practices.
208-208: Removetick()calls beforewaitFor—process.nextTickmay 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 onprocess.nextTick, which executes before microtasks but at a lower priority than React 18's internal scheduler.waitForfrom @testing-library/react already handles waiting for DOM updates from async operations, making the explicittick()calls redundant and potentially masking timing assumptions. Removeawait tick()calls at lines 208, 219, 258, and 282, then run tests to confirm they pass withwaitForalone.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
waitForto handle async content loading- Wrap components with
MemoryRouterfor 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
keyUpevent ondocument(matching the component's event listener), uses the correctkeyCode: 27, and verifies navigation occurs.
206-232: Non-Esc key test is appropriate.The negative assertion (
not.toHaveBeenCalled()) immediately afterfireEventis acceptable here since the handler is synchronous. Ifnavigatewere 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.spyOnwith proper cleanup viamockRestore()is the correct approach. The test effectively verifies thatcomponentWillUnmountproperly removes the event listener.
255-284: The test is correct as written.toHaveStylefrom jest-dom v6.9.1 usesgetComputedStyleinternally and explicitly ignores the!importantpriority flag—it only compares property values. The Modal component settingoverflow: "hidden"with!importantwill 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.erroris 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 thelocationandnavigatorprops 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.
client/components/mobile-phone-change/mobile-phone-change.test.js
Outdated
Show resolved
Hide resolved
client/components/mobile-phone-verification/mobile-phone-verification.test.js
Show resolved
Hide resolved
5682cc3 to
a876f80
Compare
There was a problem hiding this 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 (missingcomponentIsMountedguard orAbortController).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: UsemockResolvedValuefor async function mock.Line 170 still uses
mockReturnValue(true)instead ofmockResolvedValue(true)for the asyncvalidateTokenfunction. 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: UsemockResolvedValuefor async function mock.Same issue as line 170 - use
mockResolvedValue(true)instead ofmockReturnValue(true).🔎 Quick fix
- validateToken.mockReturnValue(true); + validateToken.mockResolvedValue(true);
213-213: UsemockResolvedValuefor async function mock.Use
mockResolvedValue(false)instead ofmockReturnValue(false).🔎 Quick fix
- validateToken.mockReturnValue(false); + validateToken.mockResolvedValue(false);
242-242: UsemockResolvedValuefor async function mock.Use
mockResolvedValue(true)instead ofmockReturnValue(true).🔎 Quick fix
- validateToken.mockReturnValue(true); + validateToken.mockResolvedValue(true);
266-266: UsemockResolvedValuefor async function mock.Use
mockResolvedValue(true)instead ofmockReturnValue(true).🔎 Quick fix
- validateToken.mockReturnValue(true); + validateToken.mockResolvedValue(true);
303-303: UsemockResolvedValuefor async function mock.Use
mockResolvedValue(true)instead ofmockReturnValue(true).🔎 Quick fix
- validateToken.mockReturnValue(true); + validateToken.mockResolvedValue(true);
337-337: UsemockResolvedValuefor async function mock.Use
mockResolvedValue(true)instead ofmockReturnValue(true).🔎 Quick fix
- validateToken.mockReturnValue(true); + validateToken.mockResolvedValue(true);
374-374: UsemockResolvedValuefor async function mock.Use
mockResolvedValue(true)instead ofmockReturnValue(true).🔎 Quick fix
- validateToken.mockReturnValue(true); + validateToken.mockResolvedValue(true);
441-441: UsemockResolvedValuefor async function mock.Use
mockResolvedValue(true)instead ofmockReturnValue(true).🔎 Quick fix
- validateToken.mockReturnValue(true); + validateToken.mockResolvedValue(true);
🧹 Nitpick comments (17)
client/components/payment-status/payment-status.test.js (2)
71-76: Consider usingredux-mock-storeor returning an unsubscribe function.The
subscribemethod 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-storeor@reduxjs/toolkit'sconfigureStorefor more realistic store behavior.
190-207: Consider strengthening redirect assertions.This test verifies
validateTokenwas called but doesn't explicitly assert that navigation occurred. If the component redirects verified users, consider assertingprops.navigatewas 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
afterEachhook only callsjest.clearAllMocks()which is already called at the start ofbeforeEach. 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
mapStateToPropsis a pure function test unrelated to component interactions. Consider moving it to its own describe block likedescribe("mapStateToProps", () => {...})for better test organization.
286-309: Redundant assertion within the test.Lines 307-308 duplicate the assertion already made inside
waitForat 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
getByRolewith 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 usingconfigureStorefrom Redux Toolkit orredux-mock-storefor more robust mocking.The current mock store implementation is minimal and works for these tests. However,
subscribeshould return an unsubscribe function, anddispatchtypically 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 mockedgetConfig.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.
propsis created once outsidebeforeEachand 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 inbeforeEach.🔎 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 mockedloadTranslationhas no effect.
loadTranslationis 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 aftertick()withoutwaitFor.After
await tick(), the test immediately assertssetUserDatawas 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 inwaitForfor consistency with other tests.🔎 Proposed fix
- await tick(); - - expect(props.setUserData).toHaveBeenCalledTimes(1); + await waitFor(() => { + expect(props.setUserData).toHaveBeenCalledTimes(1); + });
596-596: UsemockReset()instead ofmockClear()for full isolation.
axios.mockClear()only resets call history but preserves the mock implementation. If a previous test set a custommockImplementation, it may leak into this test. Useaxios.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.addEventListenerwithout 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 themockMessageEventshelper which provides cleaner cleanup via therestore()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 thegetConfigmock is somewhat redundant. SincegetConfigis already mocked at the module level (line 71), you could either:
- Use
mockClear()instead ofclearAllMocks()to preserve the mock implementation, or- 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
⛔ Files ignored due to path filters (19)
browserTestError.logis excluded by!**/*.logclient/components/404/__snapshots__/404.test.js.snapis excluded by!**/*.snapclient/components/contact-box/__snapshots__/contact.test.js.snapis excluded by!**/*.snapclient/components/footer/__snapshots__/footer.test.js.snapis excluded by!**/*.snapclient/components/header/__snapshots__/header.test.js.snapis excluded by!**/*.snapclient/components/login/__snapshots__/login.test.js.snapis excluded by!**/*.snapclient/components/logout/__snapshots__/logout.test.js.snapis excluded by!**/*.snapclient/components/mobile-phone-change/__snapshots__/mobile-phone-change.test.js.snapis excluded by!**/*.snapclient/components/mobile-phone-verification/__snapshots__/mobile-phone-verification.test.js.snapis excluded by!**/*.snapclient/components/modal/__snapshots__/modal.test.js.snapis excluded by!**/*.snapclient/components/organization-wrapper/__snapshots__/organization-wrapper.test.js.snapis excluded by!**/*.snapclient/components/password-change/__snapshots__/password-change.test.js.snapis excluded by!**/*.snapclient/components/password-confirm/__snapshots__/password-confirm.test.js.snapis excluded by!**/*.snapclient/components/password-reset/__snapshots__/password-reset.test.js.snapis excluded by!**/*.snapclient/components/payment-process/__snapshots__/payment-process.test.js.snapis excluded by!**/*.snapclient/components/payment-status/__snapshots__/payment-status.test.js.snapis excluded by!**/*.snapclient/components/registration/__snapshots__/registration.test.js.snapis excluded by!**/*.snapclient/components/status/__snapshots__/status.test.js.snapis excluded by!**/*.snapyarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (56)
.eslintrc.json.github/workflows/ci.ymlbabel.config.jsbrowser-test/create-mobile-configuration.jsbrowser-test/mobile-phone-change.test.jsbrowser-test/utils.jsclient/app.jsclient/components/404/404.test.jsclient/components/contact-box/contact.test.jsclient/components/footer/footer.test.jsclient/components/header/header.test.jsclient/components/login/login.test.jsclient/components/logout/logout.test.jsclient/components/mobile-phone-change/mobile-phone-change.jsclient/components/mobile-phone-change/mobile-phone-change.test.jsclient/components/mobile-phone-verification/mobile-phone-verification.jsclient/components/mobile-phone-verification/mobile-phone-verification.test.jsclient/components/modal/modal.test.jsclient/components/organization-wrapper/organization-wrapper.jsclient/components/organization-wrapper/organization-wrapper.test.jsclient/components/password-change/password-change.jsclient/components/password-change/password-change.test.jsclient/components/password-confirm/password-confirm.jsclient/components/password-confirm/password-confirm.test.jsclient/components/password-reset/password-reset.jsclient/components/password-reset/password-reset.test.jsclient/components/payment-process/payment-process.test.jsclient/components/payment-status/payment-status.jsclient/components/payment-status/payment-status.test.jsclient/components/registration/registration.jsclient/components/registration/registration.test.jsclient/components/registration/subscriptions.test.jsclient/components/registration/test-utils.jsclient/components/status/status.jsclient/components/status/status.test.jsclient/components/status/status.test.js.enzyme-backupclient/components/status/status.test.js.rtl-backupclient/utils/__mocks__/get-config.jsclient/utils/get-config.jsclient/utils/get-plan-selection.jsclient/utils/load-translation.jsclient/utils/load-translation.test.jsclient/utils/log-error.jsclient/utils/needs-verify.jsclient/utils/password-toggle.jsclient/utils/tick.jsclient/utils/utils.test.jsclient/utils/with-route-props.jsconfig/__tests__/add-org.test.jsconfig/add-org.jsconfig/jest.config.jsconfig/setupTests.jsconfig/webpack.config.jspackage.jsonserver/app.jsserver/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": trueto the env block is appropriate, as it enables Jest globals and prevents linting errors for Jest-specific identifiers likedescribe,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) andeslint-plugin-jest-dom(^5.5.0) packages are present inpackage.jsonas devDependencies, so the plugin configuration in.eslintrc.jsonis 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/firstis properly scoped with matchingeslint-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
getByRoleandgetByTextwith regex matchers provides resilient, accessible-focused assertions.
273-281: Verify the intentional use ofmustLogin: undefined.The assertion expects
mustLogin: undefinedwhich 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 wheremustLoginshould be explicitlyfalseor omitted entirely.
285-294: Good test forpayment_requires_internetflag behavior.The test correctly verifies that clicking the payment link does not trigger
setUserDatawhenpayment_requires_internetisfalse. This properly validates the conditional behavior.
511-527: LGTM!Good coverage of the draft state rendering and the
setUserDatacall withmustLogin: 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
getTextis 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.keyUpto match the component'skeyupevent 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 theoverflowstyle changes.
311-354: LGTM!Excellent test for verifying event listener cleanup on unmount. The test properly spies on
addEventListenerandremoveEventListener, 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
containerfrom 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
queryByRolewithnot.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
getByTextfor expected elements andqueryByTextfor 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
isAuthenticatedis true.
121-130: LGTM! Verification requirement correctly tested.The test properly validates that links requiring verification are hidden when
is_verifiedis 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
getTranslationStringhelper 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
renderWithProvidershelper.
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
renderWithRouterhelper appropriately wraps components withMemoryRouter, 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.querySelectorfor 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
renderWithProvidershelper 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
renderWithProvidershelper 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 andmountComponent(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-labelattributes to form inputs significantly improves screen reader support, whiledata-testidattributes enable more robust RTL-based testing. TheparentClassNameprop forPasswordToggleIconensures 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 mockingget-configbefore 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 withfireEvent, and handle asynchronous updates withwaitFor. 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_paymentandrequires_invoiceflags, 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
renderWithProvidershelper now includes LoadingContext.Provider (addressing the previous review comment), and themockMessageEventsutility provides proper cleanup via therestore()method.
138-164: LGTM! Proper test lifecycle setup.The beforeEach correctly uses
mockResolvedValuefor the asyncvalidateTokenfunction, 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-helmettoreact-helmet-asyncis 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
createRootandHelmetProviderare 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()tocreateRoot().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, andneedsVerifybefore importingOrganizationWrapperis 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
renderWithRouterhelper 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
waitForto 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
componentDidUpdatebehavior by rendering with all required providers during rerender operations. The handling of invalid params with console.error mocking is correct, and the typo fix forlastConsoleOutputhas 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
loadTranslationis called again. The proper cleanup of localStorage prevents test pollution.
568-605: Well-designed mountComponent helper for route testing.The
mountComponenthelper provides flexible initialization with custominitialEntriesfor 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-404is not a typo. The element inclient/components/404/404.js(line 19) is defined withid="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
userDatausingwaitFor, 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
historyMockinsidemountComponentensures 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_verificationis 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 asRouteron line 11) does not acceptlocationornavigatorprops. These props are specific toMemoryRouteror the low-levelRouterfromreact-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.erroris called when errors occur. The component deliberately logs errors through thelogError()utility function, which explicitly callsconsole.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.
ec9e917 to
80d13ad
Compare
There was a problem hiding this 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: UsemockResolvedValuefor asyncvalidateToken.Since
validateTokenis async, usemockResolvedValue(true)instead ofmockReturnValue(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: DuplicatemountComponentimplementation.This is the same pattern duplicated in
subscriptions.test.jsandtest-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
toHaveBeenCalledTimeschecks) can mask issues.Additionally, the
await tick()calls beforeawait waitFor()are redundant sincewaitForalready 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 addingisLoadingto LoadingContext value.The
LoadingContext.Provideronly passessetLoadingbut the context typically includes anisLoadingstate as well. IfPaymentProcessor any child component readsisLoadingfrom 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: UsemockMessageEventshelper for consistency.This test duplicates the window event mocking pattern that
mockMessageEventsalready provides. Additionally, line 407 only restoresaddEventListenerbut notremoveEventListener, 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 sharedmountComponentfromtest-utils.js.This
mountComponentfunction duplicates the implementation inclient/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 thegetConfigmock. 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
beforeEachrather thanafterEach.
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 outputFocus 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()andaxios.mockReset()are both called, butmockReset()already clears the mock and also resets the implementation. OnlymockReset()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
itblocks 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 addingdata-testidattributes 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 thePasswordToggleIconcomponent.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
handleResponsebehavior, 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:renderWithProviderssignature differs from other test files.This implementation takes
propsas the argument and internally creates the component, while other files (e.g.,password-change.test.js,registration.test.js) take acomponentas 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, orqueryByTextwould 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) andmountComponent(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
getConfigmock at line 71 always returnsmockConfig, butcreateTestPropsat line 82 accepts aconfigNameparameter (defaulting to "test-org-2") and callsgetConfig(configName). This creates a mismatch: tests may pass different config names expecting different configurations, but they all receive the samemockConfig.Consider either removing the
configNameparameter fromcreateTestPropsor 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 byrenderWithProviders- Inline
mockedStore(lines 166-181) used bymountComponentWhile 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
mountComponentneeds props-specific configuration, extendcreateMockStoreto accept parameters instead of duplicating the logic.Also applies to: 164-193
207-216: Console error spy is set up but rarely checked.The
consoleErrorSpyis created inbeforeEachfor 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
afterEachto 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
getConfigmock afterjest.clearAllMocks()(line 219) has already been called. Sincejest.clearAllMocks()clears all mock calls and instances, the explicitgetConfig.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
onChangebehavior 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:
Focus each test on its unique aspect:
- First test: Focus only on lazy loading completion
- Second test: Focus only on fallback rendering
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.methodand 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 usingmockResolvedValueOncefor sequential calls.The
postCallCountclosure pattern works correctly, but RTL typically favorsmockResolvedValueOnce()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
⛔ Files ignored due to path filters (2)
client/components/registration/__snapshots__/registration.test.js.snapis excluded by!**/*.snapyarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (13)
client/components/mobile-phone-change/mobile-phone-change.test.jsclient/components/mobile-phone-verification/mobile-phone-verification.test.jsclient/components/organization-wrapper/organization-wrapper.test.jsclient/components/password-change/password-change.test.jsclient/components/password-confirm/password-confirm.test.jsclient/components/password-reset/password-reset.test.jsclient/components/payment-process/payment-process.test.jsclient/components/payment-status/payment-status.test.jsclient/components/registration/registration.jsclient/components/registration/registration.test.jsclient/components/registration/subscriptions.test.jsclient/utils/tick.jsserver/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/firstpragma is appropriately scoped.
54-85: Mock store and render helper are correctly implemented.The minimal Redux store mock provides the necessary interface. Consider extracting
createMockStoreandrenderWithProvidersto 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()andrestore().
410-435: LGTM!Good test structure with proper spy usage,
mockResolvedValuefor async validation, and cleanup withmockRestore().
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-configmock provides the necessary translation structure, and the eslint overrides correctly acknowledge the import ordering requirement.
38-83: Well-structured test helpers.The
renderWithProviderspattern 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
mockRestoresafely 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
mockRestoremethod 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),waitForfor 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
waitForfor async assertions and proper DOM queries usinggetByTestIdandgetByRole. 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
describewith its ownbeforeEach/afterEachfor form submission tests. TheconsoleErrorSpysetup is properly scoped and cleaned up.
410-465: Excellent password toggle tests usingdata-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
renderWithRouterhelper properly wraps the component with all required providers (HelmetProvider, Redux Provider, MemoryRouter) and creates a properly structured mocked Redux store with sensible fallbacks todefaultConfig.
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.activestructure, 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.
client/components/mobile-phone-change/mobile-phone-change.test.js
Outdated
Show resolved
Hide resolved
client/components/mobile-phone-verification/mobile-phone-verification.test.js
Outdated
Show resolved
Hide resolved
client/components/mobile-phone-verification/mobile-phone-verification.test.js
Outdated
Show resolved
Hide resolved
There was a problem hiding this 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
📒 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.infototoast.successbetter reflects the semantic meaning of successfully sending a token.
139-139: LGTM: Good accessibility enhancement.Adding
aria-labelto the form improves screen reader accessibility by clearly identifying the form's purpose.
There was a problem hiding this 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
LoadingContextwith{setLoading, isLoading}, but the actual implementation inclient/utils/loading-context.jsuses{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
LoadingContextfromclient/utils/loading-context.jsto ensure consistency.
144-154:renderWithProvidersuses a different LoadingContext than the component.The
renderWithProvidershelper creates a localLoadingContext(line 150), but the actualMobilePhoneChangecomponent imports and usesLoadingContextfromclient/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
LoadingContextfrom 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 replacingtick()with RTL'swaitForfor consistency.The test uses a custom
tick()utility followed by assertions. With React Testing Library, the idiomatic approach is to wrap assertions inwaitForto 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
📒 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
waitForand verifying input attributes and onChange handling. The use ofrenderWithProvidersis 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
historyMockis now correctly created insidemountComponent, ensuring each test gets a fresh history instance. This addresses the previous review comment about shared history state leaking between tests.
There was a problem hiding this 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
📒 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.allwith 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.
There was a problem hiding this 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
historyimport is no longer used after removing thehistoryprop 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 usinggetByRoleconsistently for link assertions.The tests mix
getByRole("link", {name: "..."})(line 93) withgetByText()(lines 94, 109-110, 125) for verifying links. UsinggetByRoleconsistently 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/afterEachpattern that saves and restoresaddEventListener/removeEventListeneris 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
addEventListenerandremoveEventListenerare called withexpect.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
mockConfigobject 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) andmountComponent(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
getConfigmock inafterEach(lines 597, 735) appears unnecessary since the mock is already configured at the module level (lines 85-88) andclearAllMocks()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 likecontainer.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.rendertocreateRootis 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.StrictModehelps 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
⛔ Files ignored due to path filters (1)
client/components/contact-box/__snapshots__/contact.test.js.snapis excluded by!**/*.snap
📒 Files selected for processing (5)
client/app.jsclient/components/contact-box/contact.test.jsclient/components/footer/footer.test.jsclient/components/modal/modal.test.jsclient/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.
renderandscreenenable querying the rendered DOM, andjest-domprovides semantic matchers liketoBeInTheDocument().
5-22: LGTM! Proper mock hoisting pattern.The mock setup correctly places
jest.mockcalls before imports with appropriate ESLint suppression. The comment clearly explains whyimport/firstmust be disabled.
55-60: LGTM! Snapshot test migrated correctly.The test properly uses RTL's
renderand captures snapshots viacontainer, which is the standard approach for snapshot testing with RTL.
74-84: LGTM! Correct RTL query patterns.The test properly uses
getByAltTextfor elements that must exist (throws if missing) andqueryByAltTextfor 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
renderandscreenfrom RTL- Container-based snapshots following RTL conventions
- Correct use of
queryByfor negative assertions (line 78)- Good use of
toBeInTheDocument()matcher from jest-domThe 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 (
responseDataconstant with hardcoded test values). No security risk exists here.
b13bec3 to
feab576
Compare
There was a problem hiding this 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 expectsspyToast.toHaveBeenCalledTimes(4). If the fourth submission succeeds,toast.errorshould 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
componentIsMountedflag is properly initialized in the constructor and cleaned up incomponentWillUnmount, but this issue was already raised in a previous review: thehandleSubmitmethod'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 usingmockReturnValuefor async function in most tests.A past review requested changing
validateToken.mockReturnValuetomockResolvedValueat these lines, but only lines 149 and 415 were updated. Lines 170, 192, 213, 242, 266, 303, 337, 374, and 441 still usemockReturnValuefor the asyncvalidateTokenfunction.🔎 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
truehas 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
truetosessionStorage.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: falsebut the actualLoadingContextinclient/utils/loading-context.jsusesgetLoading: () => {}. This mismatch means tests pass with an incorrect provider shape that differs from production.Either import
loadingContextValuefromclient/utils/loading-context.jsor 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 unusedhistoryimport.The
historyimport is no longer used since React Router v6'sBrowserRouterdoesn'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:
- Implementing actual country selection testing if the feature exists
- Renaming the test to match what it actually verifies
- 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 themockMessageEventshelper 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 usingwaitForinstead of customtick()for async assertions.Using the custom
tick()utility followed by synchronous assertions can be fragile with React 18's concurrent rendering. RTL'swaitFororfindBy*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
originalAddEventListenerandoriginalRemoveEventListenervariables are declared and managed inbeforeEach/afterEachbut never used. The test at lines 307-350 that spies on these methods creates its own spies and restores them withmockRestore(), 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
mapStateToPropstest 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:
- Actually verify backdrop element presence (check for an element with appropriate class/role), or
- 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: Redundantaria-labelwith existing<label>element.The password input at line 622 already has an associated
<label htmlFor="password">(line 617), which provides the accessible name. Thearia-labelis redundant here. However, this doesn't cause issues—it's a minor point.The same applies to other inputs with both
<label>andaria-label:
- Confirm Password (line 661)
- City (line 701)
- Street (line 717)
- Zip Code (line 732)
- Tax Number (line 748)
Consider removing the
aria-labelattributes 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 ofBrowserRouterwithlocationandnavigatorprops.
BrowserRouter(aliased asRouter) is imported here but used later withlocationandnavigatorprops (line 338). In React Router v6,BrowserRouterdoes not accept these props—they are meant for the low-levelRoutercomponent. The props are silently ignored, meaninghistoryMockhas no effect on routing.For controlled routing in tests, use
MemoryRouterwithinitialEntries, or import the baseRouterfromreact-router-domfor custom history support.Suggested fix
import { - BrowserRouter as Router, + MemoryRouter, Route, Routes, - MemoryRouter, } from "react-router-dom"; -import {createMemoryHistory} from "history";Then update
mountComponentto useMemoryRouterdirectly:- 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: Redundantaxios.mockReset()in bothbeforeEachandafterEach.Calling
axios.mockReset()in both lifecycle hooks is unnecessary. ThebeforeEachreset ensures a clean state before each test runs, making theafterEachreset 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
PhoneInputshare the same placeholder (PHONE_PHOLD), so the finalwaitForassertion (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
componentDidMountand reset incomponentWillUnmount, completing the mounting guard pattern.For React 18, consider using
AbortControllerto 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:
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-testidattributes or more specific role-based queries for better maintainability.Line 235: The
tick()call beforewaitFormay be redundant, aswaitForalready 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-testidattributes to password toggle buttons in the component, allowing direct queries likescreen.getAllByTestId('password-toggle').
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (18)
client/components/404/__snapshots__/404.test.js.snapis excluded by!**/*.snapclient/components/contact-box/__snapshots__/contact.test.js.snapis excluded by!**/*.snapclient/components/footer/__snapshots__/footer.test.js.snapis excluded by!**/*.snapclient/components/header/__snapshots__/header.test.js.snapis excluded by!**/*.snapclient/components/login/__snapshots__/login.test.js.snapis excluded by!**/*.snapclient/components/logout/__snapshots__/logout.test.js.snapis excluded by!**/*.snapclient/components/mobile-phone-change/__snapshots__/mobile-phone-change.test.js.snapis excluded by!**/*.snapclient/components/mobile-phone-verification/__snapshots__/mobile-phone-verification.test.js.snapis excluded by!**/*.snapclient/components/modal/__snapshots__/modal.test.js.snapis excluded by!**/*.snapclient/components/organization-wrapper/__snapshots__/organization-wrapper.test.js.snapis excluded by!**/*.snapclient/components/password-change/__snapshots__/password-change.test.js.snapis excluded by!**/*.snapclient/components/password-confirm/__snapshots__/password-confirm.test.js.snapis excluded by!**/*.snapclient/components/password-reset/__snapshots__/password-reset.test.js.snapis excluded by!**/*.snapclient/components/payment-process/__snapshots__/payment-process.test.js.snapis excluded by!**/*.snapclient/components/payment-status/__snapshots__/payment-status.test.js.snapis excluded by!**/*.snapclient/components/registration/__snapshots__/registration.test.js.snapis excluded by!**/*.snapclient/components/status/__snapshots__/status.test.js.snapis excluded by!**/*.snapyarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (57)
.eslintrc.json.github/workflows/ci.ymlbabel.config.jsbrowser-test/create-mobile-configuration.jsbrowser-test/js-file-inject.test.jsbrowser-test/mobile-phone-change.test.jsbrowser-test/utils.jsclient/app.jsclient/components/404/404.test.jsclient/components/contact-box/contact.test.jsclient/components/footer/footer.test.jsclient/components/header/header.test.jsclient/components/login/login.test.jsclient/components/logout/logout.test.jsclient/components/mobile-phone-change/mobile-phone-change.jsclient/components/mobile-phone-change/mobile-phone-change.test.jsclient/components/mobile-phone-verification/mobile-phone-verification.jsclient/components/mobile-phone-verification/mobile-phone-verification.test.jsclient/components/modal/modal.test.jsclient/components/organization-wrapper/organization-wrapper.jsclient/components/organization-wrapper/organization-wrapper.test.jsclient/components/password-change/password-change.jsclient/components/password-change/password-change.test.jsclient/components/password-confirm/password-confirm.jsclient/components/password-confirm/password-confirm.test.jsclient/components/password-reset/password-reset.jsclient/components/password-reset/password-reset.test.jsclient/components/payment-process/payment-process.test.jsclient/components/payment-status/payment-status.jsclient/components/payment-status/payment-status.test.jsclient/components/registration/registration.jsclient/components/registration/registration.test.jsclient/components/registration/subscriptions.test.jsclient/components/registration/test-utils.jsclient/components/status/status.jsclient/components/status/status.test.jsclient/components/status/status.test.js.enzyme-backupclient/components/status/status.test.js.rtl-backupclient/utils/__mocks__/get-config.jsclient/utils/get-config.jsclient/utils/get-plan-selection.jsclient/utils/load-translation.jsclient/utils/load-translation.test.jsclient/utils/log-error.jsclient/utils/needs-verify.jsclient/utils/password-toggle.jsclient/utils/tick.jsclient/utils/utils.test.jsclient/utils/with-route-props.jsconfig/__tests__/add-org.test.jsconfig/add-org.jsconfig/jest.config.jsconfig/setupTests.jsconfig/webpack.config.jspackage.jsonserver/app.jsserver/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
validateTokenoperation incomponentDidMount.
95-95: LGTM!Using
toast.successfor a successful phone number change submission is semantically appropriate and provides clearer visual feedback to the user.
139-139: LGTM!Adding the
aria-labelimproves 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
containerfrom the render result follows RTL best practices.Also applies to: 54-55, 68-69
77-83: Correct use of RTL query variants!Properly uses
queryByRolefor negative assertions (line 78) andgetByTextfor 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 noclient/test-utils/directory in the repository, andrenderWithProviders.jsandcreateMockStore.jsare 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
createMockStoreandrenderWithProvidersfunctions, 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
getConfigmock is correctly placed before component import, and the returned config structure matches the component's expectations.
66-97: LGTM! Well-structured test helpers.The
createMockStoreandrenderWithProvidersutilities 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 theactivefield properly nested inside thedataobject, which matches how the component accessesresponse.data.active.
135-254: LGTM! Solid async test patterns.The test suite properly manages axios mocks between tests and uses
waitForcorrectly 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
postCallCountpattern (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 tosetUserData.
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
mockImplementationOncefor 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.mockcalls.
288-333: LGTM!Good coverage for lazy-loaded
PhoneInputcomponent and its fallback behavior. The use ofwaitForfor async component loading is appropriate.
335-401: LGTM!Error handling tests are well-structured with proper use of
waitForfor 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
createTestPropshelper 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
waitForfor async operations- Queries DOM via
screenand 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
keyupevent (notkeyDown) 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
toHaveStylematcher 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 forget-configproperly mirrors the expected component configuration.
52-83: LGTM: Well-structured test helpers.The
createMockStoreandrenderWithProvidershelpers 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 (getByRolewith name patterns)
✅ Appropriate async handling (waitFor,tick())
✅ Comprehensive coverage of payment status scenarios
✅ Defensive cleanup patterns for React 18 compatibilityThe migration successfully maintains test coverage while adopting RTL patterns that better reflect user interactions and accessibility.
273-281: The explicitmustLogin: undefinedis intentional and correctly tested.The component code (line 65 of payment-status.js) explicitly sets
mustLogin: settings.payment_requires_internet ? true : undefined. Whenpayment_requires_internetis false, the ternary operator evaluates toundefined. The test correctly verifies this behavior—it's not testing a hypothetical scenario, but the actual component logic. IncludingmustLogin: undefinedin 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-labelto the form element improves screen reader support. The form structure is clean with properonSubmithandler.client/app.js (1)
49-61: React 18 migration implemented correctly.The
createRootAPI is used properly, and the provider hierarchy (HelmetProvider → CookiesProvider → Redux Provider) is well-structured. This correctly replaces the legacyReactDOM.renderpattern.client/components/logout/logout.test.js (2)
36-46: Clean RTL migration with proper router context.The test correctly wraps the component in
MemoryRouterand 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
getByRolewith a name matcher is the preferred RTL approach for finding elements. The test properly verifies thatsetUserDatais called with the correctmustLogin: trueflag.client/components/header/header.test.js (3)
6-30: Pre-import mocks correctly structured.The
jest.mockcalls 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.querySelectorAllis appropriate here for verifying specific CSS classes. The eslint-disable comments acknowledge this intentional deviation.
372-388: Good keyboard interaction coverage.Testing
keyUpevents 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-designedrenderWithRouterhelper.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-asyncmanages 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: ConsistentrenderWithProvidershelper 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. Thetick()+waitForpattern 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
hidePasswordstate. Usingdata-testidfor toggle icons is appropriate here.client/components/registration/registration.js (2)
379-379: Good addition ofdata-testidfor form testing.Adding
data-testid="registration-form"enables reliable form selection in RTL tests, complementing the existingidattribute.
674-678: Helpfuldata-testidfor conditional section testing.Adding
data-testid="billing-info"to the billing container enables RTL tests to verify the section renders only whendoesPlanRequireInvoice()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 betweenrenderWithProvidersandmountComponentserves 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
waitForand 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
waitForfor 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
mockConfigincludes all necessary configuration fields for comprehensive test coverage. Thejest.mockcalls properly isolate dependencies.
148-174: Clean helper implementation for rendering tests.The
renderWithProviderhelper 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_meis unchecked, the auth token is stored insessionStoragerather thanlocalStorage, andrememberMeis set to"false"inlocalStorage.
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_realmsis enabled. The approach of mockingform.submitto verify the call is appropriate for this scenario.client/components/password-change/password-change.js (2)
38-38: Mounting guard initialized.The
componentIsMountedflag 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-configmock provides realistic test data for password field configuration.
64-95: Excellent test utilities for RTL migration.The
createMockStoreandrenderWithProvidershelpers 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_expiredis 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
rerenderusage has been properly addressed by splitting into separate test cases.
client/components/mobile-phone-change/mobile-phone-change.test.js
Outdated
Show resolved
Hide resolved
There was a problem hiding this 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 anAbortErrorthat will be caught here. The current code attempts to accesserror.response.datawhich will be undefined for abort errors, causing a runtime error. Additionally,setStatewill 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
emailInputqueried at line 242 may no longer be in the DOM when line 255 executes. The assertionexpect(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: UsemockResolvedValue(false)for consistency with async function mock.Line 212 uses
mockReturnValue(false)while other tests (lines 148, 169, 191, etc.) correctly usemockResolvedValue. SincevalidateTokenis async, usemockResolvedValue(false)for semantic correctness.🔎 Proposed fix
- validateToken.mockReturnValue(false); + validateToken.mockResolvedValue(false);client/components/login/login.test.js (1)
281-297: Typo:lastConsoleoutputshould belastConsoleOutput.The variable name has inconsistent casing - should be
lastConsoleOutputwith 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:
socialLinkscould usePropTypes.arrayOf(PropTypes.shape({...}))to define the link object structurepreHtmlandafterHtmlcould 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-enablecomment on line 21 appears without a correspondingeslint-disable. Sincejest.mockcalls 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
componentIsMountedflag prevents the "setState on unmounted component" warning, the React team recommends usingAbortControlleror 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 inrenderWithProviders.The helper works well but doesn't forward options like
containerorbaseElementfrom 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.errormock captures all console errors during tests. While this is useful for verifying expected warnings (like Reactact()warnings), it could also silently suppress unexpected errors that should fail the test. Consider adding an assertion inafterEachto verifyconsole.errorwas 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 concisemockResolvedValueOnce(...). For consistency and simplicity, consider usingmockResolvedValueOncethroughout.🔎 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 refactoringlogin_page_linktologinPageLinkfor consistency, but this requires updating the configuration layer.The prop
login_page_linkis defined in configuration files (internals/config/default.yml,internals/generators/organization/config.yml.hbs) and used in bothpassword-confirmandpassword-resetcomponents. 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
defaultPropstoundefinedis 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
componentIsMountedguard is properly set up in constructor/mount/unmount. However, several async callbacks still perform unguarded state updates:
handleSubmitsuccess callback (lines 114-123)handleSubmiterror callback (lines 131-137)createPhoneTokencallbacks (lines 166-188)resendPhoneTokensetState (lines 227-230)While the
componentDidMountflow 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
PasswordChangecomponent usesLoadingContext(line 43 in the component callsthis.context.setLoading). Without the provider, loading state changes are silently ignored. Consider wrapping withLoadingContext.Providerlike inpayment-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, theMobilePhoneVerificationcomponent usesLoadingContext. 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_phoneNumbermixes 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_formuses an underscore beforeform, which is inconsistent with full camelCase (would bephoneNumberChangeForm). 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_namemixes 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_formfollows the same pattern asphoneNumberChange_formin 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, considermobilePhoneVerificationFormfor full consistency.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (7)
client/components/404/__snapshots__/404.test.js.snapis excluded by!**/*.snapclient/components/footer/__snapshots__/footer.test.js.snapis excluded by!**/*.snapclient/components/header/__snapshots__/header.test.js.snapis excluded by!**/*.snapclient/components/login/__snapshots__/login.test.js.snapis excluded by!**/*.snapclient/components/mobile-phone-change/__snapshots__/mobile-phone-change.test.js.snapis excluded by!**/*.snapclient/components/organization-wrapper/__snapshots__/organization-wrapper.test.js.snapis excluded by!**/*.snapclient/components/registration/__snapshots__/registration.test.js.snapis excluded by!**/*.snap
📒 Files selected for processing (92)
.eslintrc.jsonCHANGES.mdbrowser-test/create-mobile-configuration.jsbrowser-test/initialize_data.pybrowser-test/mobile-phone-change.test.jsclient/actions/actions.test.jsclient/actions/logout.jsclient/app.jsclient/components/404/404.jsclient/components/404/index.cssclient/components/contact-box/contact.jsclient/components/contact-box/contact.test.jsclient/components/footer/footer.jsclient/components/footer/footer.test.jsclient/components/header/header.jsclient/components/header/header.test.jsclient/components/login/index.cssclient/components/login/index.jsclient/components/login/login.jsclient/components/login/login.test.jsclient/components/logout/logout.jsclient/components/logout/logout.test.jsclient/components/mobile-phone-change/index.jsclient/components/mobile-phone-change/mobile-phone-change.jsclient/components/mobile-phone-change/mobile-phone-change.test.jsclient/components/mobile-phone-verification/index.jsclient/components/mobile-phone-verification/mobile-phone-verification.jsclient/components/mobile-phone-verification/mobile-phone-verification.test.jsclient/components/modal/index.jsclient/components/modal/modal.jsclient/components/modal/modal.test.jsclient/components/organization-wrapper/organization-wrapper.jsclient/components/organization-wrapper/organization-wrapper.test.jsclient/components/password-change/password-change.jsclient/components/password-change/password-change.test.jsclient/components/password-confirm/password-confirm.jsclient/components/password-confirm/password-confirm.test.jsclient/components/password-reset/password-reset.jsclient/components/password-reset/password-reset.test.jsclient/components/payment-process/payment-process.jsclient/components/payment-process/payment-process.test.jsclient/components/payment-status/payment-status.jsclient/components/payment-status/payment-status.test.jsclient/components/registration/index.jsclient/components/registration/registration.jsclient/components/registration/registration.test.jsclient/components/registration/subscriptions.test.jsclient/components/status/status.jsclient/components/status/status.test.jsclient/components/status/status.test.js.enzyme-backupclient/components/status/status.test.js.rtl-backupclient/reducers/organization.jsclient/reducers/reducers.test.jsclient/test-config.jsonclient/test-translation.jsonclient/utils/__mocks__/get-config.jsclient/utils/authenticate.jsclient/utils/get-error-text.jsclient/utils/get-html.jsclient/utils/get-payment-status.jsclient/utils/handle-logout.jsclient/utils/load-translation.test.jsclient/utils/loader.jsclient/utils/modal.jsclient/utils/needs-verify.jsclient/utils/render-additional-info.jsclient/utils/session.jsclient/utils/should-link-be-shown.jsclient/utils/utils.test.jsclient/utils/validate-token.jsconfig/__tests__/add-org.test.jsconfig/add-org.jsconfig/setup.jsdocs/user/intro.rstdocs/user/settings.rstdocs/user/setup.rsti18n/de.poi18n/en.poi18n/es.poi18n/fur.poi18n/it.poi18n/ru.poi18n/sl.pointernals/config/default.ymlinternals/generators/organization/config.yml.hbsserver/controllers/mobile-phone-number-change-controller.jsserver/controllers/mobile-phone-token-controller.jsserver/controllers/obtain-token-controller.jsserver/controllers/registration-controller.jsserver/controllers/validate-token-controller.jsserver/utils/openwisp-urls.jsserver/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
There was a problem hiding this 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
orgSlugproperty thatmapStateToPropsmaps fromstate.organization.configuration.slug. The input state should includeslugand the assertion should verifyorgSlugin 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()fromclient/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), subsequentapp()calls correctly re-render. To enable hot reloading in development, pass a callback toaccept():if (module && module.hot) { module.hot.accept(() => { app(); }); }This ensures changes to
app.jstrigger 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 failureshould show toast on 500 server errorshould show toast on 504 gateway timeoutshould clear errors on successful submissionHowever, 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.cssand.jscontain 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-containeris 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, butcomponentsis already destructured fromdefaultConfigat line 542-543. The fallbackcomponents.header || defaultConfig.components.headeris always the same value sincecomponents === 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
⛔ Files ignored due to path filters (4)
client/components/footer/__snapshots__/footer.test.js.snapis excluded by!**/*.snapclient/components/header/__snapshots__/header.test.js.snapis excluded by!**/*.snapclient/components/login/__snapshots__/login.test.js.snapis excluded by!**/*.snapclient/components/organization-wrapper/__snapshots__/organization-wrapper.test.js.snapis excluded by!**/*.snap
📒 Files selected for processing (19)
README.rstclient/app.jsclient/components/header/header.jsclient/components/login/login.test.jsclient/components/modal/modal.test.jsclient/components/organization-wrapper/organization-wrapper.test.jsclient/test-config.jsonclient/utils/__mocks__/get-config.jsdocs/developer/index.rstdocs/developer/installation.rstdocs/developer/usage.rstdocs/index.rstdocs/user/extra-js.rstdocs/user/handling-radius-errors.rstdocs/user/internet-mode.rstdocs/user/intro.rstdocs/user/settings.rstdocs/user/setup.rstdocs/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:
- 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?
- 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.keyUpcorrectly matches the component'skeyupevent listener, and the setup/teardown prevents test pollution.
245-274: LGTM! Excellent side-effect testing.This test properly verifies the scrollbar hiding behavior:
overflow: hiddenis set when the modal mounts andoverflow: autois restored on unmount. The use ofunmount()andtoHaveStyle()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
createRootfromreact-dom/clientfor React 18 andHelmetProviderfromreact-helmet-async, aligning with the migration objectives.
26-26: LGTM! React Router v6 migration correctly removes history prop.The Router component correctly omits the
historyprop, 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
rootvariable with theif (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:
HelmetProvider(required at top level for react-helmet-async)CookiesProvider- Redux
ProviderAppcomponentThis 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:
mockConfiguses camelCase properties matching the PR's refactoring objectives- Mock functions are set up correctly with proper jest.fn() usage
responseDatacorrectly transformsauthTokentokeyto match API response shapecreateTestPropsprovides sensible defaults for all required props
146-172: LGTM! Clean test helper implementation.The
renderWithProviderhelper 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
beforeEachandafterEachhooks 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 (
lastConsoleOutuput→lastConsoleOutput) appears to be already corrected throughout the file.
311-341: LGTM! Comprehensive mounting helper for full integration testing.The
mountComponenthelper 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
findByPlaceholderTextandwaitForto 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
rememberMesetting.
990-1045: LGTM! Thorough radius realms form testing.Good pattern of unmounting and remounting to test different configurations. The use of
toContainHTMLfor hidden inputs (Lines 1014-1018) is pragmatic since hidden inputs lack accessible roles. The mock ofform.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
renderWithRouterhelper 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
componentDidUpdatelifecycle test now correctly:
- Wraps rerender calls with all required providers (HelmetProvider, Provider, MemoryRouter)
- Creates separate mocked stores for each rerender scenario
- Uses the corrected variable name
lastConsoleOutput- Reuses the
consoleErrorSpyfrombeforeEachinstead 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 misspellednot-foud-404remain in the codebase.
74-77: No action needed.
passwordless_authToken_nameis 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 likedefault_languageandpasswordless_authToken_nameare backend API fields (snake_case), while keys likemobilePhoneVerificationandsubscriptionsare internal configuration options (camelCase).Likely an incorrect or invalid review comment.
client/components/organization-wrapper/organization-wrapper.test.js
Outdated
Show resolved
Hide resolved
227b756 to
e01a08f
Compare
There was a problem hiding this 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
componentIsMountedguard protects only the firstsetState(line 41), butsetUserDatacalls at lines 51-56 and 62-65 execute outside the guard. If the component unmounts aftervalidateTokenresolves 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
componentIsMountedflag correctly prevents state updates after unmount in thecomponentDidMountflow. However, other async methods (handleSubmit,createPhoneToken,resendPhoneToken) still callsetStatewithout checking the mounted state, which could cause the same memory leak warnings.Consider either:
- Adding the same
this.componentIsMountedcheck before allsetStatecalls in async callbacks, or- Using
AbortControllerto cancel pending requests incomponentWillUnmount(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 beforewaitFor()are necessary.The pattern of calling
await tick()followed byawait waitFor()appears on lines 299, 329, 359, and 389. SincewaitFor()already handles async updates and promise resolution, thetick()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 useaxios.mockResolvedValueOnce(...)(line 367) andaxios.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/firstcomment has no correspondingeslint-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/firstcomment has no correspondingeslint-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 (includesmockClear()functionality), makingaxios.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
userAutoLoginelsewhere (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: UsemockResolvedValue(false)for async function.Since
validateTokenis an async function (awaited in the component), usemockResolvedValue(false)instead ofmockReturnValue(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.
BrowserRouterdoesn't acceptlocationandnavigatorprops—those are for the low-levelRoutercomponent. ThehistoryMockwon't actually control navigation here. Consider usingMemoryRouterwithinitialEntriesfor 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
setUserDatais called withmustLogin: undefinedwhenpayment_requires_internetis false and status is draft. Explicitly setting a property toundefinedis 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
⛔ Files ignored due to path filters (6)
client/components/login/__snapshots__/login.test.js.snapis excluded by!**/*.snapclient/components/mobile-phone-change/__snapshots__/mobile-phone-change.test.js.snapis excluded by!**/*.snapclient/components/mobile-phone-verification/__snapshots__/mobile-phone-verification.test.js.snapis excluded by!**/*.snapclient/components/password-change/__snapshots__/password-change.test.js.snapis excluded by!**/*.snapclient/components/password-reset/__snapshots__/password-reset.test.js.snapis excluded by!**/*.snapclient/components/registration/__snapshots__/registration.test.js.snapis excluded by!**/*.snap
📒 Files selected for processing (38)
.eslintrc.jsonbrowser-test/create-mobile-configuration.jsbrowser-test/mobile-phone-change.test.jsclient/components/contact-box/contact.jsclient/components/contact-box/contact.test.jsclient/components/footer/footer.jsclient/components/footer/footer.test.jsclient/components/header/header.test.jsclient/components/login/login.jsclient/components/login/login.test.jsclient/components/logout/logout.test.jsclient/components/mobile-phone-change/mobile-phone-change.jsclient/components/mobile-phone-change/mobile-phone-change.test.jsclient/components/mobile-phone-verification/mobile-phone-verification.jsclient/components/mobile-phone-verification/mobile-phone-verification.test.jsclient/components/modal/modal.test.jsclient/components/organization-wrapper/organization-wrapper.jsclient/components/organization-wrapper/organization-wrapper.test.jsclient/components/password-change/password-change.jsclient/components/password-change/password-change.test.jsclient/components/password-confirm/password-confirm.jsclient/components/password-confirm/password-confirm.test.jsclient/components/password-reset/password-reset.test.jsclient/components/payment-process/payment-process.jsclient/components/payment-process/payment-process.test.jsclient/components/payment-status/payment-status.jsclient/components/payment-status/payment-status.test.jsclient/components/registration/registration.jsclient/components/registration/registration.test.jsclient/components/registration/subscriptions.test.jsclient/components/status/status.jsclient/components/status/status.test.jsclient/test-config.jsonclient/utils/needs-verify.jsclient/utils/should-link-be-shown.jsclient/utils/utils.test.jsconfig/__tests__/add-org.test.jsconfig/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
successToastSelectorfrom 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
getConfigmock 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
mockConfigobject 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 ofgetTranslationStringensures translations are properly tested.
207-225: LGTM! Proper DOM-driven interaction testing.The test correctly uses
fireEvent.changeand 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
hidePasswordstate and toggle together (lines 420-441)- Either toggle button works correctly (lines 444-464)
The use of
getAllByTestId("password-toggle-icon")andwaitFor()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 = LoadingContextand callssetLoading(true/false)in the form submission handler (lines 64, 84, 94). However,renderWithProvidersdoes not wrap the component with a LoadingContext provider. Additionally,app.jsalso 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
defaultPropsadditions correctly specifyundefinedas defaults for the optional props that lack.isRequiredinpropTypes.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 elementsscreen.getByRole/getByTextfor 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 onshouldLinkBeShown.client/components/registration/registration.js (1)
378-378: LGTM on accessibility enhancements!The additions of
aria-labelattributes,data-testidhooks, andariaLabel="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
redirectToPaymentUrlto a static method is appropriate since it doesn't access instance state—it only callswindow.location.assign(). The call site update toPaymentProcess.redirectToPaymentUrl(...)is correct.Also applies to: 111-111
145-148: LGTM!The
defaultPropscorrectly match the optional props inpropTypesthat 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
getLoadingandgetLoadingContextValuemethods 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 mainrendermethod 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.mockbefore imports and provides a comprehensive default config structure that mirrors the actual configuration shape.
92-130: LGTM! Comprehensive test rendering helper.The
renderWithRouterhelper 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-404and matches the test's expectations. The misspelled#not-foud-404is 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
loadingContextValuefrom 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
mountComponenthelper correctly usesMemoryRouterwithinitialEntriesinstead of the previousBrowserRouterwith 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
renderWithProvidershelper correctly wraps the component withLoadingContext.Provider, addressing the past review feedback about missing context.
98-118: LGTM! Clean window event mocking helper.The
mockMessageEventshelper 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.mockwith__esModule: truepattern 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-nonetodisplay-flex) rather than internal component state.client/components/login/login.test.js (2)
22-82: LGTM! Comprehensive mock configuration.The
mockConfigobject 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
renderWithProviderhelper is appropriately simple for tests that don't need full routing, usingMemoryRoutercorrectly.client/components/password-reset/password-reset.test.js (3)
51-82: LGTM! Well-structured test utilities.The
createMockStoreandrenderWithProvidershelpers 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
waitForfor 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
createMockStoreandrenderWithProvidersutilities 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
onClickhandler on the Link element (line 170) that's properly bound in the constructor. WhenfireEvent.click()is triggered in the tests, it correctly invokespaymentProceedHandler, which conditionally callssetUserDatabased on thepayment_requires_internetflag. The test expectations match the actual implementation:
- When
payment_requires_internetis false,setUserDatais not called- When
payment_requires_internetis true,setUserDatais called withproceedToPayment: true
There was a problem hiding this 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:
Line 101: Accessing
error.response.datawithout checking iferror.responseexists. When a request is aborted viaAbortController, axios throws an error without aresponseproperty, causing aTypeError.Lines 108-114: The
setStatecall 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
componentIsMountedflag correctly preventssetStateafter unmount (lines 40-42), but the subsequentsetUserDatacalls (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 bothbeforeEachandafterEachis redundant—clearing inbeforeEachalone 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
afterEachat lines 129-131 appears redundant since the nested describe blocks (<PasswordConfirm /> interactionsstarting at line 193) have their own cleanup inafterEachat 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
mockConfigregardless of arguments, butcreateTestProps(line 74) defaultsconfigNameto"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 inafterEachare redundant. Module-leveljest.mock()is hoisted and not affected byrestoreAllMocks(), 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:
- Removing this test as redundant, or
- 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 deprecatedkeyCodetokeyproperty.The component registers a
keyupevent listener (despite the method namehandleKeyDown) that checks the deprecatedkeyCodeproperty. Both the component and tests should be modernized to use the standardkeyproperty 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
beforeEachcallsjest.clearAllMocks(),axios.mockClear(), andaxios.mockReset()sequentially. Sincejest.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
itblocks 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 matchingeslint-disabledirective 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
loadingstate 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
setLoadingandgetLoadingare 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-disableabove.🧹 Proposed fix
jest.mock("../../utils/check-internal-links"); -/* eslint-enable import/first */
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (6)
client/components/login/__snapshots__/login.test.js.snapis excluded by!**/*.snapclient/components/mobile-phone-change/__snapshots__/mobile-phone-change.test.js.snapis excluded by!**/*.snapclient/components/mobile-phone-verification/__snapshots__/mobile-phone-verification.test.js.snapis excluded by!**/*.snapclient/components/password-change/__snapshots__/password-change.test.js.snapis excluded by!**/*.snapclient/components/password-reset/__snapshots__/password-reset.test.js.snapis excluded by!**/*.snapclient/components/registration/__snapshots__/registration.test.js.snapis excluded by!**/*.snap
📒 Files selected for processing (38)
.eslintrc.jsonbrowser-test/create-mobile-configuration.jsbrowser-test/mobile-phone-change.test.jsclient/components/contact-box/contact.jsclient/components/contact-box/contact.test.jsclient/components/footer/footer.jsclient/components/footer/footer.test.jsclient/components/header/header.test.jsclient/components/login/login.jsclient/components/login/login.test.jsclient/components/logout/logout.test.jsclient/components/mobile-phone-change/mobile-phone-change.jsclient/components/mobile-phone-change/mobile-phone-change.test.jsclient/components/mobile-phone-verification/mobile-phone-verification.jsclient/components/mobile-phone-verification/mobile-phone-verification.test.jsclient/components/modal/modal.test.jsclient/components/organization-wrapper/organization-wrapper.jsclient/components/organization-wrapper/organization-wrapper.test.jsclient/components/password-change/password-change.jsclient/components/password-change/password-change.test.jsclient/components/password-confirm/password-confirm.jsclient/components/password-confirm/password-confirm.test.jsclient/components/password-reset/password-reset.test.jsclient/components/payment-process/payment-process.jsclient/components/payment-process/payment-process.test.jsclient/components/payment-status/payment-status.jsclient/components/payment-status/payment-status.test.jsclient/components/registration/registration.jsclient/components/registration/registration.test.jsclient/components/registration/subscriptions.test.jsclient/components/status/status.jsclient/components/status/status.test.jsclient/test-config.jsonclient/utils/needs-verify.jsclient/utils/should-link-be-shown.jsclient/utils/utils.test.jsconfig/__tests__/add-org.test.jsconfig/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
defaultPropsimproves 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(), andawait 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_internettrue/false paths, ensuringproceedToPaymentis set correctly. The explicit assertions onsetUserDatacalls 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. TheMemoryRouterimport is correctly added since theLogoutcomponent usesLinkfrom react-router-dom.
38-44: LGTM: Proper RTL snapshot test.The test correctly wraps the component with
MemoryRouterand usescontainer.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.getByRolewith an accessible name to find the button, which follows RTL best practices. The assertion correctly verifies thatsetUserDatais called withmustLogin: true, matching the component's behavior when!isAuthenticated.
97-104: LGTM: Lifecycle hook verification.The test correctly verifies that
setTitleis called duringcomponentDidMountwith 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-labelattribute improves accessibility and enables the test suite to query the form usinggetByRole("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, andfireEventaligns with the PR objectives. Thetickutility import for handling async operations is appropriate for React 18's concurrent rendering.
14-56: LGTM: Comprehensive mock configuration.The
mockConfigobject andget-configmock 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
renderWithProvidershelper correctly wraps the component with ReduxProviderandMemoryRouter, 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 offireEvent.changeto 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 bywaitFor()for async assertions is correct for React 18. The spy setup fortoastandconsole.errorenables 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
hidePasswordstate) using either toggle button. The use ofgetAllByTestIdandwaitForfor attribute changes is appropriate.client/components/registration/registration.js (1)
378-378: LGTM! Excellent accessibility and testability improvements.The additions of
data-testidattributes (for React Testing Library) andaria-labelattributes (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 callingsetState()(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
setStatecall after the asyncvalidateTokenoperation.
97-97: LGTM!Changing the toast notification from
infotosuccessbetter reflects the successful completion of the token send operation.
141-141: LGTM!The
aria-labeladdition 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
LoadingContextexports.
74-133: LGTM: Helper functions correctly implement test utilities.The
renderWithProvidershelper properly uses the actualloadingContextValuefrom 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
mountComponenthelper now properly usesMemoryRouterwithinitialEntries(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
waitForfor async assertions (e.g., lines 219-225, 271-275, 339-345) and handle React 18's concurrent rendering. Mock cleanup inafterEachensures 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
beforeEachcleanup andcreateTestPropsfactory 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 ofgetConfigbecausecreateTestPropsis 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:
The component doesn't use
getConfigdirectly.MobilePhoneVerificationreceives pre-computed configuration via props (e.g.,mobile_phone_verification,settings), not by callinggetConfig.
createTestPropsis a test utility, not production code. It only exists in the test file to build mock props for component testing.The async nature of
getConfigis tested elsewhere. The actual async handling occurs inclient/actions/set-organization.js(line 16:const orgConfig = await getConfig(slug);), which is tested separately.Synchronous mocks for test utilities are standard practice. Other test files in the codebase (e.g.,
password-change.test.js) similarly use the synchronousgetConfigfrom__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-configmock 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:
createTestPropsprovides a clean prop factorycreateMockStoresupplies necessary Redux state (organization config, language)renderWithProviderscorrectly 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_expiredstateThe use of
queryByTextto assert the cancel button's absence is the correct RTL pattern.
139-150: LGTM! Clean handleChange test.The test correctly uses RTL queries and
fireEventto 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:
validateTokenis called with correct arguments- Components redirect (don't render form) for SAML and social_login methods
- Use of
queryByRoleto assert form absence is the correct RTL patternThe 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
renderandscreenfrom RTL- Uses
getByAltText/queryByAltTextfor 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
renderWithRouterhelper 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 usesnot-found-404for both theidattribute (line 20) anddata-testidattribute (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-asyncinstead ofreact-helmetis 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, andrenderLogoutRouteconsolidates 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 withwithin().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-none→display-flex)This ensures the mobile menu remains accessible via keyboard.
380-391: LGTM - Redux dispatch mapping test is correctly preserved.The
mapDispatchToPropstest verifies the dispatch behavior independently of component rendering, which is appropriate for testing the Redux integration.
nemesifier
left a comment
There was a problem hiding this 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.
8189240 to
b1734ab
Compare
There was a problem hiding this 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 Englisharia-labelon 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 droparia-labelentirely 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 unusedariaLabelprop or implementaria-labelattribute in the component. TheparentClassNameprop is consumed (line 66), butariaLabelis passed but never destructured or used inPasswordToggleIcon(client/utils/password-toggle.js). The component renders a<div role="button">rather than an actual button, so form submission isn't triggered; however, ifaria-labelis intended, the component must apply it to the toggle element.
401-471: Add missingidto fallback input and movearia-labelintoinputProps. The fallback<input>lacksid="phone-number", breaking the association with the visible<label htmlFor="phone-number">. Additionally,react-phone-input-2forwardsinputPropsto the underlying input element;aria-labelshould be passed viainputPropsrather than as a top-level prop onPhoneInput.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: AddinputIdto Select and replace hardcoded aria-labels with i18n keys. The<label htmlFor="country">won't bind to react-select withoutinputId, breaking label-input association. Additionally, hardcoded English aria-labels ("City", "Street", "Zip Code", "Tax Number") contradict the i18n pattern used for visible labels—usetCITY_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 forauthenticate()call.The test verifies that
setUserDatais not called whenpayment_requires_internetis false, but according to the component code,paymentProceedHandleralways callsauthenticate(true)regardless of this setting. Add an assertion to verifyauthenticatewas 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 forauthenticate()call.The test verifies
setUserDatais called withproceedToPayment: true, butpaymentProceedHandleralso callsauthenticate(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:InfoModalaria 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 assertingdocument.headcontains 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-identicalmockedStoreobjects; a small helper likecreateMockStore({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
⛔ Files ignored due to path filters (1)
yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (3)
client/components/organization-wrapper/organization-wrapper.test.jsclient/components/payment-status/payment-status.test.jsclient/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
createMockStoreandrenderWithProvidersutilities 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.
UsingwaitFor()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, sodefaultConfig = 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.
There was a problem hiding this 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-enablecomment has no correspondingeslint-disablein the visible code.♻️ Suggested fix
-/* eslint-enable import/first */
92-131: Consider extracting mock store creation to reduce duplication.The
mockedStorelogic here is duplicated in thererendercalls (lines 283-292, 312-321, 394-412) and inmountComponent(lines 545-571). Extracting this into acreateMockedStore(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
createMockedStorein the rerender blocks andmountComponent.
491-499: Consider verifying redirect behavior more explicitly.The test asserts that
app-containerrenders but doesn't verify that the user was actually redirected to the login page. Consider usingscreen.getByTextor 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-containerrenders. Consider asserting that verification-page-specific content appears.
545-582: Duplication:mountComponentreimplementsrenderWithRouterlogic.This helper duplicates the mock store creation and rendering logic from
renderWithRouter(lines 92-131). Consider either reusingrenderWithRouterdirectly 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 customtick()
tick()is easy to misuse under React 18 scheduling and can create timing-dependent tests; you already usewaitForelsewhere, so it’s consistent to wait on the actual UI condition(s) or a key side-effect (e.g.,validateTokencalled).
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (3)
client/components/organization-wrapper/organization-wrapper.test.jsclient/components/payment-status/payment-status.test.jsclient/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
waitForfor async state changes, appropriately distinguish betweengetByTestId(expects element) andqueryByTestId(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
componentDidUpdatebehavior by usingrerenderand 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 configurationThe 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"inpackage.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.
There was a problem hiding this 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
globalwith underscored names is unconventional. Jest'sjest.spyOn(...).mockImplementation(...)withmockRestore()inafterEachhandles 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 onjest.spyOnwithout referencing global originals, and ensuremockRestore()is called inafterEach(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 inact()or usingwaitFormore 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
validateTokenwas called but doesn't assert the expected redirect outcome. The component should navigate to/default/statuswhen user is already verified - consider adding an assertion forprops.navigatebeing 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
validateTokenwas 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.
createMockStorereturns 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 consolidatingrenderWithRouterandmountComponenthelpers.Both helpers wrap the component with
HelmetProvider,Provider, andMemoryRouter. The main difference ismountComponentacceptsinitialEntriesexplicitly. 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/jsarrays 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:lastConsoleOutputvariable is set but only used in one test.The
consoleErrorSpyandlastConsoleOutputsetup inbeforeEachis only used in thecomponentDidUpdatetest (line 333-335). Consider moving this setup into that specific test to keep test setup minimal.
258-336:componentDidUpdatetest 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
📒 Files selected for processing (2)
client/components/organization-wrapper/organization-wrapper.test.jsclient/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 forpayment_requires_internetflag behavior.The test properly validates both the button href and the authenticate/setUserData behavior when
payment_requires_internetis 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
loadTranslationis called again. The async handling withwaitForis 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-testidattributes (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.
client/components/organization-wrapper/organization-wrapper.test.js
Outdated
Show resolved
Hide resolved
There was a problem hiding this 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:createTestPropsreuses a sharedheaderobject that tests mutate.
Several tests doprops.header.links = ..., butheader: defaultConfig.components.headeris 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_tokenlook 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-enabledirective has no correspondingeslint-disablecomment, 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 withwaitFor()at multiple points. SincewaitFor()already polls until assertions pass or timeout occurs, the additionaltick()calls may be redundant unless there's a specific timing requirement thatwaitFordoesn't handle.Consider simplifying by relying solely on
waitForwith appropriate timeout configurations if thetick()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 sincejest.clearAllMocks()on line 131 already clears all mocks, includingvalidateToken.♻️ 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
componentDidMounteffects, 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
getConfigmock is re-implemented inafterEachafter callingjest.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 ofjest.restoreAllMocks()to avoid needing this re-setup.
219-219: Use mockResolvedValue for consistency with async function.
validateTokenis an async function (awaited in the component), and other tests mock it withmockResolvedValue(true). For consistency, usemockResolvedValue(false)here instead ofmockReturnValue(false).While
mockReturnValue(false)works (the value gets auto-wrapped in a Promise byawait), usingmockResolvedValueis 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.addEventListenerinline (lines 383-388), while other postMessage tests use themockMessageEventshelper (see lines 274, 314, 348). Using the helper would:
- Ensure consistent mocking patterns across tests
- Provide proper cleanup via the
restore()method- Mock both
addEventListenerandremoveEventListener♻️ 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 bothbeforeEach(line 129) andafterEach(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()beforewaitFor().The pattern of calling
tick()immediately beforewaitFor()appears on lines 304, 334, 364, and 394. Whiletick()ensures promises resolve,waitFor()should inherently wait for async updates in React 18. Consider testing whethertick()is still necessary, aswaitFor()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:
- Verifies initial hidden state
- Toggles visibility using the first button
- Toggles visibility using the second button
- 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
mockResolvedValueOnceandmockRejectedValueOncemethods 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"inclient/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;logErroris 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: MakevalidateTokenassertion 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. Preferexpect(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 overwritedocument.addEventListener/removeEventListener; rely on spies + restore.
Assigningdocument.addEventListener = originalAddEventListeneris brittle and can fight withjest.spyOn. Preferjest.restoreAllMocks()inafterEachand avoid manual reassignment.Also applies to: 341-389
213-218: Preferkey: "Escape"overkeyCodefor the Esc simulation.
keyCodeis 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:renderWithProviderhelper is a good direction; consider reusing it everywhere.
You still have a separatemountComponentlater 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 viafinallypattern orrestoreAllMocks.
Overwritingconsole.erroris fine, but preferjest.spyOn(console, "error")+mockRestore()to avoid missing edge cases if a test throws beforeafterEach.client/components/header/header.test.js (1)
74-83: Consider extracting arenderHeader()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 nowgetState()only returnsorganization.configuration(and sometimeslanguages), 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 assertingdocument.heador 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 afterawait waitFor(...app-container...).Also applies to: 468-482, 501-510, 540-550, 632-653
238-352:console.errorassertions 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
⛔ Files ignored due to path filters (1)
client/components/login/__snapshots__/login.test.js.snapis excluded by!**/*.snap
📒 Files selected for processing (18)
client/components/404/404.test.jsclient/components/header/header.test.jsclient/components/login/login.test.jsclient/components/logout/logout.test.jsclient/components/mobile-phone-change/mobile-phone-change.test.jsclient/components/mobile-phone-verification/mobile-phone-verification.test.jsclient/components/modal/modal.test.jsclient/components/organization-wrapper/organization-wrapper.test.jsclient/components/password-change/password-change.test.jsclient/components/password-confirm/password-confirm.test.jsclient/components/password-reset/password-reset.test.jsclient/components/payment-process/payment-process.test.jsclient/components/payment-status/payment-status.test.jsclient/components/registration/registration.test.jsclient/components/registration/subscriptions.test.jsclient/components/status/status.jsclient/components/status/status.test.jsclient/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
createMockStoreandrenderWithProvidershelpers 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:
createTestPropsprovides sensible defaults with override capabilitycreateMockStoresupplies minimal Redux state structurerenderWithProvidersproperly 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
validateTokencontrolled 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
renderWithProvidershelper 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
waitForandact- 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
tickhelper for async operations.
52-56: LGTM! Proper mock setup.The mock configuration for
axiosandget-configfollows 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.changeand verifies the DOM state directly.
406-412: LGTM! Title setting verification.The test properly verifies that the
setTitlefunction 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
futureflags (v7_startTransitionandv7_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
afterEachhook.
19-38: Verify that the get-config mock behavior aligns with test expectations.The mock always returns the same configuration regardless of the
configNameargument. On line 46,createTestPropscallsgetConfig(configName)with a default of"test-org-2", but the mock doesn't differentiate based on this parameter. If tests pass differentconfigNamevalues 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-domis solid and aligns with the React 18 upgrade/testing direction.client/components/mobile-phone-change/mobile-phone-change.test.js (1)
126-141:renderWithProvidershelper looks good.
Nice consolidation of Provider + LoadingContext + Router for consistent RTL rendering.Also applies to: 145-147
nemesifier
left a comment
There was a problem hiding this 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.
nemesifier
left a comment
There was a problem hiding this 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.
1523e0b to
49e5209
Compare
There was a problem hiding this 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 sharedsuccessToastSelector(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
⛔ Files ignored due to path filters (14)
client/components/404/__snapshots__/404.test.js.snapis excluded by!**/*.snapclient/components/contact-box/__snapshots__/contact.test.js.snapis excluded by!**/*.snapclient/components/footer/__snapshots__/footer.test.js.snapis excluded by!**/*.snapclient/components/header/__snapshots__/header.test.js.snapis excluded by!**/*.snapclient/components/login/__snapshots__/login.test.js.snapis excluded by!**/*.snapclient/components/mobile-phone-change/__snapshots__/mobile-phone-change.test.js.snapis excluded by!**/*.snapclient/components/mobile-phone-verification/__snapshots__/mobile-phone-verification.test.js.snapis excluded by!**/*.snapclient/components/organization-wrapper/__snapshots__/organization-wrapper.test.js.snapis excluded by!**/*.snapclient/components/password-change/__snapshots__/password-change.test.js.snapis excluded by!**/*.snapclient/components/password-confirm/__snapshots__/password-confirm.test.js.snapis excluded by!**/*.snapclient/components/password-reset/__snapshots__/password-reset.test.js.snapis excluded by!**/*.snapclient/components/registration/__snapshots__/registration.test.js.snapis excluded by!**/*.snapclient/components/status/__snapshots__/status.test.js.snapis excluded by!**/*.snapyarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (2)
browser-test/login.test.jspackage.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.
There was a problem hiding this 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
📒 Files selected for processing (9)
browser-test/login.test.jsbrowser-test/mobile-phone-change.test.jsbrowser-test/mobile-verfication.test.jsbrowser-test/password-change.test.jsbrowser-test/password-confirm.test.jsbrowser-test/password-expired.test.jsbrowser-test/password-reset.test.jsbrowser-test/registration.test.jsbrowser-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
waitForToastTextutility 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
waitForToastTextutility is properly used with a single declaration.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
06c71d9 to
2442418
Compare
Checklist
Reference to Existing Issue
Closes #870.
Description of Changes
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
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
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)
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
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