Skip to content

fix(select): handle native select when value doesn't match any option#3971

Merged
Befkadu1 merged 2 commits intomainfrom
fix/native-select-empty-option-not-selectable
Mar 25, 2026
Merged

fix(select): handle native select when value doesn't match any option#3971
Befkadu1 merged 2 commits intomainfrom
fix/native-select-empty-option-not-selectable

Conversation

@Befkadu1
Copy link
Copy Markdown
Contributor

@Befkadu1 Befkadu1 commented Mar 23, 2026

Summary

  • On mobile devices, when a required option field has an empty option that gets filtered out by the schema, the limel-select component's value still points to that removed option
  • The native <select> then has no matching selected option, causing the browser to default-highlight the first visible option. Clicking that first option doesn't fire a change event (the browser considers it already selected), forcing users to pick an intermediate option first
  • Updated emitFirstChangeEvent() to detect when the current value doesn't match any available option, treating it as "no value" and auto-emitting the first visible option when the native picker opens

Closes Lundalogik/crm-client#420

Test coverage

Scenario Test Expects
Stale value (doesn't match any option) emits the first option when value does not match any option Emits first option
value={null} emits the first option when value is null Emits first option
value={undefined} emits the first option when value is undefined Emits first option
Value matches an option does not emit when value matches an available option No emission
First option is placeholder (empty value) does not emit when first option is a placeholder No emission
First option disabled skips disabled options and emits the first enabled one Emits second option
All options disabled does not emit when all options are disabled No emission
Empty options array does not emit when options array is empty No emission
hasChanged guard (second open) emits only once across multiple opens Emits exactly once

Test plan

  • Open a required option field on a mobile device (or Chrome DevTools device emulation)
  • Ensure the field has an empty option that is currently selected
  • Verify that the first visible option can now be selected directly without needing to select an intermediate option first
  • Verify that non-required option fields with empty options still work correctly
  • Verify that desktop (non-native) select behavior is unchanged

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Prevented unintended auto-selection in more cases: stale/null/undefined values, placeholder or empty first option, disabled-first-option, multi-select, non-mobile devices, or when a prior selection exists. Auto-selection state now resets when value or options change.
  • Tests

    • Added end-to-end tests covering open behavior for stale, null, matching, empty-first-option, disabled-first-option scenarios and repeated opens emitting a single change.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 23, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

Reset internal hasChanged when value or options change; compute the first native auto-selectable option and only auto-emit a change when that option is valid (non-disabled, truthy value) and when mobile, single-select, and not already changed. Add e2e tests covering various open behaviors.

Changes

Cohort / File(s) Summary
E2E tests
src/components/select/select.e2e.tsx
Added openSelect(root, waitForChanges) test helper and a nested describe('auto-select behavior on open') with async tests validating change emission for stale/null/undefined values, matching values, placeholder-first option, disabled-first option, empty options, and repeated opens.
Select logic
src/components/select/select.tsx
Added @Watch('value') and @Watch('options') handler to reset hasChanged. Introduced getFirstNativeAutoSelectOption() and changed openMenu() to only auto-emit when that helper returns a valid option and when mobile, single-select, and not already changed; suppressed auto-emission when current value matches an available option or the first candidate has a falsy value.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

released

Suggested reviewers

  • adrianschmidt
  • anderssonjohan
🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title directly describes the main change: fixing the native select to handle cases where the value doesn't match any available option, which is the core problem addressed by the PR.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/native-select-empty-option-not-selectable

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.

@github-actions
Copy link
Copy Markdown

Documentation has been published to https://lundalogik.github.io/lime-elements/versions/PR-3971/

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/select/select.e2e.tsx`:
- Around line 5-69: Add a new native e2e test in
src/components/select/select.e2e.tsx that covers the branch in
emitFirstChangeEvent where the first option has an empty text and should not
auto-emit; copy the existing native test structure (render a <limel-select
data-native ...>, attach a changeSpy, focus the native select via
root.shadowRoot.querySelector('select').dispatchEvent(new Event('focus')) and
assert that changeSpy was NOT called when the first option in the options array
has text: ''. Reference emitFirstChangeEvent and the existing tests "emits the
first option on open to prevent unselectable state" and "does not emit when
value matches an available option" for placement and assertions.
- Around line 33-69: Remove the extra blank lines flagged by the linter in the
select component e2e test: collapse the blank lines around the nativeSelect
variable declarations and after the expectation blocks so there are no empty
lines immediately before or after the focus/await waitForChanges sections;
specifically tidy up the blocks using the identifiers nativeSelect, changeSpy,
options, validValue and the two it(...) test cases so formatting has no stray
blank lines between statements and before the test closing braces.

In `@src/components/select/select.tsx`:
- Around line 392-398: Replace the final if/return pair that checks
firstOption?.text with a single return expression so lint stops flagging the
unreachable branch; locate the block using the firstOption variable (the lines
with the comment "Don't auto-select when the first option is an
empty/placeholder option") and change the two-step logic to a single return of
the boolean value of firstOption?.text (e.g., using Boolean(...) or
!!firstOption?.text) to preserve behavior.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: b6600145-9efa-4907-8b01-d22dca3b19dc

📥 Commits

Reviewing files that changed from the base of the PR and between 6053de0 and dd46270.

📒 Files selected for processing (2)
  • src/components/select/select.e2e.tsx
  • src/components/select/select.tsx

@Befkadu1 Befkadu1 force-pushed the fix/native-select-empty-option-not-selectable branch from dd46270 to 2f2c945 Compare March 23, 2026 14:29
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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/select/select.e2e.tsx`:
- Around line 70-71: Remove the extra blank line that was inserted between the
two test blocks in select.e2e.tsx (the duplicate empty line causing the lint
error at the end of the first test block). Edit the file to collapse the
consecutive blank lines into a single blank line or none as per surrounding
style so the test blocks are adjacent, save, and re-run the linter to confirm
the failure is resolved.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 47c49954-cac0-4313-b9db-c9fc354834e6

📥 Commits

Reviewing files that changed from the base of the PR and between dd46270 and 2f2c945.

📒 Files selected for processing (2)
  • src/components/select/select.e2e.tsx
  • src/components/select/select.tsx

@Befkadu1 Befkadu1 force-pushed the fix/native-select-empty-option-not-selectable branch 2 times, most recently from a6974a9 to eed8a12 Compare March 23, 2026 14:38
@Lundalogik Lundalogik deleted a comment from adrianschmidt-bot Mar 23, 2026
@Befkadu1 Befkadu1 force-pushed the fix/native-select-empty-option-not-selectable branch from eed8a12 to 000c14f Compare March 23, 2026 14:41
@Lundalogik Lundalogik deleted a comment from adrianschmidt-bot Mar 23, 2026
@adrianschmidt-bot
Copy link
Copy Markdown
Contributor

Hi @Befkadu1 👋

The Build and Docs CI checks are failing due to a TypeScript error in select.tsx at line 385:

Property 'value' does not exist on type 'Option<string> | Option<string>[]'.
Property 'value' does not exist on type 'Option<string>[]'.
The issue and how to fix it

On line 385, this.value is used inside .some() after the !Array.isArray(this.value) check on line 384. However, TypeScript does not carry the narrowing into the callback closure.

The simplest fix is to capture the narrowed value before the callback:

+        const currentValue = this.value;
         const hasMatchingValue =
-            this.value &&
-            !Array.isArray(this.value) &&
-            options.some((o) => o.value === this.value.value);
+            currentValue &&
+            !Array.isArray(currentValue) &&
+            options.some((o) => o.value === currentValue.value);

By assigning to a const, TypeScript can properly narrow the type through the !Array.isArray() check into the callback.

Would you like me to open a PR with the fix so you can pull it into this branch? Reply with @adrianschmidt-bot yes if so.

@Befkadu1 Befkadu1 force-pushed the fix/native-select-empty-option-not-selectable branch 2 times, most recently from dd220f0 to 33e6bd7 Compare March 23, 2026 15:10
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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/select/select.tsx`:
- Around line 382-390: The narrowing of this.value isn't preserved inside the
options.some() callback causing a TS error; extract and store the narrowed
non-array value (e.g., const narrowedValue = this.value as Option<T> | undefined
after checking !Array.isArray(this.value) and truthiness) before calling
options.some, then use narrowedValue.value inside the callback; update the
hasMatchingValue computation to use the extracted narrowedValue and keep the
existing checks so TypeScript recognizes the shape when comparing in the
callback.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 8c6dded3-51d7-4d2b-9692-9a89867dae79

📥 Commits

Reviewing files that changed from the base of the PR and between a6974a9 and 33e6bd7.

📒 Files selected for processing (2)
  • src/components/select/select.e2e.tsx
  • src/components/select/select.tsx

@Befkadu1 Befkadu1 force-pushed the fix/native-select-empty-option-not-selectable branch 2 times, most recently from 12c74d6 to ed2124e Compare March 23, 2026 15:33
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.

♻️ Duplicate comments (1)
src/components/select/select.tsx (1)

382-386: ⚠️ Potential issue | 🔴 Critical

TypeScript narrowing not preserved inside .some() callback — CI is failing.

The !Array.isArray(currentValue) check on line 385 narrows currentValue in the outer scope, but TypeScript's control flow analysis doesn't extend into the closure. Inside the callback, currentValue is still typed as Option | Option[], causing the build error.

Extract the narrowed value explicitly before the callback:

🐛 Proposed fix
         const currentValue = this.value;
+        if (!currentValue || Array.isArray(currentValue)) {
+            const firstOption = options[0];
+            return !!firstOption?.text;
+        }
+
         const hasMatchingValue =
-            currentValue &&
-            !Array.isArray(currentValue) &&
-            options.some((o) => o.value === currentValue.value);
+            options.some((o) => o.value === currentValue.value);
 
         if (hasMatchingValue) {
             return false;
         }
 
         const firstOption = options[0];
-
-        // Don't auto-select when the first option is an empty/placeholder
-        // option (no text).
         return !!firstOption?.text;

Alternatively, use a type assertion inside the callback: (currentValue as Option).value.

,

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

In `@src/components/select/select.tsx` around lines 382 - 386, The TypeScript
narrowing for currentValue (from this.value) isn't preserved inside the
options.some callback causing the CI error; fix by extracting the narrowed
single-value before the callback and using it inside the predicate (e.g., const
singleCurrent = !Array.isArray(this.value) ? this.value : undefined; then use
options.some(o => o.value === singleCurrent?.value)), or alternatively use a
local type assertion inside the callback ((currentValue as Option).value);
update references to currentValue in the options.some call to use the new
narrowed variable or the assertion so the closure has the correct type.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/components/select/select.tsx`:
- Around line 382-386: The TypeScript narrowing for currentValue (from
this.value) isn't preserved inside the options.some callback causing the CI
error; fix by extracting the narrowed single-value before the callback and using
it inside the predicate (e.g., const singleCurrent = !Array.isArray(this.value)
? this.value : undefined; then use options.some(o => o.value ===
singleCurrent?.value)), or alternatively use a local type assertion inside the
callback ((currentValue as Option).value); update references to currentValue in
the options.some call to use the new narrowed variable or the assertion so the
closure has the correct type.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5582e85b-dcbe-4d01-ba34-726153e72786

📥 Commits

Reviewing files that changed from the base of the PR and between 33e6bd7 and ed2124e.

📒 Files selected for processing (2)
  • src/components/select/select.e2e.tsx
  • src/components/select/select.tsx

@Befkadu1 Befkadu1 force-pushed the fix/native-select-empty-option-not-selectable branch from ed2124e to 9804db9 Compare March 23, 2026 16:02
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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/select/select.tsx`:
- Around line 373-375: The guard using this.hasChanged is never reset, so add
logic to clear or recompute it when external props change: in the Select
component (resetting the stale-value workaround used alongside isMobileDevice
and multiple), update componentDidUpdate (or the equivalent hook) to compare
prevProps.value !== this.props.value || prevProps.options !== this.props.options
and set this.hasChanged = false (or recompute a derived flag from current
props), and add a regression test that swaps options/value to ensure the
workaround runs again when value becomes invalid after a prop update.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 5b8dd600-0d29-4abd-8349-078cd886b6b9

📥 Commits

Reviewing files that changed from the base of the PR and between ed2124e and 9804db9.

📒 Files selected for processing (2)
  • src/components/select/select.e2e.tsx
  • src/components/select/select.tsx

@Befkadu1 Befkadu1 force-pushed the fix/native-select-empty-option-not-selectable branch from 9804db9 to a1ba8a1 Compare March 24, 2026 08:32
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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/select/select.tsx`:
- Around line 399-403: The code in select.tsx currently determines whether to
auto-select using firstOption?.value but the comment states it should suppress
auto-select when the first option has "no text"; reconcile these by either
changing the runtime check to firstOption?.text (to match the comment and treat
options like { text: '', value: 'placeholder' } as placeholders) or by updating
the comment to explicitly say it checks the option's value; modify the logic in
the block that computes firstOption (the selection/auto-emit guard) and update
the inline comment accordingly so code and comment reflect the same criterion.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 4b7999a1-b4a6-4fdc-a7bf-e52a1040caed

📥 Commits

Reviewing files that changed from the base of the PR and between 9804db9 and a1ba8a1.

📒 Files selected for processing (2)
  • src/components/select/select.e2e.tsx
  • src/components/select/select.tsx

@Befkadu1 Befkadu1 force-pushed the fix/native-select-empty-option-not-selectable branch from a1ba8a1 to 92f0be3 Compare March 24, 2026 08:46
@Befkadu1 Befkadu1 enabled auto-merge (rebase) March 24, 2026 08:47
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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/select/select.tsx`:
- Around line 384-403: The auto-select logic uses the first non-separator option
and can pick a disabled entry; update the selection to skip disabled options by
finding the first enabled option from getOptionsExcludingSeparators() instead of
using firstOption so openMenu() will emit an actually selectable value; modify
the block that computes firstOption/current auto-select (referring to
getOptionsExcludingSeparators, this.value, hasMatchingValue and openMenu) to
choose options.find(o => !o.disabled && o.text) (or equivalent) and ensure
openMenu() emits that enabled option rather than a disabled one.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 5543cda3-97b7-4774-9c08-c909dcd35746

📥 Commits

Reviewing files that changed from the base of the PR and between a1ba8a1 and 92f0be3.

📒 Files selected for processing (2)
  • src/components/select/select.e2e.tsx
  • src/components/select/select.tsx

@Befkadu1 Befkadu1 force-pushed the fix/native-select-empty-option-not-selectable branch from 92f0be3 to e652fd5 Compare March 24, 2026 09:19
@adrianschmidt-bot
Copy link
Copy Markdown
Contributor

Hi @Befkadu1 👋

It looks like the CI test failure is in the third test case: "does not emit when first option has empty text".

Details

The issue is a mismatch between the test expectation and the code logic in getFirstNativeAutoSelectOption():

  • The test expects no change event when the first option has empty text ({ text: '', value: 'placeholder' }`)
  • But the code does options.find((o) => !o.disabled && o.text), which skips the empty-text first option and finds Option A — then emits a change event with it

So the test fails because a change event is emitted (with Option A), but the test asserts it should not be.

A possible fix would be to add an early return in getFirstNativeAutoSelectOption() when the first option is a placeholder (empty text):

// If the first option is a placeholder (empty text), don't auto-select.
// The native <select> will show it by default, letting the user choose.
if (options.length > 0 && !options[0].text) {
    return undefined;
}

This way, when a placeholder option exists at the top, the method returns undefined and no change event fires — matching the test expectation.

Would you like me to open a PR with the fix so you can pull it into this branch? Reply with @adrianschmidt-bot yes if so.

@Befkadu1 Befkadu1 force-pushed the fix/native-select-empty-option-not-selectable branch from e652fd5 to b27685b Compare March 24, 2026 09:59
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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/select/select.e2e.tsx`:
- Around line 41-64: The test "does not emit when value matches an available
option" is incorrectly nested inside describe('when value does not match any
option'); either move this it block out into a new describe('when value matches
an available option') sibling block, or rename the parent describe to a more
general label like describe('auto-select on open') so the test intent matches
its context; update the test grouping around the limel-select render and the
it(...) declaration accordingly so the test suite structure clearly reflects the
matching-value scenario.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 435c3f84-c325-4860-9a48-528d3d41eeaa

📥 Commits

Reviewing files that changed from the base of the PR and between 92f0be3 and b27685b.

📒 Files selected for processing (2)
  • src/components/select/select.e2e.tsx
  • src/components/select/select.tsx

@Befkadu1 Befkadu1 force-pushed the fix/native-select-empty-option-not-selectable branch from b27685b to ba415f9 Compare March 24, 2026 10:21
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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/select/select.e2e.tsx`:
- Around line 11-111: Add a new e2e test in the same "auto-select behavior on
open" suite that verifies disabled options are skipped: render <limel-select>
with options where the first non-placeholder option has disabled: true (e.g.,
options array includes { text: 'Option A', value: 'a', disabled: true }, { text:
'Option B', value: 'b' }), use render(...) and spyOnEvent('change'), call
openSelect(root, waitForChanges) and assert the change event fires with the
first enabled option (Option B) and that the disabled option is not emitted;
place the test alongside the existing cases so it uses the same helpers
(openSelect, waitForChanges, spyOnEvent) and Option type.

In `@src/components/select/select.tsx`:
- Around line 380-405: Add an e2e test that exercises
getFirstNativeAutoSelectOption: create a select with multiple options where the
first non-placeholder option (after getOptionsExcludingSeparators) is disabled
and a later option is enabled, then assert the component skips the disabled
option and emits/selects the next enabled option; target the scenario when
isMobileDevice is true, multiple is false and hasChanged is false so the
function runs, and reference getFirstNativeAutoSelectOption and
getOptionsExcludingSeparators to locate the behavior to verify.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 9b880651-1740-47d0-b7fc-b80b8d001be1

📥 Commits

Reviewing files that changed from the base of the PR and between ba415f9 and 10a24e1.

📒 Files selected for processing (2)
  • src/components/select/select.e2e.tsx
  • src/components/select/select.tsx

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/select/select.e2e.tsx`:
- Around line 40-61: Add a parallel E2E test to cover value === undefined by
copying the existing "emits the first option when value is null" test in
src/components/select/select.e2e.tsx: create a new it block named "emits the
first option when value is undefined", render <limel-select> with the same
options but value={undefined}, use the same helpers (render, waitForChanges,
spyOnEvent, openSelect) and assert the change event was emitted once with
options[0] as the detail (same expectations as the null test).
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 6ef379cf-f0f9-449f-87a9-415b29438979

📥 Commits

Reviewing files that changed from the base of the PR and between 10a24e1 and 863c897.

📒 Files selected for processing (1)
  • src/components/select/select.e2e.tsx

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.

♻️ Duplicate comments (1)
src/components/select/select.e2e.tsx (1)

109-133: ⚠️ Potential issue | 🟡 Minor

Add explicit test for first option with empty text (text: '').

Current placeholder test covers empty value, but not the empty-text branch mentioned in this PR discussion. Please add a dedicated case to prevent regressions there.

✏️ Proposed test addition
         it('does not emit when first option is a placeholder', async () => {
             const options: Option[] = [
                 { text: 'Select one...', value: '' },
                 { text: 'Option A', value: 'a' },
             ];
             const staleValue: Option = {
                 text: '',
                 value: 'filtered-out-key',
             };

             const { root, waitForChanges, spyOnEvent } = await render(
                 <limel-select
                     data-native
                     label="Test"
                     options={options}
                     value={staleValue}
                 ></limel-select>
             );
             const changeSpy = spyOnEvent('change');
             await waitForChanges();

             await openSelect(root, waitForChanges);

             expect(changeSpy).not.toHaveReceivedEvent();
         });

+        it('does not emit when first option has empty text', async () => {
+            const options: Option[] = [
+                { text: '', value: 'placeholder' },
+                { text: 'Option A', value: 'a' },
+            ];
+            const staleValue: Option = {
+                text: '',
+                value: 'filtered-out-key',
+            };
+
+            const { root, waitForChanges, spyOnEvent } = await render(
+                <limel-select
+                    data-native
+                    label="Test"
+                    options={options}
+                    value={staleValue}
+                ></limel-select>
+            );
+            const changeSpy = spyOnEvent('change');
+            await waitForChanges();
+
+            await openSelect(root, waitForChanges);
+
+            expect(changeSpy).not.toHaveReceivedEvent();
+        });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/select/select.e2e.tsx` around lines 109 - 133, Add a new e2e
test that mirrors the existing "does not emit when first option is a
placeholder" but uses a first option with an empty text (e.g. { text: '', value:
'placeholder-key' }) to cover the empty-text branch; render <limel-select> with
that options array and a stale value, use spyOnEvent('change') and
openSelect(root, waitForChanges), then assert
changeSpy.not.toHaveReceivedEvent() to ensure no change is emitted when the
first option's text is blank.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/components/select/select.e2e.tsx`:
- Around line 109-133: Add a new e2e test that mirrors the existing "does not
emit when first option is a placeholder" but uses a first option with an empty
text (e.g. { text: '', value: 'placeholder-key' }) to cover the empty-text
branch; render <limel-select> with that options array and a stale value, use
spyOnEvent('change') and openSelect(root, waitForChanges), then assert
changeSpy.not.toHaveReceivedEvent() to ensure no change is emitted when the
first option's text is blank.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: b61641be-c952-4fe3-8259-5a54a88661b1

📥 Commits

Reviewing files that changed from the base of the PR and between 863c897 and 030b970.

📒 Files selected for processing (1)
  • src/components/select/select.e2e.tsx

@Befkadu1
Copy link
Copy Markdown
Contributor Author

Re: the suggestion to add a test for first option with empty text ({ text: '', value: 'placeholder-key' }):

This is no longer applicable. Per Johan's review, we changed the placeholder detection from checking .text to checking .value, since value is the canonical identifier and text is just a display label.

With the current logic (!options[0].value), an option like { text: '', value: 'placeholder-key' } has a truthy .value and is not treated as a placeholder — it would be auto-selected, which is the correct behavior. The suggested test would fail against the current (intentional) logic.

Befkadu1 and others added 2 commits March 25, 2026 20:34
On mobile devices, when a required option field has an empty option
that gets filtered out, the component's value still points to that
removed option. The native <select> then has no matching selected
option, causing the browser to default-highlight the first visible
option. Clicking that first option doesn't fire a change event
(since the browser considers it already selected), forcing users
to pick an intermediate option first.

This fix updates emitFirstChangeEvent() to detect when the current
value doesn't match any available option and treats it as "no value",
triggering the auto-emit of the first visible option when the native
picker opens.

Closes Lundalogik/crm-client#420

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tests verify that when the current value doesn't match any available
option (e.g. an empty option filtered out by a required field), the
component auto-emits the first option on open. Also tests that the
auto-emit does NOT happen when the value matches a valid option.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Befkadu1 Befkadu1 force-pushed the fix/native-select-empty-option-not-selectable branch from 030b970 to 9e1a056 Compare March 25, 2026 19:34
@Befkadu1 Befkadu1 merged commit 3b1e95c into main Mar 25, 2026
9 of 10 checks passed
@Befkadu1 Befkadu1 deleted the fix/native-select-empty-option-not-selectable branch March 25, 2026 19:37
@lime-opensource
Copy link
Copy Markdown
Collaborator

🎉 This PR is included in version 39.9.4 🎉

The release is available on:

Your semantic-release bot 📦🚀

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants