@weborigami/async-tree 0.5.7 → 0.6.0
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/browser.js +1 -1
- package/index.ts +31 -35
- package/main.js +1 -2
- package/package.json +4 -7
- package/shared.js +77 -12
- package/src/Tree.js +8 -2
- package/src/drivers/AsyncMap.js +210 -0
- package/src/drivers/{BrowserFileTree.js → BrowserFileMap.js} +36 -27
- package/src/drivers/{calendarTree.js → CalendarMap.js} +81 -62
- package/src/drivers/ConstantMap.js +30 -0
- package/src/drivers/DeepObjectMap.js +27 -0
- package/src/drivers/{ExplorableSiteTree.js → ExplorableSiteMap.js} +7 -7
- package/src/drivers/FileMap.js +245 -0
- package/src/drivers/{FunctionTree.js → FunctionMap.js} +19 -22
- package/src/drivers/ObjectMap.js +139 -0
- package/src/drivers/SetMap.js +13 -0
- package/src/drivers/{SiteTree.js → SiteMap.js} +16 -17
- package/src/drivers/SyncMap.js +245 -0
- package/src/jsonKeys.d.ts +2 -2
- package/src/jsonKeys.js +6 -5
- package/src/operations/addNextPrevious.js +35 -36
- package/src/operations/assign.js +30 -21
- package/src/operations/cache.js +29 -35
- package/src/operations/cachedKeyFunctions.js +1 -1
- package/src/operations/calendar.js +5 -0
- package/src/operations/clear.js +13 -12
- package/src/operations/constant.js +5 -0
- package/src/operations/deepEntries.js +23 -0
- package/src/operations/deepMap.js +9 -9
- package/src/operations/deepMerge.js +36 -25
- package/src/operations/deepReverse.js +23 -16
- package/src/operations/deepTake.js +7 -7
- package/src/operations/deepText.js +4 -4
- package/src/operations/deepValues.js +3 -6
- package/src/operations/deepValuesIterator.js +11 -11
- package/src/operations/delete.js +8 -12
- package/src/operations/entries.js +17 -10
- package/src/operations/filter.js +9 -7
- package/src/operations/first.js +12 -10
- package/src/operations/forEach.js +10 -13
- package/src/operations/from.js +31 -39
- package/src/operations/globKeys.js +22 -17
- package/src/operations/group.js +2 -2
- package/src/operations/groupBy.js +24 -22
- package/src/operations/has.js +7 -9
- package/src/operations/indent.js +2 -2
- package/src/operations/inners.js +19 -15
- package/src/operations/invokeFunctions.js +22 -10
- package/src/operations/isAsyncMutableTree.js +5 -12
- package/src/operations/isAsyncTree.js +5 -20
- package/src/operations/isMap.js +39 -0
- package/src/operations/isMaplike.js +34 -0
- package/src/operations/isReadOnlyMap.js +14 -0
- package/src/operations/isTraversable.js +3 -3
- package/src/operations/isTreelike.js +5 -30
- package/src/operations/json.js +4 -12
- package/src/operations/keys.js +17 -8
- package/src/operations/length.js +9 -8
- package/src/operations/map.js +27 -30
- package/src/operations/mapExtension.js +20 -16
- package/src/operations/mapReduce.js +22 -17
- package/src/operations/mask.js +31 -22
- package/src/operations/match.js +13 -9
- package/src/operations/merge.js +43 -35
- package/src/operations/paginate.js +26 -18
- package/src/operations/parent.js +7 -7
- package/src/operations/paths.js +8 -8
- package/src/operations/plain.js +6 -6
- package/src/operations/regExpKeys.js +21 -12
- package/src/operations/reverse.js +21 -15
- package/src/operations/root.js +6 -5
- package/src/operations/scope.js +31 -26
- package/src/operations/shuffle.js +23 -16
- package/src/operations/size.js +13 -0
- package/src/operations/sort.js +55 -40
- package/src/operations/sync.js +21 -0
- package/src/operations/take.js +23 -11
- package/src/operations/text.js +4 -4
- package/src/operations/toFunction.js +7 -7
- package/src/operations/traverse.js +4 -4
- package/src/operations/traverseOrThrow.js +13 -9
- package/src/operations/traversePath.js +2 -2
- package/src/operations/values.js +18 -9
- package/src/operations/withKeys.js +22 -16
- package/src/symbols.js +1 -0
- package/src/utilities/castArraylike.js +10 -2
- package/src/utilities/getMapArgument.js +38 -0
- package/src/utilities/getParent.js +2 -2
- package/src/utilities/isStringlike.js +7 -5
- package/src/utilities/setParent.js +7 -7
- package/src/utilities/toFunction.js +2 -2
- package/src/utilities/toPlainValue.js +22 -18
- package/test/SampleAsyncMap.js +34 -0
- package/test/browser/assert.js +20 -0
- package/test/browser/index.html +54 -21
- package/test/drivers/AsyncMap.test.js +119 -0
- package/test/drivers/{BrowserFileTree.test.js → BrowserFileMap.test.js} +42 -23
- package/test/drivers/{calendarTree.test.js → CalendarMap.test.js} +17 -19
- package/test/drivers/ConstantMap.test.js +15 -0
- package/test/drivers/DeepObjectMap.test.js +36 -0
- package/test/drivers/{ExplorableSiteTree.test.js → ExplorableSiteMap.test.js} +29 -14
- package/test/drivers/FileMap.test.js +185 -0
- package/test/drivers/FunctionMap.test.js +56 -0
- package/test/drivers/ObjectMap.test.js +166 -0
- package/test/drivers/SetMap.test.js +35 -0
- package/test/drivers/{SiteTree.test.js → SiteMap.test.js} +14 -10
- package/test/drivers/SyncMap.test.js +321 -0
- package/test/jsonKeys.test.js +2 -2
- package/test/operations/addNextPrevious.test.js +3 -2
- package/test/operations/assign.test.js +30 -35
- package/test/operations/cache.test.js +8 -6
- package/test/operations/cachedKeyFunctions.test.js +6 -5
- package/test/operations/clear.test.js +6 -27
- package/test/operations/deepEntries.test.js +32 -0
- package/test/operations/deepMerge.test.js +6 -5
- package/test/operations/deepReverse.test.js +2 -2
- package/test/operations/deepTake.test.js +2 -2
- package/test/operations/deepText.test.js +4 -4
- package/test/operations/deepValuesIterator.test.js +2 -2
- package/test/operations/delete.test.js +2 -2
- package/test/operations/extensionKeyFunctions.test.js +6 -5
- package/test/operations/filter.test.js +3 -3
- package/test/operations/from.test.js +23 -31
- package/test/operations/globKeys.test.js +9 -9
- package/test/operations/groupBy.test.js +6 -5
- package/test/operations/inners.test.js +4 -4
- package/test/operations/invokeFunctions.test.js +2 -2
- package/test/operations/isMap.test.js +15 -0
- package/test/operations/isMaplike.test.js +15 -0
- package/test/operations/json.test.js +2 -2
- package/test/operations/keys.test.js +16 -3
- package/test/operations/map.test.js +20 -18
- package/test/operations/mapExtension.test.js +6 -6
- package/test/operations/mapReduce.test.js +2 -2
- package/test/operations/mask.test.js +4 -3
- package/test/operations/match.test.js +2 -2
- package/test/operations/merge.test.js +15 -11
- package/test/operations/paginate.test.js +5 -5
- package/test/operations/parent.test.js +3 -3
- package/test/operations/paths.test.js +6 -6
- package/test/operations/plain.test.js +8 -8
- package/test/operations/regExpKeys.test.js +12 -11
- package/test/operations/reverse.test.js +4 -3
- package/test/operations/scope.test.js +6 -5
- package/test/operations/shuffle.test.js +3 -2
- package/test/operations/sort.test.js +7 -10
- package/test/operations/sync.test.js +43 -0
- package/test/operations/take.test.js +2 -2
- package/test/operations/toFunction.test.js +2 -2
- package/test/operations/traverse.test.js +4 -5
- package/test/operations/withKeys.test.js +2 -2
- package/test/utilities/setParent.test.js +6 -6
- package/test/utilities/toFunction.test.js +2 -2
- package/test/utilities/toPlainValue.test.js +51 -12
- package/src/drivers/DeepMapTree.js +0 -23
- package/src/drivers/DeepObjectTree.js +0 -18
- package/src/drivers/DeferredTree.js +0 -81
- package/src/drivers/FileTree.js +0 -276
- package/src/drivers/MapTree.js +0 -70
- package/src/drivers/ObjectTree.js +0 -158
- package/src/drivers/SetTree.js +0 -34
- package/src/drivers/constantTree.js +0 -19
- package/src/drivers/limitConcurrency.js +0 -63
- package/src/internal.js +0 -16
- package/src/utilities/getTreeArgument.js +0 -43
- package/test/drivers/DeepMapTree.test.js +0 -17
- package/test/drivers/DeepObjectTree.test.js +0 -35
- package/test/drivers/DeferredTree.test.js +0 -22
- package/test/drivers/FileTree.test.js +0 -192
- package/test/drivers/FunctionTree.test.js +0 -46
- package/test/drivers/MapTree.test.js +0 -59
- package/test/drivers/ObjectTree.test.js +0 -163
- package/test/drivers/SetTree.test.js +0 -44
- package/test/drivers/constantTree.test.js +0 -13
- package/test/drivers/limitConcurrency.test.js +0 -41
- package/test/operations/isAsyncMutableTree.test.js +0 -17
- package/test/operations/isAsyncTree.test.js +0 -26
- package/test/operations/isTreelike.test.js +0 -13
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { describe, test } from "node:test";
|
|
3
|
+
import AsyncMap from "../../src/drivers/AsyncMap.js";
|
|
4
|
+
import SampleAsyncMap from "../SampleAsyncMap.js";
|
|
5
|
+
|
|
6
|
+
describe("AsyncMap", () => {
|
|
7
|
+
test("clear", async () => {
|
|
8
|
+
const map = new SampleAsyncMap([
|
|
9
|
+
["a", 1],
|
|
10
|
+
["b", 2],
|
|
11
|
+
]);
|
|
12
|
+
assert.strictEqual(await map.size, 2);
|
|
13
|
+
await map.clear();
|
|
14
|
+
assert.strictEqual(await map.size, 0);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("entries invokes keys() and get()", async () => {
|
|
18
|
+
const map = new SampleAsyncMap([
|
|
19
|
+
["a", 1],
|
|
20
|
+
["b", 2],
|
|
21
|
+
]);
|
|
22
|
+
const entries = [];
|
|
23
|
+
for await (const entry of map.entries()) {
|
|
24
|
+
entries.push(entry);
|
|
25
|
+
}
|
|
26
|
+
assert.deepStrictEqual(entries, [
|
|
27
|
+
["a", 1],
|
|
28
|
+
["b", 2],
|
|
29
|
+
]);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("forEach", async () => {
|
|
33
|
+
const map = new SampleAsyncMap([
|
|
34
|
+
["a", 1],
|
|
35
|
+
["b", 2],
|
|
36
|
+
]);
|
|
37
|
+
const calls = [];
|
|
38
|
+
await map.forEach(async (value, key, theMap) => {
|
|
39
|
+
calls.push([key, value, theMap]);
|
|
40
|
+
});
|
|
41
|
+
assert.deepStrictEqual(calls, [
|
|
42
|
+
["a", 1, map],
|
|
43
|
+
["b", 2, map],
|
|
44
|
+
]);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("static groupBy", async () => {
|
|
48
|
+
const items = [
|
|
49
|
+
{ name: "apple", type: "fruit" },
|
|
50
|
+
{ name: "beet", type: "vegetable" },
|
|
51
|
+
{ name: "cherry", type: "fruit" },
|
|
52
|
+
];
|
|
53
|
+
const map = await AsyncMap.groupBy(
|
|
54
|
+
items,
|
|
55
|
+
async (element, index) => element.type
|
|
56
|
+
);
|
|
57
|
+
assert.deepStrictEqual(Array.from(map.entries()), [
|
|
58
|
+
[
|
|
59
|
+
"fruit",
|
|
60
|
+
[
|
|
61
|
+
{ name: "apple", type: "fruit" },
|
|
62
|
+
{ name: "cherry", type: "fruit" },
|
|
63
|
+
],
|
|
64
|
+
],
|
|
65
|
+
["vegetable", [{ name: "beet", type: "vegetable" }]],
|
|
66
|
+
]);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("has returns true if key exists in keys()", async () => {
|
|
70
|
+
const map = new AsyncMap();
|
|
71
|
+
map.keys = async function* () {
|
|
72
|
+
yield* ["a", "b/"];
|
|
73
|
+
};
|
|
74
|
+
assert.strictEqual(await map.has("a"), true);
|
|
75
|
+
assert.strictEqual(await map.has("b"), true); // trailing slash optional
|
|
76
|
+
assert.strictEqual(await map.has("b/"), true);
|
|
77
|
+
assert.strictEqual(await map.has("c"), false);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("readOnly if get() is overridden but not delete() and set()", () => {
|
|
81
|
+
const map1 = new AsyncMap();
|
|
82
|
+
assert.strictEqual(map1.readOnly, false);
|
|
83
|
+
|
|
84
|
+
const map2 = new AsyncMap();
|
|
85
|
+
map2.get = async (key) => null;
|
|
86
|
+
assert.strictEqual(map2.readOnly, true);
|
|
87
|
+
|
|
88
|
+
const map3 = new AsyncMap();
|
|
89
|
+
map3.delete = async (key) => false;
|
|
90
|
+
map3.get = async (key) => null;
|
|
91
|
+
map3.set = async (key, value) => map3;
|
|
92
|
+
assert.strictEqual(map3.readOnly, false);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("size", async () => {
|
|
96
|
+
const map = new SampleAsyncMap();
|
|
97
|
+
assert.strictEqual(await map.size, 0);
|
|
98
|
+
await map.set("a", 1);
|
|
99
|
+
assert.strictEqual(await map.size, 1);
|
|
100
|
+
await map.set("b", 2);
|
|
101
|
+
assert.strictEqual(await map.size, 2);
|
|
102
|
+
await map.delete("a");
|
|
103
|
+
assert.strictEqual(await map.size, 1);
|
|
104
|
+
await map.clear();
|
|
105
|
+
assert.strictEqual(await map.size, 0);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("values", async () => {
|
|
109
|
+
const map = new SampleAsyncMap([
|
|
110
|
+
["a", 1],
|
|
111
|
+
["b", 2],
|
|
112
|
+
]);
|
|
113
|
+
const values = [];
|
|
114
|
+
for await (const value of map.values()) {
|
|
115
|
+
values.push(value);
|
|
116
|
+
}
|
|
117
|
+
assert.deepStrictEqual(values, [1, 2]);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import assert from "node:assert";
|
|
2
2
|
import { describe, test } from "node:test";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
3
|
+
import BrowserFileMap from "../../src/drivers/BrowserFileMap.js";
|
|
4
|
+
import keys from "../../src/operations/keys.js";
|
|
5
|
+
import map from "../../src/operations/map.js";
|
|
6
|
+
import plain from "../../src/operations/plain.js";
|
|
5
7
|
|
|
6
8
|
// Skip these tests if we're not in a browser.
|
|
7
9
|
const isBrowser = typeof window !== "undefined";
|
|
8
10
|
if (isBrowser) {
|
|
9
|
-
describe("
|
|
11
|
+
describe("BrowserFileMap", async () => {
|
|
10
12
|
test("can get the keys of the tree", async () => {
|
|
11
13
|
const fixture = await createFixture();
|
|
12
|
-
assert.deepEqual(
|
|
14
|
+
assert.deepEqual(await keys(fixture), [
|
|
13
15
|
"Alice.md",
|
|
14
16
|
"Bob.md",
|
|
15
17
|
"Carol.md",
|
|
@@ -57,6 +59,24 @@ if (isBrowser) {
|
|
|
57
59
|
assert(await fixture.get("subfolder/"));
|
|
58
60
|
});
|
|
59
61
|
|
|
62
|
+
test("can delete a value", async () => {
|
|
63
|
+
const fixture = await createFixture();
|
|
64
|
+
|
|
65
|
+
// Delete existing key.
|
|
66
|
+
const deleted = await fixture.delete("Bob.md");
|
|
67
|
+
assert.equal(deleted, true);
|
|
68
|
+
|
|
69
|
+
// Delete non-existing key.
|
|
70
|
+
const deletedAgain = await fixture.delete("Bob.md");
|
|
71
|
+
assert.equal(deletedAgain, false);
|
|
72
|
+
|
|
73
|
+
assert.deepEqual(await strings(fixture), {
|
|
74
|
+
"Alice.md": "Hello, **Alice**.",
|
|
75
|
+
"Carol.md": "Hello, **Carol**.",
|
|
76
|
+
subfolder: {},
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
60
80
|
test("can set a value", async () => {
|
|
61
81
|
const fixture = await createFixture();
|
|
62
82
|
|
|
@@ -66,32 +86,31 @@ if (isBrowser) {
|
|
|
66
86
|
// New key.
|
|
67
87
|
await fixture.set("David.md", "Hello, **David**.");
|
|
68
88
|
|
|
69
|
-
// Delete key.
|
|
70
|
-
await fixture.set("Bob.md", undefined);
|
|
71
|
-
|
|
72
|
-
// Delete non-existent key.
|
|
73
|
-
await fixture.set("xyz", undefined);
|
|
74
|
-
|
|
75
89
|
assert.deepEqual(await strings(fixture), {
|
|
76
90
|
"Alice.md": "Goodbye, **Alice**.",
|
|
91
|
+
"Bob.md": "Hello, **Bob**.",
|
|
77
92
|
"Carol.md": "Hello, **Carol**.",
|
|
78
93
|
"David.md": "Hello, **David**.",
|
|
79
94
|
subfolder: {},
|
|
80
95
|
});
|
|
81
96
|
});
|
|
82
97
|
|
|
98
|
+
test("can create an empty subfolder", async () => {
|
|
99
|
+
const fixture = await createFixture();
|
|
100
|
+
await fixture.set("emptyFolder", BrowserFileMap.EMPTY);
|
|
101
|
+
assert.deepEqual(await strings(fixture), {
|
|
102
|
+
"Alice.md": "Hello, **Alice**.",
|
|
103
|
+
"Bob.md": "Hello, **Bob**.",
|
|
104
|
+
"Carol.md": "Hello, **Carol**.",
|
|
105
|
+
emptyFolder: {},
|
|
106
|
+
subfolder: {},
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
83
110
|
test("can create a subfolder via set", async () => {
|
|
84
111
|
const fixture = await createFixture();
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
const name = key.replace(/\.md$/, "");
|
|
88
|
-
return `Hello, **${name}**.`;
|
|
89
|
-
},
|
|
90
|
-
async keys() {
|
|
91
|
-
return ["Ellen.md"];
|
|
92
|
-
},
|
|
93
|
-
};
|
|
94
|
-
await fixture.set("more", tree);
|
|
112
|
+
const more = new Map([["Ellen.md", "Hello, **Ellen**."]]);
|
|
113
|
+
await fixture.set("more", more);
|
|
95
114
|
assert.deepEqual(await strings(fixture), {
|
|
96
115
|
"Alice.md": "Hello, **Alice**.",
|
|
97
116
|
"Bob.md": "Hello, **Bob**.",
|
|
@@ -141,12 +160,12 @@ async function createFixture() {
|
|
|
141
160
|
create: true,
|
|
142
161
|
});
|
|
143
162
|
|
|
144
|
-
return new
|
|
163
|
+
return new BrowserFileMap(subdirectory);
|
|
145
164
|
}
|
|
146
165
|
|
|
147
166
|
async function strings(tree) {
|
|
148
|
-
return
|
|
149
|
-
|
|
167
|
+
return plain(
|
|
168
|
+
await map(tree, {
|
|
150
169
|
deep: true,
|
|
151
170
|
value: (value) => text(value),
|
|
152
171
|
})
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import assert from "node:assert";
|
|
2
2
|
import { describe, test } from "node:test";
|
|
3
|
-
import
|
|
3
|
+
import CalendarMap from "../../src/drivers/CalendarMap.js";
|
|
4
4
|
import toPlainValue from "../../src/utilities/toPlainValue.js";
|
|
5
5
|
|
|
6
|
-
describe("
|
|
7
|
-
test("without a start or end, returns a
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
});
|
|
11
|
-
const plain = await toPlainValue(tree);
|
|
6
|
+
describe("CalendarMap", () => {
|
|
7
|
+
test("without a start or end, returns a map for today", async () => {
|
|
8
|
+
const map = new CalendarMap();
|
|
9
|
+
const plain = await toPlainValue(map);
|
|
12
10
|
const today = new Date();
|
|
13
11
|
const year = today.getFullYear();
|
|
14
12
|
const month = (today.getMonth() + 1).toString().padStart(2, "0");
|
|
@@ -22,13 +20,12 @@ describe("calendarTree", () => {
|
|
|
22
20
|
});
|
|
23
21
|
});
|
|
24
22
|
|
|
25
|
-
test("returns a
|
|
26
|
-
const
|
|
23
|
+
test("returns a map for a month range", async () => {
|
|
24
|
+
const map = new CalendarMap({
|
|
27
25
|
start: "2025-01",
|
|
28
26
|
end: "2025-02",
|
|
29
|
-
value: (year, month, day) => `${year}-${month}-${day}`,
|
|
30
27
|
});
|
|
31
|
-
const plain = await toPlainValue(
|
|
28
|
+
const plain = await toPlainValue(map);
|
|
32
29
|
assert.deepEqual(plain, {
|
|
33
30
|
2025: {
|
|
34
31
|
"01": {
|
|
@@ -98,22 +95,23 @@ describe("calendarTree", () => {
|
|
|
98
95
|
});
|
|
99
96
|
});
|
|
100
97
|
|
|
101
|
-
test("returns a
|
|
102
|
-
const
|
|
98
|
+
test("returns a map for a day range", async () => {
|
|
99
|
+
const map = new CalendarMap({
|
|
103
100
|
start: "2025-02-27",
|
|
104
101
|
end: "2025-03-02",
|
|
105
|
-
|
|
102
|
+
// Exercise custom value function
|
|
103
|
+
value: (year, month, day) => `${year}.${month}.${day}`,
|
|
106
104
|
});
|
|
107
|
-
const plain = await toPlainValue(
|
|
105
|
+
const plain = await toPlainValue(map);
|
|
108
106
|
assert.deepEqual(plain, {
|
|
109
107
|
2025: {
|
|
110
108
|
"02": {
|
|
111
|
-
27: "2025
|
|
112
|
-
28: "2025
|
|
109
|
+
27: "2025.02.27",
|
|
110
|
+
28: "2025.02.28",
|
|
113
111
|
},
|
|
114
112
|
"03": {
|
|
115
|
-
"01": "2025
|
|
116
|
-
"02": "2025
|
|
113
|
+
"01": "2025.03.01",
|
|
114
|
+
"02": "2025.03.02",
|
|
117
115
|
},
|
|
118
116
|
},
|
|
119
117
|
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { describe, test } from "node:test";
|
|
3
|
+
import ConstantMap from "../../src/drivers/ConstantMap.js";
|
|
4
|
+
import keys from "../../src/operations/keys.js";
|
|
5
|
+
import traverse from "../../src/operations/traverse.js";
|
|
6
|
+
|
|
7
|
+
describe("ConstantMap", () => {
|
|
8
|
+
test("returns a deep tree that returns constant for all keys", async () => {
|
|
9
|
+
const fixture = new ConstantMap(1);
|
|
10
|
+
assert.deepEqual(Array.from(await keys(fixture)), []);
|
|
11
|
+
assert.equal(fixture.get("a"), 1);
|
|
12
|
+
assert.equal(fixture.get("b"), 1);
|
|
13
|
+
assert.equal(await traverse(fixture, "c/", "d/", "e"), 1);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { describe, test } from "node:test";
|
|
3
|
+
import DeepObjectMap from "../../src/drivers/DeepObjectMap.js";
|
|
4
|
+
import plain from "../../src/operations/plain.js";
|
|
5
|
+
|
|
6
|
+
describe("DeepObjectMap", () => {
|
|
7
|
+
test("returns a map for a value that's a plain sub-object or sub-array", async () => {
|
|
8
|
+
const map = createFixture();
|
|
9
|
+
|
|
10
|
+
const object = await map.get("object");
|
|
11
|
+
assert.equal(object instanceof DeepObjectMap, true);
|
|
12
|
+
assert.deepEqual(await plain(object), { b: 2 });
|
|
13
|
+
assert.equal(object.parent, map);
|
|
14
|
+
|
|
15
|
+
const array = await map.get("array");
|
|
16
|
+
assert.equal(array instanceof DeepObjectMap, true);
|
|
17
|
+
assert.deepEqual(await plain(array), [3]);
|
|
18
|
+
assert.equal(array.parent, map);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("adds trailing slashes to keys for submaps including plain objects or arrays", async () => {
|
|
22
|
+
const map = createFixture();
|
|
23
|
+
const keys = Array.from(await map.keys());
|
|
24
|
+
assert.deepEqual(keys, ["a", "object/", "array/"]);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
function createFixture() {
|
|
29
|
+
return new DeepObjectMap({
|
|
30
|
+
a: 1,
|
|
31
|
+
object: {
|
|
32
|
+
b: 2,
|
|
33
|
+
},
|
|
34
|
+
array: [3],
|
|
35
|
+
});
|
|
36
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import assert from "node:assert";
|
|
2
2
|
import { beforeEach, describe, mock, test } from "node:test";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
3
|
+
import ExplorableSiteMap from "../../src/drivers/ExplorableSiteMap.js";
|
|
4
|
+
import isMap from "../../src/operations/isMap.js";
|
|
5
|
+
import map from "../../src/operations/map.js";
|
|
6
|
+
import * as trailingSlash from "../../src/trailingSlash.js";
|
|
5
7
|
|
|
6
8
|
const textDecoder = new TextDecoder();
|
|
7
9
|
const textEncoder = new TextEncoder();
|
|
@@ -34,31 +36,35 @@ const mockResponses = {
|
|
|
34
36
|
},
|
|
35
37
|
};
|
|
36
38
|
|
|
37
|
-
describe("
|
|
39
|
+
describe("ExplorableSiteMap", () => {
|
|
38
40
|
beforeEach(() => {
|
|
39
41
|
mock.method(global, "fetch", mockFetch);
|
|
40
42
|
});
|
|
41
43
|
|
|
42
44
|
test("can get the keys of a tree", async () => {
|
|
43
|
-
const fixture = new
|
|
44
|
-
const keys =
|
|
45
|
-
|
|
45
|
+
const fixture = new ExplorableSiteMap(mockHost);
|
|
46
|
+
const keys = [];
|
|
47
|
+
for await (const key of fixture.keys()) {
|
|
48
|
+
keys.push(key);
|
|
49
|
+
}
|
|
50
|
+
assert.deepEqual(keys, ["about/", "index.html"]);
|
|
46
51
|
});
|
|
47
52
|
|
|
48
53
|
test("can get a plain value for a key", async () => {
|
|
49
|
-
const fixture = new
|
|
54
|
+
const fixture = new ExplorableSiteMap(mockHost);
|
|
55
|
+
/** @type {any} */
|
|
50
56
|
const arrayBuffer = await fixture.get("index.html");
|
|
51
57
|
const text = textDecoder.decode(arrayBuffer);
|
|
52
58
|
assert.equal(text, "Home page");
|
|
53
59
|
});
|
|
54
60
|
|
|
55
61
|
test("getting an unsupported key returns undefined", async () => {
|
|
56
|
-
const fixture = new
|
|
62
|
+
const fixture = new ExplorableSiteMap(mockHost);
|
|
57
63
|
assert.equal(await fixture.get("xyz"), undefined);
|
|
58
64
|
});
|
|
59
65
|
|
|
60
66
|
test("getting a null/undefined key throws an exception", async () => {
|
|
61
|
-
const fixture = new
|
|
67
|
+
const fixture = new ExplorableSiteMap(mockHost);
|
|
62
68
|
await assert.rejects(async () => {
|
|
63
69
|
await fixture.get(null);
|
|
64
70
|
});
|
|
@@ -68,20 +74,20 @@ describe("ExplorableSiteTree", () => {
|
|
|
68
74
|
});
|
|
69
75
|
|
|
70
76
|
test("can return a new tree for a key that redirects", async () => {
|
|
71
|
-
const fixture = new
|
|
77
|
+
const fixture = new ExplorableSiteMap(mockHost);
|
|
72
78
|
const about = await fixture.get("about");
|
|
73
|
-
assert(about instanceof
|
|
79
|
+
assert(about instanceof ExplorableSiteMap);
|
|
74
80
|
assert.equal(about.href, "https://mock/about/");
|
|
75
81
|
});
|
|
76
82
|
|
|
77
83
|
test("can convert a site to a plain object", async () => {
|
|
78
|
-
const fixture = new
|
|
84
|
+
const fixture = new ExplorableSiteMap(mockHost);
|
|
79
85
|
// Convert buffers to strings.
|
|
80
|
-
const strings = await
|
|
86
|
+
const strings = await map(fixture, {
|
|
81
87
|
deep: true,
|
|
82
88
|
value: (value) => textDecoder.decode(value),
|
|
83
89
|
});
|
|
84
|
-
assert.deepEqual(await
|
|
90
|
+
assert.deepEqual(await plain(strings), {
|
|
85
91
|
about: {
|
|
86
92
|
"Alice.html": "Hello, Alice!",
|
|
87
93
|
"Bob.html": "Hello, Bob!",
|
|
@@ -114,3 +120,12 @@ async function mockFetch(href) {
|
|
|
114
120
|
status: 404,
|
|
115
121
|
};
|
|
116
122
|
}
|
|
123
|
+
|
|
124
|
+
async function plain(tree) {
|
|
125
|
+
const result = {};
|
|
126
|
+
for await (const [key, value] of tree.entries()) {
|
|
127
|
+
const normalized = trailingSlash.remove(key);
|
|
128
|
+
result[normalized] = isMap(value) ? await plain(value) : value;
|
|
129
|
+
}
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { describe, test } from "node:test";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import FileMap from "../../src/drivers/FileMap.js";
|
|
7
|
+
import map from "../../src/operations/map.js";
|
|
8
|
+
import plain from "../../src/operations/plain.js";
|
|
9
|
+
|
|
10
|
+
const dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const tempDirectory = path.join(dirname, "fixtures/temp");
|
|
12
|
+
|
|
13
|
+
const textDecoder = new TextDecoder();
|
|
14
|
+
|
|
15
|
+
describe("FileMap", () => {
|
|
16
|
+
test("can get the keys of the tree", () => {
|
|
17
|
+
const fixture = createFixture("fixtures/markdown");
|
|
18
|
+
assert.deepEqual(Array.from(fixture.keys()), [
|
|
19
|
+
"Alice.md",
|
|
20
|
+
"Bob.md",
|
|
21
|
+
"Carol.md",
|
|
22
|
+
"subfolder/",
|
|
23
|
+
]);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("can get the value for a key", () => {
|
|
27
|
+
const fixture = createFixture("fixtures/markdown");
|
|
28
|
+
const buffer = fixture.get("Alice.md");
|
|
29
|
+
const text = textDecoder.decode(buffer);
|
|
30
|
+
assert.equal(text, "Hello, **Alice**.");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("getting an unsupported key returns undefined", () => {
|
|
34
|
+
const fixture = createFixture("fixtures/markdown");
|
|
35
|
+
assert.equal(fixture.get("xyz"), undefined);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("getting empty key returns undefined", () => {
|
|
39
|
+
const fixture = createFixture("fixtures/markdown");
|
|
40
|
+
assert.equal(fixture.get(""), undefined);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("getting a null/undefined key throws an exception", async () => {
|
|
44
|
+
const fixture = createFixture("fixtures/markdown");
|
|
45
|
+
await assert.rejects(async () => {
|
|
46
|
+
await fixture.get(null);
|
|
47
|
+
});
|
|
48
|
+
await assert.rejects(async () => {
|
|
49
|
+
await fixture.get(undefined);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("can retrieve values with optional trailing slash", () => {
|
|
54
|
+
const fixture = createFixture("fixtures/markdown");
|
|
55
|
+
assert(fixture.get("Alice.md"));
|
|
56
|
+
assert(fixture.get("Alice.md/"));
|
|
57
|
+
assert(fixture.get("subfolder"));
|
|
58
|
+
assert(fixture.get("subfolder/"));
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("sets parent on subtrees", () => {
|
|
62
|
+
const fixture = createFixture("fixtures");
|
|
63
|
+
const markdown = fixture.get("markdown");
|
|
64
|
+
assert.deepEqual(markdown.parent, fixture);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("can write out a file via set()", () => {
|
|
68
|
+
createTempDirectory();
|
|
69
|
+
|
|
70
|
+
// Write out a file.
|
|
71
|
+
const fileName = "file1";
|
|
72
|
+
const fileText = "This is the first file.";
|
|
73
|
+
const tempFiles = new FileMap(tempDirectory);
|
|
74
|
+
tempFiles.set(fileName, fileText);
|
|
75
|
+
|
|
76
|
+
// Read it back in.
|
|
77
|
+
const filePath = path.join(tempDirectory, fileName);
|
|
78
|
+
const actualText = String(fs.readFileSync(filePath));
|
|
79
|
+
|
|
80
|
+
assert.equal(fileText, actualText);
|
|
81
|
+
|
|
82
|
+
removeTempDirectory();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("create subfolder via set() with EMPTY", () => {
|
|
86
|
+
createTempDirectory();
|
|
87
|
+
|
|
88
|
+
// Write out new, empty folder called "empty".
|
|
89
|
+
const tempFiles = new FileMap(tempDirectory);
|
|
90
|
+
tempFiles.set("empty", FileMap.EMPTY);
|
|
91
|
+
|
|
92
|
+
// Verify folder exists and has no contents.
|
|
93
|
+
const folderPath = path.join(tempDirectory, "empty");
|
|
94
|
+
const stats = fs.statSync(folderPath);
|
|
95
|
+
assert(stats.isDirectory());
|
|
96
|
+
const files = fs.readdirSync(folderPath);
|
|
97
|
+
assert.deepEqual(files, []);
|
|
98
|
+
|
|
99
|
+
removeTempDirectory();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("can write out subfolder via set()", async () => {
|
|
103
|
+
createTempDirectory();
|
|
104
|
+
|
|
105
|
+
// Create a tiny set of "files".
|
|
106
|
+
// @ts-ignore
|
|
107
|
+
const files = new Map([
|
|
108
|
+
["file1", "This is the first file."],
|
|
109
|
+
["subfolder", new Map([["file2", "This is the second file."]])],
|
|
110
|
+
]);
|
|
111
|
+
const object = await plain(files);
|
|
112
|
+
|
|
113
|
+
// Write out files as a new folder called "folder".
|
|
114
|
+
const tempFiles = new FileMap(tempDirectory);
|
|
115
|
+
tempFiles.set("folder", files);
|
|
116
|
+
|
|
117
|
+
// Read them back in.
|
|
118
|
+
const actualFiles = tempFiles.get("folder");
|
|
119
|
+
const strings = await map(actualFiles, {
|
|
120
|
+
deep: true,
|
|
121
|
+
value: (buffer) => textDecoder.decode(buffer),
|
|
122
|
+
});
|
|
123
|
+
const result = await plain(strings);
|
|
124
|
+
assert.deepEqual(result, object);
|
|
125
|
+
|
|
126
|
+
removeTempDirectory();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("can delete a file", () => {
|
|
130
|
+
createTempDirectory();
|
|
131
|
+
const tempFile = path.join(tempDirectory, "file");
|
|
132
|
+
fs.writeFileSync(tempFile, "");
|
|
133
|
+
const tempFiles = new FileMap(tempDirectory);
|
|
134
|
+
const deleted = tempFiles.delete("file");
|
|
135
|
+
assert(deleted);
|
|
136
|
+
let stats;
|
|
137
|
+
try {
|
|
138
|
+
stats = fs.statSync(tempFile);
|
|
139
|
+
} catch (/** @type {any} */ error) {
|
|
140
|
+
if (error.code !== "ENOENT") {
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
assert(stats === undefined);
|
|
145
|
+
removeTempDirectory();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("delete returns false if file does not exist", () => {
|
|
149
|
+
createTempDirectory();
|
|
150
|
+
const tempFiles = new FileMap(tempDirectory);
|
|
151
|
+
const deleted = tempFiles.delete("nonexistent-file");
|
|
152
|
+
assert(!deleted);
|
|
153
|
+
removeTempDirectory();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("can delete a folder", () => {
|
|
157
|
+
createTempDirectory();
|
|
158
|
+
const folder = path.join(tempDirectory, "folder");
|
|
159
|
+
fs.mkdirSync(folder);
|
|
160
|
+
const tempFiles = new FileMap(tempDirectory);
|
|
161
|
+
tempFiles.delete("folder");
|
|
162
|
+
let stats;
|
|
163
|
+
try {
|
|
164
|
+
stats = fs.statSync(folder);
|
|
165
|
+
} catch (/** @type {any} */ error) {
|
|
166
|
+
if (error.code !== "ENOENT") {
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
assert(stats === undefined);
|
|
171
|
+
removeTempDirectory();
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
function createFixture(fixturePath) {
|
|
176
|
+
return new FileMap(path.join(dirname, fixturePath));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function createTempDirectory() {
|
|
180
|
+
fs.mkdirSync(tempDirectory, { recursive: true });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function removeTempDirectory() {
|
|
184
|
+
fs.rmSync(tempDirectory, { force: true, recursive: true });
|
|
185
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { describe, test } from "node:test";
|
|
3
|
+
import FunctionMap from "../../src/drivers/FunctionMap.js";
|
|
4
|
+
import * as symbols from "../../src/symbols.js";
|
|
5
|
+
|
|
6
|
+
describe("FunctionMap", () => {
|
|
7
|
+
test("keys uses supplied domain", () => {
|
|
8
|
+
const map = createFixture();
|
|
9
|
+
assert.deepEqual(Array.from(map.keys()), [
|
|
10
|
+
"Alice.md",
|
|
11
|
+
"Bob.md",
|
|
12
|
+
"Carol.md",
|
|
13
|
+
]);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("can get the value for a key", () => {
|
|
17
|
+
const map = createFixture();
|
|
18
|
+
const alice = map.get("Alice.md");
|
|
19
|
+
assert.equal(alice, "Hello, **Alice**.");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("can get the value for an async function", async () => {
|
|
23
|
+
const map = new FunctionMap(async (s) => ({ s }));
|
|
24
|
+
const result = await map.get("test");
|
|
25
|
+
assert.deepEqual(result, {
|
|
26
|
+
s: "test",
|
|
27
|
+
});
|
|
28
|
+
assert.equal(result[symbols.parent], map);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("getting a value from function with multiple arguments curries the function", () => {
|
|
32
|
+
const map = new FunctionMap((a, b, c) => a + b + c);
|
|
33
|
+
const fnA = map.get(1);
|
|
34
|
+
const fnAB = fnA.get(2);
|
|
35
|
+
const result = fnAB.get(3);
|
|
36
|
+
assert.equal(result, 6);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("getting an unsupported key returns undefined", () => {
|
|
40
|
+
const fixture = createFixture();
|
|
41
|
+
assert.equal(fixture.get("xyz"), undefined);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
function createFixture() {
|
|
46
|
+
return new FunctionMap(
|
|
47
|
+
(key) => {
|
|
48
|
+
if (key?.endsWith?.(".md")) {
|
|
49
|
+
const name = key.slice(0, -3);
|
|
50
|
+
return `Hello, **${name}**.`;
|
|
51
|
+
}
|
|
52
|
+
return undefined;
|
|
53
|
+
},
|
|
54
|
+
["Alice.md", "Bob.md", "Carol.md"]
|
|
55
|
+
);
|
|
56
|
+
}
|