@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,113 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { BindingInstance } from "..";
|
|
3
|
+
|
|
4
|
+
describe("contains", () => {
|
|
5
|
+
it("works for simple neg case", () => {
|
|
6
|
+
const foo = new BindingInstance("foo");
|
|
7
|
+
const bar = new BindingInstance("bar");
|
|
8
|
+
expect(foo.contains(bar)).toBe(false);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("works for simple pos cases", () => {
|
|
12
|
+
const foo = new BindingInstance("foo");
|
|
13
|
+
const fooBar = new BindingInstance("foo.bar");
|
|
14
|
+
expect(foo.contains(fooBar)).toBe(true);
|
|
15
|
+
expect(fooBar.contains(foo)).toBe(false);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("can handle bindings starting with numbers", () => {
|
|
19
|
+
const instance = new BindingInstance(
|
|
20
|
+
"foo.5f4704fd-adab-49df-bcbc-5aedb04194f9.baz",
|
|
21
|
+
);
|
|
22
|
+
expect(instance.asString()).toBe(
|
|
23
|
+
"foo.5f4704fd-adab-49df-bcbc-5aedb04194f9.baz",
|
|
24
|
+
);
|
|
25
|
+
expect(instance.asArray()[1]).toBe("5f4704fd-adab-49df-bcbc-5aedb04194f9");
|
|
26
|
+
expect(instance.key()).toBe("baz");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("handles overlapping key-string matches", () => {
|
|
30
|
+
const foo = new BindingInstance("foo");
|
|
31
|
+
const fooBar = new BindingInstance("fo");
|
|
32
|
+
expect(foo.contains(fooBar)).toBe(false);
|
|
33
|
+
|
|
34
|
+
expect(
|
|
35
|
+
new BindingInstance("foo.bar.baz").contains(
|
|
36
|
+
new BindingInstance("foo.bar.bazzzz"),
|
|
37
|
+
),
|
|
38
|
+
).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe("relative", () => {
|
|
43
|
+
it("works for simple case", () => {
|
|
44
|
+
const foo = new BindingInstance("foo");
|
|
45
|
+
const fooBar = new BindingInstance("foo.bar");
|
|
46
|
+
expect(fooBar.relative(foo)).toStrictEqual(["bar"]);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("parent", () => {
|
|
51
|
+
it("works for easy ones", () => {
|
|
52
|
+
const fooBar = new BindingInstance("foo.bar");
|
|
53
|
+
expect(fooBar.parent().asString()).toBe("foo");
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe("key", () => {
|
|
58
|
+
it("works for easy ones", () => {
|
|
59
|
+
const fooBar = new BindingInstance("foo.bar");
|
|
60
|
+
expect(fooBar.key()).toBe("bar");
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe("numberic segments", () => {
|
|
65
|
+
it("splits numberic keys", () => {
|
|
66
|
+
expect(new BindingInstance("foo.1.bar").asArray()).toStrictEqual([
|
|
67
|
+
"foo",
|
|
68
|
+
1,
|
|
69
|
+
"bar",
|
|
70
|
+
]);
|
|
71
|
+
expect(new BindingInstance(["foo", 1, "bar"]).asArray()).toStrictEqual([
|
|
72
|
+
"foo",
|
|
73
|
+
1,
|
|
74
|
+
"bar",
|
|
75
|
+
]);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe("descendent binding", () => {
|
|
80
|
+
it("generate child path from string", () => {
|
|
81
|
+
const binding = new BindingInstance("foo.1.bar");
|
|
82
|
+
expect(binding.descendent("barChild.UUID").asArray()).toStrictEqual([
|
|
83
|
+
"foo",
|
|
84
|
+
1,
|
|
85
|
+
"bar",
|
|
86
|
+
"barChild",
|
|
87
|
+
"UUID",
|
|
88
|
+
]);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("generate child path from binding segments", () => {
|
|
92
|
+
const binding = new BindingInstance("foo.1.bar");
|
|
93
|
+
expect(binding.descendent(["barChild", "UUID"]).asArray()).toStrictEqual([
|
|
94
|
+
"foo",
|
|
95
|
+
1,
|
|
96
|
+
"bar",
|
|
97
|
+
"barChild",
|
|
98
|
+
"UUID",
|
|
99
|
+
]);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("generate child path from a binding", () => {
|
|
103
|
+
const binding = new BindingInstance("foo.1.bar");
|
|
104
|
+
const childBinding = new BindingInstance("barChild.UUID");
|
|
105
|
+
expect(binding.descendent(childBinding).asArray()).toStrictEqual([
|
|
106
|
+
"foo",
|
|
107
|
+
1,
|
|
108
|
+
"bar",
|
|
109
|
+
"barChild",
|
|
110
|
+
"UUID",
|
|
111
|
+
]);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { describe, test, expect, vitest } from "vitest";
|
|
2
|
+
import { BindingParser } from "..";
|
|
3
|
+
|
|
4
|
+
test("caches bindings", () => {
|
|
5
|
+
const parser = new BindingParser();
|
|
6
|
+
const b1 = parser.parse("foo.bar");
|
|
7
|
+
const b2 = parser.parse("foo.bar");
|
|
8
|
+
const b3 = parser.parse(["foo", "bar"]);
|
|
9
|
+
|
|
10
|
+
expect(b1).toBe(b2);
|
|
11
|
+
expect(b1).toBe(b3);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("does not call the update hook when readOnly is true", () => {
|
|
15
|
+
const onSetHook = vitest.fn();
|
|
16
|
+
const onGetHook = vitest.fn();
|
|
17
|
+
|
|
18
|
+
const parser = new BindingParser({
|
|
19
|
+
get: (b) => {
|
|
20
|
+
onGetHook(b);
|
|
21
|
+
return [{ bar: "blah" }];
|
|
22
|
+
},
|
|
23
|
+
set: onSetHook,
|
|
24
|
+
readOnly: true,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
parser.parse('foo[bar="baz"].blah');
|
|
28
|
+
expect(onGetHook).toBeCalledWith(parser.parse("foo"));
|
|
29
|
+
expect(onSetHook).not.toHaveBeenCalled();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("calls the update hook when data needs to be changed", () => {
|
|
33
|
+
const onSetHook = vitest.fn();
|
|
34
|
+
const onGetHook = vitest.fn();
|
|
35
|
+
|
|
36
|
+
const parser = new BindingParser({
|
|
37
|
+
get: (b) => {
|
|
38
|
+
onGetHook(b);
|
|
39
|
+
|
|
40
|
+
return [{ bar: "blah" }];
|
|
41
|
+
},
|
|
42
|
+
set: onSetHook,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
parser.parse('foo[bar="baz"].blah');
|
|
46
|
+
expect(onGetHook).toBeCalledWith(parser.parse("foo"));
|
|
47
|
+
expect(onSetHook).toBeCalledWith([[parser.parse("foo.1.bar"), "baz"]]);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("skips the update hook when data does not need to be changed", () => {
|
|
51
|
+
const onSetHook = vitest.fn();
|
|
52
|
+
const onGetHook = vitest.fn();
|
|
53
|
+
|
|
54
|
+
const parser = new BindingParser({
|
|
55
|
+
get: (b) => {
|
|
56
|
+
onGetHook(b);
|
|
57
|
+
|
|
58
|
+
return [{ bar: "baz" }];
|
|
59
|
+
},
|
|
60
|
+
set: onSetHook,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
parser.parse('foo[bar="baz"].blah');
|
|
64
|
+
expect(onGetHook).toBeCalledWith(parser.parse("foo"));
|
|
65
|
+
expect(onSetHook).not.toBeCalledWith(parser.parse("foo"));
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("throws error on bad binding syntax", () => {
|
|
69
|
+
const parser = new BindingParser();
|
|
70
|
+
expect(() => parser.parse("foo.bar[")).toThrowError(
|
|
71
|
+
/Cannot normalize path "foo.bar\[/,
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("works for binding with nested refs", () => {
|
|
76
|
+
let callCount = 0;
|
|
77
|
+
|
|
78
|
+
const parser = new BindingParser({
|
|
79
|
+
get: () => {
|
|
80
|
+
return callCount++;
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
expect(parser.parse("foo.{{bar}}.baz").asString()).toBe("foo.0.baz");
|
|
85
|
+
expect(parser.parse("foo.{{bar}}.baz").asString()).toBe("foo.1.baz");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("works for bindings with nested boolean refs", () => {
|
|
89
|
+
const parser = new BindingParser({
|
|
90
|
+
get: () => {
|
|
91
|
+
return true;
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
expect(parser.parse("foo.{{bar}}.baz").asString()).toBe("foo.true.baz");
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("works for binding with partial-nested refs", () => {
|
|
99
|
+
const parser = new BindingParser({
|
|
100
|
+
get: (binding) => {
|
|
101
|
+
if (binding.asString() === "hello") {
|
|
102
|
+
return "bar";
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return "not-{{hello}}";
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
expect(parser.parse("foo.{{hello}}_world.baz").asString()).toBe(
|
|
110
|
+
"foo.bar_world.baz",
|
|
111
|
+
);
|
|
112
|
+
expect(parser.parse("foo.{{hello}}_other_{{world}}.baz").asString()).toBe(
|
|
113
|
+
"foo.bar_other_not-bar.baz",
|
|
114
|
+
);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("works for expanded nested refs", () => {
|
|
118
|
+
const parser = new BindingParser({
|
|
119
|
+
get: (binding) => {
|
|
120
|
+
if (binding.asString() === "nested") {
|
|
121
|
+
return "nested.ref[1]";
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
expect(parser.parse("foo.{{nested}}.baz").asString()).toBe(
|
|
127
|
+
"foo.nested.ref.1.baz",
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("returns a binding if its already parsed", () => {
|
|
132
|
+
const parser = new BindingParser();
|
|
133
|
+
const binding = parser.parse("foo.bar");
|
|
134
|
+
expect(parser.parse(binding)).toBe(binding);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("top level parser returns empty key set", () => {
|
|
138
|
+
const parser = new BindingParser();
|
|
139
|
+
const binding = parser.parse("");
|
|
140
|
+
expect(binding.asArray()).toHaveLength(0);
|
|
141
|
+
expect(binding.asString()).toBe("");
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe("errors", () => {
|
|
145
|
+
test("throws when it gets an undefined nested path", () => {
|
|
146
|
+
const parser = new BindingParser({
|
|
147
|
+
get: () => undefined,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
expect(() => parser.parse("foo.{{nested}}.bar")).toThrowError();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("throws when get is used but not wired up", () => {
|
|
154
|
+
const parser = new BindingParser({
|
|
155
|
+
get: () => undefined,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
expect(() =>
|
|
159
|
+
parser.parse("foo.{{nested}}.bar"),
|
|
160
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
161
|
+
`[NestedError: Cannot resolve binding: foo.{{nested}}.bar]`,
|
|
162
|
+
);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("throws when set is used but not wired up", () => {
|
|
166
|
+
const parser = new BindingParser({
|
|
167
|
+
get: () => [],
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
expect(() => parser.parse("foo[foo=bar].bar")).toThrowError();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("throws when eval is used but not wired up", () => {
|
|
174
|
+
const parser = new BindingParser();
|
|
175
|
+
|
|
176
|
+
expect(() => parser.parse("foo.`exp()`.bar")).toThrowError();
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("throws when a path is not binding serializable", () => {
|
|
180
|
+
const parser = new BindingParser({
|
|
181
|
+
get: () => ({
|
|
182
|
+
foo: "bar",
|
|
183
|
+
}),
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
expect(() => parser.parse("foo.{{nested}}.bar")).toThrowError();
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// When exp() === '' -> ??? (foo.bar) | Error
|
|
190
|
+
|
|
191
|
+
describe("expressions", () => {
|
|
192
|
+
test("throws when expression returns undef", () => {
|
|
193
|
+
const parser = new BindingParser({
|
|
194
|
+
evaluate: () => undefined,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
expect(() => parser.parse("foo.`exp()`.bar")).toThrowError();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test("throws when expression returns empty binding", () => {
|
|
201
|
+
const parser = new BindingParser({
|
|
202
|
+
evaluate: () => "",
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
expect(() => parser.parse("foo.`exp()`.bar")).toThrowError();
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { describe, test, expect, vitest } from "vitest";
|
|
2
|
+
import get from "dlv";
|
|
3
|
+
import type { ParserSuccessResult } from "../../binding-grammar";
|
|
4
|
+
import { parseParsimmon } from "../../binding-grammar";
|
|
5
|
+
import { resolveBindingAST } from "../resolver";
|
|
6
|
+
import { getBindingSegments } from "../utils";
|
|
7
|
+
|
|
8
|
+
const testModel = {
|
|
9
|
+
foo: {
|
|
10
|
+
pets: [
|
|
11
|
+
{
|
|
12
|
+
name: "ginger",
|
|
13
|
+
type: "dog",
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: "daisy",
|
|
17
|
+
type: "dog",
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: "frodo",
|
|
21
|
+
type: "cat",
|
|
22
|
+
},
|
|
23
|
+
"other",
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const testCases: Array<[string, string]> = [
|
|
29
|
+
["foo.bar", "foo.bar"],
|
|
30
|
+
["foo.pets.1.name", "foo.pets.1.name"],
|
|
31
|
+
['foo.pets[name = "frodo"].type', "foo.pets.2.type"],
|
|
32
|
+
['foo.pets["name" = "sprinkles"].type', "foo.pets.4.type"],
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
test.each(testCases)("Resolving binding: %s", (binding, expectedResolved) => {
|
|
36
|
+
const parsedBinding = parseParsimmon(binding);
|
|
37
|
+
expect(parsedBinding.status).toBe(true);
|
|
38
|
+
const actual = resolveBindingAST(
|
|
39
|
+
(parsedBinding as ParserSuccessResult).path,
|
|
40
|
+
{
|
|
41
|
+
getValue: (path) => get(testModel, getBindingSegments(path) as any),
|
|
42
|
+
convertToPath: (p) => p,
|
|
43
|
+
evaluate: () => undefined,
|
|
44
|
+
},
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
expect(actual.path.join(".")).toBe(expectedResolved);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("works for nested keys", () => {
|
|
51
|
+
const parsedBinding = parseParsimmon("foo.{{BASE_PATH}}.bar");
|
|
52
|
+
expect(parsedBinding.status).toBe(true);
|
|
53
|
+
|
|
54
|
+
const resolved = resolveBindingAST(
|
|
55
|
+
(parsedBinding as ParserSuccessResult).path,
|
|
56
|
+
{
|
|
57
|
+
getValue: () => "path.nested[1]",
|
|
58
|
+
convertToPath: () => "path.nested.1",
|
|
59
|
+
evaluate: () => undefined,
|
|
60
|
+
},
|
|
61
|
+
);
|
|
62
|
+
expect(resolved.path.join(".")).toBe("foo.path.nested.1.bar");
|
|
63
|
+
expect(resolved.path).toStrictEqual(["foo", "path", "nested", 1, "bar"]);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe("expressions", () => {
|
|
67
|
+
test("evaluates expressions as paths", () => {
|
|
68
|
+
const parsedBinding = parseParsimmon("foo.bar.`exp()`");
|
|
69
|
+
|
|
70
|
+
const evaluate = vitest.fn().mockReturnValue(100);
|
|
71
|
+
const resolved = resolveBindingAST(
|
|
72
|
+
(parsedBinding as ParserSuccessResult).path,
|
|
73
|
+
{
|
|
74
|
+
getValue: () => undefined,
|
|
75
|
+
convertToPath: (p) => p,
|
|
76
|
+
evaluate,
|
|
77
|
+
},
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
expect(evaluate).toBeCalledWith("exp()");
|
|
81
|
+
expect(resolved.path.join(".")).toBe("foo.bar.100");
|
|
82
|
+
});
|
|
83
|
+
});
|
package/src/binding/binding.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getBindingSegments } from
|
|
1
|
+
import { getBindingSegments } from "./utils";
|
|
2
2
|
|
|
3
3
|
export interface BindingParserOptions {
|
|
4
4
|
/** Get the value for a specific binding */
|
|
@@ -31,7 +31,7 @@ export type RawBinding = string | RawBindingSegment[];
|
|
|
31
31
|
export type BindingLike = RawBinding | BindingInstance;
|
|
32
32
|
export type BindingFactory = (
|
|
33
33
|
raw: RawBinding,
|
|
34
|
-
options?: Partial<BindingParserOptions
|
|
34
|
+
options?: Partial<BindingParserOptions>,
|
|
35
35
|
) => BindingInstance;
|
|
36
36
|
|
|
37
37
|
/**
|
|
@@ -44,11 +44,11 @@ export class BindingInstance {
|
|
|
44
44
|
|
|
45
45
|
constructor(
|
|
46
46
|
raw: RawBinding,
|
|
47
|
-
factory = (rawBinding: RawBinding) => new BindingInstance(rawBinding)
|
|
47
|
+
factory = (rawBinding: RawBinding) => new BindingInstance(rawBinding),
|
|
48
48
|
) {
|
|
49
|
-
const split = Array.isArray(raw) ? raw : raw.split(
|
|
49
|
+
const split = Array.isArray(raw) ? raw : raw.split(".");
|
|
50
50
|
this.split = split.map((segment) => {
|
|
51
|
-
if (typeof segment ===
|
|
51
|
+
if (typeof segment === "number") {
|
|
52
52
|
return segment;
|
|
53
53
|
}
|
|
54
54
|
|
|
@@ -56,7 +56,7 @@ export class BindingInstance {
|
|
|
56
56
|
return isNaN(tryNum) ? segment : tryNum;
|
|
57
57
|
});
|
|
58
58
|
Object.freeze(this.split);
|
|
59
|
-
this.joined = this.split.join(
|
|
59
|
+
this.joined = this.split.join(".");
|
|
60
60
|
this.factory = factory;
|
|
61
61
|
}
|
|
62
62
|
|
package/src/binding/index.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import { SyncBailHook, SyncWaterfallHook } from
|
|
2
|
-
import { NestedError } from
|
|
3
|
-
import type { ParserResult, AnyNode } from
|
|
1
|
+
import { SyncBailHook, SyncWaterfallHook } from "tapable-ts";
|
|
2
|
+
import { NestedError } from "ts-nested-error";
|
|
3
|
+
import type { ParserResult, AnyNode } from "../binding-grammar/index";
|
|
4
4
|
import {
|
|
5
5
|
// We can swap this with whichever parser we want to use
|
|
6
6
|
parseCustom as parseBinding,
|
|
7
|
-
} from
|
|
8
|
-
import type { BindingParserOptions, BindingLike } from
|
|
9
|
-
import { BindingInstance } from
|
|
10
|
-
import { isBinding } from
|
|
11
|
-
import type { NormalizedResult, ResolveBindingASTOptions } from
|
|
12
|
-
import { resolveBindingAST } from
|
|
7
|
+
} from "../binding-grammar";
|
|
8
|
+
import type { BindingParserOptions, BindingLike } from "./binding";
|
|
9
|
+
import { BindingInstance } from "./binding";
|
|
10
|
+
import { isBinding } from "./utils";
|
|
11
|
+
import type { NormalizedResult, ResolveBindingASTOptions } from "./resolver";
|
|
12
|
+
import { resolveBindingAST } from "./resolver";
|
|
13
13
|
|
|
14
|
-
export * from
|
|
15
|
-
export * from
|
|
14
|
+
export * from "./utils";
|
|
15
|
+
export * from "./binding";
|
|
16
16
|
|
|
17
17
|
export const SIMPLE_BINDING_REGEX = /^[\w\-@]+(\.[\w\-@]+)*$/;
|
|
18
18
|
export const BINDING_BRACKETS_REGEX = /[\s()*=`{}'"[\]]/;
|
|
@@ -20,13 +20,13 @@ const LAZY_BINDING_REGEX = /^[^.]+(\..+)*$/;
|
|
|
20
20
|
|
|
21
21
|
const DEFAULT_OPTIONS: BindingParserOptions = {
|
|
22
22
|
get: () => {
|
|
23
|
-
throw new Error(
|
|
23
|
+
throw new Error("Not Implemented");
|
|
24
24
|
},
|
|
25
25
|
set: () => {
|
|
26
|
-
throw new Error(
|
|
26
|
+
throw new Error("Not Implemented");
|
|
27
27
|
},
|
|
28
28
|
evaluate: () => {
|
|
29
|
-
throw new Error(
|
|
29
|
+
throw new Error("Not Implemented");
|
|
30
30
|
},
|
|
31
31
|
};
|
|
32
32
|
|
|
@@ -59,7 +59,7 @@ export class BindingParser {
|
|
|
59
59
|
*/
|
|
60
60
|
private normalizePath(
|
|
61
61
|
path: string,
|
|
62
|
-
resolveOptions: ResolveBindingASTOptions
|
|
62
|
+
resolveOptions: ResolveBindingASTOptions,
|
|
63
63
|
) {
|
|
64
64
|
/**
|
|
65
65
|
* Ensure no binding characters exist in path and the characters remaining
|
|
@@ -70,15 +70,15 @@ export class BindingParser {
|
|
|
70
70
|
LAZY_BINDING_REGEX.test(path) &&
|
|
71
71
|
this.hooks.skipOptimization.call(path) !== true
|
|
72
72
|
) {
|
|
73
|
-
return { path: path.split(
|
|
73
|
+
return { path: path.split("."), updates: undefined } as NormalizedResult;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
const ast = this.parseCache[path] ?? parseBinding(path);
|
|
77
77
|
this.parseCache[path] = ast;
|
|
78
78
|
|
|
79
|
-
if (typeof ast !==
|
|
79
|
+
if (typeof ast !== "object" || !ast?.status) {
|
|
80
80
|
throw new TypeError(
|
|
81
|
-
`Cannot normalize path "${path}": ${ast?.error ??
|
|
81
|
+
`Cannot normalize path "${path}": ${ast?.error ?? "Unknown Error."}`,
|
|
82
82
|
);
|
|
83
83
|
}
|
|
84
84
|
|
|
@@ -90,17 +90,17 @@ export class BindingParser {
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
private getBindingForNormalizedResult(
|
|
93
|
-
normalized: NormalizedResult
|
|
93
|
+
normalized: NormalizedResult,
|
|
94
94
|
): BindingInstance {
|
|
95
|
-
const normalizedStr = normalized.path.join(
|
|
95
|
+
const normalizedStr = normalized.path.join(".");
|
|
96
96
|
|
|
97
97
|
if (this.cache[normalizedStr]) {
|
|
98
98
|
return this.cache[normalizedStr];
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
const created = new BindingInstance(
|
|
102
|
-
normalizedStr ===
|
|
103
|
-
this.parse
|
|
102
|
+
normalizedStr === "" ? [] : normalized.path,
|
|
103
|
+
this.parse,
|
|
104
104
|
);
|
|
105
105
|
this.cache[normalizedStr] = created;
|
|
106
106
|
|
|
@@ -109,7 +109,7 @@ export class BindingParser {
|
|
|
109
109
|
|
|
110
110
|
public parse(
|
|
111
111
|
rawBinding: BindingLike,
|
|
112
|
-
overrides: Partial<BindingParserOptions> = {}
|
|
112
|
+
overrides: Partial<BindingParserOptions> = {},
|
|
113
113
|
): BindingInstance {
|
|
114
114
|
if (isBinding(rawBinding)) {
|
|
115
115
|
return rawBinding;
|
|
@@ -123,12 +123,12 @@ export class BindingParser {
|
|
|
123
123
|
let updates: Record<string, any> = {};
|
|
124
124
|
|
|
125
125
|
const joined = Array.isArray(rawBinding)
|
|
126
|
-
? rawBinding.join(
|
|
126
|
+
? rawBinding.join(".")
|
|
127
127
|
: String(rawBinding);
|
|
128
128
|
|
|
129
129
|
const normalizeConfig: ResolveBindingASTOptions = {
|
|
130
130
|
getValue: (path: Array<string | number>) => {
|
|
131
|
-
const normalized = this.normalizePath(path.join(
|
|
131
|
+
const normalized = this.normalizePath(path.join("."), normalizeConfig);
|
|
132
132
|
|
|
133
133
|
return options.get(this.getBindingForNormalizedResult(normalized));
|
|
134
134
|
},
|
|
@@ -138,17 +138,17 @@ export class BindingParser {
|
|
|
138
138
|
convertToPath: (path: any) => {
|
|
139
139
|
if (path === undefined) {
|
|
140
140
|
throw new Error(
|
|
141
|
-
|
|
141
|
+
"Attempted to convert undefined value to binding path",
|
|
142
142
|
);
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
if (
|
|
146
|
-
typeof path !==
|
|
147
|
-
typeof path !==
|
|
148
|
-
typeof path !==
|
|
146
|
+
typeof path !== "string" &&
|
|
147
|
+
typeof path !== "number" &&
|
|
148
|
+
typeof path !== "boolean"
|
|
149
149
|
) {
|
|
150
150
|
throw new Error(
|
|
151
|
-
`Attempting to convert ${typeof path} to a binding path
|
|
151
|
+
`Attempting to convert ${typeof path} to a binding path.`,
|
|
152
152
|
);
|
|
153
153
|
}
|
|
154
154
|
|
|
@@ -161,10 +161,10 @@ export class BindingParser {
|
|
|
161
161
|
};
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
-
const joinedNormalizedPath = normalized.path.join(
|
|
164
|
+
const joinedNormalizedPath = normalized.path.join(".");
|
|
165
165
|
|
|
166
|
-
if (joinedNormalizedPath ===
|
|
167
|
-
throw new Error(
|
|
166
|
+
if (joinedNormalizedPath === "") {
|
|
167
|
+
throw new Error("Nested path resolved to an empty path");
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
return joinedNormalizedPath;
|
|
@@ -187,7 +187,7 @@ export class BindingParser {
|
|
|
187
187
|
(updatedBinding) => [
|
|
188
188
|
this.parse(updatedBinding),
|
|
189
189
|
updates[updatedBinding],
|
|
190
|
-
]
|
|
190
|
+
],
|
|
191
191
|
);
|
|
192
192
|
|
|
193
193
|
options.set(updateTransaction);
|