@jobber/components-native 0.40.0 → 0.41.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.
- package/dist/src/FormatFile/FormatFile.js +114 -0
- package/dist/src/FormatFile/FormatFile.style.js +16 -0
- package/dist/src/FormatFile/components/ErrorIcon/ErrorIcon.js +8 -0
- package/dist/src/FormatFile/components/ErrorIcon/ErrorIcon.style.js +10 -0
- package/dist/src/FormatFile/components/ErrorIcon/index.js +1 -0
- package/dist/src/FormatFile/components/FileView/FileView.js +67 -0
- package/dist/src/FormatFile/components/FileView/FileView.style.js +64 -0
- package/dist/src/FormatFile/components/FileView/index.js +1 -0
- package/dist/src/FormatFile/components/FormatFileBottomSheet/FormatFileBottomSheet.js +22 -0
- package/dist/src/FormatFile/components/FormatFileBottomSheet/index.js +1 -0
- package/dist/src/FormatFile/components/FormatFileBottomSheet/messages.js +13 -0
- package/dist/src/FormatFile/components/MediaView/MediaView.js +56 -0
- package/dist/src/FormatFile/components/MediaView/MediaView.style.js +27 -0
- package/dist/src/FormatFile/components/MediaView/index.js +1 -0
- package/dist/src/FormatFile/components/ProgressBar/ProgressBar.js +29 -0
- package/dist/src/FormatFile/components/ProgressBar/ProgressBar.style.js +15 -0
- package/dist/src/FormatFile/components/ProgressBar/index.js +1 -0
- package/dist/src/FormatFile/components/_mocks/mockFiles.js +78 -0
- package/dist/src/FormatFile/constants.js +14 -0
- package/dist/src/FormatFile/context/FormatFileContext.js +8 -0
- package/dist/src/FormatFile/context/types.js +1 -0
- package/dist/src/FormatFile/index.js +1 -0
- package/dist/src/FormatFile/messages.js +23 -0
- package/dist/src/FormatFile/types.js +8 -0
- package/dist/src/FormatFile/utils/computeA11yLabel.js +12 -0
- package/dist/src/FormatFile/utils/createUseCreateThumbnail.js +22 -0
- package/dist/src/FormatFile/utils/index.js +1 -0
- package/dist/src/InputText/InputText.js +8 -1
- package/dist/src/index.js +1 -0
- package/dist/src/utils/test/wait.js +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/src/Form/components/FormMessage/FormMessage.d.ts +1 -0
- package/dist/types/src/FormatFile/FormatFile.d.ts +47 -0
- package/dist/types/src/FormatFile/FormatFile.style.d.ts +14 -0
- package/dist/types/src/FormatFile/components/ErrorIcon/ErrorIcon.d.ts +2 -0
- package/dist/types/src/FormatFile/components/ErrorIcon/ErrorIcon.style.d.ts +8 -0
- package/dist/types/src/FormatFile/components/ErrorIcon/index.d.ts +1 -0
- package/dist/types/src/FormatFile/components/FileView/FileView.d.ts +12 -0
- package/dist/types/src/FormatFile/components/FileView/FileView.style.d.ts +62 -0
- package/dist/types/src/FormatFile/components/FileView/index.d.ts +1 -0
- package/dist/types/src/FormatFile/components/FormatFileBottomSheet/FormatFileBottomSheet.d.ts +11 -0
- package/dist/types/src/FormatFile/components/FormatFileBottomSheet/index.d.ts +2 -0
- package/dist/types/src/FormatFile/components/FormatFileBottomSheet/messages.d.ts +12 -0
- package/dist/types/src/FormatFile/components/MediaView/MediaView.d.ts +12 -0
- package/dist/types/src/FormatFile/components/MediaView/MediaView.style.d.ts +25 -0
- package/dist/types/src/FormatFile/components/MediaView/index.d.ts +1 -0
- package/dist/types/src/FormatFile/components/ProgressBar/ProgressBar.d.ts +19 -0
- package/dist/types/src/FormatFile/components/ProgressBar/ProgressBar.style.d.ts +13 -0
- package/dist/types/src/FormatFile/components/ProgressBar/index.d.ts +1 -0
- package/dist/types/src/FormatFile/components/_mocks/mockFiles.d.ts +18 -0
- package/dist/types/src/FormatFile/constants.d.ts +6 -0
- package/dist/types/src/FormatFile/context/FormatFileContext.d.ts +10 -0
- package/dist/types/src/FormatFile/context/types.d.ts +9 -0
- package/dist/types/src/FormatFile/index.d.ts +3 -0
- package/dist/types/src/FormatFile/messages.d.ts +22 -0
- package/dist/types/src/FormatFile/types.d.ts +105 -0
- package/dist/types/src/FormatFile/utils/computeA11yLabel.d.ts +9 -0
- package/dist/types/src/FormatFile/utils/createUseCreateThumbnail.d.ts +5 -0
- package/dist/types/src/FormatFile/utils/index.d.ts +1 -0
- package/dist/types/src/InputCurrency/InputCurrency.d.ts +1 -1
- package/dist/types/src/index.d.ts +1 -0
- package/package.json +3 -2
- package/src/FormatFile/FormatFile.style.ts +17 -0
- package/src/FormatFile/FormatFile.test.tsx +333 -0
- package/src/FormatFile/FormatFile.tsx +300 -0
- package/src/FormatFile/components/ErrorIcon/ErrorIcon.style.ts +11 -0
- package/src/FormatFile/components/ErrorIcon/ErrorIcon.tsx +12 -0
- package/src/FormatFile/components/ErrorIcon/index.ts +1 -0
- package/src/FormatFile/components/FileView/FileView.style.ts +65 -0
- package/src/FormatFile/components/FileView/FileView.tsx +134 -0
- package/src/FormatFile/components/FileView/index.ts +1 -0
- package/src/FormatFile/components/FormatFileBottomSheet/FormatFileBottomSheet.test.tsx +108 -0
- package/src/FormatFile/components/FormatFileBottomSheet/FormatFileBottomSheet.tsx +56 -0
- package/src/FormatFile/components/FormatFileBottomSheet/index.ts +2 -0
- package/src/FormatFile/components/FormatFileBottomSheet/messages.ts +14 -0
- package/src/FormatFile/components/MediaView/MediaView.style.ts +28 -0
- package/src/FormatFile/components/MediaView/MediaView.tsx +145 -0
- package/src/FormatFile/components/MediaView/index.ts +1 -0
- package/src/FormatFile/components/ProgressBar/ProgressBar.style.tsx +16 -0
- package/src/FormatFile/components/ProgressBar/ProgressBar.tsx +57 -0
- package/src/FormatFile/components/ProgressBar/index.ts +1 -0
- package/src/FormatFile/components/_mocks/mockFiles.ts +105 -0
- package/src/FormatFile/constants.ts +15 -0
- package/src/FormatFile/context/FormatFileContext.ts +13 -0
- package/src/FormatFile/context/types.ts +12 -0
- package/src/FormatFile/index.ts +13 -0
- package/src/FormatFile/messages.ts +24 -0
- package/src/FormatFile/types.ts +126 -0
- package/src/FormatFile/utils/computeA11yLabel.ts +26 -0
- package/src/FormatFile/utils/createUseCreateThumbnail.ts +33 -0
- package/src/FormatFile/utils/index.ts +1 -0
- package/src/InputCurrency/InputCurrency.tsx +1 -1
- package/src/InputText/InputText.tsx +8 -1
- package/src/index.ts +1 -0
- package/src/utils/test/wait.ts +3 -1
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { RenderAPI, fireEvent, render } from "@testing-library/react-native";
|
|
3
|
+
import { Host } from "react-native-portalize";
|
|
4
|
+
import { Alert } from "react-native";
|
|
5
|
+
import { useIntl } from "react-intl";
|
|
6
|
+
import { File, FormatFile } from ".";
|
|
7
|
+
import {
|
|
8
|
+
FILE_MOCK_FILE,
|
|
9
|
+
FILE_MOCK_IMAGE,
|
|
10
|
+
FILE_MOCK_PDF,
|
|
11
|
+
FILE_MOCK_VIDEO,
|
|
12
|
+
FILE_UPLOAD_MOCK_FILE,
|
|
13
|
+
FILE_UPLOAD_MOCK_IMAGE,
|
|
14
|
+
FILE_UPLOAD_MOCK_PDF,
|
|
15
|
+
} from "./components/_mocks/mockFiles";
|
|
16
|
+
import { messages } from "./components/FormatFileBottomSheet/messages";
|
|
17
|
+
import { messages as formatFileMessages } from "./messages";
|
|
18
|
+
import { BottomSheetOptionsSuffix } from "./components/FormatFileBottomSheet";
|
|
19
|
+
import { FileUpload, StatusCode } from "./types";
|
|
20
|
+
import { tokens } from "../utils/design";
|
|
21
|
+
|
|
22
|
+
let Platform: { OS: "ios" | "android" };
|
|
23
|
+
|
|
24
|
+
const onRemove = jest.fn();
|
|
25
|
+
const mockOnPreview = jest.fn();
|
|
26
|
+
const mockCreateThumbnail = jest.fn(async () => ({
|
|
27
|
+
thumbnail: "thumbnail",
|
|
28
|
+
error: false,
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
Platform = require("react-native").Platform;
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
jest.clearAllMocks();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const renderFormatFile = (
|
|
40
|
+
file: FileUpload | File,
|
|
41
|
+
bottomSheetOptionsSuffix?: BottomSheetOptionsSuffix,
|
|
42
|
+
showFileTypeIndicator?: boolean,
|
|
43
|
+
) => {
|
|
44
|
+
return render(
|
|
45
|
+
<Host>
|
|
46
|
+
<FormatFile
|
|
47
|
+
file={file}
|
|
48
|
+
accessibilityLabel="Custom Label"
|
|
49
|
+
accessibilityHint="Custom Hint Text"
|
|
50
|
+
onTap={() => Alert.alert("alert")}
|
|
51
|
+
onRemove={onRemove}
|
|
52
|
+
bottomSheetOptionsSuffix={bottomSheetOptionsSuffix}
|
|
53
|
+
showFileTypeIndicator={showFileTypeIndicator}
|
|
54
|
+
onPreviewPress={mockOnPreview}
|
|
55
|
+
createThumbnail={mockCreateThumbnail}
|
|
56
|
+
/>
|
|
57
|
+
</Host>,
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
function basicRenderTestWithValue() {
|
|
62
|
+
const progressBarAnimationTime = 500;
|
|
63
|
+
|
|
64
|
+
it.each([
|
|
65
|
+
[
|
|
66
|
+
"file",
|
|
67
|
+
FILE_UPLOAD_MOCK_FILE({ progress: 1, status: StatusCode.Completed }),
|
|
68
|
+
],
|
|
69
|
+
[
|
|
70
|
+
"image",
|
|
71
|
+
FILE_UPLOAD_MOCK_IMAGE({ progress: 1, status: StatusCode.Completed }),
|
|
72
|
+
],
|
|
73
|
+
])(
|
|
74
|
+
"renders a %s with custom label and hint",
|
|
75
|
+
(bottomSheetOptionsSuffix, file) => {
|
|
76
|
+
const { getByLabelText, getByHintText } = renderFormatFile(
|
|
77
|
+
file,
|
|
78
|
+
bottomSheetOptionsSuffix as BottomSheetOptionsSuffix,
|
|
79
|
+
);
|
|
80
|
+
expect(getByLabelText("Custom Label")).toBeDefined();
|
|
81
|
+
expect(getByHintText("Custom Hint Text")).toBeDefined();
|
|
82
|
+
},
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
describe.each([
|
|
86
|
+
["file", FILE_UPLOAD_MOCK_FILE({ progress: 0.9 })],
|
|
87
|
+
["image", FILE_UPLOAD_MOCK_IMAGE({ progress: 0.9 })],
|
|
88
|
+
])("when a local %s is being uploaded", (testIdType, file) => {
|
|
89
|
+
const testId = `test-${testIdType}`;
|
|
90
|
+
it("renders ProgressBar state when upload status is not completed", () => {
|
|
91
|
+
const { getByTestId } = renderFormatFile(file);
|
|
92
|
+
expect(getByTestId("format-file-progress-bar")).toBeDefined();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("renders a helpful accessibility label", () => {
|
|
96
|
+
const tree = renderFormatFile(file);
|
|
97
|
+
expect(
|
|
98
|
+
tree.getByLabelText(
|
|
99
|
+
formatFileMessages.inProgressAccessibilityLabel.defaultMessage,
|
|
100
|
+
),
|
|
101
|
+
).toBeDefined();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("renders an overlay on the image when upload status is not completed", () => {
|
|
105
|
+
const { getByTestId } = renderFormatFile(file);
|
|
106
|
+
const progressBarContainer = getByTestId(
|
|
107
|
+
"format-file-progress-bar-container",
|
|
108
|
+
);
|
|
109
|
+
const overlayStyles = progressBarContainer.props.style;
|
|
110
|
+
|
|
111
|
+
// The container needs to have a height and color in order to show the overlay
|
|
112
|
+
expect(overlayStyles).toEqual(
|
|
113
|
+
expect.arrayContaining([expect.objectContaining({ height: "100%" })]),
|
|
114
|
+
);
|
|
115
|
+
expect(overlayStyles).toEqual(
|
|
116
|
+
expect.arrayContaining([
|
|
117
|
+
expect.objectContaining({
|
|
118
|
+
backgroundColor: tokens["color-overlay--dimmed"],
|
|
119
|
+
}),
|
|
120
|
+
]),
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("renders ProgressBar state advancing with the upload percentage", () => {
|
|
125
|
+
jest.useFakeTimers();
|
|
126
|
+
const { getByTestId } = renderFormatFile(file);
|
|
127
|
+
jest.advanceTimersByTime(progressBarAnimationTime);
|
|
128
|
+
const formatFileInnerProgressBar = getByTestId(
|
|
129
|
+
"format-file-inner-progress-bar",
|
|
130
|
+
);
|
|
131
|
+
const innerProgressBarWidth = parseInt(
|
|
132
|
+
formatFileInnerProgressBar.props.style.width,
|
|
133
|
+
10,
|
|
134
|
+
);
|
|
135
|
+
expect(innerProgressBarWidth).toBeGreaterThan(20);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("shows an alert for on tap", () => {
|
|
139
|
+
const { getByTestId } = renderFormatFile(file);
|
|
140
|
+
const spy = jest.spyOn(Alert, "alert");
|
|
141
|
+
fireEvent.press(getByTestId(testId));
|
|
142
|
+
expect(spy).toHaveBeenCalled();
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe.each([
|
|
147
|
+
[
|
|
148
|
+
"file",
|
|
149
|
+
{
|
|
150
|
+
...FILE_UPLOAD_MOCK_FILE({ progress: 0.9, status: StatusCode.Failed }),
|
|
151
|
+
status: StatusCode.Failed,
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
[
|
|
155
|
+
"image",
|
|
156
|
+
{
|
|
157
|
+
...FILE_UPLOAD_MOCK_IMAGE({ progress: 0.9, status: StatusCode.Failed }),
|
|
158
|
+
status: StatusCode.Failed,
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
])("when a local %s upload has failed", (testIdType, file) => {
|
|
162
|
+
it("renders an error icon", () => {
|
|
163
|
+
const tree = renderFormatFile(file);
|
|
164
|
+
expect(tree.getByTestId("format-file-error-icon")).toBeDefined();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("renders a helpful accessibility label", () => {
|
|
168
|
+
const tree = renderFormatFile(file);
|
|
169
|
+
expect(
|
|
170
|
+
tree.getByLabelText(
|
|
171
|
+
formatFileMessages.errorAccessibilityLabel.defaultMessage,
|
|
172
|
+
),
|
|
173
|
+
).toBeDefined();
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("does not render an overlay", () => {
|
|
177
|
+
const tree = renderFormatFile(file);
|
|
178
|
+
expect(
|
|
179
|
+
tree.queryByTestId("format-file-progress-bar-container"),
|
|
180
|
+
).toBeNull();
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("shows an alert for on tap", () => {
|
|
184
|
+
const testId = `test-${testIdType}`;
|
|
185
|
+
const tree = renderFormatFile(file);
|
|
186
|
+
const spy = jest.spyOn(Alert, "alert");
|
|
187
|
+
fireEvent.press(tree.getByTestId(testId));
|
|
188
|
+
expect(spy).toHaveBeenCalled();
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
describe.each([
|
|
193
|
+
[
|
|
194
|
+
"local file",
|
|
195
|
+
"test-file",
|
|
196
|
+
FILE_UPLOAD_MOCK_FILE({ progress: 1, status: StatusCode.Completed }),
|
|
197
|
+
],
|
|
198
|
+
[
|
|
199
|
+
"local image",
|
|
200
|
+
"test-image",
|
|
201
|
+
FILE_UPLOAD_MOCK_IMAGE({ progress: 1, status: StatusCode.Completed }),
|
|
202
|
+
],
|
|
203
|
+
["external file", "test-file", FILE_MOCK_FILE],
|
|
204
|
+
["external image", "test-image", FILE_MOCK_IMAGE],
|
|
205
|
+
])(
|
|
206
|
+
"when a uploaded %s is being used",
|
|
207
|
+
(bottomSheetOptionsSuffix, testId, file) => {
|
|
208
|
+
let tree: RenderAPI;
|
|
209
|
+
const { formatMessage } = useIntl();
|
|
210
|
+
const removeLabel = formatMessage(messages.removeButton, {
|
|
211
|
+
bottomSheetOptionsSuffix: bottomSheetOptionsSuffix,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
beforeEach(() => {
|
|
215
|
+
jest.clearAllMocks();
|
|
216
|
+
jest.useFakeTimers();
|
|
217
|
+
tree = renderFormatFile(
|
|
218
|
+
file,
|
|
219
|
+
bottomSheetOptionsSuffix as BottomSheetOptionsSuffix,
|
|
220
|
+
);
|
|
221
|
+
jest.advanceTimersByTime(progressBarAnimationTime);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("shows a BottomSheet with a remove option when tapped", () => {
|
|
225
|
+
const { getByTestId, getByLabelText } = tree;
|
|
226
|
+
fireEvent.press(getByTestId(testId));
|
|
227
|
+
expect(getByLabelText(removeLabel)).toBeDefined();
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
describe("when the BottomSheet remove option is tapped", () => {
|
|
231
|
+
it("calls the onRemove action", () => {
|
|
232
|
+
const { getByTestId, getByLabelText } = tree;
|
|
233
|
+
fireEvent.press(getByTestId(testId));
|
|
234
|
+
fireEvent.press(getByLabelText(removeLabel));
|
|
235
|
+
expect(onRemove).toHaveBeenCalledTimes(1);
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it("creates a thumbnail when a media file is used", () => {
|
|
240
|
+
const expectedCalls = testId.includes("image") ? 2 : 0;
|
|
241
|
+
expect(mockCreateThumbnail).toHaveBeenCalledTimes(expectedCalls);
|
|
242
|
+
});
|
|
243
|
+
},
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
describe("when the preview option is tapped", () => {
|
|
247
|
+
const { formatMessage } = useIntl();
|
|
248
|
+
|
|
249
|
+
it("calls onPreview with a valid image", () => {
|
|
250
|
+
const previewLabel = formatMessage(messages.lightBoxPreviewButton, {
|
|
251
|
+
bottomSheetOptionsSuffix: "image",
|
|
252
|
+
});
|
|
253
|
+
const { getByTestId, getByLabelText } = renderFormatFile(
|
|
254
|
+
FILE_UPLOAD_MOCK_IMAGE({ progress: 1, status: StatusCode.Completed }),
|
|
255
|
+
"image",
|
|
256
|
+
);
|
|
257
|
+
fireEvent.press(getByTestId("test-image"));
|
|
258
|
+
fireEvent.press(getByLabelText(previewLabel));
|
|
259
|
+
expect(mockOnPreview).toHaveBeenCalledTimes(1);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("calls onPreview with a valid pdf file", () => {
|
|
263
|
+
const previewLabel = formatMessage(messages.lightBoxPreviewButton, {
|
|
264
|
+
bottomSheetOptionsSuffix: "file",
|
|
265
|
+
});
|
|
266
|
+
const { getByTestId, getByLabelText } = renderFormatFile(
|
|
267
|
+
FILE_UPLOAD_MOCK_PDF({ progress: 1, status: StatusCode.Completed }),
|
|
268
|
+
"file",
|
|
269
|
+
);
|
|
270
|
+
fireEvent.press(getByTestId("test-file"));
|
|
271
|
+
fireEvent.press(getByLabelText(previewLabel));
|
|
272
|
+
expect(mockOnPreview).toHaveBeenCalledTimes(1);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it("calls onPreview with a valid external PDF file", () => {
|
|
276
|
+
const previewLabel = formatMessage(messages.lightBoxPreviewButton, {
|
|
277
|
+
bottomSheetOptionsSuffix: "file",
|
|
278
|
+
});
|
|
279
|
+
const { getByTestId, getByLabelText } = renderFormatFile(
|
|
280
|
+
FILE_MOCK_PDF,
|
|
281
|
+
"file",
|
|
282
|
+
);
|
|
283
|
+
fireEvent.press(getByTestId("test-file"));
|
|
284
|
+
fireEvent.press(getByLabelText(previewLabel));
|
|
285
|
+
expect(mockOnPreview).toHaveBeenCalledTimes(1);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it("does not show the preview option with an unaccepted file", () => {
|
|
289
|
+
const previewLabel = formatMessage(messages.lightBoxPreviewButton, {
|
|
290
|
+
bottomSheetOptionsSuffix: "file",
|
|
291
|
+
});
|
|
292
|
+
const { getByTestId, queryByLabelText } = renderFormatFile(
|
|
293
|
+
FILE_UPLOAD_MOCK_FILE({ progress: 1, status: StatusCode.Completed }),
|
|
294
|
+
"file",
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
fireEvent.press(getByTestId("test-file"));
|
|
298
|
+
expect(queryByLabelText(previewLabel)).toBeNull();
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
describe("when an uploaded video is being viewed", () => {
|
|
303
|
+
it("shows the play icon if the file type indicator is not specified", () => {
|
|
304
|
+
const { getByTestId } = renderFormatFile(FILE_MOCK_VIDEO, "video");
|
|
305
|
+
expect(getByTestId("video")).toBeDefined();
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it("does not show the play icon if the file type indicator is set to false", () => {
|
|
309
|
+
const { queryByTestId } = renderFormatFile(
|
|
310
|
+
FILE_MOCK_VIDEO,
|
|
311
|
+
"video",
|
|
312
|
+
false,
|
|
313
|
+
);
|
|
314
|
+
expect(queryByTestId("video")).toBeNull();
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
describe("ios", () => {
|
|
320
|
+
beforeEach(() => {
|
|
321
|
+
Platform.OS = "ios";
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
basicRenderTestWithValue();
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
describe("android", () => {
|
|
328
|
+
beforeEach(() => {
|
|
329
|
+
Platform.OS = "android";
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
basicRenderTestWithValue();
|
|
333
|
+
});
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import React, { createRef, useCallback, useState } from "react";
|
|
2
|
+
import { TouchableOpacity, View } from "react-native";
|
|
3
|
+
import { useIntl } from "react-intl";
|
|
4
|
+
import { messages } from "./messages";
|
|
5
|
+
import { styles } from "./FormatFile.style";
|
|
6
|
+
import { MediaView } from "./components/MediaView";
|
|
7
|
+
import {
|
|
8
|
+
BottomSheetOptionsSuffix,
|
|
9
|
+
FormatFileBottomSheet,
|
|
10
|
+
} from "./components/FormatFileBottomSheet";
|
|
11
|
+
import { FileView } from "./components/FileView";
|
|
12
|
+
import { acceptedExtensions, videoExtensions } from "./constants";
|
|
13
|
+
import {
|
|
14
|
+
CreateThumbnail,
|
|
15
|
+
File,
|
|
16
|
+
FileUpload,
|
|
17
|
+
FormattedFile,
|
|
18
|
+
StatusCode,
|
|
19
|
+
} from "./types";
|
|
20
|
+
import { AtlantisFormatFileContext } from "./context/FormatFileContext";
|
|
21
|
+
import { createUseCreateThumbnail } from "./utils/createUseCreateThumbnail";
|
|
22
|
+
import { BottomSheetRef } from "../BottomSheet/BottomSheet";
|
|
23
|
+
|
|
24
|
+
export interface FormatFileProps<T> {
|
|
25
|
+
/**
|
|
26
|
+
* File upload details object. Can be a File or a FileUpload
|
|
27
|
+
*/
|
|
28
|
+
file: T;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Accessibility label
|
|
32
|
+
*/
|
|
33
|
+
readonly accessibilityLabel?: string;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Accessibility hint
|
|
37
|
+
*/
|
|
38
|
+
readonly accessibilityHint?: string;
|
|
39
|
+
/**
|
|
40
|
+
* A function which handles the onTap event.
|
|
41
|
+
*/
|
|
42
|
+
onTap?: (file: T) => void;
|
|
43
|
+
/**
|
|
44
|
+
* A function to be called on "Remove" Bottom Sheet Option press
|
|
45
|
+
*/
|
|
46
|
+
onRemove?: () => void;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Handler for the "Preview" Bottom Sheet Option press
|
|
50
|
+
*/
|
|
51
|
+
onPreviewPress?: (formattedFile: FormattedFile) => void;
|
|
52
|
+
/**
|
|
53
|
+
* A file type to show at Bottom Sheet options
|
|
54
|
+
*/
|
|
55
|
+
bottomSheetOptionsSuffix?: BottomSheetOptionsSuffix;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Uses a grid layout when multi-file upload is supported
|
|
59
|
+
*/
|
|
60
|
+
styleInGrid?: boolean;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* A reference to the element in the rendered output
|
|
64
|
+
*/
|
|
65
|
+
readonly testID?: string;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Set false to hide the filetype icon
|
|
69
|
+
*/
|
|
70
|
+
readonly showFileTypeIndicator?: boolean;
|
|
71
|
+
|
|
72
|
+
readonly createThumbnail?: CreateThumbnail;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
type FormatFileInternalProps = Omit<
|
|
76
|
+
FormatFileProps<File | FileUpload>,
|
|
77
|
+
"file" | "onTap"
|
|
78
|
+
> & {
|
|
79
|
+
file: FormattedFile;
|
|
80
|
+
onTap: () => void;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
interface FormatFileContentProps {
|
|
84
|
+
accessibilityLabel?: string;
|
|
85
|
+
file: FormattedFile;
|
|
86
|
+
showOverlay: boolean;
|
|
87
|
+
styleInGrid: boolean;
|
|
88
|
+
onUploadComplete: () => void;
|
|
89
|
+
isMedia: boolean;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function FormatFileContent({
|
|
93
|
+
accessibilityLabel,
|
|
94
|
+
file,
|
|
95
|
+
showOverlay,
|
|
96
|
+
styleInGrid,
|
|
97
|
+
onUploadComplete,
|
|
98
|
+
isMedia,
|
|
99
|
+
}: FormatFileContentProps): JSX.Element {
|
|
100
|
+
return (
|
|
101
|
+
<View
|
|
102
|
+
style={[
|
|
103
|
+
styles.thumbnailContainer,
|
|
104
|
+
styleInGrid && styles.thumbnailContainerGrid,
|
|
105
|
+
]}
|
|
106
|
+
>
|
|
107
|
+
{isMedia ? (
|
|
108
|
+
<MediaView
|
|
109
|
+
accessibilityLabel={accessibilityLabel}
|
|
110
|
+
file={file}
|
|
111
|
+
showOverlay={showOverlay}
|
|
112
|
+
showError={file.error}
|
|
113
|
+
styleInGrid={styleInGrid}
|
|
114
|
+
onUploadComplete={onUploadComplete}
|
|
115
|
+
/>
|
|
116
|
+
) : (
|
|
117
|
+
<FileView
|
|
118
|
+
accessibilityLabel={accessibilityLabel}
|
|
119
|
+
file={file}
|
|
120
|
+
showOverlay={showOverlay}
|
|
121
|
+
showError={file.error}
|
|
122
|
+
styleInGrid={styleInGrid}
|
|
123
|
+
onUploadComplete={onUploadComplete}
|
|
124
|
+
/>
|
|
125
|
+
)}
|
|
126
|
+
</View>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const FormatFileInternalMemoized = React.memo(FormatFileInternal);
|
|
131
|
+
|
|
132
|
+
function isMediaFile(fileType: string): boolean {
|
|
133
|
+
return fileType.includes("image") || fileType.includes("video");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function isVideo(fileName: string): boolean {
|
|
137
|
+
const extension = fileName.substring(fileName.lastIndexOf(".") + 1);
|
|
138
|
+
|
|
139
|
+
return videoExtensions.some(({ type }) => type === extension.toLowerCase());
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function getContentType(fileName = "", fileType = "unknown"): string {
|
|
143
|
+
if (isVideo(fileName)) {
|
|
144
|
+
return "video";
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return fileType;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function isAcceptedExtension(file: FormattedFile): boolean {
|
|
151
|
+
return acceptedExtensions.some(extension =>
|
|
152
|
+
// type property may return undefined on M1 Systems running iOS Simulator
|
|
153
|
+
(file.type || "").includes(extension.name),
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function parseFile(
|
|
158
|
+
file: File | FileUpload,
|
|
159
|
+
showFileTypeIndicator: boolean,
|
|
160
|
+
): FormattedFile {
|
|
161
|
+
let formattedFile: FormattedFile;
|
|
162
|
+
|
|
163
|
+
if ("progress" in file) {
|
|
164
|
+
formattedFile = {
|
|
165
|
+
source: file.sourcePath,
|
|
166
|
+
name: file.name,
|
|
167
|
+
size: file.size,
|
|
168
|
+
external: false,
|
|
169
|
+
progress: file.progress,
|
|
170
|
+
status: file.status,
|
|
171
|
+
error: file.status === StatusCode.Failed,
|
|
172
|
+
type: file.type || file.key,
|
|
173
|
+
isMedia: false,
|
|
174
|
+
showPreview: false,
|
|
175
|
+
showFileTypeIndicator: showFileTypeIndicator,
|
|
176
|
+
};
|
|
177
|
+
} else {
|
|
178
|
+
formattedFile = {
|
|
179
|
+
source: file.url,
|
|
180
|
+
thumbnailUrl: file.thumbnailUrl,
|
|
181
|
+
name: file.fileName,
|
|
182
|
+
size: file.fileSize,
|
|
183
|
+
external: true,
|
|
184
|
+
progress: 1,
|
|
185
|
+
status: StatusCode.Completed,
|
|
186
|
+
error: false,
|
|
187
|
+
type: getContentType(file.fileName, file.contentType),
|
|
188
|
+
isMedia: false,
|
|
189
|
+
showPreview: false,
|
|
190
|
+
showFileTypeIndicator: showFileTypeIndicator,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
formattedFile.isMedia = isMediaFile(formattedFile.type || "");
|
|
195
|
+
formattedFile.showPreview =
|
|
196
|
+
formattedFile.isMedia || isAcceptedExtension(formattedFile);
|
|
197
|
+
|
|
198
|
+
return formattedFile;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function FormatFile<T extends File | FileUpload>({
|
|
202
|
+
file,
|
|
203
|
+
accessibilityLabel,
|
|
204
|
+
accessibilityHint,
|
|
205
|
+
onTap,
|
|
206
|
+
onRemove,
|
|
207
|
+
bottomSheetOptionsSuffix,
|
|
208
|
+
styleInGrid = false,
|
|
209
|
+
testID,
|
|
210
|
+
showFileTypeIndicator = true,
|
|
211
|
+
createThumbnail,
|
|
212
|
+
onPreviewPress,
|
|
213
|
+
}: FormatFileProps<T>): JSX.Element {
|
|
214
|
+
const onTapModified = onTap ? () => onTap(file) : () => undefined;
|
|
215
|
+
|
|
216
|
+
const formattedFile = parseFile(file, showFileTypeIndicator);
|
|
217
|
+
|
|
218
|
+
return (
|
|
219
|
+
<FormatFileInternalMemoized
|
|
220
|
+
file={formattedFile}
|
|
221
|
+
accessibilityLabel={accessibilityLabel}
|
|
222
|
+
accessibilityHint={accessibilityHint}
|
|
223
|
+
onTap={onTapModified}
|
|
224
|
+
onRemove={onRemove}
|
|
225
|
+
bottomSheetOptionsSuffix={bottomSheetOptionsSuffix}
|
|
226
|
+
styleInGrid={styleInGrid}
|
|
227
|
+
testID={testID}
|
|
228
|
+
createThumbnail={createThumbnail}
|
|
229
|
+
onPreviewPress={onPreviewPress}
|
|
230
|
+
/>
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function FormatFileInternal({
|
|
235
|
+
file,
|
|
236
|
+
accessibilityLabel,
|
|
237
|
+
accessibilityHint,
|
|
238
|
+
onTap,
|
|
239
|
+
onRemove,
|
|
240
|
+
bottomSheetOptionsSuffix,
|
|
241
|
+
styleInGrid = false,
|
|
242
|
+
onPreviewPress,
|
|
243
|
+
testID,
|
|
244
|
+
createThumbnail: createThumbnailProp,
|
|
245
|
+
}: FormatFileInternalProps): JSX.Element {
|
|
246
|
+
const [showOverlay, setShowOverlay] = useState<boolean>(
|
|
247
|
+
file.status !== StatusCode.Completed,
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
const { formatMessage } = useIntl();
|
|
251
|
+
const bottomSheetRef = createRef<BottomSheetRef>();
|
|
252
|
+
|
|
253
|
+
const handlePreviewPress = useCallback(() => {
|
|
254
|
+
onPreviewPress?.(file);
|
|
255
|
+
}, [file, onPreviewPress]);
|
|
256
|
+
const createThumbnail = createThumbnailProp
|
|
257
|
+
? createThumbnailProp
|
|
258
|
+
: async () => ({ error: false, thumbnail: "" });
|
|
259
|
+
const { useCreateThumbnail } = createUseCreateThumbnail(createThumbnail);
|
|
260
|
+
|
|
261
|
+
return (
|
|
262
|
+
<AtlantisFormatFileContext.Provider value={{ useCreateThumbnail }}>
|
|
263
|
+
<View>
|
|
264
|
+
<TouchableOpacity
|
|
265
|
+
accessibilityRole="imagebutton"
|
|
266
|
+
accessibilityHint={
|
|
267
|
+
accessibilityHint ??
|
|
268
|
+
formatMessage(messages.defaultAccessibilityHint)
|
|
269
|
+
}
|
|
270
|
+
onPress={handleOnPress}
|
|
271
|
+
testID={testID}
|
|
272
|
+
>
|
|
273
|
+
<FormatFileContent
|
|
274
|
+
accessibilityLabel={accessibilityLabel}
|
|
275
|
+
file={file}
|
|
276
|
+
onUploadComplete={() => setShowOverlay(false)}
|
|
277
|
+
isMedia={!!file.isMedia}
|
|
278
|
+
styleInGrid={styleInGrid}
|
|
279
|
+
showOverlay={showOverlay}
|
|
280
|
+
/>
|
|
281
|
+
</TouchableOpacity>
|
|
282
|
+
<FormatFileBottomSheet
|
|
283
|
+
bottomSheetRef={bottomSheetRef}
|
|
284
|
+
onRemovePress={onRemove}
|
|
285
|
+
bottomSheetOptionsSuffix={bottomSheetOptionsSuffix}
|
|
286
|
+
onPreviewPress={file.showPreview ? handlePreviewPress : undefined}
|
|
287
|
+
/>
|
|
288
|
+
</View>
|
|
289
|
+
</AtlantisFormatFileContext.Provider>
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
function handleOnPress() {
|
|
293
|
+
if (showOverlay || !onRemove) {
|
|
294
|
+
onTap();
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
bottomSheetRef.current?.open();
|
|
299
|
+
}
|
|
300
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { StyleSheet } from "react-native";
|
|
2
|
+
import { tokens } from "../../../utils/design";
|
|
3
|
+
|
|
4
|
+
export const styles = StyleSheet.create({
|
|
5
|
+
circle: {
|
|
6
|
+
width: tokens["space-large"],
|
|
7
|
+
height: tokens["space-large"],
|
|
8
|
+
borderRadius: tokens["radius-circle"],
|
|
9
|
+
backgroundColor: tokens["color-surface"],
|
|
10
|
+
},
|
|
11
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { View } from "react-native";
|
|
3
|
+
import { styles } from "./ErrorIcon.style";
|
|
4
|
+
import { Icon } from "../../../Icon";
|
|
5
|
+
|
|
6
|
+
export function ErrorIcon(): JSX.Element {
|
|
7
|
+
return (
|
|
8
|
+
<View testID="format-file-error-icon" style={styles.circle}>
|
|
9
|
+
<Icon name="alert" color="critical" />
|
|
10
|
+
</View>
|
|
11
|
+
);
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ErrorIcon } from "./ErrorIcon";
|