claude-toolkit 0.16.0 → 0.17.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/CHANGELOG.md +20 -0
- package/README.md +54 -13
- package/bin/cli.ts +75 -19
- package/package.json +6 -7
- package/src/generator.ts +64 -26
- package/src/refresh-hint.ts +95 -0
- package/bin/postinstall.mjs +0 -69
- package/docs/README.md +0 -59
- package/docs/agents/code-reviewer.md +0 -76
- package/docs/agents/github-workflow.md +0 -98
- package/docs/best-practices/capacitor/README.md +0 -64
- package/docs/best-practices/capacitor/capacitor-8.md +0 -60
- package/docs/best-practices/capacitor/capgo-setup.md +0 -151
- package/docs/best-practices/capacitor/channels-and-rollouts.md +0 -101
- package/docs/best-practices/capacitor/live-updates-ota.md +0 -75
- package/docs/best-practices/capacitor/security-encryption.md +0 -68
- package/docs/best-practices/solidjs/README.md +0 -43
- package/docs/best-practices/solidjs/anti-patterns.md +0 -166
- package/docs/best-practices/solidjs/component-patterns.md +0 -131
- package/docs/best-practices/solidjs/context-and-global-state.md +0 -131
- package/docs/best-practices/solidjs/control-flow.md +0 -124
- package/docs/best-practices/solidjs/data-fetching.md +0 -205
- package/docs/best-practices/solidjs/effects-and-lifecycle.md +0 -113
- package/docs/best-practices/solidjs/performance.md +0 -100
- package/docs/best-practices/solidjs/props-patterns.md +0 -100
- package/docs/best-practices/solidjs/reactivity-model.md +0 -104
- package/docs/best-practices/solidjs/signals-and-state.md +0 -78
- package/docs/best-practices/solidjs/stores-and-nested-state.md +0 -111
- package/docs/best-practices/solidjs/typescript-integration.md +0 -186
- package/docs/best-practices/testing/README.md +0 -84
- package/docs/best-practices/testing/playwright-e2e.md +0 -649
- package/docs/best-practices/testing/storybook-interaction.md +0 -455
- package/docs/best-practices/testing/vitest-unit.md +0 -451
- package/docs/best-practices/typescript/README.md +0 -45
- package/docs/best-practices/typescript/any-and-unknown.md +0 -73
- package/docs/best-practices/typescript/deriving-vs-decoupling.md +0 -83
- package/docs/best-practices/typescript/discriminated-unions.md +0 -75
- package/docs/best-practices/typescript/enums-alternatives.md +0 -72
- package/docs/best-practices/typescript/essential-patterns.md +0 -119
- package/docs/best-practices/typescript/generics-patterns.md +0 -105
- package/docs/best-practices/typescript/micro-opinions.md +0 -87
- package/docs/best-practices/typescript/runtime-validation.md +0 -62
- package/docs/best-practices/typescript/satisfies-operator.md +0 -100
- package/docs/best-practices/typescript/tsconfig-cheat-sheet.md +0 -160
- package/docs/best-practices/typescript/type-organization.md +0 -64
- package/docs/best-practices/typescript/type-vs-interface.md +0 -80
- package/docs/commands/code-quality.md +0 -36
- package/docs/commands/onboard.md +0 -63
- package/docs/commands/pr-review.md +0 -91
- package/docs/commands/pr-summary.md +0 -50
- package/docs/commands/proto-check.md +0 -59
- package/docs/commands/ticket.md +0 -56
- package/docs/skills/code-style.md +0 -26
- package/docs/skills/systematic-debugging.md +0 -70
- package/docs/skills/testing-patterns.md +0 -140
- package/docs/skills/typescript-conventions.md +0 -283
- package/docs/skills/verification-before-completion.md +0 -103
- package/docs/stacks/capacitor-ota.md +0 -145
- package/docs/stacks/cloudflare-d1-kv.md +0 -144
- package/docs/stacks/esnext-idioms.md +0 -25
- package/docs/stacks/i18n-typesafe.md +0 -141
- package/docs/stacks/playwright-patterns.md +0 -179
- package/docs/stacks/protobuf-contracts.md +0 -85
- package/docs/stacks/rust-wasm-patterns.md +0 -106
- package/docs/stacks/solidjs-patterns.md +0 -110
- package/docs/stacks/storybook-patterns.md +0 -177
- package/docs/stacks/vanilla-extract-patterns.md +0 -115
- package/docs/stacks/vite-vitest-patterns.md +0 -485
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
# Props Patterns
|
|
2
|
-
|
|
3
|
-
> Sources: [Props](https://docs.solidjs.com/concepts/components/props), [Props Tutorials](https://www.solidjs.com/tutorial/props_defaults) — SolidJS Docs
|
|
4
|
-
|
|
5
|
-
## The Cardinal Rule: Never Destructure Props
|
|
6
|
-
|
|
7
|
-
Props use **getters** internally for reactive tracking. Destructuring extracts the value once, severing the reactive connection permanently.
|
|
8
|
-
|
|
9
|
-
```typescript
|
|
10
|
-
// WRONG — breaks reactivity
|
|
11
|
-
const MyComponent = ({ name, count }) => {
|
|
12
|
-
return <div>{name}: {count}</div>; // Never updates
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
// WRONG — same problem
|
|
16
|
-
const MyComponent = (props) => {
|
|
17
|
-
const { name } = props; // Captured once, never reactive
|
|
18
|
-
return <div>{name}</div>;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
// CORRECT — preserves reactivity
|
|
22
|
-
const MyComponent = (props) => {
|
|
23
|
-
return <div>{props.name}: {props.count}</div>;
|
|
24
|
-
};
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## `mergeProps` for Defaults
|
|
28
|
-
|
|
29
|
-
Set default prop values without breaking reactivity:
|
|
30
|
-
|
|
31
|
-
```typescript
|
|
32
|
-
import { mergeProps } from "solid-js";
|
|
33
|
-
|
|
34
|
-
const Button = (props) => {
|
|
35
|
-
const merged = mergeProps({ variant: "primary", size: "md" }, props);
|
|
36
|
-
return <button class={`btn-${merged.variant} btn-${merged.size}`}>
|
|
37
|
-
{merged.children}
|
|
38
|
-
</button>;
|
|
39
|
-
};
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
`mergeProps` resolves properties in reverse order — later objects override earlier ones while preserving reactive tracking.
|
|
43
|
-
|
|
44
|
-
## `splitProps` for Prop Forwarding
|
|
45
|
-
|
|
46
|
-
Safely separate local props from props to forward to children:
|
|
47
|
-
|
|
48
|
-
```typescript
|
|
49
|
-
import { splitProps } from "solid-js";
|
|
50
|
-
|
|
51
|
-
const Input = (props) => {
|
|
52
|
-
const [local, inputProps] = splitProps(props, ["label", "error"]);
|
|
53
|
-
|
|
54
|
-
return (
|
|
55
|
-
<div>
|
|
56
|
-
<label>{local.label}</label>
|
|
57
|
-
<input {...inputProps} />
|
|
58
|
-
<Show when={local.error}>
|
|
59
|
-
<span class="error">{local.error}</span>
|
|
60
|
-
</Show>
|
|
61
|
-
</div>
|
|
62
|
-
);
|
|
63
|
-
};
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
You can split into multiple groups:
|
|
67
|
-
|
|
68
|
-
```typescript
|
|
69
|
-
const [style, events, rest] = splitProps(props, ["class", "style"], ["onClick", "onInput"]);
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
## `children` Helper
|
|
73
|
-
|
|
74
|
-
Accessing `props.children` multiple times can create duplicate elements. Use the `children` helper to resolve children once:
|
|
75
|
-
|
|
76
|
-
```typescript
|
|
77
|
-
import { children } from "solid-js";
|
|
78
|
-
|
|
79
|
-
const ColoredList = (props) => {
|
|
80
|
-
const resolved = children(() => props.children);
|
|
81
|
-
|
|
82
|
-
return <div class="colored">{resolved()}</div>;
|
|
83
|
-
};
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
The helper resolves any dynamic children into a flat array, memoized for safe repeated access.
|
|
87
|
-
|
|
88
|
-
## Call Signals Before Passing as Props
|
|
89
|
-
|
|
90
|
-
When passing a signal value to a child component, call the getter. Components shouldn't need to know whether a prop came from a signal or a static value.
|
|
91
|
-
|
|
92
|
-
```typescript
|
|
93
|
-
// CORRECT — child receives a plain value
|
|
94
|
-
<UserCard name={userName()} age={userAge()} />
|
|
95
|
-
|
|
96
|
-
// Also valid — passing a reactive getter for the child to track
|
|
97
|
-
<UserCard name={userName} /> // child would read props.name()
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
The first pattern is simpler and recommended for most cases. The second is useful when you want the child to participate in fine-grained tracking.
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
# Reactivity Model
|
|
2
|
-
|
|
3
|
-
> Sources: [Fine-Grained Reactivity](https://docs.solidjs.com/advanced-concepts/fine-grained-reactivity), [Intro to Reactivity](https://docs.solidjs.com/concepts/intro-to-reactivity) — SolidJS Docs
|
|
4
|
-
|
|
5
|
-
## The Core Difference
|
|
6
|
-
|
|
7
|
-
SolidJS uses **fine-grained reactivity**. Updates target specific DOM elements that depend on changed data — not entire component trees. Components run **once**; only the reactive expressions inside them re-execute.
|
|
8
|
-
|
|
9
|
-
This means:
|
|
10
|
-
- No virtual DOM diffing
|
|
11
|
-
- No component re-renders
|
|
12
|
-
- No stale closure bugs
|
|
13
|
-
- Updates are surgical and automatic
|
|
14
|
-
|
|
15
|
-
## How It Works
|
|
16
|
-
|
|
17
|
-
### Signals, Effects, and the Subscription Model
|
|
18
|
-
|
|
19
|
-
The reactive system has two key elements: **signals** (data sources) and **observers** (effects, memos, JSX expressions).
|
|
20
|
-
|
|
21
|
-
1. A global `currentSubscriber` variable tracks the active observer
|
|
22
|
-
2. When an observer runs, it sets itself as the current subscriber
|
|
23
|
-
3. Any signal read during that execution registers the subscriber
|
|
24
|
-
4. When a signal's value changes, all registered subscribers re-execute
|
|
25
|
-
5. The subscriber reference is cleared after execution
|
|
26
|
-
|
|
27
|
-
```typescript
|
|
28
|
-
const [count, setCount] = createSignal(0);
|
|
29
|
-
|
|
30
|
-
// This effect becomes a subscriber of `count`
|
|
31
|
-
createEffect(() => {
|
|
32
|
-
console.log(count()); // Reading count() registers the dependency
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
setCount(1); // Notifies the effect, which re-runs
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
### Equality Checks
|
|
39
|
-
|
|
40
|
-
Signals only notify subscribers when the value **actually changes**. Redundant updates (setting the same value) are ignored.
|
|
41
|
-
|
|
42
|
-
```typescript
|
|
43
|
-
setCount(0); // If count is already 0, no effects fire
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
## The Synchronous Contract
|
|
47
|
-
|
|
48
|
-
Reactivity in Solid is **synchronous**. The system registers a subscriber, runs the function, and unregisters — all in one synchronous pass.
|
|
49
|
-
|
|
50
|
-
This means **async operations break tracking**:
|
|
51
|
-
|
|
52
|
-
```typescript
|
|
53
|
-
// BROKEN — fetch happens after tracking ends
|
|
54
|
-
createEffect(async () => {
|
|
55
|
-
const data = await fetch(url()); // url() is tracked
|
|
56
|
-
setResult(data); // but this runs after tracking ends
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
// CORRECT — use createResource for async
|
|
60
|
-
const [data] = createResource(url, fetchData);
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
## Dependency Tracking is Automatic
|
|
64
|
-
|
|
65
|
-
You don't declare dependencies. Solid tracks them by observing which signals are read during execution:
|
|
66
|
-
|
|
67
|
-
```typescript
|
|
68
|
-
createEffect(() => {
|
|
69
|
-
// Solid automatically knows this depends on firstName() and lastName()
|
|
70
|
-
console.log(`${firstName()} ${lastName()}`);
|
|
71
|
-
});
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
### Conditional Tracking
|
|
75
|
-
|
|
76
|
-
If a branch isn't taken, signals in that branch aren't tracked:
|
|
77
|
-
|
|
78
|
-
```typescript
|
|
79
|
-
const display = createMemo(() => {
|
|
80
|
-
if (!showDetails()) return "Hidden";
|
|
81
|
-
// name() and age() are only tracked when showDetails() is true
|
|
82
|
-
return `${name()}, age ${age()}`;
|
|
83
|
-
});
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
## Derive, Don't Sync
|
|
87
|
-
|
|
88
|
-
The golden rule of Solid reactivity:
|
|
89
|
-
|
|
90
|
-
**If a value can be computed from other reactive values, derive it — don't synchronize it with an effect.**
|
|
91
|
-
|
|
92
|
-
```typescript
|
|
93
|
-
// WRONG — synchronizing state
|
|
94
|
-
createEffect(() => {
|
|
95
|
-
setDoubled(count() * 2);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
// RIGHT — deriving state
|
|
99
|
-
const doubled = createMemo(() => count() * 2);
|
|
100
|
-
// or simply:
|
|
101
|
-
const doubled = () => count() * 2;
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
Derived values integrate naturally with the reactive graph. Effects that synchronize state create ordering problems and glitchy intermediate states.
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
# Signals & State
|
|
2
|
-
|
|
3
|
-
> Sources: [Signals](https://docs.solidjs.com/concepts/signals), [Derived Signals](https://docs.solidjs.com/concepts/derived-values/derived-signals) — SolidJS Docs
|
|
4
|
-
|
|
5
|
-
## createSignal
|
|
6
|
-
|
|
7
|
-
The primary state primitive. Returns a getter function and a setter function.
|
|
8
|
-
|
|
9
|
-
```typescript
|
|
10
|
-
const [count, setCount] = createSignal(0);
|
|
11
|
-
|
|
12
|
-
// Read: always call as a function
|
|
13
|
-
console.log(count()); // 0
|
|
14
|
-
|
|
15
|
-
// Write: direct value or updater function
|
|
16
|
-
setCount(5);
|
|
17
|
-
setCount(prev => prev + 1);
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
**Key mental model:** The getter is a function, not a value. You must call `count()` to read it. Forgetting the parentheses is the most common Solid mistake.
|
|
21
|
-
|
|
22
|
-
## Derived Values
|
|
23
|
-
|
|
24
|
-
### Inline Derivations
|
|
25
|
-
|
|
26
|
-
For simple computations, a plain function is sufficient:
|
|
27
|
-
|
|
28
|
-
```typescript
|
|
29
|
-
const doubled = () => count() * 2;
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
This re-evaluates every time it's called in a reactive context. No caching.
|
|
33
|
-
|
|
34
|
-
### createMemo
|
|
35
|
-
|
|
36
|
-
For expensive computations or values read in multiple places, use `createMemo` to cache the result:
|
|
37
|
-
|
|
38
|
-
```typescript
|
|
39
|
-
const sorted = createMemo(() => {
|
|
40
|
-
console.log("Sorting..."); // Only runs when items() changes
|
|
41
|
-
return items().slice().sort((a, b) => a.localeCompare(b));
|
|
42
|
-
});
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
**When to use `createMemo` vs inline derivation:**
|
|
46
|
-
|
|
47
|
-
| Scenario | Use |
|
|
48
|
-
|---|---|
|
|
49
|
-
| Simple, cheap computation | Inline function `() => ...` |
|
|
50
|
-
| Expensive computation (sort, filter, map) | `createMemo` |
|
|
51
|
-
| Value read in multiple reactive contexts | `createMemo` |
|
|
52
|
-
| Value used once in JSX | Inline function |
|
|
53
|
-
|
|
54
|
-
## Signals for Functions
|
|
55
|
-
|
|
56
|
-
If you need to store a function as a signal value, use the callback form of the setter to avoid Solid treating it as an updater:
|
|
57
|
-
|
|
58
|
-
```typescript
|
|
59
|
-
const [handler, setHandler] = createSignal<() => void>(() => {});
|
|
60
|
-
|
|
61
|
-
// WRONG — Solid calls this as an updater
|
|
62
|
-
setHandler(myFunction);
|
|
63
|
-
|
|
64
|
-
// RIGHT — wrap in callback
|
|
65
|
-
setHandler(() => myFunction);
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
## When to Use Signals vs Stores
|
|
69
|
-
|
|
70
|
-
| Data shape | Use |
|
|
71
|
-
|---|---|
|
|
72
|
-
| Single primitive value | `createSignal` |
|
|
73
|
-
| Simple object (few properties, flat) | `createSignal` |
|
|
74
|
-
| Complex nested object | `createStore` |
|
|
75
|
-
| Array of objects with updates | `createStore` |
|
|
76
|
-
| Shared state across components | `createStore` + context |
|
|
77
|
-
|
|
78
|
-
See [Stores & Nested State](stores-and-nested-state.md) for store patterns.
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
# Stores & Nested State
|
|
2
|
-
|
|
3
|
-
> Sources: [Stores](https://docs.solidjs.com/concepts/stores), [Complex State Management](https://docs.solidjs.com/guides/complex-state-management) — SolidJS Docs
|
|
4
|
-
|
|
5
|
-
## When to Use Stores
|
|
6
|
-
|
|
7
|
-
Use `createStore` when your state is a **complex, nested object** or an **array of objects** that receives granular updates. Stores provide fine-grained reactivity at the **property level** — updating a nested property only re-renders the parts of UI that depend on that specific property.
|
|
8
|
-
|
|
9
|
-
```typescript
|
|
10
|
-
import { createStore } from "solid-js/store";
|
|
11
|
-
|
|
12
|
-
const [state, setState] = createStore({
|
|
13
|
-
user: { name: "Alice", settings: { theme: "dark" } },
|
|
14
|
-
tasks: [
|
|
15
|
-
{ id: 1, text: "Learn Solid", completed: false },
|
|
16
|
-
],
|
|
17
|
-
});
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
**Reading:** Access properties directly (no function call needed):
|
|
21
|
-
|
|
22
|
-
```typescript
|
|
23
|
-
<span>{state.user.name}</span>
|
|
24
|
-
<span>{state.user.settings.theme}</span>
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## Path Syntax Updates
|
|
28
|
-
|
|
29
|
-
Stores use a **path syntax** for targeted, efficient updates:
|
|
30
|
-
|
|
31
|
-
```typescript
|
|
32
|
-
// Update a nested property
|
|
33
|
-
setState("user", "settings", "theme", "light");
|
|
34
|
-
|
|
35
|
-
// Update a specific array item
|
|
36
|
-
setState("tasks", 0, "completed", true);
|
|
37
|
-
|
|
38
|
-
// Update with a function
|
|
39
|
-
setState("tasks", 0, "completed", prev => !prev);
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
### Array Operations
|
|
43
|
-
|
|
44
|
-
```typescript
|
|
45
|
-
// Append to array (targeted — doesn't recreate the array)
|
|
46
|
-
setState("tasks", state.tasks.length, {
|
|
47
|
-
id: 2, text: "Ship it", completed: false,
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
// Update multiple indices
|
|
51
|
-
setState("tasks", [0, 2], "completed", true);
|
|
52
|
-
|
|
53
|
-
// Update by range
|
|
54
|
-
setState("tasks", { from: 0, to: 3 }, "completed", false);
|
|
55
|
-
|
|
56
|
-
// Update by predicate
|
|
57
|
-
setState("tasks", task => task.completed, "archived", true);
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
## `produce` for Imperative Updates
|
|
61
|
-
|
|
62
|
-
When you need to modify multiple properties at once, `produce` provides an imperative API:
|
|
63
|
-
|
|
64
|
-
```typescript
|
|
65
|
-
import { produce } from "solid-js/store";
|
|
66
|
-
|
|
67
|
-
setState("tasks", 0, produce(task => {
|
|
68
|
-
task.completed = true;
|
|
69
|
-
task.completedAt = Date.now();
|
|
70
|
-
}));
|
|
71
|
-
|
|
72
|
-
// Also works at the store root
|
|
73
|
-
setState(produce(state => {
|
|
74
|
-
state.tasks.push({ id: 3, text: "New task", completed: false });
|
|
75
|
-
state.user.name = "Bob";
|
|
76
|
-
}));
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
`produce` creates a draft, applies your mutations, then produces an immutable update. Only use it with objects and arrays (not Sets or Maps).
|
|
80
|
-
|
|
81
|
-
## `reconcile` for External Data
|
|
82
|
-
|
|
83
|
-
When you receive a whole new object (e.g., from an API response), `reconcile` diffs the old and new data, only triggering updates for properties that actually changed:
|
|
84
|
-
|
|
85
|
-
```typescript
|
|
86
|
-
import { reconcile } from "solid-js/store";
|
|
87
|
-
|
|
88
|
-
const newData = await fetchTasks();
|
|
89
|
-
setState("tasks", reconcile(newData));
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
This avoids re-rendering everything when most of the data hasn't changed.
|
|
93
|
-
|
|
94
|
-
## `unwrap` for Snapshots
|
|
95
|
-
|
|
96
|
-
Extract a plain JavaScript object from a store (strips the reactive proxy):
|
|
97
|
-
|
|
98
|
-
```typescript
|
|
99
|
-
import { unwrap } from "solid-js/store";
|
|
100
|
-
|
|
101
|
-
const snapshot = unwrap(state);
|
|
102
|
-
// Use snapshot for third-party libraries, logging, or serialization
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
## Best Practices
|
|
106
|
-
|
|
107
|
-
- **Use path syntax** for targeted updates — it's more efficient than replacing objects
|
|
108
|
-
- **Use `produce`** when modifying multiple properties in one operation
|
|
109
|
-
- **Use `reconcile`** when integrating API responses to avoid unnecessary updates
|
|
110
|
-
- **Don't mutate store properties directly** — always go through `setState`
|
|
111
|
-
- **Stores are batched automatically** — multiple `setState` calls in the same synchronous block are batched into one update
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
# TypeScript Integration
|
|
2
|
-
|
|
3
|
-
> Source: [TypeScript](https://docs.solidjs.com/configuration/typescript) — SolidJS Docs
|
|
4
|
-
|
|
5
|
-
## TSConfig for SolidJS
|
|
6
|
-
|
|
7
|
-
Required settings for Solid's JSX transformation:
|
|
8
|
-
|
|
9
|
-
```jsonc
|
|
10
|
-
{
|
|
11
|
-
"compilerOptions": {
|
|
12
|
-
"jsx": "preserve",
|
|
13
|
-
"jsxImportSource": "solid-js",
|
|
14
|
-
"strict": true,
|
|
15
|
-
"noEmit": true,
|
|
16
|
-
"isolatedModules": true
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
`"jsx": "preserve"` is required — Solid's JSX transformation is incompatible with TypeScript's built-in JSX handling.
|
|
22
|
-
|
|
23
|
-
## Typing Signals
|
|
24
|
-
|
|
25
|
-
```typescript
|
|
26
|
-
// Type is inferred from the initial value
|
|
27
|
-
const [count, setCount] = createSignal(0); // Signal<number>
|
|
28
|
-
|
|
29
|
-
// Explicit type when initial value doesn't capture the full type
|
|
30
|
-
const [user, setUser] = createSignal<User | null>(null);
|
|
31
|
-
|
|
32
|
-
// Without a default, type includes undefined
|
|
33
|
-
const [data, setData] = createSignal<string>(); // Accessor<string | undefined>
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
## Typing Stores
|
|
37
|
-
|
|
38
|
-
```typescript
|
|
39
|
-
type AppState = {
|
|
40
|
-
user: User;
|
|
41
|
-
tasks: Task[];
|
|
42
|
-
settings: { theme: "light" | "dark" };
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const [state, setState] = createStore<AppState>({
|
|
46
|
-
user: { id: "1", name: "Alice" },
|
|
47
|
-
tasks: [],
|
|
48
|
-
settings: { theme: "dark" },
|
|
49
|
-
});
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
## Typing Context
|
|
53
|
-
|
|
54
|
-
Provide a type parameter to `createContext`:
|
|
55
|
-
|
|
56
|
-
```typescript
|
|
57
|
-
type AuthContext = {
|
|
58
|
-
user: User | null;
|
|
59
|
-
login: (credentials: Credentials) => Promise<void>;
|
|
60
|
-
logout: () => void;
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
const AuthContext = createContext<AuthContext>();
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
**Factory pattern** — derive the type from the factory function:
|
|
67
|
-
|
|
68
|
-
```typescript
|
|
69
|
-
function createAuthStore() { /* ... */ }
|
|
70
|
-
|
|
71
|
-
const AuthContext = createContext<ReturnType<typeof createAuthStore>>();
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
## Component Types
|
|
75
|
-
|
|
76
|
-
```typescript
|
|
77
|
-
import type { Component, ParentComponent, VoidComponent, FlowComponent } from "solid-js";
|
|
78
|
-
|
|
79
|
-
// Standard component
|
|
80
|
-
const App: Component = () => <div>Hello</div>;
|
|
81
|
-
|
|
82
|
-
// Accepts children
|
|
83
|
-
const Card: ParentComponent<{ title: string }> = (props) => (
|
|
84
|
-
<div>
|
|
85
|
-
<h2>{props.title}</h2>
|
|
86
|
-
{props.children}
|
|
87
|
-
</div>
|
|
88
|
-
);
|
|
89
|
-
|
|
90
|
-
// No children allowed
|
|
91
|
-
const Icon: VoidComponent<{ name: string }> = (props) => (
|
|
92
|
-
<svg class={`icon-${props.name}`} />
|
|
93
|
-
);
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
### Generic Components
|
|
97
|
-
|
|
98
|
-
`Component` types don't support generics. Use function declarations:
|
|
99
|
-
|
|
100
|
-
```typescript
|
|
101
|
-
function Select<T>(props: {
|
|
102
|
-
options: T[];
|
|
103
|
-
value: T;
|
|
104
|
-
onChange: (value: T) => void;
|
|
105
|
-
label: (item: T) => string;
|
|
106
|
-
}): JSX.Element {
|
|
107
|
-
return (
|
|
108
|
-
<For each={props.options}>
|
|
109
|
-
{(option) => (
|
|
110
|
-
<button
|
|
111
|
-
classList={{ active: option === props.value }}
|
|
112
|
-
onClick={() => props.onChange(option)}
|
|
113
|
-
>
|
|
114
|
-
{props.label(option)}
|
|
115
|
-
</button>
|
|
116
|
-
)}
|
|
117
|
-
</For>
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
## Event Handlers
|
|
123
|
-
|
|
124
|
-
Inline handlers are auto-typed. For extracted handlers:
|
|
125
|
-
|
|
126
|
-
```typescript
|
|
127
|
-
const handleInput: JSX.EventHandler<HTMLInputElement, InputEvent> = (e) => {
|
|
128
|
-
// e.currentTarget is typed as HTMLInputElement
|
|
129
|
-
setValue(e.currentTarget.value);
|
|
130
|
-
};
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
## Control Flow Type Narrowing
|
|
134
|
-
|
|
135
|
-
TypeScript can't narrow types through accessors. Use the callback form of `<Show>`:
|
|
136
|
-
|
|
137
|
-
```typescript
|
|
138
|
-
// WRONG — TypeScript doesn't know user() is non-null inside Show
|
|
139
|
-
<Show when={user()}>
|
|
140
|
-
<span>{user()!.name}</span> {/* Forced to use ! */}
|
|
141
|
-
</Show>
|
|
142
|
-
|
|
143
|
-
// RIGHT — callback provides narrowed accessor
|
|
144
|
-
<Show when={user()}>
|
|
145
|
-
{(u) => <span>{u().name}</span>}
|
|
146
|
-
</Show>
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
## Extending JSX Types
|
|
150
|
-
|
|
151
|
-
### Custom Events
|
|
152
|
-
|
|
153
|
-
```typescript
|
|
154
|
-
declare module "solid-js" {
|
|
155
|
-
namespace JSX {
|
|
156
|
-
interface CustomEvents {
|
|
157
|
-
myEvent: CustomEvent<{ detail: string }>;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
### Custom Directives
|
|
164
|
-
|
|
165
|
-
```typescript
|
|
166
|
-
declare module "solid-js" {
|
|
167
|
-
namespace JSX {
|
|
168
|
-
interface Directives {
|
|
169
|
-
tooltip: string;
|
|
170
|
-
clickOutside: () => void;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
### Explicit Properties/Attributes
|
|
177
|
-
|
|
178
|
-
```typescript
|
|
179
|
-
declare module "solid-js" {
|
|
180
|
-
namespace JSX {
|
|
181
|
-
interface ExplicitProperties { /* prop:___ */ }
|
|
182
|
-
interface ExplicitAttributes { /* attr:___ */ }
|
|
183
|
-
interface ExplicitBoolAttributes { /* bool:___ */ }
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
```
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
# Testing Best Practices
|
|
2
|
-
|
|
3
|
-
> A curated collection of testing best practices for the rendezvous.nz stack, sourced from official documentation, framework maintainers, and the testing community as of 2026.
|
|
4
|
-
|
|
5
|
-
This project uses a **3-layer testing strategy** that maps to the classic testing pyramid. Each layer targets a different scope, runs in a different environment, and catches a different class of bugs.
|
|
6
|
-
|
|
7
|
-
```
|
|
8
|
-
/ E2E \ Playwright (real browsers)
|
|
9
|
-
/----------\ Full user flows, cross-browser
|
|
10
|
-
/ Interaction \ Storybook (component sandbox)
|
|
11
|
-
/----------------\ Component behavior, visual, a11y
|
|
12
|
-
/ Unit \ Vitest (Node/JSDOM)
|
|
13
|
-
/--------------------\ Pure logic, signals, utilities
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
## The Three Layers
|
|
17
|
-
|
|
18
|
-
| Layer | Tool | Scope | Environment | Speed |
|
|
19
|
-
| --------------------------------------- | ---------------- | ------------------------------------ | -------------------------------------- | ----------------- |
|
|
20
|
-
| [Unit](vitest-unit.md) | Vitest 4.x | Functions, signals, hooks, utilities | Node / JSDOM | ~ms per test |
|
|
21
|
-
| [Interaction](storybook-interaction.md) | Storybook 10.x | Component rendering, behavior, a11y | Real browser (via Vitest addon) | ~100ms per story |
|
|
22
|
-
| [E2E](playwright-e2e.md) | Playwright 1.58+ | Full user flows, API integration | Real browser (Chromium/Firefox/WebKit) | ~seconds per test |
|
|
23
|
-
|
|
24
|
-
## When to Use Which Layer
|
|
25
|
-
|
|
26
|
-
| Question | Layer |
|
|
27
|
-
| --------------------------------------------------------- | -------------------------------------- |
|
|
28
|
-
| Does this logic need a DOM? | **Unit** if no, **Interaction** if yes |
|
|
29
|
-
| Does it involve multiple pages or navigation? | **E2E** |
|
|
30
|
-
| Am I testing a single component's visual states? | **Interaction** |
|
|
31
|
-
| Am I testing a reactive primitive (signal, memo, effect)? | **Unit** |
|
|
32
|
-
| Does it require authentication or real network? | **E2E** |
|
|
33
|
-
| Am I testing component accessibility? | **Interaction** (axe-core addon) |
|
|
34
|
-
| Am I testing cross-browser rendering? | **E2E** (multi-browser projects) |
|
|
35
|
-
| Does it need the Cloudflare Worker running? | **E2E** |
|
|
36
|
-
|
|
37
|
-
## Distribution Guideline
|
|
38
|
-
|
|
39
|
-
Aim for the pyramid shape -- many fast unit tests at the base, fewer interaction tests in the middle, and a focused set of E2E tests at the top.
|
|
40
|
-
|
|
41
|
-
| Layer | Target Ratio | What to Cover |
|
|
42
|
-
| ----------- | ------------ | ---------------------------------------------------------------------------- |
|
|
43
|
-
| Unit | ~70% | Business logic, utilities, data transforms, reactive primitives, error paths |
|
|
44
|
-
| Interaction | ~20% | Component variants, user interactions, accessibility, visual regression |
|
|
45
|
-
| E2E | ~10% | Critical user journeys, authentication flows, cross-page workflows |
|
|
46
|
-
|
|
47
|
-
## Shared Principles Across All Layers
|
|
48
|
-
|
|
49
|
-
1. **Test behavior, not implementation.** Assert what the user sees or what the API returns, not internal state.
|
|
50
|
-
2. **Prefer role-based queries.** Use `getByRole`, `getByLabel`, `getByText` over test IDs or CSS selectors.
|
|
51
|
-
3. **Never sleep.** Use framework-provided waiting mechanisms (`waitFor`, `findBy*`, web-first assertions).
|
|
52
|
-
4. **One assertion focus per test.** A test should verify one behavior. Multiple assertions are fine if they verify facets of the same behavior.
|
|
53
|
-
5. **Isolate tests.** No test should depend on another test's side effects. Reset state between tests.
|
|
54
|
-
6. **Mock at boundaries.** Mock network calls and external services, not internal modules.
|
|
55
|
-
7. **SolidJS reactivity rules apply in tests.** Never destructure props. Use `renderHook` or `createRoot` for signal testing.
|
|
56
|
-
|
|
57
|
-
## SolidJS-Specific Considerations
|
|
58
|
-
|
|
59
|
-
SolidJS components run once -- only reactive expressions re-execute. This affects testing:
|
|
60
|
-
|
|
61
|
-
- **`render()` takes a callback**: `render(() => <Component />)`, not `render(<Component />)`.
|
|
62
|
-
- **Effects are async**: Use `testEffect` from `@solidjs/testing-library` for effect assertions.
|
|
63
|
-
- **Storybook decorators need `createJSXDecorator`**: Standard decorators cause duplicate DOM elements with SolidJS.
|
|
64
|
-
|
|
65
|
-
## Technology Versions (as of April 2026)
|
|
66
|
-
|
|
67
|
-
| Tool | Version | Notes |
|
|
68
|
-
| ------------------------ | ------- | ---------------------------------------------------- |
|
|
69
|
-
| Vitest | ^4.1.x | V8 coverage, test tags, builder-pattern fixtures |
|
|
70
|
-
| @solidjs/testing-library | ^0.8.x | SolidJS-specific render, renderHook, testEffect |
|
|
71
|
-
| Storybook | ^10.3.x | ESM-only, `sb.mock`, testing widget |
|
|
72
|
-
| storybook-solidjs-vite | ^10.0.x | Community-maintained SolidJS renderer |
|
|
73
|
-
| @storybook/addon-vitest | ^10.x | Replaces old test-runner, uses Vitest browser mode |
|
|
74
|
-
| Playwright | ^1.58.x | Timeline view, Chrome for Testing, WebSocket mocking |
|
|
75
|
-
| @axe-core/playwright | ^4.x | WCAG 2.1 AA automated accessibility checks |
|
|
76
|
-
| MSW | ^2.x | Network mocking for both Storybook and Vitest |
|
|
77
|
-
|
|
78
|
-
## Guides
|
|
79
|
-
|
|
80
|
-
| Guide | Summary |
|
|
81
|
-
| --------------------------------------------------------- | ---------------------------------------------------------------------------- |
|
|
82
|
-
| [Vitest Unit Testing](vitest-unit.md) | AAA pattern, signal testing, mocking, coverage, performance, anti-patterns. |
|
|
83
|
-
| [Storybook Interaction Testing](storybook-interaction.md) | CSF 3, play functions, a11y, visual regression, MSW, portable stories. |
|
|
84
|
-
| [Playwright E2E Testing](playwright-e2e.md) | Page Objects, fixtures, auth, network mocking, CI/CD, flaky test prevention. |
|