@oslokommune/punkt-react 15.0.4 → 15.1.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/CHANGELOG.md +31 -0
- package/dist/index.d.ts +173 -0
- package/dist/punkt-react.es.js +8216 -7134
- package/dist/punkt-react.umd.js +435 -435
- package/package.json +4 -4
- package/src/components/fileupload/DropZone.tsx +236 -0
- package/src/components/fileupload/FileUpload.test.tsx +584 -0
- package/src/components/fileupload/FileUpload.tsx +425 -0
- package/src/components/fileupload/QueueDisplay.test.tsx +222 -0
- package/src/components/fileupload/QueueDisplay.tsx +155 -0
- package/src/components/fileupload/QueueItemContent.tsx +200 -0
- package/src/components/fileupload/Subcomponents.tsx +363 -0
- package/src/components/fileupload/Truncate.test.tsx +256 -0
- package/src/components/fileupload/Truncate.tsx +137 -0
- package/src/components/fileupload/extensions/Comments.tsx +188 -0
- package/src/components/fileupload/extensions/Remove.tsx +10 -0
- package/src/components/fileupload/extensions/Rename.tsx +79 -0
- package/src/components/fileupload/hooks/index.ts +3 -0
- package/src/components/fileupload/hooks/useFileAttributes.ts +46 -0
- package/src/components/fileupload/hooks/useImagePreview.ts +42 -0
- package/src/components/fileupload/hooks/useOperationState.ts +30 -0
- package/src/components/fileupload/hooks.ts +4 -0
- package/src/components/fileupload/texts.ts +20 -0
- package/src/components/fileupload/types.ts +94 -0
- package/src/components/fileupload/utils.ts +56 -0
- package/src/components/index.ts +1 -0
- package/src/components/interfaces.ts +1 -0
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
import '@testing-library/jest-dom'
|
|
2
|
+
|
|
3
|
+
import { fireEvent, render, screen } from '@testing-library/react'
|
|
4
|
+
import userEvent from '@testing-library/user-event'
|
|
5
|
+
import { axe, toHaveNoViolations } from 'jest-axe'
|
|
6
|
+
import { useState } from 'react'
|
|
7
|
+
import { vi } from 'vitest'
|
|
8
|
+
|
|
9
|
+
import { PktFileUpload } from './FileUpload'
|
|
10
|
+
import { FileItem, TFileItemList } from './types'
|
|
11
|
+
|
|
12
|
+
// Polyfill DataTransfer for jsdom
|
|
13
|
+
if (!global.DataTransfer) {
|
|
14
|
+
class DataTransferPolyfill {
|
|
15
|
+
items: DataTransferItemList
|
|
16
|
+
// @ts-ignore
|
|
17
|
+
files: FileList
|
|
18
|
+
|
|
19
|
+
constructor() {
|
|
20
|
+
const files: File[] = []
|
|
21
|
+
const items: DataTransferItem[] = []
|
|
22
|
+
|
|
23
|
+
this.items = {
|
|
24
|
+
add: (file: File) => {
|
|
25
|
+
files.push(file)
|
|
26
|
+
items.push({
|
|
27
|
+
kind: 'file',
|
|
28
|
+
type: file.type,
|
|
29
|
+
getAsFile: () => file,
|
|
30
|
+
} as DataTransferItem)
|
|
31
|
+
},
|
|
32
|
+
length: items.length,
|
|
33
|
+
} as DataTransferItemList
|
|
34
|
+
|
|
35
|
+
Object.defineProperty(this, 'files', {
|
|
36
|
+
get: () => {
|
|
37
|
+
const fileList = {
|
|
38
|
+
length: files.length,
|
|
39
|
+
item: (index: number) => files[index],
|
|
40
|
+
[Symbol.iterator]: function* () {
|
|
41
|
+
yield* files
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
files.forEach((file, index) => {
|
|
45
|
+
Object.defineProperty(fileList, index, {
|
|
46
|
+
value: file,
|
|
47
|
+
enumerable: true,
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
return fileList as FileList
|
|
51
|
+
},
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
global.DataTransfer = DataTransferPolyfill as any
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Polyfill for ResizeObserver
|
|
60
|
+
if (!global.ResizeObserver) {
|
|
61
|
+
class ResizeObserverPolyfill {
|
|
62
|
+
observe() {}
|
|
63
|
+
unobserve() {}
|
|
64
|
+
disconnect() {}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
global.ResizeObserver = ResizeObserverPolyfill as any
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const NOOP = () => {}
|
|
71
|
+
|
|
72
|
+
const getVisibleFilenameNode = (filename: string) =>
|
|
73
|
+
screen.queryByText(
|
|
74
|
+
(content, element) => content === filename && element?.getAttribute('data-pkt-truncate-part') === 'first',
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
const expectVisibleFilename = (filename: string) => {
|
|
78
|
+
expect(getVisibleFilenameNode(filename)).toBeInTheDocument()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const makeFilesPropWritable = (fileInput: HTMLInputElement) => {
|
|
82
|
+
// Make the files property settable in jsdom
|
|
83
|
+
let filesValue: FileList | null = null
|
|
84
|
+
Object.defineProperty(fileInput, 'files', {
|
|
85
|
+
get: () => filesValue,
|
|
86
|
+
set: (value: FileList | null) => {
|
|
87
|
+
filesValue = value
|
|
88
|
+
},
|
|
89
|
+
configurable: true,
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
expect.extend(toHaveNoViolations)
|
|
94
|
+
|
|
95
|
+
const createMockFile = (name: string, type = 'text/plain'): File => {
|
|
96
|
+
return new File(['content'], name, { type })
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function createFileItem(name: string, fileId?: string) {
|
|
100
|
+
return new FileItem(createMockFile(name), fileId)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
describe('PktFileUpload', () => {
|
|
104
|
+
describe('Rendering', () => {
|
|
105
|
+
it('should render the drop zone with default placeholder text for multiple files', () => {
|
|
106
|
+
render(<PktFileUpload multiple name={'pktFileUpload'} />)
|
|
107
|
+
|
|
108
|
+
expect(screen.getByText(/Dra filer hit for å laste dem opp eller/)).toBeInTheDocument()
|
|
109
|
+
expect(screen.getByText('velg filer')).toBeInTheDocument()
|
|
110
|
+
expect(screen.getByText(/Format: .PDF, .JPEG, .JPG, .PNG, .HEIC, .DOC, .DOCX, .ODT/)).toBeInTheDocument()
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('should render the drop zone with single file text when multiple is false', () => {
|
|
114
|
+
render(<PktFileUpload name={'pktFileUpload'} />)
|
|
115
|
+
|
|
116
|
+
expect(screen.getByText(/Dra en fil for å laste den opp eller/)).toBeInTheDocument()
|
|
117
|
+
expect(screen.getByText('velg en fil')).toBeInTheDocument()
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('should render the attachment icon', () => {
|
|
121
|
+
const { container } = render(<PktFileUpload name={'pktFileUpload'} />)
|
|
122
|
+
|
|
123
|
+
expect(container.querySelector('.pkt-fileupload__drop-zone__placeholder__icon')).toBeInTheDocument()
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('should render with initial value', () => {
|
|
127
|
+
const initialValue: TFileItemList = [createFileItem('test.pdf', '1')]
|
|
128
|
+
|
|
129
|
+
render(<PktFileUpload value={initialValue} name={'pktFileUpload'} onFilesChanged={NOOP} />)
|
|
130
|
+
|
|
131
|
+
expectVisibleFilename('test.pdf')
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('should render multiple files in queue display', () => {
|
|
135
|
+
const initialValue: TFileItemList = [
|
|
136
|
+
createFileItem('file1.pdf', '1'),
|
|
137
|
+
createFileItem('file2.docx', '2'),
|
|
138
|
+
createFileItem('file3.png', '3'),
|
|
139
|
+
]
|
|
140
|
+
|
|
141
|
+
render(<PktFileUpload value={initialValue} multiple name={'pktFileUpload'} onFilesChanged={NOOP} />)
|
|
142
|
+
|
|
143
|
+
expectVisibleFilename('file1.pdf')
|
|
144
|
+
expectVisibleFilename('file2.docx')
|
|
145
|
+
expectVisibleFilename('file3.png')
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
describe('File selection via dialog', () => {
|
|
150
|
+
it('should trigger file input when clicking "velg filer" button', async () => {
|
|
151
|
+
const user = userEvent.setup()
|
|
152
|
+
render(<PktFileUpload multiple name="pktFileUpload" />)
|
|
153
|
+
|
|
154
|
+
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement
|
|
155
|
+
const clickSpy = vi.spyOn(fileInput, 'click')
|
|
156
|
+
|
|
157
|
+
const button = screen.getByRole('button', { name: /velg filer/i })
|
|
158
|
+
await user.click(button)
|
|
159
|
+
|
|
160
|
+
expect(clickSpy).toHaveBeenCalled()
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it('should trigger file input when clicking the dropzone', async () => {
|
|
164
|
+
const user = userEvent.setup()
|
|
165
|
+
const { container } = render(<PktFileUpload name="pktFileUpload" />)
|
|
166
|
+
|
|
167
|
+
const fileInput = container.querySelector('input[type="file"]') as HTMLInputElement
|
|
168
|
+
const clickSpy = vi.spyOn(fileInput, 'click')
|
|
169
|
+
|
|
170
|
+
const dropZone = container.querySelector('.pkt-fileupload__drop-zone')!
|
|
171
|
+
await user.click(dropZone)
|
|
172
|
+
|
|
173
|
+
expect(clickSpy).toHaveBeenCalled()
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
it('should call onFilesChanged when files are selected via dialog', () => {
|
|
177
|
+
const onFilesChanged = vi.fn()
|
|
178
|
+
render(<PktFileUpload onFilesChanged={onFilesChanged} multiple name={'pktFileUpload'} />)
|
|
179
|
+
|
|
180
|
+
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement
|
|
181
|
+
const file1 = createMockFile('test1.pdf')
|
|
182
|
+
const file2 = createMockFile('test2.pdf')
|
|
183
|
+
|
|
184
|
+
fireEvent.change(fileInput, { target: { files: [file1, file2] } })
|
|
185
|
+
|
|
186
|
+
expect(onFilesChanged).toHaveBeenCalledTimes(1)
|
|
187
|
+
const calledWith = onFilesChanged.mock.calls[0][0]
|
|
188
|
+
expect(calledWith).toHaveLength(2)
|
|
189
|
+
expect(calledWith[0].file).toBe(file1)
|
|
190
|
+
expect(calledWith[1].file).toBe(file2)
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
it('should assign unique IDs to added files', () => {
|
|
194
|
+
const onFilesChanged = vi.fn()
|
|
195
|
+
render(<PktFileUpload onFilesChanged={onFilesChanged} multiple name={'pktFileUpload'} />)
|
|
196
|
+
|
|
197
|
+
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement
|
|
198
|
+
const file1 = createMockFile('test1.pdf')
|
|
199
|
+
const file2 = createMockFile('test2.pdf')
|
|
200
|
+
|
|
201
|
+
fireEvent.change(fileInput, { target: { files: [file1, file2] } })
|
|
202
|
+
|
|
203
|
+
const calledWith = onFilesChanged.mock.calls[0][0]
|
|
204
|
+
expect(calledWith[0].fileId).toBeTruthy()
|
|
205
|
+
expect(calledWith[1].fileId).toBeTruthy()
|
|
206
|
+
expect(calledWith[0].fileId).not.toBe(calledWith[1].fileId)
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
it('should only keep one file when multiple is false', () => {
|
|
210
|
+
const onFilesChanged = vi.fn()
|
|
211
|
+
render(<PktFileUpload onFilesChanged={onFilesChanged} name={'pktFileUpload'} />)
|
|
212
|
+
|
|
213
|
+
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement
|
|
214
|
+
const file1 = createMockFile('test1.pdf')
|
|
215
|
+
const file2 = createMockFile('test2.pdf')
|
|
216
|
+
|
|
217
|
+
fireEvent.change(fileInput, { target: { files: [file1, file2] } })
|
|
218
|
+
|
|
219
|
+
const calledWith = onFilesChanged.mock.calls[0][0]
|
|
220
|
+
expect(calledWith).toHaveLength(1)
|
|
221
|
+
expect(calledWith[0].file).toBe(file1)
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
it('should clear file input value after files are selected', () => {
|
|
225
|
+
const onFilesChanged = vi.fn()
|
|
226
|
+
render(<PktFileUpload onFilesChanged={onFilesChanged} multiple name={'pktFileUpload'} />)
|
|
227
|
+
|
|
228
|
+
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement
|
|
229
|
+
const file = createMockFile('test.pdf')
|
|
230
|
+
|
|
231
|
+
fireEvent.change(fileInput, { target: { files: [file] } })
|
|
232
|
+
|
|
233
|
+
expect(fileInput.value).toBe('')
|
|
234
|
+
})
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
describe('Drag and drop functionality', () => {
|
|
238
|
+
it('should show active state when dragging over drop zone', () => {
|
|
239
|
+
const { container } = render(<PktFileUpload multiple name={'pktFileUpload'} />)
|
|
240
|
+
|
|
241
|
+
const dropZone = container.querySelector('.pkt-fileupload__drop-zone') as HTMLElement
|
|
242
|
+
|
|
243
|
+
fireEvent.dragOver(dropZone)
|
|
244
|
+
|
|
245
|
+
expect(dropZone).toHaveClass('pkt-fileupload__drop-zone--drag-active')
|
|
246
|
+
expect(screen.getByText(/Slipp filene her/)).toBeInTheDocument()
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
it('should show active state text for single file mode when dragging', () => {
|
|
250
|
+
const { container } = render(<PktFileUpload name={'pktFileUpload'} />)
|
|
251
|
+
|
|
252
|
+
const dropZone = container.querySelector('.pkt-fileupload__drop-zone') as HTMLElement
|
|
253
|
+
|
|
254
|
+
fireEvent.dragOver(dropZone)
|
|
255
|
+
|
|
256
|
+
expect(screen.getByText(/Slipp filen her/)).toBeInTheDocument()
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
it('should remove active state when drag leaves', () => {
|
|
260
|
+
const { container } = render(<PktFileUpload multiple name={'pktFileUpload'} />)
|
|
261
|
+
|
|
262
|
+
const dropZone = container.querySelector('.pkt-fileupload__drop-zone') as HTMLElement
|
|
263
|
+
|
|
264
|
+
fireEvent.dragOver(dropZone)
|
|
265
|
+
expect(dropZone).toHaveClass('pkt-fileupload__drop-zone--drag-active')
|
|
266
|
+
|
|
267
|
+
fireEvent.dragLeave(dropZone)
|
|
268
|
+
expect(dropZone).not.toHaveClass('pkt-fileupload__drop-zone--drag-active')
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
it('should call onFilesChanged when files are dropped', () => {
|
|
272
|
+
const onFilesChanged = vi.fn()
|
|
273
|
+
const { container } = render(<PktFileUpload onFilesChanged={onFilesChanged} multiple name={'pktFileUpload'} />)
|
|
274
|
+
|
|
275
|
+
const dropZone = container.querySelector('.pkt-fileupload__drop-zone') as HTMLElement
|
|
276
|
+
const file1 = createMockFile('dropped1.pdf')
|
|
277
|
+
const file2 = createMockFile('dropped2.pdf')
|
|
278
|
+
|
|
279
|
+
fireEvent.drop(dropZone, {
|
|
280
|
+
dataTransfer: {
|
|
281
|
+
files: [file1, file2],
|
|
282
|
+
},
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
expect(onFilesChanged).toHaveBeenCalledTimes(1)
|
|
286
|
+
const calledWith = onFilesChanged.mock.calls[0][0]
|
|
287
|
+
expect(calledWith).toHaveLength(2)
|
|
288
|
+
expect(calledWith[0].file).toBe(file1)
|
|
289
|
+
expect(calledWith[1].file).toBe(file2)
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
it('should remove active state after dropping files', () => {
|
|
293
|
+
const { container } = render(<PktFileUpload multiple name={'pktFileUpload'} />)
|
|
294
|
+
|
|
295
|
+
const dropZone = container.querySelector('.pkt-fileupload__drop-zone') as HTMLElement
|
|
296
|
+
const file = createMockFile('dropped.pdf')
|
|
297
|
+
|
|
298
|
+
fireEvent.dragOver(dropZone)
|
|
299
|
+
expect(dropZone).toHaveClass('pkt-fileupload__drop-zone--drag-active')
|
|
300
|
+
|
|
301
|
+
fireEvent.drop(dropZone, {
|
|
302
|
+
dataTransfer: {
|
|
303
|
+
files: [file],
|
|
304
|
+
},
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
expect(dropZone).not.toHaveClass('pkt-fileupload__drop-zone--drag-active')
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
it('should only keep one file when dropping in single file mode', () => {
|
|
311
|
+
const onFilesChanged = vi.fn()
|
|
312
|
+
const { container } = render(<PktFileUpload onFilesChanged={onFilesChanged} name={'pktFileUpload'} />)
|
|
313
|
+
|
|
314
|
+
const dropZone = container.querySelector('.pkt-fileupload__drop-zone') as HTMLElement
|
|
315
|
+
const file1 = createMockFile('dropped1.pdf')
|
|
316
|
+
const file2 = createMockFile('dropped2.pdf')
|
|
317
|
+
|
|
318
|
+
fireEvent.drop(dropZone, {
|
|
319
|
+
dataTransfer: {
|
|
320
|
+
files: [file1, file2],
|
|
321
|
+
},
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
const calledWith = onFilesChanged.mock.calls[0][0]
|
|
325
|
+
expect(calledWith).toHaveLength(1)
|
|
326
|
+
expect(calledWith[0].file).toBe(file1)
|
|
327
|
+
})
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
describe('Multiple file mode', () => {
|
|
331
|
+
it('should append new files to existing files when multiple is true', () => {
|
|
332
|
+
const onFilesChanged = vi.fn()
|
|
333
|
+
const initialValue: TFileItemList = [createFileItem('existing.pdf', '1')]
|
|
334
|
+
|
|
335
|
+
render(<PktFileUpload value={initialValue} onFilesChanged={onFilesChanged} multiple name={'pktFileUpload'} />)
|
|
336
|
+
|
|
337
|
+
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement
|
|
338
|
+
const newFile = createMockFile('new.pdf')
|
|
339
|
+
|
|
340
|
+
fireEvent.change(fileInput, { target: { files: [newFile] } })
|
|
341
|
+
|
|
342
|
+
const calledWith = onFilesChanged.mock.calls[0][0]
|
|
343
|
+
expect(calledWith).toHaveLength(2)
|
|
344
|
+
expect(calledWith[0].file.name).toBe('existing.pdf')
|
|
345
|
+
expect(calledWith[1].file.name).toBe('new.pdf')
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
it('should set file input multiple attribute when multiple is true', () => {
|
|
349
|
+
render(<PktFileUpload multiple name={'pktFileUpload'} />)
|
|
350
|
+
|
|
351
|
+
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement
|
|
352
|
+
expect(fileInput).toHaveAttribute('multiple')
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
it('should not set file input multiple attribute when multiple is false', () => {
|
|
356
|
+
render(<PktFileUpload name={'pktFileUpload'} />)
|
|
357
|
+
|
|
358
|
+
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement
|
|
359
|
+
expect(fileInput).not.toHaveAttribute('multiple')
|
|
360
|
+
})
|
|
361
|
+
})
|
|
362
|
+
|
|
363
|
+
describe('Controlled component behavior', () => {
|
|
364
|
+
it('should update display when value prop changes', () => {
|
|
365
|
+
const initialValue: TFileItemList = [createFileItem('file1.pdf', '1')]
|
|
366
|
+
|
|
367
|
+
const { rerender } = render(<PktFileUpload value={initialValue} name={'pktFileUpload'} onFilesChanged={NOOP} />)
|
|
368
|
+
|
|
369
|
+
expectVisibleFilename('file1.pdf')
|
|
370
|
+
|
|
371
|
+
const updatedValue: TFileItemList = [createFileItem('file2.pdf', '2')]
|
|
372
|
+
|
|
373
|
+
rerender(<PktFileUpload value={updatedValue} name={'pktFileUpload'} onFilesChanged={NOOP} />)
|
|
374
|
+
|
|
375
|
+
expect(getVisibleFilenameNode('file1.pdf')).not.toBeInTheDocument()
|
|
376
|
+
expectVisibleFilename('file2.pdf')
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
it('should handle empty value prop', () => {
|
|
380
|
+
render(<PktFileUpload value={[]} name={'pktFileUpload'} onFilesChanged={NOOP} />)
|
|
381
|
+
|
|
382
|
+
expect(screen.queryByText(/.pdf/)).not.toBeInTheDocument()
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
it('should work as uncontrolled component without value prop', () => {
|
|
386
|
+
const onFilesChanged = vi.fn()
|
|
387
|
+
render(<PktFileUpload onFilesChanged={onFilesChanged} multiple name={'pktFileUpload'} />)
|
|
388
|
+
|
|
389
|
+
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement
|
|
390
|
+
const file = createMockFile('test.pdf')
|
|
391
|
+
|
|
392
|
+
fireEvent.change(fileInput, { target: { files: [file] } })
|
|
393
|
+
|
|
394
|
+
expect(onFilesChanged).toHaveBeenCalledTimes(1)
|
|
395
|
+
})
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
describe('File removal', () => {
|
|
399
|
+
it('should render remove buttons for each file', () => {
|
|
400
|
+
const initialValue: TFileItemList = [createFileItem('file1.pdf', '1'), createFileItem('file2.pdf', '2')]
|
|
401
|
+
|
|
402
|
+
render(<PktFileUpload value={initialValue} name={'pktFileUpload'} onFilesChanged={NOOP} />)
|
|
403
|
+
|
|
404
|
+
const removeButtons = screen.getAllByRole('button', { name: /Slett/ })
|
|
405
|
+
expect(removeButtons).toHaveLength(2)
|
|
406
|
+
})
|
|
407
|
+
|
|
408
|
+
it.skip('should show close-circle icon on remove buttons', () => {
|
|
409
|
+
const initialValue: TFileItemList = [createFileItem('file1.pdf', '1')]
|
|
410
|
+
|
|
411
|
+
const { container } = render(<PktFileUpload value={initialValue} name={'pktFileUpload'} />)
|
|
412
|
+
|
|
413
|
+
const closeIcon = container.querySelector('pkt-icon[name="close-circle"]')
|
|
414
|
+
expect(closeIcon).toBeInTheDocument()
|
|
415
|
+
})
|
|
416
|
+
})
|
|
417
|
+
|
|
418
|
+
describe('Accessibility', () => {
|
|
419
|
+
it('should have no accessibility violations', async () => {
|
|
420
|
+
const { container } = render(<PktFileUpload multiple name={'pktFileUpload'} />)
|
|
421
|
+
// Note: The hidden file input doesn't have a label, but it's intentionally hidden
|
|
422
|
+
// and the visible UI provides the accessible interaction
|
|
423
|
+
const results = await axe(container, {
|
|
424
|
+
rules: {
|
|
425
|
+
label: { enabled: false },
|
|
426
|
+
},
|
|
427
|
+
})
|
|
428
|
+
|
|
429
|
+
expect(results).toHaveNoViolations()
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
it('should have no accessibility violations with files in queue', async () => {
|
|
433
|
+
const initialValue: TFileItemList = [createFileItem('file1.pdf', '1'), createFileItem('file2.pdf', '2')]
|
|
434
|
+
|
|
435
|
+
const { container } = render(
|
|
436
|
+
<PktFileUpload value={initialValue} multiple name={'pktFileUpload'} onFilesChanged={NOOP} />,
|
|
437
|
+
)
|
|
438
|
+
// Note: The hidden file input doesn't have a label, but it's intentionally hidden
|
|
439
|
+
// and the visible UI provides the accessible interaction
|
|
440
|
+
const results = await axe(container, {
|
|
441
|
+
rules: {
|
|
442
|
+
label: { enabled: false },
|
|
443
|
+
},
|
|
444
|
+
})
|
|
445
|
+
|
|
446
|
+
expect(results).toHaveNoViolations()
|
|
447
|
+
})
|
|
448
|
+
})
|
|
449
|
+
|
|
450
|
+
describe('Form submission values', () => {
|
|
451
|
+
it('should populate file input with selected files for form submission', () => {
|
|
452
|
+
const TestFormComponent = () => {
|
|
453
|
+
const [files, setFiles] = useState<TFileItemList>([])
|
|
454
|
+
return (
|
|
455
|
+
<form>
|
|
456
|
+
<PktFileUpload
|
|
457
|
+
multiple
|
|
458
|
+
name={'pktFileUpload'}
|
|
459
|
+
uploadStrategy="form"
|
|
460
|
+
value={files}
|
|
461
|
+
onFilesChanged={setFiles}
|
|
462
|
+
/>
|
|
463
|
+
</form>
|
|
464
|
+
)
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const { container } = render(<TestFormComponent />)
|
|
468
|
+
|
|
469
|
+
const fileInput = container.querySelector('input[type="file"]') as HTMLInputElement
|
|
470
|
+
makeFilesPropWritable(fileInput)
|
|
471
|
+
|
|
472
|
+
const file1 = createMockFile('file1.pdf')
|
|
473
|
+
const file2 = createMockFile('file2.pdf')
|
|
474
|
+
|
|
475
|
+
// Simulate file selection
|
|
476
|
+
fireEvent.change(fileInput, { target: { files: [file1, file2] } })
|
|
477
|
+
|
|
478
|
+
// Verify that the file input contains the correct files that will be submitted
|
|
479
|
+
expect(fileInput.files).not.toBeNull()
|
|
480
|
+
expect(fileInput.files!.length).toBe(2)
|
|
481
|
+
expect(fileInput.files![0].name).toBe('file1.pdf')
|
|
482
|
+
expect(fileInput.files![1].name).toBe('file2.pdf')
|
|
483
|
+
expect(fileInput).toHaveAttribute('name', 'pktFileUpload')
|
|
484
|
+
})
|
|
485
|
+
|
|
486
|
+
it('should populate file input with single file for form submission', () => {
|
|
487
|
+
const TestFormComponent = () => {
|
|
488
|
+
const [files, setFiles] = useState<TFileItemList>([])
|
|
489
|
+
return (
|
|
490
|
+
<form>
|
|
491
|
+
<PktFileUpload name={'pktFileUpload'} uploadStrategy="form" value={files} onFilesChanged={setFiles} />
|
|
492
|
+
</form>
|
|
493
|
+
)
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const { container } = render(<TestFormComponent />)
|
|
497
|
+
|
|
498
|
+
const fileInput = container.querySelector('input[type="file"]') as HTMLInputElement
|
|
499
|
+
makeFilesPropWritable(fileInput)
|
|
500
|
+
|
|
501
|
+
const file = createMockFile('single-file.pdf')
|
|
502
|
+
|
|
503
|
+
// Simulate file selection
|
|
504
|
+
fireEvent.change(fileInput, { target: { files: [file] } })
|
|
505
|
+
|
|
506
|
+
// Verify that the file input contains the correct file that will be submitted
|
|
507
|
+
expect(fileInput.files).not.toBeNull()
|
|
508
|
+
expect(fileInput.files!.length).toBe(1)
|
|
509
|
+
expect(fileInput.files![0].name).toBe('single-file.pdf')
|
|
510
|
+
expect(fileInput).toHaveAttribute('name', 'pktFileUpload')
|
|
511
|
+
})
|
|
512
|
+
|
|
513
|
+
it('should include file IDs in form FormData with custom upload strategy', () => {
|
|
514
|
+
const initialValue: TFileItemList = [
|
|
515
|
+
createFileItem('file1.pdf', 'custom-id-1'),
|
|
516
|
+
createFileItem('file2.pdf', 'custom-id-2'),
|
|
517
|
+
]
|
|
518
|
+
|
|
519
|
+
const { container } = render(
|
|
520
|
+
<form>
|
|
521
|
+
<PktFileUpload
|
|
522
|
+
value={initialValue}
|
|
523
|
+
name={'pktFileUpload'}
|
|
524
|
+
uploadStrategy="custom"
|
|
525
|
+
id="test-upload"
|
|
526
|
+
onFileUploadRequested={vi.fn()}
|
|
527
|
+
transfers={[]}
|
|
528
|
+
onFilesChanged={NOOP}
|
|
529
|
+
/>
|
|
530
|
+
</form>,
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
const form = container.querySelector('form') as HTMLFormElement
|
|
534
|
+
const formData = new FormData(form)
|
|
535
|
+
|
|
536
|
+
const fileIds = formData.getAll('pktFileUpload')
|
|
537
|
+
expect(fileIds).toHaveLength(2)
|
|
538
|
+
expect(fileIds[0]).toBe('custom-id-1')
|
|
539
|
+
expect(fileIds[1]).toBe('custom-id-2')
|
|
540
|
+
})
|
|
541
|
+
|
|
542
|
+
it('should call onFileUploadRequested when files are selected with custom upload strategy', () => {
|
|
543
|
+
const onFileUploadRequested = vi.fn()
|
|
544
|
+
const TestFormComponent = () => {
|
|
545
|
+
const [files, setFiles] = useState<TFileItemList>([])
|
|
546
|
+
return (
|
|
547
|
+
<form>
|
|
548
|
+
<PktFileUpload
|
|
549
|
+
id={'pktFileUploadId'}
|
|
550
|
+
multiple
|
|
551
|
+
name={'pktFileUpload'}
|
|
552
|
+
uploadStrategy="custom"
|
|
553
|
+
value={files}
|
|
554
|
+
onFilesChanged={setFiles}
|
|
555
|
+
onFileUploadRequested={onFileUploadRequested}
|
|
556
|
+
transfers={[]}
|
|
557
|
+
/>
|
|
558
|
+
</form>
|
|
559
|
+
)
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const { container } = render(<TestFormComponent />)
|
|
563
|
+
|
|
564
|
+
const fileInput = container.querySelector('input[type="file"]') as HTMLInputElement
|
|
565
|
+
const file1 = createMockFile('upload-test.pdf')
|
|
566
|
+
const file2 = createMockFile('upload-test2.pdf')
|
|
567
|
+
|
|
568
|
+
// Simulate file selection
|
|
569
|
+
fireEvent.change(fileInput, { target: { files: [file1, file2] } })
|
|
570
|
+
|
|
571
|
+
// Verify that onFileUploadRequested was called for each file
|
|
572
|
+
expect(onFileUploadRequested).toHaveBeenCalledTimes(2)
|
|
573
|
+
|
|
574
|
+
// Check that it was called with FileItem objects containing the correct files
|
|
575
|
+
const firstCall = onFileUploadRequested.mock.calls[0][0]
|
|
576
|
+
const secondCall = onFileUploadRequested.mock.calls[1][0]
|
|
577
|
+
|
|
578
|
+
expect(firstCall.file).toBe(file1)
|
|
579
|
+
expect(firstCall.fileId).toBeTruthy()
|
|
580
|
+
expect(secondCall.file).toBe(file2)
|
|
581
|
+
expect(secondCall.fileId).toBeTruthy()
|
|
582
|
+
})
|
|
583
|
+
})
|
|
584
|
+
})
|