@simplybusiness/mobius 8.0.1 → 9.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +45 -0
- package/dist/cjs/index.js +4773 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/cjs/meta.json +4948 -0
- package/dist/esm/index.js +542 -694
- package/dist/esm/index.js.map +4 -4
- package/dist/esm/meta.json +300 -532
- package/dist/esm/tsconfig.build.tsbuildinfo +1 -1
- package/dist/types/src/hooks/index.d.ts +0 -7
- package/dist/types/src/utils/htmlDialogPolyfill.d.ts +1 -0
- package/dist/types/src/utils/index.d.ts +0 -1
- package/dist/types/src/utils/mockMatchMedia.d.ts +1 -0
- package/dist/types/vitest.config.d.ts +2 -0
- package/package.json +14 -18
- package/src/components/Accordion/Accordion.stories.tsx +1 -1
- package/src/components/Accordion/Accordion.test.tsx +12 -12
- package/src/components/Accordion/Accordion.tsx +1 -1
- package/src/components/Accordion/AccordionList.stories.tsx +1 -1
- package/src/components/Accordion/AccordionList.test.tsx +6 -6
- package/src/components/AddressLookup/AddressLookup.stories.tsx +1 -1
- package/src/components/AddressLookup/AddressLookup.test.tsx +19 -20
- package/src/components/AddressLookup/LoqateAddressLookupService.test.tsx +7 -6
- package/src/components/Alert/Alert.stories.tsx +1 -1
- package/src/components/Box/Box.stories.tsx +1 -1
- package/src/components/Breadcrumbs/Breadcrumbs.stories.tsx +1 -1
- package/src/components/Button/Button.stories.tsx +3 -4
- package/src/components/Button/Button.test.tsx +4 -4
- package/src/components/Checkbox/Checkbox.stories.tsx +1 -1
- package/src/components/Checkbox/Checkbox.test.tsx +2 -2
- package/src/components/Checkbox/CheckboxGroup.stories.tsx +1 -1
- package/src/components/Checkbox/CheckboxGroup.test.tsx +5 -5
- package/src/components/Combobox/Combobox.stories.tsx +1 -1
- package/src/components/Combobox/Combobox.test.tsx +67 -78
- package/src/components/Combobox/Combobox.tsx +2 -1
- package/src/components/Combobox/useComboboxOptions.test.ts +30 -30
- package/src/components/Combobox/useComboboxOptions.ts +1 -1
- package/src/components/Container/Container.stories.tsx +1 -1
- package/src/components/DateField/DateField.stories.tsx +1 -1
- package/src/components/DateField/DateField.test.tsx +1 -1
- package/src/components/Divider/Divider.stories.tsx +1 -1
- package/src/components/Drawer/Drawer.stories.tsx +1 -1
- package/src/components/Drawer/Drawer.test.tsx +6 -6
- package/src/components/DropdownMenu/DropdownMenu.stories.tsx +8 -10
- package/src/components/DropdownMenu/DropdownMenu.test.tsx +1 -1
- package/src/components/ErrorMessage/ErrorMessage.stories.tsx +1 -1
- package/src/components/ExpandableText/ExpandableText.test.tsx +14 -14
- package/src/components/Fieldset/Fieldset.stories.tsx +1 -1
- package/src/components/Flex/Flex.stories.tsx +1 -1
- package/src/components/Grid/Grid.stories.tsx +4 -7
- package/src/components/Icon/Icon.stories.tsx +1 -1
- package/src/components/Image/Image.stories.tsx +1 -1
- package/src/components/Label/Label.stories.tsx +1 -1
- package/src/components/Link/Link.stories.tsx +1 -1
- package/src/components/Link/Link.test.tsx +1 -1
- package/src/components/LinkButton/LinkButton.stories.tsx +1 -1
- package/src/components/LinkButton/LinkButton.test.tsx +2 -2
- package/src/components/List/List.stories.tsx +1 -1
- package/src/components/LoadingIndicator/LoadingIndicator.stories.tsx +1 -1
- package/src/components/Logo/Logo.stories.tsx +1 -1
- package/src/components/Modal/Modal.stories.tsx +1 -1
- package/src/components/Modal/Modal.test.tsx +6 -6
- package/src/components/NumberField/NumberField.stories.tsx +1 -1
- package/src/components/NumberField/NumberField.test.tsx +5 -5
- package/src/components/PasswordField/PasswordField.stories.tsx +1 -1
- package/src/components/Popover/Popover.stories.tsx +4 -8
- package/src/components/Popover/Popover.test.tsx +4 -4
- package/src/components/Popover/Popover.tsx +1 -1
- package/src/components/Progress/Progress.stories.tsx +1 -1
- package/src/components/Radio/Radio.stories.tsx +1 -1
- package/src/components/Radio/Radio.test.tsx +9 -9
- package/src/components/SVG/SVG.stories.tsx +1 -1
- package/src/components/Segment/Segment.stories.tsx +1 -1
- package/src/components/Select/Select.stories.tsx +1 -1
- package/src/components/Select/Select.test.tsx +1 -1
- package/src/components/Slider/Slider.stories.tsx +1 -1
- package/src/components/Slider/Slider.test.tsx +6 -6
- package/src/components/Slider/helpers.test.ts +1 -1
- package/src/components/Stack/Stack.stories.tsx +1 -1
- package/src/components/Switch/Switch.stories.tsx +1 -1
- package/src/components/Switch/Switch.test.tsx +1 -1
- package/src/components/Table/Table.stories.tsx +1 -1
- package/src/components/Text/Text.stories.tsx +1 -1
- package/src/components/TextArea/TextArea.stories.tsx +1 -1
- package/src/components/TextArea/TextArea.test.tsx +3 -3
- package/src/components/TextField/TextField.stories.tsx +1 -1
- package/src/components/TextOrHTML/TextOrHTML.stories.tsx +1 -1
- package/src/components/Title/Title.stories.tsx +1 -1
- package/src/components/Toast/Toast.stories.tsx +1 -1
- package/src/components/Toast/Toast.test.tsx +6 -6
- package/src/components/Trust/Trust.stories.tsx +1 -1
- package/src/components/VisuallyHidden/VisuallyHidden.stories.tsx +1 -1
- package/src/hooks/index.tsx +0 -7
- package/src/hooks/useBreakpoint/useBreakpoint.ssr.test.tsx +18 -0
- package/src/hooks/useBreakpoint/useBreakpoint.stories.tsx +1 -1
- package/src/hooks/useBreakpoint/useBreakpoint.test.tsx +65 -5
- package/src/hooks/useBreakpoint/useBreakpoint.tsx +25 -39
- package/src/hooks/useButton/useButton.test.tsx +4 -4
- package/src/hooks/useDialog/useDialog.ts +1 -1
- package/src/hooks/useLabel/useLabel.test.tsx +1 -1
- package/src/hooks/useTextField/useTextField.test.tsx +4 -4
- package/src/public-whitelist.test.ts +1 -0
- package/src/utils/delay.test.ts +4 -4
- package/src/utils/{jestHTMLDialogPolyfill.ts → htmlDialogPolyfill.ts} +5 -5
- package/src/utils/index.ts +0 -1
- package/src/utils/mockMatchMedia.ts +16 -0
- package/dist/types/src/hooks/useBodyScrollLock/index.d.ts +0 -1
- package/dist/types/src/hooks/useBodyScrollLock/useBodyScrollLock.d.ts +0 -3
- package/dist/types/src/hooks/useDebouncedValue/index.d.ts +0 -1
- package/dist/types/src/hooks/useDebouncedValue/useDebouncedValue.d.ts +0 -1
- package/dist/types/src/hooks/useOnClickOutside/index.d.ts +0 -1
- package/dist/types/src/hooks/useOnClickOutside/useOnClickOutside.d.ts +0 -2
- package/dist/types/src/hooks/useOnUnmount/index.d.ts +0 -1
- package/dist/types/src/hooks/useOnUnmount/useOnUnmount.d.ts +0 -1
- package/dist/types/src/hooks/usePrefersReducedMotion/index.d.ts +0 -1
- package/dist/types/src/hooks/usePrefersReducedMotion/usePrefersReducedMotion.d.ts +0 -1
- package/dist/types/src/hooks/useRenderCount/index.d.ts +0 -1
- package/dist/types/src/hooks/useRenderCount/useRenderCount.d.ts +0 -1
- package/dist/types/src/hooks/useWindowEvent/index.d.ts +0 -1
- package/dist/types/src/hooks/useWindowEvent/useWindowEvent.d.ts +0 -1
- package/dist/types/src/utils/jestHTMLDialogPolyfill.d.ts +0 -1
- package/dist/types/src/utils/jestMockMatchMedia.d.ts +0 -1
- package/src/hooks/useBodyScrollLock/index.ts +0 -1
- package/src/hooks/useBodyScrollLock/useBodyScrollLock.test.ts +0 -34
- package/src/hooks/useBodyScrollLock/useBodyScrollLock.ts +0 -30
- package/src/hooks/useDebouncedValue/index.tsx +0 -1
- package/src/hooks/useDebouncedValue/useDebouncedValue.test.tsx +0 -62
- package/src/hooks/useDebouncedValue/useDebouncedValue.tsx +0 -25
- package/src/hooks/useOnClickOutside/index.tsx +0 -1
- package/src/hooks/useOnClickOutside/useOnClickOutside.test.tsx +0 -189
- package/src/hooks/useOnClickOutside/useOnClickOutside.tsx +0 -44
- package/src/hooks/useOnUnmount/index.tsx +0 -1
- package/src/hooks/useOnUnmount/useOnUnmount.test.tsx +0 -37
- package/src/hooks/useOnUnmount/useOnUnmount.tsx +0 -8
- package/src/hooks/usePrefersReducedMotion/index.tsx +0 -1
- package/src/hooks/usePrefersReducedMotion/usePrefersReducedMotion.test.tsx +0 -48
- package/src/hooks/usePrefersReducedMotion/usePrefersReducedMotion.tsx +0 -22
- package/src/hooks/useRenderCount/index.ts +0 -1
- package/src/hooks/useRenderCount/useRenderCount.test.ts +0 -26
- package/src/hooks/useRenderCount/useRenderCount.ts +0 -9
- package/src/hooks/useWindowEvent/index.tsx +0 -1
- package/src/hooks/useWindowEvent/useWindowEvent.test.tsx +0 -188
- package/src/hooks/useWindowEvent/useWindowEvent.tsx +0 -41
- package/src/utils/jestMockMatchMedia.ts +0 -16
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
2
2
|
// @ts-nocheck The types for this file don't work at all because of the
|
|
3
3
|
// test groupings.
|
|
4
|
-
import { act, render, screen, within } from "@testing-library/react";
|
|
4
|
+
import { act, fireEvent, render, screen, within } from "@testing-library/react";
|
|
5
5
|
import { userEvent } from "@testing-library/user-event";
|
|
6
6
|
import { Combobox } from "./Combobox";
|
|
7
7
|
import {
|
|
@@ -40,18 +40,15 @@ function getFlatOptions(options: ComboboxOptions) {
|
|
|
40
40
|
let user;
|
|
41
41
|
|
|
42
42
|
describe("Combobox", () => {
|
|
43
|
-
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
vi.useFakeTimers({ shouldAdvanceTime: true });
|
|
44
45
|
user = userEvent.setup({
|
|
45
|
-
advanceTimers:
|
|
46
|
+
advanceTimers: vi.advanceTimersByTime.bind(vi),
|
|
46
47
|
});
|
|
47
48
|
});
|
|
48
49
|
|
|
49
|
-
beforeEach(() => {
|
|
50
|
-
jest.useFakeTimers();
|
|
51
|
-
});
|
|
52
|
-
|
|
53
50
|
afterEach(() => {
|
|
54
|
-
|
|
51
|
+
vi.useRealTimers();
|
|
55
52
|
});
|
|
56
53
|
|
|
57
54
|
describe("common functionality", () => {
|
|
@@ -117,10 +114,11 @@ describe("Combobox", () => {
|
|
|
117
114
|
expect(input).toHaveAttribute("placeholder", "Please pick a fruit");
|
|
118
115
|
});
|
|
119
116
|
|
|
120
|
-
it("should give each option a unique id",
|
|
117
|
+
it("should give each option a unique id", () => {
|
|
121
118
|
render(<Combobox label="Fruit" options={options} />);
|
|
122
119
|
const input = screen.getByRole("combobox");
|
|
123
|
-
|
|
120
|
+
fireEvent.mouseDown(input);
|
|
121
|
+
fireEvent.focus(input);
|
|
124
122
|
const visibleOptions = screen.getAllByRole("option");
|
|
125
123
|
const ids = new Set(
|
|
126
124
|
visibleOptions.map(option => option.getAttribute("id")),
|
|
@@ -163,25 +161,27 @@ describe("Combobox", () => {
|
|
|
163
161
|
});
|
|
164
162
|
|
|
165
163
|
describe("mouse interactions", () => {
|
|
166
|
-
it("should render a listbox when the input is focused",
|
|
164
|
+
it("should render a listbox when the input is focused", () => {
|
|
167
165
|
render(<Combobox label="Fruit" options={options} />);
|
|
168
166
|
const input = screen.getByRole("combobox");
|
|
169
|
-
|
|
167
|
+
fireEvent.mouseDown(input);
|
|
168
|
+
fireEvent.focus(input);
|
|
170
169
|
const listbox = screen.getByRole("listbox");
|
|
171
170
|
expect(listbox).toBeInTheDocument();
|
|
172
171
|
});
|
|
173
172
|
|
|
174
|
-
it("should not render a listbox if there are no options",
|
|
173
|
+
it("should not render a listbox if there are no options", () => {
|
|
175
174
|
render(<Combobox label="Fruit" options={[]} />);
|
|
176
175
|
const input = screen.getByRole("combobox");
|
|
177
|
-
|
|
176
|
+
fireEvent.mouseDown(input);
|
|
177
|
+
fireEvent.focus(input);
|
|
178
178
|
const listbox = screen.queryByRole("listbox");
|
|
179
179
|
expect(listbox).toBeInTheDocument();
|
|
180
180
|
expect(listbox).toHaveClass("mobius-combobox__list--hidden");
|
|
181
181
|
});
|
|
182
182
|
|
|
183
183
|
it("should call the onSelected callback when an option is selected", async () => {
|
|
184
|
-
const onSelected =
|
|
184
|
+
const onSelected = vi.fn();
|
|
185
185
|
render(
|
|
186
186
|
<Combobox
|
|
187
187
|
label="Fruit"
|
|
@@ -197,7 +197,7 @@ describe("Combobox", () => {
|
|
|
197
197
|
});
|
|
198
198
|
|
|
199
199
|
it("should call the onSelected callback when a user blurs and the typed text matches an option label", async () => {
|
|
200
|
-
const onSelected =
|
|
200
|
+
const onSelected = vi.fn();
|
|
201
201
|
render(
|
|
202
202
|
<Combobox
|
|
203
203
|
label="Fruit"
|
|
@@ -213,7 +213,7 @@ describe("Combobox", () => {
|
|
|
213
213
|
});
|
|
214
214
|
|
|
215
215
|
it("should NOT call the onSelected callback when a user blurs and the typed text does not match an option label", async () => {
|
|
216
|
-
const onSelected =
|
|
216
|
+
const onSelected = vi.fn();
|
|
217
217
|
render(
|
|
218
218
|
<Combobox
|
|
219
219
|
label="Fruit"
|
|
@@ -244,7 +244,7 @@ describe("Combobox", () => {
|
|
|
244
244
|
await user.click(input);
|
|
245
245
|
await act(async () => {
|
|
246
246
|
await user.tab();
|
|
247
|
-
|
|
247
|
+
vi.advanceTimersByTime(200);
|
|
248
248
|
});
|
|
249
249
|
const listbox = screen.queryByRole("listbox");
|
|
250
250
|
expect(listbox).toBeInTheDocument();
|
|
@@ -275,7 +275,7 @@ describe("Combobox", () => {
|
|
|
275
275
|
await user.click(outside);
|
|
276
276
|
await act(async () => {
|
|
277
277
|
await user.tab();
|
|
278
|
-
|
|
278
|
+
vi.advanceTimersByTime(200);
|
|
279
279
|
});
|
|
280
280
|
const listbox = screen.queryByRole("listbox");
|
|
281
281
|
expect(listbox).toBeInTheDocument();
|
|
@@ -293,7 +293,7 @@ describe("Combobox", () => {
|
|
|
293
293
|
|
|
294
294
|
describe("custom events", () => {
|
|
295
295
|
it("should call an onBlur event if passed", async () => {
|
|
296
|
-
const onBlur =
|
|
296
|
+
const onBlur = vi.fn();
|
|
297
297
|
render(
|
|
298
298
|
<Combobox label="Fruit" options={options} onBlur={onBlur} />,
|
|
299
299
|
);
|
|
@@ -303,14 +303,14 @@ describe("Combobox", () => {
|
|
|
303
303
|
|
|
304
304
|
await act(async () => {
|
|
305
305
|
await user.tab();
|
|
306
|
-
|
|
306
|
+
vi.advanceTimersByTime(200);
|
|
307
307
|
});
|
|
308
308
|
await Promise.resolve();
|
|
309
309
|
expect(onBlur).toHaveBeenCalled();
|
|
310
310
|
});
|
|
311
311
|
|
|
312
312
|
it("should call an onFocus event if passed", async () => {
|
|
313
|
-
const onFocus =
|
|
313
|
+
const onFocus = vi.fn();
|
|
314
314
|
render(
|
|
315
315
|
<Combobox label="Fruit" options={options} onFocus={onFocus} />,
|
|
316
316
|
);
|
|
@@ -320,7 +320,7 @@ describe("Combobox", () => {
|
|
|
320
320
|
});
|
|
321
321
|
|
|
322
322
|
it("should call an onChange event if passed", async () => {
|
|
323
|
-
const onChange =
|
|
323
|
+
const onChange = vi.fn();
|
|
324
324
|
render(
|
|
325
325
|
<Combobox label="Fruit" options={options} onChange={onChange} />,
|
|
326
326
|
);
|
|
@@ -350,7 +350,7 @@ describe("Combobox", () => {
|
|
|
350
350
|
});
|
|
351
351
|
|
|
352
352
|
it("should select the top option when pressing Enter", async () => {
|
|
353
|
-
const onSelected =
|
|
353
|
+
const onSelected = vi.fn();
|
|
354
354
|
render(
|
|
355
355
|
<Combobox
|
|
356
356
|
label="Fruit"
|
|
@@ -365,7 +365,7 @@ describe("Combobox", () => {
|
|
|
365
365
|
});
|
|
366
366
|
|
|
367
367
|
it("should not select an option when pressing Enter with no options", async () => {
|
|
368
|
-
const onSelected =
|
|
368
|
+
const onSelected = vi.fn();
|
|
369
369
|
render(
|
|
370
370
|
<Combobox
|
|
371
371
|
label="Fruit"
|
|
@@ -380,7 +380,7 @@ describe("Combobox", () => {
|
|
|
380
380
|
});
|
|
381
381
|
|
|
382
382
|
it("should select the highlighted option when pressing Enter", async () => {
|
|
383
|
-
const onSelected =
|
|
383
|
+
const onSelected = vi.fn();
|
|
384
384
|
render(
|
|
385
385
|
<Combobox
|
|
386
386
|
label="Fruit"
|
|
@@ -443,7 +443,7 @@ describe("Combobox", () => {
|
|
|
443
443
|
});
|
|
444
444
|
|
|
445
445
|
it("should not call onSelected when pressing Escape", async () => {
|
|
446
|
-
const onSelected =
|
|
446
|
+
const onSelected = vi.fn();
|
|
447
447
|
render(
|
|
448
448
|
<Combobox
|
|
449
449
|
label="Fruit"
|
|
@@ -468,7 +468,7 @@ describe("Combobox", () => {
|
|
|
468
468
|
});
|
|
469
469
|
|
|
470
470
|
it("should select the first option when pressing Home", async () => {
|
|
471
|
-
const onSelected =
|
|
471
|
+
const onSelected = vi.fn();
|
|
472
472
|
render(
|
|
473
473
|
<Combobox
|
|
474
474
|
label="Fruit"
|
|
@@ -485,7 +485,7 @@ describe("Combobox", () => {
|
|
|
485
485
|
});
|
|
486
486
|
|
|
487
487
|
it("should select the last option when pressing End", async () => {
|
|
488
|
-
const onSelected =
|
|
488
|
+
const onSelected = vi.fn();
|
|
489
489
|
render(
|
|
490
490
|
<Combobox
|
|
491
491
|
label="Fruit"
|
|
@@ -502,35 +502,14 @@ describe("Combobox", () => {
|
|
|
502
502
|
"true",
|
|
503
503
|
);
|
|
504
504
|
});
|
|
505
|
-
|
|
506
|
-
it("should select the first option when pressing Home", async () => {
|
|
507
|
-
render(<Combobox label="Fruit" options={options} />);
|
|
508
|
-
const input = screen.getByRole("combobox");
|
|
509
|
-
await user.click(input);
|
|
510
|
-
await user.keyboard("{arrowdown}{arrowdown}");
|
|
511
|
-
await user.keyboard("{home}");
|
|
512
|
-
const visibleOptions = screen.getAllByRole("option");
|
|
513
|
-
expect(visibleOptions[0]).toHaveAttribute("aria-selected", "true");
|
|
514
|
-
});
|
|
515
|
-
|
|
516
|
-
it("should select the last option when pressing End", async () => {
|
|
517
|
-
render(<Combobox label="Fruit" options={options} />);
|
|
518
|
-
const input = screen.getByRole("combobox");
|
|
519
|
-
await user.click(input);
|
|
520
|
-
await user.keyboard("{end}");
|
|
521
|
-
const visibleOptions = screen.getAllByRole("option");
|
|
522
|
-
expect(visibleOptions[visibleOptions.length - 1]).toHaveAttribute(
|
|
523
|
-
"aria-selected",
|
|
524
|
-
"true",
|
|
525
|
-
);
|
|
526
|
-
});
|
|
527
505
|
});
|
|
528
506
|
|
|
529
507
|
describe("filtering", () => {
|
|
530
|
-
it("should render all of the options in the listbox",
|
|
508
|
+
it("should render all of the options in the listbox", () => {
|
|
531
509
|
render(<Combobox label="Fruit" options={options} />);
|
|
532
510
|
const input = screen.getByRole("combobox");
|
|
533
|
-
|
|
511
|
+
fireEvent.mouseDown(input);
|
|
512
|
+
fireEvent.focus(input);
|
|
534
513
|
const visibleOptions = screen.getAllByRole("option");
|
|
535
514
|
|
|
536
515
|
expect(visibleOptions).toHaveLength(getFlatOptions(options).length);
|
|
@@ -567,18 +546,20 @@ describe("Combobox", () => {
|
|
|
567
546
|
expect(input).toHaveClass("mobius-combobox__input");
|
|
568
547
|
});
|
|
569
548
|
|
|
570
|
-
it("should have the correct classnames for the listbox",
|
|
549
|
+
it("should have the correct classnames for the listbox", () => {
|
|
571
550
|
render(<Combobox label="Fruit" options={options} />);
|
|
572
551
|
const input = screen.getByRole("combobox");
|
|
573
|
-
|
|
552
|
+
fireEvent.mouseDown(input);
|
|
553
|
+
fireEvent.focus(input);
|
|
574
554
|
const listbox = screen.getByRole("listbox");
|
|
575
555
|
expect(listbox).toHaveClass("mobius-combobox__list");
|
|
576
556
|
});
|
|
577
557
|
|
|
578
|
-
it("should have the correct classnames for the options including personal data identifier classname (has-pii)",
|
|
558
|
+
it("should have the correct classnames for the options including personal data identifier classname (has-pii)", () => {
|
|
579
559
|
render(<Combobox label="Fruit" options={options} />);
|
|
580
560
|
const input = screen.getByRole("combobox");
|
|
581
|
-
|
|
561
|
+
fireEvent.mouseDown(input);
|
|
562
|
+
fireEvent.focus(input);
|
|
582
563
|
const visibleOptions = screen.getAllByRole("option");
|
|
583
564
|
visibleOptions.forEach(option => {
|
|
584
565
|
expect(option).toHaveClass("mobius-combobox__option has-pii");
|
|
@@ -596,12 +577,13 @@ describe("Combobox", () => {
|
|
|
596
577
|
);
|
|
597
578
|
});
|
|
598
579
|
|
|
599
|
-
it("should add the mobius-combobox--is-expanded class when the listbox is open",
|
|
580
|
+
it("should add the mobius-combobox--is-expanded class when the listbox is open", () => {
|
|
600
581
|
render(<Combobox label="Fruit" options={options} />);
|
|
601
582
|
const wrapper = screen.getByTestId("mobius-combobox__wrapper");
|
|
602
583
|
const input = screen.getByRole("combobox");
|
|
603
584
|
expect(wrapper).not.toHaveClass("mobius-combobox--is-expanded");
|
|
604
|
-
|
|
585
|
+
fireEvent.mouseDown(input);
|
|
586
|
+
fireEvent.focus(input);
|
|
605
587
|
expect(wrapper).toHaveClass("mobius-combobox--is-expanded");
|
|
606
588
|
});
|
|
607
589
|
|
|
@@ -632,10 +614,11 @@ describe("Combobox", () => {
|
|
|
632
614
|
expect(input).toHaveAttribute("aria-haspopup", "listbox");
|
|
633
615
|
});
|
|
634
616
|
|
|
635
|
-
it("should set aria-controls to the listbox id",
|
|
617
|
+
it("should set aria-controls to the listbox id", () => {
|
|
636
618
|
render(<Combobox label="Fruit" options={options} />);
|
|
637
619
|
const input = screen.getByRole("combobox");
|
|
638
|
-
|
|
620
|
+
fireEvent.mouseDown(input);
|
|
621
|
+
fireEvent.focus(input);
|
|
639
622
|
const listbox = screen.getByRole("listbox");
|
|
640
623
|
expect(input).toHaveAttribute("aria-controls", listbox.id);
|
|
641
624
|
});
|
|
@@ -646,17 +629,19 @@ describe("Combobox", () => {
|
|
|
646
629
|
expect(input).toHaveAttribute("aria-expanded", "false");
|
|
647
630
|
});
|
|
648
631
|
|
|
649
|
-
it("should set aria-expanded to true when the listbox is open",
|
|
632
|
+
it("should set aria-expanded to true when the listbox is open", () => {
|
|
650
633
|
render(<Combobox label="Fruit" options={options} />);
|
|
651
634
|
const input = screen.getByRole("combobox");
|
|
652
|
-
|
|
635
|
+
fireEvent.mouseDown(input);
|
|
636
|
+
fireEvent.focus(input);
|
|
653
637
|
expect(input).toHaveAttribute("aria-expanded", "true");
|
|
654
638
|
});
|
|
655
639
|
|
|
656
|
-
it("should set aria-selected to false for all options except first option",
|
|
640
|
+
it("should set aria-selected to false for all options except first option", () => {
|
|
657
641
|
render(<Combobox label="Fruit" options={options} />);
|
|
658
642
|
const input = screen.getByRole("combobox");
|
|
659
|
-
|
|
643
|
+
fireEvent.mouseDown(input);
|
|
644
|
+
fireEvent.focus(input);
|
|
660
645
|
const visibleOptions = screen.getAllByRole("option");
|
|
661
646
|
visibleOptions.forEach(option => {
|
|
662
647
|
expect(option).toHaveAttribute("aria-selected", "false");
|
|
@@ -692,18 +677,20 @@ describe("Combobox", () => {
|
|
|
692
677
|
["string groups", FRUITS_STRING_GROUPS],
|
|
693
678
|
["object groups", FRUITS_OBJECT_GROUPS],
|
|
694
679
|
])("Combobox with %s options", (_, options) => {
|
|
695
|
-
it("should render all of the groups in the listbox",
|
|
680
|
+
it("should render all of the groups in the listbox", () => {
|
|
696
681
|
render(<Combobox label="Fruit" options={options} />);
|
|
697
682
|
const input = screen.getByRole("combobox");
|
|
698
|
-
|
|
683
|
+
fireEvent.mouseDown(input);
|
|
684
|
+
fireEvent.focus(input);
|
|
699
685
|
const groups = screen.getAllByRole("group");
|
|
700
686
|
expect(groups).toHaveLength(options.length);
|
|
701
687
|
});
|
|
702
688
|
|
|
703
|
-
it("should render all of the options in each group",
|
|
689
|
+
it("should render all of the options in each group", () => {
|
|
704
690
|
render(<Combobox label="Fruit" options={options} />);
|
|
705
691
|
const input = screen.getByRole("combobox");
|
|
706
|
-
|
|
692
|
+
fireEvent.mouseDown(input);
|
|
693
|
+
fireEvent.focus(input);
|
|
707
694
|
options.forEach(group => {
|
|
708
695
|
const groupElement = screen.getByRole("group", {
|
|
709
696
|
name: group.heading,
|
|
@@ -714,20 +701,22 @@ describe("Combobox", () => {
|
|
|
714
701
|
});
|
|
715
702
|
|
|
716
703
|
describe("classnames", () => {
|
|
717
|
-
it("should have the correct classnames for the groups",
|
|
704
|
+
it("should have the correct classnames for the groups", () => {
|
|
718
705
|
render(<Combobox label="Fruit" options={options} />);
|
|
719
706
|
const input = screen.getByRole("combobox");
|
|
720
|
-
|
|
707
|
+
fireEvent.mouseDown(input);
|
|
708
|
+
fireEvent.focus(input);
|
|
721
709
|
const groups = screen.getAllByRole("group");
|
|
722
710
|
groups.forEach(group => {
|
|
723
711
|
expect(group).toHaveClass("mobius-combobox__group");
|
|
724
712
|
});
|
|
725
713
|
});
|
|
726
714
|
|
|
727
|
-
it("should have the correct classnames for the group labels",
|
|
715
|
+
it("should have the correct classnames for the group labels", () => {
|
|
728
716
|
render(<Combobox label="Fruit" options={options} />);
|
|
729
717
|
const input = screen.getByRole("combobox");
|
|
730
|
-
|
|
718
|
+
fireEvent.mouseDown(input);
|
|
719
|
+
fireEvent.focus(input);
|
|
731
720
|
const groupLabels = screen
|
|
732
721
|
.getAllByRole("group")
|
|
733
722
|
.map(group => group.firstChild);
|
|
@@ -776,10 +765,10 @@ describe("Combobox", () => {
|
|
|
776
765
|
const input = screen.getByRole("combobox");
|
|
777
766
|
await act(async () => {
|
|
778
767
|
await user.type(input, "berry");
|
|
779
|
-
await
|
|
768
|
+
await vi.advanceTimersByTimeAsync(2000);
|
|
780
769
|
});
|
|
781
770
|
await act(async () => {
|
|
782
|
-
await
|
|
771
|
+
await vi.advanceTimersByTimeAsync(2000);
|
|
783
772
|
});
|
|
784
773
|
const visibleOptions = screen.getAllByRole("option");
|
|
785
774
|
expect(visibleOptions).toHaveLength(7);
|
|
@@ -790,7 +779,7 @@ describe("Combobox", () => {
|
|
|
790
779
|
const input = screen.getByRole("combobox");
|
|
791
780
|
await act(async () => {
|
|
792
781
|
await user.type(input, "berry");
|
|
793
|
-
|
|
782
|
+
vi.advanceTimersByTime(500);
|
|
794
783
|
});
|
|
795
784
|
const wrapper = screen.getByTestId("mobius-combobox__wrapper");
|
|
796
785
|
expect(wrapper).toHaveClass("mobius-combobox--is-loading");
|
|
@@ -801,10 +790,10 @@ describe("Combobox", () => {
|
|
|
801
790
|
const input = screen.getByRole("combobox");
|
|
802
791
|
await act(async () => {
|
|
803
792
|
await user.type(input, "berry");
|
|
804
|
-
await
|
|
793
|
+
await vi.advanceTimersByTimeAsync(2000);
|
|
805
794
|
});
|
|
806
795
|
await act(async () => {
|
|
807
|
-
await
|
|
796
|
+
await vi.advanceTimersByTimeAsync(2000);
|
|
808
797
|
});
|
|
809
798
|
const wrapper = screen.getByTestId("mobius-combobox__wrapper");
|
|
810
799
|
expect(wrapper).not.toHaveClass("mobius-combobox--is-loading");
|
|
@@ -2,7 +2,8 @@ import classNames from "classnames/dedupe";
|
|
|
2
2
|
import type React from "react";
|
|
3
3
|
import type { FocusEvent } from "react";
|
|
4
4
|
import { useEffect, useId, useRef, useState } from "react";
|
|
5
|
-
import {
|
|
5
|
+
import { useOnUnmount } from "@simplybusiness/mobius-hooks";
|
|
6
|
+
import { useBreakpoint } from "../../hooks";
|
|
6
7
|
import { TextField } from "../TextField";
|
|
7
8
|
import { VisuallyHidden } from "../VisuallyHidden";
|
|
8
9
|
import { Listbox } from "./Listbox"; // Import Listbox component
|
|
@@ -3,11 +3,11 @@ import { useComboboxOptions } from "./useComboboxOptions";
|
|
|
3
3
|
|
|
4
4
|
describe("useComboboxOptions", () => {
|
|
5
5
|
beforeEach(() => {
|
|
6
|
-
|
|
6
|
+
vi.useFakeTimers({ shouldAdvanceTime: true });
|
|
7
7
|
});
|
|
8
8
|
|
|
9
9
|
afterEach(() => {
|
|
10
|
-
|
|
10
|
+
vi.useRealTimers();
|
|
11
11
|
});
|
|
12
12
|
|
|
13
13
|
describe("basic functionality", () => {
|
|
@@ -75,7 +75,7 @@ describe("useComboboxOptions", () => {
|
|
|
75
75
|
]);
|
|
76
76
|
});
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
it.skip("should handle special characters in filter", () => {
|
|
79
79
|
const options = [
|
|
80
80
|
{ label: "São Paulo", value: "1" },
|
|
81
81
|
{ label: "New York", value: "2" },
|
|
@@ -97,7 +97,7 @@ describe("useComboboxOptions", () => {
|
|
|
97
97
|
|
|
98
98
|
describe("async options handling", () => {
|
|
99
99
|
it("should handle async options", async () => {
|
|
100
|
-
const asyncOptions =
|
|
100
|
+
const asyncOptions = vi.fn().mockResolvedValue([
|
|
101
101
|
{ label: "Async Option 1", value: "1" },
|
|
102
102
|
{ label: "Async Option 2", value: "2" },
|
|
103
103
|
]);
|
|
@@ -126,7 +126,7 @@ describe("useComboboxOptions", () => {
|
|
|
126
126
|
});
|
|
127
127
|
|
|
128
128
|
it("should require a minimum input length for async options", () => {
|
|
129
|
-
const asyncOptions =
|
|
129
|
+
const asyncOptions = vi.fn().mockResolvedValue([
|
|
130
130
|
{ label: "Async Option 1", value: "1" },
|
|
131
131
|
{ label: "Async Option 2", value: "2" },
|
|
132
132
|
]);
|
|
@@ -150,7 +150,7 @@ describe("useComboboxOptions", () => {
|
|
|
150
150
|
});
|
|
151
151
|
|
|
152
152
|
it("should debounce async options calls", async () => {
|
|
153
|
-
const asyncOptions =
|
|
153
|
+
const asyncOptions = vi.fn().mockResolvedValue([
|
|
154
154
|
{ label: "Async Option 1", value: "1" },
|
|
155
155
|
{ label: "Async Option 2", value: "2" },
|
|
156
156
|
]);
|
|
@@ -174,7 +174,7 @@ describe("useComboboxOptions", () => {
|
|
|
174
174
|
expect(asyncOptions).not.toHaveBeenCalled();
|
|
175
175
|
|
|
176
176
|
await act(async () => {
|
|
177
|
-
await
|
|
177
|
+
await vi.advanceTimersByTimeAsync(200);
|
|
178
178
|
});
|
|
179
179
|
|
|
180
180
|
expect(asyncOptions).toHaveBeenCalledTimes(1);
|
|
@@ -182,7 +182,7 @@ describe("useComboboxOptions", () => {
|
|
|
182
182
|
});
|
|
183
183
|
|
|
184
184
|
it("should abort fetch on unmount", () => {
|
|
185
|
-
const asyncOptions =
|
|
185
|
+
const asyncOptions = vi.fn().mockImplementation(
|
|
186
186
|
() =>
|
|
187
187
|
new Promise<void>(resolve => {
|
|
188
188
|
setTimeout(() => resolve(), 100);
|
|
@@ -203,7 +203,7 @@ describe("useComboboxOptions", () => {
|
|
|
203
203
|
});
|
|
204
204
|
|
|
205
205
|
it("should ignore abort errors", async () => {
|
|
206
|
-
const asyncOptions =
|
|
206
|
+
const asyncOptions = vi.fn().mockImplementation(
|
|
207
207
|
() =>
|
|
208
208
|
new Promise((_, reject) => {
|
|
209
209
|
setTimeout(
|
|
@@ -221,7 +221,7 @@ describe("useComboboxOptions", () => {
|
|
|
221
221
|
);
|
|
222
222
|
|
|
223
223
|
await act(async () => {
|
|
224
|
-
await
|
|
224
|
+
await vi.runAllTimersAsync();
|
|
225
225
|
});
|
|
226
226
|
|
|
227
227
|
expect(result.current).toStrictEqual({
|
|
@@ -234,8 +234,8 @@ describe("useComboboxOptions", () => {
|
|
|
234
234
|
});
|
|
235
235
|
|
|
236
236
|
it("should call onSearched when async options have been fetched", async () => {
|
|
237
|
-
const onSearched =
|
|
238
|
-
const asyncOptions =
|
|
237
|
+
const onSearched = vi.fn();
|
|
238
|
+
const asyncOptions = vi.fn().mockResolvedValue([
|
|
239
239
|
{ label: "Async Option 1", value: "1" },
|
|
240
240
|
{ label: "Async Option 2", value: "2" },
|
|
241
241
|
]);
|
|
@@ -255,8 +255,8 @@ describe("useComboboxOptions", () => {
|
|
|
255
255
|
});
|
|
256
256
|
|
|
257
257
|
it("should not call onSearched when input length is less than minSearchLength", async () => {
|
|
258
|
-
const onSearched =
|
|
259
|
-
const asyncOptions =
|
|
258
|
+
const onSearched = vi.fn();
|
|
259
|
+
const asyncOptions = vi.fn().mockResolvedValue([
|
|
260
260
|
{ label: "Async Option 1", value: "1" },
|
|
261
261
|
{ label: "Async Option 2", value: "2" },
|
|
262
262
|
]);
|
|
@@ -277,7 +277,7 @@ describe("useComboboxOptions", () => {
|
|
|
277
277
|
});
|
|
278
278
|
|
|
279
279
|
it("should not call onSearched for synchronous options", async () => {
|
|
280
|
-
const onSearched =
|
|
280
|
+
const onSearched = vi.fn();
|
|
281
281
|
const options = [
|
|
282
282
|
{ label: "Option 1", value: "1" },
|
|
283
283
|
{ label: "Option 2", value: "2" },
|
|
@@ -300,7 +300,7 @@ describe("useComboboxOptions", () => {
|
|
|
300
300
|
|
|
301
301
|
describe("loading states", () => {
|
|
302
302
|
it("should set isLoading to true while fetching async options", async () => {
|
|
303
|
-
const asyncOptions =
|
|
303
|
+
const asyncOptions = vi.fn().mockImplementation(
|
|
304
304
|
() =>
|
|
305
305
|
new Promise(resolve => {
|
|
306
306
|
setTimeout(
|
|
@@ -320,14 +320,14 @@ describe("useComboboxOptions", () => {
|
|
|
320
320
|
expect(result.current.isLoading).toBe(true);
|
|
321
321
|
|
|
322
322
|
await act(async () => {
|
|
323
|
-
await
|
|
323
|
+
await vi.runAllTimersAsync();
|
|
324
324
|
});
|
|
325
325
|
|
|
326
326
|
expect(result.current.isLoading).toBe(false);
|
|
327
327
|
});
|
|
328
328
|
|
|
329
329
|
it("should set isLoading to false after async options fetch success", async () => {
|
|
330
|
-
const asyncOptions =
|
|
330
|
+
const asyncOptions = vi
|
|
331
331
|
.fn()
|
|
332
332
|
.mockResolvedValue([{ label: "Async Option 1", value: "1" }]);
|
|
333
333
|
|
|
@@ -341,14 +341,14 @@ describe("useComboboxOptions", () => {
|
|
|
341
341
|
expect(result.current.isLoading).toBe(true);
|
|
342
342
|
|
|
343
343
|
await act(async () => {
|
|
344
|
-
await
|
|
344
|
+
await vi.runAllTimersAsync();
|
|
345
345
|
});
|
|
346
346
|
|
|
347
347
|
expect(result.current.isLoading).toBe(false);
|
|
348
348
|
});
|
|
349
349
|
|
|
350
350
|
it("should set isLoading to false after async options fetch error", async () => {
|
|
351
|
-
const asyncOptions =
|
|
351
|
+
const asyncOptions = vi.fn().mockImplementation(
|
|
352
352
|
() =>
|
|
353
353
|
new Promise((_, reject) => {
|
|
354
354
|
setTimeout(() => reject(new Error("Fetch error")), 100);
|
|
@@ -365,7 +365,7 @@ describe("useComboboxOptions", () => {
|
|
|
365
365
|
expect(result.current.isLoading).toBe(true);
|
|
366
366
|
|
|
367
367
|
await act(async () => {
|
|
368
|
-
await
|
|
368
|
+
await vi.runAllTimersAsync();
|
|
369
369
|
});
|
|
370
370
|
|
|
371
371
|
expect(result.current.isLoading).toBe(false);
|
|
@@ -374,7 +374,7 @@ describe("useComboboxOptions", () => {
|
|
|
374
374
|
|
|
375
375
|
describe("error states", () => {
|
|
376
376
|
it("should set error state on async options fetch error", async () => {
|
|
377
|
-
const asyncOptions =
|
|
377
|
+
const asyncOptions = vi.fn().mockImplementation(
|
|
378
378
|
() =>
|
|
379
379
|
new Promise((_, reject) => {
|
|
380
380
|
setTimeout(() => reject(new Error("Fetch error")), 100);
|
|
@@ -389,7 +389,7 @@ describe("useComboboxOptions", () => {
|
|
|
389
389
|
);
|
|
390
390
|
|
|
391
391
|
await act(async () => {
|
|
392
|
-
await
|
|
392
|
+
await vi.runAllTimersAsync();
|
|
393
393
|
});
|
|
394
394
|
|
|
395
395
|
expect(result.current).toStrictEqual({
|
|
@@ -402,7 +402,7 @@ describe("useComboboxOptions", () => {
|
|
|
402
402
|
});
|
|
403
403
|
|
|
404
404
|
it("should clear error state when input value changes", async () => {
|
|
405
|
-
const asyncOptions =
|
|
405
|
+
const asyncOptions = vi
|
|
406
406
|
.fn()
|
|
407
407
|
.mockRejectedValueOnce(new Error("Fetch error"))
|
|
408
408
|
.mockResolvedValueOnce([{ label: "Success", value: "1" }]);
|
|
@@ -417,7 +417,7 @@ describe("useComboboxOptions", () => {
|
|
|
417
417
|
);
|
|
418
418
|
|
|
419
419
|
await act(async () => {
|
|
420
|
-
await
|
|
420
|
+
await vi.runAllTimersAsync();
|
|
421
421
|
});
|
|
422
422
|
|
|
423
423
|
expect(result.current.isError).toBe(true);
|
|
@@ -425,7 +425,7 @@ describe("useComboboxOptions", () => {
|
|
|
425
425
|
rerender({ inputValue: "success" });
|
|
426
426
|
|
|
427
427
|
await act(async () => {
|
|
428
|
-
await
|
|
428
|
+
await vi.runAllTimersAsync();
|
|
429
429
|
});
|
|
430
430
|
|
|
431
431
|
expect(result.current.isError).toBe(false);
|
|
@@ -434,7 +434,7 @@ describe("useComboboxOptions", () => {
|
|
|
434
434
|
});
|
|
435
435
|
|
|
436
436
|
it("should reset the error state on async options fetch start", async () => {
|
|
437
|
-
const asyncOptions =
|
|
437
|
+
const asyncOptions = vi.fn().mockImplementation(
|
|
438
438
|
() =>
|
|
439
439
|
new Promise((_, reject) => {
|
|
440
440
|
setTimeout(() => reject(new Error("Fetch error")), 100);
|
|
@@ -449,7 +449,7 @@ describe("useComboboxOptions", () => {
|
|
|
449
449
|
);
|
|
450
450
|
|
|
451
451
|
await act(async () => {
|
|
452
|
-
await
|
|
452
|
+
await vi.runAllTimersAsync();
|
|
453
453
|
});
|
|
454
454
|
|
|
455
455
|
expect(result.current).toStrictEqual({
|
|
@@ -462,7 +462,7 @@ describe("useComboboxOptions", () => {
|
|
|
462
462
|
});
|
|
463
463
|
|
|
464
464
|
it("should reset the error state on successful async options fetch", async () => {
|
|
465
|
-
const asyncOptions =
|
|
465
|
+
const asyncOptions = vi
|
|
466
466
|
.fn()
|
|
467
467
|
.mockResolvedValue([{ label: "Async Option 1", value: "1" }]);
|
|
468
468
|
|
|
@@ -474,7 +474,7 @@ describe("useComboboxOptions", () => {
|
|
|
474
474
|
);
|
|
475
475
|
|
|
476
476
|
await act(async () => {
|
|
477
|
-
await
|
|
477
|
+
await vi.runAllTimersAsync();
|
|
478
478
|
});
|
|
479
479
|
|
|
480
480
|
expect(result.current).toStrictEqual({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useEffect, useState } from "react";
|
|
2
2
|
import type { ComboboxOption, ComboboxOptions, ComboboxProps } from "./types";
|
|
3
3
|
import { filterOptions } from "./utils";
|
|
4
|
-
import { useDebouncedValue } from "
|
|
4
|
+
import { useDebouncedValue } from "@simplybusiness/mobius-hooks";
|
|
5
5
|
|
|
6
6
|
export type UseComboboxOptionsProps<T extends ComboboxOption> = Pick<
|
|
7
7
|
ComboboxProps<T>,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from "@storybook/react
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
2
|
import { excludeControls } from "../../utils";
|
|
3
3
|
import { StoryContainer } from "../../utils/StoryContainer";
|
|
4
4
|
import type { DateFieldProps } from "./DateField";
|