Skip to content

Commit 12b8d94

Browse files
committed
docs: align best practices with claude plugin guidance
1 parent 6f70abf commit 12b8d94

3 files changed

Lines changed: 151 additions & 3 deletions

File tree

docs/client.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ Register clients as resources for automatic lifecycle management. Resources are
4343
disposed in reverse order after the scenario completes.
4444

4545
```typescript
46-
import { client, scenario } from "jsr:@probitas/probitas";
46+
import { client, expect, scenario } from "jsr:@probitas/probitas";
4747

4848
scenario("Example")
4949
.resource(
@@ -839,6 +839,25 @@ scenario("Bad Example")
839839
.build();
840840
```
841841

842+
### Use Environment-Driven Endpoints
843+
844+
Parameterize service URLs so scenarios run consistently across environments and
845+
avoid hard-coded localhost values.
846+
847+
```typescript
848+
import { client, scenario } from "jsr:@probitas/probitas";
849+
850+
const apiUrl = Deno.env.get("API_URL") ?? "http://localhost:8080";
851+
852+
scenario("API check")
853+
.resource("http", () => client.http.createHttpClient({ url: apiUrl }))
854+
.step("Ping service", async (ctx) => {
855+
const res = await ctx.resources.http.get("/health");
856+
expect(res).toBeOk();
857+
})
858+
.build();
859+
```
860+
842861
### Use Type Parameters
843862

844863
Provide type parameters for type-safe responses:

docs/expect.md

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,8 @@ expect(res.status).toBe(200);
409409

410410
### Chain Related Assertions
411411

412-
Group related assertions in a single chain for readability:
412+
Keep related checks in a single `expect()` chain so failures stay grouped and
413+
the assertion state stays consistent:
413414

414415
```typescript
415416
import { client, expect } from "jsr:@probitas/probitas";
@@ -425,7 +426,7 @@ expect(res)
425426
.toHaveStatus(200)
426427
.toHaveJsonMatching({ id: 1, name: "Alice" });
427428

428-
// Less ideal: Separate expect calls
429+
// Avoid: Separate expect calls on the same subject
429430
expect(res).toBeOk();
430431
expect(res).toHaveStatus(200);
431432
expect(res).toHaveJsonMatching({ id: 1, name: "Alice" });
@@ -453,6 +454,24 @@ expect(res)
453454
expect(res).not.toHaveJsonProperty("password");
454455
```
455456

457+
### Avoid Manual Checks
458+
459+
Use fluent assertions instead of `if/throw` logic or `JSON.stringify` checks.
460+
Assertions provide clearer diffs and consistent failure output.
461+
462+
```typescript
463+
// Avoid - manual validation
464+
if (res.status !== 200) {
465+
throw new Error(`Expected 200, got ${res.status}`);
466+
}
467+
468+
// Good - fluent assertion chain
469+
expect(res)
470+
.toBeOk()
471+
.toHaveStatus(200)
472+
.not.toHaveJsonProperty(["metadata", "x-internal-token"]);
473+
```
474+
456475
### Validate Structure Before Values
457476

458477
Check structure exists before asserting on specific values:
@@ -494,3 +513,15 @@ expect(res).toHaveJsonMatching({
494513

495514
// This ignores other fields like createdAt, updatedAt, etc.
496515
```
516+
517+
### Use Array Paths for Nested Properties
518+
519+
For nested fields, prefer array paths over dot notation and keep positive and
520+
negative checks in the same chain.
521+
522+
```typescript
523+
expect(res)
524+
.toBeOk()
525+
.toHaveJsonProperty("metadata")
526+
.not.toHaveJsonProperty(["metadata", "x-internal-token"]);
527+
```

docs/scenario.md

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,95 @@ export default scenario("Full Stack Test", {
777777

778778
## Best Practices
779779

780+
### Make Step Dependencies Explicit
781+
782+
Keep steps in the same scenario only when they need data from `ctx.previous`.
783+
If a step does not read the previous result, extract it into its own scenario
784+
and share a resource factory instead.
785+
786+
```typescript
787+
import { client, scenario } from "jsr:@probitas/probitas";
788+
789+
const http = () =>
790+
client.http.createHttpClient({
791+
url: Deno.env.get("API_URL") ?? "http://localhost:8080",
792+
});
793+
794+
// Avoid - unrelated steps bundled together
795+
scenario("User checks")
796+
.resource("http", http)
797+
.step("Create user", async (ctx) => {
798+
await ctx.resources.http.post("/users", { body: { name: "Alice" } });
799+
})
800+
.step("List users", async (ctx) => {
801+
await ctx.resources.http.get("/users");
802+
})
803+
.build();
804+
805+
// Good - separate scenarios that can run independently
806+
export default [
807+
scenario("Create user").resource("http", http).step(() => {}).build(),
808+
scenario("List users").resource("http", http).step(() => {}).build(),
809+
];
810+
```
811+
812+
### Finish Builders and Exports
813+
814+
Every scenario must end with `.build()` and be exported as `export default`,
815+
either as a single scenario or an array of scenarios.
816+
817+
```typescript
818+
// Correct - single scenario
819+
export default scenario("Example").step(() => {}).build();
820+
821+
// Correct - multiple scenarios
822+
export default [
823+
scenario("First").step(() => {}).build(),
824+
scenario("Second").step(() => {}).build(),
825+
];
826+
```
827+
828+
### Use Environment-Driven URLs
829+
830+
Parameterize endpoints so tests run consistently across environments and avoid
831+
hard-coding localhost URLs.
832+
833+
```typescript
834+
import { client, scenario } from "jsr:@probitas/probitas";
835+
836+
const apiUrl = Deno.env.get("API_URL") ?? "http://localhost:8080";
837+
838+
export default scenario("API test")
839+
.resource("http", () => client.http.createHttpClient({ url: apiUrl }))
840+
.step(() => {})
841+
.build();
842+
```
843+
844+
### Prefer Fluent Assertions
845+
846+
Use `expect()` chains instead of manual `if/throw` checks, and keep related
847+
assertions in a single chain for clearer failures.
848+
849+
```typescript
850+
import { client, expect } from "jsr:@probitas/probitas";
851+
852+
await using http = client.http.createHttpClient({
853+
url: "http://localhost:8080",
854+
});
855+
const res = await http.get("/users/1");
856+
857+
// Avoid - manual checks
858+
if (res.status !== 200) {
859+
throw new Error(`Expected 200, got ${res.status}`);
860+
}
861+
862+
// Good - fluent assertions
863+
expect(res)
864+
.toBeOk()
865+
.toHaveStatus(200)
866+
.not.toHaveJsonProperty(["metadata", "x-internal-token"]);
867+
```
868+
780869
### Use Descriptive Step Names
781870

782871
Good names make test output readable and debugging easier.
@@ -1148,3 +1237,12 @@ export default [
11481237
.build(),
11491238
];
11501239
```
1240+
1241+
## Common Mistakes
1242+
1243+
- Forgetting `export default` or `.build()` when returning the scenario builder
1244+
- Bundling independent tests in one scenario instead of splitting them when
1245+
they do not use `ctx.previous`
1246+
- Hard-coding endpoints instead of using environment-driven URLs
1247+
- Using manual `if/throw` checks instead of fluent `expect()` chains
1248+
- Omitting cleanup functions from `.setup()` when creating external fixtures

0 commit comments

Comments
 (0)