@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,47 @@
1
+ import { describe, test, expect, beforeEach } from "vitest";
2
+ import type { DataModelWithParser } from "../../data";
3
+ import { LocalModel, withParser } from "../../data";
4
+ import { BindingParser } from "../../binding";
5
+ import { deleteDataVal, setDataVal, getDataVal } from "../evaluator-functions";
6
+ import type { ExpressionContext } from "../types";
7
+
8
+ describe("eval functions", () => {
9
+ let model: DataModelWithParser;
10
+ let context: ExpressionContext;
11
+
12
+ beforeEach(() => {
13
+ const localModel = new LocalModel();
14
+ const bindingParser = new BindingParser({
15
+ get: localModel.get,
16
+ set: localModel.set,
17
+ evaluate: () => undefined,
18
+ });
19
+ model = withParser(localModel, bindingParser.parse);
20
+ context = {
21
+ model,
22
+ evaluate: () => undefined,
23
+ };
24
+ });
25
+
26
+ test("deleteDataVal", () => {
27
+ model.set([["foo.bar", 2]]);
28
+ expect(model.get("foo.bar")).toBe(2);
29
+
30
+ deleteDataVal(context, "foo.bar");
31
+ expect(model.get("foo.bar")).toBeUndefined();
32
+ });
33
+
34
+ test("setDataVal", () => {
35
+ model.set([["foo.bar", 2]]);
36
+ expect(model.get("foo.bar")).toBe(2);
37
+
38
+ setDataVal(context, "foo.bar", 10);
39
+ expect(model.get("foo.bar")).toBe(10);
40
+ });
41
+
42
+ test("getDataVal", () => {
43
+ model.set([["foo.bar", 2]]);
44
+ expect(model.get("foo.bar")).toBe(2);
45
+ expect(getDataVal(context, "foo.bar")).toBe(2);
46
+ });
47
+ });
@@ -0,0 +1,410 @@
1
+ import { describe, it, expect, vitest, test, beforeEach } from "vitest";
2
+ import type { DataModelWithParser } from "../../data";
3
+ import { LocalModel, withParser } from "../../data";
4
+ import type { BindingLike } from "../../binding";
5
+ import { BindingParser } from "../../binding";
6
+ import type { ExpressionType } from "..";
7
+ import { ExpressionEvaluator } from "..";
8
+
9
+ describe("evaluator", () => {
10
+ let evaluator: ExpressionEvaluator;
11
+ let model: DataModelWithParser;
12
+ let parseBinding: BindingParser["parse"];
13
+
14
+ beforeEach(() => {
15
+ const localModel = new LocalModel();
16
+ const bindingParser = new BindingParser({
17
+ get: localModel.get,
18
+ set: localModel.set,
19
+ evaluate: (exp: ExpressionType) => {
20
+ return evaluator.evaluate(exp);
21
+ },
22
+ });
23
+
24
+ parseBinding = bindingParser.parse;
25
+ model = withParser(localModel, bindingParser.parse);
26
+ evaluator = new ExpressionEvaluator({ model });
27
+ });
28
+
29
+ test("resolveOptions hook", () => {
30
+ model = withParser(new LocalModel({ foo: 2 }), parseBinding);
31
+ evaluator = new ExpressionEvaluator({ model });
32
+
33
+ const testFn = vitest.fn();
34
+
35
+ evaluator.hooks.resolveOptions.tap("test", (hookOptions) => {
36
+ testFn.mockImplementation((value: any) => hookOptions.model.set(value));
37
+
38
+ return { ...hookOptions, model: { ...hookOptions.model, set: testFn } };
39
+ });
40
+
41
+ evaluator.evaluate("{{foo}} = 3");
42
+ expect(model.get("foo")).toStrictEqual(3);
43
+ expect(testFn).toBeCalled();
44
+ });
45
+
46
+ test("member expression", () => {
47
+ evaluator.setExpressionVariable("foo", { bar: "baz" });
48
+ expect(evaluator.evaluate('foo["bar"]')).toStrictEqual("baz");
49
+ });
50
+ test("custom variables (identifier)", () => {
51
+ evaluator.setExpressionVariable("foo", 3);
52
+ evaluator.setExpressionVariable("bar", "baz");
53
+ evaluator.setExpressionVariable("bang", [1, 2, 3]);
54
+ expect(evaluator.evaluate("foo")).toStrictEqual(3);
55
+ expect(evaluator.evaluate("foo + 1")).toStrictEqual(4);
56
+ expect(evaluator.evaluate("bar")).toStrictEqual("baz");
57
+ expect(evaluator.evaluate("bang")).toStrictEqual([1, 2, 3]);
58
+ });
59
+ test("literals", () => {
60
+ expect(evaluator.evaluate("2")).toStrictEqual(2);
61
+ expect(evaluator.evaluate("true")).toStrictEqual(true);
62
+ expect(evaluator.evaluate("false")).toStrictEqual(false);
63
+ expect(evaluator.evaluate("undefined")).toBe(undefined);
64
+ expect(evaluator.evaluate(undefined)).toBe(undefined);
65
+ expect(evaluator.evaluate(null)).toBe(null);
66
+ expect(evaluator.evaluate("null")).toBe(null);
67
+
68
+ expect(evaluator.evaluate(2)).toStrictEqual(2);
69
+ expect(evaluator.evaluate(true)).toStrictEqual(true);
70
+ expect(evaluator.evaluate(false)).toStrictEqual(false);
71
+ });
72
+
73
+ test("Objects", () => {
74
+ expect(
75
+ evaluator.evaluate('[{"foo": "value"}, {"bar": "value"}]'),
76
+ ).toStrictEqual([
77
+ {
78
+ foo: "value",
79
+ },
80
+ { bar: "value" },
81
+ ]);
82
+ expect(evaluator.evaluate('{"foo": "value"}')).toStrictEqual({
83
+ foo: "value",
84
+ });
85
+ expect(evaluator.evaluate('{"foo": 1 + 2}')).toStrictEqual({
86
+ foo: 3,
87
+ });
88
+ expect(evaluator.evaluate('{"foo": 1 > 2}')).toStrictEqual({
89
+ foo: false,
90
+ });
91
+ expect(evaluator.evaluate('{"foo": 1 + 2, "bar": 4}')).toStrictEqual({
92
+ foo: 3,
93
+ bar: 4,
94
+ });
95
+ expect(
96
+ evaluator.evaluate('{"foo": 2, "bar": { "baz": "foo" }}'),
97
+ ).toStrictEqual({
98
+ foo: 2,
99
+ bar: {
100
+ baz: "foo",
101
+ },
102
+ });
103
+
104
+ expect(evaluator.evaluate({ value: "1 + 2" })).toStrictEqual(3);
105
+ });
106
+ test("functions", () => {
107
+ model = withParser(new LocalModel({ test: 2 }), parseBinding);
108
+ evaluator.addExpressionFunction("publish", (_context, key, value: any) => {
109
+ model.set([[key as BindingLike, value]]);
110
+ });
111
+ evaluator.evaluate('publish("test", {"key": "value"})');
112
+ expect(model.get("test")).toStrictEqual({ key: "value" });
113
+ });
114
+
115
+ test("eval in context", () => {
116
+ model = withParser(new LocalModel({ test: 2 }), parseBinding);
117
+ evaluator.addExpressionFunction("exec", (_context, value: string) => {
118
+ _context.evaluate(value);
119
+ });
120
+ evaluator.evaluate('exec("{{foo}} = true")', { model });
121
+ expect(model.get("foo")).toBe(true);
122
+ });
123
+
124
+ test("binary operators", () => {
125
+ expect(evaluator.evaluate("1 == 1")).toStrictEqual(true);
126
+ expect(evaluator.evaluate("1 == 2")).toStrictEqual(false);
127
+
128
+ expect(evaluator.evaluate("1 === 1")).toStrictEqual(true);
129
+ expect(evaluator.evaluate("1 === 2")).toStrictEqual(false);
130
+
131
+ expect(evaluator.evaluate("1 != 1")).toStrictEqual(false);
132
+ expect(evaluator.evaluate("1 != 2")).toStrictEqual(true);
133
+
134
+ expect(evaluator.evaluate("1 !== 1")).toStrictEqual(false);
135
+ expect(evaluator.evaluate("1 !== 2")).toStrictEqual(true);
136
+
137
+ expect(evaluator.evaluate("1 < 2")).toStrictEqual(true);
138
+ expect(evaluator.evaluate("1 < 1")).toStrictEqual(false);
139
+ expect(evaluator.evaluate("1 < 0")).toStrictEqual(false);
140
+ expect(evaluator.evaluate("2 > 1")).toStrictEqual(true);
141
+ expect(evaluator.evaluate("1 > 1")).toStrictEqual(false);
142
+ expect(evaluator.evaluate("0 > 1")).toStrictEqual(false);
143
+
144
+ expect(evaluator.evaluate("1 <= 2")).toStrictEqual(true);
145
+ expect(evaluator.evaluate("1 <= 1")).toStrictEqual(true);
146
+ expect(evaluator.evaluate("1 <= 0")).toStrictEqual(false);
147
+ expect(evaluator.evaluate("2 >= 1")).toStrictEqual(true);
148
+ expect(evaluator.evaluate("1 >= 1")).toStrictEqual(true);
149
+ expect(evaluator.evaluate("0 >= 1")).toStrictEqual(false);
150
+
151
+ expect(evaluator.evaluate("1 + 2")).toStrictEqual(3);
152
+ expect(evaluator.evaluate("5 - 3")).toStrictEqual(2);
153
+ expect(evaluator.evaluate("2 * 3")).toStrictEqual(6);
154
+ expect(evaluator.evaluate("6 / 3")).toStrictEqual(2);
155
+ expect(evaluator.evaluate("8 % 3")).toStrictEqual(2);
156
+ });
157
+
158
+ test("add binary operators", () => {
159
+ evaluator.addBinaryOperator("==", () => 5);
160
+
161
+ expect(evaluator.evaluate("1 == 2")).toStrictEqual(5);
162
+ });
163
+ test("add unary operators", () => {
164
+ evaluator.addUnaryOperator("!", () => 5);
165
+
166
+ expect(evaluator.evaluate("!1")).toStrictEqual(5);
167
+ });
168
+
169
+ test("logical operators", () => {
170
+ expect(evaluator.evaluate("true && false")).toStrictEqual(false);
171
+ expect(evaluator.evaluate("true && true")).toStrictEqual(true);
172
+ expect(evaluator.evaluate("1 && 2")).toStrictEqual(2);
173
+ expect(evaluator.evaluate("1 && 0")).toStrictEqual(0);
174
+ });
175
+
176
+ test("unary operators", () => {
177
+ expect(evaluator.evaluate("!false")).toStrictEqual(true);
178
+ expect(evaluator.evaluate("!true")).toStrictEqual(false);
179
+ expect(evaluator.evaluate("+1")).toStrictEqual(1);
180
+ expect(evaluator.evaluate("-1")).toStrictEqual(-1);
181
+ expect(evaluator.evaluate("-(-1)")).toStrictEqual(1);
182
+ });
183
+
184
+ test("model ref", () => {
185
+ model = withParser(new LocalModel({ foo: { bar: "baz" } }), parseBinding);
186
+ expect(
187
+ evaluator.evaluate("{{foo.bar}}", {
188
+ model,
189
+ }),
190
+ ).toStrictEqual("baz");
191
+ });
192
+
193
+ test("model ref and Object", () => {
194
+ model = withParser(
195
+ new LocalModel({ bar: { hello: "world" } }),
196
+ parseBinding,
197
+ );
198
+ expect(
199
+ evaluator.evaluate('{ "foo": {{bar}} }', {
200
+ model,
201
+ }),
202
+ ).toStrictEqual({ foo: { hello: "world" } });
203
+ });
204
+
205
+ test("ternary operator (conditional)", () => {
206
+ expect(evaluator.evaluate("true ? true : false")).toStrictEqual(true);
207
+ expect(evaluator.evaluate("false ? true : false")).toStrictEqual(false);
208
+ });
209
+
210
+ test("array", () => {
211
+ expect(evaluator.evaluate("[1,2,3]")).toStrictEqual([1, 2, 3]);
212
+ });
213
+
214
+ test("assignment", () => {
215
+ model = withParser(new LocalModel({ foo: 2 }), parseBinding);
216
+ evaluator.evaluate("{{foo}} = 3", {
217
+ model,
218
+ });
219
+ expect(model.get("foo")).toStrictEqual(3);
220
+ });
221
+
222
+ test("local assignment and re-assignment", () => {
223
+ evaluator.evaluate("foo = 5");
224
+ expect(evaluator.evaluate("foo")).toStrictEqual(5);
225
+ evaluator.evaluate("foo = 7");
226
+ expect(evaluator.evaluate("foo")).toStrictEqual(7);
227
+ });
228
+
229
+ test("object assignment", () => {
230
+ model = withParser(new LocalModel({ foo: 2 }), parseBinding);
231
+ evaluator.evaluate('{{foo}} = {"foo": 2}', {
232
+ model,
233
+ });
234
+
235
+ expect(model.get("foo")).toStrictEqual({ foo: 2 });
236
+ });
237
+ describe("modification", () => {
238
+ describe("on model", () => {
239
+ beforeEach(() => {
240
+ model = withParser(new LocalModel({ foo: 1 }), parseBinding);
241
+ });
242
+
243
+ test("simple add", () => {
244
+ evaluator.evaluate("{{foo}} += 3", { model });
245
+ expect(model.get("foo")).toStrictEqual(4);
246
+ });
247
+
248
+ test("simple substract", () => {
249
+ evaluator.evaluate("{{foo}} -= 3", { model });
250
+ expect(model.get("foo")).toStrictEqual(-2);
251
+ });
252
+ });
253
+
254
+ describe("on expression variable", () => {
255
+ beforeEach(() => {
256
+ evaluator.setExpressionVariable("foo", 1);
257
+ });
258
+
259
+ test("simple add", () => {
260
+ evaluator.evaluate("foo += 3");
261
+ expect(evaluator.getExpressionVariable("foo")).toStrictEqual(4);
262
+ });
263
+
264
+ test("simple subtract", () => {
265
+ evaluator.evaluate("foo -= 3");
266
+ expect(evaluator.getExpressionVariable("foo")).toStrictEqual(-2);
267
+ });
268
+ });
269
+ });
270
+
271
+ describe("Call Expressions", () => {
272
+ beforeEach(() => {
273
+ model = withParser(new LocalModel({ foo: 1 }), parseBinding);
274
+ });
275
+
276
+ test("get model", () => {
277
+ expect(
278
+ evaluator.evaluate('getDataVal("foo")', {
279
+ model,
280
+ }),
281
+ ).toStrictEqual(1);
282
+ });
283
+ test("set model", () => {
284
+ evaluator.evaluate('setDataVal("foo", 2)', {
285
+ model,
286
+ });
287
+ expect(model.get("foo")).toStrictEqual(2);
288
+ });
289
+ test("conditional", () => {
290
+ expect(
291
+ evaluator.evaluate("conditional(true, true, false)"),
292
+ ).toStrictEqual(true);
293
+
294
+ expect(
295
+ evaluator.evaluate("conditional(false, true, false)"),
296
+ ).toStrictEqual(false);
297
+ });
298
+ });
299
+ describe("not supported", () => {
300
+ test("this ref", () => {
301
+ expect(() => evaluator.evaluate("this")).toThrow();
302
+ });
303
+
304
+ test("compound expression", () => {
305
+ expect(() => evaluator.evaluate("foo bar")).toThrow();
306
+ });
307
+ });
308
+
309
+ describe("error handling", () => {
310
+ test("skips throwing error when handler is provided, but not when throwErrors is true", () => {
311
+ const errorHandler = vitest.fn();
312
+
313
+ evaluator.hooks.onError.tap("test", (e) => {
314
+ errorHandler(e);
315
+
316
+ return true;
317
+ });
318
+
319
+ evaluator.evaluate("foo()");
320
+
321
+ expect(errorHandler).toBeCalledTimes(1);
322
+
323
+ expect(() =>
324
+ evaluator.evaluate("foo()", { throwErrors: true, model }),
325
+ ).toThrowError();
326
+ });
327
+ });
328
+
329
+ describe("complex", () => {
330
+ beforeEach(() => {
331
+ model = withParser(
332
+ new LocalModel({ foo: { bar: true }, baz: { other: false } }),
333
+ parseBinding,
334
+ );
335
+ });
336
+
337
+ it("can set a model to an object", () => {
338
+ evaluator.evaluate("{{foo.nested}} = false || {{baz}}", { model });
339
+ expect(model.get("foo.nested.other")).toStrictEqual(false);
340
+ });
341
+ });
342
+
343
+ describe("shortcuts binary ops", () => {
344
+ const aFunc = vitest.fn();
345
+ const bFunc = vitest.fn();
346
+
347
+ beforeEach(() => {
348
+ aFunc.mockReset();
349
+ bFunc.mockReset();
350
+
351
+ evaluator.addExpressionFunction("a", aFunc);
352
+ evaluator.addExpressionFunction("b", bFunc);
353
+ });
354
+
355
+ it("shortcuts || on true", () => {
356
+ aFunc.mockReturnValue(true);
357
+ evaluator.evaluate("a() || b()");
358
+
359
+ expect(aFunc).toBeCalledTimes(1);
360
+ expect(bFunc).not.toBeCalled();
361
+ });
362
+
363
+ it("works for full ||", () => {
364
+ aFunc.mockReturnValue(0);
365
+ evaluator.evaluate("a() || b()");
366
+
367
+ expect(aFunc).toBeCalledTimes(1);
368
+ expect(bFunc).toBeCalledTimes(1);
369
+ });
370
+
371
+ it("shortcuts && on false", () => {
372
+ aFunc.mockReturnValue(0);
373
+ evaluator.evaluate("a() && b()");
374
+
375
+ expect(aFunc).toBeCalledTimes(1);
376
+ expect(bFunc).not.toBeCalled();
377
+ });
378
+
379
+ it("works for full &&", () => {
380
+ aFunc.mockReturnValue(true);
381
+ evaluator.evaluate("a() && b()");
382
+
383
+ expect(aFunc).toBeCalledTimes(1);
384
+ expect(bFunc).toBeCalledTimes(1);
385
+ });
386
+ });
387
+
388
+ test("throws errors for unknown expressions", () => {
389
+ expect(() => evaluator.evaluate("foo()")).toThrowError(
390
+ "Error evaluating expression: foo()",
391
+ );
392
+ });
393
+
394
+ test("enables hooks to change expression", () => {
395
+ evaluator.hooks.beforeEvaluate.tap("test", (expression) => {
396
+ return `'foo' == 'bar'`;
397
+ });
398
+
399
+ expect(evaluator.evaluate("bar()")).toStrictEqual(false);
400
+ });
401
+
402
+ test("ignores props other than value on expression", () => {
403
+ expect(
404
+ evaluator.evaluate({
405
+ _comment: "hello world",
406
+ value: true,
407
+ } as any),
408
+ ).toStrictEqual(true);
409
+ });
410
+ });
@@ -0,0 +1,115 @@
1
+ import { describe, it, test, expect } from "vitest";
2
+ import { parseExpression } from "../parser";
3
+ import { ExpNodeOpaqueIdentifier } from "../types";
4
+
5
+ test("the happy stuff", () => {
6
+ expect(parseExpression("foo")).toMatchSnapshot();
7
+ expect(parseExpression("foo === bar")).toMatchSnapshot();
8
+ expect(parseExpression("foo || bar")).toMatchSnapshot();
9
+ expect(parseExpression("foo(bar)")).toMatchSnapshot();
10
+ expect(parseExpression('{{foo}} = "bar"')).toMatchSnapshot();
11
+ expect(parseExpression('{{foo}} ? "bar" : "baz"')).toMatchSnapshot();
12
+ expect(parseExpression("foo[bar]")).toMatchSnapshot();
13
+ expect(
14
+ parseExpression('{{foo}} == "string\nwith\tbreaks"'),
15
+ ).toMatchSnapshot();
16
+ expect(parseExpression("foo = [1, 2, 3]")).toMatchSnapshot();
17
+ });
18
+
19
+ test("nested binary op location", () => {
20
+ expect(parseExpression("foo === bar === baz")).toStrictEqual({
21
+ __id: ExpNodeOpaqueIdentifier,
22
+ type: "BinaryExpression",
23
+ operator: "===",
24
+ left: {
25
+ __id: ExpNodeOpaqueIdentifier,
26
+ type: "BinaryExpression",
27
+ operator: "===",
28
+ left: {
29
+ __id: ExpNodeOpaqueIdentifier,
30
+ type: "Identifier",
31
+ name: "foo",
32
+ location: {
33
+ start: { character: 0 },
34
+ end: { character: 3 },
35
+ },
36
+ },
37
+ right: {
38
+ __id: ExpNodeOpaqueIdentifier,
39
+ type: "Identifier",
40
+ name: "bar",
41
+ location: {
42
+ start: { character: 8 },
43
+ end: { character: 11 },
44
+ },
45
+ },
46
+ location: {
47
+ start: { character: 0 },
48
+ end: { character: 11 },
49
+ },
50
+ },
51
+ right: {
52
+ __id: ExpNodeOpaqueIdentifier,
53
+ type: "Identifier",
54
+ name: "baz",
55
+ location: {
56
+ start: { character: 16 },
57
+ end: { character: 19 },
58
+ },
59
+ },
60
+ location: {
61
+ start: { character: 0 },
62
+ end: { character: 19 },
63
+ },
64
+ });
65
+ });
66
+
67
+ test("the bad stuff", () => {
68
+ expect(() => parseExpression("{{foo")).toThrowError();
69
+ expect(() => parseExpression("{{foo}")).toThrowError();
70
+ expect(() => parseExpression('foo["bar"')).toThrowError();
71
+ expect(() => parseExpression('foo["bar')).toThrowError();
72
+ expect(() => parseExpression("foo]")).toThrowError();
73
+ expect(() => parseExpression("foo = (bar")).toThrowError();
74
+ expect(() => parseExpression("foo(")).toThrowError();
75
+ });
76
+ describe("expression parser", () => {
77
+ test("objects- in parser", () => {
78
+ expect(parseExpression('{"foo": "value"}')).toMatchSnapshot();
79
+ expect(parseExpression('{"foo": {{some.binding}}}')).toMatchSnapshot();
80
+ expect(parseExpression('{"foo": 1 + 2}')).toMatchSnapshot();
81
+ expect(parseExpression('{"foo": "value", "bar": "baz"}')).toMatchSnapshot();
82
+ expect(
83
+ parseExpression('{"foo": "value", "bar": { "baz": "foo" }}'),
84
+ ).toMatchSnapshot();
85
+ expect(
86
+ parseExpression('publish("test", {"key": "value"})'),
87
+ ).toMatchSnapshot();
88
+ // no closing brace
89
+ expect(() => parseExpression('{"foo": "value"')).toThrowError();
90
+ // no key
91
+ expect(() => parseExpression('{: "value"}')).toThrowError();
92
+ // no value
93
+ expect(() => parseExpression('{"key": }')).toThrowError();
94
+ // no colon
95
+ expect(() => parseExpression('{"key" "value" }')).toThrowError();
96
+ // no comma
97
+ expect(() =>
98
+ parseExpression('{"key": "value" "key2": "value" }'),
99
+ ).toThrowError();
100
+ });
101
+ });
102
+
103
+ describe("graceful parsing", () => {
104
+ it("returns and sets the error for invalid expression", () => {
105
+ const parsed = parseExpression("{{foo}} = {{bar}", {
106
+ strict: false,
107
+ });
108
+
109
+ expect(parsed).toBeTruthy();
110
+ expect(parsed.error).toBeTruthy();
111
+ expect(parsed.error?.message).toBe(
112
+ 'Unclosed brace after "bar}" at character 16',
113
+ );
114
+ });
115
+ });
@@ -0,0 +1,44 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { parseExpression } from "../parser";
3
+ import { findClosestNodeAtPosition } from "../utils";
4
+
5
+ describe("findClosestNodeAtPosition", () => {
6
+ it("finds the right nodes", () => {
7
+ const expression = '{{foo}} = test("bar", 12, ["baz", "qux"]) + !true';
8
+ const parsed = parseExpression(expression);
9
+
10
+ expect(findClosestNodeAtPosition(parsed, { character: 1 })).toStrictEqual(
11
+ expect.objectContaining({
12
+ type: "ModelRef",
13
+ ref: "foo",
14
+ }),
15
+ );
16
+
17
+ expect(findClosestNodeAtPosition(parsed, { character: 8 })).toStrictEqual(
18
+ expect.objectContaining({
19
+ type: "Assignment",
20
+ }),
21
+ );
22
+
23
+ expect(findClosestNodeAtPosition(parsed, { character: 12 })).toStrictEqual(
24
+ expect.objectContaining({
25
+ type: "Identifier",
26
+ name: "test",
27
+ }),
28
+ );
29
+
30
+ expect(findClosestNodeAtPosition(parsed, { character: 16 })).toStrictEqual(
31
+ expect.objectContaining({
32
+ type: "Literal",
33
+ value: "bar",
34
+ }),
35
+ );
36
+
37
+ expect(findClosestNodeAtPosition(parsed, { character: 23 })).toStrictEqual(
38
+ expect.objectContaining({
39
+ type: "Literal",
40
+ value: 12,
41
+ }),
42
+ );
43
+ });
44
+ });
@@ -1,17 +1,17 @@
1
- import type { Binding } from '@player-ui/types';
1
+ import type { Binding } from "@player-ui/types";
2
2
 
3
- import type { BindingLike } from '../binding';
3
+ import type { BindingLike } from "../binding";
4
4
  import type {
5
5
  ExpressionHandler,
6
6
  ExpressionContext,
7
7
  ExpressionNode,
8
- } from './types';
8
+ } from "./types";
9
9
 
10
10
  /** Sets a value to the data-model */
11
11
  export const setDataVal: ExpressionHandler<[Binding, any], any> = (
12
12
  _context: ExpressionContext,
13
13
  binding,
14
- value
14
+ value,
15
15
  ) => {
16
16
  _context.model.set([[binding as BindingLike, value]]);
17
17
  };
@@ -19,7 +19,7 @@ export const setDataVal: ExpressionHandler<[Binding, any], any> = (
19
19
  /** Fetches a valid from the data-model */
20
20
  export const getDataVal: ExpressionHandler<[Binding], unknown> = (
21
21
  _context: ExpressionContext,
22
- binding
22
+ binding,
23
23
  ) => {
24
24
  return _context.model.get(binding as BindingLike);
25
25
  };
@@ -27,7 +27,7 @@ export const getDataVal: ExpressionHandler<[Binding], unknown> = (
27
27
  /** Deletes a value from the model */
28
28
  export const deleteDataVal: ExpressionHandler<[Binding], void> = (
29
29
  _context: ExpressionContext,
30
- binding
30
+ binding,
31
31
  ) => {
32
32
  return _context.model.delete(binding);
33
33
  };