Skip to content

ACM-31937 Node pool dropdown has misaligned selection indicator and clear button#6091

Open
oksanabaza wants to merge 1 commit intostolostron:mainfrom
oksanabaza:ACM-31937
Open

ACM-31937 Node pool dropdown has misaligned selection indicator and clear button#6091
oksanabaza wants to merge 1 commit intostolostron:mainfrom
oksanabaza:ACM-31937

Conversation

@oksanabaza
Copy link
Copy Markdown
Contributor

@oksanabaza oksanabaza commented May 5, 2026

📝 Summary

Ticket Summary (Title):

ACM-31937 Node pool dropdown has misaligned selection indicator and clear button

Ticket Link:

https://redhat.atlassian.net/browse/ACM-31937

Type of Change:

  • 🐞 Bug Fix
  • ✨ Feature
  • 🔧 Refactor
  • 💸 Tech Debt
  • 🧪 Test-related
  • 📄 Docs

✅ Checklist

General

  • PR title follows the convention (e.g. ACM-12340 Fix bug with...)
  • Code builds and runs locally without errors
  • No console logs, commented-out code, or unnecessary files
  • All commits are meaningful and well-labeled
  • All new display strings are externalized for localization (English only)
  • (Nice to have) JSDoc comments added for new functions and interfaces

If Feature

  • UI/UX reviewed (if applicable)
  • All acceptance criteria met
  • Unit test coverage added or updated
  • Relevant documentation or comments included

If Bugfix

  • Root cause and fix summary are documented in the ticket (for future reference / errata)
  • Fix tested thoroughly and resolves the issue
  • Test(s) added to prevent regression

🗒️ Notes for Reviewers

Summary by CodeRabbit

Release Notes

  • New Features
    • GitHub URL, Helm Repository URL, and ObjectStore URL combobox fields now accept custom user-created values alongside available predefined options.
    • Improved combobox control interactions with enhanced keyboard navigation support and dedicated input commit behavior via Enter key.

…lacing with AcmSelectBase

Signed-off-by: Oksana Bazylieva <obazylie@redhat.com>
@openshift-ci
Copy link
Copy Markdown

openshift-ci Bot commented May 5, 2026

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: oksanabaza

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 label May 5, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

📝 Walkthrough

Walkthrough

This PR refactors combobox components and updates their test suites to support creatable (user-typed) values and improved input commit handling. Core changes include new onTypeaheadInputCommit callback and inputProps API in AcmSelectBase, a functional refactor of ControlPanelComboBox, and enabling creatable: true on Git/Helm/ObjectStore URL controls.

Changes

Combobox Creatable & Commit Flow Enhancement

Layer / File(s) Summary
Component API
frontend/src/components/AcmSelectBase.tsx
Add inputProps, onTypeaheadInputCommit props; introduce skipBlurCommitRef to prevent unwanted blur commits; route Enter/Tab actions through commitTypeaheadInput callback; wire blur handler to conditionally commit typed input; capture description from child options.
Core Refactoring
frontend/src/components/TemplateEditor/controls/ControlPanelComboBox.js
Convert class component to functional component using AcmSelectBase. Implement onChange handler to detect custom (non-list) typed values, deduplicate options from userData + available, reverse-map values via availableMap, and call handleControlChange() only when active value changes. Simplify error/loading UI.
Component Integration
frontend/src/components/TemplateEditor/controls/ControlPanelMultiSelect.js, frontend/src/components/TemplateEditor/controls/ControlPanelSingleSelect.tsx
Move data-testid from component props into inputProps parameter for consistency with AcmSelectBase input attachment.
Control Configuration
frontend/src/routes/Applications/CreateSubscriptionApplication/controlData/ControlDataGit.js, ControlDataHelm.js, ControlDataObjectStore.js
Enable creatable: true on githubURL, helmURL, and objectstoreURL combobox controls to allow user-entered custom values.
Test Coverage & Interaction Updates
frontend/src/components/TemplateEditor/controls/ControlPanelComboBox.test.js, TemplateEditor.test.js, SubscriptionApplication.test.tsx, CreateClusterPool.test.tsx, CreateCluster.test.tsx, OverviewClusterLabelSelector.test.tsx
Rewrite ControlPanelComboBox test suite with menu-toggle and role-based option selection; introduce commitComboBoxByTestId() helper across subscription/cluster creation tests; update selector strategies from label-based to test-id/role-based queries for combobox interactions.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately reflects the main change: migrating ControlPanelComboBox from PatternFly 5 to PatternFly 6 to fix visual alignment issues with the selection indicator and clear button.
Description check ✅ Passed The PR description follows the template structure with ticket summary, link, type of change, and completed checklist items for a bug fix.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

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

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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

❤️ Share

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

Copy link
Copy Markdown

@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/src/routes/Home/Overview/OverviewClusterLabelSelector.test.tsx (1)

75-76: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Index-based getAllByText chip assertions are brittle.

getAllByText('Amazon')[1] hard-codes the assumption that exactly two 'Amazon' nodes exist in the DOM at assertion time (e.g., one in the dropdown trigger and one in the chip). If AcmSelectBase (the new PF6 control) does not surface the selected value in the trigger button, there will only be one node, and [1] will be undefinedexpect(undefined).toBeTruthy() throws rather than passing, making the failure message misleading. Conversely, if more nodes exist the index shifts silently.

Prefer scoping the assertion to the chip container:

♻️ Suggested fix
-    await waitFor(() => expect(getAllByText('cloud')[0]).toBeTruthy())
-    await waitFor(() => expect(getAllByText('Amazon')[1]).toBeTruthy())
+    // Chip group labels render inside a labelled region; scope assertions there
+    await waitFor(() => expect(screen.getByRole('group', { name: 'cloud' })).toBeTruthy())
+    await waitFor(() => expect(screen.getByText('Amazon', { selector: '.pf-v6-c-chip__text, [class*="chipText"]' })).toBeTruthy())

Or, more portably, assert via chip role/label without relying on DOM order:

-    await waitFor(() => expect(getAllByText('cloud')[0]).toBeTruthy())
-    await waitFor(() => expect(getAllByText('Amazon')[1]).toBeTruthy())
+    await waitFor(() => expect(screen.getAllByText('cloud').length).toBeGreaterThanOrEqual(1))
+    await waitFor(() => expect(screen.getAllByText('Amazon').length).toBeGreaterThanOrEqual(1))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/routes/Home/Overview/OverviewClusterLabelSelector.test.tsx`
around lines 75 - 76, The assertions using index-based getAllByText (e.g.,
getAllByText('Amazon')[1] and getAllByText('cloud')[0]) are brittle; change them
to scope the query to the chip container or assert by role/label instead: locate
the chip container rendered by the component (or add/use an existing test
id/class) and replace getAllByText(...)[i] with
within(chipContainer).getByText('Amazon') (or use getByRole/getByLabelText for
the chip/button), so the test no longer depends on DOM order or whether
AcmSelectBase surfaces the selected value in the trigger. Ensure you update the
test to reference the chip container variable and remove index-based array
accesses.
🧹 Nitpick comments (10)
frontend/src/routes/Applications/CreateSubscriptionApplication/SubscriptionApplication.test.tsx (1)

422-426: ⚡ Quick win

Extract commitComboBoxByTestId to lib/test-util.ts to eliminate duplication across three test files.

The helper is defined identically in CreateCluster.test.tsx, CreateClusterPool.test.tsx, and SubscriptionApplication.test.tsx. Extract once to prevent divergence and improve maintainability.

♻️ Suggested extraction

In frontend/src/lib/test-util.ts, add:

+export const commitComboBoxByTestId = async (id: string, value: string): Promise<void> => {
+  const input = await screen.findByTestId(id)
+  fireEvent.change(input, { target: { value } })
+  fireEvent.keyDown(input, { key: 'Enter', code: 'Enter', charCode: 13 })
+}

Then remove the local definition from each test file and import from '../../../lib/test-util' (adjust path per file).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/src/routes/Applications/CreateSubscriptionApplication/SubscriptionApplication.test.tsx`
around lines 422 - 426, Extract the duplicate commitComboBoxByTestId helper into
a shared module: create frontend/src/lib/test-util.ts exporting an async
function named commitComboBoxByTestId(id: string, value: string) that performs
screen.findByTestId, fireEvent.change and fireEvent.keyDown as in the diff; then
remove the local commitComboBoxByTestId definitions from CreateCluster.test.tsx,
CreateClusterPool.test.tsx and SubscriptionApplication.test.tsx and import the
helper (e.g. import { commitComboBoxByTestId } from '../../../lib/test-util') in
each test file so all three tests use the single shared implementation.
frontend/src/components/TemplateEditor/controls/ControlPanelComboBox.test.js (3)

115-117: ⚡ Quick win

screen.getAllByRole('combobox') + .find(el => el.type === 'text') is fragile.

MenuToggle itself has role="combobox" (see AcmSelectBase.tsx Line 642), so getAllByRole('combobox') returns both the toggle and the input. The fallback inputs[0] may pick the toggle, and userEvent.type on the toggle won't filter options. Prefer screen.getByTestId('masterType') (the input now carries data-testid={controlId} via inputProps) for an unambiguous target.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/TemplateEditor/controls/ControlPanelComboBox.test.js`
around lines 115 - 117, Replace the fragile selector that calls
screen.getAllByRole('combobox') and then finds by element.type with a
deterministic test-id lookup: stop using inputs.find((el) => el.type === 'text')
|| inputs[0] and instead use screen.getByTestId('masterType') (the input
receives data-testid via inputProps/controlId) as the target for userEvent.type;
this avoids hitting the MenuToggle role="combobox" element from AcmSelectBase
and ensures userEvent.type is invoked on the actual text input.

189-214: 💤 Low value

Asserting on a mutated control.active couples tests to internal mutation.

expect(control.active).toBe('test-path') works only because ControlPanelComboBox.onChange mutates the prop object directly (Line 46 in ControlPanelComboBox.js). That's the implementation reality, but the test's intent — "Enter commits the typed value" — is more robustly expressed via handleControlChange arguments / call order, not mutation side-effects on the input object. Consider asserting via handleChange invocation with the expected value, which would survive a future refactor that stops mutating control in place.

As per coding guidelines, "Verify test coverage is meaningful, not just for metrics" and "Check that tests actually test the described behavior."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/TemplateEditor/controls/ControlPanelComboBox.test.js`
around lines 189 - 214, The test currently asserts on mutated control.active
(coupling to internal mutation); instead assert that ControlPanelComboBox's
onChange invoked the provided handleControlChange with the committed value —
replace the control.active expectation with assertions on the mock handleChange
(e.g. that handleChange was called and that one of its calls includes an object
containing id: 'githubPath' and active: 'test-path' or the expected payload
shape), referencing ControlPanelComboBox and its onChange/handleControlChange
behavior so the spec verifies the outward contract rather than in-place
mutation.

125-135: ⚡ Quick win

'clears selection' does not open the dropdown but still finds a clear button — verify it's the right one.

screen.getByRole('button', { name: /clear/i }) is a loose regex; it can match anything whose accessible name contains "clear" (e.g. menu items in other tests). Here it works because propsPlain.control.active === 'm5.xlarge' causes the clear-input button (aria-label="Clear input value") to render, but the regex isn't anchored. Tighten to name: /clear input value/i to ensure the assertion targets the intended control and won't falsely match a future addition.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/TemplateEditor/controls/ControlPanelComboBox.test.js`
around lines 125 - 135, The test "'clears selection'" in
ControlPanelComboBox.test.js is using a loose matcher for the clear button;
update the call to screen.getByRole in that test to target the specific
clear-input control by using the exact accessible name (e.g. change the matcher
from /clear/i to /clear input value/i) so it reliably selects the Clear input
value button rendered by ControlPanelComboBox (propsPlain, control.active) and
avoids accidentally matching unrelated menu items; keep the rest of the test
(render, userEvent.click, waitFor expect(handleChange)) the same.
frontend/src/components/AcmSelectBase.tsx (1)

296-303: 💤 Low value

commitTypeaheadInput uses stale inputValue as fallback.

Callers always pass event.currentTarget.value, so the ?? inputValue branch is effectively unreachable on the current call sites. Either make value required to remove the dead fallback, or document when the fallback applies. As-is, a future caller calling commitTypeaheadInput() with no argument could read a stale inputValue (closure captures it via useCallback deps, but only one render behind in racey scenarios).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/AcmSelectBase.tsx` around lines 296 - 303,
commitTypeaheadInput currently falls back to a potentially-stale inputValue,
which is dead code for current callers; make the value parameter required and
remove the fallback and inputValue capture: change commitTypeaheadInput to
(value: string) => { if (!onTypeaheadInputCommit) return;
onTypeaheadInputCommit(value); closeMenu(); }, remove inputValue from the
useCallback dependencies, and update all callers to always pass
event.currentTarget.value; alternatively, if you need a no-arg call path keep
the fallback but replace inputValue with a mutable ref (e.g.,
inputValueRef.current) to avoid stale closures.
frontend/src/routes/Infrastructure/Clusters/ManagedClusters/CreateCluster/CreateCluster.test.tsx (2)

3508-3511: 💤 Low value

Brittle PatternFly v6 class selector.

Targeting the clear button via inputElement.closest('.pf-v6-c-menu-toggle') couples the test to PatternFly's internal v6 class name. Any PF major bump silently breaks these tests with a confusing null.getByRole error (the cast as HTMLElement will not catch a null closest result). Prefer scoping by a stable selector — e.g., scoping within to the toggle button found via getByRole('button', { name: /menu toggle/i }) or to the container of the input's data-testid.

Also applies to: 4062-4065

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/src/routes/Infrastructure/Clusters/ManagedClusters/CreateCluster/CreateCluster.test.tsx`
around lines 3508 - 3511, The test is brittle because it uses
inputElement.closest('.pf-v6-c-menu-toggle') which couples to PatternFly v6 CSS;
replace that lookup with a stable role-based or testid-based scope: locate the
menu toggle using getByRole('button', { name: /menu toggle/i }) or the input
container via its data-testid, then call within(...) on that element to get the
clear button and click it (update the lines that set clearButton and the
within(...) call where inputElement.closest('.pf-v6-c-menu-toggle') is used).

89-93: 💤 Low value

Helper bypasses React state by using fireEvent.change.

fireEvent.change synchronously sets value without going through userEvent's realistic input pipeline. Combined with the immediate keyDown(Enter), this works because AcmSelectBase's onInputKeyDown reads event.currentTarget.value, but it skips the controlled onChange flow (so inputValue state may not update before commit). It works today, but is fragile if AcmSelectBase later relies on inputValue state inside commitTypeaheadInput. Consider userEvent.type(input, '${value}{enter}') for parity with the other KubeVirt tests in this file.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/src/routes/Infrastructure/Clusters/ManagedClusters/CreateCluster/CreateCluster.test.tsx`
around lines 89 - 93, The helper commitComboBoxByTestId bypasses React's
controlled input flow by using fireEvent.change plus fireEvent.keyDown; update
it to simulate real user input using userEvent.type so the component's
onChange/onInput handlers and inputValue state update before committing—replace
the fireEvent.change + fireEvent.keyDown sequence in commitComboBoxByTestId with
userEvent.type(input, `${value}{enter}`) (keeping the same test-id lookup) to
mirror other KubeVirt tests and ensure AcmSelectBase and its
commitTypeaheadInput see the updated state.
frontend/src/components/TemplateEditor/controls/ControlPanelComboBox.js (3)

56-60: 💤 Low value

control.active can desync from lastActive when a custom value equals current lastActive.

In the custom branch (Line 46) control.active is set to typedValue unconditionally, but the trailing assignment to control.active = mappedValue only runs when lastActive !== mappedValue. If mappedValue === lastActive (no-op path) and typedValue === mappedValue, the result is fine; but the two writes to control.active in different branches make the final state hard to reason about. Collapsing to a single write at the end (after computing mappedValue) avoids ambiguity.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/TemplateEditor/controls/ControlPanelComboBox.js`
around lines 56 - 60, Compute the final mappedValue first (using typedValue for
the custom branch) and collapse the writes so you only assign control.active =
mappedValue once; then if control.lastActive !== mappedValue update
control.lastActive = mappedValue and call handleControlChange(). In other words,
remove the unconditional control.active = typedValue in the custom branch,
determine mappedValue, set control.active = mappedValue, and only update
lastActive + invoke handleControlChange when lastActive actually changes
(referencing control.active, control.lastActive, mappedValue, typedValue, and
handleControlChange).

87-87: 💤 Low value

key={controlId}-${name} will remount AcmSelectBase on every i18n / name change.

If name is a localized string (e.g., 'Instance type'), language switching forces a full remount of the select, dropping focus and inputValue mid-interaction. controlId alone should be sufficiently unique.

Details
-  const key = `${controlId}-${name}`
+  const key = controlId

Also applies to: 99-119

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/TemplateEditor/controls/ControlPanelComboBox.js` at
line 87, The key currently built as `${controlId}-${name}` causes AcmSelectBase
to remount whenever the localized `name` changes (e.g., on i18n switch), losing
focus and input; change the key construction to use only the stable identifier
`controlId` wherever `${controlId}-${name}` is used (including the occurrence
around AcmSelectBase and the repeated spots referenced at lines 99-119) so the
select is not remounted on name/i18n updates.

65-71: 💤 Low value

uniqueAvailable and activeDisplay deps look correct, but availableMap reference identity matters.

availableMap is read off control and used in useMemo deps. If control.availableMap is rebuilt on every parent render (e.g., via fetchAvailable.setAvailableMap), activeDisplay will recompute even when content is unchanged. Acceptable cost given how small the map is, but worth being aware of for hot paths.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/TemplateEditor/controls/ControlPanelComboBox.js`
around lines 65 - 71, The review notes that using control.availableMap
(availableMap) as a dependency can cause unnecessary recomputations if its
reference changes even when contents are equal; update the memo usage to
stabilize identity or compare contents: inside the uniqueAvailable/activeDisplay
logic (useMemo for uniqueAvailable and activeDisplay) either derive availableMap
from a stable source on control or compute a shallow key-based fingerprint
(e.g., JSON.stringify(Object.keys(availableMap).sort()) or similar) and use that
fingerprint in the dependency array so activeDisplay only recomputes when the
actual map contents change; ensure you update the dependency arrays for
activeDisplay (and uniqueAvailable if relevant) to reference the stable value
(the fingerprint or stable map) rather than the raw availableMap reference.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/src/components/AcmSelectBase.tsx`:
- Around line 370-374: When handling keyboard commits for typeahead in
AcmSelectBase, add the same blur-commit guard used in selectOption and mouse
handlers by setting skipBlurCommitRef.current = true immediately before calling
commitTypeaheadInput in both the Tab branch (current case 'Tab') and the Enter
branch's typeahead else-branch (the branch that checks onTypeaheadInputCommit
and inputValue !== selectedItem); this prevents the blur handler from invoking
commitTypeaheadInput a second time when the input loses focus.

In `@frontend/src/components/TemplateEditor/controls/ControlPanelComboBox.js`:
- Around line 37-63: The custom-value detection in the onChange callback (used
by ControlPanelComboBox) only checks against available (display labels) and
control.userData, so values that match an availableMap value slip through;
update the isCustomValue logic to also treat any string present in
Object.values(control.availableMap || {}) as non-custom (i.e., compute a
set/array that is [...(control.userData||[]), ...available,
...Object.values(control.availableMap||{})] and use that when computing
isCustomValue) so we don't append mapped values to control.userData or create
raw-value options; keep the rest of the flow (setting control.active,
lastActive, calling get(control, 'fetchAvailable.setAvailableMap') and
handleControlChange) unchanged.

In
`@frontend/src/components/TemplateEditor/controls/ControlPanelComboBox.test.js`:
- Around line 238-252: The test "displays full value from short active value"
currently only opens the dropdown and asserts an option exists (from available)
so update it to verify the component's rendered display uses the activeDisplay
conversion: render ControlPanelComboBox with control.active = 'm5.xlarge' and
assert the toggle/input element (the element returned by
screen.getByRole('button', { name: /menu toggle/i }) or the visible input)
contains the full label string produced by activeDisplay (the same string used
in the option: 'm5.xlarge - 4 vCPU, 16 GiB RAM - General Purpose');
alternatively if you prefer not to assert UI text, rename the test to reflect
that it only validates the dropdown contains the option. Ensure references to
activeDisplay and ControlPanelComboBox are used so the change targets the
conversion behavior.

In `@frontend/src/components/TemplateEditor/TemplateEditor.test.js`:
- Around line 85-86: The test uses userEvent.type(...) for combobox fields
githubURL and githubPath which fires per-character events but no trailing Enter
so onTypeaheadInputCommit never runs; replace those userEvent.type calls with
the commitComboBoxByTestId pattern used elsewhere: simulate
fireEvent.change(...) to set the input value for test ids 'githubURL' and
'githubPath' and then simulate fireEvent.keyDown(element, { key: 'Enter' }) to
commit—ensuring onTypeaheadInputCommit is invoked and the form state is actually
updated.

---

Outside diff comments:
In `@frontend/src/routes/Home/Overview/OverviewClusterLabelSelector.test.tsx`:
- Around line 75-76: The assertions using index-based getAllByText (e.g.,
getAllByText('Amazon')[1] and getAllByText('cloud')[0]) are brittle; change them
to scope the query to the chip container or assert by role/label instead: locate
the chip container rendered by the component (or add/use an existing test
id/class) and replace getAllByText(...)[i] with
within(chipContainer).getByText('Amazon') (or use getByRole/getByLabelText for
the chip/button), so the test no longer depends on DOM order or whether
AcmSelectBase surfaces the selected value in the trigger. Ensure you update the
test to reference the chip container variable and remove index-based array
accesses.

---

Nitpick comments:
In `@frontend/src/components/AcmSelectBase.tsx`:
- Around line 296-303: commitTypeaheadInput currently falls back to a
potentially-stale inputValue, which is dead code for current callers; make the
value parameter required and remove the fallback and inputValue capture: change
commitTypeaheadInput to (value: string) => { if (!onTypeaheadInputCommit)
return; onTypeaheadInputCommit(value); closeMenu(); }, remove inputValue from
the useCallback dependencies, and update all callers to always pass
event.currentTarget.value; alternatively, if you need a no-arg call path keep
the fallback but replace inputValue with a mutable ref (e.g.,
inputValueRef.current) to avoid stale closures.

In `@frontend/src/components/TemplateEditor/controls/ControlPanelComboBox.js`:
- Around line 56-60: Compute the final mappedValue first (using typedValue for
the custom branch) and collapse the writes so you only assign control.active =
mappedValue once; then if control.lastActive !== mappedValue update
control.lastActive = mappedValue and call handleControlChange(). In other words,
remove the unconditional control.active = typedValue in the custom branch,
determine mappedValue, set control.active = mappedValue, and only update
lastActive + invoke handleControlChange when lastActive actually changes
(referencing control.active, control.lastActive, mappedValue, typedValue, and
handleControlChange).
- Line 87: The key currently built as `${controlId}-${name}` causes
AcmSelectBase to remount whenever the localized `name` changes (e.g., on i18n
switch), losing focus and input; change the key construction to use only the
stable identifier `controlId` wherever `${controlId}-${name}` is used (including
the occurrence around AcmSelectBase and the repeated spots referenced at lines
99-119) so the select is not remounted on name/i18n updates.
- Around line 65-71: The review notes that using control.availableMap
(availableMap) as a dependency can cause unnecessary recomputations if its
reference changes even when contents are equal; update the memo usage to
stabilize identity or compare contents: inside the uniqueAvailable/activeDisplay
logic (useMemo for uniqueAvailable and activeDisplay) either derive availableMap
from a stable source on control or compute a shallow key-based fingerprint
(e.g., JSON.stringify(Object.keys(availableMap).sort()) or similar) and use that
fingerprint in the dependency array so activeDisplay only recomputes when the
actual map contents change; ensure you update the dependency arrays for
activeDisplay (and uniqueAvailable if relevant) to reference the stable value
(the fingerprint or stable map) rather than the raw availableMap reference.

In
`@frontend/src/components/TemplateEditor/controls/ControlPanelComboBox.test.js`:
- Around line 115-117: Replace the fragile selector that calls
screen.getAllByRole('combobox') and then finds by element.type with a
deterministic test-id lookup: stop using inputs.find((el) => el.type === 'text')
|| inputs[0] and instead use screen.getByTestId('masterType') (the input
receives data-testid via inputProps/controlId) as the target for userEvent.type;
this avoids hitting the MenuToggle role="combobox" element from AcmSelectBase
and ensures userEvent.type is invoked on the actual text input.
- Around line 189-214: The test currently asserts on mutated control.active
(coupling to internal mutation); instead assert that ControlPanelComboBox's
onChange invoked the provided handleControlChange with the committed value —
replace the control.active expectation with assertions on the mock handleChange
(e.g. that handleChange was called and that one of its calls includes an object
containing id: 'githubPath' and active: 'test-path' or the expected payload
shape), referencing ControlPanelComboBox and its onChange/handleControlChange
behavior so the spec verifies the outward contract rather than in-place
mutation.
- Around line 125-135: The test "'clears selection'" in
ControlPanelComboBox.test.js is using a loose matcher for the clear button;
update the call to screen.getByRole in that test to target the specific
clear-input control by using the exact accessible name (e.g. change the matcher
from /clear/i to /clear input value/i) so it reliably selects the Clear input
value button rendered by ControlPanelComboBox (propsPlain, control.active) and
avoids accidentally matching unrelated menu items; keep the rest of the test
(render, userEvent.click, waitFor expect(handleChange)) the same.

In
`@frontend/src/routes/Applications/CreateSubscriptionApplication/SubscriptionApplication.test.tsx`:
- Around line 422-426: Extract the duplicate commitComboBoxByTestId helper into
a shared module: create frontend/src/lib/test-util.ts exporting an async
function named commitComboBoxByTestId(id: string, value: string) that performs
screen.findByTestId, fireEvent.change and fireEvent.keyDown as in the diff; then
remove the local commitComboBoxByTestId definitions from CreateCluster.test.tsx,
CreateClusterPool.test.tsx and SubscriptionApplication.test.tsx and import the
helper (e.g. import { commitComboBoxByTestId } from '../../../lib/test-util') in
each test file so all three tests use the single shared implementation.

In
`@frontend/src/routes/Infrastructure/Clusters/ManagedClusters/CreateCluster/CreateCluster.test.tsx`:
- Around line 3508-3511: The test is brittle because it uses
inputElement.closest('.pf-v6-c-menu-toggle') which couples to PatternFly v6 CSS;
replace that lookup with a stable role-based or testid-based scope: locate the
menu toggle using getByRole('button', { name: /menu toggle/i }) or the input
container via its data-testid, then call within(...) on that element to get the
clear button and click it (update the lines that set clearButton and the
within(...) call where inputElement.closest('.pf-v6-c-menu-toggle') is used).
- Around line 89-93: The helper commitComboBoxByTestId bypasses React's
controlled input flow by using fireEvent.change plus fireEvent.keyDown; update
it to simulate real user input using userEvent.type so the component's
onChange/onInput handlers and inputValue state update before committing—replace
the fireEvent.change + fireEvent.keyDown sequence in commitComboBoxByTestId with
userEvent.type(input, `${value}{enter}`) (keeping the same test-id lookup) to
mirror other KubeVirt tests and ensure AcmSelectBase and its
commitTypeaheadInput see the updated state.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 83de18d8-8465-4726-8ec5-7475251c3d6c

📥 Commits

Reviewing files that changed from the base of the PR and between 0c671d0 and 68a365d.

⛔ Files ignored due to path filters (2)
  • frontend/src/components/TemplateEditor/controls/__snapshots__/ControlPanelMultiSelect.test.js.snap is excluded by !**/*.snap, !**/*.snap
  • frontend/src/components/TemplateEditor/controls/__snapshots__/ControlPanelSingleSelect.test.js.snap is excluded by !**/*.snap, !**/*.snap
📒 Files selected for processing (13)
  • frontend/src/components/AcmSelectBase.tsx
  • frontend/src/components/TemplateEditor/TemplateEditor.test.js
  • frontend/src/components/TemplateEditor/controls/ControlPanelComboBox.js
  • frontend/src/components/TemplateEditor/controls/ControlPanelComboBox.test.js
  • frontend/src/components/TemplateEditor/controls/ControlPanelMultiSelect.js
  • frontend/src/components/TemplateEditor/controls/ControlPanelSingleSelect.tsx
  • frontend/src/routes/Applications/CreateSubscriptionApplication/SubscriptionApplication.test.tsx
  • frontend/src/routes/Applications/CreateSubscriptionApplication/controlData/ControlDataGit.js
  • frontend/src/routes/Applications/CreateSubscriptionApplication/controlData/ControlDataHelm.js
  • frontend/src/routes/Applications/CreateSubscriptionApplication/controlData/ControlDataObjectStore.js
  • frontend/src/routes/Home/Overview/OverviewClusterLabelSelector.test.tsx
  • frontend/src/routes/Infrastructure/Clusters/ClusterPools/CreateClusterPool/CreateClusterPool.test.tsx
  • frontend/src/routes/Infrastructure/Clusters/ManagedClusters/CreateCluster/CreateCluster.test.tsx

Comment on lines +370 to 374
case 'Tab':
if (onTypeaheadInputCommit && inputValue !== selectedItem) {
commitTypeaheadInput(event.currentTarget.value)
}
break
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

wc -l frontend/src/components/AcmSelectBase.tsx

Repository: stolostron/console

Length of output: 108


🏁 Script executed:

# First, let's look at the Tab handler context (around lines 360-380)
sed -n '355,385p' frontend/src/components/AcmSelectBase.tsx

Repository: stolostron/console

Length of output: 916


🏁 Script executed:

# Now look at the mouse-driven selection (around line 479)
sed -n '475,490p' frontend/src/components/AcmSelectBase.tsx

Repository: stolostron/console

Length of output: 511


🏁 Script executed:

# Find the blur handler to understand skipBlurCommitRef usage
rg -n "onBlur|skipBlurCommitRef" frontend/src/components/AcmSelectBase.tsx -A 2 -B 2

Repository: stolostron/console

Length of output: 1298


🏁 Script executed:

# Check the lines 707-715 mentioned in "Also applies to"
sed -n '705,720p' frontend/src/components/AcmSelectBase.tsx

Repository: stolostron/console

Length of output: 638


🏁 Script executed:

# Search for all Tab key handlers to check if there are multiple instances
rg -n "case 'Tab'" frontend/src/components/AcmSelectBase.tsx -A 5 -B 2

Repository: stolostron/console

Length of output: 300


🏁 Script executed:

# Search for all Enter key handlers to compare patterns
rg -n "case 'Enter'" frontend/src/components/AcmSelectBase.tsx -A 10 -B 2

Repository: stolostron/console

Length of output: 571


🏁 Script executed:

# Check the full commitTypeaheadInput function to understand what it does
rg -n "commitTypeaheadInput" frontend/src/components/AcmSelectBase.tsx -B 2 -A 8

Repository: stolostron/console

Length of output: 1472


Add skipBlurCommitRef.current = true guard before Tab commit to prevent double-commit on blur.

When Tab is pressed, commitTypeaheadInput runs but does not set skipBlurCommitRef.current = true. Tab causes the input to lose focus, triggering onBlur (lines 707-715); since the ref is not set, the blur handler evaluates its condition again and may call commitTypeaheadInput a second time with the same value due to async state updates.

The same gap exists in the Enter handler's typeahead branch (line 362). The fix mirrors patterns used in selectOption (line 479) and mouse handlers (lines 563, 681, 750).

Suggested fix
      case 'Tab':
        if (onTypeaheadInputCommit && inputValue !== selectedItem) {
+         skipBlurCommitRef.current = true
          commitTypeaheadInput(event.currentTarget.value)
        }
        break

Consider applying the same guard to the Enter handler's else branch (line 360–362).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/AcmSelectBase.tsx` around lines 370 - 374, When
handling keyboard commits for typeahead in AcmSelectBase, add the same
blur-commit guard used in selectOption and mouse handlers by setting
skipBlurCommitRef.current = true immediately before calling commitTypeaheadInput
in both the Tab branch (current case 'Tab') and the Enter branch's typeahead
else-branch (the branch that checks onTypeaheadInputCommit and inputValue !==
selectedItem); this prevents the blur handler from invoking commitTypeaheadInput
a second time when the input loses focus.

Comment on lines +37 to +63
const onChange = useCallback(
(value) => {
if (control.disabled) return

const typedValue = (value || '').trim()
const userData = control.userData ?? []
const isCustomValue = typedValue.length > 0 && !userData.includes(typedValue) && !available.includes(typedValue)

if (isCustomValue) {
control.active = typedValue
control.userData = uniq([...userData, typedValue])
get(control, 'fetchAvailable.setAvailableMap')?.(control)
}
}
const { active, available } = control
const { currentSelection } = state
let { isOpen, preselect, searchText } = state
const { isBlurred, typedText } = state
const setAvailableMap = get(control, 'fetchAvailable.setAvailableMap') || noop

/////////////////////////////////////////////////////////////
// search mode
if (searchText && searchText.length && !preselect) {
// nothing selected, filter list
if (currentSelection === undefined) {
if (isBlurred) {
const { userData = [] } = control
if (!userData.includes(searchText) && available && !available.includes(searchText)) {
control.active = searchText
userData.push(searchText)
set(control, 'userData', userData)
const mappedValue =
typedValue && typeof control.availableMap?.[typedValue] === 'string'
? control.availableMap[typedValue]
: typedValue

// make sure whatever user types in has an availableMap entry
setAvailableMap(control)
}
handleComboChange(searchText)
searchText = null
isOpen = false
} else {
isOpen = true
}
} else {
// handle change
handleComboChange(currentSelection)
isOpen = false
searchText = null
if (control.lastActive !== mappedValue) {
control.active = mappedValue
control.lastActive = mappedValue
handleControlChange()
}
} else if (currentSelection !== undefined) {
// handle change
handleComboChange(currentSelection)
searchText = null
isOpen = false
preselect = false
} else if (isBlurred && !preselect) {
handleComboChange(typedText || active)
isOpen = false
}
return {
active,
currentSelection: undefined,
isOpen,
isBlurred: false,
preselect,
searchText,
}
}

constructor(props) {
super(props)
this.state = {
isOpen: false,
isBlurred: false,
searchText: null,
sortToTop: null,
}
}

setControlRef = (ref) => {
this.controlRef = ref
}

setInputRef = (ref) => {
this.inputRef = ref
}

setMenuRef = (ref) => {
this.menuRef = ref
}

setClearRef = (ref) => {
this.clearRef = ref
}

setToggleRef = (ref) => {
this.toggleRef = ref
}

render() {
const { isOpen, searchText, sortToTop } = this.state
const { controlId, i18n, control, controlData } = this.props
const {
name,
userData = [],
availableMap,
exception,
hasReplacements,
isRefetching,
disabled,
simplified,
describe,
} = control
const { isLoading } = control
let { active, available = [], placeholder = '' } = control
if (!placeholder) {
},
[available, control, handleControlChange]
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Custom-value detection compares against display labels but ignores mapped values.

available typically holds display labels (e.g. 'OpenShift 4.8.0-fc.7-x86_64') while availableMap maps label → real value (e.g. 'quay.io/.../ocp-release:...'). When a caller commits a value that matches an availableMap value (not key) — which is exactly what commitComboBoxByTestId('imageSet', clusterImageSetAws.spec.releaseImage) does in CreateCluster.test.tsxisCustomValue is true, the release image is appended to control.userData, and an option with that raw URL appears in the dropdown.

Consider also excluding values present in Object.values(control.availableMap || {}) from the custom-value check:

Suggested guard
-      const isCustomValue = typedValue.length > 0 && !userData.includes(typedValue) && !available.includes(typedValue)
+      const mappedValues = control.availableMap ? Object.values(control.availableMap) : []
+      const isCustomValue =
+        typedValue.length > 0 &&
+        !userData.includes(typedValue) &&
+        !available.includes(typedValue) &&
+        !mappedValues.includes(typedValue)
📝 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
const onChange = useCallback(
(value) => {
if (control.disabled) return
const typedValue = (value || '').trim()
const userData = control.userData ?? []
const isCustomValue = typedValue.length > 0 && !userData.includes(typedValue) && !available.includes(typedValue)
if (isCustomValue) {
control.active = typedValue
control.userData = uniq([...userData, typedValue])
get(control, 'fetchAvailable.setAvailableMap')?.(control)
}
}
const { active, available } = control
const { currentSelection } = state
let { isOpen, preselect, searchText } = state
const { isBlurred, typedText } = state
const setAvailableMap = get(control, 'fetchAvailable.setAvailableMap') || noop
/////////////////////////////////////////////////////////////
// search mode
if (searchText && searchText.length && !preselect) {
// nothing selected, filter list
if (currentSelection === undefined) {
if (isBlurred) {
const { userData = [] } = control
if (!userData.includes(searchText) && available && !available.includes(searchText)) {
control.active = searchText
userData.push(searchText)
set(control, 'userData', userData)
const mappedValue =
typedValue && typeof control.availableMap?.[typedValue] === 'string'
? control.availableMap[typedValue]
: typedValue
// make sure whatever user types in has an availableMap entry
setAvailableMap(control)
}
handleComboChange(searchText)
searchText = null
isOpen = false
} else {
isOpen = true
}
} else {
// handle change
handleComboChange(currentSelection)
isOpen = false
searchText = null
if (control.lastActive !== mappedValue) {
control.active = mappedValue
control.lastActive = mappedValue
handleControlChange()
}
} else if (currentSelection !== undefined) {
// handle change
handleComboChange(currentSelection)
searchText = null
isOpen = false
preselect = false
} else if (isBlurred && !preselect) {
handleComboChange(typedText || active)
isOpen = false
}
return {
active,
currentSelection: undefined,
isOpen,
isBlurred: false,
preselect,
searchText,
}
}
constructor(props) {
super(props)
this.state = {
isOpen: false,
isBlurred: false,
searchText: null,
sortToTop: null,
}
}
setControlRef = (ref) => {
this.controlRef = ref
}
setInputRef = (ref) => {
this.inputRef = ref
}
setMenuRef = (ref) => {
this.menuRef = ref
}
setClearRef = (ref) => {
this.clearRef = ref
}
setToggleRef = (ref) => {
this.toggleRef = ref
}
render() {
const { isOpen, searchText, sortToTop } = this.state
const { controlId, i18n, control, controlData } = this.props
const {
name,
userData = [],
availableMap,
exception,
hasReplacements,
isRefetching,
disabled,
simplified,
describe,
} = control
const { isLoading } = control
let { active, available = [], placeholder = '' } = control
if (!placeholder) {
},
[available, control, handleControlChange]
)
const onChange = useCallback(
(value) => {
if (control.disabled) return
const typedValue = (value || '').trim()
const userData = control.userData ?? []
const mappedValues = control.availableMap ? Object.values(control.availableMap) : []
const isCustomValue =
typedValue.length > 0 &&
!userData.includes(typedValue) &&
!available.includes(typedValue) &&
!mappedValues.includes(typedValue)
if (isCustomValue) {
control.active = typedValue
control.userData = uniq([...userData, typedValue])
get(control, 'fetchAvailable.setAvailableMap')?.(control)
}
const mappedValue =
typedValue && typeof control.availableMap?.[typedValue] === 'string'
? control.availableMap[typedValue]
: typedValue
if (control.lastActive !== mappedValue) {
control.active = mappedValue
control.lastActive = mappedValue
handleControlChange()
}
},
[available, control, handleControlChange]
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/TemplateEditor/controls/ControlPanelComboBox.js`
around lines 37 - 63, The custom-value detection in the onChange callback (used
by ControlPanelComboBox) only checks against available (display labels) and
control.userData, so values that match an availableMap value slip through;
update the isCustomValue logic to also treat any string present in
Object.values(control.availableMap || {}) as non-custom (i.e., compute a
set/array that is [...(control.userData||[]), ...available,
...Object.values(control.availableMap||{})] and use that when computing
isCustomValue) so we don't append mapped values to control.userData or create
raw-value options; keep the rest of the flow (setting control.active,
lastActive, calling get(control, 'fetchAvailable.setAvailableMap') and
handleControlChange) unchanged.

Comment on lines +238 to +252
it('displays full value from short active value', async () => {
const control = {
...propsPlain.control,
active: 'm5.xlarge',
}
render(<ControlPanelComboBox {...propsPlain} control={control} />)

const toggle = screen.getByRole('button', { name: /menu toggle/i })
userEvent.click(toggle)

await waitFor(() => {
const option = screen.getByText('m5.xlarge - 4 vCPU, 16 GiB RAM - General Purpose')
expect(option).toBeInTheDocument()
})
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Test name promises more than it asserts.

'displays full value from short active value' only opens the dropdown and confirms the matching option exists. That option is rendered for any active value because options come from available, not from active. This doesn't actually verify the short-to-full conversion done by activeDisplay (Lines 68-71 in ControlPanelComboBox.js). Either assert on the toggle/input's displayed text matching the full label, or rename the test.

As per coding guidelines, "Before claiming what the code under test does or allows, confirm it from the actual source ...; do not infer behavior from test names or comments alone."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/TemplateEditor/controls/ControlPanelComboBox.test.js`
around lines 238 - 252, The test "displays full value from short active value"
currently only opens the dropdown and asserts an option exists (from available)
so update it to verify the component's rendered display uses the activeDisplay
conversion: render ControlPanelComboBox with control.active = 'm5.xlarge' and
assert the toggle/input element (the element returned by
screen.getByRole('button', { name: /menu toggle/i }) or the visible input)
contains the full label string produced by activeDisplay (the same string used
in the option: 'm5.xlarge - 4 vCPU, 16 GiB RAM - General Purpose');
alternatively if you prefer not to assert UI text, rename the test to reflect
that it only validates the dropdown contains the option. Ensure references to
activeDisplay and ControlPanelComboBox are used so the change targets the
conversion behavior.

Comment on lines +85 to +86
userEvent.type(screen.getByTestId('githubURL'), 'https://github.com/fxiang1/app-samples')
userEvent.type(screen.getByTestId('githubPath'), 'ansible')
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

# First, locate and examine the test file
fd -type f "TemplateEditor.test.js" --exec wc -l {} \;

Repository: stolostron/console

Length of output: 233


🏁 Script executed:

# Get the file location
fd -type f "TemplateEditor.test.js" | head -5

Repository: stolostron/console

Length of output: 233


🏁 Script executed:

# Read the context around lines 85-86
fd -type f "TemplateEditor.test.js" -exec cat -n {} \; | sed -n '75,100p'

Repository: stolostron/console

Length of output: 233


🏁 Script executed:

# Check the full test to understand assertions
fd -type f "TemplateEditor.test.js" -exec wc -l {} \;

Repository: stolostron/console

Length of output: 233


🏁 Script executed:

# Search for SubscriptionApplication.test.tsx and the commitComboBoxByTestId helper
fd -type f "SubscriptionApplication.test.*" | head -1

Repository: stolostron/console

Length of output: 233


🏁 Script executed:

# Look for commitComboBoxByTestId definition
rg "commitComboBoxByTestId" -A 5 -B 2

Repository: stolostron/console

Length of output: 13994


🏁 Script executed:

# Check typeahead/combobox component implementations
fd -type f -name "*ComboBox*" -o -name "*Typeahead*" -o -name "*AcmSelect*" | grep -v node_modules | head -10

Repository: stolostron/console

Length of output: 233


🏁 Script executed:

# Search for onTypeaheadInputCommit usage
rg "onTypeaheadInputCommit" -B 3 -A 3

Repository: stolostron/console

Length of output: 4379


🏁 Script executed:

# Find TemplateEditor.test.js
find . -name "TemplateEditor.test.js" -type f

Repository: stolostron/console

Length of output: 126


🏁 Script executed:

# Read the full TemplateEditor.test.js file to understand context and assertions
find . -name "TemplateEditor.test.js" -type f -exec cat -n {} \;

Repository: stolostron/console

Length of output: 19457


Align userEvent.type() with the commitComboBoxByTestId pattern for combobox fields.

userEvent.type() fires per-character key events but does not emit a trailing Enter, so onTypeaheadInputCommit is never triggered. Since this test lacks assertions validating the submitted values, it may pass without actually committing githubURL and githubPath to form state. SubscriptionApplication.test.tsx, CreateCluster.test.tsx, and CreateClusterPool.test.tsx consistently use fireEvent.change() + fireEvent.keyDown(Enter) for these fields.

🔧 Suggested fix
-    userEvent.type(screen.getByTestId('githubURL'), 'https://github.com/fxiang1/app-samples')
-    userEvent.type(screen.getByTestId('githubPath'), 'ansible')
+    const githubURLInput = screen.getByTestId('githubURL')
+    fireEvent.change(githubURLInput, { target: { value: 'https://github.com/fxiang1/app-samples' } })
+    fireEvent.keyDown(githubURLInput, { key: 'Enter', code: 'Enter' })
+    const githubPathInput = screen.getByTestId('githubPath')
+    fireEvent.change(githubPathInput, { target: { value: 'ansible' } })
+    fireEvent.keyDown(githubPathInput, { key: 'Enter', code: 'Enter' })
📝 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
userEvent.type(screen.getByTestId('githubURL'), 'https://github.com/fxiang1/app-samples')
userEvent.type(screen.getByTestId('githubPath'), 'ansible')
const githubURLInput = screen.getByTestId('githubURL')
fireEvent.change(githubURLInput, { target: { value: 'https://github.com/fxiang1/app-samples' } })
fireEvent.keyDown(githubURLInput, { key: 'Enter', code: 'Enter' })
const githubPathInput = screen.getByTestId('githubPath')
fireEvent.change(githubPathInput, { target: { value: 'ansible' } })
fireEvent.keyDown(githubPathInput, { key: 'Enter', code: 'Enter' })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/TemplateEditor/TemplateEditor.test.js` around lines
85 - 86, The test uses userEvent.type(...) for combobox fields githubURL and
githubPath which fires per-character events but no trailing Enter so
onTypeaheadInputCommit never runs; replace those userEvent.type calls with the
commitComboBoxByTestId pattern used elsewhere: simulate fireEvent.change(...) to
set the input value for test ids 'githubURL' and 'githubPath' and then simulate
fireEvent.keyDown(element, { key: 'Enter' }) to commit—ensuring
onTypeaheadInputCommit is invoked and the form state is actually updated.

@oksanabaza oksanabaza requested a review from jeswanke May 5, 2026 16:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant