Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 32 additions & 7 deletions src/file-selector.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {FileWithPath} from './file';
import {fromEvent} from './file-selector';


it('returns a Promise', async () => {
const evt = new Event('test');
expect(fromEvent(evt)).toBeInstanceOf(Promise);
Expand Down Expand Up @@ -34,7 +33,7 @@ it('should return the evt {target} {files} if the passed event is an input evt',
expect(file.type).toBe(mockFile.type);
expect(file.size).toBe(mockFile.size);
expect(file.lastModified).toBe(mockFile.lastModified);
expect(file.path).toBe(name);
expect(file.path).toBe(`./${name}`);
});

it('should return an empty array if the evt {target} has no {files} prop', async () => {
Expand All @@ -59,7 +58,7 @@ it('should return files if the arg is a list of FileSystemFileHandle', async ()
expect(file.type).toBe(mockFile.type);
expect(file.size).toBe(mockFile.size);
expect(file.lastModified).toBe(mockFile.lastModified);
expect(file.path).toBe(name);
expect(file.path).toBe(`./${name}`);
});

it('should return an empty array if the passed event is not a DragEvent', async () => {
Expand All @@ -85,7 +84,7 @@ it('should return {files} from DataTransfer if {items} is not defined (e.g. IE11
expect(file.type).toBe(mockFile.type);
expect(file.size).toBe(mockFile.size);
expect(file.lastModified).toBe(mockFile.lastModified);
expect(file.path).toBe(name);
expect(file.path).toBe(`./${name}`);
});

it('should return files from DataTransfer {items} if the passed event is a DragEvent', async () => {
Expand All @@ -106,7 +105,32 @@ it('should return files from DataTransfer {items} if the passed event is a DragE
expect(file.type).toBe(mockFile.type);
expect(file.size).toBe(mockFile.size);
expect(file.lastModified).toBe(mockFile.lastModified);
expect(file.path).toBe(name);
expect(file.path).toBe(`./${name}`);
});

it('should use the {fullPath} for {path} if {webkitGetAsEntry} is supported and the items are FileSystemFileEntry', async () => {
const name = 'test.json';
const fullPath = '/testfolder/test.json'
const mockFile = createFile(name, {ping: true}, {
type: 'application/json'
});

const file = fileSystemFileEntryFromFile(mockFile);
file.fullPath = fullPath
const item = dataTransferItemFromEntry(file, mockFile);
const evt = dragEvtFromFilesAndItems([], [item]);

const files = await fromEvent(evt);
expect(files).toHaveLength(1);
expect(files.every(file => file instanceof File)).toBe(true);

const [f] = files as FileWithPath[];

expect(f.name).toBe(mockFile.name);
expect(f.type).toBe(mockFile.type);
expect(f.size).toBe(mockFile.size);
expect(f.lastModified).toBe(mockFile.lastModified);
expect(f.path).toBe(fullPath);
});

it('skips DataTransfer {items} that are of kind "string"', async () => {
Expand All @@ -127,7 +151,7 @@ it('skips DataTransfer {items} that are of kind "string"', async () => {
expect(file.type).toBe(mockFile.type);
expect(file.size).toBe(mockFile.size);
expect(file.lastModified).toBe(mockFile.lastModified);
expect(file.path).toBe(name);
expect(file.path).toBe(`./${name}`);
});

it('can read a tree of directories recursively and return a flat list of FileWithPath objects', async () => {
Expand Down Expand Up @@ -277,7 +301,7 @@ it('should use getAsFileSystemHandle when available', async () => {
expect(file.type).toBe(f.type);
expect(file.size).toBe(f.size);
expect(file.lastModified).toBe(f.lastModified);
expect(file.path).toBe(name);
expect(file.path).toBe(`./${name}`);
});

function dragEvtFromItems(items: DataTransferItem | DataTransferItem[], type: string = 'drop'): DragEvent {
Expand Down Expand Up @@ -477,6 +501,7 @@ interface DirEntry extends Entry {
interface Entry {
isDirectory: boolean;
isFile: boolean;
fullPath?: string;
}

interface DirReader {
Expand Down
6 changes: 3 additions & 3 deletions src/file-selector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ function toFilePromises(item: DataTransferItem) {
return fromDirEntry(entry) as any;
}

return fromDataTransferItem(item);
return fromDataTransferItem(item, entry);
}

function flatten<T>(items: any[]): T[] {
Expand All @@ -120,7 +120,7 @@ function flatten<T>(items: any[]): T[] {
], []);
}

function fromDataTransferItem(item: DataTransferItem) {
function fromDataTransferItem(item: DataTransferItem, entry?: FileSystemEntry | null) {
if (typeof (item as any).getAsFileSystemHandle === 'function') {
return (item as any).getAsFileSystemHandle()
.then(async (h: any) => {
Expand All @@ -133,7 +133,7 @@ function fromDataTransferItem(item: DataTransferItem) {
if (!file) {
return Promise.reject(`${item} is not a File`);
}
const fwp = toFileWithPath(file);
const fwp = toFileWithPath(file, entry?.fullPath ?? undefined);
return Promise.resolve(fwp);
}

Expand Down
40 changes: 39 additions & 1 deletion src/file.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ describe('toFile()', () => {
const name = 'test.json';
const file = new File([], name);
const fileWithPath = toFileWithPath(file);
expect(fileWithPath.path).toBe(name);
expect(fileWithPath.path).toBe(`./${name}`);
});

it('uses the File {webkitRelativePath} as {path} if it exists', () => {
Expand All @@ -66,6 +66,44 @@ describe('toFile()', () => {
expect(fileWithPath.path).toBe(path);
});

it('sets the {relativePath} if provided without overwriting {path}', () => {
const fullPath = '/Users/test/Desktop/test/test.json';
const path = '/test/test.json';
const file = new File([], 'test.json');

// @ts-expect-error
file.path = fullPath;
const fileWithPath = toFileWithPath(file, path);
expect(fileWithPath.path).toBe(fullPath);
expect(fileWithPath.relativePath).toBe(path);
});

test('{relativePath} is enumerable', () => {
const path = '/test/test.json';
const file = new File([], 'test.json');
const fileWithPath = toFileWithPath(file, path);

expect(Object.keys(fileWithPath)).toContain('relativePath');

const keys: string[] = [];
for (const key in fileWithPath) {
keys.push(key);
}

expect(keys).toContain('relativePath');
});

it('uses the File {webkitRelativePath} as {relativePath} if it exists', () => {
const name = 'test.json';
const path = 'test/test.json';
const file = new File([], name);
Object.defineProperty(file, 'webkitRelativePath', {
value: path
});
const fileWithPath = toFileWithPath(file);
expect(fileWithPath.relativePath).toBe(path);
});

it('sets the {type} from extension', () => {
const types = Array.from(COMMON_MIME_TYPES.values());
const files = Array.from(COMMON_MIME_TYPES.keys())
Expand Down
36 changes: 22 additions & 14 deletions src/file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1203,21 +1203,17 @@ export const COMMON_MIME_TYPES = new Map([

export function toFileWithPath(file: FileWithPath, path?: string, h?: FileSystemHandle): FileWithPath {
const f = withMimeType(file);
const {webkitRelativePath} = file;
const p = typeof path === 'string'
? path
// If <input webkitdirectory> is set,
// the File will have a {webkitRelativePath} property
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/webkitdirectory
: typeof webkitRelativePath === 'string' && webkitRelativePath.length > 0
? webkitRelativePath
: `./${file.name}`;
if (typeof f.path !== 'string') { // on electron, path is already set to the absolute path
const {webkitRelativePath} = file;
Object.defineProperty(f, 'path', {
value: typeof path === 'string'
? path
// If <input webkitdirectory> is set,
// the File will have a {webkitRelativePath} property
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/webkitdirectory
: typeof webkitRelativePath === 'string' && webkitRelativePath.length > 0
? webkitRelativePath
: file.name,
writable: false,
configurable: false,
enumerable: true
});
setObjProp(f, 'path', p);
}
if (h !== undefined) {
Object.defineProperty(f, 'handle', {
Expand All @@ -1227,12 +1223,15 @@ export function toFileWithPath(file: FileWithPath, path?: string, h?: FileSystem
enumerable: true
});
}
// Always populate a relative path so that even electron apps have access to a relativePath value
setObjProp(f, 'relativePath', p);
return f;
}

export interface FileWithPath extends File {
readonly path?: string;
readonly handle?: FileSystemFileHandle;
readonly relativePath?: string;
}

function withMimeType(file: FileWithPath) {
Expand All @@ -1255,3 +1254,12 @@ function withMimeType(file: FileWithPath) {

return file;
}

function setObjProp(f: FileWithPath, key: string, value: string) {
Object.defineProperty(f, key, {
value,
writable: false,
configurable: false,
enumerable: true
})
}