com um clique
write-tests
// 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 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.
Rust code style and conventions for Syncpack. Use when writing or modifying Rust code. Covers functional patterns, imports, naming, and quality standards.
Run coverage, inspect results, and identify missing test scenarios for a given source file. Use when analysing test coverage or finding untested branches.
Add new features to Syncpack including commands and validation logic. Use when implementing new CLI commands, adding InstanceState variants, or extending version group behaviour.
Add and update the documentation website for Syncpack. Use when making user-facing changes to the codebase.
Debug and fix bugs in Syncpack using scientific debugging methodology. Use when a test is failing, unexpected behaviour occurs, or investigating issues. Covers hypothesis-driven debugging and TDD-based fixes.
Put the answer first, then context and details follow. Use when writing documentation, tutorials, error messages, or any communication where readers need to decide quickly if something is relevant to them.
| 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. |
TestBuilder — never construct Context manually.expect(&ctx).to_have_instances(vec![...]) — never index ctx.instances.#[tokio::test] + .run().await is the canonical entry.*_test.rs and copy the patternjust test → confirm REDjust formatuse {
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,
},
]);
}
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() { ... }
}
| 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.rs ↔ src/foo_test.rs |
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().await → Context | 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 {
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.md: banned, pinned, sameRange, pnpm catalogs, bun catalogs, semver ranges, registry updates.
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).
| 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::* |
just test # all
cargo test pinned_test # pattern match
cargo test test_name -- --nocapture # with stdout
src/version_group/banned_test.rs — banned + custom typessrc/version_group/pinned_test.rs — sub-modules, semver-group interactionsrc/version_group/same_range_test.rs — range satisfactionsrc/version_group/catalog_test.rs — pnpm catalogssrc/version_group/bun_catalog_test.rs — bun catalogs (sync .build())src/version_group/preferred_semver_test.rs — registry updates, update targets