Skip to content

Commit 4f379f0

Browse files
saul-preparedrolandjitsu
authored andcommitted
feat: always populate {relativePath} for access across browser and electron
1 parent 72d5fa6 commit 4f379f0

File tree

4 files changed

+103
-4
lines changed

4 files changed

+103
-4
lines changed

src/file-selector.spec.ts

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import {FileWithPath} from './file';
1+
import { FileWithPath } from './file';
22
import {fromEvent} from './file-selector';
3-
3+
const toFileWithPathSpy = jest.spyOn(jest.requireActual('./file'), 'toFileWithPath');
44

55
it('returns a Promise', async () => {
66
const evt = new Event('test');
@@ -109,6 +109,37 @@ it('should return files from DataTransfer {items} if the passed event is a DragE
109109
expect(file.path).toBe(name);
110110
});
111111

112+
it('should call toFilePath with undefined path if {webkitGetAsEntry} is not a function', async () => {
113+
toFileWithPathSpy.mockClear();
114+
const name = 'test.json';
115+
const mockFile = createFile(name, {ping: true}, {
116+
type: 'application/json'
117+
});
118+
119+
const item = dataTransferItemFromFile(mockFile);
120+
const evt = dragEvtFromFilesAndItems([], [item]);
121+
await fromEvent(evt);
122+
expect(toFileWithPathSpy).toBeCalledTimes(1);
123+
expect(toFileWithPathSpy).toBeCalledWith(mockFile, undefined);
124+
});
125+
126+
it('should call toFilePath with {fullPath} path if file can be converted into an Entry', async () => {
127+
toFileWithPathSpy.mockClear();
128+
const name = 'test.json';
129+
const fullPath = '/testfolder/test.json'
130+
const mockFile = createFile(name, {ping: true}, {
131+
type: 'application/json'
132+
});
133+
134+
const file = fileSystemFileEntryFromFile(mockFile);
135+
file.fullPath = fullPath
136+
const item = dataTransferItemFromEntry(file, mockFile);
137+
const evt = dragEvtFromFilesAndItems([], [item]);
138+
await fromEvent(evt);
139+
expect(toFileWithPathSpy).toBeCalledTimes(1);
140+
expect(toFileWithPathSpy).toBeCalledWith(mockFile, fullPath);
141+
});
142+
112143
it('skips DataTransfer {items} that are of kind "string"', async () => {
113144
const name = 'test.json';
114145
const mockFile = createFile(name, {ping: true}, {
@@ -477,6 +508,7 @@ interface DirEntry extends Entry {
477508
interface Entry {
478509
isDirectory: boolean;
479510
isFile: boolean;
511+
fullPath?: string;
480512
}
481513

482514
interface DirReader {

src/file-selector.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,10 +130,16 @@ function fromDataTransferItem(item: DataTransferItem) {
130130
});
131131
}
132132
const file = item.getAsFile();
133+
134+
let fileAsEntry: FileSystemEntry | null = null;
135+
if (typeof item.webkitGetAsEntry === 'function') {
136+
fileAsEntry = item.webkitGetAsEntry();
137+
}
138+
133139
if (!file) {
134140
return Promise.reject(`${item} is not a File`);
135141
}
136-
const fwp = toFileWithPath(file);
142+
const fwp = toFileWithPath(file, fileAsEntry?.fullPath ?? undefined);
137143
return Promise.resolve(fwp);
138144
}
139145

src/file.spec.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,51 @@ 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 = [];
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+
107+
it('uses the File {name} as {relativePath} if not provided and prefix with forward slash (/)', () => {
108+
const name = 'test.json';
109+
const file = new File([], name);
110+
const fileWithPath = toFileWithPath(file);
111+
expect(fileWithPath.relativePath).toBe('/' + name);
112+
});
113+
69114
it('sets the {type} from extension', () => {
70115
const types = Array.from(COMMON_MIME_TYPES.values());
71116
const files = Array.from(COMMON_MIME_TYPES.keys())

src/file.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1203,8 +1203,8 @@ 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;
12061207
if (typeof f.path !== 'string') { // on electron, path is already set to the absolute path
1207-
const {webkitRelativePath} = file;
12081208
Object.defineProperty(f, 'path', {
12091209
value: typeof path === 'string'
12101210
? path
@@ -1227,12 +1227,28 @@ export function toFileWithPath(file: FileWithPath, path?: string, h?: FileSystem
12271227
enumerable: true
12281228
});
12291229
}
1230+
// Always populate a relative path so that even electron apps have access to a relativePath value
1231+
Object.defineProperty(f, 'relativePath', {
1232+
value: typeof path === 'string'
1233+
? path
1234+
// If <input webkitdirectory> is set,
1235+
// the File will have a {webkitRelativePath} property
1236+
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/webkitdirectory
1237+
: typeof webkitRelativePath === 'string' && webkitRelativePath.length > 0
1238+
? webkitRelativePath
1239+
: `/${file.name}`, // prepend forward slash (/) to ensure consistancy when path isn't supplied.
1240+
writable: false,
1241+
configurable: false,
1242+
enumerable: true
1243+
})
1244+
12301245
return f;
12311246
}
12321247

12331248
export interface FileWithPath extends File {
12341249
readonly path?: string;
12351250
readonly handle?: FileSystemFileHandle;
1251+
readonly relativePath?: string;
12361252
}
12371253

12381254
function withMimeType(file: FileWithPath) {

0 commit comments

Comments
 (0)