@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.
Files changed (143) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/dist/cjs/index.js +4773 -0
  3. package/dist/cjs/index.js.map +7 -0
  4. package/dist/cjs/meta.json +4948 -0
  5. package/dist/esm/index.js +542 -694
  6. package/dist/esm/index.js.map +4 -4
  7. package/dist/esm/meta.json +300 -532
  8. package/dist/esm/tsconfig.build.tsbuildinfo +1 -1
  9. package/dist/types/src/hooks/index.d.ts +0 -7
  10. package/dist/types/src/utils/htmlDialogPolyfill.d.ts +1 -0
  11. package/dist/types/src/utils/index.d.ts +0 -1
  12. package/dist/types/src/utils/mockMatchMedia.d.ts +1 -0
  13. package/dist/types/vitest.config.d.ts +2 -0
  14. package/package.json +14 -18
  15. package/src/components/Accordion/Accordion.stories.tsx +1 -1
  16. package/src/components/Accordion/Accordion.test.tsx +12 -12
  17. package/src/components/Accordion/Accordion.tsx +1 -1
  18. package/src/components/Accordion/AccordionList.stories.tsx +1 -1
  19. package/src/components/Accordion/AccordionList.test.tsx +6 -6
  20. package/src/components/AddressLookup/AddressLookup.stories.tsx +1 -1
  21. package/src/components/AddressLookup/AddressLookup.test.tsx +19 -20
  22. package/src/components/AddressLookup/LoqateAddressLookupService.test.tsx +7 -6
  23. package/src/components/Alert/Alert.stories.tsx +1 -1
  24. package/src/components/Box/Box.stories.tsx +1 -1
  25. package/src/components/Breadcrumbs/Breadcrumbs.stories.tsx +1 -1
  26. package/src/components/Button/Button.stories.tsx +3 -4
  27. package/src/components/Button/Button.test.tsx +4 -4
  28. package/src/components/Checkbox/Checkbox.stories.tsx +1 -1
  29. package/src/components/Checkbox/Checkbox.test.tsx +2 -2
  30. package/src/components/Checkbox/CheckboxGroup.stories.tsx +1 -1
  31. package/src/components/Checkbox/CheckboxGroup.test.tsx +5 -5
  32. package/src/components/Combobox/Combobox.stories.tsx +1 -1
  33. package/src/components/Combobox/Combobox.test.tsx +67 -78
  34. package/src/components/Combobox/Combobox.tsx +2 -1
  35. package/src/components/Combobox/useComboboxOptions.test.ts +30 -30
  36. package/src/components/Combobox/useComboboxOptions.ts +1 -1
  37. package/src/components/Container/Container.stories.tsx +1 -1
  38. package/src/components/DateField/DateField.stories.tsx +1 -1
  39. package/src/components/DateField/DateField.test.tsx +1 -1
  40. package/src/components/Divider/Divider.stories.tsx +1 -1
  41. package/src/components/Drawer/Drawer.stories.tsx +1 -1
  42. package/src/components/Drawer/Drawer.test.tsx +6 -6
  43. package/src/components/DropdownMenu/DropdownMenu.stories.tsx +8 -10
  44. package/src/components/DropdownMenu/DropdownMenu.test.tsx +1 -1
  45. package/src/components/ErrorMessage/ErrorMessage.stories.tsx +1 -1
  46. package/src/components/ExpandableText/ExpandableText.test.tsx +14 -14
  47. package/src/components/Fieldset/Fieldset.stories.tsx +1 -1
  48. package/src/components/Flex/Flex.stories.tsx +1 -1
  49. package/src/components/Grid/Grid.stories.tsx +4 -7
  50. package/src/components/Icon/Icon.stories.tsx +1 -1
  51. package/src/components/Image/Image.stories.tsx +1 -1
  52. package/src/components/Label/Label.stories.tsx +1 -1
  53. package/src/components/Link/Link.stories.tsx +1 -1
  54. package/src/components/Link/Link.test.tsx +1 -1
  55. package/src/components/LinkButton/LinkButton.stories.tsx +1 -1
  56. package/src/components/LinkButton/LinkButton.test.tsx +2 -2
  57. package/src/components/List/List.stories.tsx +1 -1
  58. package/src/components/LoadingIndicator/LoadingIndicator.stories.tsx +1 -1
  59. package/src/components/Logo/Logo.stories.tsx +1 -1
  60. package/src/components/Modal/Modal.stories.tsx +1 -1
  61. package/src/components/Modal/Modal.test.tsx +6 -6
  62. package/src/components/NumberField/NumberField.stories.tsx +1 -1
  63. package/src/components/NumberField/NumberField.test.tsx +5 -5
  64. package/src/components/PasswordField/PasswordField.stories.tsx +1 -1
  65. package/src/components/Popover/Popover.stories.tsx +4 -8
  66. package/src/components/Popover/Popover.test.tsx +4 -4
  67. package/src/components/Popover/Popover.tsx +1 -1
  68. package/src/components/Progress/Progress.stories.tsx +1 -1
  69. package/src/components/Radio/Radio.stories.tsx +1 -1
  70. package/src/components/Radio/Radio.test.tsx +9 -9
  71. package/src/components/SVG/SVG.stories.tsx +1 -1
  72. package/src/components/Segment/Segment.stories.tsx +1 -1
  73. package/src/components/Select/Select.stories.tsx +1 -1
  74. package/src/components/Select/Select.test.tsx +1 -1
  75. package/src/components/Slider/Slider.stories.tsx +1 -1
  76. package/src/components/Slider/Slider.test.tsx +6 -6
  77. package/src/components/Slider/helpers.test.ts +1 -1
  78. package/src/components/Stack/Stack.stories.tsx +1 -1
  79. package/src/components/Switch/Switch.stories.tsx +1 -1
  80. package/src/components/Switch/Switch.test.tsx +1 -1
  81. package/src/components/Table/Table.stories.tsx +1 -1
  82. package/src/components/Text/Text.stories.tsx +1 -1
  83. package/src/components/TextArea/TextArea.stories.tsx +1 -1
  84. package/src/components/TextArea/TextArea.test.tsx +3 -3
  85. package/src/components/TextField/TextField.stories.tsx +1 -1
  86. package/src/components/TextOrHTML/TextOrHTML.stories.tsx +1 -1
  87. package/src/components/Title/Title.stories.tsx +1 -1
  88. package/src/components/Toast/Toast.stories.tsx +1 -1
  89. package/src/components/Toast/Toast.test.tsx +6 -6
  90. package/src/components/Trust/Trust.stories.tsx +1 -1
  91. package/src/components/VisuallyHidden/VisuallyHidden.stories.tsx +1 -1
  92. package/src/hooks/index.tsx +0 -7
  93. package/src/hooks/useBreakpoint/useBreakpoint.ssr.test.tsx +18 -0
  94. package/src/hooks/useBreakpoint/useBreakpoint.stories.tsx +1 -1
  95. package/src/hooks/useBreakpoint/useBreakpoint.test.tsx +65 -5
  96. package/src/hooks/useBreakpoint/useBreakpoint.tsx +25 -39
  97. package/src/hooks/useButton/useButton.test.tsx +4 -4
  98. package/src/hooks/useDialog/useDialog.ts +1 -1
  99. package/src/hooks/useLabel/useLabel.test.tsx +1 -1
  100. package/src/hooks/useTextField/useTextField.test.tsx +4 -4
  101. package/src/public-whitelist.test.ts +1 -0
  102. package/src/utils/delay.test.ts +4 -4
  103. package/src/utils/{jestHTMLDialogPolyfill.ts → htmlDialogPolyfill.ts} +5 -5
  104. package/src/utils/index.ts +0 -1
  105. package/src/utils/mockMatchMedia.ts +16 -0
  106. package/dist/types/src/hooks/useBodyScrollLock/index.d.ts +0 -1
  107. package/dist/types/src/hooks/useBodyScrollLock/useBodyScrollLock.d.ts +0 -3
  108. package/dist/types/src/hooks/useDebouncedValue/index.d.ts +0 -1
  109. package/dist/types/src/hooks/useDebouncedValue/useDebouncedValue.d.ts +0 -1
  110. package/dist/types/src/hooks/useOnClickOutside/index.d.ts +0 -1
  111. package/dist/types/src/hooks/useOnClickOutside/useOnClickOutside.d.ts +0 -2
  112. package/dist/types/src/hooks/useOnUnmount/index.d.ts +0 -1
  113. package/dist/types/src/hooks/useOnUnmount/useOnUnmount.d.ts +0 -1
  114. package/dist/types/src/hooks/usePrefersReducedMotion/index.d.ts +0 -1
  115. package/dist/types/src/hooks/usePrefersReducedMotion/usePrefersReducedMotion.d.ts +0 -1
  116. package/dist/types/src/hooks/useRenderCount/index.d.ts +0 -1
  117. package/dist/types/src/hooks/useRenderCount/useRenderCount.d.ts +0 -1
  118. package/dist/types/src/hooks/useWindowEvent/index.d.ts +0 -1
  119. package/dist/types/src/hooks/useWindowEvent/useWindowEvent.d.ts +0 -1
  120. package/dist/types/src/utils/jestHTMLDialogPolyfill.d.ts +0 -1
  121. package/dist/types/src/utils/jestMockMatchMedia.d.ts +0 -1
  122. package/src/hooks/useBodyScrollLock/index.ts +0 -1
  123. package/src/hooks/useBodyScrollLock/useBodyScrollLock.test.ts +0 -34
  124. package/src/hooks/useBodyScrollLock/useBodyScrollLock.ts +0 -30
  125. package/src/hooks/useDebouncedValue/index.tsx +0 -1
  126. package/src/hooks/useDebouncedValue/useDebouncedValue.test.tsx +0 -62
  127. package/src/hooks/useDebouncedValue/useDebouncedValue.tsx +0 -25
  128. package/src/hooks/useOnClickOutside/index.tsx +0 -1
  129. package/src/hooks/useOnClickOutside/useOnClickOutside.test.tsx +0 -189
  130. package/src/hooks/useOnClickOutside/useOnClickOutside.tsx +0 -44
  131. package/src/hooks/useOnUnmount/index.tsx +0 -1
  132. package/src/hooks/useOnUnmount/useOnUnmount.test.tsx +0 -37
  133. package/src/hooks/useOnUnmount/useOnUnmount.tsx +0 -8
  134. package/src/hooks/usePrefersReducedMotion/index.tsx +0 -1
  135. package/src/hooks/usePrefersReducedMotion/usePrefersReducedMotion.test.tsx +0 -48
  136. package/src/hooks/usePrefersReducedMotion/usePrefersReducedMotion.tsx +0 -22
  137. package/src/hooks/useRenderCount/index.ts +0 -1
  138. package/src/hooks/useRenderCount/useRenderCount.test.ts +0 -26
  139. package/src/hooks/useRenderCount/useRenderCount.ts +0 -9
  140. package/src/hooks/useWindowEvent/index.tsx +0 -1
  141. package/src/hooks/useWindowEvent/useWindowEvent.test.tsx +0 -188
  142. package/src/hooks/useWindowEvent/useWindowEvent.tsx +0 -41
  143. 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
- beforeAll(() => {
43
+ beforeEach(() => {
44
+ vi.useFakeTimers({ shouldAdvanceTime: true });
44
45
  user = userEvent.setup({
45
- advanceTimers: jest.advanceTimersByTime,
46
+ advanceTimers: vi.advanceTimersByTime.bind(vi),
46
47
  });
47
48
  });
48
49
 
49
- beforeEach(() => {
50
- jest.useFakeTimers();
51
- });
52
-
53
50
  afterEach(() => {
54
- jest.useRealTimers();
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", async () => {
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
- await user.click(input);
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", async () => {
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
- await user.click(input);
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", async () => {
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
- await user.click(input);
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 = jest.fn();
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 = jest.fn();
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 = jest.fn();
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
- jest.advanceTimersByTime(200);
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
- jest.advanceTimersByTime(200);
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 = jest.fn();
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
- jest.advanceTimersByTime(200);
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 = jest.fn();
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 = jest.fn();
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 = jest.fn();
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 = jest.fn();
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 = jest.fn();
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 = jest.fn();
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 = jest.fn();
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 = jest.fn();
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", async () => {
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
- await user.click(input);
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", async () => {
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
- await user.click(input);
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)", async () => {
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
- await user.click(input);
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", async () => {
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
- await user.click(input);
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", async () => {
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
- await user.click(input);
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", async () => {
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
- await user.click(input);
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", async () => {
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
- await user.click(input);
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", async () => {
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
- await user.click(input);
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", async () => {
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
- await user.click(input);
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", async () => {
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
- await user.click(input);
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", async () => {
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
- await user.click(input);
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 jest.advanceTimersByTimeAsync(2000);
768
+ await vi.advanceTimersByTimeAsync(2000);
780
769
  });
781
770
  await act(async () => {
782
- await jest.advanceTimersByTimeAsync(2000);
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
- jest.advanceTimersByTime(500);
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 jest.advanceTimersByTimeAsync(2000);
793
+ await vi.advanceTimersByTimeAsync(2000);
805
794
  });
806
795
  await act(async () => {
807
- await jest.advanceTimersByTimeAsync(2000);
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 { useBreakpoint, useOnUnmount } from "../../hooks";
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
- jest.useFakeTimers();
6
+ vi.useFakeTimers({ shouldAdvanceTime: true });
7
7
  });
8
8
 
9
9
  afterEach(() => {
10
- jest.useRealTimers();
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
- xit("should handle special characters in filter", () => {
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 = jest.fn().mockResolvedValue([
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 = jest.fn().mockResolvedValue([
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 = jest.fn().mockResolvedValue([
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 jest.advanceTimersByTimeAsync(200);
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 = jest.fn().mockImplementation(
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 = jest.fn().mockImplementation(
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 jest.runAllTimersAsync();
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 = jest.fn();
238
- const asyncOptions = jest.fn().mockResolvedValue([
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 = jest.fn();
259
- const asyncOptions = jest.fn().mockResolvedValue([
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 = jest.fn();
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 = jest.fn().mockImplementation(
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 jest.runAllTimersAsync();
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 = jest
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 jest.runAllTimersAsync();
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 = jest.fn().mockImplementation(
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 jest.runAllTimersAsync();
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 = jest.fn().mockImplementation(
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 jest.runAllTimersAsync();
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 = jest
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 jest.runAllTimersAsync();
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 jest.runAllTimersAsync();
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 = jest.fn().mockImplementation(
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 jest.runAllTimersAsync();
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 = jest
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 jest.runAllTimersAsync();
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 "../../hooks";
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-webpack5";
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
2
  import type { ContainerProps } from "./Container";
3
3
  import { Container } from "./Container";
4
4
 
@@ -1,4 +1,4 @@
1
- import type { Meta, StoryObj } from "@storybook/react-webpack5";
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";