@player-ui/player 0.8.0--canary.307.9621 → 0.8.0-next.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/Player.native.js +11630 -0
- package/dist/Player.native.js.map +1 -0
- package/dist/cjs/index.cjs +5626 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/{index.esm.js → index.legacy-esm.js} +2044 -1667
- package/dist/{index.cjs.js → index.mjs} +2052 -1761
- package/dist/index.mjs.map +1 -0
- package/package.json +29 -63
- package/src/__tests__/data.test.ts +498 -0
- package/src/__tests__/flow.test.ts +312 -0
- package/src/__tests__/helpers/action-exp.plugin.ts +22 -0
- package/src/__tests__/helpers/actions.flow.ts +67 -0
- package/src/__tests__/helpers/binding.plugin.ts +125 -0
- package/src/__tests__/helpers/expression.plugin.ts +88 -0
- package/src/__tests__/helpers/transform-plugin.ts +19 -0
- package/src/__tests__/helpers/validation.flow.ts +56 -0
- package/src/__tests__/player.test.ts +597 -0
- package/src/__tests__/string-resolver.test.ts +186 -0
- package/src/__tests__/validation.test.ts +3555 -0
- package/src/__tests__/view.test.ts +715 -0
- package/src/binding/__tests__/binding.test.ts +113 -0
- package/src/binding/__tests__/index.test.ts +208 -0
- package/src/binding/__tests__/resolver.test.ts +83 -0
- package/src/binding/binding.ts +6 -6
- package/src/binding/index.ts +34 -34
- package/src/binding/resolver.ts +19 -19
- package/src/binding/utils.ts +7 -7
- package/src/binding-grammar/__tests__/parser.test.ts +64 -0
- package/src/binding-grammar/__tests__/test-utils/ast-cases.ts +198 -0
- package/src/binding-grammar/__tests__/test-utils/perf-test.ts +66 -0
- package/src/binding-grammar/ast.ts +11 -11
- package/src/binding-grammar/custom/index.ts +19 -22
- package/src/binding-grammar/ebnf/index.ts +20 -21
- package/src/binding-grammar/ebnf/types.ts +13 -13
- package/src/binding-grammar/index.ts +4 -4
- package/src/binding-grammar/parsimmon/index.ts +14 -14
- package/src/controllers/constants/__tests__/index.test.ts +106 -0
- package/src/controllers/constants/index.ts +3 -3
- package/src/controllers/constants/utils.ts +4 -4
- package/src/controllers/data/controller.ts +22 -22
- package/src/controllers/data/index.ts +1 -1
- package/src/controllers/data/utils.ts +7 -7
- package/src/controllers/flow/__tests__/controller.test.ts +195 -0
- package/src/controllers/flow/__tests__/flow.test.ts +381 -0
- package/src/controllers/flow/controller.ts +13 -13
- package/src/controllers/flow/flow.ts +23 -23
- package/src/controllers/flow/index.ts +2 -2
- package/src/controllers/index.ts +5 -5
- package/src/controllers/validation/binding-tracker.ts +71 -59
- package/src/controllers/validation/controller.ts +104 -104
- package/src/controllers/validation/index.ts +2 -2
- package/src/controllers/view/asset-transform.ts +20 -20
- package/src/controllers/view/controller.ts +27 -27
- package/src/controllers/view/index.ts +4 -4
- package/src/controllers/view/store.ts +3 -3
- package/src/controllers/view/types.ts +7 -7
- package/src/data/__tests__/__snapshots__/dependency-tracker.test.ts.snap +64 -0
- package/src/data/__tests__/dependency-tracker.test.ts +146 -0
- package/src/data/__tests__/local-model.test.ts +46 -0
- package/src/data/__tests__/model.test.ts +78 -0
- package/src/data/dependency-tracker.ts +16 -16
- package/src/data/index.ts +4 -4
- package/src/data/local-model.ts +6 -6
- package/src/data/model.ts +17 -17
- package/src/data/noop-model.ts +1 -1
- package/src/expressions/__tests__/__snapshots__/parser.test.ts.snap +854 -0
- package/src/expressions/__tests__/evaluator-functions.test.ts +47 -0
- package/src/expressions/__tests__/evaluator.test.ts +410 -0
- package/src/expressions/__tests__/parser.test.ts +115 -0
- package/src/expressions/__tests__/utils.test.ts +44 -0
- package/src/expressions/evaluator-functions.ts +6 -6
- package/src/expressions/evaluator.ts +71 -67
- package/src/expressions/index.ts +4 -4
- package/src/expressions/parser.ts +102 -105
- package/src/expressions/types.ts +29 -21
- package/src/expressions/utils.ts +32 -21
- package/src/index.ts +13 -13
- package/src/logger/__tests__/consoleLogger.test.ts +46 -0
- package/src/logger/__tests__/noopLogger.test.ts +13 -0
- package/src/logger/__tests__/proxyLogger.test.ts +31 -0
- package/src/logger/__tests__/tapableLogger.test.ts +41 -0
- package/src/logger/consoleLogger.ts +9 -9
- package/src/logger/index.ts +5 -5
- package/src/logger/noopLogger.ts +1 -1
- package/src/logger/proxyLogger.ts +6 -6
- package/src/logger/tapableLogger.ts +7 -7
- package/src/logger/types.ts +2 -2
- package/src/player.ts +60 -58
- package/src/plugins/default-exp-plugin.ts +10 -10
- package/src/plugins/default-view-plugin.ts +29 -0
- package/src/plugins/flow-exp-plugin.ts +6 -6
- package/src/schema/__tests__/schema.test.ts +243 -0
- package/src/schema/index.ts +2 -2
- package/src/schema/schema.ts +24 -24
- package/src/schema/types.ts +4 -4
- package/src/string-resolver/__tests__/index.test.ts +361 -0
- package/src/string-resolver/index.ts +17 -17
- package/src/types.ts +17 -17
- package/src/utils/__tests__/replaceParams.test.ts +33 -0
- package/src/utils/index.ts +1 -1
- package/src/utils/replaceParams.ts +1 -1
- package/src/validator/__tests__/binding-map-splice.test.ts +53 -0
- package/src/validator/__tests__/validation-middleware.test.ts +127 -0
- package/src/validator/binding-map-splice.ts +5 -5
- package/src/validator/index.ts +4 -4
- package/src/validator/registry.ts +1 -1
- package/src/validator/types.ts +13 -13
- package/src/validator/validation-middleware.ts +15 -15
- package/src/view/__tests__/view.immutable.test.ts +269 -0
- package/src/view/__tests__/view.test.ts +959 -0
- package/src/view/builder/index.test.ts +69 -0
- package/src/view/builder/index.ts +3 -3
- package/src/view/index.ts +5 -5
- package/src/view/parser/__tests__/__snapshots__/parser.test.ts.snap +394 -0
- package/src/view/parser/__tests__/parser.test.ts +264 -0
- package/src/view/parser/index.ts +43 -33
- package/src/view/parser/types.ts +11 -11
- package/src/view/parser/utils.ts +5 -5
- package/src/view/plugins/__tests__/__snapshots__/template.test.ts.snap +278 -0
- package/src/view/plugins/__tests__/applicability.test.ts +265 -0
- package/src/view/plugins/__tests__/string.test.ts +122 -0
- package/src/view/plugins/__tests__/template.test.ts +724 -0
- package/src/view/plugins/applicability.ts +19 -19
- package/src/view/plugins/index.ts +4 -5
- package/src/view/plugins/options.ts +1 -1
- package/src/view/plugins/string-resolver.ts +22 -22
- package/src/view/plugins/switch.ts +22 -23
- package/src/view/plugins/template-plugin.ts +26 -27
- package/src/view/resolver/__tests__/dependencies.test.ts +321 -0
- package/src/view/resolver/__tests__/edgecases.test.ts +626 -0
- package/src/view/resolver/index.ts +42 -42
- package/src/view/resolver/types.ts +21 -20
- package/src/view/resolver/utils.ts +9 -9
- package/src/view/view.ts +32 -22
- package/types/binding/binding.d.ts +50 -0
- package/types/binding/index.d.ts +29 -0
- package/types/binding/resolver.d.ts +26 -0
- package/types/binding/utils.d.ts +12 -0
- package/types/binding-grammar/ast.d.ts +67 -0
- package/types/binding-grammar/custom/index.d.ts +4 -0
- package/types/binding-grammar/ebnf/index.d.ts +4 -0
- package/types/binding-grammar/ebnf/types.d.ts +75 -0
- package/types/binding-grammar/index.d.ts +5 -0
- package/types/binding-grammar/parsimmon/index.d.ts +4 -0
- package/types/controllers/constants/index.d.ts +45 -0
- package/types/controllers/constants/utils.d.ts +6 -0
- package/types/controllers/data/controller.d.ts +45 -0
- package/types/controllers/data/index.d.ts +2 -0
- package/types/controllers/data/utils.d.ts +14 -0
- package/types/controllers/flow/controller.d.ts +25 -0
- package/types/controllers/flow/flow.d.ts +50 -0
- package/types/controllers/flow/index.d.ts +3 -0
- package/types/controllers/index.d.ts +6 -0
- package/types/controllers/validation/binding-tracker.d.ts +32 -0
- package/types/controllers/validation/controller.d.ts +151 -0
- package/types/controllers/validation/index.d.ts +3 -0
- package/types/controllers/view/asset-transform.d.ts +19 -0
- package/types/controllers/view/controller.d.ts +37 -0
- package/types/controllers/view/index.d.ts +5 -0
- package/types/controllers/view/store.d.ts +20 -0
- package/types/controllers/view/types.d.ts +16 -0
- package/types/data/dependency-tracker.d.ts +49 -0
- package/types/data/index.d.ts +5 -0
- package/types/data/local-model.d.ts +16 -0
- package/types/data/model.d.ts +86 -0
- package/types/data/noop-model.d.ts +13 -0
- package/types/expressions/evaluator-functions.d.ts +15 -0
- package/types/expressions/evaluator.d.ts +52 -0
- package/types/expressions/index.d.ts +5 -0
- package/types/expressions/parser.d.ts +10 -0
- package/types/expressions/types.d.ts +144 -0
- package/types/expressions/utils.d.ts +12 -0
- package/types/index.d.ts +14 -0
- package/types/logger/consoleLogger.d.ts +17 -0
- package/types/logger/index.d.ts +6 -0
- package/types/logger/noopLogger.d.ts +10 -0
- package/types/logger/proxyLogger.d.ts +15 -0
- package/types/logger/tapableLogger.d.ts +23 -0
- package/types/logger/types.d.ts +6 -0
- package/types/player.d.ts +101 -0
- package/types/plugins/default-exp-plugin.d.ts +9 -0
- package/types/plugins/default-view-plugin.d.ts +9 -0
- package/types/plugins/flow-exp-plugin.d.ts +11 -0
- package/types/schema/index.d.ts +3 -0
- package/types/schema/schema.d.ts +36 -0
- package/types/schema/types.d.ts +38 -0
- package/types/string-resolver/index.d.ts +30 -0
- package/types/types.d.ts +73 -0
- package/types/utils/index.d.ts +2 -0
- package/types/utils/replaceParams.d.ts +9 -0
- package/types/validator/binding-map-splice.d.ts +10 -0
- package/types/validator/index.d.ts +5 -0
- package/types/validator/registry.d.ts +11 -0
- package/types/validator/types.d.ts +53 -0
- package/types/validator/validation-middleware.d.ts +36 -0
- package/types/view/builder/index.d.ts +35 -0
- package/types/view/index.d.ts +6 -0
- package/types/view/parser/index.d.ts +52 -0
- package/types/view/parser/types.d.ts +109 -0
- package/types/view/parser/utils.d.ts +6 -0
- package/types/view/plugins/applicability.d.ts +10 -0
- package/types/view/plugins/index.d.ts +5 -0
- package/types/view/plugins/options.d.ts +4 -0
- package/types/view/plugins/string-resolver.d.ts +13 -0
- package/types/view/plugins/switch.d.ts +14 -0
- package/types/view/plugins/template-plugin.d.ts +33 -0
- package/types/view/resolver/index.d.ts +73 -0
- package/types/view/resolver/types.d.ts +129 -0
- package/types/view/resolver/utils.d.ts +11 -0
- package/types/view/view.d.ts +37 -0
- package/dist/index.d.ts +0 -1814
- package/dist/player.dev.js +0 -11472
- package/dist/player.prod.js +0 -2
- package/src/view/plugins/plugin.ts +0 -21
|
@@ -0,0 +1,626 @@
|
|
|
1
|
+
import { describe, it, expect, vitest } from "vitest";
|
|
2
|
+
import { replaceAt, set, omit } from "timm";
|
|
3
|
+
import { BindingParser } from "../../../binding";
|
|
4
|
+
import { ExpressionEvaluator } from "../../../expressions";
|
|
5
|
+
import { LocalModel, withParser } from "../../../data";
|
|
6
|
+
import { SchemaController } from "../../../schema";
|
|
7
|
+
import type { Logger } from "../../../logger";
|
|
8
|
+
import { TapableLogger } from "../../../logger";
|
|
9
|
+
import { Resolver } from "..";
|
|
10
|
+
import type { Node } from "../../parser";
|
|
11
|
+
import { NodeType, Parser } from "../../parser";
|
|
12
|
+
import { StringResolverPlugin } from "../../plugins";
|
|
13
|
+
|
|
14
|
+
describe("Dynamic AST Transforms", () => {
|
|
15
|
+
const content = {
|
|
16
|
+
id: "main-view",
|
|
17
|
+
type: "questionAnswer",
|
|
18
|
+
title: [
|
|
19
|
+
{
|
|
20
|
+
asset: {
|
|
21
|
+
id: "title",
|
|
22
|
+
type: "text",
|
|
23
|
+
value: "Cool Page",
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
primaryInfo: [
|
|
28
|
+
{
|
|
29
|
+
asset: {
|
|
30
|
+
id: "subtitle",
|
|
31
|
+
type: "text",
|
|
32
|
+
value: "{{year}}",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
it("Dynamically added Nodes are properly resolved/cached on rerender", () => {
|
|
39
|
+
const model = new LocalModel({
|
|
40
|
+
year: "2021",
|
|
41
|
+
});
|
|
42
|
+
const parser = new Parser();
|
|
43
|
+
const bindingParser = new BindingParser();
|
|
44
|
+
const inputBinding = bindingParser.parse("year");
|
|
45
|
+
const rootNode = parser.parseObject(content);
|
|
46
|
+
|
|
47
|
+
const resolver = new Resolver(rootNode!, {
|
|
48
|
+
model,
|
|
49
|
+
parseBinding: bindingParser.parse.bind(bindingParser),
|
|
50
|
+
parseNode: parser.parseObject.bind(parser),
|
|
51
|
+
evaluator: new ExpressionEvaluator({
|
|
52
|
+
model: withParser(model, bindingParser.parse),
|
|
53
|
+
}),
|
|
54
|
+
schema: new SchemaController(),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// basic transform to change the asset
|
|
58
|
+
resolver.hooks.beforeResolve.tap("test-plugin", (node) => {
|
|
59
|
+
if (
|
|
60
|
+
node?.type === NodeType.Asset ||
|
|
61
|
+
node?.type === NodeType.View ||
|
|
62
|
+
node?.type === NodeType.Value
|
|
63
|
+
) {
|
|
64
|
+
let newNode = node;
|
|
65
|
+
|
|
66
|
+
newNode.children?.forEach((child, i) => {
|
|
67
|
+
if (child.path.length === 1) {
|
|
68
|
+
// We have a child for this key
|
|
69
|
+
// Check if it's an array and shouldn't be
|
|
70
|
+
const { value: childNode } = child;
|
|
71
|
+
if (childNode.type === "multi-node") {
|
|
72
|
+
if (childNode.values.length === 1) {
|
|
73
|
+
// If there's only 1 node, no need for a collection, just up-level the asset that's there
|
|
74
|
+
const firstChild = childNode.values[0];
|
|
75
|
+
newNode = set(
|
|
76
|
+
newNode,
|
|
77
|
+
"children",
|
|
78
|
+
replaceAt(newNode.children ?? [], i, {
|
|
79
|
+
path: child.path,
|
|
80
|
+
value: {
|
|
81
|
+
...firstChild,
|
|
82
|
+
},
|
|
83
|
+
}),
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
if (newNode !== node) {
|
|
90
|
+
// We updated something, set the children of the newNode to have the correct parent
|
|
91
|
+
newNode.children?.forEach((child) => {
|
|
92
|
+
// Don't worry about mutating here any new children are ones we created above
|
|
93
|
+
// eslint-disable-next-line no-param-reassign
|
|
94
|
+
child.value.parent = newNode;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return newNode;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return node;
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
new StringResolverPlugin().applyResolver(resolver);
|
|
105
|
+
|
|
106
|
+
const firstUpdate = resolver.update();
|
|
107
|
+
expect(firstUpdate).toStrictEqual({
|
|
108
|
+
id: "main-view",
|
|
109
|
+
type: "questionAnswer",
|
|
110
|
+
title: {
|
|
111
|
+
asset: {
|
|
112
|
+
id: "title",
|
|
113
|
+
type: "text",
|
|
114
|
+
value: "Cool Page",
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
primaryInfo: {
|
|
118
|
+
asset: {
|
|
119
|
+
id: "subtitle",
|
|
120
|
+
type: "text",
|
|
121
|
+
value: "2021",
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
model.set([[inputBinding, "2022"]]);
|
|
127
|
+
const secondUpdate = resolver.update(new Set([inputBinding]));
|
|
128
|
+
expect(secondUpdate).toStrictEqual({
|
|
129
|
+
id: "main-view",
|
|
130
|
+
type: "questionAnswer",
|
|
131
|
+
title: {
|
|
132
|
+
asset: {
|
|
133
|
+
id: "title",
|
|
134
|
+
type: "text",
|
|
135
|
+
value: "Cool Page",
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
primaryInfo: {
|
|
139
|
+
asset: {
|
|
140
|
+
id: "subtitle",
|
|
141
|
+
type: "text",
|
|
142
|
+
value: "2022",
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("Nodes are properly cached on rerender", () => {
|
|
149
|
+
const model = new LocalModel({
|
|
150
|
+
year: "2021",
|
|
151
|
+
});
|
|
152
|
+
const parser = new Parser();
|
|
153
|
+
const bindingParser = new BindingParser();
|
|
154
|
+
const inputBinding = bindingParser.parse("year");
|
|
155
|
+
const rootNode = parser.parseObject(content);
|
|
156
|
+
|
|
157
|
+
const resolver = new Resolver(rootNode!, {
|
|
158
|
+
model,
|
|
159
|
+
parseBinding: bindingParser.parse.bind(bindingParser),
|
|
160
|
+
parseNode: parser.parseObject.bind(parser),
|
|
161
|
+
evaluator: new ExpressionEvaluator({
|
|
162
|
+
model: withParser(model, bindingParser.parse),
|
|
163
|
+
}),
|
|
164
|
+
schema: new SchemaController(),
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
resolver.update();
|
|
168
|
+
|
|
169
|
+
const resolveCache = resolver.getResolveCache();
|
|
170
|
+
|
|
171
|
+
resolver.update(new Set([inputBinding]));
|
|
172
|
+
|
|
173
|
+
const newResolveCache = resolver.getResolveCache();
|
|
174
|
+
|
|
175
|
+
expect(resolveCache.size).toBe(newResolveCache.size);
|
|
176
|
+
|
|
177
|
+
// The cached items between each re-render should stay the same
|
|
178
|
+
for (const [k, v] of resolveCache) {
|
|
179
|
+
const excludingUpdated = omit(v, "updated");
|
|
180
|
+
|
|
181
|
+
expect(newResolveCache.has(k)).toBe(true);
|
|
182
|
+
expect(newResolveCache.get(k)).toMatchObject(excludingUpdated);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("Cached node points to the correct parent node", () => {
|
|
187
|
+
const view = {
|
|
188
|
+
id: "main-view",
|
|
189
|
+
type: "questionAnswer",
|
|
190
|
+
title: [
|
|
191
|
+
{
|
|
192
|
+
asset: {
|
|
193
|
+
id: "title",
|
|
194
|
+
type: "text",
|
|
195
|
+
value: "Cool Page",
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
primaryInfo: [
|
|
200
|
+
{
|
|
201
|
+
asset: {
|
|
202
|
+
id: "input",
|
|
203
|
+
type: "input",
|
|
204
|
+
value: "{{year}}",
|
|
205
|
+
label: {
|
|
206
|
+
asset: {
|
|
207
|
+
id: "label",
|
|
208
|
+
type: "text",
|
|
209
|
+
value: "label",
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
],
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const model = new LocalModel({
|
|
218
|
+
year: "2021",
|
|
219
|
+
});
|
|
220
|
+
const parser = new Parser();
|
|
221
|
+
const bindingParser = new BindingParser();
|
|
222
|
+
const inputBinding = bindingParser.parse("year");
|
|
223
|
+
const rootNode = parser.parseObject(view);
|
|
224
|
+
|
|
225
|
+
const resolver = new Resolver(rootNode!, {
|
|
226
|
+
model,
|
|
227
|
+
parseBinding: bindingParser.parse.bind(bindingParser),
|
|
228
|
+
parseNode: parser.parseObject.bind(parser),
|
|
229
|
+
evaluator: new ExpressionEvaluator({
|
|
230
|
+
model: withParser(model, bindingParser.parse),
|
|
231
|
+
}),
|
|
232
|
+
schema: new SchemaController(),
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
let inputNode: Node.Node | undefined;
|
|
236
|
+
let labelNode: Node.Node | undefined;
|
|
237
|
+
|
|
238
|
+
resolver.hooks.beforeResolve.tap("test", (node, options) => {
|
|
239
|
+
if (node?.type === "asset" && node.value.id === "input") {
|
|
240
|
+
// Add to dependencies
|
|
241
|
+
options.data.model.get(inputBinding);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return node;
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
resolver.hooks.afterResolve.tap("test", (value, node) => {
|
|
248
|
+
if (node.type === "asset") {
|
|
249
|
+
const { id } = node.value;
|
|
250
|
+
|
|
251
|
+
if (id === "input") inputNode = node;
|
|
252
|
+
|
|
253
|
+
if (id === "label") labelNode = node;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return value;
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
resolver.update();
|
|
260
|
+
|
|
261
|
+
model.set([[inputBinding, "2022"]]);
|
|
262
|
+
|
|
263
|
+
resolver.update(new Set([inputBinding]));
|
|
264
|
+
|
|
265
|
+
// Check that label (which is cached) still points to the correct parent node.
|
|
266
|
+
expect(labelNode?.parent).toBe(inputNode ?? {});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it("Fixes parent references when beforeResolve taps make changes", () => {
|
|
270
|
+
const model = new LocalModel({
|
|
271
|
+
year: "2021",
|
|
272
|
+
});
|
|
273
|
+
const parser = new Parser();
|
|
274
|
+
const bindingParser = new BindingParser();
|
|
275
|
+
const rootNode = parser.parseObject(content);
|
|
276
|
+
|
|
277
|
+
const resolver = new Resolver(rootNode!, {
|
|
278
|
+
model,
|
|
279
|
+
parseBinding: bindingParser.parse.bind(bindingParser),
|
|
280
|
+
parseNode: parser.parseObject.bind(parser),
|
|
281
|
+
evaluator: new ExpressionEvaluator({
|
|
282
|
+
model: withParser(model, bindingParser.parse),
|
|
283
|
+
}),
|
|
284
|
+
schema: new SchemaController(),
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
let parent;
|
|
288
|
+
resolver.hooks.beforeResolve.tap("test", (node) => {
|
|
289
|
+
if (node?.type !== NodeType.Asset || node.value.id !== "subtitle") {
|
|
290
|
+
return node;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
parent = node.parent;
|
|
294
|
+
return {
|
|
295
|
+
...node,
|
|
296
|
+
parent: undefined,
|
|
297
|
+
};
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
let resolvedNode: Node.Node | undefined;
|
|
301
|
+
resolver.hooks.afterResolve.tap("test", (resolvedValue, node) => {
|
|
302
|
+
if (node?.type === NodeType.Asset && node.value.id === "subtitle") {
|
|
303
|
+
resolvedNode = node;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return resolvedValue;
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
resolver.update();
|
|
310
|
+
|
|
311
|
+
expect(parent).not.toBeUndefined();
|
|
312
|
+
expect(resolvedNode).not.toBeUndefined();
|
|
313
|
+
expect(resolvedNode?.parent).toBe(parent);
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
describe("Duplicate IDs", () => {
|
|
318
|
+
it("Throws an error if two assets have the same id", () => {
|
|
319
|
+
const content = {
|
|
320
|
+
id: "action",
|
|
321
|
+
type: "collection",
|
|
322
|
+
values: [
|
|
323
|
+
{
|
|
324
|
+
asset: {
|
|
325
|
+
id: "action-1",
|
|
326
|
+
type: "action",
|
|
327
|
+
label: {
|
|
328
|
+
asset: {
|
|
329
|
+
id: "action-label-1",
|
|
330
|
+
type: "text",
|
|
331
|
+
value: "Clicked {{count1}} times",
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
asset: {
|
|
338
|
+
id: "action-1",
|
|
339
|
+
type: "action",
|
|
340
|
+
label: {
|
|
341
|
+
asset: {
|
|
342
|
+
id: "action-label-2",
|
|
343
|
+
type: "text",
|
|
344
|
+
value: "Clicked {{count2}} times",
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
},
|
|
349
|
+
],
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
const model = new LocalModel({
|
|
353
|
+
count1: 0,
|
|
354
|
+
count2: 0,
|
|
355
|
+
});
|
|
356
|
+
const parser = new Parser();
|
|
357
|
+
const bindingParser = new BindingParser();
|
|
358
|
+
const rootNode = parser.parseObject(content, NodeType.View);
|
|
359
|
+
|
|
360
|
+
const logger = new TapableLogger();
|
|
361
|
+
|
|
362
|
+
const testLogger: Logger = {
|
|
363
|
+
trace: vitest.fn(),
|
|
364
|
+
debug: vitest.fn(),
|
|
365
|
+
info: vitest.fn(),
|
|
366
|
+
warn: vitest.fn(),
|
|
367
|
+
error: vitest.fn(),
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
logger.addHandler(testLogger);
|
|
371
|
+
|
|
372
|
+
const resolver = new Resolver(rootNode!, {
|
|
373
|
+
model,
|
|
374
|
+
parseBinding: bindingParser.parse.bind(bindingParser),
|
|
375
|
+
parseNode: parser.parseObject.bind(parser),
|
|
376
|
+
evaluator: new ExpressionEvaluator({
|
|
377
|
+
model: withParser(model, bindingParser.parse),
|
|
378
|
+
}),
|
|
379
|
+
schema: new SchemaController(),
|
|
380
|
+
logger,
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
new StringResolverPlugin().applyResolver(resolver);
|
|
384
|
+
|
|
385
|
+
const firstUpdate = resolver.update();
|
|
386
|
+
|
|
387
|
+
expect(testLogger.error).toBeCalledTimes(1);
|
|
388
|
+
expect(testLogger.error).toBeCalledWith(
|
|
389
|
+
"Cache conflict: Found Asset/View nodes that have conflicting ids: action-1, may cause cache issues.",
|
|
390
|
+
);
|
|
391
|
+
(testLogger.error as jest.Mock).mockClear();
|
|
392
|
+
|
|
393
|
+
expect(firstUpdate).toStrictEqual({
|
|
394
|
+
id: "action",
|
|
395
|
+
type: "collection",
|
|
396
|
+
values: [
|
|
397
|
+
{
|
|
398
|
+
asset: {
|
|
399
|
+
id: "action-1",
|
|
400
|
+
type: "action",
|
|
401
|
+
label: {
|
|
402
|
+
asset: {
|
|
403
|
+
id: "action-label-1",
|
|
404
|
+
type: "text",
|
|
405
|
+
value: "Clicked 0 times",
|
|
406
|
+
},
|
|
407
|
+
},
|
|
408
|
+
},
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
asset: {
|
|
412
|
+
id: "action-1",
|
|
413
|
+
type: "action",
|
|
414
|
+
label: {
|
|
415
|
+
asset: {
|
|
416
|
+
id: "action-label-2",
|
|
417
|
+
type: "text",
|
|
418
|
+
value: "Clicked 0 times",
|
|
419
|
+
},
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
],
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
resolver.update();
|
|
427
|
+
|
|
428
|
+
expect(testLogger.error).not.toBeCalled();
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
it("Throws a warning if two views have the same id", () => {
|
|
432
|
+
const content = {
|
|
433
|
+
id: "action",
|
|
434
|
+
type: "collection",
|
|
435
|
+
values: [
|
|
436
|
+
{
|
|
437
|
+
id: "value-1",
|
|
438
|
+
binding: "count1",
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
id: "value-1",
|
|
442
|
+
binding: "count2",
|
|
443
|
+
},
|
|
444
|
+
],
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
const model = new LocalModel({
|
|
448
|
+
count1: 0,
|
|
449
|
+
count2: 0,
|
|
450
|
+
});
|
|
451
|
+
const parser = new Parser();
|
|
452
|
+
const bindingParser = new BindingParser();
|
|
453
|
+
const rootNode = parser.parseObject(content, NodeType.View);
|
|
454
|
+
|
|
455
|
+
const logger = new TapableLogger();
|
|
456
|
+
|
|
457
|
+
const testLogger: Logger = {
|
|
458
|
+
trace: vitest.fn(),
|
|
459
|
+
debug: vitest.fn(),
|
|
460
|
+
info: vitest.fn(),
|
|
461
|
+
warn: vitest.fn(),
|
|
462
|
+
error: vitest.fn(),
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
logger.addHandler(testLogger);
|
|
466
|
+
|
|
467
|
+
const resolver = new Resolver(rootNode!, {
|
|
468
|
+
model,
|
|
469
|
+
parseBinding: bindingParser.parse.bind(bindingParser),
|
|
470
|
+
parseNode: parser.parseObject.bind(parser),
|
|
471
|
+
evaluator: new ExpressionEvaluator({
|
|
472
|
+
model: withParser(model, bindingParser.parse),
|
|
473
|
+
}),
|
|
474
|
+
schema: new SchemaController(),
|
|
475
|
+
logger,
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
new StringResolverPlugin().applyResolver(resolver);
|
|
479
|
+
|
|
480
|
+
const firstUpdate = resolver.update();
|
|
481
|
+
|
|
482
|
+
expect(testLogger.info).toBeCalledTimes(1);
|
|
483
|
+
expect(testLogger.info).toBeCalledWith(
|
|
484
|
+
"Cache conflict: Found Value nodes that have conflicting ids: value-1, may cause cache issues. To improve performance make value node IDs globally unique.",
|
|
485
|
+
);
|
|
486
|
+
(testLogger.info as jest.Mock).mockClear();
|
|
487
|
+
expect(firstUpdate).toStrictEqual(content);
|
|
488
|
+
|
|
489
|
+
resolver.update();
|
|
490
|
+
|
|
491
|
+
expect(testLogger.info).not.toHaveBeenCalled();
|
|
492
|
+
});
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
describe("AST caching", () => {
|
|
496
|
+
it("skipping resolution of nodes should still repopulate AST map for itself and children", () => {
|
|
497
|
+
const content = {
|
|
498
|
+
id: "collection",
|
|
499
|
+
type: "collection",
|
|
500
|
+
values: [
|
|
501
|
+
{
|
|
502
|
+
id: "value-1",
|
|
503
|
+
type: "collection",
|
|
504
|
+
values: [
|
|
505
|
+
{
|
|
506
|
+
id: "value-1-1",
|
|
507
|
+
},
|
|
508
|
+
],
|
|
509
|
+
},
|
|
510
|
+
],
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
const model = new LocalModel();
|
|
514
|
+
const parser = new Parser();
|
|
515
|
+
const bindingParser = new BindingParser();
|
|
516
|
+
const rootNode = parser.parseObject(content, NodeType.View);
|
|
517
|
+
const resolver = new Resolver(rootNode!, {
|
|
518
|
+
model,
|
|
519
|
+
parseBinding: bindingParser.parse.bind(bindingParser),
|
|
520
|
+
parseNode: parser.parseObject.bind(parser),
|
|
521
|
+
evaluator: new ExpressionEvaluator({
|
|
522
|
+
model: withParser(model, bindingParser.parse),
|
|
523
|
+
}),
|
|
524
|
+
schema: new SchemaController(),
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
const resolvedNodes: any[] = [];
|
|
528
|
+
resolver.hooks.afterResolve.tap("afterResolve", (value, node) => {
|
|
529
|
+
resolvedNodes.push(node);
|
|
530
|
+
return value;
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
resolver.hooks.skipResolve.tap(
|
|
534
|
+
"skipResolve",
|
|
535
|
+
() => resolvedNodes.length >= 5,
|
|
536
|
+
);
|
|
537
|
+
|
|
538
|
+
new StringResolverPlugin().applyResolver(resolver);
|
|
539
|
+
|
|
540
|
+
expect(resolvedNodes).toHaveLength(0);
|
|
541
|
+
|
|
542
|
+
resolver.update();
|
|
543
|
+
|
|
544
|
+
const frozenResolvedNodes = [...resolvedNodes];
|
|
545
|
+
expect(frozenResolvedNodes).toHaveLength(5);
|
|
546
|
+
|
|
547
|
+
const sourceNodes = frozenResolvedNodes.map((node) => {
|
|
548
|
+
const sourceNode = resolver.getSourceNode(node);
|
|
549
|
+
expect(sourceNode).toBeDefined();
|
|
550
|
+
return sourceNode;
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
resolver.update();
|
|
554
|
+
|
|
555
|
+
frozenResolvedNodes.forEach((node, index) => {
|
|
556
|
+
const sourceNode = resolver.getSourceNode(node);
|
|
557
|
+
expect(sourceNode).toBeDefined();
|
|
558
|
+
expect(sourceNode).toStrictEqual(sourceNodes[index]!);
|
|
559
|
+
});
|
|
560
|
+
});
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
describe("Root AST Immutability", () => {
|
|
564
|
+
it("modifying nodes in beforeResolve should not impact the original tree", () => {
|
|
565
|
+
const content = {
|
|
566
|
+
id: "action",
|
|
567
|
+
type: "collection",
|
|
568
|
+
values: [
|
|
569
|
+
{
|
|
570
|
+
id: "value-1",
|
|
571
|
+
binding: "count1",
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
id: "value-1",
|
|
575
|
+
binding: "count2",
|
|
576
|
+
},
|
|
577
|
+
],
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
const model = new LocalModel();
|
|
581
|
+
const parser = new Parser();
|
|
582
|
+
const bindingParser = new BindingParser();
|
|
583
|
+
const rootNode = parser.parseObject(content, NodeType.View);
|
|
584
|
+
const resolver = new Resolver(rootNode!, {
|
|
585
|
+
model,
|
|
586
|
+
parseBinding: bindingParser.parse.bind(bindingParser),
|
|
587
|
+
parseNode: parser.parseObject.bind(parser),
|
|
588
|
+
evaluator: new ExpressionEvaluator({
|
|
589
|
+
model: withParser(model, bindingParser.parse),
|
|
590
|
+
}),
|
|
591
|
+
schema: new SchemaController(),
|
|
592
|
+
});
|
|
593
|
+
let finalNode;
|
|
594
|
+
|
|
595
|
+
resolver.hooks.beforeResolve.tap("beforeResolve", (node) => {
|
|
596
|
+
if (node?.type !== NodeType.View) return node;
|
|
597
|
+
|
|
598
|
+
// eslint-disable-next-line no-param-reassign
|
|
599
|
+
node.value.type = "not-collection";
|
|
600
|
+
return node;
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
resolver.hooks.afterResolve.tap("afterResolve", (value, node) => {
|
|
604
|
+
if (node?.type === NodeType.View) {
|
|
605
|
+
finalNode = node;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
return value;
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
resolver.update();
|
|
612
|
+
|
|
613
|
+
expect(rootNode).toBe(resolver.root);
|
|
614
|
+
expect(rootNode).not.toBe(finalNode);
|
|
615
|
+
expect(finalNode).toMatchObject({
|
|
616
|
+
value: {
|
|
617
|
+
type: "not-collection",
|
|
618
|
+
},
|
|
619
|
+
});
|
|
620
|
+
expect(rootNode).toMatchObject({
|
|
621
|
+
value: {
|
|
622
|
+
type: "collection",
|
|
623
|
+
},
|
|
624
|
+
});
|
|
625
|
+
});
|
|
626
|
+
});
|