Skip to content

Commit 9c8be3a

Browse files
author
Roland Groza
committed
fix: use .getAsFile() API if file entry is null or if entry is a dir
- throw if reading dir or file fails using FileSystem API - return original File object instead of cloning - improve code readability
1 parent 02103be commit 9c8be3a

File tree

4 files changed

+179
-195
lines changed

4 files changed

+179
-195
lines changed

src/file-selector.spec.ts

Lines changed: 128 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ it('should return an empty array if the passed event is not a DragEvent', async
1313
expect(files).toHaveLength(0);
1414
});
1515

16-
it('should return the DataTransfer {files} if the passed event is a DragEvent', async () => {
16+
it('should return the evt {target} {files} if the passed event is an input evt', async () => {
1717
const name = 'test.json';
1818
const mockFile = createFile(name, {ping: true}, {
1919
type: 'application/json'
2020
});
21-
const evt = dragEvtFromFiles(mockFile);
21+
const evt = inputEvtFromFiles(mockFile);
2222

2323
const files = await fromEvent(evt);
2424
expect(files).toHaveLength(1);
@@ -33,27 +33,13 @@ it('should return the DataTransfer {files} if the passed event is a DragEvent',
3333
expect(file.path).toBe(name);
3434
});
3535

36-
it('should return the evt {target} {files} if the passed event is an input evt', async () => {
37-
const name = 'test.json';
38-
const mockFile = createFile(name, {ping: true}, {
39-
type: 'application/json'
40-
});
41-
const evt = inputEvtFromFiles(mockFile);
42-
36+
it('should return an empty array if the evt {target} has no {files} prop', async () => {
37+
const evt = inputEvtFromFiles();
4338
const files = await fromEvent(evt);
44-
expect(files).toHaveLength(1);
45-
expect(files.every(file => file instanceof File)).toBe(true);
46-
47-
const [file] = files as FileWithPath[];
48-
49-
expect(file.name).toBe(mockFile.name);
50-
expect(file.type).toBe(mockFile.type);
51-
expect(file.size).toBe(mockFile.size);
52-
expect(file.lastModified).toBe(mockFile.lastModified);
53-
expect(file.path).toBe(name);
39+
expect(files).toHaveLength(0);
5440
});
5541

56-
it('uses the DataTransfer {items} instead of {files} if it exists', async () => {
42+
it('should return files from DataTransfer {items} if the passed event is a DragEvent', async () => {
5743
const name = 'test.json';
5844
const mockFile = createFile(name, {ping: true}, {
5945
type: 'application/json'
@@ -74,25 +60,6 @@ it('uses the DataTransfer {items} instead of {files} if it exists', async () =>
7460
expect(file.path).toBe(name);
7561
});
7662

77-
it('uses the DataTransfer {files} if {items} is empty', async () => {
78-
const name = 'test.json';
79-
const mockFile = createFile(name, {ping: true}, {
80-
type: 'application/json'
81-
});
82-
const evt = dragEvtFromFilesAndItems([mockFile], []);
83-
84-
const files = await fromEvent(evt);
85-
expect(files.every(file => file instanceof File)).toBe(true);
86-
87-
const [file] = files as FileWithPath[];
88-
89-
expect(file.name).toBe(mockFile.name);
90-
expect(file.type).toBe(mockFile.type);
91-
expect(file.size).toBe(mockFile.size);
92-
expect(file.lastModified).toBe(mockFile.lastModified);
93-
expect(file.path).toBe(name);
94-
});
95-
9663
it('skips DataTransfer {items} that are of kind "string"', async () => {
9764
const name = 'test.json';
9865
const mockFile = createFile(name, {ping: true}, {
@@ -128,22 +95,20 @@ it('can read a tree of directories recursively and return a flat list of FileWit
12895
createFile('.DS_Store', {macOs: true}),
12996
createFile('Thumbs.db', {windows: true})
13097
];
131-
const entries = [
132-
fileSystemFileEntryFromFile(f1),
133-
fileSystemFileEntryFromFile(f2),
134-
fileSystemDirEntryFromFile([
98+
const evt = dragEvtFromItems([
99+
dataTransferItemFromEntry(fileSystemFileEntryFromFile(f1), f1),
100+
dataTransferItemFromEntry(fileSystemFileEntryFromFile(f2), f2),
101+
dataTransferItemFromEntry(fileSystemDirEntryFromFile([
135102
fileSystemFileEntryFromFile(f3),
136103
fileSystemDirEntryFromFile([
137104
fileSystemFileEntryFromFile(f4)
138105
]),
139106
fileSystemFileEntryFromFile(f5)
140-
], 2),
141-
fileSystemFileEntryFromFile(f6),
142-
fileSystemFileEntryFromFile(f7),
143-
fileSystemFileEntryFromFile(f8)
144-
];
145-
146-
const evt = dragEvtFromItems(entries.map(dataTransferItemFromEntry));
107+
], 2)),
108+
dataTransferItemFromEntry(fileSystemFileEntryFromFile(f6), f6),
109+
dataTransferItemFromEntry(fileSystemFileEntryFromFile(f7), f7),
110+
dataTransferItemFromEntry(fileSystemFileEntryFromFile(f8), f8)
111+
]);
147112

148113
const items = await fromEvent(evt);
149114
const files = sortFiles(items as FileWithPath[]);
@@ -170,34 +135,81 @@ it('returns the DataTransfer {items} if the DragEvent {type} is not "drop"', asy
170135
expect(itm.kind).toBe('file');
171136
});
172137

173-
// tslint:disable-next-line: max-line-length
174-
it('filters DataTransfer {items} if the DragEvent {type} is not "drop" and DataTransferItem {kind} is "string"', async () => {
175-
const name = 'test.json';
176-
const mockFile = createFile(name, {ping: true}, {
177-
type: 'application/json'
178-
});
179-
const file = dataTransferItemFromFile(mockFile);
180-
const str = dataTransferItemFromStr('test');
181-
const evt = dragEvtFromItems([file, str], 'dragenter');
138+
it(
139+
'filters DataTransfer {items} if the DragEvent {type} is not "drop" and DataTransferItem {kind} is "string"',
140+
async () => {
141+
const name = 'test.json';
142+
const mockFile = createFile(name, {ping: true}, {
143+
type: 'application/json'
144+
});
145+
const file = dataTransferItemFromFile(mockFile);
146+
const str = dataTransferItemFromStr('test');
147+
const evt = dragEvtFromItems([file, str], 'dragenter');
182148

183-
const items = await fromEvent(evt);
184-
expect(items).toHaveLength(1);
149+
const items = await fromEvent(evt);
150+
expect(items).toHaveLength(1);
185151

186-
const [item] = items as DataTransferItem[];
152+
const [item] = items as DataTransferItem[];
187153

188-
expect(item.kind).toBe(file.kind);
189-
expect(item.kind).toBe('file');
154+
expect(item.kind).toBe(file.kind);
155+
expect(item.kind).toBe('file');
156+
}
157+
);
158+
159+
it('should throw if reading dir entries fails', async done => {
160+
const mockFiles = sortFiles([
161+
createFile('ping.json', {ping: true}),
162+
createFile('pong.json', {pong: true})
163+
]);
164+
const [f1, f2] = mockFiles;
165+
const evt = dragEvtFromItems([
166+
dataTransferItemFromEntry(fileSystemDirEntryFromFile([
167+
fileSystemFileEntryFromFile(f1),
168+
fileSystemFileEntryFromFile(f2)
169+
], 1, 1))
170+
]);
171+
172+
try {
173+
await fromEvent(evt);
174+
done.fail('Getting the files should have failed');
175+
} catch (err) {
176+
done();
177+
}
190178
});
191179

180+
it('should throw if reading file entry fails', async done => {
181+
const mockFiles = sortFiles([
182+
createFile('ping.json', {ping: true}),
183+
createFile('pong.json', {pong: true})
184+
]);
185+
const [f1, f2] = mockFiles;
186+
const evt = dragEvtFromItems([
187+
dataTransferItemFromEntry(fileSystemDirEntryFromFile([
188+
fileSystemFileEntryFromFile(f1),
189+
fileSystemFileEntryFromFile(f2, 'Oops :(')
190+
], 1, 1))
191+
]);
192+
193+
try {
194+
await fromEvent(evt);
195+
done.fail('Getting the files should have failed');
196+
} catch (err) {
197+
done();
198+
}
199+
});
200+
201+
it('should throw if DataTransferItem is not a File', async done => {
202+
const item = dataTransferItem(null, 'file');
203+
const evt = dragEvtFromFilesAndItems([], [item]);
204+
205+
try {
206+
await fromEvent(evt);
207+
done.fail('Getting the files should have failed');
208+
} catch (err) {
209+
done();
210+
}
211+
});
192212

193-
function dragEvtFromFiles(files: File | File[], type: string = 'drop'): DragEvent {
194-
return {
195-
type,
196-
dataTransfer: {
197-
files: Array.isArray(files) ? files : [files]
198-
}
199-
} as any;
200-
}
201213

202214
function dragEvtFromItems(items: DataTransferItem | DataTransferItem[], type: string = 'drop'): DragEvent {
203215
return {
@@ -227,6 +239,16 @@ function dataTransferItemFromFile(file: File): DataTransferItem {
227239
} as any;
228240
}
229241

242+
function dataTransferItem(file?: any, kind?: string, type: string = ''): DataTransferItem {
243+
return {
244+
kind,
245+
type,
246+
getAsFile() {
247+
return file;
248+
}
249+
} as any;
250+
}
251+
230252
function dataTransferItemFromStr(str: string): DataTransferItem {
231253
return {
232254
kind: 'string',
@@ -240,26 +262,37 @@ function dataTransferItemFromStr(str: string): DataTransferItem {
240262
} as any;
241263
}
242264

243-
function dataTransferItemFromEntry(entry: FileEntry | DirEntry): DataTransferItem {
265+
function dataTransferItemFromEntry(entry: FileEntry | DirEntry, file?: File): DataTransferItem {
244266
return {
245267
kind: 'file',
268+
getAsFile() {
269+
return file;
270+
},
246271
webkitGetAsEntry: () => {
247272
return entry;
248273
}
249274
} as any;
250275
}
251276

252-
function fileSystemFileEntryFromFile(file: File): FileEntry {
277+
function fileSystemFileEntryFromFile(file: File, err?: any): FileEntry {
253278
return {
254279
isDirectory: false,
255280
isFile: true,
256-
file(cb: (file: File) => void) {
257-
cb(file);
281+
file(cb, errCb) {
282+
if (err) {
283+
errCb(err);
284+
} else {
285+
cb(file);
286+
}
258287
}
259288
};
260289
}
261290

262-
function fileSystemDirEntryFromFile(files: Array<FileEntry | DirEntry>, batchSize: number = 1): DirEntry {
291+
function fileSystemDirEntryFromFile(
292+
files: Array<FileEntry | DirEntry>,
293+
batchSize: number = 1,
294+
throwAfter: number = 0
295+
): DirEntry {
263296
const copy = files.slice(0);
264297
const batches: Array<Array<FileEntry | DirEntry>> = [];
265298

@@ -278,10 +311,14 @@ function fileSystemDirEntryFromFile(files: Array<FileEntry | DirEntry>, batchSiz
278311
let cbCount = 0;
279312

280313
return {
281-
readEntries(cb) {
314+
readEntries(cb, errCb) {
282315
const batch = batches[cbCount];
283316
cbCount++;
284317

318+
if (throwAfter !== 0 && cbCount === throwAfter) {
319+
errCb('Failed to read files');
320+
}
321+
285322
if (batch) {
286323
cb(batch);
287324
} else {
@@ -295,9 +332,11 @@ function fileSystemDirEntryFromFile(files: Array<FileEntry | DirEntry>, batchSiz
295332

296333
function inputEvtFromFiles(...files: File[]): Event {
297334
const input = document.createElement('input');
298-
Object.defineProperty(input, 'files', {
299-
value: files
300-
});
335+
if (files.length) {
336+
Object.defineProperty(input, 'files', {
337+
value: files
338+
});
339+
}
301340
return {
302341
target: input
303342
} as any;
@@ -316,7 +355,10 @@ function sortFiles<T extends File>(files: T[]) {
316355

317356

318357
interface FileEntry extends Entry {
319-
file(cb: (file: File) => void): void;
358+
file(
359+
cb: (file: File) => void,
360+
errCb: (err: any) => void
361+
): void;
320362
}
321363

322364
interface DirEntry extends Entry {
@@ -329,5 +371,8 @@ interface Entry {
329371
}
330372

331373
interface DirReader {
332-
readEntries(cb: (entries: Array<FileEntry | DirEntry>) => void): void;
374+
readEntries(
375+
cb: (entries: Array<FileEntry | DirEntry>) => void,
376+
errCb: (err: any) => void
377+
): void;
333378
}

0 commit comments

Comments
 (0)