@skill-test/vitest 0.5.0 → 0.7.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 +51 -24
- package/dist/index.d.ts +16 -12
- package/dist/index.js +16 -12
- package/dist/vitest.d.ts +8 -3
- package/dist/vitest.js +8 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -6,29 +6,51 @@ deterministic checks. Built on
|
|
|
6
6
|
[`@skill-test/sdk`](../../sdks/typescript/README.md) — the SDK's API is
|
|
7
7
|
re-exported here, so a vitest suite needs only this one dependency.
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Define the whole case in code (recommended)
|
|
10
|
+
|
|
11
|
+
Build the case — skill, input, evals, an optional simulated user, mocks — right
|
|
12
|
+
in the test, and register it in one line with `skillTest`. Everything the YAML
|
|
13
|
+
carries has a typed builder:
|
|
10
14
|
|
|
11
15
|
```ts
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
})
|
|
16
|
+
import { skillTest, testCase, boolean, numeric } from "@skill-test/vitest";
|
|
17
|
+
|
|
18
|
+
skillTest(
|
|
19
|
+
"greeter names the patient",
|
|
20
|
+
testCase({
|
|
21
|
+
skill: "skills/greeter", // resolved relative to the working dir
|
|
22
|
+
input: "Greet Dr. Smith, who has an appointment today.",
|
|
23
|
+
evals: [
|
|
24
|
+
boolean("the reply greets Dr. Smith by name"),
|
|
25
|
+
numeric("how warm is the tone", { min: 0, max: 10, threshold: 7 }),
|
|
26
|
+
],
|
|
27
|
+
}),
|
|
28
|
+
);
|
|
22
29
|
```
|
|
23
30
|
|
|
24
|
-
|
|
31
|
+
Multi-turn cases add `user(...)`; call-count checks use `called` / `notCalled`
|
|
32
|
+
taking the `stub`/`spy` object itself or a name you gave it. For a matrix or extra deterministic checks,
|
|
33
|
+
call `runSkill` from an ordinary `test` — it takes the same case object:
|
|
25
34
|
|
|
26
35
|
```ts
|
|
27
|
-
import {
|
|
28
|
-
|
|
29
|
-
|
|
36
|
+
import { runSkill, testCase, boolean, assistantText } from "@skill-test/vitest";
|
|
37
|
+
|
|
38
|
+
test("greeter across the matrix", async () => {
|
|
39
|
+
const report = await runSkill(
|
|
40
|
+
testCase({ skill: "skills/greeter", input: "Greet Dr. Smith", evals: [boolean("greets by name")] }),
|
|
41
|
+
{ platforms: ["claude-code"], models: ["claude-opus-4-8"] },
|
|
42
|
+
);
|
|
43
|
+
expect(report.passed).toBe(true);
|
|
44
|
+
expect(assistantText(report.runs[0]!.transcript)).toContain("Dr. Smith");
|
|
45
|
+
});
|
|
30
46
|
```
|
|
31
47
|
|
|
48
|
+
## Or point at a YAML file
|
|
49
|
+
|
|
50
|
+
`skillTest` and `runSkill` accept a path just as well
|
|
51
|
+
(`skillTest("greeter", "cases/greet.yaml")`). The full field reference for both
|
|
52
|
+
forms is [`docs/schema.md`](../../docs/schema.md).
|
|
53
|
+
|
|
32
54
|
## Assert on tool use, and stream
|
|
33
55
|
|
|
34
56
|
The SDK's tool-event and streaming surfaces are re-exported too. `toolCalls`
|
|
@@ -38,27 +60,33 @@ test can **short-circuit** on bad behavior:
|
|
|
38
60
|
|
|
39
61
|
```ts
|
|
40
62
|
import { it, expect } from "vitest";
|
|
41
|
-
import { runSkill, toolCalls,
|
|
63
|
+
import { runSkill, streamSkill, testCase, toolCalls, boolean } from "@skill-test/vitest";
|
|
64
|
+
|
|
65
|
+
const editCase = testCase({
|
|
66
|
+
skill: "skills/editor",
|
|
67
|
+
input: "Update the config and commit it.",
|
|
68
|
+
evals: [boolean("the change was committed")],
|
|
69
|
+
});
|
|
42
70
|
|
|
43
71
|
it("commits without deleting", async () => {
|
|
44
|
-
const report = await runSkill(
|
|
72
|
+
const report = await runSkill(editCase);
|
|
45
73
|
const calls = toolCalls(report.runs[0]!.transcript);
|
|
46
74
|
expect(calls.some((c) => String(c.input?.command).includes("git commit"))).toBe(true);
|
|
47
75
|
expect(calls.some((c) => String(c.input?.command).includes("rm -rf"))).toBe(false);
|
|
48
76
|
});
|
|
49
77
|
|
|
50
78
|
it("makes no network call", async () => {
|
|
51
|
-
for await (const ev of streamSkill(
|
|
79
|
+
for await (const ev of streamSkill(editCase)) {
|
|
52
80
|
expect(ev.event.name).not.toBe("curl"); // break to abort early
|
|
53
81
|
}
|
|
54
82
|
});
|
|
55
83
|
```
|
|
56
84
|
|
|
57
|
-
##
|
|
85
|
+
## Auto-discover existing YAML cases
|
|
58
86
|
|
|
59
|
-
|
|
60
|
-
test module collect
|
|
61
|
-
|
|
87
|
+
Cases can also live as data: name each `*.skilltest.yaml` (or `.yml`) and let
|
|
88
|
+
one test module collect the whole tree — useful when a suite already has YAML
|
|
89
|
+
cases, or when non-engineers author them:
|
|
62
90
|
|
|
63
91
|
```ts
|
|
64
92
|
// skills.test.ts
|
|
@@ -80,8 +108,7 @@ This is the closest vitest equivalent to pytest's auto-collection: vitest only
|
|
|
80
108
|
collects its own test modules, so the one-line `discover()` call stands in for a
|
|
81
109
|
file collector. Adding a case is then just dropping in a YAML file — no code
|
|
82
110
|
change. Pass run options as the second argument (`discover("cases", { platforms:
|
|
83
|
-
["claude-code"] })`)
|
|
84
|
-
`runSkill` in an ordinary `test()` instead.
|
|
111
|
+
["claude-code"] })`). YAML cases and code-defined ones mix freely in one suite.
|
|
85
112
|
|
|
86
113
|
## Configuration
|
|
87
114
|
|
package/dist/index.d.ts
CHANGED
|
@@ -2,23 +2,27 @@
|
|
|
2
2
|
* `@skill-test/vitest` — run AI-skill tests and natural-language evals in vitest.
|
|
3
3
|
*
|
|
4
4
|
* The vitest integration on top of `@skill-test/sdk`, whose API is re-exported
|
|
5
|
-
* here so a vitest suite needs only this one dependency
|
|
5
|
+
* here so a vitest suite needs only this one dependency. Define the whole case
|
|
6
|
+
* in code (the recommended form) and register it in one line:
|
|
6
7
|
*
|
|
7
8
|
* ```ts
|
|
8
|
-
* import { skillTest,
|
|
9
|
+
* import { skillTest, testCase, boolean } from "@skill-test/vitest";
|
|
9
10
|
*
|
|
10
|
-
* skillTest(
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* expect(assistantText(report.runs[0]!.transcript)).toContain("Dr. Smith");
|
|
19
|
-
* });
|
|
11
|
+
* skillTest(
|
|
12
|
+
* "greeter names the patient",
|
|
13
|
+
* testCase({
|
|
14
|
+
* skill: "skills/greeter",
|
|
15
|
+
* input: "Greet Dr. Smith.",
|
|
16
|
+
* evals: [boolean("the reply greets Dr. Smith by name")],
|
|
17
|
+
* }),
|
|
18
|
+
* );
|
|
20
19
|
* ```
|
|
21
20
|
*
|
|
21
|
+
* Existing YAML cases stay first-class: `skillTest("greeter", "cases/greet.yaml")`
|
|
22
|
+
* runs a file, and `discover("cases")` auto-collects a whole tree of
|
|
23
|
+
* `*.skilltest.yaml` cases. For matrices or deterministic mix-in checks, call
|
|
24
|
+
* the SDK's `runSkill` from an ordinary `test()`.
|
|
25
|
+
*
|
|
22
26
|
* This module (via the helpers) imports `vitest`, so only load it inside a
|
|
23
27
|
* vitest run. `@skill-test/vitest/vitest` remains as an alias for the helpers.
|
|
24
28
|
*/
|
package/dist/index.js
CHANGED
|
@@ -2,23 +2,27 @@
|
|
|
2
2
|
* `@skill-test/vitest` — run AI-skill tests and natural-language evals in vitest.
|
|
3
3
|
*
|
|
4
4
|
* The vitest integration on top of `@skill-test/sdk`, whose API is re-exported
|
|
5
|
-
* here so a vitest suite needs only this one dependency
|
|
5
|
+
* here so a vitest suite needs only this one dependency. Define the whole case
|
|
6
|
+
* in code (the recommended form) and register it in one line:
|
|
6
7
|
*
|
|
7
8
|
* ```ts
|
|
8
|
-
* import { skillTest,
|
|
9
|
+
* import { skillTest, testCase, boolean } from "@skill-test/vitest";
|
|
9
10
|
*
|
|
10
|
-
* skillTest(
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* expect(assistantText(report.runs[0]!.transcript)).toContain("Dr. Smith");
|
|
19
|
-
* });
|
|
11
|
+
* skillTest(
|
|
12
|
+
* "greeter names the patient",
|
|
13
|
+
* testCase({
|
|
14
|
+
* skill: "skills/greeter",
|
|
15
|
+
* input: "Greet Dr. Smith.",
|
|
16
|
+
* evals: [boolean("the reply greets Dr. Smith by name")],
|
|
17
|
+
* }),
|
|
18
|
+
* );
|
|
20
19
|
* ```
|
|
21
20
|
*
|
|
21
|
+
* Existing YAML cases stay first-class: `skillTest("greeter", "cases/greet.yaml")`
|
|
22
|
+
* runs a file, and `discover("cases")` auto-collects a whole tree of
|
|
23
|
+
* `*.skilltest.yaml` cases. For matrices or deterministic mix-in checks, call
|
|
24
|
+
* the SDK's `runSkill` from an ordinary `test()`.
|
|
25
|
+
*
|
|
22
26
|
* This module (via the helpers) imports `vitest`, so only load it inside a
|
|
23
27
|
* vitest run. `@skill-test/vitest/vitest` remains as an alias for the helpers.
|
|
24
28
|
*/
|
package/dist/vitest.d.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import { type RunOptions } from "@skill-test/sdk";
|
|
1
|
+
import { type RunOptions, type TestCaseInput } from "@skill-test/sdk";
|
|
2
2
|
/** Filename suffixes a case file must carry to be auto-discovered. */
|
|
3
3
|
export declare const CASE_SUFFIXES: readonly [".skilltest.yaml", ".skilltest.yml"];
|
|
4
|
-
/**
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Register a vitest test that runs a case and asserts every eval passed. The
|
|
6
|
+
* case may be a code-defined {@link TestCaseInput} (the recommended form —
|
|
7
|
+
* build it with `testCase`/`boolean`/`numeric`/… re-exported here) or a path to
|
|
8
|
+
* a YAML file.
|
|
9
|
+
*/
|
|
10
|
+
export declare function skillTest(name: string, caseInput: string | TestCaseInput, options?: RunOptions): void;
|
|
6
11
|
/**
|
|
7
12
|
* Recursively find every `*.skilltest.yaml` / `*.skilltest.yml` case under `dir`
|
|
8
13
|
* and register each as a vitest test (named by its path relative to `dir`).
|
package/dist/vitest.js
CHANGED
|
@@ -19,10 +19,15 @@ import { describeFailures, runSkill } from "@skill-test/sdk";
|
|
|
19
19
|
import { expect, test } from "vitest";
|
|
20
20
|
/** Filename suffixes a case file must carry to be auto-discovered. */
|
|
21
21
|
export const CASE_SUFFIXES = [".skilltest.yaml", ".skilltest.yml"];
|
|
22
|
-
/**
|
|
23
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Register a vitest test that runs a case and asserts every eval passed. The
|
|
24
|
+
* case may be a code-defined {@link TestCaseInput} (the recommended form —
|
|
25
|
+
* build it with `testCase`/`boolean`/`numeric`/… re-exported here) or a path to
|
|
26
|
+
* a YAML file.
|
|
27
|
+
*/
|
|
28
|
+
export function skillTest(name, caseInput, options = {}) {
|
|
24
29
|
test(name, async () => {
|
|
25
|
-
const report = await runSkill(
|
|
30
|
+
const report = await runSkill(caseInput, options);
|
|
26
31
|
expect(report.passed, describeFailures(report)).toBe(true);
|
|
27
32
|
});
|
|
28
33
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skill-test/vitest",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "vitest integration for skilltest: register AI-skill test cases as vitest tests, built on @skill-test/sdk.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"dist"
|
|
29
29
|
],
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@skill-test/sdk": "0.
|
|
31
|
+
"@skill-test/sdk": "0.7.0"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"vitest": ">=1.0.0"
|