Skip to content

Commit 4d3d216

Browse files
authored
test: add cli e2e snapshots (#35)
## Summary - Add a dedicated Rstest e2e config and scripts for CLI product-level tests. - Add e2e cases that execute the built `spm` bin and snapshot terminal `status`, `stdout`, and `stderr`. - Cover help/version, init, add, install, update, patch/patch-commit, and CLI error paths. - Run e2e in CI after the package build and existing test suite. ## Related Links <!-- Provide links to related issues, discussions, or design notes --> N/A ## Checklist <!-- Check and mark with an "x" --> - [x] Tests updated (or not required). - [x] Documentation updated (or not required). Testing: - `pnpm run test:e2e` - `pnpm run test:e2e:run` - `pnpm test` - `pnpm exec biome check e2e rstest.e2e.config.ts package.json biome.json .github/workflows/test.yml` Note: full `pnpm check` still reports pre-existing unrelated Biome findings in `packages/skills-package-manager/src/npm/packPackage.ts`, `packages/skills-package-manager/src/pipeline/queue.ts`, and `website/theme/components/HomePage/index.css`.
1 parent 6dc9415 commit 4d3d216

20 files changed

Lines changed: 750 additions & 1 deletion

.github/workflows/test.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,6 @@ jobs:
4747

4848
- name: Run Tests
4949
run: pnpm run test
50+
51+
- name: Run E2E Tests
52+
run: pnpm run test:e2e:run

biome.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
},
88
"files": {
99
"ignoreUnknown": false,
10-
"includes": ["packages/**", "website/**", "scripts/**", "*.ts", "*.json"]
10+
"includes": ["packages/**", "website/**", "scripts/**", "e2e/**/*.ts", "*.ts", "*.json"]
1111
},
1212
"formatter": {
1313
"enabled": true,
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Rstest Snapshot v1
2+
3+
exports[`spm add e2e > adds, installs, and links a selected local skill for an agent 1`] = `
4+
"status: 0
5+
signal: <none>
6+
stdout:
7+
┌ spm
8+
9+
◇ Found 1 skill
10+
11+
◇ Installed skills
12+
13+
└ Added agent-skill
14+
stderr:
15+
<empty>"
16+
`;
17+
18+
exports[`spm add e2e > lists skills from a local source without writing a manifest 1`] = `
19+
"status: 0
20+
signal: <none>
21+
stdout:
22+
┌ spm
23+
24+
◇ Found 1 skill
25+
listed-skill /skills/listed-skill - Listed skill
26+
27+
└ Listed skills
28+
stderr:
29+
<empty>"
30+
`;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Rstest Snapshot v1
2+
3+
exports[`spm error e2e > reports a missing add specifier 1`] = `
4+
"status: 1
5+
signal: <none>
6+
stdout:
7+
<empty>
8+
stderr:
9+
Missing required specifier"
10+
`;
11+
12+
exports[`spm error e2e > reports an unknown command 1`] = `
13+
"status: 1
14+
signal: <none>
15+
stdout:
16+
<empty>
17+
stderr:
18+
Unknown command: unknown"
19+
`;
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Rstest Snapshot v1
2+
3+
exports[`spm help and version e2e > prints package version from the built bin 1`] = `
4+
"status: 0
5+
signal: <none>
6+
stdout:
7+
<package-version>
8+
stderr:
9+
<empty>"
10+
`;
11+
12+
exports[`spm help and version e2e > prints top-level help from the built bin 1`] = `
13+
"status: 0
14+
signal: <none>
15+
stdout:
16+
spm/<package-version>
17+
18+
Usage:
19+
$ spm <command> [options]
20+
21+
Commands:
22+
add [...positionals]
23+
install [...args]
24+
patch <skill>
25+
patch-commit <editDir>
26+
update [...skills]
27+
init [...args]
28+
29+
For more info, run any command with the \`--help\` flag:
30+
$ spm add --help
31+
$ spm install --help
32+
$ spm patch --help
33+
$ spm patch-commit --help
34+
$ spm update --help
35+
$ spm init --help
36+
37+
Options:
38+
-h, --help Display this message
39+
-v, --version Display version number
40+
stderr:
41+
<empty>"
42+
`;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Rstest Snapshot v1
2+
3+
exports[`spm init e2e > fails without overwriting an existing manifest 1`] = `
4+
"status: 1
5+
signal: <none>
6+
stdout:
7+
<empty>
8+
stderr:
9+
Error [EMANIFESTEXISTS]: skills.json already exists"
10+
`;
11+
12+
exports[`spm init e2e > writes the default manifest with --yes 1`] = `
13+
"status: 0
14+
signal: <none>
15+
stdout:
16+
<empty>
17+
stderr:
18+
<empty>"
19+
`;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Rstest Snapshot v1
2+
3+
exports[`spm install e2e > installs a linked skill and creates configured agent links 1`] = `
4+
"status: 0
5+
signal: <none>
6+
stdout:
7+
spm install: starting (1 skill)
8+
spm install: resolving...
9+
spm install: fetching...
10+
spm install: Progress: resolved 1, reused 0, downloaded 0, added 1, done
11+
stderr:
12+
<empty>"
13+
`;
14+
15+
exports[`spm install e2e > skips cleanly when skills.json is missing 1`] = `
16+
"status: 0
17+
signal: <none>
18+
stdout:
19+
<empty>
20+
stderr:
21+
<empty>"
22+
`;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Rstest Snapshot v1
2+
3+
exports[`spm patch e2e > prepares and commits a skill patch from the built bin 1`] = `
4+
"status: 0
5+
signal: <none>
6+
stdout:
7+
<project>/patch-edit
8+
stderr:
9+
<empty>"
10+
`;
11+
12+
exports[`spm patch e2e > prepares and commits a skill patch from the built bin 2`] = `
13+
"status: 0
14+
signal: <none>
15+
stdout:
16+
patches/patch-skill.patch
17+
stderr:
18+
<empty>"
19+
`;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Rstest Snapshot v1
2+
3+
exports[`spm update e2e > reports unknown update targets 1`] = `
4+
"status: 1
5+
signal: <none>
6+
stdout:
7+
<empty>
8+
stderr:
9+
Error [ESKILLNOTFOUND]: Unknown skill: missing-skill"
10+
`;
11+
12+
exports[`spm update e2e > skips link specifiers without rewriting the manifest 1`] = `
13+
"status: 0
14+
signal: <none>
15+
stdout:
16+
<empty>
17+
stderr:
18+
<empty>"
19+
`;

e2e/cases/add.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import path from 'node:path'
2+
import { describe, expect, it } from '@rstest/core'
3+
import {
4+
cleanupTempProject,
5+
createSkillSource,
6+
createTempProject,
7+
formatProjectTerminalSnapshot,
8+
isSymlink,
9+
pathExists,
10+
readJson,
11+
runSpm,
12+
} from '../helpers/cli'
13+
14+
describe('spm add e2e', () => {
15+
it('lists skills from a local source without writing a manifest', () => {
16+
const project = createTempProject('add-list')
17+
18+
try {
19+
createSkillSource(project, 'listed-skill', 'Listed skill')
20+
21+
const result = runSpm(['add', './skill-source', '--list'], { cwd: project })
22+
23+
expect(result.status).toBe(0)
24+
expect(pathExists(path.join(project, 'skills.json'))).toBe(false)
25+
expect(formatProjectTerminalSnapshot(result, project)).toMatchSnapshot()
26+
} finally {
27+
cleanupTempProject(project)
28+
}
29+
})
30+
31+
it('adds, installs, and links a selected local skill for an agent', () => {
32+
const project = createTempProject('add-agent')
33+
34+
try {
35+
createSkillSource(project, 'agent-skill', 'Agent skill')
36+
37+
const result = runSpm(
38+
['add', './skill-source', '--skill', 'agent-skill', '--agent', 'claude-code', '-y'],
39+
{
40+
cwd: project,
41+
},
42+
)
43+
const manifest = readJson<{ skills: Record<string, string>; linkTargets: string[] }>(
44+
path.join(project, 'skills.json'),
45+
)
46+
47+
expect(result.status).toBe(0)
48+
expect(manifest.skills['agent-skill'].startsWith('link:')).toBe(true)
49+
expect(manifest.skills['agent-skill'].replace(/\\/g, '/')).toContain(
50+
'/skill-source/skills/agent-skill',
51+
)
52+
expect(manifest.linkTargets).toEqual(['.claude/skills'])
53+
expect(pathExists(path.join(project, '.agents/skills/agent-skill/SKILL.md'))).toBe(true)
54+
expect(isSymlink(path.join(project, '.claude/skills/agent-skill'))).toBe(true)
55+
expect(formatProjectTerminalSnapshot(result, project)).toMatchSnapshot()
56+
} finally {
57+
cleanupTempProject(project)
58+
}
59+
})
60+
})

0 commit comments

Comments
 (0)