@reventlessdev/reventless-gwt 1.0.0-alpha.74

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 (172) hide show
  1. package/CHANGELOG.md +698 -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 +603 -0
  10. package/src/Behavior_GWT.res.mjs +426 -0
  11. package/src/Bind.res +29 -0
  12. package/src/Bind.res.mjs +22 -0
  13. package/src/BuildClassifier.res +137 -0
  14. package/src/BuildClassifier.res.mjs +136 -0
  15. package/src/Cancellation.res +31 -0
  16. package/src/Cancellation.res.mjs +45 -0
  17. package/src/ChildProcess.res +84 -0
  18. package/src/ChildProcess.res.mjs +92 -0
  19. package/src/Cli.res +671 -0
  20. package/src/Cli.res.mjs +754 -0
  21. package/src/Collector.res +201 -0
  22. package/src/Collector.res.mjs +215 -0
  23. package/src/ComponentMeta.res +104 -0
  24. package/src/ComponentMeta.res.mjs +120 -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 +103 -0
  33. package/src/DomainDeadCode.res +93 -0
  34. package/src/DomainDeadCode.res.mjs +69 -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 +36 -0
  40. package/src/Filter.res.mjs +40 -0
  41. package/src/Flow_GWT.res +664 -0
  42. package/src/Flow_GWT.res.mjs +604 -0
  43. package/src/FormatterHuman.res +138 -0
  44. package/src/FormatterHuman.res.mjs +122 -0
  45. package/src/FormatterJson.res +257 -0
  46. package/src/FormatterJson.res.mjs +320 -0
  47. package/src/FormatterJunit.res +93 -0
  48. package/src/FormatterJunit.res.mjs +90 -0
  49. package/src/FormatterTap.res +139 -0
  50. package/src/FormatterTap.res.mjs +149 -0
  51. package/src/FormatterVsCode.res +362 -0
  52. package/src/FormatterVsCode.res.mjs +564 -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 +135 -0
  62. package/src/LocalHost.res.mjs +127 -0
  63. package/src/Mapping_GWT.res +474 -0
  64. package/src/Mapping_GWT.res.mjs +322 -0
  65. package/src/MultiSourceProjection_GWT.res +316 -0
  66. package/src/MultiSourceProjection_GWT.res.mjs +354 -0
  67. package/src/OutboundTranslation_GWT.res +238 -0
  68. package/src/OutboundTranslation_GWT.res.mjs +186 -0
  69. package/src/Outcome.res +161 -0
  70. package/src/Outcome.res.mjs +140 -0
  71. package/src/PackageScan.res +80 -0
  72. package/src/PackageScan.res.mjs +81 -0
  73. package/src/PlatformRunner.res +160 -0
  74. package/src/PlatformRunner.res.mjs +155 -0
  75. package/src/PlatformScan.res +138 -0
  76. package/src/PlatformScan.res.mjs +131 -0
  77. package/src/ProcessManager.res +91 -0
  78. package/src/ProcessManager.res.mjs +80 -0
  79. package/src/Projection_GWT.res +312 -0
  80. package/src/Projection_GWT.res.mjs +332 -0
  81. package/src/Query_GWT.res +379 -0
  82. package/src/Query_GWT.res.mjs +367 -0
  83. package/src/RenderRescript.res +125 -0
  84. package/src/RenderRescript.res.mjs +141 -0
  85. package/src/RunnerTypes.res +68 -0
  86. package/src/RunnerTypes.res.mjs +60 -0
  87. package/src/SideEffect_GWT.res +157 -0
  88. package/src/SideEffect_GWT.res.mjs +113 -0
  89. package/src/StateChange/ExternalAddCategorySlice.res +25 -0
  90. package/src/StateChange/ExternalAddCategorySlice.res.mjs +34 -0
  91. package/src/StateChange/ExternalAddCategorySlice_Behavior.res +19 -0
  92. package/src/StateChange/ExternalAddCategorySlice_Behavior.res.mjs +46 -0
  93. package/src/StateChange/WithFixtures.res +22 -0
  94. package/src/StateChange/WithFixtures.res.mjs +31 -0
  95. package/src/StateChange/WithFixtures_Behavior.res +17 -0
  96. package/src/StateChange/WithFixtures_Behavior.res.mjs +37 -0
  97. package/src/StateChange/WithManualOpen.res +23 -0
  98. package/src/StateChange/WithManualOpen.res.mjs +31 -0
  99. package/src/StateChange/WithManualOpen_Behavior.res +17 -0
  100. package/src/StateChange/WithManualOpen_Behavior.res.mjs +37 -0
  101. package/src/StubRuntime.res +53 -0
  102. package/src/StubRuntime.res.mjs +54 -0
  103. package/src/TestFixtures.res +16 -0
  104. package/src/TestFixtures.res.mjs +34 -0
  105. package/src/Watch.res +92 -0
  106. package/src/Watch.res.mjs +83 -0
  107. package/src/WatcherProbe.res +48 -0
  108. package/src/WatcherProbe.res.mjs +46 -0
  109. package/tests/AutomationGwtTest.res +71 -0
  110. package/tests/AutomationGwtTest.res.mjs +189 -0
  111. package/tests/BuildClassifierTest.res +94 -0
  112. package/tests/BuildClassifierTest.res.mjs +132 -0
  113. package/tests/ComponentMetaTest.res +52 -0
  114. package/tests/ComponentMetaTest.res.mjs +57 -0
  115. package/tests/DelegateGwtTest.res +487 -0
  116. package/tests/DelegateGwtTest.res.mjs +891 -0
  117. package/tests/DiscoveryTest.res +42 -0
  118. package/tests/DiscoveryTest.res.mjs +43 -0
  119. package/tests/DomainDeadCodeTest.res +116 -0
  120. package/tests/DomainDeadCodeTest.res.mjs +155 -0
  121. package/tests/DomainGraphTest.res +365 -0
  122. package/tests/DomainGraphTest.res.mjs +413 -0
  123. package/tests/FlowAggregateGwtTest.res +169 -0
  124. package/tests/FlowAggregateGwtTest.res.mjs +269 -0
  125. package/tests/FlowCrossPluginGwtTest.res +340 -0
  126. package/tests/FlowCrossPluginGwtTest.res.mjs +798 -0
  127. package/tests/FlowGwtTest.res +222 -0
  128. package/tests/FlowGwtTest.res.mjs +432 -0
  129. package/tests/FormatterVsCodeMessageTest.res +41 -0
  130. package/tests/FormatterVsCodeMessageTest.res.mjs +42 -0
  131. package/tests/InboundTranslationGwtTest.res +34 -0
  132. package/tests/InboundTranslationGwtTest.res.mjs +98 -0
  133. package/tests/LocalHostIntegration.res +70 -0
  134. package/tests/LocalHostIntegration.res.mjs +64 -0
  135. package/tests/LocalHostTest.res +71 -0
  136. package/tests/LocalHostTest.res.mjs +63 -0
  137. package/tests/MappingGwtTest.res +348 -0
  138. package/tests/MappingGwtTest.res.mjs +592 -0
  139. package/tests/OutboundTranslationGwtTest.res +59 -0
  140. package/tests/OutboundTranslationGwtTest.res.mjs +138 -0
  141. package/tests/PlatformRunnerTest.res +66 -0
  142. package/tests/PlatformRunnerTest.res.mjs +85 -0
  143. package/tests/PlatformScanTest.res +61 -0
  144. package/tests/PlatformScanTest.res.mjs +56 -0
  145. package/tests/QueryGwtTest.res +161 -0
  146. package/tests/QueryGwtTest.res.mjs +314 -0
  147. package/tests/QueryResolverGwtTest.res +71 -0
  148. package/tests/QueryResolverGwtTest.res.mjs +136 -0
  149. package/tests/RunnerUnitTest.res +219 -0
  150. package/tests/RunnerUnitTest.res.mjs +244 -0
  151. package/tests/SideEffect/FakeOrderNotification.res +23 -0
  152. package/tests/SideEffect/FakeOrderNotification.res.mjs +36 -0
  153. package/tests/SideEffect/FakeOrderNotificationGwtTest.res +35 -0
  154. package/tests/SideEffect/FakeOrderNotificationGwtTest.res.mjs +73 -0
  155. package/tests/SideEffect/MockEmail.res +18 -0
  156. package/tests/SideEffect/MockEmail.res.mjs +47 -0
  157. package/tests/StateChange/ExternalAddCategorySlice_GWT.res +27 -0
  158. package/tests/StateChange/ExternalAddCategorySlice_GWT.res.mjs +70 -0
  159. package/tests/StateChange/WithFixtures_Fixtures.res +9 -0
  160. package/tests/StateChange/WithFixtures_Fixtures.res.mjs +20 -0
  161. package/tests/StateChange/WithFixtures_GWT.res +23 -0
  162. package/tests/StateChange/WithFixtures_GWT.res.mjs +56 -0
  163. package/tests/StateChange/WithManualOpen_Fixtures.res +7 -0
  164. package/tests/StateChange/WithManualOpen_Fixtures.res.mjs +20 -0
  165. package/tests/StateChange/WithManualOpen_GWT.res +17 -0
  166. package/tests/StateChange/WithManualOpen_GWT.res.mjs +56 -0
  167. package/tests/StateChangeSliceGwtTest.res +160 -0
  168. package/tests/StateChangeSliceGwtTest.res.mjs +280 -0
  169. package/tests/StateViewSliceGwtTest.res +64 -0
  170. package/tests/StateViewSliceGwtTest.res.mjs +161 -0
  171. package/tests/WatchDebounceTest.res +78 -0
  172. package/tests/WatchDebounceTest.res.mjs +97 -0
@@ -0,0 +1,138 @@
1
+ // Terminal-coloured human formatter. Shipped as the default output for
2
+ // `reventless-gwt run`. Uses picocolors for ANSI colours — it is a tiny
3
+ // zero-dependency helper that transparently disables colour when stdout
4
+ // is not a TTY.
5
+
6
+ type colour = string => string
7
+ type picocolors = {
8
+ red: colour,
9
+ green: colour,
10
+ yellow: colour,
11
+ cyan: colour,
12
+ gray: colour,
13
+ bold: colour,
14
+ dim: colour,
15
+ }
16
+
17
+ @module("picocolors") external pc: picocolors = "default"
18
+
19
+ @val external processStdout: {"write": string => unit} = "process.stdout"
20
+ @val external processCwd: unit => string = "process.cwd"
21
+ @module("node:path") external pathBasename: string => string = "basename"
22
+ @module("node:path") external pathRelative: (string, string) => string = "relative"
23
+
24
+ let write = (s: string) => processStdout["write"](s)
25
+ let writeLine = (s: string) => write(s ++ "\n")
26
+
27
+ let formatFilePath = (abs: string) => {
28
+ let base = pathBasename(abs)
29
+ let name = if String.endsWith(base, ".res.mjs") {
30
+ String.slice(base, ~start=0, ~end=String.length(base) - 8)
31
+ } else {
32
+ base
33
+ }
34
+ let rel = pathRelative(processCwd(), abs)
35
+ `${name} - ${rel}`
36
+ }
37
+
38
+ let formatLocation = (loc: option<Collector.location>) =>
39
+ switch loc {
40
+ | None => ""
41
+ | Some(l) => `${l.file}:${Int.toString(l.line)}`
42
+ }
43
+
44
+ let renderMismatch = (m: Outcome.mismatch) =>
45
+ switch m {
46
+ | EventsMismatch({expected, actual}) => {
47
+ let exp = RenderRescript.renderMany(expected)
48
+ let act = RenderRescript.renderMany(actual)
49
+ ` expected: ${exp}\n actual: ${act}`
50
+ }
51
+ | ErrorMismatch({expected, actual, actualEvents}) => {
52
+ let exp = `Error(${RenderRescript.render(expected)})`
53
+ let act = switch actual {
54
+ | Some(v) => `Error(${RenderRescript.render(v)})`
55
+ | None => `Ok(${RenderRescript.renderMany(actualEvents)})`
56
+ }
57
+ ` expected: ${exp}\n actual: ${act}`
58
+ }
59
+ | StateMismatch({key, expected, actual}) =>
60
+ ` key: "${key}"\n expected: ${RenderRescript.renderOption(
61
+ expected,
62
+ )}\n actual: ${RenderRescript.renderOption(actual)}`
63
+ | NoEventExpected({actual}) =>
64
+ ` expected no events\n actual: ${RenderRescript.renderMany(actual)}`
65
+ | TodoMismatch({expected, actual}) => {
66
+ let fmt = (arr: array<(string, JSON.t)>) =>
67
+ arr
68
+ ->Array.map(((id, v)) => `(${id}, ${RenderRescript.render(v)})`)
69
+ ->Array.join(", ")
70
+ ` expected: [${fmt(expected)}]\n actual: [${fmt(actual)}]`
71
+ }
72
+ | AppendConditionMismatch({expected, actual}) =>
73
+ ` expected: ${RenderRescript.render(expected)}\n actual: ${RenderRescript.render(
74
+ actual,
75
+ )}`
76
+ | TranslateError({expected, actual}) =>
77
+ ` expected: ${expected}\n actual: ${actual->Option.getOr("(none)")}`
78
+ | QueryRowsMismatch({expected, actual}) =>
79
+ ` expected: ${RenderRescript.renderMany(expected)}\n actual: ${RenderRescript.renderMany(
80
+ actual,
81
+ )}`
82
+ | PublishedActionsMismatch({expected, actual}) =>
83
+ ` expected: ${RenderRescript.renderMany(expected)}\n actual: ${RenderRescript.renderMany(
84
+ actual,
85
+ )}`
86
+ | Throw({error, stack}) => ` error: ${error}\n${stack}`
87
+ }
88
+
89
+ let emitTest = (t: RunnerTypes.testResult) => {
90
+ let path = t.describePath->Array.join(" > ")
91
+ let full = path == "" ? t.name : `${path} > ${t.name}`
92
+ let loc = formatLocation(t.location)
93
+ let locStr = loc == "" ? "" : ` ${pc.dim(loc)}`
94
+ switch t.status {
95
+ | Pass => writeLine(` ${pc.green("✓")} ${full}${locStr}`)
96
+ | Skip =>
97
+ writeLine(
98
+ ` ${pc.yellow("○")} ${full}${locStr} ${pc.dim(
99
+ t.skipReason->Option.getOr("skipped"),
100
+ )}`,
101
+ )
102
+ | Fail => {
103
+ writeLine(` ${pc.red("✗")} ${pc.bold(full)}${locStr}`)
104
+ switch t.mismatch {
105
+ | Some(m) => {
106
+ writeLine("")
107
+ writeLine(renderMismatch(m))
108
+ let hint = Hint.forMismatch(~slice=?t.slice, m)
109
+ writeLine("")
110
+ writeLine(` ${pc.cyan(Hint.format(hint))}`)
111
+ writeLine("")
112
+ }
113
+ | None => ()
114
+ }
115
+ }
116
+ }
117
+ }
118
+
119
+ let emitFile = (f: RunnerTypes.fileResult) => {
120
+ writeLine("")
121
+ writeLine(pc.bold(formatFilePath(f.path)))
122
+ f.tests->Array.forEach(emitTest)
123
+ }
124
+
125
+ let emitSummary = (s: RunnerTypes.summary) => {
126
+ writeLine("")
127
+ let line = `Tests: ${Int.toString(s.total)} ${pc.green(
128
+ Int.toString(s.passed) ++ " passed",
129
+ )} ${pc.red(Int.toString(s.failed) ++ " failed")} ${pc.yellow(
130
+ Int.toString(s.skipped) ++ " skipped",
131
+ )} (${Float.toString(s.durationMs)} ms)`
132
+ writeLine(line)
133
+ }
134
+
135
+ let emit = (r: RunnerTypes.runResult) => {
136
+ r.files->Array.forEach(emitFile)
137
+ emitSummary(r.summary)
138
+ }
@@ -0,0 +1,122 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Nodepath from "node:path";
4
+ import Picocolors from "picocolors";
5
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
6
+ import * as Hint$ReventlessGwt from "./Hint.res.mjs";
7
+ import * as RenderRescript$ReventlessGwt from "./RenderRescript.res.mjs";
8
+
9
+ function write(s) {
10
+ process.stdout.write(s);
11
+ }
12
+
13
+ function writeLine(s) {
14
+ process.stdout.write(s + "\n");
15
+ }
16
+
17
+ function formatFilePath(abs) {
18
+ let base = Nodepath.basename(abs);
19
+ let name = base.endsWith(".res.mjs") ? base.slice(0, base.length - 8 | 0) : base;
20
+ let rel = Nodepath.relative(process.cwd(), abs);
21
+ return name + ` - ` + rel;
22
+ }
23
+
24
+ function formatLocation(loc) {
25
+ if (loc !== undefined) {
26
+ return loc.file + `:` + loc.line.toString();
27
+ } else {
28
+ return "";
29
+ }
30
+ }
31
+
32
+ function renderMismatch(m) {
33
+ switch (m.TAG) {
34
+ case "EventsMismatch" :
35
+ let exp = RenderRescript$ReventlessGwt.renderMany(m.expected);
36
+ let act = RenderRescript$ReventlessGwt.renderMany(m.actual);
37
+ return ` expected: ` + exp + `\n actual: ` + act;
38
+ case "ErrorMismatch" :
39
+ let actual = m.actual;
40
+ let exp$1 = `Error(` + RenderRescript$ReventlessGwt.render(undefined, m.expected) + `)`;
41
+ let act$1 = actual !== undefined ? `Error(` + RenderRescript$ReventlessGwt.render(undefined, actual) + `)` : `Ok(` + RenderRescript$ReventlessGwt.renderMany(m.actualEvents) + `)`;
42
+ return ` expected: ` + exp$1 + `\n actual: ` + act$1;
43
+ case "StateMismatch" :
44
+ return ` key: "` + m.key + `"\n expected: ` + RenderRescript$ReventlessGwt.renderOption(m.expected) + `\n actual: ` + RenderRescript$ReventlessGwt.renderOption(m.actual);
45
+ case "NoEventExpected" :
46
+ return ` expected no events\n actual: ` + RenderRescript$ReventlessGwt.renderMany(m.actual);
47
+ case "TodoMismatch" :
48
+ let fmt = arr => arr.map(param => `(` + param[0] + `, ` + RenderRescript$ReventlessGwt.render(undefined, param[1]) + `)`).join(", ");
49
+ return ` expected: [` + fmt(m.expected) + `]\n actual: [` + fmt(m.actual) + `]`;
50
+ case "AppendConditionMismatch" :
51
+ return ` expected: ` + RenderRescript$ReventlessGwt.render(undefined, m.expected) + `\n actual: ` + RenderRescript$ReventlessGwt.render(undefined, m.actual);
52
+ case "TranslateError" :
53
+ return ` expected: ` + m.expected + `\n actual: ` + Stdlib_Option.getOr(m.actual, "(none)");
54
+ case "QueryRowsMismatch" :
55
+ case "PublishedActionsMismatch" :
56
+ return ` expected: ` + RenderRescript$ReventlessGwt.renderMany(m.expected) + `\n actual: ` + RenderRescript$ReventlessGwt.renderMany(m.actual);
57
+ case "Throw" :
58
+ return ` error: ` + m.error + `\n` + m.stack;
59
+ }
60
+ }
61
+
62
+ function emitTest(t) {
63
+ let path = t.describePath.join(" > ");
64
+ let full = path === "" ? t.name : path + ` > ` + t.name;
65
+ let loc = formatLocation(t.location);
66
+ let locStr = loc === "" ? "" : ` ` + Picocolors.dim(loc);
67
+ let match = t.status;
68
+ switch (match) {
69
+ case "Pass" :
70
+ let s = ` ` + Picocolors.green("✓") + ` ` + full + locStr;
71
+ return process.stdout.write(s + "\n");
72
+ case "Fail" :
73
+ let s$1 = ` ` + Picocolors.red("✗") + ` ` + Picocolors.bold(full) + locStr;
74
+ process.stdout.write(s$1 + "\n");
75
+ let m = t.mismatch;
76
+ if (m === undefined) {
77
+ return;
78
+ }
79
+ process.stdout.write("\n");
80
+ let s$2 = renderMismatch(m);
81
+ process.stdout.write(s$2 + "\n");
82
+ let hint = Hint$ReventlessGwt.forMismatch(t.slice, m);
83
+ process.stdout.write("\n");
84
+ let s$3 = ` ` + Picocolors.cyan(Hint$ReventlessGwt.format(hint));
85
+ process.stdout.write(s$3 + "\n");
86
+ return process.stdout.write("\n");
87
+ case "Skip" :
88
+ let s$4 = ` ` + Picocolors.yellow("○") + ` ` + full + locStr + ` ` + Picocolors.dim(Stdlib_Option.getOr(t.skipReason, "skipped"));
89
+ return process.stdout.write(s$4 + "\n");
90
+ }
91
+ }
92
+
93
+ function emitFile(f) {
94
+ process.stdout.write("\n");
95
+ let s = Picocolors.bold(formatFilePath(f.path));
96
+ process.stdout.write(s + "\n");
97
+ f.tests.forEach(emitTest);
98
+ }
99
+
100
+ function emitSummary(s) {
101
+ process.stdout.write("\n");
102
+ let s$1 = `Tests: ` + s.total.toString() + ` ` + Picocolors.green(s.passed.toString() + " passed") + ` ` + Picocolors.red(s.failed.toString() + " failed") + ` ` + Picocolors.yellow(s.skipped.toString() + " skipped") + ` (` + s.durationMs.toString() + ` ms)`;
103
+ process.stdout.write(s$1 + "\n");
104
+ }
105
+
106
+ function emit(r) {
107
+ r.files.forEach(emitFile);
108
+ emitSummary(r.summary);
109
+ }
110
+
111
+ export {
112
+ write,
113
+ writeLine,
114
+ formatFilePath,
115
+ formatLocation,
116
+ renderMismatch,
117
+ emitTest,
118
+ emitFile,
119
+ emitSummary,
120
+ emit,
121
+ }
122
+ /* node:path Not a pure module */
@@ -0,0 +1,257 @@
1
+ // Structured JSON formatter — the AI loop's primary input and the substrate
2
+ // for any programmatic consumer. Ships both a single-envelope mode and an
3
+ // NDJSON streaming mode (`--stream`). Shape matches §3.3 of
4
+ // `docs/analysis/given-when-then-specifications.md`.
5
+
6
+ // 1.1.0 — additive: `PublishedActionsMismatch` mismatch kind for the
7
+ // Delegate_GWT / cross-plugin Flow_GWT boundary steps.
8
+ let schemaVersion = "1.1.0"
9
+
10
+ @val external processStdout: {"write": string => unit} = "process.stdout"
11
+ let write = (s: string) => processStdout["write"](s)
12
+ let writeLine = (s: string) => write(s ++ "\n")
13
+
14
+ // Re-render a JSON value as a dual-rendered `{type, payload, rendered}` entry.
15
+ let renderValue = (j: JSON.t): JSON.t => {
16
+ let obj = Dict.make()
17
+ switch j {
18
+ | Object(dict) =>
19
+ switch dict->Dict.get("TAG") {
20
+ | Some(String(tag)) => {
21
+ obj->Dict.set("type", JSON.Encode.string(tag))
22
+ let payloadKeys =
23
+ dict
24
+ ->Dict.keysToArray
25
+ ->Array.filter(k => k != "TAG" && String.startsWith(k, "_"))
26
+ let payload = switch payloadKeys {
27
+ | [] => JSON.Encode.null
28
+ | ["_0"] =>
29
+ dict->Dict.get("_0")->Option.getOr(JSON.Encode.null)
30
+ | _ => {
31
+ let payloadDict = Dict.make()
32
+ payloadKeys->Array.forEach(k =>
33
+ payloadDict->Dict.set(
34
+ k,
35
+ dict->Dict.get(k)->Option.getOr(JSON.Encode.null),
36
+ )
37
+ )
38
+ JSON.Encode.object(payloadDict)
39
+ }
40
+ }
41
+ obj->Dict.set("payload", payload)
42
+ }
43
+ | _ => {
44
+ obj->Dict.set("type", JSON.Encode.null)
45
+ obj->Dict.set("payload", j)
46
+ }
47
+ }
48
+ | String(s) => {
49
+ obj->Dict.set("type", JSON.Encode.string(s))
50
+ obj->Dict.set("payload", JSON.Encode.null)
51
+ }
52
+ | _ => {
53
+ obj->Dict.set("type", JSON.Encode.null)
54
+ obj->Dict.set("payload", j)
55
+ }
56
+ }
57
+ obj->Dict.set("rendered", JSON.Encode.string(RenderRescript.render(j)))
58
+ JSON.Encode.object(obj)
59
+ }
60
+
61
+ let renderValues = (arr: array<JSON.t>): JSON.t =>
62
+ arr->Array.map(renderValue)->JSON.Encode.array
63
+
64
+ let mismatchJson = (m: Outcome.mismatch, slice: option<string>): JSON.t => {
65
+ let obj = Dict.make()
66
+ obj->Dict.set("kind", JSON.Encode.string(Outcome.kindName(m)))
67
+ let optVal = o =>
68
+ switch o {
69
+ | Some(v) => renderValue(v)
70
+ | None => JSON.Encode.null
71
+ }
72
+ switch m {
73
+ | EventsMismatch({expected, actual}) => {
74
+ obj->Dict.set("expected", renderValues(expected))
75
+ obj->Dict.set("actual", renderValues(actual))
76
+ obj->Dict.set("fieldDiff", Diff.toJsonArray(Diff.diffArrays(expected, actual)))
77
+ }
78
+ | ErrorMismatch({expected, actual, actualEvents}) => {
79
+ obj->Dict.set("expected", renderValue(expected))
80
+ obj->Dict.set("actual", optVal(actual))
81
+ obj->Dict.set("actualEvents", renderValues(actualEvents))
82
+ }
83
+ | StateMismatch({key, expected, actual}) => {
84
+ obj->Dict.set("key", JSON.Encode.string(key))
85
+ obj->Dict.set("expected", optVal(expected))
86
+ obj->Dict.set("actual", optVal(actual))
87
+ let eJson = expected->Option.getOr(JSON.Encode.null)
88
+ let aJson = actual->Option.getOr(JSON.Encode.null)
89
+ obj->Dict.set("fieldDiff", Diff.toJsonArray(Diff.diff(eJson, aJson)))
90
+ }
91
+ | NoEventExpected({actual}) => obj->Dict.set("actual", renderValues(actual))
92
+ | TodoMismatch({expected, actual}) => {
93
+ let pairArr = pairs =>
94
+ pairs
95
+ ->Array.map(((id, v)) => {
96
+ let d = Dict.make()
97
+ d->Dict.set("id", JSON.Encode.string(id))
98
+ d->Dict.set("value", renderValue(v))
99
+ JSON.Encode.object(d)
100
+ })
101
+ ->JSON.Encode.array
102
+ obj->Dict.set("expected", pairArr(expected))
103
+ obj->Dict.set("actual", pairArr(actual))
104
+ }
105
+ | AppendConditionMismatch({expected, actual}) => {
106
+ obj->Dict.set("expected", expected)
107
+ obj->Dict.set("actual", actual)
108
+ }
109
+ | TranslateError({expected, actual}) => {
110
+ obj->Dict.set("expected", JSON.Encode.string(expected))
111
+ obj->Dict.set(
112
+ "actual",
113
+ actual->Option.mapOr(JSON.Encode.null, JSON.Encode.string),
114
+ )
115
+ }
116
+ | QueryRowsMismatch({expected, actual}) => {
117
+ obj->Dict.set("expected", renderValues(expected))
118
+ obj->Dict.set("actual", renderValues(actual))
119
+ obj->Dict.set("fieldDiff", Diff.toJsonArray(Diff.diffArrays(expected, actual)))
120
+ }
121
+ | PublishedActionsMismatch({expected, actual}) => {
122
+ obj->Dict.set("expected", renderValues(expected))
123
+ obj->Dict.set("actual", renderValues(actual))
124
+ obj->Dict.set("fieldDiff", Diff.toJsonArray(Diff.diffArrays(expected, actual)))
125
+ }
126
+ | Throw({error, stack}) => {
127
+ obj->Dict.set("error", JSON.Encode.string(error))
128
+ obj->Dict.set("stack", JSON.Encode.string(stack))
129
+ }
130
+ }
131
+ let hint = Hint.forMismatch(~slice?, m)
132
+ obj->Dict.set("hint", Hint.toJson(hint))
133
+ JSON.Encode.object(obj)
134
+ }
135
+
136
+ let locationJson = (loc: option<Collector.location>): JSON.t =>
137
+ switch loc {
138
+ | None => JSON.Encode.null
139
+ | Some(l) => {
140
+ let d = Dict.make()
141
+ d->Dict.set("file", JSON.Encode.string(l.file))
142
+ d->Dict.set("line", JSON.Encode.int(l.line))
143
+ d->Dict.set("column", JSON.Encode.int(l.column))
144
+ JSON.Encode.object(d)
145
+ }
146
+ }
147
+
148
+ let testJson = (t: RunnerTypes.testResult): JSON.t => {
149
+ let d = Dict.make()
150
+ d->Dict.set("id", JSON.Encode.string(t.id))
151
+ d->Dict.set("name", JSON.Encode.string(t.name))
152
+ d->Dict.set(
153
+ "describePath",
154
+ t.describePath->Array.map(JSON.Encode.string)->JSON.Encode.array,
155
+ )
156
+ d->Dict.set("status", JSON.Encode.string(RunnerTypes.statusString(t.status)))
157
+ d->Dict.set("durationMs", JSON.Encode.float(t.durationMs))
158
+ d->Dict.set("location", locationJson(t.location))
159
+ switch t.mismatch {
160
+ | Some(m) => d->Dict.set("mismatch", mismatchJson(m, t.slice))
161
+ | None => ()
162
+ }
163
+ switch t.skipReason {
164
+ | Some(r) => d->Dict.set("skipReason", JSON.Encode.string(r))
165
+ | None => ()
166
+ }
167
+ JSON.Encode.object(d)
168
+ }
169
+
170
+ let fileJson = (f: RunnerTypes.fileResult): JSON.t => {
171
+ let d = Dict.make()
172
+ d->Dict.set("path", JSON.Encode.string(f.path))
173
+ d->Dict.set("tests", f.tests->Array.map(testJson)->JSON.Encode.array)
174
+ JSON.Encode.object(d)
175
+ }
176
+
177
+ let summaryJson = (s: RunnerTypes.summary): JSON.t => {
178
+ let d = Dict.make()
179
+ d->Dict.set("total", JSON.Encode.int(s.total))
180
+ d->Dict.set("passed", JSON.Encode.int(s.passed))
181
+ d->Dict.set("failed", JSON.Encode.int(s.failed))
182
+ d->Dict.set("skipped", JSON.Encode.int(s.skipped))
183
+ d->Dict.set("files", JSON.Encode.int(s.files))
184
+ JSON.Encode.object(d)
185
+ }
186
+
187
+ let envelope = (r: RunnerTypes.runResult, ~toolVersion: string): JSON.t => {
188
+ let d = Dict.make()
189
+ d->Dict.set("schemaVersion", JSON.Encode.string(schemaVersion))
190
+ d->Dict.set("tool", JSON.Encode.string("reventless-gwt"))
191
+ d->Dict.set("toolVersion", JSON.Encode.string(toolVersion))
192
+ d->Dict.set("startedAt", JSON.Encode.string(r.summary.startedAt))
193
+ d->Dict.set("durationMs", JSON.Encode.float(r.summary.durationMs))
194
+ d->Dict.set("summary", summaryJson(r.summary))
195
+ d->Dict.set("files", r.files->Array.map(fileJson)->JSON.Encode.array)
196
+ JSON.Encode.object(d)
197
+ }
198
+
199
+ let emit = (r: RunnerTypes.runResult, ~toolVersion: string) =>
200
+ write(JSON.stringify(envelope(r, ~toolVersion), ~space=2) ++ "\n")
201
+
202
+ // Streaming variant — NDJSON events.
203
+ let streamRunStart = (~toolVersion: string, ~startedAt: string) => {
204
+ let d = Dict.make()
205
+ d->Dict.set("type", JSON.Encode.string("runStarted"))
206
+ d->Dict.set("schemaVersion", JSON.Encode.string(schemaVersion))
207
+ d->Dict.set("toolVersion", JSON.Encode.string(toolVersion))
208
+ d->Dict.set("at", JSON.Encode.string(startedAt))
209
+ writeLine(JSON.stringify(JSON.Encode.object(d)))
210
+ }
211
+
212
+ let streamFileStart = (path: string) => {
213
+ let d = Dict.make()
214
+ d->Dict.set("type", JSON.Encode.string("fileStarted"))
215
+ d->Dict.set("path", JSON.Encode.string(path))
216
+ writeLine(JSON.stringify(JSON.Encode.object(d)))
217
+ }
218
+
219
+ let streamTest = (t: RunnerTypes.testResult) => {
220
+ let d = Dict.make()
221
+ d->Dict.set("type", JSON.Encode.string("testResult"))
222
+ let tj = testJson(t)
223
+ // Fold testJson's object keys into the envelope.
224
+ switch tj {
225
+ | Object(inner) =>
226
+ inner->Dict.keysToArray->Array.forEach(k =>
227
+ d->Dict.set(k, inner->Dict.getUnsafe(k))
228
+ )
229
+ | _ => ()
230
+ }
231
+ writeLine(JSON.stringify(JSON.Encode.object(d)))
232
+ }
233
+
234
+ let streamFileFinished = (f: RunnerTypes.fileResult) => {
235
+ let passed = ref(0)
236
+ let failed = ref(0)
237
+ f.tests->Array.forEach(t =>
238
+ switch t.status {
239
+ | Pass => passed := passed.contents + 1
240
+ | Fail => failed := failed.contents + 1
241
+ | Skip => ()
242
+ }
243
+ )
244
+ let d = Dict.make()
245
+ d->Dict.set("type", JSON.Encode.string("fileFinished"))
246
+ d->Dict.set("path", JSON.Encode.string(f.path))
247
+ d->Dict.set("passed", JSON.Encode.int(passed.contents))
248
+ d->Dict.set("failed", JSON.Encode.int(failed.contents))
249
+ writeLine(JSON.stringify(JSON.Encode.object(d)))
250
+ }
251
+
252
+ let streamRunEnd = (r: RunnerTypes.runResult) => {
253
+ let d = Dict.make()
254
+ d->Dict.set("type", JSON.Encode.string("runFinished"))
255
+ d->Dict.set("summary", summaryJson(r.summary))
256
+ writeLine(JSON.stringify(JSON.Encode.object(d)))
257
+ }