claude-toolkit 0.1.12 → 0.1.20
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 +32 -0
- package/README.md +3 -0
- package/core/skills/ct-testing-patterns/SKILL.md +37 -1
- package/docs/README.md +3 -0
- package/docs/best-practices/testing/README.md +84 -0
- package/docs/best-practices/testing/playwright-e2e.md +649 -0
- package/docs/best-practices/testing/storybook-interaction.md +455 -0
- package/docs/best-practices/testing/vitest-unit.md +451 -0
- package/docs/stacks/playwright-patterns.md +179 -0
- package/docs/stacks/storybook-patterns.md +177 -0
- package/docs/stacks/vite-vitest-patterns.md +485 -0
- package/package.json +1 -1
- package/stacks/playwright/skills/ct-playwright-patterns/SKILL.md +168 -0
- package/stacks/playwright/stack.json +39 -0
- package/stacks/solidjs/stack.json +7 -1
- package/stacks/storybook/skills/ct-storybook-patterns/SKILL.md +166 -0
- package/stacks/storybook/stack.json +46 -0
- package/stacks/vite/skills/ct-vite-vitest-patterns/SKILL.md +492 -0
- package/stacks/vite/stack.json +66 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ct-playwright-patterns
|
|
3
|
+
description: Playwright E2E testing patterns -- Page Objects, fixtures, auth, network mocking, and CI/CD
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Playwright E2E Patterns
|
|
7
|
+
|
|
8
|
+
E2E tests sit at the top of the testing pyramid. They exercise full user journeys in real browsers. Write fewer, make each one count.
|
|
9
|
+
|
|
10
|
+
## Project Structure
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
e2e/
|
|
14
|
+
fixtures/ # Custom Playwright fixtures
|
|
15
|
+
index.ts # Extended test with page objects
|
|
16
|
+
pages/ # Page Object classes
|
|
17
|
+
components/ # Component Object classes (reusable fragments)
|
|
18
|
+
specs/ # Test files organized by feature
|
|
19
|
+
helpers/ # Utilities (data factories, API helpers)
|
|
20
|
+
.auth/ # storageState files (gitignored)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Page Object Model + Fixtures
|
|
24
|
+
|
|
25
|
+
Combine Page Objects with custom fixtures. Tests receive pre-built page objects:
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
// e2e/pages/LoginPage.ts
|
|
29
|
+
export class LoginPage {
|
|
30
|
+
private readonly emailInput: Locator;
|
|
31
|
+
private readonly submitButton: Locator;
|
|
32
|
+
|
|
33
|
+
constructor(private readonly page: Page) {
|
|
34
|
+
this.emailInput = page.getByLabel("Email");
|
|
35
|
+
this.submitButton = page.getByRole("button", { name: "Sign in" });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async goto() { await this.page.goto("/login"); }
|
|
39
|
+
async login(email: string, password: string) {
|
|
40
|
+
await this.emailInput.fill(email);
|
|
41
|
+
await this.submitButton.click();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// e2e/fixtures/index.ts
|
|
46
|
+
export const test = base.extend<Fixtures>({
|
|
47
|
+
loginPage: async ({ page }, use) => { await use(new LoginPage(page)); },
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Fixtures are lazy (created on demand) and composable (can depend on each other).
|
|
52
|
+
|
|
53
|
+
## Authentication
|
|
54
|
+
|
|
55
|
+
Use project dependencies with `storageState` to authenticate once and reuse:
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
// e2e/auth.setup.ts
|
|
59
|
+
setup("authenticate", async ({ page }) => {
|
|
60
|
+
await page.goto("/login");
|
|
61
|
+
await page.getByLabel("Email").fill("test@example.com");
|
|
62
|
+
await page.getByRole("button", { name: "Sign in" }).click();
|
|
63
|
+
await page.waitForURL("/dashboard");
|
|
64
|
+
await page.context().storageState({ path: "e2e/.auth/user.json" });
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Config: `dependencies: ["setup"]` + `storageState: "e2e/.auth/user.json"`. Add `e2e/.auth/` to `.gitignore`.
|
|
69
|
+
|
|
70
|
+
For multi-role testing, create separate setup files per role.
|
|
71
|
+
|
|
72
|
+
## Locator Priority
|
|
73
|
+
|
|
74
|
+
1. `getByRole()` -- accessibility semantics, survives refactors
|
|
75
|
+
2. `getByLabel()` -- form elements
|
|
76
|
+
3. `getByPlaceholder()` -- input hints
|
|
77
|
+
4. `getByText()` -- visible text
|
|
78
|
+
5. `getByTestId()` -- stable but less semantic
|
|
79
|
+
6. CSS/XPath -- **avoid**
|
|
80
|
+
|
|
81
|
+
## Web-First Assertions
|
|
82
|
+
|
|
83
|
+
Always use auto-retrying assertions:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
// GOOD
|
|
87
|
+
await expect(page.getByRole("alert")).toBeVisible();
|
|
88
|
+
await expect(page).toHaveURL("/dashboard");
|
|
89
|
+
|
|
90
|
+
// BAD -- checks once, no retry
|
|
91
|
+
expect(await page.getByRole("alert").isVisible()).toBeTruthy();
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Key assertions: `toBeVisible()`, `toBeEnabled()`, `toHaveText()`, `toHaveURL()`, `toHaveCount()`, `toHaveScreenshot()`, `toMatchAriaSnapshot()`.
|
|
95
|
+
|
|
96
|
+
## Network Mocking
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
// Mock API
|
|
100
|
+
await page.route("**/api/users", async (route) => {
|
|
101
|
+
await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify([{ name: "Alice" }]) });
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Block analytics
|
|
105
|
+
await page.route("**/analytics/**", (route) => route.abort());
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Mock ~80% of API calls for speed; keep ~20% hitting real endpoints. Always call `route.continue()`, `route.fulfill()`, or `route.abort()`. Use `page.unrouteAll()` in cleanup.
|
|
109
|
+
|
|
110
|
+
## WebSocket Mocking (v1.53+)
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
await page.routeWebSocket("wss://example.com/ws", (ws) => {
|
|
114
|
+
ws.onMessage((message) => { ws.send("mocked response"); });
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Accessibility Testing
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import AxeBuilder from "@axe-core/playwright";
|
|
122
|
+
|
|
123
|
+
test("WCAG 2.1 AA", async ({ page }) => {
|
|
124
|
+
await page.goto("/");
|
|
125
|
+
const results = await new AxeBuilder({ page }).withTags(["wcag2a", "wcag2aa", "wcag21aa"]).analyze();
|
|
126
|
+
expect(results.violations).toEqual([]);
|
|
127
|
+
});
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Aria snapshots (v1.52+): `await expect(nav).toMatchAriaSnapshot(...)`.
|
|
131
|
+
|
|
132
|
+
## Parallelization
|
|
133
|
+
|
|
134
|
+
- File-level parallel (default) -- start here
|
|
135
|
+
- In-file: `test.describe.configure({ mode: "parallel" })`
|
|
136
|
+
- Full: `fullyParallel: true` in config
|
|
137
|
+
|
|
138
|
+
Each worker gets its own `BrowserContext` (isolated cookies/storage). Use `mode: "serial"` sparingly.
|
|
139
|
+
|
|
140
|
+
## CI/CD
|
|
141
|
+
|
|
142
|
+
- Use official Playwright Docker image (`mcr.microsoft.com/playwright:v1.58.0-noble`)
|
|
143
|
+
- Upload reports + traces as artifacts
|
|
144
|
+
- Use `--shard` for suites exceeding 5 minutes
|
|
145
|
+
- `retries: process.env.CI ? 2 : 0`
|
|
146
|
+
- Traces: `trace: "on-first-retry"`
|
|
147
|
+
|
|
148
|
+
## Flaky Test Prevention
|
|
149
|
+
|
|
150
|
+
| Cause | Fix |
|
|
151
|
+
|---|---|
|
|
152
|
+
| `waitForTimeout()` | Web-first assertions or `waitForResponse` |
|
|
153
|
+
| Brittle selectors | `getByRole`, `getByLabel`, `getByTestId` |
|
|
154
|
+
| Shared state | Fresh `BrowserContext` per test |
|
|
155
|
+
| External API flakiness | Mock with `page.route()` |
|
|
156
|
+
| Animation timing | `--force-prefers-reduced-motion` |
|
|
157
|
+
| Missing `await` | #1 source of false-passing tests. Lint for it. |
|
|
158
|
+
|
|
159
|
+
## Anti-Patterns
|
|
160
|
+
|
|
161
|
+
1. **Using `waitForTimeout()`** -- Use web-first assertions or explicit wait conditions.
|
|
162
|
+
2. **Fragile CSS/XPath selectors** -- Use role-based and semantic locators.
|
|
163
|
+
3. **Using `{ force: true }`** -- Masks real actionability problems.
|
|
164
|
+
4. **Missing `await` on assertions** -- #1 source of false-passing tests.
|
|
165
|
+
5. **Tests without assertions** -- A test that clicks without asserting is just a script.
|
|
166
|
+
6. **Using Playwright for unit tests** -- Use Vitest for pure logic.
|
|
167
|
+
7. **Committing raw codegen output** -- Always refactor into Page Objects.
|
|
168
|
+
8. **Not cleaning up route handlers** -- Use `page.unrouteAll()`.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ct-playwright-patterns",
|
|
3
|
+
"description": "Playwright E2E testing patterns with Page Objects, fixtures, and CI/CD",
|
|
4
|
+
"defaultMappings": {
|
|
5
|
+
"e2e": "ct-playwright-patterns",
|
|
6
|
+
"playwright.config.ts": "ct-playwright-patterns"
|
|
7
|
+
},
|
|
8
|
+
"fileExtensions": ["spec.ts"],
|
|
9
|
+
"skillRules": {
|
|
10
|
+
"ct-playwright-patterns": {
|
|
11
|
+
"description": "Playwright E2E testing with Page Objects, fixtures, auth, and network mocking",
|
|
12
|
+
"priority": 7,
|
|
13
|
+
"triggers": {
|
|
14
|
+
"keywords": [
|
|
15
|
+
"playwright",
|
|
16
|
+
"e2e",
|
|
17
|
+
"end-to-end",
|
|
18
|
+
"page object",
|
|
19
|
+
"browser test",
|
|
20
|
+
"storageState"
|
|
21
|
+
],
|
|
22
|
+
"keywordPatterns": ["\\bplaywright\\b", "\\be2e\\b", "\\bpage\\s*object\\b"],
|
|
23
|
+
"pathPatterns": ["**/e2e/**", "**/*.spec.ts", "**/playwright.config.*"],
|
|
24
|
+
"intentPatterns": [
|
|
25
|
+
"(?:create|write|add).*(?:e2e|end.to.end|playwright).*(?:test)",
|
|
26
|
+
"(?:page object|fixture|browser test)"
|
|
27
|
+
],
|
|
28
|
+
"contentPatterns": [
|
|
29
|
+
"@playwright/test",
|
|
30
|
+
"page.goto",
|
|
31
|
+
"page.getByRole",
|
|
32
|
+
"storageState",
|
|
33
|
+
"base.extend"
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
"relatedSkills": ["ct-storybook-patterns", "ct-testing-patterns", "ct-vite-vitest-patterns"]
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -46,7 +46,13 @@
|
|
|
46
46
|
"<Switch"
|
|
47
47
|
]
|
|
48
48
|
},
|
|
49
|
-
"relatedSkills": [
|
|
49
|
+
"relatedSkills": [
|
|
50
|
+
"ct-vanilla-extract-patterns",
|
|
51
|
+
"ct-testing-patterns",
|
|
52
|
+
"ct-vite-vitest-patterns",
|
|
53
|
+
"ct-storybook-patterns",
|
|
54
|
+
"ct-playwright-patterns"
|
|
55
|
+
]
|
|
50
56
|
}
|
|
51
57
|
}
|
|
52
58
|
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ct-storybook-patterns
|
|
3
|
+
description: Storybook interaction testing, CSF 3 stories, play functions, a11y, and visual regression for SolidJS components
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Storybook Patterns
|
|
7
|
+
|
|
8
|
+
Storybook is the middle layer of the testing pyramid: component interaction testing, visual regression, and accessibility auditing in a real browser.
|
|
9
|
+
|
|
10
|
+
## SolidJS Integration
|
|
11
|
+
|
|
12
|
+
Uses community packages: `storybook-solidjs` (renderer) + `storybook-solidjs-vite` (builder).
|
|
13
|
+
|
|
14
|
+
**Critical:** Always use `createJSXDecorator` for JSX decorators. Standard decorators cause duplicate DOM elements with SolidJS.
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
import { createJSXDecorator } from "storybook-solidjs";
|
|
18
|
+
|
|
19
|
+
const ThemeDecorator = createJSXDecorator((Story) => (
|
|
20
|
+
<ThemeProvider><Story /></ThemeProvider>
|
|
21
|
+
));
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## CSF 3 Stories
|
|
25
|
+
|
|
26
|
+
Use CSF 3 (not CSF Factories -- React-only as of Storybook 10.3).
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
import type { Meta, StoryObj } from "storybook-solidjs";
|
|
30
|
+
|
|
31
|
+
const meta = {
|
|
32
|
+
title: "Components/Button",
|
|
33
|
+
component: Button,
|
|
34
|
+
tags: ["autodocs"],
|
|
35
|
+
} satisfies Meta<typeof Button>;
|
|
36
|
+
|
|
37
|
+
export default meta;
|
|
38
|
+
type Story = StoryObj<typeof meta>;
|
|
39
|
+
|
|
40
|
+
export const Primary: Story = {
|
|
41
|
+
args: { variant: "primary", children: "Click me" },
|
|
42
|
+
};
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Coverage Checklist
|
|
46
|
+
|
|
47
|
+
Every component needs stories for: all visual states (default, hover, focus, active, disabled, loading, error, empty), viewport sizes where layout differs, theme variants, edge cases (long text, missing data).
|
|
48
|
+
|
|
49
|
+
## Interaction Testing
|
|
50
|
+
|
|
51
|
+
Import everything from `@storybook/test` (instrumented versions). Never import from `@testing-library/dom` directly.
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
import { expect, fn, userEvent, within } from "@storybook/test";
|
|
55
|
+
|
|
56
|
+
export const SubmitForm: Story = {
|
|
57
|
+
args: { onSubmit: fn() },
|
|
58
|
+
play: async ({ canvasElement, args, step }) => {
|
|
59
|
+
const canvas = within(canvasElement);
|
|
60
|
+
await step("Fill form", async () => {
|
|
61
|
+
await userEvent.type(canvas.getByLabelText("Email"), "user@example.com");
|
|
62
|
+
});
|
|
63
|
+
await step("Submit", async () => {
|
|
64
|
+
await userEvent.click(canvas.getByRole("button", { name: "Sign in" }));
|
|
65
|
+
});
|
|
66
|
+
await expect(args.onSubmit).toHaveBeenCalledOnce();
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Key rules:**
|
|
72
|
+
1. Always `await` expect calls (enables Interactions panel logging).
|
|
73
|
+
2. Use `step()` to organize complex interactions.
|
|
74
|
+
3. Use `fn()` for spying (auto-restored between stories).
|
|
75
|
+
|
|
76
|
+
## Running in CI (Vitest Addon)
|
|
77
|
+
|
|
78
|
+
Use `@storybook/addon-vitest` (replaces old test-runner). No running Storybook instance needed.
|
|
79
|
+
|
|
80
|
+
Required dependencies: `@storybook/addon-vitest`, `@vitest/browser`, `@vitest/browser-playwright`, `@vitest/coverage-v8`.
|
|
81
|
+
|
|
82
|
+
Add as an inline project in your main Vite config using `test.projects`:
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
// vite.config.ts
|
|
86
|
+
import { storybookTest } from "@storybook/addon-vitest/vitest-plugin";
|
|
87
|
+
import { playwright } from "@vitest/browser-playwright";
|
|
88
|
+
|
|
89
|
+
export default defineConfig({
|
|
90
|
+
plugins: [/* ...app plugins */],
|
|
91
|
+
test: {
|
|
92
|
+
projects: [
|
|
93
|
+
{
|
|
94
|
+
extends: true,
|
|
95
|
+
test: { name: "unit", environment: "jsdom", setupFiles: ["./tests/setup.ts"] },
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
extends: true,
|
|
99
|
+
plugins: [storybookTest({ configDir: ".storybook" })],
|
|
100
|
+
test: {
|
|
101
|
+
name: "storybook",
|
|
102
|
+
browser: { enabled: true, headless: true, provider: playwright(), instances: [{ browser: "chromium" }] },
|
|
103
|
+
setupFiles: [".storybook/vitest.setup.ts"],
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Accessibility
|
|
112
|
+
|
|
113
|
+
Built on axe-core. Set globally in `.storybook/preview.ts`:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
export default {
|
|
117
|
+
parameters: { a11y: { test: "error" } }, // "error" | "todo" | "off"
|
|
118
|
+
tags: ["a11y-test"],
|
|
119
|
+
};
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## MSW for API Components
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
// .storybook/preview.ts
|
|
126
|
+
import { initialize, mswLoader } from "msw-storybook-addon";
|
|
127
|
+
initialize();
|
|
128
|
+
export default { loaders: [mswLoader] };
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Per-story handlers via `parameters.msw.handlers`. Keep success handlers in shared `mocks/handlers.ts`, override with error/edge-case at story level.
|
|
132
|
+
|
|
133
|
+
## Module Mocking (Storybook 10)
|
|
134
|
+
|
|
135
|
+
`sb.mock` for internal module replacement. Register in `.storybook/preview.ts` only:
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
import { sb } from "@storybook/test";
|
|
139
|
+
sb.mock("../src/api/client", () => ({ fetchUser: async () => ({ name: "Mock" }) }));
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
MSW for network-level; `sb.mock` for internal modules.
|
|
143
|
+
|
|
144
|
+
## Portable Stories
|
|
145
|
+
|
|
146
|
+
Reuse stories in Vitest with `composeStories`:
|
|
147
|
+
|
|
148
|
+
```tsx
|
|
149
|
+
import { composeStories } from "storybook-solidjs";
|
|
150
|
+
import * as stories from "./Button.stories";
|
|
151
|
+
const { Primary } = composeStories(stories);
|
|
152
|
+
|
|
153
|
+
test("renders primary", () => {
|
|
154
|
+
const { getByRole } = render(() => <Primary />);
|
|
155
|
+
expect(getByRole("button")).toBeInTheDocument();
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Anti-Patterns
|
|
160
|
+
|
|
161
|
+
1. **Destructuring SolidJS props in stories** -- Pass props via `args`; use `createJSXDecorator` for decorators.
|
|
162
|
+
2. **Importing from `@testing-library/dom`** -- Use `@storybook/test` for instrumented versions.
|
|
163
|
+
3. **Not awaiting `expect()` in play functions** -- Always `await expect(...)`.
|
|
164
|
+
4. **Skipping error/edge-case stories** -- Always include loading, error, empty, boundary states.
|
|
165
|
+
5. **Using `@storybook/test-runner`** -- Use `@storybook/addon-vitest` instead.
|
|
166
|
+
6. **Registering `sb.mock` in story files** -- Register in `.storybook/preview.ts` only.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ct-storybook-patterns",
|
|
3
|
+
"description": "Storybook interaction testing, CSF 3 stories, and visual regression for SolidJS",
|
|
4
|
+
"defaultMappings": {
|
|
5
|
+
".storybook": "ct-storybook-patterns",
|
|
6
|
+
"src/**/*.stories.tsx": "ct-storybook-patterns"
|
|
7
|
+
},
|
|
8
|
+
"fileExtensions": ["stories.tsx", "stories.ts"],
|
|
9
|
+
"skillRules": {
|
|
10
|
+
"ct-storybook-patterns": {
|
|
11
|
+
"description": "Storybook CSF 3 stories, play functions, a11y testing, and visual regression",
|
|
12
|
+
"priority": 7,
|
|
13
|
+
"triggers": {
|
|
14
|
+
"keywords": [
|
|
15
|
+
"storybook",
|
|
16
|
+
"story",
|
|
17
|
+
"stories",
|
|
18
|
+
"csf",
|
|
19
|
+
"play function",
|
|
20
|
+
"interaction test",
|
|
21
|
+
"visual regression",
|
|
22
|
+
"chromatic"
|
|
23
|
+
],
|
|
24
|
+
"keywordPatterns": ["\\bstorybook\\b", "\\bstories\\b", "\\bplay\\s+function\\b"],
|
|
25
|
+
"pathPatterns": ["**/*.stories.tsx", "**/*.stories.ts", "**/.storybook/**"],
|
|
26
|
+
"intentPatterns": [
|
|
27
|
+
"(?:create|write|add).*(?:story|stories|storybook)",
|
|
28
|
+
"(?:interaction|visual|a11y).*(?:test|testing)"
|
|
29
|
+
],
|
|
30
|
+
"contentPatterns": [
|
|
31
|
+
"satisfies Meta",
|
|
32
|
+
"StoryObj",
|
|
33
|
+
"@storybook/test",
|
|
34
|
+
"createJSXDecorator",
|
|
35
|
+
"storybook-solidjs"
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
"relatedSkills": [
|
|
39
|
+
"ct-solidjs-patterns",
|
|
40
|
+
"ct-testing-patterns",
|
|
41
|
+
"ct-vite-vitest-patterns",
|
|
42
|
+
"ct-playwright-patterns"
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|