@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,195 @@
1
+ import { it, expect, vitest } from "vitest";
2
+ import { FlowController } from "..";
3
+
4
+ it("ends when the flow does", async () => {
5
+ const controller = new FlowController({
6
+ BEGIN: "foo",
7
+ foo: {
8
+ startState: "View1",
9
+ View1: {
10
+ state_type: "VIEW",
11
+ ref: "foo",
12
+ transitions: {
13
+ "*": "End",
14
+ },
15
+ },
16
+ End: {
17
+ state_type: "END",
18
+ outcome: "yay",
19
+ },
20
+ },
21
+ });
22
+ const controllerResult = controller.start();
23
+ controller.transition("foo");
24
+ const { outcome } = await controllerResult;
25
+ expect(outcome).toBe("yay");
26
+ });
27
+
28
+ it("calls another flow", async () => {
29
+ const controller = new FlowController({
30
+ BEGIN: "foo",
31
+ foo: {
32
+ startState: "View1",
33
+ View1: {
34
+ state_type: "VIEW",
35
+ ref: "foo",
36
+ transitions: {
37
+ "foo-1": "Flow2",
38
+ },
39
+ },
40
+ Flow2: {
41
+ state_type: "FLOW",
42
+ ref: "bar",
43
+ transitions: {
44
+ yay: "End",
45
+ },
46
+ },
47
+ End: {
48
+ state_type: "END",
49
+ outcome: "yay",
50
+ },
51
+ },
52
+ bar: {
53
+ startState: "View2",
54
+ View2: {
55
+ state_type: "VIEW",
56
+ ref: "bar",
57
+ transitions: {
58
+ "foo-2": "Done",
59
+ },
60
+ },
61
+ Done: {
62
+ state_type: "END",
63
+ outcome: "yay",
64
+ },
65
+ },
66
+ });
67
+
68
+ const controllerResult = controller.start();
69
+ expect(controller.current?.id).toBe("foo");
70
+ controller.transition("foo-1");
71
+ expect(controller.current?.id).toBe("bar");
72
+ controller.transition("foo-2");
73
+
74
+ const { outcome } = await controllerResult;
75
+ expect(outcome).toBe("yay");
76
+ expect(controller.current?.id).toBe("foo");
77
+ });
78
+
79
+ it("can switch between parent and sub flow", async () => {
80
+ const controller = new FlowController({
81
+ BEGIN: "Flow-1",
82
+ "Flow-1": {
83
+ startState: "Initial",
84
+ END_Back: {
85
+ outcome: "backBeforeTopic",
86
+ state_type: "END",
87
+ },
88
+ "Go-To-Flow-2": {
89
+ state_type: "FLOW",
90
+ ref: "Flow-2",
91
+ transitions: {
92
+ "*": "Initial",
93
+ },
94
+ },
95
+ Initial: {
96
+ ref: "Initial-View",
97
+ state_type: "VIEW",
98
+ transitions: {
99
+ Prev: "END_Back",
100
+ "*": "Go-To-Flow-2",
101
+ },
102
+ nodeName: "Initial",
103
+ fromAction: null,
104
+ },
105
+ },
106
+ "Flow-2": {
107
+ startState: "Result",
108
+ END_Done: {
109
+ state_type: "END",
110
+ outcome: "Done",
111
+ },
112
+ Result: {
113
+ ref: "Result-View",
114
+ state_type: "VIEW",
115
+ transitions: {
116
+ "*": "END_Done",
117
+ },
118
+ },
119
+ },
120
+ });
121
+
122
+ controller.start();
123
+ expect(controller.current?.id).toBe("Flow-1");
124
+ controller.transition("Next");
125
+ expect(controller.current?.id).toBe("Flow-2");
126
+ controller.transition("Back");
127
+
128
+ // Wait for all pending promises (in this case, the sub-flow promise) to complete.
129
+ await vitest.waitFor(() => expect(controller.current?.id).toBe("Flow-1"));
130
+
131
+ controller.transition("Next");
132
+ expect(controller.current?.id).toBe("Flow-2");
133
+ expect(controller.current?.currentState?.name).toBe("Result");
134
+ });
135
+
136
+ it("fails if BEGIN doesnt point to a flow", async () => {
137
+ const controller = new FlowController({
138
+ BEGIN: "foo",
139
+ } as any);
140
+
141
+ await expect(controller.start()).rejects.toThrowError(
142
+ "No flow defined for: foo",
143
+ );
144
+ });
145
+
146
+ it("fails if state isnt an object", async () => {
147
+ const controller = new FlowController({
148
+ BEGIN: "foo",
149
+ foo: "bar",
150
+ } as any);
151
+ await expect(controller.start()).rejects.toThrowError(
152
+ "Flow: foo needs to be an object",
153
+ );
154
+ });
155
+
156
+ it("fails if no BEGIN state", async () => {
157
+ const controller = new FlowController({} as any);
158
+ await expect(controller.start()).rejects.toThrowError(
159
+ "Must supply a BEGIN state",
160
+ );
161
+ });
162
+
163
+ it("fails if flow not started", async () => {
164
+ const controller = new FlowController({} as any);
165
+ expect(controller.transition).toThrowError(
166
+ "Not currently in a flow. Cannot transition.",
167
+ );
168
+ });
169
+
170
+ it("does not set current to undefined after run resolves with the last flow", async () => {
171
+ const controller = new FlowController({
172
+ BEGIN: "FLOW_1",
173
+ FLOW_1: {
174
+ startState: "VIEW_1",
175
+ VIEW_1: {
176
+ ref: "view-1",
177
+ state_type: "VIEW",
178
+ transitions: {
179
+ "*": "End",
180
+ },
181
+ },
182
+ End: {
183
+ state_type: "END",
184
+ outcome: "done",
185
+ },
186
+ },
187
+ } as any);
188
+ const p = controller.start();
189
+ controller.transition("Next");
190
+
191
+ await p;
192
+
193
+ expect(controller.current).toBeDefined();
194
+ expect(controller.current?.currentState?.value.outcome).toBe("done");
195
+ });
@@ -0,0 +1,381 @@
1
+ import { describe, it, test, expect, vitest } from "vitest";
2
+ import { FlowInstance } from "..";
3
+
4
+ test("starts the right state", () => {
5
+ const flow = new FlowInstance("flow", {
6
+ startState: "View1",
7
+ View1: {
8
+ state_type: "VIEW",
9
+ ref: "foo",
10
+ transitions: {
11
+ Next: "View2",
12
+ },
13
+ },
14
+ } as const);
15
+
16
+ flow.start();
17
+ expect(flow.currentState!.name).toBe("View1");
18
+ });
19
+
20
+ test("works with just END state", async () => {
21
+ const flow = new FlowInstance("flow", {
22
+ startState: "END_done",
23
+ END_before_topic: { state_type: "END", outcome: "BACK" },
24
+ END_done: { state_type: "END", outcome: "doneWithTopic" },
25
+ } as const);
26
+
27
+ expect(await flow.start()).toStrictEqual({
28
+ state_type: "END",
29
+ outcome: "doneWithTopic",
30
+ });
31
+ });
32
+
33
+ test("simple transitions between states", () => {
34
+ const flow = new FlowInstance("flow", {
35
+ startState: "View1",
36
+ View1: {
37
+ state_type: "VIEW",
38
+ ref: "foo",
39
+
40
+ transitions: {
41
+ Next: "View2",
42
+ },
43
+ },
44
+ View2: {
45
+ state_type: "VIEW",
46
+ ref: "foo",
47
+ transitions: {
48
+ Prev: "View1",
49
+ },
50
+ },
51
+ } as const);
52
+
53
+ flow.start();
54
+ expect(flow.currentState!.name).toBe("View1");
55
+ flow.transition("Next");
56
+ expect(flow.currentState!.name).toBe("View2");
57
+ flow.transition("Prev");
58
+ expect(flow.currentState!.name).toBe("View1");
59
+ });
60
+
61
+ test("transition from end state returns", () => {
62
+ const flow = new FlowInstance("flow", {
63
+ startState: "View1",
64
+ View1: {
65
+ state_type: "VIEW",
66
+ ref: "foo",
67
+ transitions: {
68
+ Next: "Done1",
69
+ },
70
+ },
71
+ Done1: {
72
+ state_type: "END",
73
+ outcome: "foo-bar",
74
+ },
75
+ });
76
+
77
+ flow.start();
78
+ flow.transition("Next");
79
+ expect(() => flow.transition("Prev")).not.toThrowError();
80
+ });
81
+
82
+ test("fails when theres no startState", async () => {
83
+ const flow = new FlowInstance("flow", {
84
+ View1: {
85
+ state_type: "Foo",
86
+ transitions: {},
87
+ },
88
+ } as any);
89
+
90
+ await expect(flow.start()).rejects.toThrowError(
91
+ "No 'startState' defined for flow",
92
+ );
93
+ });
94
+
95
+ test("uses * as fallback transition", () => {
96
+ const flow = new FlowInstance("flow", {
97
+ startState: "View1",
98
+ View1: {
99
+ state_type: "VIEW",
100
+ ref: "foo",
101
+ transitions: {
102
+ "*": "SpecialView",
103
+ },
104
+ },
105
+ SpecialView: {
106
+ ref: "foo",
107
+ state_type: "VIEW",
108
+ transitions: {},
109
+ },
110
+ });
111
+
112
+ flow.start();
113
+ expect(flow.currentState!.name).toBe("View1");
114
+ flow.transition("Prev");
115
+ expect(flow.currentState!.name).toBe("SpecialView");
116
+ });
117
+
118
+ test("Do not throw exception when no transitions", () => {
119
+ const flow = new FlowInstance("flow", {
120
+ startState: "View1",
121
+ View1: {
122
+ state_type: "VIEW",
123
+ ref: "foo",
124
+ transitions: {
125
+ Next: "View2",
126
+ },
127
+ },
128
+ });
129
+
130
+ flow.start();
131
+ expect(flow.currentState!.name).toBe("View1");
132
+ expect(() => flow.transition("Prev")).not.toThrowError();
133
+ });
134
+
135
+ test("Fails to transition when not started", () => {
136
+ const flow = new FlowInstance("flow", {
137
+ startState: "View1",
138
+ View1: {
139
+ state_type: "VIEW",
140
+ ref: "foo",
141
+ transitions: {
142
+ Next: "View2",
143
+ },
144
+ },
145
+ });
146
+
147
+ expect(() => flow.transition("foo")).toThrowError();
148
+ });
149
+
150
+ test("Fails to transition during another transition", async () => {
151
+ const flow = new FlowInstance("flow", {
152
+ startState: "View1",
153
+ View1: {
154
+ state_type: "VIEW",
155
+ onStart: "foo bar",
156
+ ref: "foo",
157
+ transitions: {
158
+ Next: "View2",
159
+ },
160
+ },
161
+ View2: {
162
+ state_type: "VIEW",
163
+ ref: "bar",
164
+ transitions: {
165
+ Next: "View3",
166
+ },
167
+ },
168
+ });
169
+
170
+ let deferredVar: string;
171
+
172
+ const transition = () => {
173
+ try {
174
+ flow.transition("Next");
175
+ return "foo";
176
+ } catch (error: unknown) {
177
+ return "bar";
178
+ }
179
+ };
180
+
181
+ flow.hooks.resolveTransitionNode.intercept({
182
+ call: (nextState) => {
183
+ if (nextState?.onStart) {
184
+ deferredVar = transition();
185
+ }
186
+ },
187
+ });
188
+
189
+ flow.start();
190
+
191
+ await vitest.waitFor(() => {
192
+ expect(deferredVar).toBeDefined();
193
+ });
194
+
195
+ expect(deferredVar!).toBe("bar");
196
+ });
197
+
198
+ describe("promise api", () => {
199
+ it("resolves when were done", async () => {
200
+ const flow = new FlowInstance("flow", {
201
+ startState: "View1",
202
+ View1: {
203
+ state_type: "VIEW",
204
+ ref: "foo",
205
+ transitions: {
206
+ Next: "Done1",
207
+ },
208
+ },
209
+ Done1: {
210
+ state_type: "END",
211
+ outcome: "foo-bar",
212
+ },
213
+ });
214
+
215
+ const flowProm = flow.start();
216
+ flow.transition("Next");
217
+ const result = await flowProm;
218
+ expect(result.state_type).toBe("END");
219
+ expect(result.outcome).toBe("foo-bar");
220
+ });
221
+ });
222
+
223
+ test("reuses the same promise if started again", async () => {
224
+ const flow = new FlowInstance("flow", {
225
+ startState: "View1",
226
+ View1: {
227
+ state_type: "VIEW",
228
+ ref: "foo",
229
+ transitions: { "*": "End1" },
230
+ },
231
+ End1: {
232
+ state_type: "END",
233
+ outcome: "bar",
234
+ },
235
+ });
236
+
237
+ flow.start();
238
+ flow.transition("foo");
239
+ const result = await flow.start();
240
+ expect(result.outcome).toBe("bar");
241
+ });
242
+
243
+ test("calls onStart hook", async () => {
244
+ const flow = new FlowInstance("flow", {
245
+ onStart: "foo bar",
246
+ } as any);
247
+ const hook = vitest.fn();
248
+ flow.hooks.onStart.tap("test", hook);
249
+ const result = flow.start();
250
+ expect(hook).toBeCalledWith("foo bar");
251
+
252
+ await expect(result).rejects.toThrowError("No 'startState' defined for flow");
253
+ });
254
+
255
+ test("calls the onEnd hook", async () => {
256
+ const flow = new FlowInstance("flow", {
257
+ onEnd: "foo bar",
258
+ startState: "FOO",
259
+ FOO: {
260
+ state_type: "END",
261
+ outcome: "done",
262
+ },
263
+ });
264
+ const hook = vitest.fn();
265
+
266
+ flow.hooks.onEnd.tap("test", hook);
267
+ const result = flow.start();
268
+ expect(hook).toBeCalledWith("foo bar");
269
+ expect(await result).toStrictEqual({
270
+ state_type: "END",
271
+ outcome: "done",
272
+ });
273
+ });
274
+
275
+ test("keeps current state if skipped", () => {
276
+ const flow = new FlowInstance("flow", {
277
+ startState: "View1",
278
+ View1: {
279
+ state_type: "VIEW",
280
+ ref: "foo",
281
+ transitions: {
282
+ Next: "View2",
283
+ },
284
+ },
285
+ View2: {
286
+ state_type: "VIEW",
287
+ ref: "bar",
288
+ transitions: {},
289
+ },
290
+ });
291
+
292
+ flow.hooks.skipTransition.tap("test", (curr) => curr !== undefined);
293
+ flow.start();
294
+ expect(flow.currentState!.name).toBe("View1");
295
+ flow.transition("Next");
296
+ expect(flow.currentState!.name).toBe("View1");
297
+ });
298
+
299
+ test("fails to transition if not started", () => {
300
+ const flow = new FlowInstance("flow", {
301
+ startState: "View1",
302
+ });
303
+
304
+ expect(() => flow.transition("foo")).toThrowError();
305
+ });
306
+
307
+ test("fails if no transition", () => {
308
+ const flow = new FlowInstance("flow", {
309
+ startState: "View1",
310
+ View1: {
311
+ state_type: "VIEW",
312
+ ref: "foo",
313
+ },
314
+ } as any);
315
+ flow.start();
316
+ expect(() => flow.transition("foo")).toThrowError();
317
+ });
318
+
319
+ test("fails if no startState points to unknown state", async () => {
320
+ const flow = new FlowInstance("flow", {
321
+ startState: "View2",
322
+ } as any);
323
+
324
+ await expect(flow.start()).rejects.toThrowError(
325
+ "No flow definition for: View2 was found.",
326
+ );
327
+ });
328
+
329
+ test("force transition", () => {
330
+ const flow = new FlowInstance("flow", {
331
+ startState: "View1",
332
+ View1: {
333
+ state_type: "VIEW",
334
+ ref: "foo",
335
+ transitions: {
336
+ Next: "Done1",
337
+ },
338
+ },
339
+ Done1: {
340
+ state_type: "END",
341
+ outcome: "foo-bar",
342
+ },
343
+ });
344
+
345
+ flow.start();
346
+ flow.hooks.skipTransition.tap("tst", () => true);
347
+ expect(flow.currentState?.name).toBe("View1");
348
+
349
+ // Transition without the force flag
350
+ flow.transition("Next");
351
+
352
+ // Should stay on the same page
353
+ expect(flow.currentState?.name).toBe("View1");
354
+
355
+ flow.transition("Next", { force: true });
356
+
357
+ // Forced transition ignore the skip hook
358
+ expect(flow.currentState?.name).toBe("Done1");
359
+ });
360
+
361
+ test("fails if transitioning to unknown state", () => {
362
+ const flow = new FlowInstance("flow", {
363
+ startState: "View1",
364
+ View1: {
365
+ state_type: "VIEW",
366
+ ref: "foo",
367
+ transitions: {
368
+ Next: "Done1",
369
+ },
370
+ },
371
+ Done1: {
372
+ outcome: "foo-bar",
373
+ },
374
+ } as any);
375
+
376
+ flow.start();
377
+
378
+ expect(flow.currentState?.name).toBe("View1");
379
+ flow.transition("Next");
380
+ expect(flow.currentState?.name).toBe("View1");
381
+ });
@@ -1,8 +1,8 @@
1
- import { SyncHook } from 'tapable-ts';
2
- import type { Navigation, NavigationFlowEndState } from '@player-ui/types';
3
- import type { Logger } from '../../logger';
4
- import type { NamedState, TransitionOptions } from './flow';
5
- import { FlowInstance } from './flow';
1
+ import { SyncHook } from "tapable-ts";
2
+ import type { Navigation, NavigationFlowEndState } from "@player-ui/types";
3
+ import type { Logger } from "../../logger";
4
+ import type { TransitionOptions } from "./flow";
5
+ import { FlowInstance } from "./flow";
6
6
 
7
7
  /** A manager for the navigation section of a Content blob */
8
8
  export class FlowController {
@@ -20,7 +20,7 @@ export class FlowController {
20
20
  options?: {
21
21
  /** A logger instance to use */
22
22
  logger?: Logger;
23
- }
23
+ },
24
24
  ) {
25
25
  this.navigation = navigation;
26
26
  this.navStack = [];
@@ -35,7 +35,7 @@ export class FlowController {
35
35
  /** Navigate to another state in the state-machine */
36
36
  public transition(stateTransition: string, options?: TransitionOptions) {
37
37
  if (this.current === undefined) {
38
- throw new Error('Not currently in a flow. Cannot transition.');
38
+ throw new Error("Not currently in a flow. Cannot transition.");
39
39
  }
40
40
 
41
41
  this.current.transition(stateTransition, options);
@@ -54,9 +54,9 @@ export class FlowController {
54
54
 
55
55
  const startFlow = this.navigation[startState];
56
56
 
57
- if (startFlow === null || typeof startFlow !== 'object') {
57
+ if (startFlow === null || typeof startFlow !== "object") {
58
58
  return Promise.reject(
59
- new Error(`Flow: ${startState} needs to be an object`)
59
+ new Error(`Flow: ${startState} needs to be an object`),
60
60
  );
61
61
  }
62
62
 
@@ -65,13 +65,13 @@ export class FlowController {
65
65
  const flow = new FlowInstance(startState, startFlow, { logger: this.log });
66
66
  this.addNewFlow(flow);
67
67
 
68
- flow.hooks.afterTransition.tap('flow-controller', (flowInstance) => {
69
- if (flowInstance.currentState?.value.state_type === 'FLOW') {
68
+ flow.hooks.afterTransition.tap("flow-controller", (flowInstance) => {
69
+ if (flowInstance.currentState?.value.state_type === "FLOW") {
70
70
  const subflowId = flowInstance.currentState?.value.ref;
71
71
  this.log?.debug(`Loading subflow ${subflowId}`);
72
72
  this.run(subflowId).then((subFlowEndState) => {
73
73
  this.log?.debug(
74
- `Subflow ended. Using outcome: ${subFlowEndState.outcome}`
74
+ `Subflow ended. Using outcome: ${subFlowEndState.outcome}`,
75
75
  );
76
76
  flowInstance.transition(subFlowEndState?.outcome);
77
77
  });
@@ -91,7 +91,7 @@ export class FlowController {
91
91
 
92
92
  public async start(): Promise<NavigationFlowEndState> {
93
93
  if (!this.navigation.BEGIN) {
94
- return Promise.reject(new Error('Must supply a BEGIN state'));
94
+ return Promise.reject(new Error("Must supply a BEGIN state"));
95
95
  }
96
96
 
97
97
  return this.run(this.navigation.BEGIN);