@reventlessdev/reventless-gwt 1.0.0-alpha.76 → 1.0.0-alpha.77

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 (45) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/package.json +6 -6
  3. package/src/Behavior_GWT.res +125 -159
  4. package/src/Behavior_GWT.res.mjs +156 -59
  5. package/src/BuildClassifier.res +15 -5
  6. package/src/BuildClassifier.res.mjs +3 -3
  7. package/src/Cli.res +157 -29
  8. package/src/Cli.res.mjs +138 -24
  9. package/src/Collector.res +2 -2
  10. package/src/Collector.res.mjs +2 -2
  11. package/src/ComponentMeta.res +16 -43
  12. package/src/ComponentMeta.res.mjs +12 -41
  13. package/src/Discovery.res +19 -19
  14. package/src/Discovery.res.mjs +5 -9
  15. package/src/DomainGraph.res +0 -0
  16. package/src/DomainGraph.res.mjs +0 -0
  17. package/src/FormatterHuman.res +14 -43
  18. package/src/FormatterHuman.res.mjs +21 -28
  19. package/src/FormatterJson.res +12 -5
  20. package/src/FormatterJson.res.mjs +8 -6
  21. package/src/FormatterTap.res +13 -52
  22. package/src/FormatterTap.res.mjs +25 -43
  23. package/src/FormatterVsCode.res +4 -19
  24. package/src/FormatterVsCode.res.mjs +4 -23
  25. package/src/LocalHost.res +11 -27
  26. package/src/LocalHost.res.mjs +6 -34
  27. package/src/MismatchRender.res +72 -0
  28. package/src/MismatchRender.res.mjs +127 -0
  29. package/src/Outcome.res +0 -64
  30. package/src/Outcome.res.mjs +0 -47
  31. package/src/PlatformScan.res +14 -10
  32. package/src/PlatformScan.res.mjs +14 -13
  33. package/tests/BuildClassifierTest.res +39 -0
  34. package/tests/BuildClassifierTest.res.mjs +44 -0
  35. package/tests/CliWatchScopeTest.res +62 -0
  36. package/tests/CliWatchScopeTest.res.mjs +95 -0
  37. package/tests/ComponentMetaTest.res +15 -0
  38. package/tests/ComponentMetaTest.res.mjs +14 -0
  39. package/tests/DomainGraphTest.res +2 -2
  40. package/tests/FlowAggregateGwtTest.res +2 -0
  41. package/tests/FlowAggregateGwtTest.res.mjs +4 -0
  42. package/tests/FormatterGoldenTest.res +125 -0
  43. package/tests/FormatterGoldenTest.res.mjs +162 -0
  44. package/tests/MappingGwtTest.res +2 -0
  45. package/tests/MappingGwtTest.res.mjs +4 -0
@@ -5,42 +5,16 @@
5
5
 
6
6
  type component = {kind: string, name: string}
7
7
 
8
- // Folder names that denote a component kind. A file counts as a component when
9
- // its immediate parent folder is one of these.
10
- let kindFolders = [
11
- "Aggregate",
12
- "StateChangeSlice",
13
- "StateViewSlice",
14
- "StateViewSliceStream",
15
- "ReadModel",
16
- "ReadModelStream",
17
- "AutomationSlice",
18
- "InboundTranslationSlice",
19
- "OutboundTranslationSlice",
20
- "Extension",
21
- "ExtensionPoint",
22
- "Task",
23
- ]
24
-
25
- let isKindFolder = (segment: string) => kindFolders->Array.includes(segment)
26
-
27
- // Body-file suffixes stripped to recover the spec stem (longest first so
28
- // `_ExtensionPointMapping` is removed before `_ExtensionPoint`). Spec and body
29
- // files in one folder collapse to the same component name.
30
- let bodySuffixes = [
31
- "_ExtensionPointMapping",
32
- "_ExtensionPoint",
33
- "_Extension",
34
- "_Projections",
35
- "_Projection",
36
- "_Mappings",
37
- "_Behavior",
38
- "_Automation",
39
- "_Translation",
40
- ]
8
+ // The folder→kind vocabulary and body-file suffixes are the single source in
9
+ // `Reventless.ComponentKind` (shared with the plugin generator). Deriving from it
10
+ // means the gwt discovery recognises exactly the folder spellings the generator
11
+ // classifies — including the plural / short forms this file used to miss.
12
+ module Kind = Reventless.ComponentKind
41
13
 
14
+ // Strip a body-file suffix (longest first) to recover the spec stem, so spec and
15
+ // body files in one folder collapse to the same component name.
42
16
  let stripBody = (stem: string) =>
43
- switch bodySuffixes->Array.find(suf => String.endsWith(stem, suf)) {
17
+ switch Kind.bodySuffixes->Array.find(suf => String.endsWith(stem, suf)) {
44
18
  | Some(suf) => String.slice(stem, ~start=0, ~end=String.length(stem) - String.length(suf))
45
19
  | None => stem
46
20
  }
@@ -87,18 +61,17 @@ let stem = (filename: string) =>
87
61
  }
88
62
 
89
63
  // {kind, name} for a discovered GWT test file, or None if it isn't inside a
90
- // recognised kind folder.
64
+ // recognised kind folder. `kind` is the canonical folder name, so a plural
65
+ // folder (`Aggregates/`) reports the same kind as its singular form.
91
66
  let componentOfTestFile = (path: string): option<component> =>
92
- switch parentFolder(path) {
93
- | Some(folder) if isKindFolder(folder) =>
94
- Some({kind: folder, name: path->basename->stem->stripGwt->stripBody})
95
- | _ => None
67
+ switch parentFolder(path)->Option.flatMap(Kind.folderToKind) {
68
+ | Some(kind) => Some({kind: Kind.folderName(kind), name: path->basename->stem->stripGwt->stripBody})
69
+ | None => None
96
70
  }
97
71
 
98
72
  // {kind, name} for a src/ component file (spec or body), or None.
99
73
  let componentOfSrcFile = (path: string): option<component> =>
100
- switch parentFolder(path) {
101
- | Some(folder) if isKindFolder(folder) =>
102
- Some({kind: folder, name: path->basename->stem->stripBody})
103
- | _ => None
74
+ switch parentFolder(path)->Option.flatMap(Kind.folderToKind) {
75
+ | Some(kind) => Some({kind: Kind.folderName(kind), name: path->basename->stem->stripBody})
76
+ | None => None
104
77
  }
@@ -1,40 +1,11 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
4
-
5
- let kindFolders = [
6
- "Aggregate",
7
- "StateChangeSlice",
8
- "StateViewSlice",
9
- "StateViewSliceStream",
10
- "ReadModel",
11
- "ReadModelStream",
12
- "AutomationSlice",
13
- "InboundTranslationSlice",
14
- "OutboundTranslationSlice",
15
- "Extension",
16
- "ExtensionPoint",
17
- "Task"
18
- ];
19
-
20
- function isKindFolder(segment) {
21
- return kindFolders.includes(segment);
22
- }
23
-
24
- let bodySuffixes = [
25
- "_ExtensionPointMapping",
26
- "_ExtensionPoint",
27
- "_Extension",
28
- "_Projections",
29
- "_Projection",
30
- "_Mappings",
31
- "_Behavior",
32
- "_Automation",
33
- "_Translation"
34
- ];
4
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
5
+ import * as ComponentKind$Reventless from "@reventlessdev/reventless-spec/src/components/ComponentKind.res.mjs";
35
6
 
36
7
  function stripBody(stem) {
37
- let suf = bodySuffixes.find(suf => stem.endsWith(suf));
8
+ let suf = ComponentKind$Reventless.bodySuffixes.find(suf => stem.endsWith(suf));
38
9
  if (suf !== undefined) {
39
10
  return stem.slice(0, stem.length - suf.length | 0);
40
11
  } else {
@@ -86,29 +57,29 @@ function stem(filename) {
86
57
  }
87
58
 
88
59
  function componentOfTestFile(path) {
89
- let folder = parentFolder(path);
90
- if (folder !== undefined && kindFolders.includes(folder)) {
60
+ let kind = Stdlib_Option.flatMap(parentFolder(path), ComponentKind$Reventless.folderToKind);
61
+ if (kind !== undefined) {
91
62
  return {
92
- kind: folder,
63
+ kind: ComponentKind$Reventless.folderName(kind),
93
64
  name: stripBody(stripGwt(stem(basename(path))))
94
65
  };
95
66
  }
96
67
  }
97
68
 
98
69
  function componentOfSrcFile(path) {
99
- let folder = parentFolder(path);
100
- if (folder !== undefined && kindFolders.includes(folder)) {
70
+ let kind = Stdlib_Option.flatMap(parentFolder(path), ComponentKind$Reventless.folderToKind);
71
+ if (kind !== undefined) {
101
72
  return {
102
- kind: folder,
73
+ kind: ComponentKind$Reventless.folderName(kind),
103
74
  name: stripBody(stem(basename(path)))
104
75
  };
105
76
  }
106
77
  }
107
78
 
79
+ let Kind;
80
+
108
81
  export {
109
- kindFolders,
110
- isKindFolder,
111
- bodySuffixes,
82
+ Kind,
112
83
  stripBody,
113
84
  stripGwt,
114
85
  basename,
package/src/Discovery.res CHANGED
@@ -43,36 +43,37 @@ let isGwtTestFile = (name: string) =>
43
43
  String.endsWith(name, "GwtTest.res.mjs") ||
44
44
  String.endsWith(name, "Gwt.res.mjs")
45
45
 
46
- let rec walk = async (dir: string, acc: array<string>): array<string> => {
46
+ // Depth-first, pushing into a shared accumulator instead of rebuilding it with
47
+ // `Array.concat` per entry (which was O(n²) over a large tree). The traversal
48
+ // order is unchanged: files and nested subtree results still land in on-disk
49
+ // entry order.
50
+ let rec walk = async (dir: string, acc: array<string>): unit => {
47
51
  let entries = try {
48
52
  await _readdir(dir, {withFileTypes: true})
49
53
  } catch {
50
54
  | _ => []
51
55
  }
52
56
  if isPruned(entries) {
53
- acc
57
+ ()
54
58
  } else {
55
- let found = ref(acc)
56
- for i in 0 to entries->Array.length - 1 {
57
- let entry = entries->Array.getUnsafe(i)
58
- if !shouldIgnore(entry.name) {
59
- let full = join(dir, entry.name)
60
- if entry._isDirectory() {
61
- let nested = await walk(full, [])
62
- found := Array.concat(found.contents, nested)
63
- } else if entry._isFile() && isGwtTestFile(entry.name) {
64
- found := Array.concat(found.contents, [full])
59
+ for i in 0 to entries->Array.length - 1 {
60
+ let entry = entries->Array.getUnsafe(i)
61
+ if !shouldIgnore(entry.name) {
62
+ let full = join(dir, entry.name)
63
+ if entry._isDirectory() {
64
+ await walk(full, acc)
65
+ } else if entry._isFile() && isGwtTestFile(entry.name) {
66
+ acc->Array.push(full)
67
+ }
65
68
  }
66
69
  }
67
70
  }
68
- found.contents
69
- }
70
71
  }
71
72
 
72
73
  // Returns absolute paths of `*GWT*.res.mjs` test files reachable from the
73
74
  // supplied roots. A root may be a directory or a single file.
74
75
  let discover = async (roots: array<string>): array<string> => {
75
- let found = ref([])
76
+ let found = []
76
77
  for i in 0 to roots->Array.length - 1 {
77
78
  let root = roots->Array.getUnsafe(i)
78
79
  let absolute = isAbsolute(root) ? root : resolve(root)
@@ -83,16 +84,15 @@ let discover = async (roots: array<string>): array<string> => {
83
84
  | _ => false
84
85
  }
85
86
  if isDir {
86
- let collected = await walk(absolute, [])
87
- found := Array.concat(found.contents, collected)
87
+ await walk(absolute, found)
88
88
  } else if isGwtTestFile(absolute) {
89
- found := Array.concat(found.contents, [absolute])
89
+ found->Array.push(absolute)
90
90
  }
91
91
  }
92
92
  // Deduplicate (if roots overlap).
93
93
  let seen = Dict.make()
94
94
  let unique = []
95
- found.contents->Array.forEach(path =>
95
+ found->Array.forEach(path =>
96
96
  switch seen->Dict.get(path) {
97
97
  | Some(_) => ()
98
98
  | None => {
@@ -39,22 +39,19 @@ async function walk(dir, acc) {
39
39
  entries = [];
40
40
  }
41
41
  if (isPruned(entries)) {
42
- return acc;
42
+ return;
43
43
  }
44
- let found = acc;
45
44
  for (let i = 0, i_finish = entries.length; i < i_finish; ++i) {
46
45
  let entry = entries[i];
47
46
  if (!ignoreNames.includes(entry.name)) {
48
47
  let full = Nodepath.join(dir, entry.name);
49
48
  if (entry.isDirectory()) {
50
- let nested = await walk(full, []);
51
- found = found.concat(nested);
49
+ await walk(full, acc);
52
50
  } else if (entry.isFile() && isGwtTestFile(entry.name)) {
53
- found = found.concat([full]);
51
+ acc.push(full);
54
52
  }
55
53
  }
56
54
  }
57
- return found;
58
55
  }
59
56
 
60
57
  async function discover(roots) {
@@ -70,10 +67,9 @@ async function discover(roots) {
70
67
  isDir = false;
71
68
  }
72
69
  if (isDir) {
73
- let collected = await walk(absolute, []);
74
- found = found.concat(collected);
70
+ await walk(absolute, found);
75
71
  } else if (isGwtTestFile(absolute)) {
76
- found = found.concat([absolute]);
72
+ found.push(absolute);
77
73
  }
78
74
  }
79
75
  let seen = {};
Binary file
Binary file
@@ -41,51 +41,22 @@ let formatLocation = (loc: option<Collector.location>) =>
41
41
  | Some(l) => `${l.file}:${Int.toString(l.line)}`
42
42
  }
43
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}`
44
+ // Frames one normalized field as a terminal line. Labels are padded so values
45
+ // align at column 12 (`key:`, `expected:`, `actual:`); `error:` is its own
46
+ // shorter form and the `Throw` stack is dumped raw beneath it.
47
+ let renderField = (f: MismatchRender.field) =>
48
+ switch f {
49
+ | Expected(v) => ` expected: ${v}`
50
+ | Actual(v) => ` actual: ${v}`
51
+ | Key(k) => ` key: "${k}"`
52
+ | ExpectedNoEvents => " expected no events"
53
+ | Error(e) => ` error: ${e}`
54
+ | Stack(s) => s
87
55
  }
88
56
 
57
+ let renderMismatch = (m: Outcome.mismatch) =>
58
+ MismatchRender.normalize(m).fields->Array.map(renderField)->Array.join("\n")
59
+
89
60
  let emitTest = (t: RunnerTypes.testResult) => {
90
61
  let path = t.describePath->Array.join(" > ")
91
62
  let full = path == "" ? t.name : `${path} > ${t.name}`
@@ -4,7 +4,7 @@ import * as Nodepath from "node:path";
4
4
  import Picocolors from "picocolors";
5
5
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
6
6
  import * as Hint$ReventlessGwt from "./Hint.res.mjs";
7
- import * as RenderRescript$ReventlessGwt from "./RenderRescript.res.mjs";
7
+ import * as MismatchRender$ReventlessGwt from "./MismatchRender.res.mjs";
8
8
 
9
9
  function write(s) {
10
10
  process.stdout.write(s);
@@ -29,36 +29,28 @@ function formatLocation(loc) {
29
29
  }
30
30
  }
31
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;
32
+ function renderField(f) {
33
+ if (typeof f !== "object") {
34
+ return " expected no events";
35
+ }
36
+ switch (f.TAG) {
37
+ case "Expected" :
38
+ return ` expected: ` + f._0;
39
+ case "Actual" :
40
+ return ` actual: ` + f._0;
41
+ case "Key" :
42
+ return ` key: "` + f._0 + `"`;
43
+ case "Error" :
44
+ return ` error: ` + f._0;
45
+ case "Stack" :
46
+ return f._0;
59
47
  }
60
48
  }
61
49
 
50
+ function renderMismatch(m) {
51
+ return MismatchRender$ReventlessGwt.normalize(m).fields.map(renderField).join("\n");
52
+ }
53
+
62
54
  function emitTest(t) {
63
55
  let path = t.describePath.join(" > ");
64
56
  let full = path === "" ? t.name : path + ` > ` + t.name;
@@ -113,6 +105,7 @@ export {
113
105
  writeLine,
114
106
  formatFilePath,
115
107
  formatLocation,
108
+ renderField,
116
109
  renderMismatch,
117
110
  emitTest,
118
111
  emitFile,
@@ -5,7 +5,10 @@
5
5
 
6
6
  // 1.1.0 — additive: `PublishedActionsMismatch` mismatch kind for the
7
7
  // Delegate_GWT / cross-plugin Flow_GWT boundary steps.
8
- let schemaVersion = "1.1.0"
8
+ // The default the emitted envelope carries; the `--schema-version` CLI flag
9
+ // overrides it (threaded through `emit` / `streamRunStart`) so a consumer can
10
+ // pin the schema version its AI prompt was built against.
11
+ let defaultSchemaVersion = "1.1.0"
9
12
 
10
13
  @val external processStdout: {"write": string => unit} = "process.stdout"
11
14
  let write = (s: string) => processStdout["write"](s)
@@ -184,7 +187,11 @@ let summaryJson = (s: RunnerTypes.summary): JSON.t => {
184
187
  JSON.Encode.object(d)
185
188
  }
186
189
 
187
- let envelope = (r: RunnerTypes.runResult, ~toolVersion: string): JSON.t => {
190
+ let envelope = (
191
+ r: RunnerTypes.runResult,
192
+ ~toolVersion: string,
193
+ ~schemaVersion: string,
194
+ ): JSON.t => {
188
195
  let d = Dict.make()
189
196
  d->Dict.set("schemaVersion", JSON.Encode.string(schemaVersion))
190
197
  d->Dict.set("tool", JSON.Encode.string("reventless-gwt"))
@@ -196,11 +203,11 @@ let envelope = (r: RunnerTypes.runResult, ~toolVersion: string): JSON.t => {
196
203
  JSON.Encode.object(d)
197
204
  }
198
205
 
199
- let emit = (r: RunnerTypes.runResult, ~toolVersion: string) =>
200
- write(JSON.stringify(envelope(r, ~toolVersion), ~space=2) ++ "\n")
206
+ let emit = (r: RunnerTypes.runResult, ~toolVersion: string, ~schemaVersion=defaultSchemaVersion) =>
207
+ write(JSON.stringify(envelope(r, ~toolVersion, ~schemaVersion), ~space=2) ++ "\n")
201
208
 
202
209
  // Streaming variant — NDJSON events.
203
- let streamRunStart = (~toolVersion: string, ~startedAt: string) => {
210
+ let streamRunStart = (~toolVersion: string, ~startedAt: string, ~schemaVersion=defaultSchemaVersion) => {
204
211
  let d = Dict.make()
205
212
  d->Dict.set("type", JSON.Encode.string("runStarted"))
206
213
  d->Dict.set("schemaVersion", JSON.Encode.string(schemaVersion))
@@ -7,7 +7,7 @@ import * as Outcome$ReventlessGwt from "./Outcome.res.mjs";
7
7
  import * as RunnerTypes$ReventlessGwt from "./RunnerTypes.res.mjs";
8
8
  import * as RenderRescript$ReventlessGwt from "./RenderRescript.res.mjs";
9
9
 
10
- let schemaVersion = "1.1.0";
10
+ let defaultSchemaVersion = "1.1.0";
11
11
 
12
12
  function write(s) {
13
13
  process.stdout.write(s);
@@ -214,7 +214,7 @@ function summaryJson(s) {
214
214
  return d;
215
215
  }
216
216
 
217
- function envelope(r, toolVersion) {
217
+ function envelope(r, toolVersion, schemaVersion) {
218
218
  let d = {};
219
219
  d["schemaVersion"] = schemaVersion;
220
220
  d["tool"] = "reventless-gwt";
@@ -226,11 +226,13 @@ function envelope(r, toolVersion) {
226
226
  return d;
227
227
  }
228
228
 
229
- function emit(r, toolVersion) {
230
- process.stdout.write(JSON.stringify(envelope(r, toolVersion), undefined, 2) + "\n");
229
+ function emit(r, toolVersion, schemaVersionOpt) {
230
+ let schemaVersion = schemaVersionOpt !== undefined ? schemaVersionOpt : defaultSchemaVersion;
231
+ process.stdout.write(JSON.stringify(envelope(r, toolVersion, schemaVersion), undefined, 2) + "\n");
231
232
  }
232
233
 
233
- function streamRunStart(toolVersion, startedAt) {
234
+ function streamRunStart(toolVersion, startedAt, schemaVersionOpt) {
235
+ let schemaVersion = schemaVersionOpt !== undefined ? schemaVersionOpt : defaultSchemaVersion;
234
236
  let d = {};
235
237
  d["type"] = "runStarted";
236
238
  d["schemaVersion"] = schemaVersion;
@@ -299,7 +301,7 @@ function streamRunEnd(r) {
299
301
  }
300
302
 
301
303
  export {
302
- schemaVersion,
304
+ defaultSchemaVersion,
303
305
  write,
304
306
  writeLine,
305
307
  renderValue,
@@ -18,59 +18,20 @@ let yamlLine = (~indent=2, key: string, value: string) =>
18
18
  String.repeat(" ", indent) ++ key ++ ": " ++ value
19
19
 
20
20
  let renderMismatchYaml = (m: Outcome.mismatch) => {
21
- let lines = []
22
- lines->Array.push(yamlLine("kind", yamlString(Outcome.kindName(m))))
23
- switch m {
24
- | EventsMismatch({expected, actual}) => {
25
- lines->Array.push(yamlLine("expected", yamlString(RenderRescript.renderMany(expected))))
26
- lines->Array.push(yamlLine("actual", yamlString(RenderRescript.renderMany(actual))))
21
+ let n = MismatchRender.normalize(m)
22
+ let lines = [yamlLine("kind", yamlString(n.kind))]
23
+ // `ExpectedNoEvents` is a Human-only literal (TAP simply omits `expected`);
24
+ // every other field maps to one yaml `key: "escaped-value"` line.
25
+ n.fields->Array.forEach(f =>
26
+ switch f {
27
+ | Expected(v) => lines->Array.push(yamlLine("expected", yamlString(v)))
28
+ | Actual(v) => lines->Array.push(yamlLine("actual", yamlString(v)))
29
+ | Key(k) => lines->Array.push(yamlLine("key", yamlString(k)))
30
+ | Error(e) => lines->Array.push(yamlLine("error", yamlString(e)))
31
+ | Stack(s) => lines->Array.push(yamlLine("stack", yamlString(s)))
32
+ | ExpectedNoEvents => ()
27
33
  }
28
- | ErrorMismatch({expected, actual, actualEvents}) => {
29
- lines->Array.push(
30
- yamlLine("expected", yamlString("Error(" ++ RenderRescript.render(expected) ++ ")")),
31
- )
32
- let actualStr = switch actual {
33
- | Some(v) => "Error(" ++ RenderRescript.render(v) ++ ")"
34
- | None => "Ok(" ++ RenderRescript.renderMany(actualEvents) ++ ")"
35
- }
36
- lines->Array.push(yamlLine("actual", yamlString(actualStr)))
37
- }
38
- | StateMismatch({key, expected, actual}) => {
39
- lines->Array.push(yamlLine("key", yamlString(key)))
40
- lines->Array.push(yamlLine("expected", yamlString(RenderRescript.renderOption(expected))))
41
- lines->Array.push(yamlLine("actual", yamlString(RenderRescript.renderOption(actual))))
42
- }
43
- | NoEventExpected({actual}) =>
44
- lines->Array.push(yamlLine("actual", yamlString(RenderRescript.renderMany(actual))))
45
- | TodoMismatch({expected, actual}) => {
46
- let fmt = arr =>
47
- arr
48
- ->Array.map(((id, v)) => "(" ++ id ++ ", " ++ RenderRescript.render(v) ++ ")")
49
- ->Array.join(", ")
50
- lines->Array.push(yamlLine("expected", yamlString("[" ++ fmt(expected) ++ "]")))
51
- lines->Array.push(yamlLine("actual", yamlString("[" ++ fmt(actual) ++ "]")))
52
- }
53
- | AppendConditionMismatch({expected, actual}) => {
54
- lines->Array.push(yamlLine("expected", yamlString(RenderRescript.render(expected))))
55
- lines->Array.push(yamlLine("actual", yamlString(RenderRescript.render(actual))))
56
- }
57
- | TranslateError({expected, actual}) => {
58
- lines->Array.push(yamlLine("expected", yamlString(expected)))
59
- lines->Array.push(yamlLine("actual", yamlString(actual->Option.getOr("(none)"))))
60
- }
61
- | QueryRowsMismatch({expected, actual}) => {
62
- lines->Array.push(yamlLine("expected", yamlString(RenderRescript.renderMany(expected))))
63
- lines->Array.push(yamlLine("actual", yamlString(RenderRescript.renderMany(actual))))
64
- }
65
- | PublishedActionsMismatch({expected, actual}) => {
66
- lines->Array.push(yamlLine("expected", yamlString(RenderRescript.renderMany(expected))))
67
- lines->Array.push(yamlLine("actual", yamlString(RenderRescript.renderMany(actual))))
68
- }
69
- | Throw({error, stack}) => {
70
- lines->Array.push(yamlLine("error", yamlString(error)))
71
- lines->Array.push(yamlLine("stack", yamlString(stack)))
72
- }
73
- }
34
+ )
74
35
  lines->Array.join("\n")
75
36
  }
76
37