Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/storybook-11.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'kahraman': minor
---

Support Storybook 11 and its prereleases while retaining Storybook 9 and 10 compatibility. Add an opt-in default addon factory for CSF Next (`addons: [kahraman()]`) with typed diagnostics parameters. Existing named exports and the `kahraman/preview` annotation remain available.

Raise the minimum Node.js version to 22.12 to match Storybook 11.
35 changes: 34 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,17 @@ concurrency:
jobs:
check:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [22, 24]
steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4

- uses: actions/setup-node@v4
with:
node-version: 24
node-version: ${{ matrix.node-version }}
cache: pnpm

- name: Install
Expand Down Expand Up @@ -57,3 +60,33 @@ jobs:
run: |
pnpm run registry:build
git diff --exit-code registry.json

storybook-compatibility:
runs-on: ubuntu-latest
strategy:
matrix:
storybook-version: ['9.1.20', '10.6.1']
steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4

- uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm

- name: Install
run: pnpm install --frozen-lockfile

- name: Use Storybook ${{ matrix.storybook-version }}
run: pnpm add --save-dev storybook@${{ matrix.storybook-version }}

- name: Typecheck
run: pnpm run typecheck

- name: Test
run: pnpm run test

- name: Build
run: pnpm run build
39 changes: 33 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ ESM only and is renderer-agnostic: it reads only `canvasElement` and `userEvent`
from the story context, so it works with the React, Vue, and Svelte renderers
alike.

Supports Storybook 9, 10, and 11, including the Storybook 11 prerelease. Node.js
22.12 or newer is required.

### Or vendor the source with jsrepo

Prefer to own the code? `kahraman` is also published as a
Expand Down Expand Up @@ -278,7 +281,24 @@ readable:
Testing Library's `getByRole` misses dump a listing of _every_ accessible role
on the page — hundreds of lines on a full app mount. The `kahraman/preview`
annotation filters that to just the queried role's near-misses and caps the DOM
dump. Add it to your Storybook preview:
dump. For CSF Next, register the addon factory in your Storybook preview:

```ts
// .storybook/preview.ts
import { definePreview } from '@storybook/react-vite' // use your framework
import kahraman from 'kahraman'

export default definePreview({
addons: [kahraman()],
// ...your other preview config
})
```

Calling `kahraman()` opts into diagnostics and types `parameters.kahraman`.
The named actor and locator exports work without registering the addon.

For an object-style preview, the existing `kahraman/preview` annotation still
works:

```ts
// .storybook/preview.ts
Expand Down Expand Up @@ -320,15 +340,22 @@ package:

## Testing the pure helpers

`kahraman`'s own test suite unit-tests only the **Storybook-free** logic:
`kahraman`'s own test suite unit-tests the **Storybook-free** logic:
locator label construction, step-label formatting, role-listing filtering,
message capping, and stack-frame retargeting. The full actor behavior requires a
real Storybook browser context and is exercised by consumers' stories — that
boundary is intentional. The DOM-touching modules (`actor.ts`, `preview.ts`) run
only in the browser.
message capping, and stack-frame retargeting. It also checks CSF Next addon
composition and diagnostics configuration against the real Storybook APIs. CI
checks compatibility with Storybook 9, 10, and 11. The full actor behavior
requires a real Storybook browser context and is exercised by consumers'
stories. That boundary is intentional. The DOM-touching code in `actor.ts` and
`preview.ts` runs in the browser; the configuration tests do not render a story
or query the DOM.

## Development

Development uses Storybook 11 alpha, pinned in `pnpm-lock.yaml`. There is no
local Storybook instance in this repository. Use Node.js 22.18+ or 24.11+ to run
the development toolchain, which has a higher minimum than the published package.

This repo uses the [Vite+](https://viteplus.dev/) / Oxc toolchain. The plain
`pnpm` commands below remain the contributor path. If you use [mise](https://mise.jdx.dev/),
the common workflows are also available as `mise run prepare`, `mise run dev`,
Expand Down
1 change: 1 addition & 0 deletions jsrepo.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export default defineConfig({
{ path: './src/steps.ts' },
{ path: './src/invariant.ts' },
{ path: './src/context.ts' },
{ path: './test/addon.test.ts', role: 'test' },
{ path: './test/loc.test.ts', role: 'test' },
{ path: './test/steps.test.ts', role: 'test' },
{ path: './examples/pageActor.ts', role: 'example' },
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,17 +82,17 @@
"fallow": "^3.5.1",
"jsrepo": "^3.8.1",
"publint": "^0.3.21",
"storybook": "^10.5.0",
"storybook": "^11.0.0-alpha.4",
"tsdown": "^0.22.7",
"typescript": "^7.0.2",
"vite-plus": "^0.2.4",
"vitest": "4.1.10"
},
"peerDependencies": {
"storybook": ">=9.0.0"
"storybook": "^9.0.0 || ^10.0.0 || ^11.0.0-0"
},
"engines": {
"node": ">=20.11.0"
"node": ">=22.12.0"
},
"packageManager": "pnpm@11.13.1"
}
15 changes: 8 additions & 7 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

50 changes: 49 additions & 1 deletion registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
"description": "createActor() and the role/text/heading/button/link locator DSL, with step-trace diagnostics and call-site stack retargeting.",
"type": "lib",
"add": "when-added",
"registryDependencies": [],
"registryDependencies": [
"preview"
],
"dependencies": [],
"devDependencies": [],
"files": [
Expand All @@ -30,6 +32,15 @@
"path": "index.ts",
"relativePath": "src/index.ts",
"_imports_": [
{
"import": "./preview",
"item": "preview",
"file": {
"type": "lib",
"path": "preview.ts"
},
"meta": {}
},
{
"import": "./actor",
"item": "actor",
Expand Down Expand Up @@ -168,6 +179,43 @@
"dependencies": [],
"devDependencies": []
},
{
"type": "lib",
"role": "test",
"path": "addon.test.ts",
"relativePath": "test/addon.test.ts",
"_imports_": [
{
"import": "../src/index",
"item": "actor",
"file": {
"type": "lib",
"path": "index.ts"
},
"meta": {}
},
{
"import": "../src/preview",
"item": "preview",
"file": {
"type": "lib",
"path": "preview.ts"
},
"meta": {}
}
],
"registryDependencies": [
"preview"
],
"dependencies": [],
"devDependencies": [
{
"ecosystem": "js",
"name": "vitest",
"version": "4.1.10"
}
]
},
{
"type": "lib",
"role": "test",
Expand Down
7 changes: 7 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
import { definePreviewAddon } from 'storybook/internal/csf'
import type { KahramanParameters } from './preview'
import addonAnnotations from './preview'

/** Register the opt-in diagnostics annotation with a CSF Next preview. */
export default () => definePreviewAddon<{ parameters: KahramanParameters }>(addonAnnotations)

export { createActor } from './actor'
export type { Actor, ActorOptions, BaseActor, HopeThat } from './actor'

Expand Down
58 changes: 58 additions & 0 deletions test/addon.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { definePreview } from 'storybook/internal/csf'
import type { InferTypes } from 'storybook/internal/csf'
import { configure, getConfig } from 'storybook/test'
import { afterEach, beforeEach, expect, expectTypeOf, test } from 'vitest'
import kahraman, { button, createActor, heading, invariant, link, role, text } from '../src/index'
import annotations from '../src/preview'
import type { KahramanParameters } from '../src/preview'

const customElementError = (message: string | null) => new Error(message ?? 'Custom error')

let originalConfig: ReturnType<typeof getConfig>

beforeEach(() => {
originalConfig = getConfig()
})

afterEach(() => {
configure(originalConfig)
})

test('registers the existing diagnostics hook with a CSF Next preview', () => {
const addon = kahraman()
const preview = definePreview({ addons: [addon] })

expect(addon.beforeEach).toBe(annotations.beforeEach)
expect(preview.composed.beforeEach).toContain(annotations.beforeEach)
expectTypeOf<InferTypes<[typeof addon]>['parameters']>().toEqualTypeOf<KahramanParameters>()
})

test('creating the addon leaves diagnostics opt-in', () => {
const originalHandler = getConfig().getElementError

kahraman()

expect(getConfig().getElementError).toBe(originalHandler)
})

test('the registered hook uses per-story diagnostics and resets to defaults', () => {
const addon = kahraman()

expect(addon.beforeEach).toBe(annotations.beforeEach)
annotations.beforeEach({ parameters: { kahraman: { getElementError: customElementError } } })
expect(getConfig().getElementError).toBe(customElementError)

annotations.beforeEach({ parameters: {} })
expect(getConfig().getElementError).not.toBe(customElementError)
})

test('the legacy annotation and named actor exports remain available', () => {
expect(annotations.beforeEach).toBeTypeOf('function')
expect(createActor()).toHaveProperty('init')
expect(button('Save').__label).toBe('button "Save"')
expect(heading('Dashboard').__label).toBe('heading "Dashboard"')
expect(link('Home').__label).toBe('link "Home"')
expect(role('textbox', 'Email').__label).toBe('textbox "Email"')
expect(text('Welcome').__label).toBe('text "Welcome"')
expect(() => invariant(true, 'passes')).not.toThrow()
})
5 changes: 2 additions & 3 deletions vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ import { defineConfig } from 'vite-plus'
// `lint` block — Vite+ keeps all of this in vite.config.ts rather than in
// separate .oxfmtrc.json / .oxlintrc.json files.
//
// The unit tests cover the PURE, Storybook-free logic only: locator label
// construction, step-label formatting, role-listing filtering, message capping,
// and stack-frame retargeting. Full actor behaviour needs a Storybook browser
// The unit tests cover pure helpers plus CSF Next addon composition and
// diagnostics configuration. Full actor behaviour needs a Storybook browser
// context and is out of scope here — see README (Testing the pure helpers).
export default defineConfig({
fmt: {
Expand Down
Loading