Files
sashinexists/node_modules/@weborigami/async-tree/test/Tree.test.js
2024-12-07 13:18:31 +11:00

387 lines
9.8 KiB
JavaScript

import assert from "node:assert";
import { describe, test } from "node:test";
import MapTree from "../src/drivers/MapTree.js";
import { DeepObjectTree, ObjectTree, Tree } from "../src/internal.js";
import * as symbols from "../src/symbols.js";
describe("Tree", () => {
test("assign applies one tree to another", async () => {
const target = new DeepObjectTree({
a: 1,
b: 2,
more: {
d: 3,
},
});
const source = new DeepObjectTree({
a: 4, // Overwrite existing value
b: undefined, // Delete
c: 5, // Add
more: {
// Should leave existing `more` keys alone.
e: 6, // Add
},
// Add new subtree
extra: {
f: 7,
},
});
// Apply changes.
const result = await Tree.assign(target, source);
assert.equal(result, target);
const plain = await Tree.plain(target);
assert.deepEqual(plain, {
a: 4,
c: 5,
more: {
d: 3,
e: 6,
},
extra: {
f: 7,
},
});
});
test("assign() can apply updates to an array", async () => {
const target = new ObjectTree(["a", "b", "c"]);
await Tree.assign(target, ["d", "e"]);
assert.deepEqual(await Tree.plain(target), ["d", "e", "c"]);
});
test("clear() removes all values", async () => {
const fixture = createFixture();
await Tree.clear(fixture);
assert.deepEqual(Array.from(await Tree.entries(fixture)), []);
});
test("entries() returns the [key, value] pairs", async () => {
const fixture = createFixture();
assert.deepEqual(Array.from(await Tree.entries(fixture)), [
["Alice.md", "Hello, **Alice**."],
["Bob.md", "Hello, **Bob**."],
["Carol.md", "Hello, **Carol**."],
]);
});
test("forEach() invokes a callback for each entry", async () => {
const fixture = createFixture();
const results = {};
await Tree.forEach(fixture, async (value, key) => {
results[key] = value;
});
assert.deepEqual(results, {
"Alice.md": "Hello, **Alice**.",
"Bob.md": "Hello, **Bob**.",
"Carol.md": "Hello, **Carol**.",
});
});
test("from() returns an async tree as is", async () => {
const tree1 = new ObjectTree({
a: "Hello, a.",
});
const tree2 = Tree.from(tree1);
assert.equal(tree2, tree1);
});
test("from() uses an object's unpack() method if defined", async () => {
const obj = new String();
/** @type {any} */ (obj).unpack = () => ({
a: "Hello, a.",
});
const tree = Tree.from(obj);
assert.deepEqual(await Tree.plain(tree), {
a: "Hello, a.",
});
});
test("from returns a deep object tree if deep option is true", async () => {
const obj = {
sub: {
a: 1,
},
};
const tree = Tree.from(obj, { deep: true });
assert(tree instanceof DeepObjectTree);
});
test("from returns a deep object tree if object has [deep] symbol set", async () => {
const obj = {
sub: {
a: 1,
},
};
Object.defineProperty(obj, symbols.deep, { value: true });
const tree = Tree.from(obj);
assert(tree instanceof DeepObjectTree);
});
test("from() creates a deferred tree if unpack() returns a promise", async () => {
const obj = new String();
/** @type {any} */ (obj).unpack = async () => ({
a: "Hello, a.",
});
const tree = Tree.from(obj);
assert.deepEqual(await Tree.plain(tree), {
a: "Hello, a.",
});
});
test("from() autoboxes primitive values", async () => {
const tree = Tree.from("Hello, world.");
const slice = await tree.get("slice");
const result = await slice(0, 5);
assert.equal(result, "Hello");
});
test("has returns true if the key exists", async () => {
const fixture = createFixture();
assert.equal(await Tree.has(fixture, "Alice.md"), true);
assert.equal(await Tree.has(fixture, "David.md"), false);
});
test("isAsyncTree returns true if the object is a tree", () => {
const missingGetAndKeys = {};
assert(!Tree.isAsyncTree(missingGetAndKeys));
const missingIterator = {
async get() {},
};
assert(!Tree.isAsyncTree(missingIterator));
const missingGet = {
async keys() {},
};
assert(!Tree.isAsyncTree(missingGet));
const hasGetAndKeys = {
async get() {},
async keys() {},
};
assert(Tree.isAsyncTree(hasGetAndKeys));
});
test("isAsyncMutableTree returns true if the object is a mutable tree", () => {
assert.equal(
Tree.isAsyncMutableTree({
get() {},
keys() {},
}),
false
);
assert.equal(Tree.isAsyncMutableTree(createFixture()), true);
});
test("isTreelike() returns true if the argument can be cast to an async tree", () => {
assert(!Tree.isTreelike(null));
assert(Tree.isTreelike({}));
assert(Tree.isTreelike([]));
assert(Tree.isTreelike(new Map()));
assert(Tree.isTreelike(new Set()));
});
test("map() maps values", async () => {
const tree = new DeepObjectTree({
a: "Alice",
more: {
b: "Bob",
},
});
const mapped = Tree.map(tree, {
deep: true,
value: (value) => value.toUpperCase(),
});
assert.deepEqual(await Tree.plain(mapped), {
a: "ALICE",
more: {
b: "BOB",
},
});
});
test("mapReduce() can map values and reduce them", async () => {
const tree = new DeepObjectTree({
a: 1,
b: 2,
more: {
c: 3,
},
d: 4,
});
const reduced = await Tree.mapReduce(
tree,
(value) => value,
async (values) => String.prototype.concat(...values)
);
assert.deepEqual(reduced, "1234");
});
test("paths returns an array of paths to the values in the tree", async () => {
const tree = new DeepObjectTree({
a: 1,
b: 2,
c: {
d: 3,
e: 4,
},
});
assert.deepEqual(await Tree.paths(tree), ["a", "b", "c/d", "c/e"]);
});
test("plain() produces a plain object version of a tree", async () => {
const tree = new ObjectTree({
a: 1,
// Slashes should be normalized
"sub1/": {
b: 2,
},
sub2: {
c: 3,
},
});
const plain = await Tree.plain(tree);
assert.deepEqual(plain, {
a: 1,
sub1: {
b: 2,
},
sub2: {
c: 3,
},
});
});
test("plain() produces an array for an array-like tree", async () => {
const original = ["a", "b", "c"];
const tree = new ObjectTree(original);
const plain = await Tree.plain(tree);
assert.deepEqual(plain, original);
});
test("plain() leaves an array-like tree as an object if keys aren't consecutive", async () => {
const original = {
0: "a",
1: "b",
// missing
3: "c",
};
const tree = new ObjectTree(original);
const plain = await Tree.plain(tree);
assert.deepEqual(plain, original);
});
test("plain() returns empty array or object for ObjectTree as necessary", async () => {
const tree = new ObjectTree({});
assert.deepEqual(await Tree.plain(tree), {});
const arrayTree = new ObjectTree([]);
assert.deepEqual(await Tree.plain(arrayTree), []);
});
test("plain() awaits async properties", async () => {
const object = {
get name() {
return Promise.resolve("Alice");
},
};
assert.deepEqual(await Tree.plain(object), { name: "Alice" });
});
test("plain() coerces TypedArray values to strings", async () => {
const tree = new ObjectTree({
a: new TextEncoder().encode("Hello, world."),
});
const plain = await Tree.plain(tree);
assert.equal(plain.a, "Hello, world.");
});
test("remove method removes a value", async () => {
const fixture = createFixture();
await Tree.remove(fixture, "Alice.md");
assert.deepEqual(Array.from(await Tree.entries(fixture)), [
["Bob.md", "Hello, **Bob**."],
["Carol.md", "Hello, **Carol**."],
]);
});
test("toFunction returns a function that invokes a tree's get() method", async () => {
const tree = new ObjectTree({
a: 1,
b: 2,
});
const fn = Tree.toFunction(tree);
assert.equal(await fn("a"), 1);
assert.equal(await fn("b"), 2);
});
test("traverse() a path of keys", async () => {
const tree = new ObjectTree({
a1: 1,
a2: {
b1: 2,
b2: {
c1: 3,
c2: 4,
},
},
});
assert.equal(await Tree.traverse(tree), tree);
assert.equal(await Tree.traverse(tree, "a1"), 1);
assert.equal(await Tree.traverse(tree, "a2", "b2", "c2"), 4);
assert.equal(
await Tree.traverse(tree, "a2", "doesntexist", "c2"),
undefined
);
});
test("traverse() a function with fixed number of arguments", async () => {
const tree = (a, b) => ({
c: "Result",
});
assert.equal(await Tree.traverse(tree, "a", "b", "c"), "Result");
});
test("traverse() from one tree into another", async () => {
const tree = new ObjectTree({
a: {
b: new MapTree([
["c", "Hello"],
["d", "Goodbye"],
]),
},
});
assert.equal(await Tree.traverse(tree, "a", "b", "c"), "Hello");
});
test("traversePath() traverses a slash-separated path", async () => {
const tree = new ObjectTree({
a: {
b: {
c: "Hello",
},
},
});
assert.equal(await Tree.traversePath(tree, "a/b/c"), "Hello");
});
test("values() returns the store's values", async () => {
const fixture = createFixture();
assert.deepEqual(Array.from(await Tree.values(fixture)), [
"Hello, **Alice**.",
"Hello, **Bob**.",
"Hello, **Carol**.",
]);
});
});
function createFixture() {
return new ObjectTree({
"Alice.md": "Hello, **Alice**.",
"Bob.md": "Hello, **Bob**.",
"Carol.md": "Hello, **Carol**.",
});
}