@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.
Files changed (214) hide show
  1. package/dist/Player.native.js +11630 -0
  2. package/dist/Player.native.js.map +1 -0
  3. package/dist/cjs/index.cjs +5626 -0
  4. package/dist/cjs/index.cjs.map +1 -0
  5. package/dist/{index.esm.js → index.legacy-esm.js} +2044 -1667
  6. package/dist/{index.cjs.js → index.mjs} +2052 -1761
  7. package/dist/index.mjs.map +1 -0
  8. package/package.json +29 -63
  9. package/src/__tests__/data.test.ts +498 -0
  10. package/src/__tests__/flow.test.ts +312 -0
  11. package/src/__tests__/helpers/action-exp.plugin.ts +22 -0
  12. package/src/__tests__/helpers/actions.flow.ts +67 -0
  13. package/src/__tests__/helpers/binding.plugin.ts +125 -0
  14. package/src/__tests__/helpers/expression.plugin.ts +88 -0
  15. package/src/__tests__/helpers/transform-plugin.ts +19 -0
  16. package/src/__tests__/helpers/validation.flow.ts +56 -0
  17. package/src/__tests__/player.test.ts +597 -0
  18. package/src/__tests__/string-resolver.test.ts +186 -0
  19. package/src/__tests__/validation.test.ts +3555 -0
  20. package/src/__tests__/view.test.ts +715 -0
  21. package/src/binding/__tests__/binding.test.ts +113 -0
  22. package/src/binding/__tests__/index.test.ts +208 -0
  23. package/src/binding/__tests__/resolver.test.ts +83 -0
  24. package/src/binding/binding.ts +6 -6
  25. package/src/binding/index.ts +34 -34
  26. package/src/binding/resolver.ts +19 -19
  27. package/src/binding/utils.ts +7 -7
  28. package/src/binding-grammar/__tests__/parser.test.ts +64 -0
  29. package/src/binding-grammar/__tests__/test-utils/ast-cases.ts +198 -0
  30. package/src/binding-grammar/__tests__/test-utils/perf-test.ts +66 -0
  31. package/src/binding-grammar/ast.ts +11 -11
  32. package/src/binding-grammar/custom/index.ts +19 -22
  33. package/src/binding-grammar/ebnf/index.ts +20 -21
  34. package/src/binding-grammar/ebnf/types.ts +13 -13
  35. package/src/binding-grammar/index.ts +4 -4
  36. package/src/binding-grammar/parsimmon/index.ts +14 -14
  37. package/src/controllers/constants/__tests__/index.test.ts +106 -0
  38. package/src/controllers/constants/index.ts +3 -3
  39. package/src/controllers/constants/utils.ts +4 -4
  40. package/src/controllers/data/controller.ts +22 -22
  41. package/src/controllers/data/index.ts +1 -1
  42. package/src/controllers/data/utils.ts +7 -7
  43. package/src/controllers/flow/__tests__/controller.test.ts +195 -0
  44. package/src/controllers/flow/__tests__/flow.test.ts +381 -0
  45. package/src/controllers/flow/controller.ts +13 -13
  46. package/src/controllers/flow/flow.ts +23 -23
  47. package/src/controllers/flow/index.ts +2 -2
  48. package/src/controllers/index.ts +5 -5
  49. package/src/controllers/validation/binding-tracker.ts +71 -59
  50. package/src/controllers/validation/controller.ts +104 -104
  51. package/src/controllers/validation/index.ts +2 -2
  52. package/src/controllers/view/asset-transform.ts +20 -20
  53. package/src/controllers/view/controller.ts +27 -27
  54. package/src/controllers/view/index.ts +4 -4
  55. package/src/controllers/view/store.ts +3 -3
  56. package/src/controllers/view/types.ts +7 -7
  57. package/src/data/__tests__/__snapshots__/dependency-tracker.test.ts.snap +64 -0
  58. package/src/data/__tests__/dependency-tracker.test.ts +146 -0
  59. package/src/data/__tests__/local-model.test.ts +46 -0
  60. package/src/data/__tests__/model.test.ts +78 -0
  61. package/src/data/dependency-tracker.ts +16 -16
  62. package/src/data/index.ts +4 -4
  63. package/src/data/local-model.ts +6 -6
  64. package/src/data/model.ts +17 -17
  65. package/src/data/noop-model.ts +1 -1
  66. package/src/expressions/__tests__/__snapshots__/parser.test.ts.snap +854 -0
  67. package/src/expressions/__tests__/evaluator-functions.test.ts +47 -0
  68. package/src/expressions/__tests__/evaluator.test.ts +410 -0
  69. package/src/expressions/__tests__/parser.test.ts +115 -0
  70. package/src/expressions/__tests__/utils.test.ts +44 -0
  71. package/src/expressions/evaluator-functions.ts +6 -6
  72. package/src/expressions/evaluator.ts +71 -67
  73. package/src/expressions/index.ts +4 -4
  74. package/src/expressions/parser.ts +102 -105
  75. package/src/expressions/types.ts +29 -21
  76. package/src/expressions/utils.ts +32 -21
  77. package/src/index.ts +13 -13
  78. package/src/logger/__tests__/consoleLogger.test.ts +46 -0
  79. package/src/logger/__tests__/noopLogger.test.ts +13 -0
  80. package/src/logger/__tests__/proxyLogger.test.ts +31 -0
  81. package/src/logger/__tests__/tapableLogger.test.ts +41 -0
  82. package/src/logger/consoleLogger.ts +9 -9
  83. package/src/logger/index.ts +5 -5
  84. package/src/logger/noopLogger.ts +1 -1
  85. package/src/logger/proxyLogger.ts +6 -6
  86. package/src/logger/tapableLogger.ts +7 -7
  87. package/src/logger/types.ts +2 -2
  88. package/src/player.ts +60 -58
  89. package/src/plugins/default-exp-plugin.ts +10 -10
  90. package/src/plugins/default-view-plugin.ts +29 -0
  91. package/src/plugins/flow-exp-plugin.ts +6 -6
  92. package/src/schema/__tests__/schema.test.ts +243 -0
  93. package/src/schema/index.ts +2 -2
  94. package/src/schema/schema.ts +24 -24
  95. package/src/schema/types.ts +4 -4
  96. package/src/string-resolver/__tests__/index.test.ts +361 -0
  97. package/src/string-resolver/index.ts +17 -17
  98. package/src/types.ts +17 -17
  99. package/src/utils/__tests__/replaceParams.test.ts +33 -0
  100. package/src/utils/index.ts +1 -1
  101. package/src/utils/replaceParams.ts +1 -1
  102. package/src/validator/__tests__/binding-map-splice.test.ts +53 -0
  103. package/src/validator/__tests__/validation-middleware.test.ts +127 -0
  104. package/src/validator/binding-map-splice.ts +5 -5
  105. package/src/validator/index.ts +4 -4
  106. package/src/validator/registry.ts +1 -1
  107. package/src/validator/types.ts +13 -13
  108. package/src/validator/validation-middleware.ts +15 -15
  109. package/src/view/__tests__/view.immutable.test.ts +269 -0
  110. package/src/view/__tests__/view.test.ts +959 -0
  111. package/src/view/builder/index.test.ts +69 -0
  112. package/src/view/builder/index.ts +3 -3
  113. package/src/view/index.ts +5 -5
  114. package/src/view/parser/__tests__/__snapshots__/parser.test.ts.snap +394 -0
  115. package/src/view/parser/__tests__/parser.test.ts +264 -0
  116. package/src/view/parser/index.ts +43 -33
  117. package/src/view/parser/types.ts +11 -11
  118. package/src/view/parser/utils.ts +5 -5
  119. package/src/view/plugins/__tests__/__snapshots__/template.test.ts.snap +278 -0
  120. package/src/view/plugins/__tests__/applicability.test.ts +265 -0
  121. package/src/view/plugins/__tests__/string.test.ts +122 -0
  122. package/src/view/plugins/__tests__/template.test.ts +724 -0
  123. package/src/view/plugins/applicability.ts +19 -19
  124. package/src/view/plugins/index.ts +4 -5
  125. package/src/view/plugins/options.ts +1 -1
  126. package/src/view/plugins/string-resolver.ts +22 -22
  127. package/src/view/plugins/switch.ts +22 -23
  128. package/src/view/plugins/template-plugin.ts +26 -27
  129. package/src/view/resolver/__tests__/dependencies.test.ts +321 -0
  130. package/src/view/resolver/__tests__/edgecases.test.ts +626 -0
  131. package/src/view/resolver/index.ts +42 -42
  132. package/src/view/resolver/types.ts +21 -20
  133. package/src/view/resolver/utils.ts +9 -9
  134. package/src/view/view.ts +32 -22
  135. package/types/binding/binding.d.ts +50 -0
  136. package/types/binding/index.d.ts +29 -0
  137. package/types/binding/resolver.d.ts +26 -0
  138. package/types/binding/utils.d.ts +12 -0
  139. package/types/binding-grammar/ast.d.ts +67 -0
  140. package/types/binding-grammar/custom/index.d.ts +4 -0
  141. package/types/binding-grammar/ebnf/index.d.ts +4 -0
  142. package/types/binding-grammar/ebnf/types.d.ts +75 -0
  143. package/types/binding-grammar/index.d.ts +5 -0
  144. package/types/binding-grammar/parsimmon/index.d.ts +4 -0
  145. package/types/controllers/constants/index.d.ts +45 -0
  146. package/types/controllers/constants/utils.d.ts +6 -0
  147. package/types/controllers/data/controller.d.ts +45 -0
  148. package/types/controllers/data/index.d.ts +2 -0
  149. package/types/controllers/data/utils.d.ts +14 -0
  150. package/types/controllers/flow/controller.d.ts +25 -0
  151. package/types/controllers/flow/flow.d.ts +50 -0
  152. package/types/controllers/flow/index.d.ts +3 -0
  153. package/types/controllers/index.d.ts +6 -0
  154. package/types/controllers/validation/binding-tracker.d.ts +32 -0
  155. package/types/controllers/validation/controller.d.ts +151 -0
  156. package/types/controllers/validation/index.d.ts +3 -0
  157. package/types/controllers/view/asset-transform.d.ts +19 -0
  158. package/types/controllers/view/controller.d.ts +37 -0
  159. package/types/controllers/view/index.d.ts +5 -0
  160. package/types/controllers/view/store.d.ts +20 -0
  161. package/types/controllers/view/types.d.ts +16 -0
  162. package/types/data/dependency-tracker.d.ts +49 -0
  163. package/types/data/index.d.ts +5 -0
  164. package/types/data/local-model.d.ts +16 -0
  165. package/types/data/model.d.ts +86 -0
  166. package/types/data/noop-model.d.ts +13 -0
  167. package/types/expressions/evaluator-functions.d.ts +15 -0
  168. package/types/expressions/evaluator.d.ts +52 -0
  169. package/types/expressions/index.d.ts +5 -0
  170. package/types/expressions/parser.d.ts +10 -0
  171. package/types/expressions/types.d.ts +144 -0
  172. package/types/expressions/utils.d.ts +12 -0
  173. package/types/index.d.ts +14 -0
  174. package/types/logger/consoleLogger.d.ts +17 -0
  175. package/types/logger/index.d.ts +6 -0
  176. package/types/logger/noopLogger.d.ts +10 -0
  177. package/types/logger/proxyLogger.d.ts +15 -0
  178. package/types/logger/tapableLogger.d.ts +23 -0
  179. package/types/logger/types.d.ts +6 -0
  180. package/types/player.d.ts +101 -0
  181. package/types/plugins/default-exp-plugin.d.ts +9 -0
  182. package/types/plugins/default-view-plugin.d.ts +9 -0
  183. package/types/plugins/flow-exp-plugin.d.ts +11 -0
  184. package/types/schema/index.d.ts +3 -0
  185. package/types/schema/schema.d.ts +36 -0
  186. package/types/schema/types.d.ts +38 -0
  187. package/types/string-resolver/index.d.ts +30 -0
  188. package/types/types.d.ts +73 -0
  189. package/types/utils/index.d.ts +2 -0
  190. package/types/utils/replaceParams.d.ts +9 -0
  191. package/types/validator/binding-map-splice.d.ts +10 -0
  192. package/types/validator/index.d.ts +5 -0
  193. package/types/validator/registry.d.ts +11 -0
  194. package/types/validator/types.d.ts +53 -0
  195. package/types/validator/validation-middleware.d.ts +36 -0
  196. package/types/view/builder/index.d.ts +35 -0
  197. package/types/view/index.d.ts +6 -0
  198. package/types/view/parser/index.d.ts +52 -0
  199. package/types/view/parser/types.d.ts +109 -0
  200. package/types/view/parser/utils.d.ts +6 -0
  201. package/types/view/plugins/applicability.d.ts +10 -0
  202. package/types/view/plugins/index.d.ts +5 -0
  203. package/types/view/plugins/options.d.ts +4 -0
  204. package/types/view/plugins/string-resolver.d.ts +13 -0
  205. package/types/view/plugins/switch.d.ts +14 -0
  206. package/types/view/plugins/template-plugin.d.ts +33 -0
  207. package/types/view/resolver/index.d.ts +73 -0
  208. package/types/view/resolver/types.d.ts +129 -0
  209. package/types/view/resolver/utils.d.ts +11 -0
  210. package/types/view/view.d.ts +37 -0
  211. package/dist/index.d.ts +0 -1814
  212. package/dist/player.dev.js +0 -11472
  213. package/dist/player.prod.js +0 -2
  214. 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
+ });
@@ -1,4 +1,4 @@
1
- import { getBindingSegments } from './utils';
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 === 'number') {
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
 
@@ -1,18 +1,18 @@
1
- import { SyncBailHook, SyncWaterfallHook } from 'tapable-ts';
2
- import { NestedError } from 'ts-nested-error';
3
- import type { ParserResult, AnyNode } from '../binding-grammar';
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 '../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';
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 './utils';
15
- export * from './binding';
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('Not Implemented');
23
+ throw new Error("Not Implemented");
24
24
  },
25
25
  set: () => {
26
- throw new Error('Not Implemented');
26
+ throw new Error("Not Implemented");
27
27
  },
28
28
  evaluate: () => {
29
- throw new Error('Not Implemented');
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('.'), updates: undefined } as NormalizedResult;
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 !== 'object' || !ast?.status) {
79
+ if (typeof ast !== "object" || !ast?.status) {
80
80
  throw new TypeError(
81
- `Cannot normalize path "${path}": ${ast?.error ?? 'Unknown 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 === '' ? [] : normalized.path,
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('.'), normalizeConfig);
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
- 'Attempted to convert undefined value to binding path'
141
+ "Attempted to convert undefined value to binding path",
142
142
  );
143
143
  }
144
144
 
145
145
  if (
146
- typeof path !== 'string' &&
147
- typeof path !== 'number' &&
148
- typeof path !== 'boolean'
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('Nested path resolved to an empty path');
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);