@jobber/components-native 0.101.3 → 0.101.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/dist/package.json +19 -11
  2. package/dist/src/ActionItem/ActionItem.test.js +81 -0
  3. package/dist/src/ActionItem/ActionItemGroup.test.js +25 -0
  4. package/dist/src/ActionItem/components/ActionItemContainer.test.js +24 -0
  5. package/dist/src/ActionLabel/ActionLabel.test.js +81 -0
  6. package/dist/src/ActivityIndicator/ActivityIndicator.test.js +23 -0
  7. package/dist/src/AtlantisContext/AtlantisContext.test.js +35 -0
  8. package/dist/src/AtlantisThemeContext/AtlantisThemeContext.test.js +65 -0
  9. package/dist/src/AtlantisThemeContext/buildThemedStyles.test.js +43 -0
  10. package/dist/src/AutoLink/AutoLink.test.js +133 -0
  11. package/dist/src/AutoLink/components/Link/Link.test.js +18 -0
  12. package/dist/src/Banner/Banner.test.js +98 -0
  13. package/dist/src/BottomSheet/BottomSheet.test.js +105 -0
  14. package/dist/src/BottomSheet/components/BottomSheetOption/BottomSheetOption.test.js +19 -0
  15. package/dist/src/BottomSheet/hooks/useBottomSheetBackHandler.test.js +68 -0
  16. package/dist/src/Button/Button.test.js +228 -0
  17. package/dist/src/Button/components/InternalButtonLoading/InternalButtonLoading.test.js +25 -0
  18. package/dist/src/ButtonGroup/ButtonGroup.test.js +153 -0
  19. package/dist/src/Card/Card.test.js +80 -0
  20. package/dist/src/Card/components/InternalCardHeader.test.js +18 -0
  21. package/dist/src/Checkbox/Checkbox.test.js +135 -0
  22. package/dist/src/Checkbox/CheckboxGroup.test.js +197 -0
  23. package/dist/src/Checkbox/CheckboxGroupReducer.test.js +25 -0
  24. package/dist/src/Chip/Chip.test.js +69 -0
  25. package/dist/src/Content/Content.test.js +250 -0
  26. package/dist/src/ContentOverlay/ContentOverlay.test.js +297 -0
  27. package/dist/src/ContentOverlay/computeContentOverlayBehavior.test.js +197 -0
  28. package/dist/src/ContentOverlay/hooks/useBottomSheetModalBackHandler.test.js +62 -0
  29. package/dist/src/ContentOverlay/hooks/useKeyboardVisibility.test.js +41 -0
  30. package/dist/src/ContentOverlay/hooks/useViewLayoutHeight.test.js +62 -0
  31. package/dist/src/Disclosure/Disclosure.test.js +64 -0
  32. package/dist/src/Divider/Divider.test.js +65 -0
  33. package/dist/src/EmptyState/EmptyState.test.js +82 -0
  34. package/dist/src/ErrorMessageWrapper/ErrorMessageWrapper.test.js +29 -0
  35. package/dist/src/Flex/Flex.test.js +104 -0
  36. package/dist/src/Form/Form.test.js +393 -0
  37. package/dist/src/Form/components/FormErrorBanner/FormErrorBanner.test.js +41 -0
  38. package/dist/src/Form/components/FormMessage/FormMessage.test.js +73 -0
  39. package/dist/src/Form/components/FormMessageBanner/FormMessageBanner.test.js +30 -0
  40. package/dist/src/Form/components/FormSaveButton/FormSaveButton.test.js +82 -0
  41. package/dist/src/Form/context/AtlantisFormContext.test.js +28 -0
  42. package/dist/src/Form/hooks/useScrollToError/useScrollToError.test.js +89 -0
  43. package/dist/src/FormField/FormField.test.js +81 -0
  44. package/dist/src/FormatFile/FormatFile.test.js +212 -0
  45. package/dist/src/FormatFile/FormatFileThumbnail.test.js +192 -0
  46. package/dist/src/FormatFile/components/FormatFileBottomSheet/FormatFileBottomSheet.test.js +74 -0
  47. package/dist/src/FormatFile/components/MediaView/MediaView.test.js +202 -0
  48. package/dist/src/FormatFile/utils/parseFile.test.js +188 -0
  49. package/dist/src/Glimmer/Glimmer.test.js +61 -0
  50. package/dist/src/Heading/Heading.test.js +61 -0
  51. package/dist/src/Icon/Icon.test.js +40 -0
  52. package/dist/src/IconButton/IconButton.test.js +38 -0
  53. package/dist/src/InputCurrency/InputCurrency.test.js +106 -0
  54. package/dist/src/InputDate/InputDate.test.js +184 -0
  55. package/dist/src/InputEmail/InputEmail.test.js +27 -0
  56. package/dist/src/InputFieldWrapper/InputFieldWrapper.test.js +279 -0
  57. package/dist/src/InputFieldWrapper/components/ClearAction/ClearAction.test.js +9 -0
  58. package/dist/src/InputFieldWrapper/components/Prefix/Prefix.test.js +130 -0
  59. package/dist/src/InputFieldWrapper/components/Suffix/Suffix.test.js +51 -0
  60. package/dist/src/InputNumber/InputNumber.test.js +220 -0
  61. package/dist/src/InputPassword/InputPassword.test.js +63 -0
  62. package/dist/src/InputPressable/InputPressable.test.js +138 -0
  63. package/dist/src/InputSearch/InputSearch.test.js +54 -0
  64. package/dist/src/InputText/InputText.test.js +652 -0
  65. package/dist/src/InputText/context/InputAccessoriesProvider.test.js +71 -0
  66. package/dist/src/InputTime/InputTime.test.js +199 -0
  67. package/dist/src/InputTime/utils/utils.test.js +32 -0
  68. package/dist/src/ProgressBar/ProgressBar.test.js +89 -0
  69. package/dist/src/Select/Select.test.js +183 -0
  70. package/dist/src/Select/components/SelectDefaultPicker/SelectDefaultPicker.test.js +51 -0
  71. package/dist/src/Select/components/SelectInternalPicker/SelectInternalPicker.test.js +72 -0
  72. package/dist/src/StatusLabel/StatusLabel.test.js +51 -0
  73. package/dist/src/Switch/Switch.test.js +60 -0
  74. package/dist/src/Switch/components/BaseSwitch/BaseSwitch.test.js +61 -0
  75. package/dist/src/Text/Text.test.js +161 -0
  76. package/dist/src/TextList/TextList.test.js +16 -0
  77. package/dist/src/ThumbnailList/ThumbnailList.test.js +72 -0
  78. package/dist/src/Toast/Toast.test.js +51 -0
  79. package/dist/src/Typography/Typography.test.js +225 -0
  80. package/dist/src/hooks/useAtlantisI18n/useAtlantisI18n.test.js +103 -0
  81. package/dist/src/utils/meta/meta.test.js +83 -0
  82. package/dist/tsconfig.build.json +5 -1
  83. package/dist/tsconfig.build.tsbuildinfo +1 -1
  84. package/dist/tsconfig.eslint.json +14 -0
  85. package/dist/tsconfig.json +3 -4
  86. package/dist/types/src/ActionItem/ActionItem.test.d.ts +1 -0
  87. package/dist/types/src/ActionItem/ActionItemGroup.test.d.ts +1 -0
  88. package/dist/types/src/ActionItem/components/ActionItemContainer.test.d.ts +1 -0
  89. package/dist/types/src/ActionLabel/ActionLabel.test.d.ts +1 -0
  90. package/dist/types/src/ActivityIndicator/ActivityIndicator.test.d.ts +1 -0
  91. package/dist/types/src/AtlantisContext/AtlantisContext.test.d.ts +1 -0
  92. package/dist/types/src/AtlantisThemeContext/AtlantisThemeContext.test.d.ts +1 -0
  93. package/dist/types/src/AtlantisThemeContext/buildThemedStyles.test.d.ts +1 -0
  94. package/dist/types/src/AutoLink/AutoLink.test.d.ts +1 -0
  95. package/dist/types/src/AutoLink/components/Link/Link.test.d.ts +1 -0
  96. package/dist/types/src/Banner/Banner.test.d.ts +1 -0
  97. package/dist/types/src/BottomSheet/BottomSheet.test.d.ts +1 -0
  98. package/dist/types/src/BottomSheet/components/BottomSheetOption/BottomSheetOption.test.d.ts +1 -0
  99. package/dist/types/src/BottomSheet/hooks/useBottomSheetBackHandler.test.d.ts +1 -0
  100. package/dist/types/src/Button/Button.test.d.ts +1 -0
  101. package/dist/types/src/Button/components/InternalButtonLoading/InternalButtonLoading.test.d.ts +1 -0
  102. package/dist/types/src/ButtonGroup/ButtonGroup.test.d.ts +1 -0
  103. package/dist/types/src/Card/Card.test.d.ts +1 -0
  104. package/dist/types/src/Card/components/InternalCardHeader.test.d.ts +1 -0
  105. package/dist/types/src/Checkbox/Checkbox.test.d.ts +1 -0
  106. package/dist/types/src/Checkbox/CheckboxGroup.test.d.ts +1 -0
  107. package/dist/types/src/Checkbox/CheckboxGroupReducer.test.d.ts +1 -0
  108. package/dist/types/src/Chip/Chip.test.d.ts +1 -0
  109. package/dist/types/src/Content/Content.test.d.ts +1 -0
  110. package/dist/types/src/ContentOverlay/BottomSheetKeyboardAwareScrollView.d.ts +2 -1
  111. package/dist/types/src/ContentOverlay/ContentOverlay.test.d.ts +1 -0
  112. package/dist/types/src/ContentOverlay/computeContentOverlayBehavior.test.d.ts +1 -0
  113. package/dist/types/src/ContentOverlay/hooks/useBottomSheetModalBackHandler.test.d.ts +1 -0
  114. package/dist/types/src/ContentOverlay/hooks/useKeyboardVisibility.test.d.ts +1 -0
  115. package/dist/types/src/ContentOverlay/hooks/useViewLayoutHeight.test.d.ts +1 -0
  116. package/dist/types/src/Disclosure/Disclosure.test.d.ts +1 -0
  117. package/dist/types/src/Divider/Divider.test.d.ts +1 -0
  118. package/dist/types/src/EmptyState/EmptyState.test.d.ts +1 -0
  119. package/dist/types/src/ErrorMessageWrapper/ErrorMessageWrapper.test.d.ts +1 -0
  120. package/dist/types/src/Flex/Flex.test.d.ts +1 -0
  121. package/dist/types/src/Form/Form.test.d.ts +1 -0
  122. package/dist/types/src/Form/components/FormErrorBanner/FormErrorBanner.test.d.ts +1 -0
  123. package/dist/types/src/Form/components/FormMessage/FormMessage.test.d.ts +1 -0
  124. package/dist/types/src/Form/components/FormMessageBanner/FormMessageBanner.test.d.ts +1 -0
  125. package/dist/types/src/Form/components/FormSaveButton/FormSaveButton.test.d.ts +1 -0
  126. package/dist/types/src/Form/context/AtlantisFormContext.test.d.ts +1 -0
  127. package/dist/types/src/Form/hooks/useScrollToError/useScrollToError.test.d.ts +1 -0
  128. package/dist/types/src/FormField/FormField.test.d.ts +1 -0
  129. package/dist/types/src/FormatFile/FormatFile.test.d.ts +1 -0
  130. package/dist/types/src/FormatFile/FormatFileThumbnail.test.d.ts +1 -0
  131. package/dist/types/src/FormatFile/components/FormatFileBottomSheet/FormatFileBottomSheet.test.d.ts +1 -0
  132. package/dist/types/src/FormatFile/components/MediaView/MediaView.test.d.ts +1 -0
  133. package/dist/types/src/FormatFile/utils/parseFile.test.d.ts +1 -0
  134. package/dist/types/src/Glimmer/Glimmer.test.d.ts +1 -0
  135. package/dist/types/src/Heading/Heading.test.d.ts +1 -0
  136. package/dist/types/src/Icon/Icon.test.d.ts +1 -0
  137. package/dist/types/src/IconButton/IconButton.test.d.ts +1 -0
  138. package/dist/types/src/InputCurrency/InputCurrency.test.d.ts +1 -0
  139. package/dist/types/src/InputDate/InputDate.test.d.ts +1 -0
  140. package/dist/types/src/InputEmail/InputEmail.test.d.ts +1 -0
  141. package/dist/types/src/InputFieldWrapper/InputFieldWrapper.test.d.ts +1 -0
  142. package/dist/types/src/InputFieldWrapper/components/ClearAction/ClearAction.test.d.ts +1 -0
  143. package/dist/types/src/InputFieldWrapper/components/Prefix/Prefix.test.d.ts +1 -0
  144. package/dist/types/src/InputFieldWrapper/components/Suffix/Suffix.test.d.ts +1 -0
  145. package/dist/types/src/InputNumber/InputNumber.test.d.ts +1 -0
  146. package/dist/types/src/InputPassword/InputPassword.test.d.ts +1 -0
  147. package/dist/types/src/InputPressable/InputPressable.test.d.ts +1 -0
  148. package/dist/types/src/InputSearch/InputSearch.test.d.ts +1 -0
  149. package/dist/types/src/InputText/InputText.test.d.ts +1 -0
  150. package/dist/types/src/InputText/context/InputAccessoriesProvider.test.d.ts +1 -0
  151. package/dist/types/src/InputTime/InputTime.test.d.ts +1 -0
  152. package/dist/types/src/InputTime/utils/utils.test.d.ts +1 -0
  153. package/dist/types/src/ProgressBar/ProgressBar.test.d.ts +1 -0
  154. package/dist/types/src/Select/Select.test.d.ts +1 -0
  155. package/dist/types/src/Select/components/SelectDefaultPicker/SelectDefaultPicker.test.d.ts +1 -0
  156. package/dist/types/src/Select/components/SelectInternalPicker/SelectInternalPicker.test.d.ts +1 -0
  157. package/dist/types/src/StatusLabel/StatusLabel.test.d.ts +1 -0
  158. package/dist/types/src/Switch/Switch.test.d.ts +1 -0
  159. package/dist/types/src/Switch/components/BaseSwitch/BaseSwitch.test.d.ts +1 -0
  160. package/dist/types/src/Text/Text.test.d.ts +1 -0
  161. package/dist/types/src/TextList/TextList.test.d.ts +1 -0
  162. package/dist/types/src/ThumbnailList/ThumbnailList.test.d.ts +1 -0
  163. package/dist/types/src/Toast/Toast.test.d.ts +1 -0
  164. package/dist/types/src/Typography/Typography.test.d.ts +1 -0
  165. package/dist/types/src/hooks/useAtlantisI18n/useAtlantisI18n.test.d.ts +1 -0
  166. package/dist/types/src/utils/meta/meta.test.d.ts +1 -0
  167. package/package.json +19 -11
  168. package/src/Button/Button.test.tsx +6 -2
  169. package/src/ContentOverlay/hooks/useViewLayoutHeight.test.ts +3 -3
  170. package/src/Divider/Divider.stories.tsx +1 -1
  171. package/src/Flex/Flex.test.tsx +1 -1
  172. package/src/Form/Form.test.tsx +3 -1
  173. package/src/FormField/FormField.test.tsx +5 -1
  174. package/src/Heading/__snapshots__/Heading.test.tsx.snap +1 -1
  175. package/src/InputDate/InputDate.test.tsx +7 -1
  176. package/src/InputText/InputText.test.tsx +2 -1
  177. package/src/InputTime/InputTime.test.tsx +7 -1
  178. package/src/Select/Select.test.tsx +1 -1
  179. package/src/StatusLabel/__snapshots__/StatusLabel.test.tsx.snap +8 -8
  180. package/src/Text/__snapshots__/Text.test.tsx.snap +2 -2
  181. package/src/ThumbnailList/__snapshots__/ThumbnailList.test.tsx.snap +1 -1
  182. package/src/Typography/__snapshots__/Typography.test.tsx.snap +4 -4
@@ -0,0 +1,192 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import React from "react";
11
+ import { render, waitFor } from "@testing-library/react-native";
12
+ import { FormatFileThumbnail } from "./FormatFileThumbnail";
13
+ import { FILE_MOCK_FILE, FILE_MOCK_IMAGE, FILE_MOCK_VIDEO, FILE_UPLOAD_MOCK_FILE, FILE_UPLOAD_MOCK_IMAGE, } from "./components/_mocks/mockFiles";
14
+ import { StatusCode } from "./types";
15
+ import { tokens } from "../utils/design";
16
+ const mockCreateThumbnail = jest.fn(() => __awaiter(void 0, void 0, void 0, function* () {
17
+ return ({
18
+ thumbnail: "thumbnail",
19
+ error: false,
20
+ });
21
+ }));
22
+ afterEach(() => {
23
+ jest.clearAllMocks();
24
+ });
25
+ describe("FormatFileThumbnail", () => {
26
+ describe("rendering media files", () => {
27
+ it("renders an image file upload", () => {
28
+ const file = FILE_UPLOAD_MOCK_IMAGE({
29
+ progress: 1,
30
+ status: StatusCode.Completed,
31
+ });
32
+ const { getByTestId } = renderThumbnail(file);
33
+ expect(getByTestId("test-image")).toBeDefined();
34
+ });
35
+ it("renders an external image file", () => {
36
+ const { getByTestId } = renderThumbnail(FILE_MOCK_IMAGE);
37
+ expect(getByTestId("test-image")).toBeDefined();
38
+ });
39
+ it("creates a thumbnail for media files", () => {
40
+ renderThumbnail(FILE_UPLOAD_MOCK_IMAGE({
41
+ progress: 1,
42
+ status: StatusCode.Completed,
43
+ }));
44
+ expect(mockCreateThumbnail).toHaveBeenCalledTimes(1);
45
+ });
46
+ it("does not create a thumbnail for non-media files", () => {
47
+ renderThumbnail(FILE_UPLOAD_MOCK_FILE({
48
+ progress: 1,
49
+ status: StatusCode.Completed,
50
+ }));
51
+ expect(mockCreateThumbnail).not.toHaveBeenCalled();
52
+ });
53
+ });
54
+ describe("rendering non-media files", () => {
55
+ it("renders a file upload", () => {
56
+ const file = FILE_UPLOAD_MOCK_FILE({
57
+ progress: 1,
58
+ status: StatusCode.Completed,
59
+ });
60
+ const { getByTestId } = renderThumbnail(file);
61
+ expect(getByTestId("test-file")).toBeDefined();
62
+ });
63
+ it("renders an external file", () => {
64
+ const { getByTestId } = renderThumbnail(FILE_MOCK_FILE);
65
+ expect(getByTestId("test-file")).toBeDefined();
66
+ });
67
+ });
68
+ describe("upload progress", () => {
69
+ it("shows a progress bar when upload is in progress", () => {
70
+ const file = FILE_UPLOAD_MOCK_IMAGE({ progress: 0.5 });
71
+ const { getByTestId } = renderThumbnail(file);
72
+ expect(getByTestId("format-file-progress-bar-container")).toBeDefined();
73
+ });
74
+ it("shows a progress overlay with correct styles", () => {
75
+ const file = FILE_UPLOAD_MOCK_IMAGE({ progress: 0.5 });
76
+ const { getByTestId } = renderThumbnail(file);
77
+ const progressBarContainer = getByTestId("format-file-progress-bar-container");
78
+ const overlayStyles = progressBarContainer.props.style;
79
+ expect(overlayStyles).toEqual(expect.arrayContaining([expect.objectContaining({ height: "100%" })]));
80
+ expect(overlayStyles).toEqual(expect.arrayContaining([
81
+ expect.objectContaining({
82
+ backgroundColor: tokens["color-overlay--dimmed"],
83
+ }),
84
+ ]));
85
+ });
86
+ it("renders a progress bar that advances with upload percentage", () => __awaiter(void 0, void 0, void 0, function* () {
87
+ jest.useFakeTimers();
88
+ const file = FILE_UPLOAD_MOCK_IMAGE({ progress: 0.9 });
89
+ const { getByTestId } = renderThumbnail(file);
90
+ jest.advanceTimersByTime(500);
91
+ const innerProgressBar = yield waitFor(() => getByTestId("format-file-inner-progress-bar"));
92
+ const width = parseInt(innerProgressBar.props.style.width, 10);
93
+ expect(width).toBeGreaterThan(20);
94
+ jest.useRealTimers();
95
+ }));
96
+ });
97
+ describe("error state", () => {
98
+ it("renders an error icon for a failed image upload", () => {
99
+ const file = Object.assign({}, FILE_UPLOAD_MOCK_IMAGE({
100
+ progress: 0.9,
101
+ status: StatusCode.Failed,
102
+ }));
103
+ const { getByTestId } = renderThumbnail(file);
104
+ expect(getByTestId("format-file-error-icon")).toBeDefined();
105
+ });
106
+ it("renders a helpful error accessibility label", () => {
107
+ const file = Object.assign({}, FILE_UPLOAD_MOCK_IMAGE({
108
+ progress: 0.9,
109
+ status: StatusCode.Failed,
110
+ }));
111
+ const tree = renderThumbnail(file);
112
+ expect(tree.getByLabelText("Failed to upload.")).toBeDefined();
113
+ });
114
+ });
115
+ describe("accessibility", () => {
116
+ it("renders with a custom accessibility label", () => {
117
+ const file = FILE_UPLOAD_MOCK_IMAGE({
118
+ progress: 1,
119
+ status: StatusCode.Completed,
120
+ });
121
+ const { getByLabelText } = renderThumbnail(file);
122
+ expect(getByLabelText("Custom Label")).toBeDefined();
123
+ });
124
+ });
125
+ describe("size prop", () => {
126
+ it("applies size dimensions to the outer container", () => {
127
+ const file = FILE_UPLOAD_MOCK_IMAGE({
128
+ progress: 1,
129
+ status: StatusCode.Completed,
130
+ });
131
+ const { getByTestId } = render(React.createElement(FormatFileThumbnail, { file: file, accessibilityLabel: "Custom Label", createThumbnail: mockCreateThumbnail, size: { width: 100, height: 100 }, testID: "thumbnail-container" }));
132
+ const container = getByTestId("thumbnail-container");
133
+ const containerStyle = container.props.style;
134
+ expect(containerStyle).toEqual(expect.arrayContaining([
135
+ expect.objectContaining({ width: 100, height: 100 }),
136
+ ]));
137
+ });
138
+ it("applies container visual styles (border, radius, overflow)", () => {
139
+ const file = FILE_UPLOAD_MOCK_IMAGE({
140
+ progress: 1,
141
+ status: StatusCode.Completed,
142
+ });
143
+ const { getByTestId } = render(React.createElement(FormatFileThumbnail, { file: file, accessibilityLabel: "Custom Label", createThumbnail: mockCreateThumbnail, size: { width: 100, height: 100 }, testID: "thumbnail-container" }));
144
+ const container = getByTestId("thumbnail-container");
145
+ const containerStyle = container.props.style;
146
+ expect(containerStyle).toEqual(expect.arrayContaining([
147
+ expect.objectContaining({
148
+ borderWidth: tokens["border-base"],
149
+ borderColor: tokens["color-border"],
150
+ borderRadius: tokens["radius-base"],
151
+ overflow: "hidden",
152
+ }),
153
+ ]));
154
+ });
155
+ it("does not apply marginBottom from the base container styles", () => {
156
+ const file = FILE_UPLOAD_MOCK_IMAGE({
157
+ progress: 1,
158
+ status: StatusCode.Completed,
159
+ });
160
+ const { getByTestId } = render(React.createElement(FormatFileThumbnail, { file: file, accessibilityLabel: "Custom Label", createThumbnail: mockCreateThumbnail, size: { width: 100, height: 100 }, testID: "thumbnail-container" }));
161
+ const container = getByTestId("thumbnail-container");
162
+ const containerStyle = container.props.style;
163
+ // marginBottom is overridden to 0 since spacing is a layout concern
164
+ expect(containerStyle).toEqual(expect.arrayContaining([expect.objectContaining({ marginBottom: 0 })]));
165
+ });
166
+ });
167
+ describe("video files", () => {
168
+ it("shows the play icon for video files by default", () => {
169
+ const { getByTestId } = renderThumbnail(FILE_MOCK_VIDEO);
170
+ expect(getByTestId("video")).toBeDefined();
171
+ });
172
+ it("hides the play icon when showFileTypeIndicator is false", () => {
173
+ const { queryByTestId } = render(React.createElement(FormatFileThumbnail, { file: FILE_MOCK_VIDEO, accessibilityLabel: "Custom Label", createThumbnail: mockCreateThumbnail, showFileTypeIndicator: false, size: { width: 100, height: 100 } }));
174
+ expect(queryByTestId("video")).toBeNull();
175
+ });
176
+ });
177
+ describe("does NOT render BottomSheet or TouchableOpacity", () => {
178
+ it("does not render a bottom sheet", () => {
179
+ const file = FILE_UPLOAD_MOCK_IMAGE({
180
+ progress: 1,
181
+ status: StatusCode.Completed,
182
+ });
183
+ const { queryByLabelText } = renderThumbnail(file);
184
+ // BottomSheet options should not exist
185
+ expect(queryByLabelText("Remove image")).toBeNull();
186
+ expect(queryByLabelText("Preview image")).toBeNull();
187
+ });
188
+ });
189
+ });
190
+ function renderThumbnail(file) {
191
+ return render(React.createElement(FormatFileThumbnail, { file: file, accessibilityLabel: "Custom Label", createThumbnail: mockCreateThumbnail, size: { width: 100, height: 100 } }));
192
+ }
@@ -0,0 +1,74 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import React, { createRef } from "react";
11
+ import { fireEvent, render } from "@testing-library/react-native";
12
+ import { act } from "react-test-renderer";
13
+ import { FormatFileBottomSheet } from "./FormatFileBottomSheet";
14
+ let Platform;
15
+ const onRemove = jest.fn();
16
+ const onPreview = jest.fn();
17
+ const bottomSheetRef = createRef();
18
+ beforeEach(() => {
19
+ Platform = require("react-native").Platform;
20
+ });
21
+ const renderBottomSheet = (bottomSheetOptionsSuffix) => {
22
+ return render(React.createElement(FormatFileBottomSheet, { onPreviewPress: onPreview, onRemovePress: onRemove, bottomSheetRef: bottomSheetRef, bottomSheetOptionsSuffix: bottomSheetOptionsSuffix }));
23
+ };
24
+ const basicRenderTestWithValue = () => {
25
+ describe.each([["image"], ["receipt"], ["file"], ["video"]])("when FormatFileBottomSheet for %s is opened", bottomSheetOptionsSuffix => {
26
+ const previewLabel = `Preview ${bottomSheetOptionsSuffix}`;
27
+ const removeLabel = `Remove ${bottomSheetOptionsSuffix}`;
28
+ let tree;
29
+ beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
30
+ tree = renderBottomSheet(bottomSheetOptionsSuffix);
31
+ yield act(() => __awaiter(void 0, void 0, void 0, function* () {
32
+ var _a;
33
+ (_a = bottomSheetRef.current) === null || _a === void 0 ? void 0 : _a.open();
34
+ }));
35
+ }));
36
+ afterEach(() => {
37
+ jest.clearAllMocks();
38
+ });
39
+ describe("onPreviewPress", () => {
40
+ it("renders the preview option", () => __awaiter(void 0, void 0, void 0, function* () {
41
+ const { findByLabelText } = tree;
42
+ expect(yield findByLabelText(previewLabel)).toBeDefined();
43
+ }));
44
+ it("is called when pressed", () => __awaiter(void 0, void 0, void 0, function* () {
45
+ const { findByLabelText } = tree;
46
+ fireEvent.press(yield findByLabelText(previewLabel));
47
+ expect(onPreview).toHaveBeenCalledTimes(1);
48
+ }));
49
+ });
50
+ describe("onRemovePress", () => {
51
+ it("renders the remove option", () => __awaiter(void 0, void 0, void 0, function* () {
52
+ const { findByLabelText } = tree;
53
+ expect(yield findByLabelText(removeLabel)).toBeDefined();
54
+ }));
55
+ it("is called when pressed", () => __awaiter(void 0, void 0, void 0, function* () {
56
+ const { findByLabelText } = tree;
57
+ fireEvent.press(yield findByLabelText(removeLabel));
58
+ expect(onRemove).toHaveBeenCalledTimes(1);
59
+ }));
60
+ });
61
+ });
62
+ };
63
+ describe("ios", () => {
64
+ beforeEach(() => {
65
+ Platform.OS = "ios";
66
+ });
67
+ basicRenderTestWithValue();
68
+ });
69
+ describe("android", () => {
70
+ beforeEach(() => {
71
+ Platform.OS = "android";
72
+ });
73
+ basicRenderTestWithValue();
74
+ });
@@ -0,0 +1,202 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import React from "react";
11
+ import { fireEvent, render, waitFor } from "@testing-library/react-native";
12
+ import { MediaView } from "./MediaView";
13
+ import { StatusCode } from "../../types";
14
+ import { AtlantisFormatFileContext } from "../../context/FormatFileContext";
15
+ jest.mock("../../../hooks/useAtlantisI18n", () => ({
16
+ useAtlantisI18n: () => ({ t: (key) => key }),
17
+ }));
18
+ describe("MediaView", () => {
19
+ const mockFile = {
20
+ showPreview: true,
21
+ source: "https://example.com/image1.jpg",
22
+ thumbnailUrl: undefined,
23
+ name: "test.jpg",
24
+ size: 1024,
25
+ external: false,
26
+ progress: 0,
27
+ status: StatusCode.Completed,
28
+ error: false,
29
+ type: "image/jpeg",
30
+ isMedia: true,
31
+ showFileTypeIndicator: false,
32
+ };
33
+ const defaultProps = {
34
+ accessibilityLabel: "Test image",
35
+ showOverlay: false,
36
+ showError: false,
37
+ file: mockFile,
38
+ styleInGrid: false,
39
+ onUploadComplete: jest.fn(),
40
+ };
41
+ const mockContextValue = {
42
+ useCreateThumbnail: () => ({ thumbnail: undefined, error: false }),
43
+ };
44
+ const renderWithContext = (props = defaultProps) => {
45
+ return render(React.createElement(AtlantisFormatFileContext.Provider, { value: mockContextValue },
46
+ React.createElement(MediaView, Object.assign({}, props))));
47
+ };
48
+ describe("Normal loading flow", () => {
49
+ it("shows loading indicator when onLoadStart fires", () => {
50
+ const { getByTestId, queryByTestId } = renderWithContext();
51
+ const image = getByTestId("test-image");
52
+ expect(queryByTestId("ActivityIndicator")).toBeNull();
53
+ fireEvent(image, "loadStart");
54
+ expect(queryByTestId("ActivityIndicator")).toBeTruthy();
55
+ });
56
+ it("hides loading indicator when onLoadEnd fires", () => {
57
+ const { getByTestId, queryByTestId } = renderWithContext();
58
+ const image = getByTestId("test-image");
59
+ fireEvent(image, "loadStart");
60
+ expect(queryByTestId("ActivityIndicator")).toBeTruthy();
61
+ fireEvent(image, "loadEnd");
62
+ expect(queryByTestId("ActivityIndicator")).toBeNull();
63
+ });
64
+ });
65
+ describe("Race condition handling (cached images)", () => {
66
+ it("does not get stuck loading when onLoadEnd fires before onLoadStart", () => {
67
+ const { getByTestId, queryByTestId } = renderWithContext();
68
+ const image = getByTestId("test-image");
69
+ // Simulate cached image: LoadEnd fires BEFORE LoadStart
70
+ fireEvent(image, "loadEnd");
71
+ expect(queryByTestId("ActivityIndicator")).toBeNull();
72
+ fireEvent(image, "loadStart");
73
+ expect(queryByTestId("ActivityIndicator")).toBeNull();
74
+ });
75
+ it("does not show infinite spinner when load events fire out of order", () => {
76
+ const { getByTestId, queryByTestId } = renderWithContext();
77
+ const image = getByTestId("test-image");
78
+ // Race condition scenario
79
+ fireEvent(image, "loadEnd");
80
+ fireEvent(image, "loadStart");
81
+ fireEvent(image, "loadEnd");
82
+ expect(queryByTestId("ActivityIndicator")).toBeNull();
83
+ });
84
+ });
85
+ describe("URI changes", () => {
86
+ it("shows loading indicator when URI changes to a new image", () => __awaiter(void 0, void 0, void 0, function* () {
87
+ const { getByTestId, queryByTestId, rerender } = renderWithContext();
88
+ const image = getByTestId("test-image");
89
+ // First image: simulate cached load (LoadEnd before LoadStart)
90
+ fireEvent(image, "loadEnd");
91
+ fireEvent(image, "loadStart");
92
+ expect(queryByTestId("ActivityIndicator")).toBeNull();
93
+ // Change URI to a new image
94
+ const newFile = Object.assign(Object.assign({}, mockFile), { source: "https://example.com/image2.jpg" });
95
+ rerender(React.createElement(AtlantisFormatFileContext.Provider, { value: mockContextValue },
96
+ React.createElement(MediaView, Object.assign({}, defaultProps, { file: newFile }))));
97
+ yield waitFor(() => {
98
+ const updatedImage = getByTestId("test-image");
99
+ // New image starts loading
100
+ fireEvent(updatedImage, "loadStart");
101
+ expect(queryByTestId("ActivityIndicator")).toBeTruthy();
102
+ });
103
+ }));
104
+ it("shows loading indicator on second URI change", () => __awaiter(void 0, void 0, void 0, function* () {
105
+ const { getByTestId, queryByTestId, rerender } = renderWithContext();
106
+ const image1 = getByTestId("test-image");
107
+ fireEvent(image1, "loadStart");
108
+ fireEvent(image1, "loadEnd");
109
+ expect(queryByTestId("ActivityIndicator")).toBeNull();
110
+ const file2 = Object.assign(Object.assign({}, mockFile), { source: "https://example.com/image2.jpg" });
111
+ rerender(React.createElement(AtlantisFormatFileContext.Provider, { value: mockContextValue },
112
+ React.createElement(MediaView, Object.assign({}, defaultProps, { file: file2 }))));
113
+ yield waitFor(() => {
114
+ const image2 = getByTestId("test-image");
115
+ fireEvent(image2, "loadStart");
116
+ expect(queryByTestId("ActivityIndicator")).toBeTruthy();
117
+ });
118
+ }));
119
+ it("shows loading indicator on third URI change", () => __awaiter(void 0, void 0, void 0, function* () {
120
+ const { getByTestId, queryByTestId, rerender } = renderWithContext();
121
+ const image1 = getByTestId("test-image");
122
+ fireEvent(image1, "loadStart");
123
+ fireEvent(image1, "loadEnd");
124
+ const file2 = Object.assign(Object.assign({}, mockFile), { source: "https://example.com/image2.jpg" });
125
+ rerender(React.createElement(AtlantisFormatFileContext.Provider, { value: mockContextValue },
126
+ React.createElement(MediaView, Object.assign({}, defaultProps, { file: file2 }))));
127
+ const image2 = getByTestId("test-image");
128
+ fireEvent(image2, "loadEnd");
129
+ const file3 = Object.assign(Object.assign({}, mockFile), { source: "https://example.com/image3.jpg" });
130
+ rerender(React.createElement(AtlantisFormatFileContext.Provider, { value: mockContextValue },
131
+ React.createElement(MediaView, Object.assign({}, defaultProps, { file: file3 }))));
132
+ yield waitFor(() => {
133
+ const image3 = getByTestId("test-image");
134
+ fireEvent(image3, "loadStart");
135
+ expect(queryByTestId("ActivityIndicator")).toBeTruthy();
136
+ });
137
+ }));
138
+ it("handles URI change from cached to uncached image correctly", () => __awaiter(void 0, void 0, void 0, function* () {
139
+ const { getByTestId, queryByTestId, rerender } = renderWithContext();
140
+ // First image: cached (LoadEnd before LoadStart)
141
+ const image1 = getByTestId("test-image");
142
+ fireEvent(image1, "loadEnd");
143
+ fireEvent(image1, "loadStart");
144
+ expect(queryByTestId("ActivityIndicator")).toBeNull();
145
+ // Second image: uncached (normal LoadStart then LoadEnd)
146
+ const file2 = Object.assign(Object.assign({}, mockFile), { source: "https://example.com/image2.jpg" });
147
+ rerender(React.createElement(AtlantisFormatFileContext.Provider, { value: mockContextValue },
148
+ React.createElement(MediaView, Object.assign({}, defaultProps, { file: file2 }))));
149
+ yield waitFor(() => {
150
+ const image2 = getByTestId("test-image");
151
+ fireEvent(image2, "loadStart");
152
+ expect(queryByTestId("ActivityIndicator")).toBeTruthy();
153
+ fireEvent(image2, "loadEnd");
154
+ expect(queryByTestId("ActivityIndicator")).toBeNull();
155
+ });
156
+ }));
157
+ });
158
+ describe("thumbnailUrl changes", () => {
159
+ it("shows loading indicator when thumbnailUrl changes", () => __awaiter(void 0, void 0, void 0, function* () {
160
+ const { getByTestId, queryByTestId, rerender } = renderWithContext();
161
+ // First load completes
162
+ const image1 = getByTestId("test-image");
163
+ fireEvent(image1, "loadStart");
164
+ fireEvent(image1, "loadEnd");
165
+ expect(queryByTestId("ActivityIndicator")).toBeNull();
166
+ // Thumbnail URL changes (common when thumbnail generation completes)
167
+ const fileWithThumbnail = Object.assign(Object.assign({}, mockFile), { thumbnailUrl: "https://example.com/thumbnail.jpg" });
168
+ rerender(React.createElement(AtlantisFormatFileContext.Provider, { value: mockContextValue },
169
+ React.createElement(MediaView, Object.assign({}, defaultProps, { file: fileWithThumbnail }))));
170
+ yield waitFor(() => {
171
+ const image2 = getByTestId("test-image");
172
+ fireEvent(image2, "loadStart");
173
+ expect(queryByTestId("ActivityIndicator")).toBeTruthy();
174
+ });
175
+ }));
176
+ });
177
+ describe("Context thumbnail changes", () => {
178
+ it("shows loading indicator when context thumbnail changes", () => __awaiter(void 0, void 0, void 0, function* () {
179
+ const mockContextWithThumbnail = {
180
+ useCreateThumbnail: () => ({
181
+ thumbnail: "https://example.com/context-thumbnail.jpg",
182
+ error: false,
183
+ }),
184
+ };
185
+ const { getByTestId, queryByTestId, rerender } = render(React.createElement(AtlantisFormatFileContext.Provider, { value: mockContextValue },
186
+ React.createElement(MediaView, Object.assign({}, defaultProps))));
187
+ // First load completes
188
+ const image1 = getByTestId("test-image");
189
+ fireEvent(image1, "loadStart");
190
+ fireEvent(image1, "loadEnd");
191
+ expect(queryByTestId("ActivityIndicator")).toBeNull();
192
+ // Context provides a new thumbnail
193
+ rerender(React.createElement(AtlantisFormatFileContext.Provider, { value: mockContextWithThumbnail },
194
+ React.createElement(MediaView, Object.assign({}, defaultProps))));
195
+ yield waitFor(() => {
196
+ const image2 = getByTestId("test-image");
197
+ fireEvent(image2, "loadStart");
198
+ expect(queryByTestId("ActivityIndicator")).toBeTruthy();
199
+ });
200
+ }));
201
+ });
202
+ });
@@ -0,0 +1,188 @@
1
+ /* eslint-disable max-statements */
2
+ import { isMediaFile, parseFile } from "./parseFile";
3
+ import { StatusCode } from "../types";
4
+ const makeFileUpload = (overrides = {}) => (Object.assign({ uuid: "uuid-123", key: "path/test.txt", name: "test.txt", type: "application/txt", size: 1024, progress: 0.5, status: StatusCode.InProgress, sourcePath: "path/test.txt", batchContext: { batchId: "batch-1", batchCount: 1 }, cancel: jest.fn() }, overrides));
5
+ const makeFile = (overrides = {}) => (Object.assign({ fileName: "document.txt", fileSize: 2048, contentType: "text/plain", url: "https://example.com/file.txt", thumbnailUrl: "https://example.com/thumb.txt" }, overrides));
6
+ describe("isMediaFile", () => {
7
+ it.each `
8
+ fileType | expected
9
+ ${"image/png"} | ${true}
10
+ ${"image/jpeg"} | ${true}
11
+ ${"video/mp4"} | ${true}
12
+ ${"video/quicktime"} | ${true}
13
+ ${"application/pdf"} | ${false}
14
+ ${"text/plain"} | ${false}
15
+ ${""} | ${false}
16
+ `("returns $expected for '$fileType'", ({ fileType, expected }) => {
17
+ expect(isMediaFile(fileType)).toBe(expected);
18
+ });
19
+ });
20
+ describe("parseFile", () => {
21
+ describe("with a FileUpload (local upload)", () => {
22
+ it("maps basic fields", () => {
23
+ const file = makeFileUpload({
24
+ sourcePath: "local/image.png",
25
+ name: "image.png",
26
+ size: 5000,
27
+ progress: 0.7,
28
+ status: StatusCode.InProgress,
29
+ type: "image/png",
30
+ });
31
+ const result = parseFile(file, true);
32
+ expect(result).toMatchObject({
33
+ source: "local/image.png",
34
+ name: "image.png",
35
+ size: 5000,
36
+ progress: 0.7,
37
+ status: StatusCode.InProgress,
38
+ external: false,
39
+ showFileTypeIndicator: true,
40
+ });
41
+ });
42
+ it("sets error to true when status is Failed", () => {
43
+ const file = makeFileUpload({ status: StatusCode.Failed });
44
+ const result = parseFile(file, true);
45
+ expect(result.error).toBe(true);
46
+ });
47
+ it("sets error to false when status is not Failed", () => {
48
+ const file = makeFileUpload({ status: StatusCode.Completed });
49
+ const result = parseFile(file, true);
50
+ expect(result.error).toBe(false);
51
+ });
52
+ it("uses file.key as type fallback when type is empty", () => {
53
+ const file = makeFileUpload({ type: "", key: "path/fallback.png" });
54
+ const result = parseFile(file, true);
55
+ expect(result.type).toBe("path/fallback.png");
56
+ });
57
+ it("identifies an image upload as media", () => {
58
+ const file = makeFileUpload({ type: "image/png" });
59
+ const result = parseFile(file, true);
60
+ expect(result.isMedia).toBe(true);
61
+ });
62
+ it("identifies a video upload as media", () => {
63
+ const file = makeFileUpload({ type: "video/mp4" });
64
+ const result = parseFile(file, true);
65
+ expect(result.isMedia).toBe(true);
66
+ });
67
+ it("identifies a text file upload as non-media", () => {
68
+ const file = makeFileUpload({ type: "application/txt" });
69
+ const result = parseFile(file, true);
70
+ expect(result.isMedia).toBe(false);
71
+ });
72
+ it("sets showPreview for media files", () => {
73
+ const file = makeFileUpload({ type: "image/jpeg" });
74
+ const result = parseFile(file, true);
75
+ expect(result.showPreview).toBe(true);
76
+ });
77
+ it("sets showPreview for accepted non-media extensions (pdf)", () => {
78
+ const file = makeFileUpload({ type: "file/pdf" });
79
+ const result = parseFile(file, true);
80
+ expect(result.showPreview).toBe(true);
81
+ });
82
+ it("sets showPreview for accepted non-media extensions (docx)", () => {
83
+ const file = makeFileUpload({ type: "application/docx" });
84
+ const result = parseFile(file, true);
85
+ expect(result.showPreview).toBe(true);
86
+ });
87
+ it("does not set showPreview for unrecognized file types", () => {
88
+ const file = makeFileUpload({ type: "application/txt" });
89
+ const result = parseFile(file, true);
90
+ expect(result.showPreview).toBe(false);
91
+ });
92
+ it("passes showFileTypeIndicator through", () => {
93
+ const file = makeFileUpload();
94
+ expect(parseFile(file, true).showFileTypeIndicator).toBe(true);
95
+ expect(parseFile(file, false).showFileTypeIndicator).toBe(false);
96
+ });
97
+ });
98
+ describe("with a File (external/remote file)", () => {
99
+ it("maps basic fields", () => {
100
+ const file = makeFile({
101
+ url: "https://example.com/photo.jpg",
102
+ thumbnailUrl: "https://example.com/thumb.jpg",
103
+ fileName: "photo.jpg",
104
+ fileSize: 3000,
105
+ contentType: "image/jpeg",
106
+ });
107
+ const result = parseFile(file, true);
108
+ expect(result).toMatchObject({
109
+ source: "https://example.com/photo.jpg",
110
+ thumbnailUrl: "https://example.com/thumb.jpg",
111
+ name: "photo.jpg",
112
+ size: 3000,
113
+ external: true,
114
+ progress: 1,
115
+ status: StatusCode.Completed,
116
+ error: false,
117
+ showFileTypeIndicator: true,
118
+ });
119
+ });
120
+ it("identifies an image file as media", () => {
121
+ const file = makeFile({ contentType: "image/png" });
122
+ const result = parseFile(file, true);
123
+ expect(result.isMedia).toBe(true);
124
+ });
125
+ it("identifies a non-media file as non-media", () => {
126
+ const file = makeFile({ contentType: "text/plain" });
127
+ const result = parseFile(file, true);
128
+ expect(result.isMedia).toBe(false);
129
+ });
130
+ it("detects video content type from file extension (.mp4)", () => {
131
+ const file = makeFile({ fileName: "clip.mp4", contentType: undefined });
132
+ const result = parseFile(file, true);
133
+ expect(result.type).toBe("video");
134
+ expect(result.isMedia).toBe(true);
135
+ });
136
+ it("detects video content type from file extension (.mov)", () => {
137
+ const file = makeFile({ fileName: "clip.mov", contentType: undefined });
138
+ const result = parseFile(file, true);
139
+ expect(result.type).toBe("video");
140
+ });
141
+ it("detects video content type from file extension (.hevc)", () => {
142
+ const file = makeFile({ fileName: "clip.hevc", contentType: undefined });
143
+ const result = parseFile(file, true);
144
+ expect(result.type).toBe("video");
145
+ });
146
+ it("detects video content type from file extension (.3gpp)", () => {
147
+ const file = makeFile({ fileName: "clip.3gpp", contentType: undefined });
148
+ const result = parseFile(file, true);
149
+ expect(result.type).toBe("video");
150
+ });
151
+ it("falls back to contentType when extension is not a video", () => {
152
+ const file = makeFile({
153
+ fileName: "report.pdf",
154
+ contentType: "application/pdf",
155
+ });
156
+ const result = parseFile(file, true);
157
+ expect(result.type).toBe("application/pdf");
158
+ });
159
+ it("falls back to 'unknown' when both fileName and contentType are missing", () => {
160
+ const file = makeFile({
161
+ fileName: undefined,
162
+ contentType: undefined,
163
+ });
164
+ const result = parseFile(file, true);
165
+ expect(result.type).toBe("unknown");
166
+ });
167
+ it("sets showPreview for media files", () => {
168
+ const file = makeFile({ contentType: "image/jpeg" });
169
+ const result = parseFile(file, true);
170
+ expect(result.showPreview).toBe(true);
171
+ });
172
+ it("sets showPreview for accepted extension types", () => {
173
+ const file = makeFile({ contentType: "application/pdf" });
174
+ const result = parseFile(file, true);
175
+ expect(result.showPreview).toBe(true);
176
+ });
177
+ it("does not set showPreview for unrecognized types", () => {
178
+ const file = makeFile({ contentType: "text/plain" });
179
+ const result = parseFile(file, true);
180
+ expect(result.showPreview).toBe(false);
181
+ });
182
+ it("handles video extension detection case-insensitively", () => {
183
+ const file = makeFile({ fileName: "CLIP.MP4", contentType: undefined });
184
+ const result = parseFile(file, true);
185
+ expect(result.type).toBe("video");
186
+ });
187
+ });
188
+ });