@humanfs/test
Version:
Testing utilities for hfs
1,400 lines (1,184 loc) • 44.4 kB
JavaScript
/**
* @fileoverview Tester for HfsImpl classes. This is used to ensure that all
* HfsImpl classes have the same API and behavior.
*/
/* global TextEncoder, TextDecoder, URL */
//------------------------------------------------------------------------------
// Types
//------------------------------------------------------------------------------
/** @typedef {import("@humanfs/types").HfsImpl} HfsImpl*/
/**
* @typedef {Object} Assert
* @property {Function} strictEqual Asserts that two values are strictly equal.
* @property {Function} rejects Asserts that a promise rejects.
* @property {Function} deepStrictEqual Asserts that two values are deeply equal.
* @property {Function} ok Asserts that a value is truthy.
* @property {Function} fail Asserts that a value is falsy.
*/
/**
* @typedef {Object} BddTest
* @property {Function} it A test function.
* @property {Function} describe A test suite function.
* @property {Function} beforeEach A function to run before each test.
* @property {Function} afterEach A function to run after each test.
*/
//-----------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------
const encoder = new TextEncoder();
const HELLO_WORLD = "Hello, world!";
const GOODBYE_WORLD = "Goodbye, world!";
const HELLO_WORLD_BYTES = encoder.encode(HELLO_WORLD);
const GOODBYE_WORLD_BYTES = encoder.encode(GOODBYE_WORLD);
const HELLO_WORLD_JSON = encoder.encode(
JSON.stringify({ message: HELLO_WORLD }),
);
/**
* Converts a file path into a URL with a file protocol. This first normalizes
* the file path to use forward slashes and then creates a URL with the file
* protocol.
* @param {string} filePath The file path to convert.
* @returns {URL} The URL representing the file path.
*/
function filePathToUrl(filePath) {
const normalizedPath = filePath.replace(/\\/g, "/");
return new URL("file:///" + normalizedPath);
}
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
export class HfsImplTester {
/**
* The assert object to use.
* @type {Assert}
*/
#assert;
/**
* The test library to use.
* @type {BddTest}
*/
#test;
/**
* The directory where output files should be written.
* @type {string}
*/
#outputDir;
/**
* The expected entries in the output directory.
* @type {string[]|undefined}
*/
#expectedEntries;
/**
* Creates a new instance.
* @param {object} options Options for the tester.
* @param {Assert} options.assert The assert function to use.
* @param {BddTest} options.test The test library to use.
* @param {string} options.outputDir The directory where output files should be written.
* @param {string[]} [options.expectedEntries] The expected entries in the output directory.
*/
constructor({ assert, test, outputDir, expectedEntries }) {
if (!outputDir) {
throw new Error("outputDir is required");
}
this.#assert = assert;
this.#test = test;
this.#outputDir = outputDir;
this.#expectedEntries = expectedEntries;
}
/**
* Runs the tests.
* @param {object} options Options for the test.
* @param {string} options.name The name of the test.
* @param {HfsImpl} options.impl The HfsImpl instance to test.
* @returns {Promise<void>}
*/
async test({ name, impl }) {
const { it, describe, beforeEach, afterEach } = this.#test;
const assert = this.#assert;
describe(name, () => {
// set up the fixtures directory
beforeEach(async () => {
await impl.createDirectory(this.#outputDir);
await impl.write(
this.#outputDir + "/hello.txt",
HELLO_WORLD_BYTES,
);
await impl.write(
this.#outputDir + "/message.json",
HELLO_WORLD_JSON,
);
});
// clean up the fixtures directory
afterEach(async () => {
await impl.deleteAll(this.#outputDir);
});
if (impl.bytes) {
describe("bytes()", () => {
it("should read a file and return the contents as an Uint8Array", async () => {
const filePath = this.#outputDir + "/hello.txt";
const result = await impl.bytes(filePath);
assert.ok(result instanceof Uint8Array);
const decoder = new TextDecoder();
assert.strictEqual(decoder.decode(result), HELLO_WORLD);
});
it("should read a file and return the contents as an Uint8Array when using a file URL", async () => {
const filePath = this.#outputDir + "/hello.txt";
const fileUrl = filePathToUrl(filePath);
const result = await impl.bytes(fileUrl);
assert.ok(result instanceof Uint8Array);
const decoder = new TextDecoder();
assert.strictEqual(decoder.decode(result), HELLO_WORLD);
});
it("should return undefined when a file doesn't exist", async () => {
const result = await impl.bytes(
this.#outputDir + "/nonexistent.txt",
);
assert.strictEqual(
result,
undefined,
"Expected undefined when reading a nonexistent file",
);
});
it("should return undefined when a file with the given file URL doesn't exist", async () => {
const result = await impl.bytes(
filePathToUrl(this.#outputDir + "/nonexistent.txt"),
);
assert.strictEqual(
result,
undefined,
"Expected undefined when reading a nonexistent file",
);
});
});
}
describe("write()", () => {
beforeEach(async () => {
await impl.createDirectory(this.#outputDir + "/tmp-write");
});
afterEach(async () => {
await impl.deleteAll(this.#outputDir + "/tmp-write");
});
it("should write a Uint8Array to a file", async () => {
const filePath =
this.#outputDir +
"/tmp-write/test-generated-arraybuffer.txt";
await impl.write(filePath, HELLO_WORLD_BYTES);
// make sure the file was written
const resultBytes = await impl.bytes(filePath);
const result = new TextDecoder().decode(resultBytes);
assert.strictEqual(result, HELLO_WORLD);
});
it("should write a Uint8Array to a file URL", async () => {
const filePath =
this.#outputDir +
"/tmp-write/test-generated-arraybuffer.txt";
const fileUrl = filePathToUrl(filePath);
await impl.write(fileUrl, HELLO_WORLD_BYTES);
// make sure the file was written
const resultBytes = await impl.bytes(filePath);
const result = new TextDecoder().decode(resultBytes);
assert.strictEqual(result, HELLO_WORLD);
});
it("should write a Uint8Array subarray to a file", async () => {
const filePath =
this.#outputDir +
"/tmp-write/test-generated-arraybuffer.txt";
const bytes = HELLO_WORLD_BYTES.subarray(4, 7);
await impl.write(filePath, bytes);
// make sure the file was written
const resultBytes = await impl.bytes(filePath);
const result = new TextDecoder().decode(resultBytes);
assert.strictEqual(result, "o, ");
});
it("should write a Uint8Array subarray to a file URL", async () => {
const filePath =
this.#outputDir +
"/tmp-write/test-generated-arraybuffer.txt";
const fileUrl = filePathToUrl(filePath);
const bytes = HELLO_WORLD_BYTES.subarray(4, 7);
await impl.write(fileUrl, bytes);
// make sure the file was written
const resultBytes = await impl.bytes(filePath);
const result = new TextDecoder().decode(resultBytes);
assert.strictEqual(result, "o, ");
});
it("should write to an already existing file", async () => {
const filePath =
this.#outputDir + "/tmp-write/test-generated-text.txt";
await impl.write(filePath, HELLO_WORLD_BYTES);
await impl.write(filePath, GOODBYE_WORLD_BYTES);
// make sure the file was written
const resultBytes = await impl.bytes(filePath);
const result = new TextDecoder().decode(resultBytes);
assert.strictEqual(result, GOODBYE_WORLD);
});
it("should write to an already existing file URL", async () => {
const filePath =
this.#outputDir + "/tmp-write/test-generated-text.txt";
const fileUrl = filePathToUrl(filePath);
await impl.write(fileUrl, HELLO_WORLD_BYTES);
await impl.write(fileUrl, GOODBYE_WORLD_BYTES);
// make sure the file was written
const resultBytes = await impl.bytes(fileUrl);
const result = new TextDecoder().decode(resultBytes);
assert.strictEqual(result, "Goodbye, world!");
});
it("should write a file when the directory doesn't exist", async () => {
const filePath =
this.#outputDir +
"/tmp-write/nonexistent/test-generated-text.txt";
await impl.write(filePath, HELLO_WORLD_BYTES);
// make sure the file was written
const resultBytes = await impl.bytes(filePath);
const result = new TextDecoder().decode(resultBytes);
assert.strictEqual(result, HELLO_WORLD);
});
it("should write a file URL when the directory doesn't exist", async () => {
const filePath =
this.#outputDir +
"/tmp-write/nonexistent/test-generated-text.txt";
const fileUrl = filePathToUrl(filePath);
await impl.write(fileUrl, HELLO_WORLD_BYTES);
// make sure the file was written
const resultBytes = await impl.bytes(fileUrl);
const result = new TextDecoder().decode(resultBytes);
assert.strictEqual(result, HELLO_WORLD);
});
});
if (impl.append) {
describe("append()", () => {
const dirPath = this.#outputDir + "/tmp-append";
beforeEach(async () => {
await impl.createDirectory(dirPath);
});
afterEach(async () => {
await impl.deleteAll(dirPath);
});
it("should append a Uint8Array to a file", async () => {
const filePath =
dirPath + "/test-generated-arraybuffer.txt";
await impl.write(filePath, HELLO_WORLD_BYTES);
await impl.append(filePath, GOODBYE_WORLD_BYTES);
// make sure the file was written
const resultBytes = await impl.bytes(filePath);
const result = new TextDecoder().decode(resultBytes);
assert.strictEqual(result, HELLO_WORLD + GOODBYE_WORLD);
});
it("should append a Uint8Array to a file URL", async () => {
const filePath =
dirPath + "/test-generated-arraybuffer.txt";
const fileUrl = filePathToUrl(filePath);
await impl.write(fileUrl, HELLO_WORLD_BYTES);
await impl.append(fileUrl, GOODBYE_WORLD_BYTES);
// make sure the file was written
const resultBytes = await impl.bytes(filePath);
const result = new TextDecoder().decode(resultBytes);
assert.strictEqual(result, HELLO_WORLD + GOODBYE_WORLD);
});
it("should append a Uint8Array subarray to a file", async () => {
const filePath =
dirPath + "/test-generated-arraybuffer.txt";
const bytes = new TextEncoder()
.encode(HELLO_WORLD + " " + GOODBYE_WORLD)
.subarray(4, 7);
await impl.append(filePath, bytes);
// make sure the file was written
const resultBytes = await impl.bytes(filePath);
const result = new TextDecoder().decode(resultBytes);
assert.strictEqual(result, "o, ");
});
it("should append a Uint8Array subarray to a file URL", async () => {
const filePath =
dirPath + "/test-generated-arraybuffer.txt";
const fileUrl = filePathToUrl(filePath);
const bytes = new TextEncoder()
.encode(HELLO_WORLD + " " + GOODBYE_WORLD)
.subarray(4, 7);
await impl.append(fileUrl, bytes);
// make sure the file was written
const resultBytes = await impl.bytes(filePath);
const result = new TextDecoder().decode(resultBytes);
assert.strictEqual(result, "o, ");
});
it("should append to an already existing file", async () => {
const filePath = dirPath + "/test-generated-text.txt";
await impl.append(filePath, HELLO_WORLD_BYTES);
await impl.append(filePath, GOODBYE_WORLD_BYTES);
// make sure the file was written
const resultBytes = await impl.bytes(filePath);
const result = new TextDecoder().decode(resultBytes);
assert.strictEqual(result, HELLO_WORLD + GOODBYE_WORLD);
});
it("should append to an already existing file URL", async () => {
const filePath = dirPath + "/test-generated-text.txt";
const fileUrl = filePathToUrl(filePath);
await impl.append(fileUrl, HELLO_WORLD_BYTES);
await impl.append(fileUrl, GOODBYE_WORLD_BYTES);
// make sure the file was written
const resultBytes = await impl.bytes(fileUrl);
const result = new TextDecoder().decode(resultBytes);
assert.strictEqual(result, HELLO_WORLD + GOODBYE_WORLD);
});
it("should append to a file when the directory doesn't exist", async () => {
const filePath =
this.#outputDir +
"/tmp-append/nonexistent/test-generated-text.txt";
await impl.append(filePath, HELLO_WORLD_BYTES);
// make sure the file was written
const resultBytes = await impl.bytes(filePath);
const result = new TextDecoder().decode(resultBytes);
assert.strictEqual(result, HELLO_WORLD);
});
it("should append to a file URL when the directory doesn't exist", async () => {
const filePath =
this.#outputDir +
"/tmp-append/nonexistent/test-generated-text.txt";
const fileUrl = filePathToUrl(filePath);
await impl.append(fileUrl, HELLO_WORLD_BYTES);
// make sure the file was written
const resultBytes = await impl.bytes(fileUrl);
const result = new TextDecoder().decode(resultBytes);
assert.strictEqual(result, HELLO_WORLD);
});
it("should create a file when the file doesn't exist", async () => {
const filePath = dirPath + "/test-generated-text.txt";
await impl.append(filePath, HELLO_WORLD_BYTES);
// make sure the file was written
const resultBytes = await impl.bytes(filePath);
const result = new TextDecoder().decode(resultBytes);
assert.strictEqual(result, HELLO_WORLD);
});
it("should create a file when the file URL doesn't exist", async () => {
const filePath = dirPath + "/test-generated-text.txt";
const fileUrl = filePathToUrl(filePath);
await impl.append(fileUrl, HELLO_WORLD_BYTES);
// make sure the file was written
const resultBytes = await impl.bytes(fileUrl);
const result = new TextDecoder().decode(resultBytes);
assert.strictEqual(result, HELLO_WORLD);
});
});
}
describe("isFile()", () => {
it("should return true if a file exists", async () => {
const filePath = this.#outputDir + "/hello.txt";
const result = await impl.isFile(filePath);
assert.strictEqual(result, true);
});
it("should return true if a file URL exists", async () => {
const filePath = this.#outputDir + "/hello.txt";
const fileUrl = filePathToUrl(filePath);
const result = await impl.isFile(fileUrl);
assert.strictEqual(result, true);
});
it("should return false if a file doesn't exist", async () => {
const filePath = this.#outputDir + "/nonexistent.txt";
const result = await impl.isFile(filePath);
assert.strictEqual(result, false);
});
it("should return false if a file URL doesn't exist", async () => {
const filePath = this.#outputDir + "/nonexistent.txt";
const fileUrl = filePathToUrl(filePath);
const result = await impl.isFile(fileUrl);
assert.strictEqual(result, false);
});
it("should return false if a directory exists", async () => {
const result = await impl.isFile(this.#outputDir);
assert.strictEqual(result, false);
});
it("should return false if a directory exists at the file URL", async () => {
const dirUrl = filePathToUrl(this.#outputDir);
const result = await impl.isFile(dirUrl);
assert.strictEqual(result, false);
});
});
describe("isDirectory()", () => {
it("should return true if a directory exists", async () => {
const result = await impl.isDirectory(this.#outputDir);
assert.strictEqual(result, true);
});
it("should return true if a directory exists at the file URL", async () => {
const dirUrl = filePathToUrl(this.#outputDir);
const result = await impl.isDirectory(dirUrl);
assert.strictEqual(result, true);
});
it("should return false if a directory doesn't exist", async () => {
const dirPath = this.#outputDir + "/nonexistent";
const result = await impl.isDirectory(dirPath);
assert.strictEqual(result, false);
});
it("should return false if a directory doesn't exist at the file URL", async () => {
const dirUrl = filePathToUrl(
this.#outputDir + "/nonexistent",
);
const result = await impl.isDirectory(dirUrl);
assert.strictEqual(result, false);
});
it("should return false if a file exists", async () => {
const filePath = this.#outputDir + "/hello.txt";
const result = await impl.isDirectory(filePath);
assert.strictEqual(result, false);
});
it("should return false if a file exists at the file URL", async () => {
const filePath = this.#outputDir + "/hello.txt";
const fileUrl = filePathToUrl(filePath);
const result = await impl.isDirectory(fileUrl);
assert.strictEqual(result, false);
});
});
describe("createDirectory()", () => {
afterEach(async () => {
await impl.deleteAll(this.#outputDir + "/tmp-create");
});
it("should create a directory", async () => {
const dirPath = this.#outputDir + "/tmp-create";
await impl.createDirectory(dirPath);
const result = await impl.isDirectory(dirPath);
assert.ok(result, "Expected directory to exist");
});
it("should create a directory at the file URL", async () => {
const dirPath = this.#outputDir + "/tmp-create";
const dirUrl = filePathToUrl(dirPath);
await impl.createDirectory(dirUrl);
const result = await impl.isDirectory(dirPath);
assert.ok(result, "Expected directory to exist");
});
it("should create a directory recursively", async () => {
const dirPath = this.#outputDir + "/tmp-create/subdir";
await impl.createDirectory(dirPath);
const result = await impl.isDirectory(dirPath);
assert.ok(result, "Expected directory to exist");
});
it("should create a directory recursively at the file URL", async () => {
const dirPath = this.#outputDir + "/tmp-create/subdir";
const dirUrl = filePathToUrl(dirPath);
await impl.createDirectory(dirUrl);
const result = await impl.isDirectory(dirPath);
assert.ok(result, "Expected directory to exist");
});
it("should not reject a promise when the directory already exists", async () => {
const dirPath = this.#outputDir + "/tmp-create";
await impl.createDirectory(dirPath);
return impl.createDirectory(dirPath);
});
it("should not reject a promise when the directory already exists at the file URL", async () => {
const dirPath = this.#outputDir + "/tmp-create";
const dirUrl = filePathToUrl(dirPath);
await impl.createDirectory(dirUrl);
return impl.createDirectory(dirUrl);
});
});
if (impl.delete) {
describe("delete()", () => {
let dirPath = this.#outputDir + "/tmp-delete";
beforeEach(async () => {
await impl.createDirectory(dirPath);
await impl.createDirectory(dirPath + "/subdir");
await impl.createDirectory(dirPath + "/empty-subdir");
await impl.createDirectory(
dirPath + "/subdir/subsubdir",
);
await impl.write(
dirPath + "/subdir/subsubdir/test.txt",
HELLO_WORLD_BYTES,
);
});
afterEach(async () => {
await impl.deleteAll(dirPath);
});
it("should delete a file", async () => {
const filePath = dirPath + "/subdir/subsubdir/test.txt";
const result = await impl.delete(filePath);
assert.strictEqual(await impl.isFile(filePath), false);
assert.strictEqual(result, true);
});
it("should delete a file at the file URL", async () => {
const filePath = dirPath + "/subdir/subsubdir/test.txt";
const fileUrl = filePathToUrl(filePath);
const result = await impl.delete(fileUrl);
assert.strictEqual(await impl.isFile(filePath), false);
assert.strictEqual(result, true);
});
it("should delete an empty directory", async () => {
const emptySubdirPath = dirPath + "/empty-subdir";
const result = await impl.delete(emptySubdirPath);
assert.strictEqual(
await impl.isDirectory(emptySubdirPath),
false,
);
assert.strictEqual(result, true);
});
it("should delete an empty directory at the file URL", async () => {
const emptySubdirPath = dirPath + "/empty-subdir";
const emptySubdirUrl = filePathToUrl(emptySubdirPath);
const result = await impl.delete(emptySubdirUrl);
assert.strictEqual(
await impl.isDirectory(emptySubdirPath),
false,
);
assert.strictEqual(result, true);
});
it("should return false when the file doesn't exist", async () => {
const filePath = dirPath + "/nonexistent.txt";
const result = await impl.delete(filePath);
assert.strictEqual(result, false);
});
it("should return false when the file doesn't exist at the file URL", async () => {
const filePath = dirPath + "/nonexistent.txt";
const fileUrl = filePathToUrl(filePath);
const result = await impl.delete(fileUrl);
assert.strictEqual(result, false);
});
it("should reject a promise when path is a nonempty directory", async () => {
const subdirPath = dirPath + "/subdir";
await assert.rejects(
() => impl.delete(subdirPath),
/ENOTEMPTY/,
);
});
it("should reject a promise when path is a nonempty directory at the file URL", async () => {
const subdirPath = dirPath + "/subdir";
const subdirUrl = filePathToUrl(subdirPath);
await assert.rejects(
() => impl.delete(subdirUrl),
/ENOTEMPTY/,
);
});
});
}
describe("deleteAll()", () => {
let dirPath = this.#outputDir + "/tmp-delete";
beforeEach(async () => {
await impl.createDirectory(dirPath);
await impl.createDirectory(dirPath + "/subdir");
await impl.createDirectory(dirPath + "/empty-subdir");
await impl.createDirectory(dirPath + "/subdir/subsubdir");
await impl.write(
dirPath + "/subdir/subsubdir/test.txt",
HELLO_WORLD_BYTES,
);
});
afterEach(async () => {
await impl.deleteAll(dirPath);
});
it("should delete a file", async () => {
const filePath = dirPath + "/subdir/subsubdir/test.txt";
const result = await impl.deleteAll(filePath);
assert.strictEqual(await impl.isFile(filePath), false);
assert.strictEqual(result, true);
});
it("should delete a file at the file URL", async () => {
const filePath = dirPath + "/subdir/subsubdir/test.txt";
const fileUrl = filePathToUrl(filePath);
const result = await impl.deleteAll(fileUrl);
assert.strictEqual(await impl.isFile(filePath), false);
assert.strictEqual(result, true);
});
it("should delete a directory", async () => {
const subsubdirPath = dirPath + "/subdir/subsubdir";
const result = await impl.deleteAll(subsubdirPath);
assert.strictEqual(
await impl.isDirectory(subsubdirPath),
false,
);
assert.strictEqual(result, true);
});
it("should delete a directory at the file URL", async () => {
const subsubdirPath = dirPath + "/subdir/subsubdir";
const subsubdirUrl = filePathToUrl(subsubdirPath);
const result = await impl.deleteAll(subsubdirUrl);
assert.strictEqual(
await impl.isDirectory(subsubdirPath),
false,
);
assert.strictEqual(result, true);
});
it("should delete a directory recursively", async () => {
const subdirPath = dirPath + "/subdir";
const result = await impl.deleteAll(subdirPath);
assert.strictEqual(
await impl.isDirectory(subdirPath),
false,
);
assert.strictEqual(result, true);
});
it("should delete a directory recursively at the file URL", async () => {
const subdirPath = dirPath + "/subdir";
const subdirUrl = filePathToUrl(subdirPath);
const result = await impl.deleteAll(subdirUrl);
assert.strictEqual(
await impl.isDirectory(subdirPath),
false,
);
assert.strictEqual(result, true);
});
it("should return false when the file doesn't exist", async () => {
const filePath = dirPath + "/nonexistent.txt";
assert.strictEqual(await impl.isFile(filePath), false);
const result = await impl.deleteAll(filePath);
assert.strictEqual(result, false);
});
it("should return false when the file doesn't exist at the file URL", async () => {
const filePath = dirPath + "/nonexistent.txt";
const fileUrl = filePathToUrl(filePath);
assert.strictEqual(await impl.isFile(filePath), false);
const result = await impl.deleteAll(fileUrl);
assert.strictEqual(result, false);
});
});
if (impl.list) {
if (!this.#expectedEntries) {
throw new Error(
"expectedEntries is required for testing list()",
);
}
describe("list()", () => {
it("should return an async iterable", async () => {
const dirPath = this.#outputDir + "/tmp-list";
await impl.createDirectory(dirPath + "/subdir");
await impl.write(
dirPath + "/test1.txt",
HELLO_WORLD_BYTES,
);
await impl.write(
dirPath + "/test2.txt",
HELLO_WORLD_BYTES,
);
const result = await impl.list(dirPath);
assert.ok(result[Symbol.asyncIterator]);
});
it("should return an async iterable when using a file URL", async () => {
const dirPath = this.#outputDir + "/tmp-list";
await impl.createDirectory(dirPath + "/subdir");
await impl.write(
dirPath + "/test1.txt",
HELLO_WORLD_BYTES,
);
await impl.write(
dirPath + "/test2.txt",
HELLO_WORLD_BYTES,
);
const dirUrl = filePathToUrl(dirPath);
const result = await impl.list(dirUrl);
assert.ok(result[Symbol.asyncIterator]);
});
it("should list the contents of the top-level directory", async () => {
const result = [];
for await (const entry of impl.list(".")) {
result.push(entry.name);
}
assert.deepStrictEqual(result, this.#expectedEntries);
});
it("should list the contents of a directory", async () => {
const dirPath = this.#outputDir + "/tmp-list";
await impl.createDirectory(dirPath + "/subdir");
await impl.write(
dirPath + "/test1.txt",
HELLO_WORLD_BYTES,
);
await impl.write(
dirPath + "/test2.txt",
HELLO_WORLD_BYTES,
);
const result = [];
for await (const entry of impl.list(dirPath)) {
result.push(entry);
}
const expected = [
{
name: "subdir",
isDirectory: true,
isFile: false,
isSymlink: false,
},
{
name: "test1.txt",
isDirectory: false,
isFile: true,
isSymlink: false,
},
{
name: "test2.txt",
isDirectory: false,
isFile: true,
isSymlink: false,
},
];
for (const entry of expected) {
const item = result.find(
item => item.name === entry.name,
);
assert.ok(item);
assert.strictEqual(
item.isDirectory,
entry.isDirectory,
);
assert.strictEqual(item.isFile, entry.isFile);
assert.strictEqual(item.isSymlink, entry.isSymlink);
}
});
it("should list the contents of a directory when using a file URL", async () => {
const dirPath = this.#outputDir + "/tmp-list";
await impl.createDirectory(dirPath + "/subdir");
await impl.write(
dirPath + "/test1.txt",
HELLO_WORLD_BYTES,
);
await impl.write(
dirPath + "/test2.txt",
HELLO_WORLD_BYTES,
);
const dirUrl = filePathToUrl(dirPath);
const result = [];
for await (const entry of impl.list(dirUrl)) {
result.push(entry);
}
const expected = [
{
name: "subdir",
isDirectory: true,
isFile: false,
isSymlink: false,
},
{
name: "test1.txt",
isDirectory: false,
isFile: true,
isSymlink: false,
},
{
name: "test2.txt",
isDirectory: false,
isFile: true,
isSymlink: false,
},
];
for (const entry of expected) {
const item = result.find(
item => item.name === entry.name,
);
assert.ok(item);
assert.strictEqual(
item.isDirectory,
entry.isDirectory,
);
assert.strictEqual(item.isFile, entry.isFile);
assert.strictEqual(item.isSymlink, entry.isSymlink);
}
});
});
}
if (impl.size) {
describe("size()", () => {
it("should return the size of a file", async () => {
const filePath = this.#outputDir + "/hello.txt";
const result = await impl.size(filePath);
assert.strictEqual(result, 13);
});
it("should return the size of a file when using a file URL", async () => {
const filePath = this.#outputDir + "/hello.txt";
const fileUrl = filePathToUrl(filePath);
const result = await impl.size(fileUrl);
assert.strictEqual(result, 13);
});
it("should return undefined if the file doesn't exist", async () => {
const filePath = this.#outputDir + "/nonexistent.txt";
const result = await impl.size(filePath);
assert.strictEqual(result, undefined);
});
it("should return undefined if the file doesn't exist at the file URL", async () => {
const filePath = this.#outputDir + "/nonexistent.txt";
const fileUrl = filePathToUrl(filePath);
const result = await impl.size(fileUrl);
assert.strictEqual(result, undefined);
});
});
}
if (impl.lastModified) {
describe("lastModified()", () => {
it("should return the last modified date of a file", async () => {
const filePath = this.#outputDir + "/hello.txt";
const result = await impl.lastModified(filePath);
assert.ok(result instanceof Date);
});
it("should return the last modified date of a directory", async () => {
const result = await impl.lastModified(this.#outputDir);
assert.ok(result instanceof Date);
});
it("should return the last modified date of a file when using a file URL", async () => {
const filePath = this.#outputDir + "/hello.txt";
const fileUrl = filePathToUrl(filePath);
const result = await impl.lastModified(fileUrl);
assert.ok(result instanceof Date);
});
it("should return undefined if the file doesn't exist", async () => {
const filePath = this.#outputDir + "/nonexistent.txt";
const result = await impl.lastModified(filePath);
assert.strictEqual(result, undefined);
});
it("should return undefined if the directory doesn't exist", async () => {
const dirPath = this.#outputDir + "/nonexistent";
const result = await impl.lastModified(dirPath);
assert.strictEqual(result, undefined);
});
it("should return undefined if the file doesn't exist at the file URL", async () => {
const filePath = this.#outputDir + "/nonexistent.txt";
const fileUrl = filePathToUrl(filePath);
const result = await impl.lastModified(fileUrl);
assert.strictEqual(result, undefined);
});
});
}
if (impl.copy) {
describe("copy()", () => {
let dirPath = this.#outputDir + "/tmp-copy";
beforeEach(async () => {
await impl.createDirectory(dirPath);
await impl.createDirectory(dirPath + "/subdir");
await impl.createDirectory(dirPath + "/empty-subdir");
await impl.createDirectory(
dirPath + "/subdir/subsubdir",
);
await impl.write(
dirPath + "/subdir/subsubdir/test.txt",
HELLO_WORLD_BYTES,
);
});
afterEach(async () => {
await impl.deleteAll(dirPath);
});
it("should copy a file", async () => {
const sourcePath =
dirPath + "/subdir/subsubdir/test.txt";
const destPath =
dirPath + "/subdir/subsubdir/test-copy.txt";
await impl.copy(sourcePath, destPath);
assert.strictEqual(await impl.isFile(destPath), true);
});
it("should copy a file at the file URL", async () => {
const sourcePath =
dirPath + "/subdir/subsubdir/test.txt";
const destPath =
dirPath + "/subdir/subsubdir/test-copy.txt";
const sourceUrl = filePathToUrl(sourcePath);
const destUrl = filePathToUrl(destPath);
await impl.copy(sourceUrl, destUrl);
assert.strictEqual(await impl.isFile(destPath), true);
});
/*
* Different runtimes return different error codes
* when a directory is used in place of a file.
*
* Node.js on Windows: EPERM
* Node.js on macOS: ENOTSUP
* Node.js on Linux: EISDIR
* Deno: EPERM
*
* Unfortunately, that means we need to accept any of
* these errors for the following tests.
*
* Note: EISDIR is the most accurate error code but
* others are accepted for compatibility.
*/
it("should reject a promise when attempting to copy a directory", async () => {
const sourcePath = dirPath + "/subdir";
const destPath = dirPath + "/subdir-copy";
await assert.rejects(
() => impl.copy(sourcePath, destPath),
/EISDIR|EPERM|ENOTSUP/,
);
});
it("should reject a promise when attempting to copy a directory at the file URL", async () => {
const sourcePath = dirPath + "/subdir";
const destPath = dirPath + "/subdir-copy";
const sourceUrl = filePathToUrl(sourcePath);
const destUrl = filePathToUrl(destPath);
await assert.rejects(
() => impl.copy(sourceUrl, destUrl),
/EISDIR|EPERM|ENOTSUP/,
);
});
it("should reject a promise when the destination is a directory", async () => {
const sourcePath =
dirPath + "/subdir/subsubdir/test.txt";
const destPath = dirPath + "/subdir";
await assert.rejects(
() => impl.copy(sourcePath, destPath),
/EISDIR|EPERM|ENOTSUP/,
);
});
it("should reject a promise when the source file doesn't exist", async () => {
const sourcePath = dirPath + "/nonexistent.txt";
const destPath = dirPath + "/nonexistent-copy.txt";
await assert.rejects(
() => impl.copy(sourcePath, destPath),
/ENOENT/,
);
});
});
}
if (impl.copyAll) {
describe("copyAll()", () => {
let dirPath = this.#outputDir + "/tmp-copy-all";
beforeEach(async () => {
await impl.createDirectory(dirPath);
await impl.createDirectory(dirPath + "/subdir");
await impl.createDirectory(dirPath + "/empty-subdir");
await impl.createDirectory(
dirPath + "/subdir/subsubdir",
);
await impl.write(
dirPath + "/subdir/subsubdir/test.txt",
HELLO_WORLD_BYTES,
);
});
afterEach(async () => {
await impl.deleteAll(dirPath);
});
it("should copy a file", async () => {
const sourcePath =
dirPath + "/subdir/subsubdir/test.txt";
const destPath =
dirPath + "/subdir/subsubdir/test-copy.txt";
await impl.copyAll(sourcePath, destPath);
assert.strictEqual(await impl.isFile(destPath), true);
});
it("should copy a file at the file URL", async () => {
const sourcePath =
dirPath + "/subdir/subsubdir/test.txt";
const destPath =
dirPath + "/subdir/subsubdir/test-copy.txt";
const sourceUrl = filePathToUrl(sourcePath);
const destUrl = filePathToUrl(destPath);
await impl.copyAll(sourceUrl, destUrl);
assert.strictEqual(await impl.isFile(destPath), true);
});
it("should copy a directory and it's subdirectories", async () => {
const sourcePath = dirPath + "/subdir";
const destPath = dirPath + "/subdir-copy";
await impl.copyAll(sourcePath, destPath);
assert.strictEqual(
await impl.isDirectory(destPath),
true,
);
assert.strictEqual(
await impl.isFile(destPath + "/subsubdir/test.txt"),
true,
);
});
it("should copy a directory and its subdirectories at the file URL", async () => {
const sourcePath = dirPath + "/subdir";
const destPath = dirPath + "/subdir-copy";
const sourceUrl = filePathToUrl(sourcePath);
const destUrl = filePathToUrl(destPath);
await impl.copyAll(sourceUrl, destUrl);
assert.strictEqual(
await impl.isDirectory(destPath),
true,
);
assert.strictEqual(
await impl.isFile(destPath + "/subsubdir/test.txt"),
true,
);
});
it("should reject a promise when the source file doesn't exist", async () => {
const sourcePath = dirPath + "/nonexistent.txt";
const destPath = dirPath + "/nonexistent-copy.txt";
await assert.rejects(
() => impl.copyAll(sourcePath, destPath),
/ENOENT/,
);
});
});
}
if (impl.move) {
describe("move()", () => {
let dirPath = this.#outputDir + "/tmp-move";
beforeEach(async () => {
await impl.createDirectory(dirPath);
await impl.createDirectory(dirPath + "/subdir");
await impl.write(
dirPath + "/test1.txt",
HELLO_WORLD_BYTES,
);
await impl.write(
dirPath + "/test2.txt",
HELLO_WORLD_BYTES,
);
});
afterEach(async () => {
await impl.deleteAll(dirPath);
});
it("should move a file", async () => {
const filePath = dirPath + "/test1.txt";
const newFilePath = dirPath + "/test1-moved.txt";
await impl.move(filePath, newFilePath);
assert.strictEqual(await impl.isFile(filePath), false);
assert.strictEqual(
await impl.isFile(newFilePath),
true,
);
const resultBytes = await impl.bytes(newFilePath);
const text = new TextDecoder().decode(resultBytes);
assert.strictEqual(text, HELLO_WORLD);
});
it("should move a file at the file URL", async () => {
const filePath = dirPath + "/test1.txt";
const newFilePath = dirPath + "/test1-moved.txt";
const fileUrl = filePathToUrl(filePath);
const newFileUrl = filePathToUrl(newFilePath);
await impl.move(fileUrl, newFileUrl);
assert.strictEqual(await impl.isFile(filePath), false);
assert.strictEqual(
await impl.isFile(newFilePath),
true,
);
const resultBytes = await impl.bytes(newFilePath);
const text = new TextDecoder().decode(resultBytes);
assert.strictEqual(text, HELLO_WORLD);
});
it("should move a file to a new directory", async () => {
const filePath = dirPath + "/test1.txt";
const newFilePath = dirPath + "/subdir/test1.txt";
await impl.move(filePath, newFilePath);
assert.strictEqual(
await impl.isFile(filePath),
false,
"File should not exist at the old location.",
);
assert.strictEqual(
await impl.isFile(newFilePath),
true,
"File should exist at the new location.",
);
const resultBytes = await impl.bytes(newFilePath);
const text = new TextDecoder().decode(resultBytes);
assert.strictEqual(
text,
HELLO_WORLD,
"The file should contain the correct contents at the new location.",
);
});
it("should reject a promise when the source file doesn't exist", async () => {
const filePath = dirPath + "/nonexistent.txt";
const newFilePath = dirPath + "/nonexistent-moved.txt";
await assert.rejects(
() => impl.move(filePath, newFilePath),
/ENOENT/,
);
});
it("should reject a promise when the source is a directory", async () => {
const subdirPath = dirPath + "/subdir";
const newFilePath = dirPath + "/subdir-moved";
await assert.rejects(
() => impl.move(subdirPath, newFilePath),
/EISDIR|EPERM|ENOTSUP/,
);
});
});
}
if (impl.moveAll) {
describe("moveAll()", () => {
let dirPath = this.#outputDir + "/tmp-move-all";
beforeEach(async () => {
await impl.createDirectory(dirPath);
await impl.createDirectory(dirPath + "/subdir");
await impl.write(
dirPath + "/test1.txt",
HELLO_WORLD_BYTES,
);
await impl.write(
dirPath + "/test2.txt",
HELLO_WORLD_BYTES,
);
await impl.write(
dirPath + "/subdir/test3.txt",
HELLO_WORLD_BYTES,
);
});
afterEach(async () => {
await impl.deleteAll(dirPath);
});
it("should move a file", async () => {
const sourcePath = dirPath + "/test1.txt";
const destPath = dirPath + "/test1-moved.txt";
await impl.moveAll(sourcePath, destPath);
assert.strictEqual(
await impl.isFile(sourcePath),
false,
);
assert.strictEqual(await impl.isFile(destPath), true);
const resultBytes = await impl.bytes(destPath);
const text = new TextDecoder().decode(resultBytes);
assert.strictEqual(text, HELLO_WORLD);
});
it("should move a file at the file URL", async () => {
const sourcePath = dirPath + "/test1.txt";
const destPath = dirPath + "/test1-moved.txt";
const sourceUrl = filePathToUrl(sourcePath);
const destUrl = filePathToUrl(destPath);
await impl.moveAll(sourceUrl, destUrl);
assert.strictEqual(
await impl.isFile(sourcePath),
false,
);
assert.strictEqual(await impl.isFile(destPath), true);
const resultBytes = await impl.bytes(destPath);
const text = new TextDecoder().decode(resultBytes);
assert.strictEqual(text, HELLO_WORLD);
});
it("should move a file to a new directory", async () => {
const sourcePath = dirPath + "/test1.txt";
const destPath = dirPath + "/subdir/test1.txt";
await impl.moveAll(sourcePath, destPath);
assert.strictEqual(
await impl.isFile(sourcePath),
false,
"File should not exist at the old location.",
);
assert.strictEqual(
await impl.isFile(destPath),
true,
"File should exist at the new location.",
);
const resultBytes = await impl.bytes(destPath);
const text = new TextDecoder().decode(resultBytes);
assert.strictEqual(
text,
HELLO_WORLD,
"The file should contain the correct contents at the new location.",
);
});
it("should reject a promise when the source doesn't exist", async () => {
const sourcePath = dirPath + "/nonexistent.txt";
const destPath = dirPath + "/nonexistent-moved.txt";
await assert.rejects(
() => impl.moveAll(sourcePath, destPath),
/ENOENT/,
);
});
it("should move a directory to a new directory", async () => {
const sourcePath = dirPath + "/subdir";
const destPath = dirPath + "/subdir-moved";
await impl.moveAll(sourcePath, destPath);
assert.strictEqual(
await impl.isDirectory(sourcePath),
false,
"Directory should not exist at the old location.",
);
assert.strictEqual(
await impl.isDirectory(destPath),
true,
"Directory should exist at the new location.",
);
const resultBytes = await impl.bytes(
destPath + "/test3.txt",
);
const text = new TextDecoder().decode(resultBytes);
assert.strictEqual(text, HELLO_WORLD); //, "The file should contain the correct contents at the new location.");
});
});
}
});
}
}