@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,312 @@
1
+ import { test, vitest, expect } from "vitest";
2
+ import type { FlowController } from "../controllers";
3
+ import type { DataController } from "..";
4
+ import { Player } from "..";
5
+ import type { InProgressState } from "../types";
6
+
7
+ test("transitions on action nodes", async () => {
8
+ const player = new Player();
9
+
10
+ player.start({
11
+ id: "test-flow",
12
+ data: {
13
+ my: {
14
+ puppy: "Ginger",
15
+ },
16
+ },
17
+ navigation: {
18
+ BEGIN: "FLOW_1",
19
+ FLOW_1: {
20
+ startState: "ACTION_1",
21
+ ACTION_1: {
22
+ state_type: "ACTION",
23
+ exp: "{{my.puppy}}",
24
+ transitions: {
25
+ Ginger: "EXTERNAL_1",
26
+ },
27
+ },
28
+ EXTERNAL_1: {
29
+ state_type: "EXTERNAL",
30
+ ref: "view_1",
31
+ param: {
32
+ best: "{{my.puppy}}",
33
+ },
34
+ transitions: {},
35
+ },
36
+ },
37
+ },
38
+ });
39
+
40
+ await vitest.waitFor(() =>
41
+ expect(player.getState().status).toBe("in-progress"),
42
+ );
43
+
44
+ const state = player.getState();
45
+ const currentState = (state as InProgressState).controllers.flow.current
46
+ ?.currentState;
47
+ expect(currentState?.name).toBe("EXTERNAL_1");
48
+ expect(currentState?.value).toStrictEqual({
49
+ state_type: "EXTERNAL",
50
+ ref: "view_1",
51
+ param: {
52
+ best: "Ginger",
53
+ },
54
+ transitions: {},
55
+ });
56
+ });
57
+
58
+ test("resolves data when transitioning", async () => {
59
+ const player = new Player();
60
+
61
+ let flowController: FlowController | undefined;
62
+
63
+ player.hooks.flowController.tap("test", (fc) => {
64
+ flowController = fc;
65
+ });
66
+
67
+ const flowResponse = player.start({
68
+ id: "test-flow",
69
+ views: [
70
+ { id: "view-1", type: "view" },
71
+ { id: "view-2", type: "view" },
72
+ ],
73
+ data: {
74
+ viewNum: 2,
75
+ outcomeData: "not good",
76
+ },
77
+ navigation: {
78
+ BEGIN: "FLOW_1",
79
+ FLOW_1: {
80
+ startState: "VIEW_1",
81
+ VIEW_1: {
82
+ state_type: "VIEW",
83
+ ref: "view-1",
84
+ transitions: {
85
+ Next: "VIEW_{{viewNum}}",
86
+ },
87
+ },
88
+ VIEW_2: {
89
+ state_type: "VIEW",
90
+ ref: "view-{{viewNum}}",
91
+ transitions: {
92
+ "*": "END",
93
+ },
94
+ },
95
+ END: {
96
+ state_type: "END",
97
+ outcome: "{{outcomeData}}",
98
+ },
99
+ },
100
+ },
101
+ });
102
+
103
+ /** A helper to get the current view */
104
+ const getView = () =>
105
+ (player.getState() as InProgressState).controllers.view.currentView
106
+ ?.lastUpdate;
107
+
108
+ expect(getView()).toStrictEqual({
109
+ id: "view-1",
110
+ type: "view",
111
+ });
112
+
113
+ flowController?.transition("Next");
114
+
115
+ expect(getView()).toStrictEqual({
116
+ id: "view-2",
117
+ type: "view",
118
+ });
119
+
120
+ flowController?.transition("Next");
121
+ expect((await flowResponse).endState).toStrictEqual({
122
+ state_type: "END",
123
+ outcome: "not good",
124
+ });
125
+ });
126
+
127
+ test("resolves dynamic view ids", () => {
128
+ const player = new Player();
129
+
130
+ player.start({
131
+ id: "test-flow",
132
+ views: [{ id: "view-1-{{name}}", type: "view" }],
133
+ data: {
134
+ viewNum: 1,
135
+ name: "adam",
136
+ outcomeData: "not good",
137
+ },
138
+ navigation: {
139
+ BEGIN: "FLOW_1",
140
+ FLOW_1: {
141
+ startState: "VIEW_1",
142
+ VIEW_1: {
143
+ state_type: "VIEW",
144
+ ref: "view-1-adam",
145
+ transitions: {
146
+ Next: "END",
147
+ },
148
+ },
149
+ END: {
150
+ state_type: "END",
151
+ outcome: "{{outcomeData}}",
152
+ },
153
+ },
154
+ },
155
+ });
156
+
157
+ const state = player.getState();
158
+
159
+ expect(state.status).toBe("in-progress");
160
+ expect(
161
+ (state as InProgressState).controllers.view.currentView?.lastUpdate?.id,
162
+ ).toBe("view-1-adam");
163
+ });
164
+
165
+ test("resolves data when completing", async () => {
166
+ const player = new Player();
167
+
168
+ let flowController: FlowController | undefined;
169
+ let dataController: DataController | undefined;
170
+
171
+ player.hooks.flowController.tap("test", (fc) => {
172
+ flowController = fc;
173
+ });
174
+
175
+ player.hooks.dataController.tap("test", (dc) => {
176
+ dataController = dc;
177
+ });
178
+
179
+ const flowResponse = player.start({
180
+ id: "test-flow",
181
+ views: [
182
+ { id: "view-1", type: "view" },
183
+ { id: "view-2", type: "view" },
184
+ ],
185
+ data: {
186
+ viewNum: 2,
187
+ outcomeData: "not good",
188
+ },
189
+ navigation: {
190
+ BEGIN: "FLOW_1",
191
+ FLOW_1: {
192
+ startState: "VIEW_1",
193
+ VIEW_1: {
194
+ state_type: "VIEW",
195
+ ref: "view-1",
196
+ transitions: {
197
+ Next: "VIEW_{{viewNum}}",
198
+ },
199
+ },
200
+ VIEW_2: {
201
+ state_type: "VIEW",
202
+ ref: "view-{{viewNum}}",
203
+ transitions: {
204
+ "*": "END",
205
+ },
206
+ },
207
+ END: {
208
+ state_type: "END",
209
+ outcome: "{{outcomeData}}",
210
+ },
211
+ },
212
+ },
213
+ });
214
+
215
+ /** A helper to get the current view */
216
+ const getView = () =>
217
+ (player.getState() as InProgressState).controllers.view.currentView
218
+ ?.lastUpdate;
219
+
220
+ expect(getView()).toStrictEqual({
221
+ id: "view-1",
222
+ type: "view",
223
+ });
224
+
225
+ flowController?.transition("Next");
226
+
227
+ dataController?.set({ testData: "testValue" });
228
+
229
+ expect(getView()).toStrictEqual({
230
+ id: "view-2",
231
+ type: "view",
232
+ });
233
+
234
+ flowController?.transition("Next");
235
+ const result = await flowResponse;
236
+
237
+ expect(result.data).toStrictEqual({
238
+ viewNum: 2,
239
+ outcomeData: "not good",
240
+ testData: "testValue",
241
+ });
242
+ });
243
+
244
+ test("resolves param on end nodes", async () => {
245
+ const player = new Player();
246
+
247
+ const flowResponse = player.start({
248
+ id: "test-flow",
249
+ views: [
250
+ { id: "view-1", type: "view" },
251
+ { id: "view-2", type: "view" },
252
+ ],
253
+ data: {
254
+ cat: {
255
+ name: "Sam",
256
+ },
257
+ },
258
+ navigation: {
259
+ BEGIN: "FLOW_1",
260
+ FLOW_1: {
261
+ startState: "VIEW_1",
262
+ VIEW_1: {
263
+ state_type: "VIEW",
264
+ ref: "view-1",
265
+ transitions: {
266
+ Next: "ACTION_1",
267
+ },
268
+ },
269
+ ACTION_1: {
270
+ state_type: "ACTION",
271
+ exp: '{{cat.name}} = "FRODO"',
272
+ transitions: {
273
+ "*": "END",
274
+ },
275
+ },
276
+ END: {
277
+ state_type: "END",
278
+ outcome: "favoritePet",
279
+ param: "{{cat.name}}",
280
+ },
281
+ },
282
+ },
283
+ });
284
+
285
+ (player.getState() as InProgressState).controllers.flow.transition("Next");
286
+
287
+ expect((await flowResponse).endState).toStrictEqual({
288
+ state_type: "END",
289
+ outcome: "favoritePet",
290
+ param: "FRODO",
291
+ });
292
+ });
293
+
294
+ test("works with iffe flows", async () => {
295
+ const player = new Player();
296
+ const flowResponse = player.start({
297
+ id: "first-end-flow",
298
+ navigation: {
299
+ BEGIN: "DoneWithTopicFlow",
300
+ DoneWithTopicFlow: {
301
+ startState: "END_done",
302
+ END_before_topic: { state_type: "END", outcome: "BACK" },
303
+ END_done: { state_type: "END", outcome: "doneWithTopic" },
304
+ },
305
+ },
306
+ });
307
+
308
+ expect((await flowResponse).endState).toStrictEqual({
309
+ state_type: "END",
310
+ outcome: "doneWithTopic",
311
+ });
312
+ });
@@ -0,0 +1,22 @@
1
+ import type { Player, PlayerPlugin } from "../../player";
2
+
3
+ export class ActionExpPlugin implements PlayerPlugin {
4
+ name = "action-plugin";
5
+
6
+ apply(player: Player) {
7
+ player.hooks.view.tap("test", (view) => {
8
+ view.hooks.resolver.tap("test", (resolver) => {
9
+ resolver.hooks.beforeResolve.tap("test", (n) => {
10
+ return {
11
+ ...n,
12
+ plugins: {
13
+ stringResolver: {
14
+ propertiesToSkip: ["exp"],
15
+ },
16
+ },
17
+ };
18
+ });
19
+ });
20
+ });
21
+ }
22
+ }
@@ -0,0 +1,67 @@
1
+ export default {
2
+ id: "generated-flow",
3
+ views: [
4
+ {
5
+ id: "action",
6
+ type: "collection",
7
+ values: [
8
+ {
9
+ asset: {
10
+ id: "just-a-label",
11
+ type: "text",
12
+ value: "Click some stuff!",
13
+ },
14
+ },
15
+ {
16
+ asset: {
17
+ id: "action-1",
18
+ type: "action",
19
+ exp: "{{count1}} += 1",
20
+ label: {
21
+ asset: {
22
+ id: "action-label-1",
23
+ type: "text",
24
+ value: "Clicked {{count1}} times",
25
+ },
26
+ },
27
+ },
28
+ },
29
+ {
30
+ asset: {
31
+ id: "action-2",
32
+ type: "action",
33
+ exp: "{{count2}} += 1",
34
+ label: {
35
+ asset: {
36
+ id: "action-label-2",
37
+ type: "text",
38
+ value: "Clicked {{count2}} times",
39
+ },
40
+ },
41
+ },
42
+ },
43
+ ],
44
+ },
45
+ ],
46
+ data: {
47
+ count1: 0,
48
+ count2: 0,
49
+ },
50
+ navigation: {
51
+ BEGIN: "FLOW_1",
52
+ FLOW_1: {
53
+ startState: "VIEW_1",
54
+ VIEW_1: {
55
+ state_type: "VIEW",
56
+ ref: "action",
57
+ transitions: {
58
+ "*": "END_Done",
59
+ },
60
+ },
61
+ END_Done: {
62
+ state_type: "END",
63
+ outcome: "done",
64
+ },
65
+ },
66
+ },
67
+ };
@@ -0,0 +1,125 @@
1
+ import type { FormatType } from "../../schema";
2
+ import type { ValidationController } from "../../controllers/validation";
3
+ import type { Player, PlayerPlugin } from "../../player";
4
+
5
+ /**
6
+ * Adds a validation provider to the validator registry
7
+ *
8
+ * @param vc - validation controller
9
+ */
10
+ export const addValidator = (vc: ValidationController) => {
11
+ vc.hooks.createValidatorRegistry.tap("test", (registry) => {
12
+ registry.register<{
13
+ /** specific names to match against */
14
+ names: string[];
15
+ }>("names", (context, val, options) => {
16
+ if (options?.names?.includes(val)) {
17
+ return undefined;
18
+ }
19
+
20
+ return {
21
+ message: `Names just be in: ${options?.names?.join(",")}`,
22
+ };
23
+ });
24
+
25
+ registry.register<any>("expression", (context, value, options) => {
26
+ if (options?.exp === undefined) {
27
+ return;
28
+ }
29
+
30
+ const result = context.evaluate(options.exp);
31
+
32
+ if (!result) {
33
+ return { message: "Expression evaluation failed" };
34
+ }
35
+ });
36
+
37
+ registry.register("required", (context, value) => {
38
+ if (value === undefined || value === null || value === "") {
39
+ return {
40
+ message: "A value is required",
41
+ severity: "error",
42
+ };
43
+ }
44
+ });
45
+
46
+ registry.register<any>("integer", (context, value) => {
47
+ if (typeof value !== "number" || Math.floor(value) !== value) {
48
+ return {
49
+ message: "Value must be an integer",
50
+ parameters: {
51
+ type: typeof value,
52
+ flooredValue: Math.floor(value),
53
+ value,
54
+ },
55
+ severity: "error",
56
+ };
57
+ }
58
+ });
59
+ });
60
+ };
61
+
62
+ /** A plugin that tracks bindings and attaches validations to anything w/ a binding property */
63
+ export default class TrackBindingPlugin implements PlayerPlugin {
64
+ name = "track-binding";
65
+
66
+ apply(player: Player) {
67
+ player.hooks.validationController.tap(this.name, (validationProvider) => {
68
+ addValidator(validationProvider);
69
+ });
70
+ const indexFormatter: FormatType<
71
+ number,
72
+ string,
73
+ {
74
+ /**
75
+ * @param
76
+ * @ignore
77
+ */
78
+ options: Array<string>;
79
+ }
80
+ > = {
81
+ name: "indexOf",
82
+ format: (val, options) => {
83
+ if (typeof val === "number" && options?.options) {
84
+ return options.options[val];
85
+ }
86
+
87
+ return undefined;
88
+ },
89
+ deformat: (val, options) => {
90
+ if (typeof val === "string" && options?.options) {
91
+ return options.options.indexOf(val);
92
+ }
93
+ },
94
+ };
95
+
96
+ player.hooks.schema.tap("test", (schema) => {
97
+ schema.addFormatters([indexFormatter] as any);
98
+ });
99
+
100
+ player.hooks.viewController.tap("test", (vc) => {
101
+ vc.hooks.view.tap("test", (view) => {
102
+ view.hooks.resolver.tap("test", (resolver) => {
103
+ resolver.hooks.resolve.tap("test", (val, node, options) => {
104
+ if (val?.binding) {
105
+ const currentValue = options?.data.model.get(val.binding);
106
+ options.validation?.track(val.binding);
107
+ const valObj = options.validation?.get(val.binding);
108
+
109
+ if (valObj) {
110
+ return {
111
+ ...val,
112
+ value: currentValue,
113
+ validation: valObj,
114
+ allValidations: options.validation?.getAll(),
115
+ };
116
+ }
117
+ }
118
+
119
+ return val;
120
+ });
121
+ });
122
+ });
123
+ });
124
+ }
125
+ }
@@ -0,0 +1,88 @@
1
+ import type { Player, PlayerPlugin } from "../../player";
2
+ import type {
3
+ Binding,
4
+ Expression,
5
+ ExpressionHandler,
6
+ ValidatorFunction,
7
+ } from "../..";
8
+
9
+ /**
10
+ * Adds a validation provider to the validator registry
11
+ *
12
+ * @param vc - validation controller
13
+ */
14
+ export const sumFunction: ExpressionHandler<
15
+ [Binding | unknown, string | string[]],
16
+ number
17
+ > = (ctx, modelOrReference, propName) => {
18
+ const values = Array.isArray(modelOrReference)
19
+ ? modelOrReference
20
+ : [modelOrReference];
21
+
22
+ let total = 0;
23
+ values.forEach((value) => {
24
+ total += ctx.model.get(value);
25
+ });
26
+
27
+ return total;
28
+ };
29
+
30
+ /** A plugin that tracks bindings and attaches validations to anything w/ a binding property */
31
+ export default class TestExpressionPlugin implements PlayerPlugin {
32
+ name = "expressions";
33
+
34
+ apply(player: Player) {
35
+ player.hooks.expressionEvaluator.tap(this.name, (expressionEvaluator) => {
36
+ expressionEvaluator.addExpressionFunction("sumValues", sumFunction);
37
+ });
38
+ }
39
+ }
40
+
41
+ /** A test validator function that makes sure the value is present */
42
+ export const required: ValidatorFunction<{
43
+ /** An optional expression to limit the required check only if true */
44
+ if?: Expression;
45
+
46
+ /** An optional expression to limit the required check only if false */
47
+ ifNot?: Expression;
48
+ }> = (context, value) => {
49
+ if (value === undefined || value === null || value === "") {
50
+ const message = context.constants.getConstants(
51
+ "validation.required",
52
+ "constants",
53
+ "A value is required",
54
+ ) as string;
55
+ return { message, severity: "error" };
56
+ }
57
+ };
58
+
59
+ /** A test validator function that makes sure the value is present given a condition */
60
+ export const requiredIf: ValidatorFunction<{
61
+ /** binding of the required field */
62
+ param?: string;
63
+ /** expression that needs to be true for the field to be required */
64
+ exp?: string;
65
+ }> = (context, value, options) => {
66
+ let ifExp = "false";
67
+ const evaluatedVal = context.evaluate(options?.exp || options?.param);
68
+
69
+ if (typeof evaluatedVal === "boolean") ifExp = JSON.stringify(evaluatedVal);
70
+
71
+ return required(context, value, { ...options, if: ifExp });
72
+ };
73
+
74
+ /** A test plugin that registers requiredIf validation */
75
+ export class RequiredIfValidationProviderPlugin implements PlayerPlugin {
76
+ name = "RequiredIfValidationProvider";
77
+
78
+ apply(player: Player) {
79
+ player.hooks.validationController.tap(this.name, (validationController) => {
80
+ validationController.hooks.createValidatorRegistry.tap(
81
+ this.name,
82
+ (validationRegistry) => {
83
+ validationRegistry.register("requiredIf", requiredIf);
84
+ },
85
+ );
86
+ });
87
+ }
88
+ }
@@ -0,0 +1,19 @@
1
+ import type { Player, PlayerPlugin } from "../..";
2
+
3
+ /** Just need something quick to use in tests */
4
+ export class AssetTransformPlugin implements PlayerPlugin {
5
+ name = "asset-transform-test";
6
+ public readonly registry: Array<[string, any]>;
7
+
8
+ constructor(transforms: Array<[string, any]>) {
9
+ this.registry = transforms;
10
+ }
11
+
12
+ apply(player: Player) {
13
+ player.hooks.viewController.tap(this.name, (vc) => {
14
+ this.registry.forEach(([key, value]) =>
15
+ vc.transformRegistry.set(key, value),
16
+ );
17
+ });
18
+ }
19
+ }
@@ -0,0 +1,56 @@
1
+ export default {
2
+ id: "minimal-player-content-response-format",
3
+ topic: "MOCK",
4
+ schema: {
5
+ ROOT: {
6
+ returns: {
7
+ type: "returnsType",
8
+ },
9
+ },
10
+ returnsType: {
11
+ input: {
12
+ type: "TextType",
13
+ validation: [
14
+ {
15
+ type: "required",
16
+ },
17
+ {
18
+ param: 6,
19
+ type: "maxLength",
20
+ },
21
+ ],
22
+ },
23
+ },
24
+ },
25
+ data: {},
26
+ views: [
27
+ {
28
+ id: "KitchenSink-View1",
29
+ title: {
30
+ asset: {
31
+ id: "KitchenSink-View1-Title",
32
+ type: "text",
33
+ value: "Minimal JSON Example",
34
+ },
35
+ },
36
+ type: "questionAnswer",
37
+ },
38
+ ],
39
+ navigation: {
40
+ BEGIN: "KitchenSinkFlow",
41
+ KitchenSinkFlow: {
42
+ END_Done: {
43
+ outcome: "doneWithTopic",
44
+ state_type: "END",
45
+ },
46
+ VIEW_KitchenSink_1: {
47
+ ref: "KitchenSink-View1",
48
+ state_type: "VIEW",
49
+ transitions: {
50
+ "*": "END_Done",
51
+ },
52
+ },
53
+ startState: "VIEW_KitchenSink_1",
54
+ },
55
+ },
56
+ };