-
Notifications
You must be signed in to change notification settings - Fork 239
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(upload)!: replace element properties (#794)
BREAKING CHANGE: `init` parameter has been removed from `userEvent.upload`.
- Loading branch information
1 parent
df75e5f
commit 4873895
Showing
7 changed files
with
175 additions
and
60 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,19 @@ | ||
// FileList can not be created per constructor. | ||
|
||
export function createFileList(files: File[]): FileList { | ||
const f = [...files] | ||
const list: FileList & Iterable<File> = { | ||
...files, | ||
length: files.length, | ||
item: (index: number) => list[index], | ||
[Symbol.iterator]: function* nextFile() { | ||
for (let i = 0; i < list.length; i++) { | ||
yield list[i] | ||
} | ||
}, | ||
} | ||
list.constructor = FileList | ||
Object.setPrototypeOf(list, FileList.prototype) | ||
Object.freeze(list) | ||
|
||
Object.setPrototypeOf(f, FileList.prototype) | ||
|
||
return f as unknown as FileList | ||
return list | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
// It is not possible to create a real FileList programmatically. | ||
// Therefore assigning `files` property with a programmatically created FileList results in an error. | ||
// Just assigning the property (as per fireEvent) breaks the interweaving with the `value` property. | ||
|
||
const fakeFiles = Symbol('files and value properties are mocked') | ||
|
||
declare global { | ||
interface HTMLInputElement { | ||
[fakeFiles]?: { | ||
restore: () => void | ||
} | ||
} | ||
} | ||
|
||
export function setFiles( | ||
el: HTMLInputElement & {type: 'file'}, | ||
files: FileList, | ||
) { | ||
el[fakeFiles]?.restore() | ||
|
||
const objectDescriptors = Object.getOwnPropertyDescriptors(el) | ||
const prototypeDescriptors = Object.getOwnPropertyDescriptors( | ||
Object.getPrototypeOf(el), | ||
) | ||
|
||
function restore() { | ||
Object.defineProperties(el, { | ||
files: { | ||
...prototypeDescriptors.files, | ||
...objectDescriptors.files, | ||
}, | ||
value: { | ||
...prototypeDescriptors.value, | ||
...objectDescriptors.value, | ||
}, | ||
type: { | ||
...prototypeDescriptors.type, | ||
...objectDescriptors.type, | ||
}, | ||
}) | ||
} | ||
el[fakeFiles] = {restore} | ||
|
||
Object.defineProperties(el, { | ||
files: { | ||
...prototypeDescriptors.files, | ||
...objectDescriptors.files, | ||
get: () => files, | ||
}, | ||
value: { | ||
...prototypeDescriptors.value, | ||
...objectDescriptors.value, | ||
get: () => (files.length ? `C:\\fakepath\\${files[0].name}` : ''), | ||
set(v: string) { | ||
if (v === '') { | ||
restore() | ||
} else { | ||
objectDescriptors.value.set?.call(el, v) | ||
} | ||
}, | ||
}, | ||
// eslint-disable-next-line accessor-pairs | ||
type: { | ||
...prototypeDescriptors.type, | ||
...objectDescriptors.type, | ||
set(v: string) { | ||
if (v !== 'file') { | ||
restore() | ||
// In the browser the value will be empty. | ||
// In Jsdom the value will be the same as | ||
// before this element became file input - which might be empty. | ||
;(el as HTMLInputElement).type = v | ||
} | ||
}, | ||
}, | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import {createFileList} from '#src/utils' | ||
import {setFiles} from '#src/utils/edit/setFiles' | ||
import {setup} from '#testHelpers/utils' | ||
|
||
test('set files', () => { | ||
const {element} = setup<HTMLInputElement & {type: 'file'}>( | ||
`<input type="file"/>`, | ||
) | ||
|
||
const list = createFileList([new File(['foo'], 'foo.txt')]) | ||
setFiles(element, list) | ||
|
||
expect(element).toHaveProperty('files', list) | ||
expect(element).toHaveValue('C:\\fakepath\\foo.txt') | ||
}) | ||
|
||
test('switching type resets value', () => { | ||
const {element} = setup<HTMLInputElement>(`<input type="text"/>`) | ||
|
||
element.type = 'file' | ||
|
||
expect(element).toHaveValue('') | ||
|
||
const list = createFileList([new File(['foo'], 'foo.txt')]) | ||
setFiles(element as HTMLInputElement & {type: 'file'}, list) | ||
|
||
element.type = 'file' | ||
|
||
expect(element).toHaveValue('C:\\fakepath\\foo.txt') | ||
|
||
element.type = 'text' | ||
|
||
expect(element).toHaveValue('') | ||
expect(element).toHaveProperty('type', 'text') | ||
}) | ||
|
||
test('setting value resets `files`', () => { | ||
const {element} = setup<HTMLInputElement & {type: 'file'}>( | ||
`<input type="file"/>`, | ||
) | ||
|
||
const list = createFileList([new File(['foo'], 'foo.txt')]) | ||
setFiles(element, list) | ||
|
||
// Everything but an empty string throws an error in the browser | ||
expect(() => { | ||
element.value = 'foo' | ||
}).toThrow() | ||
|
||
expect(element).toHaveProperty('files', list) | ||
|
||
element.value = '' | ||
|
||
expect(element).toHaveProperty('files', expect.objectContaining({length: 0})) | ||
}) | ||
|
||
test('is save to call multiple times', () => { | ||
const {element} = setup<HTMLInputElement & {type: 'file'}>( | ||
`<input type="file"/>`, | ||
) | ||
|
||
const list = createFileList([new File(['foo'], 'foo.txt')]) | ||
setFiles(element, list) | ||
setFiles(element, list) | ||
|
||
expect(element).toHaveValue('C:\\fakepath\\foo.txt') | ||
element.value = '' | ||
expect(element).toHaveValue('') | ||
}) |