executable-stories-vitest 8.7.0 → 8.8.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/README.md +12 -0
- package/dist/index.cjs +111 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +111 -2
- package/dist/index.js.map +1 -1
- package/dist/reporter.cjs +66 -1
- package/dist/reporter.cjs.map +1 -1
- package/dist/reporter.d.cts +28 -0
- package/dist/reporter.d.ts +28 -0
- package/dist/reporter.js +66 -1
- package/dist/reporter.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/story-api.ts
|
|
2
|
+
import { expect, onTestFinished } from "vitest";
|
|
2
3
|
import { createRequire } from "module";
|
|
3
4
|
import { tryGetActiveOtelContext, resolveTraceUrl } from "executable-stories-core/utils/otel-detect";
|
|
4
5
|
import { buildHtmlDocEntry } from "executable-stories-core/utils/doc-builders";
|
|
@@ -214,8 +215,41 @@ function init(task, options) {
|
|
|
214
215
|
attachments: [],
|
|
215
216
|
activeTimers: /* @__PURE__ */ new Map(),
|
|
216
217
|
timerCounter: 0,
|
|
217
|
-
traceUrlTemplate
|
|
218
|
+
traceUrlTemplate,
|
|
219
|
+
pendingStep: null
|
|
218
220
|
};
|
|
221
|
+
const ctx = activeContext;
|
|
222
|
+
try {
|
|
223
|
+
onTestFinished(() => {
|
|
224
|
+
flushPendingAssertions(ctx);
|
|
225
|
+
task.meta.story = ctx.meta;
|
|
226
|
+
});
|
|
227
|
+
} catch {
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function flushPendingAssertions(ctx) {
|
|
231
|
+
const step = ctx.pendingStep;
|
|
232
|
+
const before = ctx.pendingFrom;
|
|
233
|
+
ctx.pendingStep = null;
|
|
234
|
+
ctx.pendingFrom = void 0;
|
|
235
|
+
if (!step || before === void 0) return;
|
|
236
|
+
const after = readAssertionCount();
|
|
237
|
+
if (after === void 0) return;
|
|
238
|
+
step.assertions = after - before;
|
|
239
|
+
}
|
|
240
|
+
function readAssertionCount() {
|
|
241
|
+
try {
|
|
242
|
+
const state2 = expect.getState?.();
|
|
243
|
+
return typeof state2?.assertionCalls === "number" ? state2.assertionCalls : void 0;
|
|
244
|
+
} catch {
|
|
245
|
+
return void 0;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
function recordAssertions(step, before) {
|
|
249
|
+
if (before === void 0) return;
|
|
250
|
+
const after = readAssertionCount();
|
|
251
|
+
if (after === void 0) return;
|
|
252
|
+
step.assertions = after - before;
|
|
219
253
|
}
|
|
220
254
|
function createStepMarker(keyword) {
|
|
221
255
|
function stepMarker(text, docsOrBody) {
|
|
@@ -234,8 +268,13 @@ function createStepMarker(keyword) {
|
|
|
234
268
|
docs: stepDocs,
|
|
235
269
|
...isCallback ? { wrapped: true } : {}
|
|
236
270
|
};
|
|
271
|
+
flushPendingAssertions(ctx);
|
|
237
272
|
ctx.meta.steps.push(step);
|
|
238
273
|
ctx.currentStep = step;
|
|
274
|
+
if (!isCallback) {
|
|
275
|
+
ctx.pendingStep = step;
|
|
276
|
+
ctx.pendingFrom = readAssertionCount();
|
|
277
|
+
}
|
|
239
278
|
syncMetaToTask();
|
|
240
279
|
if (isChildrenArray) {
|
|
241
280
|
const children = docsOrBody;
|
|
@@ -255,27 +294,32 @@ function createStepMarker(keyword) {
|
|
|
255
294
|
if (!isCallback) return;
|
|
256
295
|
const body = docsOrBody;
|
|
257
296
|
const start = performance.now();
|
|
297
|
+
const assertionsBefore = readAssertionCount();
|
|
258
298
|
try {
|
|
259
299
|
const result = body();
|
|
260
300
|
if (result instanceof Promise) {
|
|
261
301
|
return result.then(
|
|
262
302
|
(val) => {
|
|
263
303
|
step.durationMs = performance.now() - start;
|
|
304
|
+
recordAssertions(step, assertionsBefore);
|
|
264
305
|
syncMetaToTask();
|
|
265
306
|
return val;
|
|
266
307
|
},
|
|
267
308
|
(err) => {
|
|
268
309
|
step.durationMs = performance.now() - start;
|
|
310
|
+
recordAssertions(step, assertionsBefore);
|
|
269
311
|
syncMetaToTask();
|
|
270
312
|
throw err;
|
|
271
313
|
}
|
|
272
314
|
);
|
|
273
315
|
}
|
|
274
316
|
step.durationMs = performance.now() - start;
|
|
317
|
+
recordAssertions(step, assertionsBefore);
|
|
275
318
|
syncMetaToTask();
|
|
276
319
|
return result;
|
|
277
320
|
} catch (err) {
|
|
278
321
|
step.durationMs = performance.now() - start;
|
|
322
|
+
recordAssertions(step, assertionsBefore);
|
|
279
323
|
syncMetaToTask();
|
|
280
324
|
throw err;
|
|
281
325
|
}
|
|
@@ -664,6 +708,11 @@ var StoryReporter = class {
|
|
|
664
708
|
startTime = 0;
|
|
665
709
|
packageVersion;
|
|
666
710
|
gitSha;
|
|
711
|
+
/**
|
|
712
|
+
* Left unknown until onInit sees a config. Claiming full coverage without
|
|
713
|
+
* having looked would let a later merge retire scenarios on a guess.
|
|
714
|
+
*/
|
|
715
|
+
runScope;
|
|
667
716
|
coverageByFile = {};
|
|
668
717
|
constructor(options = {}) {
|
|
669
718
|
this.options = options;
|
|
@@ -672,6 +721,9 @@ var StoryReporter = class {
|
|
|
672
721
|
this.ctx = ctx;
|
|
673
722
|
this.startTime = Date.now();
|
|
674
723
|
const root = ctx.config?.root ?? process.cwd();
|
|
724
|
+
if (ctx.config) {
|
|
725
|
+
this.runScope = ctx.config.testNamePattern != null ? "filtered" : "full";
|
|
726
|
+
}
|
|
675
727
|
const includeMetadata = this.options.markdown?.includeMetadata ?? true;
|
|
676
728
|
if (includeMetadata) {
|
|
677
729
|
this.packageVersion = readPackageVersion(root);
|
|
@@ -688,6 +740,8 @@ var StoryReporter = class {
|
|
|
688
740
|
if (reason === "interrupted") return;
|
|
689
741
|
const root = this.ctx?.config?.root ?? process.cwd();
|
|
690
742
|
const rawTestCases = this.collectTestCases(testModules, root);
|
|
743
|
+
const coveredSourceFiles = this.collectCoveredSourceFiles(testModules, root);
|
|
744
|
+
const incompleteSourceFiles = this.collectIncompleteSourceFiles(testModules, root);
|
|
691
745
|
const features = collectFeatures(testModules, root);
|
|
692
746
|
const rawRun = {
|
|
693
747
|
testCases: rawTestCases,
|
|
@@ -697,7 +751,10 @@ var StoryReporter = class {
|
|
|
697
751
|
projectRoot: root,
|
|
698
752
|
packageVersion: this.packageVersion,
|
|
699
753
|
gitSha: this.gitSha,
|
|
700
|
-
ci: detectCI()
|
|
754
|
+
ci: detectCI(),
|
|
755
|
+
...this.runScope ? { runScope: this.runScope } : {},
|
|
756
|
+
...coveredSourceFiles.length > 0 ? { coveredSourceFiles } : {},
|
|
757
|
+
...incompleteSourceFiles.length > 0 ? { incompleteSourceFiles } : {}
|
|
701
758
|
};
|
|
702
759
|
const rawRunPath = this.options.rawRunPath;
|
|
703
760
|
if (rawRunPath) {
|
|
@@ -774,6 +831,58 @@ var StoryReporter = class {
|
|
|
774
831
|
/**
|
|
775
832
|
* Collect test cases from Vitest test modules.
|
|
776
833
|
*/
|
|
834
|
+
/**
|
|
835
|
+
* Every module vitest ran, whether or not it produced a story.
|
|
836
|
+
*
|
|
837
|
+
* The collected test cases only name modules that produced something, so a
|
|
838
|
+
* file whose last story was deleted would otherwise look like a file that did
|
|
839
|
+
* not run and keep its old scenarios for good.
|
|
840
|
+
*/
|
|
841
|
+
collectCoveredSourceFiles(testModules, root) {
|
|
842
|
+
const files = /* @__PURE__ */ new Set();
|
|
843
|
+
for (const mod of testModules) {
|
|
844
|
+
const moduleId = mod.moduleId ?? mod.relativeModuleId ?? "";
|
|
845
|
+
if (!moduleId) continue;
|
|
846
|
+
const absolute = path.isAbsolute(moduleId) ? moduleId : path.resolve(root, moduleId);
|
|
847
|
+
files.add(toRelativePosix(absolute, root));
|
|
848
|
+
}
|
|
849
|
+
return [...files].sort();
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* Files whose scenarios could not be collected in full.
|
|
853
|
+
*
|
|
854
|
+
* A module that errored, or a test that failed without ever declaring a
|
|
855
|
+
* story, is missing scenarios because the run broke. Treating that as
|
|
856
|
+
* deletion would throw away documentation at the exact moment the suite is
|
|
857
|
+
* unhealthy. Checked per test, not per file: one healthy story in a module
|
|
858
|
+
* does not vouch for its broken siblings.
|
|
859
|
+
*/
|
|
860
|
+
collectIncompleteSourceFiles(testModules, root) {
|
|
861
|
+
const incomplete = /* @__PURE__ */ new Set();
|
|
862
|
+
for (const mod of testModules) {
|
|
863
|
+
const moduleId = mod.moduleId ?? mod.relativeModuleId ?? "";
|
|
864
|
+
if (!moduleId) continue;
|
|
865
|
+
const absolute = path.isAbsolute(moduleId) ? moduleId : path.resolve(root, moduleId);
|
|
866
|
+
const sourceFile = toRelativePosix(absolute, root);
|
|
867
|
+
const errors = mod.errors?.() ?? [];
|
|
868
|
+
const collection = mod.children;
|
|
869
|
+
if (errors.length > 0 || !collection) {
|
|
870
|
+
const state2 = mod.state?.();
|
|
871
|
+
if (errors.length > 0 || state2 === "failed") incomplete.add(sourceFile);
|
|
872
|
+
continue;
|
|
873
|
+
}
|
|
874
|
+
for (const test of collection.allTests()) {
|
|
875
|
+
const meta = this.getStoryMeta(test);
|
|
876
|
+
if (meta?.scenario && Array.isArray(meta.steps)) continue;
|
|
877
|
+
if (test.options?.mode === "todo") continue;
|
|
878
|
+
if (test.result?.()?.state === "failed") {
|
|
879
|
+
incomplete.add(sourceFile);
|
|
880
|
+
break;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
return [...incomplete].sort();
|
|
885
|
+
}
|
|
777
886
|
collectTestCases(testModules, root) {
|
|
778
887
|
const testCases = [];
|
|
779
888
|
for (const mod of testModules) {
|