Skip to content

Commit e06340c

Browse files
authored
fix(number-input): only show invalid state when invalid and invalidText are set (#2384)
Fixes #1180. `NumberInput` now behaves consistently with `TextInput`. The invalid state requires explicit `invalid={true}` plus truthy `invalidText`, rather than auto-triggering on min/max violations.
1 parent c2f49f4 commit e06340c

File tree

2 files changed

+67
-20
lines changed

2 files changed

+67
-20
lines changed

src/NumberInput/NumberInput.svelte

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -163,11 +163,7 @@
163163
164164
$: incrementLabel = translateWithId("increment");
165165
$: decrementLabel = translateWithId("decrement");
166-
$: hasError =
167-
(invalid && !readonly) ||
168-
(!allowEmpty && value == null) ||
169-
value > max ||
170-
(typeof value === "number" && value < min);
166+
$: hasError = invalid && invalidText && !readonly;
171167
$: errorId = `error-${id}`;
172168
$: ariaLabel =
173169
$$props["aria-label"] ||

tests/NumberInput/NumberInput.test.ts

Lines changed: 66 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -175,16 +175,16 @@ describe("NumberInput", () => {
175175
expect(input).toHaveValue(null);
176176
});
177177

178-
it("should handle min/max validation", async () => {
178+
it("should accept values outside min/max range without showing invalid state", async () => {
179+
// Per issue #1180, NumberInput should not automatically show invalid state
180+
// based on min/max constraints - it should require explicit invalid={true}
179181
render(NumberInput, { props: { min: 4, max: 20 } });
180182

181183
const input = screen.getByRole("spinbutton");
182184
await user.type(input, "25");
183185
expect(screen.getByTestId("value").textContent).toBe("25");
184-
expect(screen.getByRole("spinbutton")).toHaveAttribute(
185-
"aria-invalid",
186-
"true",
187-
);
186+
// Should NOT show invalid state since invalid prop is false
187+
expect(screen.getByRole("spinbutton")).not.toHaveAttribute("aria-invalid");
188188
});
189189

190190
it("should not show helper text when invalid", () => {
@@ -449,6 +449,54 @@ describe("NumberInput", () => {
449449
).not.toBeInTheDocument();
450450
});
451451

452+
// Regression test for https://github.com/carbon-design-system/carbon-components-svelte/issues/1180
453+
it("should not show invalid state when value exceeds max but invalid prop is false", async () => {
454+
// NumberInput should be consistent with TextInput - only show invalid state when invalid={true}
455+
render(NumberInput, { props: { max: 10, value: 15 } });
456+
457+
const input = screen.getByRole("spinbutton");
458+
// Should NOT show invalid state since invalid prop is false
459+
expect(input).not.toHaveAttribute("aria-invalid");
460+
expect(input.closest(".bx--number")).not.toHaveAttribute("data-invalid");
461+
});
462+
463+
// Regression test for https://github.com/carbon-design-system/carbon-components-svelte/issues/1180
464+
it("should not show invalid state when value is below min but invalid prop is false", () => {
465+
render(NumberInput, { props: { min: 5, value: 2 } });
466+
467+
const input = screen.getByRole("spinbutton");
468+
// Should NOT show invalid state since invalid prop is false
469+
expect(input).not.toHaveAttribute("aria-invalid");
470+
expect(input.closest(".bx--number")).not.toHaveAttribute("data-invalid");
471+
});
472+
473+
it("should show invalid state when invalid prop is true and invalidText is provided", () => {
474+
// NumberInput should require both invalid=true AND invalidText to show invalid state
475+
render(NumberInput, {
476+
props: { invalid: true, invalidText: "This field is invalid" },
477+
});
478+
479+
const input = screen.getByRole("spinbutton");
480+
expect(input).toHaveAttribute("aria-invalid", "true");
481+
expect(input.closest(".bx--number")).toHaveAttribute(
482+
"data-invalid",
483+
"true",
484+
);
485+
expect(screen.getByText("This field is invalid")).toBeInTheDocument();
486+
});
487+
488+
// Regression test for https://github.com/carbon-design-system/carbon-components-svelte/issues/1180
489+
it("should not show invalid state when invalid prop is true but invalidText is empty", () => {
490+
render(NumberInput, {
491+
props: { invalid: true, invalidText: "" },
492+
});
493+
494+
const input = screen.getByRole("spinbutton");
495+
// Should NOT show invalid state since invalidText is empty
496+
expect(input).not.toHaveAttribute("aria-invalid");
497+
expect(input.closest(".bx--number")).not.toHaveAttribute("data-invalid");
498+
});
499+
452500
it("should bind ref to input element", () => {
453501
render(NumberInput, {
454502
props: { ref: null },
@@ -615,15 +663,14 @@ describe("NumberInput", () => {
615663
expect(inputHandler.mock.calls[0][0].detail).toBe(null);
616664
});
617665

618-
it("should set error state when value is null and allowEmpty is false", () => {
666+
it("should not show invalid state when value is null and allowEmpty is false without invalid prop", () => {
667+
// Per issue #1180, NumberInput should only show invalid state when invalid={true}
619668
render(NumberInput, { props: { value: null, allowEmpty: false } });
620669

621670
const input = screen.getByRole("spinbutton");
622-
expect(input).toHaveAttribute("aria-invalid", "true");
623-
expect(input.closest(".bx--number")).toHaveAttribute(
624-
"data-invalid",
625-
"true",
626-
);
671+
// Should NOT automatically show invalid state - requires explicit invalid={true}
672+
expect(input).not.toHaveAttribute("aria-invalid");
673+
expect(input.closest(".bx--number")).not.toHaveAttribute("data-invalid");
627674
});
628675

629676
it("should not set error state when value is null and allowEmpty is true", () => {
@@ -633,20 +680,24 @@ describe("NumberInput", () => {
633680
expect(input).not.toHaveAttribute("aria-invalid");
634681
});
635682

636-
it("should set error state when value exceeds max", async () => {
683+
it("should not show invalid state when value exceeds max without invalid prop", async () => {
684+
// Per issue #1180, NumberInput should only show invalid state when invalid={true}
637685
render(NumberInput, { props: { max: 10 } });
638686

639687
const input = screen.getByRole("spinbutton");
640688
await user.type(input, "15");
641689

642-
expect(input).toHaveAttribute("aria-invalid", "true");
690+
// Should NOT automatically show invalid state - requires explicit invalid={true}
691+
expect(input).not.toHaveAttribute("aria-invalid");
643692
});
644693

645-
it("should set error state when value is below min", async () => {
694+
it("should not show invalid state when value is below min without invalid prop", async () => {
695+
// Per issue #1180, NumberInput should only show invalid state when invalid={true}
646696
render(NumberInput, { props: { min: 5, value: 3 } });
647697

648698
const input = screen.getByRole("spinbutton");
649-
expect(input).toHaveAttribute("aria-invalid", "true");
699+
// Should NOT automatically show invalid state - requires explicit invalid={true}
700+
expect(input).not.toHaveAttribute("aria-invalid");
650701
});
651702

652703
it("should not set error state when readonly and invalid", () => {

0 commit comments

Comments
 (0)