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 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 g=memo(function({active:a,snapshotResults:r=[]}){let p=r.reduce((t,e)=>{let S=`(${e.snapshotRootDir.slice(SNAPSHOT_ROOT_DIR.length+1)}) ${e.fileName}`;return t[S]=[...t[S]??[],e],t},{});return jsx(AddonPanel,{active:a,children:r.length>0?jsx(ScrollArea,{vertical:true,children:Object.entries(p).map(([t,e])=>jsx("div",{children:e.some(s=>s.type==="diff")?jsxs(Fragment,{children:[jsx(m,{failed:true,children:t}),e.map(s=>jsx("img",{src:`data:image/png;base64,${s.base64}`,alt:s.fileName},s.filePath))]}):jsxs(Fragment,{children:[jsx(m,{children:t}),jsx("img",{src:`data:image/png;base64,${e[0].base64}`,alt:e[0].fileName},e[0].filePath)]})},t))}):jsx(Placeholder,{children:"There is no snapshots for this story"})})}),m=styled.div(({theme:o,failed:a})=>({paddingBlock:"0.5rem",paddingInline:"0.2rem",backgroundColor:a?o.background.negative:o.background.positive,color:o.barTextColor}));var i="storybook-addon-vis",E=`${i}/vis_panel`;var A="IMAGE_SNAPSHOT_RESULTS_REQUEST",h="IMAGE_SNAPSHOT_RESULTS_RESPONSE";function u({name:o,importPath:a}){return {type:A,name:o,importPath:a}}addons.register(i,o=>{addons.add(E,{type:types.PANEL,title:"Vis",match:({tabId:a,viewMode:r})=>!a&&r==="story",render({active:a}){if(!a)return null;let[r,p]=useState([]),t=o.getCurrentStoryData();return useEffect(()=>{let e=o.on(i,s=>{s.name===t.name&&s.importPath===t.importPath&&s.type===h&&p(s.results);});return o.emit(i,u(t)),e},[t]),jsx(g,{active:a,snapshotResults:r})}});});//# sourceMappingURL=manager.js.map
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
@@ -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","dispose","event"],"mappings":"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","file":"manager.js","sourcesContent":["import { memo } from 'react'\nimport { AddonPanel, 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}\n\nexport const VisPanel = memo(function VisResultsPanel({ active, snapshotResults = [] }: 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>{key}</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\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 dispose = api.on(NAME, (event: VisEvent) => {\n\t\t\t\t\tif (event.name !== storyData.name) return\n\t\t\t\t\tif (event.importPath !== storyData.importPath) return\n\n\t\t\t\t\tif (event.type === IMAGE_SNAPSHOT_RESULTS_RESPONSE) {\n\t\t\t\t\t\tsetSnapshotResults(event.results)\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 dispose\n\t\t\t}, [storyData])\n\t\t\treturn <VisPanel active={active} snapshotResults={snapshotResults} />\n\t\t},\n\t})\n})\n"]}
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.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.0"
70
+ "vitest-plugin-vis": "^3.0.1"
70
71
  },
71
72
  "devDependencies": {
72
- "@repobuddy/storybook": "^0.12.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 `customizeSnapshotSubpath` to customize the snapshot sub-path to avoid conflicts.
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
- customizeSnapshotSubpath: (subpath) => subpath
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/
@@ -0,0 +1,7 @@
1
+ import { Meta } from "@storybook/blocks";
2
+ import changelog from "../CHANGELOG.md?raw";
3
+ import { Markdown } from "@storybook/blocks";
4
+
5
+ <Meta title="Changelog" />
6
+
7
+ <Markdown>{changelog}</Markdown>
@@ -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 VisResultsPanel({ active, snapshotResults = [] }: PanelProps) {
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>{key}</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 dispose = api.on(NAME, (event: VisEvent) => {
27
- if (event.name !== storyData.name) return
28
- if (event.importPath !== storyData.importPath) return
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
- if (event.type === IMAGE_SNAPSHOT_RESULTS_RESPONSE) {
31
- setSnapshotResults(event.results)
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 <VisPanel active={active} snapshotResults={snapshotResults} />
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
  })
@@ -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