diff --git a/packages/memory/src/index.js b/packages/memory/src/index.js index 43931bd..31bcb0c 100644 --- a/packages/memory/src/index.js +++ b/packages/memory/src/index.js @@ -4,4 +4,5 @@ */ export * from "./memory-hfs.js"; +export { MemoryHfsVolume } from "./memory-hfs-volume.js"; export { Hfs } from "@humanfs/core"; diff --git a/packages/memory/src/memory-hfs-volume.js b/packages/memory/src/memory-hfs-volume.js index b6e2b4a..c47e5d7 100644 --- a/packages/memory/src/memory-hfs-volume.js +++ b/packages/memory/src/memory-hfs-volume.js @@ -583,6 +583,70 @@ export class MemoryHfsVolume { })); } + /** + * Moves an object to a new parent. + * @param {string} id The ID of the object to move. + * @param {string} parentId The ID of the new parent directory. + * @returns {void} + * @throws {NotFoundError} If the object or parent is not found. + * @throws {DirectoryError} If the parent is not a directory. + */ + moveObject(id, parentId) { + const object = this.#objects.get(id); + + if (!object) { + throw new NotFoundError(`moveObject ${id}`); + } + + const parent = this.#objects.get(parentId); + + if (!parent) { + throw new NotFoundError(`moveObject ${parentId}`); + } + + if (parent.kind !== "directory") { + throw new DirectoryError(`moveObject ${parentId}`); + } + + const directory = /** @type {MemoryHfsDirectory} */ (parent); + + object.parent.delete(object.name); + directory.add(object); + } + + /** + * Copies an object to a new parent. + * @param {string} id The ID of the object to copy. + * @param {string} parentId The ID of the new parent directory. + * @returns {void} + * @throws {NotFoundError} If the object or parent is not found. + * @throws {DirectoryError} If the parent is not a directory. + * @throws {DirectoryError} If the parent is a file. + */ + copyObject(id, parentId) { + const object = this.#objects.get(id); + + if (!object) { + throw new NotFoundError(`copyObject ${id}`); + } + + const parent = this.#objects.get(parentId); + + if (!parent) { + throw new NotFoundError(`copyObject ${parentId}`); + } + + if (parent.kind !== "directory") { + throw new DirectoryError(`copyObject ${parentId}`); + } + + const directory = /** @type {MemoryHfsDirectory} */ (parent); + const copy = object.clone(); + + directory.add(copy); + this.#objects.set(copy.id, copy); + } + //----------------------------------------------------------------------------- // Path-Based Methods //----------------------------------------------------------------------------- diff --git a/packages/memory/tests/memory-hfs-volume.test.js b/packages/memory/tests/memory-hfs-volume.test.js index cd23eef..2622ab0 100644 --- a/packages/memory/tests/memory-hfs-volume.test.js +++ b/packages/memory/tests/memory-hfs-volume.test.js @@ -475,4 +475,94 @@ describe("MemoryHfsVolume", () => { assert.throws(() => volume.readDirectoryObject(id), /EISDIR/); }); }); + + describe("moveObject()", () => { + it("should move a file to a new location", () => { + volume.writeFile("file.txt", HELLO_WORLD); + volume.mkdirp("dir"); + + const id = volume.getObjectIdFromPath("file.txt"); + const parentId = volume.getObjectIdFromPath("dir"); + volume.moveObject(id, parentId); + + const file = volume.readFile("dir/file.txt"); + assert.deepEqual(file, HELLO_WORLD); + }); + + it("should move a directory to a new location", () => { + volume.mkdirp("dir"); + volume.mkdirp("dir2"); + + const id = volume.getObjectIdFromPath("dir"); + const parentId = volume.getObjectIdFromPath("dir2"); + volume.moveObject(id, parentId); + + const stat = volume.stat("dir2/dir"); + assert.strictEqual(stat.kind, "directory"); + }); + + it("should throw an error when the ID doesn't exist", () => { + assert.throws(() => volume.moveObject("file-1", "dir-1"), /ENOENT/); + }); + + it("should throw an error when the parent ID doesn't exist", () => { + volume.writeFile("file.txt", HELLO_WORLD); + const id = volume.getObjectIdFromPath("file.txt"); + assert.throws(() => volume.moveObject(id, "dir-1"), /ENOENT/); + }); + + it("should throw an error when the parent ID is a file", () => { + volume.writeFile("file.txt", HELLO_WORLD); + volume.writeFile("file2.txt", HELLO_WORLD); + + const id = volume.getObjectIdFromPath("file.txt"); + const parentId = volume.getObjectIdFromPath("file2.txt"); + assert.throws(() => volume.moveObject(id, parentId), /EISDIR/); + }); + }); + + describe("copyObject()", () => { + it("should copy a file to a new location", () => { + volume.writeFile("file.txt", HELLO_WORLD); + volume.mkdirp("dir"); + + const id = volume.getObjectIdFromPath("file.txt"); + const parentId = volume.getObjectIdFromPath("dir"); + volume.copyObject(id, parentId); + + const file = volume.readFile("dir/file.txt"); + assert.deepEqual(file, HELLO_WORLD); + }); + + it("should copy a directory to a new location", () => { + volume.mkdirp("dir"); + volume.mkdirp("dir2"); + + const id = volume.getObjectIdFromPath("dir"); + const parentId = volume.getObjectIdFromPath("dir2"); + volume.copyObject(id, parentId); + + const stat = volume.stat("dir2/dir"); + assert.strictEqual(stat.kind, "directory"); + }); + + it("should throw an error when the ID doesn't exist", () => { + assert.throws(() => volume.copyObject("file-1", "dir-1"), /ENOENT/); + }); + + it("should throw an error when the parent ID doesn't exist", () => { + volume.writeFile("file.txt", HELLO_WORLD); + const id = volume.getObjectIdFromPath("file.txt"); + assert.throws(() => volume.copyObject(id, "dir-1"), /ENOENT/); + }); + + it("should throw an error when the parent ID is a file", () => { + volume.writeFile("file.txt", HELLO_WORLD); + volume.writeFile("file2.txt", HELLO_WORLD); + + const id = volume.getObjectIdFromPath("file.txt"); + const parentId = volume.getObjectIdFromPath("file2.txt"); + assert.throws(() => volume.copyObject(id, parentId), /EISDIR/); + }); + }); });