Skip to content

Commit 0dff2f9

Browse files
saul-preparedrolandjitsu
authored andcommitted
feat!: always set a {relativePath} close #69
BREAKING CHANGE: `{path}` is now relative when falling back to the file name
1 parent 72d5fa6 commit 0dff2f9

File tree

4 files changed

+96
-25
lines changed

4 files changed

+96
-25
lines changed

src/file-selector.spec.ts

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import {FileWithPath} from './file';
22
import {fromEvent} from './file-selector';
33

4-
54
it('returns a Promise', async () => {
65
const evt = new Event('test');
76
expect(fromEvent(evt)).toBeInstanceOf(Promise);
@@ -34,7 +33,7 @@ it('should return the evt {target} {files} if the passed event is an input evt',
3433
expect(file.type).toBe(mockFile.type);
3534
expect(file.size).toBe(mockFile.size);
3635
expect(file.lastModified).toBe(mockFile.lastModified);
37-
expect(file.path).toBe(name);
36+
expect(file.path).toBe(`./${name}`);
3837
});
3938

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

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

9190
it('should return files from DataTransfer {items} if the passed event is a DragEvent', async () => {
@@ -106,7 +105,32 @@ it('should return files from DataTransfer {items} if the passed event is a DragE
106105
expect(file.type).toBe(mockFile.type);
107106
expect(file.size).toBe(mockFile.size);
108107
expect(file.lastModified).toBe(mockFile.lastModified);
109-
expect(file.path).toBe(name);
108+
expect(file.path).toBe(`./${name}`);
109+
});
110+
111+
it('should use the {fullPath} for {path} if {webkitGetAsEntry} is supported and the items are FileSystemFileEntry', async () => {
112+
const name = 'test.json';
113+
const fullPath = '/testfolder/test.json'
114+
const mockFile = createFile(name, {ping: true}, {
115+
type: 'application/json'
116+
});
117+
118+
const file = fileSystemFileEntryFromFile(mockFile);
119+
file.fullPath = fullPath
120+
const item = dataTransferItemFromEntry(file, mockFile);
121+
const evt = dragEvtFromFilesAndItems([], [item]);
122+
123+
const files = await fromEvent(evt);
124+
expect(files).toHaveLength(1);
125+
expect(files.every(file => file instanceof File)).toBe(true);
126+
127+
const [f] = files as FileWithPath[];
128+
129+
expect(f.name).toBe(mockFile.name);
130+
expect(f.type).toBe(mockFile.type);
131+
expect(f.size).toBe(mockFile.size);
132+
expect(f.lastModified).toBe(mockFile.lastModified);
133+
expect(f.path).toBe(fullPath);
110134
});
111135

112136
it('skips DataTransfer {items} that are of kind "string"', async () => {
@@ -127,7 +151,7 @@ it('skips DataTransfer {items} that are of kind "string"', async () => {
127151
expect(file.type).toBe(mockFile.type);
128152
expect(file.size).toBe(mockFile.size);
129153
expect(file.lastModified).toBe(mockFile.lastModified);
130-
expect(file.path).toBe(name);
154+
expect(file.path).toBe(`./${name}`);
131155
});
132156

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

283307
function dragEvtFromItems(items: DataTransferItem | DataTransferItem[], type: string = 'drop'): DragEvent {
@@ -477,6 +501,7 @@ interface DirEntry extends Entry {
477501
interface Entry {
478502
isDirectory: boolean;
479503
isFile: boolean;
504+
fullPath?: string;
480505
}
481506

482507
interface DirReader {

src/file-selector.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ function toFilePromises(item: DataTransferItem) {
110110
return fromDirEntry(entry) as any;
111111
}
112112

113-
return fromDataTransferItem(item);
113+
return fromDataTransferItem(item, entry);
114114
}
115115

116116
function flatten<T>(items: any[]): T[] {
@@ -120,7 +120,7 @@ function flatten<T>(items: any[]): T[] {
120120
], []);
121121
}
122122

123-
function fromDataTransferItem(item: DataTransferItem) {
123+
function fromDataTransferItem(item: DataTransferItem, entry?: FileSystemEntry | null) {
124124
if (typeof (item as any).getAsFileSystemHandle === 'function') {
125125
return (item as any).getAsFileSystemHandle()
126126
.then(async (h: any) => {
@@ -133,7 +133,7 @@ function fromDataTransferItem(item: DataTransferItem) {
133133
if (!file) {
134134
return Promise.reject(`${item} is not a File`);
135135
}
136-
const fwp = toFileWithPath(file);
136+
const fwp = toFileWithPath(file, entry?.fullPath ?? undefined);
137137
return Promise.resolve(fwp);
138138
}
139139

src/file.spec.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ describe('toFile()', () => {
5252
const name = 'test.json';
5353
const file = new File([], name);
5454
const fileWithPath = toFileWithPath(file);
55-
expect(fileWithPath.path).toBe(name);
55+
expect(fileWithPath.path).toBe(`./${name}`);
5656
});
5757

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

69+
it('sets the {relativePath} if provided without overwriting {path}', () => {
70+
const fullPath = '/Users/test/Desktop/test/test.json';
71+
const path = '/test/test.json';
72+
const file = new File([], 'test.json');
73+
74+
// @ts-expect-error
75+
file.path = fullPath;
76+
const fileWithPath = toFileWithPath(file, path);
77+
expect(fileWithPath.path).toBe(fullPath);
78+
expect(fileWithPath.relativePath).toBe(path);
79+
});
80+
81+
test('{relativePath} is enumerable', () => {
82+
const path = '/test/test.json';
83+
const file = new File([], 'test.json');
84+
const fileWithPath = toFileWithPath(file, path);
85+
86+
expect(Object.keys(fileWithPath)).toContain('relativePath');
87+
88+
const keys: string[] = [];
89+
for (const key in fileWithPath) {
90+
keys.push(key);
91+
}
92+
93+
expect(keys).toContain('relativePath');
94+
});
95+
96+
it('uses the File {webkitRelativePath} as {relativePath} if it exists', () => {
97+
const name = 'test.json';
98+
const path = 'test/test.json';
99+
const file = new File([], name);
100+
Object.defineProperty(file, 'webkitRelativePath', {
101+
value: path
102+
});
103+
const fileWithPath = toFileWithPath(file);
104+
expect(fileWithPath.relativePath).toBe(path);
105+
});
106+
69107
it('sets the {type} from extension', () => {
70108
const types = Array.from(COMMON_MIME_TYPES.values());
71109
const files = Array.from(COMMON_MIME_TYPES.keys())

src/file.ts

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1203,21 +1203,17 @@ export const COMMON_MIME_TYPES = new Map([
12031203

12041204
export function toFileWithPath(file: FileWithPath, path?: string, h?: FileSystemHandle): FileWithPath {
12051205
const f = withMimeType(file);
1206+
const {webkitRelativePath} = file;
1207+
const p = typeof path === 'string'
1208+
? path
1209+
// If <input webkitdirectory> is set,
1210+
// the File will have a {webkitRelativePath} property
1211+
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/webkitdirectory
1212+
: typeof webkitRelativePath === 'string' && webkitRelativePath.length > 0
1213+
? webkitRelativePath
1214+
: `./${file.name}`;
12061215
if (typeof f.path !== 'string') { // on electron, path is already set to the absolute path
1207-
const {webkitRelativePath} = file;
1208-
Object.defineProperty(f, 'path', {
1209-
value: typeof path === 'string'
1210-
? path
1211-
// If <input webkitdirectory> is set,
1212-
// the File will have a {webkitRelativePath} property
1213-
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/webkitdirectory
1214-
: typeof webkitRelativePath === 'string' && webkitRelativePath.length > 0
1215-
? webkitRelativePath
1216-
: file.name,
1217-
writable: false,
1218-
configurable: false,
1219-
enumerable: true
1220-
});
1216+
setObjProp(f, 'path', p);
12211217
}
12221218
if (h !== undefined) {
12231219
Object.defineProperty(f, 'handle', {
@@ -1227,12 +1223,15 @@ export function toFileWithPath(file: FileWithPath, path?: string, h?: FileSystem
12271223
enumerable: true
12281224
});
12291225
}
1226+
// Always populate a relative path so that even electron apps have access to a relativePath value
1227+
setObjProp(f, 'relativePath', p);
12301228
return f;
12311229
}
12321230

12331231
export interface FileWithPath extends File {
12341232
readonly path?: string;
12351233
readonly handle?: FileSystemFileHandle;
1234+
readonly relativePath?: string;
12361235
}
12371236

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

12561255
return file;
12571256
}
1257+
1258+
function setObjProp(f: FileWithPath, key: string, value: string) {
1259+
Object.defineProperty(f, key, {
1260+
value,
1261+
writable: false,
1262+
configurable: false,
1263+
enumerable: true
1264+
})
1265+
}

0 commit comments

Comments
 (0)