Jsdom: FileList์— ํŒŒ์ผ์„ ์ถ”๊ฐ€ํ•˜๋Š” ์ผ๋ถ€ ๋ฉ”์†Œ๋“œ ๋…ธ์ถœ

์— ๋งŒ๋“  2015๋…„ 10์›” 23์ผ  ยท  30์ฝ”๋ฉ˜ํŠธ  ยท  ์ถœ์ฒ˜: jsdom/jsdom

FileList๋Š” ์‚ฌ์–‘์—์„œ ์“ธ ์ˆ˜ ์—†์ง€๋งŒ input.files ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋งŒ๋“ค๋ ค๋ฉด util ๋ฉ”์„œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•˜์—ฌ ์ˆ˜์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

/cc @cpojer

๊ฐ€์žฅ ์œ ์šฉํ•œ ๋Œ“๊ธ€

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์ด ํ…Œ์ŠคํŠธ ๋ชฉ์ ์œผ๋กœ ์ด๋Ÿฌํ•œ ๊ธฐ๋Šฅ์„ ์ฆ‰์‹œ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด ๋” ์ข‹์„ ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

๋ชจ๋“  30 ๋Œ“๊ธ€

input.files = createFileList(file1, file2, ...) ์ด๋ฉด ์ข‹์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

@cpojer ๊ฑฐ๊ธฐ์— ๋„ฃ์„ ์‹ค์ œ File ๊ฐœ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๊นŒ?

input.files ์“ฐ๊ธฐ ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋งŒ๋“œ๋Š” ๊ฒƒ์€ ์•„๋งˆ๋„ ๋‚˜์  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์•„๋งˆ๋„ ์›์‹œ ๋ฐฐ์—ด์„ ๋ฐ˜ํ™˜ํ•˜์—ฌ ์Šค์Šค๋กœ ์ฑ„์šฐ๊ฑฐ๋‚˜ fillFileList(input.files, [file]) ์™€ ๊ฐ™์€ ๊ฒƒ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์‹ค์ œ๋กœ๋Š” ๋ชจ์˜ ๋ฐ์ดํ„ฐ๋งŒ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ .files ์— ๊ฐœ์ฒด ๋ฐฐ์—ด์„ ์ฑ„์›๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๊ทธ๊ฒƒ์„ File ๊ฐ์ฒด๋กœ ์š”๊ตฌํ•˜๋Š” ๊ฒƒ์ด ํ•ฉ๋ฆฌ์ ์ด๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์„œ defineProperty๊ฐ€ ๊ดœ์ฐฎ์„ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๊นŒ? DOM ์†์„ฑ์„ ์žฌ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ๋Š” ์ด์œ ๋Š”...

๊ทธ๋ž˜๋„ ์‹ค์ œ ๋ฐ์ดํ„ฐ๋กœ ์‹ค์ œ FileList๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด ํ›จ์”ฌ ๋‚ซ์Šต๋‹ˆ๋‹ค.

๋ฐฐ์—ด ์ธ๋ฑ์Šค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ FileList ํ•ญ๋ชฉ์— ์•ก์„ธ์Šคํ•˜๋Š” ๊ฒƒ๋„ ๊ฐ€๋Šฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. .item ์ด ํ•„๋“œ์— ์•ก์„ธ์Šคํ•˜๋Š” ์œ ์ผํ•œ ๋ฐฉ๋ฒ•์€ ์•„๋‹™๋‹ˆ๋‹ค.

์•Œ๊ฒ ์Šต๋‹ˆ๋‹ค. ๋ช‡ ๊ฐ€์ง€ ์ž ์žฌ์ ์ธ ๋ฌธ์ œ๊ฐ€ ์žˆ๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • FileList์— ์ƒ‰์ธํ™”๋œ ์•ก์„ธ์Šค๊ฐ€ ์ œ๋Œ€๋กœ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค(๋ฒ„๊ทธ).
  • ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด FileList ๊ฐœ์ฒด๋ฅผ ์ƒ์„ฑํ•  ๋ฐฉ๋ฒ•์ด ์—†์Šต๋‹ˆ๋‹ค(์›น ํ”Œ๋žซํผ ๊ธฐ๋Šฅ ์ฐจ์ด).

๊ทธ๋Ÿฌ๋‚˜ 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 ํ™˜๊ฒฝ์„ ์‹คํ–‰ํ–ˆ์Šต๋‹ˆ๋‹ค.

์ด ํŽ˜์ด์ง€๊ฐ€ ๋„์›€์ด ๋˜์—ˆ๋‚˜์š”?
0 / 5 - 0 ๋“ฑ๊ธ‰