factorio-test-cli 3.3.0 → 3.3.1

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.
@@ -132,6 +132,9 @@ function createOutputComponents(options) {
132
132
  progress.handleTestFinished(test);
133
133
  progress.withPermanentOutput(() => printer.printTestResult(test));
134
134
  });
135
+ collector.on("describeBlockFailed", (block) => {
136
+ progress.withPermanentOutput(() => printer.printTestResult(block));
137
+ });
135
138
  handler.on("result", () => {
136
139
  progress.finish();
137
140
  printer.resetMessage();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "factorio-test-cli",
3
- "version": "3.3.0",
3
+ "version": "3.3.1",
4
4
  "description": "A CLI to run FactorioTest.",
5
5
  "license": "MIT",
6
6
  "repository": {
package/test-output.js CHANGED
@@ -87,16 +87,18 @@ export class OutputFormatter {
87
87
  formatTestResult(test) {
88
88
  if (this.options.quiet)
89
89
  return;
90
- const showLogs = test.result === "failed" || this.options.showPassedLogs;
90
+ const prefix = this.getPrefix(test.result);
91
+ const duration = test.durationMs !== undefined ? ` (${formatDuration(test.durationMs)})` : "";
92
+ console.log(`${prefix} ${test.path}${duration}`);
93
+ const showLogs = test.result === "failed" || test.result === "error" || this.options.showPassedLogs;
91
94
  if (showLogs && test.logs.length > 0) {
95
+ console.log("Log messages:");
92
96
  for (const log of test.logs) {
93
97
  console.log(" " + log);
94
98
  }
95
99
  }
96
- const prefix = this.getPrefix(test.result);
97
- const duration = test.durationMs !== undefined ? ` (${formatDuration(test.durationMs)})` : "";
98
- console.log(`${prefix} ${test.path}${duration}`);
99
- if (test.result === "failed") {
100
+ if ((test.result === "failed" || test.result === "error") && test.errors.length > 0) {
101
+ console.log("Errors:");
100
102
  for (const error of test.errors) {
101
103
  console.log(" " + error);
102
104
  }
@@ -107,6 +109,7 @@ export class OutputFormatter {
107
109
  return;
108
110
  if (!this.options.quiet) {
109
111
  this.printRecapSection(data.tests, "failed", "Failures:");
112
+ this.printRecapSection(data.tests, "error", "Describe block errors:");
110
113
  this.printRecapSection(data.tests, "todo", "Todo:");
111
114
  }
112
115
  this.printCountsLine(data.summary);
@@ -126,6 +129,8 @@ export class OutputFormatter {
126
129
  const segments = [];
127
130
  if (summary.failed > 0)
128
131
  segments.push(chalk.red(`${summary.failed} failed`));
132
+ if (summary.describeBlockErrors > 0)
133
+ segments.push(chalk.red(`${summary.describeBlockErrors} errors`));
129
134
  if (summary.todo > 0)
130
135
  segments.push(chalk.magenta(`${summary.todo} todo`));
131
136
  if (summary.skipped > 0)
@@ -140,6 +145,8 @@ export class OutputFormatter {
140
145
  return chalk.green("PASS");
141
146
  case "failed":
142
147
  return chalk.red("FAIL");
148
+ case "error":
149
+ return chalk.red("ERROR");
143
150
  case "skipped":
144
151
  return chalk.yellow("SKIP");
145
152
  case "todo":
@@ -150,12 +150,19 @@ describe("OutputFormatter", () => {
150
150
  expect(output.some((line) => line.includes("assertion failed"))).toBe(true);
151
151
  expect(output.some((line) => line.includes("at test.ts:10"))).toBe(true);
152
152
  });
153
- it("shows logs before failed test result", () => {
153
+ it("shows status line first, then logs and errors with headers", () => {
154
154
  const formatter = new OutputFormatter({});
155
155
  formatter.formatTestResult(failedTest);
156
- const logIndex = output.findIndex((line) => line.includes("debug output"));
157
156
  const failIndex = output.findIndex((line) => line.includes("FAIL"));
158
- expect(logIndex).toBeLessThan(failIndex);
157
+ const logHeaderIndex = output.findIndex((line) => line.includes("Log messages:"));
158
+ const logIndex = output.findIndex((line) => line.includes("debug output"));
159
+ const errorHeaderIndex = output.findIndex((line) => line.includes("Errors:"));
160
+ const errorIndex = output.findIndex((line) => line.includes("assertion failed"));
161
+ expect(failIndex).toBe(0);
162
+ expect(logHeaderIndex).toBeGreaterThan(failIndex);
163
+ expect(logIndex).toBeGreaterThan(logHeaderIndex);
164
+ expect(errorHeaderIndex).toBeGreaterThan(logIndex);
165
+ expect(errorIndex).toBeGreaterThan(errorHeaderIndex);
159
166
  });
160
167
  it("hides logs for passed tests by default", () => {
161
168
  const formatter = new OutputFormatter({});
@@ -199,6 +206,22 @@ describe("OutputFormatter", () => {
199
206
  expect(output[0]).toContain("TODO");
200
207
  expect(output[0]).toContain("todo test");
201
208
  });
209
+ it("formats describe block error with ERROR prefix", () => {
210
+ const formatter = new OutputFormatter({});
211
+ const errorTest = {
212
+ path: "root > block",
213
+ result: "error",
214
+ errors: ["Error running afterAll: Oh no"],
215
+ logs: ["hook log"],
216
+ };
217
+ formatter.formatTestResult(errorTest);
218
+ expect(output[0]).toContain("ERROR");
219
+ expect(output[0]).toContain("root > block");
220
+ expect(output.some((line) => line.includes("Log messages:"))).toBe(true);
221
+ expect(output.some((line) => line.includes("hook log"))).toBe(true);
222
+ expect(output.some((line) => line.includes("Errors:"))).toBe(true);
223
+ expect(output.some((line) => line.includes("Oh no"))).toBe(true);
224
+ });
202
225
  });
203
226
  describe("OutputFormatter.formatSummary", () => {
204
227
  let consoleSpy;
@@ -298,6 +321,37 @@ describe("OutputFormatter.formatSummary", () => {
298
321
  expect(countsLine).toContain("1 passed");
299
322
  expect(countsLine).toContain("(4 total)");
300
323
  });
324
+ it("shows describe block errors in separate section from failures", () => {
325
+ const formatter = new OutputFormatter({});
326
+ const data = {
327
+ tests: [
328
+ { path: "test", result: "failed", errors: ["test err"], logs: [] },
329
+ { path: "block", result: "error", errors: ["block err"], logs: [] },
330
+ ],
331
+ summary: makeSummary({ ran: 1, failed: 1, describeBlockErrors: 1, status: "failed" }),
332
+ };
333
+ formatter.formatSummary(data);
334
+ expect(output.some((l) => l.includes("Failures:"))).toBe(true);
335
+ expect(output.some((l) => l.includes("Describe block errors:"))).toBe(true);
336
+ const failuresIdx = output.findIndex((l) => l.includes("Failures:"));
337
+ const errorsIdx = output.findIndex((l) => l.includes("Describe block errors:"));
338
+ const failLine = output.findIndex((l) => l.includes("FAIL"));
339
+ const errorLine = output.findIndex((l) => l.includes("ERROR"));
340
+ expect(failLine).toBeGreaterThan(failuresIdx);
341
+ expect(failLine).toBeLessThan(errorsIdx);
342
+ expect(errorLine).toBeGreaterThan(errorsIdx);
343
+ });
344
+ it("includes describe block errors count in summary line", () => {
345
+ const formatter = new OutputFormatter({});
346
+ const data = {
347
+ tests: [{ path: "block", result: "error", errors: ["err"], logs: [] }],
348
+ summary: makeSummary({ passed: 1, describeBlockErrors: 2, status: "failed" }),
349
+ };
350
+ formatter.formatSummary(data);
351
+ const countsLine = output.find((l) => l.includes("Tests:"));
352
+ expect(countsLine).toContain("2 errors");
353
+ expect(countsLine).toContain("1 passed");
354
+ });
301
355
  it("does nothing when summary is undefined", () => {
302
356
  const formatter = new OutputFormatter({});
303
357
  formatter.formatSummary({ tests: [] });
package/test-results.js CHANGED
@@ -5,11 +5,13 @@ export class TestRunCollector extends EventEmitter {
5
5
  data = { tests: [] };
6
6
  currentTest;
7
7
  currentLogs = [];
8
+ pendingLogs = [];
8
9
  testStartTime;
9
10
  handleEvent(event) {
10
11
  switch (event.type) {
11
12
  case "testStarted":
12
13
  this.flushCurrentTest();
14
+ this.pendingLogs = [];
13
15
  this.testStartTime = performance.now();
14
16
  this.currentTest = {
15
17
  path: event.test.path,
@@ -61,6 +63,20 @@ export class TestRunCollector extends EventEmitter {
61
63
  logs: [],
62
64
  });
63
65
  break;
66
+ case "describeBlockFailed": {
67
+ this.flushCurrentTest();
68
+ const captured = {
69
+ path: event.block.path,
70
+ source: event.block.source,
71
+ result: "error",
72
+ errors: event.errors,
73
+ logs: [...this.pendingLogs],
74
+ };
75
+ this.data.tests.push(captured);
76
+ this.emit("describeBlockFailed", captured);
77
+ this.pendingLogs = [];
78
+ break;
79
+ }
64
80
  case "testRunFinished":
65
81
  this.flushCurrentTest();
66
82
  this.data.summary = event.results;
@@ -75,6 +91,9 @@ export class TestRunCollector extends EventEmitter {
75
91
  if (this.currentTest) {
76
92
  this.currentLogs.push(line);
77
93
  }
94
+ else {
95
+ this.pendingLogs.push(line);
96
+ }
78
97
  }
79
98
  getData() {
80
99
  return this.data;
@@ -38,12 +38,69 @@ describe("TestRunCollector", () => {
38
38
  const data = collector.getData();
39
39
  expect(data.tests[0].logs).toEqual(["log line 1", "log line 2"]);
40
40
  });
41
- it("does not capture logs when no test is running", () => {
41
+ it("does not attach pending logs to skipped tests", () => {
42
42
  collector.captureLog("orphan log");
43
43
  collector.handleEvent({ type: "testSkipped", test: { path: "test" } });
44
44
  const data = collector.getData();
45
45
  expect(data.tests[0].logs).toEqual([]);
46
46
  });
47
+ it("handles describeBlockFailed with errors and pending logs", () => {
48
+ collector.captureLog("hook output");
49
+ collector.handleEvent({
50
+ type: "describeBlockFailed",
51
+ block: { path: "root > block", source: { file: "test.ts", line: 5 } },
52
+ errors: ["Error running afterAll: Oh no"],
53
+ });
54
+ const data = collector.getData();
55
+ expect(data.tests).toHaveLength(1);
56
+ expect(data.tests[0]).toMatchObject({
57
+ path: "root > block",
58
+ result: "error",
59
+ errors: ["Error running afterAll: Oh no"],
60
+ logs: ["hook output"],
61
+ source: { file: "test.ts", line: 5 },
62
+ });
63
+ });
64
+ it("emits describeBlockFailed event", () => {
65
+ const handler = vi.fn();
66
+ collector.on("describeBlockFailed", handler);
67
+ collector.handleEvent({
68
+ type: "describeBlockFailed",
69
+ block: { path: "block" },
70
+ errors: ["err"],
71
+ });
72
+ expect(handler).toHaveBeenCalledOnce();
73
+ expect(handler.mock.calls[0][0].path).toBe("block");
74
+ });
75
+ it("clears pending logs when a new test starts", () => {
76
+ collector.captureLog("before test");
77
+ collector.handleEvent({ type: "testStarted", test: { path: "test" } });
78
+ collector.handleEvent({ type: "testPassed", test: { path: "test" } });
79
+ collector.handleEvent({
80
+ type: "describeBlockFailed",
81
+ block: { path: "block" },
82
+ errors: ["err"],
83
+ });
84
+ const data = collector.getData();
85
+ const block = data.tests.find((t) => t.path === "block");
86
+ expect(block.logs).toEqual([]);
87
+ });
88
+ it("captures logs after test finishes for describe block failure", () => {
89
+ collector.handleEvent({ type: "testStarted", test: { path: "test" } });
90
+ collector.captureLog("test log");
91
+ collector.handleEvent({ type: "testPassed", test: { path: "test" } });
92
+ collector.captureLog("after_all log");
93
+ collector.handleEvent({
94
+ type: "describeBlockFailed",
95
+ block: { path: "block" },
96
+ errors: ["hook error"],
97
+ });
98
+ const data = collector.getData();
99
+ expect(data.tests[0].logs).toEqual(["test log"]);
100
+ const block = data.tests[1];
101
+ expect(block.logs).toEqual(["after_all log"]);
102
+ expect(block.errors).toEqual(["hook error"]);
103
+ });
47
104
  it("handles testSkipped without prior testStarted", () => {
48
105
  collector.handleEvent({ type: "testSkipped", test: { path: "skipped test" } });
49
106
  const data = collector.getData();