@weborigami/async-tree 0.0.64-beta.2 → 0.0.65-beta.1
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/main.js +3 -3
- package/package.json +4 -4
- package/src/BrowserFileTree.js +7 -0
- package/src/FileTree.js +5 -3
- package/src/FunctionTree.js +2 -2
- package/src/MapTree.js +7 -0
- package/src/ObjectTree.js +5 -9
- package/src/SetTree.js +7 -0
- package/src/SiteTree.js +9 -2
- package/src/Tree.js +10 -14
- package/src/calendarTree.js +77 -0
- package/src/{keysJson.js → jsonKeys.js} +2 -2
- package/src/operations/cache.js +21 -6
- package/test/BrowserFileTree.test.js +10 -0
- package/test/FileTree.test.js +12 -2
- package/test/MapTree.test.js +10 -0
- package/test/ObjectTree.test.js +10 -0
- package/test/SetTree.test.js +11 -0
- package/test/SiteTree.test.js +10 -0
- package/test/calendarTree.test.js +111 -0
- package/test/jsonKeys.test.js +24 -0
- package/test/operations/cache.test.js +10 -3
- /package/src/{keysJson.d.ts → jsonKeys.d.ts} +0 -0
package/browser.js
CHANGED
|
@@ -4,10 +4,10 @@ export { default as DeferredTree } from "./src/DeferredTree.js";
|
|
|
4
4
|
// Skip FileTree.js, which is Node.js only.
|
|
5
5
|
export { default as BrowserFileTree } from "./src/BrowserFileTree.js";
|
|
6
6
|
export { default as FunctionTree } from "./src/FunctionTree.js";
|
|
7
|
+
export * as keysJson from "./src/jsonKeys.js";
|
|
7
8
|
export { default as MapTree } from "./src/MapTree.js";
|
|
8
9
|
export { default as ObjectTree } from "./src/ObjectTree.js";
|
|
9
10
|
export { default as SetTree } from "./src/SetTree.js";
|
|
10
11
|
export { default as SiteTree } from "./src/SiteTree.js";
|
|
11
12
|
export * as Tree from "./src/Tree.js";
|
|
12
|
-
export * as keysJson from "./src/keysJson.js";
|
|
13
13
|
export * from "./src/utilities.js";
|
package/main.js
CHANGED
|
@@ -6,10 +6,8 @@ export { default as FunctionTree } from "./src/FunctionTree.js";
|
|
|
6
6
|
export { default as MapTree } from "./src/MapTree.js";
|
|
7
7
|
// Skip BrowserFileTree.js, which is browser-only.
|
|
8
8
|
export { default as DeepMapTree } from "./src/DeepMapTree.js";
|
|
9
|
-
export { default as SetTree } from "./src/SetTree.js";
|
|
10
|
-
export { default as SiteTree } from "./src/SiteTree.js";
|
|
11
9
|
export { DeepObjectTree, ObjectTree, Tree } from "./src/internal.js";
|
|
12
|
-
export * as
|
|
10
|
+
export * as jsonKeys from "./src/jsonKeys.js";
|
|
13
11
|
export { default as cache } from "./src/operations/cache.js";
|
|
14
12
|
export { default as concat } from "./src/operations/concat.js";
|
|
15
13
|
export { default as deepMerge } from "./src/operations/deepMerge.js";
|
|
@@ -24,6 +22,8 @@ export { default as merge } from "./src/operations/merge.js";
|
|
|
24
22
|
export { default as scope } from "./src/operations/scope.js";
|
|
25
23
|
export { default as sort } from "./src/operations/sort.js";
|
|
26
24
|
export { default as take } from "./src/operations/take.js";
|
|
25
|
+
export { default as SetTree } from "./src/SetTree.js";
|
|
26
|
+
export { default as SiteTree } from "./src/SiteTree.js";
|
|
27
27
|
export * as symbols from "./src/symbols.js";
|
|
28
28
|
export { default as cachedKeyFunctions } from "./src/transforms/cachedKeyFunctions.js";
|
|
29
29
|
export { default as deepReverse } from "./src/transforms/deepReverse.js";
|
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weborigami/async-tree",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.65-beta.1",
|
|
4
4
|
"description": "Asynchronous tree drivers based on standard JavaScript classes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./main.js",
|
|
7
7
|
"browser": "./browser.js",
|
|
8
8
|
"types": "./index.ts",
|
|
9
9
|
"devDependencies": {
|
|
10
|
-
"@types/node": "
|
|
11
|
-
"typescript": "5.5.
|
|
10
|
+
"@types/node": "22.5.4",
|
|
11
|
+
"typescript": "5.5.4"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@weborigami/types": "0.0.
|
|
14
|
+
"@weborigami/types": "0.0.65-beta.1"
|
|
15
15
|
},
|
|
16
16
|
"scripts": {
|
|
17
17
|
"test": "node --test --test-reporter=spec",
|
package/src/BrowserFileTree.js
CHANGED
|
@@ -34,6 +34,13 @@ export default class BrowserFileTree {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
async get(key) {
|
|
37
|
+
if (key == null) {
|
|
38
|
+
// Reject nullish key.
|
|
39
|
+
throw new ReferenceError(
|
|
40
|
+
`${this.constructor.name}: Cannot get a null or undefined key.`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
37
44
|
const directory = await this.getDirectory();
|
|
38
45
|
|
|
39
46
|
// Try the key as a subfolder name.
|
package/src/FileTree.js
CHANGED
|
@@ -49,9 +49,11 @@ export default class FileTree {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
async get(key) {
|
|
52
|
-
if (
|
|
53
|
-
//
|
|
54
|
-
|
|
52
|
+
if (key == null) {
|
|
53
|
+
// Reject nullish key.
|
|
54
|
+
throw new ReferenceError(
|
|
55
|
+
`${this.constructor.name}: Cannot get a null or undefined key.`
|
|
56
|
+
);
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
const filePath = path.resolve(this.dirname, key);
|
package/src/FunctionTree.js
CHANGED
|
@@ -27,11 +27,11 @@ export default class FunctionTree {
|
|
|
27
27
|
this.fn.length <= 1
|
|
28
28
|
? // Function takes no arguments, one argument, or a variable number of
|
|
29
29
|
// arguments: invoke it.
|
|
30
|
-
await this.fn.call(
|
|
30
|
+
await this.fn.call(this.parent, key)
|
|
31
31
|
: // Bind the key to the first parameter. Subsequent get calls will
|
|
32
32
|
// eventually bind all parameters until only one remains. At that point,
|
|
33
33
|
// the above condition will apply and the function will be invoked.
|
|
34
|
-
Reflect.construct(this.constructor, [this.fn.bind(
|
|
34
|
+
Reflect.construct(this.constructor, [this.fn.bind(this.parent, key)]);
|
|
35
35
|
setParent(value, this);
|
|
36
36
|
return value;
|
|
37
37
|
}
|
package/src/MapTree.js
CHANGED
|
@@ -23,6 +23,13 @@ export default class MapTree {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
async get(key) {
|
|
26
|
+
if (key == null) {
|
|
27
|
+
// Reject nullish key.
|
|
28
|
+
throw new ReferenceError(
|
|
29
|
+
`${this.constructor.name}: Cannot get a null or undefined key.`
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
26
33
|
const value = this.map.get(key);
|
|
27
34
|
setParent(value, this);
|
|
28
35
|
return value;
|
package/src/ObjectTree.js
CHANGED
|
@@ -25,15 +25,11 @@ export default class ObjectTree {
|
|
|
25
25
|
* @param {any} key
|
|
26
26
|
*/
|
|
27
27
|
async get(key) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
key &&
|
|
34
|
-
!this.object.hasOwnProperty(key)
|
|
35
|
-
) {
|
|
36
|
-
return undefined;
|
|
28
|
+
if (key == null) {
|
|
29
|
+
// Reject nullish key.
|
|
30
|
+
throw new ReferenceError(
|
|
31
|
+
`${this.constructor.name}: Cannot get a null or undefined key.`
|
|
32
|
+
);
|
|
37
33
|
}
|
|
38
34
|
|
|
39
35
|
let value = await this.object[key];
|
package/src/SetTree.js
CHANGED
|
@@ -17,6 +17,13 @@ export default class SetTree {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
async get(key) {
|
|
20
|
+
if (key == null) {
|
|
21
|
+
// Reject nullish key.
|
|
22
|
+
throw new ReferenceError(
|
|
23
|
+
`${this.constructor.name}: Cannot get a null or undefined key.`
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
20
27
|
const value = this.values[key];
|
|
21
28
|
setParent(value, this);
|
|
22
29
|
return value;
|
package/src/SiteTree.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Tree } from "./internal.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as jsonKeys from "./jsonKeys.js";
|
|
3
3
|
import { setParent } from "./utilities.js";
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -31,6 +31,13 @@ export default class SiteTree {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
async get(key) {
|
|
34
|
+
if (key == null) {
|
|
35
|
+
// Reject nullish key.
|
|
36
|
+
throw new ReferenceError(
|
|
37
|
+
`${this.constructor.name}: Cannot get a null or undefined key.`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
34
41
|
// If there is only one key and it's the empty string, and the site is
|
|
35
42
|
// explorable, we take the route as "index.html". With this and subsequent
|
|
36
43
|
// checks, we try to avoid sniffing the site to see if it's explorable, as
|
|
@@ -86,7 +93,7 @@ export default class SiteTree {
|
|
|
86
93
|
.then((response) => (response.ok ? response.text() : null))
|
|
87
94
|
.then((text) => {
|
|
88
95
|
try {
|
|
89
|
-
return text ?
|
|
96
|
+
return text ? jsonKeys.parse(text) : null;
|
|
90
97
|
} catch (error) {
|
|
91
98
|
// Got a response, but it's not JSON. Most likely the site doesn't
|
|
92
99
|
// actually have a .keys.json file, and is returning a Not Found page,
|
package/src/Tree.js
CHANGED
|
@@ -402,13 +402,10 @@ export async function traverse(treelike, ...keys) {
|
|
|
402
402
|
* @param {...any} keys
|
|
403
403
|
*/
|
|
404
404
|
export async function traverseOrThrow(treelike, ...keys) {
|
|
405
|
-
if (!treelike) {
|
|
406
|
-
throw new TraverseError("Tried to traverse a null or undefined value");
|
|
407
|
-
}
|
|
408
|
-
|
|
409
405
|
// Start our traversal at the root of the tree.
|
|
410
406
|
/** @type {any} */
|
|
411
407
|
let value = treelike;
|
|
408
|
+
let position = 0;
|
|
412
409
|
|
|
413
410
|
// If traversal operation was called with a `this` context, use that as the
|
|
414
411
|
// target for function calls.
|
|
@@ -419,13 +416,11 @@ export async function traverseOrThrow(treelike, ...keys) {
|
|
|
419
416
|
let key;
|
|
420
417
|
while (remainingKeys.length > 0) {
|
|
421
418
|
if (value === undefined) {
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
.join("/")}`;
|
|
428
|
-
throw new TraverseError(message, treelike, keys);
|
|
419
|
+
throw new TraverseError("Tried to traverse a null or undefined value", {
|
|
420
|
+
tree: treelike,
|
|
421
|
+
keys,
|
|
422
|
+
position,
|
|
423
|
+
});
|
|
429
424
|
}
|
|
430
425
|
|
|
431
426
|
// If the value is packed and can be unpacked, unpack it.
|
|
@@ -466,6 +461,8 @@ export async function traverseOrThrow(treelike, ...keys) {
|
|
|
466
461
|
value = originalValue;
|
|
467
462
|
}
|
|
468
463
|
}
|
|
464
|
+
|
|
465
|
+
position++;
|
|
469
466
|
}
|
|
470
467
|
|
|
471
468
|
return value;
|
|
@@ -485,11 +482,10 @@ export async function traversePath(tree, path) {
|
|
|
485
482
|
|
|
486
483
|
// Error class thrown by traverseOrThrow()
|
|
487
484
|
class TraverseError extends ReferenceError {
|
|
488
|
-
constructor(message,
|
|
485
|
+
constructor(message, options) {
|
|
489
486
|
super(message);
|
|
490
|
-
this.tree = tree;
|
|
491
487
|
this.name = "TraverseError";
|
|
492
|
-
this
|
|
488
|
+
Object.assign(this, options);
|
|
493
489
|
}
|
|
494
490
|
}
|
|
495
491
|
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Return a tree of years, months, and days from a start date to an end date.
|
|
3
|
+
*
|
|
4
|
+
* Both the start and end date can be provided in "YYYY-MM-DD", "YYYY-MM", or
|
|
5
|
+
* "YYYY" format. If a start year is provided, but a month is not, then the
|
|
6
|
+
* first month of the year will be used; if a start month is provided, but a day
|
|
7
|
+
* is not, then the first day of that month will be used. Similar logic applies
|
|
8
|
+
* to the end date, using the last month of the year or the last day of the
|
|
9
|
+
* month.
|
|
10
|
+
*
|
|
11
|
+
* If a start date is omitted, today will be used, likewise for the end date.
|
|
12
|
+
*
|
|
13
|
+
* @param {string} [start] - Start date in "YYYY-MM-DD", "YYYY-MM", or "YYYY"
|
|
14
|
+
* format
|
|
15
|
+
* @param {string} [end] - End date in "YYYY-MM-DD", "YYYY-MM", or "YYYY" format
|
|
16
|
+
*/
|
|
17
|
+
export default function calendarTree(start, end) {
|
|
18
|
+
const startParts = start?.split("-") ?? [];
|
|
19
|
+
const endParts = end?.split("-") ?? [];
|
|
20
|
+
|
|
21
|
+
const today = new Date();
|
|
22
|
+
|
|
23
|
+
const startYear = startParts[0]
|
|
24
|
+
? parseInt(startParts[0])
|
|
25
|
+
: today.getFullYear();
|
|
26
|
+
const startMonth = startParts[1]
|
|
27
|
+
? parseInt(startParts[1])
|
|
28
|
+
: startParts[0]
|
|
29
|
+
? 1
|
|
30
|
+
: today.getMonth() + 1;
|
|
31
|
+
const startDay = startParts[2]
|
|
32
|
+
? parseInt(startParts[2])
|
|
33
|
+
: startParts[1]
|
|
34
|
+
? 1
|
|
35
|
+
: today.getDate();
|
|
36
|
+
|
|
37
|
+
const endYear = endParts[0] ? parseInt(endParts[0]) : today.getFullYear();
|
|
38
|
+
const endMonth = endParts[1]
|
|
39
|
+
? parseInt(endParts[1])
|
|
40
|
+
: endParts[0]
|
|
41
|
+
? 12
|
|
42
|
+
: today.getMonth() + 1;
|
|
43
|
+
const endDay = endParts[2]
|
|
44
|
+
? parseInt(endParts[2])
|
|
45
|
+
: endParts[1]
|
|
46
|
+
? daysInMonth(endYear, endMonth)
|
|
47
|
+
: today.getDate();
|
|
48
|
+
|
|
49
|
+
let years = {};
|
|
50
|
+
for (let year = startYear; year <= endYear; year++) {
|
|
51
|
+
let months = new Map();
|
|
52
|
+
const firstMonth = year === startYear ? startMonth : 1;
|
|
53
|
+
const lastMonth = year === endYear ? endMonth : 12;
|
|
54
|
+
for (let month = firstMonth; month <= lastMonth; month++) {
|
|
55
|
+
const monthPadded = month.toString().padStart(2, "0");
|
|
56
|
+
let days = new Map();
|
|
57
|
+
const firstDay =
|
|
58
|
+
year === startYear && month === startMonth ? startDay : 1;
|
|
59
|
+
const lastDay =
|
|
60
|
+
year === endYear && month === endMonth
|
|
61
|
+
? endDay
|
|
62
|
+
: daysInMonth(year, month);
|
|
63
|
+
for (let day = firstDay; day <= lastDay; day++) {
|
|
64
|
+
const dayPadded = day.toString().padStart(2, "0");
|
|
65
|
+
days.set(dayPadded, null);
|
|
66
|
+
}
|
|
67
|
+
months.set(monthPadded, days);
|
|
68
|
+
}
|
|
69
|
+
years[year] = months;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return years;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function daysInMonth(year, month) {
|
|
76
|
+
return new Date(year, month, 0).getDate();
|
|
77
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Tree } from "./internal.js";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* The
|
|
5
|
-
*
|
|
4
|
+
* The JSON Keys protocol lets a site expose the keys of a node in the site so
|
|
5
|
+
* that they can be read by SiteTree.
|
|
6
6
|
*
|
|
7
7
|
* This file format is a JSON array of key descriptors: a string like
|
|
8
8
|
* "index.html" for a specific resource available at the node, or a string with
|
package/src/operations/cache.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ObjectTree, Tree } from "../internal.js";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Caches values from a source tree in a second cache tree. Cache source tree
|
|
@@ -14,16 +14,30 @@ import { DeepObjectTree, Tree } from "../internal.js";
|
|
|
14
14
|
* @typedef {import("../../index.ts").Treelike} Treelike
|
|
15
15
|
*
|
|
16
16
|
* @param {Treelike} sourceTreelike
|
|
17
|
-
* @param {AsyncMutableTree} [
|
|
17
|
+
* @param {AsyncMutableTree} [cacheTreelike]
|
|
18
18
|
* @param {Treelike} [filterTreelike]
|
|
19
19
|
* @returns {AsyncTree & { description: string }}
|
|
20
20
|
*/
|
|
21
|
-
export default function treeCache(
|
|
21
|
+
export default function treeCache(
|
|
22
|
+
sourceTreelike,
|
|
23
|
+
cacheTreelike,
|
|
24
|
+
filterTreelike
|
|
25
|
+
) {
|
|
22
26
|
const source = Tree.from(sourceTreelike);
|
|
23
27
|
const filter = filterTreelike ? Tree.from(filterTreelike) : undefined;
|
|
24
28
|
|
|
25
29
|
/** @type {AsyncMutableTree} */
|
|
26
|
-
|
|
30
|
+
let cache;
|
|
31
|
+
if (cacheTreelike) {
|
|
32
|
+
// @ts-ignore
|
|
33
|
+
cache = Tree.from(cacheTreelike);
|
|
34
|
+
if (!Tree.isAsyncMutableTree(cache)) {
|
|
35
|
+
throw new Error("Cache tree must define a set() method.");
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
cache = new ObjectTree({});
|
|
39
|
+
}
|
|
40
|
+
|
|
27
41
|
let keys;
|
|
28
42
|
return {
|
|
29
43
|
description: "cache",
|
|
@@ -47,8 +61,9 @@ export default function treeCache(sourceTreelike, cacheTree, filterTreelike) {
|
|
|
47
61
|
// Construct merged tree for a tree result.
|
|
48
62
|
if (cacheValue === undefined) {
|
|
49
63
|
// Construct new container in cache
|
|
50
|
-
|
|
51
|
-
cacheValue =
|
|
64
|
+
cacheValue = new ObjectTree({});
|
|
65
|
+
cacheValue.parent = this;
|
|
66
|
+
await cache.set(key, cacheValue);
|
|
52
67
|
}
|
|
53
68
|
value = treeCache(value, cacheValue, filterValue);
|
|
54
69
|
} else {
|
|
@@ -30,6 +30,16 @@ if (isBrowser) {
|
|
|
30
30
|
assert.equal(await fixture.get("xyz"), undefined);
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
+
test("getting a null/undefined key throws an exception", async () => {
|
|
34
|
+
const fixture = await createFixture();
|
|
35
|
+
await assert.rejects(async () => {
|
|
36
|
+
await fixture.get(null);
|
|
37
|
+
});
|
|
38
|
+
await assert.rejects(async () => {
|
|
39
|
+
await fixture.get(undefined);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
33
43
|
test("can set a value", async () => {
|
|
34
44
|
const fixture = await createFixture();
|
|
35
45
|
|
package/test/FileTree.test.js
CHANGED
|
@@ -11,7 +11,7 @@ const tempDirectory = path.join(dirname, "fixtures/temp");
|
|
|
11
11
|
|
|
12
12
|
const textDecoder = new TextDecoder();
|
|
13
13
|
|
|
14
|
-
describe
|
|
14
|
+
describe("FileTree", async () => {
|
|
15
15
|
test("can get the keys of the tree", async () => {
|
|
16
16
|
const fixture = createFixture("fixtures/markdown");
|
|
17
17
|
assert.deepEqual(Array.from(await fixture.keys()), [
|
|
@@ -33,6 +33,16 @@ describe.only("FileTree", async () => {
|
|
|
33
33
|
assert.equal(await fixture.get("xyz"), undefined);
|
|
34
34
|
});
|
|
35
35
|
|
|
36
|
+
test("getting a null/undefined key throws an exception", async () => {
|
|
37
|
+
const fixture = createFixture("fixtures/markdown");
|
|
38
|
+
await assert.rejects(async () => {
|
|
39
|
+
await fixture.get(null);
|
|
40
|
+
});
|
|
41
|
+
await assert.rejects(async () => {
|
|
42
|
+
await fixture.get(undefined);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
36
46
|
test("sets parent on subtrees", async () => {
|
|
37
47
|
const fixture = createFixture("fixtures");
|
|
38
48
|
const markdown = await fixture.get("markdown");
|
|
@@ -64,7 +74,7 @@ describe.only("FileTree", async () => {
|
|
|
64
74
|
await removeTempDirectory();
|
|
65
75
|
});
|
|
66
76
|
|
|
67
|
-
test
|
|
77
|
+
test("can create empty subfolder via set()", async () => {
|
|
68
78
|
await createTempDirectory();
|
|
69
79
|
|
|
70
80
|
// Write out new, empty folder called "empty".
|
package/test/MapTree.test.js
CHANGED
|
@@ -26,6 +26,16 @@ describe("MapTree", () => {
|
|
|
26
26
|
const fixture = createFixture();
|
|
27
27
|
assert.equal(await fixture.get("d"), undefined);
|
|
28
28
|
});
|
|
29
|
+
|
|
30
|
+
test("getting a null/undefined key throws an exception", async () => {
|
|
31
|
+
const fixture = createFixture();
|
|
32
|
+
await assert.rejects(async () => {
|
|
33
|
+
await fixture.get(null);
|
|
34
|
+
});
|
|
35
|
+
await assert.rejects(async () => {
|
|
36
|
+
await fixture.get(undefined);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
29
39
|
});
|
|
30
40
|
|
|
31
41
|
function createFixture() {
|
package/test/ObjectTree.test.js
CHANGED
|
@@ -24,6 +24,16 @@ describe("ObjectTree", () => {
|
|
|
24
24
|
assert.equal(await fixture.get("xyz"), undefined);
|
|
25
25
|
});
|
|
26
26
|
|
|
27
|
+
test("getting a null/undefined key throws an exception", async () => {
|
|
28
|
+
const fixture = createFixture();
|
|
29
|
+
await assert.rejects(async () => {
|
|
30
|
+
await fixture.get(null);
|
|
31
|
+
});
|
|
32
|
+
await assert.rejects(async () => {
|
|
33
|
+
await fixture.get(undefined);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
27
37
|
test("can set a value", async () => {
|
|
28
38
|
const tree = new ObjectTree({
|
|
29
39
|
a: 1,
|
package/test/SetTree.test.js
CHANGED
|
@@ -23,6 +23,17 @@ describe("SetTree", () => {
|
|
|
23
23
|
assert.equal(await fixture.get(3), undefined);
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
+
test("getting a null/undefined key throws an exception", async () => {
|
|
27
|
+
const set = new Set(["a", "b", "c"]);
|
|
28
|
+
const fixture = new SetTree(set);
|
|
29
|
+
await assert.rejects(async () => {
|
|
30
|
+
await fixture.get(null);
|
|
31
|
+
});
|
|
32
|
+
await assert.rejects(async () => {
|
|
33
|
+
await fixture.get(undefined);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
26
37
|
test("sets parent on subtrees", async () => {
|
|
27
38
|
const set = new Set();
|
|
28
39
|
set.add(new ObjectTree({}));
|
package/test/SiteTree.test.js
CHANGED
|
@@ -66,6 +66,16 @@ describe("SiteTree", () => {
|
|
|
66
66
|
assert.equal(await fixture.get("xyz"), undefined);
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
+
test("getting a null/undefined key throws an exception", async () => {
|
|
70
|
+
const fixture = new SiteTree(mockHost);
|
|
71
|
+
await assert.rejects(async () => {
|
|
72
|
+
await fixture.get(null);
|
|
73
|
+
});
|
|
74
|
+
await assert.rejects(async () => {
|
|
75
|
+
await fixture.get(undefined);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
69
79
|
test("a redirect on a site with keys returns a SiteTree for the new URL", async () => {
|
|
70
80
|
const fixture = new SiteTree(mockHost);
|
|
71
81
|
const about = await fixture.get("about");
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { describe, test } from "node:test";
|
|
3
|
+
import { toPlainValue } from "../main.js";
|
|
4
|
+
import calendarTree from "../src/calendarTree.js";
|
|
5
|
+
|
|
6
|
+
describe("calendarTree", () => {
|
|
7
|
+
test("without a start or end, returns a tree for today", async () => {
|
|
8
|
+
const tree = calendarTree();
|
|
9
|
+
const plain = await toPlainValue(tree);
|
|
10
|
+
const today = new Date();
|
|
11
|
+
const year = today.getFullYear();
|
|
12
|
+
const month = (today.getMonth() + 1).toString().padStart(2, "0");
|
|
13
|
+
const day = today.getDate().toString().padStart(2, "0");
|
|
14
|
+
assert.deepEqual(plain, {
|
|
15
|
+
[year]: {
|
|
16
|
+
[month]: {
|
|
17
|
+
[day]: null,
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("returns a tree for a month range", async () => {
|
|
24
|
+
const tree = calendarTree("2025-01", "2025-02");
|
|
25
|
+
const plain = await toPlainValue(tree);
|
|
26
|
+
assert.deepEqual(plain, {
|
|
27
|
+
2025: {
|
|
28
|
+
"01": {
|
|
29
|
+
"01": null,
|
|
30
|
+
"02": null,
|
|
31
|
+
"03": null,
|
|
32
|
+
"04": null,
|
|
33
|
+
"05": null,
|
|
34
|
+
"06": null,
|
|
35
|
+
"07": null,
|
|
36
|
+
"08": null,
|
|
37
|
+
"09": null,
|
|
38
|
+
10: null,
|
|
39
|
+
11: null,
|
|
40
|
+
12: null,
|
|
41
|
+
13: null,
|
|
42
|
+
14: null,
|
|
43
|
+
15: null,
|
|
44
|
+
16: null,
|
|
45
|
+
17: null,
|
|
46
|
+
18: null,
|
|
47
|
+
19: null,
|
|
48
|
+
20: null,
|
|
49
|
+
21: null,
|
|
50
|
+
22: null,
|
|
51
|
+
23: null,
|
|
52
|
+
24: null,
|
|
53
|
+
25: null,
|
|
54
|
+
26: null,
|
|
55
|
+
27: null,
|
|
56
|
+
28: null,
|
|
57
|
+
29: null,
|
|
58
|
+
30: null,
|
|
59
|
+
31: null,
|
|
60
|
+
},
|
|
61
|
+
"02": {
|
|
62
|
+
"01": null,
|
|
63
|
+
"02": null,
|
|
64
|
+
"03": null,
|
|
65
|
+
"04": null,
|
|
66
|
+
"05": null,
|
|
67
|
+
"06": null,
|
|
68
|
+
"07": null,
|
|
69
|
+
"08": null,
|
|
70
|
+
"09": null,
|
|
71
|
+
10: null,
|
|
72
|
+
11: null,
|
|
73
|
+
12: null,
|
|
74
|
+
13: null,
|
|
75
|
+
14: null,
|
|
76
|
+
15: null,
|
|
77
|
+
16: null,
|
|
78
|
+
17: null,
|
|
79
|
+
18: null,
|
|
80
|
+
19: null,
|
|
81
|
+
20: null,
|
|
82
|
+
21: null,
|
|
83
|
+
22: null,
|
|
84
|
+
23: null,
|
|
85
|
+
24: null,
|
|
86
|
+
25: null,
|
|
87
|
+
26: null,
|
|
88
|
+
27: null,
|
|
89
|
+
28: null,
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("returns a tree for a day range", async () => {
|
|
96
|
+
const tree = calendarTree("2025-02-27", "2025-03-02");
|
|
97
|
+
const plain = await toPlainValue(tree);
|
|
98
|
+
assert.deepEqual(plain, {
|
|
99
|
+
2025: {
|
|
100
|
+
"02": {
|
|
101
|
+
27: null,
|
|
102
|
+
28: null,
|
|
103
|
+
},
|
|
104
|
+
"03": {
|
|
105
|
+
"01": null,
|
|
106
|
+
"02": null,
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { DeepObjectTree } from "@weborigami/async-tree";
|
|
2
|
+
import assert from "node:assert";
|
|
3
|
+
import { describe, test } from "node:test";
|
|
4
|
+
import * as jsonKeys from "../src/jsonKeys.js";
|
|
5
|
+
|
|
6
|
+
describe("jsonKeys", () => {
|
|
7
|
+
test("parses JSON Keys", async () => {
|
|
8
|
+
const json = '["index.html","about/"]';
|
|
9
|
+
const parsed = jsonKeys.parse(json);
|
|
10
|
+
assert.deepStrictEqual(parsed, {
|
|
11
|
+
"index.html": false,
|
|
12
|
+
about: true,
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("stringifies JSON Keys", async () => {
|
|
17
|
+
const tree = new DeepObjectTree({
|
|
18
|
+
about: {},
|
|
19
|
+
"index.html": "Home",
|
|
20
|
+
});
|
|
21
|
+
const json = await jsonKeys.stringify(tree);
|
|
22
|
+
assert.strictEqual(json, '["about/","index.html"]');
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import assert from "node:assert";
|
|
2
2
|
import { describe, test } from "node:test";
|
|
3
|
-
import { ObjectTree, Tree } from "../../src/internal.js";
|
|
3
|
+
import { DeepObjectTree, ObjectTree, Tree } from "../../src/internal.js";
|
|
4
4
|
import cache from "../../src/operations/cache.js";
|
|
5
5
|
|
|
6
6
|
describe("cache", () => {
|
|
7
7
|
test("caches reads of values from one tree into another", async () => {
|
|
8
8
|
const objectCache = new ObjectTree({});
|
|
9
9
|
const fixture = cache(
|
|
10
|
-
|
|
10
|
+
new DeepObjectTree({
|
|
11
11
|
a: 1,
|
|
12
12
|
b: 2,
|
|
13
13
|
c: 3,
|
|
@@ -18,7 +18,7 @@ describe("cache", () => {
|
|
|
18
18
|
objectCache
|
|
19
19
|
);
|
|
20
20
|
|
|
21
|
-
const keys =
|
|
21
|
+
const keys = [...(await fixture.keys())];
|
|
22
22
|
assert.deepEqual(keys, ["a", "b", "c", "more"]);
|
|
23
23
|
|
|
24
24
|
assert.equal(await objectCache.get("a"), undefined);
|
|
@@ -28,6 +28,13 @@ describe("cache", () => {
|
|
|
28
28
|
assert.equal(await objectCache.get("b"), undefined);
|
|
29
29
|
assert.equal(await fixture.get("b"), 2);
|
|
30
30
|
assert.equal(await objectCache.get("b"), 2);
|
|
31
|
+
|
|
32
|
+
assert.equal(await objectCache.get("more"), undefined);
|
|
33
|
+
const more = await fixture.get("more");
|
|
34
|
+
assert.deepEqual([...(await more.keys())], ["d"]);
|
|
35
|
+
assert.equal(await more.get("d"), 4);
|
|
36
|
+
const moreCache = await objectCache.get("more");
|
|
37
|
+
assert.equal(await moreCache.get("d"), 4);
|
|
31
38
|
});
|
|
32
39
|
|
|
33
40
|
test("if a cache filter is supplied, it only caches values whose keys match the filter", async () => {
|
|
File without changes
|