@transferwise/components 46.38.0 → 46.40.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 (202) hide show
  1. package/build/index.js +198 -461
  2. package/build/index.js.map +1 -1
  3. package/build/index.mjs +198 -461
  4. package/build/index.mjs.map +1 -1
  5. package/build/types/flowNavigation/backButton/BackButton.d.ts +5 -17
  6. package/build/types/flowNavigation/backButton/BackButton.d.ts.map +1 -1
  7. package/build/types/flowNavigation/backButton/index.d.ts +2 -2
  8. package/build/types/flowNavigation/backButton/index.d.ts.map +1 -1
  9. package/build/types/index.d.ts +3 -1
  10. package/build/types/index.d.ts.map +1 -1
  11. package/build/types/info/Info.d.ts +2 -2
  12. package/build/types/info/Info.d.ts.map +1 -1
  13. package/build/types/info/index.d.ts +1 -1
  14. package/build/types/info/index.d.ts.map +1 -1
  15. package/build/types/overlayHeader/OverlayHeader.d.ts +9 -18
  16. package/build/types/overlayHeader/OverlayHeader.d.ts.map +1 -1
  17. package/build/types/overlayHeader/index.d.ts +2 -1
  18. package/build/types/overlayHeader/index.d.ts.map +1 -1
  19. package/build/types/processIndicator/ProcessIndicator.d.ts +1 -1
  20. package/build/types/processIndicator/ProcessIndicator.d.ts.map +1 -1
  21. package/build/types/upload/Upload.d.ts +91 -55
  22. package/build/types/upload/Upload.d.ts.map +1 -1
  23. package/build/types/upload/Upload.messages.d.ts +42 -60
  24. package/build/types/upload/Upload.messages.d.ts.map +1 -1
  25. package/build/types/upload/index.d.ts +2 -2
  26. package/build/types/upload/index.d.ts.map +1 -1
  27. package/build/types/upload/steps/completeStep/completeStep.d.ts +11 -18
  28. package/build/types/upload/steps/completeStep/completeStep.d.ts.map +1 -1
  29. package/build/types/upload/steps/completeStep/index.d.ts +2 -1
  30. package/build/types/upload/steps/completeStep/index.d.ts.map +1 -1
  31. package/build/types/upload/steps/index.d.ts +3 -4
  32. package/build/types/upload/steps/index.d.ts.map +1 -1
  33. package/build/types/upload/steps/processingStep/index.d.ts +2 -1
  34. package/build/types/upload/steps/processingStep/index.d.ts.map +1 -1
  35. package/build/types/upload/steps/processingStep/processingStep.d.ts +11 -13
  36. package/build/types/upload/steps/processingStep/processingStep.d.ts.map +1 -1
  37. package/build/types/upload/steps/uploadImageStep/index.d.ts +2 -1
  38. package/build/types/upload/steps/uploadImageStep/index.d.ts.map +1 -1
  39. package/build/types/upload/steps/uploadImageStep/uploadImageStep.d.ts +14 -18
  40. package/build/types/upload/steps/uploadImageStep/uploadImageStep.d.ts.map +1 -1
  41. package/build/types/upload/utils/asyncFileRead/asyncFileRead.d.ts +1 -1
  42. package/build/types/upload/utils/asyncFileRead/asyncFileRead.d.ts.map +1 -1
  43. package/build/types/upload/utils/asyncFileRead/index.d.ts +1 -1
  44. package/build/types/upload/utils/asyncFileRead/index.d.ts.map +1 -1
  45. package/build/types/upload/utils/getFileType/getFileType.d.ts +1 -1
  46. package/build/types/upload/utils/getFileType/getFileType.d.ts.map +1 -1
  47. package/build/types/upload/utils/getFileType/index.d.ts +1 -1
  48. package/build/types/upload/utils/getFileType/index.d.ts.map +1 -1
  49. package/build/types/upload/utils/index.d.ts +5 -7
  50. package/build/types/upload/utils/index.d.ts.map +1 -1
  51. package/build/types/upload/utils/isSizeValid/index.d.ts +1 -1
  52. package/build/types/upload/utils/isSizeValid/index.d.ts.map +1 -1
  53. package/build/types/upload/utils/isSizeValid/isSizeValid.d.ts +1 -1
  54. package/build/types/upload/utils/isSizeValid/isSizeValid.d.ts.map +1 -1
  55. package/build/types/upload/utils/isTypeValid/index.d.ts +1 -1
  56. package/build/types/upload/utils/isTypeValid/index.d.ts.map +1 -1
  57. package/build/types/upload/utils/isTypeValid/isTypeValid.d.ts +1 -1
  58. package/build/types/upload/utils/isTypeValid/isTypeValid.d.ts.map +1 -1
  59. package/build/types/upload/utils/postData/index.d.ts +1 -1
  60. package/build/types/upload/utils/postData/index.d.ts.map +1 -1
  61. package/build/types/upload/utils/postData/postData.d.ts +11 -1
  62. package/build/types/upload/utils/postData/postData.d.ts.map +1 -1
  63. package/package.json +24 -26
  64. package/src/accordion/Accordion.spec.js +5 -5
  65. package/src/accordion/AccordionItem/AccordionItem.spec.js +2 -2
  66. package/src/actionButton/ActionButton.spec.tsx +4 -5
  67. package/src/alert/Alert.spec.tsx +4 -4
  68. package/src/alert/Alert.story.tsx +6 -5
  69. package/src/button/Button.spec.js +4 -5
  70. package/src/card/Card.spec.tsx +4 -4
  71. package/src/carousel/Carousel.spec.tsx +17 -17
  72. package/src/checkbox/Checkbox.spec.tsx +0 -2
  73. package/src/checkboxButton/CheckboxButton.spec.tsx +0 -2
  74. package/src/checkboxOption/CheckboxOption.spec.tsx +0 -2
  75. package/src/chevron/Chevron.spec.tsx +0 -1
  76. package/src/chips/Chips.spec.tsx +0 -1
  77. package/src/chips/Chips.story.tsx +5 -3
  78. package/src/circularButton/CircularButton.spec.tsx +4 -5
  79. package/src/common/RadioButton/RadioButton.spec.tsx +2 -2
  80. package/src/common/card/Card.story.tsx +1 -0
  81. package/src/common/closeButton/CloseButton.spec.tsx +0 -1
  82. package/src/common/flowHeader/FlowHeader.spec.tsx +0 -1
  83. package/src/dateInput/DateInput.story.tsx +21 -16
  84. package/src/dateLookup/DateLookup.rtl.spec.tsx +18 -16
  85. package/src/dateLookup/DateLookup.testingLibrary.spec.js +47 -44
  86. package/src/dateLookup/DateLookup.tests.story.tsx +4 -2
  87. package/src/decision/Decision.spec.js +0 -2
  88. package/src/dimmer/Dimmer.rtl.spec.js +10 -10
  89. package/src/drawer/Drawer.rtl.spec.tsx +2 -2
  90. package/src/emphasis/Emphasis.spec.tsx +0 -1
  91. package/src/field/Field.spec.tsx +2 -2
  92. package/src/flowNavigation/FlowNavigation.spec.js +0 -2
  93. package/src/flowNavigation/animatedLabel/AnimatedLabel.spec.js +0 -1
  94. package/src/flowNavigation/backButton/BackButton.tsx +29 -0
  95. package/src/flowNavigation/backButton/index.ts +2 -0
  96. package/src/header/Header.spec.tsx +6 -6
  97. package/src/image/Image.spec.tsx +0 -1
  98. package/src/index.ts +3 -1
  99. package/src/info/Info.story.tsx +15 -9
  100. package/src/info/Info.tsx +2 -2
  101. package/src/info/index.ts +1 -1
  102. package/src/inlineAlert/InlineAlert.spec.tsx +0 -1
  103. package/src/inputs/SelectInput.spec.tsx +26 -47
  104. package/src/link/Link.spec.tsx +0 -1
  105. package/src/listItem/ListItem.spec.tsx +0 -1
  106. package/src/moneyInput/MoneyInput.rtl.spec.tsx +2 -2
  107. package/src/moneyInput/MoneyInput.story.tsx +1 -4
  108. package/src/overlayHeader/{OverlayHeader.spec.js → OverlayHeader.spec.tsx} +1 -1
  109. package/src/overlayHeader/{OverlayHeader.story.js → OverlayHeader.story.tsx} +10 -2
  110. package/src/overlayHeader/{OverlayHeader.js → OverlayHeader.tsx} +12 -19
  111. package/src/overlayHeader/index.ts +2 -0
  112. package/src/phoneNumberInput/PhoneNumberInput.story.tsx +1 -0
  113. package/src/popover/Popover.spec.tsx +10 -10
  114. package/src/processIndicator/ProcessIndicator.tsx +1 -1
  115. package/src/progress/Progress.spec.tsx +0 -1
  116. package/src/progressBar/ProgressBar.spec.tsx +0 -1
  117. package/src/segmentedControl/SegmentedControl.spec.tsx +10 -11
  118. package/src/select/Select.spec.js +71 -71
  119. package/src/test-utils/index.js +1 -1
  120. package/src/test-utils/jest.setup.js +2 -0
  121. package/src/tooltip/Tooltip.spec.tsx +15 -16
  122. package/src/upload/Upload.spec.js +3 -14
  123. package/src/upload/Upload.story.tsx +37 -0
  124. package/src/upload/{Upload.js → Upload.tsx} +164 -169
  125. package/src/upload/index.ts +2 -0
  126. package/src/upload/steps/completeStep/completeStep.spec.js +3 -2
  127. package/src/upload/steps/completeStep/completeStep.tsx +74 -0
  128. package/src/upload/steps/completeStep/index.ts +2 -0
  129. package/src/upload/steps/{index.js → index.ts} +0 -1
  130. package/src/upload/steps/processingStep/index.ts +2 -0
  131. package/src/upload/steps/processingStep/processingStep.tsx +53 -0
  132. package/src/upload/steps/uploadImageStep/index.ts +2 -0
  133. package/src/upload/steps/uploadImageStep/{uploadImageStep.js → uploadImageStep.tsx} +17 -23
  134. package/src/upload/utils/asyncFileRead/asyncFileRead.spec.ts +14 -0
  135. package/src/upload/utils/asyncFileRead/asyncFileRead.ts +12 -0
  136. package/src/upload/utils/getFileType/getFileType.spec.ts +22 -0
  137. package/src/upload/utils/getFileType/getFileType.ts +16 -0
  138. package/src/upload/utils/{index.js → index.ts} +0 -2
  139. package/src/upload/utils/isSizeValid/{isSizeValid.spec.js → isSizeValid.spec.ts} +3 -3
  140. package/src/upload/utils/isSizeValid/isSizeValid.ts +3 -0
  141. package/src/upload/utils/isTypeValid/isTypeValid.spec.ts +62 -0
  142. package/src/upload/utils/isTypeValid/isTypeValid.ts +19 -0
  143. package/src/upload/utils/postData/postData.spec.ts +65 -0
  144. package/src/upload/utils/postData/postData.ts +36 -0
  145. package/src/uploadInput/UploadInput.spec.tsx +9 -10
  146. package/src/uploadInput/UploadInput.story.tsx +8 -180
  147. package/src/uploadInput/UploadInput.tests.story.tsx +212 -0
  148. package/src/uploadInput/UploadInput.tsx +1 -1
  149. package/src/uploadInput/uploadButton/UploadButton.spec.tsx +4 -4
  150. package/src/uploadInput/uploadItem/UploadItem.spec.tsx +4 -4
  151. package/build/types/upload/steps/mediaUploadStep/index.d.ts +0 -2
  152. package/build/types/upload/steps/mediaUploadStep/index.d.ts.map +0 -1
  153. package/build/types/upload/steps/mediaUploadStep/mediaUploadStep.d.ts +0 -24
  154. package/build/types/upload/steps/mediaUploadStep/mediaUploadStep.d.ts.map +0 -1
  155. package/build/types/upload/uploadSteps.d.ts +0 -5
  156. package/build/types/upload/uploadSteps.d.ts.map +0 -1
  157. package/build/types/upload/utils/getSupportedSpotMimeTypes/getSupportedSpotMimeTypes.d.ts +0 -2
  158. package/build/types/upload/utils/getSupportedSpotMimeTypes/getSupportedSpotMimeTypes.d.ts.map +0 -1
  159. package/build/types/upload/utils/getSupportedSpotMimeTypes/index.d.ts +0 -2
  160. package/build/types/upload/utils/getSupportedSpotMimeTypes/index.d.ts.map +0 -1
  161. package/build/types/upload/utils/requestMedia/index.d.ts +0 -2
  162. package/build/types/upload/utils/requestMedia/index.d.ts.map +0 -1
  163. package/build/types/upload/utils/requestMedia/requestMedia.d.ts +0 -2
  164. package/build/types/upload/utils/requestMedia/requestMedia.d.ts.map +0 -1
  165. package/src/flowNavigation/backButton/BackButton.js +0 -32
  166. package/src/flowNavigation/backButton/BackButton.spec.js +0 -16
  167. package/src/flowNavigation/backButton/__snapshots__/BackButton.spec.js.snap +0 -37
  168. package/src/flowNavigation/backButton/index.js +0 -3
  169. package/src/overlayHeader/index.js +0 -1
  170. package/src/upload/Upload.story.js +0 -36
  171. package/src/upload/index.js +0 -2
  172. package/src/upload/steps/completeStep/completeStep.js +0 -98
  173. package/src/upload/steps/completeStep/index.js +0 -1
  174. package/src/upload/steps/mediaUploadStep/index.js +0 -1
  175. package/src/upload/steps/mediaUploadStep/mediaUploadStep.js +0 -80
  176. package/src/upload/steps/mediaUploadStep/mediaUploadStep.spec.js +0 -77
  177. package/src/upload/steps/processingStep/index.js +0 -1
  178. package/src/upload/steps/processingStep/processingStep.js +0 -73
  179. package/src/upload/steps/uploadImageStep/index.js +0 -1
  180. package/src/upload/uploadSteps.ts +0 -5
  181. package/src/upload/utils/asyncFileRead/asyncFileRead.js +0 -11
  182. package/src/upload/utils/asyncFileRead/asyncFileRead.spec.js +0 -17
  183. package/src/upload/utils/getFileType/getFileType.js +0 -19
  184. package/src/upload/utils/getFileType/getFileType.spec.js +0 -33
  185. package/src/upload/utils/getSupportedSpotMimeTypes/getSupportedSpotMimeTypes.js +0 -18
  186. package/src/upload/utils/getSupportedSpotMimeTypes/getSupportedSpotMimeTypes.spec.js +0 -22
  187. package/src/upload/utils/getSupportedSpotMimeTypes/index.js +0 -1
  188. package/src/upload/utils/isSizeValid/isSizeValid.js +0 -1
  189. package/src/upload/utils/isTypeValid/isTypeValid.js +0 -26
  190. package/src/upload/utils/isTypeValid/isTypeValid.spec.js +0 -68
  191. package/src/upload/utils/postData/postData.js +0 -18
  192. package/src/upload/utils/postData/postData.spec.js +0 -109
  193. package/src/upload/utils/requestMedia/index.js +0 -1
  194. package/src/upload/utils/requestMedia/requestMedia.js +0 -26
  195. package/src/upload/utils/requestMedia/requestMedia.spec.js +0 -44
  196. /package/src/overlayHeader/__snapshots__/{OverlayHeader.spec.js.snap → OverlayHeader.spec.tsx.snap} +0 -0
  197. /package/src/upload/{Upload.messages.js → Upload.messages.ts} +0 -0
  198. /package/src/upload/utils/asyncFileRead/{index.js → index.ts} +0 -0
  199. /package/src/upload/utils/getFileType/{index.js → index.ts} +0 -0
  200. /package/src/upload/utils/isSizeValid/{index.js → index.ts} +0 -0
  201. /package/src/upload/utils/isTypeValid/{index.js → index.ts} +0 -0
  202. /package/src/upload/utils/postData/{index.js → index.ts} +0 -0
@@ -0,0 +1,53 @@
1
+ import Button from '../../../button';
2
+ import { Status, Typography } from '../../../common';
3
+ import ProcessIndicator, { ProcessIndicatorStatus } from '../../../processIndicator';
4
+ import Title from '../../../title';
5
+
6
+ export interface ProcessingStepProps {
7
+ isComplete: boolean;
8
+ isError: boolean;
9
+ isSuccess: boolean;
10
+ onAnimationCompleted: (status: ProcessIndicatorStatus) => void;
11
+ onClear: React.MouseEventHandler<HTMLButtonElement>;
12
+ psButtonText: string;
13
+ psProcessingText: string;
14
+ psButtonDisabled: boolean;
15
+ }
16
+
17
+ export default function ProcessingStep({
18
+ isComplete,
19
+ isError,
20
+ isSuccess,
21
+ onAnimationCompleted,
22
+ onClear,
23
+ psButtonText,
24
+ psProcessingText,
25
+ psButtonDisabled,
26
+ }: ProcessingStepProps) {
27
+ let processStatus = Status.PROCESSING;
28
+ if (isError) {
29
+ processStatus = Status.FAILED;
30
+ }
31
+ if (isSuccess) {
32
+ processStatus = Status.SUCCEEDED;
33
+ }
34
+
35
+ return (
36
+ <div className="droppable-processing-card droppable-card" aria-hidden={isComplete}>
37
+ <div className="droppable-card-content">
38
+ <ProcessIndicator
39
+ status={processStatus}
40
+ onAnimationCompleted={(status) => onAnimationCompleted(status)}
41
+ />
42
+ <Title className="m-y-2" type={Typography.TITLE_BODY} aria-live="polite">
43
+ {psProcessingText}
44
+ </Title>
45
+ {psButtonText && (
46
+ <Button disabled={psButtonDisabled} onClick={onClear}>
47
+ {psButtonText}
48
+ </Button>
49
+ )}
50
+ </div>
51
+ </div>
52
+ );
53
+ }
@@ -0,0 +1,2 @@
1
+ export { default } from './uploadImageStep';
2
+ export type { UploadImageStepProps } from './uploadImageStep';
@@ -1,17 +1,24 @@
1
1
  import { Upload as UploadIcon } from '@transferwise/icons';
2
- import PropTypes from 'prop-types';
3
2
  import { createRef, PureComponent } from 'react';
4
3
 
5
- class UploadImageStep extends PureComponent {
6
- constructor() {
7
- super();
8
- this.uploadInputRef = createRef();
9
- }
4
+ export interface UploadImageStepProps {
5
+ fileDropped: (file: File) => void;
6
+ isComplete: boolean;
7
+ usAccept: string;
8
+ usButtonText: string;
9
+ usDisabled: boolean;
10
+ usHelpImage: React.ReactNode;
11
+ usLabel: string;
12
+ usPlaceholder: string;
13
+ }
14
+
15
+ export default class UploadImageStep extends PureComponent<UploadImageStepProps> {
16
+ uploadInputRef = createRef<HTMLInputElement>();
10
17
 
11
18
  onManualUpload = () => {
12
19
  const { fileDropped } = this.props;
13
- if (this.uploadInputRef && this.uploadInputRef.current) {
14
- const file = this.uploadInputRef.current.files[0];
20
+ const file = this.uploadInputRef.current?.files?.[0];
21
+ if (file != null) {
15
22
  fileDropped(file);
16
23
  }
17
24
  };
@@ -43,7 +50,7 @@ class UploadImageStep extends PureComponent {
43
50
  <div className="droppable-card-content">
44
51
  <div className="m-b-3">{this.getImage()}</div>
45
52
  {usLabel && <h4 className="np-text-title-body m-b-1">{usLabel}</h4>}
46
- {usPlaceholder && <p className="np-text-body-large m-b-3">{`${usPlaceholder}`}</p>}
53
+ {usPlaceholder && <p className="np-text-body-large m-b-3">{String(usPlaceholder)}</p>}
47
54
  <label className={`btn btn-primary btn-md ${usDisabled ? 'disabled' : ''}`}>
48
55
  {usButtonText ? (
49
56
  <span>{usButtonText}</span>
@@ -53,7 +60,7 @@ class UploadImageStep extends PureComponent {
53
60
  <input
54
61
  ref={this.uploadInputRef}
55
62
  type="file"
56
- accept={usAccept === '*' ? null : usAccept}
63
+ accept={usAccept === '*' ? undefined : usAccept}
57
64
  className="tw-droppable-input hidden"
58
65
  disabled={usDisabled}
59
66
  name="file-upload"
@@ -66,16 +73,3 @@ class UploadImageStep extends PureComponent {
66
73
  );
67
74
  }
68
75
  }
69
-
70
- UploadImageStep.propTypes = {
71
- fileDropped: PropTypes.func.isRequired,
72
- isComplete: PropTypes.bool.isRequired,
73
- usAccept: PropTypes.string.isRequired,
74
- usButtonText: PropTypes.string.isRequired,
75
- usDisabled: PropTypes.bool.isRequired,
76
- usHelpImage: PropTypes.node.isRequired,
77
- usLabel: PropTypes.string.isRequired,
78
- usPlaceholder: PropTypes.string.isRequired,
79
- };
80
-
81
- export default UploadImageStep;
@@ -0,0 +1,14 @@
1
+ import { asyncFileRead } from '.';
2
+
3
+ describe('asyncFileRead', () => {
4
+ it('should resolve with data64', async () => {
5
+ const file = new Blob(['foo'], { type: 'text/plain' });
6
+ await expect(asyncFileRead(file)).resolves.toBe('data:text/plain;base64,Zm9v');
7
+ });
8
+
9
+ it('should reject if wrong file is given', async () => {
10
+ const file = 'Not a blob';
11
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
12
+ await expect(asyncFileRead(file as any)).rejects.toThrow();
13
+ });
14
+ });
@@ -0,0 +1,12 @@
1
+ export async function asyncFileRead(file: Blob) {
2
+ return new Promise<string>((resolve, reject) => {
3
+ const reader = new FileReader();
4
+ reader.addEventListener('load', () => {
5
+ resolve(reader.result as string);
6
+ });
7
+ reader.addEventListener('error', () => {
8
+ reject(reader.error ?? new Error('Cannot read file'));
9
+ });
10
+ reader.readAsDataURL(file);
11
+ });
12
+ }
@@ -0,0 +1,22 @@
1
+ import { getFileType } from '.';
2
+
3
+ const imageFile = new Blob(undefined, { type: 'image/png' });
4
+
5
+ const data64Img = 'data:image/png;something';
6
+
7
+ describe('getFileType', () => {
8
+ describe('returns empty string', () => {
9
+ it(`when file has empty type and file64 hasn't been provided`, () => {
10
+ expect(getFileType(new Blob())).toBe('');
11
+ });
12
+ });
13
+
14
+ describe('returns file-type', () => {
15
+ it('when file has type defined', () => {
16
+ expect(getFileType(imageFile)).toBe(imageFile.type);
17
+ });
18
+ it('when file has no type and file64 is supported', () => {
19
+ expect(getFileType(new Blob(), data64Img)).toBe('image/png');
20
+ });
21
+ });
22
+ });
@@ -0,0 +1,16 @@
1
+ export function getFileType(file: Blob, file64?: string) {
2
+ if (file.type) {
3
+ return file.type;
4
+ }
5
+
6
+ if (file64) {
7
+ const regex = /^data:([a-z]+\/[a-z]+);/;
8
+ const typeFromEncoded = regex.exec(file64);
9
+
10
+ if (typeFromEncoded?.[1]) {
11
+ return typeFromEncoded[1];
12
+ }
13
+ }
14
+
15
+ return '';
16
+ }
@@ -3,5 +3,3 @@ export { asyncFileRead } from './asyncFileRead';
3
3
  export { isSizeValid } from './isSizeValid';
4
4
  export { isTypeValid } from './isTypeValid';
5
5
  export { getFileType } from './getFileType';
6
- export { getSupportedSpotMimeTypes } from './getSupportedSpotMimeTypes';
7
- export { requestMedia } from './requestMedia';
@@ -4,14 +4,14 @@ const MAXSIZE = 5000;
4
4
 
5
5
  describe('isSizeValid', () => {
6
6
  it('should return true for valid size', () => {
7
- expect(isSizeValid({ size: MAXSIZE }, MAXSIZE)).toBe(true);
7
+ expect(isSizeValid(new Blob([new ArrayBuffer(MAXSIZE)]), MAXSIZE)).toBe(true);
8
8
  });
9
9
 
10
10
  it('should return false for valid size', () => {
11
- expect(isSizeValid({ size: MAXSIZE + 1 }, MAXSIZE)).toBe(false);
11
+ expect(isSizeValid(new Blob([new ArrayBuffer(MAXSIZE + 1)]), MAXSIZE)).toBe(false);
12
12
  });
13
13
 
14
14
  it('should return false for invalid MaxSize', () => {
15
- expect(isSizeValid({ size: MAXSIZE }, MAXSIZE - 0.5)).toBe(false);
15
+ expect(isSizeValid(new Blob([new ArrayBuffer(MAXSIZE)]), MAXSIZE - 0.5)).toBe(false);
16
16
  });
17
17
  });
@@ -0,0 +1,3 @@
1
+ export function isSizeValid(file: Blob, maxSize: number) {
2
+ return Number.isInteger(maxSize) && file.size <= maxSize;
3
+ }
@@ -0,0 +1,62 @@
1
+ import { isTypeValid } from '.';
2
+
3
+ const pdfFile = new Blob(undefined, {
4
+ type: 'application/pdf',
5
+ });
6
+
7
+ const pngFile = new Blob(undefined, {
8
+ type: 'image/png',
9
+ });
10
+
11
+ const data64Img = 'data:image/png;something';
12
+
13
+ describe('isTypeValid', () => {
14
+ describe('when type is provided', () => {
15
+ it.each([pdfFile, pngFile])('returns true for wildcard rule %s', (file) => {
16
+ expect(isTypeValid(file, '*')).toBe(true);
17
+ });
18
+
19
+ it('returns true for matching type', () => {
20
+ expect(isTypeValid(pdfFile, 'application/*')).toBe(true);
21
+ });
22
+
23
+ it('returns true for matching type when multiple rules provided', () => {
24
+ expect(isTypeValid(pdfFile, 'application/*, image/*')).toBe(true);
25
+ });
26
+
27
+ it('returns true for matching subtype', () => {
28
+ expect(isTypeValid(pdfFile, 'application/pdf')).toBe(true);
29
+ });
30
+
31
+ it('returns true for matching subtype when multiple rules provided', () => {
32
+ expect(isTypeValid(pngFile, 'application/xls, image/png')).toBe(true);
33
+ });
34
+
35
+ it('can parse multiple types with extra whitespace', () => {
36
+ expect(isTypeValid(pngFile, ' application/xls, image/png ')).toBe(true);
37
+ });
38
+
39
+ it('returns false for unsupported type', () => {
40
+ expect(isTypeValid(pngFile, 'application/*')).toBe(false);
41
+ });
42
+
43
+ it('returns false for unsupported subtype', () => {
44
+ expect(isTypeValid(pdfFile, 'application/xls')).toBe(false);
45
+ });
46
+
47
+ it('returns false for unsupported subtype when multiple rules provided', () => {
48
+ expect(isTypeValid(pngFile, 'application/xls, image/jpeg')).toBe(false);
49
+ });
50
+ });
51
+ describe('when type is not provided', () => {
52
+ it('returns true for supported file', () => {
53
+ expect(isTypeValid(new Blob(), 'image/png', data64Img)).toBe(true);
54
+ expect(isTypeValid(new Blob(), 'image/*', data64Img)).toBe(true);
55
+ });
56
+
57
+ it('returns false for unsupported file', () => {
58
+ expect(isTypeValid(new Blob(), 'image/jpeg', data64Img)).toBe(false);
59
+ expect(isTypeValid(new Blob(), 'application/*', data64Img)).toBe(false);
60
+ });
61
+ });
62
+ });
@@ -0,0 +1,19 @@
1
+ import { getFileType } from '../getFileType';
2
+
3
+ export function isTypeValid(file: Blob, rule: string, file64?: string) {
4
+ if (!rule) {
5
+ return false;
6
+ }
7
+
8
+ const allowedTypes = rule.replace(/\s/g, '').split(',');
9
+ const fileType = getFileType(file, file64);
10
+
11
+ if (rule === '*' || allowedTypes.includes(fileType)) {
12
+ return true;
13
+ }
14
+
15
+ return allowedTypes.some((type) => {
16
+ const [typeAllowed, extensionAllowed] = type.split('/');
17
+ return extensionAllowed === '*' && fileType.includes(typeAllowed);
18
+ });
19
+ }
@@ -0,0 +1,65 @@
1
+ import { postData } from '.';
2
+ import { ResponseError } from './postData';
3
+
4
+ const HTTPOPTIONS = { url: 'a-url' };
5
+ const DATA = 'some-data';
6
+
7
+ describe('postData', () => {
8
+ afterEach(() => {
9
+ (global.fetch as jest.Mock).mockClear();
10
+ });
11
+
12
+ it('should work with resolve', async () => {
13
+ const RESOLVE_RESPONSE = new Response();
14
+ jest.spyOn(global, 'fetch').mockImplementation(async () => RESOLVE_RESPONSE);
15
+
16
+ await expect(postData(HTTPOPTIONS, DATA)).resolves.toBe(RESOLVE_RESPONSE);
17
+ });
18
+
19
+ it('should throw when call fails', async () => {
20
+ const REJECT_RESPONSE = new ResponseError(
21
+ new Response(null, { status: 500, statusText: 'Rejected' }),
22
+ );
23
+ jest.spyOn(global, 'fetch').mockImplementation(async () => {
24
+ throw REJECT_RESPONSE;
25
+ });
26
+
27
+ await expect(postData(HTTPOPTIONS, DATA)).rejects.toThrow(REJECT_RESPONSE);
28
+ });
29
+
30
+ it('should throw an Error when API returns an error code', async () => {
31
+ const ERROR_RESPONSE = new Response(null, { status: 500, statusText: 'Internal server error' });
32
+ jest.spyOn(global, 'fetch').mockImplementation(async () => ERROR_RESPONSE);
33
+
34
+ await expect(postData(HTTPOPTIONS, DATA)).rejects.toThrow();
35
+ });
36
+
37
+ it('should pass additional form data to request body', async () => {
38
+ const mockFetch = jest.fn(async () => new Response());
39
+ jest.spyOn(global, 'fetch').mockImplementation(mockFetch);
40
+
41
+ const data = new FormData();
42
+ data.append('file', 'file');
43
+ data.append('profileId', '1');
44
+ await postData(HTTPOPTIONS, data);
45
+
46
+ expect(mockFetch).toHaveBeenCalledWith('a-url', { method: 'POST', body: data });
47
+ });
48
+
49
+ it('should override `Content-type` and add any custom headers to the request', async () => {
50
+ const mockFetch = jest.fn(async () => new Response());
51
+ jest.spyOn(global, 'fetch').mockImplementation(mockFetch);
52
+
53
+ const headers: HeadersInit = {
54
+ 'Content-type': 'foo',
55
+ 'Accept-language': 'hu',
56
+ };
57
+ await postData({ ...HTTPOPTIONS, headers }, DATA);
58
+
59
+ expect(mockFetch).toHaveBeenCalledWith('a-url', {
60
+ method: 'POST',
61
+ headers,
62
+ body: DATA,
63
+ });
64
+ });
65
+ });
@@ -0,0 +1,36 @@
1
+ export class ResponseError extends Error {
2
+ response: Response;
3
+ status: number;
4
+
5
+ constructor(response: Response, message?: string) {
6
+ super(message);
7
+ this.name = 'ResponseError';
8
+ this.response = response;
9
+ this.status = response.status;
10
+ }
11
+ }
12
+
13
+ export interface PostDataHTTPOptions extends Omit<RequestInit, 'body'> {
14
+ url: string;
15
+ method?: 'POST' | 'PUT' | 'PATCH';
16
+ }
17
+
18
+ export type PostDataFetcher = (input: string, init: RequestInit) => Promise<Response>;
19
+
20
+ export async function postData(
21
+ { url, method = 'POST', ...httpOptions }: PostDataHTTPOptions,
22
+ data: RequestInit['body'],
23
+ fetcher: PostDataFetcher = fetch,
24
+ ) {
25
+ const response = await fetcher(url, {
26
+ method,
27
+ body: data,
28
+ ...httpOptions,
29
+ });
30
+
31
+ if (!response.ok) {
32
+ throw new ResponseError(response, response.statusText);
33
+ }
34
+
35
+ return response;
36
+ }
@@ -1,5 +1,5 @@
1
1
  import { within } from '@testing-library/react';
2
- import userEvent from '@testing-library/user-event';
2
+ import { userEvent } from '@testing-library/user-event';
3
3
 
4
4
  import { Status } from '../common';
5
5
  import { mockMatchMedia, render, screen, waitFor, waitForElementToBeRemoved } from '../test-utils';
@@ -9,6 +9,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
10
  import { act } from 'react';
11
11
 
12
+ const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTimeAsync });
13
+
12
14
  mockMatchMedia();
13
15
 
14
16
  describe('UploadInput', () => {
@@ -58,10 +60,7 @@ describe('UploadInput', () => {
58
60
  renderComponent({ ...props, onFilesChange });
59
61
 
60
62
  const input = screen.getByTestId(UPLOAD_BUTTON_TEST_IDS.uploadInput);
61
- userEvent.upload(input, [pngFile, jpgFile]);
62
- await act(async () => {
63
- await jest.runOnlyPendingTimersAsync();
64
- });
63
+ await user.upload(input, [pngFile, jpgFile]);
65
64
 
66
65
  expect(props.onUploadFile).toHaveBeenCalledTimes(1);
67
66
  expect(onFilesChange).toHaveBeenCalledTimes(2);
@@ -116,7 +115,7 @@ describe('UploadInput', () => {
116
115
  expect(screen.getByTestId(UPLOAD_BUTTON_TEST_IDS.uploadInput)).toHaveAttribute('multiple');
117
116
 
118
117
  const input = screen.getByTestId(UPLOAD_BUTTON_TEST_IDS.uploadInput);
119
- userEvent.upload(input, [pngFile, jpgFile]);
118
+ await user.upload(input, [pngFile, jpgFile]);
120
119
 
121
120
  await waitFor(() => {
122
121
  expect(props.onUploadFile).toHaveBeenCalledTimes(2);
@@ -244,11 +243,11 @@ describe('UploadInput', () => {
244
243
  });
245
244
 
246
245
  const input = screen.getByTestId(UPLOAD_BUTTON_TEST_IDS.uploadInput);
247
- userEvent.upload(input, [pngFile, jpgFile]);
246
+ await user.upload(input, [pngFile, jpgFile]);
248
247
 
249
248
  const pngFile2 = new File(['foo2'], 'foo2.png', { type: 'image/png' });
250
249
  const jpgFile2 = new File(['foo2'], 'foo2.jpg', { type: 'image/jpeg' });
251
- userEvent.upload(input, [pngFile2, jpgFile2]);
250
+ await user.upload(input, [pngFile2, jpgFile2]);
252
251
 
253
252
  await waitFor(() => {
254
253
  expect(screen.getByText(maxFilesReachedMessage)).toBeInTheDocument();
@@ -274,7 +273,7 @@ describe('UploadInput', () => {
274
273
  const input = screen.getByTestId(UPLOAD_BUTTON_TEST_IDS.uploadInput);
275
274
  const pngFile2 = new File(['foo2'], 'foo2.png', { type: 'image/png' });
276
275
 
277
- userEvent.upload(input, [pngFile, jpgFile, pngFile2]);
276
+ await user.upload(input, [pngFile, jpgFile, pngFile2]);
278
277
 
279
278
  await waitFor(() => {
280
279
  expect(screen.getByText(defaultMaxFilesReachedMessage)).toBeInTheDocument();
@@ -305,7 +304,7 @@ describe('UploadInput', () => {
305
304
  const overSizedFile = new File([''], 'testFile.png', { type: 'image/png' });
306
305
  Object.defineProperty(overSizedFile, 'size', { value: twoKbInBytes });
307
306
 
308
- userEvent.upload(input, [overSizedFile]);
307
+ await user.upload(input, overSizedFile);
309
308
  await waitFor(() => {
310
309
  expect(screen.getByText(sizeLimitErrorMessage)).toBeInTheDocument();
311
310
  });