Skip to content

Commit 315b663

Browse files
saul-preparedrolandjitsu
authored andcommitted
feat: always set a {relativePath} close #69
1 parent 72d5fa6 commit 315b663

File tree

4 files changed

+105
-20
lines changed

4 files changed

+105
-20
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: 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: 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: 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+
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: 23 additions & 15 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,16 +1223,19 @@ 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) {
1239-
const {name} = file;
1238+
const { name } = file;
12401239
const hasExtension = name && name.lastIndexOf('.') !== -1;
12411240

12421241
if (hasExtension && !file.type) {
@@ -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)