@transferwise/components 46.34.0 → 46.35.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 (69) hide show
  1. package/build/index.js +170 -192
  2. package/build/index.js.map +1 -1
  3. package/build/index.mjs +164 -186
  4. package/build/index.mjs.map +1 -1
  5. package/build/types/accordion/AccordionItem/AccordionItem.d.ts.map +1 -1
  6. package/build/types/common/hooks/useMedia.d.ts.map +1 -1
  7. package/build/types/common/panel/Panel.d.ts.map +1 -1
  8. package/build/types/common/responsivePanel/ResponsivePanel.d.ts.map +1 -1
  9. package/build/types/dimmer/Dimmer.d.ts +1 -11
  10. package/build/types/dimmer/Dimmer.d.ts.map +1 -1
  11. package/build/types/drawer/Drawer.d.ts +4 -4
  12. package/build/types/index.d.ts +3 -2
  13. package/build/types/index.d.ts.map +1 -1
  14. package/build/types/inputs/SelectInput.d.ts.map +1 -1
  15. package/build/types/modal/Modal.d.ts.map +1 -1
  16. package/build/types/processIndicator/ProcessIndicator.d.ts +36 -19
  17. package/build/types/processIndicator/ProcessIndicator.d.ts.map +1 -1
  18. package/build/types/processIndicator/index.d.ts +2 -2
  19. package/build/types/processIndicator/index.d.ts.map +1 -1
  20. package/build/types/promoCard/PromoCard.d.ts.map +1 -1
  21. package/build/types/select/searchBox/SearchBox.d.ts +1 -1
  22. package/build/types/snackbar/Snackbar.d.ts +0 -1
  23. package/build/types/snackbar/Snackbar.d.ts.map +1 -1
  24. package/build/types/test-utils/wait.d.ts +2 -0
  25. package/build/types/test-utils/wait.d.ts.map +1 -0
  26. package/build/types/tooltip/Tooltip.d.ts +1 -1
  27. package/build/types/tooltip/Tooltip.d.ts.map +1 -1
  28. package/build/types/uploadInput/uploadItem/UploadItem.d.ts.map +1 -1
  29. package/build/types/withDisplayFormat/WithDisplayFormat.d.ts.map +1 -1
  30. package/package.json +11 -15
  31. package/src/accordion/AccordionItem/AccordionItem.tsx +2 -4
  32. package/src/avatarWrapper/AvatarWrapper.story.tsx +1 -3
  33. package/src/button/Button.tsx +1 -1
  34. package/src/common/hooks/useConditionalListener/useConditionalListener.spec.js +1 -1
  35. package/src/common/hooks/useHasIntersected/useHasIntersected.spec.js +3 -3
  36. package/src/common/hooks/useMedia.spec.ts +1 -1
  37. package/src/common/hooks/useMedia.ts +1 -2
  38. package/src/common/panel/Panel.tsx +90 -92
  39. package/src/common/responsivePanel/ResponsivePanel.tsx +34 -38
  40. package/src/dateLookup/DateLookup.rtl.spec.tsx +181 -5
  41. package/src/dateLookup/DateLookup.testingLibrary.spec.js +171 -124
  42. package/src/drawer/Drawer.js +3 -3
  43. package/src/field/Field.tsx +3 -3
  44. package/src/index.ts +3 -2
  45. package/src/inputs/SelectInput.story.tsx +3 -2
  46. package/src/inputs/SelectInput.tsx +10 -2
  47. package/src/modal/Modal.tsx +1 -2
  48. package/src/processIndicator/ProcessIndicator.rtl.spec.tsx +45 -0
  49. package/src/processIndicator/ProcessIndicator.tsx +110 -0
  50. package/src/promoCard/PromoCard.tsx +1 -2
  51. package/src/radio/__snapshots__/Radio.rtl.spec.tsx.snap +0 -1
  52. package/src/snackbar/Snackbar.spec.js +8 -2
  53. package/src/snackbar/Snackbar.story.tsx +3 -1
  54. package/src/snackbar/Snackbar.tsx +1 -1
  55. package/src/tabs/Tabs.spec.js +46 -27
  56. package/src/test-utils/index.js +5 -7
  57. package/src/test-utils/jest.setup.js +9 -3
  58. package/src/test-utils/wait.ts +7 -0
  59. package/src/tooltip/Tooltip.tsx +44 -46
  60. package/src/tooltip/__snapshots__/Tooltip.spec.tsx.snap +2 -2
  61. package/src/upload/Upload.spec.js +34 -13
  62. package/src/uploadInput/UploadInput.spec.tsx +21 -23
  63. package/src/uploadInput/uploadItem/UploadItem.tsx +1 -3
  64. package/src/withDisplayFormat/WithDisplayFormat.spec.js +63 -32
  65. package/src/withDisplayFormat/WithDisplayFormat.tsx +4 -6
  66. package/src/dateLookup/DateLookup.keyboardEvents.spec.js +0 -180
  67. package/src/processIndicator/ProcessIndicator.js +0 -117
  68. package/src/processIndicator/ProcessIndicator.spec.js +0 -101
  69. /package/src/processIndicator/{index.js → index.ts} +0 -0
@@ -1,9 +1,10 @@
1
1
  import { mount } from 'enzyme';
2
2
  import ReactDOM from 'react-dom';
3
3
 
4
- import SnackbarAppendingToBody, { Snackbar, CSS_TRANSITION_DURATION } from './Snackbar';
4
+ import SnackbarAppendingToBody, { Snackbar } from './Snackbar';
5
5
  import { SnackbarConsumer } from './SnackbarContext';
6
6
  import SnackbarProvider from './SnackbarProvider';
7
+ import { act } from 'react';
7
8
 
8
9
  describe('Snackbar', () => {
9
10
  const timeout = 1000;
@@ -78,7 +79,12 @@ describe('Snackbar', () => {
78
79
  expect(snackbar().text()).toContain(props.text);
79
80
  expect(snackbar()).toHaveLength(1);
80
81
 
81
- jest.advanceTimersByTime(timeout + CSS_TRANSITION_DURATION + 500);
82
+ await act(async () => {
83
+ await jest.runOnlyPendingTimersAsync();
84
+ });
85
+ await act(async () => {
86
+ await jest.runOnlyPendingTimersAsync();
87
+ });
82
88
 
83
89
  expect(snackbar().text()).not.toContain(props.text);
84
90
  });
@@ -1,11 +1,12 @@
1
1
  import { action } from '@storybook/addon-actions';
2
2
  import { number } from '@storybook/addon-knobs';
3
3
  import { StoryContext } from '@storybook/react';
4
- import { userEvent, within } from '@storybook/test';
4
+ import { userEvent, waitFor, within } from '@storybook/test';
5
5
  import { Mobile, Theme, Switch, Bulb, Info, Coins } from '@transferwise/icons';
6
6
 
7
7
  import Button from '../button';
8
8
  import CheckboxOption from '../checkboxOption/CheckboxOption';
9
+ import { wait } from '../test-utils/wait';
9
10
 
10
11
  import { Snackbar } from './Snackbar';
11
12
  import { SnackbarConsumer } from './SnackbarContext';
@@ -122,6 +123,7 @@ export const basic = () => {
122
123
  };
123
124
 
124
125
  basic.play = async ({ canvasElement }: StoryContext) => {
126
+ await wait(0); // TODO: Remove
125
127
  const canvas = within(canvasElement);
126
128
  await userEvent.click(canvas.getByRole('button'));
127
129
  };
@@ -7,7 +7,7 @@ import { Theme, ThemeDark, ThemeLight } from '../common';
7
7
  import { DirectionContext } from '../provider/direction';
8
8
  import withNextPortal from '../withNextPortal/withNextPortal';
9
9
 
10
- export const CSS_TRANSITION_DURATION = 400;
10
+ const CSS_TRANSITION_DURATION = 400;
11
11
 
12
12
  export interface SnackbarProps {
13
13
  action?: {
@@ -1,5 +1,6 @@
1
1
  import { Spring } from '@react-spring/web';
2
2
  import { mount } from 'enzyme';
3
+ import { act } from 'react';
3
4
 
4
5
  import { Size, Width } from '../common';
5
6
 
@@ -8,8 +9,6 @@ import TabPanel from './TabPanel';
8
9
  import Tabs from './Tabs';
9
10
  import { getElasticDragDifference } from './utils';
10
11
 
11
- jest.useFakeTimers();
12
-
13
12
  jest.mock('@react-spring/web', () => ({
14
13
  animated: {
15
14
  div: ({ children, style }) => (
@@ -37,6 +36,7 @@ describe('Tabs', () => {
37
36
  let props;
38
37
 
39
38
  beforeEach(() => {
39
+ jest.useFakeTimers();
40
40
  props = {
41
41
  animatePanelsOnClick: true,
42
42
  tabs: generateTabs(),
@@ -57,6 +57,11 @@ describe('Tabs', () => {
57
57
  jest.clearAllMocks();
58
58
  });
59
59
 
60
+ afterEach(async () => {
61
+ await jest.runOnlyPendingTimersAsync();
62
+ jest.useRealTimers();
63
+ });
64
+
60
65
  it('renders with right props', () => {
61
66
  expect(component.find(Tabs)).toHaveLength(1);
62
67
  expect(component.find(Tabs).props()).toStrictEqual({ ...props });
@@ -172,11 +177,13 @@ describe('Tabs', () => {
172
177
  expect(component.find(Tab)).toHaveLength(props.tabs.length);
173
178
  });
174
179
 
175
- it('does not animate when a tab before the selected tab goes from disabled to enabled', () => {
180
+ it('does not animate when a tab before the selected tab goes from disabled to enabled', async () => {
176
181
  component.setProps({ selected: 2 });
177
182
  expect(component.state('isAnimating')).toBe(true);
178
183
 
179
- triggerSpringOnRest();
184
+ await act(async () => {
185
+ await jest.runOnlyPendingTimersAsync();
186
+ });
180
187
  expect(component.state('isAnimating')).toBe(false);
181
188
 
182
189
  component.setProps({ tabs: generateTabs([false, false, false]) });
@@ -248,14 +255,16 @@ describe('Tabs', () => {
248
255
  ${5} | ${Width.AUTO} | ${'240px'} | ${'-900px'}
249
256
  `(
250
257
  'when selecting tab number %selected when headerWidth is set to %headerWidth',
251
- ({ selected, headerWidth, lineTranslateX, sliderTranslateX }) => {
258
+ async ({ selected, headerWidth, lineTranslateX, sliderTranslateX }) => {
252
259
  component.setProps({ headerWidth });
253
260
 
254
261
  const getLineStyles = () => getComputedStyle(component.find('.tabs__line').getDOMNode());
255
262
  const getSliderStyles = () =>
256
263
  getComputedStyle(component.find('.tabs__slider').getDOMNode());
257
264
 
258
- component.setProps({ selected });
265
+ await act(async () => {
266
+ component.setProps({ selected });
267
+ });
259
268
 
260
269
  expect(component.state('isAnimating')).toBe(true);
261
270
  expect(getLineStyles().getPropertyValue('transform')).toBe(`translateX(${lineTranslateX})`);
@@ -263,7 +272,9 @@ describe('Tabs', () => {
263
272
  `translateX(${sliderTranslateX})`,
264
273
  );
265
274
 
266
- triggerSpringOnRest();
275
+ await act(async () => {
276
+ await jest.runOnlyPendingTimersAsync();
277
+ });
267
278
 
268
279
  expect(component.state('isAnimating')).toBe(false);
269
280
  expect(getLineStyles().getPropertyValue('transform')).toBe(`translateX(${lineTranslateX})`);
@@ -276,15 +287,11 @@ describe('Tabs', () => {
276
287
 
277
288
  const getLineStyles = () => getComputedStyle(component.find('.tabs__line').getDOMNode());
278
289
 
279
- triggerSpringOnRest();
280
-
281
290
  expect(getLineStyles().getPropertyValue('transform')).toBe(`translateX(60px)`);
282
291
  expect(component.state('fullWidthTabs')).toBeFalsy();
283
292
 
284
293
  component.setProps({ tabs: generateTabs([false, true, false, false, false, false, false]) });
285
294
 
286
- triggerSpringOnRest();
287
-
288
295
  expect(getLineStyles().getPropertyValue('transform')).toBe(`translateX(100%)`);
289
296
  expect(component.state('fullWidthTabs')).toBeTruthy();
290
297
  });
@@ -319,41 +326,57 @@ describe('Tabs', () => {
319
326
  });
320
327
  });
321
328
 
322
- it('displays all tabs when animating', () => {
323
- component.setState({ isAnimating: true });
329
+ it('displays all tabs when animating', async () => {
330
+ await act(async () => {
331
+ component.setState({ isAnimating: true });
332
+ });
324
333
 
325
334
  component.find(TabPanel).forEach((tab) => {
326
335
  expect(tab.prop('style').display).toBe('block');
327
336
  });
328
337
  });
329
338
 
330
- it('displays all tabs when swiping', () => {
331
- component.setState({ isSwiping: true });
339
+ it('displays all tabs when swiping', async () => {
340
+ await act(async () => {
341
+ component.setState({ isSwiping: true });
342
+ });
332
343
 
333
344
  component.find(TabPanel).forEach((tab) => {
334
345
  expect(tab.prop('style').display).toBe('block');
335
346
  });
336
347
  });
337
348
 
338
- it('has `overflow: hidden` on the parent when animating/swiping', () => {
339
- component.setState({ isSwiping: false, isAnimating: false });
349
+ it('has `overflow: hidden` on the parent when animating/swiping', async () => {
350
+ await act(async () => {
351
+ component.setState({ isSwiping: false, isAnimating: false });
352
+ });
340
353
  expect(getPanelContainerOverflow(component)).toBe('visible');
341
354
 
342
- component.setState({ isSwiping: true });
355
+ await act(async () => {
356
+ component.setState({ isSwiping: true });
357
+ });
343
358
  expect(getPanelContainerOverflow(component)).toBe('hidden');
344
359
 
345
- component.setState({ isSwiping: false, isAnimating: true });
360
+ await act(async () => {
361
+ component.setState({ isSwiping: false, isAnimating: true });
362
+ });
346
363
  expect(getPanelContainerOverflow(component)).toBe('hidden');
347
364
  });
348
365
 
349
- it('sets the panel width according to if animating/swiping', () => {
350
- component.setState({ isSwiping: false, isAnimating: false });
366
+ it('sets the panel width according to if animating/swiping', async () => {
367
+ await act(async () => {
368
+ component.setState({ isSwiping: false, isAnimating: false });
369
+ });
351
370
  expect(getPanelWidth(component)).toBe('100%');
352
371
 
353
- component.setState({ isSwiping: true });
372
+ await act(async () => {
373
+ component.setState({ isSwiping: true });
374
+ });
354
375
  expect(getPanelWidth(component)).toBe('300px');
355
376
 
356
- component.setState({ isSwiping: false, isAnimating: true });
377
+ await act(async () => {
378
+ component.setState({ isSwiping: false, isAnimating: true });
379
+ });
357
380
  expect(getPanelWidth(component)).toBe('300px');
358
381
  });
359
382
  });
@@ -373,10 +396,6 @@ function createClientXY(x, y) {
373
396
  return { clientX: x, clientY: y };
374
397
  }
375
398
 
376
- function triggerSpringOnRest() {
377
- jest.runAllTimers();
378
- }
379
-
380
399
  function getPanelContainerOverflow(component) {
381
400
  return component.find('.tabs__panel-container').prop('style').overflow;
382
401
  }
@@ -1,5 +1,4 @@
1
- import { render } from '@testing-library/react';
2
- import { renderHook } from '@testing-library/react-hooks';
1
+ import { render, renderHook } from '@testing-library/react';
3
2
  import userEvent from '@testing-library/user-event';
4
3
 
5
4
  import { Provider } from '..';
@@ -30,9 +29,8 @@ function customRenderHook(callback, { locale = DEFAULT_LOCALE, messages = en } =
30
29
  });
31
30
  }
32
31
 
33
- export * from './window-mock';
34
- export * from './story-config';
35
- export * from './fake-data';
36
32
  export * from '@testing-library/react';
37
- export { userEvent };
38
- export { customRender as render, customRenderHook as renderHook };
33
+ export * from './fake-data';
34
+ export * from './story-config';
35
+ export * from './window-mock';
36
+ export { customRender as render, customRenderHook as renderHook, userEvent };
@@ -1,17 +1,23 @@
1
- const Adapter = require('@wojtekmaj/enzyme-adapter-react-17');
1
+ const { default: Adapter } = require('@cfaester/enzyme-adapter-react-18');
2
2
  const Enzyme = require('enzyme');
3
+ const util = require('node:util');
3
4
 
4
5
  global.fetch = require('jest-fetch-mock');
6
+
5
7
  Enzyme.configure({ adapter: new Adapter() });
6
8
 
7
9
  global.requestAnimationFrame = (callback) => callback();
8
10
 
11
+ Object.defineProperty(global, 'TextEncoder', {
12
+ value: util.TextEncoder,
13
+ });
14
+
9
15
  // https://github.com/esphen/jest-prop-type-error/blob/master/index.js
10
16
  // This mock will make tests fail when props error occurs.
11
17
  const { error, warn } = console;
12
18
  // eslint-disable-next-line no-console
13
19
  console.error = (message, ...args) => {
14
- if (/(Invalid prop|Failed prop type)/gi.test(message)) {
20
+ if (/(Invalid prop|Failed prop type)/i.test(message)) {
15
21
  throw new Error(message);
16
22
  }
17
23
 
@@ -20,7 +26,7 @@ console.error = (message, ...args) => {
20
26
 
21
27
  // eslint-disable-next-line no-console
22
28
  console.warn = (message, ...args) => {
23
- if (/(Call to useTheme outside a ThemeProvider)/gi.test(message)) {
29
+ if (/(Call to useTheme outside a ThemeProvider)/i.test(message)) {
24
30
  return;
25
31
  }
26
32
 
@@ -0,0 +1,7 @@
1
+ export async function wait(ms: number) {
2
+ return new Promise<void>((resolve) => {
3
+ window.setTimeout(() => {
4
+ resolve();
5
+ }, ms);
6
+ });
7
+ }
@@ -1,14 +1,14 @@
1
1
  /* eslint-disable @typescript-eslint/ban-ts-comment */
2
- import { useId } from '@radix-ui/react-id';
3
2
  import classNames from 'classnames';
4
3
  import {
4
+ PropsWithChildren,
5
+ ReactElement,
6
+ ReactNode,
5
7
  cloneElement,
8
+ useEffect,
9
+ useId,
6
10
  useRef,
7
11
  useState,
8
- useEffect,
9
- ReactNode,
10
- ReactElement,
11
- PropsWithChildren,
12
12
  } from 'react';
13
13
  import { usePopper } from 'react-popper';
14
14
 
@@ -75,49 +75,47 @@ const Tooltip = ({
75
75
  }, [open]);
76
76
 
77
77
  return (
78
- <>
79
- <span
80
- ref={anchorReference}
81
- className="tw-tooltip-container"
82
- onMouseOver={() => setOpen(true)}
83
- onFocus={() => setOpen(true)}
84
- onMouseOut={() => setOpen(false)}
85
- onBlur={() => setOpen(false)}
78
+ <span
79
+ ref={anchorReference}
80
+ className="tw-tooltip-container"
81
+ onMouseOver={() => setOpen(true)}
82
+ onFocus={() => setOpen(true)}
83
+ onMouseOut={() => setOpen(false)}
84
+ onBlur={() => setOpen(false)}
85
+ >
86
+ {children
87
+ ? cloneElement(children as ReactElement, {
88
+ 'aria-describedby': `${tooltipId}-tooltip`,
89
+ })
90
+ : null}
91
+ <div
92
+ // @ts-expect-error
93
+ ref={setPopperElement}
94
+ className={classNames(
95
+ 'np-tooltip',
96
+ 'np-panel',
97
+ open ? `np-panel--open np-tooltip--open` : null,
98
+ className,
99
+ )}
100
+ // eslint-disable-next-line react/forbid-dom-props
101
+ style={{ ...styles.popper }}
102
+ {...attributes.popper}
103
+ aria-hidden={!open}
104
+ role="tooltip"
105
+ id={`${tooltipId}-tooltip`}
86
106
  >
87
- {children
88
- ? cloneElement(children as ReactElement, {
89
- 'aria-describedby': `${tooltipId}-tooltip`,
90
- })
91
- : null}
92
- <div
93
- // @ts-expect-error
94
- ref={setPopperElement}
95
- className={classNames(
96
- 'np-tooltip',
97
- 'np-panel',
98
- open ? `np-panel--open np-tooltip--open` : null,
99
- className,
100
- )}
101
- // eslint-disable-next-line react/forbid-dom-props
102
- style={{ ...styles.popper }}
103
- {...attributes.popper}
104
- aria-hidden={!open}
105
- role="tooltip"
106
- id={`${tooltipId}-tooltip`}
107
- >
108
- <div className="np-panel__content tooltip-inner">
109
- {label}
110
- <div
111
- // @ts-expect-error
112
- ref={setArrowElement}
113
- className={classNames('np-panel__arrow')}
114
- // eslint-disable-next-line react/forbid-dom-props
115
- style={styles.arrow}
116
- />
117
- </div>
107
+ <div className="np-panel__content tooltip-inner">
108
+ {label}
109
+ <div
110
+ // @ts-expect-error
111
+ ref={setArrowElement}
112
+ className={classNames('np-panel__arrow')}
113
+ // eslint-disable-next-line react/forbid-dom-props
114
+ style={styles.arrow}
115
+ />
118
116
  </div>
119
- </span>
120
- </>
117
+ </div>
118
+ </span>
121
119
  );
122
120
  };
123
121
 
@@ -6,14 +6,14 @@ exports[`Tooltip Component renders an empty list when no items are passed 1`] =
6
6
  class="tw-tooltip-container"
7
7
  >
8
8
  <span
9
- aria-describedby="radix-0-tooltip"
9
+ aria-describedby=":r0:-tooltip"
10
10
  >
11
11
  Hover me
12
12
  </span>
13
13
  <div
14
14
  aria-hidden="true"
15
15
  class="np-tooltip np-panel"
16
- id="radix-0-tooltip"
16
+ id=":r0:-tooltip"
17
17
  role="tooltip"
18
18
  style="position: absolute; left: 0px; top: 0px;"
19
19
  >
@@ -1,4 +1,5 @@
1
1
  import { shallow, mount } from 'enzyme';
2
+ import { act } from 'react';
2
3
 
3
4
  import { ANIMATION_DURATION_IN_MS } from '../processIndicator';
4
5
 
@@ -6,7 +7,6 @@ import { CompleteStep, UploadImageStep, MediaUploadStep, ProcessingStep } from '
6
7
 
7
8
  import Upload from '.';
8
9
 
9
- jest.useFakeTimers();
10
10
  jest.mock('./utils/postData', () => ({
11
11
  postData: async () => 'ServerResponse',
12
12
  }));
@@ -15,12 +15,12 @@ jest.mock('./utils/asyncFileRead');
15
15
  const { asyncFileRead } = require('./utils/asyncFileRead');
16
16
 
17
17
  const defaultLocale = 'en-GB';
18
- const formatMessage = (id) => `${id}`;
18
+ const formatMessage = (id) => String(id);
19
19
  jest.mock('react-intl', () => ({
20
20
  injectIntl: (Component) =>
21
21
  function (props) {
22
22
  return (
23
- <Component {...props} intl={{ locale: defaultLocale, formatMessage: (id) => `${id}` }} />
23
+ <Component {...props} intl={{ locale: defaultLocale, formatMessage: (id) => String(id) }} />
24
24
  );
25
25
  },
26
26
  useIntl: () => ({ formatMessage }),
@@ -87,11 +87,14 @@ const COMPLETED_STEP_PROPS = {
87
87
  describe('Upload', () => {
88
88
  let component;
89
89
  beforeEach(() => {
90
+ jest.useFakeTimers();
90
91
  component = shallow(<Upload {...props} />).dive();
91
92
  asyncFileRead.mockImplementation(async () => 'a value');
92
93
  });
93
94
 
94
- afterEach(() => {
95
+ afterEach(async () => {
96
+ await jest.runOnlyPendingTimersAsync();
97
+ jest.useRealTimers();
95
98
  jest.clearAllMocks();
96
99
  jest.clearAllTimers();
97
100
  });
@@ -213,6 +216,14 @@ describe('Upload', () => {
213
216
  });
214
217
 
215
218
  describe('when file is processed', () => {
219
+ const waitForUpload = async () => {
220
+ for (let i = 0; i < 4; i += 1) {
221
+ await act(async () => {
222
+ await jest.runOnlyPendingTimersAsync();
223
+ });
224
+ }
225
+ };
226
+
216
227
  it('step changes from UploadImageStep to CompleteStep', async () => {
217
228
  component = mount(<Upload {...props} />);
218
229
  const upload = component.children();
@@ -220,8 +231,10 @@ describe('Upload', () => {
220
231
  expect(upload.find(ProcessingStep)).toHaveLength(0);
221
232
  expect(upload.find(CompleteStep)).toHaveLength(0);
222
233
 
223
- await upload.instance().fileDropped(TEST_FILE);
224
- jest.advanceTimersByTime(props.animationDelay + ANIMATION_DELAY);
234
+ await act(async () => {
235
+ await upload.instance().fileDropped(TEST_FILE);
236
+ });
237
+ await waitForUpload();
225
238
  component.update();
226
239
 
227
240
  expect(component.find(UploadImageStep)).toHaveLength(0);
@@ -236,8 +249,10 @@ describe('Upload', () => {
236
249
  throw 'An error';
237
250
  });
238
251
 
239
- await upload.instance().fileDropped(TEST_FILE);
240
- jest.advanceTimersByTime(props.animationDelay + ANIMATION_DELAY);
252
+ await act(async () => {
253
+ await upload.instance().fileDropped(TEST_FILE);
254
+ });
255
+ await waitForUpload();
241
256
  component.update();
242
257
 
243
258
  expect(component.find(CompleteStep).props()).toStrictEqual({
@@ -252,8 +267,10 @@ describe('Upload', () => {
252
267
  it('onSuccess is called with response when httpOptions are provided', async () => {
253
268
  component = mount(<Upload {...props} httpOptions={{ url: 'a-url' }} />).children();
254
269
 
255
- await component.instance().fileDropped(TEST_FILE);
256
- jest.advanceTimersByTime(props.animationDelay + ANIMATION_DELAY);
270
+ await act(async () => {
271
+ await component.instance().fileDropped(TEST_FILE);
272
+ });
273
+ await waitForUpload();
257
274
 
258
275
  expect(props.onSuccess).toHaveBeenCalledWith('ServerResponse', TEST_FILE.name);
259
276
  });
@@ -261,11 +278,15 @@ describe('Upload', () => {
261
278
  it('wont process new file while current process is in progress', async () => {
262
279
  component = mount(<Upload {...props} httpOptions={{ url: 'a-url' }} />).children();
263
280
 
264
- await component.instance().fileDropped(TEST_FILE);
265
- const result = await component.instance().fileDropped(TEST_FILE);
266
- jest.advanceTimersByTime(props.animationDelay + ANIMATION_DELAY);
281
+ await act(async () => {
282
+ await component.instance().fileDropped(TEST_FILE);
283
+ });
284
+ const result = await act(async () => component.instance().fileDropped(TEST_FILE));
267
285
 
268
286
  expect(result).toBe(false);
287
+
288
+ await waitForUpload();
289
+
269
290
  expect(props.onSuccess).toHaveBeenCalledTimes(1);
270
291
  });
271
292
  });
@@ -7,8 +7,7 @@ import { mockMatchMedia, render, screen, waitFor, waitForElementToBeRemoved } fr
7
7
  import UploadInput, { UploadInputProps } from './UploadInput';
8
8
  import { TEST_IDS as UPLOAD_BUTTON_TEST_IDS } from './uploadButton/UploadButton';
9
9
  import { TEST_IDS as UPLOAD_ITEM_TEST_IDS } from './uploadItem/UploadItem';
10
-
11
- const spiedDateObject = jest.spyOn(global, 'Date');
10
+ import { act } from 'react';
12
11
 
13
12
  mockMatchMedia();
14
13
 
@@ -42,43 +41,35 @@ describe('UploadInput', () => {
42
41
  render(<UploadInput {...customProps} />);
43
42
 
44
43
  beforeEach(() => {
45
- (spiedDateObject as jest.Mock).mockImplementation(() => ({
46
- getTime: jest.fn().mockReturnValue(Math.random()),
47
- }));
44
+ jest.useFakeTimers();
48
45
  });
49
46
 
50
- afterAll(() => {
51
- (spiedDateObject as jest.Mock).mockRestore();
47
+ afterEach(async () => {
48
+ await jest.runOnlyPendingTimersAsync();
49
+ jest.useRealTimers();
52
50
  });
53
51
 
54
52
  describe('single file upload', () => {
55
53
  it('should trigger onUploadFiles & onFilesChange with a single FormData entry containing `file` field', async () => {
56
- const mockTimeStampValue1 = '11111111';
57
- const mockTimeStampValue2 = '22222222';
58
- (spiedDateObject as jest.Mock).mockImplementation(() => ({
59
- getTime: jest
60
- .fn()
61
- .mockReturnValueOnce(mockTimeStampValue1)
62
- .mockReturnValueOnce(mockTimeStampValue2),
63
- }));
54
+ const date = Date.now();
55
+ jest.setSystemTime(date);
64
56
 
65
57
  const onFilesChange = jest.fn();
66
58
  renderComponent({ ...props, onFilesChange });
67
59
 
68
60
  const input = screen.getByTestId(UPLOAD_BUTTON_TEST_IDS.uploadInput);
69
61
  userEvent.upload(input, [pngFile, jpgFile]);
70
-
71
- await waitFor(() => {
72
- expect(props.onUploadFile).toHaveBeenCalledTimes(1);
62
+ await act(async () => {
63
+ await jest.runOnlyPendingTimersAsync();
73
64
  });
74
65
 
66
+ expect(props.onUploadFile).toHaveBeenCalledTimes(1);
75
67
  expect(onFilesChange).toHaveBeenCalledTimes(2);
76
68
  expect(onFilesChange).toHaveBeenNthCalledWith(1, [
77
69
  {
78
70
  filename: 'foo.png',
79
- id: `foo.png_3_${mockTimeStampValue1}`,
71
+ id: `foo.png_3_${date}`,
80
72
  status: 'pending',
81
- url: undefined,
82
73
  },
83
74
  ]);
84
75
 
@@ -150,6 +141,10 @@ describe('UploadInput', () => {
150
141
 
151
142
  const fileToDelete = screen.getAllByTestId(UPLOAD_ITEM_TEST_IDS.uploadItem)[0];
152
143
  within(fileToDelete).getByLabelText('Remove file', { exact: false }).click();
144
+ await act(async () => {
145
+ await jest.runOnlyPendingTimersAsync();
146
+ });
147
+
153
148
  screen.getByText('Remove').click();
154
149
 
155
150
  await waitForElementToBeRemoved(fileToDelete);
@@ -179,7 +174,7 @@ describe('UploadInput', () => {
179
174
  ]);
180
175
  });
181
176
 
182
- it('should delete file with failed state without modal confirmation', () => {
177
+ it('should delete file with failed state without modal confirmation', async () => {
183
178
  const files = [
184
179
  {
185
180
  id: 1,
@@ -197,6 +192,9 @@ describe('UploadInput', () => {
197
192
 
198
193
  const fileToDelete = screen.getAllByTestId(UPLOAD_ITEM_TEST_IDS.uploadItem)[0];
199
194
  within(fileToDelete).getByLabelText('Remove file', { exact: false }).click();
195
+ await act(async () => {
196
+ await jest.runOnlyPendingTimersAsync();
197
+ });
200
198
 
201
199
  expect(fileToDelete).not.toBeInTheDocument();
202
200
 
@@ -240,7 +238,7 @@ describe('UploadInput', () => {
240
238
  onUploadFile: mockOnUploadFileFn,
241
239
  });
242
240
 
243
- mockOnUploadFileFn.mockImplementation((formData: FormData) => {
241
+ mockOnUploadFileFn.mockImplementation(async (formData: FormData) => {
244
242
  const file = formData.get('file');
245
243
  return Promise.resolve({ file, id: Math.random() });
246
244
  });
@@ -268,7 +266,7 @@ describe('UploadInput', () => {
268
266
  onUploadFile: mockOnUploadFileFn,
269
267
  });
270
268
 
271
- mockOnUploadFileFn.mockImplementation((formData: FormData) => {
269
+ mockOnUploadFileFn.mockImplementation(async (formData: FormData) => {
272
270
  const file = formData.get('file');
273
271
  return Promise.resolve({ file, id: Math.random() });
274
272
  });
@@ -83,9 +83,7 @@ const UploadItem = ({
83
83
  };
84
84
 
85
85
  const getErrorMessage = () =>
86
- (typeof error === 'object' && error.message) ||
87
- error ||
88
- formatMessage(MESSAGES.uploadingFailed);
86
+ typeof error === 'object' ? error.message : error || formatMessage(MESSAGES.uploadingFailed);
89
87
 
90
88
  const getDescription = () => {
91
89
  if (error || status === Status.FAILED) {