Agent SkillsJamieMason/syncpack › write-tests

write-tests

GitHub

指导为 Syncpack 编写测试,强调使用 TestBuilder 模式、TDD 流程及断言规范。涵盖异步测试入口、模块化组织、文件位置映射及 Builder 方法用法,确保测试一致性与可维护性。

.claude/skills/write-tests/SKILL.md JamieMason/syncpack

Trigger Scenarios

需要为 Syncpack 新功能编写单元测试 询问如何构造测试上下文或断言结果 查找特定功能对应的测试文件位置

Install

npx skills add JamieMason/syncpack --skill write-tests -g -y
More Options

Non-standard path

npx skills add https://github.com/JamieMason/syncpack/tree/main/.claude/skills/write-tests -g -y

Use without installing

npx skills use JamieMason/syncpack@write-tests

指定 Agent (Claude Code)

npx skills add JamieMason/syncpack --skill write-tests -a claude-code -g -y

安装 repo 全部 skill

npx skills add JamieMason/syncpack --all -g -y

预览 repo 内 skill

npx skills add JamieMason/syncpack --list

SKILL.md

Frontmatter
{
    "name": "write-tests",
    "description": "Write tests for Syncpack using the TestBuilder pattern. Use when adding tests for commands, validation logic, or any new functionality. Covers TestBuilder API, assertion patterns, and common test scenarios."
}

Write Tests

Golden rules

  • Use TestBuilder — never construct Context manually.
  • Assert with expect(&ctx).to_have_instances(vec![...]) — never index ctx.instances.
  • Async-first: #[tokio::test] + .run().await is the canonical entry.

TDD

  1. Read 2-3 tests in the same *_test.rs and copy the pattern
  2. Write failing test → just test → confirm RED
  3. Ask before implementing
  4. Implement minimal code → GREEN → just format

Quick start

use {
  crate::{
    instance::{FixableInstance::*, InstanceState, SuspectInstance::*, UnfixableInstance::*, ValidInstance::*},
    test::{
      builder::TestBuilder,
      expect::{expect, ExpectedInstance},
    },
  },
  serde_json::json,
};

#[tokio::test]
async fn pinned_version_replaces_anything_different() {
  let ctx = TestBuilder::new()
    .with_package(json!({
      "name": "package-a",
      "version": "1.0.0",
      "devDependencies": {"foo": "workspace:*"}
    }))
    .with_version_group(json!({
      "dependencies": ["foo"],
      "pinVersion": "1.2.0"
    }))
    .run()
    .await;
  expect(&ctx).to_have_instances(vec![
    ExpectedInstance {
      state: InstanceState::valid(IsLocalAndValid),
      dependency_name: "package-a",
      id: "package-a in /version of package-a",
      actual: "1.0.0",
      expected: Some("1.0.0"),
      overridden: None,
    },
    ExpectedInstance {
      state: InstanceState::fixable(DiffersToPin),
      dependency_name: "foo",
      id: "foo in /devDependencies of package-a",
      actual: "workspace:*",
      expected: Some("1.2.0"),
      overridden: None,
    },
  ]);
}

Sub-module organisation

Group related scenarios under nested modules — see pinned_test.rs, catalog_defs_test.rs:

mod local {
  use super::*;
  #[tokio::test] async fn refuses_to_pin_local_version() { ... }
}

mod normal {
  use super::*;
  #[tokio::test] async fn an_already_pinned_version_is_valid() { ... }
}

mod registry_updates {
  use super::*;
  #[tokio::test] async fn def_marked_outdated_when_registry_has_newer_version() { ... }
}

File location

Test type Location
Version-group behaviour (pin, ban, ranges …) src/version_group/<group>_test.rs
Catalog discovery wiring src/version_group/{catalog,bun_catalog}_test.rs
Fix mutations src/commands/fix_test.rs
Format pass src/visit_formatting/format_test.rs
Other unit tests Co-located: src/foo.rssrc/foo_test.rs

Builder methods

Source of truth: src/test/builder.rs.

Method Purpose
.with_package(json!({...})) Add one package.json
.with_packages(vec![...]) Add many
.with_version_group(json!({...})) Add one version group
.with_version_groups(vec![...]) Add many
.with_semver_group(json!({...})) Add a semver group
.with_config(json!({...})) Base config (e.g. customTypes, dependencyGroups)
.with_strict(bool) Strict mode (Suspect → error)
.with_subcommand("update") Override subcommand (default: lint, or update if registry set)
.with_pnpm_catalogs(yaml) Inject pnpm-workspace.yaml; implies pnpm PM
.with_bun_catalogs(json!({...})) Synthetic Bun root with /catalog, /catalogs/{n}; implies Bun PM
.with_bun_workspaces_catalogs(json!({...})) Same, nested under /workspaces/
.with_{pnpm,bun,npm,yarn,unknown}_package_manager() Force PM detection
.with_registry_updates(json!({"react":[...]})) Mock npm registry; implies subcommand=update
.with_update_target(UpdateTarget::Minor) Bound update target
.run().awaitContext Primary end-to-end (full pipeline through disk + discovery)
.build() Sync, no visit — context-wiring tests
.build_and_visit_packages() Sync + visit_packages — fix tests, older suites
.build_and_visit_formatting() Sync + visit_formatting
.build_with_registry_and_visit().await Sync wiring + async registry mock + visit

ExpectedInstance fields

ExpectedInstance {
  state: InstanceState::fixable(DiffersToPin),  // valid / fixable / unfixable / suspect
  dependency_name: "react",                     // = `internal_name` (alias-aware)
  id: "react in /dependencies of package-a",    // {dep} in {/path} of {package_or_yaml}
  actual: "17.0.0",                             // raw specifier on disk
  expected: Some("18.0.0"),                     // None = ignore; Some("") = remove
  overridden: None,                             // semver-group override target, if any
}

id location examples:

  • /dependencies, /devDependencies, /peerDependencies
  • /version of package-a (local version)
  • /packageManager, /engines/node
  • /catalog of pnpm-workspace.yaml, /catalogs/<name> of pnpm-workspace.yaml
  • /customVersion, /custom/config/version (via customTypes)

Patterns

patterns.md: banned, pinned, sameRange, pnpm catalogs, bun catalogs, semver ranges, registry updates.

Fix tests

src/commands/fix_test.rs builds with .build_and_visit_packages() then runs fix::run(ctx, &SilentReporter, &disk). dry_run = true is the default (set by mock::config_from_mock), so is_dirty() and post-fix contents stay observable. Set ctx.config.cli.dry_run = false only when asserting writes through a recording MockDiskIo (see pnpm_fix_writes_yaml_to_disk).

Common mistakes

Wrong Right
#[test] fn foo() + .run().await #[tokio::test] async fn foo()
use crate::instance_state::* use crate::instance::*
"pinned": "1.0.0" "pinVersion": "1.0.0"
Context { ... } TestBuilder::new()...
ctx.instances[0] expect(&ctx).to_have_instances(vec![...])
.build() then check states .run().await (or .build_and_visit_packages() for sync)
Missing SuspectInstance::* import Import all 4: FixableInstance::*, ValidInstance::*, SuspectInstance::*, UnfixableInstance::*

Running

just test                            # all
cargo test pinned_test               # pattern match
cargo test test_name -- --nocapture  # with stdout

Reference tests

  • src/version_group/banned_test.rs — banned + custom types
  • src/version_group/pinned_test.rs — sub-modules, semver-group interaction
  • src/version_group/same_range_test.rs — range satisfaction
  • src/version_group/catalog_test.rs — pnpm catalogs
  • src/version_group/bun_catalog_test.rs — bun catalogs (sync .build())
  • src/version_group/preferred_semver_test.rs — registry updates, update targets

Version History

  • 15.3.2 Current 2026-07-06 19:59

Same Skill Collection

.claude/skills/add-feature/SKILL.md
.claude/skills/document-code/SKILL.md
.claude/skills/fix-bug/SKILL.md
.claude/skills/front-loading/SKILL.md
.claude/skills/optimize-benchmarks/SKILL.md
.claude/skills/review-docs/SKILL.md
.claude/skills/search-code/SKILL.md
.claude/skills/signal-over-noise/SKILL.md
.claude/skills/testing-gaps/SKILL.md
.claude/skills/write-code/SKILL.md

Metadata

Files
0
Version
15.3.2
Hash
7e6637f0
Indexed
2026-07-06 19:59

- 위키
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-08-06 17:29
浙ICP备14020137号-1 $방문자$