@weborigami/async-tree 0.0.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ReadMe.md +1 -0
- package/browser.js +13 -0
- package/index.ts +44 -0
- package/main.js +23 -0
- package/package.json +20 -0
- package/src/BrowserFileTree.js +137 -0
- package/src/DeferredTree.js +72 -0
- package/src/FileTree.js +193 -0
- package/src/FunctionTree.js +47 -0
- package/src/MapTree.js +51 -0
- package/src/ObjectTree.js +103 -0
- package/src/SetTree.js +40 -0
- package/src/SiteTree.js +124 -0
- package/src/Tree.d.ts +22 -0
- package/src/Tree.js +404 -0
- package/src/keysJson.d.ts +4 -0
- package/src/keysJson.js +56 -0
- package/src/operations/cache.js +75 -0
- package/src/operations/merge.js +60 -0
- package/src/operations/mergeDeep.js +47 -0
- package/src/transforms/cachedKeyMaps.js +86 -0
- package/src/transforms/groupBy.js +40 -0
- package/src/transforms/keyMapsForExtensions.js +64 -0
- package/src/transforms/map.js +106 -0
- package/src/transforms/regExpKeys.js +65 -0
- package/src/transforms/sort.js +22 -0
- package/src/transforms/sortBy.js +29 -0
- package/src/transforms/sortNatural.js +10 -0
- package/src/utilities.d.ts +10 -0
- package/src/utilities.js +124 -0
- package/test/BrowserFileTree.test.js +119 -0
- package/test/DeferredTree.test.js +23 -0
- package/test/FileTree.test.js +134 -0
- package/test/FunctionTree.test.js +51 -0
- package/test/MapTree.test.js +37 -0
- package/test/ObjectTree.test.js +159 -0
- package/test/SetTree.test.js +32 -0
- package/test/SiteTree.test.js +110 -0
- package/test/Tree.test.js +347 -0
- package/test/browser/assert.js +45 -0
- package/test/browser/index.html +35 -0
- package/test/browser/testRunner.js +51 -0
- package/test/fixtures/markdown/Alice.md +1 -0
- package/test/fixtures/markdown/Bob.md +1 -0
- package/test/fixtures/markdown/Carol.md +1 -0
- package/test/operations/cache.test.js +57 -0
- package/test/operations/merge.test.js +39 -0
- package/test/operations/mergeDeep.test.js +38 -0
- package/test/transforms/cachedKeyMaps.test.js +41 -0
- package/test/transforms/groupBy.test.js +29 -0
- package/test/transforms/keyMapsForExtensions.test.js +70 -0
- package/test/transforms/map.test.js +174 -0
- package/test/transforms/regExpKeys.test.js +25 -0
- package/test/transforms/sort.test.js +30 -0
- package/test/transforms/sortBy.test.js +22 -0
- package/test/transforms/sortNatural.test.js +21 -0
- package/test/utilities.test.js +24 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { beforeEach, describe, mock, test } from "node:test";
|
|
3
|
+
import SiteTree from "../src/SiteTree.js";
|
|
4
|
+
import * as Tree from "../src/Tree.js";
|
|
5
|
+
|
|
6
|
+
const mockHost = "https://mock";
|
|
7
|
+
|
|
8
|
+
const mockResponses = {
|
|
9
|
+
"/.keys.json": {
|
|
10
|
+
data: JSON.stringify(["about/"]),
|
|
11
|
+
},
|
|
12
|
+
"/about": {
|
|
13
|
+
redirected: true,
|
|
14
|
+
status: 301,
|
|
15
|
+
url: "https://mock/about/",
|
|
16
|
+
},
|
|
17
|
+
"/about/.keys.json": {
|
|
18
|
+
data: JSON.stringify(["Alice.html", "Bob.html", "Carol.html"]),
|
|
19
|
+
},
|
|
20
|
+
"/about/Alice.html": {
|
|
21
|
+
data: "Hello, Alice!",
|
|
22
|
+
},
|
|
23
|
+
"/about/Bob.html": {
|
|
24
|
+
data: "Hello, Bob!",
|
|
25
|
+
},
|
|
26
|
+
"/about/Carol.html": {
|
|
27
|
+
data: "Hello, Carol!",
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
describe("SiteTree", () => {
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
mock.method(global, "fetch", mockFetch);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("resolve() returns a new SiteTree for the given relative route", () => {
|
|
37
|
+
const fixture = new SiteTree(mockHost);
|
|
38
|
+
const about = fixture.resolve("about");
|
|
39
|
+
assert.equal(about.href, "https://mock/about/");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("can get the keys of the tree", async () => {
|
|
43
|
+
const fixture = new SiteTree(mockHost);
|
|
44
|
+
const about = fixture.resolve("about");
|
|
45
|
+
const keys = await about.keys();
|
|
46
|
+
assert.deepEqual([...keys], ["Alice.html", "Bob.html", "Carol.html"]);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("can get the value for a key", async () => {
|
|
50
|
+
const fixture = new SiteTree(mockHost);
|
|
51
|
+
const about = fixture.resolve("about");
|
|
52
|
+
const alice = await about.get("Alice.html");
|
|
53
|
+
assert.equal(alice, "Hello, Alice!");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("getting an unsupported key returns undefined", async () => {
|
|
57
|
+
const fixture = new SiteTree(mockHost);
|
|
58
|
+
assert.equal(await fixture.get("xyz"), undefined);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("a redirect on a site with keys returns a SiteTree for the new URL", async () => {
|
|
62
|
+
const fixture = new SiteTree(mockHost);
|
|
63
|
+
const about = await fixture.get("about");
|
|
64
|
+
assert.equal(about.href, "https://mock/about/");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("can determine whether a key is for a subtree", async () => {
|
|
68
|
+
const fixture = new SiteTree(mockHost);
|
|
69
|
+
assert.equal(await fixture.isKeyForSubtree("about"), true);
|
|
70
|
+
const about = fixture.resolve("about");
|
|
71
|
+
assert.equal(await about.isKeyForSubtree("Alice.html"), false);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("can convert a SiteGraph to a plain object", async () => {
|
|
75
|
+
const fixture = new SiteTree(mockHost);
|
|
76
|
+
// Convert buffers to strings.
|
|
77
|
+
const strings = Tree.map(fixture, (value) => value.toString());
|
|
78
|
+
assert.deepEqual(await Tree.plain(strings), {
|
|
79
|
+
about: {
|
|
80
|
+
"Alice.html": "Hello, Alice!",
|
|
81
|
+
"Bob.html": "Hello, Bob!",
|
|
82
|
+
"Carol.html": "Hello, Carol!",
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
async function mockFetch(href) {
|
|
89
|
+
if (!href.startsWith(mockHost)) {
|
|
90
|
+
return { status: 404 };
|
|
91
|
+
}
|
|
92
|
+
const path = href.slice(mockHost.length);
|
|
93
|
+
const mockedResponse = mockResponses[path];
|
|
94
|
+
if (mockedResponse) {
|
|
95
|
+
return Object.assign(
|
|
96
|
+
{
|
|
97
|
+
// Returns a Buffer, not an ArrayBuffer
|
|
98
|
+
arrayBuffer: () => Buffer.from(mockedResponse.data),
|
|
99
|
+
ok: true,
|
|
100
|
+
status: 200,
|
|
101
|
+
text: () => mockedResponse.data,
|
|
102
|
+
},
|
|
103
|
+
mockedResponse
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
ok: false,
|
|
108
|
+
status: 404,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { describe, test } from "node:test";
|
|
3
|
+
import MapTree from "../src/MapTree.js";
|
|
4
|
+
import ObjectTree from "../src/ObjectTree.js";
|
|
5
|
+
import * as Tree from "../src/Tree.js";
|
|
6
|
+
|
|
7
|
+
describe("Tree", () => {
|
|
8
|
+
test("assign applies one tree to another", async () => {
|
|
9
|
+
const target = new ObjectTree({
|
|
10
|
+
a: 1,
|
|
11
|
+
b: 2,
|
|
12
|
+
more: {
|
|
13
|
+
d: 3,
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const source = {
|
|
18
|
+
a: 4, // Overwrite existing value
|
|
19
|
+
b: undefined, // Delete
|
|
20
|
+
c: 5, // Add
|
|
21
|
+
more: {
|
|
22
|
+
// Should leave existing `more` keys alone.
|
|
23
|
+
e: 6, // Add
|
|
24
|
+
},
|
|
25
|
+
// Add new subtree
|
|
26
|
+
extra: {
|
|
27
|
+
f: 7,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Apply changes.
|
|
32
|
+
const result = await Tree.assign(target, source);
|
|
33
|
+
|
|
34
|
+
assert.equal(result, target);
|
|
35
|
+
assert.deepEqual(await Tree.plain(target), {
|
|
36
|
+
a: 4,
|
|
37
|
+
c: 5,
|
|
38
|
+
more: {
|
|
39
|
+
d: 3,
|
|
40
|
+
e: 6,
|
|
41
|
+
},
|
|
42
|
+
extra: {
|
|
43
|
+
f: 7,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("assign() can apply updates to an array", async () => {
|
|
49
|
+
const target = new ObjectTree(["a", "b", "c"]);
|
|
50
|
+
await Tree.assign(target, ["d", "e"]);
|
|
51
|
+
assert.deepEqual(await Tree.plain(target), ["d", "e", "c"]);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("clear() removes all values", async () => {
|
|
55
|
+
const fixture = createFixture();
|
|
56
|
+
await Tree.clear(fixture);
|
|
57
|
+
assert.deepEqual([...(await Tree.entries(fixture))], []);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("entries() returns the [key, value] pairs", async () => {
|
|
61
|
+
const fixture = createFixture();
|
|
62
|
+
assert.deepEqual(
|
|
63
|
+
[...(await Tree.entries(fixture))],
|
|
64
|
+
[
|
|
65
|
+
["Alice.md", "Hello, **Alice**."],
|
|
66
|
+
["Bob.md", "Hello, **Bob**."],
|
|
67
|
+
["Carol.md", "Hello, **Carol**."],
|
|
68
|
+
]
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("forEach() invokes a callback for each entry", async () => {
|
|
73
|
+
const fixture = createFixture();
|
|
74
|
+
const results = {};
|
|
75
|
+
await Tree.forEach(fixture, async (value, key) => {
|
|
76
|
+
results[key] = value;
|
|
77
|
+
});
|
|
78
|
+
assert.deepEqual(results, {
|
|
79
|
+
"Alice.md": "Hello, **Alice**.",
|
|
80
|
+
"Bob.md": "Hello, **Bob**.",
|
|
81
|
+
"Carol.md": "Hello, **Carol**.",
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("from() returns an async tree as is", async () => {
|
|
86
|
+
const tree1 = new ObjectTree({
|
|
87
|
+
a: "Hello, a.",
|
|
88
|
+
});
|
|
89
|
+
const tree2 = Tree.from(tree1);
|
|
90
|
+
assert.equal(tree2, tree1);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("from() uses an object's unpack() method if defined", async () => {
|
|
94
|
+
const obj = {
|
|
95
|
+
unpack() {
|
|
96
|
+
return {
|
|
97
|
+
a: "Hello, a.",
|
|
98
|
+
};
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
const tree = Tree.from(obj);
|
|
102
|
+
assert.deepEqual(await Tree.plain(tree), {
|
|
103
|
+
a: "Hello, a.",
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("from() creates a deferred tree if unpack() returns a promise", async () => {
|
|
108
|
+
const obj = {
|
|
109
|
+
async unpack() {
|
|
110
|
+
return {
|
|
111
|
+
a: "Hello, a.",
|
|
112
|
+
};
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
const tree = Tree.from(obj);
|
|
116
|
+
assert.deepEqual(await Tree.plain(tree), {
|
|
117
|
+
a: "Hello, a.",
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("has returns true if the key exists", async () => {
|
|
122
|
+
const fixture = createFixture();
|
|
123
|
+
assert.equal(await Tree.has(fixture, "Alice.md"), true);
|
|
124
|
+
assert.equal(await Tree.has(fixture, "David.md"), false);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("isAsyncTree returns true if the object is a tree", () => {
|
|
128
|
+
const missingGetAndKeys = {};
|
|
129
|
+
assert(!Tree.isAsyncTree(missingGetAndKeys));
|
|
130
|
+
|
|
131
|
+
const missingIterator = {
|
|
132
|
+
async get() {},
|
|
133
|
+
};
|
|
134
|
+
assert(!Tree.isAsyncTree(missingIterator));
|
|
135
|
+
|
|
136
|
+
const missingGet = {
|
|
137
|
+
async keys() {},
|
|
138
|
+
};
|
|
139
|
+
assert(!Tree.isAsyncTree(missingGet));
|
|
140
|
+
|
|
141
|
+
const hasGetAndKeys = {
|
|
142
|
+
async get() {},
|
|
143
|
+
async keys() {},
|
|
144
|
+
};
|
|
145
|
+
assert(Tree.isAsyncTree(hasGetAndKeys));
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("isAsyncMutableTree returns true if the object is a mutable tree", () => {
|
|
149
|
+
assert.equal(
|
|
150
|
+
Tree.isAsyncMutableTree({
|
|
151
|
+
get() {},
|
|
152
|
+
keys() {},
|
|
153
|
+
}),
|
|
154
|
+
false
|
|
155
|
+
);
|
|
156
|
+
assert.equal(Tree.isAsyncMutableTree(createFixture()), true);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("isTreelike() returns true if the argument can be cast to an async tree", () => {
|
|
160
|
+
assert(!Tree.isTreelike(null));
|
|
161
|
+
assert(Tree.isTreelike({}));
|
|
162
|
+
assert(Tree.isTreelike([]));
|
|
163
|
+
assert(Tree.isTreelike(new Map()));
|
|
164
|
+
assert(Tree.isTreelike(new Set()));
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("isKeyForSubtree() returns true if the key is for a subtree", async () => {
|
|
168
|
+
const tree = new ObjectTree({
|
|
169
|
+
a: 1,
|
|
170
|
+
more: {
|
|
171
|
+
b: 2,
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
assert(!(await Tree.isKeyForSubtree(tree, "a")));
|
|
175
|
+
assert(await Tree.isKeyForSubtree(tree, "more"));
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("map() maps values", async () => {
|
|
179
|
+
const tree = {
|
|
180
|
+
a: "Alice",
|
|
181
|
+
more: {
|
|
182
|
+
b: "Bob",
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
const mapped = Tree.map(tree, (value) => value.toUpperCase());
|
|
186
|
+
assert.deepEqual(await Tree.plain(mapped), {
|
|
187
|
+
a: "ALICE",
|
|
188
|
+
more: {
|
|
189
|
+
b: "BOB",
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test("mapReduce() can map values and reduce them", async () => {
|
|
195
|
+
const tree = {
|
|
196
|
+
a: 1,
|
|
197
|
+
b: 2,
|
|
198
|
+
more: {
|
|
199
|
+
c: 3,
|
|
200
|
+
},
|
|
201
|
+
d: 4,
|
|
202
|
+
};
|
|
203
|
+
const reduced = await Tree.mapReduce(
|
|
204
|
+
tree,
|
|
205
|
+
(value) => value,
|
|
206
|
+
async (values) => String.prototype.concat(...values)
|
|
207
|
+
);
|
|
208
|
+
assert.deepEqual(reduced, "1234");
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test("plain() produces a plain object version of a tree", async () => {
|
|
212
|
+
const original = {
|
|
213
|
+
a: 1,
|
|
214
|
+
b: 2,
|
|
215
|
+
c: 3,
|
|
216
|
+
more: {
|
|
217
|
+
d: 4,
|
|
218
|
+
e: 5,
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
const tree = new ObjectTree(original);
|
|
222
|
+
const plain = await Tree.plain(tree);
|
|
223
|
+
assert.deepEqual(plain, original);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test("plain() produces an array for an array-like tree", async () => {
|
|
227
|
+
const original = ["a", "b", "c"];
|
|
228
|
+
const tree = new ObjectTree(original);
|
|
229
|
+
const plain = await Tree.plain(tree);
|
|
230
|
+
assert.deepEqual(plain, original);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test("plain() leaves an array-like tree as an object if keys aren't consecutive", async () => {
|
|
234
|
+
const original = {
|
|
235
|
+
0: "a",
|
|
236
|
+
1: "b",
|
|
237
|
+
// missing
|
|
238
|
+
3: "c",
|
|
239
|
+
};
|
|
240
|
+
const tree = new ObjectTree(original);
|
|
241
|
+
const plain = await Tree.plain(tree);
|
|
242
|
+
assert.deepEqual(plain, original);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test("remove method removes a value", async () => {
|
|
246
|
+
const fixture = createFixture();
|
|
247
|
+
await Tree.remove(fixture, "Alice.md");
|
|
248
|
+
assert.deepEqual(
|
|
249
|
+
[...(await Tree.entries(fixture))],
|
|
250
|
+
[
|
|
251
|
+
["Bob.md", "Hello, **Bob**."],
|
|
252
|
+
["Carol.md", "Hello, **Carol**."],
|
|
253
|
+
]
|
|
254
|
+
);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test("toFunction returns a function that invokes a tree's get() method", async () => {
|
|
258
|
+
const tree = new ObjectTree({
|
|
259
|
+
a: 1,
|
|
260
|
+
b: 2,
|
|
261
|
+
});
|
|
262
|
+
const fn = Tree.toFunction(tree);
|
|
263
|
+
assert.equal(await fn("a"), 1);
|
|
264
|
+
assert.equal(await fn("b"), 2);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
test("traverse() a path of keys", async () => {
|
|
268
|
+
const tree = new ObjectTree({
|
|
269
|
+
a1: 1,
|
|
270
|
+
a2: {
|
|
271
|
+
b1: 2,
|
|
272
|
+
b2: {
|
|
273
|
+
c1: 3,
|
|
274
|
+
c2: 4,
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
assert.equal(await Tree.traverse(tree), tree);
|
|
279
|
+
assert.equal(await Tree.traverse(tree, "a1"), 1);
|
|
280
|
+
assert.equal(await Tree.traverse(tree, "a2", "b2", "c2"), 4);
|
|
281
|
+
assert.equal(
|
|
282
|
+
await Tree.traverse(tree, "a2", "doesntexist", "c2"),
|
|
283
|
+
undefined
|
|
284
|
+
);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
test("traverse() from one tree into another", async () => {
|
|
288
|
+
const tree = new ObjectTree({
|
|
289
|
+
a: {
|
|
290
|
+
b: new MapTree([
|
|
291
|
+
["c", "Hello"],
|
|
292
|
+
["d", "Goodbye"],
|
|
293
|
+
]),
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
assert.equal(await Tree.traverse(tree, "a", "b", "c"), "Hello");
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
test("traversing a final empty string can unpack the last value", async () => {
|
|
300
|
+
class Unpackable {
|
|
301
|
+
unpack() {
|
|
302
|
+
return "Content";
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
const unpackable = new Unpackable();
|
|
306
|
+
const tree = new ObjectTree({
|
|
307
|
+
unpackable,
|
|
308
|
+
});
|
|
309
|
+
const result = await Tree.traverse(tree, "unpackable", "");
|
|
310
|
+
assert.equal(result, "Content");
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
test("traversing a final empty string returns a value as is if it's not unpackable", async () => {
|
|
314
|
+
const tree = new ObjectTree({
|
|
315
|
+
a: "Hello",
|
|
316
|
+
});
|
|
317
|
+
const result = await Tree.traverse(tree, "a", "");
|
|
318
|
+
assert.equal(result, "Hello");
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
test("traversePath() traverses a slash-separated path", async () => {
|
|
322
|
+
const tree = new ObjectTree({
|
|
323
|
+
a: {
|
|
324
|
+
b: {
|
|
325
|
+
c: "Hello",
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
assert.equal(await Tree.traversePath(tree, "a/b/c"), "Hello");
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
test("values() returns the store's values", async () => {
|
|
333
|
+
const fixture = createFixture();
|
|
334
|
+
assert.deepEqual(
|
|
335
|
+
[...(await Tree.values(fixture))],
|
|
336
|
+
["Hello, **Alice**.", "Hello, **Bob**.", "Hello, **Carol**."]
|
|
337
|
+
);
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
function createFixture() {
|
|
342
|
+
return new ObjectTree({
|
|
343
|
+
"Alice.md": "Hello, **Alice**.",
|
|
344
|
+
"Bob.md": "Hello, **Bob**.",
|
|
345
|
+
"Carol.md": "Hello, **Carol**.",
|
|
346
|
+
});
|
|
347
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A simple test runner for the browser to run the subset of the Node.s test
|
|
3
|
+
* runner used by the project.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export default function assert(condition) {
|
|
7
|
+
if (!condition) {
|
|
8
|
+
throw new Error("Assertion failed");
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
assert.equal = (actual, expected) => {
|
|
13
|
+
if (Number.isNaN(actual) && Number.isNaN(expected)) {
|
|
14
|
+
return;
|
|
15
|
+
} else if (actual == expected) {
|
|
16
|
+
return;
|
|
17
|
+
} else {
|
|
18
|
+
throw new Error(`Expected ${expected} but got ${actual}`);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// This is a simplified deepEqual test that examines the conditions we care
|
|
23
|
+
// about. For reference, the actual Node assert.deepEqual is much more complex:
|
|
24
|
+
// see https://github.com/nodejs/node/blob/main/lib/internal/util/comparisons.js
|
|
25
|
+
assert.deepEqual = (actual, expected) => {
|
|
26
|
+
if (actual === expected) {
|
|
27
|
+
return;
|
|
28
|
+
} else if (
|
|
29
|
+
typeof actual === "object" &&
|
|
30
|
+
actual != null &&
|
|
31
|
+
typeof expected === "object" &&
|
|
32
|
+
expected != null &&
|
|
33
|
+
Object.keys(actual).length === Object.keys(expected).length
|
|
34
|
+
) {
|
|
35
|
+
for (const prop in actual) {
|
|
36
|
+
if (!expected.hasOwnProperty(prop)) {
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
assert.deepEqual(actual[prop], expected[prop]);
|
|
40
|
+
}
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
throw new Error(`Expected ${expected} but got ${actual}`);
|
|
45
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
6
|
+
<script type="importmap">
|
|
7
|
+
{
|
|
8
|
+
"imports": {
|
|
9
|
+
"node:assert": "./assert.js",
|
|
10
|
+
"node:test": "./testRunner.js"
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
</script>
|
|
14
|
+
<!-- Omit FileTree.test.js, which is Node.js only -->
|
|
15
|
+
<!-- Omit SiteTree.test.js, which requires mocks -->
|
|
16
|
+
<script type="module" src="../BrowserFileTree.test.js"></script>
|
|
17
|
+
<script type="module" src="../DeferredTree.test.js"></script>
|
|
18
|
+
<script type="module" src="../FunctionTree.test.js"></script>
|
|
19
|
+
<script type="module" src="../MapTree.test.js"></script>
|
|
20
|
+
<script type="module" src="../ObjectTree.test.js"></script>
|
|
21
|
+
<script type="module" src="../SetTree.test.js"></script>
|
|
22
|
+
<script type="module" src="../Tree.test.js"></script>
|
|
23
|
+
<script type="module" src="../operations/cache.test.js"></script>
|
|
24
|
+
<script type="module" src="../operations/merge.test.js"></script>
|
|
25
|
+
<script type="module" src="../operations/mergeDeep.test.js"></script>
|
|
26
|
+
<script type="module" src="../transforms/cachedKeyFns.test.js"></script>
|
|
27
|
+
<script
|
|
28
|
+
type="module"
|
|
29
|
+
src="../transforms/keyMapsForExtensions.test.js"
|
|
30
|
+
></script>
|
|
31
|
+
<script type="module" src="../transforms/map.test.js"></script>
|
|
32
|
+
<script type="module" src="../utilities.test.js"></script>
|
|
33
|
+
</head>
|
|
34
|
+
<body></body>
|
|
35
|
+
</html>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A simple test runner for the browser to run the subset of the Node.s test
|
|
3
|
+
* runner used by the project.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
let promises = {};
|
|
7
|
+
let currentSuite;
|
|
8
|
+
|
|
9
|
+
const markers = {
|
|
10
|
+
success: "✅",
|
|
11
|
+
skipped: "ー",
|
|
12
|
+
fail: "❌",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export async function describe(name, fn) {
|
|
16
|
+
promises[name] = [];
|
|
17
|
+
currentSuite = name;
|
|
18
|
+
await fn();
|
|
19
|
+
const results = await Promise.all(promises[name]);
|
|
20
|
+
const someFailed = results.some((result) => result.result === "fail");
|
|
21
|
+
const header = `${someFailed ? markers.fail : markers.success} ${name}`;
|
|
22
|
+
console[someFailed ? "group" : "groupCollapsed"](header);
|
|
23
|
+
for (const result of results) {
|
|
24
|
+
const marker = markers[result.result];
|
|
25
|
+
const name = result.name;
|
|
26
|
+
const message = result.result === "fail" ? `: ${result.message}` : "";
|
|
27
|
+
const skipped = result.result === "skipped" ? " [skipped]" : "";
|
|
28
|
+
console.log(`${marker} ${name}${message}${skipped}`);
|
|
29
|
+
}
|
|
30
|
+
console.groupEnd();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Node test() calls can call an async function, but the test() function isn't
|
|
34
|
+
// declared async. We implicitly wrap the test call with a Promise and add it to
|
|
35
|
+
// the list of promises for the current suite.
|
|
36
|
+
export async function test(name, fn) {
|
|
37
|
+
promises[currentSuite].push(runTest(name, fn));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
test.skip = (name, fn) => {
|
|
41
|
+
promises[currentSuite].push(Promise.resolve({ result: "skipped", name }));
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
async function runTest(name, fn) {
|
|
45
|
+
try {
|
|
46
|
+
await fn();
|
|
47
|
+
return { result: "success", name };
|
|
48
|
+
} catch (/** @type {any} */ error) {
|
|
49
|
+
return { result: "fail", name, message: error.message };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Hello, **Alice**.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Hello, **Bob**.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Hello, **Carol**.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { describe, test } from "node:test";
|
|
3
|
+
import ObjectTree from "../../src/ObjectTree.js";
|
|
4
|
+
import * as Tree from "../../src/Tree.js";
|
|
5
|
+
import cache from "../../src/operations/cache.js";
|
|
6
|
+
|
|
7
|
+
describe("cache", () => {
|
|
8
|
+
test("caches reads of values from one tree into another", async () => {
|
|
9
|
+
const objectCache = new ObjectTree({});
|
|
10
|
+
const fixture = cache(
|
|
11
|
+
Tree.from({
|
|
12
|
+
a: 1,
|
|
13
|
+
b: 2,
|
|
14
|
+
c: 3,
|
|
15
|
+
more: {
|
|
16
|
+
d: 4,
|
|
17
|
+
},
|
|
18
|
+
}),
|
|
19
|
+
objectCache
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const keys = Array.from(await fixture.keys());
|
|
23
|
+
assert.deepEqual(keys, ["a", "b", "c", "more"]);
|
|
24
|
+
|
|
25
|
+
assert.equal(await objectCache.get("a"), undefined);
|
|
26
|
+
assert.equal(await fixture.get("a"), 1);
|
|
27
|
+
assert.equal(await objectCache.get("a"), 1); // Now in cache
|
|
28
|
+
|
|
29
|
+
assert.equal(await objectCache.get("b"), undefined);
|
|
30
|
+
assert.equal(await fixture.get("b"), 2);
|
|
31
|
+
assert.equal(await objectCache.get("b"), 2);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("if a cache filter is supplied, it only caches values whose keys match the filter", async () => {
|
|
35
|
+
const objectCache = new ObjectTree({});
|
|
36
|
+
const fixture = cache(
|
|
37
|
+
Tree.from({
|
|
38
|
+
"a.txt": "a",
|
|
39
|
+
"b.txt": "b",
|
|
40
|
+
}),
|
|
41
|
+
objectCache,
|
|
42
|
+
Tree.from({
|
|
43
|
+
"a.txt": true,
|
|
44
|
+
})
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// Access some values to populate the cache.
|
|
48
|
+
assert.equal(await fixture.get("a.txt"), "a");
|
|
49
|
+
assert.equal(await fixture.get("b.txt"), "b");
|
|
50
|
+
|
|
51
|
+
// The a.txt value should be cached because it matches the filter.
|
|
52
|
+
assert.equal(await objectCache.get("a.txt"), "a");
|
|
53
|
+
|
|
54
|
+
// The b.txt value should not be cached because it does not match the filter.
|
|
55
|
+
assert.equal(await objectCache.get("b.txt"), undefined);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { describe, test } from "node:test";
|
|
3
|
+
import * as Tree from "../../src/Tree.js";
|
|
4
|
+
import merge from "../../src/operations/merge.js";
|
|
5
|
+
|
|
6
|
+
describe("merge", () => {
|
|
7
|
+
test("performs a shallow merge", async () => {
|
|
8
|
+
const fixture = merge(
|
|
9
|
+
Tree.from({
|
|
10
|
+
a: 1,
|
|
11
|
+
b: {
|
|
12
|
+
c: 2,
|
|
13
|
+
},
|
|
14
|
+
}),
|
|
15
|
+
Tree.from({
|
|
16
|
+
// Will be obscured by `b` above
|
|
17
|
+
b: {
|
|
18
|
+
d: 3,
|
|
19
|
+
},
|
|
20
|
+
e: {
|
|
21
|
+
f: 4,
|
|
22
|
+
},
|
|
23
|
+
})
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
assert.deepEqual(await Tree.plain(fixture), {
|
|
27
|
+
a: 1,
|
|
28
|
+
b: {
|
|
29
|
+
c: 2,
|
|
30
|
+
},
|
|
31
|
+
e: {
|
|
32
|
+
f: 4,
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const d = await Tree.traverse(fixture, "b", "d");
|
|
37
|
+
assert.equal(d, undefined);
|
|
38
|
+
});
|
|
39
|
+
});
|