@reventlessdev/reventless-gwt 1.0.0-alpha.100

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 (184) hide show
  1. package/CHANGELOG.md +916 -0
  2. package/LICENSE +202 -0
  3. package/bin/reventless-dev.mjs +21 -0
  4. package/package.json +54 -0
  5. package/rescript.json +36 -0
  6. package/src/.gwtignore +10 -0
  7. package/src/Automation_GWT.res +207 -0
  8. package/src/Automation_GWT.res.mjs +160 -0
  9. package/src/Behavior_GWT.res +569 -0
  10. package/src/Behavior_GWT.res.mjs +523 -0
  11. package/src/Bind.res +29 -0
  12. package/src/Bind.res.mjs +22 -0
  13. package/src/BuildClassifier.res +147 -0
  14. package/src/BuildClassifier.res.mjs +136 -0
  15. package/src/Cancellation.res +37 -0
  16. package/src/Cancellation.res.mjs +46 -0
  17. package/src/ChildProcess.res +102 -0
  18. package/src/ChildProcess.res.mjs +107 -0
  19. package/src/Cli.res +905 -0
  20. package/src/Cli.res.mjs +952 -0
  21. package/src/Collector.res +212 -0
  22. package/src/Collector.res.mjs +218 -0
  23. package/src/ComponentMeta.res +77 -0
  24. package/src/ComponentMeta.res.mjs +91 -0
  25. package/src/ComponentScan.res +103 -0
  26. package/src/ComponentScan.res.mjs +105 -0
  27. package/src/Delegate_GWT.res +303 -0
  28. package/src/Delegate_GWT.res.mjs +372 -0
  29. package/src/Diff.res +91 -0
  30. package/src/Diff.res.mjs +98 -0
  31. package/src/Discovery.res +105 -0
  32. package/src/Discovery.res.mjs +99 -0
  33. package/src/DomainDeadCode.res +98 -0
  34. package/src/DomainDeadCode.res.mjs +72 -0
  35. package/src/DomainGraph.res +0 -0
  36. package/src/DomainGraph.res.mjs +0 -0
  37. package/src/EventMapping_GWT.res +29 -0
  38. package/src/EventMapping_GWT.res.mjs +44 -0
  39. package/src/Filter.res +45 -0
  40. package/src/Filter.res.mjs +47 -0
  41. package/src/Flow_GWT.res +664 -0
  42. package/src/Flow_GWT.res.mjs +604 -0
  43. package/src/FormatterHuman.res +109 -0
  44. package/src/FormatterHuman.res.mjs +115 -0
  45. package/src/FormatterJson.res +264 -0
  46. package/src/FormatterJson.res.mjs +322 -0
  47. package/src/FormatterJunit.res +93 -0
  48. package/src/FormatterJunit.res.mjs +90 -0
  49. package/src/FormatterTap.res +100 -0
  50. package/src/FormatterTap.res.mjs +131 -0
  51. package/src/FormatterVsCode.res +352 -0
  52. package/src/FormatterVsCode.res.mjs +546 -0
  53. package/src/Hint.res +119 -0
  54. package/src/Hint.res.mjs +124 -0
  55. package/src/InboundTranslation_GWT.res +83 -0
  56. package/src/InboundTranslation_GWT.res.mjs +94 -0
  57. package/src/JestBind.res +70 -0
  58. package/src/JestBind.res.mjs +63 -0
  59. package/src/Loader.res +24 -0
  60. package/src/Loader.res.mjs +23 -0
  61. package/src/LocalHost.res +119 -0
  62. package/src/LocalHost.res.mjs +99 -0
  63. package/src/Mapping_GWT.res +474 -0
  64. package/src/Mapping_GWT.res.mjs +322 -0
  65. package/src/MismatchRender.res +72 -0
  66. package/src/MismatchRender.res.mjs +127 -0
  67. package/src/MultiSourceProjection_GWT.res +316 -0
  68. package/src/MultiSourceProjection_GWT.res.mjs +354 -0
  69. package/src/OutboundTranslation_GWT.res +238 -0
  70. package/src/OutboundTranslation_GWT.res.mjs +186 -0
  71. package/src/Outcome.res +97 -0
  72. package/src/Outcome.res.mjs +93 -0
  73. package/src/PackageScan.res +80 -0
  74. package/src/PackageScan.res.mjs +81 -0
  75. package/src/PlatformRunner.res +181 -0
  76. package/src/PlatformRunner.res.mjs +176 -0
  77. package/src/PlatformScan.res +142 -0
  78. package/src/PlatformScan.res.mjs +132 -0
  79. package/src/ProcessManager.res +135 -0
  80. package/src/ProcessManager.res.mjs +119 -0
  81. package/src/Projection_GWT.res +312 -0
  82. package/src/Projection_GWT.res.mjs +332 -0
  83. package/src/Query_GWT.res +379 -0
  84. package/src/Query_GWT.res.mjs +367 -0
  85. package/src/RenderRescript.res +125 -0
  86. package/src/RenderRescript.res.mjs +141 -0
  87. package/src/RunWorker.res +24 -0
  88. package/src/RunWorker.res.mjs +16 -0
  89. package/src/RunnerTypes.res +68 -0
  90. package/src/RunnerTypes.res.mjs +60 -0
  91. package/src/SideEffect_GWT.res +157 -0
  92. package/src/SideEffect_GWT.res.mjs +113 -0
  93. package/src/StateChange/ExternalAddCategorySlice.res +25 -0
  94. package/src/StateChange/ExternalAddCategorySlice.res.mjs +34 -0
  95. package/src/StateChange/ExternalAddCategorySlice_Behavior.res +19 -0
  96. package/src/StateChange/ExternalAddCategorySlice_Behavior.res.mjs +46 -0
  97. package/src/StateChange/WithFixtures.res +22 -0
  98. package/src/StateChange/WithFixtures.res.mjs +31 -0
  99. package/src/StateChange/WithFixtures_Behavior.res +17 -0
  100. package/src/StateChange/WithFixtures_Behavior.res.mjs +37 -0
  101. package/src/StateChange/WithManualOpen.res +23 -0
  102. package/src/StateChange/WithManualOpen.res.mjs +31 -0
  103. package/src/StateChange/WithManualOpen_Behavior.res +17 -0
  104. package/src/StateChange/WithManualOpen_Behavior.res.mjs +37 -0
  105. package/src/StubRuntime.res +54 -0
  106. package/src/StubRuntime.res.mjs +55 -0
  107. package/src/TestFixtures.res +16 -0
  108. package/src/TestFixtures.res.mjs +34 -0
  109. package/src/Watch.res +108 -0
  110. package/src/Watch.res.mjs +85 -0
  111. package/src/WatcherProbe.res +56 -0
  112. package/src/WatcherProbe.res.mjs +59 -0
  113. package/src/Worker.res +24 -0
  114. package/src/Worker.res.mjs +9 -0
  115. package/tests/AutomationGwtTest.res +71 -0
  116. package/tests/AutomationGwtTest.res.mjs +189 -0
  117. package/tests/BuildClassifierTest.res +133 -0
  118. package/tests/BuildClassifierTest.res.mjs +176 -0
  119. package/tests/CliRunEntryTest.res +57 -0
  120. package/tests/CliRunEntryTest.res.mjs +65 -0
  121. package/tests/CliWatchScopeTest.res +62 -0
  122. package/tests/CliWatchScopeTest.res.mjs +95 -0
  123. package/tests/ComponentMetaTest.res +67 -0
  124. package/tests/ComponentMetaTest.res.mjs +71 -0
  125. package/tests/DelegateGwtTest.res +487 -0
  126. package/tests/DelegateGwtTest.res.mjs +891 -0
  127. package/tests/DiscoveryTest.res +42 -0
  128. package/tests/DiscoveryTest.res.mjs +43 -0
  129. package/tests/DomainDeadCodeTest.res +119 -0
  130. package/tests/DomainDeadCodeTest.res.mjs +158 -0
  131. package/tests/DomainGraphTest.res +448 -0
  132. package/tests/DomainGraphTest.res.mjs +505 -0
  133. package/tests/FlowAggregateGwtTest.res +171 -0
  134. package/tests/FlowAggregateGwtTest.res.mjs +273 -0
  135. package/tests/FlowCrossPluginGwtTest.res +340 -0
  136. package/tests/FlowCrossPluginGwtTest.res.mjs +798 -0
  137. package/tests/FlowGwtTest.res +222 -0
  138. package/tests/FlowGwtTest.res.mjs +433 -0
  139. package/tests/FormatterGoldenTest.res +125 -0
  140. package/tests/FormatterGoldenTest.res.mjs +162 -0
  141. package/tests/FormatterVsCodeMessageTest.res +41 -0
  142. package/tests/FormatterVsCodeMessageTest.res.mjs +42 -0
  143. package/tests/InboundTranslationGwtTest.res +34 -0
  144. package/tests/InboundTranslationGwtTest.res.mjs +99 -0
  145. package/tests/LocalHostIntegration.res +70 -0
  146. package/tests/LocalHostIntegration.res.mjs +64 -0
  147. package/tests/LocalHostTest.res +71 -0
  148. package/tests/LocalHostTest.res.mjs +63 -0
  149. package/tests/MappingGwtTest.res +350 -0
  150. package/tests/MappingGwtTest.res.mjs +596 -0
  151. package/tests/OutboundTranslationGwtTest.res +59 -0
  152. package/tests/OutboundTranslationGwtTest.res.mjs +139 -0
  153. package/tests/PlatformRunnerTest.res +66 -0
  154. package/tests/PlatformRunnerTest.res.mjs +85 -0
  155. package/tests/PlatformScanTest.res +61 -0
  156. package/tests/PlatformScanTest.res.mjs +56 -0
  157. package/tests/QueryGwtTest.res +161 -0
  158. package/tests/QueryGwtTest.res.mjs +314 -0
  159. package/tests/QueryResolverGwtTest.res +71 -0
  160. package/tests/QueryResolverGwtTest.res.mjs +136 -0
  161. package/tests/RunnerUnitTest.res +219 -0
  162. package/tests/RunnerUnitTest.res.mjs +244 -0
  163. package/tests/SideEffect/FakeOrderNotification.res +23 -0
  164. package/tests/SideEffect/FakeOrderNotification.res.mjs +36 -0
  165. package/tests/SideEffect/FakeOrderNotificationGwtTest.res +35 -0
  166. package/tests/SideEffect/FakeOrderNotificationGwtTest.res.mjs +73 -0
  167. package/tests/SideEffect/MockEmail.res +18 -0
  168. package/tests/SideEffect/MockEmail.res.mjs +47 -0
  169. package/tests/StateChange/ExternalAddCategorySlice_GWT.res +27 -0
  170. package/tests/StateChange/ExternalAddCategorySlice_GWT.res.mjs +70 -0
  171. package/tests/StateChange/WithFixtures_Fixtures.res +9 -0
  172. package/tests/StateChange/WithFixtures_Fixtures.res.mjs +20 -0
  173. package/tests/StateChange/WithFixtures_GWT.res +23 -0
  174. package/tests/StateChange/WithFixtures_GWT.res.mjs +56 -0
  175. package/tests/StateChange/WithManualOpen_Fixtures.res +7 -0
  176. package/tests/StateChange/WithManualOpen_Fixtures.res.mjs +20 -0
  177. package/tests/StateChange/WithManualOpen_GWT.res +17 -0
  178. package/tests/StateChange/WithManualOpen_GWT.res.mjs +56 -0
  179. package/tests/StateChangeSliceGwtTest.res +160 -0
  180. package/tests/StateChangeSliceGwtTest.res.mjs +280 -0
  181. package/tests/StateViewSliceGwtTest.res +64 -0
  182. package/tests/StateViewSliceGwtTest.res.mjs +161 -0
  183. package/tests/WatchDebounceTest.res +106 -0
  184. package/tests/WatchDebounceTest.res.mjs +128 -0
@@ -0,0 +1,125 @@
1
+ // Golden-output tests pinning the per-mismatch rendering of the three
2
+ // string-emitting formatters:
3
+ // - `Outcome.format` (used by the JUnit `<failure>` body)
4
+ // - `FormatterHuman.renderMismatch` (terminal output)
5
+ // - `FormatterTap.renderMismatchYaml` (TAP YAML diagnostic block)
6
+ //
7
+ // These three functions are exactly what the deferred `Outcome.mismatch`
8
+ // normalization (plan C4) will unify. Pinning their current output byte-for-byte
9
+ // lets that refactor prove it preserved Human/TAP rendering — and makes the
10
+ // deliberate JUnit change (it currently renders via raw `Outcome.format`, unlike
11
+ // the RenderRescript-based Human/TAP) explicit and reviewable rather than silent.
12
+ //
13
+ // The fixtures cover all 10 mismatch kinds — the full structural variety the
14
+ // normalization must preserve: array expected/actual (Events/QueryRows/
15
+ // PublishedActions), the Error()/Ok() wrapping with a `None` actual
16
+ // (ErrorMismatch), the `key` extra + option rendering (StateMismatch), a
17
+ // single-sided actual (NoEventExpected), plain-string (non-JSON) sides
18
+ // (TranslateError), the `(id, value)` pair rendering (TodoMismatch), a nested
19
+ // JSON value (AppendConditionMismatch), and the `Throw` stack special case.
20
+
21
+ open JestGlobals
22
+
23
+ let evA = JSON.parseOrThrow(`{"TAG":"ProductAdded","_0":{"productId":"p1","name":"Widget"}}`)
24
+ let evB = JSON.parseOrThrow(`{"TAG":"ProductRenamed","_0":{"productId":"p1","name":"Gadget"}}`)
25
+ let stateActive = JSON.parseOrThrow(`{"status":"active"}`)
26
+ let stateArchived = JSON.parseOrThrow(`{"status":"archived"}`)
27
+ let appendExpected = JSON.parseOrThrow(`{"query":[]}`)
28
+ let appendActual = JSON.parseOrThrow(`{"query":[{"eventTypes":["ProductAdded"]}]}`)
29
+
30
+ type golden = {
31
+ name: string,
32
+ mismatch: Outcome.mismatch,
33
+ format: string,
34
+ human: string,
35
+ tap: string,
36
+ }
37
+
38
+ let goldens: array<golden> = [
39
+ {
40
+ name: "EventsMismatch",
41
+ mismatch: EventsMismatch({expected: [evA], actual: [evB]}),
42
+ format: "EventsMismatch:\n expected: [{\n \"TAG\": \"ProductAdded\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Widget\"\n }\n}]\n actual: [{\n \"TAG\": \"ProductRenamed\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Gadget\"\n }\n}]",
43
+ human: " expected: [ProductAdded({productId: \"p1\", name: \"Widget\"})]\n actual: [ProductRenamed({productId: \"p1\", name: \"Gadget\"})]",
44
+ tap: " kind: \"EventsMismatch\"\n expected: \"[ProductAdded({productId: \\\"p1\\\", name: \\\"Widget\\\"})]\"\n actual: \"[ProductRenamed({productId: \\\"p1\\\", name: \\\"Gadget\\\"})]\"",
45
+ },
46
+ {
47
+ name: "ErrorMismatch",
48
+ mismatch: ErrorMismatch({
49
+ expected: JSON.String("CategoryAlreadyExists"),
50
+ actual: None,
51
+ actualEvents: [evA],
52
+ }),
53
+ format: "ErrorMismatch:\n expected error: \"CategoryAlreadyExists\"\n actual error: (none)\n actual events: [{\n \"TAG\": \"ProductAdded\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Widget\"\n }\n}]",
54
+ human: " expected: Error(\"CategoryAlreadyExists\")\n actual: Ok([ProductAdded({productId: \"p1\", name: \"Widget\"})])",
55
+ tap: " kind: \"ErrorMismatch\"\n expected: \"Error(\\\"CategoryAlreadyExists\\\")\"\n actual: \"Ok([ProductAdded({productId: \\\"p1\\\", name: \\\"Widget\\\"})])\"",
56
+ },
57
+ {
58
+ name: "StateMismatch",
59
+ mismatch: StateMismatch({key: "p1", expected: Some(stateActive), actual: Some(stateArchived)}),
60
+ format: "StateMismatch (key: p1):\n expected: {\n \"status\": \"active\"\n}\n actual: {\n \"status\": \"archived\"\n}",
61
+ human: " key: \"p1\"\n expected: {status: \"active\"}\n actual: {status: \"archived\"}",
62
+ tap: " kind: \"StateMismatch\"\n key: \"p1\"\n expected: \"{status: \\\"active\\\"}\"\n actual: \"{status: \\\"archived\\\"}\"",
63
+ },
64
+ {
65
+ name: "NoEventExpected",
66
+ mismatch: NoEventExpected({actual: [evA]}),
67
+ format: "NoEventExpected: expected no events, got:\n [{\n \"TAG\": \"ProductAdded\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Widget\"\n }\n}]",
68
+ human: " expected no events\n actual: [ProductAdded({productId: \"p1\", name: \"Widget\"})]",
69
+ tap: " kind: \"NoEventExpected\"\n actual: \"[ProductAdded({productId: \\\"p1\\\", name: \\\"Widget\\\"})]\"",
70
+ },
71
+ {
72
+ name: "TranslateError",
73
+ mismatch: TranslateError({expected: "boom", actual: Some("kaboom")}),
74
+ format: "TranslateError:\n expected: boom\n actual: kaboom",
75
+ human: " expected: boom\n actual: kaboom",
76
+ tap: " kind: \"TranslateError\"\n expected: \"boom\"\n actual: \"kaboom\"",
77
+ },
78
+ {
79
+ name: "Throw",
80
+ mismatch: Throw({error: "unexpected", stack: "at foo"}),
81
+ format: "Throw: unexpected\nat foo",
82
+ human: " error: unexpected\nat foo",
83
+ tap: " kind: \"Throw\"\n error: \"unexpected\"\n stack: \"at foo\"",
84
+ },
85
+ {
86
+ name: "TodoMismatch",
87
+ mismatch: TodoMismatch({expected: [("case-1", evA)], actual: [("case-2", evB)]}),
88
+ format: "TodoMismatch:\n expected: [(case-1, {\n \"TAG\": \"ProductAdded\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Widget\"\n }\n})]\n actual: [(case-2, {\n \"TAG\": \"ProductRenamed\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Gadget\"\n }\n})]",
89
+ human: " expected: [(case-1, ProductAdded({productId: \"p1\", name: \"Widget\"}))]\n actual: [(case-2, ProductRenamed({productId: \"p1\", name: \"Gadget\"}))]",
90
+ tap: " kind: \"TodoMismatch\"\n expected: \"[(case-1, ProductAdded({productId: \\\"p1\\\", name: \\\"Widget\\\"}))]\"\n actual: \"[(case-2, ProductRenamed({productId: \\\"p1\\\", name: \\\"Gadget\\\"}))]\"",
91
+ },
92
+ {
93
+ name: "AppendConditionMismatch",
94
+ mismatch: AppendConditionMismatch({expected: appendExpected, actual: appendActual}),
95
+ format: "AppendConditionMismatch:\n expected: {\n \"query\": []\n}\n actual: {\n \"query\": [\n {\n \"eventTypes\": [\n \"ProductAdded\"\n ]\n }\n ]\n}",
96
+ human: " expected: {query: []}\n actual: {query: [{eventTypes: [\"ProductAdded\"]}]}",
97
+ tap: " kind: \"AppendConditionMismatch\"\n expected: \"{query: []}\"\n actual: \"{query: [{eventTypes: [\\\"ProductAdded\\\"]}]}\"",
98
+ },
99
+ {
100
+ name: "QueryRowsMismatch",
101
+ mismatch: QueryRowsMismatch({expected: [evA], actual: [evB]}),
102
+ format: "QueryRowsMismatch:\n expected: [{\n \"TAG\": \"ProductAdded\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Widget\"\n }\n}]\n actual: [{\n \"TAG\": \"ProductRenamed\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Gadget\"\n }\n}]",
103
+ human: " expected: [ProductAdded({productId: \"p1\", name: \"Widget\"})]\n actual: [ProductRenamed({productId: \"p1\", name: \"Gadget\"})]",
104
+ tap: " kind: \"QueryRowsMismatch\"\n expected: \"[ProductAdded({productId: \\\"p1\\\", name: \\\"Widget\\\"})]\"\n actual: \"[ProductRenamed({productId: \\\"p1\\\", name: \\\"Gadget\\\"})]\"",
105
+ },
106
+ {
107
+ name: "PublishedActionsMismatch",
108
+ mismatch: PublishedActionsMismatch({expected: [evA], actual: [evB]}),
109
+ format: "PublishedActionsMismatch:\n expected: [{\n \"TAG\": \"ProductAdded\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Widget\"\n }\n}]\n actual: [{\n \"TAG\": \"ProductRenamed\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Gadget\"\n }\n}]",
110
+ human: " expected: [ProductAdded({productId: \"p1\", name: \"Widget\"})]\n actual: [ProductRenamed({productId: \"p1\", name: \"Gadget\"})]",
111
+ tap: " kind: \"PublishedActionsMismatch\"\n expected: \"[ProductAdded({productId: \\\"p1\\\", name: \\\"Widget\\\"})]\"\n actual: \"[ProductRenamed({productId: \\\"p1\\\", name: \\\"Gadget\\\"})]\"",
112
+ },
113
+ ]
114
+
115
+ describe("Formatter golden outputs (per-mismatch rendering)", () => {
116
+ goldens->Array.forEach(g => {
117
+ testSync(`${g.name} — Outcome.format`, () => expect(Outcome.format(g.mismatch))->toEqual(g.format))
118
+ testSync(`${g.name} — FormatterHuman.renderMismatch`, () =>
119
+ expect(FormatterHuman.renderMismatch(g.mismatch))->toEqual(g.human)
120
+ )
121
+ testSync(`${g.name} — FormatterTap.renderMismatchYaml`, () =>
122
+ expect(FormatterTap.renderMismatchYaml(g.mismatch))->toEqual(g.tap)
123
+ )
124
+ })
125
+ })
@@ -0,0 +1,162 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Outcome$ReventlessGwt from "../src/Outcome.res.mjs";
4
+ import * as FormatterTap$ReventlessGwt from "../src/FormatterTap.res.mjs";
5
+ import * as FormatterHuman$ReventlessGwt from "../src/FormatterHuman.res.mjs";
6
+
7
+ let evA = JSON.parse(`{"TAG":"ProductAdded","_0":{"productId":"p1","name":"Widget"}}`);
8
+
9
+ let evB = JSON.parse(`{"TAG":"ProductRenamed","_0":{"productId":"p1","name":"Gadget"}}`);
10
+
11
+ let stateActive = JSON.parse(`{"status":"active"}`);
12
+
13
+ let stateArchived = JSON.parse(`{"status":"archived"}`);
14
+
15
+ let appendExpected = JSON.parse(`{"query":[]}`);
16
+
17
+ let appendActual = JSON.parse(`{"query":[{"eventTypes":["ProductAdded"]}]}`);
18
+
19
+ let goldens = [
20
+ {
21
+ name: "EventsMismatch",
22
+ mismatch: {
23
+ TAG: "EventsMismatch",
24
+ expected: [evA],
25
+ actual: [evB]
26
+ },
27
+ format: "EventsMismatch:\n expected: [{\n \"TAG\": \"ProductAdded\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Widget\"\n }\n}]\n actual: [{\n \"TAG\": \"ProductRenamed\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Gadget\"\n }\n}]",
28
+ human: " expected: [ProductAdded({productId: \"p1\", name: \"Widget\"})]\n actual: [ProductRenamed({productId: \"p1\", name: \"Gadget\"})]",
29
+ tap: " kind: \"EventsMismatch\"\n expected: \"[ProductAdded({productId: \\\"p1\\\", name: \\\"Widget\\\"})]\"\n actual: \"[ProductRenamed({productId: \\\"p1\\\", name: \\\"Gadget\\\"})]\""
30
+ },
31
+ {
32
+ name: "ErrorMismatch",
33
+ mismatch: {
34
+ TAG: "ErrorMismatch",
35
+ expected: "CategoryAlreadyExists",
36
+ actual: undefined,
37
+ actualEvents: [evA]
38
+ },
39
+ format: "ErrorMismatch:\n expected error: \"CategoryAlreadyExists\"\n actual error: (none)\n actual events: [{\n \"TAG\": \"ProductAdded\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Widget\"\n }\n}]",
40
+ human: " expected: Error(\"CategoryAlreadyExists\")\n actual: Ok([ProductAdded({productId: \"p1\", name: \"Widget\"})])",
41
+ tap: " kind: \"ErrorMismatch\"\n expected: \"Error(\\\"CategoryAlreadyExists\\\")\"\n actual: \"Ok([ProductAdded({productId: \\\"p1\\\", name: \\\"Widget\\\"})])\""
42
+ },
43
+ {
44
+ name: "StateMismatch",
45
+ mismatch: {
46
+ TAG: "StateMismatch",
47
+ key: "p1",
48
+ expected: stateActive,
49
+ actual: stateArchived
50
+ },
51
+ format: "StateMismatch (key: p1):\n expected: {\n \"status\": \"active\"\n}\n actual: {\n \"status\": \"archived\"\n}",
52
+ human: " key: \"p1\"\n expected: {status: \"active\"}\n actual: {status: \"archived\"}",
53
+ tap: " kind: \"StateMismatch\"\n key: \"p1\"\n expected: \"{status: \\\"active\\\"}\"\n actual: \"{status: \\\"archived\\\"}\""
54
+ },
55
+ {
56
+ name: "NoEventExpected",
57
+ mismatch: {
58
+ TAG: "NoEventExpected",
59
+ actual: [evA]
60
+ },
61
+ format: "NoEventExpected: expected no events, got:\n [{\n \"TAG\": \"ProductAdded\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Widget\"\n }\n}]",
62
+ human: " expected no events\n actual: [ProductAdded({productId: \"p1\", name: \"Widget\"})]",
63
+ tap: " kind: \"NoEventExpected\"\n actual: \"[ProductAdded({productId: \\\"p1\\\", name: \\\"Widget\\\"})]\""
64
+ },
65
+ {
66
+ name: "TranslateError",
67
+ mismatch: {
68
+ TAG: "TranslateError",
69
+ expected: "boom",
70
+ actual: "kaboom"
71
+ },
72
+ format: "TranslateError:\n expected: boom\n actual: kaboom",
73
+ human: " expected: boom\n actual: kaboom",
74
+ tap: " kind: \"TranslateError\"\n expected: \"boom\"\n actual: \"kaboom\""
75
+ },
76
+ {
77
+ name: "Throw",
78
+ mismatch: {
79
+ TAG: "Throw",
80
+ error: "unexpected",
81
+ stack: "at foo"
82
+ },
83
+ format: "Throw: unexpected\nat foo",
84
+ human: " error: unexpected\nat foo",
85
+ tap: " kind: \"Throw\"\n error: \"unexpected\"\n stack: \"at foo\""
86
+ },
87
+ {
88
+ name: "TodoMismatch",
89
+ mismatch: {
90
+ TAG: "TodoMismatch",
91
+ expected: [[
92
+ "case-1",
93
+ evA
94
+ ]],
95
+ actual: [[
96
+ "case-2",
97
+ evB
98
+ ]]
99
+ },
100
+ format: "TodoMismatch:\n expected: [(case-1, {\n \"TAG\": \"ProductAdded\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Widget\"\n }\n})]\n actual: [(case-2, {\n \"TAG\": \"ProductRenamed\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Gadget\"\n }\n})]",
101
+ human: " expected: [(case-1, ProductAdded({productId: \"p1\", name: \"Widget\"}))]\n actual: [(case-2, ProductRenamed({productId: \"p1\", name: \"Gadget\"}))]",
102
+ tap: " kind: \"TodoMismatch\"\n expected: \"[(case-1, ProductAdded({productId: \\\"p1\\\", name: \\\"Widget\\\"}))]\"\n actual: \"[(case-2, ProductRenamed({productId: \\\"p1\\\", name: \\\"Gadget\\\"}))]\""
103
+ },
104
+ {
105
+ name: "AppendConditionMismatch",
106
+ mismatch: {
107
+ TAG: "AppendConditionMismatch",
108
+ expected: appendExpected,
109
+ actual: appendActual
110
+ },
111
+ format: "AppendConditionMismatch:\n expected: {\n \"query\": []\n}\n actual: {\n \"query\": [\n {\n \"eventTypes\": [\n \"ProductAdded\"\n ]\n }\n ]\n}",
112
+ human: " expected: {query: []}\n actual: {query: [{eventTypes: [\"ProductAdded\"]}]}",
113
+ tap: " kind: \"AppendConditionMismatch\"\n expected: \"{query: []}\"\n actual: \"{query: [{eventTypes: [\\\"ProductAdded\\\"]}]}\""
114
+ },
115
+ {
116
+ name: "QueryRowsMismatch",
117
+ mismatch: {
118
+ TAG: "QueryRowsMismatch",
119
+ expected: [evA],
120
+ actual: [evB]
121
+ },
122
+ format: "QueryRowsMismatch:\n expected: [{\n \"TAG\": \"ProductAdded\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Widget\"\n }\n}]\n actual: [{\n \"TAG\": \"ProductRenamed\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Gadget\"\n }\n}]",
123
+ human: " expected: [ProductAdded({productId: \"p1\", name: \"Widget\"})]\n actual: [ProductRenamed({productId: \"p1\", name: \"Gadget\"})]",
124
+ tap: " kind: \"QueryRowsMismatch\"\n expected: \"[ProductAdded({productId: \\\"p1\\\", name: \\\"Widget\\\"})]\"\n actual: \"[ProductRenamed({productId: \\\"p1\\\", name: \\\"Gadget\\\"})]\""
125
+ },
126
+ {
127
+ name: "PublishedActionsMismatch",
128
+ mismatch: {
129
+ TAG: "PublishedActionsMismatch",
130
+ expected: [evA],
131
+ actual: [evB]
132
+ },
133
+ format: "PublishedActionsMismatch:\n expected: [{\n \"TAG\": \"ProductAdded\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Widget\"\n }\n}]\n actual: [{\n \"TAG\": \"ProductRenamed\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Gadget\"\n }\n}]",
134
+ human: " expected: [ProductAdded({productId: \"p1\", name: \"Widget\"})]\n actual: [ProductRenamed({productId: \"p1\", name: \"Gadget\"})]",
135
+ tap: " kind: \"PublishedActionsMismatch\"\n expected: \"[ProductAdded({productId: \\\"p1\\\", name: \\\"Widget\\\"})]\"\n actual: \"[ProductRenamed({productId: \\\"p1\\\", name: \\\"Gadget\\\"})]\""
136
+ }
137
+ ];
138
+
139
+ globalThis.describe("Formatter golden outputs (per-mismatch rendering)", () => {
140
+ goldens.forEach(g => {
141
+ globalThis.test(g.name + ` — Outcome.format`, () => {
142
+ globalThis.expect(Outcome$ReventlessGwt.format(g.mismatch)).toEqual(g.format);
143
+ });
144
+ globalThis.test(g.name + ` — FormatterHuman.renderMismatch`, () => {
145
+ globalThis.expect(FormatterHuman$ReventlessGwt.renderMismatch(g.mismatch)).toEqual(g.human);
146
+ });
147
+ globalThis.test(g.name + ` — FormatterTap.renderMismatchYaml`, () => {
148
+ globalThis.expect(FormatterTap$ReventlessGwt.renderMismatchYaml(g.mismatch)).toEqual(g.tap);
149
+ });
150
+ });
151
+ });
152
+
153
+ export {
154
+ evA,
155
+ evB,
156
+ stateActive,
157
+ stateArchived,
158
+ appendExpected,
159
+ appendActual,
160
+ goldens,
161
+ }
162
+ /* evA Not a pure module */
@@ -0,0 +1,41 @@
1
+ // Unit test for FormatterVsCode.messagePayload — the failure payload the
2
+ // VS Code extension renders. Asserts the `kind` discriminator (the mismatch
3
+ // family) is present so a client can gate the apply-expected quick-fix.
4
+
5
+ open JestGlobals
6
+
7
+ let failingResult = (~mismatch): RunnerTypes.testResult => {
8
+ id: "t1",
9
+ name: "rejects when category already exists",
10
+ describePath: [],
11
+ status: RunnerTypes.Fail,
12
+ durationMs: 1.0,
13
+ location: None,
14
+ slice: None,
15
+ mismatch,
16
+ skipReason: None,
17
+ }
18
+
19
+ describe("FormatterVsCode.messagePayload", () => {
20
+ testPromise("includes the mismatch kind for an EventsMismatch", async () => {
21
+ let m = FormatterVsCode.messagePayload(
22
+ failingResult(~mismatch=Some(Outcome.EventsMismatch({expected: [], actual: []}))),
23
+ )
24
+ expect(m.kind)->toEqual(Some("EventsMismatch"))
25
+ })
26
+
27
+ testPromise("includes the mismatch kind for an ErrorMismatch", async () => {
28
+ let m = FormatterVsCode.messagePayload(
29
+ failingResult(
30
+ ~mismatch=Some(
31
+ Outcome.ErrorMismatch({
32
+ expected: JSON.String("CategoryAlreadyExists"),
33
+ actual: None,
34
+ actualEvents: [],
35
+ }),
36
+ ),
37
+ ),
38
+ )
39
+ expect(m.kind)->toEqual(Some("ErrorMismatch"))
40
+ })
41
+ })
@@ -0,0 +1,42 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as FormatterVsCode$ReventlessGwt from "../src/FormatterVsCode.res.mjs";
4
+
5
+ function failingResult(mismatch) {
6
+ return {
7
+ id: "t1",
8
+ name: "rejects when category already exists",
9
+ describePath: [],
10
+ status: "Fail",
11
+ durationMs: 1.0,
12
+ location: undefined,
13
+ slice: undefined,
14
+ mismatch: mismatch,
15
+ skipReason: undefined
16
+ };
17
+ }
18
+
19
+ globalThis.describe("FormatterVsCode.messagePayload", () => {
20
+ globalThis.test("includes the mismatch kind for an EventsMismatch", async () => {
21
+ let m = FormatterVsCode$ReventlessGwt.messagePayload(failingResult({
22
+ TAG: "EventsMismatch",
23
+ expected: [],
24
+ actual: []
25
+ }));
26
+ globalThis.expect(m.kind).toEqual("EventsMismatch");
27
+ });
28
+ globalThis.test("includes the mismatch kind for an ErrorMismatch", async () => {
29
+ let m = FormatterVsCode$ReventlessGwt.messagePayload(failingResult({
30
+ TAG: "ErrorMismatch",
31
+ expected: "CategoryAlreadyExists",
32
+ actual: undefined,
33
+ actualEvents: []
34
+ }));
35
+ globalThis.expect(m.kind).toEqual("ErrorMismatch");
36
+ });
37
+ });
38
+
39
+ export {
40
+ failingResult,
41
+ }
42
+ /* Not a pure module */
@@ -0,0 +1,34 @@
1
+ // Worked example for InboundTranslationSlice_GWT — a payment webhook that
2
+ // translates `status` into either a `ConfirmPayment` command or a
3
+ // translation error for unknown statuses.
4
+
5
+ @@reventless.gwt
6
+
7
+ module PaymentWebhookSlice = {
8
+ let name = "PaymentWebhook"
9
+
10
+ @schema
11
+ type externalInput = {paymentId: string, orderId: string, status: string}
12
+
13
+ @schema
14
+ type command = ConfirmPayment({orderId: string, paymentId: string})
15
+
16
+ let translate = input =>
17
+ switch input.status {
18
+ | "completed" =>
19
+ Ok([(input.orderId, ConfirmPayment({orderId: input.orderId, paymentId: input.paymentId}))])
20
+ | _ => Error("Unknown payment status: " ++ input.status)
21
+ }
22
+ }
23
+
24
+ describe("PaymentWebhook InboundTranslationSlice", () => {
25
+ test("completed status emits ConfirmPayment", () =>
26
+ whenInput({paymentId: "p1", orderId: "o1", status: "completed"})
27
+ ->thenCommand("o1", ConfirmPayment({orderId: "o1", paymentId: "p1"}))
28
+ )
29
+
30
+ test("unknown status surfaces translate error", () =>
31
+ whenInput({paymentId: "p1", orderId: "o1", status: "garbage"})
32
+ ->thenTranslateError("Unknown payment status: garbage")
33
+ )
34
+ })
@@ -0,0 +1,99 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as S from "sury/src/S.res.mjs";
4
+ import * as InboundTranslation_GWT$ReventlessGwt from "../src/InboundTranslation_GWT.res.mjs";
5
+
6
+ let externalInputSchema = S.schema(s => ({
7
+ paymentId: s.m(S.string),
8
+ orderId: s.m(S.string),
9
+ status: s.m(S.string)
10
+ }));
11
+
12
+ let commandSchema = S.schema(s => ({
13
+ TAG: "ConfirmPayment",
14
+ orderId: s.m(S.string),
15
+ paymentId: s.m(S.string)
16
+ }));
17
+
18
+ function translate(input) {
19
+ let match = input.status;
20
+ if (match === "completed") {
21
+ return {
22
+ TAG: "Ok",
23
+ _0: [[
24
+ input.orderId,
25
+ {
26
+ TAG: "ConfirmPayment",
27
+ orderId: input.orderId,
28
+ paymentId: input.paymentId
29
+ }
30
+ ]]
31
+ };
32
+ } else {
33
+ return {
34
+ TAG: "Error",
35
+ _0: "Unknown payment status: " + input.status
36
+ };
37
+ }
38
+ }
39
+
40
+ function commandAuthorization(param) {
41
+ return "AllowAuthenticated";
42
+ }
43
+
44
+ let PaymentWebhookSlice = {
45
+ name: "PaymentWebhook",
46
+ externalInputSchema: externalInputSchema,
47
+ commandSchema: commandSchema,
48
+ translate: translate,
49
+ commandAuthorization: commandAuthorization,
50
+ externalSystem: undefined
51
+ };
52
+
53
+ let include = InboundTranslation_GWT$ReventlessGwt.Make(PaymentWebhookSlice);
54
+
55
+ let describe = include.describe;
56
+
57
+ let test = include.test;
58
+
59
+ let whenInput = include.whenInput;
60
+
61
+ let thenCommand = include.thenCommand;
62
+
63
+ let thenTranslateError = include.thenTranslateError;
64
+
65
+ describe("PaymentWebhook InboundTranslationSlice", () => {
66
+ test("completed status emits ConfirmPayment", () => thenCommand(whenInput({
67
+ paymentId: "p1",
68
+ orderId: "o1",
69
+ status: "completed"
70
+ }), "o1", {
71
+ TAG: "ConfirmPayment",
72
+ orderId: "o1",
73
+ paymentId: "p1"
74
+ }));
75
+ test("unknown status surfaces translate error", () => thenTranslateError(whenInput({
76
+ paymentId: "p1",
77
+ orderId: "o1",
78
+ status: "garbage"
79
+ }), "Unknown payment status: garbage"));
80
+ });
81
+
82
+ let Spec = include.Spec;
83
+
84
+ let thenCommands = include.thenCommands;
85
+
86
+ let thenNoCommand = include.thenNoCommand;
87
+
88
+ export {
89
+ PaymentWebhookSlice,
90
+ Spec,
91
+ describe,
92
+ test,
93
+ whenInput,
94
+ thenCommands,
95
+ thenCommand,
96
+ thenNoCommand,
97
+ thenTranslateError,
98
+ }
99
+ /* externalInputSchema Not a pure module */
@@ -0,0 +1,70 @@
1
+ // Integration test for LocalHost.loadGraph — the cold-load path that instantiates
2
+ // reventless-local's Platform and builds the real example plugins against it.
3
+ //
4
+ // Runs under `node --test` (NOT Jest) on purpose: cold-loading the local platform
5
+ // under Jest's experimental-vm-modules hits the graphql/yoga CJS-in-ESM interop
6
+ // wall documented in tests/SplitApiFixtures.res. Plain node ESM is exactly the
7
+ // runtime the CLI host uses, and where the Phase 4.5 spike proved cold-load works
8
+ // (docs/analysis/reventless-vscode-domain-graph-design.md "Spike result"). It is
9
+ // named without the `Test` suffix so the Jest `testMatch` glob
10
+ // (`tests/**/*Test.res.mjs`) skips it — `pnpm run test:host` runs it via node. The
11
+ // pure helpers (name derivation, discovery) are the Jest test in tests/LocalHostTest.res.
12
+ //
13
+ // Prerequisite: reventless-local + the online-shop-aggregates examples must be
14
+ // compiled (`pnpm run build` at the repo root). Run: `pnpm run test:host`.
15
+
16
+ @module("node:test") external test: (string, unit => promise<unit>) => unit = "test"
17
+ @module("node:assert/strict") external equal: ('a, 'a) => unit = "equal"
18
+ @module("node:assert/strict") external deepEqual: ('a, 'a) => unit = "deepEqual"
19
+ @module("node:assert/strict") external ok: (bool, ~message: string=?) => unit = "ok"
20
+
21
+ @module("node:url") external fileURLToPath: string => string = "fileURLToPath"
22
+ @module("node:path") external dirname: string => string = "dirname"
23
+ @module("node:path") @variadic external join: array<string> => string = "join"
24
+
25
+ let here = dirname(fileURLToPath(%raw(`import.meta.url`)))
26
+ let repoRoot = join([here, "..", "..", ".."]) // reventless-gwt/test → repo root
27
+ let catalogDir = join([repoRoot, "examples", "online-shop-aggregates", "catalog"])
28
+ let orderingDir = join([repoRoot, "examples", "online-shop-aggregates", "ordering"])
29
+ let platformPath = join([repoRoot, "reventless", "reventless-local", "src", "Platform.res.mjs"])
30
+
31
+ let structureFor = (g: LocalHost.graph, name) =>
32
+ g.structures->Array.find(((n, _)) => n == name)->Option.map(((_, s)) => s)
33
+
34
+ test("loadGraph cold-loads structures from the real plugins", async () => {
35
+ let plugins = LocalHost.discover(~packageDirs=[catalogDir, orderingDir])
36
+ let g = await LocalHost.loadGraph(~platformModulePath=platformPath, ~plugins)
37
+
38
+ // structures: resolved plain records, plugin-namespaced event types.
39
+ deepEqual(g.structures->Array.map(((n, _)) => n), ["Catalog", "Ordering"])
40
+
41
+ let catalog = structureFor(g, "Catalog")->Option.getOrThrow
42
+ let category = catalog.aggregates->Array.find((a: Reventless.Plugin.writableDef) => a.name == "Category")->Option.getOrThrow
43
+ ok(category.producedEventTypes->Array.includes("Catalog.Added"))
44
+
45
+ let ordering = structureFor(g, "Ordering")->Option.getOrThrow
46
+ let order = ordering.aggregates->Array.find((a: Reventless.Plugin.writableDef) => a.name == "Order")->Option.getOrThrow
47
+ ok(order.producedEventTypes->Array.includes("Ordering.Placed"))
48
+
49
+ // extensionPoints (producer side): Catalog owns Catalog.Products, fed by the
50
+ // Product aggregate's internal events — so the EP's sourceEventTypes are a
51
+ // subset of the producer's producedEventTypes, the link the event graph draws.
52
+ let catalogEps = catalog.extensionPoints->Option.getOr([])
53
+ let productsEp =
54
+ catalogEps->Array.find((e: Reventless.Plugin.extensionPointDef) => e.name == "Catalog.Products")->Option.getOrThrow
55
+ ok(productsEp.delegateNames->Array.includes("Product"), ~message=productsEp.delegateNames->Array.join(","))
56
+ ok(
57
+ productsEp.sourceEventTypes->Array.includes("Catalog.Added"),
58
+ ~message=productsEp.sourceEventTypes->Array.join(","),
59
+ )
60
+ let product = catalog.aggregates->Array.find((a: Reventless.Plugin.writableDef) => a.name == "Product")->Option.getOrThrow
61
+ ok(productsEp.sourceEventTypes->Array.every(e => product.producedEventTypes->Array.includes(e)))
62
+ })
63
+
64
+ test("loadGraph is cold — a second call in the same process succeeds (cache-busting works)", async () => {
65
+ let plugins = LocalHost.discover(~packageDirs=[catalogDir])
66
+ let g1 = await LocalHost.loadGraph(~platformModulePath=platformPath, ~plugins)
67
+ let g2 = await LocalHost.loadGraph(~platformModulePath=platformPath, ~plugins)
68
+ equal(g1.structures->Array.length, 1)
69
+ equal(g2.structures->Array.length, 1)
70
+ })
@@ -0,0 +1,64 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Nodeurl from "node:url";
4
+ import * as Nodepath from "node:path";
5
+ import * as Nodetest from "node:test";
6
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
7
+ import * as Strict from "node:assert/strict";
8
+ import * as LocalHost$ReventlessGwt from "../src/LocalHost.res.mjs";
9
+
10
+ let here = Nodepath.dirname(Nodeurl.fileURLToPath(import.meta.url));
11
+
12
+ let repoRoot = Nodepath.join(here, "..", "..", "..");
13
+
14
+ let catalogDir = Nodepath.join(repoRoot, "examples", "online-shop-aggregates", "catalog");
15
+
16
+ let orderingDir = Nodepath.join(repoRoot, "examples", "online-shop-aggregates", "ordering");
17
+
18
+ let platformPath = Nodepath.join(repoRoot, "reventless", "reventless-local", "src", "Platform.res.mjs");
19
+
20
+ function structureFor(g, name) {
21
+ return Stdlib_Option.map(g.structures.find(param => param[0] === name), param => param[1]);
22
+ }
23
+
24
+ Nodetest.test("loadGraph cold-loads structures from the real plugins", async () => {
25
+ let plugins = LocalHost$ReventlessGwt.discover([
26
+ catalogDir,
27
+ orderingDir
28
+ ]);
29
+ let g = await LocalHost$ReventlessGwt.loadGraph(platformPath, plugins);
30
+ Strict.deepEqual(g.structures.map(param => param[0]), [
31
+ "Catalog",
32
+ "Ordering"
33
+ ]);
34
+ let catalog = Stdlib_Option.getOrThrow(structureFor(g, "Catalog"), undefined);
35
+ let category = Stdlib_Option.getOrThrow(catalog.aggregates.find(a => a.name === "Category"), undefined);
36
+ Strict.ok(category.producedEventTypes.includes("Catalog.Added"));
37
+ let ordering = Stdlib_Option.getOrThrow(structureFor(g, "Ordering"), undefined);
38
+ let order = Stdlib_Option.getOrThrow(ordering.aggregates.find(a => a.name === "Order"), undefined);
39
+ Strict.ok(order.producedEventTypes.includes("Ordering.Placed"));
40
+ let catalogEps = Stdlib_Option.getOr(catalog.extensionPoints, []);
41
+ let productsEp = Stdlib_Option.getOrThrow(catalogEps.find(e => e.name === "Catalog.Products"), undefined);
42
+ Strict.ok(productsEp.delegateNames.includes("Product"), productsEp.delegateNames.join(","));
43
+ Strict.ok(productsEp.sourceEventTypes.includes("Catalog.Added"), productsEp.sourceEventTypes.join(","));
44
+ let product = Stdlib_Option.getOrThrow(catalog.aggregates.find(a => a.name === "Product"), undefined);
45
+ Strict.ok(productsEp.sourceEventTypes.every(e => product.producedEventTypes.includes(e)));
46
+ });
47
+
48
+ Nodetest.test("loadGraph is cold — a second call in the same process succeeds (cache-busting works)", async () => {
49
+ let plugins = LocalHost$ReventlessGwt.discover([catalogDir]);
50
+ let g1 = await LocalHost$ReventlessGwt.loadGraph(platformPath, plugins);
51
+ let g2 = await LocalHost$ReventlessGwt.loadGraph(platformPath, plugins);
52
+ Strict.equal(g1.structures.length, 1);
53
+ Strict.equal(g2.structures.length, 1);
54
+ });
55
+
56
+ export {
57
+ here,
58
+ repoRoot,
59
+ catalogDir,
60
+ orderingDir,
61
+ platformPath,
62
+ structureFor,
63
+ }
64
+ /* here Not a pure module */