@oneuptime/common 9.2.20 → 9.2.22

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 (94) hide show
  1. package/Server/Services/AIService.ts +1 -1
  2. package/Tests/Server/API/BaseAPI.test.ts +9 -4
  3. package/Tests/Server/Middleware/ProjectAuthorization.test.ts +133 -162
  4. package/Tests/Server/Services/ProbeService.test.ts +91 -784
  5. package/Tests/Server/Services/ScheduledMaintenanceService.test.ts +131 -112
  6. package/Tests/Server/Services/TeamMemberService.test.ts +87 -1343
  7. package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +18 -9
  8. package/Tests/Server/Utils/Cookie.test.ts +10 -2
  9. package/Tests/Types/HashedString.test.ts +52 -8
  10. package/Tests/UI/Components/404.test.tsx +10 -15
  11. package/Tests/UI/Components/Breadcrumbs.test.tsx +6 -2
  12. package/Tests/UI/Components/Button.test.tsx +12 -12
  13. package/Tests/UI/Components/Card.test.tsx +4 -2
  14. package/Tests/UI/Components/ConfirmModal.test.tsx +1 -1
  15. package/Tests/UI/Components/Dropdown.test.tsx +37 -4
  16. package/Tests/UI/Components/DuplicateModel.test.tsx +49 -45
  17. package/Tests/UI/Components/FilePicker.test.tsx +258 -178
  18. package/Tests/UI/Components/List.test.tsx +3 -1
  19. package/Tests/UI/Components/MarkdownEditor.test.tsx +6 -5
  20. package/Tests/UI/Components/MasterPage.test.tsx +1 -1
  21. package/Tests/UI/Components/Modal.test.tsx +5 -5
  22. package/Tests/UI/Components/NavBar.test.tsx +14 -1
  23. package/Tests/UI/Components/OrderedStatesList.test.tsx +1 -1
  24. package/Tests/UI/Components/Pagination.test.tsx +6 -2
  25. package/Tests/Utils/API.test.ts +133 -11
  26. package/Tests/__mocks__/azure.js +2 -0
  27. package/Tests/__mocks__/botbuilder-stdlib.js +2 -0
  28. package/Tests/__mocks__/botbuilder.js +10 -0
  29. package/Tests/__mocks__/locter.js +5 -0
  30. package/Tests/__mocks__/otpauth.js +30 -0
  31. package/Tests/__mocks__/simplewebauthn.js +34 -0
  32. package/Tests/__mocks__/styleMock.js +1 -0
  33. package/Tests/__mocks__/uuid.js +31 -0
  34. package/Tests/__mocks__/yaml.js +11 -0
  35. package/Tests/jest.setup.ts +14 -0
  36. package/UI/Components/AI/AITemplates.ts +226 -0
  37. package/UI/Components/AI/GenerateFromAIModal.tsx +21 -270
  38. package/build/dist/Server/Services/AIService.js +1 -1
  39. package/build/dist/Server/Services/AIService.js.map +1 -1
  40. package/build/dist/Tests/Server/API/BaseAPI.test.js +7 -2
  41. package/build/dist/Tests/Server/API/BaseAPI.test.js.map +1 -1
  42. package/build/dist/Tests/Server/Middleware/ProjectAuthorization.test.js +89 -101
  43. package/build/dist/Tests/Server/Middleware/ProjectAuthorization.test.js.map +1 -1
  44. package/build/dist/Tests/Server/Services/ProbeService.test.js +95 -687
  45. package/build/dist/Tests/Server/Services/ProbeService.test.js.map +1 -1
  46. package/build/dist/Tests/Server/Services/ScheduledMaintenanceService.test.js +108 -89
  47. package/build/dist/Tests/Server/Services/ScheduledMaintenanceService.test.js.map +1 -1
  48. package/build/dist/Tests/Server/Services/TeamMemberService.test.js +85 -924
  49. package/build/dist/Tests/Server/Services/TeamMemberService.test.js.map +1 -1
  50. package/build/dist/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.js +14 -9
  51. package/build/dist/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.js.map +1 -1
  52. package/build/dist/Tests/Server/Utils/Cookie.test.js +10 -4
  53. package/build/dist/Tests/Server/Utils/Cookie.test.js.map +1 -1
  54. package/build/dist/Tests/Types/HashedString.test.js +39 -6
  55. package/build/dist/Tests/Types/HashedString.test.js.map +1 -1
  56. package/build/dist/Tests/UI/Components/404.test.js +10 -10
  57. package/build/dist/Tests/UI/Components/404.test.js.map +1 -1
  58. package/build/dist/Tests/UI/Components/Breadcrumbs.test.js +6 -2
  59. package/build/dist/Tests/UI/Components/Breadcrumbs.test.js.map +1 -1
  60. package/build/dist/Tests/UI/Components/Button.test.js +12 -12
  61. package/build/dist/Tests/UI/Components/Card.test.js +4 -2
  62. package/build/dist/Tests/UI/Components/Card.test.js.map +1 -1
  63. package/build/dist/Tests/UI/Components/ConfirmModal.test.js +1 -1
  64. package/build/dist/Tests/UI/Components/ConfirmModal.test.js.map +1 -1
  65. package/build/dist/Tests/UI/Components/Dropdown.test.js +19 -3
  66. package/build/dist/Tests/UI/Components/Dropdown.test.js.map +1 -1
  67. package/build/dist/Tests/UI/Components/DuplicateModel.test.js +46 -41
  68. package/build/dist/Tests/UI/Components/DuplicateModel.test.js.map +1 -1
  69. package/build/dist/Tests/UI/Components/FilePicker.test.js +210 -117
  70. package/build/dist/Tests/UI/Components/FilePicker.test.js.map +1 -1
  71. package/build/dist/Tests/UI/Components/List.test.js +3 -1
  72. package/build/dist/Tests/UI/Components/List.test.js.map +1 -1
  73. package/build/dist/Tests/UI/Components/MarkdownEditor.test.js +6 -5
  74. package/build/dist/Tests/UI/Components/MarkdownEditor.test.js.map +1 -1
  75. package/build/dist/Tests/UI/Components/MasterPage.test.js +1 -1
  76. package/build/dist/Tests/UI/Components/MasterPage.test.js.map +1 -1
  77. package/build/dist/Tests/UI/Components/Modal.test.js +5 -5
  78. package/build/dist/Tests/UI/Components/Modal.test.js.map +1 -1
  79. package/build/dist/Tests/UI/Components/NavBar.test.js +13 -1
  80. package/build/dist/Tests/UI/Components/NavBar.test.js.map +1 -1
  81. package/build/dist/Tests/UI/Components/OrderedStatesList.test.js +1 -1
  82. package/build/dist/Tests/UI/Components/OrderedStatesList.test.js.map +1 -1
  83. package/build/dist/Tests/UI/Components/Pagination.test.js +6 -2
  84. package/build/dist/Tests/UI/Components/Pagination.test.js.map +1 -1
  85. package/build/dist/Tests/Utils/API.test.js +100 -9
  86. package/build/dist/Tests/Utils/API.test.js.map +1 -1
  87. package/build/dist/Tests/jest.setup.js +13 -0
  88. package/build/dist/Tests/jest.setup.js.map +1 -0
  89. package/build/dist/UI/Components/AI/AITemplates.js +218 -0
  90. package/build/dist/UI/Components/AI/AITemplates.js.map +1 -0
  91. package/build/dist/UI/Components/AI/GenerateFromAIModal.js +5 -238
  92. package/build/dist/UI/Components/AI/GenerateFromAIModal.js.map +1 -1
  93. package/jest.config.json +18 -1
  94. package/package.json +1 -1
@@ -2,15 +2,7 @@ import FilePicker from "../../../UI/Components/FilePicker/FilePicker";
2
2
  import ModelAPI from "../../../UI/Utils/ModelAPI/ModelAPI";
3
3
  import { describe, expect, beforeEach, jest } from "@jest/globals";
4
4
  import "@testing-library/jest-dom/extend-expect";
5
- import {
6
- fireEvent,
7
- queryAllByAttribute,
8
- queryByAttribute,
9
- queryByTestId,
10
- render,
11
- screen,
12
- waitFor,
13
- } from "@testing-library/react";
5
+ import { fireEvent, render, screen, waitFor } from "@testing-library/react";
14
6
  import HTTPResponse from "../../../Types/API/HTTPResponse";
15
7
  import MimeType from "../../../Types/File/MimeType";
16
8
  import ObjectID from "../../../Types/ObjectID";
@@ -22,6 +14,8 @@ import Faker from "../../../Utils/Faker";
22
14
 
23
15
  const mockOnChange: MockFunction = getJestMockFunction();
24
16
  const mockOnBlur: MockFunction = getJestMockFunction();
17
+ const mockOnFocus: MockFunction = getJestMockFunction();
18
+ const mockOnClick: MockFunction = getJestMockFunction();
25
19
 
26
20
  jest.mock("../../../UI/Utils/ModelAPI/ModelAPI", () => {
27
21
  return {
@@ -39,6 +33,11 @@ interface DefaultProps {
39
33
  value?: FileModel[] | undefined;
40
34
  isMultiFilePicker?: boolean;
41
35
  readOnly?: boolean;
36
+ placeholder?: string;
37
+ onFocus?: () => void;
38
+ onClick?: () => void;
39
+ error?: string;
40
+ dataTestId?: string;
42
41
  }
43
42
 
44
43
  interface DataTransfer {
@@ -55,25 +54,22 @@ type MockCreateResponseFunction = (
55
54
  const mockCreateResponse: MockCreateResponseFunction = async (
56
55
  file: File,
57
56
  ): Promise<HTTPResponse<FileModel>> => {
58
- return new HTTPResponse(
59
- 200,
60
- {
61
- file: Buffer.from(await file.arrayBuffer()),
62
- name: file.name,
63
- type: file.type as MimeType,
64
- slug: file.name,
65
- isPublic: true,
66
- },
67
- {},
68
- );
57
+ const fileModel: FileModel = new FileModel();
58
+ fileModel.file = Buffer.from(await file.arrayBuffer());
59
+ fileModel.name = file.name;
60
+ fileModel.fileType = file.type as MimeType;
61
+ fileModel.slug = file.name;
62
+ fileModel.isPublic = true;
63
+ return new HTTPResponse(200, fileModel as any, {});
69
64
  };
70
65
 
71
- type MockFileModelFunction = (file: File) => Promise<FileModel>;
66
+ type MockFileModelFunction = (file: File, id?: string) => Promise<FileModel>;
72
67
 
73
68
  const mockFileModel: MockFileModelFunction = async (
74
69
  file: File,
70
+ id?: string,
75
71
  ): Promise<FileModel> => {
76
- const fileModel: FileModel = new FileModel(new ObjectID("123"));
72
+ const fileModel: FileModel = new FileModel(new ObjectID(id || "123"));
77
73
  fileModel.name = file.name;
78
74
  fileModel.fileType = file.type as MimeType;
79
75
  fileModel.slug = file.name;
@@ -82,17 +78,16 @@ const mockFileModel: MockFileModelFunction = async (
82
78
  return fileModel;
83
79
  };
84
80
 
85
- type MockFileFunction = () => File;
81
+ type MockFileFunction = (name?: string) => File;
86
82
 
87
- const mockFile: MockFileFunction = (): File => {
83
+ const mockFile: MockFileFunction = (name?: string): File => {
88
84
  const mockArrayBuffer: MockFunction = getJestMockFunction();
89
85
  mockArrayBuffer.mockResolvedValue(new ArrayBuffer(10)); // Mocked array buffer of size 10
90
86
 
91
- const file: File = new File(
92
- [Faker.generateRandomString()],
93
- Faker.generateRandomString() + ".png",
94
- { type: MimeType.png },
95
- );
87
+ const fileName: string = name || Faker.generateRandomString() + ".png";
88
+ const file: File = new File([Faker.generateRandomString()], fileName, {
89
+ type: MimeType.png,
90
+ });
96
91
  file.arrayBuffer = mockArrayBuffer;
97
92
  return file;
98
93
  };
@@ -106,89 +101,115 @@ const defaultProps: DefaultProps = {
106
101
  };
107
102
 
108
103
  describe("FilePicker", () => {
109
- const MOCK_FILE_URL: string = "https://mock-file-url";
110
-
111
- beforeAll(() => {
112
- global.URL.createObjectURL = jest.fn(() => {
113
- return MOCK_FILE_URL;
114
- });
115
- });
116
-
117
- afterAll(() => {
118
- (
119
- global.URL.createObjectURL as jest.MockedFunction<
120
- typeof global.URL.createObjectURL
121
- >
122
- ).mockRestore();
123
- });
124
-
125
104
  beforeEach(() => {
105
+ jest.clearAllMocks();
126
106
  delete defaultProps.isMultiFilePicker;
127
107
  delete defaultProps.initialValue;
128
108
  delete defaultProps.value;
129
109
  delete defaultProps.readOnly;
110
+ delete defaultProps.placeholder;
111
+ delete defaultProps.onFocus;
112
+ delete defaultProps.onClick;
113
+ delete defaultProps.error;
114
+ delete defaultProps.dataTestId;
130
115
  });
131
116
 
117
+ // Basic rendering tests
132
118
  it("should render without crashing", () => {
133
119
  render(<FilePicker {...defaultProps} />);
134
- expect(screen.getByText("Upload a file")).toBeInTheDocument();
135
- expect(screen.getByRole("complementary")).toBeInTheDocument(); // aside element
120
+ expect(screen.getByText("Upload files")).toBeInTheDocument();
136
121
  });
137
122
 
138
- it("should render with initial value", async () => {
139
- defaultProps.initialValue = await mockFileModel(mockFile());
140
- const { container } = render(<FilePicker {...defaultProps} />);
141
- expect(
142
- queryByAttribute("src", container, MOCK_FILE_URL),
143
- ).toBeInTheDocument();
123
+ it("should render with custom placeholder text", () => {
124
+ defaultProps.placeholder = "Drop your files here";
125
+ render(<FilePicker {...defaultProps} />);
126
+ expect(screen.getByText("Drop your files here")).toBeInTheDocument();
144
127
  });
145
128
 
146
- it("should not render if file is missing the `file` attribute", async () => {
147
- const file: FileModel = await mockFileModel(mockFile());
148
- delete file.file;
149
- defaultProps.initialValue = file;
150
- const { container } = render(<FilePicker {...defaultProps} />);
151
- expect(
152
- queryByAttribute("src", container, MOCK_FILE_URL),
153
- ).not.toBeInTheDocument();
129
+ it("should display allowed mime types", () => {
130
+ render(<FilePicker {...defaultProps} />);
131
+ expect(screen.getByText(/PNG/)).toBeInTheDocument();
132
+ });
133
+
134
+ it("should display max file size message", () => {
135
+ render(<FilePicker {...defaultProps} />);
136
+ expect(screen.getByText(/Max 10MB each/)).toBeInTheDocument();
137
+ });
138
+
139
+ // Initial value tests - NEW TESTS replacing skipped ones
140
+ it("should render with initial value and display file name", async () => {
141
+ const file: File = mockFile("test-document.png");
142
+ defaultProps.initialValue = await mockFileModel(file);
143
+ render(<FilePicker {...defaultProps} />);
144
+
145
+ expect(screen.getByText("test-document.png")).toBeInTheDocument();
146
+ expect(screen.getByText("Uploaded files")).toBeInTheDocument();
154
147
  });
155
148
 
156
- it("should render with initial value as array", async () => {
149
+ it("should render with initial value and show Remove button", async () => {
150
+ const file: File = mockFile("my-file.png");
151
+ defaultProps.initialValue = await mockFileModel(file);
152
+ render(<FilePicker {...defaultProps} />);
153
+
154
+ expect(screen.getByText("Remove")).toBeInTheDocument();
155
+ });
156
+
157
+ it("should render with initial value as array and display all file names", async () => {
158
+ const file1: File = mockFile("first-file.png");
159
+ const file2: File = mockFile("second-file.png");
157
160
  defaultProps.initialValue = [
158
- await mockFileModel(mockFile()),
159
- await mockFileModel(mockFile()),
161
+ await mockFileModel(file1, "id1"),
162
+ await mockFileModel(file2, "id2"),
160
163
  ];
161
164
  render(<FilePicker {...defaultProps} />);
162
- const { container } = render(<FilePicker {...defaultProps} />);
163
- expect(queryAllByAttribute("src", container, MOCK_FILE_URL)).toHaveLength(
164
- 2,
165
- );
165
+
166
+ expect(screen.getByText("first-file.png")).toBeInTheDocument();
167
+ expect(screen.getByText("second-file.png")).toBeInTheDocument();
168
+ expect(screen.getAllByText("Remove")).toHaveLength(2);
166
169
  });
167
170
 
168
- it("should render with value array with one element", async () => {
169
- defaultProps.value = [await mockFileModel(mockFile())];
170
- const { container } = render(<FilePicker {...defaultProps} />);
171
- expect(
172
- queryByAttribute("src", container, MOCK_FILE_URL),
173
- ).toBeInTheDocument();
171
+ it("should render with initial value array and show Uploaded files section", async () => {
172
+ const file1: File = mockFile("doc1.png");
173
+ const file2: File = mockFile("doc2.png");
174
+ defaultProps.initialValue = [
175
+ await mockFileModel(file1, "id1"),
176
+ await mockFileModel(file2, "id2"),
177
+ ];
178
+ render(<FilePicker {...defaultProps} />);
179
+
180
+ expect(screen.getByText("Uploaded files")).toBeInTheDocument();
181
+ });
182
+
183
+ // Value prop tests - NEW TESTS replacing skipped ones
184
+ it("should render with value array containing one element", async () => {
185
+ const file: File = mockFile("single-file.png");
186
+ defaultProps.value = [await mockFileModel(file)];
187
+ render(<FilePicker {...defaultProps} />);
188
+
189
+ expect(screen.getByText("single-file.png")).toBeInTheDocument();
190
+ expect(screen.getByText("Uploaded files")).toBeInTheDocument();
174
191
  });
175
192
 
176
- it("should render with value array with more than one element", async () => {
193
+ it("should render with value array containing multiple elements", async () => {
194
+ const file1: File = mockFile("file-a.png");
195
+ const file2: File = mockFile("file-b.png");
196
+ const file3: File = mockFile("file-c.png");
177
197
  defaultProps.value = [
178
- await mockFileModel(mockFile()),
179
- await mockFileModel(mockFile()),
198
+ await mockFileModel(file1, "a"),
199
+ await mockFileModel(file2, "b"),
200
+ await mockFileModel(file3, "c"),
180
201
  ];
181
202
  render(<FilePicker {...defaultProps} />);
182
- const { container } = render(<FilePicker {...defaultProps} />);
183
- expect(queryAllByAttribute("src", container, MOCK_FILE_URL)).toHaveLength(
184
- 2,
185
- );
186
- });
187
203
 
188
- it("should not upload file when dropped and readOnly is true", async () => {
189
- defaultProps.readOnly = true;
204
+ expect(screen.getByText("file-a.png")).toBeInTheDocument();
205
+ expect(screen.getByText("file-b.png")).toBeInTheDocument();
206
+ expect(screen.getByText("file-c.png")).toBeInTheDocument();
207
+ expect(screen.getAllByText("Remove")).toHaveLength(3);
208
+ });
190
209
 
191
- const file: File = mockFile();
210
+ // Upload tests - NEW TESTS replacing skipped ones
211
+ it("should upload a file when dropped and display its name", async () => {
212
+ const file: File = mockFile("uploaded-doc.png");
192
213
  const data: DataTransfer = {
193
214
  dataTransfer: {
194
215
  files: [file],
@@ -202,23 +223,20 @@ describe("FilePicker", () => {
202
223
  ModelAPI.create as jest.MockedFunction<typeof ModelAPI.create>
203
224
  ).mockResolvedValue(createResponse);
204
225
 
205
- const { container } = render(<FilePicker {...defaultProps} />);
226
+ render(<FilePicker {...defaultProps} />);
206
227
 
207
- const dropzone: HTMLElement = screen.getByLabelText("Upload a file");
208
- fireEvent.drop(dropzone, data);
228
+ const dropzone: HTMLElement = screen.getByText("Upload files");
229
+ await act(async () => {
230
+ fireEvent.drop(dropzone, data);
231
+ });
209
232
 
210
233
  await waitFor(() => {
211
- expect(
212
- queryByAttribute("src", container, MOCK_FILE_URL),
213
- ).not.toBeInTheDocument();
234
+ expect(screen.getByText("uploaded-doc.png")).toBeInTheDocument();
214
235
  });
215
236
  });
216
237
 
217
- it('should throw an "File too large" when uploading a file that fails on arrayBuffer()', async () => {
218
- const file: File = mockFile();
219
- file.arrayBuffer = getJestMockFunction().mockRejectedValue(
220
- new Error("File too large"),
221
- );
238
+ it("should call onChange callback after successful upload", async () => {
239
+ const file: File = mockFile("callback-test.png");
222
240
  const data: DataTransfer = {
223
241
  dataTransfer: {
224
242
  files: [file],
@@ -226,20 +244,26 @@ describe("FilePicker", () => {
226
244
  },
227
245
  };
228
246
 
229
- const { container } = render(<FilePicker {...defaultProps} />);
247
+ const createResponse: HTTPResponse<FileModel> =
248
+ await mockCreateResponse(file);
249
+ (
250
+ ModelAPI.create as jest.MockedFunction<typeof ModelAPI.create>
251
+ ).mockResolvedValue(createResponse);
230
252
 
231
- const dropzone: HTMLElement = screen.getByLabelText("Upload a file");
232
- fireEvent.drop(dropzone, data);
253
+ render(<FilePicker {...defaultProps} />);
254
+
255
+ const dropzone: HTMLElement = screen.getByText("Upload files");
256
+ await act(async () => {
257
+ fireEvent.drop(dropzone, data);
258
+ });
233
259
 
234
260
  await waitFor(() => {
235
- expect(
236
- queryByAttribute("src", container, MOCK_FILE_URL),
237
- ).not.toBeInTheDocument();
261
+ expect(mockOnChange).toHaveBeenCalled();
238
262
  });
239
263
  });
240
264
 
241
- it("should upload a file when dropped", async () => {
242
- const file: File = mockFile();
265
+ it("should call onBlur callback after successful upload", async () => {
266
+ const file: File = mockFile("blur-test.png");
243
267
  const data: DataTransfer = {
244
268
  dataTransfer: {
245
269
  files: [file],
@@ -253,25 +277,20 @@ describe("FilePicker", () => {
253
277
  ModelAPI.create as jest.MockedFunction<typeof ModelAPI.create>
254
278
  ).mockResolvedValue(createResponse);
255
279
 
256
- const { container } = render(<FilePicker {...defaultProps} />);
280
+ render(<FilePicker {...defaultProps} />);
257
281
 
258
- const dropzone: HTMLElement = screen.getByLabelText("Upload a file");
282
+ const dropzone: HTMLElement = screen.getByText("Upload files");
259
283
  await act(async () => {
260
284
  fireEvent.drop(dropzone, data);
261
285
  });
262
286
 
263
287
  await waitFor(() => {
264
- expect(mockOnChange).toHaveBeenCalledWith([createResponse.data]);
265
- expect(mockOnBlur).toHaveBeenCalled();
266
288
  expect(mockOnBlur).toHaveBeenCalled();
267
- expect(
268
- queryByAttribute("src", container, MOCK_FILE_URL),
269
- ).toBeInTheDocument();
270
289
  });
271
290
  });
272
291
 
273
- it("should upload a file when dropped", async () => {
274
- const file: File = mockFile();
292
+ it("should display Uploaded files section after upload", async () => {
293
+ const file: File = mockFile("section-test.png");
275
294
  const data: DataTransfer = {
276
295
  dataTransfer: {
277
296
  files: [file],
@@ -285,35 +304,52 @@ describe("FilePicker", () => {
285
304
  ModelAPI.create as jest.MockedFunction<typeof ModelAPI.create>
286
305
  ).mockResolvedValue(createResponse);
287
306
 
288
- const { container } = render(<FilePicker {...defaultProps} />);
307
+ render(<FilePicker {...defaultProps} />);
289
308
 
290
- const dropzone: HTMLElement = screen.getByLabelText("Upload a file");
309
+ const dropzone: HTMLElement = screen.getByText("Upload files");
291
310
  await act(async () => {
292
311
  fireEvent.drop(dropzone, data);
293
312
  });
294
313
 
295
314
  await waitFor(() => {
296
- expect(mockOnChange).toHaveBeenCalledWith([createResponse.data]);
297
- expect(mockOnBlur).toHaveBeenCalled();
298
- expect(mockOnBlur).toHaveBeenCalled();
299
- expect(
300
- queryByAttribute("src", container, MOCK_FILE_URL),
301
- ).toBeInTheDocument();
315
+ expect(screen.getByText("Uploaded files")).toBeInTheDocument();
302
316
  });
303
317
  });
304
318
 
305
- it("should show loader a file when files are being uploaded", async () => {
306
- const uploadPromise: Promise<unknown> = new Promise((resolve: any) => {
307
- (global as any).mockUploadResolve = resolve; // Store resolve function globally or in a scope accessible outside the test
319
+ // Delete file tests - NEW TESTS replacing skipped ones
320
+ it("should remove file when Remove button is clicked", async () => {
321
+ const file: File = mockFile("removable-file.png");
322
+ defaultProps.initialValue = await mockFileModel(file);
323
+ render(<FilePicker {...defaultProps} />);
324
+
325
+ expect(screen.getByText("removable-file.png")).toBeInTheDocument();
326
+
327
+ const removeButton: HTMLElement = screen.getByText("Remove");
328
+ await act(async () => {
329
+ fireEvent.click(removeButton);
308
330
  });
309
331
 
310
- (
311
- ModelAPI.create as jest.MockedFunction<typeof ModelAPI.create>
312
- ).mockImplementation((): any => {
313
- return uploadPromise;
332
+ expect(screen.queryByText("removable-file.png")).not.toBeInTheDocument();
333
+ });
334
+
335
+ it("should call onChange with empty array when last file is removed", async () => {
336
+ const file: File = mockFile("last-file.png");
337
+ defaultProps.initialValue = await mockFileModel(file);
338
+ render(<FilePicker {...defaultProps} />);
339
+
340
+ const removeButton: HTMLElement = screen.getByText("Remove");
341
+ await act(async () => {
342
+ fireEvent.click(removeButton);
314
343
  });
315
344
 
316
- const file: File = mockFile();
345
+ expect(mockOnChange).toHaveBeenCalledWith([]);
346
+ });
347
+
348
+ // ReadOnly tests
349
+ it("should not upload file when dropped and readOnly is true", async () => {
350
+ defaultProps.readOnly = true;
351
+
352
+ const file: File = mockFile("readonly-test.png");
317
353
  const data: DataTransfer = {
318
354
  dataTransfer: {
319
355
  files: [file],
@@ -321,32 +357,103 @@ describe("FilePicker", () => {
321
357
  },
322
358
  };
323
359
 
324
- const createResponse: HTTPResponse<FileModel> =
325
- await mockCreateResponse(file);
360
+ (
361
+ ModelAPI.create as jest.MockedFunction<typeof ModelAPI.create>
362
+ ).mockResolvedValue(await mockCreateResponse(file));
326
363
 
327
- const { container } = render(<FilePicker {...defaultProps} />);
364
+ render(<FilePicker {...defaultProps} />);
328
365
 
329
- const dropzone: HTMLElement = screen.getByLabelText("Upload a file");
330
- await act(async () => {
331
- fireEvent.drop(dropzone, data);
366
+ const dropzone: HTMLElement = screen.getByLabelText("Upload files");
367
+ fireEvent.drop(dropzone, data);
368
+
369
+ await waitFor(() => {
370
+ expect(ModelAPI.create).not.toHaveBeenCalled();
332
371
  });
372
+ });
333
373
 
334
- expect(queryByTestId(container, "loader")).toBeInTheDocument();
374
+ it("should render in read-only mode without errors", () => {
375
+ defaultProps.readOnly = true;
376
+ render(<FilePicker {...defaultProps} />);
335
377
 
336
- (global as any).mockUploadResolve(createResponse);
378
+ // Component should still render in read-only mode
379
+ expect(screen.getByText("Upload files")).toBeInTheDocument();
380
+ });
337
381
 
338
- await waitFor(() => {
339
- expect(mockOnChange).toHaveBeenCalledWith([createResponse.data]);
340
- expect(mockOnBlur).toHaveBeenCalled();
341
- expect(mockOnBlur).toHaveBeenCalled();
342
- expect(
343
- queryByAttribute("src", container, MOCK_FILE_URL),
344
- ).toBeInTheDocument();
345
- });
382
+ // Error handling tests
383
+ it("should display error prop when provided", () => {
384
+ defaultProps.error = "Something went wrong";
385
+ render(<FilePicker {...defaultProps} />);
386
+
387
+ expect(screen.getByTestId("error-message")).toBeInTheDocument();
388
+ expect(screen.getByText("Something went wrong")).toBeInTheDocument();
389
+ });
390
+
391
+ it("should not display error message when error prop is not provided", () => {
392
+ render(<FilePicker {...defaultProps} />);
393
+
394
+ expect(screen.queryByTestId("error-message")).not.toBeInTheDocument();
395
+ });
396
+
397
+ // Callback tests
398
+ it("should call onFocus when dropzone is clicked", () => {
399
+ defaultProps.onFocus = mockOnFocus;
400
+ render(<FilePicker {...defaultProps} />);
401
+
402
+ const container: HTMLElement = screen
403
+ .getByText("Upload files")
404
+ .closest("div")!.parentElement!.parentElement!;
405
+ fireEvent.click(container);
406
+
407
+ expect(mockOnFocus).toHaveBeenCalled();
346
408
  });
347
409
 
348
- it("should delete an uploaded file when clicking on it", async () => {
349
- const file: File = mockFile();
410
+ it("should call onClick when dropzone is clicked", () => {
411
+ defaultProps.onClick = mockOnClick;
412
+ render(<FilePicker {...defaultProps} />);
413
+
414
+ const container: HTMLElement = screen
415
+ .getByText("Upload files")
416
+ .closest("div")!.parentElement!.parentElement!;
417
+ fireEvent.click(container);
418
+
419
+ expect(mockOnClick).toHaveBeenCalled();
420
+ });
421
+
422
+ // Data test id
423
+ it("should render with custom data-testid", () => {
424
+ defaultProps.dataTestId = "custom-file-picker";
425
+ render(<FilePicker {...defaultProps} />);
426
+
427
+ expect(screen.getByTestId("custom-file-picker")).toBeInTheDocument();
428
+ });
429
+
430
+ // Multi-file picker tests
431
+ it("should show Add more files text when files exist and isMultiFilePicker is true", async () => {
432
+ defaultProps.isMultiFilePicker = true;
433
+ const file: File = mockFile("existing.png");
434
+ defaultProps.initialValue = await mockFileModel(file);
435
+ render(<FilePicker {...defaultProps} />);
436
+
437
+ expect(screen.getByText("Add more files")).toBeInTheDocument();
438
+ });
439
+
440
+ // File without file attribute test
441
+ it("should not render if file is missing the file attribute", async () => {
442
+ const file: FileModel = await mockFileModel(mockFile("no-buffer.png"));
443
+ delete file.file;
444
+ defaultProps.initialValue = file;
445
+ render(<FilePicker {...defaultProps} />);
446
+
447
+ // File name should still be shown but file size won't be available
448
+ expect(screen.getByText("no-buffer.png")).toBeInTheDocument();
449
+ });
450
+
451
+ // Error on arrayBuffer test
452
+ it("should handle error when file arrayBuffer fails", async () => {
453
+ const file: File = mockFile("error-file.png");
454
+ file.arrayBuffer = getJestMockFunction().mockRejectedValue(
455
+ new Error("File too large"),
456
+ );
350
457
  const data: DataTransfer = {
351
458
  dataTransfer: {
352
459
  files: [file],
@@ -354,40 +461,13 @@ describe("FilePicker", () => {
354
461
  },
355
462
  };
356
463
 
357
- const createResponse: HTTPResponse<FileModel> =
358
- await mockCreateResponse(file);
359
- (
360
- ModelAPI.create as jest.MockedFunction<typeof ModelAPI.create>
361
- ).mockResolvedValue(createResponse);
362
-
363
- const { container } = render(<FilePicker {...defaultProps} />);
364
-
365
- const dropzone: HTMLElement = screen.getByLabelText("Upload a file");
366
- await act(async () => {
367
- fireEvent.drop(dropzone, data);
368
- });
369
-
370
- await waitFor(() => {
371
- // file should be in the dropzone
372
- expect(
373
- queryByAttribute("src", container, MOCK_FILE_URL),
374
- ).toBeInTheDocument();
375
- });
464
+ render(<FilePicker {...defaultProps} />);
376
465
 
377
- const deleteIcon: ChildNode = screen.getByRole("icon").childNodes.item(0); // svg item
378
- // remove file by clicking on it
379
- if (deleteIcon) {
380
- await act(async () => {
381
- fireEvent.click(deleteIcon.childNodes.item(0), data);
382
- });
383
- }
466
+ const dropzone: HTMLElement = screen.getByLabelText("Upload files");
467
+ fireEvent.drop(dropzone, data);
384
468
 
385
469
  await waitFor(() => {
386
- // file should have been removed
387
- expect(mockOnChange).toHaveBeenCalledWith([createResponse.data]);
388
- expect(
389
- queryByAttribute("src", container, MOCK_FILE_URL),
390
- ).not.toBeInTheDocument();
470
+ expect(screen.queryByText("error-file.png")).not.toBeInTheDocument();
391
471
  });
392
472
  });
393
473
  });
@@ -92,7 +92,9 @@ describe("List", () => {
92
92
 
93
93
  it("handles onNavigateToPage callback", () => {
94
94
  render(<List {...defaultProps} />);
95
- fireEvent.click(screen.getByText("Next"));
95
+ // There are multiple "Next" elements (mobile and desktop), get the first one
96
+ const nextButtons: HTMLElement[] = screen.getAllByText("Next");
97
+ fireEvent.click(nextButtons[0]!);
96
98
 
97
99
  expect(defaultProps.onNavigateToPage).toHaveBeenCalledWith(2, 5);
98
100
  });
@@ -61,7 +61,8 @@ describe("MarkdownEditor", () => {
61
61
  const textarea: HTMLTextAreaElement = screen.getByRole(
62
62
  "textbox",
63
63
  ) as HTMLTextAreaElement;
64
- expect(textarea.spellcheck).toBe(true);
64
+ // jsdom doesn't properly reflect spellcheck property, so we check the attribute
65
+ expect(textarea.getAttribute("spellcheck")).toBe("true");
65
66
  });
66
67
 
67
68
  test("should enable spell check when disableSpellCheck is undefined", () => {
@@ -76,7 +77,7 @@ describe("MarkdownEditor", () => {
76
77
  const textarea: HTMLTextAreaElement = screen.getByRole(
77
78
  "textbox",
78
79
  ) as HTMLTextAreaElement;
79
- expect(textarea.spellcheck).toBe(true);
80
+ expect(textarea.getAttribute("spellcheck")).toBe("true");
80
81
  });
81
82
 
82
83
  test("should disable spell check when disableSpellCheck is true", () => {
@@ -91,7 +92,7 @@ describe("MarkdownEditor", () => {
91
92
  const textarea: HTMLTextAreaElement = screen.getByRole(
92
93
  "textbox",
93
94
  ) as HTMLTextAreaElement;
94
- expect(textarea.spellcheck).toBe(false);
95
+ expect(textarea.getAttribute("spellcheck")).toBe("false");
95
96
  });
96
97
 
97
98
  test("should handle spell check prop changes", () => {
@@ -106,7 +107,7 @@ describe("MarkdownEditor", () => {
106
107
  let textarea: HTMLTextAreaElement = screen.getByRole(
107
108
  "textbox",
108
109
  ) as HTMLTextAreaElement;
109
- expect(textarea.spellcheck).toBe(true);
110
+ expect(textarea.getAttribute("spellcheck")).toBe("true");
110
111
 
111
112
  rerender(
112
113
  <MarkdownEditor
@@ -117,7 +118,7 @@ describe("MarkdownEditor", () => {
117
118
  );
118
119
 
119
120
  textarea = screen.getByRole("textbox") as HTMLTextAreaElement;
120
- expect(textarea.spellcheck).toBe(false);
121
+ expect(textarea.getAttribute("spellcheck")).toBe("false");
121
122
  });
122
123
 
123
124
  test("should show help text", () => {
@@ -23,7 +23,7 @@ describe("MasterPage", () => {
23
23
  it("should render correctly with isLoading", () => {
24
24
  render(<MasterPage {...defaultProps} isLoading />);
25
25
 
26
- const loader: HTMLElement = screen.getByRole("bar-loader");
26
+ const loader: HTMLElement = screen.getByTestId("bar-loader");
27
27
  expect(loader).toBeInTheDocument();
28
28
  });
29
29