uikit-react-public 0.30.1 → 0.32.4

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 (49) hide show
  1. package/dist/components/Heading/Heading.stories.d.ts +5 -7
  2. package/dist/components/MenuNew/Menu.context.d.ts +9 -0
  3. package/dist/components/MenuNew/Menu.d.ts +2 -1
  4. package/dist/components/MenuNew/MenuHeading.d.ts +1 -1
  5. package/dist/components/MenuNew/MenuItemText.d.ts +2 -1
  6. package/dist/components/MenuNew/PrimaryMenuItem.d.ts +1 -1
  7. package/dist/components/Select/Select.types.d.ts +1 -1
  8. package/dist/components/Sidebar/Sidebar.d.ts +12 -0
  9. package/dist/components/Sidebar/Sidebar.stories.d.ts +38 -0
  10. package/dist/components/Sidebar/__tests__/Sidebar.test.d.ts +1 -0
  11. package/dist/components/Sidebar/index.d.ts +2 -0
  12. package/dist/components/Snackbar/Snackbar.d.ts +9 -5
  13. package/dist/components/Snackbar/Snackbar.stories.d.ts +3 -1
  14. package/dist/components/Snackbar/Snackbars.d.ts +19 -0
  15. package/dist/components/Snackbar/Snackbars.stories.d.ts +52 -0
  16. package/dist/components/Snackbar/__tests__/Snackbars.test.d.ts +1 -0
  17. package/dist/components/Snackbar/index.d.ts +2 -0
  18. package/dist/components/index.d.ts +4 -1
  19. package/dist/index.js +5979 -5448
  20. package/lib/components/Dropdown/Dropdown.stories.tsx +69 -74
  21. package/lib/components/Heading/Documentation.mdx +4 -14
  22. package/lib/components/Heading/Heading.stories.tsx +16 -30
  23. package/lib/components/MenuNew/Menu.context.tsx +18 -0
  24. package/lib/components/MenuNew/Menu.tsx +14 -9
  25. package/lib/components/MenuNew/MenuDivider.tsx +12 -1
  26. package/lib/components/MenuNew/MenuHeading.tsx +6 -0
  27. package/lib/components/MenuNew/MenuItemText.tsx +27 -3
  28. package/lib/components/MenuNew/MenuSection.tsx +11 -0
  29. package/lib/components/MenuNew/PrimaryMenuItem.tsx +71 -2
  30. package/lib/components/Select/Select.types.ts +1 -7
  31. package/lib/components/Select/__tests__/Select.test.tsx +112 -0
  32. package/lib/components/Select/__tests__/__snapshots__/Select.test.tsx.snap +5 -0
  33. package/lib/components/Select/subcomponents/CustomSelect.tsx +75 -3
  34. package/lib/components/Select/subcomponents/FilterInput.tsx +1 -1
  35. package/lib/components/Sidebar/Sidebar.stories.tsx +94 -0
  36. package/lib/components/Sidebar/Sidebar.tsx +208 -0
  37. package/lib/components/Sidebar/__tests__/Sidebar.test.tsx +96 -0
  38. package/lib/components/Sidebar/__tests__/__snapshots__/Sidebar.test.tsx.snap +272 -0
  39. package/lib/components/Sidebar/index.ts +2 -0
  40. package/lib/components/Snackbar/Snackbar.stories.tsx +22 -0
  41. package/lib/components/Snackbar/Snackbar.tsx +168 -119
  42. package/lib/components/Snackbar/Snackbars.stories.tsx +141 -0
  43. package/lib/components/Snackbar/Snackbars.tsx +377 -0
  44. package/lib/components/Snackbar/__tests__/Snackbar.test.tsx +222 -10
  45. package/lib/components/Snackbar/__tests__/Snackbars.test.tsx +377 -0
  46. package/lib/components/Snackbar/__tests__/__snapshots__/Snackbar.test.tsx.snap +46 -25
  47. package/lib/components/Snackbar/index.ts +6 -0
  48. package/lib/components/index.ts +10 -1
  49. package/package.json +1 -1
@@ -2,18 +2,32 @@ import { describe, expect, test, vi, beforeEach } from 'vitest';
2
2
  import { render, screen } from '@testing-library/react';
3
3
  // import '@testing-library/jest-dom';
4
4
  import Snackbar from '../Snackbar';
5
- import { ThemeContextProvider } from '../../../theme/useTheme';
5
+ import Icon from '../../Icon';
6
+ import { ThemeContextProvider, themes } from '../../../theme/useTheme';
7
+
8
+ const tokenTestTheme = {
9
+ ...themes.light,
10
+ colour: {
11
+ ...themes.light.colour,
12
+ fill: {
13
+ ...themes.light.colour.fill,
14
+ brandPrimary: '#010101',
15
+ critical: '#020202',
16
+ success: '#030303',
17
+ },
18
+ },
19
+ };
6
20
 
7
21
  describe('Snackbar', () => {
8
22
  beforeEach(() => {
9
- vi.clearAllMocks();
23
+ vi.restoreAllMocks();
10
24
  });
11
25
 
12
26
  // Snapshot tests
13
27
 
14
28
  test('snapshot: minimal props', () => {
15
29
  const renderResult = render(
16
- <ThemeContextProvider>
30
+ <ThemeContextProvider theme='light'>
17
31
  <Snackbar action='close'>Test snackbar</Snackbar>
18
32
  </ThemeContextProvider>
19
33
  );
@@ -22,7 +36,7 @@ describe('Snackbar', () => {
22
36
 
23
37
  test('snapshot: custom test id', () => {
24
38
  const renderResult = render(
25
- <ThemeContextProvider>
39
+ <ThemeContextProvider theme='light'>
26
40
  <Snackbar
27
41
  action='undo'
28
42
  testId='custom-test-id'
@@ -39,7 +53,7 @@ describe('Snackbar', () => {
39
53
  test('Test ID: Default', () => {
40
54
  const defaultTestId = 'ucl-uikit-snackbar';
41
55
  render(
42
- <ThemeContextProvider>
56
+ <ThemeContextProvider theme='light'>
43
57
  <Snackbar action='close'>Test snackbar</Snackbar>
44
58
  </ThemeContextProvider>
45
59
  );
@@ -49,7 +63,7 @@ describe('Snackbar', () => {
49
63
 
50
64
  test('Test ID: Custom', () => {
51
65
  render(
52
- <ThemeContextProvider>
66
+ <ThemeContextProvider theme='light'>
53
67
  <Snackbar
54
68
  action='undo'
55
69
  testId='custom-test-id'
@@ -62,10 +76,123 @@ describe('Snackbar', () => {
62
76
  expect(customTestId).toBeDefined();
63
77
  });
64
78
 
79
+ test('passes ref to the root element', () => {
80
+ const ref = { current: null as HTMLDivElement | null };
81
+
82
+ render(
83
+ <ThemeContextProvider theme='light'>
84
+ <Snackbar ref={ref}>Test snackbar</Snackbar>
85
+ </ThemeContextProvider>
86
+ );
87
+
88
+ expect(ref.current).toBe(screen.getByTestId('ucl-uikit-snackbar'));
89
+ });
90
+
91
+ test('Default variant uses polite status semantics', () => {
92
+ render(
93
+ <ThemeContextProvider theme='light'>
94
+ <Snackbar action='undo'>Test snackbar</Snackbar>
95
+ </ThemeContextProvider>
96
+ );
97
+
98
+ const snackbar = screen.getByRole('status');
99
+ expect(snackbar.textContent).toBe('Test snackbar');
100
+ });
101
+
102
+ test('Error variant uses assertive alert semantics', () => {
103
+ render(
104
+ <ThemeContextProvider theme='light'>
105
+ <Snackbar
106
+ action='close'
107
+ variant='error'
108
+ >
109
+ Test snackbar
110
+ </Snackbar>
111
+ </ThemeContextProvider>
112
+ );
113
+
114
+ const snackbar = screen.getByRole('alert');
115
+ expect(snackbar.textContent).toBe('Test snackbar');
116
+ });
117
+
118
+ test('Success variant uses polite status semantics', () => {
119
+ render(
120
+ <ThemeContextProvider theme='light'>
121
+ <Snackbar
122
+ action='close'
123
+ variant='success'
124
+ >
125
+ Test snackbar
126
+ </Snackbar>
127
+ </ThemeContextProvider>
128
+ );
129
+
130
+ const snackbar = screen.getByRole('status');
131
+ expect(snackbar.textContent).toBe('Test snackbar');
132
+ });
133
+
134
+ test.each([
135
+ ['default', 'rgb(1, 1, 1)'],
136
+ ['error', 'rgb(2, 2, 2)'],
137
+ ['success', 'rgb(3, 3, 3)'],
138
+ ] as const)(
139
+ '%s variant uses its semantic background token',
140
+ (variant, colour) => {
141
+ render(
142
+ <ThemeContextProvider theme={tokenTestTheme}>
143
+ <Snackbar
144
+ action='close'
145
+ variant={variant}
146
+ >
147
+ Test snackbar
148
+ </Snackbar>
149
+ </ThemeContextProvider>
150
+ );
151
+
152
+ expect(
153
+ getComputedStyle(screen.getByTestId('ucl-uikit-snackbar'))
154
+ .backgroundColor
155
+ ).toBe(colour);
156
+ }
157
+ );
158
+
159
+ test('Close button has a default accessible name', () => {
160
+ render(
161
+ <ThemeContextProvider theme='light'>
162
+ <Snackbar action='close'>Test snackbar</Snackbar>
163
+ </ThemeContextProvider>
164
+ );
165
+
166
+ const closeButton = screen.getByRole('button', {
167
+ name: 'Dismiss notification',
168
+ });
169
+
170
+ expect(closeButton).toBeDefined();
171
+ });
172
+
173
+ test('Close button accessible name can be customised', () => {
174
+ render(
175
+ <ThemeContextProvider theme='light'>
176
+ <Snackbar
177
+ action='close'
178
+ closeButtonAriaLabel='Dismiss saved notification'
179
+ >
180
+ Test snackbar
181
+ </Snackbar>
182
+ </ThemeContextProvider>
183
+ );
184
+
185
+ const closeButton = screen.getByRole('button', {
186
+ name: 'Dismiss saved notification',
187
+ });
188
+
189
+ expect(closeButton).toBeDefined();
190
+ });
191
+
65
192
  test('Close callback is called', () => {
66
193
  const onClose = vi.fn();
67
194
  render(
68
- <ThemeContextProvider>
195
+ <ThemeContextProvider theme='light'>
69
196
  <Snackbar
70
197
  action='close'
71
198
  onClose={onClose}
@@ -82,7 +209,7 @@ describe('Snackbar', () => {
82
209
  test('Undo callback is called', () => {
83
210
  const onUndo = vi.fn();
84
211
  render(
85
- <ThemeContextProvider>
212
+ <ThemeContextProvider theme='light'>
86
213
  <Snackbar
87
214
  action='undo'
88
215
  onUndo={onUndo}
@@ -96,10 +223,95 @@ describe('Snackbar', () => {
96
223
  expect(onUndo).toHaveBeenCalled();
97
224
  });
98
225
 
226
+ test('Undo action uses sentence case label', () => {
227
+ render(
228
+ <ThemeContextProvider theme='light'>
229
+ <Snackbar action='undo'>Test snackbar</Snackbar>
230
+ </ThemeContextProvider>
231
+ );
232
+
233
+ expect(screen.getByRole('button', { name: 'Undo' })).toBeDefined();
234
+ });
235
+
236
+ test('Default variant renders info icon', () => {
237
+ vi.spyOn(Icon, 'Info').mockImplementation((props) => (
238
+ <svg
239
+ data-testid='info-icon'
240
+ {...props}
241
+ />
242
+ ));
243
+
244
+ render(
245
+ <ThemeContextProvider theme='light'>
246
+ <Snackbar action='close'>Test snackbar</Snackbar>
247
+ </ThemeContextProvider>
248
+ );
249
+
250
+ expect(screen.getByTestId('info-icon')).toBeDefined();
251
+ });
252
+
253
+ test('Error variant renders warning icon', () => {
254
+ vi.spyOn(Icon, 'AlertTriangle').mockImplementation((props) => (
255
+ <svg
256
+ data-testid='alert-triangle-icon'
257
+ {...props}
258
+ />
259
+ ));
260
+
261
+ render(
262
+ <ThemeContextProvider theme='light'>
263
+ <Snackbar
264
+ action='close'
265
+ variant='error'
266
+ >
267
+ Test snackbar
268
+ </Snackbar>
269
+ </ThemeContextProvider>
270
+ );
271
+
272
+ expect(screen.getByTestId('alert-triangle-icon')).toBeDefined();
273
+ });
274
+
275
+ test('Success variant renders check icon', () => {
276
+ vi.spyOn(Icon, 'CheckCircle').mockImplementation((props) => (
277
+ <svg
278
+ data-testid='check-circle-icon'
279
+ {...props}
280
+ />
281
+ ));
282
+
283
+ render(
284
+ <ThemeContextProvider theme='light'>
285
+ <Snackbar
286
+ action='close'
287
+ variant='success'
288
+ >
289
+ Test snackbar
290
+ </Snackbar>
291
+ </ThemeContextProvider>
292
+ );
293
+
294
+ expect(screen.getByTestId('check-circle-icon')).toBeDefined();
295
+ });
296
+
297
+ test('Decorative icons are hidden from assistive technology', () => {
298
+ render(
299
+ <ThemeContextProvider theme='light'>
300
+ <Snackbar action='close'>Test snackbar</Snackbar>
301
+ </ThemeContextProvider>
302
+ );
303
+
304
+ const icons = screen.getAllByTestId('ucl-uikit-icon');
305
+
306
+ icons.forEach((icon) => {
307
+ expect(icon.getAttribute('aria-hidden')).toBe('true');
308
+ });
309
+ });
310
+
99
311
  test("Close callback cannot be called when action is 'undo'", () => {
100
312
  const onClose = vi.fn();
101
313
  render(
102
- <ThemeContextProvider>
314
+ <ThemeContextProvider theme='light'>
103
315
  <Snackbar
104
316
  action='undo'
105
317
  onClose={onClose}
@@ -117,7 +329,7 @@ describe('Snackbar', () => {
117
329
  test("Undo callback cannot be called when action is 'close'", () => {
118
330
  const onUndo = vi.fn();
119
331
  render(
120
- <ThemeContextProvider>
332
+ <ThemeContextProvider theme='light'>
121
333
  <Snackbar
122
334
  action='close'
123
335
  onUndo={onUndo}
@@ -0,0 +1,377 @@
1
+ import { act, render, screen } from '@testing-library/react';
2
+ import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
3
+ import { ThemeContextProvider } from '../../../theme/useTheme';
4
+ import Snackbars, { type SnackbarsItem } from '../Snackbars';
5
+
6
+ const renderSnackbars = (
7
+ items: SnackbarsItem[],
8
+ onDismiss = vi.fn(),
9
+ props: Partial<React.ComponentProps<typeof Snackbars>> = {}
10
+ ) =>
11
+ render(
12
+ <ThemeContextProvider>
13
+ <Snackbars
14
+ items={items}
15
+ onDismiss={onDismiss}
16
+ duration={0}
17
+ {...props}
18
+ />
19
+ </ThemeContextProvider>
20
+ );
21
+
22
+ describe('Snackbars', () => {
23
+ beforeEach(() => {
24
+ vi.useFakeTimers();
25
+ });
26
+
27
+ afterEach(() => {
28
+ vi.clearAllTimers();
29
+ vi.useRealTimers();
30
+ });
31
+
32
+ test('renders a single snackbar with default lifecycle wrapper', () => {
33
+ renderSnackbars([{ id: 'saved', children: 'Saved' }]);
34
+
35
+ expect(screen.getByTestId('ucl-uikit-snackbars')).toBeDefined();
36
+ expect(screen.getByTestId('ucl-uikit-snackbars-saved')).toBeDefined();
37
+ expect(screen.getByText('Saved')).toBeDefined();
38
+ });
39
+
40
+ test('passes snackbar props through to each rendered snackbar', () => {
41
+ const onFocus = vi.fn();
42
+ const onBlur = vi.fn();
43
+
44
+ renderSnackbars([
45
+ {
46
+ id: 'saved',
47
+ children: 'Saved',
48
+ closeButtonAriaLabel: 'Close saved notification',
49
+ onFocus,
50
+ onBlur,
51
+ tabIndex: 0,
52
+ title: 'Saved notification',
53
+ },
54
+ ]);
55
+
56
+ const snackbar = screen.getByTestId('ucl-uikit-snackbars-saved');
57
+
58
+ expect(snackbar).toHaveAttribute('title', 'Saved notification');
59
+ expect(
60
+ screen.getByLabelText('Close saved notification')
61
+ ).toBeInTheDocument();
62
+
63
+ act(() => {
64
+ snackbar.focus();
65
+ });
66
+
67
+ expect(onFocus).toHaveBeenCalledTimes(1);
68
+
69
+ act(() => {
70
+ snackbar.blur();
71
+ });
72
+
73
+ expect(onBlur).toHaveBeenCalledTimes(1);
74
+ });
75
+
76
+ test('renders up to maxVisible snackbars', () => {
77
+ renderSnackbars(
78
+ [
79
+ { id: 'one', children: 'One' },
80
+ { id: 'two', children: 'Two' },
81
+ { id: 'three', children: 'Three' },
82
+ ],
83
+ vi.fn(),
84
+ { maxVisible: 2 }
85
+ );
86
+
87
+ expect(screen.getByText('One')).toBeDefined();
88
+ expect(screen.getByText('Two')).toBeDefined();
89
+ expect(screen.queryByText('Three')).toBeNull();
90
+ });
91
+
92
+ test('close action dismisses after transition duration', () => {
93
+ const onDismiss = vi.fn();
94
+ renderSnackbars([{ id: 'saved', children: 'Saved' }], onDismiss, {
95
+ transitionDuration: 200,
96
+ });
97
+
98
+ const closeButton = screen.getByTestId(
99
+ 'ucl-uikit-snackbars-saved-close-button'
100
+ );
101
+
102
+ act(() => {
103
+ closeButton.click();
104
+ });
105
+
106
+ expect(closeButton).toBeDisabled();
107
+ expect(onDismiss).not.toHaveBeenCalled();
108
+
109
+ act(() => {
110
+ vi.advanceTimersByTime(200);
111
+ });
112
+
113
+ expect(onDismiss).toHaveBeenCalledWith('saved');
114
+ });
115
+
116
+ test('close action is disabled while exiting', () => {
117
+ const onClose = vi.fn();
118
+ const onDismiss = vi.fn();
119
+ renderSnackbars([{ id: 'saved', children: 'Saved', onClose }], onDismiss, {
120
+ transitionDuration: 200,
121
+ });
122
+
123
+ const closeButton = screen.getByTestId(
124
+ 'ucl-uikit-snackbars-saved-close-button'
125
+ );
126
+
127
+ act(() => {
128
+ closeButton.click();
129
+ closeButton.click();
130
+ });
131
+
132
+ expect(closeButton).toBeDisabled();
133
+ expect(onClose).toHaveBeenCalledTimes(1);
134
+ expect(onDismiss).not.toHaveBeenCalled();
135
+ });
136
+
137
+ test('undo action calls onUndo and dismisses after transition duration', () => {
138
+ const onDismiss = vi.fn();
139
+ const onUndo = vi.fn();
140
+ renderSnackbars(
141
+ [
142
+ {
143
+ id: 'undoable',
144
+ action: 'undo',
145
+ children: 'Undoable',
146
+ onUndo,
147
+ },
148
+ ],
149
+ onDismiss,
150
+ { transitionDuration: 200 }
151
+ );
152
+
153
+ act(() => {
154
+ screen.getByTestId('ucl-uikit-snackbars-undoable-undo-button').click();
155
+ });
156
+
157
+ expect(
158
+ screen.getByTestId('ucl-uikit-snackbars-undoable-undo-button')
159
+ ).toBeDisabled();
160
+ expect(onUndo).toHaveBeenCalledTimes(1);
161
+ expect(onDismiss).not.toHaveBeenCalled();
162
+
163
+ act(() => {
164
+ vi.advanceTimersByTime(200);
165
+ });
166
+
167
+ expect(onDismiss).toHaveBeenCalledWith('undoable');
168
+ });
169
+
170
+ test('undo action is disabled while exiting', () => {
171
+ const onDismiss = vi.fn();
172
+ const onUndo = vi.fn();
173
+ renderSnackbars(
174
+ [
175
+ {
176
+ id: 'undoable',
177
+ action: 'undo',
178
+ children: 'Undoable',
179
+ onUndo,
180
+ },
181
+ ],
182
+ onDismiss,
183
+ { transitionDuration: 200 }
184
+ );
185
+
186
+ const undoButton = screen.getByTestId(
187
+ 'ucl-uikit-snackbars-undoable-undo-button'
188
+ );
189
+
190
+ act(() => {
191
+ undoButton.click();
192
+ undoButton.click();
193
+ });
194
+
195
+ expect(undoButton).toBeDisabled();
196
+ expect(onUndo).toHaveBeenCalledTimes(1);
197
+ expect(onDismiss).not.toHaveBeenCalled();
198
+ });
199
+
200
+ test('auto-dismisses after duration and transition duration', () => {
201
+ const onDismiss = vi.fn();
202
+ renderSnackbars([{ id: 'saved', children: 'Saved' }], onDismiss, {
203
+ duration: 100,
204
+ transitionDuration: 200,
205
+ });
206
+
207
+ act(() => {
208
+ vi.advanceTimersByTime(100);
209
+ });
210
+
211
+ expect(onDismiss).not.toHaveBeenCalled();
212
+
213
+ act(() => {
214
+ vi.advanceTimersByTime(200);
215
+ });
216
+
217
+ expect(onDismiss).toHaveBeenCalledWith('saved');
218
+ });
219
+
220
+ test('does not auto-dismiss when duration is zero', () => {
221
+ const onDismiss = vi.fn();
222
+ renderSnackbars([{ id: 'saved', children: 'Saved' }], onDismiss, {
223
+ duration: 0,
224
+ transitionDuration: 50,
225
+ });
226
+
227
+ act(() => {
228
+ vi.advanceTimersByTime(1000);
229
+ });
230
+
231
+ expect(onDismiss).not.toHaveBeenCalled();
232
+ });
233
+
234
+ test('undo action still auto-dismisses when duration is zero', () => {
235
+ const onDismiss = vi.fn();
236
+ renderSnackbars(
237
+ [{ id: 'undoable', action: 'undo', children: 'Undoable' }],
238
+ onDismiss,
239
+ {
240
+ duration: 0,
241
+ transitionDuration: 50,
242
+ }
243
+ );
244
+
245
+ act(() => {
246
+ vi.advanceTimersByTime(5000);
247
+ });
248
+
249
+ expect(onDismiss).not.toHaveBeenCalled();
250
+
251
+ act(() => {
252
+ vi.advanceTimersByTime(50);
253
+ });
254
+
255
+ expect(onDismiss).toHaveBeenCalledWith('undoable');
256
+ });
257
+
258
+ test('undo action item duration zero falls back to the collection duration', () => {
259
+ const onDismiss = vi.fn();
260
+ renderSnackbars(
261
+ [
262
+ {
263
+ id: 'undoable',
264
+ action: 'undo',
265
+ children: 'Undoable',
266
+ duration: 0,
267
+ },
268
+ ],
269
+ onDismiss,
270
+ {
271
+ duration: 100,
272
+ transitionDuration: 50,
273
+ }
274
+ );
275
+
276
+ act(() => {
277
+ vi.advanceTimersByTime(100);
278
+ });
279
+
280
+ expect(onDismiss).not.toHaveBeenCalled();
281
+
282
+ act(() => {
283
+ vi.advanceTimersByTime(50);
284
+ });
285
+
286
+ expect(onDismiss).toHaveBeenCalledWith('undoable');
287
+ });
288
+
289
+ test('pauses auto-dismiss while a snackbar has focus', () => {
290
+ const onDismiss = vi.fn();
291
+ renderSnackbars([{ id: 'saved', children: 'Saved' }], onDismiss, {
292
+ duration: 100,
293
+ transitionDuration: 50,
294
+ });
295
+
296
+ act(() => {
297
+ vi.advanceTimersByTime(60);
298
+ });
299
+
300
+ act(() => {
301
+ screen.getByTestId('ucl-uikit-snackbars-saved-close-button').focus();
302
+ });
303
+
304
+ act(() => {
305
+ vi.advanceTimersByTime(500);
306
+ });
307
+
308
+ expect(onDismiss).not.toHaveBeenCalled();
309
+
310
+ act(() => {
311
+ screen.getByTestId('ucl-uikit-snackbars-saved-close-button').blur();
312
+ });
313
+
314
+ act(() => {
315
+ vi.advanceTimersByTime(39);
316
+ });
317
+
318
+ expect(onDismiss).not.toHaveBeenCalled();
319
+
320
+ act(() => {
321
+ vi.advanceTimersByTime(1);
322
+ });
323
+
324
+ expect(onDismiss).not.toHaveBeenCalled();
325
+
326
+ act(() => {
327
+ vi.advanceTimersByTime(50);
328
+ });
329
+
330
+ expect(onDismiss).toHaveBeenCalledWith('saved');
331
+ });
332
+
333
+ test('adding a snackbar does not refresh existing snackbar duration timers', () => {
334
+ const onDismiss = vi.fn();
335
+ const { rerender } = render(
336
+ <ThemeContextProvider>
337
+ <Snackbars
338
+ items={[{ id: 'first', children: 'First' }]}
339
+ onDismiss={onDismiss}
340
+ duration={100}
341
+ transitionDuration={50}
342
+ />
343
+ </ThemeContextProvider>
344
+ );
345
+
346
+ act(() => {
347
+ vi.advanceTimersByTime(75);
348
+ });
349
+
350
+ rerender(
351
+ <ThemeContextProvider>
352
+ <Snackbars
353
+ items={[
354
+ { id: 'first', children: 'First' },
355
+ { id: 'second', children: 'Second' },
356
+ ]}
357
+ onDismiss={onDismiss}
358
+ duration={100}
359
+ transitionDuration={50}
360
+ />
361
+ </ThemeContextProvider>
362
+ );
363
+
364
+ act(() => {
365
+ vi.advanceTimersByTime(25);
366
+ });
367
+
368
+ expect(onDismiss).not.toHaveBeenCalled();
369
+
370
+ act(() => {
371
+ vi.advanceTimersByTime(50);
372
+ });
373
+
374
+ expect(onDismiss).toHaveBeenCalledTimes(1);
375
+ expect(onDismiss).toHaveBeenCalledWith('first');
376
+ });
377
+ });