FileList๋ ์ฌ์์์ ์ธ ์ ์์ง๋ง input.files
ํ
์คํธ ๊ฐ๋ฅํ๊ฒ ๋ง๋ค๋ ค๋ฉด util ๋ฉ์๋๋ฅผ ๊ตฌํํ์ฌ ์์ ํด์ผ ํฉ๋๋ค.
/cc @cpojer
input.files = createFileList(file1, file2, ...)
์ด๋ฉด ์ข์ ๊ฒ์
๋๋ค.
@cpojer ๊ฑฐ๊ธฐ์ ๋ฃ์ ์ค์ File
๊ฐ์ฒด๋ฅผ ์์ฑํ๊ณ ์์ต๋๊น?
input.files
์ฐ๊ธฐ ๊ฐ๋ฅํ๊ฒ ๋ง๋๋ ๊ฒ์ ์๋ง๋ ๋์ ๊ฒ์
๋๋ค. ์๋ง๋ ์์ ๋ฐฐ์ด์ ๋ฐํํ์ฌ ์ค์ค๋ก ์ฑ์ฐ๊ฑฐ๋ fillFileList(input.files, [file])
์ ๊ฐ์ ๊ฒ์ ํ ์ ์์ต๋๋ค.
์ค์ ๋ก๋ ๋ชจ์ ๋ฐ์ดํฐ๋ง ์ฌ์ฉํ๋ฏ๋ก .files
์ ๊ฐ์ฒด ๋ฐฐ์ด์ ์ฑ์๋๋ค. ๊ทธ๋ฌ๋ ๊ทธ๊ฒ์ File ๊ฐ์ฒด๋ก ์๊ตฌํ๋ ๊ฒ์ด ํฉ๋ฆฌ์ ์ด๋ผ๊ณ ์๊ฐํฉ๋๋ค.
์ฌ๊ธฐ์ defineProperty๊ฐ ๊ด์ฐฎ์ ๊ฒ ๊ฐ์ต๋๊น? DOM ์์ฑ์ ์ฌ๊ตฌ์ฑํ ์ ์๋ ์ด์ ๋...
๊ทธ๋๋ ์ค์ ๋ฐ์ดํฐ๋ก ์ค์ FileList๋ฅผ ๋ง๋๋ ๊ฒ์ด ํจ์ฌ ๋ซ์ต๋๋ค.
๋ฐฐ์ด ์ธ๋ฑ์ค๋ฅผ ์ฌ์ฉํ์ฌ FileList ํญ๋ชฉ์ ์ก์ธ์คํ๋ ๊ฒ๋ ๊ฐ๋ฅํด์ผ ํฉ๋๋ค. .item
์ด ํ๋์ ์ก์ธ์คํ๋ ์ ์ผํ ๋ฐฉ๋ฒ์ ์๋๋๋ค.
์๊ฒ ์ต๋๋ค. ๋ช ๊ฐ์ง ์ ์ฌ์ ์ธ ๋ฌธ์ ๊ฐ ์๋ ๊ฒ ๊ฐ์ต๋๋ค.
๊ทธ๋ฌ๋ inputEl.files๋ฅผ ์์ ํ๋ ๊ฒ์ ๋ฌธ์ ๊ฐ ์๋๋๋ค.
๋ค, ์ ๋ง์ ์์ง๋์ด๋ค์๊ฒ ์ ๊ท ๊ณผ์ ๋ณด๋ค Object.defineProperty๋ฅผ ์ฌ์ฉํ๋ผ๊ณ ๋งํ๋ ๊ฒ์ ๊ต์ฅํ์ง ์๋ค๋ ๊ฒ์ ์๋ฏธํ์ง๋ง ์ ๋ ๊ทธ๊ฑธ๋ก ์ด ์ ์์ต๋๋ค.
๊ธ์, ๊ทธ๋ค์ ์ด์จ๋ ์ค์ ๋ธ๋ผ์ฐ์ ์์ ๊ทธ๋ ๊ฒํด์ผํ๋ฏ๋ก ๋์๊ฒ๋ ํฉ๋ฆฌ์ ์ผ๋ก ๋ณด์ ๋๋ค ...
์๋ ํ์ธ์, 1๋ ์ด ๋์ ๊ฒ ๊ฐ์ต๋๋ค. ์ด ๋ฌธ์ ์ ๊ด๋ จํ์ฌ ์งํ ์ํฉ์ด ์๋์ง ๋ฌป๊ณ ์ถ์ต๋๋ค. Jest ํ๋ ์์ํฌ๋ฅผ ์ฌ์ฉํ์ฌ ๋ด๋ถ์ ์ผ๋ก jsdom์ ์ฌ์ฉํ๋ React/Redux ์ฑ์ ํ ์คํธํฉ๋๋ค. ํ๋ ์ด์์ File ๊ฐ์ฒด๋ก FileList๋ฅผ ๋์ ์ผ๋ก ์์ฑํด์ผ ํ๋ ๋ฌธ์ ๊ฐ ์์ต๋๋ค.
lib/jsdom/living/filelist.js๋ฅผ ๋ณด๋ฉด FileList์ ๋ํ ์์ฑ์๊ฐ ์์ง๋ง ํ์ผ์ ์ ๋ฌํ ์ ์๋ ์ต์ ์ด ์๋ค๋ ๊ฒ์ ์ ์ ์์ต๋๋ค. ๋๋ FileList์ File์ ๋ณด์์์ ์ด์ ๋ก ์ฌ์์ ๋ฐ๋ผ ์์ฑ์๊ฐ ์๋ค๋ ๊ฒ์ ์ดํดํ์ง๋ง ์์ฑ์๊ฐ File ๊ฐ์ฒด์ ๋ฐฐ์ด์ ํ์ฉํ๊ฑฐ๋ ์ต์ํ File์ ์ถ๊ฐํ ์ ์๋ ์ถ๊ฐ ๋ฉ์๋(_setItem_์ด๋ผ๊ณ ๊ฐ์ ํด ๋ด ์๋ค)๋ฅผ ํ์ฉํ ์๋๊ฐ ์์ต๋๊น? ํนํ ํ ์คํธ ๋ชฉ์ ์ผ๋ก ๊ฐ์ฒด๋ฅผ ๋ชฉ๋ก์ ์ถ๊ฐํ์๊ฒ ์ต๋๊น?
FileList์ ๊ด๋ จ๋ ๋ ๋ค๋ฅธ ๋ฌธ์ ๋ ์์ต๋๋ค. ๋ด๊ฐ ์ฐฉ๊ฐํ์ง ์๋๋ค๋ฉด NodeList(lib/jsdom/living/node-list.js)์ ๊ฐ์ ๋ฐฐ์ด๊ณผ ๊ฐ์ ๊ฐ์ฒด์ฌ์ผ ํฉ๋๋ค. ์ฆ, ๋ ๊ฐ์ง ๋ฐฉ๋ฒ์ผ๋ก File ๊ฐ์ฒด์ ์ก์ธ์คํ ์ ์์ด์ผ ํฉ๋๋ค.
var fileList = document.getElementById("myfileinput").files;
fileList[0];
fileList.item(0);
ํ์ฌ๋ ๋ฉ์๋๋ฅผ ํตํด์๋ง ์ ๊ทผ์ด ๊ฐ๋ฅํฉ๋๋ค. ์ด๋ NodeList์์์ ๋์ผํ ๋ ผ๋ฆฌ๊ฐ ์ฌ๊ธฐ์ ์ ์ฉ๋์ด์ผ ํจ์ ์๋ฏธํฉ๋๋ค.
FileList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
ํ์ผ ๋ฐฐ์ด์ ์์ฑ์์ ์ ๋ฌํ๋๋ก ํ์ฉํ๋ ค๋ฉด ํ์ผ์ ๋ค์๊ณผ ๊ฐ์ด ์ ์ฅํด์ผ ํฉ๋๋ค.
for (let i = 0; i < files.length; ++i) {
this[i] = files[i];
}
๋๋ ์ด๊ฒ์ ์ด๋ป๊ฒ ํด์ผ ํ๋์ง์ ๋ํด ์ด๋ค ๊ฐ๋ ฅํ ์๊ฒฌ๋ ๊ฐ์ง๊ณ ์์ง ์์ต๋๋ค. ์ด๊ฒ๋ค์ ๋จ์ง ์ ๊ฐ ์ง์ ํ๋ ค๊ณ ํ๋ ๊ฒ์ ๋ ์ ์ค๋ช ํ๊ธฐ ์ํด ์ฌ์ฉํ๋ ์์ผ ๋ฟ์ ๋๋ค.
์ถ๊ฐ ์ง๋ฌธ (์ฌ๊ธฐ์ ์ง๋ฌธํ๊ธฐ ์ ์ ๋ถํ์ํ ๋ฌธ์ ๋ฅผ ์ด๊ณ ์ถ์ง ์์ต๋๋ค):
1.) ๋ฌธ์์ด๋ก ์ ๊ณต๋ ๊ฒฝ๋ก๋ฅผ ์ฌ์ฉํ์ฌ ์ค์ ํ์ผ์์ File ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ ํ ์คํธ ๋ชฉ์ ์ผ๋ก File ์์ฑ์๋ฅผ ์ถ๊ฐํ๋ ๊ฒ์ด ๋์์ด ๋ ๊น์? ์ด๊ฒ์ ํ์ฉํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฐพ์์ต๋๋ค(์ฃ์กํฉ๋๋ค. ๋ ์ด์ ๋งํฌ๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค).
const file = new File('../fixtures/files/test-image.png');
์ด๋ ๊ฒ ํ๋ฉด ์๋์ผ๋ก ๋ง๋ค ํ์ ์์ด ์์ฑ(ํฌ๊ธฐ, lastModified, ์ ํ...)์ด ์๋ ํ์ผ์ด ์์ฑ๋์์ต๋๋ค.
const file = new File([''], 'test-image.png', {
lastModified: 1449505890000,
lastModifiedDate: new Date(1449505890000),
name: "ecp-logo.png",
size: 44320,
type: "image/png",
});
์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์ด๋ป๊ฒ ์๋ํ๋์ง๋ ๋ชจ๋ฅด๊ฒ ์ง๋ง 1๋ ๋๊ฒ ์ ์ง ๊ด๋ฆฌ๋์ง ์์ ์ฌ์ฉ์ ์ค๋จํ๋ค๋ ๊ฒ๋ฟ์ ๋๋ค. ๋ ์ด์ ์ฐพ์ ์ ์๋ ๊ฒ ๊ฐ์ต๋๋ค.
2.) window.URL.createObjectURL์ jsdom์์ ์ง์ํ์ง ์์ต๋๋ค. ๋ณด๊ณ ํด์ผ ํ๋์ง ํ์คํ์ง ์์ต๋๋ค.
jsdom ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฝ๋๋ฅผ ๋ณ๊ฒฝํ์ง ์๊ณ ๋ FileList๋ฅผ ๋ง๋ค ์ ์์์ต๋๋ค.
const createFile = (size = 44320, name = 'ecp-logo.png', type = 'image/png') =>
new File([new ArrayBuffer(size)], name , {
type: type,
});
const createFileList = (file) => {
const fileList = new FileList();
fileList[0] = file;
return fileList;
}
const fileList = createFileList(createFile());
์ด ๊ฒฝ์ฐ jsdom์์ ์ ๊ณตํ๋ FileList์ ์์ฑ์๋ฅผ ํธ์ถํ๋ FileList ๊ฐ์ฒด๋ฅผ ์์ฑํฉ๋๋ค. ๊ทธ ํ ๋ฐฐ์ด ํ๊ธฐ๋ฒ์ ์ฌ์ฉํ์ฌ ํด๋น ๋ชฉ๋ก์ ํ์ผ์ ์ถ๊ฐํฉ๋๋ค. ์ ๊ฒฝ์ฐ์๋ ๋ฐฐ์ด์ 1๊ฐ์ ํ์ผ๋ง ํ์ํ์ง๋ง FileList์ ์ฌ๋ฌ ํ์ผ์ ์ถ๊ฐํ๊ธฐ ์ํด for ๋ฃจํ๋ก ๋ณ๊ฒฝํ ์๋ ์์ต๋๋ค. ๊ธฐ๋ฅ์ด ์ฌ์์ ๋ฐ๋ฅด๋ jsdom์์๋ ์ ๊ณตํ๋ File ์์ฑ์๋ฅผ ํตํด ์ฌ์ฉ์ ์ ์ ์ด๋ฆ/์ ํ/ํฌ๊ธฐ๋ก ํ์ผ์ ๋ง๋ค๊ณ ์์ต๋๋ค.
์ด๊ฒ์ jsdom ํ๊ฒฝ์์ FileList๋ฅผ ์กฐ๋กฑํ๋ ๋ฐฉ๋ฒ์ ์ฐพ๋ ์ฌ๋์๊ฒ ๋์์ด ๋ ์ ์์ง๋ง ์ฌ์ ํ ํ ๊ฐ์ง ๋ฌธ์ ๊ฐ ์์ต๋๋ค. ์ด ๋ฐฉ๋ฒ์ ์ฌ์ฉํ์ฌ ํ์ผ ๋ฐฐ์ด๋ก FileList๋ฅผ ์์ฑํ๋ฉด FileList.item(index) ๋ฐฉ๋ฒ์ ์ฌ์ฉํ์ฌ ๋ฐฐ์ด์์ ํญ๋ชฉ์ ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค. ๊ทธ๋ฌ๋ ๊ทธ ๋ฐฉ๋ฒ๋ ๋ค์๊ณผ ๊ฐ์ด ์ฌ์ ์ํ์ฌ ์์ ํ ์ ์์ต๋๋ค.
const createFileList = (file) => {
const fileList = new FileList();
fileList[0] = file;
fileList.item = index => fileList[index]; // override method functionality
return fileList;
}
๋๋ ์ฌ์ ํ jsdom์ด ํ ์คํธ ๋ชฉ์ ์ผ๋ก ์ด๋ฌํ ๊ธฐ๋ฅ์ ์ฆ์ ์ ๊ณตํ ์ ์๋ค๋ฉด ๋ ์ข์ ๊ฒ์ด๋ผ๊ณ ์๊ฐํฉ๋๋ค.
์๋
์๋ค์,
FileList๋ฅผ ๋ฐํํ๋ $("#selectorID of Upload file")[0].files[0] ๊ฐ์ฒด๋ฅผ ์กฐ๋กฑํ๋ ๋์ ๋ช ๊ฐ์ง ๋ฌธ์ ์ ์ง๋ฉดํ๊ณ ์์ต๋๋ค.
๋๊ตฐ๊ฐ FileList ๊ฐ์ฒด๋ฅผ ๋ง๋๋ ๋ฐ ๋์์ ์ค ์ ์์ต๋๊น? WWW ์ด๋์์๋ ์ฐธ์กฐ ๋งํฌ๋ฅผ ์ฐพ์ ์ ์๊ธฐ ๋๋ฌธ์
ํ์ฌ๋ก์๋ ๋ถ๊ฐ๋ฅํฉ๋๋ค.
Domenic๋, ๊ฐ์ฌํฉ๋๋ค.
ํ์ผ ์ ๋ก๋ ๋ณ๊ฒฝ ์ด๋ฒคํธ์ ๋ํ ํ ์คํธ ์ผ์ด์ค๋ฅผ ์์ฑํด์ผ ํฉ๋๋ค. ํด๋น ํ ์คํธ ์๋๋ฆฌ์ค๋ฅผ ์คํํ๋ ๋ค๋ฅธ ๋ฐฉ๋ฒ.
์ด์ ๋ํ ์ง์ ์ด ์์ต๋๊น?
@niksajanjic ์ด ์ค๋ ๋์์ ์ ๊ณตํ๋ ์ต์์ ์๋ฃจ์
์ ๊ตฌํํ์ต๋๊น?
์ฐธ์กฐ: const file = new File('../fixtures/files/test-image.png');
์๋๋ฉด ๊ทธ์ ๋น์ทํ ๊ฒ์
๋๊น?
์ต์์
@domenic ๋ฌธ์ ์ ๋ํ ํด๋น ํธ์ํฐ ๊ฒ์๋ฌผ์์ ์ฝ๊ฐ์ ๋ฐ๋ ์๊ฒฌ์ด ์๋ ๊ฒ ๊ฐ์ต๋๋ค.
@domenic ์ข์, ๋๋ ์ด๊ฒ์ ๋ํ ๊ธฐ๋ณธ ์์์ ์ค์ ํ์ต๋๋ค. ๋ชจ๋ ๊ฒ์ฌ๊ฐ ์๋ ๊ฒ์ ์๋์ง๋ง ๋ณธ์ง์ ์ผ๋ก @niksajanjic ์ด ๋งํ ๋ด์ฉ์ ๋๋ค.
ํ์ผ ์์ฑ
function createFile(file_path) {
const { mtimeMs: lastModified, size } = fs.statSync(file_path)
return new File(
[new fs.readFileSync(file_path)],
path.basename(file_path),
{
lastModified,
type: mime.lookup(file_path) || '',
}
)
}
ํ์ผ ๋ชฉ๋ก ์ถ๊ฐ
function addFileList(input, file_paths) {
if (typeof file_paths === 'string')
file_paths = [file_paths]
else if (!Array.isArray(file_paths)) {
throw new Error('file_paths needs to be a file path string or an Array of file path strings')
}
const file_list = file_paths.map(fp => createFile(fp))
file_list.__proto__ = Object.create(FileList.prototype)
Object.defineProperty(input, 'files', {
value: file_list,
writeable: false,
})
return input
}
๋ฐ๋ชจ ํ์ผ
/*eslint-disable no-console, no-unused-vars */
/*
https://github.com/jsdom/jsdom/issues/1272
*/
const fs = require('fs')
const path = require('path')
const mime = require('mime-types')
const { JSDOM } = require('jsdom')
const dom = new JSDOM(`
<!DOCTYPE html>
<body>
<input type="file">
</body>
`)
const { window } = dom
const { document, File, FileList } = window
const file_paths = [
'/Users/williamrusnack/Documents/form_database/test/try-input-file.html',
'/Users/williamrusnack/Documents/form_database/test/try-jsdom-input-file.js',
]
function createFile(file_path) {
const { mtimeMs: lastModified, size } = fs.statSync(file_path)
return new File(
[new fs.readFileSync(file_path)],
path.basename(file_path),
{
lastModified,
type: mime.lookup(file_path) || '',
}
)
}
function addFileList(input, file_paths) {
if (typeof file_paths === 'string')
file_paths = [file_paths]
else if (!Array.isArray(file_paths)) {
throw new Error('file_paths needs to be a file path string or an Array of file path strings')
}
const file_list = file_paths.map(fp => createFile(fp))
file_list.__proto__ = Object.create(FileList.prototype)
Object.defineProperty(input, 'files', {
value: file_list,
writeable: false,
})
return input
}
const input = document.querySelector('input')
addFileList(input, file_paths)
for (let i = 0; i < input.files.length; ++i) {
const file = input.files[i]
console.log('file', file)
console.log('file.name', file.name)
console.log('file.size', file.size)
console.log('file.type', file.type)
console.log('file.lastModified', file.lastModified)
console.log()
}
@BebeSparkelSparkel ๋๋ ๊ฑฐ๊ธฐ์ ์๋ ๋์ ํ๊ธฐ ํฌ์คํธ์์ ์ค๋ช ํ ๋ฐฉ์์ผ๋ก ํ ์คํธ๋ฅผ ํ์ต๋๋ค. ๋ถํํ๋ ๋ช ๋ฌ ํ ๋๋ฃ ์ค ํ ๋ช ์ด ํด๋น ์ฝ๋๋ฅผ ๋ณต์ฌํ๋ ค๊ณ ํ์ ๋ ์ต์ ๋ฒ์ ์ jsdom ์์ ์ ์ค๋จํ์ต๋๋ค. ๋ฐ๋ผ์ ๊ทธ ์์ ์์ FileList์ ๋ํ ํ ์คํธ๋ฅผ ์ฃผ์ ์ฒ๋ฆฌํด์ผ ํ์ต๋๋ค. ๊ทธ ์ดํ๋ก ์ฐ๋ฆฌ๋ ์ด๋ฌํ ํ ์คํธ๋ฅผ ์์ฑํ ๋ฐฉ๋ฒ์ ์ฐพ์ ์ ์์๊ณ ์ง๋ ๋ช ๋ฌ ๋์ ์๋ฌด๋ ๊ทธ๊ฒ์ ๋ค์ ์ดํด๋ณด๊ณ ๋ฐฉ๋ฒ์ ์ฐพ์ผ๋ ค๊ณ ํ ์๊ฐ์ด ์์์ต๋๋ค.
@niksajanjic ์
๋ฐ์ดํธ
๊ทธ๋๋ ํ์ํ์ ๋ถ๋ค์ ๋ณด์๊ณ ์ฌ์ฉํ์ธ์~
jsdom ํ๋ก์ ํธ๊ฐ ์๋ฃจ์
์ ์ ๊ณตํ ๋๊น์ง ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ๊ฐ๋จํ ๋์ฐ๋ฏธ ์คํฌ๋ฆฝํธ๋ฅผ ๋ง๋ค์์ต๋๋ค.
https://bitbucket.org/william_rusnack/addfilelist/src/master/
ํ์ผ ํ๋ ์ถ๊ฐ:
const input = document.querySelector('input[type=file]')
addFileList(input, 'path/to/file')
์ฌ๋ฌ ํ์ผ ์ถ๊ฐ:
const input = document.querySelector('input[type=file]')
addFileList(input, [
'path/to/file',
'path/to/another/file',
// add as many as you want
])
npm install https://github.com/BebeSparkelSparkel/addFileList.git
```์๋ฐ์คํฌ๋ฆฝํธ
const { addFileList } = require('addFileList')
## Functions
**addFileList**(input, file_paths)
Effects: puts the file_paths as File object into input.files as a FileList
Returns: input
Arguments:
- input: HTML input element
- file_paths: String or Array of string file paths to put in input.files
`const { addFileList } = require('addFileList')`
## Example
Extract from example.js
```javascript
// add a single file
addFileList(input, 'example.js')
// log input's FileList
console.log(input.files)
// log file properties
const [ file ] = input.files
console.log(file)
console.log(
'\nlastModified', file.lastModified,
'\nname', file.name,
'\nsize', file.size,
'\ntype', file.type,
'\n'
)
๊ฒฐ๊ณผ
$ node example.js
FileList [ File {} ]
File {}
lastModified 1518523506000
name example.js
size 647
type application/javascript
@BebeSparkelSparkel ๊ทํ์ ์ ์ฅ์๊ฐ ์ญ์ ๋ ๊ฒ ๊ฐ์ต๋๊น?
๋น์ทํ ๋ฌธ์ ๊ฐ ์์์ต๋๋ค. FileList
๋ฅผ ์
๋ ฅ์ผ๋ก ๋ฐ๋ ํจ์๋ฅผ ์์ฑํ๊ณ jest๋ฅผ ์ฌ์ฉํ์ฌ ์ด์ ๋ํ ๋จ์ ํ
์คํธ๋ฅผ ์์ฑํ๋ ค๊ณ ํ์ต๋๋ค. ์๋ ๋์ฐ๋ฏธ ํจ์๋ฅผ ์ฌ์ฉํ์ฌ ๋ด ํจ์์ ํจ๊ป ์๋ํ๊ธฐ์ ์ถฉ๋ถํ FileList
๊ฐ์ฒด๋ฅผ ์คํธํํ ์ ์์์ต๋๋ค. ๊ตฌ๋ฌธ์ Flow ์ฃผ์์ด ์๋ ES6์
๋๋ค. ์ค์ FileList
ํด๋์ค์ ๊ธฐ๋ฅ์ ๊ฐ์ฅํ๊ธฐ ๋๋ฌธ์ ๋ชจ๋ ์ํฉ์์ ์๋ํ๋ค๋ ์ฝ์์ ์์ต๋๋ค...
const createFileList = (files: Array<File>): FileList => {
return {
length: files.length,
item: (index: number) => files[index],
* [Symbol.iterator]() {
for (let i = 0; i < files.length; i++) {
yield files[i];
}
},
...files,
};
};
ํ๋ก ํธ์๋์์ '์ค์ ' FileList
๊ตฌ์ฑํ๊ธฐ ์ํด ๋ค์๊ณผ ๊ฐ์ ์์
์ ์ํํ ์ ์์ต๋๋ค.
export const makeFileList = files => {
const reducer = (dataTransfer, file) => {
dataTransfer.items.add(file)
return dataTransfer
}
return files.reduce(reducer, new DataTransfer()).files
}
์ฐธ์กฐ: https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer
๋ถํํ๋ jsdom
๋ ์ค์ ๋ก DataTransfer
์์ง ์ง์ํ์ง ์๋ ๊ฒ ๊ฐ์ผ๋ฏ๋ก ๋ด ํ
์คํธ์์๋ ์๋ํ์ง ์์ต๋๋ค.
๋ค๋ฅธ ์ฐธ์กฐ:
์์ค๋ฅผ ์กฐ๊ธ ์ฐพ์๋ณด๋ exports.FileList = require("./generated/FileList").interface;
~ํ์ง๋ง GitHub์์ ./generated
๋น๋๋ก ๋๋๋ ์์ค๋ฅผ ์ฐพ๋ ์์น๊ฐ ๋ช
ํํ์ง ์์์ต๋๋ค ~
npm ํจํค์ง์ ์ฃผ์ ๋ด๋ณด๋ด๊ธฐ ๋ ./lib/api.js
์ด๋ฉฐ ๋งค์ฐ ์์ ๊ณต๊ฐ API๋ฅผ ๋ด๋ณด๋
๋๋ค.
exports.JSDOM = JSDOM;
exports.VirtualConsole = VirtualConsole;
exports.CookieJar = CookieJar;
exports.ResourceLoader = ResourceLoader;
exports.toughCookie = toughCookie;
ํ์ง๋ง ๋ด ./node_modules/jsdom/lib/jsdom
๋๋ ํ ๋ฆฌ๋ฅผ ๋ณด๋ฉด.. ./node_modules/jsdom/lib/jsdom/living/file-api
ํฌํจํ์ฌ ๋ชจ๋ ๋ด๋ถ/๊ตฌํ ํ์ผ๋ ๊ฑฐ๊ธฐ์ ์์์ ์ ์ ์์ต๋๋ค.
Blob-impl.js File-impl.js FileList-impl.js FileReader-impl.js
FileList-impl.js ์๋ FileList
API๋ฅผ ์ง์ํ๋ ์ค์ JS ๊ตฌํ์ด ํฌํจ๋์ด ์์ต๋๋ค.
์ด์ ./node_modules/jsdom/lib/jsdom/living/generated/FileList.js
๋ฅผ ๋ณด๋ฉด ๋๋ฌด ์ต์ํ ๊ฒ์ ํฌํจํ์ฌ ์ ์์ ์ธ ์ฌ์ฉ์ ํตํด ๋ณด๊ฒ ๋๋ ์ค์ ์์ฑ๋ '๊ณต๊ฐ API'๋ฅผ ๋ณผ ์ ์์ต๋๋ค.
class FileList {
constructor() {
throw new TypeError("Illegal constructor");
}
์ด ํ์ผ์ iface.interface
ํค๋ง ์ฌ์ฉํ๋ '์ผ๋ฐ' ๊ณต๊ฐ API ๋
ธ์ถ์ ํตํด ์ป์ ์ ์๋ ๊ฒ๋ณด๋ค ํจ์ฌ ๋ ๋ง์ ๊ธฐ๋ฅ์ ํฌํจํ๋ module.exports = iface;
๋ด๋ณด๋
๋๋ค. ๋ฐ๋ผ์ require("./generated/FileList")
์ง์ ์ฌ์ฉํ๋ฉด ์ฌ๋ฏธ์๋ ์ผ์ ํ ์ ์์ต๋๋ค. ๊ตฌํ ์ธ๋ถ ์ ๋ณด๋ฅผ ์ ๊ฑฐํ๋ฉด ๋ค์๊ณผ ๊ฐ์ ์ธํฐํ์ด์ค๊ฐ ์์ต๋๋ค.
const iface = {
_mixedIntoPredicates: [],
is(obj) {..snip..},
isImpl(obj) {..snip..},
convert(obj, { context = "The provided value" } = {}) {..snip..},
create(constructorArgs, privateData) {..snip..},
createImpl(constructorArgs, privateData) {..snip..},
_internalSetup(obj) {},
setup(obj, constructorArgs, privateData) {...snip...},
interface: FileList,
expose: {
Window: { FileList },
Worker: { FileList }
}
}; // iface
์ด์ ๋ ๋ง์ ๊ถํ์ด ์๋ค๋ ๊ฒ์ ์์์ผ๋ฏ๋ก jsdom
์ ๋ค๋ฅธ ์์ญ์์ ์ด๋ป๊ฒ ์ก์ธ์ค
ํ ๋ฒ ๋ด ์ดฌ์ HTMLInputElement-impl
, ๊ทธ๊ฒ์ ์ฌ์ฉํ๋ ๊ฒ FileList.createImpl()
,ํ์ง๋ง ๋ถํํ๊ฒ๋ ์ค์ ๋ก ์ด๋ป๊ฒ PARAMS๋ฅผ ์ฌ์ฉํ๋ ์ฐ๋ฆฌ๋ฅผ ํ์ํ์ง ์์ต๋๋ค :
createImpl
๋ ๋ด๋ณด๋ธ iface
์์ setup
์ฃผ์์ ์์ ๋ํผ์
๋๋ค.
createImpl(constructorArgs, privateData) {
let obj = Object.create(FileList.prototype);
obj = this.setup(obj, constructorArgs, privateData);
return utils.implForWrapper(obj);
},
์ฝ์์์ ์ด๊ฒ์ ๊ฐ์ง๊ณ ๋๋ค ๋ณด๋ฉด FileListImpl
๊ฐ ๋ท๋ฐ์นจ๋๋ Array
์์์ ํํ API๊ฐ ์๋ ๊ฒ ๊ฐ์ต๋๋ค. ๋ฐ๋ผ์ ๋ค์๊ณผ ๊ฐ์ ์์
์ ์ํํ ์ ์์ต๋๋ค.
var flist = require('./node_modules/jsdom/lib/jsdom/living/generated/FileList.js')
var myFileListImpl = flist.createImpl()
myFileListImpl.push('aa')
์ฌ๊ธฐ์๋ Symbol(wrapper)
์์ฑ์ด ์์ผ๋ฉฐ ์ก์ธ์คํ๋ ค๋ฉด ./node_modules/jsdom/lib/jsdom/living/generated/utils.js:37
๋ฅผ ์ฌ์ฉํด์ผ ํฉ๋๋ค.
var utils = require('./node_modules/jsdom/lib/jsdom/living/generated/utils.js')
var wrapper = myFileListImpl[utils.wrapperSymbol]
๋ด๋ณด๋ธ iface
์๋ convert
ํจ์๊ฐ ์์ผ๋ฉฐ throw new TypeError(
${context}๋ 'FileList' ์ ํ์ด ์๋๋๋ค. );
์ ๊ณต๋ ๊ฐ์ฒด๊ฐ ์๋ ๊ฒฝ์ฐ FileList
. ์ฐ๋ฆฌ๋ ์ด๊ฒ์ ํ
์คํธํ๋ ๋ฐ ์ฌ์ฉํ ์ ์์ต๋๋ค.
์์ myFileListImpl
์์ ํธ์ถํ๋ฉด ์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค.
flist.convert(myFileListImpl)
์์์ ์ถ์ถํ wrapper
๋ฅผ ์ฌ์ฉํ๋ฉด ์ค๋ฅ๊ฐ ๋ฐ์ํ์ง ์๋๋ค๋ ๊ฒ์ ์ ์ ์์ต๋๋ค.
flist.convert(myFileListImpl[utils.wrapperSymbol])
์ด๋ฅผ ํตํด myFileListImpl
์์ ํ๊ณ ํ์ฉ ๊ฐ๋ฅํ FileList
๊ฐ์ฒด๋ฅผ ๋ค์ ๊ฐ์ ธ์ ํ์ํ ๊ณณ์ ์ ๋ฌํ ์ ์์ต๋๋ค. ์์ ํ ์๋ํ๋ ์์ (์ด์ ์ฝ๋ ๋์ util.wrapperForImpl()
์ฌ์ฉ):
var _FileList = require('./node_modules/jsdom/lib/jsdom/living/generated/FileList.js')
var utils = require('./node_modules/jsdom/lib/jsdom/living/generated/utils.js')
var myMutableFileListImpl = _FileList.createImpl()
myMutableFileListImpl.length // 0
myMutableFileListImpl.push(new File([], 'a.jpg'))
myMutableFileListImpl.length // 1
var myFileList = utils.wrapperForImpl(myMutableFileListImpl)
_FileList.convert(myFileList) // no error
myFileList.length // 1
myFileList[0] // the File{} object
์ด์ ๊ทธ ์ง์์ผ๋ก ์๋ ๋ธ๋ผ์ฐ์ ํด๊ฒฐ ๋ฐฉ๋ฒ ํดํน์ jsdom ํ ์คํธ ๋ฒ์ ์ ๊ตฌํํ ์ ์์ต๋๋ค(๊ฒ์ผ๋ฆ์ ์ํด ImmutableJS ์์ ๊ตฌํ๋จ).
import { Map, Record } from 'immutable'
import jsdomFileList from 'jsdom/lib/jsdom/living/generated/FileList'
import { wrapperForImpl } from 'jsdom/lib/jsdom/living/generated/utils'
// Note: relying on internal API's is super hacky, and will probably break
// As soon as we can, we should use whatever the proper outcome from this issue is:
// https://github.com/jsdom/jsdom/issues/1272#issuecomment-486088445
export const makeFileList = files => {
const reducer = (fileListImpl, file) => {
fileListImpl.push(file)
return fileListImpl
}
const fileListImpl = files.reduce(reducer, jsdomFileList.createImpl())
return wrapperForImpl(fileListImpl)
}
export class DataTransferStub extends Record({ items: Map() }) {
get files() {
return makeFileList(this.items.toList().toArray())
}
}
export const stubGlobalDataTransfer = () => {
global.DataTransfer = DataTransferStub
}
export const restoreGlobalDataTransfer = () => {
global.DataTransfer = undefined
}
๊ทธ๋ฐ ๋ค์ ๋ด ava ํ ์คํธ์์ ์ฌ์ฉํ ์ ์๋๋ก ์๋์ผ๋ก ์ ์ญ ๋ณ์์ ์ฐ๊ฒฐํฉ๋๋ค.
import {
restoreGlobalDataTransfer,
stubGlobalDataTransfer,
} from ../helpers/jsdom-helpers'
test.before(t => {
stubGlobalDataTransfer()
})
test.after(t => {
restoreGlobalDataTransfer()
})
๋ชจ๋ ์ต์ ๋ธ๋ผ์ฐ์ (IE <= 11 ์๋)๋ ์ด์ input.files๋ฅผ FileList https://stackoverflow.com/a/47522812/2744776์ผ๋ก ์ค์ ํ๋ ๊ฒ์ ์ง์ํฉ๋๋ค.
์ด๊ฒ์ ์ต๊ทผ ๋ฆด๋ฆฌ์ค์์ ๋ณ๊ฒฝ๋ ๊ฒ ๊ฐ์ต๋๋ค. ๋๋ฅผ ์ํด ์ผํ ๊ฒ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
const jsdomUtils = require('jsdom/lib/jsdom/living/generated/utils');
const jsdomFileList = require('jsdom/lib/jsdom/living/generated/FileList');
function makeFileList(...files) {
const impl = jsdomFileList.createImpl(window);
const ret = Object.assign([...files], {
item: (ix) => ret[ix],
[jsdomUtils.implSymbol]: impl,
});
impl[jsdomUtils.wrapperSymbol] = ret;
Object.setPrototypeOf(ret, FileList.prototype);
return ret;
}
Jest๋ฅผ ํตํด JSDOM์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ํ ์คํธ VM ์ธ๋ถ์ ๋ด๋ถ๊ฐ ํ์ํ์ง ํ์ธํด์ผ ํฉ๋๋ค. ๋ค์๊ณผ ๊ฐ์ด ์ฌ์ฉ์ ์ง์ ํ ์คํธ ํ๊ฒฝ์ ๋ง๋ค์์ต๋๋ค.
const JsdomEnvironment = require('jest-environment-jsdom');
/** See jsdom/jsdom#1272 */
class EnvWithSyntheticFileList extends JsdomEnvironment {
async setup() {
await super.setup();
this.global.jsdomUtils = require('jsdom/lib/jsdom/living/generated/utils');
this.global.jsdomFileList = require('jsdom/lib/jsdom/living/generated/FileList');
}
}
module.exports = EnvWithSyntheticFileList;
๊ทธ๋์ '์ธ๋ถ' ์์ ํ์ ์ ๊ทผํ ์ ์์์ต๋๋ค.
์ด๊ฒ์ ์ต๊ทผ ๋ฆด๋ฆฌ์ค์์ ๋ณ๊ฒฝ๋ ๊ฒ ๊ฐ์ต๋๋ค. ๋๋ฅผ ์ํด ์ผํ ๊ฒ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
const jsdomUtils = require('jsdom/lib/jsdom/living/generated/utils'); const jsdomFileList = require('jsdom/lib/jsdom/living/generated/FileList'); function makeFileList(...files) { const impl = jsdomFileList.createImpl(window); const ret = Object.assign([...files], { item: (ix) => ret[ix], [jsdomUtils.implSymbol]: impl, }); impl[jsdomUtils.wrapperSymbol] = ret; Object.setPrototypeOf(ret, FileList.prototype); return ret; }
Jest๋ฅผ ํตํด JSDOM์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ํ ์คํธ VM ์ธ๋ถ์ ๋ด๋ถ๊ฐ ํ์ํ์ง ํ์ธํด์ผ ํฉ๋๋ค. ๋ค์๊ณผ ๊ฐ์ด ์ฌ์ฉ์ ์ง์ ํ ์คํธ ํ๊ฒฝ์ ๋ง๋ค์์ต๋๋ค.
const JsdomEnvironment = require('jest-environment-jsdom'); /** See jsdom/jsdom#1272 */ class EnvWithSyntheticFileList extends JsdomEnvironment { async setup() { await super.setup(); this.global.jsdomUtils = require('jsdom/lib/jsdom/living/generated/utils'); this.global.jsdomFileList = require('jsdom/lib/jsdom/living/generated/FileList'); } } module.exports = EnvWithSyntheticFileList;
๊ทธ๋์ '์ธ๋ถ' ์์ ํ์ ์ ๊ทผํ ์ ์์์ต๋๋ค.
์ด๊ฒ์ ๋๋ฅผ ๊ฐ๊น์ ์ง๋ง ๋ด๋ถ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ํต๊ณผํ ์ ์์ต๋๋ค.
export.is = ๊ฐ => {
return utils.isObject(๊ฐ) && utils.hasOwn(๊ฐ, implSymbol) && ๊ฐ[implSymbol] Impl.implementation์ instanceof;
};
TypeError: 'HTMLInputElement'์์ 'files' ์์ฑ์ ์ค์ ํ์ง ๋ชปํ์ต๋๋ค. ์ ๊ณต๋ ๊ฐ์ด 'FileList' ์ ํ์ด ์๋๋๋ค.
๋ณด๋ธ ์๊ฐ. ์ํผ ์ค๋ง
์ด๊ฒ์ ์ต๊ทผ ๋ฆด๋ฆฌ์ค์์ ๋ณ๊ฒฝ๋ ๊ฒ ๊ฐ์ต๋๋ค. ๋๋ฅผ ์ํด ์ผํ ๊ฒ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
const jsdomUtils = require('jsdom/lib/jsdom/living/generated/utils'); const jsdomFileList = require('jsdom/lib/jsdom/living/generated/FileList'); function makeFileList(...files) { const impl = jsdomFileList.createImpl(window); const ret = Object.assign([...files], { item: (ix) => ret[ix], [jsdomUtils.implSymbol]: impl, }); impl[jsdomUtils.wrapperSymbol] = ret; Object.setPrototypeOf(ret, FileList.prototype); return ret; }
Jest๋ฅผ ํตํด JSDOM์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ํ ์คํธ VM ์ธ๋ถ์ ๋ด๋ถ๊ฐ ํ์ํ์ง ํ์ธํด์ผ ํฉ๋๋ค. ๋ค์๊ณผ ๊ฐ์ด ์ฌ์ฉ์ ์ง์ ํ ์คํธ ํ๊ฒฝ์ ๋ง๋ค์์ต๋๋ค.
const JsdomEnvironment = require('jest-environment-jsdom'); /** See jsdom/jsdom#1272 */ class EnvWithSyntheticFileList extends JsdomEnvironment { async setup() { await super.setup(); this.global.jsdomUtils = require('jsdom/lib/jsdom/living/generated/utils'); this.global.jsdomFileList = require('jsdom/lib/jsdom/living/generated/FileList'); } } module.exports = EnvWithSyntheticFileList;
๊ทธ๋์ '์ธ๋ถ' ์์ ํ์ ์ ๊ทผํ ์ ์์์ต๋๋ค.
์ด๊ฒ์ ๋๋ฅผ ๊ฐ๊น์ ์ง๋ง ๋ด๋ถ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ํต๊ณผํ ์ ์์ต๋๋ค.
export.is = ๊ฐ => {
return utils.isObject(๊ฐ) && utils.hasOwn(๊ฐ, implSymbol) && ๊ฐ[implSymbol] Impl.implementation์ instanceof;
};TypeError: 'HTMLInputElement'์์ 'files' ์์ฑ์ ์ค์ ํ์ง ๋ชปํ์ต๋๋ค. ์ ๊ณต๋ ๊ฐ์ด 'FileList' ์ ํ์ด ์๋๋๋ค.
๋ณด๋ธ ์๊ฐ. ์ํผ ์ค๋ง
์๋ก์ด ๋๋๋ก ๋ค์ ์์ํ ํ ๋ฌธ์ ๋ฅผ ์ฐพ์์ต๋๋ค. ์ฌํํผ ๋๋ ๊ธฐํธ์ ๋ํ ์ฐธ์กฐ๋ฅผ ๋ฒ๋ ธ๋ 2๊ฐ์ ๊ฐ๋ณ jsdom ํ๊ฒฝ์ ์คํํ์ต๋๋ค.
๊ฐ์ฅ ์ ์ฉํ ๋๊ธ
jsdom ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฝ๋๋ฅผ ๋ณ๊ฒฝํ์ง ์๊ณ ๋ FileList๋ฅผ ๋ง๋ค ์ ์์์ต๋๋ค.
์ด ๊ฒฝ์ฐ jsdom์์ ์ ๊ณตํ๋ FileList์ ์์ฑ์๋ฅผ ํธ์ถํ๋ FileList ๊ฐ์ฒด๋ฅผ ์์ฑํฉ๋๋ค. ๊ทธ ํ ๋ฐฐ์ด ํ๊ธฐ๋ฒ์ ์ฌ์ฉํ์ฌ ํด๋น ๋ชฉ๋ก์ ํ์ผ์ ์ถ๊ฐํฉ๋๋ค. ์ ๊ฒฝ์ฐ์๋ ๋ฐฐ์ด์ 1๊ฐ์ ํ์ผ๋ง ํ์ํ์ง๋ง FileList์ ์ฌ๋ฌ ํ์ผ์ ์ถ๊ฐํ๊ธฐ ์ํด for ๋ฃจํ๋ก ๋ณ๊ฒฝํ ์๋ ์์ต๋๋ค. ๊ธฐ๋ฅ์ด ์ฌ์์ ๋ฐ๋ฅด๋ jsdom์์๋ ์ ๊ณตํ๋ File ์์ฑ์๋ฅผ ํตํด ์ฌ์ฉ์ ์ ์ ์ด๋ฆ/์ ํ/ํฌ๊ธฐ๋ก ํ์ผ์ ๋ง๋ค๊ณ ์์ต๋๋ค.
์ด๊ฒ์ jsdom ํ๊ฒฝ์์ FileList๋ฅผ ์กฐ๋กฑํ๋ ๋ฐฉ๋ฒ์ ์ฐพ๋ ์ฌ๋์๊ฒ ๋์์ด ๋ ์ ์์ง๋ง ์ฌ์ ํ ํ ๊ฐ์ง ๋ฌธ์ ๊ฐ ์์ต๋๋ค. ์ด ๋ฐฉ๋ฒ์ ์ฌ์ฉํ์ฌ ํ์ผ ๋ฐฐ์ด๋ก FileList๋ฅผ ์์ฑํ๋ฉด FileList.item(index) ๋ฐฉ๋ฒ์ ์ฌ์ฉํ์ฌ ๋ฐฐ์ด์์ ํญ๋ชฉ์ ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค. ๊ทธ๋ฌ๋ ๊ทธ ๋ฐฉ๋ฒ๋ ๋ค์๊ณผ ๊ฐ์ด ์ฌ์ ์ํ์ฌ ์์ ํ ์ ์์ต๋๋ค.
๋๋ ์ฌ์ ํ jsdom์ด ํ ์คํธ ๋ชฉ์ ์ผ๋ก ์ด๋ฌํ ๊ธฐ๋ฅ์ ์ฆ์ ์ ๊ณตํ ์ ์๋ค๋ฉด ๋ ์ข์ ๊ฒ์ด๋ผ๊ณ ์๊ฐํฉ๋๋ค.