storybook-addon-vis 1.0.0 → 1.0.2
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/dist/manager.js +1 -1
- package/dist/manager.js.map +1 -1
- package/package.json +5 -5
- package/readme.md +2 -2
- package/src/advance_setup.mdx +133 -0
- package/src/basic_setup.mdx +121 -0
- package/src/changelog.mdx +7 -0
- package/src/components/vis_panel.tsx +13 -3
- package/src/installation.mdx +27 -0
- package/src/manager.tsx +22 -9
- package/src/overview.mdx +18 -0
package/dist/manager.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {memo,useState,useEffect}from'react';import {addons,types}from'storybook/internal/manager-api';import {AddonPanel,ScrollArea,Placeholder}from'storybook/internal/components';import {styled}from'storybook/internal/theming';import {SNAPSHOT_ROOT_DIR}from'vitest-plugin-vis/client-api';import {jsx,jsxs,Fragment}from'react/jsx-runtime';var
|
|
1
|
+
import {memo,useState,useEffect}from'react';import {addons,types}from'storybook/internal/manager-api';import {RefreshIcon}from'@storybook/icons';import {AddonPanel,ScrollArea,IconButton,Placeholder}from'storybook/internal/components';import {styled}from'storybook/internal/theming';import {SNAPSHOT_ROOT_DIR}from'vitest-plugin-vis/client-api';import {jsx,jsxs,Fragment}from'react/jsx-runtime';var h=memo(function({active:s,snapshotResults:r=[],onRefresh:S}){let a=r.reduce((n,e)=>{let R=`(${e.snapshotRootDir.slice(SNAPSHOT_ROOT_DIR.length+1)}) ${e.fileName}`;return n[R]=[...n[R]??[],e],n},{});return jsx(AddonPanel,{active:s,children:r.length>0?jsx(ScrollArea,{vertical:true,children:Object.entries(a).map(([n,e])=>jsx("div",{children:e.some(i=>i.type==="diff")?jsxs(Fragment,{children:[jsx(g,{failed:true,children:n}),e.map(i=>jsx("img",{src:`data:image/png;base64,${i.base64}`,alt:i.fileName},i.filePath))]}):jsxs(Fragment,{children:[jsxs(g,{children:[jsx("span",{children:n}),jsx(IconButton,{onClick:()=>S(),children:jsx(RefreshIcon,{})})]}),jsx("img",{src:`data:image/png;base64,${e[0].base64}`,alt:e[0].fileName},e[0].filePath)]})},n))}):jsx(Placeholder,{children:"There is no snapshots for this story"})})}),g=styled.div(({theme:t,failed:s})=>({display:"flex",alignItems:"center",gap:"0.5rem",paddingBlock:"0.5rem",paddingInline:"0.2rem",backgroundColor:s?t.background.negative:t.background.positive,color:t.barTextColor}));var p="storybook-addon-vis",E=`${p}/vis_panel`;var O="IMAGE_SNAPSHOT_RESULTS_REQUEST",d="IMAGE_SNAPSHOT_RESULTS_RESPONSE";function l({name:t,importPath:s}){return {type:O,name:t,importPath:s}}addons.register(p,t=>{addons.add(E,{type:types.PANEL,title:"Vis",match:({tabId:s,viewMode:r})=>!s&&r==="story",render({active:s}){if(!s)return null;let[r,S]=useState([]),a=t.getCurrentStoryData();return useEffect(()=>{let n=[t.on(p,e=>{e.name===a.name&&e.importPath===a.importPath&&e.type===d&&S(e.results);})];return t.emit(p,l(a)),()=>n.forEach(e=>e())},[a]),jsx(h,{active:s,snapshotResults:r,onRefresh:()=>{t.emit(p,l(a));}})}});});//# sourceMappingURL=manager.js.map
|
|
2
2
|
//# sourceMappingURL=manager.js.map
|
package/dist/manager.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/vis_panel.tsx","../src/shared/contants.ts","../src/shared/events.ts","../src/manager.tsx"],"names":["VisPanel","memo","active","snapshotResults","groupedResults","acc","result","key","SNAPSHOT_ROOT_DIR","jsx","AddonPanel","ScrollArea","results","jsxs","Fragment","SnapshotRow","Placeholder","styled","theme","failed","NAME","VIS_PANEL_ID","IMAGE_SNAPSHOT_RESULTS_REQUEST","IMAGE_SNAPSHOT_RESULTS_RESPONSE","requestImageSnapshotResults","name","importPath","addons","api","types","tabId","viewMode","setSnapshotResults","useState","storyData","useEffect","
|
|
1
|
+
{"version":3,"sources":["../src/components/vis_panel.tsx","../src/shared/contants.ts","../src/shared/events.ts","../src/manager.tsx"],"names":["VisPanel","memo","active","snapshotResults","onRefresh","groupedResults","acc","result","key","SNAPSHOT_ROOT_DIR","jsx","AddonPanel","ScrollArea","results","jsxs","Fragment","SnapshotRow","IconButton","RefreshIcon","Placeholder","styled","theme","failed","NAME","VIS_PANEL_ID","IMAGE_SNAPSHOT_RESULTS_REQUEST","IMAGE_SNAPSHOT_RESULTS_RESPONSE","requestImageSnapshotResults","name","importPath","addons","api","types","tabId","viewMode","setSnapshotResults","useState","storyData","useEffect","disposes","event","dispose"],"mappings":"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","file":"manager.js","sourcesContent":["import { RefreshIcon } from '@storybook/icons'\nimport { memo } from 'react'\nimport { AddonPanel, IconButton, Placeholder, ScrollArea } from 'storybook/internal/components'\nimport { styled } from 'storybook/internal/theming'\nimport { SNAPSHOT_ROOT_DIR } from 'vitest-plugin-vis/client-api'\nimport type { ImageSnapshotResults } from '../shared/events.ts'\n\ninterface PanelProps {\n\tactive: boolean\n\tsnapshotResults: ImageSnapshotResults[]\n\tonRefresh: () => void\n}\n\nexport const VisPanel = memo(function VisPanel({ active, snapshotResults = [], onRefresh }: PanelProps) {\n\tconst groupedResults = snapshotResults.reduce(\n\t\t(acc, result) => {\n\t\t\tconst suiteName = result.snapshotRootDir.slice(SNAPSHOT_ROOT_DIR.length + 1)\n\t\t\tconst key = `(${suiteName}) ${result.fileName}`\n\t\t\tacc[key] = [...(acc[key] ?? []), result]\n\t\t\treturn acc\n\t\t},\n\t\t{} as Record<string, ImageSnapshotResults[]>,\n\t)\n\treturn (\n\t\t<AddonPanel active={active}>\n\t\t\t{snapshotResults.length > 0 ? (\n\t\t\t\t<ScrollArea vertical>\n\t\t\t\t\t{Object.entries(groupedResults).map(([key, results]) => (\n\t\t\t\t\t\t<div key={key}>\n\t\t\t\t\t\t\t{results.some((result) => result.type === 'diff') ? (\n\t\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t\t<SnapshotRow failed>{key}</SnapshotRow>\n\t\t\t\t\t\t\t\t\t{results.map((result) => (\n\t\t\t\t\t\t\t\t\t\t<img key={result.filePath} src={`data:image/png;base64,${result.base64}`} alt={result.fileName} />\n\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t\t<SnapshotRow>\n\t\t\t\t\t\t\t\t\t\t<span>{key}</span>\n\t\t\t\t\t\t\t\t\t\t<IconButton onClick={() => onRefresh()}>\n\t\t\t\t\t\t\t\t\t\t\t<RefreshIcon />\n\t\t\t\t\t\t\t\t\t\t</IconButton>\n\t\t\t\t\t\t\t\t\t</SnapshotRow>\n\t\t\t\t\t\t\t\t\t<img\n\t\t\t\t\t\t\t\t\t\tkey={results[0].filePath}\n\t\t\t\t\t\t\t\t\t\tsrc={`data:image/png;base64,${results[0].base64}`}\n\t\t\t\t\t\t\t\t\t\talt={results[0].fileName}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t))}\n\t\t\t\t</ScrollArea>\n\t\t\t) : (\n\t\t\t\t<Placeholder>There is no snapshots for this story</Placeholder>\n\t\t\t)}\n\t\t</AddonPanel>\n\t)\n})\n\nconst SnapshotRow = styled.div<{ failed?: boolean }>(({ theme, failed }) => ({\n\tdisplay: 'flex',\n\talignItems: 'center',\n\tgap: '0.5rem',\n\tpaddingBlock: '0.5rem',\n\tpaddingInline: '0.2rem',\n\tbackgroundColor: failed ? theme.background.negative : theme.background.positive,\n\tcolor: theme.barTextColor,\n}))\n","export const NAME = 'storybook-addon-vis'\nexport const VIS_PANEL_ID = `${NAME}/vis_panel`\n","export type VisEvent = ImageSnapshotResultsRequest | ImageSnapshotResultsResponse\n\nexport const IMAGE_SNAPSHOT_RESULTS_REQUEST = 'IMAGE_SNAPSHOT_RESULTS_REQUEST'\nexport const IMAGE_SNAPSHOT_RESULTS_RESPONSE = 'IMAGE_SNAPSHOT_RESULTS_RESPONSE'\n\nexport function requestImageSnapshotResults({ name, importPath }: { name: string; importPath: string }) {\n\treturn {\n\t\ttype: IMAGE_SNAPSHOT_RESULTS_REQUEST,\n\t\tname,\n\t\timportPath,\n\t}\n}\n\nexport type ImageSnapshotResultsRequest = {\n\ttype: typeof IMAGE_SNAPSHOT_RESULTS_REQUEST\n\tname: string\n\timportPath: string\n}\n\nexport type ImageSnapshotResultsResponse = {\n\ttype: typeof IMAGE_SNAPSHOT_RESULTS_RESPONSE\n\tname: string\n\timportPath: string\n\tresults: ImageSnapshotResults[]\n}\n\nexport type ImageSnapshotResults = {\n\tfilePath: string\n\tfileName: string\n\tsnapshotRootDir: string\n\ttype: 'baseline' | 'diff' | 'result'\n\tbase64: string\n}\n","import { useEffect, useState } from 'react'\nimport { addons, types } from 'storybook/internal/manager-api'\nimport { VisPanel } from './components/vis_panel.tsx'\nimport { NAME, VIS_PANEL_ID } from './shared/contants.ts'\nimport {\n\tIMAGE_SNAPSHOT_RESULTS_RESPONSE,\n\trequestImageSnapshotResults,\n\ttype ImageSnapshotResults,\n\ttype VisEvent,\n} from './shared/events.ts'\n\n// Register the addon\naddons.register(NAME, (api) => {\n\t// Register the tool\n\taddons.add(VIS_PANEL_ID, {\n\t\ttype: types.PANEL,\n\t\ttitle: 'Vis',\n\t\tmatch: ({ tabId, viewMode }) => !tabId && viewMode === 'story',\n\t\trender({ active }) {\n\t\t\tif (!active) return null\n\t\t\tconst [snapshotResults, setSnapshotResults] = useState<ImageSnapshotResults[]>([])\n\n\t\t\tconst storyData = api.getCurrentStoryData()\n\n\t\t\tuseEffect(() => {\n\t\t\t\tconst disposes = [\n\t\t\t\t\tapi.on(NAME, (event: VisEvent) => {\n\t\t\t\t\t\tif (event.name !== storyData.name) return\n\t\t\t\t\t\tif (event.importPath !== storyData.importPath) return\n\n\t\t\t\t\t\tif (event.type === IMAGE_SNAPSHOT_RESULTS_RESPONSE) {\n\t\t\t\t\t\t\tsetSnapshotResults(event.results)\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t\t// api.on('UNIVERSAL_STORE:storybook/test', (event) => {\n\t\t\t\t\t// \tconsole.log('storybook/test', event)\n\t\t\t\t\t// }),\n\t\t\t\t]\n\t\t\t\tapi.emit(NAME, requestImageSnapshotResults(storyData))\n\t\t\t\treturn () => disposes.forEach((dispose) => dispose())\n\t\t\t}, [storyData])\n\t\t\treturn (\n\t\t\t\t<VisPanel\n\t\t\t\t\tactive={active}\n\t\t\t\t\tsnapshotResults={snapshotResults}\n\t\t\t\t\tonRefresh={() => {\n\t\t\t\t\t\tapi.emit(NAME, requestImageSnapshotResults(storyData))\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t)\n\t\t},\n\t})\n})\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "storybook-addon-vis",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Storybook Vitest visual testing addon",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"image-snapshot",
|
|
@@ -61,15 +61,16 @@
|
|
|
61
61
|
"!**/*.{spec,test,unit,accept,integrate,system,perf,stress,stories,study}.*"
|
|
62
62
|
],
|
|
63
63
|
"dependencies": {
|
|
64
|
+
"@storybook/icons": "^1.4.0",
|
|
64
65
|
"glob": "^11.0.2",
|
|
65
66
|
"is-ci": "^4.1.0",
|
|
66
67
|
"memoize": "^10.1.0",
|
|
67
68
|
"pathe": "^2.0.3",
|
|
68
69
|
"type-plus": "8.0.0-beta.7",
|
|
69
|
-
"vitest-plugin-vis": "^3.0.
|
|
70
|
+
"vitest-plugin-vis": "^3.0.1"
|
|
70
71
|
},
|
|
71
72
|
"devDependencies": {
|
|
72
|
-
"@repobuddy/storybook": "^0.
|
|
73
|
+
"@repobuddy/storybook": "^0.13.0",
|
|
73
74
|
"@storybook/addon-essentials": "^8.6.14",
|
|
74
75
|
"@storybook/addon-links": "^8.6.14",
|
|
75
76
|
"@storybook/addon-storysource": "^8.6.14",
|
|
@@ -107,7 +108,6 @@
|
|
|
107
108
|
"zx": "^8.0.0"
|
|
108
109
|
},
|
|
109
110
|
"peerDependencies": {
|
|
110
|
-
"@types/pixelmatch": "^5.2.6",
|
|
111
111
|
"@vitest/browser": "^2 || ^3",
|
|
112
112
|
"storybook": "^8.3.0",
|
|
113
113
|
"vitest": "^2 || ^3"
|
|
@@ -145,7 +145,7 @@
|
|
|
145
145
|
},
|
|
146
146
|
"scripts": {
|
|
147
147
|
"build": "tsup --tsconfig tsconfig.tsup.json",
|
|
148
|
-
"build:sb": "storybook build",
|
|
148
|
+
"build:sb": "cross-env NODE_ENV=production storybook build",
|
|
149
149
|
"clean": "rimraf coverage dist esm --glob *.tsbuildinfo vitest.*.timestamp*",
|
|
150
150
|
"clean:vis:local": "rimraf __vis__/local",
|
|
151
151
|
"cov": "vitest run --coverage",
|
package/readme.md
CHANGED
|
@@ -229,7 +229,7 @@ By default, [`storybook-addon-vis`][storybook-addon-vis] removes that folder to
|
|
|
229
229
|
|
|
230
230
|
If you place your test files in multiple folders,
|
|
231
231
|
such as in both `tests` and `src` folders,
|
|
232
|
-
you can use `
|
|
232
|
+
you can use `snapshotSubpath` to customize the snapshot sub-path to avoid conflicts.
|
|
233
233
|
|
|
234
234
|
```ts
|
|
235
235
|
// vitest.config.ts
|
|
@@ -240,7 +240,7 @@ export default defineConfig({
|
|
|
240
240
|
plugins: [
|
|
241
241
|
storybookVis({
|
|
242
242
|
// keep the folder structure
|
|
243
|
-
|
|
243
|
+
snapshotSubpath: (subpath) => subpath
|
|
244
244
|
})
|
|
245
245
|
],
|
|
246
246
|
// ...
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { Meta } from "@storybook/blocks";
|
|
2
|
+
|
|
3
|
+
<Meta title="Advance Setup" />
|
|
4
|
+
|
|
5
|
+
# Advance Setup
|
|
6
|
+
|
|
7
|
+
[storybook-addon-vis][storybook-addon-vis] is very flexible and provides a lot of options to customize the visual testing.
|
|
8
|
+
|
|
9
|
+
It is built on top of [vitest-plugin-vis][vitest-plugin-vis],
|
|
10
|
+
so most of the options are the same as [vitest-plugin-vis][vitest-plugin-vis].
|
|
11
|
+
You can check its documentation for those options.
|
|
12
|
+
|
|
13
|
+
This document will focus on things that are specific to [storybook-addon-vis][storybook-addon-vis].
|
|
14
|
+
|
|
15
|
+
## Removal of presets
|
|
16
|
+
|
|
17
|
+
[vitest-plugin-vis][vitest-plugin-vis] provides `auto` or `manual` presets,
|
|
18
|
+
but [storybook-addon-vis][storybook-addon-vis] does not provide any presets.
|
|
19
|
+
|
|
20
|
+
This is because those presets setup the `vitest.setup.ts` for you,
|
|
21
|
+
but [storybook-addon-vis][storybook-addon-vis] needs to update the `vitest.setup.ts` file you already have from [Storybook Test addon][storybook-test-addon],
|
|
22
|
+
so those presets does not work and we need to customize the file directly.
|
|
23
|
+
|
|
24
|
+
## Disable Vitest global API
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
// vitest.config.ts
|
|
28
|
+
|
|
29
|
+
export default defineConfig({
|
|
30
|
+
test: {
|
|
31
|
+
globals: false,
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
We recommend to set `globals` to `false` (which is the default).
|
|
37
|
+
Setting `globals` to `true` actually works ok during test.
|
|
38
|
+
But they don't exist in the story files:
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
// some.stories.tsx
|
|
42
|
+
export const Story = {
|
|
43
|
+
async play() {
|
|
44
|
+
// does not work
|
|
45
|
+
expect(true).toBeTruthy()
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
This is obvious because the story files are executed on the browser.
|
|
51
|
+
In fact, you need to import the functions from `@storybook/test` instead:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
// some.test.ts
|
|
55
|
+
import { expect } from 'vitest'
|
|
56
|
+
|
|
57
|
+
// some.stories.ts
|
|
58
|
+
import { expect } from '@storybook/test'
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Setting `globals: true` (and adding `types: ["vitest/globals"]` in your `tsconfig.json`)
|
|
62
|
+
causes inconsistency and confuses both the editor and you.
|
|
63
|
+
|
|
64
|
+
## Disable auto snapshot for tests
|
|
65
|
+
|
|
66
|
+
[storybook-addon-vis][storybook-addon-vis] uses the same auto snapshot feature as [vitest-plugin-vis][vitest-plugin-vis].
|
|
67
|
+
|
|
68
|
+
That means if you set `auto: true` in your `vitest.setup.ts`,
|
|
69
|
+
your render tests will be automatically snapshot tested:
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
// vitest.setup.ts
|
|
73
|
+
|
|
74
|
+
vis.setup({ auto: true })
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
When you use it to take snapshots for multiple themes, e.g. light and dark,
|
|
78
|
+
the same will occurs:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
vis.setup({
|
|
82
|
+
auto: {
|
|
83
|
+
async light() { document.body.classList.remove('dark') },
|
|
84
|
+
async dark() { document.body.classList.add('dark') },
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
If this is not what you want, you can disable the auto snapshot for tests by checking for the `snapshot` tag:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
vis.setup({
|
|
93
|
+
auto: {
|
|
94
|
+
async light({ tags }) {
|
|
95
|
+
if (!tags.includes('snapshot')) return false
|
|
96
|
+
document.body.classList.remove('dark')
|
|
97
|
+
},
|
|
98
|
+
async dark({ tags }) {
|
|
99
|
+
if (!tags.includes('snapshot')) return false
|
|
100
|
+
document.body.classList.add('dark')
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Taking specific snapshots
|
|
107
|
+
|
|
108
|
+
Say you want to take snapshots for multiple themes,
|
|
109
|
+
but for certain stories, you want to take skip snapshots for a specific theme.
|
|
110
|
+
|
|
111
|
+
You can do this by using a custom tag:
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
// some.stories.tsx
|
|
115
|
+
export const Story = {
|
|
116
|
+
tags: ['snapshot', 'skip-dark']
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
// vitest.setup.ts
|
|
122
|
+
|
|
123
|
+
vis.setup({
|
|
124
|
+
auto: {
|
|
125
|
+
async dark({ tags }) {
|
|
126
|
+
if (tags.includes('skip-dark')) return false
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
[storybook-addon-vis]: https://www.npmjs.com/package/storybook-addon-vis
|
|
133
|
+
[vitest-plugin-vis]: https://www.npmjs.com/package/vitest-plugin-vis
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { Meta } from "@storybook/blocks";
|
|
2
|
+
|
|
3
|
+
<Meta title="Basic Setup" />
|
|
4
|
+
|
|
5
|
+
# Basic Setup
|
|
6
|
+
|
|
7
|
+
The basic setup is pretty straightforward.
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## Vitest
|
|
11
|
+
|
|
12
|
+
For [Vitest], you need to add the following to your `vitest.config.ts`:
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
// vitest.config.ts
|
|
16
|
+
|
|
17
|
+
import { storybookTest } from '@storybook/experimental-addon-test/vitest-plugin'
|
|
18
|
+
import { storybookVis } from 'storybook-addon-vis/vitest-plugin'
|
|
19
|
+
import { defineConfig } from 'vitest/config'
|
|
20
|
+
|
|
21
|
+
export default defineConfig({
|
|
22
|
+
plugins: [
|
|
23
|
+
storybookTest(),
|
|
24
|
+
storybookVis() // ADD THIS
|
|
25
|
+
],
|
|
26
|
+
test: {
|
|
27
|
+
// vitest v2
|
|
28
|
+
browser: {
|
|
29
|
+
enabled: true,
|
|
30
|
+
headless: true,
|
|
31
|
+
provider: 'playwright',
|
|
32
|
+
name: 'chromium',
|
|
33
|
+
},
|
|
34
|
+
// vitest v3
|
|
35
|
+
browser: {
|
|
36
|
+
enabled: true,
|
|
37
|
+
headless: true,
|
|
38
|
+
provider: 'playwright',
|
|
39
|
+
instances: [
|
|
40
|
+
{ browser: 'chromium' }
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
// recommend to set to false
|
|
44
|
+
globals: false,
|
|
45
|
+
// Needed by both Storybook Test Addon and Storybook Visual Testing
|
|
46
|
+
setupFiles: ['./vitest.setup.ts'],
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
And your `vitest.setup.ts`:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
// vitest.setup.ts
|
|
55
|
+
|
|
56
|
+
import { vis, visAnnotations } from 'storybook-addon-vis/vitest-setup'
|
|
57
|
+
import { beforeAll } from 'vitest'
|
|
58
|
+
import * as projectAnnotations from './preview.ts'
|
|
59
|
+
|
|
60
|
+
const project = setProjectAnnotations([
|
|
61
|
+
projectAnnotations,
|
|
62
|
+
visAnnotations // ADD THIS
|
|
63
|
+
])
|
|
64
|
+
|
|
65
|
+
beforeAll(project.beforeAll)
|
|
66
|
+
|
|
67
|
+
// ADD THIS to setup visual testing
|
|
68
|
+
vis.setup()
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Storybook
|
|
72
|
+
|
|
73
|
+
For [Storybook], update your `.storybook/main.ts` to add the addon:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
// .storybook/main.ts
|
|
77
|
+
|
|
78
|
+
const config: StorybookConfig = {
|
|
79
|
+
// ...
|
|
80
|
+
addons: [
|
|
81
|
+
// ...
|
|
82
|
+
'@storybook/experimental-addon-test',
|
|
83
|
+
'storybook-addon-vis' // ADD THIS
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
With this setup, [storybook-addon-vis] is configured to do the following:
|
|
89
|
+
|
|
90
|
+
- Take snapshots for every story with the `snapshot` tag.
|
|
91
|
+
- Use `pixelmatch` as the comparison algorithm.
|
|
92
|
+
- Compare snapshots with a failure threshold of `0 pixels`.
|
|
93
|
+
- Timeout for image comparison is set to `30000 ms`.
|
|
94
|
+
- Local (non-CI) image snapshots are saved in the `<root>/__vis__/local` directory.
|
|
95
|
+
- CI image snapshots are saved in the `<root>/__vis__/<process.platform>` directory.
|
|
96
|
+
- Baseline images are saved in the `<root>/__vis__/*/__baselines__` directory.
|
|
97
|
+
- Current test images are saved in the `<root>/__vis__/*/__results__` directory.
|
|
98
|
+
- Diff images (if any) are saved in the `<root>/__vis__/*/__diffs__` directory.
|
|
99
|
+
- You can review the baseline and diff images in addon `Vis` panel.
|
|
100
|
+
|
|
101
|
+
## Git (or other version control system)
|
|
102
|
+
|
|
103
|
+
Since [storybook-addon-vis][storybook-addon-vis] will save snapshot on your file system,
|
|
104
|
+
you need to tell your v2ersion control system to ignore the snapshot directory.
|
|
105
|
+
|
|
106
|
+
With the basic setup, add this to your `.gitignore` (or your version control system's ignore file):
|
|
107
|
+
|
|
108
|
+
```ini
|
|
109
|
+
# snapshot images
|
|
110
|
+
__vis__/local
|
|
111
|
+
__vis__/**/__results__
|
|
112
|
+
__vis__/**/__diffs__
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Note that the `__vis__/*/__baselines__` should not be ignored,
|
|
116
|
+
as it is used to store the baseline images.
|
|
117
|
+
They should be committed to your repository.
|
|
118
|
+
|
|
119
|
+
[storybook-addon-vis]: https://www.npmjs.com/package/storybook-addon-vis
|
|
120
|
+
[Vitest]: https://vitest.dev/
|
|
121
|
+
[Storybook]: https://storybook.js.org/
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { RefreshIcon } from '@storybook/icons'
|
|
1
2
|
import { memo } from 'react'
|
|
2
|
-
import { AddonPanel, Placeholder, ScrollArea } from 'storybook/internal/components'
|
|
3
|
+
import { AddonPanel, IconButton, Placeholder, ScrollArea } from 'storybook/internal/components'
|
|
3
4
|
import { styled } from 'storybook/internal/theming'
|
|
4
5
|
import { SNAPSHOT_ROOT_DIR } from 'vitest-plugin-vis/client-api'
|
|
5
6
|
import type { ImageSnapshotResults } from '../shared/events.ts'
|
|
@@ -7,9 +8,10 @@ import type { ImageSnapshotResults } from '../shared/events.ts'
|
|
|
7
8
|
interface PanelProps {
|
|
8
9
|
active: boolean
|
|
9
10
|
snapshotResults: ImageSnapshotResults[]
|
|
11
|
+
onRefresh: () => void
|
|
10
12
|
}
|
|
11
13
|
|
|
12
|
-
export const VisPanel = memo(function
|
|
14
|
+
export const VisPanel = memo(function VisPanel({ active, snapshotResults = [], onRefresh }: PanelProps) {
|
|
13
15
|
const groupedResults = snapshotResults.reduce(
|
|
14
16
|
(acc, result) => {
|
|
15
17
|
const suiteName = result.snapshotRootDir.slice(SNAPSHOT_ROOT_DIR.length + 1)
|
|
@@ -34,7 +36,12 @@ export const VisPanel = memo(function VisResultsPanel({ active, snapshotResults
|
|
|
34
36
|
</>
|
|
35
37
|
) : (
|
|
36
38
|
<>
|
|
37
|
-
<SnapshotRow>
|
|
39
|
+
<SnapshotRow>
|
|
40
|
+
<span>{key}</span>
|
|
41
|
+
<IconButton onClick={() => onRefresh()}>
|
|
42
|
+
<RefreshIcon />
|
|
43
|
+
</IconButton>
|
|
44
|
+
</SnapshotRow>
|
|
38
45
|
<img
|
|
39
46
|
key={results[0].filePath}
|
|
40
47
|
src={`data:image/png;base64,${results[0].base64}`}
|
|
@@ -53,6 +60,9 @@ export const VisPanel = memo(function VisResultsPanel({ active, snapshotResults
|
|
|
53
60
|
})
|
|
54
61
|
|
|
55
62
|
const SnapshotRow = styled.div<{ failed?: boolean }>(({ theme, failed }) => ({
|
|
63
|
+
display: 'flex',
|
|
64
|
+
alignItems: 'center',
|
|
65
|
+
gap: '0.5rem',
|
|
56
66
|
paddingBlock: '0.5rem',
|
|
57
67
|
paddingInline: '0.2rem',
|
|
58
68
|
backgroundColor: failed ? theme.background.negative : theme.background.positive,
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Meta } from "@storybook/blocks";
|
|
2
|
+
|
|
3
|
+
<Meta title="Installation" />
|
|
4
|
+
|
|
5
|
+
# Installation
|
|
6
|
+
|
|
7
|
+
[storybook-addon-vis][storybook-addon-vis] works with [Storybook Test addon][storybook-test-addon],
|
|
8
|
+
so naturally you need to have it setup first.
|
|
9
|
+
|
|
10
|
+
After that, you can install [storybook-addon-vis][storybook-addon-vis] with your package manager of choice:
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
npm install --save-dev storybook-addon-vis
|
|
14
|
+
```
|
|
15
|
+
```sh
|
|
16
|
+
pnpm add --save-dev storybook-addon-vis
|
|
17
|
+
```
|
|
18
|
+
```sh
|
|
19
|
+
yarn add --save-dev storybook-addon-vis
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Next, you need to setup [storybook-addon-vis][storybook-addon-vis] for both [Storybook] and [Vitest].
|
|
23
|
+
|
|
24
|
+
[storybook-addon-vis]: https://www.npmjs.com/package/storybook-addon-vis
|
|
25
|
+
[storybook-test-addon]: https://storybook.js.org/docs/writing-tests/test-addon
|
|
26
|
+
[Storybook]: https://storybook.js.org/
|
|
27
|
+
[Vitest]: https://vitest.dev/
|
package/src/manager.tsx
CHANGED
|
@@ -23,18 +23,31 @@ addons.register(NAME, (api) => {
|
|
|
23
23
|
const storyData = api.getCurrentStoryData()
|
|
24
24
|
|
|
25
25
|
useEffect(() => {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
const disposes = [
|
|
27
|
+
api.on(NAME, (event: VisEvent) => {
|
|
28
|
+
if (event.name !== storyData.name) return
|
|
29
|
+
if (event.importPath !== storyData.importPath) return
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
if (event.type === IMAGE_SNAPSHOT_RESULTS_RESPONSE) {
|
|
32
|
+
setSnapshotResults(event.results)
|
|
33
|
+
}
|
|
34
|
+
}),
|
|
35
|
+
// api.on('UNIVERSAL_STORE:storybook/test', (event) => {
|
|
36
|
+
// console.log('storybook/test', event)
|
|
37
|
+
// }),
|
|
38
|
+
]
|
|
34
39
|
api.emit(NAME, requestImageSnapshotResults(storyData))
|
|
35
|
-
return dispose
|
|
40
|
+
return () => disposes.forEach((dispose) => dispose())
|
|
36
41
|
}, [storyData])
|
|
37
|
-
return
|
|
42
|
+
return (
|
|
43
|
+
<VisPanel
|
|
44
|
+
active={active}
|
|
45
|
+
snapshotResults={snapshotResults}
|
|
46
|
+
onRefresh={() => {
|
|
47
|
+
api.emit(NAME, requestImageSnapshotResults(storyData))
|
|
48
|
+
}}
|
|
49
|
+
/>
|
|
50
|
+
)
|
|
38
51
|
},
|
|
39
52
|
})
|
|
40
53
|
})
|
package/src/overview.mdx
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Meta } from "@storybook/blocks";
|
|
2
|
+
|
|
3
|
+
<Meta title="Overview" />
|
|
4
|
+
|
|
5
|
+
# Storybook Visual Testing addon
|
|
6
|
+
|
|
7
|
+
[storybook-addon-vis][storybook-addon-vis] enables self contained Visual Testing for [Storybook][storybook].
|
|
8
|
+
|
|
9
|
+
It offers a new unique features:
|
|
10
|
+
|
|
11
|
+
- You can capture and compare image snapshot both automatically and manually.
|
|
12
|
+
- You can take a snapshot of the whole page or a specific component.
|
|
13
|
+
- You can take snapshots in different environments, platform, browser, screen size and themes.
|
|
14
|
+
- The snapshots are stored locally in the `__vis__` directory.
|
|
15
|
+
- You can enable, disable, and customize how the snapshots are taken and compared.
|
|
16
|
+
|
|
17
|
+
[storybook-addon-vis]: https://github.com/repobuddy/storybook-addon-vis
|
|
18
|
+
[storybook]: https://storybook.js.org
|