@as-pect/csv-reporter 8.1.0 → 9.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/__tests__/CSVReporter.spec.ts +240 -0
- package/index.ts +50 -54
- package/jest.config.js +17 -0
- package/lib/index.d.ts +11 -10
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +46 -53
- package/lib/index.js.map +1 -1
- package/package.json +8 -7
- package/readme.md +36 -3
- package/tsconfig.json +7 -3
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# @as-pect/csv-reporter
|
|
2
|
+
|
|
3
|
+
## 9.0.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 65058fe: Share file-backed reporter output behavior across CSV and JSON reporters while preserving existing output paths and formats.
|
|
8
|
+
- 703fde8: Document the as-pect CSV v1 column contract and add compatibility coverage for CSV escaping and empty actual/expected fields.
|
|
9
|
+
- 5abb0e1: Treat groups and tests skipped by `--group` or `--test` filters as skipped instead of failed, and suppress per-file reports and aggregate counts for files with no executed results.
|
|
10
|
+
- dd71792: Anchor built-in file-backed reporter output to the Test session project path, and allow programmatic reporter users to pass an explicit output root.
|
|
11
|
+
- Updated dependencies [dbaaae5]
|
|
12
|
+
- Updated dependencies [65058fe]
|
|
13
|
+
- Updated dependencies [5abb0e1]
|
|
14
|
+
- Updated dependencies [4420210]
|
|
15
|
+
- @as-pect/core@9.0.0
|
|
16
|
+
- @as-pect/reporter-output@9.0.0
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { access, mkdir, mkdtemp, readFile, rm } from "fs/promises";
|
|
2
|
+
import { join, relative } from "path";
|
|
3
|
+
import CSVReporter from "../index.js";
|
|
4
|
+
import type { SuiteReport, SuiteResultReport } from "@as-pect/core";
|
|
5
|
+
|
|
6
|
+
class TestCSVReporter extends CSVReporter {
|
|
7
|
+
public async writeAndWait(report: Pick<SuiteReport, "fileName" | "hasResults" | "results">): Promise<void> {
|
|
8
|
+
this.onReportFinish({ report: report as SuiteReport });
|
|
9
|
+
await this.onFlush();
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function testResult(overrides: Partial<SuiteResultReport> = {}): SuiteResultReport {
|
|
14
|
+
return {
|
|
15
|
+
type: "test",
|
|
16
|
+
groupName: "math",
|
|
17
|
+
name: "adds values",
|
|
18
|
+
ran: true,
|
|
19
|
+
negated: false,
|
|
20
|
+
pass: true,
|
|
21
|
+
runtime: 3,
|
|
22
|
+
message: "ok",
|
|
23
|
+
stackTrace: null,
|
|
24
|
+
rtraceDelta: 0,
|
|
25
|
+
logs: [],
|
|
26
|
+
actual: "3",
|
|
27
|
+
expected: "3",
|
|
28
|
+
actualValue: null,
|
|
29
|
+
expectedValue: null,
|
|
30
|
+
...overrides,
|
|
31
|
+
} as SuiteResultReport;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function parseRows(output: string): string[][] {
|
|
35
|
+
const rows: string[][] = [];
|
|
36
|
+
let row: string[] = [];
|
|
37
|
+
let field = "";
|
|
38
|
+
let quoted = false;
|
|
39
|
+
|
|
40
|
+
for (let i = 0; i < output.length; i++) {
|
|
41
|
+
const char = output[i];
|
|
42
|
+
const next = output[i + 1];
|
|
43
|
+
|
|
44
|
+
if (quoted) {
|
|
45
|
+
if (char === '"' && next === '"') {
|
|
46
|
+
field += '"';
|
|
47
|
+
i++;
|
|
48
|
+
} else if (char === '"') {
|
|
49
|
+
quoted = false;
|
|
50
|
+
} else {
|
|
51
|
+
field += char;
|
|
52
|
+
}
|
|
53
|
+
} else if (char === '"') {
|
|
54
|
+
quoted = true;
|
|
55
|
+
} else if (char === ",") {
|
|
56
|
+
row.push(field);
|
|
57
|
+
field = "";
|
|
58
|
+
} else if (char === "\n") {
|
|
59
|
+
row.push(field);
|
|
60
|
+
rows.push(row);
|
|
61
|
+
row = [];
|
|
62
|
+
field = "";
|
|
63
|
+
} else if (char !== "\r") {
|
|
64
|
+
field += char;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (field !== "" || row.length > 0) {
|
|
69
|
+
row.push(field);
|
|
70
|
+
rows.push(row);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return rows;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function reporterCreatesFile(report: Pick<SuiteReport, "hasResults" | "results">): Promise<boolean> {
|
|
77
|
+
const tempDir = await mkdtemp(join(process.cwd(), "tmp-csv-reporter-"));
|
|
78
|
+
const fileName = join(relative(process.cwd(), tempDir), "example.spec.ts");
|
|
79
|
+
const reporter = new TestCSVReporter();
|
|
80
|
+
const outputPath = join(tempDir, "example.spec.csv");
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
await reporter.writeAndWait({ fileName, ...report });
|
|
84
|
+
await access(outputPath);
|
|
85
|
+
return true;
|
|
86
|
+
} catch {
|
|
87
|
+
return false;
|
|
88
|
+
} finally {
|
|
89
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function runReporter(results: SuiteResultReport[]): Promise<string[][]> {
|
|
94
|
+
const tempDir = await mkdtemp(join(process.cwd(), "tmp-csv-reporter-"));
|
|
95
|
+
const fileName = join(relative(process.cwd(), tempDir), "example.spec.ts");
|
|
96
|
+
const reporter = new TestCSVReporter();
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
await reporter.writeAndWait({ fileName, hasResults: true, results });
|
|
100
|
+
const output = await readFile(join(tempDir, "example.spec.csv"), "utf8");
|
|
101
|
+
return parseRows(output);
|
|
102
|
+
} finally {
|
|
103
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function runNestedReporter(): Promise<string[][]> {
|
|
108
|
+
const tempDir = await mkdtemp(join(process.cwd(), "tmp-csv-reporter-"));
|
|
109
|
+
const sourceDir = join(tempDir, "assembly", "__tests__");
|
|
110
|
+
const fileName = join(relative(process.cwd(), sourceDir), "entry.spec.ts");
|
|
111
|
+
const reporter = new TestCSVReporter();
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
await mkdir(sourceDir, { recursive: true });
|
|
115
|
+
await reporter.writeAndWait({ fileName, hasResults: true, results: [testResult()] });
|
|
116
|
+
const output = await readFile(join(sourceDir, "entry.spec.csv"), "utf8");
|
|
117
|
+
return parseRows(output);
|
|
118
|
+
} finally {
|
|
119
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function runProjectRootReporter(): Promise<string[][]> {
|
|
124
|
+
const tempDir = await mkdtemp(join(process.cwd(), "tmp-csv-reporter-root-"));
|
|
125
|
+
const sourceDir = join(tempDir, "assembly", "__tests__");
|
|
126
|
+
const reporter = new TestCSVReporter(tempDir);
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
await mkdir(sourceDir, { recursive: true });
|
|
130
|
+
await reporter.writeAndWait({
|
|
131
|
+
fileName: "assembly/__tests__/entry.spec.ts",
|
|
132
|
+
hasResults: true,
|
|
133
|
+
results: [testResult()],
|
|
134
|
+
});
|
|
135
|
+
const output = await readFile(join(sourceDir, "entry.spec.csv"), "utf8");
|
|
136
|
+
return parseRows(output);
|
|
137
|
+
} finally {
|
|
138
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function writeReportWithMissingOutputDirectory(): Promise<void> {
|
|
143
|
+
const tempDir = await mkdtemp(join(process.cwd(), "tmp-csv-reporter-"));
|
|
144
|
+
const fileName = join(relative(process.cwd(), tempDir), "missing", "entry.spec.ts");
|
|
145
|
+
const reporter = new TestCSVReporter();
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
await reporter.writeAndWait({ fileName, hasResults: true, results: [testResult()] });
|
|
149
|
+
} finally {
|
|
150
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
describe("CSVReporter", () => {
|
|
155
|
+
it("does not write a file for reports with no executed results", async () => {
|
|
156
|
+
await expect(
|
|
157
|
+
reporterCreatesFile({
|
|
158
|
+
hasResults: false,
|
|
159
|
+
results: [testResult({ name: "skipped by filter", ran: false })],
|
|
160
|
+
}),
|
|
161
|
+
).resolves.toBe(false);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("writes the documented CSV header order", async () => {
|
|
165
|
+
await expect(runReporter([])).resolves.toEqual([
|
|
166
|
+
["Group", "Name", "Ran", "Negated", "Pass", "Runtime", "Message", "Actual", "Expected"],
|
|
167
|
+
]);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("writes the CSV file next to a nested test entry", async () => {
|
|
171
|
+
await expect(runNestedReporter()).resolves.toEqual([
|
|
172
|
+
["Group", "Name", "Ran", "Negated", "Pass", "Runtime", "Message", "Actual", "Expected"],
|
|
173
|
+
["math", "adds values", "RAN", "FALSE", "PASS", "3", "ok", "3", "3"],
|
|
174
|
+
]);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("writes project-relative report files under the configured output root", async () => {
|
|
178
|
+
await expect(runProjectRootReporter()).resolves.toEqual([
|
|
179
|
+
["Group", "Name", "Ran", "Negated", "Pass", "Runtime", "Message", "Actual", "Expected"],
|
|
180
|
+
["math", "adds values", "RAN", "FALSE", "PASS", "3", "ok", "3", "3"],
|
|
181
|
+
]);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("surfaces output stream errors through flush", async () => {
|
|
185
|
+
await expect(writeReportWithMissingOutputDirectory()).rejects.toMatchObject({ code: "ENOENT" });
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("writes test rows in the same column order as the header", async () => {
|
|
189
|
+
await expect(runReporter([testResult()])).resolves.toEqual([
|
|
190
|
+
["Group", "Name", "Ran", "Negated", "Pass", "Runtime", "Message", "Actual", "Expected"],
|
|
191
|
+
["math", "adds values", "RAN", "FALSE", "PASS", "3", "ok", "3", "3"],
|
|
192
|
+
]);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("writes NOT RUN under the Ran column for skipped test rows", async () => {
|
|
196
|
+
await expect(runReporter([testResult({ name: "skips values", ran: false })])).resolves.toEqual([
|
|
197
|
+
["Group", "Name", "Ran", "Negated", "Pass", "Runtime", "Message", "Actual", "Expected"],
|
|
198
|
+
["math", "skips values", "NOT RUN", "FALSE", "PASS", "3", "ok", "3", "3"],
|
|
199
|
+
]);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("writes todo rows in the same column order as the header", async () => {
|
|
203
|
+
await expect(
|
|
204
|
+
runReporter([
|
|
205
|
+
{
|
|
206
|
+
type: "todo",
|
|
207
|
+
groupName: "math",
|
|
208
|
+
description: "handles division",
|
|
209
|
+
},
|
|
210
|
+
]),
|
|
211
|
+
).resolves.toEqual([
|
|
212
|
+
["Group", "Name", "Ran", "Negated", "Pass", "Runtime", "Message", "Actual", "Expected"],
|
|
213
|
+
["math", "handles division", "TODO", "", "", "", "", "", ""],
|
|
214
|
+
]);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it("emits values with comma, quote, and newline as parseable CSV fields", async () => {
|
|
218
|
+
await expect(
|
|
219
|
+
runReporter([
|
|
220
|
+
testResult({
|
|
221
|
+
groupName: "math, core",
|
|
222
|
+
name: 'quotes "work"',
|
|
223
|
+
message: "line one\nline two",
|
|
224
|
+
actual: "1,2",
|
|
225
|
+
expected: '"1,2"',
|
|
226
|
+
}),
|
|
227
|
+
]),
|
|
228
|
+
).resolves.toEqual([
|
|
229
|
+
["Group", "Name", "Ran", "Negated", "Pass", "Runtime", "Message", "Actual", "Expected"],
|
|
230
|
+
["math, core", 'quotes "work"', "RAN", "FALSE", "PASS", "3", "line one\nline two", "1,2", '"1,2"'],
|
|
231
|
+
]);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("keeps null actual and expected values as empty CSV fields", async () => {
|
|
235
|
+
await expect(runReporter([testResult({ actual: null, expected: null })])).resolves.toEqual([
|
|
236
|
+
["Group", "Name", "Ran", "Negated", "Pass", "Runtime", "Message", "Actual", "Expected"],
|
|
237
|
+
["math", "adds values", "RAN", "FALSE", "PASS", "3", "ok", "", ""],
|
|
238
|
+
]);
|
|
239
|
+
});
|
|
240
|
+
});
|
package/index.ts
CHANGED
|
@@ -1,22 +1,12 @@
|
|
|
1
1
|
import { Stringifier, stringify } from "csv-stringify";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { finished } from "stream/promises";
|
|
3
|
+
import { TestContext, IReporter, IWritable, SuiteReport, SuiteReportEvent, SuiteResultReport } from "@as-pect/core";
|
|
4
|
+
import { ReporterFileOutput } from "@as-pect/reporter-output";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* This is a list of all the columns in the exported csv file.
|
|
8
8
|
*/
|
|
9
|
-
const csvColumns = [
|
|
10
|
-
"Group",
|
|
11
|
-
"Name",
|
|
12
|
-
"Ran",
|
|
13
|
-
"Negated",
|
|
14
|
-
"Pass",
|
|
15
|
-
"Runtime",
|
|
16
|
-
"Message",
|
|
17
|
-
"Actual",
|
|
18
|
-
"Expected",
|
|
19
|
-
];
|
|
9
|
+
const csvColumns = ["Group", "Name", "Ran", "Negated", "Pass", "Runtime", "Message", "Actual", "Expected"];
|
|
20
10
|
|
|
21
11
|
/**
|
|
22
12
|
* This class is responsible for creating a csv file located at {testName}.spec.csv. It will
|
|
@@ -27,55 +17,61 @@ export default class CSVReporter implements IReporter {
|
|
|
27
17
|
public stderr: IWritable | null = null;
|
|
28
18
|
|
|
29
19
|
protected output: Stringifier | null = null;
|
|
30
|
-
protected
|
|
20
|
+
protected fileOutput: ReporterFileOutput;
|
|
31
21
|
|
|
32
|
-
public
|
|
33
|
-
this.
|
|
34
|
-
const extension = extname(ctx.fileName);
|
|
35
|
-
const dir = dirname(ctx.fileName);
|
|
36
|
-
const base = basename(ctx.fileName, extension);
|
|
37
|
-
const outPath = join(process.cwd(), dir, base + ".csv");
|
|
38
|
-
this.fileName = createWriteStream(outPath, "utf8");
|
|
39
|
-
this.output.pipe(this.fileName);
|
|
40
|
-
this.output.write(csvColumns);
|
|
22
|
+
public constructor(outputRoot?: string) {
|
|
23
|
+
this.fileOutput = new ReporterFileOutput(undefined, outputRoot);
|
|
41
24
|
}
|
|
42
25
|
|
|
43
|
-
public
|
|
44
|
-
if (node.type === TestNodeType.Group) {
|
|
45
|
-
this.onGroupFinish(node);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
26
|
+
public onEnter(_ctx: TestContext): void {}
|
|
48
27
|
|
|
49
|
-
public
|
|
50
|
-
|
|
28
|
+
public onExit(_ctx: TestContext): void {}
|
|
29
|
+
|
|
30
|
+
public onReportFinish(event: SuiteReportEvent): void {
|
|
31
|
+
this.writeReport(event.report);
|
|
51
32
|
}
|
|
52
33
|
|
|
53
|
-
|
|
54
|
-
|
|
34
|
+
public onFinish(ctx: TestContext): void {
|
|
35
|
+
this.writeReport(SuiteReport.from(ctx));
|
|
36
|
+
}
|
|
55
37
|
|
|
56
|
-
|
|
57
|
-
|
|
38
|
+
public onFlush(): Promise<void> {
|
|
39
|
+
return this.fileOutput.flush();
|
|
58
40
|
}
|
|
59
41
|
|
|
60
|
-
protected
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
42
|
+
protected writeReport(report: SuiteReport): void {
|
|
43
|
+
if (report.hasResults === false) return;
|
|
44
|
+
|
|
45
|
+
const fileReport = this.fileOutput.start(report, ".csv");
|
|
46
|
+
if (fileReport === null) return;
|
|
47
|
+
|
|
48
|
+
this.output = stringify({ columns: csvColumns });
|
|
49
|
+
this.output.pipe(fileReport.stream);
|
|
50
|
+
this.fileOutput.trackFlush(finished(this.output).then(() => undefined));
|
|
51
|
+
this.output.write(csvColumns);
|
|
52
|
+
|
|
53
|
+
for (const result of report.results) {
|
|
54
|
+
this.onResult(result);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
this.output.end();
|
|
76
58
|
}
|
|
77
59
|
|
|
78
|
-
protected
|
|
79
|
-
|
|
60
|
+
protected onResult(result: SuiteResultReport): void {
|
|
61
|
+
if (result.type === "test") {
|
|
62
|
+
this.output!.write([
|
|
63
|
+
result.groupName,
|
|
64
|
+
result.name,
|
|
65
|
+
result.ran ? "RAN" : "NOT RUN",
|
|
66
|
+
result.negated ? "TRUE" : "FALSE",
|
|
67
|
+
result.pass ? "PASS" : "FAIL",
|
|
68
|
+
result.runtime.toString(),
|
|
69
|
+
result.message,
|
|
70
|
+
result.actual || "",
|
|
71
|
+
result.expected || "",
|
|
72
|
+
]);
|
|
73
|
+
} else {
|
|
74
|
+
this.output!.write([result.groupName, result.description, "TODO", "", "", "", "", "", ""]);
|
|
75
|
+
}
|
|
80
76
|
}
|
|
81
|
-
}
|
|
77
|
+
}
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { resolve } from "path";
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
testEnvironment: "node",
|
|
5
|
+
transform: {
|
|
6
|
+
"^.+\\.tsx?$": ["ts-jest", { useESM: true }],
|
|
7
|
+
},
|
|
8
|
+
testMatch: ["**/__tests__/**/*.spec.[jt]s"],
|
|
9
|
+
testPathIgnorePatterns: ["/node_modules/"],
|
|
10
|
+
extensionsToTreatAsEsm: [".ts"],
|
|
11
|
+
moduleNameMapper: {
|
|
12
|
+
"^(\\.{1,2}/.*)\\.js$": "$1",
|
|
13
|
+
"^@as-pect/core$": resolve("../core/src/index.ts"),
|
|
14
|
+
"^@as-pect/reporter-output$": resolve("../reporter-output/index.ts"),
|
|
15
|
+
"^@as-pect/snapshots$": resolve("../snapshots/src/index.ts"),
|
|
16
|
+
},
|
|
17
|
+
};
|
package/lib/index.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
1
|
import { Stringifier } from "csv-stringify";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { TestContext, IReporter, IWritable, SuiteReport, SuiteReportEvent, SuiteResultReport } from "@as-pect/core";
|
|
3
|
+
import { ReporterFileOutput } from "@as-pect/reporter-output";
|
|
5
4
|
/**
|
|
6
5
|
* This class is responsible for creating a csv file located at {testName}.spec.csv. It will
|
|
7
6
|
* contain a set of tests with relevant pass and fail information.
|
|
@@ -10,12 +9,14 @@ export default class CSVReporter implements IReporter {
|
|
|
10
9
|
stdout: IWritable | null;
|
|
11
10
|
stderr: IWritable | null;
|
|
12
11
|
protected output: Stringifier | null;
|
|
13
|
-
protected
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
protected fileOutput: ReporterFileOutput;
|
|
13
|
+
constructor(outputRoot?: string);
|
|
14
|
+
onEnter(_ctx: TestContext): void;
|
|
15
|
+
onExit(_ctx: TestContext): void;
|
|
16
|
+
onReportFinish(event: SuiteReportEvent): void;
|
|
17
|
+
onFinish(ctx: TestContext): void;
|
|
18
|
+
onFlush(): Promise<void>;
|
|
19
|
+
protected writeReport(report: SuiteReport): void;
|
|
20
|
+
protected onResult(result: SuiteResultReport): void;
|
|
20
21
|
}
|
|
21
22
|
//# sourceMappingURL=index.d.ts.map
|
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAa,MAAM,eAAe,CAAC;AAEvD,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACpH,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAO9D;;;GAGG;AACH,MAAM,CAAC,OAAO,OAAO,WAAY,YAAW,SAAS;IAC5C,MAAM,EAAE,SAAS,GAAG,IAAI,CAAQ;IAChC,MAAM,EAAE,SAAS,GAAG,IAAI,CAAQ;IAEvC,SAAS,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAAQ;IAC5C,SAAS,CAAC,UAAU,EAAE,kBAAkB,CAAC;gBAEtB,UAAU,CAAC,EAAE,MAAM;IAI/B,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI;IAEhC,MAAM,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI;IAE/B,cAAc,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI;IAI7C,QAAQ,CAAC,GAAG,EAAE,WAAW,GAAG,IAAI;IAIhC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAI/B,SAAS,CAAC,WAAW,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;IAkBhD,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI;CAiBpD"}
|
package/lib/index.js
CHANGED
|
@@ -1,20 +1,11 @@
|
|
|
1
1
|
import { stringify } from "csv-stringify";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { finished } from "stream/promises";
|
|
3
|
+
import { SuiteReport } from "@as-pect/core";
|
|
4
|
+
import { ReporterFileOutput } from "@as-pect/reporter-output";
|
|
4
5
|
/**
|
|
5
6
|
* This is a list of all the columns in the exported csv file.
|
|
6
7
|
*/
|
|
7
|
-
const csvColumns = [
|
|
8
|
-
"Group",
|
|
9
|
-
"Name",
|
|
10
|
-
"Ran",
|
|
11
|
-
"Negated",
|
|
12
|
-
"Pass",
|
|
13
|
-
"Runtime",
|
|
14
|
-
"Message",
|
|
15
|
-
"Actual",
|
|
16
|
-
"Expected",
|
|
17
|
-
];
|
|
8
|
+
const csvColumns = ["Group", "Name", "Ran", "Negated", "Pass", "Runtime", "Message", "Actual", "Expected"];
|
|
18
9
|
/**
|
|
19
10
|
* This class is responsible for creating a csv file located at {testName}.spec.csv. It will
|
|
20
11
|
* contain a set of tests with relevant pass and fail information.
|
|
@@ -23,51 +14,53 @@ export default class CSVReporter {
|
|
|
23
14
|
stdout = null;
|
|
24
15
|
stderr = null;
|
|
25
16
|
output = null;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
this.
|
|
29
|
-
const extension = extname(ctx.fileName);
|
|
30
|
-
const dir = dirname(ctx.fileName);
|
|
31
|
-
const base = basename(ctx.fileName, extension);
|
|
32
|
-
const outPath = join(process.cwd(), dir, base + ".csv");
|
|
33
|
-
this.fileName = createWriteStream(outPath, "utf8");
|
|
34
|
-
this.output.pipe(this.fileName);
|
|
35
|
-
this.output.write(csvColumns);
|
|
17
|
+
fileOutput;
|
|
18
|
+
constructor(outputRoot) {
|
|
19
|
+
this.fileOutput = new ReporterFileOutput(undefined, outputRoot);
|
|
36
20
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
21
|
+
onEnter(_ctx) { }
|
|
22
|
+
onExit(_ctx) { }
|
|
23
|
+
onReportFinish(event) {
|
|
24
|
+
this.writeReport(event.report);
|
|
41
25
|
}
|
|
42
|
-
onFinish() {
|
|
43
|
-
this.
|
|
26
|
+
onFinish(ctx) {
|
|
27
|
+
this.writeReport(SuiteReport.from(ctx));
|
|
44
28
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return;
|
|
48
|
-
group.groupTests.forEach((test) => this.onTestFinish(group, test));
|
|
49
|
-
group.groupTodos.forEach((desc) => this.onTodo(group, desc));
|
|
29
|
+
onFlush() {
|
|
30
|
+
return this.fileOutput.flush();
|
|
50
31
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
: "",
|
|
66
|
-
]);
|
|
32
|
+
writeReport(report) {
|
|
33
|
+
if (report.hasResults === false)
|
|
34
|
+
return;
|
|
35
|
+
const fileReport = this.fileOutput.start(report, ".csv");
|
|
36
|
+
if (fileReport === null)
|
|
37
|
+
return;
|
|
38
|
+
this.output = stringify({ columns: csvColumns });
|
|
39
|
+
this.output.pipe(fileReport.stream);
|
|
40
|
+
this.fileOutput.trackFlush(finished(this.output).then(() => undefined));
|
|
41
|
+
this.output.write(csvColumns);
|
|
42
|
+
for (const result of report.results) {
|
|
43
|
+
this.onResult(result);
|
|
44
|
+
}
|
|
45
|
+
this.output.end();
|
|
67
46
|
}
|
|
68
|
-
|
|
69
|
-
|
|
47
|
+
onResult(result) {
|
|
48
|
+
if (result.type === "test") {
|
|
49
|
+
this.output.write([
|
|
50
|
+
result.groupName,
|
|
51
|
+
result.name,
|
|
52
|
+
result.ran ? "RAN" : "NOT RUN",
|
|
53
|
+
result.negated ? "TRUE" : "FALSE",
|
|
54
|
+
result.pass ? "PASS" : "FAIL",
|
|
55
|
+
result.runtime.toString(),
|
|
56
|
+
result.message,
|
|
57
|
+
result.actual || "",
|
|
58
|
+
result.expected || "",
|
|
59
|
+
]);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
this.output.write([result.groupName, result.description, "TODO", "", "", "", "", "", ""]);
|
|
63
|
+
}
|
|
70
64
|
}
|
|
71
65
|
}
|
|
72
|
-
;
|
|
73
66
|
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,SAAS,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@as-pect/csv-reporter",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.0.0",
|
|
4
4
|
"description": "csv reporter for as-pect",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"scripts": {
|
|
9
9
|
"tsc:all": "tsc",
|
|
10
|
-
"test": "
|
|
10
|
+
"test": "node --experimental-vm-modules ../../node_modules/jest/bin/jest.js"
|
|
11
11
|
},
|
|
12
12
|
"repository": {
|
|
13
13
|
"type": "git",
|
|
@@ -15,11 +15,12 @@
|
|
|
15
15
|
},
|
|
16
16
|
"author": "Joshua Tenner",
|
|
17
17
|
"license": "MIT",
|
|
18
|
-
"devDependencies": {
|
|
19
|
-
"csv-stringify": "^6.2.3"
|
|
20
|
-
},
|
|
21
18
|
"dependencies": {
|
|
22
|
-
"@as-pect/core": "^
|
|
19
|
+
"@as-pect/core": "^9.0.0",
|
|
20
|
+
"@as-pect/reporter-output": "^9.0.0",
|
|
21
|
+
"csv-stringify": "^6.7.0"
|
|
23
22
|
},
|
|
24
|
-
"
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
}
|
|
25
26
|
}
|
package/readme.md
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
# jtenner/as-pect - @as-pect/csv-reporter
|
|
2
2
|
|
|
3
|
-
[](https://travis-ci.org/jtenner/as-pect)
|
|
3
|
+
[](https://github.com/jtenner/as-pect/actions/workflows/test.yml)
|
|
5
4
|
[](https://coveralls.io/github/jtenner/as-pect?branch=master)
|
|
6
|
-
[](https://github.com/changesets/changesets)
|
|
7
6
|
|
|
8
7
|
Write your module in AssemblyScript and get blazing fast bootstrapped tests
|
|
9
8
|
with WebAssembly speeds!
|
|
@@ -12,6 +11,40 @@ with WebAssembly speeds!
|
|
|
12
11
|
|
|
13
12
|
To view the documentation, it's located [here](https://tenner-joshua.gitbook.io/as-pect/) on the gitbook. If there are any issues with the docs, please feel free to file an issue!
|
|
14
13
|
|
|
14
|
+
## Reporter API
|
|
15
|
+
|
|
16
|
+
This package writes CSV rows from the Suite report facts delivered to `onReportFinish({ report })`. Custom file reporters should prefer the report event callbacks from `@as-pect/core` and treat `onFinish(ctx)` as a legacy compatibility callback.
|
|
17
|
+
|
|
18
|
+
## as-pect CSV v1 contract
|
|
19
|
+
|
|
20
|
+
`@as-pect/csv-reporter` emits an as-pect-specific tabular export for spreadsheet and lightweight data-processing workflows. It is not a cross-tool test-result standard.
|
|
21
|
+
|
|
22
|
+
The CSV file is written next to the test entry with the entry extension replaced by `.csv`. For example, `assembly/__tests__/entry.spec.ts` writes `assembly/__tests__/entry.spec.csv`. Empty reports where `SuiteReport.hasResults === false` do not create a file.
|
|
23
|
+
|
|
24
|
+
The v1 column order is stable and compatibility-tested:
|
|
25
|
+
|
|
26
|
+
| Column | Test result values | Todo result values |
|
|
27
|
+
| --- | --- | --- |
|
|
28
|
+
| `Group` | Suite result `groupName` | Todo `groupName` |
|
|
29
|
+
| `Name` | Test name | Todo description |
|
|
30
|
+
| `Ran` | `RAN` or `NOT RUN` | `TODO` |
|
|
31
|
+
| `Negated` | `TRUE` or `FALSE` | Empty |
|
|
32
|
+
| `Pass` | `PASS` or `FAIL` | Empty |
|
|
33
|
+
| `Runtime` | Runtime in milliseconds | Empty |
|
|
34
|
+
| `Message` | Result message | Empty |
|
|
35
|
+
| `Actual` | Actual value text, or empty when null | Empty |
|
|
36
|
+
| `Expected` | Expected value text, or empty when null | Empty |
|
|
37
|
+
|
|
38
|
+
Example output:
|
|
39
|
+
|
|
40
|
+
```csv
|
|
41
|
+
Group,Name,Ran,Negated,Pass,Runtime,Message,Actual,Expected
|
|
42
|
+
math,adds values,RAN,FALSE,PASS,3,ok,3,3
|
|
43
|
+
math,handles division,TODO,,,,,,
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Values are emitted through `csv-stringify`, so commas, quotes, and newlines are escaped as CSV fields instead of changing the column contract. Future column additions or vocabulary changes should use a new CSV contract version or an explicit migration note.
|
|
47
|
+
|
|
15
48
|
## Contributors
|
|
16
49
|
|
|
17
50
|
To contribute please see [CONTRIBUTING.md](./CONTRIBUTING.md).
|
package/tsconfig.json
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
// "removeComments": true, /* Do not emit comments to output. */
|
|
18
18
|
// "noEmit": true, /* Do not emit outputs. */
|
|
19
19
|
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
|
20
|
-
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
|
20
|
+
// // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
|
21
21
|
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
|
22
22
|
|
|
23
23
|
/* Strict Type-Checking Options */
|
|
@@ -37,12 +37,12 @@
|
|
|
37
37
|
"noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
|
38
38
|
|
|
39
39
|
/* Module Resolution Options */
|
|
40
|
-
"moduleResolution": "
|
|
40
|
+
"moduleResolution": "bundler", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
|
41
41
|
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
|
42
42
|
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
|
43
43
|
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
|
44
44
|
// "typeRoots": [], /* List of folders to include type definitions from. */
|
|
45
|
-
|
|
45
|
+
"types": ["node"], /* Type declaration files to be included in compilation. */
|
|
46
46
|
"allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
|
47
47
|
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
|
48
48
|
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
|
@@ -53,8 +53,12 @@
|
|
|
53
53
|
// "inlineSourceMap": true /* Emit a single file with source maps instead of having a separate file. */
|
|
54
54
|
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
|
55
55
|
|
|
56
|
+
"skipLibCheck": true,
|
|
57
|
+
|
|
56
58
|
/* Experimental Options */
|
|
57
59
|
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
|
58
60
|
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
|
59
61
|
},
|
|
62
|
+
"include": ["index.ts"],
|
|
63
|
+
"exclude": ["__tests__", "lib", "node_modules"]
|
|
60
64
|
}
|