Skip to content

CONSOLE-5176: Refactor RTL Tests from fireEvent to userEvent#16294

Open
cajieh wants to merge 1 commit intoopenshift:mainfrom
cajieh:refactor-rtl-ests-fire-event-user-event
Open

CONSOLE-5176: Refactor RTL Tests from fireEvent to userEvent#16294
cajieh wants to merge 1 commit intoopenshift:mainfrom
cajieh:refactor-rtl-ests-fire-event-user-event

Conversation

@cajieh
Copy link
Copy Markdown
Contributor

@cajieh cajieh commented Apr 16, 2026

Summary by CodeRabbit

  • Tests
    • Updated test suite infrastructure across multiple packages to use modern async/await patterns for user interaction simulation, improving test reliability and maintainability.

@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Apr 16, 2026
@openshift-ci-robot
Copy link
Copy Markdown
Contributor

openshift-ci-robot commented Apr 16, 2026

@cajieh: This pull request references CONSOLE-5176 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "5.0.0" version, but no target version was set.

Details

In response to this:

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci openshift-ci bot requested review from rhamilto and sg00dwin April 16, 2026 12:35
@openshift-ci openshift-ci bot added component/core Related to console core functionality component/dev-console Related to dev-console component/knative Related to knative-plugin component/olm Related to OLM component/shared Related to console-shared component/topology Related to topology labels Apr 16, 2026
@cajieh cajieh force-pushed the refactor-rtl-ests-fire-event-user-event branch from c8f15f8 to 2b14b5d Compare April 16, 2026 12:35
@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci bot commented Apr 16, 2026

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: cajieh

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Apr 16, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 16, 2026

📝 Walkthrough

Walkthrough

This pull request systematically migrates test suites across the OpenShift Console frontend from fireEvent (React Testing Library) to @testing-library/user-event for simulating user interactions. Affected test files convert synchronous test cases to async, initialize userEvent instances via userEvent.setup(), and replace direct event dispatching (e.g., fireEvent.click, fireEvent.change) with awaited user actions (user.click, user.type, user.clear, user.keyboard). Several test files also undergo minor refactoring—removing unused mocks, simplifying assertions, changing element selectors from role-based to test-ID-based queries, or consolidating validation checks—while preserving existing test coverage and assertions. No changes affect exported or public entity declarations.

🚥 Pre-merge checks | ✅ 10
✅ Passed checks (10 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: refactoring RTL tests from fireEvent to userEvent across the codebase.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Stable And Deterministic Test Names ✅ Passed This check is not applicable to the provided pull request. The custom check validates Ginkgo test names (a Go BDD testing framework), but this PR exclusively refactors React Testing Library tests in JavaScript/TypeScript files (.spec.tsx, .spec.ts). All changes are Jest tests using standard describe() and it() syntax, which are outside the scope of Ginkgo test name stability rules.
Test Structure And Quality ✅ Passed Custom check designed for Ginkgo test code is not applicable to this JavaScript/TypeScript React component test refactoring PR.
Microshift Test Compatibility ✅ Passed The MicroShift Test Compatibility check validates Ginkgo e2e tests in Go, which is not applicable to this React Testing Library refactoring of TypeScript/JavaScript frontend unit tests.
Single Node Openshift (Sno) Test Compatibility ✅ Passed This custom check is not applicable to the pull request. The PR exclusively refactors React component unit tests using Jest and React Testing Library, migrating from fireEvent to userEvent. No Go test files, Ginkgo test patterns, or infrastructure topology concerns are present.
Topology-Aware Scheduling Compatibility ✅ Passed This PR is exclusively a test refactoring migrating from fireEvent to userEvent in React Testing Library tests across 40+ files with no production code changes.
Ote Binary Stdout Contract ✅ Passed OTE Binary Stdout Contract check is inapplicable; PR exclusively modifies frontend React/TypeScript test files (40+ .spec.tsx files) replacing fireEvent with userEvent, without touching Go code or process-level implementations.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed This PR refactors client-side React component tests migrating from fireEvent to userEvent, with no Ginkgo e2e tests added.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@openshift-ci-robot
Copy link
Copy Markdown
Contributor

openshift-ci-robot commented Apr 16, 2026

@cajieh: This pull request references CONSOLE-5176 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "5.0.0" version, but no target version was set.

Details

In response to this:

Summary by CodeRabbit

  • Tests
  • Updated test suite infrastructure across multiple packages to use modern async/await patterns for user interaction simulation, improving test reliability and maintainability.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (1)
frontend/public/components/modals/__tests__/impersonate-user-modal-integration.spec.tsx (1)

236-247: ⚠️ Potential issue | 🟠 Major

Avoid conditional no-op in deselection assertion path.

Line 237 gates the core behavior under if (closeButtons.length > 0), so the test can pass even when deselection UI is broken. Assert presence, then always execute deselect path.

Proposed fix
       const closeButtons = document.querySelectorAll('.pf-v6-c-label__actions button');
-      if (closeButtons.length > 0) {
-        await act(async () => {
-          await user.click(closeButtons[0]);
-        });
-
-        await waitFor(() => {
-          const chips = document.querySelectorAll('.pf-v6-c-label');
-          expect(chips.length).toBe(0);
-        });
-      }
+      expect(closeButtons.length).toBeGreaterThan(0);
+      await user.click(closeButtons[0]);
+
+      await waitFor(() => {
+        const chips = document.querySelectorAll('.pf-v6-c-label');
+        expect(chips.length).toBe(0);
+      });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/public/components/modals/__tests__/impersonate-user-modal-integration.spec.tsx`
around lines 236 - 247, The test currently short-circuits deselection when no
close button exists; change the flow to explicitly assert the presence of the
close button and then always run the deselect path: locate the closeButtons
using document.querySelectorAll('.pf-v6-c-label__actions button') (as assigned
to closeButtons), add an expectation like
expect(closeButtons.length).toBeGreaterThan(0) to fail fast if UI is broken,
then always run the act(async () => await user.click(closeButtons[0])) and the
subsequent waitFor that asserts chips length is 0 so the test cannot pass
silently when deselection UI is missing.
🧹 Nitpick comments (15)
frontend/public/components/utils/__tests__/kebab.spec.tsx (1)

19-67: Optional: reduce repeated userEvent.setup() boilerplate.

You can trim repetition by initializing user in a beforeEach inside each describe block.

♻️ Small cleanup example
 describe('KebabItem', () => {
+  let user: ReturnType<typeof userEvent.setup>;
+  beforeEach(() => {
+    user = userEvent.setup();
+  });
+
   it('should disable option without callback / href (i.e. option does nothing)', async () => {
-    const user = userEvent.setup();
     const nothingOption = { ...mockOption, href: undefined };
     const trackOnClick = jest.fn();
     const renderItem = render(<KebabItem onClick={trackOnClick} option={nothingOption} />);
     await user.click(renderItem.getByRole('menuitem'));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/public/components/utils/__tests__/kebab.spec.tsx` around lines 19 -
67, Refactor the repeated userEvent.setup() calls by creating a shared `let
user: ReturnType<typeof userEvent.setup>` and initializing it in a beforeEach
within each describe block that contains the tests (e.g., the describe for
KebabItem_ and the describe for KebabItemAccessReview_); then remove the
per-test `const user = userEvent.setup()` lines so tests that call
user.click(...) use the beforeEach-initialized `user` instead (update any
imports/types if needed and keep test logic in KebabItem,
KebabItemAccessReview_, and related mocks unchanged).
frontend/packages/dev-console/src/components/user-preferences/__tests__/SecureRouteFields.spec.tsx (1)

60-72: Nice consistency in the second dropdown test; consider a small helper to reduce duplication.

Both dropdown-behavior tests repeat the same setup/click flow; extracting a tiny helper would improve maintainability.

♻️ Optional refactor
+const openInsecureTrafficDropdown = async () => {
+  const user = userEvent.setup();
+  await user.click(screen.getByTestId('insecure-traffic'));
+};

   it('should not show Allow option in Insecure traffic dropdown if TLS termination is Passthrough', async () => {
-    const user = userEvent.setup();
     mockUsePreferredRoutingOptions.mockReturnValue([
       {
         secure: true,
         tlsTermination: 'passthrough',
         insecureTraffic: 'Redirect',
       },
       () => {},
       true,
     ]);
     render(<SecureRouteFields />);
-    const inSecureTraffic = screen.getByTestId('insecure-traffic');
-    await user.click(inSecureTraffic);
+    await openInsecureTrafficDropdown();

   it('should show Allow, None and  Redirect options in Insecure traffic dropdown if TLS termination is not Passthrough', async () => {
-    const user = userEvent.setup();
     mockUsePreferredRoutingOptions.mockReturnValue([
       {
         secure: true,
         tlsTermination: 'edge',
         insecureTraffic: 'Redirect',
       },
       () => {},
       true,
     ]);
     render(<SecureRouteFields />);
-    const inSecureTraffic = screen.getByTestId('insecure-traffic');
-    await user.click(inSecureTraffic);
+    await openInsecureTrafficDropdown();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/packages/dev-console/src/components/user-preferences/__tests__/SecureRouteFields.spec.tsx`
around lines 60 - 72, Extract a small test helper to encapsulate the repeated
setup/click flow used in SecureRouteFields tests: move the userEvent.setup(),
mockUsePreferredRoutingOptions.mockReturnValue([...]), render(<SecureRouteFields
/>) and the await user.click(screen.getByTestId('insecure-traffic')) into a
helper function (e.g., renderAndClickInsecureTraffic or setupSecureRouteFields)
and replace the duplicated blocks in both dropdown-behavior tests with calls to
that helper; keep references to mockUsePreferredRoutingOptions,
SecureRouteFields, and the 'insecure-traffic' testId so the behavior remains
identical.
frontend/packages/operator-lifecycle-manager/src/components/modals/__tests__/installplan-approval-modal.spec.tsx (1)

107-118: Decouple this test from shared fixture defaults.

Line 108 relies on testInstallPlan’s current default approval. If that fixture ever defaults to Manual, Line 116 won’t change state and Line 118 may not exercise submit due to disabled Save.

Suggested hardening
   it('calls k8sUpdate with updated install plan when form is submitted', async () => {
     const user = userEvent.setup();
     const installPlan = _.cloneDeep(testInstallPlan);
+    installPlan.spec.approval = InstallPlanApproval.Automatic;
     mockModelFor.mockReturnValue(InstallPlanModel);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/packages/operator-lifecycle-manager/src/components/modals/__tests__/installplan-approval-modal.spec.tsx`
around lines 107 - 118, The test depends on the shared fixture testInstallPlan
default approval; make the test explicit by mutating the cloned installPlan
before rendering so its approval is not already "Manual" (e.g. set
installPlan.spec.approval = 'Automatic' or the appropriate ApprovalAutomatic
constant) so that clicking the Manual radio (InstallPlanApprovalModal) actually
changes state and enables the Save button; keep using the cloned installPlan and
the same installPlanApprovalModalProps when calling renderWithProviders.
frontend/public/components/modals/__tests__/configure-update-strategy-modal.spec.tsx (1)

69-88: Deduplicate the RollingUpdate test harness.

The same harness is defined twice; extracting a shared helper will reduce drift and keep these tests easier to maintain.

♻️ Proposed refactor
+  const renderRollingUpdateInputsHarness = () => {
+    const RollingUpdateInputsHarness = () => {
+      const [maxUnavailable, setMaxUnavailable] = useState('25%');
+      const [maxSurge, setMaxSurge] = useState('25%');
+      return (
+        <ConfigureUpdateStrategy
+          {...mockProps}
+          strategyType="RollingUpdate"
+          maxUnavailable={maxUnavailable}
+          maxSurge={maxSurge}
+          onChangeMaxUnavailable={(v) => {
+            setMaxUnavailable(String(v));
+            mockProps.onChangeMaxUnavailable(v);
+          }}
+          onChangeMaxSurge={(v) => {
+            setMaxSurge(String(v));
+            mockProps.onChangeMaxSurge(v);
+          }}
+        />
+      );
+    };
+
+    renderWithProviders(<RollingUpdateInputsHarness />);
+  };

   it('calls onChangeMaxUnavailable when input changes', async () => {
     const user = userEvent.setup();
-    const RollingUpdateInputsHarness = () => { ... };
-    renderWithProviders(<RollingUpdateInputsHarness />);
+    renderRollingUpdateInputsHarness();
     const maxUnavailableInput = screen.getByTestId('max-unavailable-input');
     await user.clear(maxUnavailableInput);
     await user.type(maxUnavailableInput, '50%');
     await waitFor(() => {
       expect(mockProps.onChangeMaxUnavailable).toHaveBeenLastCalledWith('50%');
     });
   });

   it('calls onChangeMaxSurge when input changes', async () => {
     const user = userEvent.setup();
-    const RollingUpdateInputsHarness = () => { ... };
-    renderWithProviders(<RollingUpdateInputsHarness />);
+    renderRollingUpdateInputsHarness();
     const maxSurgeInput = screen.getByTestId('max-surge-input');
     await user.clear(maxSurgeInput);
     await user.type(maxSurgeInput, '1');
     await waitFor(() => {
       expect(mockProps.onChangeMaxSurge).toHaveBeenLastCalledWith('1');
     });
   });

Also applies to: 105-124

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

In
`@frontend/public/components/modals/__tests__/configure-update-strategy-modal.spec.tsx`
around lines 69 - 88, The RollingUpdateInputsHarness component is duplicated;
extract it into a single reusable helper used by both tests to avoid drift:
create one shared RollingUpdateInputsHarness function that initializes
maxUnavailable and maxSurge via useState, calls setMaxUnavailable/setMaxSurge
and mockProps.onChangeMaxUnavailable/onChangeMaxSurge in the same way, and
render ConfigureUpdateStrategy with strategyType="RollingUpdate" and the same
props; replace both inline duplicates (the blocks using ConfigureUpdateStrategy
with maxUnavailable/maxSurge and onChange handlers) with calls to this shared
helper so both tests use the identical component.
frontend/packages/console-shared/src/components/getting-started/__tests__/GettingStartedCard.spec.tsx (2)

60-61: Make click-handler assertions strict.

Lines 61 and 78 only assert “called”. Using exact call counts better detects accidental double invocation.

Stricter interaction assertions
- expect(onClick).toHaveBeenCalled();
+ expect(onClick).toHaveBeenCalledTimes(1);

Also applies to: 77-78

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

In
`@frontend/packages/console-shared/src/components/getting-started/__tests__/GettingStartedCard.spec.tsx`
around lines 60 - 61, The test currently asserts only that the click handler was
called; change these to strict call-count assertions so accidental
double-invocations are caught—replace expect(onClick).toHaveBeenCalled() with
expect(onClick).toHaveBeenCalledTimes(1) for the user.click() interaction on
screen.getByTestId('item link-1') and the other similar interaction (the second
occurrence around lines 77-78), ensuring both click assertions check for exactly
one invocation of the onClick mock.

32-35: Use a semantic heading assertion for the title.

Line 34 uses getByText, which won’t catch heading-level regressions. Prefer querying by role/level for stronger a11y coverage.

Refactor assertion
- expect(screen.getByText('Test Card')).toBeInTheDocument();
+ expect(screen.getByRole('heading', { level: 3, name: 'Test Card' })).toBeInTheDocument();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/packages/console-shared/src/components/getting-started/__tests__/GettingStartedCard.spec.tsx`
around lines 32 - 35, Replace the title assertion that uses
screen.getByText('Test Card') in the GettingStartedCard test with a semantic
heading query to catch heading-level regressions; specifically, change it to use
screen.getByRole('heading', { name: 'Test Card', level: 2 }) (or the correct
heading level used by the GettingStartedCard component) so the test asserts both
accessible role and label instead of plain text.
frontend/packages/container-security/src/components/__tests__/ImageVulnerabilityToggleGroup.spec.tsx (1)

32-39: Use an async query after the click to reduce flake risk.

After the await user.click() at line 37, UI updates can be asynchronous. The synchronous screen.getByText at line 39 can race with these updates in slower environments. Use await screen.findByText() instead for resilience.

Proposed change
-    expect(screen.getByText(/No.*app dependency.*vulnerabilities/i)).toBeVisible();
+    expect(await screen.findByText(/No.*app dependency.*vulnerabilities/i)).toBeVisible();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/packages/container-security/src/components/__tests__/ImageVulnerabilityToggleGroup.spec.tsx`
around lines 32 - 39, In the ImageVulnerabilityToggleGroup test "should render
empty state when switching to App dependency with no vulnerabilities", replace
the synchronous assertion that uses screen.getByText after await
user.click(appDependencyButton) with an asynchronous query (await
screen.findByText) so the test waits for the UI update; update the assertion to
await screen.findByText(/No.*app dependency.*vulnerabilities/i) and assert
visibility on the resolved element.
frontend/packages/console-app/src/components/modals/resource-limits/__tests__/ResourceLimitsModal.spec.tsx (2)

64-87: Add one negative-path test for Save disablement guard.

Current coverage verifies submit/cancel happy paths, but not the guard driven by errors/isSubmitting. A focused case prevents regressions in modal submit protection.

Suggested test addition
+  it('does not submit when Save is disabled due to validation errors', async () => {
+    const user = userEvent.setup();
+    const handleSubmit = jest.fn();
+    renderWithProviders(
+      <ResourceLimitsModal
+        {...formProps}
+        handleSubmit={handleSubmit}
+        errors={{ cpuLimit: 'Required' }}
+        isSubmitting={false}
+      />,
+    );
+
+    const saveButton = screen.getByRole('button', { name: 'Save' });
+    expect(saveButton).toBeDisabled();
+    await user.click(saveButton);
+    expect(handleSubmit).not.toHaveBeenCalled();
+  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/packages/console-app/src/components/modals/resource-limits/__tests__/ResourceLimitsModal.spec.tsx`
around lines 64 - 87, Add a negative-path test to ResourceLimitsModal that
verifies the Save button is disabled and does not call handleSubmit when the
form is guarded by validation or submission state: render <ResourceLimitsModal
{...formProps} /> with formProps adjusted to include a non-empty errors object
or isSubmitting: true, assert screen.getByRole('button', { name: 'Save' }) is
disabled (or has aria-disabled) and then attempt a user.click and expect
formProps.handleSubmit not toHaveBeenCalled; use the existing formProps,
userEvent.setup(), and screen helpers to mirror the other tests.

22-61: Use satisfies instead of as Props to enforce stricter type checking and prevent unused fixture fields.

At Line 61, as Props bypasses excess-property checks, allowing the large resource fixture block (Lines 26–60) to persist even if the component's ResourceLimitsModalProps interface doesn't require it. The component only declares { cancel?: () => void }, so resource is extraneous.

Replace the type assertion with satisfies ResourceLimitsModalProps. This will catch excess properties at compile time and prevent fixture drift:

Refactor
-    formProps = {
+    formProps = {
       ...formikFormProps,
       isSubmitting: false,
       cancel: jest.fn(),
-      resource: {
-        apiVersion: 'apps/v1',
-        kind: 'Deployment',
-        metadata: {
-          name: 'xyz-deployment',
-        },
-        spec: {
-          selector: {
-            matchLabels: {
-              app: 'hello-openshift',
-            },
-          },
-          replicas: 1,
-          template: {
-            metadata: {
-              labels: {
-                app: 'hello-openshift',
-              },
-            },
-            spec: {
-              containers: [
-                {
-                  name: 'hello-openshift',
-                  image: 'openshift/hello-openshift',
-                  ports: [
-                    {
-                      containerPort: 8080,
-                    },
-                  ],
-                },
-              ],
-            },
-          },
-        },
-      },
-    } as Props;
+    } satisfies ResourceLimitsModalProps;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/packages/console-app/src/components/modals/resource-limits/__tests__/ResourceLimitsModal.spec.tsx`
around lines 22 - 61, The test uses a wide fixture typed with "as Props" which
suppresses excess-property checks; replace the trailing "as Props" on the
formProps object with "satisfies ResourceLimitsModalProps" (reference: formProps
and ResourceLimitsModalProps) so TypeScript will flag extraneous fields like the
large resource block and force you to either remove or trim it; ensure the
cancel property remains (cancel: jest.fn()) and run typechecking to fix any
reported excess properties.
frontend/public/components/modals/__tests__/impersonate-user-modal-integration.spec.tsx (1)

64-70: Remove manual act() wrappers around render() and user interaction calls.

@testing-library/react v16 + @testing-library/user-event v14.6.1 handle async operations internally. Manual act() wrapping is redundant, adds noise, and makes tests harder to maintain. RTL's render() already handles the necessary act() wrapping, and userEvent methods integrate seamlessly without explicit wrappers.

Applies to: Lines 64–70, 96–102, 110–112, 121–123, 142–148, 157–159, 167–169, 179–181, 202–208, 216–218, 225–227, 238–240, 261–267, 272–274, 282–284, 296–302, 307–309, 317–319, 332–338, 370–376, 379–385, 396–402 in frontend/public/components/modals/__tests__/impersonate-user-modal-integration.spec.tsx

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

In
`@frontend/public/components/modals/__tests__/impersonate-user-modal-integration.spec.tsx`
around lines 64 - 70, Remove the manual act(...) wrappers around render(...) and
userEvent calls in the ImpersonateUserModal tests: find occurrences of act(async
() => { render(...); }) and act(async () => { await userEvent.*(...) }) in
impersonate-user-modal-integration.spec.tsx and replace them with direct calls
to render(...) and await userEvent.*(...) respectively (keep async/await on
userEvent where needed); rely on `@testing-library/react` and
`@testing-library/user-event` to handle act for you and do not import or use act
around render, user interactions, or assertions involving the
ImpersonateUserModal, mockStore, onClose, and onImpersonate test helpers.
frontend/packages/knative-plugin/src/components/sink-source/__tests__/SinkSourceModal.spec.tsx (2)

12-18: Keep the Button mock complete to preserve secondary button test coverage.

The component renders a cancel button (line 67 of SinkSourceModal.tsx) with type="button", but the current mock returns null for non-submit buttons. This gaps test visibility: the cancel button's existence, visibility, and onClick handler cannot be asserted in tests, creating regression risk for secondary actions.

♻️ Proposed refactor
-  Button: jest.fn(({ children, type, ...props }) =>
-    type === 'submit' ? (
-      <button type="submit" {...props}>
-        {children}
-      </button>
-    ) : null,
-  ),
+  Button: jest.fn(({ children, type = 'button', ...props }) => (
+    <button type={type} {...props}>
+      {children}
+    </button>
+  )),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/packages/knative-plugin/src/components/sink-source/__tests__/SinkSourceModal.spec.tsx`
around lines 12 - 18, The Button mock in SinkSourceModal.spec.tsx currently only
renders when type === 'submit', hiding the modal's cancel button; update the
mock implementation of Button (jest.fn(({ children, type, ...props }) => ...))
so it returns a real <button> element for non-submit types as well (e.g., when
type === 'button' or undefined) forwarding children and props so tests can
assert the cancel button's presence and onClick; ensure the mock still renders
the submit button as before and preserves prop forwarding to keep all existing
assertions working with SinkSourceModal's cancel button.

49-53: Consider enhancing the requestSubmit polyfill to support the submitter parameter and dispatch SubmitEvent instead of plain Event.

The current polyfill works for this test's assertions, but the enhanced version ensures compatibility with form handlers that inspect event.submitter to determine which button triggered submission—behavior that @testing-library/user-event v14 expects when simulating submit button clicks. The improvement is backward-compatible (falls back to plain Event in unsupported environments) and aligns with HTML spec semantics.

Enhanced polyfill for future-proof form testing
-    HTMLFormElement.prototype.requestSubmit = function requestSubmitPolyfill(
-      this: HTMLFormElement,
-    ) {
-      this.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));
-    };
+    HTMLFormElement.prototype.requestSubmit = function requestSubmitPolyfill(
+      this: HTMLFormElement,
+      submitter?: HTMLElement | null,
+    ) {
+      const event =
+        typeof SubmitEvent === 'function'
+          ? new SubmitEvent('submit', {
+              bubbles: true,
+              cancelable: true,
+              submitter: submitter ?? undefined,
+            })
+          : new Event('submit', { bubbles: true, cancelable: true });
+      this.dispatchEvent(event);
+    };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/packages/knative-plugin/src/components/sink-source/__tests__/SinkSourceModal.spec.tsx`
around lines 49 - 53, Update the requestSubmit polyfill
(HTMLFormElement.prototype.requestSubmit / requestSubmitPolyfill) to accept an
optional submitter parameter and dispatch a SubmitEvent with the submitter set
when available; if the environment lacks SubmitEvent support, fall back to
creating a plain Event but attach a submitter property so handlers relying on
event.submitter (and tests using `@testing-library/user-event` v14) still work.
Ensure you preserve bubbles/cancelable flags and maintain backward compatibility
by only using SubmitEvent when supported.
frontend/packages/console-shared/src/components/formik-fields/__tests__/NumberSpinnerField.spec.tsx (1)

6-20: Use role-based queries (getByRole) instead of test IDs for core interactive elements to strengthen a11y signal in tests.

The input field and buttons should be queryable by role since PatternFly's NumberInput renders a proper <input type="number"> (which has the spinbutton role) and buttons with accessible labels. Replace the getByTestId('number-spinner-field') calls with getByRole('spinbutton') and the getByTestId('Increment'/'Decrement') calls with getByRole('button', { name: /increment|decrement/i }) to better detect accessibility regressions while keeping test IDs as a fallback for edge cases.

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

In
`@frontend/packages/console-shared/src/components/formik-fields/__tests__/NumberSpinnerField.spec.tsx`
around lines 6 - 20, Update the tests to use role-based queries: replace the
getInput helper (currently using screen.getByTestId('number-spinner-field')) to
use screen.getByRole('spinbutton') and, in the button checks inside the "Number
Spinner Field" suite where screen.getByTestId('Increment') and 'Decrement' are
used, replace them with screen.getByRole('button', { name: /increment/i }) and
screen.getByRole('button', { name: /decrement/i }); keep using
mockFormikRenderer(<NumberSpinnerField name="spinnerField" />) as-is and retain
testId queries only as a fallback if role queries fail.
frontend/packages/console-shared/src/components/formik-fields/key-value-file-input-field/__tests__/KeyValueFileInputField.spec.tsx (2)

46-49: Drop unnecessary act wrapper around userEvent.

At Line 46, wrapping awaited user.click / user.tab in act is redundant with modern userEvent and can conflict with testing-library/no-unnecessary-act.

Proposed diff
-import { screen, render, act, waitFor } from '@testing-library/react';
+import { screen, render, waitFor } from '@testing-library/react';
@@
-  await act(async () => {
-    await user.click(KeyField);
-    await user.tab();
-  });
+  await user.click(KeyField);
+  await user.tab();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/packages/console-shared/src/components/formik-fields/key-value-file-input-field/__tests__/KeyValueFileInputField.spec.tsx`
around lines 46 - 49, Remove the unnecessary act wrapper around userEvent calls
in the test: replace the block using act(async () => { await
user.click(KeyField); await user.tab(); }) with direct awaited user calls (await
user.click(KeyField); await user.tab();). Update the test in
KeyValueFileInputField.spec.tsx where KeyField and the user helper are used,
removing the import/usage of act for that interaction to satisfy
testing-library/no-unnecessary-act and rely on userEvent's built-in async
handling.

82-85: Prefer direct async queries over waitFor + queryBy* for these cases.

Lines 82/99/118 can be simplified for clearer intent and less polling noise: use direct queryByTestId for absence and findBy*/findAllBy* for presence/count.

Proposed diff
-  await waitFor(() => {
-    const removeButton = screen.queryByTestId('remove-key-value-button');
-    expect(removeButton).toBeNull();
-  });
+  expect(screen.queryByTestId('remove-key-value-button')).toBeNull();
@@
-  await waitFor(() => {
-    const removeButton = screen.queryByTestId('remove-key-value-button');
-    expect(removeButton).not.toBeNull();
-  });
+  expect(await screen.findByTestId('remove-key-value-button')).not.toBeNull();
@@
-  await waitFor(() => {
-    const keyValuePair = screen.queryAllByTestId('key-value-pair');
-    expect(keyValuePair).toHaveLength(2);
-  });
+  expect(await screen.findAllByTestId('key-value-pair')).toHaveLength(2);

Also applies to: 99-102, 118-121

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

In
`@frontend/packages/console-shared/src/components/formik-fields/key-value-file-input-field/__tests__/KeyValueFileInputField.spec.tsx`
around lines 82 - 85, Replace the waitFor + queryByTestId patterns with direct
async queries: for assertions that something is absent (currently using await
waitFor(() => { const removeButton =
screen.queryByTestId('remove-key-value-button');
expect(removeButton).toBeNull(); })) remove the waitFor and just use const
removeButton = screen.queryByTestId('remove-key-value-button');
expect(removeButton).toBeNull(); and for assertions that wait for presence or
count (the other two occurrences) replace waitFor + queryBy*/getAllBy* with
await screen.findByTestId('...') or await screen.findAllByTestId('...')
respectively (use findByTestId for single-element presence and findAllByTestId
for count checks) so tests use direct async queries instead of polling with
waitFor.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@frontend/packages/console-shared/src/components/formik-fields/__tests__/NumberSpinnerField.spec.tsx`:
- Around line 10-47: The tests for NumberSpinnerField were narrowed to only type
and click flows and need to restore behavioral coverage: update
NumberSpinnerField.spec.tsx to re-add assertions that helper
text/aria-describedby wiring works (render with helperText prop and assert
getInput().getAttribute('aria-describedby') points to the helper element and
that the helper text is present), restore min/max behavior checks by rendering
NumberSpinnerField/NumberSpinner with min and max props and asserting values
clamp at min/max when typing, clearing, or using Increment/Decrement, and verify
step behavior and default value handling (render with initial value or empty
state and assert expected initial value like '0'); use existing helpers
(mockFormikRenderer, getInput, screen.getByTestId('Increment'|'Decrement')) and
keep async userEvent flows in act/waitFor as in other tests.

In
`@frontend/packages/console-shared/src/components/getting-started/__tests__/GettingStartedCard.spec.tsx`:
- Around line 7-94: Tests for GettingStartedCard lost coverage of key behaviors:
conditional rendering branches and external-link hardening attributes. Update
the test suite for GettingStartedCard to (1) assert that external links (links
with external: true) render rel="noopener noreferrer" and target="_blank" (use
the link with id 'link-2'), (2) add assertions for conditional rendering paths
such as when a link has loading: true vs has href/onClick (use 'loading-link'
and the existing onClick cases), and (3) verify that internal vs external link
markup differs as expected; reference test utilities like renderWithProviders,
screen.getByTestId('item link-2'), screen.getByTestId('item loading-link'), and
the moreLink case to locate elements.

In
`@frontend/packages/knative-plugin/src/components/traffic-splitting/__tests__/TrafficSplittingModal.spec.tsx`:
- Around line 15-21: The mocked Button in TrafficSplittingModal.spec.tsx
currently returns null for non-submit types which hides the Cancel button from
tests; update the mock (the jest.fn for Button) to render a real <button> for
all types (or at least when type === 'button') preserving children and props so
Cancel/actions are present, e.g., return a button element that spreads props and
renders children; run the tests and update any assertions to check for the
Cancel button rendered by TrafficSplittingModal.tsx (the modal component that
renders Save type="submit" and Cancel type="button").

In
`@frontend/packages/webterminal-plugin/src/components/cloud-shell/__tests__/MultiTabbedTerminal.spec.tsx`:
- Around line 21-31: The helper clickMultipleTimes currently captures a single
HTMLElement and repeatedly clicks it, which can target a detached node when the
"Add new tab" button disappears; update clickMultipleTimes to accept a getter
(e.g., () => HTMLElement | null) or a selector and, inside the loop, query the
current element each iteration (using the provided getter or DOM query) and bail
out if it returns null, then perform the await act(() =>
user.click(currentElement)) only when the element is present; update callers in
the tests to pass a fresh-querying getter (or selector) instead of a captured
element so clicks stop gracefully when the button is removed.

---

Outside diff comments:
In
`@frontend/public/components/modals/__tests__/impersonate-user-modal-integration.spec.tsx`:
- Around line 236-247: The test currently short-circuits deselection when no
close button exists; change the flow to explicitly assert the presence of the
close button and then always run the deselect path: locate the closeButtons
using document.querySelectorAll('.pf-v6-c-label__actions button') (as assigned
to closeButtons), add an expectation like
expect(closeButtons.length).toBeGreaterThan(0) to fail fast if UI is broken,
then always run the act(async () => await user.click(closeButtons[0])) and the
subsequent waitFor that asserts chips length is 0 so the test cannot pass
silently when deselection UI is missing.

---

Nitpick comments:
In
`@frontend/packages/console-app/src/components/modals/resource-limits/__tests__/ResourceLimitsModal.spec.tsx`:
- Around line 64-87: Add a negative-path test to ResourceLimitsModal that
verifies the Save button is disabled and does not call handleSubmit when the
form is guarded by validation or submission state: render <ResourceLimitsModal
{...formProps} /> with formProps adjusted to include a non-empty errors object
or isSubmitting: true, assert screen.getByRole('button', { name: 'Save' }) is
disabled (or has aria-disabled) and then attempt a user.click and expect
formProps.handleSubmit not toHaveBeenCalled; use the existing formProps,
userEvent.setup(), and screen helpers to mirror the other tests.
- Around line 22-61: The test uses a wide fixture typed with "as Props" which
suppresses excess-property checks; replace the trailing "as Props" on the
formProps object with "satisfies ResourceLimitsModalProps" (reference: formProps
and ResourceLimitsModalProps) so TypeScript will flag extraneous fields like the
large resource block and force you to either remove or trim it; ensure the
cancel property remains (cancel: jest.fn()) and run typechecking to fix any
reported excess properties.

In
`@frontend/packages/console-shared/src/components/formik-fields/__tests__/NumberSpinnerField.spec.tsx`:
- Around line 6-20: Update the tests to use role-based queries: replace the
getInput helper (currently using screen.getByTestId('number-spinner-field')) to
use screen.getByRole('spinbutton') and, in the button checks inside the "Number
Spinner Field" suite where screen.getByTestId('Increment') and 'Decrement' are
used, replace them with screen.getByRole('button', { name: /increment/i }) and
screen.getByRole('button', { name: /decrement/i }); keep using
mockFormikRenderer(<NumberSpinnerField name="spinnerField" />) as-is and retain
testId queries only as a fallback if role queries fail.

In
`@frontend/packages/console-shared/src/components/formik-fields/key-value-file-input-field/__tests__/KeyValueFileInputField.spec.tsx`:
- Around line 46-49: Remove the unnecessary act wrapper around userEvent calls
in the test: replace the block using act(async () => { await
user.click(KeyField); await user.tab(); }) with direct awaited user calls (await
user.click(KeyField); await user.tab();). Update the test in
KeyValueFileInputField.spec.tsx where KeyField and the user helper are used,
removing the import/usage of act for that interaction to satisfy
testing-library/no-unnecessary-act and rely on userEvent's built-in async
handling.
- Around line 82-85: Replace the waitFor + queryByTestId patterns with direct
async queries: for assertions that something is absent (currently using await
waitFor(() => { const removeButton =
screen.queryByTestId('remove-key-value-button');
expect(removeButton).toBeNull(); })) remove the waitFor and just use const
removeButton = screen.queryByTestId('remove-key-value-button');
expect(removeButton).toBeNull(); and for assertions that wait for presence or
count (the other two occurrences) replace waitFor + queryBy*/getAllBy* with
await screen.findByTestId('...') or await screen.findAllByTestId('...')
respectively (use findByTestId for single-element presence and findAllByTestId
for count checks) so tests use direct async queries instead of polling with
waitFor.

In
`@frontend/packages/console-shared/src/components/getting-started/__tests__/GettingStartedCard.spec.tsx`:
- Around line 60-61: The test currently asserts only that the click handler was
called; change these to strict call-count assertions so accidental
double-invocations are caught—replace expect(onClick).toHaveBeenCalled() with
expect(onClick).toHaveBeenCalledTimes(1) for the user.click() interaction on
screen.getByTestId('item link-1') and the other similar interaction (the second
occurrence around lines 77-78), ensuring both click assertions check for exactly
one invocation of the onClick mock.
- Around line 32-35: Replace the title assertion that uses
screen.getByText('Test Card') in the GettingStartedCard test with a semantic
heading query to catch heading-level regressions; specifically, change it to use
screen.getByRole('heading', { name: 'Test Card', level: 2 }) (or the correct
heading level used by the GettingStartedCard component) so the test asserts both
accessible role and label instead of plain text.

In
`@frontend/packages/container-security/src/components/__tests__/ImageVulnerabilityToggleGroup.spec.tsx`:
- Around line 32-39: In the ImageVulnerabilityToggleGroup test "should render
empty state when switching to App dependency with no vulnerabilities", replace
the synchronous assertion that uses screen.getByText after await
user.click(appDependencyButton) with an asynchronous query (await
screen.findByText) so the test waits for the UI update; update the assertion to
await screen.findByText(/No.*app dependency.*vulnerabilities/i) and assert
visibility on the resolved element.

In
`@frontend/packages/dev-console/src/components/user-preferences/__tests__/SecureRouteFields.spec.tsx`:
- Around line 60-72: Extract a small test helper to encapsulate the repeated
setup/click flow used in SecureRouteFields tests: move the userEvent.setup(),
mockUsePreferredRoutingOptions.mockReturnValue([...]), render(<SecureRouteFields
/>) and the await user.click(screen.getByTestId('insecure-traffic')) into a
helper function (e.g., renderAndClickInsecureTraffic or setupSecureRouteFields)
and replace the duplicated blocks in both dropdown-behavior tests with calls to
that helper; keep references to mockUsePreferredRoutingOptions,
SecureRouteFields, and the 'insecure-traffic' testId so the behavior remains
identical.

In
`@frontend/packages/knative-plugin/src/components/sink-source/__tests__/SinkSourceModal.spec.tsx`:
- Around line 12-18: The Button mock in SinkSourceModal.spec.tsx currently only
renders when type === 'submit', hiding the modal's cancel button; update the
mock implementation of Button (jest.fn(({ children, type, ...props }) => ...))
so it returns a real <button> element for non-submit types as well (e.g., when
type === 'button' or undefined) forwarding children and props so tests can
assert the cancel button's presence and onClick; ensure the mock still renders
the submit button as before and preserves prop forwarding to keep all existing
assertions working with SinkSourceModal's cancel button.
- Around line 49-53: Update the requestSubmit polyfill
(HTMLFormElement.prototype.requestSubmit / requestSubmitPolyfill) to accept an
optional submitter parameter and dispatch a SubmitEvent with the submitter set
when available; if the environment lacks SubmitEvent support, fall back to
creating a plain Event but attach a submitter property so handlers relying on
event.submitter (and tests using `@testing-library/user-event` v14) still work.
Ensure you preserve bubbles/cancelable flags and maintain backward compatibility
by only using SubmitEvent when supported.

In
`@frontend/packages/operator-lifecycle-manager/src/components/modals/__tests__/installplan-approval-modal.spec.tsx`:
- Around line 107-118: The test depends on the shared fixture testInstallPlan
default approval; make the test explicit by mutating the cloned installPlan
before rendering so its approval is not already "Manual" (e.g. set
installPlan.spec.approval = 'Automatic' or the appropriate ApprovalAutomatic
constant) so that clicking the Manual radio (InstallPlanApprovalModal) actually
changes state and enables the Save button; keep using the cloned installPlan and
the same installPlanApprovalModalProps when calling renderWithProviders.

In
`@frontend/public/components/modals/__tests__/configure-update-strategy-modal.spec.tsx`:
- Around line 69-88: The RollingUpdateInputsHarness component is duplicated;
extract it into a single reusable helper used by both tests to avoid drift:
create one shared RollingUpdateInputsHarness function that initializes
maxUnavailable and maxSurge via useState, calls setMaxUnavailable/setMaxSurge
and mockProps.onChangeMaxUnavailable/onChangeMaxSurge in the same way, and
render ConfigureUpdateStrategy with strategyType="RollingUpdate" and the same
props; replace both inline duplicates (the blocks using ConfigureUpdateStrategy
with maxUnavailable/maxSurge and onChange handlers) with calls to this shared
helper so both tests use the identical component.

In
`@frontend/public/components/modals/__tests__/impersonate-user-modal-integration.spec.tsx`:
- Around line 64-70: Remove the manual act(...) wrappers around render(...) and
userEvent calls in the ImpersonateUserModal tests: find occurrences of act(async
() => { render(...); }) and act(async () => { await userEvent.*(...) }) in
impersonate-user-modal-integration.spec.tsx and replace them with direct calls
to render(...) and await userEvent.*(...) respectively (keep async/await on
userEvent where needed); rely on `@testing-library/react` and
`@testing-library/user-event` to handle act for you and do not import or use act
around render, user interactions, or assertions involving the
ImpersonateUserModal, mockStore, onClose, and onImpersonate test helpers.

In `@frontend/public/components/utils/__tests__/kebab.spec.tsx`:
- Around line 19-67: Refactor the repeated userEvent.setup() calls by creating a
shared `let user: ReturnType<typeof userEvent.setup>` and initializing it in a
beforeEach within each describe block that contains the tests (e.g., the
describe for KebabItem_ and the describe for KebabItemAccessReview_); then
remove the per-test `const user = userEvent.setup()` lines so tests that call
user.click(...) use the beforeEach-initialized `user` instead (update any
imports/types if needed and keep test logic in KebabItem,
KebabItemAccessReview_, and related mocks unchanged).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: ec8ee7a1-e993-44d6-b331-0e57c249dbf7

📥 Commits

Reviewing files that changed from the base of the PR and between 0354897 and 2b14b5d.

📒 Files selected for processing (57)
  • frontend/packages/console-app/src/__tests__/hooks/useCSPViolationDetector.spec.tsx
  • frontend/packages/console-app/src/components/modals/resource-limits/__tests__/ResourceLimitsModal.spec.tsx
  • frontend/packages/console-shared/src/components/alerts/__tests__/SwitchToYAMLAlert.spec.tsx
  • frontend/packages/console-shared/src/components/editor/__tests__/CodeEditorToolbar.spec.tsx
  • frontend/packages/console-shared/src/components/form-utils/__tests__/FlexForm.spec.tsx
  • frontend/packages/console-shared/src/components/form-utils/__tests__/FormFooter.spec.tsx
  • frontend/packages/console-shared/src/components/formik-fields/__tests__/NumberSpinnerField.spec.tsx
  • frontend/packages/console-shared/src/components/formik-fields/key-value-file-input-field/__tests__/KeyValueFileInputField.spec.tsx
  • frontend/packages/console-shared/src/components/formik-fields/multi-column-field/__tests__/MultiColumnFieldFooter.spec.tsx
  • frontend/packages/console-shared/src/components/getting-started/__tests__/GettingStartedCard.spec.tsx
  • frontend/packages/console-shared/src/components/getting-started/__tests__/RestoreGettingStartedButton.spec.tsx
  • frontend/packages/console-shared/src/components/modals/__tests__/FetchProgressModal.spec.tsx
  • frontend/packages/console-shared/src/components/modals/__tests__/TextInputModal.spec.tsx
  • frontend/packages/console-shared/src/components/progressive-list/__tests__/ProgressiveList.spec.tsx
  • frontend/packages/console-shared/src/components/toast/__tests__/ToastProvider.spec.tsx
  • frontend/packages/console-shared/src/test-utils/unit-test-utils.tsx
  • frontend/packages/container-security/src/components/__tests__/ImageVulnerabilityToggleGroup.spec.tsx
  • frontend/packages/dev-console/src/components/deployments/__tests__/AdvancedSection.spec.tsx
  • frontend/packages/dev-console/src/components/deployments/__tests__/DeploymentForm.spec.tsx
  • frontend/packages/dev-console/src/components/deployments/__tests__/DeploymentStrategySection.spec.tsx
  • frontend/packages/dev-console/src/components/deployments/__tests__/EnvironmentVariablesSection.spec.tsx
  • frontend/packages/dev-console/src/components/deployments/__tests__/ImagesSection.spec.tsx
  • frontend/packages/dev-console/src/components/deployments/__tests__/PauseRolloutsSection.spec.tsx
  • frontend/packages/dev-console/src/components/user-preferences/__tests__/SecureRouteFields.spec.tsx
  • frontend/packages/knative-plugin/src/components/overview/__tests__/RevisionsOverviewList.spec.tsx
  • frontend/packages/knative-plugin/src/components/sink-source/__tests__/SinkSourceModal.spec.tsx
  • frontend/packages/knative-plugin/src/components/traffic-splitting/__tests__/TrafficSplittingModal.spec.tsx
  • frontend/packages/operator-lifecycle-manager-v1/src/components/cluster-extension/__tests__/ClusterExtensionForm.spec.tsx
  • frontend/packages/operator-lifecycle-manager/src/components/__tests__/install-plan.spec.tsx
  • frontend/packages/operator-lifecycle-manager/src/components/descriptors/spec/__tests__/resource-requirements.spec.tsx
  • frontend/packages/operator-lifecycle-manager/src/components/modals/__tests__/installplan-approval-modal.spec.tsx
  • frontend/packages/operator-lifecycle-manager/src/components/modals/__tests__/subscription-channel-modal.spec.tsx
  • frontend/packages/operator-lifecycle-manager/src/components/modals/__tests__/uninstall-operator-modal.spec.tsx
  • frontend/packages/topology/src/__tests__/TopologySideBar.spec.tsx
  • frontend/packages/topology/src/components/export-app/__tests__/ExportApplicationModal.spec.tsx
  • frontend/packages/webterminal-plugin/src/components/cloud-shell/__tests__/MinimizeRestoreButton.spec.tsx
  • frontend/packages/webterminal-plugin/src/components/cloud-shell/__tests__/MultiTabbedTerminal.spec.tsx
  • frontend/public/components/__tests__/storage-class-form.spec.tsx
  • frontend/public/components/cluster-settings/__tests__/basicauth-idp-form.spec.tsx
  • frontend/public/components/cluster-settings/__tests__/github-idp-form.spec.tsx
  • frontend/public/components/cluster-settings/__tests__/gitlab-idp-form.spec.tsx
  • frontend/public/components/cluster-settings/__tests__/google-idp-form.spec.tsx
  • frontend/public/components/cluster-settings/__tests__/htpasswd-idp-form.spec.tsx
  • frontend/public/components/cluster-settings/__tests__/keystone-idp-form.spec.tsx
  • frontend/public/components/cluster-settings/__tests__/ldap-idp-form.spec.tsx
  • frontend/public/components/cluster-settings/__tests__/openid-idp-form.spec.tsx
  • frontend/public/components/cluster-settings/__tests__/request-header-idp-form.spec.tsx
  • frontend/public/components/cluster-settings/__tests__/test-utils.ts
  • frontend/public/components/factory/__tests__/list-page.spec.tsx
  • frontend/public/components/modals/__tests__/configure-update-strategy-modal.spec.tsx
  • frontend/public/components/modals/__tests__/impersonate-user-modal-integration.spec.tsx
  • frontend/public/components/modals/__tests__/impersonate-user-modal.spec.tsx
  • frontend/public/components/modals/__tests__/replace-code-modal.spec.tsx
  • frontend/public/components/utils/__tests__/download-button.spec.tsx
  • frontend/public/components/utils/__tests__/kebab.spec.tsx
  • frontend/public/components/utils/__tests__/name-value-editor.spec.tsx
  • frontend/public/components/utils/__tests__/single-typeahead-dropdown.spec.tsx

Comment on lines +10 to 47
describe('Number Spinner Field', () => {
it('should render input field with type number', () => {
mockFormikRenderer(<NumberSpinnerField name="spinnerField" />);
const input = getInput();
expect(input.type).toEqual('number');
});

it('displays 0 when initial value is empty', () => {
mockFormikRenderer(<NumberSpinnerField name="spinnerField" />, { spinnerField: '' });
expect(getInput()).toHaveValue(0);
it('should render input with increment decrement button', () => {
mockFormikRenderer(<NumberSpinnerField name="spinnerField" />);
screen.getByTestId('Increment');
screen.getByTestId('Decrement');
});

it('updates value when typing in input field', async () => {
const user = userEvent.setup();
it('should only put numbers in input field', async () => {
mockFormikRenderer(<NumberSpinnerField name="spinnerField" />, { spinnerField: '' });

await user.clear(getInput());
await user.type(getInput(), '42');

expect(getInput()).toHaveValue(42);
});

it('increments value when increment button is clicked', async () => {
const user = userEvent.setup();
mockFormikRenderer(<NumberSpinnerField name="spinnerField" />, { spinnerField: 0 });
expect(getInput().value).toEqual('0');

await user.click(screen.getByRole('button', { name: /increment/i }));

expect(getInput()).toHaveValue(1);
});

it('decrements value when decrement button is clicked', async () => {
const user = userEvent.setup();
mockFormikRenderer(<NumberSpinnerField name="spinnerField" />, { spinnerField: 5 });

await user.click(screen.getByRole('button', { name: /decrement/i }));

expect(getInput()).toHaveValue(4);
});

it('associates help text with input via aria-describedby', () => {
mockFormikRenderer(<NumberSpinnerField name="spinnerField" helpText="Helpful description" />);
expect(getInput()).toHaveAttribute('aria-describedby');
});

it('input is accessible by spinbutton role', () => {
mockFormikRenderer(<NumberSpinnerField name="spinnerField" label="Replicas" />);
expect(screen.getByRole('spinbutton')).toBeVisible();
});

it('does not decrement below min when value equals min', async () => {
const user = userEvent.setup();
mockFormikRenderer(<NumberSpinnerField name="spinnerField" min={3} />, {
spinnerField: 3,
});

await user.click(screen.getByRole('button', { name: /decrement/i }));

expect(getInput()).toHaveValue(3);
});

it('does not increment above max when value equals max', async () => {
const user = userEvent.setup();
mockFormikRenderer(<NumberSpinnerField name="spinnerField" max={7} />, {
spinnerField: 7,
await act(async () => {
const user = userEvent.setup();
await user.clear(getInput());
await user.type(getInput(), '12');
});

await user.click(screen.getByRole('button', { name: /increment/i }));

expect(getInput()).toHaveValue(7);
await waitFor(() => expect(getInput().value).toEqual('12'));
});

it('increments value when ArrowUp is pressed on the input', async () => {
it('should increment or decrement value based on clicked button', async () => {
const user = userEvent.setup();
mockFormikRenderer(<NumberSpinnerField name="spinnerField" />, { spinnerField: 2 });

await user.click(getInput());
await user.keyboard('{ArrowUp}');

expect(getInput()).toHaveValue(3);
});

it('decrements value when ArrowDown is pressed on the input', async () => {
const user = userEvent.setup();
mockFormikRenderer(<NumberSpinnerField name="spinnerField" />, { spinnerField: 4 });

await user.click(getInput());
await user.keyboard('{ArrowDown}');
mockFormikRenderer(<NumberSpinnerField name="spinnerField" />, { spinnerField: '' });

expect(getInput()).toHaveValue(3);
expect(getInput().value).toEqual('0');
await user.click(screen.getByTestId('Increment'));
await user.click(screen.getByTestId('Increment'));
expect(getInput().value).toEqual('2');
await user.click(screen.getByTestId('Decrement'));
expect(getInput().value).toEqual('1');
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Please restore dropped behavior coverage, not just interaction API parity.

Line 10–47 now validates only a narrow path (type + click flow). This refactor removed checks for component behaviors that are part of NumberSpinnerField/NumberSpinner contract (e.g., aria-describedby/helper text wiring and min/max-related behavior). Since PR scope is interaction API migration, this test-surface reduction is a regression risk.

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

In
`@frontend/packages/console-shared/src/components/formik-fields/__tests__/NumberSpinnerField.spec.tsx`
around lines 10 - 47, The tests for NumberSpinnerField were narrowed to only
type and click flows and need to restore behavioral coverage: update
NumberSpinnerField.spec.tsx to re-add assertions that helper
text/aria-describedby wiring works (render with helperText prop and assert
getInput().getAttribute('aria-describedby') points to the helper element and
that the helper text is present), restore min/max behavior checks by rendering
NumberSpinnerField/NumberSpinner with min and max props and asserting values
clamp at min/max when typing, clearing, or using Increment/Decrement, and verify
step behavior and default value handling (render with initial value or empty
state and assert expected initial value like '0'); use existing helpers
(mockFormikRenderer, getInput, screen.getByTestId('Increment'|'Decrement')) and
keep async userEvent flows in act/waitFor as in other tests.

Comment on lines 7 to 94
describe('GettingStartedCard', () => {
const defaultProps: GettingStartedCardProps = {
id: 'test-card',
title: 'Test Card',
description: 'This is a test card.',
links: [
{ id: 'link-1', title: 'Internal Link', href: '/internal' },
{ id: 'link-2', title: 'External Link', href: 'https://example.com', external: true },
{
id: 'link-1',
title: 'Internal Link',
href: '/internal',
},
{
id: 'link-2',
title: 'External Link',
href: 'https://example.com',
external: true,
},
],
moreLink: { id: 'more-link', title: 'More Info', href: '/more' },
moreLink: {
id: 'more-link',
title: 'More Info',
href: '/more',
},
};

it('renders card title, description, and icon', async () => {
const TestIcon = () => <div>TestIcon</div>;
renderWithProviders(<GettingStartedCard {...defaultProps} icon={<TestIcon />} />);
await waitFor(() => {
expect(screen.getByRole('heading', { name: /Test Card/i, level: 3 })).toBeVisible();
});
expect(screen.getByText('This is a test card.')).toBeVisible();
expect(screen.getByText('TestIcon')).toBeVisible();
});

it('renders internal links with arrow icon', async () => {
renderWithProviders(<GettingStartedCard {...defaultProps} />);
const internalLink = await screen.findByTestId('item link-1');
expect(within(internalLink).getByText('Internal Link')).toBeVisible();
expect(within(internalLink).getByText('ArrowRightIcon')).toBeVisible();
});

it('renders external links with external link icon and target blank', async () => {
it('renders title and description', () => {
renderWithProviders(<GettingStartedCard {...defaultProps} />);
const externalLink = await screen.findByTestId('item link-2');
expect(within(externalLink).getByText('External Link')).toBeVisible();
expect(within(externalLink).getByText('ExternalLinkAltIcon')).toBeVisible();
expect(externalLink).toHaveAttribute('target', '_blank');
expect(externalLink).toHaveAttribute('rel', 'noopener noreferrer');
expect(screen.getByText('Test Card')).toBeInTheDocument();
expect(screen.getByText('This is a test card.')).toBeInTheDocument();
});

it('renders moreLink as internal link or button based on props', async () => {
it('renders all links', () => {
renderWithProviders(<GettingStartedCard {...defaultProps} />);
expect(await screen.findByRole('link', { name: 'More Info' })).toHaveAttribute('href', '/more');
expect(screen.getByTestId('item link-1')).toBeInTheDocument();
expect(screen.getByTestId('item link-2')).toBeInTheDocument();
expect(screen.getByTestId('item more-link')).toBeInTheDocument();
});

it('renders moreLink as button when onClick is provided and calls handler', async () => {
it('calls onClick for internal link', async () => {
const user = userEvent.setup();
const onClick = jest.fn();
const props = { ...defaultProps, moreLink: { id: 'more-link', title: 'More Info', onClick } };
const props = {
...defaultProps,
links: [
{
id: 'link-1',
title: 'Internal Link',
href: '/internal',
onClick,
},
],
};
renderWithProviders(<GettingStartedCard {...props} />);
await user.click(await screen.findByRole('button', { name: 'More Info' }));
expect(onClick).toHaveBeenCalledTimes(1);
await user.click(screen.getByTestId('item link-1'));
expect(onClick).toHaveBeenCalled();
});

it('calls onClick for internal link when clicked', async () => {
it('calls onClick for moreLink', async () => {
const user = userEvent.setup();
const onClick = jest.fn();
const props = {
...defaultProps,
links: [{ id: 'link-1', title: 'Internal Link', href: '/internal', onClick }],
moreLink: {
id: 'more-link',
title: 'More Info',
href: '/more',
onClick,
},
};
renderWithProviders(<GettingStartedCard {...props} />);
await user.click(await screen.findByTestId('item link-1'));
expect(onClick).toHaveBeenCalledTimes(1);
await user.click(screen.getByTestId('item more-link'));
expect(onClick).toHaveBeenCalled();
});

it('renders skeleton for loading links', async () => {
const props = { ...defaultProps, links: [{ id: 'loading-link', loading: true }] };
renderWithProviders(<GettingStartedCard {...props} />);
expect(await screen.findByTestId('getting-started-skeleton')).toBeVisible();
});

it('does not render links section when links array is empty', async () => {
const props = { ...defaultProps, links: [] };
renderWithProviders(<GettingStartedCard {...props} />);
await waitFor(() => {
expect(screen.queryByRole('list')).not.toBeInTheDocument();
});
});

it('does not render moreLink when not provided', async () => {
const props = { ...defaultProps, moreLink: undefined };
renderWithProviders(<GettingStartedCard {...props} />);
await waitFor(() => {
expect(screen.queryByRole('link', { name: 'More Info' })).not.toBeInTheDocument();
});
});

it('applies custom title color when provided', async () => {
const props = { ...defaultProps, titleColor: '#ff0000' };
it('renders skeleton for loading links', () => {
const props = {
...defaultProps,
links: [
{
id: 'loading-link',
loading: true,
},
],
};
renderWithProviders(<GettingStartedCard {...props} />);
expect(await screen.findByRole('heading', { name: 'Test Card' })).toHaveStyle({
color: '#ff0000',
});
expect(screen.getByTestId('getting-started-skeleton')).toBeInTheDocument();
});
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Preserve behavioral coverage; this currently narrows it beyond the migration scope.

This suite now validates mostly presence/clicks, but it no longer guards key behaviors of GettingStartedCard (external-link hardening attrs and conditional rendering branches). That weakens regression detection for security and UX-critical paths.

Suggested test additions to restore critical coverage
 it('renders all links', () => {
   renderWithProviders(<GettingStartedCard {...defaultProps} />);
-  expect(screen.getByTestId('item link-1')).toBeInTheDocument();
-  expect(screen.getByTestId('item link-2')).toBeInTheDocument();
+  expect(screen.getByTestId('item link-1')).toBeInTheDocument();
+  const external = screen.getByTestId('item link-2');
+  expect(external).toBeInTheDocument();
+  expect(external).toHaveAttribute('target', '_blank');
+  expect(external).toHaveAttribute('rel', expect.stringContaining('noopener'));
+  expect(external).toHaveAttribute('rel', expect.stringContaining('noreferrer'));
   expect(screen.getByTestId('item more-link')).toBeInTheDocument();
 });
+
+it('does not render list when links are empty', () => {
+  renderWithProviders(<GettingStartedCard {...defaultProps} links={[]} />);
+  expect(screen.queryByTestId('item link-1')).not.toBeInTheDocument();
+});
📝 Committable suggestion

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

Suggested change
describe('GettingStartedCard', () => {
const defaultProps: GettingStartedCardProps = {
id: 'test-card',
title: 'Test Card',
description: 'This is a test card.',
links: [
{ id: 'link-1', title: 'Internal Link', href: '/internal' },
{ id: 'link-2', title: 'External Link', href: 'https://example.com', external: true },
{
id: 'link-1',
title: 'Internal Link',
href: '/internal',
},
{
id: 'link-2',
title: 'External Link',
href: 'https://example.com',
external: true,
},
],
moreLink: { id: 'more-link', title: 'More Info', href: '/more' },
moreLink: {
id: 'more-link',
title: 'More Info',
href: '/more',
},
};
it('renders card title, description, and icon', async () => {
const TestIcon = () => <div>TestIcon</div>;
renderWithProviders(<GettingStartedCard {...defaultProps} icon={<TestIcon />} />);
await waitFor(() => {
expect(screen.getByRole('heading', { name: /Test Card/i, level: 3 })).toBeVisible();
});
expect(screen.getByText('This is a test card.')).toBeVisible();
expect(screen.getByText('TestIcon')).toBeVisible();
});
it('renders internal links with arrow icon', async () => {
renderWithProviders(<GettingStartedCard {...defaultProps} />);
const internalLink = await screen.findByTestId('item link-1');
expect(within(internalLink).getByText('Internal Link')).toBeVisible();
expect(within(internalLink).getByText('ArrowRightIcon')).toBeVisible();
});
it('renders external links with external link icon and target blank', async () => {
it('renders title and description', () => {
renderWithProviders(<GettingStartedCard {...defaultProps} />);
const externalLink = await screen.findByTestId('item link-2');
expect(within(externalLink).getByText('External Link')).toBeVisible();
expect(within(externalLink).getByText('ExternalLinkAltIcon')).toBeVisible();
expect(externalLink).toHaveAttribute('target', '_blank');
expect(externalLink).toHaveAttribute('rel', 'noopener noreferrer');
expect(screen.getByText('Test Card')).toBeInTheDocument();
expect(screen.getByText('This is a test card.')).toBeInTheDocument();
});
it('renders moreLink as internal link or button based on props', async () => {
it('renders all links', () => {
renderWithProviders(<GettingStartedCard {...defaultProps} />);
expect(await screen.findByRole('link', { name: 'More Info' })).toHaveAttribute('href', '/more');
expect(screen.getByTestId('item link-1')).toBeInTheDocument();
expect(screen.getByTestId('item link-2')).toBeInTheDocument();
expect(screen.getByTestId('item more-link')).toBeInTheDocument();
});
it('renders moreLink as button when onClick is provided and calls handler', async () => {
it('calls onClick for internal link', async () => {
const user = userEvent.setup();
const onClick = jest.fn();
const props = { ...defaultProps, moreLink: { id: 'more-link', title: 'More Info', onClick } };
const props = {
...defaultProps,
links: [
{
id: 'link-1',
title: 'Internal Link',
href: '/internal',
onClick,
},
],
};
renderWithProviders(<GettingStartedCard {...props} />);
await user.click(await screen.findByRole('button', { name: 'More Info' }));
expect(onClick).toHaveBeenCalledTimes(1);
await user.click(screen.getByTestId('item link-1'));
expect(onClick).toHaveBeenCalled();
});
it('calls onClick for internal link when clicked', async () => {
it('calls onClick for moreLink', async () => {
const user = userEvent.setup();
const onClick = jest.fn();
const props = {
...defaultProps,
links: [{ id: 'link-1', title: 'Internal Link', href: '/internal', onClick }],
moreLink: {
id: 'more-link',
title: 'More Info',
href: '/more',
onClick,
},
};
renderWithProviders(<GettingStartedCard {...props} />);
await user.click(await screen.findByTestId('item link-1'));
expect(onClick).toHaveBeenCalledTimes(1);
await user.click(screen.getByTestId('item more-link'));
expect(onClick).toHaveBeenCalled();
});
it('renders skeleton for loading links', async () => {
const props = { ...defaultProps, links: [{ id: 'loading-link', loading: true }] };
renderWithProviders(<GettingStartedCard {...props} />);
expect(await screen.findByTestId('getting-started-skeleton')).toBeVisible();
});
it('does not render links section when links array is empty', async () => {
const props = { ...defaultProps, links: [] };
renderWithProviders(<GettingStartedCard {...props} />);
await waitFor(() => {
expect(screen.queryByRole('list')).not.toBeInTheDocument();
});
});
it('does not render moreLink when not provided', async () => {
const props = { ...defaultProps, moreLink: undefined };
renderWithProviders(<GettingStartedCard {...props} />);
await waitFor(() => {
expect(screen.queryByRole('link', { name: 'More Info' })).not.toBeInTheDocument();
});
});
it('applies custom title color when provided', async () => {
const props = { ...defaultProps, titleColor: '#ff0000' };
it('renders skeleton for loading links', () => {
const props = {
...defaultProps,
links: [
{
id: 'loading-link',
loading: true,
},
],
};
renderWithProviders(<GettingStartedCard {...props} />);
expect(await screen.findByRole('heading', { name: 'Test Card' })).toHaveStyle({
color: '#ff0000',
});
expect(screen.getByTestId('getting-started-skeleton')).toBeInTheDocument();
});
});
describe('GettingStartedCard', () => {
const defaultProps: GettingStartedCardProps = {
id: 'test-card',
title: 'Test Card',
description: 'This is a test card.',
links: [
{
id: 'link-1',
title: 'Internal Link',
href: '/internal',
},
{
id: 'link-2',
title: 'External Link',
href: 'https://example.com',
external: true,
},
],
moreLink: {
id: 'more-link',
title: 'More Info',
href: '/more',
},
};
it('renders title and description', () => {
renderWithProviders(<GettingStartedCard {...defaultProps} />);
expect(screen.getByText('Test Card')).toBeInTheDocument();
expect(screen.getByText('This is a test card.')).toBeInTheDocument();
});
it('renders all links', () => {
renderWithProviders(<GettingStartedCard {...defaultProps} />);
expect(screen.getByTestId('item link-1')).toBeInTheDocument();
const external = screen.getByTestId('item link-2');
expect(external).toBeInTheDocument();
expect(external).toHaveAttribute('target', '_blank');
expect(external).toHaveAttribute('rel', expect.stringContaining('noopener'));
expect(external).toHaveAttribute('rel', expect.stringContaining('noreferrer'));
expect(screen.getByTestId('item more-link')).toBeInTheDocument();
});
it('calls onClick for internal link', async () => {
const user = userEvent.setup();
const onClick = jest.fn();
const props = {
...defaultProps,
links: [
{
id: 'link-1',
title: 'Internal Link',
href: '/internal',
onClick,
},
],
};
renderWithProviders(<GettingStartedCard {...props} />);
await user.click(screen.getByTestId('item link-1'));
expect(onClick).toHaveBeenCalled();
});
it('calls onClick for moreLink', async () => {
const user = userEvent.setup();
const onClick = jest.fn();
const props = {
...defaultProps,
moreLink: {
id: 'more-link',
title: 'More Info',
href: '/more',
onClick,
},
};
renderWithProviders(<GettingStartedCard {...props} />);
await user.click(screen.getByTestId('item more-link'));
expect(onClick).toHaveBeenCalled();
});
it('renders skeleton for loading links', () => {
const props = {
...defaultProps,
links: [
{
id: 'loading-link',
loading: true,
},
],
};
renderWithProviders(<GettingStartedCard {...props} />);
expect(screen.getByTestId('getting-started-skeleton')).toBeInTheDocument();
});
it('does not render list when links are empty', () => {
renderWithProviders(<GettingStartedCard {...defaultProps} links={[]} />);
expect(screen.queryByTestId('item link-1')).not.toBeInTheDocument();
});
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/packages/console-shared/src/components/getting-started/__tests__/GettingStartedCard.spec.tsx`
around lines 7 - 94, Tests for GettingStartedCard lost coverage of key
behaviors: conditional rendering branches and external-link hardening
attributes. Update the test suite for GettingStartedCard to (1) assert that
external links (links with external: true) render rel="noopener noreferrer" and
target="_blank" (use the link with id 'link-2'), (2) add assertions for
conditional rendering paths such as when a link has loading: true vs has
href/onClick (use 'loading-link' and the existing onClick cases), and (3) verify
that internal vs external link markup differs as expected; reference test
utilities like renderWithProviders, screen.getByTestId('item link-2'),
screen.getByTestId('item loading-link'), and the moreLink case to locate
elements.

Comment on lines +15 to +21
Button: jest.fn(({ children, type, ...props }) =>
type === 'submit' ? (
<button type="submit" {...props}>
{children}
</button>
) : null,
),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Mocked Button hides the Cancel action and weakens test fidelity.

At Lines 15-21, non-submit PatternFly buttons are rendered as null. In frontend/packages/knative-plugin/src/components/traffic-splitting/TrafficSplittingModal.tsx (Lines 34-46), the modal renders both Save (type="submit") and Cancel (type="button"). This mock can mask regressions around modal actions and button presence.

Proposed fix
-  Button: jest.fn(({ children, type, ...props }) =>
-    type === 'submit' ? (
-      <button type="submit" {...props}>
-        {children}
-      </button>
-    ) : null,
-  ),
+  Button: jest.fn(({ children, type = 'button', ...props }) => (
+    <button type={type} {...props}>
+      {children}
+    </button>
+  )),
📝 Committable suggestion

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

Suggested change
Button: jest.fn(({ children, type, ...props }) =>
type === 'submit' ? (
<button type="submit" {...props}>
{children}
</button>
) : null,
),
Button: jest.fn(({ children, type = 'button', ...props }) => (
<button type={type} {...props}>
{children}
</button>
)),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/packages/knative-plugin/src/components/traffic-splitting/__tests__/TrafficSplittingModal.spec.tsx`
around lines 15 - 21, The mocked Button in TrafficSplittingModal.spec.tsx
currently returns null for non-submit types which hides the Cancel button from
tests; update the mock (the jest.fn for Button) to render a real <button> for
all types (or at least when type === 'button') preserving children and props so
Cancel/actions are present, e.g., return a button element that spreads props and
renders children; run the tests and update any assertions to check for the
Cancel button rendered by TrafficSplittingModal.tsx (the modal component that
renders Save type="submit" and Cancel type="button").

Comment on lines +21 to 31
const clickMultipleTimes = async (
user: ReturnType<typeof userEvent.setup>,
element: HTMLElement,
times: number,
) => {
for (let i = 0; i < times; i++) {
// eslint-disable-next-line no-await-in-loop
await act(async () => {
fireEvent.click(element);
await user.click(element);
});
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid repeated clicks on a potentially detached “Add new tab” element.

At Line 29, clickMultipleTimes keeps clicking the same captured node. In the callers (Line 89, Line 101), once max tabs are reached, the add button can disappear, and later user.click calls may target a detached element and fail.

🔧 Suggested fix
-const clickMultipleTimes = async (
-  user: ReturnType<typeof userEvent.setup>,
-  element: HTMLElement,
-  times: number,
-) => {
+const clickMultipleTimes = async (
+  user: ReturnType<typeof userEvent.setup>,
+  getElement: () => HTMLElement | null,
+  times: number,
+) => {
   for (let i = 0; i < times; i++) {
-    // eslint-disable-next-line no-await-in-loop
-    await act(async () => {
-      await user.click(element);
-    });
+    const element = getElement();
+    expect(element).toBeTruthy();
+    // eslint-disable-next-line no-await-in-loop
+    await user.click(element!);
   }
 };
@@
-const addTerminalButton = multiTabTerminalWrapper.getByLabelText('Add new tab');
-await clickMultipleTimes(user, addTerminalButton, 8);
+await clickMultipleTimes(
+  user,
+  () => multiTabTerminalWrapper.queryByLabelText('Add new tab'),
+  7,
+);
@@
-const addTerminalButton = multiTabTerminalWrapper.getByLabelText('Add new tab');
-await clickMultipleTimes(user, addTerminalButton, 8);
+await clickMultipleTimes(
+  user,
+  () => multiTabTerminalWrapper.queryByLabelText('Add new tab'),
+  7,
+);

Also applies to: 89-89, 101-101

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

In
`@frontend/packages/webterminal-plugin/src/components/cloud-shell/__tests__/MultiTabbedTerminal.spec.tsx`
around lines 21 - 31, The helper clickMultipleTimes currently captures a single
HTMLElement and repeatedly clicks it, which can target a detached node when the
"Add new tab" button disappears; update clickMultipleTimes to accept a getter
(e.g., () => HTMLElement | null) or a selector and, inside the loop, query the
current element each iteration (using the provided getter or DOM query) and bail
out if it returns null, then perform the await act(() =>
user.click(currentElement)) only when the element is present; update callers in
the tests to pass a fresh-querying getter (or selector) instead of a captured
element so clicks stop gracefully when the button is removed.

@cajieh cajieh force-pushed the refactor-rtl-ests-fire-event-user-event branch from 2b14b5d to 3a0f805 Compare April 16, 2026 20:58
@cajieh
Copy link
Copy Markdown
Contributor Author

cajieh commented Apr 17, 2026

/test e2e-gcp-console

@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci bot commented Apr 17, 2026

@cajieh: all tests passed!

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

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

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. component/core Related to console core functionality component/dev-console Related to dev-console component/knative Related to knative-plugin component/olm Related to OLM component/shared Related to console-shared component/topology Related to topology jira/valid-reference Indicates that this PR references a valid Jira ticket of any type.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants