@magicborn/dialogue-forge 0.1.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 (241) hide show
  1. package/README.md +233 -0
  2. package/bin/dialogue-forge.js +78 -0
  3. package/demo/app/layout.tsx +36 -0
  4. package/demo/app/page.tsx +440 -0
  5. package/demo/components/ThemeSwitcher.tsx +611 -0
  6. package/demo/next.config.mjs +7 -0
  7. package/demo/package.json +29 -0
  8. package/demo/postcss.config.mjs +7 -0
  9. package/demo/public/logo.svg +1 -0
  10. package/demo/styles/globals.css +19 -0
  11. package/demo/tailwind.config.ts +90 -0
  12. package/demo/tsconfig.json +42 -0
  13. package/dist/components/ChoiceEdgeV2.d.ts +3 -0
  14. package/dist/components/ChoiceEdgeV2.js +103 -0
  15. package/dist/components/CodeBlock.d.ts +8 -0
  16. package/dist/components/CodeBlock.js +24 -0
  17. package/dist/components/ConditionAutocomplete.d.ts +14 -0
  18. package/dist/components/ConditionAutocomplete.js +284 -0
  19. package/dist/components/ConditionalNodeV2.d.ts +16 -0
  20. package/dist/components/ConditionalNodeV2.js +147 -0
  21. package/dist/components/DialogueEditorV2.d.ts +22 -0
  22. package/dist/components/DialogueEditorV2.js +1170 -0
  23. package/dist/components/EdgeIcon.d.ts +8 -0
  24. package/dist/components/EdgeIcon.js +13 -0
  25. package/dist/components/ExampleLoader.d.ts +11 -0
  26. package/dist/components/ExampleLoader.js +52 -0
  27. package/dist/components/ExampleLoaderButton.d.ts +15 -0
  28. package/dist/components/ExampleLoaderButton.js +102 -0
  29. package/dist/components/FlagManager.d.ts +11 -0
  30. package/dist/components/FlagManager.js +282 -0
  31. package/dist/components/FlagSelector.d.ts +11 -0
  32. package/dist/components/FlagSelector.js +235 -0
  33. package/dist/components/GuidePanel.d.ts +7 -0
  34. package/dist/components/GuidePanel.js +1176 -0
  35. package/dist/components/Minimap.d.ts +16 -0
  36. package/dist/components/Minimap.js +93 -0
  37. package/dist/components/NPCEdgeV2.d.ts +3 -0
  38. package/dist/components/NPCEdgeV2.js +104 -0
  39. package/dist/components/NPCNodeV2.d.ts +26 -0
  40. package/dist/components/NPCNodeV2.js +86 -0
  41. package/dist/components/NodeEditor.d.ts +18 -0
  42. package/dist/components/NodeEditor.js +1025 -0
  43. package/dist/components/PlayView.d.ts +12 -0
  44. package/dist/components/PlayView.js +307 -0
  45. package/dist/components/PlayerNodeV2.d.ts +16 -0
  46. package/dist/components/PlayerNodeV2.js +139 -0
  47. package/dist/components/ReactFlowPOC.d.ts +61 -0
  48. package/dist/components/ReactFlowPOC.js +312 -0
  49. package/dist/components/ScenePlayer.d.ts +18 -0
  50. package/dist/components/ScenePlayer.js +196 -0
  51. package/dist/components/YarnView.d.ts +9 -0
  52. package/dist/components/YarnView.js +45 -0
  53. package/dist/components/ZoomControls.d.ts +11 -0
  54. package/dist/components/ZoomControls.js +34 -0
  55. package/dist/esm/components/ChoiceEdgeV2.d.ts +3 -0
  56. package/dist/esm/components/ChoiceEdgeV2.js +67 -0
  57. package/dist/esm/components/CodeBlock.d.ts +8 -0
  58. package/dist/esm/components/CodeBlock.js +18 -0
  59. package/dist/esm/components/ConditionAutocomplete.d.ts +14 -0
  60. package/dist/esm/components/ConditionAutocomplete.js +248 -0
  61. package/dist/esm/components/ConditionalNodeV2.d.ts +16 -0
  62. package/dist/esm/components/ConditionalNodeV2.js +111 -0
  63. package/dist/esm/components/DialogueEditorV2.d.ts +22 -0
  64. package/dist/esm/components/DialogueEditorV2.js +1134 -0
  65. package/dist/esm/components/EdgeIcon.d.ts +8 -0
  66. package/dist/esm/components/EdgeIcon.js +7 -0
  67. package/dist/esm/components/ExampleLoader.d.ts +11 -0
  68. package/dist/esm/components/ExampleLoader.js +46 -0
  69. package/dist/esm/components/ExampleLoaderButton.d.ts +15 -0
  70. package/dist/esm/components/ExampleLoaderButton.js +66 -0
  71. package/dist/esm/components/FlagManager.d.ts +11 -0
  72. package/dist/esm/components/FlagManager.js +246 -0
  73. package/dist/esm/components/FlagSelector.d.ts +11 -0
  74. package/dist/esm/components/FlagSelector.js +199 -0
  75. package/dist/esm/components/GuidePanel.d.ts +7 -0
  76. package/dist/esm/components/GuidePanel.js +1140 -0
  77. package/dist/esm/components/Minimap.d.ts +16 -0
  78. package/dist/esm/components/Minimap.js +57 -0
  79. package/dist/esm/components/NPCEdgeV2.d.ts +3 -0
  80. package/dist/esm/components/NPCEdgeV2.js +68 -0
  81. package/dist/esm/components/NPCNodeV2.d.ts +26 -0
  82. package/dist/esm/components/NPCNodeV2.js +80 -0
  83. package/dist/esm/components/NodeEditor.d.ts +18 -0
  84. package/dist/esm/components/NodeEditor.js +989 -0
  85. package/dist/esm/components/PlayView.d.ts +12 -0
  86. package/dist/esm/components/PlayView.js +271 -0
  87. package/dist/esm/components/PlayerNodeV2.d.ts +16 -0
  88. package/dist/esm/components/PlayerNodeV2.js +103 -0
  89. package/dist/esm/components/ReactFlowPOC.d.ts +61 -0
  90. package/dist/esm/components/ReactFlowPOC.js +275 -0
  91. package/dist/esm/components/ScenePlayer.d.ts +18 -0
  92. package/dist/esm/components/ScenePlayer.js +160 -0
  93. package/dist/esm/components/YarnView.d.ts +9 -0
  94. package/dist/esm/components/YarnView.js +39 -0
  95. package/dist/esm/components/ZoomControls.d.ts +11 -0
  96. package/dist/esm/components/ZoomControls.js +28 -0
  97. package/dist/esm/examples/example-loader.d.ts +29 -0
  98. package/dist/esm/examples/example-loader.js +103 -0
  99. package/dist/esm/examples/examples-registry.d.ts +38 -0
  100. package/dist/esm/examples/examples-registry.js +153 -0
  101. package/dist/esm/examples/index.d.ts +26 -0
  102. package/dist/esm/examples/index.js +50 -0
  103. package/dist/esm/examples/legacy-examples.d.ts +9 -0
  104. package/dist/esm/examples/legacy-examples.js +814 -0
  105. package/dist/esm/examples/yarn-examples.d.ts +35 -0
  106. package/dist/esm/examples/yarn-examples.js +181 -0
  107. package/dist/esm/index.d.ts +21 -0
  108. package/dist/esm/index.js +26 -0
  109. package/dist/esm/lib/flag-manager.d.ts +21 -0
  110. package/dist/esm/lib/flag-manager.js +93 -0
  111. package/dist/esm/lib/yarn-converter/__tests__/round-trip.test.d.ts +1 -0
  112. package/dist/esm/lib/yarn-converter/__tests__/round-trip.test.js +169 -0
  113. package/dist/esm/lib/yarn-converter.d.ts +17 -0
  114. package/dist/esm/lib/yarn-converter.js +521 -0
  115. package/dist/esm/lib/yarn-runner/__tests__/condition-evaluator.test.d.ts +1 -0
  116. package/dist/esm/lib/yarn-runner/__tests__/condition-evaluator.test.js +171 -0
  117. package/dist/esm/lib/yarn-runner/__tests__/node-processor.test.d.ts +1 -0
  118. package/dist/esm/lib/yarn-runner/__tests__/node-processor.test.js +237 -0
  119. package/dist/esm/lib/yarn-runner/__tests__/variable-manager.test.d.ts +1 -0
  120. package/dist/esm/lib/yarn-runner/__tests__/variable-manager.test.js +106 -0
  121. package/dist/esm/lib/yarn-runner/condition-evaluator.d.ts +12 -0
  122. package/dist/esm/lib/yarn-runner/condition-evaluator.js +56 -0
  123. package/dist/esm/lib/yarn-runner/index.d.ts +12 -0
  124. package/dist/esm/lib/yarn-runner/index.js +11 -0
  125. package/dist/esm/lib/yarn-runner/node-processor.d.ts +18 -0
  126. package/dist/esm/lib/yarn-runner/node-processor.js +129 -0
  127. package/dist/esm/lib/yarn-runner/variable-manager.d.ts +51 -0
  128. package/dist/esm/lib/yarn-runner/variable-manager.js +120 -0
  129. package/dist/esm/lib/yarn-runner/variable-operations.d.ts +16 -0
  130. package/dist/esm/lib/yarn-runner/variable-operations.js +88 -0
  131. package/dist/esm/types/conditionals.d.ts +29 -0
  132. package/dist/esm/types/conditionals.js +1 -0
  133. package/dist/esm/types/constants.d.ts +59 -0
  134. package/dist/esm/types/constants.js +55 -0
  135. package/dist/esm/types/flags.d.ts +49 -0
  136. package/dist/esm/types/flags.js +49 -0
  137. package/dist/esm/types/game-state.d.ts +62 -0
  138. package/dist/esm/types/game-state.js +6 -0
  139. package/dist/esm/types/index.d.ts +77 -0
  140. package/dist/esm/types/index.js +1 -0
  141. package/dist/esm/utils/constants.d.ts +5 -0
  142. package/dist/esm/utils/constants.js +5 -0
  143. package/dist/esm/utils/feature-flags.d.ts +11 -0
  144. package/dist/esm/utils/feature-flags.js +11 -0
  145. package/dist/esm/utils/game-state-flattener.d.ts +41 -0
  146. package/dist/esm/utils/game-state-flattener.js +135 -0
  147. package/dist/esm/utils/layout/collision.d.ts +27 -0
  148. package/dist/esm/utils/layout/collision.js +74 -0
  149. package/dist/esm/utils/layout/index.d.ts +82 -0
  150. package/dist/esm/utils/layout/index.js +98 -0
  151. package/dist/esm/utils/layout/registry.d.ts +91 -0
  152. package/dist/esm/utils/layout/registry.js +148 -0
  153. package/dist/esm/utils/layout/strategies/dagre.d.ts +19 -0
  154. package/dist/esm/utils/layout/strategies/dagre.js +182 -0
  155. package/dist/esm/utils/layout/strategies/force.d.ts +21 -0
  156. package/dist/esm/utils/layout/strategies/force.js +178 -0
  157. package/dist/esm/utils/layout/strategies/grid.d.ts +17 -0
  158. package/dist/esm/utils/layout/strategies/grid.js +91 -0
  159. package/dist/esm/utils/layout/strategies/index.d.ts +8 -0
  160. package/dist/esm/utils/layout/strategies/index.js +8 -0
  161. package/dist/esm/utils/layout/types.d.ts +100 -0
  162. package/dist/esm/utils/layout/types.js +7 -0
  163. package/dist/esm/utils/layout.d.ts +9 -0
  164. package/dist/esm/utils/layout.js +17 -0
  165. package/dist/esm/utils/node-helpers.d.ts +7 -0
  166. package/dist/esm/utils/node-helpers.js +94 -0
  167. package/dist/esm/utils/reactflow-converter.d.ts +42 -0
  168. package/dist/esm/utils/reactflow-converter.js +217 -0
  169. package/dist/examples/example-loader.d.ts +29 -0
  170. package/dist/examples/example-loader.js +109 -0
  171. package/dist/examples/examples-registry.d.ts +38 -0
  172. package/dist/examples/examples-registry.js +160 -0
  173. package/dist/examples/index.d.ts +26 -0
  174. package/dist/examples/index.js +63 -0
  175. package/dist/examples/legacy-examples.d.ts +9 -0
  176. package/dist/examples/legacy-examples.js +817 -0
  177. package/dist/examples/yarn-examples.d.ts +35 -0
  178. package/dist/examples/yarn-examples.js +189 -0
  179. package/dist/index.d.ts +21 -0
  180. package/dist/index.js +66 -0
  181. package/dist/lib/flag-manager.d.ts +21 -0
  182. package/dist/lib/flag-manager.js +99 -0
  183. package/dist/lib/yarn-converter/__tests__/round-trip.test.d.ts +1 -0
  184. package/dist/lib/yarn-converter/__tests__/round-trip.test.js +171 -0
  185. package/dist/lib/yarn-converter.d.ts +17 -0
  186. package/dist/lib/yarn-converter.js +525 -0
  187. package/dist/lib/yarn-runner/__tests__/condition-evaluator.test.d.ts +1 -0
  188. package/dist/lib/yarn-runner/__tests__/condition-evaluator.test.js +173 -0
  189. package/dist/lib/yarn-runner/__tests__/node-processor.test.d.ts +1 -0
  190. package/dist/lib/yarn-runner/__tests__/node-processor.test.js +239 -0
  191. package/dist/lib/yarn-runner/__tests__/variable-manager.test.d.ts +1 -0
  192. package/dist/lib/yarn-runner/__tests__/variable-manager.test.js +108 -0
  193. package/dist/lib/yarn-runner/condition-evaluator.d.ts +12 -0
  194. package/dist/lib/yarn-runner/condition-evaluator.js +60 -0
  195. package/dist/lib/yarn-runner/index.d.ts +12 -0
  196. package/dist/lib/yarn-runner/index.js +21 -0
  197. package/dist/lib/yarn-runner/node-processor.d.ts +18 -0
  198. package/dist/lib/yarn-runner/node-processor.js +133 -0
  199. package/dist/lib/yarn-runner/variable-manager.d.ts +51 -0
  200. package/dist/lib/yarn-runner/variable-manager.js +124 -0
  201. package/dist/lib/yarn-runner/variable-operations.d.ts +16 -0
  202. package/dist/lib/yarn-runner/variable-operations.js +92 -0
  203. package/dist/types/conditionals.d.ts +29 -0
  204. package/dist/types/conditionals.js +2 -0
  205. package/dist/types/constants.d.ts +59 -0
  206. package/dist/types/constants.js +58 -0
  207. package/dist/types/flags.d.ts +49 -0
  208. package/dist/types/flags.js +52 -0
  209. package/dist/types/game-state.d.ts +62 -0
  210. package/dist/types/game-state.js +7 -0
  211. package/dist/types/index.d.ts +77 -0
  212. package/dist/types/index.js +2 -0
  213. package/dist/utils/constants.d.ts +5 -0
  214. package/dist/utils/constants.js +8 -0
  215. package/dist/utils/feature-flags.d.ts +11 -0
  216. package/dist/utils/feature-flags.js +14 -0
  217. package/dist/utils/game-state-flattener.d.ts +41 -0
  218. package/dist/utils/game-state-flattener.js +140 -0
  219. package/dist/utils/layout/collision.d.ts +27 -0
  220. package/dist/utils/layout/collision.js +77 -0
  221. package/dist/utils/layout/index.d.ts +82 -0
  222. package/dist/utils/layout/index.js +109 -0
  223. package/dist/utils/layout/registry.d.ts +91 -0
  224. package/dist/utils/layout/registry.js +151 -0
  225. package/dist/utils/layout/strategies/dagre.d.ts +19 -0
  226. package/dist/utils/layout/strategies/dagre.js +189 -0
  227. package/dist/utils/layout/strategies/force.d.ts +21 -0
  228. package/dist/utils/layout/strategies/force.js +182 -0
  229. package/dist/utils/layout/strategies/grid.d.ts +17 -0
  230. package/dist/utils/layout/strategies/grid.js +95 -0
  231. package/dist/utils/layout/strategies/index.d.ts +8 -0
  232. package/dist/utils/layout/strategies/index.js +14 -0
  233. package/dist/utils/layout/types.d.ts +100 -0
  234. package/dist/utils/layout/types.js +8 -0
  235. package/dist/utils/layout.d.ts +9 -0
  236. package/dist/utils/layout.js +25 -0
  237. package/dist/utils/node-helpers.d.ts +7 -0
  238. package/dist/utils/node-helpers.js +101 -0
  239. package/dist/utils/reactflow-converter.d.ts +42 -0
  240. package/dist/utils/reactflow-converter.js +223 -0
  241. package/package.json +70 -0
@@ -0,0 +1,171 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { evaluateCondition, evaluateConditions } from '../condition-evaluator';
3
+ import { CONDITION_OPERATOR } from '../../../types/constants';
4
+ describe('condition-evaluator', () => {
5
+ describe('evaluateCondition', () => {
6
+ const variables = {
7
+ flag1: true,
8
+ flag2: false,
9
+ flag3: 42,
10
+ flag4: 'hello',
11
+ flag5: 0,
12
+ };
13
+ const memoryFlags = new Set(['memory1', 'memory2']);
14
+ it('should evaluate IS_SET correctly', () => {
15
+ const condition = {
16
+ flag: 'flag1',
17
+ operator: CONDITION_OPERATOR.IS_SET,
18
+ };
19
+ expect(evaluateCondition(condition, variables, memoryFlags)).toBe(true);
20
+ const condition2 = {
21
+ flag: 'flag2',
22
+ operator: CONDITION_OPERATOR.IS_SET,
23
+ };
24
+ expect(evaluateCondition(condition2, variables, memoryFlags)).toBe(false);
25
+ const condition3 = {
26
+ flag: 'memory1',
27
+ operator: CONDITION_OPERATOR.IS_SET,
28
+ };
29
+ expect(evaluateCondition(condition3, variables, memoryFlags)).toBe(true);
30
+ });
31
+ it('should evaluate IS_NOT_SET correctly', () => {
32
+ const condition = {
33
+ flag: 'nonexistent',
34
+ operator: CONDITION_OPERATOR.IS_NOT_SET,
35
+ };
36
+ expect(evaluateCondition(condition, variables, memoryFlags)).toBe(true);
37
+ const condition2 = {
38
+ flag: 'flag1',
39
+ operator: CONDITION_OPERATOR.IS_NOT_SET,
40
+ };
41
+ expect(evaluateCondition(condition2, variables, memoryFlags)).toBe(false);
42
+ });
43
+ it('should evaluate EQUALS correctly', () => {
44
+ const condition = {
45
+ flag: 'flag3',
46
+ operator: CONDITION_OPERATOR.EQUALS,
47
+ value: 42,
48
+ };
49
+ expect(evaluateCondition(condition, variables, memoryFlags)).toBe(true);
50
+ const condition2 = {
51
+ flag: 'flag4',
52
+ operator: CONDITION_OPERATOR.EQUALS,
53
+ value: 'hello',
54
+ };
55
+ expect(evaluateCondition(condition2, variables, memoryFlags)).toBe(true);
56
+ const condition3 = {
57
+ flag: 'flag3',
58
+ operator: CONDITION_OPERATOR.EQUALS,
59
+ value: 10,
60
+ };
61
+ expect(evaluateCondition(condition3, variables, memoryFlags)).toBe(false);
62
+ });
63
+ it('should evaluate NOT_EQUALS correctly', () => {
64
+ const condition = {
65
+ flag: 'flag3',
66
+ operator: CONDITION_OPERATOR.NOT_EQUALS,
67
+ value: 10,
68
+ };
69
+ expect(evaluateCondition(condition, variables, memoryFlags)).toBe(true);
70
+ const condition2 = {
71
+ flag: 'flag3',
72
+ operator: CONDITION_OPERATOR.NOT_EQUALS,
73
+ value: 42,
74
+ };
75
+ expect(evaluateCondition(condition2, variables, memoryFlags)).toBe(false);
76
+ });
77
+ it('should evaluate GREATER_THAN correctly', () => {
78
+ const condition = {
79
+ flag: 'flag3',
80
+ operator: CONDITION_OPERATOR.GREATER_THAN,
81
+ value: 40,
82
+ };
83
+ expect(evaluateCondition(condition, variables, memoryFlags)).toBe(true);
84
+ const condition2 = {
85
+ flag: 'flag3',
86
+ operator: CONDITION_OPERATOR.GREATER_THAN,
87
+ value: 50,
88
+ };
89
+ expect(evaluateCondition(condition2, variables, memoryFlags)).toBe(false);
90
+ });
91
+ it('should evaluate LESS_THAN correctly', () => {
92
+ const condition = {
93
+ flag: 'flag3',
94
+ operator: CONDITION_OPERATOR.LESS_THAN,
95
+ value: 50,
96
+ };
97
+ expect(evaluateCondition(condition, variables, memoryFlags)).toBe(true);
98
+ const condition2 = {
99
+ flag: 'flag3',
100
+ operator: CONDITION_OPERATOR.LESS_THAN,
101
+ value: 40,
102
+ };
103
+ expect(evaluateCondition(condition2, variables, memoryFlags)).toBe(false);
104
+ });
105
+ it('should evaluate GREATER_EQUAL correctly', () => {
106
+ const condition = {
107
+ flag: 'flag3',
108
+ operator: CONDITION_OPERATOR.GREATER_EQUAL,
109
+ value: 42,
110
+ };
111
+ expect(evaluateCondition(condition, variables, memoryFlags)).toBe(true);
112
+ const condition2 = {
113
+ flag: 'flag3',
114
+ operator: CONDITION_OPERATOR.GREATER_EQUAL,
115
+ value: 41,
116
+ };
117
+ expect(evaluateCondition(condition2, variables, memoryFlags)).toBe(true);
118
+ });
119
+ it('should evaluate LESS_EQUAL correctly', () => {
120
+ const condition = {
121
+ flag: 'flag3',
122
+ operator: CONDITION_OPERATOR.LESS_EQUAL,
123
+ value: 42,
124
+ };
125
+ expect(evaluateCondition(condition, variables, memoryFlags)).toBe(true);
126
+ const condition2 = {
127
+ flag: 'flag3',
128
+ operator: CONDITION_OPERATOR.LESS_EQUAL,
129
+ value: 43,
130
+ };
131
+ expect(evaluateCondition(condition2, variables, memoryFlags)).toBe(true);
132
+ });
133
+ it('should treat undefined as 0 for numeric comparisons', () => {
134
+ const condition = {
135
+ flag: 'nonexistent',
136
+ operator: CONDITION_OPERATOR.GREATER_THAN,
137
+ value: -1,
138
+ };
139
+ expect(evaluateCondition(condition, variables, memoryFlags)).toBe(true);
140
+ const condition2 = {
141
+ flag: 'nonexistent',
142
+ operator: CONDITION_OPERATOR.LESS_THAN,
143
+ value: 1,
144
+ };
145
+ expect(evaluateCondition(condition2, variables, memoryFlags)).toBe(true);
146
+ });
147
+ });
148
+ describe('evaluateConditions', () => {
149
+ const variables = {
150
+ flag1: true,
151
+ flag2: 42,
152
+ };
153
+ it('should return true when all conditions are true', () => {
154
+ const conditions = [
155
+ { flag: 'flag1', operator: CONDITION_OPERATOR.IS_SET },
156
+ { flag: 'flag2', operator: CONDITION_OPERATOR.EQUALS, value: 42 },
157
+ ];
158
+ expect(evaluateConditions(conditions, variables)).toBe(true);
159
+ });
160
+ it('should return false when any condition is false', () => {
161
+ const conditions = [
162
+ { flag: 'flag1', operator: CONDITION_OPERATOR.IS_SET },
163
+ { flag: 'flag2', operator: CONDITION_OPERATOR.EQUALS, value: 10 },
164
+ ];
165
+ expect(evaluateConditions(conditions, variables)).toBe(false);
166
+ });
167
+ it('should return true for empty conditions array', () => {
168
+ expect(evaluateConditions([], variables)).toBe(true);
169
+ });
170
+ });
171
+ });
@@ -0,0 +1,237 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { processNode, isValidNextNode } from '../node-processor';
3
+ import { VariableManager } from '../variable-manager';
4
+ describe('node-processor', () => {
5
+ let variableManager;
6
+ let availableNodes;
7
+ beforeEach(() => {
8
+ variableManager = new VariableManager({ flag1: true, flag2: 42 });
9
+ availableNodes = {
10
+ node1: {
11
+ id: 'node1',
12
+ type: 'npc',
13
+ content: 'Hello',
14
+ x: 0,
15
+ y: 0,
16
+ },
17
+ node2: {
18
+ id: 'node2',
19
+ type: 'npc',
20
+ content: 'World',
21
+ x: 0,
22
+ y: 0,
23
+ },
24
+ };
25
+ });
26
+ describe('processNode', () => {
27
+ describe('NPC nodes', () => {
28
+ it('should process simple NPC node', () => {
29
+ const node = {
30
+ id: 'test',
31
+ type: 'npc',
32
+ content: 'Hello world',
33
+ speaker: 'NPC',
34
+ nextNodeId: 'node1',
35
+ x: 0,
36
+ y: 0,
37
+ };
38
+ const result = processNode(node, variableManager);
39
+ expect(result.content).toBe('Hello world');
40
+ expect(result.speaker).toBe('NPC');
41
+ expect(result.nextNodeId).toBe('node1');
42
+ expect(result.isEnd).toBe(false);
43
+ expect(result.isPlayerChoice).toBe(false);
44
+ });
45
+ it('should mark as end when no nextNodeId', () => {
46
+ const node = {
47
+ id: 'test',
48
+ type: 'npc',
49
+ content: 'End',
50
+ x: 0,
51
+ y: 0,
52
+ };
53
+ const result = processNode(node, variableManager);
54
+ expect(result.isEnd).toBe(true);
55
+ expect(result.nextNodeId).toBeUndefined();
56
+ });
57
+ });
58
+ describe('Player nodes', () => {
59
+ it('should return choices for player node', () => {
60
+ const node = {
61
+ id: 'test',
62
+ type: 'player',
63
+ content: '',
64
+ choices: [
65
+ {
66
+ id: 'c1',
67
+ text: 'Choice 1',
68
+ nextNodeId: 'node1',
69
+ },
70
+ {
71
+ id: 'c2',
72
+ text: 'Choice 2',
73
+ nextNodeId: 'node2',
74
+ },
75
+ ],
76
+ x: 0,
77
+ y: 0,
78
+ };
79
+ const result = processNode(node, variableManager);
80
+ expect(result.isPlayerChoice).toBe(true);
81
+ expect(result.choices).toHaveLength(2);
82
+ expect(result.choices?.[0].text).toBe('Choice 1');
83
+ });
84
+ it('should filter choices based on conditions', () => {
85
+ const node = {
86
+ id: 'test',
87
+ type: 'player',
88
+ content: '',
89
+ choices: [
90
+ {
91
+ id: 'c1',
92
+ text: 'Choice 1',
93
+ nextNodeId: 'node1',
94
+ conditions: [
95
+ { flag: 'flag1', operator: 'is_set' },
96
+ ],
97
+ },
98
+ {
99
+ id: 'c2',
100
+ text: 'Choice 2',
101
+ nextNodeId: 'node2',
102
+ conditions: [
103
+ { flag: 'flag3', operator: 'is_set' },
104
+ ],
105
+ },
106
+ ],
107
+ x: 0,
108
+ y: 0,
109
+ };
110
+ const result = processNode(node, variableManager);
111
+ expect(result.choices).toHaveLength(1);
112
+ expect(result.choices?.[0].text).toBe('Choice 1');
113
+ });
114
+ });
115
+ describe('Conditional nodes', () => {
116
+ it('should process conditional node with matching if block', () => {
117
+ const node = {
118
+ id: 'test',
119
+ type: 'conditional',
120
+ content: '',
121
+ conditionalBlocks: [
122
+ {
123
+ id: 'b1',
124
+ type: 'if',
125
+ condition: [{ flag: 'flag1', operator: 'is_set' }],
126
+ content: 'If content',
127
+ speaker: 'Speaker',
128
+ nextNodeId: 'node1',
129
+ },
130
+ {
131
+ id: 'b2',
132
+ type: 'else',
133
+ content: 'Else content',
134
+ },
135
+ ],
136
+ x: 0,
137
+ y: 0,
138
+ };
139
+ const result = processNode(node, variableManager);
140
+ expect(result.content).toBe('If content');
141
+ expect(result.speaker).toBe('Speaker');
142
+ expect(result.nextNodeId).toBe('node1');
143
+ });
144
+ it('should process conditional node with matching elseif block', () => {
145
+ const node = {
146
+ id: 'test',
147
+ type: 'conditional',
148
+ content: '',
149
+ conditionalBlocks: [
150
+ {
151
+ id: 'b1',
152
+ type: 'if',
153
+ condition: [{ flag: 'flag3', operator: 'is_set' }],
154
+ content: 'If content',
155
+ },
156
+ {
157
+ id: 'b2',
158
+ type: 'elseif',
159
+ condition: [{ flag: 'flag1', operator: 'is_set' }],
160
+ content: 'Elseif content',
161
+ },
162
+ {
163
+ id: 'b3',
164
+ type: 'else',
165
+ content: 'Else content',
166
+ },
167
+ ],
168
+ x: 0,
169
+ y: 0,
170
+ };
171
+ const result = processNode(node, variableManager);
172
+ expect(result.content).toBe('Elseif content');
173
+ });
174
+ it('should process conditional node with else block when no conditions match', () => {
175
+ const node = {
176
+ id: 'test',
177
+ type: 'conditional',
178
+ content: '',
179
+ conditionalBlocks: [
180
+ {
181
+ id: 'b1',
182
+ type: 'if',
183
+ condition: [{ flag: 'flag3', operator: 'is_set' }],
184
+ content: 'If content',
185
+ },
186
+ {
187
+ id: 'b2',
188
+ type: 'else',
189
+ content: 'Else content',
190
+ },
191
+ ],
192
+ x: 0,
193
+ y: 0,
194
+ };
195
+ const result = processNode(node, variableManager);
196
+ expect(result.content).toBe('Else content');
197
+ });
198
+ it('should return end when no block matches', () => {
199
+ const node = {
200
+ id: 'test',
201
+ type: 'conditional',
202
+ content: '',
203
+ conditionalBlocks: [
204
+ {
205
+ id: 'b1',
206
+ type: 'if',
207
+ condition: [{ flag: 'flag3', operator: 'is_set' }],
208
+ content: 'If content',
209
+ },
210
+ ],
211
+ x: 0,
212
+ y: 0,
213
+ };
214
+ const result = processNode(node, variableManager);
215
+ expect(result.isEnd).toBe(true);
216
+ expect(result.content).toBe('');
217
+ });
218
+ });
219
+ });
220
+ describe('isValidNextNode', () => {
221
+ it('should return true for valid node ID', () => {
222
+ expect(isValidNextNode('node1', availableNodes)).toBe(true);
223
+ });
224
+ it('should return false for undefined', () => {
225
+ expect(isValidNextNode(undefined, availableNodes)).toBe(false);
226
+ });
227
+ it('should return false for empty string', () => {
228
+ expect(isValidNextNode('', availableNodes)).toBe(false);
229
+ });
230
+ it('should return false for whitespace-only string', () => {
231
+ expect(isValidNextNode(' ', availableNodes)).toBe(false);
232
+ });
233
+ it('should return false for non-existent node', () => {
234
+ expect(isValidNextNode('nonexistent', availableNodes)).toBe(false);
235
+ });
236
+ });
237
+ });
@@ -0,0 +1,106 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { VariableManager } from '../variable-manager';
3
+ describe('VariableManager', () => {
4
+ let manager;
5
+ beforeEach(() => {
6
+ manager = new VariableManager();
7
+ });
8
+ describe('constructor', () => {
9
+ it('should initialize with empty state by default', () => {
10
+ expect(manager.get('test')).toBeUndefined();
11
+ expect(manager.getAllMemoryFlags().size).toBe(0);
12
+ });
13
+ it('should initialize with provided variables', () => {
14
+ const vars = { flag1: true, flag2: 42 };
15
+ const manager2 = new VariableManager(vars);
16
+ expect(manager2.get('flag1')).toBe(true);
17
+ expect(manager2.get('flag2')).toBe(42);
18
+ });
19
+ it('should initialize with provided memory flags', () => {
20
+ const memoryFlags = new Set(['mem1', 'mem2']);
21
+ const manager2 = new VariableManager(undefined, memoryFlags);
22
+ expect(manager2.hasMemoryFlag('mem1')).toBe(true);
23
+ expect(manager2.hasMemoryFlag('mem2')).toBe(true);
24
+ });
25
+ });
26
+ describe('set and get', () => {
27
+ it('should set and get boolean values', () => {
28
+ manager.set('flag1', true);
29
+ expect(manager.get('flag1')).toBe(true);
30
+ });
31
+ it('should set and get number values', () => {
32
+ manager.set('flag2', 42);
33
+ expect(manager.get('flag2')).toBe(42);
34
+ });
35
+ it('should set and get string values', () => {
36
+ manager.set('flag3', 'hello');
37
+ expect(manager.get('flag3')).toBe('hello');
38
+ });
39
+ it('should return undefined for unset variables', () => {
40
+ expect(manager.get('nonexistent')).toBeUndefined();
41
+ });
42
+ });
43
+ describe('memory flags', () => {
44
+ it('should add memory flags', () => {
45
+ manager.addMemoryFlag('mem1');
46
+ expect(manager.hasMemoryFlag('mem1')).toBe(true);
47
+ expect(manager.get('mem1')).toBe(true);
48
+ });
49
+ it('should remove memory flags', () => {
50
+ manager.addMemoryFlag('mem1');
51
+ manager.removeMemoryFlag('mem1');
52
+ expect(manager.hasMemoryFlag('mem1')).toBe(false);
53
+ });
54
+ it('should return memory flags as true when getting', () => {
55
+ manager.addMemoryFlag('mem1');
56
+ expect(manager.get('mem1')).toBe(true);
57
+ });
58
+ });
59
+ describe('getAllVariables', () => {
60
+ it('should return a copy of all variables', () => {
61
+ manager.set('flag1', true);
62
+ manager.set('flag2', 42);
63
+ const vars = manager.getAllVariables();
64
+ expect(vars).toEqual({ flag1: true, flag2: 42 });
65
+ // Should be a copy, not a reference
66
+ vars.flag1 = false;
67
+ expect(manager.get('flag1')).toBe(true);
68
+ });
69
+ });
70
+ describe('getAllMemoryFlags', () => {
71
+ it('should return a copy of memory flags', () => {
72
+ manager.addMemoryFlag('mem1');
73
+ manager.addMemoryFlag('mem2');
74
+ const flags = manager.getAllMemoryFlags();
75
+ expect(flags.has('mem1')).toBe(true);
76
+ expect(flags.has('mem2')).toBe(true);
77
+ // Should be a copy
78
+ flags.delete('mem1');
79
+ expect(manager.hasMemoryFlag('mem1')).toBe(true);
80
+ });
81
+ });
82
+ describe('clearMemoryFlags', () => {
83
+ it('should clear all memory flags', () => {
84
+ manager.addMemoryFlag('mem1');
85
+ manager.addMemoryFlag('mem2');
86
+ manager.clearMemoryFlags();
87
+ expect(manager.getAllMemoryFlags().size).toBe(0);
88
+ });
89
+ });
90
+ describe('reset', () => {
91
+ it('should reset to initial state', () => {
92
+ manager.set('flag1', true);
93
+ manager.addMemoryFlag('mem1');
94
+ manager.reset();
95
+ expect(manager.get('flag1')).toBeUndefined();
96
+ expect(manager.hasMemoryFlag('mem1')).toBe(false);
97
+ });
98
+ it('should reset to provided state', () => {
99
+ manager.set('flag1', true);
100
+ manager.reset({ flag2: 42 }, new Set(['mem2']));
101
+ expect(manager.get('flag1')).toBeUndefined();
102
+ expect(manager.get('flag2')).toBe(42);
103
+ expect(manager.hasMemoryFlag('mem2')).toBe(true);
104
+ });
105
+ });
106
+ });
@@ -0,0 +1,12 @@
1
+ import { Condition } from '../../types';
2
+ export interface VariableState {
3
+ [key: string]: boolean | number | string | undefined;
4
+ }
5
+ /**
6
+ * Evaluates a single condition against variable state
7
+ */
8
+ export declare function evaluateCondition(condition: Condition, variables: VariableState, memoryFlags?: Set<string>): boolean;
9
+ /**
10
+ * Evaluates multiple conditions with AND logic
11
+ */
12
+ export declare function evaluateConditions(conditions: Condition[], variables: VariableState, memoryFlags?: Set<string>): boolean;
@@ -0,0 +1,56 @@
1
+ import { CONDITION_OPERATOR } from '../../types/constants';
2
+ /**
3
+ * Evaluates a single condition against variable state
4
+ */
5
+ export function evaluateCondition(condition, variables, memoryFlags) {
6
+ let value = variables[condition.flag];
7
+ // If not in variables, check memoryFlags (dialogue flags)
8
+ if (value === undefined && memoryFlags) {
9
+ value = memoryFlags.has(condition.flag) ? true : undefined;
10
+ }
11
+ // For numeric comparisons, treat undefined as 0
12
+ const numericOperators = [
13
+ CONDITION_OPERATOR.GREATER_THAN,
14
+ CONDITION_OPERATOR.LESS_THAN,
15
+ CONDITION_OPERATOR.GREATER_EQUAL,
16
+ CONDITION_OPERATOR.LESS_EQUAL
17
+ ];
18
+ const isNumericComparison = numericOperators.includes(condition.operator);
19
+ if (isNumericComparison && value === undefined) {
20
+ value = 0;
21
+ }
22
+ switch (condition.operator) {
23
+ case CONDITION_OPERATOR.IS_SET:
24
+ return value !== undefined && value !== false && value !== 0 && value !== '';
25
+ case CONDITION_OPERATOR.IS_NOT_SET:
26
+ return value === undefined || value === false || value === 0 || value === '';
27
+ case CONDITION_OPERATOR.EQUALS:
28
+ return value === condition.value;
29
+ case CONDITION_OPERATOR.NOT_EQUALS:
30
+ return value !== condition.value;
31
+ case CONDITION_OPERATOR.GREATER_THAN:
32
+ const numValueGT = typeof value === 'number' ? value : (typeof value === 'string' ? parseFloat(value) : 0);
33
+ const numCondGT = typeof condition.value === 'number' ? condition.value : parseFloat(String(condition.value));
34
+ return !isNaN(numValueGT) && !isNaN(numCondGT) && numValueGT > numCondGT;
35
+ case CONDITION_OPERATOR.LESS_THAN:
36
+ const numValueLT = typeof value === 'number' ? value : (typeof value === 'string' ? parseFloat(value) : 0);
37
+ const numCondLT = typeof condition.value === 'number' ? condition.value : parseFloat(String(condition.value));
38
+ return !isNaN(numValueLT) && !isNaN(numCondLT) && numValueLT < numCondLT;
39
+ case CONDITION_OPERATOR.GREATER_EQUAL:
40
+ const numValueGE = typeof value === 'number' ? value : (typeof value === 'string' ? parseFloat(value) : 0);
41
+ const numCondGE = typeof condition.value === 'number' ? condition.value : parseFloat(String(condition.value));
42
+ return !isNaN(numValueGE) && !isNaN(numCondGE) && numValueGE >= numCondGE;
43
+ case CONDITION_OPERATOR.LESS_EQUAL:
44
+ const numValueLE = typeof value === 'number' ? value : (typeof value === 'string' ? parseFloat(value) : 0);
45
+ const numCondLE = typeof condition.value === 'number' ? condition.value : parseFloat(String(condition.value));
46
+ return !isNaN(numValueLE) && !isNaN(numCondLE) && numValueLE <= numCondLE;
47
+ default:
48
+ return true;
49
+ }
50
+ }
51
+ /**
52
+ * Evaluates multiple conditions with AND logic
53
+ */
54
+ export function evaluateConditions(conditions, variables, memoryFlags) {
55
+ return conditions.every(cond => evaluateCondition(cond, variables, memoryFlags));
56
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Yarn Spinner Runner
3
+ *
4
+ * Modular execution engine for running Yarn Spinner dialogue trees.
5
+ * This module provides the core logic for processing nodes, evaluating conditions,
6
+ * and managing state, separate from the UI components.
7
+ */
8
+ export { VariableManager } from './variable-manager';
9
+ export { evaluateCondition, evaluateConditions } from './condition-evaluator';
10
+ export { processNode, isValidNextNode, ProcessedNodeResult } from './node-processor';
11
+ export { executeVariableOperation, processVariableOperationsInContent } from './variable-operations';
12
+ export type { VariableState } from './condition-evaluator';
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Yarn Spinner Runner
3
+ *
4
+ * Modular execution engine for running Yarn Spinner dialogue trees.
5
+ * This module provides the core logic for processing nodes, evaluating conditions,
6
+ * and managing state, separate from the UI components.
7
+ */
8
+ export { VariableManager } from './variable-manager';
9
+ export { evaluateCondition, evaluateConditions } from './condition-evaluator';
10
+ export { processNode, isValidNextNode } from './node-processor';
11
+ export { executeVariableOperation, processVariableOperationsInContent } from './variable-operations';
@@ -0,0 +1,18 @@
1
+ import { DialogueNode, Choice } from '../../types';
2
+ import { VariableManager } from './variable-manager';
3
+ export interface ProcessedNodeResult {
4
+ content: string;
5
+ speaker?: string;
6
+ nextNodeId?: string;
7
+ isEnd: boolean;
8
+ isPlayerChoice: boolean;
9
+ choices?: Choice[];
10
+ }
11
+ /**
12
+ * Processes a dialogue node and returns the result
13
+ */
14
+ export declare function processNode(node: DialogueNode, variableManager: VariableManager): ProcessedNodeResult;
15
+ /**
16
+ * Validates that a nextNodeId exists and is valid
17
+ */
18
+ export declare function isValidNextNode(nextNodeId: string | undefined, availableNodes: Record<string, DialogueNode>): boolean;