@vizzly-testing/cli 0.30.0 → 0.31.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/README.md +9 -9
- package/dist/api/client.js +6 -3
- package/dist/cli.js +58 -29
- package/dist/client/index.js +21 -29
- package/dist/commands/api.js +145 -90
- package/dist/commands/baselines.js +78 -47
- package/dist/commands/builds.js +9 -8
- package/dist/commands/comparisons.js +7 -8
- package/dist/commands/config-cmd.js +15 -7
- package/dist/commands/context.js +329 -18
- package/dist/commands/doctor.js +116 -81
- package/dist/commands/init.js +202 -201
- package/dist/commands/login.js +134 -65
- package/dist/commands/logout.js +17 -6
- package/dist/commands/preview.js +29 -57
- package/dist/commands/project.js +120 -0
- package/dist/commands/projects.js +9 -2
- package/dist/commands/review.js +236 -202
- package/dist/commands/run.js +75 -72
- package/dist/commands/status.js +228 -150
- package/dist/commands/tdd-daemon-process.js +98 -0
- package/dist/commands/tdd-daemon.js +318 -212
- package/dist/commands/tdd.js +23 -8
- package/dist/commands/upload.js +10 -9
- package/dist/commands/whoami.js +63 -25
- package/dist/config/core.js +36 -35
- package/dist/context/local-workspace-provider.js +132 -12
- package/dist/context/provider-resolver.js +5 -1
- package/dist/index.js +5 -4
- package/dist/plugin-loader.js +55 -49
- package/dist/project/core.js +2 -2
- package/dist/project/operations.js +6 -3
- package/dist/reporter/reporter-bundle.css +1 -1
- package/dist/reporter/reporter-bundle.iife.js +7 -7
- package/dist/sdk/index.js +162 -107
- package/dist/server/handlers/tdd-handler.js +30 -54
- package/dist/server/routers/baseline.js +195 -178
- package/dist/server/routers/events.js +212 -174
- package/dist/server/routers/projects.js +14 -5
- package/dist/server/routers/screenshot.js +1 -31
- package/dist/services/build-manager.js +19 -97
- package/dist/services/config-service.js +26 -34
- package/dist/services/index.js +2 -4
- package/dist/services/static-report-generator.js +11 -12
- package/dist/services/test-runner.js +21 -27
- package/dist/tdd/index.js +1 -1
- package/dist/tdd/metadata/region-metadata.js +0 -37
- package/dist/tdd/runtime-context.js +78 -0
- package/dist/tdd/server-registry.js +38 -17
- package/dist/tdd/services/baseline-manager.js +23 -0
- package/dist/tdd/services/hotspot-service.js +0 -13
- package/dist/tdd/tdd-service.js +82 -113
- package/dist/types/client.d.ts +36 -1
- package/dist/types/index.d.ts +114 -22
- package/dist/types/sdk.d.ts +40 -0
- package/dist/uploader/core.js +5 -4
- package/dist/uploader/index.js +79 -2
- package/dist/uploader/operations.js +40 -5
- package/dist/utils/api-url.js +14 -0
- package/dist/utils/async-utils.js +26 -0
- package/dist/utils/ci-env.js +19 -12
- package/dist/utils/config-loader.js +39 -65
- package/dist/utils/config-schema.js +19 -55
- package/dist/utils/fetch-utils.js +12 -5
- package/dist/utils/git.js +35 -72
- package/dist/utils/global-config.js +20 -1
- package/dist/utils/output.js +19 -7
- package/dist/utils/patterns.js +14 -0
- package/dist/utils/project-link-store.js +105 -0
- package/dist/utils/screenshot-options.js +28 -0
- package/dist/utils/secret-store.js +65 -0
- package/package.json +11 -8
- package/dist/services/screenshot-server.js +0 -65
- package/dist/services/server-manager.js +0 -81
- package/dist/services/uploader.js +0 -100
- package/dist/tdd/services/region-service.js +0 -75
- package/dist/utils/build-history.js +0 -103
|
@@ -189,7 +189,7 @@ Error generating stack: `+e.message+`
|
|
|
189
189
|
bg-slate-800/30 hover:bg-slate-800/50
|
|
190
190
|
border rounded-lg transition-all
|
|
191
191
|
${o?`border-red-500/40 hover:border-red-500/60`:s?`border-blue-500/40 hover:border-blue-500/60`:`border-slate-700/50 hover:border-slate-600`}
|
|
192
|
-
`,children:[(0,P.jsxs)(`button`,{type:`button`,onClick:()=>t(a),className:`relative w-16 h-11 rounded-md overflow-hidden flex-shrink-0 bg-slate-900 hover:ring-2 hover:ring-amber-500/50 transition-all`,children:[u?(0,P.jsx)(yo,{src:u,alt:e.name,className:`w-full h-full object-cover object-top`}):(0,P.jsx)(`div`,{className:`w-full h-full flex items-center justify-center text-slate-600`,children:(0,P.jsx)(an,{className:`w-5 h-5`})}),o&&(0,P.jsx)(`div`,{className:`absolute inset-0 bg-red-500/20 flex items-center justify-center`,children:(0,P.jsx)(`div`,{className:`w-2 h-2 bg-red-500 rounded-full animate-pulse`})}),!o&&s&&(0,P.jsx)(`div`,{className:`absolute inset-0 bg-blue-500/10`}),e.comparisons.length>1&&(0,P.jsx)(`div`,{className:`absolute bottom-0.5 right-0.5 px-1 py-0.5 text-[9px] font-bold bg-slate-900/90 text-slate-300 rounded`,children:e.comparisons.length})]}),(0,P.jsxs)(`button`,{type:`button`,onClick:()=>t(a),className:`flex-1 min-w-0 text-left`,children:[(0,P.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,P.jsx)(`h3`,{className:`text-sm font-medium text-white truncate`,children:e.name}),c>0&&(0,P.jsxs)(`span`,{className:`text-xs font-mono text-red-400`,children:[c.toFixed(1),`%`]})]}),(0,P.jsx)(`div`,{className:`mt-1`,children:(0,P.jsx)(wo,{group:e})})]}),(0,P.jsxs)(`div`,{className:`flex items-center gap-2 flex-shrink-0`,children:[(0,P.jsx)(Co,{group:e}),l&&(0,P.jsxs)(`div`,{className:`flex items-center gap-1 ${e.acceptedCount===0?`opacity-100`:`opacity-0 group-hover:opacity-100`} transition-opacity`,children:[(0,P.jsx)(L,{variant:`ghost`,size:`sm`,onClick:t=>{t.stopPropagation(),n(e)},disabled:i,className:`!p-1.5 text-emerald-400 hover:bg-emerald-500/20`,title:`Accept ${e.comparisons.length>1?`all ${e.comparisons.length} variants`:``}`,children:(0,P.jsx)(Bt,{className:`w-4 h-4`})}),(0,P.jsx)(L,{variant:`ghost`,size:`sm`,onClick:t=>{t.stopPropagation(),r(e)},disabled:i,className:`!p-1.5 text-red-400 hover:bg-red-500/20`,title:`Reject`,children:(0,P.jsx)($n,{className:`w-4 h-4`})})]}),(0,P.jsx)(Jt,{className:`w-4 h-4 text-slate-500 group-hover:text-slate-300 transition-colors`})]})]})}function Eo({comparisons:e,onSelectComparison:t,onAcceptComparison:n,onRejectComparison:r,onAcceptGroup:i,onRejectGroup:a,loadingStates:o={}}){let s=(0,N.useMemo)(()=>!e||e.length===0?[]:xo(e),[e]);if(s.length===0)return(0,P.jsx)(`div`,{className:`text-center py-12 text-slate-400`,children:`No screenshots to display`});let c=e=>{let t=e.comparisons.map(bo).filter(Boolean);if(i){i(t);return}if(n)for(let e of t)n(e)},l=e=>{let t=e.comparisons.map(bo).filter(Boolean);if(a){a(t);return}if(r)for(let e of t)r(e)},u=Object.values(o).some(e=>e===`accepting`);return(0,P.jsx)(`div`,{className:`space-y-2`,children:s.map(e=>(0,P.jsx)(To,{group:e,onSelect:t,onAcceptGroup:c,onRejectGroup:l,isAccepting:u},e.name))})}function Do(e){return e<=480?ln:e<=1024?dn:an}function Oo({value:e,onChange:t,options:n,placeholder:r,renderOption:i,renderValue:a,testId:o}){let[s,c]=(0,N.useState)(!1),l=(0,N.useRef)(null),u=(0,N.useCallback)(e=>{t(e),c(!1)},[t]);return(0,P.jsxs)(`div`,{className:`relative`,ref:l,onBlur:(0,N.useCallback)(e=>{l.current?.contains(e.relatedTarget)||c(!1)},[]),children:[(0,P.jsxs)(`button`,{type:`button`,onClick:()=>c(!s),"data-testid":o,className:`
|
|
192
|
+
`,children:[(0,P.jsxs)(`button`,{type:`button`,onClick:()=>t(a),className:`relative w-16 h-11 rounded-md overflow-hidden flex-shrink-0 bg-slate-900 hover:ring-2 hover:ring-amber-500/50 transition-all`,children:[u?(0,P.jsx)(yo,{src:u,alt:e.name,className:`w-full h-full object-cover object-top`}):(0,P.jsx)(`div`,{className:`w-full h-full flex items-center justify-center text-slate-600`,children:(0,P.jsx)(an,{className:`w-5 h-5`})}),o&&(0,P.jsx)(`div`,{className:`absolute inset-0 bg-red-500/20 flex items-center justify-center`,children:(0,P.jsx)(`div`,{className:`w-2 h-2 bg-red-500 rounded-full animate-pulse`})}),!o&&s&&(0,P.jsx)(`div`,{className:`absolute inset-0 bg-blue-500/10`}),e.comparisons.length>1&&(0,P.jsx)(`div`,{className:`absolute bottom-0.5 right-0.5 px-1 py-0.5 text-[9px] font-bold bg-slate-900/90 text-slate-300 rounded`,children:e.comparisons.length})]}),(0,P.jsxs)(`button`,{type:`button`,onClick:()=>t(a),className:`flex-1 min-w-0 text-left`,children:[(0,P.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,P.jsx)(`h3`,{className:`text-sm font-medium text-white truncate`,children:e.name}),c>0&&(0,P.jsxs)(`span`,{className:`text-xs font-mono text-red-400`,children:[c.toFixed(1),`%`]})]}),(0,P.jsx)(`div`,{className:`mt-1`,children:(0,P.jsx)(wo,{group:e})})]}),(0,P.jsxs)(`div`,{className:`flex items-center gap-2 flex-shrink-0`,children:[(0,P.jsx)(Co,{group:e}),l&&(0,P.jsxs)(`div`,{className:`flex items-center gap-1 ${e.acceptedCount===0?`opacity-100`:`opacity-0 group-hover:opacity-100`} transition-opacity`,children:[(0,P.jsx)(L,{variant:`ghost`,size:`sm`,onClick:t=>{t.stopPropagation(),n(e)},disabled:i,className:`!p-1.5 text-emerald-400 hover:bg-emerald-500/20`,title:`Accept ${e.comparisons.length>1?`all ${e.comparisons.length} variants`:``}`,children:(0,P.jsx)(Bt,{className:`w-4 h-4`})}),(0,P.jsx)(L,{variant:`ghost`,size:`sm`,onClick:t=>{t.stopPropagation(),r(e)},disabled:i,className:`!p-1.5 text-red-400 hover:bg-red-500/20`,title:`Reject`,children:(0,P.jsx)($n,{className:`w-4 h-4`})})]}),(0,P.jsx)(Jt,{className:`w-4 h-4 text-slate-500 group-hover:text-slate-300 transition-colors`})]})]})}function Eo({comparisons:e,onSelectComparison:t,onAcceptComparison:n,onRejectComparison:r,onAcceptGroup:i,onRejectGroup:a,loadingStates:o={}}){let s=(0,N.useMemo)(()=>!e||e.length===0?[]:xo(e),[e]);if(s.length===0)return(0,P.jsx)(`div`,{className:`text-center py-12 text-slate-400`,children:`No screenshots to display`});let c=e=>{let t=e.comparisons.map(bo).filter(Boolean);if(i){i(t);return}if(n)for(let e of t)n(e)},l=e=>{let t=e.comparisons.map(bo).filter(Boolean);if(a){a(t);return}if(r)for(let e of t)r(e)},u=Object.values(o).some(e=>e===`accepting`);return(0,P.jsx)(`div`,{className:`space-y-2`,children:s.map(e=>(0,P.jsx)(To,{group:e,onSelect:t,onAcceptGroup:c,onRejectGroup:l,isAccepting:u},e.name))})}function Do(e){return e<=480?ln:e<=1024?dn:an}function Oo(e){return Number(e.split(`×`)[0])}function ko({value:e,onChange:t,options:n,placeholder:r,renderOption:i,renderValue:a,testId:o}){let[s,c]=(0,N.useState)(!1),l=(0,N.useRef)(null),u=(0,N.useCallback)(e=>{t(e),c(!1)},[t]);return(0,P.jsxs)(`div`,{className:`relative`,ref:l,onBlur:(0,N.useCallback)(e=>{l.current?.contains(e.relatedTarget)||c(!1)},[]),children:[(0,P.jsxs)(`button`,{type:`button`,onClick:()=>c(!s),"data-testid":o,className:`
|
|
193
193
|
inline-flex items-center gap-2 px-2.5 h-8 rounded-md text-xs font-medium
|
|
194
194
|
bg-transparent border transition-all
|
|
195
195
|
${s?`border-amber-500/50 ring-1 ring-amber-500/20`:`border-slate-700/50 hover:border-slate-600`}
|
|
@@ -199,26 +199,26 @@ Error generating stack: `+e.message+`
|
|
|
199
199
|
`,children:[(0,P.jsx)(`span`,{className:`w-4 h-4`}),(0,P.jsx)(`span`,{children:r})]}),n.map(t=>(0,P.jsx)(`button`,{type:`button`,onClick:()=>u(t),className:`
|
|
200
200
|
w-full flex items-center gap-2 px-3 py-2 text-sm text-left transition-colors
|
|
201
201
|
${e===t?`bg-amber-500/10 text-amber-400`:`text-slate-300 hover:bg-slate-700/50`}
|
|
202
|
-
`,children:i(t)},t))]})})]})}function
|
|
202
|
+
`,children:i(t)},t))]})})]})}function Ao({value:e,onChange:t}){let[n,r]=(0,N.useState)(!1),i=(0,N.useRef)(null),a=[{value:oo.PRIORITY,label:`Priority`,description:`Failed first`},{value:oo.NAME,label:`Name`,description:`A to Z`},{value:oo.TIME,label:`Time`,description:`Recent first`}],o=a.find(t=>t.value===e)||a[0];return(0,P.jsxs)(`div`,{className:`relative`,ref:i,children:[(0,P.jsxs)(`button`,{type:`button`,onClick:()=>r(!n),className:`
|
|
203
203
|
inline-flex items-center gap-2 px-2.5 h-8 rounded-md text-xs font-medium
|
|
204
204
|
bg-transparent border transition-all
|
|
205
205
|
${n?`border-amber-500/50 ring-1 ring-amber-500/20`:`border-slate-700/50 hover:border-slate-600`}
|
|
206
206
|
`,children:[(0,P.jsx)(Ot,{className:`w-3.5 h-3.5 text-slate-500`}),(0,P.jsx)(`span`,{className:`text-slate-300`,children:o.label}),(0,P.jsx)(Wt,{className:`w-3 h-3 text-slate-500 transition-transform ${n?`rotate-180`:``}`})]}),n&&(0,P.jsxs)(P.Fragment,{children:[(0,P.jsx)(`div`,{className:`fixed inset-0 z-40`,onClick:()=>r(!1)}),(0,P.jsx)(`div`,{className:`absolute top-full right-0 mt-1 z-50 w-48 bg-slate-800 border border-slate-700/50 rounded-lg shadow-xl overflow-hidden`,children:(0,P.jsx)(`div`,{className:`py-1`,children:a.map(n=>(0,P.jsxs)(`button`,{type:`button`,onClick:()=>{t(n.value),r(!1)},className:`
|
|
207
207
|
w-full flex flex-col items-start px-3 py-2 text-left transition-colors
|
|
208
208
|
${e===n.value?`bg-amber-500/10`:`hover:bg-slate-700/50`}
|
|
209
|
-
`,children:[(0,P.jsx)(`span`,{className:e===n.value?`text-amber-400 font-medium`:`text-slate-300`,children:n.label}),(0,P.jsx)(`span`,{className:`text-xs text-slate-500`,children:n.description})]},n.value))})})]})]})}function
|
|
209
|
+
`,children:[(0,P.jsx)(`span`,{className:e===n.value?`text-amber-400 font-medium`:`text-slate-300`,children:n.label}),(0,P.jsx)(`span`,{className:`text-xs text-slate-500`,children:n.description})]},n.value))})})]})]})}function jo({label:e,icon:t,onRemove:n}){return(0,P.jsxs)(`span`,{className:`inline-flex items-center gap-1.5 px-2 py-1 bg-amber-500/10 border border-amber-500/20 rounded-md text-xs text-amber-400`,children:[t&&(0,P.jsx)(t,{className:`w-3 h-3`}),(0,P.jsx)(`span`,{children:e}),(0,P.jsx)(`button`,{type:`button`,onClick:n,className:`ml-0.5 hover:text-amber-200 transition-colors`,children:(0,P.jsx)(tr,{className:`w-3 h-3`})})]})}function Mo({filter:e,setFilter:t,sortBy:n,setSortBy:r,searchQuery:i,setSearchQuery:a,selectedBrowser:o,setSelectedBrowser:s,selectedViewport:c,setSelectedViewport:l,availableFilters:u,counts:d,onRefresh:f,loading:p}){let[m,h]=(0,N.useState)(!1),g=u.browsers.length>1||u.viewports.length>1,_=(0,N.useMemo)(()=>{let t=0;return e!==H.ALL&&t++,o!==`all`&&t++,c!==`all`&&t++,i.trim()&&t++,t},[e,o,c,i]),v=(0,N.useCallback)(()=>{t(H.ALL),s(`all`),l(`all`),a(``)},[t,s,l,a]),y=(0,N.useCallback)(e=>(0,P.jsxs)(P.Fragment,{children:[(0,P.jsx)(Di,{browser:e,className:`w-4 h-4`}),(0,P.jsx)(`span`,{className:`capitalize`,children:e})]}),[]),b=(0,N.useCallback)(e=>e===`all`?(0,P.jsx)(`span`,{className:`text-slate-400`,children:`All browsers`}):(0,P.jsxs)(`span`,{className:`flex items-center gap-2`,children:[(0,P.jsx)(Di,{browser:e,className:`w-4 h-4`}),(0,P.jsx)(`span`,{className:`text-slate-300 capitalize`,children:e})]}),[]),x=(0,N.useCallback)(e=>(0,P.jsxs)(P.Fragment,{children:[(0,P.jsx)(Do(Oo(e)),{className:`w-4 h-4 text-slate-400`}),(0,P.jsx)(`span`,{className:`font-mono text-xs`,children:e})]}),[]),S=(0,N.useCallback)(e=>e===`all`?(0,P.jsx)(`span`,{className:`text-slate-400`,children:`All viewports`}):(0,P.jsxs)(`span`,{className:`flex items-center gap-2`,children:[(0,P.jsx)(Do(Oo(e)),{className:`w-4 h-4 text-slate-400`}),(0,P.jsx)(`span`,{className:`text-slate-300 font-mono text-xs`,children:e})]}),[]);return(0,P.jsxs)(`div`,{className:`space-y-3`,children:[(0,P.jsxs)(`div`,{className:`flex flex-col gap-3 p-3 bg-slate-800/30 border border-slate-700/40 rounded-xl`,children:[(0,P.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,P.jsx)(`div`,{className:`flex-1`,children:(0,P.jsx)(ba,{value:i,onChange:a,placeholder:`Search screenshots...`,className:`w-full`})}),g&&(0,P.jsxs)(`button`,{type:`button`,onClick:()=>h(!m),className:`
|
|
210
210
|
relative md:hidden inline-flex items-center justify-center w-8 h-8 rounded-md transition-all
|
|
211
211
|
${m||_>0?`bg-amber-500/20 text-amber-400 border border-amber-500/30`:`text-slate-400 border border-slate-700/50 hover:border-slate-600`}
|
|
212
|
-
`,children:[(0,P.jsx)(mt,{className:`w-4 h-4`}),_>0&&(0,P.jsx)(`span`,{className:`absolute -top-1 -right-1 w-4 h-4 bg-amber-500 text-slate-900 text-[10px] font-bold rounded-full flex items-center justify-center`,children:_})]}),(0,P.jsx)(`div`,{className:`hidden md:block`,children:(0,P.jsx)(
|
|
212
|
+
`,children:[(0,P.jsx)(mt,{className:`w-4 h-4`}),_>0&&(0,P.jsx)(`span`,{className:`absolute -top-1 -right-1 w-4 h-4 bg-amber-500 text-slate-900 text-[10px] font-bold rounded-full flex items-center justify-center`,children:_})]}),(0,P.jsx)(`div`,{className:`hidden md:block`,children:(0,P.jsx)(Ao,{value:n,onChange:r})}),(0,P.jsx)(`button`,{type:`button`,onClick:f,disabled:p,className:`inline-flex items-center justify-center w-8 h-8 rounded-md border border-slate-700/50 text-slate-400 hover:text-white hover:border-slate-600 transition-all disabled:opacity-50`,title:`Refresh`,children:(0,P.jsx)(vt,{className:`w-4 h-4 ${p?`animate-spin`:``}`})})]}),(0,P.jsxs)(`div`,{className:`hidden md:flex items-center gap-3 flex-wrap`,children:[(0,P.jsxs)(`div`,{className:`flex items-center gap-1.5`,children:[(0,P.jsx)(z,{label:`All`,count:d.all,color:`gray`,active:e===H.ALL,onClick:()=>t(H.ALL),testId:`filter-status-all`}),(0,P.jsx)(z,{label:`Failed`,count:d.failed,color:`red`,active:e===H.FAILED,onClick:()=>t(H.FAILED),testId:`filter-status-failed`}),(0,P.jsx)(z,{label:`New`,count:d.new,color:`blue`,active:e===H.NEW,onClick:()=>t(H.NEW),testId:`filter-status-new`}),(0,P.jsx)(z,{label:`Passed`,count:d.passed,color:`emerald`,active:e===H.PASSED,onClick:()=>t(H.PASSED),testId:`filter-status-passed`})]}),g&&(0,P.jsx)(`div`,{className:`w-px h-6 bg-slate-700/50`}),u.browsers.length>1&&(0,P.jsx)(ko,{value:o,onChange:s,options:u.browsers,placeholder:`All browsers`,renderOption:y,renderValue:b,testId:`filter-browser`}),u.viewports.length>1&&(0,P.jsx)(ko,{value:c,onChange:l,options:u.viewports,placeholder:`All viewports`,renderOption:x,renderValue:S,testId:`filter-viewport`})]}),(0,P.jsxs)(`div`,{className:`md:hidden flex items-center gap-1.5 overflow-x-auto pb-1 -mx-1 px-1 scrollbar-hide`,children:[(0,P.jsx)(z,{label:`All`,count:d.all,color:`gray`,active:e===H.ALL,onClick:()=>t(H.ALL),testId:`mobile-filter-status-all`}),(0,P.jsx)(z,{label:`Failed`,count:d.failed,color:`red`,active:e===H.FAILED,onClick:()=>t(H.FAILED),testId:`mobile-filter-status-failed`}),(0,P.jsx)(z,{label:`New`,count:d.new,color:`blue`,active:e===H.NEW,onClick:()=>t(H.NEW),testId:`mobile-filter-status-new`}),(0,P.jsx)(z,{label:`Passed`,count:d.passed,color:`emerald`,active:e===H.PASSED,onClick:()=>t(H.PASSED),testId:`mobile-filter-status-passed`})]}),m&&g&&(0,P.jsxs)(`div`,{className:`md:hidden space-y-3 pt-2 border-t border-slate-700/30`,children:[(0,P.jsxs)(`div`,{className:`flex items-center justify-between`,children:[(0,P.jsx)(`span`,{className:`text-sm text-slate-400`,children:`Sort by`}),(0,P.jsx)(Ao,{value:n,onChange:r})]}),u.browsers.length>1&&(0,P.jsxs)(`div`,{className:`flex items-center justify-between`,children:[(0,P.jsx)(`span`,{className:`text-sm text-slate-400`,children:`Browser`}),(0,P.jsx)(ko,{value:o,onChange:s,options:u.browsers,placeholder:`All browsers`,renderOption:y,renderValue:b,testId:`filter-browser-mobile`})]}),u.viewports.length>1&&(0,P.jsxs)(`div`,{className:`flex items-center justify-between`,children:[(0,P.jsx)(`span`,{className:`text-sm text-slate-400`,children:`Viewport`}),(0,P.jsx)(ko,{value:c,onChange:l,options:u.viewports,placeholder:`All viewports`,renderOption:x,renderValue:S,testId:`filter-viewport-mobile`})]})]})]}),_>0&&(0,P.jsxs)(`div`,{className:`flex items-center gap-2 flex-wrap`,children:[(0,P.jsx)(`span`,{className:`text-xs text-slate-500`,children:`Filters:`}),e!==H.ALL&&(0,P.jsx)(jo,{label:e.charAt(0).toUpperCase()+e.slice(1),onRemove:()=>t(H.ALL)}),o!==`all`&&(0,P.jsx)(jo,{label:o,icon:()=>(0,P.jsx)(Di,{browser:o,className:`w-3 h-3`}),onRemove:()=>s(`all`)}),c!==`all`&&(0,P.jsx)(jo,{label:c,icon:Do(Oo(c)),onRemove:()=>l(`all`)}),i.trim()&&(0,P.jsx)(jo,{label:`"${i}"`,onRemove:()=>a(``)}),(0,P.jsx)(`button`,{type:`button`,onClick:v,className:`text-xs text-slate-500 hover:text-slate-300 transition-colors ml-2`,children:`Clear all`})]})]})}function No(e){return Array.isArray(e)?e.filter(Boolean):e?[e]:[]}function Po({failedCount:e,newCount:t,onAcceptAll:n,isLoading:r}){let i=e+t,a=e>0;return(0,P.jsxs)(`div`,{className:`
|
|
213
213
|
relative overflow-hidden rounded-xl border
|
|
214
214
|
${a?`bg-gradient-to-r from-red-500/10 via-red-500/5 to-transparent border-red-500/20`:`bg-gradient-to-r from-emerald-500/10 via-emerald-500/5 to-transparent border-emerald-500/20`}
|
|
215
215
|
`,children:[(0,P.jsx)(`div`,{className:`absolute inset-0 opacity-5`,children:(0,P.jsx)(`div`,{className:`absolute inset-0`,style:{backgroundImage:`radial-gradient(circle at 1px 1px, currentColor 1px, transparent 0)`,backgroundSize:`24px 24px`}})}),(0,P.jsxs)(`div`,{className:`relative flex items-center gap-4 p-4`,children:[(0,P.jsx)(`div`,{className:`
|
|
216
216
|
flex-shrink-0 w-10 h-10 rounded-lg flex items-center justify-center
|
|
217
217
|
${a?`bg-red-500/20 text-red-400`:`bg-emerald-500/20 text-emerald-400`}
|
|
218
|
-
`,children:a?(0,P.jsx)(hn,{className:`w-5 h-5`}):(0,P.jsx)(Vn,{className:`w-5 h-5`})}),(0,P.jsxs)(`div`,{className:`flex-1 min-w-0`,children:[(0,P.jsxs)(`div`,{className:`flex items-center gap-3 flex-wrap`,children:[e>0&&(0,P.jsxs)(`span`,{className:`inline-flex items-center gap-1.5 px-2 py-0.5 rounded-md bg-red-500/20 text-red-400 text-sm font-medium`,children:[(0,P.jsx)(`span`,{className:`w-1.5 h-1.5 rounded-full bg-red-400`}),e,` changed`]}),t>0&&(0,P.jsxs)(`span`,{className:`inline-flex items-center gap-1.5 px-2 py-0.5 rounded-md bg-blue-500/20 text-blue-400 text-sm font-medium`,children:[(0,P.jsx)(`span`,{className:`w-1.5 h-1.5 rounded-full bg-blue-400`}),t,` new`]})]}),(0,P.jsx)(`p`,{className:`text-sm text-slate-400 mt-1`,children:a?`Review visual differences and accept to update baselines`:`New screenshots ready to be saved as baselines`})]}),(0,P.jsxs)(L,{variant:`success`,onClick:n,loading:r,icon:Bt,className:`flex-shrink-0`,"data-testid":`btn-accept-all`,children:[`Accept All (`,i,`)`]})]})]})}function Po(){let[,e]=Fr(),{addToast:t,confirm:n}=Ja(),[r,i]=(0,N.useState)({}),{data:a,isLoading:o,refetch:s}=di(),c=pi(),l=gi(),u=_i(),{filteredComparisons:d,filter:f,setFilter:p,sortBy:m,setSortBy:h,searchQuery:g,setSearchQuery:_,selectedBrowser:v,setSelectedBrowser:y,selectedViewport:b,setSelectedViewport:x,availableFilters:S,counts:ee}=vo(a?.comparisons),C=(0,N.useCallback)(t=>{let n=t.id||t.signature||t.name;n&&e(`/comparison/${encodeURIComponent(n)}`)},[e]),w=(0,N.useCallback)((e,t)=>{i(n=>{let r={...n};for(let n of e)n&&(t?r[n]=t:delete r[n]);return r})},[]),te=(0,N.useCallback)(e=>{!e||e.length===0||(w(e,`accepting`),l.mutate(e,{onSuccess:()=>{w(e,`accepted`)},onError:n=>{let r=Mo(n?.succeededIds),i=Mo(n?.failedIds);r.length>0&&w(r,`accepted`),i.length>0?w(i,null):w(e,null),t(`Failed to accept: ${n.message}`,`error`)}}))},[l,t,w]),ne=(0,N.useCallback)(e=>{!e||e.length===0||(w(e,`rejecting`),u.mutate(e,{onSuccess:()=>{w(e,`rejected`)},onError:n=>{let r=Mo(n?.succeededIds),i=Mo(n?.failedIds);r.length>0&&w(r,`rejected`),i.length>0?w(i,null):w(e,null),t(`Failed to reject: ${n.message}`,`error`)}}))},[u,t,w]),T=(0,N.useCallback)(e=>te(e?[e]:[]),[te]),re=(0,N.useCallback)(e=>ne(e?[e]:[]),[ne]),E=(0,N.useCallback)(async()=>{await n(`This will update all failed and new screenshots.`,`Accept all changes as new baselines?`)&&c.mutate(void 0,{onSuccess:()=>{t(`All baselines accepted successfully`,`success`)},onError:e=>{console.error(`Failed to accept all baselines:`,e),t(`Failed to accept all baselines. Please try again.`,`error`)}})},[c,t,n]),D=!a?.comparisons||a.comparisons.length===0,O=f!==`all`||g.trim()||v!==`all`||b!==`all`,k=a?.comparisons?.some(e=>go(e.status)),A=a?.comparisons?.filter(e=>e.status===`failed`).length||0,ie=a?.comparisons?.filter(e=>ho(e.status)).length||0;return D?(0,P.jsx)(Mi,{icon:It,title:`No Screenshots Yet`,description:`Run your tests with vizzlyScreenshot() to start capturing visual comparisons.`}):(0,P.jsxs)(`div`,{className:`space-y-6`,children:[(0,P.jsx)(jo,{filter:f,setFilter:p,sortBy:m,setSortBy:h,searchQuery:g,setSearchQuery:_,selectedBrowser:v,setSelectedBrowser:y,selectedViewport:b,setSelectedViewport:x,availableFilters:S,counts:ee,onRefresh:s,loading:o}),k&&(0,P.jsx)(No,{failedCount:A,newCount:ie,onAcceptAll:E,isLoading:c.isPending}),d.length===0?(0,P.jsx)(R,{hover:!1,children:(0,P.jsx)(ki,{className:`py-12`,children:(0,P.jsx)(Mi,{icon:xn,title:`No matches`,description:`No comparisons match your current filters.`,action:O&&(0,P.jsx)(L,{variant:`ghost`,size:`sm`,onClick:()=>{p(`all`),_(``),y(`all`),x(`all`)},children:`Clear all filters`})})})}):(0,P.jsx)(Eo,{comparisons:d,onSelectComparison:C,onAcceptComparison:T,onRejectComparison:re,onAcceptGroup:te,onRejectGroup:ne,loadingStates:r})]})}function Fo(e={}){return at({queryKey:F.config,queryFn:Jr.get,...e})}function Io(){let e=We();return ot({mutationFn:Jr.updateProject,onSuccess:()=>{e.invalidateQueries({queryKey:F.config})}})}function Lo(e){return{threshold:String(e?.comparison?.threshold??2),port:String(e?.server?.port??47392),timeout:String(e?.server?.timeout??3e4),buildName:e?.build?.name??`Build {timestamp}`,environment:e?.build?.environment??`test`,openReport:e?.tdd?.openReport??!1}}function Ro(e){if(e==null||String(e).trim()===``)return null;let t=Number(e);return Number.isFinite(t)?t:null}function zo(e){let t=Ro(e);return Number.isInteger(t)?t:null}function Bo(e){let t={threshold:null,port:null,timeout:null},n=Ro(e.threshold);(n===null||n<0)&&(t.threshold=`Threshold must be a number greater than or equal to 0.`);let r=zo(e.port);(r===null||r<1||r>65535)&&(t.port=`Port must be a whole number between 1 and 65535.`);let i=zo(e.timeout);return(i===null||i<0)&&(t.timeout=`Timeout must be a whole number greater than or equal to 0.`),t}function Vo({source:e}){return(0,P.jsx)(Ti,{variant:{default:`default`,project:`info`,global:`purple`,env:`success`,cli:`warning`}[e]||`default`,size:`sm`,children:{default:`Default`,project:`Project`,global:`Global`,env:`Environment`,cli:`CLI Flag`}[e]||e})}function Ho({title:e,source:t,description:n,children:r,noSource:i}){return(0,P.jsxs)(`div`,{className:`space-y-4`,children:[(0,P.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,P.jsx)(`h3`,{className:`text-sm font-semibold text-white uppercase tracking-wide`,children:e}),!i&&(0,P.jsx)(Vo,{source:t||`default`})]}),n&&(0,P.jsx)(`p`,{className:`text-sm text-slate-400`,children:n}),(0,P.jsx)(`div`,{className:`space-y-4`,children:r})]})}function Uo({config:e,sources:t,onSave:n,isSaving:r}){let[i,a]=(0,N.useState)(Lo(e)),[o,s]=(0,N.useState)(!1),c=(0,N.useMemo)(()=>Bo(i),[i]),l=Object.values(c).some(Boolean),u=(0,N.useCallback)((e,t)=>{a(n=>({...n,[e]:t})),s(!0)},[]),d=(0,N.useCallback)(()=>{a(Lo(e)),s(!1)},[e]),f=(0,N.useCallback)(()=>{let e=Ro(i.threshold),t=zo(i.port),r=zo(i.timeout);e===null||t===null||r===null||n({comparison:{threshold:e},server:{port:t,timeout:r},build:{name:i.buildName,environment:i.environment},tdd:{openReport:i.openReport}},()=>s(!1))},[i,n]);return(0,P.jsxs)(`div`,{className:`grid grid-cols-1 lg:grid-cols-2 gap-6`,children:[(0,P.jsxs)(`div`,{className:`space-y-6`,children:[(0,P.jsx)(R,{hover:!1,children:(0,P.jsx)(ki,{children:(0,P.jsxs)(Ho,{title:`Comparison`,source:t?.comparison,description:`Configure how screenshots are compared`,children:[(0,P.jsx)(B,{label:`Threshold`,type:`number`,value:i.threshold,onChange:e=>u(`threshold`,e.target.value),hint:`CIEDE2000 Delta E. 0 = exact, 2 = recommended`,step:`0.1`,min:`0`}),c.threshold&&(0,P.jsx)(`p`,{className:`-mt-2 text-xs text-red-400`,children:c.threshold})]})})}),(0,P.jsx)(R,{hover:!1,children:(0,P.jsx)(ki,{children:(0,P.jsxs)(Ho,{title:`Server`,source:t?.server,description:`Local screenshot server configuration`,children:[(0,P.jsx)(B,{label:`Port`,type:`number`,value:i.port,onChange:e=>u(`port`,e.target.value),hint:`Default: 47392`}),c.port&&(0,P.jsx)(`p`,{className:`-mt-2 text-xs text-red-400`,children:c.port}),(0,P.jsx)(B,{label:`Timeout`,type:`number`,value:i.timeout,onChange:e=>u(`timeout`,e.target.value),hint:`Request timeout in milliseconds`}),c.timeout&&(0,P.jsx)(`p`,{className:`-mt-2 text-xs text-red-400`,children:c.timeout})]})})})]}),(0,P.jsxs)(`div`,{className:`space-y-6`,children:[(0,P.jsx)(R,{hover:!1,children:(0,P.jsx)(ki,{children:(0,P.jsxs)(Ho,{title:`Build`,source:t?.build,description:`Build naming and environment`,children:[(0,P.jsx)(B,{label:`Name Template`,type:`text`,value:i.buildName,onChange:e=>u(`buildName`,e.target.value),hint:`Use {timestamp} for current time`}),(0,P.jsx)(Pi,{label:`Environment`,value:i.environment,onChange:e=>u(`environment`,e.target.value),options:[{value:`test`,label:`Test`},{value:`development`,label:`Development`},{value:`staging`,label:`Staging`},{value:`production`,label:`Production`}]})]})})}),(0,P.jsx)(R,{hover:!1,children:(0,P.jsx)(ki,{children:(0,P.jsx)(Ho,{title:`TDD Mode`,source:t?.tdd,description:`Development workflow preferences`,children:(0,P.jsx)(Fi,{label:`Auto-open Report`,description:`Open dashboard in browser when starting TDD mode`,checked:i.openReport,onChange:e=>u(`openReport`,e.target.checked)})})})})]}),(0,P.jsx)(`div`,{className:`lg:col-span-2`,children:(0,P.jsx)(R,{hover:!1,children:(0,P.jsxs)(ki,{className:`flex items-center justify-between`,children:[(0,P.jsx)(`div`,{className:`text-sm text-slate-400`,children:l?(0,P.jsx)(`span`,{className:`text-red-400`,children:`Fix validation errors before saving`}):o?(0,P.jsx)(`span`,{className:`text-amber-400`,children:`You have unsaved changes`}):(0,P.jsx)(`span`,{children:`Settings saved to project config`})}),(0,P.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,P.jsx)(L,{variant:`ghost`,onClick:d,disabled:!o||r,children:`Reset`}),(0,P.jsx)(L,{variant:`primary`,onClick:f,loading:r,disabled:!o||l,children:`Save Changes`})]})]})})})]})}function Wo(){let{data:e,isLoading:t,error:n,refetch:r}=Fo(),i=Io(),{addToast:a}=Ja(),o=(0,N.useCallback)((e,t)=>{i.mutate(e,{onSuccess:()=>{t(),a(`Settings saved successfully!`,`success`)},onError:e=>{a(`Failed to save settings: ${e.message}`,`error`)}})},[i,a]);return t?(0,P.jsxs)(`div`,{className:`space-y-6`,children:[(0,P.jsxs)(`div`,{children:[(0,P.jsx)(V,{variant:`heading`,className:`w-32 mb-2`}),(0,P.jsx)(V,{variant:`text`,className:`w-64`})]}),(0,P.jsxs)(`div`,{className:`grid grid-cols-1 lg:grid-cols-2 gap-6`,children:[(0,P.jsxs)(`div`,{className:`space-y-6`,children:[(0,P.jsx)(R,{hover:!1,children:(0,P.jsxs)(ki,{className:`space-y-4`,children:[(0,P.jsx)(V,{variant:`text`,className:`w-24`}),(0,P.jsx)(V,{variant:`text`,className:`w-full h-10`})]})}),(0,P.jsx)(R,{hover:!1,children:(0,P.jsxs)(ki,{className:`space-y-4`,children:[(0,P.jsx)(V,{variant:`text`,className:`w-20`}),(0,P.jsx)(V,{variant:`text`,className:`w-full h-10`}),(0,P.jsx)(V,{variant:`text`,className:`w-full h-10`})]})})]}),(0,P.jsxs)(`div`,{className:`space-y-6`,children:[(0,P.jsx)(R,{hover:!1,children:(0,P.jsxs)(ki,{className:`space-y-4`,children:[(0,P.jsx)(V,{variant:`text`,className:`w-16`}),(0,P.jsx)(V,{variant:`text`,className:`w-full h-10`}),(0,P.jsx)(V,{variant:`text`,className:`w-full h-10`})]})}),(0,P.jsx)(R,{hover:!1,children:(0,P.jsxs)(ki,{className:`space-y-4`,children:[(0,P.jsx)(V,{variant:`text`,className:`w-24`}),(0,P.jsx)(V,{variant:`text`,className:`w-full h-10`})]})})]})]})]}):n?(0,P.jsxs)(`div`,{className:`space-y-6`,children:[(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`h1`,{className:`text-2xl font-bold text-white`,children:`Settings`}),(0,P.jsx)(`p`,{className:`text-slate-400 mt-1`,children:`Configure your Vizzly local development server`})]}),(0,P.jsxs)(xi,{variant:`danger`,title:`Failed to load settings`,dismissible:!0,onDismiss:()=>r(),children:[n.message,(0,P.jsx)(L,{variant:`danger`,size:`sm`,onClick:()=>r(),className:`mt-3`,children:`Retry`})]})]}):(0,P.jsxs)(`div`,{className:`space-y-6`,children:[(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`h1`,{className:`text-2xl font-bold text-white`,children:`Settings`}),(0,P.jsx)(`p`,{className:`text-slate-400 mt-1`,children:`Configure your Vizzly local development server`})]}),(0,P.jsx)(Uo,{config:e?.config,sources:e?.sources,onSave:o,isSaving:i.isPending},JSON.stringify(e?.config))]})}function Go({icon:e,label:t,value:n,subvalue:r,variant:i,iconColor:a}){return(0,P.jsx)(R,{variant:i,hover:!1,children:(0,P.jsx)(ki,{padding:`p-5`,children:(0,P.jsxs)(`div`,{className:`flex items-start justify-between`,children:[(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`p`,{className:`text-xs font-medium text-slate-500 uppercase tracking-wide mb-1`,children:t}),(0,P.jsx)(`p`,{className:`text-2xl font-semibold font-mono text-white`,children:n}),r&&(0,P.jsx)(`p`,{className:`text-xs text-slate-400 mt-1`,children:r})]}),(0,P.jsx)(`div`,{className:`w-10 h-10 rounded-lg flex items-center justify-center ${a}`,children:(0,P.jsx)(e,{className:`w-5 h-5`})})]})})})}function Ko(){let{addToast:e,confirm:t}=Ja(),{data:n,isLoading:r,refetch:i}=di(),a=pi(),o=mi(),s=n?.comparisons,c=n?.baseline,l=s?.length||0,u=s?.filter(e=>e.status===`passed`).length||0,d=s?.filter(e=>e.status===`failed`).length||0,f=s?.filter(e=>ho(e.status)).length||0,p=l>0?Math.round(u/l*100):0,m=s?.some(e=>go(e.status)),h=(0,N.useCallback)(async()=>{await t(`This will update all failed and new screenshots.`,`Accept all changes as new baselines?`)&&a.mutate(void 0,{onSuccess:()=>{e(`All baselines accepted successfully`,`success`)},onError:t=>{console.error(`Failed to accept all baselines:`,t),e(`Failed to accept all baselines. Please try again.`,`error`)}})},[a,e,t]),g=(0,N.useCallback)(async()=>{await t(`This will delete all baseline images and clear comparison history.`,`Reset all baselines?`)&&o.mutate(void 0,{onSuccess:()=>{e(`Baselines reset successfully`,`success`)},onError:t=>{console.error(`Failed to reset baselines:`,t),e(`Failed to reset baselines. Please try again.`,`error`)}})},[o,e,t]);return(0,P.jsxs)(`div`,{className:`space-y-6`,children:[(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`h1`,{className:`text-2xl font-bold text-white`,children:`Statistics`}),(0,P.jsx)(`p`,{className:`text-slate-400 mt-1`,children:`Visual regression testing overview`})]}),(0,P.jsxs)(`div`,{className:`grid grid-cols-1 lg:grid-cols-3 gap-4`,children:[(0,P.jsx)(R,{hover:!1,children:(0,P.jsxs)(ki,{className:`flex flex-col items-center justify-center py-8`,children:[(0,P.jsx)(Ii,{value:p,label:`Pass Rate`}),(0,P.jsxs)(`p`,{className:`text-sm text-slate-400 mt-4`,children:[u,` of `,l,` screenshots passing`]})]})}),(0,P.jsxs)(`div`,{className:`lg:col-span-2 grid grid-cols-2 gap-4`,children:[(0,P.jsx)(Go,{icon:zn,label:`Total Screenshots`,value:l,iconColor:`bg-slate-700/50 text-slate-400`}),(0,P.jsx)(Go,{icon:Ht,label:`Passed`,value:u,variant:`success`,iconColor:`bg-emerald-500/10 text-emerald-400`}),(0,P.jsx)(Go,{icon:hn,label:`Failed`,value:d,variant:d>0?`danger`:void 0,iconColor:`bg-red-500/10 text-red-400`}),(0,P.jsx)(Go,{icon:Vn,label:`New`,value:f,variant:f>0?`info`:void 0,iconColor:`bg-blue-500/10 text-blue-400`})]})]}),c&&(0,P.jsxs)(R,{hover:!1,children:[(0,P.jsx)(Oi,{icon:Qt,title:`Current Baseline`,iconColor:`bg-amber-500/10 text-amber-400`,actions:f>0&&(0,P.jsxs)(Ti,{variant:`warning`,dot:!0,children:[f,` new without baseline`]})}),(0,P.jsx)(ki,{children:(0,P.jsxs)(`div`,{className:`grid grid-cols-2 gap-6`,children:[(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`p`,{className:`text-xs font-medium text-slate-500 uppercase tracking-wide mb-1`,children:`Build Name`}),(0,P.jsx)(`p`,{className:`text-white font-medium`,children:c.buildName||`default`})]}),(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`p`,{className:`text-xs font-medium text-slate-500 uppercase tracking-wide mb-1`,children:`Created`}),(0,P.jsx)(`p`,{className:`text-white font-medium`,children:c.createdAt?new Date(c.createdAt).toLocaleString():`Unknown`})]})]})})]}),(0,P.jsxs)(R,{hover:!1,children:[(0,P.jsx)(Oi,{title:`Baseline Management`,description:`Accept changes to update baselines or reset to restore previous state`}),(0,P.jsxs)(Ai,{className:`flex flex-wrap gap-3`,children:[(0,P.jsx)(L,{variant:`success`,onClick:h,loading:a.isPending,disabled:!m||r,icon:Ht,children:m?`Accept All Changes`:`No Changes to Accept`}),(0,P.jsx)(L,{variant:`secondary`,onClick:g,loading:o.isPending,disabled:r,icon:vt,children:`Reset Baselines`}),(0,P.jsx)(L,{variant:`ghost`,onClick:()=>i(),loading:r,icon:vt,className:`ml-auto`,children:`Refresh`})]})]})]})}function qo(e,t){let n=[];if(t===`shell`)return e.split(`
|
|
218
|
+
`,children:a?(0,P.jsx)(hn,{className:`w-5 h-5`}):(0,P.jsx)(Vn,{className:`w-5 h-5`})}),(0,P.jsxs)(`div`,{className:`flex-1 min-w-0`,children:[(0,P.jsxs)(`div`,{className:`flex items-center gap-3 flex-wrap`,children:[e>0&&(0,P.jsxs)(`span`,{className:`inline-flex items-center gap-1.5 px-2 py-0.5 rounded-md bg-red-500/20 text-red-400 text-sm font-medium`,children:[(0,P.jsx)(`span`,{className:`w-1.5 h-1.5 rounded-full bg-red-400`}),e,` changed`]}),t>0&&(0,P.jsxs)(`span`,{className:`inline-flex items-center gap-1.5 px-2 py-0.5 rounded-md bg-blue-500/20 text-blue-400 text-sm font-medium`,children:[(0,P.jsx)(`span`,{className:`w-1.5 h-1.5 rounded-full bg-blue-400`}),t,` new`]})]}),(0,P.jsx)(`p`,{className:`text-sm text-slate-400 mt-1`,children:a?`Review visual differences and accept to update baselines`:`New screenshots ready to be saved as baselines`})]}),(0,P.jsxs)(L,{variant:`success`,onClick:n,loading:r,icon:Bt,className:`flex-shrink-0`,"data-testid":`btn-accept-all`,children:[`Accept All (`,i,`)`]})]})]})}function Fo(){let[,e]=Fr(),{addToast:t,confirm:n}=Ja(),[r,i]=(0,N.useState)({}),{data:a,isLoading:o,refetch:s}=di(),c=pi(),l=gi(),u=_i(),{filteredComparisons:d,filter:f,setFilter:p,sortBy:m,setSortBy:h,searchQuery:g,setSearchQuery:_,selectedBrowser:v,setSelectedBrowser:y,selectedViewport:b,setSelectedViewport:x,availableFilters:S,counts:ee}=vo(a?.comparisons),C=(0,N.useCallback)(t=>{let n=t.id||t.signature||t.name;n&&e(`/comparison/${encodeURIComponent(n)}`)},[e]),w=(0,N.useCallback)((e,t)=>{i(n=>{let r={...n};for(let n of e)n&&(t?r[n]=t:delete r[n]);return r})},[]),te=(0,N.useCallback)(e=>{!e||e.length===0||(w(e,`accepting`),l.mutate(e,{onSuccess:()=>{w(e,`accepted`)},onError:n=>{let r=No(n?.succeededIds),i=No(n?.failedIds);r.length>0&&w(r,`accepted`),i.length>0?w(i,null):w(e,null),t(`Failed to accept: ${n.message}`,`error`)}}))},[l,t,w]),ne=(0,N.useCallback)(e=>{!e||e.length===0||(w(e,`rejecting`),u.mutate(e,{onSuccess:()=>{w(e,`rejected`)},onError:n=>{let r=No(n?.succeededIds),i=No(n?.failedIds);r.length>0&&w(r,`rejected`),i.length>0?w(i,null):w(e,null),t(`Failed to reject: ${n.message}`,`error`)}}))},[u,t,w]),T=(0,N.useCallback)(e=>te(e?[e]:[]),[te]),re=(0,N.useCallback)(e=>ne(e?[e]:[]),[ne]),E=(0,N.useCallback)(async()=>{await n(`This will update all failed and new screenshots.`,`Accept all changes as new baselines?`)&&c.mutate(void 0,{onSuccess:()=>{t(`All baselines accepted successfully`,`success`)},onError:e=>{console.error(`Failed to accept all baselines:`,e),t(`Failed to accept all baselines. Please try again.`,`error`)}})},[c,t,n]),D=!a?.comparisons||a.comparisons.length===0,O=f!==`all`||g.trim()||v!==`all`||b!==`all`,k=a?.comparisons?.some(e=>go(e.status)),A=a?.comparisons?.filter(e=>e.status===`failed`).length||0,ie=a?.comparisons?.filter(e=>ho(e.status)).length||0;return D?(0,P.jsx)(Mi,{icon:It,title:`No Screenshots Yet`,description:`Run your tests with vizzlyScreenshot() to start capturing visual comparisons.`}):(0,P.jsxs)(`div`,{className:`space-y-6`,children:[(0,P.jsx)(Mo,{filter:f,setFilter:p,sortBy:m,setSortBy:h,searchQuery:g,setSearchQuery:_,selectedBrowser:v,setSelectedBrowser:y,selectedViewport:b,setSelectedViewport:x,availableFilters:S,counts:ee,onRefresh:s,loading:o}),k&&(0,P.jsx)(Po,{failedCount:A,newCount:ie,onAcceptAll:E,isLoading:c.isPending}),d.length===0?(0,P.jsx)(R,{hover:!1,children:(0,P.jsx)(ki,{className:`py-12`,children:(0,P.jsx)(Mi,{icon:xn,title:`No matches`,description:`No comparisons match your current filters.`,action:O&&(0,P.jsx)(L,{variant:`ghost`,size:`sm`,onClick:()=>{p(`all`),_(``),y(`all`),x(`all`)},children:`Clear all filters`})})})}):(0,P.jsx)(Eo,{comparisons:d,onSelectComparison:C,onAcceptComparison:T,onRejectComparison:re,onAcceptGroup:te,onRejectGroup:ne,loadingStates:r})]})}function Io(e={}){return at({queryKey:F.config,queryFn:Jr.get,...e})}function Lo(){let e=We();return ot({mutationFn:Jr.updateProject,onSuccess:()=>{e.invalidateQueries({queryKey:F.config})}})}function Ro(e){return{threshold:String(e?.comparison?.threshold??2),port:String(e?.server?.port??47392),timeout:String(e?.server?.timeout??3e4),buildName:e?.build?.name??`Build {timestamp}`,environment:e?.build?.environment??`test`,openReport:e?.tdd?.openReport??!1}}function zo(e){if(e==null||String(e).trim()===``)return null;let t=Number(e);return Number.isFinite(t)?t:null}function Bo(e){let t=zo(e);return Number.isInteger(t)?t:null}function Vo(e){let t={threshold:null,port:null,timeout:null},n=zo(e.threshold);(n===null||n<0)&&(t.threshold=`Threshold must be a number greater than or equal to 0.`);let r=Bo(e.port);(r===null||r<1||r>65535)&&(t.port=`Port must be a whole number between 1 and 65535.`);let i=Bo(e.timeout);return(i===null||i<0)&&(t.timeout=`Timeout must be a whole number greater than or equal to 0.`),t}function Ho({source:e}){return(0,P.jsx)(Ti,{variant:{default:`default`,project:`info`,global:`purple`,env:`success`,cli:`warning`}[e]||`default`,size:`sm`,children:{default:`Default`,project:`Project`,global:`Global`,env:`Environment`,cli:`CLI Flag`}[e]||e})}function Uo({title:e,source:t,description:n,children:r,noSource:i}){return(0,P.jsxs)(`div`,{className:`space-y-4`,children:[(0,P.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,P.jsx)(`h3`,{className:`text-sm font-semibold text-white uppercase tracking-wide`,children:e}),!i&&(0,P.jsx)(Ho,{source:t||`default`})]}),n&&(0,P.jsx)(`p`,{className:`text-sm text-slate-400`,children:n}),(0,P.jsx)(`div`,{className:`space-y-4`,children:r})]})}function Wo({config:e,sources:t,onSave:n,isSaving:r}){let[i,a]=(0,N.useState)(Ro(e)),[o,s]=(0,N.useState)(!1),c=(0,N.useMemo)(()=>Vo(i),[i]),l=Object.values(c).some(Boolean),u=(0,N.useCallback)((e,t)=>{a(n=>({...n,[e]:t})),s(!0)},[]),d=(0,N.useCallback)(()=>{a(Ro(e)),s(!1)},[e]),f=(0,N.useCallback)(()=>{let e=zo(i.threshold),t=Bo(i.port),r=Bo(i.timeout);e===null||t===null||r===null||n({comparison:{threshold:e},server:{port:t,timeout:r},build:{name:i.buildName,environment:i.environment},tdd:{openReport:i.openReport}},()=>s(!1))},[i,n]);return(0,P.jsxs)(`div`,{className:`grid grid-cols-1 lg:grid-cols-2 gap-6`,children:[(0,P.jsxs)(`div`,{className:`space-y-6`,children:[(0,P.jsx)(R,{hover:!1,children:(0,P.jsx)(ki,{children:(0,P.jsxs)(Uo,{title:`Comparison`,source:t?.comparison,description:`Configure how screenshots are compared`,children:[(0,P.jsx)(B,{label:`Threshold`,type:`number`,value:i.threshold,onChange:e=>u(`threshold`,e.target.value),hint:`CIEDE2000 Delta E. 0 = exact, 2 = recommended`,step:`0.1`,min:`0`}),c.threshold&&(0,P.jsx)(`p`,{className:`-mt-2 text-xs text-red-400`,children:c.threshold})]})})}),(0,P.jsx)(R,{hover:!1,children:(0,P.jsx)(ki,{children:(0,P.jsxs)(Uo,{title:`Server`,source:t?.server,description:`Local screenshot server configuration`,children:[(0,P.jsx)(B,{label:`Port`,type:`number`,value:i.port,onChange:e=>u(`port`,e.target.value),hint:`Default: 47392`}),c.port&&(0,P.jsx)(`p`,{className:`-mt-2 text-xs text-red-400`,children:c.port}),(0,P.jsx)(B,{label:`Timeout`,type:`number`,value:i.timeout,onChange:e=>u(`timeout`,e.target.value),hint:`Request timeout in milliseconds`}),c.timeout&&(0,P.jsx)(`p`,{className:`-mt-2 text-xs text-red-400`,children:c.timeout})]})})})]}),(0,P.jsxs)(`div`,{className:`space-y-6`,children:[(0,P.jsx)(R,{hover:!1,children:(0,P.jsx)(ki,{children:(0,P.jsxs)(Uo,{title:`Build`,source:t?.build,description:`Build naming and environment`,children:[(0,P.jsx)(B,{label:`Name Template`,type:`text`,value:i.buildName,onChange:e=>u(`buildName`,e.target.value),hint:`Use {timestamp} for current time`}),(0,P.jsx)(Pi,{label:`Environment`,value:i.environment,onChange:e=>u(`environment`,e.target.value),options:[{value:`test`,label:`Test`},{value:`development`,label:`Development`},{value:`staging`,label:`Staging`},{value:`production`,label:`Production`}]})]})})}),(0,P.jsx)(R,{hover:!1,children:(0,P.jsx)(ki,{children:(0,P.jsx)(Uo,{title:`TDD Mode`,source:t?.tdd,description:`Development workflow preferences`,children:(0,P.jsx)(Fi,{label:`Auto-open Report`,description:`Open dashboard in browser when starting TDD mode`,checked:i.openReport,onChange:e=>u(`openReport`,e.target.checked)})})})})]}),(0,P.jsx)(`div`,{className:`lg:col-span-2`,children:(0,P.jsx)(R,{hover:!1,children:(0,P.jsxs)(ki,{className:`flex items-center justify-between`,children:[(0,P.jsx)(`div`,{className:`text-sm text-slate-400`,children:l?(0,P.jsx)(`span`,{className:`text-red-400`,children:`Fix validation errors before saving`}):o?(0,P.jsx)(`span`,{className:`text-amber-400`,children:`You have unsaved changes`}):(0,P.jsx)(`span`,{children:`Settings saved to project config`})}),(0,P.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,P.jsx)(L,{variant:`ghost`,onClick:d,disabled:!o||r,children:`Reset`}),(0,P.jsx)(L,{variant:`primary`,onClick:f,loading:r,disabled:!o||l,children:`Save Changes`})]})]})})})]})}function Go(){let{data:e,isLoading:t,error:n,refetch:r}=Io(),i=Lo(),{addToast:a}=Ja(),o=(0,N.useCallback)((e,t)=>{i.mutate(e,{onSuccess:()=>{t(),a(`Settings saved successfully!`,`success`)},onError:e=>{a(`Failed to save settings: ${e.message}`,`error`)}})},[i,a]);return t?(0,P.jsxs)(`div`,{className:`space-y-6`,children:[(0,P.jsxs)(`div`,{children:[(0,P.jsx)(V,{variant:`heading`,className:`w-32 mb-2`}),(0,P.jsx)(V,{variant:`text`,className:`w-64`})]}),(0,P.jsxs)(`div`,{className:`grid grid-cols-1 lg:grid-cols-2 gap-6`,children:[(0,P.jsxs)(`div`,{className:`space-y-6`,children:[(0,P.jsx)(R,{hover:!1,children:(0,P.jsxs)(ki,{className:`space-y-4`,children:[(0,P.jsx)(V,{variant:`text`,className:`w-24`}),(0,P.jsx)(V,{variant:`text`,className:`w-full h-10`})]})}),(0,P.jsx)(R,{hover:!1,children:(0,P.jsxs)(ki,{className:`space-y-4`,children:[(0,P.jsx)(V,{variant:`text`,className:`w-20`}),(0,P.jsx)(V,{variant:`text`,className:`w-full h-10`}),(0,P.jsx)(V,{variant:`text`,className:`w-full h-10`})]})})]}),(0,P.jsxs)(`div`,{className:`space-y-6`,children:[(0,P.jsx)(R,{hover:!1,children:(0,P.jsxs)(ki,{className:`space-y-4`,children:[(0,P.jsx)(V,{variant:`text`,className:`w-16`}),(0,P.jsx)(V,{variant:`text`,className:`w-full h-10`}),(0,P.jsx)(V,{variant:`text`,className:`w-full h-10`})]})}),(0,P.jsx)(R,{hover:!1,children:(0,P.jsxs)(ki,{className:`space-y-4`,children:[(0,P.jsx)(V,{variant:`text`,className:`w-24`}),(0,P.jsx)(V,{variant:`text`,className:`w-full h-10`})]})})]})]})]}):n?(0,P.jsxs)(`div`,{className:`space-y-6`,children:[(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`h1`,{className:`text-2xl font-bold text-white`,children:`Settings`}),(0,P.jsx)(`p`,{className:`text-slate-400 mt-1`,children:`Configure your Vizzly local development server`})]}),(0,P.jsxs)(xi,{variant:`danger`,title:`Failed to load settings`,dismissible:!0,onDismiss:()=>r(),children:[n.message,(0,P.jsx)(L,{variant:`danger`,size:`sm`,onClick:()=>r(),className:`mt-3`,children:`Retry`})]})]}):(0,P.jsxs)(`div`,{className:`space-y-6`,children:[(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`h1`,{className:`text-2xl font-bold text-white`,children:`Settings`}),(0,P.jsx)(`p`,{className:`text-slate-400 mt-1`,children:`Configure your Vizzly local development server`})]}),(0,P.jsx)(Wo,{config:e?.config,sources:e?.sources,onSave:o,isSaving:i.isPending},JSON.stringify(e?.config))]})}function Ko({icon:e,label:t,value:n,subvalue:r,variant:i,iconColor:a}){return(0,P.jsx)(R,{variant:i,hover:!1,children:(0,P.jsx)(ki,{padding:`p-5`,children:(0,P.jsxs)(`div`,{className:`flex items-start justify-between`,children:[(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`p`,{className:`text-xs font-medium text-slate-500 uppercase tracking-wide mb-1`,children:t}),(0,P.jsx)(`p`,{className:`text-2xl font-semibold font-mono text-white`,children:n}),r&&(0,P.jsx)(`p`,{className:`text-xs text-slate-400 mt-1`,children:r})]}),(0,P.jsx)(`div`,{className:`w-10 h-10 rounded-lg flex items-center justify-center ${a}`,children:(0,P.jsx)(e,{className:`w-5 h-5`})})]})})})}function qo(){let{addToast:e,confirm:t}=Ja(),{data:n,isLoading:r,refetch:i}=di(),a=pi(),o=mi(),s=n?.comparisons,c=n?.baseline,l=s?.length||0,u=s?.filter(e=>e.status===`passed`).length||0,d=s?.filter(e=>e.status===`failed`).length||0,f=s?.filter(e=>ho(e.status)).length||0,p=l>0?Math.round(u/l*100):0,m=s?.some(e=>go(e.status)),h=(0,N.useCallback)(async()=>{await t(`This will update all failed and new screenshots.`,`Accept all changes as new baselines?`)&&a.mutate(void 0,{onSuccess:()=>{e(`All baselines accepted successfully`,`success`)},onError:t=>{console.error(`Failed to accept all baselines:`,t),e(`Failed to accept all baselines. Please try again.`,`error`)}})},[a,e,t]),g=(0,N.useCallback)(async()=>{await t(`This will delete all baseline images and clear comparison history.`,`Reset all baselines?`)&&o.mutate(void 0,{onSuccess:()=>{e(`Baselines reset successfully`,`success`)},onError:t=>{console.error(`Failed to reset baselines:`,t),e(`Failed to reset baselines. Please try again.`,`error`)}})},[o,e,t]);return(0,P.jsxs)(`div`,{className:`space-y-6`,children:[(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`h1`,{className:`text-2xl font-bold text-white`,children:`Statistics`}),(0,P.jsx)(`p`,{className:`text-slate-400 mt-1`,children:`Visual regression testing overview`})]}),(0,P.jsxs)(`div`,{className:`grid grid-cols-1 lg:grid-cols-3 gap-4`,children:[(0,P.jsx)(R,{hover:!1,children:(0,P.jsxs)(ki,{className:`flex flex-col items-center justify-center py-8`,children:[(0,P.jsx)(Ii,{value:p,label:`Pass Rate`}),(0,P.jsxs)(`p`,{className:`text-sm text-slate-400 mt-4`,children:[u,` of `,l,` screenshots passing`]})]})}),(0,P.jsxs)(`div`,{className:`lg:col-span-2 grid grid-cols-2 gap-4`,children:[(0,P.jsx)(Ko,{icon:zn,label:`Total Screenshots`,value:l,iconColor:`bg-slate-700/50 text-slate-400`}),(0,P.jsx)(Ko,{icon:Ht,label:`Passed`,value:u,variant:`success`,iconColor:`bg-emerald-500/10 text-emerald-400`}),(0,P.jsx)(Ko,{icon:hn,label:`Failed`,value:d,variant:d>0?`danger`:void 0,iconColor:`bg-red-500/10 text-red-400`}),(0,P.jsx)(Ko,{icon:Vn,label:`New`,value:f,variant:f>0?`info`:void 0,iconColor:`bg-blue-500/10 text-blue-400`})]})]}),c&&(0,P.jsxs)(R,{hover:!1,children:[(0,P.jsx)(Oi,{icon:Qt,title:`Current Baseline`,iconColor:`bg-amber-500/10 text-amber-400`,actions:f>0&&(0,P.jsxs)(Ti,{variant:`warning`,dot:!0,children:[f,` new without baseline`]})}),(0,P.jsx)(ki,{children:(0,P.jsxs)(`div`,{className:`grid grid-cols-2 gap-6`,children:[(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`p`,{className:`text-xs font-medium text-slate-500 uppercase tracking-wide mb-1`,children:`Build Name`}),(0,P.jsx)(`p`,{className:`text-white font-medium`,children:c.buildName||`default`})]}),(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`p`,{className:`text-xs font-medium text-slate-500 uppercase tracking-wide mb-1`,children:`Created`}),(0,P.jsx)(`p`,{className:`text-white font-medium`,children:c.createdAt?new Date(c.createdAt).toLocaleString():`Unknown`})]})]})})]}),(0,P.jsxs)(R,{hover:!1,children:[(0,P.jsx)(Oi,{title:`Baseline Management`,description:`Accept changes to update baselines or reset to restore previous state`}),(0,P.jsxs)(Ai,{className:`flex flex-wrap gap-3`,children:[(0,P.jsx)(L,{variant:`success`,onClick:h,loading:a.isPending,disabled:!m||r,icon:Ht,children:m?`Accept All Changes`:`No Changes to Accept`}),(0,P.jsx)(L,{variant:`secondary`,onClick:g,loading:o.isPending,disabled:r,icon:vt,children:`Reset Baselines`}),(0,P.jsx)(L,{variant:`ghost`,onClick:()=>i(),loading:r,icon:vt,className:`ml-auto`,children:`Refresh`})]})]})]})}function Jo(e,t){let n=[];if(t===`shell`)return e.split(`
|
|
219
219
|
`).forEach((t,r)=>{let i=e.split(`
|
|
220
220
|
`).slice(0,r).join(`
|
|
221
|
-
`).length+ +(r>0);t.trim().startsWith(`#`)?n.push({type:`comment`,start:i,end:i+t.length}):(t.trim().startsWith(`npm`)||t.trim().startsWith(`npx`))&&n.push({type:`command`,start:i,end:i+t.length})}),n;for(let{type:t,regex:r}of[{type:`comment`,regex:/\/\/.*$/gm},{type:`comment`,regex:/\/\*[\s\S]*?\*\//g},{type:`string`,regex:/'([^'\\]|\\.)*'/g},{type:`string`,regex:/"([^"\\]|\\.)*"/g},{type:`string`,regex:/`([^`\\]|\\.)*`/g},{type:`keyword`,regex:/\b(import|export|from|const|let|var|function|class|if|else|return|await|async|test|it|describe|expect|func|let|class|import|require|RSpec|do|end|def|Vizzly)\b/g},{type:`function`,regex:/\b([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/g},{type:`number`,regex:/\b\d+\.?\d*\b/g}]){let i=r.exec(e);for(;i!==null;)n.push({type:t,start:i.index,end:i.index+i[0].length}),i=r.exec(e)}n.sort((e,t)=>e.start-t.start);let r=[],i=0;return n.forEach(e=>{e.start>=i&&(r.push(e),i=e.end)}),r}function
|
|
221
|
+
`).length+ +(r>0);t.trim().startsWith(`#`)?n.push({type:`comment`,start:i,end:i+t.length}):(t.trim().startsWith(`npm`)||t.trim().startsWith(`npx`))&&n.push({type:`command`,start:i,end:i+t.length})}),n;for(let{type:t,regex:r}of[{type:`comment`,regex:/\/\/.*$/gm},{type:`comment`,regex:/\/\*[\s\S]*?\*\//g},{type:`string`,regex:/'([^'\\]|\\.)*'/g},{type:`string`,regex:/"([^"\\]|\\.)*"/g},{type:`string`,regex:/`([^`\\]|\\.)*`/g},{type:`keyword`,regex:/\b(import|export|from|const|let|var|function|class|if|else|return|await|async|test|it|describe|expect|func|let|class|import|require|RSpec|do|end|def|Vizzly)\b/g},{type:`function`,regex:/\b([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/g},{type:`number`,regex:/\b\d+\.?\d*\b/g}]){let i=r.exec(e);for(;i!==null;)n.push({type:t,start:i.index,end:i.index+i[0].length}),i=r.exec(e)}n.sort((e,t)=>e.start-t.start);let r=[],i=0;return n.forEach(e=>{e.start>=i&&(r.push(e),i=e.end)}),r}function Yo(e){return`code-token code-token--${e}`}function Xo(e,t){let n=Jo(e,t),r=[],i=0;return n.forEach((t,n)=>{t.start>i&&r.push(e.slice(i,t.start)),r.push((0,P.jsx)(`span`,{className:Yo(t.type),children:e.slice(t.start,t.end)},`${t.type}-${n}`)),i=t.end}),i<e.length&&r.push(e.slice(i)),r}function Zo({code:e,language:t=`javascript`}){return(0,P.jsx)(`pre`,{className:`bg-slate-950 rounded-lg p-4 overflow-x-auto text-sm`,children:(0,P.jsx)(`code`,{className:`font-mono text-slate-300 block whitespace-pre`,children:Xo(e,t===`shell`||e.trim().startsWith(`#`)||e.trim().startsWith(`npm`)?`shell`:t)})})}var Qo=[{id:`playwright`,name:`Playwright`,code:`import { vizzlyScreenshot } from '@vizzly-testing/cli/client';
|
|
222
222
|
|
|
223
223
|
test('homepage', async ({ page }) => {
|
|
224
224
|
await page.goto('/');
|
|
@@ -267,4 +267,4 @@ class VisualTests: XCTestCase {
|
|
|
267
267
|
# Just run the CLI against your Storybook build:
|
|
268
268
|
|
|
269
269
|
npm run build-storybook
|
|
270
|
-
npx vizzly storybook ./storybook-static`}];function
|
|
270
|
+
npx vizzly storybook ./storybook-static`}];function $o(){let[e,t]=(0,N.useState)(`playwright`),n=Qo.find(t=>t.id===e);return(0,P.jsx)(`div`,{className:`flex flex-col items-center justify-center py-12 px-6`,children:(0,P.jsxs)(`div`,{className:`max-w-2xl w-full`,children:[(0,P.jsxs)(`div`,{className:`text-center mb-8`,children:[(0,P.jsx)(`div`,{className:`w-16 h-16 rounded-2xl bg-amber-500/10 flex items-center justify-center mx-auto mb-5`,children:(0,P.jsx)(Qt,{className:`w-8 h-8 text-amber-400`})}),(0,P.jsx)(`h1`,{className:`text-2xl font-bold text-white mb-2`,children:`Waiting for Screenshots`}),(0,P.jsx)(`p`,{className:`text-slate-400`,children:`Run your tests to start capturing visual comparisons`}),(0,P.jsxs)(`div`,{className:`mt-4 flex items-center justify-center gap-2 text-sm text-slate-500`,children:[(0,P.jsx)(`div`,{className:`w-2 h-2 rounded-full bg-amber-500 animate-pulse`}),`Listening for screenshots...`]})]}),(0,P.jsxs)(`div`,{className:`vz-card overflow-hidden`,children:[(0,P.jsx)(`div`,{className:`border-b border-slate-700/50 px-4 pt-4`,children:(0,P.jsx)(`div`,{className:`flex items-center gap-1 overflow-x-auto scrollbar-hide -mb-px`,children:Qo.map(n=>(0,P.jsx)(`button`,{type:`button`,onClick:()=>t(n.id),className:`px-4 py-2.5 text-sm font-medium rounded-t-lg transition-colors whitespace-nowrap ${e===n.id?`bg-slate-800 text-white border-t border-x border-slate-700/50`:`text-slate-400 hover:text-white hover:bg-slate-800/50`}`,children:n.name},n.id))})}),(0,P.jsx)(`div`,{className:`p-4`,children:(0,P.jsx)(Zo,{code:n?.code||``})})]}),(0,P.jsx)(`div`,{className:`mt-6 text-center`,children:(0,P.jsxs)(`a`,{href:`https://docs.vizzly.dev/`,target:`_blank`,rel:`noopener noreferrer`,className:`inline-flex items-center gap-2 text-sm text-slate-400 hover:text-amber-400 transition-colors`,children:[(0,P.jsx)(Mt,{className:`w-4 h-4`}),`View full documentation`]})})]})})}function es(){return(0,P.jsxs)(`div`,{className:`flex flex-col items-center justify-center py-32`,children:[(0,P.jsx)(Sa,{size:`lg`,className:`text-amber-400 mb-4`}),(0,P.jsx)(`p`,{className:`text-slate-400 text-sm`,children:`Loading report data...`})]})}function ts({error:e,onRetry:t}){return(0,P.jsx)(Mi,{icon:hn,title:`Failed to load report`,description:e||`An unexpected error occurred while loading the report data.`,action:(0,P.jsx)(`button`,{type:`button`,onClick:t,className:`px-4 py-2 bg-amber-500 hover:bg-amber-400 text-slate-900 font-medium rounded-lg transition-colors`,children:`Try Again`})})}function ns(){let[e,t]=Fr(),n=e===`/settings`||e===`/builds`,{data:r,isLoading:i,error:a,refetch:o}=di({enabled:!n,polling:!n}),s=e.startsWith(`/comparison/`),c=e===`/stats`?`stats`:e===`/settings`?`settings`:e===`/builds`?`builds`:`comparisons`,l=e=>{t(e===`stats`?`/stats`:e===`settings`?`/settings`:e===`builds`?`/builds`:`/`)};return i&&!r&&!n?(0,P.jsx)(za,{currentView:c,onNavigate:l,loading:i,children:(0,P.jsx)(es,{})}):a&&!r&&!n?(0,P.jsx)(za,{currentView:c,onNavigate:l,loading:i,children:(0,P.jsx)(ts,{error:a.message,onRetry:o})}):!r&&!n?(0,P.jsx)(za,{currentView:c,onNavigate:l,loading:i,children:(0,P.jsx)($o,{})}):s?r?(0,P.jsx)(Ur,{children:(0,P.jsx)(Vr,{path:`/comparison/:id`,children:(0,P.jsx)(fo,{})})}):(0,P.jsx)(za,{currentView:`comparisons`,onNavigate:l,loading:i,children:(0,P.jsx)($o,{})}):(0,P.jsx)(za,{currentView:c,onNavigate:l,loading:i,children:(0,P.jsxs)(Ur,{children:[(0,P.jsx)(Vr,{path:`/stats`,children:r?(0,P.jsx)(qo,{}):(0,P.jsx)($o,{})}),(0,P.jsx)(Vr,{path:`/settings`,children:(0,P.jsx)(Go,{})}),(0,P.jsx)(Vr,{path:`/builds`,children:(0,P.jsx)(io,{})}),(0,P.jsx)(Vr,{path:`/`,children:r?(0,P.jsx)(Fo,{}):(0,P.jsx)($o,{})})]})})}var rs=new Re({defaultOptions:{queries:{staleTime:5*1e3,refetchOnWindowFocus:!0,retry:1}}}),is=()=>{let e=document.getElementById(`vizzly-reporter-root`);e||(e=document.createElement(`div`),e.id=`vizzly-reporter-root`,document.body.appendChild(e)),nr.createRoot(e).render((0,P.jsx)(N.StrictMode,{children:(0,P.jsx)(Ge,{client:rs,children:(0,P.jsx)($r,{children:(0,P.jsx)(qa,{children:(0,P.jsx)(ns,{})})})})}))};document.readyState===`loading`?document.addEventListener(`DOMContentLoaded`,is):is()})();
|
package/dist/sdk/index.js
CHANGED
|
@@ -11,13 +11,15 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { EventEmitter } from 'node:events';
|
|
14
|
+
import { createServer } from 'node:http';
|
|
14
15
|
import { VizzlyError } from '../errors/vizzly-error.js';
|
|
15
|
-
import {
|
|
16
|
-
import { createUploader } from '../services/uploader.js';
|
|
16
|
+
import { handleRequest as handleScreenshotRequest, startServer as startScreenshotServer, stopServer as stopScreenshotServer } from '../screenshot-server/index.js';
|
|
17
17
|
import { createTDDService } from '../tdd/tdd-service.js';
|
|
18
|
+
import { createUploader } from '../uploader/index.js';
|
|
18
19
|
import { loadConfig } from '../utils/config-loader.js';
|
|
19
20
|
import { resolveImageBuffer } from '../utils/file-helpers.js';
|
|
20
21
|
import * as output from '../utils/output.js';
|
|
22
|
+
import { createScreenshotProperties } from '../utils/screenshot-options.js';
|
|
21
23
|
|
|
22
24
|
/**
|
|
23
25
|
* Create a new Vizzly instance with custom configuration
|
|
@@ -52,79 +54,94 @@ import * as output from '../utils/output.js';
|
|
|
52
54
|
* // Cleanup
|
|
53
55
|
* await vizzly.stop();
|
|
54
56
|
*/
|
|
55
|
-
export function createVizzly(config = {}, options = {}) {
|
|
57
|
+
export async function createVizzly(config = {}, options = {}) {
|
|
56
58
|
// Configure output based on options
|
|
57
59
|
output.configure({
|
|
58
60
|
verbose: options.verbose || false
|
|
59
61
|
});
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
let loadConfigFn = options.loadConfig || loadConfig;
|
|
63
|
+
let fileConfig = await loadConfigFn();
|
|
64
|
+
let resolvedConfig = {
|
|
65
|
+
...fileConfig,
|
|
63
66
|
...config
|
|
64
67
|
};
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
68
|
+
return new VizzlySDK(resolvedConfig, {
|
|
69
|
+
createUploader: (uploaderOptions = {}, activeConfig = resolvedConfig) => {
|
|
70
|
+
let {
|
|
71
|
+
upload: uploadConfig = activeConfig.upload,
|
|
72
|
+
...serviceOptions
|
|
73
|
+
} = uploaderOptions;
|
|
74
|
+
let createUploaderService = options.createUploader || createUploader;
|
|
75
|
+
return createUploaderService({
|
|
76
|
+
apiKey: activeConfig.apiKey,
|
|
77
|
+
apiUrl: activeConfig.apiUrl,
|
|
78
|
+
upload: uploadConfig
|
|
79
|
+
}, {
|
|
80
|
+
...options,
|
|
81
|
+
...serviceOptions
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
createTDDService: (tddOptions = {}, activeConfig = resolvedConfig) => {
|
|
85
|
+
let createTDDServiceInstance = options.createTDDService || createTDDService;
|
|
86
|
+
return createTDDServiceInstance(activeConfig, {
|
|
87
|
+
...options,
|
|
88
|
+
...tddOptions
|
|
89
|
+
});
|
|
90
|
+
},
|
|
91
|
+
createScreenshotServer: createLocalScreenshotServer,
|
|
92
|
+
fetch: options.fetch,
|
|
93
|
+
loadConfig: loadConfigFn
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
function createLocalBuildManager() {
|
|
97
|
+
let screenshots = new Map();
|
|
98
|
+
return {
|
|
99
|
+
async addScreenshot(buildId, screenshot) {
|
|
100
|
+
if (!screenshots.has(buildId)) {
|
|
101
|
+
screenshots.set(buildId, []);
|
|
102
|
+
}
|
|
103
|
+
screenshots.get(buildId).push(screenshot);
|
|
104
|
+
},
|
|
105
|
+
getScreenshots(buildId) {
|
|
106
|
+
return screenshots.get(buildId) || [];
|
|
107
|
+
}
|
|
104
108
|
};
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
}
|
|
110
|
+
function createLocalScreenshotServer(config, buildManager, options = {}) {
|
|
111
|
+
let server = null;
|
|
112
|
+
let deps = {
|
|
113
|
+
createHttpServer: options.createHttpServer || createServer,
|
|
114
|
+
output: options.output || output,
|
|
115
|
+
createError: options.createError || ((message, code) => new VizzlyError(message, code))
|
|
112
116
|
};
|
|
113
117
|
return {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
118
|
+
async start() {
|
|
119
|
+
server = await startScreenshotServer({
|
|
120
|
+
config,
|
|
121
|
+
requestHandler: (req, res) => handleScreenshotRequest({
|
|
122
|
+
req,
|
|
123
|
+
res,
|
|
124
|
+
deps: {
|
|
125
|
+
buildManager,
|
|
126
|
+
createError: deps.createError,
|
|
127
|
+
output: deps.output
|
|
128
|
+
}
|
|
129
|
+
}),
|
|
130
|
+
deps
|
|
131
|
+
});
|
|
132
|
+
},
|
|
133
|
+
async stop() {
|
|
134
|
+
await stopScreenshotServer({
|
|
135
|
+
server,
|
|
136
|
+
deps: {
|
|
137
|
+
output: deps.output
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
server = null;
|
|
141
|
+
},
|
|
142
|
+
isRunning() {
|
|
143
|
+
return Boolean(server?.listening);
|
|
144
|
+
}
|
|
128
145
|
};
|
|
129
146
|
}
|
|
130
147
|
|
|
@@ -153,7 +170,7 @@ export class VizzlySDK extends EventEmitter {
|
|
|
153
170
|
constructor(config, services) {
|
|
154
171
|
super();
|
|
155
172
|
this.config = config;
|
|
156
|
-
this.services = services;
|
|
173
|
+
this.services = services || {};
|
|
157
174
|
this.server = null;
|
|
158
175
|
this.currentBuildId = null;
|
|
159
176
|
}
|
|
@@ -181,6 +198,59 @@ export class VizzlySDK extends EventEmitter {
|
|
|
181
198
|
};
|
|
182
199
|
}
|
|
183
200
|
|
|
201
|
+
/**
|
|
202
|
+
* Merge new config values into the active SDK config.
|
|
203
|
+
* @param {Object} newConfig - Config updates
|
|
204
|
+
* @returns {Object} Updated config
|
|
205
|
+
*/
|
|
206
|
+
updateConfig(newConfig) {
|
|
207
|
+
Object.assign(this.config, newConfig);
|
|
208
|
+
return this.getConfig();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Reload file config and re-apply current in-memory overrides.
|
|
213
|
+
* @returns {Promise<Object>} Updated config
|
|
214
|
+
*/
|
|
215
|
+
async init() {
|
|
216
|
+
let loadConfigFn = this.services.loadConfig || loadConfig;
|
|
217
|
+
let fileConfig = await loadConfigFn();
|
|
218
|
+
let currentConfig = {
|
|
219
|
+
...this.config
|
|
220
|
+
};
|
|
221
|
+
Object.assign(this.config, fileConfig, currentConfig);
|
|
222
|
+
return this.getConfig();
|
|
223
|
+
}
|
|
224
|
+
createUploader(options = {}) {
|
|
225
|
+
let {
|
|
226
|
+
upload: uploadConfig = this.config.upload,
|
|
227
|
+
...serviceOptions
|
|
228
|
+
} = options;
|
|
229
|
+
let createUploaderService = this.services.createUploader || ((uploaderOptions, activeConfig = this.config) => {
|
|
230
|
+
let {
|
|
231
|
+
upload: resolvedUploadConfig = activeConfig.upload,
|
|
232
|
+
...resolvedServiceOptions
|
|
233
|
+
} = uploaderOptions;
|
|
234
|
+
return createUploader({
|
|
235
|
+
apiKey: activeConfig.apiKey,
|
|
236
|
+
apiUrl: activeConfig.apiUrl,
|
|
237
|
+
upload: resolvedUploadConfig
|
|
238
|
+
}, resolvedServiceOptions);
|
|
239
|
+
});
|
|
240
|
+
return createUploaderService({
|
|
241
|
+
upload: uploadConfig,
|
|
242
|
+
...serviceOptions
|
|
243
|
+
}, this.config);
|
|
244
|
+
}
|
|
245
|
+
createTDDService(options = {}) {
|
|
246
|
+
let createTDDServiceInstance = this.services.createTDDService || ((tddOptions, activeConfig = this.config) => createTDDService(activeConfig, tddOptions));
|
|
247
|
+
return createTDDServiceInstance(options, this.config);
|
|
248
|
+
}
|
|
249
|
+
async startTDD(options = {}) {
|
|
250
|
+
let tddService = this.createTDDService(options);
|
|
251
|
+
return tddService.start(options);
|
|
252
|
+
}
|
|
253
|
+
|
|
184
254
|
/**
|
|
185
255
|
* Start the Vizzly server
|
|
186
256
|
* @returns {Promise<{port: number, url: string}>} Server information
|
|
@@ -193,25 +263,12 @@ export class VizzlySDK extends EventEmitter {
|
|
|
193
263
|
url: `http://localhost:${this.config.server?.port || 3000}`
|
|
194
264
|
};
|
|
195
265
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
screenshots: new Map(),
|
|
200
|
-
currentBuildId: null,
|
|
201
|
-
async addScreenshot(buildId, screenshot) {
|
|
202
|
-
if (!this.screenshots.has(buildId)) {
|
|
203
|
-
this.screenshots.set(buildId, []);
|
|
204
|
-
}
|
|
205
|
-
this.screenshots.get(buildId).push(screenshot);
|
|
206
|
-
},
|
|
207
|
-
getScreenshots(buildId) {
|
|
208
|
-
return this.screenshots.get(buildId) || [];
|
|
209
|
-
}
|
|
210
|
-
};
|
|
211
|
-
this.server = new ScreenshotServer(this.config, buildManager);
|
|
266
|
+
let createScreenshotServer = this.services.createScreenshotServer || createLocalScreenshotServer;
|
|
267
|
+
let buildManager = createLocalBuildManager();
|
|
268
|
+
this.server = createScreenshotServer(this.config, buildManager);
|
|
212
269
|
await this.server.start();
|
|
213
|
-
|
|
214
|
-
|
|
270
|
+
let port = this.config.server?.port || 3000;
|
|
271
|
+
let serverInfo = {
|
|
215
272
|
port,
|
|
216
273
|
url: `http://localhost:${port}`
|
|
217
274
|
};
|
|
@@ -230,31 +287,32 @@ export class VizzlySDK extends EventEmitter {
|
|
|
230
287
|
* @throws {VizzlyError} When file cannot be read due to permissions or I/O errors
|
|
231
288
|
*/
|
|
232
289
|
async screenshot(name, imageBuffer, options = {}) {
|
|
233
|
-
if (
|
|
290
|
+
if (this.server?.isRunning?.() !== true) {
|
|
234
291
|
throw new VizzlyError('Server not running. Call start() first.', 'SERVER_NOT_RUNNING');
|
|
235
292
|
}
|
|
236
293
|
|
|
237
294
|
// Resolve Buffer or file path using shared utility
|
|
238
|
-
|
|
295
|
+
let buffer = resolveImageBuffer(imageBuffer, 'screenshot');
|
|
239
296
|
|
|
240
297
|
// Generate or use provided build ID
|
|
241
|
-
|
|
298
|
+
let buildId = options.buildId || this.currentBuildId || 'default';
|
|
242
299
|
this.currentBuildId = buildId;
|
|
243
300
|
|
|
244
301
|
// Convert Buffer to base64 for JSON transport
|
|
245
|
-
|
|
246
|
-
|
|
302
|
+
let imageBase64 = buffer.toString('base64');
|
|
303
|
+
let screenshotData = {
|
|
247
304
|
buildId,
|
|
248
305
|
name,
|
|
249
306
|
image: imageBase64,
|
|
250
307
|
type: 'base64',
|
|
251
|
-
properties: options
|
|
308
|
+
properties: createScreenshotProperties(options)
|
|
252
309
|
};
|
|
253
310
|
|
|
254
311
|
// POST to the local screenshot server
|
|
255
|
-
|
|
312
|
+
let serverUrl = `http://localhost:${this.config.server?.port || 3000}`;
|
|
313
|
+
let fetchFn = this.services.fetch || fetch;
|
|
256
314
|
try {
|
|
257
|
-
|
|
315
|
+
let response = await fetchFn(`${serverUrl}/screenshot`, {
|
|
258
316
|
method: 'POST',
|
|
259
317
|
headers: {
|
|
260
318
|
'Content-Type': 'application/json'
|
|
@@ -262,7 +320,7 @@ export class VizzlySDK extends EventEmitter {
|
|
|
262
320
|
body: JSON.stringify(screenshotData)
|
|
263
321
|
});
|
|
264
322
|
if (!response.ok) {
|
|
265
|
-
|
|
323
|
+
let errorData = await response.json().catch(() => ({
|
|
266
324
|
error: 'Unknown error'
|
|
267
325
|
}));
|
|
268
326
|
throw new VizzlyError(`Screenshot capture failed: ${errorData.error}`, 'SCREENSHOT_FAILED', {
|
|
@@ -295,23 +353,21 @@ export class VizzlySDK extends EventEmitter {
|
|
|
295
353
|
async upload(options = {}) {
|
|
296
354
|
if (!this.services?.uploader) {
|
|
297
355
|
this.services = this.services || {};
|
|
298
|
-
this.services.uploader = createUploader({
|
|
299
|
-
apiKey: this.config.apiKey,
|
|
300
|
-
apiUrl: this.config.apiUrl,
|
|
356
|
+
this.services.uploader = this.createUploader({
|
|
301
357
|
upload: this.config.upload
|
|
302
358
|
});
|
|
303
359
|
}
|
|
304
360
|
|
|
305
361
|
// Get the screenshots directory from config or default
|
|
306
|
-
|
|
307
|
-
|
|
362
|
+
let screenshotsDir = options.screenshotsDir || this.config?.upload?.screenshotsDir || './screenshots';
|
|
363
|
+
let uploadOptions = {
|
|
308
364
|
screenshotsDir,
|
|
309
365
|
buildName: options.buildName || this.config.buildName,
|
|
310
366
|
branch: options.branch || this.config.branch,
|
|
311
367
|
commit: options.commit || this.config.commit,
|
|
312
368
|
message: options.message || this.config.message,
|
|
313
369
|
environment: options.environment || this.config.environment || 'production',
|
|
314
|
-
threshold: options.threshold
|
|
370
|
+
threshold: options.threshold ?? this.config.threshold,
|
|
315
371
|
onProgress: progress => {
|
|
316
372
|
this.emit('upload:progress', progress);
|
|
317
373
|
if (options.onProgress) {
|
|
@@ -320,7 +376,7 @@ export class VizzlySDK extends EventEmitter {
|
|
|
320
376
|
}
|
|
321
377
|
};
|
|
322
378
|
try {
|
|
323
|
-
|
|
379
|
+
let result = await this.services.uploader.upload(uploadOptions);
|
|
324
380
|
this.emit('upload:completed', result);
|
|
325
381
|
return result;
|
|
326
382
|
} catch (error) {
|
|
@@ -340,13 +396,13 @@ export class VizzlySDK extends EventEmitter {
|
|
|
340
396
|
async compare(name, imageBuffer) {
|
|
341
397
|
if (!this.services?.tddService) {
|
|
342
398
|
this.services = this.services || {};
|
|
343
|
-
this.services.tddService = createTDDService(
|
|
399
|
+
this.services.tddService = this.createTDDService();
|
|
344
400
|
}
|
|
345
401
|
|
|
346
402
|
// Resolve Buffer or file path using shared utility
|
|
347
|
-
|
|
403
|
+
let buffer = resolveImageBuffer(imageBuffer, 'compare');
|
|
348
404
|
try {
|
|
349
|
-
|
|
405
|
+
let result = await this.services.tddService.compareScreenshot(name, buffer);
|
|
350
406
|
this.emit('comparison:completed', result);
|
|
351
407
|
return result;
|
|
352
408
|
} catch (error) {
|
|
@@ -358,10 +414,9 @@ export class VizzlySDK extends EventEmitter {
|
|
|
358
414
|
}
|
|
359
415
|
}
|
|
360
416
|
}
|
|
361
|
-
|
|
362
|
-
// Export service creators for advanced usage
|
|
363
|
-
export { createUploader } from '../services/uploader.js';
|
|
364
417
|
export { createTDDService } from '../tdd/tdd-service.js';
|
|
418
|
+
// Export service creators for advanced usage
|
|
419
|
+
export { createUploader } from '../uploader/index.js';
|
|
365
420
|
// Re-export key utilities and errors
|
|
366
421
|
export { loadConfig } from '../utils/config-loader.js';
|
|
367
422
|
export * as output from '../utils/output.js';
|