@transferwise/components 0.0.0-experimental-faee41b → 0.0.0-experimental-96d193a

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 (112) hide show
  1. package/build/alert/Alert.js +3 -1
  2. package/build/alert/Alert.js.map +1 -1
  3. package/build/alert/Alert.mjs +3 -1
  4. package/build/alert/Alert.mjs.map +1 -1
  5. package/build/dimmer/Dimmer.js +1 -3
  6. package/build/dimmer/Dimmer.js.map +1 -1
  7. package/build/dimmer/Dimmer.mjs +1 -3
  8. package/build/dimmer/Dimmer.mjs.map +1 -1
  9. package/build/drawer/Drawer.js +0 -2
  10. package/build/drawer/Drawer.js.map +1 -1
  11. package/build/drawer/Drawer.mjs +0 -2
  12. package/build/drawer/Drawer.mjs.map +1 -1
  13. package/build/field/Field.js +2 -0
  14. package/build/field/Field.js.map +1 -1
  15. package/build/field/Field.mjs +2 -0
  16. package/build/field/Field.mjs.map +1 -1
  17. package/build/i18n/en.json +5 -0
  18. package/build/i18n/en.json.js +5 -0
  19. package/build/i18n/en.json.js.map +1 -1
  20. package/build/i18n/en.json.mjs +5 -0
  21. package/build/i18n/en.json.mjs.map +1 -1
  22. package/build/inlineAlert/InlineAlert.js +3 -1
  23. package/build/inlineAlert/InlineAlert.js.map +1 -1
  24. package/build/inlineAlert/InlineAlert.mjs +3 -1
  25. package/build/inlineAlert/InlineAlert.mjs.map +1 -1
  26. package/build/modal/Modal.js +0 -3
  27. package/build/modal/Modal.js.map +1 -1
  28. package/build/modal/Modal.mjs +0 -3
  29. package/build/modal/Modal.mjs.map +1 -1
  30. package/build/statusIcon/StatusIcon.js +50 -16
  31. package/build/statusIcon/StatusIcon.js.map +1 -1
  32. package/build/statusIcon/StatusIcon.messages.js +24 -0
  33. package/build/statusIcon/StatusIcon.messages.js.map +1 -0
  34. package/build/statusIcon/StatusIcon.messages.mjs +22 -0
  35. package/build/statusIcon/StatusIcon.messages.mjs.map +1 -0
  36. package/build/statusIcon/StatusIcon.mjs +48 -14
  37. package/build/statusIcon/StatusIcon.mjs.map +1 -1
  38. package/build/types/alert/Alert.d.ts +6 -1
  39. package/build/types/alert/Alert.d.ts.map +1 -1
  40. package/build/types/dimmer/Dimmer.d.ts +1 -2
  41. package/build/types/dimmer/Dimmer.d.ts.map +1 -1
  42. package/build/types/drawer/Drawer.d.ts +1 -2
  43. package/build/types/drawer/Drawer.d.ts.map +1 -1
  44. package/build/types/field/Field.d.ts +6 -1
  45. package/build/types/field/Field.d.ts.map +1 -1
  46. package/build/types/inlineAlert/InlineAlert.d.ts +2 -1
  47. package/build/types/inlineAlert/InlineAlert.d.ts.map +1 -1
  48. package/build/types/modal/Modal.d.ts +1 -2
  49. package/build/types/modal/Modal.d.ts.map +1 -1
  50. package/build/types/statusIcon/StatusIcon.d.ts +6 -1
  51. package/build/types/statusIcon/StatusIcon.d.ts.map +1 -1
  52. package/build/types/statusIcon/StatusIcon.messages.d.ts +29 -0
  53. package/build/types/statusIcon/StatusIcon.messages.d.ts.map +1 -0
  54. package/build/types/upload/Upload.d.ts +5 -0
  55. package/build/types/upload/Upload.d.ts.map +1 -1
  56. package/build/types/upload/steps/uploadImageStep/uploadImageStep.d.ts +1 -0
  57. package/build/types/upload/steps/uploadImageStep/uploadImageStep.d.ts.map +1 -1
  58. package/build/types/uploadInput/UploadInput.d.ts +0 -9
  59. package/build/types/uploadInput/UploadInput.d.ts.map +1 -1
  60. package/build/types/uploadInput/uploadItem/UploadItem.d.ts +1 -16
  61. package/build/types/uploadInput/uploadItem/UploadItem.d.ts.map +1 -1
  62. package/build/types/uploadInput/uploadItem/UploadItemLink.d.ts.map +1 -1
  63. package/build/upload/Upload.js +4 -2
  64. package/build/upload/Upload.js.map +1 -1
  65. package/build/upload/Upload.mjs +4 -2
  66. package/build/upload/Upload.mjs.map +1 -1
  67. package/build/upload/steps/uploadImageStep/uploadImageStep.js +4 -2
  68. package/build/upload/steps/uploadImageStep/uploadImageStep.js.map +1 -1
  69. package/build/upload/steps/uploadImageStep/uploadImageStep.mjs +4 -2
  70. package/build/upload/steps/uploadImageStep/uploadImageStep.mjs.map +1 -1
  71. package/build/uploadInput/UploadInput.js +66 -71
  72. package/build/uploadInput/UploadInput.js.map +1 -1
  73. package/build/uploadInput/UploadInput.mjs +67 -72
  74. package/build/uploadInput/UploadInput.mjs.map +1 -1
  75. package/build/uploadInput/uploadItem/UploadItem.js +4 -13
  76. package/build/uploadInput/uploadItem/UploadItem.js.map +1 -1
  77. package/build/uploadInput/uploadItem/UploadItem.mjs +4 -13
  78. package/build/uploadInput/uploadItem/UploadItem.mjs.map +1 -1
  79. package/build/uploadInput/uploadItem/UploadItemLink.js +0 -1
  80. package/build/uploadInput/uploadItem/UploadItemLink.js.map +1 -1
  81. package/build/uploadInput/uploadItem/UploadItemLink.mjs +0 -1
  82. package/build/uploadInput/uploadItem/UploadItemLink.mjs.map +1 -1
  83. package/package.json +3 -3
  84. package/src/alert/Alert.spec.tsx +10 -0
  85. package/src/alert/Alert.tsx +7 -1
  86. package/src/dimmer/Dimmer.spec.js +0 -8
  87. package/src/dimmer/Dimmer.tsx +0 -4
  88. package/src/drawer/Drawer.spec.js +6 -25
  89. package/src/drawer/Drawer.tsx +1 -3
  90. package/src/field/Field.spec.tsx +19 -0
  91. package/src/field/Field.story.tsx +20 -4
  92. package/src/field/Field.tsx +7 -1
  93. package/src/i18n/en.json +5 -0
  94. package/src/inlineAlert/InlineAlert.spec.tsx +12 -1
  95. package/src/inlineAlert/InlineAlert.tsx +5 -1
  96. package/src/modal/Modal.spec.js +1 -19
  97. package/src/modal/Modal.tsx +0 -4
  98. package/src/statusIcon/StatusIcon.docs.mdx +22 -0
  99. package/src/statusIcon/StatusIcon.messages.ts +34 -0
  100. package/src/statusIcon/StatusIcon.spec.tsx +31 -4
  101. package/src/statusIcon/StatusIcon.story.tsx +2 -5
  102. package/src/statusIcon/StatusIcon.tsx +59 -14
  103. package/src/upload/Upload.spec.js +19 -0
  104. package/src/upload/Upload.tsx +7 -0
  105. package/src/upload/steps/uploadImageStep/uploadImageStep.spec.js +13 -0
  106. package/src/upload/steps/uploadImageStep/uploadImageStep.tsx +14 -3
  107. package/src/uploadInput/UploadInput.spec.tsx +9 -121
  108. package/src/uploadInput/UploadInput.tests.story.tsx +140 -209
  109. package/src/uploadInput/UploadInput.tsx +77 -110
  110. package/src/uploadInput/uploadItem/UploadItem.spec.tsx +0 -1
  111. package/src/uploadInput/uploadItem/UploadItem.tsx +6 -30
  112. package/src/uploadInput/uploadItem/UploadItemLink.tsx +1 -9
@@ -0,0 +1,34 @@
1
+ import { defineMessages } from 'react-intl';
2
+
3
+ export default defineMessages({
4
+ errorLabel: {
5
+ id: 'neptune.StatusIcon.iconLabel.error',
6
+ defaultMessage: 'Error.',
7
+ description:
8
+ 'Visually hidden label read by screen readers, describing the Error icon – normally a prefix to remaining visible content, e.g. validation result.',
9
+ },
10
+ successLabel: {
11
+ id: 'neptune.StatusIcon.iconLabel.success',
12
+ defaultMessage: 'Success.',
13
+ description:
14
+ 'Visually hidden label read by screen readers, describing the Success icon – normally a prefix to remaining visible content, e.g. validation result.',
15
+ },
16
+ warningLabel: {
17
+ id: 'neptune.StatusIcon.iconLabel.warning',
18
+ defaultMessage: 'Warning.',
19
+ description:
20
+ 'Visually hidden label read by screen readers, describing the Warning icon – normally a prefix to remaining visible content, e.g. validation result.',
21
+ },
22
+ pendingLabel: {
23
+ id: 'neptune.StatusIcon.iconLabel.pending',
24
+ defaultMessage: 'Pending.',
25
+ description:
26
+ 'Visually hidden label read by screen readers, describing the Pending icon – normally a prefix to remaining visible content, e.g. validation result.',
27
+ },
28
+ informationLabel: {
29
+ id: 'neptune.StatusIcon.iconLabel.information',
30
+ defaultMessage: 'Information.',
31
+ description:
32
+ 'Visually hidden label read by screen readers, describing the Information icon – normally a prefix to remaining visible content, e.g. validation result.',
33
+ },
34
+ });
@@ -1,19 +1,21 @@
1
- import { Sentiment, Size, SizeLarge, SizeMedium, SizeSmall } from '../common';
1
+ import { Sentiment, Size } from '../common';
2
2
  import { render, cleanup, screen, mockMatchMedia } from '../test-utils';
3
3
 
4
- import StatusIcon from '.';
4
+ import StatusIcon, { StatusIconProps } from '.';
5
5
 
6
6
  mockMatchMedia();
7
7
 
8
8
  describe('StatusIcon', () => {
9
9
  const renderStatusIcon = (props?: {
10
- sentiment?: Sentiment;
11
- size?: SizeLarge | SizeMedium | SizeSmall;
10
+ sentiment?: StatusIconProps['sentiment'];
11
+ size?: StatusIconProps['size'];
12
+ iconLabel?: StatusIconProps['iconLabel'];
12
13
  }) => {
13
14
  return render(
14
15
  <StatusIcon
15
16
  sentiment={props?.sentiment || Sentiment.NEUTRAL}
16
17
  size={props?.size || Size.MEDIUM}
18
+ iconLabel={props?.iconLabel}
17
19
  />,
18
20
  );
19
21
  };
@@ -79,4 +81,29 @@ describe('StatusIcon', () => {
79
81
  renderStatusIcon();
80
82
  expect(screen.getByTestId('info-icon')).toHaveClass('light');
81
83
  });
84
+
85
+ describe('accessible name', () => {
86
+ it.each([
87
+ ['Error', Sentiment.NEGATIVE],
88
+ ['Success', Sentiment.POSITIVE],
89
+ ['Warning', Sentiment.WARNING],
90
+ ['Pending', Sentiment.PENDING],
91
+ ['Information', Sentiment.NEUTRAL],
92
+ ['Error', Sentiment.ERROR],
93
+ ['Information', Sentiment.INFO],
94
+ ['Success', Sentiment.SUCCESS],
95
+ ])("should set '%s' as an accessible name for the '%s' sentiment", (label, sentiment) => {
96
+ renderStatusIcon({ sentiment });
97
+
98
+ expect(screen.getByLabelText(`${label}.`)).toBeInTheDocument();
99
+ });
100
+
101
+ it('should allow for `iconLabel` overrides', () => {
102
+ const iconLabel = 'Custom label';
103
+ renderStatusIcon({ sentiment: Sentiment.NEGATIVE, iconLabel });
104
+
105
+ expect(screen.getByLabelText(iconLabel)).toBeInTheDocument();
106
+ expect(screen.queryByLabelText(`Error.`)).not.toBeInTheDocument();
107
+ });
108
+ });
82
109
  });
@@ -7,16 +7,13 @@ import StatusIcon from './StatusIcon';
7
7
  export default {
8
8
  component: StatusIcon,
9
9
  title: 'Other/StatusIcon',
10
+ tags: ['autodocs'],
10
11
  } satisfies Meta<typeof StatusIcon>;
11
12
 
12
13
  type Story = StoryObj<typeof StatusIcon>;
13
14
 
14
15
  export const Basic: Story = {
15
- render: (args) => (
16
- <span style={{ display: 'flex' }}>
17
- <StatusIcon {...args} />
18
- </span>
19
- ),
16
+ render: (args) => <StatusIcon {...args} />,
20
17
  };
21
18
 
22
19
  export const Variants: Story = {
@@ -1,34 +1,79 @@
1
1
  import { Info, Alert, Cross, Check, ClockBorderless } from '@transferwise/icons';
2
2
  import { clsx } from 'clsx';
3
+ import { useIntl } from 'react-intl';
3
4
 
4
5
  import { SizeSmall, SizeMedium, SizeLarge, Sentiment, Size, Breakpoint } from '../common';
5
6
  import Circle, { CircleProps } from '../common/circle';
6
7
  import { useMedia } from '../common/hooks/useMedia';
7
8
 
9
+ import messages from './StatusIcon.messages';
10
+
8
11
  export type StatusIconProps = {
9
12
  sentiment: `${Sentiment}`;
10
13
  size: SizeSmall | SizeMedium | SizeLarge;
14
+ /**
15
+ * Override for the sentiment's-derived, default, accessible
16
+ * name announced by the screen readers
17
+ * */
18
+ iconLabel?: string;
11
19
  };
12
20
 
13
- const iconTypeMap = {
14
- positive: Check,
15
- neutral: Info,
16
- warning: Alert,
17
- negative: Cross,
18
- pending: ClockBorderless,
19
- info: Info,
20
- error: Cross,
21
- success: Check,
22
- } satisfies Record<`${Sentiment}`, React.ElementType>;
23
-
24
21
  const mapLegacySize = {
25
22
  [String(Size.SMALL)]: 16,
26
23
  [String(Size.MEDIUM)]: 40,
27
24
  [String(Size.LARGE)]: 48,
28
25
  } satisfies Record<string, CircleProps['size']>;
29
26
 
30
- const StatusIcon = ({ sentiment = 'neutral', size: sizeProp = 'md' }: StatusIconProps) => {
31
- const Icon = iconTypeMap[sentiment];
27
+ const StatusIcon = ({
28
+ sentiment = 'neutral',
29
+ size: sizeProp = 'md',
30
+ iconLabel,
31
+ }: StatusIconProps) => {
32
+ const intl = useIntl();
33
+
34
+ const iconMetaBySentiment: Record<
35
+ `${Sentiment}`,
36
+ {
37
+ Icon: React.ElementType;
38
+ defaultIconLabel: string;
39
+ }
40
+ > = {
41
+ [Sentiment.NEGATIVE]: {
42
+ Icon: Cross,
43
+ defaultIconLabel: intl.formatMessage(messages.errorLabel),
44
+ },
45
+ [Sentiment.POSITIVE]: {
46
+ Icon: Check,
47
+ defaultIconLabel: intl.formatMessage(messages.successLabel),
48
+ },
49
+ [Sentiment.WARNING]: {
50
+ Icon: Alert,
51
+ defaultIconLabel: intl.formatMessage(messages.warningLabel),
52
+ },
53
+ [Sentiment.PENDING]: {
54
+ Icon: ClockBorderless,
55
+ defaultIconLabel: intl.formatMessage(messages.pendingLabel),
56
+ },
57
+ [Sentiment.NEUTRAL]: {
58
+ Icon: Info,
59
+ defaultIconLabel: intl.formatMessage(messages.informationLabel),
60
+ },
61
+ // deprecated
62
+ [Sentiment.ERROR]: {
63
+ Icon: Cross,
64
+ defaultIconLabel: intl.formatMessage(messages.errorLabel),
65
+ },
66
+ [Sentiment.INFO]: {
67
+ Icon: Info,
68
+ defaultIconLabel: intl.formatMessage(messages.informationLabel),
69
+ },
70
+ [Sentiment.SUCCESS]: {
71
+ Icon: Check,
72
+ defaultIconLabel: intl.formatMessage(messages.successLabel),
73
+ },
74
+ };
75
+ const { Icon, defaultIconLabel } = iconMetaBySentiment[sentiment];
76
+
32
77
  const iconColor = sentiment === 'warning' || sentiment === 'pending' ? 'dark' : 'light';
33
78
  const isTinyViewport = useMedia(`(max-width: ${Breakpoint.ZOOM_400}px)`);
34
79
  const size = sizeProp === Size.SMALL && isTinyViewport ? 32 : mapLegacySize[sizeProp];
@@ -39,7 +84,7 @@ const StatusIcon = ({ sentiment = 'neutral', size: sizeProp = 'md' }: StatusIcon
39
84
  data-testid="status-icon"
40
85
  className={clsx('status-circle', `status-circle-${sizeProp}`, sentiment)}
41
86
  >
42
- <Icon className={clsx('status-icon', iconColor)} />
87
+ <Icon className={clsx('status-icon', iconColor)} title={String(iconLabel ?? defaultIconLabel)} />
43
88
  </Circle>
44
89
  );
45
90
  };
@@ -7,6 +7,7 @@ import { ANIMATION_DURATION_IN_MS } from '../processIndicator';
7
7
  import { CompleteStep, UploadImageStep, ProcessingStep } from './steps';
8
8
 
9
9
  import Upload from '.';
10
+ import { Cross } from '@transferwise/icons';
10
11
 
11
12
  mockMatchMedia();
12
13
 
@@ -252,6 +253,7 @@ describe('Upload', () => {
252
253
  ...UPLOADIMAGE_STEP_PROPS,
253
254
  isComplete: false,
254
255
  errorMessage: 'csFailureText',
256
+ errorIconLabel: undefined,
255
257
  usButtonText: 'Try again',
256
258
  usHelpImage: null,
257
259
  });
@@ -282,5 +284,22 @@ describe('Upload', () => {
282
284
 
283
285
  expect(props.onSuccess).toHaveBeenCalledTimes(1);
284
286
  });
287
+
288
+ it('should respect `errorIconLabel` override', async () => {
289
+ const errorIconLabel = 'Custom error icon label';
290
+ component = mount(<Upload {...props} errorIconLabel={errorIconLabel} />);
291
+ const upload = component.children();
292
+ asyncFileRead.mockImplementation(async () => {
293
+ throw 'An error';
294
+ });
295
+
296
+ await act(async () => {
297
+ await upload.instance().fileDropped(TEST_FILE);
298
+ });
299
+ await waitForUpload();
300
+ component.update();
301
+
302
+ expect(component.find(Cross).at(1).prop('title')).toBe(errorIconLabel);
303
+ });
285
304
  });
286
305
  });
@@ -56,6 +56,11 @@ export interface UploadProps extends WrappedComponentProps {
56
56
  usHelpImage?: React.ReactNode;
57
57
  usLabel?: string;
58
58
  usPlaceholder?: string;
59
+ /**
60
+ * Override for the [InlineAlert icon's default, accessible name](/?path=/docs/other-statusicon-accessibility--docs)
61
+ * announced by the screen readers
62
+ * */
63
+ errorIconLabel?: string;
59
64
  /** @deprecated Only a single variant exists, please remove this prop. */
60
65
  uploadStep?: `${UploadStep}`;
61
66
  onCancel?: () => void;
@@ -338,6 +343,7 @@ export class Upload extends Component<UploadProps, UploadState> {
338
343
  csSuccessText,
339
344
  size,
340
345
  intl,
346
+ errorIconLabel,
341
347
  } = this.props;
342
348
 
343
349
  const {
@@ -422,6 +428,7 @@ export class Upload extends Component<UploadProps, UploadState> {
422
428
  ? response.status
423
429
  : undefined,
424
430
  )}
431
+ errorIconLabel={errorIconLabel}
425
432
  />
426
433
  )}
427
434
  {isProcessing && (
@@ -3,6 +3,7 @@ import { shallow } from 'enzyme';
3
3
  import StatusIcon from '../../../statusIcon/StatusIcon';
4
4
 
5
5
  import UploadImageStep from '.';
6
+ import { InlineAlert } from '../../../index';
6
7
 
7
8
  describe('uploadImageStep', () => {
8
9
  const UPLOADIMAGE_STEP_PROPS = {
@@ -66,6 +67,18 @@ describe('uploadImageStep', () => {
66
67
  expect(component.find('.test-image')).toHaveLength(1);
67
68
  });
68
69
 
70
+ it('should respect `errorIconLabel` override', () => {
71
+ const errorIconLabel = 'Error icon label';
72
+ component = shallow(
73
+ <UploadImageStep
74
+ {...UPLOADIMAGE_STEP_PROPS}
75
+ errorMessage="Maximum filesize is 2MB."
76
+ errorIconLabel={errorIconLabel}
77
+ />,
78
+ );
79
+ expect(component.find(InlineAlert).prop('iconLabel')).toBe(errorIconLabel);
80
+ });
81
+
69
82
  describe('when errorMessage is not empty', () => {
70
83
  it('renders errorMessage and icon when error is true', () => {
71
84
  component = shallow(
@@ -14,6 +14,7 @@ export interface UploadImageStepProps {
14
14
  usLabel: string;
15
15
  usPlaceholder: string;
16
16
  errorMessage?: string | string[];
17
+ errorIconLabel?: string;
17
18
  }
18
19
 
19
20
  export default class UploadImageStep extends PureComponent<UploadImageStepProps> {
@@ -55,8 +56,16 @@ export default class UploadImageStep extends PureComponent<UploadImageStepProps>
55
56
  };
56
57
 
57
58
  render() {
58
- const { isComplete, usAccept, usButtonText, usDisabled, usLabel, usPlaceholder, errorMessage } =
59
- this.props;
59
+ const {
60
+ errorMessage,
61
+ isComplete,
62
+ errorIconLabel,
63
+ usAccept,
64
+ usButtonText,
65
+ usDisabled,
66
+ usLabel,
67
+ usPlaceholder,
68
+ } = this.props;
60
69
 
61
70
  return (
62
71
  <div className="droppable-default-card" aria-hidden={isComplete}>
@@ -88,7 +97,9 @@ export default class UploadImageStep extends PureComponent<UploadImageStepProps>
88
97
  {errorMessage && (
89
98
  <div className="upload-error-message">
90
99
  <div className="m-t-3 has-error">
91
- <InlineAlert type={Sentiment.NEGATIVE}>{errorMessage}</InlineAlert>
100
+ <InlineAlert type={Sentiment.NEGATIVE} iconLabel={errorIconLabel}>
101
+ {errorMessage}
102
+ </InlineAlert>
92
103
  </div>
93
104
  </div>
94
105
  )}
@@ -1,31 +1,17 @@
1
- import { Matcher, within } from '@testing-library/react';
1
+ import { within } from '@testing-library/react';
2
2
  import { userEvent } from '@testing-library/user-event';
3
3
  import { act } from 'react';
4
4
 
5
5
  import { Status } from '../common';
6
6
  import { Field } from '../field/Field';
7
- import { mockMatchMedia, render, screen, waitFor } from '../test-utils';
7
+ import { mockMatchMedia, render, screen, waitFor, waitForElementToBeRemoved } from '../test-utils';
8
8
 
9
9
  import UploadInput, { UploadInputProps } from './UploadInput';
10
10
  import { TEST_IDS as UPLOAD_BUTTON_TEST_IDS } from './uploadButton/UploadButton';
11
+ import { TEST_IDS as UPLOAD_ITEM_TEST_IDS } from './uploadItem/UploadItem';
11
12
 
12
13
  const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTimeAsync });
13
14
 
14
- const deleteFileAndWaitForFocus = async (fileToDeleteTestId: Matcher, nextFocusTestId: Matcher) => {
15
- const fileToDelete = screen.getByTestId(fileToDeleteTestId);
16
-
17
- await user.click(within(fileToDelete).getByLabelText('Remove file', { exact: false }));
18
-
19
- const removeButton = screen.queryByText('Remove');
20
- if (removeButton) {
21
- await user.click(removeButton);
22
- }
23
-
24
- await waitFor(() => {
25
- expect(screen.getByTestId(nextFocusTestId)).toHaveFocus();
26
- });
27
- };
28
-
29
15
  mockMatchMedia();
30
16
 
31
17
  describe('UploadInput', () => {
@@ -153,21 +139,15 @@ describe('UploadInput', () => {
153
139
  onFilesChange,
154
140
  });
155
141
 
156
- const fileToDelete = screen.getByTestId('1-uploadItem');
142
+ const fileToDelete = screen.getAllByTestId(UPLOAD_ITEM_TEST_IDS.uploadItem)[0];
143
+ within(fileToDelete).getByLabelText('Remove file', { exact: false }).click();
157
144
  await act(async () => {
158
- within(fileToDelete).getByLabelText('Remove file', { exact: false }).click();
159
145
  await jest.runOnlyPendingTimersAsync();
160
146
  });
161
147
 
162
- await act(async () => {
163
- screen.getByText('Remove').click();
164
- await jest.runOnlyPendingTimersAsync();
165
- });
166
-
167
- await waitFor(() => {
168
- expect(screen.queryByTestId('1-uploadItem')).not.toBeInTheDocument();
169
- });
148
+ screen.getByText('Remove').click();
170
149
 
150
+ await waitForElementToBeRemoved(fileToDelete);
171
151
  expect(props.onDeleteFile).toHaveBeenCalledWith(files[0].id);
172
152
 
173
153
  expect(onFilesChange).toHaveBeenCalledTimes(2);
@@ -210,9 +190,9 @@ describe('UploadInput', () => {
210
190
  onFilesChange,
211
191
  });
212
192
 
213
- const fileToDelete = screen.getByTestId('1-uploadItem');
193
+ const fileToDelete = screen.getAllByTestId(UPLOAD_ITEM_TEST_IDS.uploadItem)[0];
194
+ within(fileToDelete).getByLabelText('Remove file', { exact: false }).click();
214
195
  await act(async () => {
215
- within(fileToDelete).getByLabelText('Remove file', { exact: false }).click();
216
196
  await jest.runOnlyPendingTimersAsync();
217
197
  });
218
198
 
@@ -232,98 +212,6 @@ describe('UploadInput', () => {
232
212
 
233
213
  expect(screen.queryByLabelText('Remove file ', { exact: false })).not.toBeInTheDocument();
234
214
  });
235
-
236
- it('should focus the next item after a file is deleted', async () => {
237
- const files = [
238
- { id: 1, filename: 'Sales-2024-invoice.pdf', status: Status.DONE },
239
- { id: 2, filename: 'CoWork-0317-invoice.pdf', status: Status.DONE },
240
- { id: 3, filename: 'purchase-receipt.pdf', status: Status.DONE },
241
- ];
242
-
243
- renderComponent({ ...props, files, multiple: true, onFilesChange });
244
-
245
- // Delete the first file and expect focus to move to the next one
246
- await deleteFileAndWaitForFocus('1-uploadItem', '2-action');
247
- });
248
-
249
- it('should focus the previous item after the last file is deleted', async () => {
250
- const files = [
251
- {
252
- id: 1,
253
- filename: 'Sales-2024-invoice.pdf',
254
- status: Status.DONE,
255
- },
256
- {
257
- id: 2,
258
- filename: 'CoWork-0317-invoice.pdf',
259
- status: Status.DONE,
260
- },
261
- {
262
- id: 3,
263
- filename: 'purchase-receipt.pdf',
264
- status: Status.DONE,
265
- },
266
- ];
267
-
268
- renderComponent({
269
- ...props,
270
- files,
271
- multiple: true,
272
- onFilesChange,
273
- });
274
-
275
- await deleteFileAndWaitForFocus('3-uploadItem', '2-action');
276
- });
277
-
278
- it('should focus the upload input after the only file is deleted', async () => {
279
- const singleFile = [
280
- {
281
- id: 3,
282
- filename: 'purchase-receipt.pdf',
283
- status: Status.DONE,
284
- },
285
- ];
286
-
287
- renderComponent({
288
- ...props,
289
- files: singleFile,
290
- multiple: true,
291
- onFilesChange,
292
- });
293
-
294
- await deleteFileAndWaitForFocus('3-uploadItem', 'uploadInput');
295
- });
296
-
297
- it('should focus on the next item or upload input after each file is deleted in sequence', async () => {
298
- const filesWithFailed = [
299
- {
300
- id: 1,
301
- filename: 'Sales-2024-invoice.pdf',
302
- status: Status.DONE,
303
- },
304
- {
305
- id: 2,
306
- filename: 'CoWork-0317-invoice.pdf',
307
- status: Status.FAILED,
308
- },
309
- {
310
- id: 3,
311
- filename: 'purchase-receipt.pdf',
312
- status: Status.DONE,
313
- },
314
- ];
315
-
316
- renderComponent({
317
- ...props,
318
- files: filesWithFailed,
319
- multiple: true,
320
- onFilesChange,
321
- });
322
-
323
- await deleteFileAndWaitForFocus('3-uploadItem', '2-action');
324
- await deleteFileAndWaitForFocus('1-uploadItem', '2-action');
325
- await deleteFileAndWaitForFocus('2-uploadItem', 'uploadInput');
326
- });
327
215
  });
328
216
 
329
217
  describe('Max File Upload limit', () => {