@systemfsoftware/xstate-test 0.0.0-bootstrap.0 → 2.0.0-beta.2
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 +9 -0
- package/LICENSE +21 -0
- package/README.md +1821 -2
- package/dist/adapter-Dd4Go0U3.d.ts +58 -0
- package/dist/effect-schema.d.ts +28 -0
- package/dist/effect-schema.mjs +40 -0
- package/dist/finalizerError-DOptAIDs.mjs +8 -0
- package/dist/index-CSbyUxJr.d.ts +2062 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.mjs +178 -0
- package/dist/playwright.d.ts +204 -0
- package/dist/playwright.mjs +287 -0
- package/dist/propertyTest-5bO0RsUu.mjs +4660 -0
- package/dist/propertyTest-BHnBV8ax.d.ts +147 -0
- package/dist/schema.d.ts +59 -0
- package/dist/schema.mjs +822 -0
- package/dist/vitest.d.ts +74 -0
- package/dist/vitest.mjs +139 -0
- package/package.json +86 -5
package/dist/vitest.d.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { a as propertyTest, o as testPaths } from "./propertyTest-BHnBV8ax.js";
|
|
2
|
+
import { ActorLogic } from "@systemfsoftware/xstate";
|
|
3
|
+
import { TestAPI } from "vitest";
|
|
4
|
+
type Source = ActorLogic<any, any, any>;
|
|
5
|
+
/**
|
|
6
|
+
* A Vitest-compatible `it` or `test`: `(name, fn, timeout?) => void`.
|
|
7
|
+
*
|
|
8
|
+
* @experimental
|
|
9
|
+
*/
|
|
10
|
+
export type ModelTestBase = (name: string, fn: (context: any) => Promise<void>, timeout?: number) => unknown;
|
|
11
|
+
/**
|
|
12
|
+
* What an expected failure must look like. See `it.model.fails`.
|
|
13
|
+
*
|
|
14
|
+
* @experimental
|
|
15
|
+
*/
|
|
16
|
+
export interface ModelTestFailureExpectation {
|
|
17
|
+
/** A substring of, or a pattern matching, the failure message. */
|
|
18
|
+
readonly message?: string | RegExp;
|
|
19
|
+
}
|
|
20
|
+
/** @experimental */
|
|
21
|
+
export interface ModelTestFunction {
|
|
22
|
+
/**
|
|
23
|
+
* Registers a test that runs `propertyTest(source, options)`. Failures are
|
|
24
|
+
* saved under the test file and name, and replayed first on the next run.
|
|
25
|
+
* On failure, the coverage report is printed.
|
|
26
|
+
*/
|
|
27
|
+
<TSource extends Source>(name: string, source: TSource, options: Parameters<typeof propertyTest<TSource>>[1], timeout?: number): void;
|
|
28
|
+
/**
|
|
29
|
+
* Registers a test that passes only when `propertyTest(source, options)`
|
|
30
|
+
* fails, and its message matches `expected.message`.
|
|
31
|
+
*/
|
|
32
|
+
fails<TSource extends Source>(name: string, source: TSource, options: Parameters<typeof propertyTest<TSource>>[1], expected?: ModelTestFailureExpectation): void;
|
|
33
|
+
}
|
|
34
|
+
/** @experimental */
|
|
35
|
+
export interface PathsTestFunction {
|
|
36
|
+
/** Registers a test that runs `testPaths(source, options)`. */
|
|
37
|
+
<TSource extends Source>(name: string, source: TSource, options?: Parameters<typeof testPaths<TSource>>[1], timeout?: number): void;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The members `withModelTests()` adds to `it` or `test`.
|
|
41
|
+
*
|
|
42
|
+
* @experimental
|
|
43
|
+
*/
|
|
44
|
+
export interface ModelTestAPI {
|
|
45
|
+
readonly model: ModelTestFunction;
|
|
46
|
+
readonly paths: PathsTestFunction;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Adds `model` and `paths` to a Vitest `it` or `test`:
|
|
50
|
+
*
|
|
51
|
+
* ```ts
|
|
52
|
+
* import { it as vitestIt } from 'vitest';
|
|
53
|
+
* import { withModelTests } from '@systemfsoftware/xstate-test/vitest';
|
|
54
|
+
*
|
|
55
|
+
* const it = withModelTests(vitestIt);
|
|
56
|
+
* it.model('the cart matches the model', cartMachine, { sut });
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* @experimental
|
|
60
|
+
*/
|
|
61
|
+
export declare function withModelTests<TBase extends ModelTestBase>(base: TBase): TBase & ModelTestAPI;
|
|
62
|
+
/**
|
|
63
|
+
* Vitest's global `it`, with `it.model` and `it.paths`.
|
|
64
|
+
*
|
|
65
|
+
* @experimental
|
|
66
|
+
*/
|
|
67
|
+
export declare const it: TestAPI & ModelTestAPI;
|
|
68
|
+
/**
|
|
69
|
+
* Vitest's global `test`, with `test.model` and `test.paths`.
|
|
70
|
+
*
|
|
71
|
+
* @experimental
|
|
72
|
+
*/
|
|
73
|
+
export declare const test: TestAPI & ModelTestAPI;
|
|
74
|
+
export {};
|
package/dist/vitest.mjs
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { C as consoleLineLogger, O as testCoverageToJSON, n as propertyTest, r as testPaths, w as formatTestCoverage } from "./propertyTest-5bO0RsUu.mjs";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Cause from "effect/Cause";
|
|
4
|
+
import * as Logger from "effect/Logger";
|
|
5
|
+
// src/vitest.ts
|
|
6
|
+
/**
|
|
7
|
+
* Awaits `value` as an Effect. A value and a thenable are both accepted, and a
|
|
8
|
+
* rejection stays the same error object when the effect is run.
|
|
9
|
+
*/
|
|
10
|
+
const awaited = (value) => Effect.promise(() => Promise.resolve(value));
|
|
11
|
+
function getTestKey(context) {
|
|
12
|
+
const names = [context.task.name];
|
|
13
|
+
for (let suite = context.task.suite; suite; suite = suite.suite) if (suite.name.length > 0) names.unshift(suite.name);
|
|
14
|
+
return [context.task.file?.name, ...names].filter(Boolean).join(" > ");
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Keys the failure database by the test, unless `failures` is `false`, a
|
|
18
|
+
* custom store, or sets its own `key`.
|
|
19
|
+
*/
|
|
20
|
+
function withTestFailures(options, context) {
|
|
21
|
+
const failures = options.failures;
|
|
22
|
+
if (failures === false) return options;
|
|
23
|
+
if (failures !== void 0 && typeof failures === "object" && ("load" in failures || failures.key !== void 0)) return options;
|
|
24
|
+
return {
|
|
25
|
+
...options,
|
|
26
|
+
failures: {
|
|
27
|
+
...typeof failures === "object" ? failures : {},
|
|
28
|
+
key: getTestKey(context)
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** The test timeout for a campaign bounded by `until: { timeMs }`. */
|
|
33
|
+
function getTimeout(options, timeout) {
|
|
34
|
+
if (timeout !== void 0) return timeout;
|
|
35
|
+
const until = options?.until;
|
|
36
|
+
return typeof until === "object" && typeof until.timeMs === "number" ? until.timeMs + 5e3 : void 0;
|
|
37
|
+
}
|
|
38
|
+
function getCoverage(error) {
|
|
39
|
+
return error?.coverage;
|
|
40
|
+
}
|
|
41
|
+
function attachCoverage(context, coverage) {
|
|
42
|
+
if (coverage !== void 0) context.task.meta["xstateTestCoverage"] = testCoverageToJSON(coverage);
|
|
43
|
+
}
|
|
44
|
+
function getMessage(error) {
|
|
45
|
+
return error instanceof Error ? error.message : String(error);
|
|
46
|
+
}
|
|
47
|
+
function matchesExpectation(error, expected) {
|
|
48
|
+
const pattern = expected?.message;
|
|
49
|
+
if (pattern === void 0) return true;
|
|
50
|
+
const message = getMessage(error);
|
|
51
|
+
return typeof pattern === "string" ? message.includes(pattern) : pattern.test(message);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Adds `model` and `paths` to a Vitest `it` or `test`:
|
|
55
|
+
*
|
|
56
|
+
* ```ts
|
|
57
|
+
* import { it as vitestIt } from 'vitest';
|
|
58
|
+
* import { withModelTests } from '@systemfsoftware/xstate-test/vitest';
|
|
59
|
+
*
|
|
60
|
+
* const it = withModelTests(vitestIt);
|
|
61
|
+
* it.model('the cart matches the model', cartMachine, { sut });
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* @experimental
|
|
65
|
+
*/
|
|
66
|
+
function withModelTests(base) {
|
|
67
|
+
return withModelAPI(() => base);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* A callable that forwards everything to the base `it`, plus `model` and
|
|
71
|
+
* `paths`. The base is not modified.
|
|
72
|
+
*/
|
|
73
|
+
function withModelAPI(resolve) {
|
|
74
|
+
const api = createModelTestAPI(resolve);
|
|
75
|
+
return new Proxy(function() {}, {
|
|
76
|
+
apply: (_target, thisArg, args) => Reflect.apply(resolve(), thisArg, args),
|
|
77
|
+
get: (_target, property) => property === "model" || property === "paths" ? api[property] : Reflect.get(resolve(), property)
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
function createModelTestAPI(getBase) {
|
|
81
|
+
const run = (context, campaign) => Effect.runPromise(Effect.onError(Effect.gen(function* () {
|
|
82
|
+
const { coverage } = yield* awaited(campaign());
|
|
83
|
+
attachCoverage(context, coverage);
|
|
84
|
+
}), (cause) => {
|
|
85
|
+
const coverage = getCoverage(Cause.squash(cause));
|
|
86
|
+
attachCoverage(context, coverage);
|
|
87
|
+
if (coverage !== void 0) Effect.runSync(Effect.log(formatTestCoverage(coverage)).pipe(Effect.provide(Logger.layer([consoleLineLogger]))));
|
|
88
|
+
return Effect.void;
|
|
89
|
+
}));
|
|
90
|
+
const model = ((name, source, options, timeout) => {
|
|
91
|
+
getBase()(name, (context) => run(context, () => propertyTest(source, withTestFailures(options, context))), getTimeout(options, timeout));
|
|
92
|
+
});
|
|
93
|
+
model.fails = (name, source, options, expected) => {
|
|
94
|
+
getBase()(name, (context) => Effect.runPromise(Effect.gen(function* () {
|
|
95
|
+
const outcome = yield* Effect.catchCause(Effect.gen(function* () {
|
|
96
|
+
const { coverage } = yield* awaited(propertyTest(source, withTestFailures(options, context)));
|
|
97
|
+
attachCoverage(context, coverage);
|
|
98
|
+
return { failed: false };
|
|
99
|
+
}), (cause) => {
|
|
100
|
+
const error = Cause.squash(cause);
|
|
101
|
+
attachCoverage(context, getCoverage(error));
|
|
102
|
+
return Effect.succeed({
|
|
103
|
+
failed: true,
|
|
104
|
+
error
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
if (!outcome.failed) throw new Error(`Expected "${name}" to fail, but the campaign passed.`);
|
|
108
|
+
if (!matchesExpectation(outcome.error, expected)) throw new Error(`Expected "${name}" to fail with a message matching ${String(expected.message)}, but it failed with:\n${getMessage(outcome.error)}`, { cause: outcome.error });
|
|
109
|
+
})), getTimeout(options, void 0));
|
|
110
|
+
};
|
|
111
|
+
const paths = (name, source, options, timeout) => {
|
|
112
|
+
getBase()(name, (context) => run(context, () => testPaths(source, withTestFailures(options ?? {}, context))), getTimeout(options, timeout));
|
|
113
|
+
};
|
|
114
|
+
return {
|
|
115
|
+
model,
|
|
116
|
+
paths
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/** A global `it` or `test`, resolved when it is first used. */
|
|
120
|
+
function fromGlobal(name) {
|
|
121
|
+
return withModelAPI(() => {
|
|
122
|
+
const base = globalThis[name];
|
|
123
|
+
if (typeof base !== "function") throw new Error(`\`${name}\` from @xstate/test/vitest uses Vitest's global \`${name}\`, which is not defined. Set \`test.globals: true\` in the Vitest config, or wrap Vitest's own: \`withModelTests(${name})\`.`);
|
|
124
|
+
return base;
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Vitest's global `it`, with `it.model` and `it.paths`.
|
|
129
|
+
*
|
|
130
|
+
* @experimental
|
|
131
|
+
*/
|
|
132
|
+
const it = fromGlobal("it");
|
|
133
|
+
/**
|
|
134
|
+
* Vitest's global `test`, with `test.model` and `test.paths`.
|
|
135
|
+
*
|
|
136
|
+
* @experimental
|
|
137
|
+
*/
|
|
138
|
+
const test = fromGlobal("test");
|
|
139
|
+
export { it, test, withModelTests };
|
package/package.json
CHANGED
|
@@ -1,10 +1,91 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@systemfsoftware/xstate-test",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
3
|
+
"license": "MIT",
|
|
4
|
+
"version": "2.0.0-beta.2",
|
|
5
|
+
"author": "Ryan Lee <drdgvhbh@gmail.com>",
|
|
5
6
|
"repository": {
|
|
6
7
|
"type": "git",
|
|
7
|
-
"url": "git+https://github.com/systemfsoftware/xstate.git"
|
|
8
|
+
"url": "git+https://github.com/systemfsoftware/xstate.git",
|
|
9
|
+
"directory": "packages/xstate-test"
|
|
8
10
|
},
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
+
"homepage": "https://github.com/systemfsoftware/xstate/tree/main/packages/xstate-test#readme",
|
|
12
|
+
"bugs": "https://github.com/systemfsoftware/xstate/issues",
|
|
13
|
+
"description": "Model-based testing for XState machines — forked under systemfsoftware from statelyai/xstate (@xstate/test at 2146ae26).",
|
|
14
|
+
"type": "module",
|
|
15
|
+
"sideEffects": false,
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"peerDependencies": {
|
|
20
|
+
"@systemfsoftware/xstate": "^6.0.0-beta.65",
|
|
21
|
+
"effect": "^4.1.0",
|
|
22
|
+
"fast-check": "^4",
|
|
23
|
+
"vitest": "^5",
|
|
24
|
+
"zod": "^4.0.0"
|
|
25
|
+
},
|
|
26
|
+
"peerDependenciesMeta": {
|
|
27
|
+
"effect": {
|
|
28
|
+
"optional": true
|
|
29
|
+
},
|
|
30
|
+
"vitest": {
|
|
31
|
+
"optional": true
|
|
32
|
+
},
|
|
33
|
+
"zod": {
|
|
34
|
+
"optional": true
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@effect/tsgo": "0.64.1",
|
|
39
|
+
"@microsoft/api-extractor": "7.59.1",
|
|
40
|
+
"@playwright/test": "1.63.0",
|
|
41
|
+
"@systemfsoftware/arethetypeswrong-cli": "file:../../.deps/arethetypeswrong-cli.tgz",
|
|
42
|
+
"@systemfsoftware/tsconfig": "file:../../.deps/tsconfig.tgz",
|
|
43
|
+
"@systemfsoftware/vitest": "file:../../.deps/vitest.tgz",
|
|
44
|
+
"@systemfsoftware/xstate": "^6.0.0-beta.65",
|
|
45
|
+
"@types/node": "24.19.1",
|
|
46
|
+
"effect": "4.1.2",
|
|
47
|
+
"fast-check": "4.9.0",
|
|
48
|
+
"rimraf": "6.1.3",
|
|
49
|
+
"tsdown": "0.23.0",
|
|
50
|
+
"typescript": "7.0.2",
|
|
51
|
+
"vitest": "5.0.3",
|
|
52
|
+
"zod": "4.4.3"
|
|
53
|
+
},
|
|
54
|
+
"publishConfig": {
|
|
55
|
+
"access": "public",
|
|
56
|
+
"provenance": true
|
|
57
|
+
},
|
|
58
|
+
"exports": {
|
|
59
|
+
".": {
|
|
60
|
+
"types": "./dist/index.d.ts",
|
|
61
|
+
"default": "./dist/index.mjs"
|
|
62
|
+
},
|
|
63
|
+
"./effect-schema": {
|
|
64
|
+
"types": "./dist/effect-schema.d.ts",
|
|
65
|
+
"default": "./dist/effect-schema.mjs"
|
|
66
|
+
},
|
|
67
|
+
"./playwright": {
|
|
68
|
+
"types": "./dist/playwright.d.ts",
|
|
69
|
+
"default": "./dist/playwright.mjs"
|
|
70
|
+
},
|
|
71
|
+
"./schema": {
|
|
72
|
+
"types": "./dist/schema.d.ts",
|
|
73
|
+
"default": "./dist/schema.mjs"
|
|
74
|
+
},
|
|
75
|
+
"./vitest": {
|
|
76
|
+
"types": "./dist/vitest.d.ts",
|
|
77
|
+
"default": "./dist/vitest.mjs"
|
|
78
|
+
},
|
|
79
|
+
"./package.json": "./package.json"
|
|
80
|
+
},
|
|
81
|
+
"scripts": {
|
|
82
|
+
"clean": "rimraf dist",
|
|
83
|
+
"build": "tsdown -l warn && pnpm api:check",
|
|
84
|
+
"typecheck": "tsc -b",
|
|
85
|
+
"test": "vitest run",
|
|
86
|
+
"lint:tsgo": "effect-tsgo diagnostics --project tsconfig.app.json --format ${TSGO_FORMAT:-text} && ../../scripts/tsgo-test-diagnostics.ts",
|
|
87
|
+
"attw": "attw --pack .",
|
|
88
|
+
"api:check": "api-extractor run && api-extractor run --config api-extractor.effect-schema.json && api-extractor run --config api-extractor.playwright.json && api-extractor run --config api-extractor.schema.json && api-extractor run --config api-extractor.vitest.json",
|
|
89
|
+
"api:update": "api-extractor run --local && api-extractor run --local --config api-extractor.effect-schema.json && api-extractor run --local --config api-extractor.playwright.json && api-extractor run --local --config api-extractor.schema.json && api-extractor run --local --config api-extractor.vitest.json"
|
|
90
|
+
}
|
|
91
|
+
}
|