as-test 0.4.3 → 0.5.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.
Files changed (67) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +196 -82
  3. package/as-test.config.schema.json +137 -0
  4. package/assembly/coverage.ts +19 -0
  5. package/assembly/index.ts +172 -85
  6. package/assembly/src/expectation.ts +263 -199
  7. package/assembly/src/log.ts +1 -9
  8. package/assembly/src/suite.ts +61 -25
  9. package/assembly/src/tests.ts +2 -0
  10. package/assembly/util/wipc.ts +286 -0
  11. package/bin/build.js +86 -41
  12. package/bin/index.js +337 -68
  13. package/bin/init.js +441 -183
  14. package/bin/reporter.js +1 -1
  15. package/bin/reporters/default.js +379 -0
  16. package/bin/reporters/types.js +1 -0
  17. package/bin/run.js +882 -194
  18. package/bin/types.js +14 -7
  19. package/bin/util.js +54 -3
  20. package/package.json +34 -16
  21. package/transform/lib/builder.js +169 -169
  22. package/transform/lib/builder.js.map +1 -1
  23. package/transform/lib/coverage.js +47 -1
  24. package/transform/lib/coverage.js.map +1 -1
  25. package/transform/lib/index.js +70 -0
  26. package/transform/lib/index.js.map +1 -1
  27. package/transform/lib/location.js +20 -0
  28. package/transform/lib/location.js.map +1 -0
  29. package/transform/lib/log.js +118 -0
  30. package/transform/lib/log.js.map +1 -0
  31. package/transform/lib/mock.js +2 -2
  32. package/transform/lib/mock.js.map +1 -1
  33. package/transform/lib/util.js +3 -3
  34. package/transform/lib/util.js.map +1 -1
  35. package/.github/workflows/as-test.yml +0 -26
  36. package/.prettierrc +0 -3
  37. package/as-test.config.json +0 -19
  38. package/assembly/__tests__/array.spec.ts +0 -25
  39. package/assembly/__tests__/math.spec.ts +0 -16
  40. package/assembly/__tests__/mock.spec.ts +0 -22
  41. package/assembly/__tests__/mock.ts +0 -7
  42. package/assembly/__tests__/sleep.spec.ts +0 -28
  43. package/assembly/tsconfig.json +0 -97
  44. package/assets/img/screenshot.png +0 -0
  45. package/cli/build.ts +0 -117
  46. package/cli/index.ts +0 -190
  47. package/cli/init.ts +0 -247
  48. package/cli/reporter.ts +0 -1
  49. package/cli/run.ts +0 -286
  50. package/cli/tsconfig.json +0 -9
  51. package/cli/types.ts +0 -29
  52. package/cli/util.ts +0 -65
  53. package/run/package.json +0 -27
  54. package/tests/array.run.js +0 -7
  55. package/tests/math.run.js +0 -7
  56. package/tests/mock.run.js +0 -14
  57. package/tests/sleep.run.js +0 -7
  58. package/transform/src/builder.ts +0 -1474
  59. package/transform/src/coverage.ts +0 -580
  60. package/transform/src/index.ts +0 -73
  61. package/transform/src/linker.ts +0 -41
  62. package/transform/src/mock.ts +0 -163
  63. package/transform/src/range.ts +0 -12
  64. package/transform/src/types.ts +0 -35
  65. package/transform/src/util.ts +0 -81
  66. package/transform/src/visitor.ts +0 -744
  67. package/transform/tsconfig.json +0 -10
@@ -0,0 +1,379 @@
1
+ import chalk from "chalk";
2
+ import { diff } from "typer-diff";
3
+ import { formatTime } from "../util.js";
4
+ export const createReporter = (context) => {
5
+ return new DefaultReporter(context);
6
+ };
7
+ class DefaultReporter {
8
+ constructor(context) {
9
+ this.context = context;
10
+ this.currentFile = null;
11
+ this.openSuites = [];
12
+ this.verboseSuites = [];
13
+ this.renderedLines = 0;
14
+ this.fileHasWarning = false;
15
+ this.verboseMode = false;
16
+ }
17
+ canRewriteLine() {
18
+ return Boolean(this.context.stdout.isTTY);
19
+ }
20
+ badgeRunning() {
21
+ return chalk.bgBlackBright.white(" .... ");
22
+ }
23
+ badgeFromVerdict(verdict) {
24
+ if (verdict == "ok")
25
+ return chalk.bgGreenBright.black(" PASS ");
26
+ if (verdict == "fail")
27
+ return chalk.bgRed.white(" FAIL ");
28
+ return chalk.bgBlackBright.white(" SKIP ");
29
+ }
30
+ clearRenderedBlock() {
31
+ if (!this.renderedLines || !this.canRewriteLine())
32
+ return;
33
+ for (let i = 0; i < this.renderedLines; i++) {
34
+ this.context.stdout.write("\r\x1b[2K");
35
+ if (i < this.renderedLines - 1) {
36
+ this.context.stdout.write("\x1b[1A");
37
+ }
38
+ }
39
+ this.renderedLines = 0;
40
+ }
41
+ drawLiveBlock(lines) {
42
+ this.clearRenderedBlock();
43
+ if (!lines.length)
44
+ return;
45
+ this.context.stdout.write(lines.join("\n"));
46
+ this.renderedLines = lines.length;
47
+ }
48
+ renderLiveState() {
49
+ if (!this.canRewriteLine() || !this.currentFile)
50
+ return;
51
+ const lines = [`${this.badgeRunning()} ${this.currentFile}`];
52
+ for (const suite of this.openSuites) {
53
+ lines.push(`${" ".repeat(suite.depth + 1)}${this.badgeRunning()} ${suite.description}`);
54
+ }
55
+ this.drawLiveBlock(lines);
56
+ }
57
+ renderVerboseState(fileEnd) {
58
+ if (!this.canRewriteLine() || !this.currentFile)
59
+ return;
60
+ const lines = [
61
+ fileEnd
62
+ ? this.renderFileResult(fileEnd)
63
+ : `${this.badgeRunning()} ${this.currentFile}`,
64
+ ];
65
+ for (const suite of this.verboseSuites) {
66
+ const badge = suite.verdict == "running"
67
+ ? this.badgeRunning()
68
+ : this.badgeFromVerdict(suite.verdict);
69
+ lines.push(`${" ".repeat(suite.depth + 1)}${badge} ${suite.description}`);
70
+ }
71
+ this.drawLiveBlock(lines);
72
+ }
73
+ setVerboseSuiteVerdict(depth, description, verdict) {
74
+ for (let i = this.verboseSuites.length - 1; i >= 0; i--) {
75
+ const suite = this.verboseSuites[i];
76
+ if (suite.depth == depth &&
77
+ (!description.length || suite.description == description) &&
78
+ suite.verdict == "running") {
79
+ if (description.length)
80
+ suite.description = description;
81
+ suite.verdict = verdict;
82
+ return;
83
+ }
84
+ }
85
+ this.verboseSuites.push({ depth, description, verdict });
86
+ }
87
+ collapseToDepth(depth) {
88
+ while (this.openSuites.length > depth) {
89
+ this.openSuites.pop();
90
+ }
91
+ }
92
+ renderSuiteCompleteFrame(depth, description, verdict) {
93
+ if (!this.canRewriteLine() || !this.currentFile)
94
+ return;
95
+ const lines = [`${this.badgeRunning()} ${this.currentFile}`];
96
+ for (let i = 0; i < depth; i++) {
97
+ const suite = this.openSuites[i];
98
+ if (!suite)
99
+ continue;
100
+ lines.push(`${" ".repeat(suite.depth + 1)}${this.badgeRunning()} ${suite.description}`);
101
+ }
102
+ lines.push(`${" ".repeat(depth + 1)}${this.badgeFromVerdict(verdict)} ${description}`);
103
+ this.drawLiveBlock(lines);
104
+ }
105
+ renderFileResult(event) {
106
+ const verdict = event.verdict ?? "none";
107
+ const time = event.time ? ` ${chalk.dim(event.time)}` : "";
108
+ if (verdict == "fail")
109
+ return `${chalk.bgRed.white(" FAIL ")} ${event.file}${time}`;
110
+ if (this.fileHasWarning)
111
+ return `${chalk.bgYellow.black(" WARN ")} ${event.file}${time}`;
112
+ if (verdict == "ok")
113
+ return `${chalk.bgGreenBright.black(" PASS ")} ${event.file}${time}`;
114
+ return `${chalk.bgBlackBright.white(" SKIP ")} ${event.file}${time}`;
115
+ }
116
+ onRunStart(event) {
117
+ this.verboseMode = Boolean(event.verbose);
118
+ if (event.clean)
119
+ return;
120
+ if (event.snapshotEnabled) {
121
+ this.context.stdout.write(chalk.bgBlue(" SNAPSHOT ") +
122
+ ` ${chalk.dim(event.updateSnapshots ? "update mode enabled" : "read-only mode")}\n\n`);
123
+ }
124
+ }
125
+ onFileStart(event) {
126
+ this.currentFile = event.file;
127
+ this.openSuites = [];
128
+ this.verboseSuites = [];
129
+ this.fileHasWarning = false;
130
+ if (this.verboseMode && this.canRewriteLine()) {
131
+ this.renderVerboseState();
132
+ return;
133
+ }
134
+ if (this.verboseMode || !this.canRewriteLine()) {
135
+ this.context.stdout.write(`${this.badgeRunning()} ${event.file}\n`);
136
+ return;
137
+ }
138
+ this.renderLiveState();
139
+ }
140
+ onFileEnd(event) {
141
+ if (this.verboseMode && this.canRewriteLine()) {
142
+ this.renderVerboseState(event);
143
+ this.context.stdout.write("\n");
144
+ this.renderedLines = 0;
145
+ this.currentFile = null;
146
+ this.openSuites = [];
147
+ this.verboseSuites = [];
148
+ this.fileHasWarning = false;
149
+ return;
150
+ }
151
+ const result = this.renderFileResult(event);
152
+ this.clearRenderedBlock();
153
+ this.context.stdout.write(`${result}\n`);
154
+ this.currentFile = null;
155
+ this.openSuites = [];
156
+ this.verboseSuites = [];
157
+ this.fileHasWarning = false;
158
+ }
159
+ onSuiteStart(event) {
160
+ const depth = Math.max(event.depth, 0);
161
+ if (this.verboseMode && this.canRewriteLine()) {
162
+ if (this.currentFile !== event.file)
163
+ return;
164
+ this.verboseSuites.push({
165
+ depth,
166
+ description: event.description,
167
+ verdict: "running",
168
+ });
169
+ this.renderVerboseState();
170
+ return;
171
+ }
172
+ if (this.verboseMode || !this.canRewriteLine()) {
173
+ this.context.stdout.write(`${" ".repeat(depth + 1)}${this.badgeRunning()} ${event.description}\n`);
174
+ return;
175
+ }
176
+ if (this.currentFile !== event.file)
177
+ return;
178
+ this.collapseToDepth(depth);
179
+ this.openSuites.push({ depth, description: event.description });
180
+ this.renderLiveState();
181
+ }
182
+ onSuiteEnd(event) {
183
+ const depth = Math.max(event.depth, 0);
184
+ const verdict = String(event.verdict ?? "none");
185
+ if (this.verboseMode && this.canRewriteLine()) {
186
+ if (this.currentFile !== event.file)
187
+ return;
188
+ this.setVerboseSuiteVerdict(depth, event.description, verdict);
189
+ this.renderVerboseState();
190
+ return;
191
+ }
192
+ if (this.verboseMode || !this.canRewriteLine()) {
193
+ this.context.stdout.write(`${" ".repeat(depth + 1)}${this.badgeFromVerdict(verdict)} ${event.description}\n`);
194
+ return;
195
+ }
196
+ if (this.currentFile !== event.file)
197
+ return;
198
+ this.collapseToDepth(depth + 1);
199
+ const current = this.openSuites[depth];
200
+ const description = event.description || current?.description || "suite";
201
+ if (!current) {
202
+ this.openSuites.push({ depth, description });
203
+ }
204
+ else {
205
+ current.description = description;
206
+ }
207
+ this.renderSuiteCompleteFrame(depth, description, verdict);
208
+ this.collapseToDepth(depth);
209
+ this.renderLiveState();
210
+ }
211
+ onAssertionFail(_event) { }
212
+ onSnapshotMissing(event) {
213
+ this.fileHasWarning = true;
214
+ const warnLine = `${chalk.bgYellow.black(" WARN ")} missing snapshot for ${chalk.dim(event.key)}. Re-run with ${chalk.bold("--update-snapshots")} to create it.\n`;
215
+ if (!this.canRewriteLine() || !this.currentFile) {
216
+ this.context.stdout.write(warnLine);
217
+ return;
218
+ }
219
+ this.clearRenderedBlock();
220
+ this.context.stdout.write(warnLine);
221
+ if (this.verboseMode) {
222
+ this.renderVerboseState();
223
+ }
224
+ else {
225
+ this.renderLiveState();
226
+ }
227
+ }
228
+ onRunComplete(event) {
229
+ this.clearRenderedBlock();
230
+ if (!event.clean) {
231
+ renderFailedSuites(event.stats.failedEntries);
232
+ }
233
+ if (event.snapshotEnabled) {
234
+ renderSnapshotSummary(event.snapshotSummary);
235
+ }
236
+ if (event.coverageSummary.enabled) {
237
+ renderCoverageSummary(event.coverageSummary);
238
+ if (event.showCoverage && event.coverageSummary.uncovered) {
239
+ renderCoveragePoints(event.coverageSummary.files);
240
+ }
241
+ }
242
+ renderTotals(event.stats);
243
+ }
244
+ }
245
+ function renderFailedSuites(failedEntries) {
246
+ if (!failedEntries.length)
247
+ return;
248
+ console.log("");
249
+ const printed = new Set();
250
+ for (const failed of failedEntries) {
251
+ const failedAny = failed;
252
+ if (!failedAny?.file)
253
+ continue;
254
+ const file = String(failedAny.file);
255
+ collectSuiteFailures(failed, file, [], printed);
256
+ }
257
+ }
258
+ function collectSuiteFailures(suite, file, path, printed) {
259
+ const suiteAny = suite;
260
+ const nextPath = [...path, String(suiteAny.description ?? "unknown")];
261
+ const tests = Array.isArray(suiteAny.tests)
262
+ ? suiteAny.tests
263
+ : [];
264
+ for (let i = 0; i < tests.length; i++) {
265
+ const test = tests[i];
266
+ if (test.verdict != "fail")
267
+ continue;
268
+ const assertionIndex = i + 1;
269
+ const title = `${nextPath.join(" > ")}#${assertionIndex}`;
270
+ const loc = String(test.location ?? "");
271
+ const where = loc.length ? `${file}:${loc}` : file;
272
+ const dedupeKey = `${file}::${title}::${String(test.left)}::${String(test.right)}`;
273
+ if (printed.has(dedupeKey))
274
+ continue;
275
+ printed.add(dedupeKey);
276
+ const left = JSON.stringify(test.left);
277
+ const right = JSON.stringify(test.right);
278
+ const diffResult = diff(left, right);
279
+ let expected = "";
280
+ for (const res of diffResult.diff) {
281
+ switch (res.type) {
282
+ case "correct":
283
+ expected += chalk.dim(res.value);
284
+ break;
285
+ case "extra":
286
+ expected += chalk.red.strikethrough(res.value);
287
+ break;
288
+ case "missing":
289
+ expected += chalk.bgBlack(res.value);
290
+ break;
291
+ case "wrong":
292
+ expected += chalk.bgRed(res.value);
293
+ break;
294
+ case "untouched":
295
+ case "spacer":
296
+ break;
297
+ }
298
+ }
299
+ console.log(`${chalk.bgRed(" FAIL ")} ${chalk.dim(title)} ${chalk.dim("(" + where + ")")}`);
300
+ console.log(`${chalk.dim("(expected) ->")} ${expected}`);
301
+ console.log(`${chalk.dim("(received) ->")} ${chalk.dim(left)}\n`);
302
+ }
303
+ const suites = Array.isArray(suiteAny.suites)
304
+ ? suiteAny.suites
305
+ : [];
306
+ for (const sub of suites) {
307
+ collectSuiteFailures(sub, file, nextPath, printed);
308
+ }
309
+ }
310
+ function renderSnapshotSummary(snapshotSummary) {
311
+ console.log("");
312
+ console.log(`${chalk.bold("Snapshots:")} ${chalk.greenBright(snapshotSummary.matched)} matched, ${chalk.blueBright(snapshotSummary.created)} created, ${chalk.blueBright(snapshotSummary.updated)} updated, ${snapshotSummary.failed ? chalk.red(snapshotSummary.failed) : chalk.greenBright("0")} failed`);
313
+ }
314
+ function renderTotals(stats) {
315
+ console.log("");
316
+ process.stdout.write(chalk.bold("Files: "));
317
+ process.stdout.write(stats.failedFiles
318
+ ? chalk.bold.red(stats.failedFiles + " failed")
319
+ : chalk.bold.greenBright("0 failed"));
320
+ process.stdout.write(", " +
321
+ (stats.skippedFiles
322
+ ? chalk.gray(stats.skippedFiles + " skipped")
323
+ : chalk.gray("0 skipped")));
324
+ process.stdout.write(", " +
325
+ (stats.failedFiles + stats.passedFiles + stats.skippedFiles) +
326
+ " total\n");
327
+ process.stdout.write(chalk.bold("Suites: "));
328
+ process.stdout.write(stats.failedSuites
329
+ ? chalk.bold.red(stats.failedSuites + " failed")
330
+ : chalk.bold.greenBright("0 failed"));
331
+ process.stdout.write(", " +
332
+ (stats.skippedSuites
333
+ ? chalk.gray(stats.skippedSuites + " skipped")
334
+ : chalk.gray("0 skipped")));
335
+ process.stdout.write(", " +
336
+ (stats.failedSuites + stats.passedSuites + stats.skippedSuites) +
337
+ " total\n");
338
+ process.stdout.write(chalk.bold("Tests: "));
339
+ process.stdout.write(stats.failedTests
340
+ ? chalk.bold.red(stats.failedTests + " failed")
341
+ : chalk.bold.greenBright("0 failed"));
342
+ process.stdout.write(", " +
343
+ (stats.skippedTests
344
+ ? chalk.gray(stats.skippedTests + " skipped")
345
+ : chalk.gray("0 skipped")));
346
+ process.stdout.write(", " + (stats.failedTests + stats.passedTests + stats.skippedTests) + " total\n");
347
+ process.stdout.write(chalk.bold("Time: ") + formatTime(stats.time) + "\n");
348
+ }
349
+ function renderCoverageSummary(summary) {
350
+ const pct = summary.total
351
+ ? ((summary.covered * 100) / summary.total).toFixed(2)
352
+ : "100.00";
353
+ const color = Number(pct) >= 90
354
+ ? chalk.greenBright
355
+ : Number(pct) >= 75
356
+ ? chalk.yellowBright
357
+ : chalk.redBright;
358
+ console.log("");
359
+ console.log(`${chalk.bold("Coverage:")} ${color(pct + "%")} ${chalk.dim(`(${summary.covered}/${summary.total} points, ${summary.uncovered} uncovered)${Number(pct) < 100.0 ? " run with --show-coverage to see details" : ""}`)}`);
360
+ }
361
+ function renderCoveragePoints(files) {
362
+ console.log("");
363
+ console.log(chalk.bold("Coverage Points:"));
364
+ const sortedFiles = [...files].sort((a, b) => a.file.localeCompare(b.file));
365
+ for (const file of sortedFiles) {
366
+ const points = [...file.points].sort((a, b) => {
367
+ if (a.line != b.line)
368
+ return a.line - b.line;
369
+ if (a.column != b.column)
370
+ return a.column - b.column;
371
+ return a.type.localeCompare(b.type);
372
+ });
373
+ for (const point of points) {
374
+ if (point.executed)
375
+ continue;
376
+ console.log(`${chalk.bgRed(" MISS ")} ${chalk.dim(`${point.file}:${point.line}:${point.column}`)} ${chalk.dim(point.type)}`);
377
+ }
378
+ }
379
+ }
@@ -0,0 +1 @@
1
+ export {};