From bf47b96cec4b1c8c029a0668ea4c1cf8cbcd05f0 Mon Sep 17 00:00:00 2001
From: streamich <streamich@gmail.com>
Date: Wed, 14 Jun 2023 20:47:13 +0200
Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20implement=20.resolve()?=
 =?UTF-8?q?=20method?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../NodeFileSystemDirectoryHandle.ts          | 28 +++++++++-----
 src/node-to-fsa/NodeFileSystemFileHandle.ts   |  4 +-
 .../NodeFileSystemDirectoryHandle.test.ts     | 38 +++++++++++++++++++
 3 files changed, 59 insertions(+), 11 deletions(-)

diff --git a/src/node-to-fsa/NodeFileSystemDirectoryHandle.ts b/src/node-to-fsa/NodeFileSystemDirectoryHandle.ts
index 2cdfd47c8..d767c9d84 100644
--- a/src/node-to-fsa/NodeFileSystemDirectoryHandle.ts
+++ b/src/node-to-fsa/NodeFileSystemDirectoryHandle.ts
@@ -10,10 +10,10 @@ import type Dirent from "../Dirent";
 export class NodeFileSystemDirectoryHandle extends NodeFileSystemHandle {
   constructor (
     protected readonly fs: NodeFsaFs,
-    protected readonly path: string,
+    public readonly __path: string,
     protected readonly ctx: Partial<NodeFsaContext> = createCtx(ctx),
   ) {
-    super('directory', basename(path, ctx.separator!));
+    super('directory', basename(__path, ctx.separator!));
   }
 
   /**
@@ -23,7 +23,7 @@ export class NodeFileSystemDirectoryHandle extends NodeFileSystemHandle {
    * @see https://developer.mozilla.org/en-US/docs/Web/API/FileSystemDirectoryHandle/keys
    */
   public async * keys(): AsyncIterableIterator<string> {
-    const list = await this.fs.promises.readdir(this.path);
+    const list = await this.fs.promises.readdir(this.__path);
     for (const name of list) yield '' + name;
   }
 
@@ -31,7 +31,7 @@ export class NodeFileSystemDirectoryHandle extends NodeFileSystemHandle {
    * @see https://developer.mozilla.org/en-US/docs/Web/API/FileSystemDirectoryHandle/entries
    */
   public async * entries(): AsyncIterableIterator<[string, NodeFileSystemHandle]> {
-    const {path, fs, ctx} = this;
+    const {__path: path, fs, ctx} = this;
     const list = await fs.promises.readdir(path, {withFileTypes: true});
     for (const d of list) {
       const dirent = d as Dirent;
@@ -64,7 +64,7 @@ export class NodeFileSystemDirectoryHandle extends NodeFileSystemHandle {
    */
   public async getDirectoryHandle(name: string, options?: GetDirectoryHandleOptions): Promise<NodeFileSystemDirectoryHandle> {
     assertName(name, 'getDirectoryHandle', 'FileSystemDirectoryHandle');
-    const filename = this.path + this.ctx.separator! + name;
+    const filename = this.__path + this.ctx.separator! + name;
     try {
       const stats = await this.fs.promises.stat(filename);
       if (!stats.isDirectory()) throw newTypeMismatchError();
@@ -100,7 +100,7 @@ export class NodeFileSystemDirectoryHandle extends NodeFileSystemHandle {
    */
   public async getFileHandle(name: string, options?: GetFileHandleOptions): Promise<NodeFileSystemFileHandle> {
     assertName(name, 'getFileHandle', 'FileSystemDirectoryHandle');
-    const filename = this.path + this.ctx.separator! + name;
+    const filename = this.__path + this.ctx.separator! + name;
     try {
       const stats = await this.fs.promises.stat(filename);
       if (!stats.isFile()) throw newTypeMismatchError();
@@ -136,7 +136,7 @@ export class NodeFileSystemDirectoryHandle extends NodeFileSystemHandle {
    */
   public async removeEntry(name: string, {recursive = false}: RemoveEntryOptions = {}): Promise<void> {
     assertName(name, 'removeEntry', 'FileSystemDirectoryHandle');
-    const filename = this.path + this.ctx.separator! + name;
+    const filename = this.__path + this.ctx.separator! + name;
     const promises = this.fs.promises;
     try {
       const stats = await promises.stat(filename);
@@ -172,8 +172,18 @@ export class NodeFileSystemDirectoryHandle extends NodeFileSystemHandle {
    * @param possibleDescendant The {@link NodeFileSystemFileHandle} from which
    *        to return the relative path.
    */
-  public resolve(possibleDescendant: NodeFileSystemHandle): Promise<string[] | null> {
-    throw new Error('Not implemented');
+  public async resolve(possibleDescendant: NodeFileSystemHandle): Promise<string[] | null> {
+    if (possibleDescendant instanceof NodeFileSystemDirectoryHandle || possibleDescendant instanceof NodeFileSystemFileHandle) {
+      const path = this.__path;
+      const childPath = possibleDescendant.__path;
+      if (!childPath.startsWith(path)) return null;
+      let relative = childPath.slice(path.length);
+      if (relative === '') return [];
+      const separator = this.ctx.separator!;
+      if (relative[0] === separator) relative = relative.slice(1);
+      return relative.split(separator);
+    }
+    return null;
   }
 }
 
diff --git a/src/node-to-fsa/NodeFileSystemFileHandle.ts b/src/node-to-fsa/NodeFileSystemFileHandle.ts
index 7df0e7d94..6b13174ad 100644
--- a/src/node-to-fsa/NodeFileSystemFileHandle.ts
+++ b/src/node-to-fsa/NodeFileSystemFileHandle.ts
@@ -6,10 +6,10 @@ import type {NodeFsaContext, NodeFsaFs} from "./types";
 export class NodeFileSystemFileHandle extends NodeFileSystemHandle {
   constructor (
     protected readonly fs: NodeFsaFs,
-    protected readonly path: string,
+    public readonly __path: string,
     protected readonly ctx: Partial<NodeFsaContext> = createCtx(ctx),
   ) {
-    super('file', basename(path, ctx.separator!));
+    super('file', basename(__path, ctx.separator!));
   }
 
   /**
diff --git a/src/node-to-fsa/__tests__/NodeFileSystemDirectoryHandle.test.ts b/src/node-to-fsa/__tests__/NodeFileSystemDirectoryHandle.test.ts
index be9724760..68d70b73b 100644
--- a/src/node-to-fsa/__tests__/NodeFileSystemDirectoryHandle.test.ts
+++ b/src/node-to-fsa/__tests__/NodeFileSystemDirectoryHandle.test.ts
@@ -321,3 +321,41 @@ describe('.removeEntry()', () => {
     expect(res).toBe(undefined);
   });
 });
+
+describe('.resolve()', () => {
+  test('return empty array for itself', async () => {
+    const {dir} = setup({});
+    const res = await dir.resolve(dir);
+    expect(res).toEqual([]);
+  });
+
+  test('can resolve one level deep child', async () => {
+    const {dir} = setup({
+      file: 'contents',
+    });
+    const child = await dir.getFileHandle('file');
+    const res = await dir.resolve(child);
+    expect(res).toEqual(['file']);
+  });
+
+  test('can resolve two level deep child', async () => {
+    const {dir} = setup({
+      'dir/file': 'contents',
+    });
+    const child1 = await dir.getDirectoryHandle('dir');
+    const child2 = await child1.getFileHandle('file');
+    const res = await dir.resolve(child2);
+    expect(res).toEqual(['dir', 'file']);
+    const res2 = await child1.resolve(child2);
+    expect(res2).toEqual(['file']);
+  });
+
+  test('returns "null" if not a descendant', async () => {
+    const {dir} = setup({
+      'dir/file': 'contents',
+    });
+    const child1 = await dir.getDirectoryHandle('dir');
+    const res = await child1.resolve(dir);
+    expect(res).toBe(null);
+  });
+});