dirvi-lib 0.7.0 → 0.8.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/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/tree-surfer/cursor.d.ts +52 -26
- package/dist/tree-surfer/cursor.d.ts.map +1 -1
- package/dist/tree-surfer/cursor.js +19 -44
- package/dist/tree-surfer/fold-node/fold-node.arb.d.ts +2 -0
- package/dist/tree-surfer/fold-node/fold-node.arb.d.ts.map +1 -0
- package/dist/tree-surfer/fold-node/fold-node.arb.js +1 -0
- package/dist/tree-surfer/fold-node/fold-node.impl.d.ts +3 -4
- package/dist/tree-surfer/fold-node/fold-node.impl.d.ts.map +1 -1
- package/dist/tree-surfer/fold-node/fold-node.impl.js +73 -67
- package/dist/tree-surfer/fold-node/fold-node.schema.d.ts +3 -5
- package/dist/tree-surfer/fold-node/fold-node.schema.d.ts.map +1 -1
- package/dist/tree-surfer/fold-node/fold-node.schema.js +2 -9
- package/dist/tree-surfer/fold-node/fold-node.types.d.ts +169 -46
- package/dist/tree-surfer/fold-node/fold-node.types.d.ts.map +1 -1
- package/dist/tree-surfer/fold-node/tree-node.test.d.ts +2 -0
- package/dist/tree-surfer/fold-node/tree-node.test.d.ts.map +1 -0
- package/dist/tree-surfer/fold-node/tree-node.test.js +90 -0
- package/dist/tree-surfer/index.d.ts +3 -1
- package/dist/tree-surfer/index.d.ts.map +1 -1
- package/dist/tree-surfer/index.js +3 -1
- package/dist/tree-surfer/nav-node/nav-node.impl.d.ts +5 -5
- package/dist/tree-surfer/nav-node/nav-node.impl.d.ts.map +1 -1
- package/dist/tree-surfer/nav-node/nav-node.impl.js +84 -99
- package/dist/tree-surfer/nav-node/nav-node.types.d.ts +62 -34
- package/dist/tree-surfer/nav-node/nav-node.types.d.ts.map +1 -1
- package/dist/tree-surfer/nav-node/nav-node.types.js +1 -3
- package/dist/tree-surfer/state/state.impl.d.ts +7 -0
- package/dist/tree-surfer/state/state.impl.d.ts.map +1 -0
- package/dist/tree-surfer/state/state.impl.js +89 -0
- package/dist/tree-surfer/state/state.schemas.d.ts +9 -0
- package/dist/tree-surfer/state/state.schemas.d.ts.map +1 -0
- package/dist/tree-surfer/state/state.schemas.js +8 -0
- package/dist/tree-surfer/state/state.types.d.ts +21 -0
- package/dist/tree-surfer/state/state.types.d.ts.map +1 -0
- package/dist/tree-surfer/state/state.types.js +1 -0
- package/dist/tree-surfer/tree-node/tree-node.arb.d.ts +82 -17
- package/dist/tree-surfer/tree-node/tree-node.arb.d.ts.map +1 -1
- package/dist/tree-surfer/tree-node/tree-node.arb.debug.test.d.ts +2 -0
- package/dist/tree-surfer/tree-node/tree-node.arb.debug.test.d.ts.map +1 -0
- package/dist/tree-surfer/tree-node/tree-node.arb.debug.test.js +34 -0
- package/dist/tree-surfer/tree-node/tree-node.arb.js +164 -56
- package/dist/tree-surfer/tree-node/tree-node.impl.d.ts +1 -1
- package/dist/tree-surfer/tree-node/tree-node.impl.d.ts.map +1 -1
- package/dist/tree-surfer/tree-node/tree-node.impl.js +41 -48
- package/dist/tree-surfer/tree-node/tree-node.schemas.d.ts +2 -2
- package/dist/tree-surfer/tree-node/tree-node.schemas.d.ts.map +1 -1
- package/dist/tree-surfer/tree-node/tree-node.schemas.js +2 -2
- package/dist/tree-surfer/tree-node/tree-node.test.js +144 -76
- package/dist/tree-surfer/tree-node/tree-node.types.d.ts +89 -107
- package/dist/tree-surfer/tree-node/tree-node.types.d.ts.map +1 -1
- package/dist/tree-surfer-ui/application/intent-to-effect.d.ts +6 -2
- package/dist/tree-surfer-ui/application/intent-to-effect.d.ts.map +1 -1
- package/dist/tree-surfer-ui/application/intent-to-effect.js +1 -6
- package/dist/tree-surfer-ui/application/user-input-to-intent.d.ts +10 -2
- package/dist/tree-surfer-ui/application/user-input-to-intent.d.ts.map +1 -1
- package/dist/tree-surfer-ui/application/user-input-to-intent.js +12 -5
- package/dist/tree-surfer-ui/domain/effect.d.ts +9 -8
- package/dist/tree-surfer-ui/domain/effect.d.ts.map +1 -1
- package/dist/tree-surfer-ui/domain/index.d.ts +1 -1
- package/dist/tree-surfer-ui/domain/index.d.ts.map +1 -1
- package/dist/tree-surfer-ui/domain/index.js +1 -1
- package/dist/tree-surfer-ui/domain/{input-state.d.ts → input-mode-state.d.ts} +2 -2
- package/dist/tree-surfer-ui/domain/input-mode-state.d.ts.map +1 -0
- package/package.json +3 -2
- package/dist/tree-surfer/state.d.ts +0 -25
- package/dist/tree-surfer/state.d.ts.map +0 -1
- package/dist/tree-surfer/state.js +0 -118
- package/dist/tree-surfer-ui/domain/input-state.d.ts.map +0 -1
- /package/dist/tree-surfer-ui/domain/{input-state.js → input-mode-state.js} +0 -0
|
@@ -1,33 +1,98 @@
|
|
|
1
1
|
import * as fc from 'fast-check';
|
|
2
2
|
import type { TreeNode } from './tree-node.types.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
/**
|
|
4
|
+
* The ID type used by the generated test trees.
|
|
5
|
+
*
|
|
6
|
+
* Short strings keep generated counterexamples readable.
|
|
7
|
+
*/
|
|
8
|
+
type TestNodeId = string;
|
|
9
|
+
/**
|
|
10
|
+
* Generates valid string IDs for test nodes. Note that the API just does
|
|
11
|
+
* comparisons on the IDs so we can safely restrict to any subset.
|
|
12
|
+
*/
|
|
13
|
+
export declare const testIdArb: fc.Arbitrary<string>;
|
|
14
|
+
/**
|
|
15
|
+
* Tree node whose IDs are strings, without additional domain properties.
|
|
16
|
+
*/
|
|
17
|
+
export type StringNode = TreeNode<TestNodeId, StringNode>;
|
|
18
|
+
type Path = TestNodeId[];
|
|
19
|
+
type NodeAtPath = {
|
|
20
|
+
path: Path;
|
|
8
21
|
node: StringNode;
|
|
9
22
|
};
|
|
10
|
-
type
|
|
23
|
+
type NodeWithReachableNodes = {
|
|
11
24
|
node: StringNode;
|
|
12
|
-
|
|
25
|
+
reachableNodes: NodeAtPath[];
|
|
13
26
|
};
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Generates a leaf node.
|
|
29
|
+
*/
|
|
30
|
+
export declare const generatedLeafNodeArb: fc.Arbitrary<NodeWithReachableNodes>;
|
|
31
|
+
/**
|
|
32
|
+
* Generates a closed branch.
|
|
33
|
+
*/
|
|
34
|
+
export declare const generatedClosedBranchNodeArb: fc.Arbitrary<NodeWithReachableNodes>;
|
|
35
|
+
/**
|
|
36
|
+
* Generates a leaf, an open branch, or a closed branch.
|
|
37
|
+
*
|
|
38
|
+
* Open branches have unique child IDs. Root IDs are made unique separately
|
|
39
|
+
* when generating a forest.
|
|
40
|
+
*
|
|
41
|
+
* The weighted choice gives recursive open branches a geometric dropoff.
|
|
42
|
+
*/
|
|
43
|
+
export declare const generatedNodeArb: fc.Arbitrary<NodeWithReachableNodes>;
|
|
44
|
+
/**
|
|
45
|
+
* Generates an array of generated nodes with unique sibling IDs.
|
|
46
|
+
*
|
|
47
|
+
* The result is suitable for use as the loaded children of an open branch.
|
|
48
|
+
*/
|
|
49
|
+
export declare const generatedNodesArrayArb: fc.Arbitrary<NodeWithReachableNodes[]>;
|
|
50
|
+
/**
|
|
51
|
+
* An array of root-level nodes and all paths that can be resolved through
|
|
52
|
+
* those nodes.
|
|
53
|
+
*
|
|
54
|
+
* Paths through closed branches stop at the closed branch because its
|
|
55
|
+
* descendants are not loaded.
|
|
56
|
+
*/
|
|
57
|
+
export declare const nodeAndPathsArb: fc.Arbitrary<{
|
|
58
|
+
root: any;
|
|
59
|
+
pathEntries: NodeAtPath[];
|
|
19
60
|
}>;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Generates root-level nodes and a path to one of their reachable nodes.
|
|
63
|
+
*
|
|
64
|
+
* Every generated path is valid for `getAtPath`; unreachable paths are not
|
|
65
|
+
* included.
|
|
66
|
+
*/
|
|
67
|
+
export declare const nodeAndPathAndExpectedArb: fc.Arbitrary<{
|
|
68
|
+
root: any;
|
|
69
|
+
path: Path;
|
|
23
70
|
expected: any;
|
|
24
71
|
}>;
|
|
72
|
+
/**
|
|
73
|
+
* Generates root-level nodes and a path to one of their branch nodes.
|
|
74
|
+
*
|
|
75
|
+
* Both open and closed branches are included. A path to a closed branch is
|
|
76
|
+
* valid, but a path through a closed branch cannot reach any descendants.
|
|
77
|
+
*/
|
|
25
78
|
export declare const nodesArrayAndBranchPathArb: fc.Arbitrary<{
|
|
26
|
-
|
|
27
|
-
path:
|
|
79
|
+
root: any;
|
|
80
|
+
path: Path;
|
|
28
81
|
expected: any;
|
|
29
82
|
}>;
|
|
83
|
+
/**
|
|
84
|
+
* Generates a new array of loaded child nodes.
|
|
85
|
+
*
|
|
86
|
+
* The generated nodes have unique IDs, making the result suitable for use as
|
|
87
|
+
* the children of an open branch.
|
|
88
|
+
*/
|
|
30
89
|
export declare const newBranchesArb: fc.Arbitrary<any[]>;
|
|
90
|
+
/**
|
|
91
|
+
* Generates either a new array of loaded child nodes or `null`.
|
|
92
|
+
*
|
|
93
|
+
* An array represents an open branch, including an open branch with no
|
|
94
|
+
* children. `null` represents a closed branch.
|
|
95
|
+
*/
|
|
31
96
|
export declare const newBranchesOrNullArb: fc.Arbitrary<any[] | null>;
|
|
32
97
|
export {};
|
|
33
98
|
//# sourceMappingURL=tree-node.arb.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tree-node.arb.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer/tree-node/tree-node.arb.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AAEjC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAErD,KAAK,
|
|
1
|
+
{"version":3,"file":"tree-node.arb.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer/tree-node/tree-node.arb.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AAEjC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAErD;;;;GAIG;AACH,KAAK,UAAU,GAAG,MAAM,CAAC;AASzB;;;GAGG;AACH,eAAO,MAAM,SAAS,sBAIpB,CAAC;AAEH;;GAEG;AAEH,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;AAC1D,KAAK,IAAI,GAAG,UAAU,EAAE,CAAC;AAEzB,KAAK,UAAU,GAAG;IAChB,IAAI,EAAE,IAAI,CAAC;IACX,IAAI,EAAE,UAAU,CAAC;CAClB,CAAC;AAEF,KAAK,sBAAsB,GAAG;IAC5B,IAAI,EAAE,UAAU,CAAC;IACjB,cAAc,EAAE,UAAU,EAAE,CAAC;CAC9B,CAAC;AAqEF;;GAEG;AACH,eAAO,MAAM,oBAAoB,sCAEhC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,4BAA4B,sCAExC,CAAC;AAaF;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,EAAE,EAAE,CAAC,SAAS,CAAC,sBAAsB,CA0C5D,CAAC;AAEP;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,wCAIjC,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,eAAe;;;EAK3B,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB;;;;EAQnC,CAAC;AAEJ;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B;;;;EAYpC,CAAC;AAEJ;;;;;GAKG;AACH,eAAO,MAAM,cAAc,qBAE1B,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,4BAGhC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree-node.arb.debug.test.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer/tree-node/tree-node.arb.debug.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { describe, it } from 'vitest';
|
|
2
|
+
import * as fc from 'fast-check';
|
|
3
|
+
import { generatedNodeArb, generatedNodesArrayArb, } from './tree-node.arb.js';
|
|
4
|
+
/**
|
|
5
|
+
* Used for looking at the tree distributions generated by the arbs.
|
|
6
|
+
*/
|
|
7
|
+
describe('print generated tree node arbitraries', () => {
|
|
8
|
+
it.only('generated nodes', () => {
|
|
9
|
+
const samples = fc.sample(generatedNodeArb, {
|
|
10
|
+
numRuns: 10,
|
|
11
|
+
seed: 12345,
|
|
12
|
+
});
|
|
13
|
+
for (const [index, generated] of samples.entries()) {
|
|
14
|
+
console.log(`\n--- node ${index + 1} ---`);
|
|
15
|
+
console.dir(generated.node, {
|
|
16
|
+
depth: Infinity,
|
|
17
|
+
colors: false,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
it.only('generated root-level node arrays', () => {
|
|
22
|
+
const samples = fc.sample(generatedNodesArrayArb, {
|
|
23
|
+
numRuns: 5,
|
|
24
|
+
seed: 12345,
|
|
25
|
+
});
|
|
26
|
+
for (const [index, generatedNodes] of samples.entries()) {
|
|
27
|
+
console.log(`\n--- root array ${index + 1} ---`);
|
|
28
|
+
console.dir(generatedNodes.map(({ node }) => node), {
|
|
29
|
+
depth: Infinity,
|
|
30
|
+
colors: false,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -1,80 +1,188 @@
|
|
|
1
1
|
import * as fc from 'fast-check';
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Characters used by readable generated node IDs.
|
|
4
|
+
*/
|
|
5
|
+
const smallLetterCharacters = Array.from('abcdefghijklmnopqrstuvwxyz');
|
|
6
|
+
/**
|
|
7
|
+
* Generates valid string IDs for test nodes. Note that the API just does
|
|
8
|
+
* comparisons on the IDs so we can safely restrict to any subset.
|
|
9
|
+
*/
|
|
10
|
+
export const testIdArb = fc.string({
|
|
11
|
+
unit: fc.constantFrom(...smallLetterCharacters),
|
|
3
12
|
minLength: 1,
|
|
4
13
|
maxLength: 8,
|
|
5
14
|
});
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Generates the metadata for a leaf node.
|
|
17
|
+
*
|
|
18
|
+
* @param id - The ID assigned to the leaf.
|
|
19
|
+
* @returns The leaf and the path that resolves to it.
|
|
20
|
+
*/
|
|
21
|
+
const generatedLeafNodeWithReachableNodes = (id) => {
|
|
22
|
+
const node = { id };
|
|
23
|
+
return {
|
|
24
|
+
node,
|
|
25
|
+
reachableNodes: [{ path: [], node }],
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Generates the metadata for a closed branch.
|
|
30
|
+
*
|
|
31
|
+
* A closed branch contributes no descendant paths because its children are not
|
|
32
|
+
* loaded.
|
|
33
|
+
*
|
|
34
|
+
* @param id - The ID assigned to the branch.
|
|
35
|
+
* @returns The closed branch and the path that resolves to it.
|
|
36
|
+
*/
|
|
37
|
+
const generatedClosedBranchWithReachableNodes = (id) => {
|
|
38
|
+
const node = {
|
|
39
|
+
id,
|
|
40
|
+
children: null,
|
|
41
|
+
};
|
|
42
|
+
return {
|
|
43
|
+
node,
|
|
44
|
+
reachableNodes: [{ path: [], node }],
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Generates the metadata for an open branch and its descendants.
|
|
49
|
+
*
|
|
50
|
+
* @param id - The ID assigned to the branch.
|
|
51
|
+
* @param children - The generated children of the branch.
|
|
52
|
+
* @returns The open branch and paths to the branch and all its descendants.
|
|
53
|
+
*/
|
|
54
|
+
const generatedOpenBranchWithReachableNodes = (id, children) => {
|
|
55
|
+
const node = {
|
|
56
|
+
id,
|
|
57
|
+
children: children.map(({ node }) => node),
|
|
58
|
+
};
|
|
59
|
+
const descendantNodes = children.flatMap(({ node: child, reachableNodes }) => reachableNodes.map(({ path, node }) => ({
|
|
60
|
+
path: [child.id, ...path],
|
|
61
|
+
node,
|
|
62
|
+
})));
|
|
63
|
+
return {
|
|
64
|
+
node,
|
|
65
|
+
reachableNodes: [{ path: [], node }, ...descendantNodes],
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Generates a leaf node.
|
|
70
|
+
*/
|
|
71
|
+
export const generatedLeafNodeArb = testIdArb.map(generatedLeafNodeWithReachableNodes);
|
|
72
|
+
/**
|
|
73
|
+
* Generates a closed branch.
|
|
74
|
+
*/
|
|
75
|
+
export const generatedClosedBranchNodeArb = testIdArb.map(generatedClosedBranchWithReachableNodes);
|
|
76
|
+
/**
|
|
77
|
+
* Generates a leaf or a closed branch.
|
|
78
|
+
*
|
|
79
|
+
* These node types terminate a generated path because they have no reachable
|
|
80
|
+
* descendants.
|
|
81
|
+
*/
|
|
82
|
+
const generatedTerminalNodeArb = fc.oneof(generatedLeafNodeArb, generatedClosedBranchNodeArb);
|
|
83
|
+
/**
|
|
84
|
+
* Generates a leaf, an open branch, or a closed branch.
|
|
85
|
+
*
|
|
86
|
+
* Open branches have unique child IDs. Root IDs are made unique separately
|
|
87
|
+
* when generating a forest.
|
|
88
|
+
*
|
|
89
|
+
* The weighted choice gives recursive open branches a geometric dropoff.
|
|
90
|
+
*/
|
|
91
|
+
export const generatedNodeArb = fc.letrec((tie) => {
|
|
92
|
+
const generatedChildNodeArb = tie('node');
|
|
93
|
+
/**
|
|
94
|
+
* Generates the loaded children of an open branch.
|
|
95
|
+
*
|
|
96
|
+
* Sibling IDs must be unique so that child lookup is unambiguous. The
|
|
97
|
+
* maximum length also limits the branching factor of recursive examples.
|
|
98
|
+
*/
|
|
99
|
+
const generatedOpenChildrenArb = fc.uniqueArray(generatedChildNodeArb, {
|
|
20
100
|
minLength: 0,
|
|
101
|
+
maxLength: 4,
|
|
21
102
|
size: 'medium',
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
:
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
],
|
|
49
|
-
};
|
|
50
|
-
})),
|
|
51
|
-
})).node;
|
|
103
|
+
selector: ({ node }) => node.id,
|
|
104
|
+
});
|
|
105
|
+
/**
|
|
106
|
+
* Generates an open branch and its reachable descendants.
|
|
107
|
+
*/
|
|
108
|
+
const generatedOpenBranchNodeArb = fc
|
|
109
|
+
.tuple(testIdArb, generatedOpenChildrenArb)
|
|
110
|
+
.map(([id, children]) => generatedOpenBranchWithReachableNodes(id, children));
|
|
111
|
+
return {
|
|
112
|
+
node: fc
|
|
113
|
+
.integer({
|
|
114
|
+
min: 0,
|
|
115
|
+
max: 9999,
|
|
116
|
+
})
|
|
117
|
+
.chain((choice) =>
|
|
118
|
+
// 5046 works for getAtPath tests, but too heavy for modifyAtPath tests
|
|
119
|
+
choice >= 5050
|
|
120
|
+
? generatedOpenBranchNodeArb
|
|
121
|
+
: generatedTerminalNodeArb),
|
|
122
|
+
};
|
|
123
|
+
}).node;
|
|
124
|
+
/**
|
|
125
|
+
* Generates an array of generated nodes with unique sibling IDs.
|
|
126
|
+
*
|
|
127
|
+
* The result is suitable for use as the loaded children of an open branch.
|
|
128
|
+
*/
|
|
52
129
|
export const generatedNodesArrayArb = fc.uniqueArray(generatedNodeArb, {
|
|
53
130
|
minLength: 0,
|
|
54
131
|
size: 'small',
|
|
55
132
|
selector: (generated) => generated.node.id,
|
|
56
133
|
});
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
134
|
+
/**
|
|
135
|
+
* An array of root-level nodes and all paths that can be resolved through
|
|
136
|
+
* those nodes.
|
|
137
|
+
*
|
|
138
|
+
* Paths through closed branches stop at the closed branch because its
|
|
139
|
+
* descendants are not loaded.
|
|
140
|
+
*/
|
|
141
|
+
export const nodeAndPathsArb = generatedNodeArb.map(({ node: root, reachableNodes }) => ({
|
|
142
|
+
root,
|
|
143
|
+
pathEntries: reachableNodes,
|
|
60
144
|
}));
|
|
61
|
-
|
|
145
|
+
/**
|
|
146
|
+
* Generates root-level nodes and a path to one of their reachable nodes.
|
|
147
|
+
*
|
|
148
|
+
* Every generated path is valid for `getAtPath`; unreachable paths are not
|
|
149
|
+
* included.
|
|
150
|
+
*/
|
|
151
|
+
export const nodeAndPathAndExpectedArb = nodeAndPathsArb
|
|
62
152
|
.filter(({ pathEntries }) => pathEntries.length > 0)
|
|
63
|
-
.chain(({
|
|
64
|
-
|
|
153
|
+
.chain(({ root, pathEntries }) => fc.constantFrom(...pathEntries).map(({ path, node }) => ({
|
|
154
|
+
root,
|
|
65
155
|
path,
|
|
66
156
|
expected: node,
|
|
67
157
|
})));
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
158
|
+
/**
|
|
159
|
+
* Generates root-level nodes and a path to one of their branch nodes.
|
|
160
|
+
*
|
|
161
|
+
* Both open and closed branches are included. A path to a closed branch is
|
|
162
|
+
* valid, but a path through a closed branch cannot reach any descendants.
|
|
163
|
+
*/
|
|
164
|
+
export const nodesArrayAndBranchPathArb = nodeAndPathsArb
|
|
165
|
+
.map(({ root, pathEntries }) => ({
|
|
166
|
+
root,
|
|
71
167
|
branchPathEntries: pathEntries.filter(({ node }) => 'children' in node),
|
|
72
168
|
}))
|
|
73
169
|
.filter(({ branchPathEntries }) => branchPathEntries.length > 0)
|
|
74
|
-
.chain(({
|
|
75
|
-
|
|
170
|
+
.chain(({ root, branchPathEntries }) => fc.constantFrom(...branchPathEntries).map(({ path, node }) => ({
|
|
171
|
+
root,
|
|
76
172
|
path,
|
|
77
173
|
expected: node,
|
|
78
174
|
})));
|
|
175
|
+
/**
|
|
176
|
+
* Generates a new array of loaded child nodes.
|
|
177
|
+
*
|
|
178
|
+
* The generated nodes have unique IDs, making the result suitable for use as
|
|
179
|
+
* the children of an open branch.
|
|
180
|
+
*/
|
|
79
181
|
export const newBranchesArb = generatedNodesArrayArb.map((generatedNodes) => generatedNodes.map(({ node }) => node));
|
|
182
|
+
/**
|
|
183
|
+
* Generates either a new array of loaded child nodes or `null`.
|
|
184
|
+
*
|
|
185
|
+
* An array represents an open branch, including an open branch with no
|
|
186
|
+
* children. `null` represents a closed branch.
|
|
187
|
+
*/
|
|
80
188
|
export const newBranchesOrNullArb = fc.oneof(newBranchesArb, fc.constant(null));
|
|
@@ -10,5 +10,5 @@ import type { SerializableKey, TreeNode, TreeNodeApi } from './tree-node.types.j
|
|
|
10
10
|
* Node IDs are compared using JavaScript value equality. Sibling IDs
|
|
11
11
|
* should be unique.
|
|
12
12
|
*/
|
|
13
|
-
export declare function createTreeNodeApi<Id extends SerializableKey,
|
|
13
|
+
export declare function createTreeNodeApi<Id extends SerializableKey, Node extends TreeNode<Id, Node>>(): TreeNodeApi<Id, Node>;
|
|
14
14
|
//# sourceMappingURL=tree-node.impl.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tree-node.impl.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer/tree-node/tree-node.impl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAKV,eAAe,EACf,QAAQ,EACR,WAAW,EACZ,MAAM,sBAAsB,CAAC;AAE9B;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAC/B,EAAE,SAAS,eAAe,EAC1B,
|
|
1
|
+
{"version":3,"file":"tree-node.impl.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer/tree-node/tree-node.impl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAKV,eAAe,EACf,QAAQ,EACR,WAAW,EACZ,MAAM,sBAAsB,CAAC;AAE9B;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAC/B,EAAE,SAAS,eAAe,EAC1B,IAAI,SAAS,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,KAC5B,WAAW,CAAC,EAAE,EAAE,IAAI,CAAC,CAqGzB"}
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
*/
|
|
12
12
|
export function createTreeNodeApi() {
|
|
13
13
|
const treeNodeApi = {
|
|
14
|
-
// Type Narrowers:
|
|
15
14
|
isLeaf(node) {
|
|
16
15
|
return !('children' in node);
|
|
17
16
|
},
|
|
@@ -30,60 +29,54 @@ export function createTreeNodeApi() {
|
|
|
30
29
|
getChildById(node, id) {
|
|
31
30
|
return node.children.find((child) => child.id === id);
|
|
32
31
|
},
|
|
33
|
-
getAtPath(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
let node = entries.find((candidate) => candidate.id === path[0]);
|
|
39
|
-
for (let index = 1; node !== undefined && index < path.length; index += 1) {
|
|
40
|
-
if (treeNodeApi.isOpenBranch(node)) {
|
|
41
|
-
node = treeNodeApi.getChildById(node, path[index]);
|
|
32
|
+
getAtPath(root, path, selector) {
|
|
33
|
+
let node = root;
|
|
34
|
+
for (const id of path) {
|
|
35
|
+
if (!treeNodeApi.isBranch(node) || !treeNodeApi.isOpenBranch(node)) {
|
|
36
|
+
return undefined;
|
|
42
37
|
}
|
|
43
|
-
|
|
44
|
-
|
|
38
|
+
const child = treeNodeApi.getChildById(node, id);
|
|
39
|
+
if (child === undefined) {
|
|
40
|
+
return undefined;
|
|
45
41
|
}
|
|
42
|
+
node = child;
|
|
46
43
|
}
|
|
47
|
-
return
|
|
44
|
+
return selector(node);
|
|
48
45
|
},
|
|
49
|
-
modifyAtPath(
|
|
46
|
+
modifyAtPath(root, path, modifier) {
|
|
50
47
|
if (path.length === 0) {
|
|
51
|
-
return
|
|
48
|
+
return modifier(root);
|
|
52
49
|
}
|
|
53
|
-
|
|
54
|
-
const updatedEntries = [...entries];
|
|
55
|
-
let currentEntries = updatedEntries;
|
|
56
|
-
for (let index = 0; index < path.length; index += 1) {
|
|
57
|
-
const id = path[index];
|
|
58
|
-
const childIndex = currentEntries.findIndex((candidate) => candidate.id === id);
|
|
59
|
-
if (childIndex === -1) {
|
|
60
|
-
return undefined;
|
|
61
|
-
}
|
|
62
|
-
const child = currentEntries[childIndex];
|
|
63
|
-
if (index === path.length - 1) {
|
|
64
|
-
const replacement = modifier(child);
|
|
65
|
-
if (replacement === undefined) {
|
|
66
|
-
return undefined;
|
|
67
|
-
}
|
|
68
|
-
currentEntries[childIndex] = replacement;
|
|
69
|
-
return updatedEntries;
|
|
70
|
-
}
|
|
71
|
-
if (!treeNodeApi.isBranch(child) || !treeNodeApi.isOpenBranch(child)) {
|
|
72
|
-
// The path cannot continue through a leaf or closed branch.
|
|
73
|
-
return undefined;
|
|
74
|
-
}
|
|
75
|
-
// Clone the child array before modifying it.
|
|
76
|
-
const updatedChildren = [...treeNodeApi.getChildren(child)];
|
|
77
|
-
// Clone the ancestor node and attach the cloned child array.
|
|
78
|
-
currentEntries[childIndex] = {
|
|
79
|
-
...child,
|
|
80
|
-
children: updatedChildren,
|
|
81
|
-
};
|
|
82
|
-
// Continue traversal through the cloned children.
|
|
83
|
-
currentEntries = updatedChildren;
|
|
84
|
-
}
|
|
85
|
-
return undefined;
|
|
50
|
+
return modifyChildAtPath(root, path, modifier);
|
|
86
51
|
},
|
|
87
52
|
};
|
|
53
|
+
function modifyChildAtPath(node, path, modifier) {
|
|
54
|
+
const childId = path[0];
|
|
55
|
+
if (childId === undefined ||
|
|
56
|
+
!treeNodeApi.isBranch(node) ||
|
|
57
|
+
!treeNodeApi.isOpenBranch(node)) {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
const childIndex = node.children.findIndex((child) => child.id === childId);
|
|
61
|
+
if (childIndex === -1) {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
const child = node.children[childIndex];
|
|
65
|
+
if (child === undefined) {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
const updatedChild = path.length === 1
|
|
69
|
+
? modifier(child)
|
|
70
|
+
: modifyChildAtPath(child, path.slice(1), modifier);
|
|
71
|
+
if (updatedChild === undefined) {
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
const children = [...node.children];
|
|
75
|
+
children[childIndex] = updatedChild;
|
|
76
|
+
return {
|
|
77
|
+
...node,
|
|
78
|
+
children,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
88
81
|
return treeNodeApi;
|
|
89
82
|
}
|
|
@@ -16,7 +16,7 @@ export declare const serializableKeySchema: z.ZodUnion<readonly [z.ZodString, z.
|
|
|
16
16
|
*
|
|
17
17
|
* The concrete application can extend these schemas with additional fields.
|
|
18
18
|
*/
|
|
19
|
-
export declare function createTreeNodeSchemas<Id extends SerializableKey,
|
|
19
|
+
export declare function createTreeNodeSchemas<Id extends SerializableKey, Node extends TreeNode<Id, Node>>(idSchema: z.ZodType<Id>, nodeSchema: z.ZodType<Node>): {
|
|
20
20
|
leafSchema: z.ZodObject<{
|
|
21
21
|
id: z.ZodType<Id, unknown, z.core.$ZodTypeInternals<Id, unknown>>;
|
|
22
22
|
}, z.core.$strip>;
|
|
@@ -26,7 +26,7 @@ export declare function createTreeNodeSchemas<Id extends SerializableKey, ChildN
|
|
|
26
26
|
}, z.core.$strip>;
|
|
27
27
|
openBranchSchema: z.ZodObject<{
|
|
28
28
|
id: z.ZodType<Id, unknown, z.core.$ZodTypeInternals<Id, unknown>>;
|
|
29
|
-
children: z.ZodArray<z.ZodType<
|
|
29
|
+
children: z.ZodArray<z.ZodType<Node, unknown, z.core.$ZodTypeInternals<Node, unknown>>>;
|
|
30
30
|
}, z.core.$strip>;
|
|
31
31
|
};
|
|
32
32
|
//# sourceMappingURL=tree-node.schemas.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tree-node.schemas.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer/tree-node/tree-node.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAEjE;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB,iDAA6C,CAAC;AAEhF;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,EAAE,SAAS,eAAe,EAC1B,
|
|
1
|
+
{"version":3,"file":"tree-node.schemas.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer/tree-node/tree-node.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAEjE;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB,iDAA6C,CAAC;AAEhF;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,EAAE,SAAS,eAAe,EAC1B,IAAI,SAAS,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,EAC/B,QAAQ,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC;;;;;;;;;;;;EAoBrD"}
|
|
@@ -15,7 +15,7 @@ export const serializableKeySchema = z.union([z.string(), z.number().finite()]);
|
|
|
15
15
|
*
|
|
16
16
|
* The concrete application can extend these schemas with additional fields.
|
|
17
17
|
*/
|
|
18
|
-
export function createTreeNodeSchemas(idSchema,
|
|
18
|
+
export function createTreeNodeSchemas(idSchema, nodeSchema) {
|
|
19
19
|
const leafSchema = z.object({
|
|
20
20
|
id: idSchema,
|
|
21
21
|
});
|
|
@@ -25,7 +25,7 @@ export function createTreeNodeSchemas(idSchema, childSchema) {
|
|
|
25
25
|
});
|
|
26
26
|
const openBranchSchema = z.object({
|
|
27
27
|
id: idSchema,
|
|
28
|
-
children: z.array(
|
|
28
|
+
children: z.array(nodeSchema),
|
|
29
29
|
});
|
|
30
30
|
return {
|
|
31
31
|
leafSchema,
|