Skip to content

Commit

Permalink
feat: 🎸 implement .getDirectoryHandle() method
Browse files Browse the repository at this point in the history
  • Loading branch information
streamich committed Jun 20, 2023
1 parent f13de3b commit b6b026a
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 3 deletions.
28 changes: 25 additions & 3 deletions src/node-to-fsa/NodeFileSystemDirectoryHandle.ts
@@ -1,5 +1,5 @@
import {NodeFileSystemHandle} from "./NodeFileSystemHandle";
import {basename, ctx as createCtx} from "./util";
import {assertName, basename, ctx as createCtx} from "./util";
import {NodeFileSystemFileHandle} from "./NodeFileSystemFileHandle";
import type {NodeFsaContext, NodeFsaFs} from "./types";

Expand Down Expand Up @@ -57,8 +57,30 @@ export class NodeFileSystemDirectoryHandle extends NodeFileSystemHandle {
* @param options An optional object containing options for the retrieved
* subdirectory.
*/
public getDirectoryHandle(name: string, options?: GetDirectoryHandleOptions): Promise<NodeFileSystemDirectoryHandle> {
throw new Error('Not implemented');
public async getDirectoryHandle(name: string, options?: GetDirectoryHandleOptions): Promise<NodeFileSystemDirectoryHandle> {
assertName(name, 'getDirectoryHandle', 'FileSystemDirectoryHandle');
try {
const filename = this.path + this.ctx.separator! + name;
const stats = await this.fs.promises.stat(filename);
if (!stats.isDirectory()) {
throw new DOMException('The path supplied exists, but was not an entry of requested type.', 'TypeMismatchError');
}
return new NodeFileSystemDirectoryHandle(this.fs, filename, this.ctx);
} catch (error) {
if (error instanceof DOMException) throw error;
if (error && typeof error === 'object') {
switch (error.code) {
case 'ENOENT': {
throw new DOMException('A requested file or directory could not be found at the time an operation was processed.', 'NotFoundError');
}
case 'EPERM':
case 'EACCES': {
throw new DOMException('Permission not granted.', 'NotAllowedError');
}
}
}
throw error;
}
}

/**
Expand Down
49 changes: 49 additions & 0 deletions src/node-to-fsa/__tests__/NodeFileSystemDirectoryHandle.test.ts
Expand Up @@ -129,3 +129,52 @@ describe('.values()', () => {
expect(handles.find(handle => handle.name === 'another')).toBeInstanceOf(NodeFileSystemDirectoryHandle);
});
});

describe('.getDirectoryHandle()', () => {
test('throws "NotFoundError" DOMException if sub-directory not found', async () => {
const {dir} = setup({a: null});
try {
await dir.getDirectoryHandle('b');
throw new Error('Not this error.');
} catch (error) {
expect(error).toBeInstanceOf(DOMException);
expect(error.name).toBe('NotFoundError');
expect(error.message).toBe('A requested file or directory could not be found at the time an operation was processed.');
}
});

test('throws "TypeMismatchError" DOMException if entry is not a directory', async () => {
const {dir} = setup({file: 'contents'});
try {
await dir.getDirectoryHandle('file');
throw new Error('Not this error.');
} catch (error) {
expect(error).toBeInstanceOf(DOMException);
expect(error.name).toBe('TypeMismatchError');
expect(error.message).toBe('The path supplied exists, but was not an entry of requested type.');
}
});

const invalidNames = ['.', '..', '/', '/a', 'a/', 'a//b', 'a/.', 'a/..', 'a/b/.', 'a/b/..', '\\', '\\a', 'a\\', 'a\\\\b', 'a\\.'];

for (const invalidName of invalidNames) {
test(`throws on invalid file name: "${invalidName}"`, async () => {
const {dir} = setup({file: 'contents'});
try {
await dir.getDirectoryHandle(invalidName);
throw new Error('Not this error.');
} catch (error) {
expect(error).toBeInstanceOf(TypeError);
expect(error.message).toBe(`Failed to execute 'getDirectoryHandle' on 'FileSystemDirectoryHandle': Name is not allowed.`);
}
});
}

test('can retrieve a child directory', async () => {
const {dir} = setup({file: 'contents', subdir: null});
const subdir = await dir.getDirectoryHandle('subdir');
expect(subdir.kind).toBe('directory');
expect(subdir.name).toBe('subdir');
expect(subdir).toBeInstanceOf(NodeFileSystemDirectoryHandle);
});
});
7 changes: 7 additions & 0 deletions src/node-to-fsa/util.ts
Expand Up @@ -14,3 +14,10 @@ export const basename = (path: string, separator: string) => {
const lastSlashIndex = path.lastIndexOf(separator);
return lastSlashIndex === -1 ? path : path.slice(lastSlashIndex + 1);
};

const nameRegex = /^(\.{1,2})|(.*(\/|\\).*)$/;

export const assertName = (name: string, method: string, klass: string) => {
const isInvalid = nameRegex.test(name);
if (isInvalid) throw new TypeError(`Failed to execute '${method}' on '${klass}': Name is not allowed.`);
};

0 comments on commit b6b026a

Please sign in to comment.