@vizzly-testing/cli
Version:
Visual review platform for UI developers and designers
559 lines (558 loc) • 25.3 kB
JavaScript
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
import { renderToString } from "react-dom/server";
const statusConfig = {
failed: {
label: "Changed",
borderClass: "border-l-red-500",
badgeClass: "bg-red-500/15 text-red-400 ring-red-500/30",
dotClass: "bg-red-500",
sectionTitle: "Visual Changes",
sectionIcon: "◐"
},
new: {
label: "New",
borderClass: "border-l-blue-500",
badgeClass: "bg-blue-500/15 text-blue-400 ring-blue-500/30",
dotClass: "bg-blue-500",
sectionTitle: "New Screenshots",
sectionIcon: "+"
},
passed: {
label: "Passed",
borderClass: "border-l-emerald-500",
badgeClass: "bg-emerald-500/15 text-emerald-400 ring-emerald-500/30",
dotClass: "bg-emerald-500",
sectionTitle: "Passed",
sectionIcon: "✓"
},
error: {
label: "Error",
borderClass: "border-l-orange-500",
badgeClass: "bg-orange-500/15 text-orange-400 ring-orange-500/30",
dotClass: "bg-orange-500",
sectionTitle: "Errors",
sectionIcon: "!"
}
};
function StatusBadge({ status }) {
let config = statusConfig[status] || statusConfig.error;
return /* @__PURE__ */ jsxs(
"span",
{
className: `inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-xs font-medium ring-1 ring-inset ${config.badgeClass}`,
children: [
/* @__PURE__ */ jsx("span", { className: `w-1.5 h-1.5 rounded-full ${config.dotClass}` }),
config.label
]
}
);
}
function DiffBadge({ percentage }) {
if (percentage === void 0 || percentage === null || percentage === 0)
return null;
return /* @__PURE__ */ jsxs("span", { className: "font-mono text-xs text-red-400/80 tabular-nums", children: [
percentage.toFixed(2),
"%"
] });
}
function MetaInfo({ properties }) {
if (!properties) return null;
let { viewport_width, viewport_height, browser } = properties;
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs text-slate-500", children: [
viewport_width && viewport_height && /* @__PURE__ */ jsxs("span", { className: "font-mono tabular-nums", children: [
viewport_width,
"×",
viewport_height
] }),
browser && /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx("span", { className: "text-slate-600", children: "·" }),
/* @__PURE__ */ jsx("span", { className: "capitalize", children: browser })
] })
] });
}
function FailedComparison({ comparison, isEven }) {
let { name, status, current, baseline, diff, diffPercentage, properties } = comparison;
let config = statusConfig[status] || statusConfig.failed;
return /* @__PURE__ */ jsxs("details", { className: "group", children: [
/* @__PURE__ */ jsxs(
"summary",
{
className: `
flex items-center gap-4 p-4 cursor-pointer
${isEven ? "bg-slate-800/30" : "bg-slate-800/50"}
hover:bg-slate-700/50
border-l-4 ${config.borderClass}
rounded-r-lg transition-all duration-150
list-none [&::-webkit-details-marker]:hidden
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-amber-500/50 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-900
`,
children: [
/* @__PURE__ */ jsxs("div", { className: "relative w-16 h-10 flex-shrink-0 rounded overflow-hidden bg-slate-900", children: [
(current || baseline) && /* @__PURE__ */ jsx(
"img",
{
src: current || baseline,
alt: "",
className: "w-full h-full object-cover object-top"
}
),
diff && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-red-500/20 flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "w-2 h-2 bg-red-500 rounded-full animate-pulse" }) })
] }),
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
/* @__PURE__ */ jsx("span", { className: "font-medium text-white truncate", children: name }),
/* @__PURE__ */ jsx(DiffBadge, { percentage: diffPercentage })
] }),
/* @__PURE__ */ jsx(MetaInfo, { properties })
] }),
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
/* @__PURE__ */ jsx(StatusBadge, { status }),
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-slate-500 group-hover:text-slate-400 transition-colors", children: [
/* @__PURE__ */ jsx("span", { className: "text-xs hidden sm:inline opacity-0 group-hover:opacity-100 transition-opacity", children: "Details" }),
/* @__PURE__ */ jsx(
"svg",
{
className: "w-5 h-5 transition-transform duration-200 group-open:rotate-180",
fill: "none",
viewBox: "0 0 24 24",
stroke: "currentColor",
"aria-hidden": "true",
children: /* @__PURE__ */ jsx(
"path",
{
strokeLinecap: "round",
strokeLinejoin: "round",
strokeWidth: 2,
d: "M19 9l-7 7-7-7"
}
)
}
)
] })
] })
]
}
),
/* @__PURE__ */ jsxs("div", { className: "mt-1 p-4 bg-slate-800/20 rounded-lg border border-slate-700/30 animate-[fadeIn_150ms_ease-out]", children: [
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-2 gap-4", children: [
baseline && /* @__PURE__ */ jsxs("div", { children: [
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-2", children: [
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-slate-400 uppercase tracking-wider", children: "Baseline" }),
/* @__PURE__ */ jsx("span", { className: "text-xs text-slate-600", children: "Expected" })
] }),
/* @__PURE__ */ jsx(
"a",
{
href: baseline,
target: "_blank",
rel: "noopener noreferrer",
className: "block rounded-lg overflow-hidden border border-slate-700/50 hover:border-slate-600 transition-colors",
children: /* @__PURE__ */ jsx(
"img",
{
src: baseline,
alt: `${name} baseline`,
className: "w-full"
}
)
}
)
] }),
current && /* @__PURE__ */ jsxs("div", { children: [
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-2", children: [
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-slate-400 uppercase tracking-wider", children: "Current" }),
/* @__PURE__ */ jsx("span", { className: "text-xs text-slate-600", children: "Actual" })
] }),
/* @__PURE__ */ jsx(
"a",
{
href: current,
target: "_blank",
rel: "noopener noreferrer",
className: "block rounded-lg overflow-hidden border border-slate-700/50 hover:border-slate-600 transition-colors",
children: /* @__PURE__ */ jsx("img", { src: current, alt: `${name} current`, className: "w-full" })
}
)
] })
] }),
diff && /* @__PURE__ */ jsxs("div", { className: "mt-4 pt-4 border-t border-slate-700/30", children: [
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-2", children: [
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-red-400 uppercase tracking-wider", children: "Difference" }),
diffPercentage > 0 && /* @__PURE__ */ jsxs("span", { className: "text-xs text-red-400/60 font-mono", children: [
diffPercentage.toFixed(2),
"% of pixels changed"
] })
] }),
/* @__PURE__ */ jsx(
"a",
{
href: diff,
target: "_blank",
rel: "noopener noreferrer",
className: "block rounded-lg overflow-hidden border border-red-500/30 hover:border-red-500/50 transition-colors max-w-2xl",
children: /* @__PURE__ */ jsx("img", { src: diff, alt: `${name} diff`, className: "w-full" })
}
)
] })
] })
] });
}
function NewComparison({ comparison, isEven }) {
let { name, current, baseline, properties } = comparison;
let imageSrc = current || baseline;
return /* @__PURE__ */ jsxs("details", { className: "group", children: [
/* @__PURE__ */ jsxs(
"summary",
{
className: `
flex items-center gap-4 p-4 cursor-pointer
${isEven ? "bg-slate-800/30" : "bg-slate-800/50"}
hover:bg-slate-700/50
border-l-4 border-l-blue-500
rounded-r-lg transition-all duration-150
list-none [&::-webkit-details-marker]:hidden
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-amber-500/50 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-900
`,
children: [
/* @__PURE__ */ jsx("div", { className: "w-16 h-10 flex-shrink-0 rounded overflow-hidden bg-slate-900", children: imageSrc && /* @__PURE__ */ jsx(
"img",
{
src: imageSrc,
alt: "",
className: "w-full h-full object-cover object-top"
}
) }),
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
/* @__PURE__ */ jsx("span", { className: "font-medium text-white truncate block", children: name }),
/* @__PURE__ */ jsx(MetaInfo, { properties })
] }),
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
/* @__PURE__ */ jsx(StatusBadge, { status: "new" }),
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-slate-500 group-hover:text-slate-400 transition-colors", children: [
/* @__PURE__ */ jsx("span", { className: "text-xs hidden sm:inline opacity-0 group-hover:opacity-100 transition-opacity", children: "Preview" }),
/* @__PURE__ */ jsx(
"svg",
{
className: "w-5 h-5 transition-transform duration-200 group-open:rotate-180",
fill: "none",
viewBox: "0 0 24 24",
stroke: "currentColor",
"aria-hidden": "true",
children: /* @__PURE__ */ jsx(
"path",
{
strokeLinecap: "round",
strokeLinejoin: "round",
strokeWidth: 2,
d: "M19 9l-7 7-7-7"
}
)
}
)
] })
] })
]
}
),
/* @__PURE__ */ jsxs("div", { className: "mt-1 p-4 bg-slate-800/20 rounded-lg border border-slate-700/30 animate-[fadeIn_150ms_ease-out]", children: [
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-2", children: [
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-blue-400 uppercase tracking-wider", children: "New Screenshot" }),
/* @__PURE__ */ jsx("span", { className: "text-xs text-slate-600", children: "No baseline to compare" })
] }),
imageSrc && /* @__PURE__ */ jsx(
"a",
{
href: imageSrc,
target: "_blank",
rel: "noopener noreferrer",
className: "block rounded-lg overflow-hidden border border-blue-500/30 hover:border-blue-500/50 transition-colors max-w-2xl",
children: /* @__PURE__ */ jsx("img", { src: imageSrc, alt: name, className: "w-full" })
}
)
] })
] });
}
function PassedComparison({ comparison }) {
let { name, properties } = comparison;
return /* @__PURE__ */ jsxs(
"div",
{
className: `
flex items-center gap-3 px-4 py-2.5
bg-slate-800/20 hover:bg-slate-800/30
border-l-4 border-l-emerald-500/50
rounded-r-lg transition-colors
`,
children: [
/* @__PURE__ */ jsx("div", { className: "w-5 h-5 rounded-full bg-emerald-500/20 flex items-center justify-center flex-shrink-0", children: /* @__PURE__ */ jsx(
"svg",
{
className: "w-3 h-3 text-emerald-400",
fill: "currentColor",
viewBox: "0 0 20 20",
"aria-hidden": "true",
children: /* @__PURE__ */ jsx(
"path",
{
fillRule: "evenodd",
d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z",
clipRule: "evenodd"
}
)
}
) }),
/* @__PURE__ */ jsx("span", { className: "flex-1 text-sm text-slate-300 truncate", children: name }),
/* @__PURE__ */ jsx(MetaInfo, { properties })
]
}
);
}
function SectionHeader({ title, count, icon, colorClass }) {
if (count === 0) return null;
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 mb-3", children: [
/* @__PURE__ */ jsx("span", { className: `text-lg font-mono ${colorClass}`, children: icon }),
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-slate-300 uppercase tracking-wider", children: title }),
/* @__PURE__ */ jsxs("span", { className: "text-sm text-slate-500 font-mono", children: [
"(",
count,
")"
] })
] });
}
function SummaryBar({ stats }) {
let hasIssues = stats.failed > 0 || stats.new > 0;
return /* @__PURE__ */ jsxs(
"div",
{
className: `
flex flex-wrap items-center gap-6 p-4 rounded-xl mb-8
${hasIssues ? "bg-slate-800/60 border border-slate-700/50" : "bg-emerald-500/10 border border-emerald-500/20"}
`,
children: [
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-3", children: hasIssues ? /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx("div", { className: "w-10 h-10 rounded-full bg-amber-500/20 flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "text-amber-400 text-lg", children: "◐" }) }),
/* @__PURE__ */ jsxs("div", { children: [
/* @__PURE__ */ jsx("div", { className: "text-white font-semibold", children: "Review Required" }),
/* @__PURE__ */ jsxs("div", { className: "text-sm text-slate-400", children: [
stats.failed + (stats.new || 0),
" items need attention"
] })
] })
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx("div", { className: "w-10 h-10 rounded-full bg-emerald-500/20 flex items-center justify-center", children: /* @__PURE__ */ jsx(
"svg",
{
className: "w-5 h-5 text-emerald-400",
fill: "currentColor",
viewBox: "0 0 20 20",
"aria-hidden": "true",
children: /* @__PURE__ */ jsx(
"path",
{
fillRule: "evenodd",
d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z",
clipRule: "evenodd"
}
)
}
) }),
/* @__PURE__ */ jsxs("div", { children: [
/* @__PURE__ */ jsx("div", { className: "text-white font-semibold", children: "All Tests Passed" }),
/* @__PURE__ */ jsx("div", { className: "text-sm text-slate-400", children: "No visual changes detected" })
] })
] }) }),
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-6 ml-auto", children: [
/* @__PURE__ */ jsxs("div", { className: "text-center", children: [
/* @__PURE__ */ jsx("div", { className: "text-2xl font-bold text-white tabular-nums", children: stats.total }),
/* @__PURE__ */ jsx("div", { className: "text-xs text-slate-500 uppercase tracking-wider", children: "Total" })
] }),
/* @__PURE__ */ jsxs("div", { className: "text-center", children: [
/* @__PURE__ */ jsx("div", { className: "text-2xl font-bold text-emerald-400 tabular-nums", children: stats.passed }),
/* @__PURE__ */ jsx("div", { className: "text-xs text-slate-500 uppercase tracking-wider", children: "Passed" })
] }),
stats.failed > 0 && /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
/* @__PURE__ */ jsx("div", { className: "text-2xl font-bold text-red-400 tabular-nums", children: stats.failed }),
/* @__PURE__ */ jsx("div", { className: "text-xs text-slate-500 uppercase tracking-wider", children: "Changed" })
] }),
(stats.new || 0) > 0 && /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
/* @__PURE__ */ jsx("div", { className: "text-2xl font-bold text-blue-400 tabular-nums", children: stats.new }),
/* @__PURE__ */ jsx("div", { className: "text-xs text-slate-500 uppercase tracking-wider", children: "New" })
] })
] })
]
}
);
}
function formatTimestamp(timestamp) {
if (!timestamp) return null;
let date = new Date(timestamp);
return date.toLocaleDateString(void 0, {
year: "numeric",
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit"
});
}
function Header({ timestamp }) {
return /* @__PURE__ */ jsx("header", { className: "bg-slate-950/80 backdrop-blur-sm border-b border-slate-800/60 sticky top-0 z-10", children: /* @__PURE__ */ jsxs("div", { className: "max-w-6xl mx-auto px-6 py-4 flex items-center justify-between", children: [
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
/* @__PURE__ */ jsx("div", { className: "w-9 h-9 bg-gradient-to-br from-amber-400 to-amber-600 rounded-lg flex items-center justify-center shadow-lg shadow-amber-500/20", children: /* @__PURE__ */ jsxs(
"svg",
{
className: "w-5 h-5 text-slate-900",
viewBox: "0 0 24 24",
fill: "currentColor",
"aria-hidden": "true",
children: [
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "3" }),
/* @__PURE__ */ jsx("path", { d: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z" })
]
}
) }),
/* @__PURE__ */ jsxs("div", { children: [
/* @__PURE__ */ jsx("div", { className: "text-lg font-semibold text-white tracking-tight", children: "Vizzly" }),
/* @__PURE__ */ jsx("div", { className: "text-xs text-slate-500", children: "Visual Test Report" })
] })
] }),
timestamp && /* @__PURE__ */ jsx("div", { className: "text-xs text-slate-500 tabular-nums", children: formatTimestamp(timestamp) })
] }) });
}
function StaticReportView({ reportData }) {
if (!reportData || !reportData.comparisons) {
return /* @__PURE__ */ jsx("div", { className: "min-h-screen bg-slate-900 text-white flex items-center justify-center", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
/* @__PURE__ */ jsx("div", { className: "text-xl font-medium mb-2", children: "No Report Data" }),
/* @__PURE__ */ jsx("p", { className: "text-slate-400", children: "No comparison data available for this report." })
] }) });
}
let { comparisons } = reportData;
let stats = {
total: comparisons.length,
passed: comparisons.filter((c) => c.status === "passed").length,
failed: comparisons.filter((c) => c.status === "failed").length,
new: comparisons.filter((c) => c.status === "new").length,
error: comparisons.filter((c) => c.status === "error").length
};
let failed = comparisons.filter((c) => c.status === "failed");
let newItems = comparisons.filter((c) => c.status === "new");
let passed = comparisons.filter((c) => c.status === "passed");
let errors = comparisons.filter((c) => c.status === "error");
return /* @__PURE__ */ jsxs("div", { className: "min-h-screen bg-slate-900 text-white", children: [
/* @__PURE__ */ jsx(Header, { timestamp: reportData.timestamp }),
/* @__PURE__ */ jsxs("main", { className: "max-w-6xl mx-auto px-6 py-8", children: [
/* @__PURE__ */ jsx(SummaryBar, { stats }),
failed.length > 0 && /* @__PURE__ */ jsxs("section", { className: "mb-8", children: [
/* @__PURE__ */ jsx(
SectionHeader,
{
title: "Visual Changes",
count: failed.length,
icon: "◐",
colorClass: "text-red-400"
}
),
/* @__PURE__ */ jsx("div", { className: "space-y-2", children: failed.map((comparison, index) => /* @__PURE__ */ jsx(
FailedComparison,
{
comparison,
isEven: index % 2 === 0
},
comparison.id || comparison.signature || `failed-${index}`
)) })
] }),
newItems.length > 0 && /* @__PURE__ */ jsxs("section", { className: "mb-8", children: [
/* @__PURE__ */ jsx(
SectionHeader,
{
title: "New Screenshots",
count: newItems.length,
icon: "+",
colorClass: "text-blue-400"
}
),
/* @__PURE__ */ jsx("div", { className: "space-y-2", children: newItems.map((comparison, index) => /* @__PURE__ */ jsx(
NewComparison,
{
comparison,
isEven: index % 2 === 0
},
comparison.id || comparison.signature || `new-${index}`
)) })
] }),
errors.length > 0 && /* @__PURE__ */ jsxs("section", { className: "mb-8", children: [
/* @__PURE__ */ jsx(
SectionHeader,
{
title: "Errors",
count: errors.length,
icon: "!",
colorClass: "text-orange-400"
}
),
/* @__PURE__ */ jsx("div", { className: "space-y-2", children: errors.map((comparison, index) => /* @__PURE__ */ jsx(
FailedComparison,
{
comparison,
isEven: index % 2 === 0
},
comparison.id || comparison.signature || `error-${index}`
)) })
] }),
passed.length > 0 && /* @__PURE__ */ jsx("section", { className: "mb-8", children: /* @__PURE__ */ jsxs("details", { className: "group", children: [
/* @__PURE__ */ jsx("summary", { className: "cursor-pointer list-none [&::-webkit-details-marker]:hidden", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 mb-3", children: [
/* @__PURE__ */ jsx("span", { className: "text-lg font-mono text-emerald-400", children: "✓" }),
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-slate-300 uppercase tracking-wider", children: "Passed" }),
/* @__PURE__ */ jsxs("span", { className: "text-sm text-slate-500 font-mono", children: [
"(",
passed.length,
")"
] }),
/* @__PURE__ */ jsx(
"svg",
{
className: "w-4 h-4 text-slate-500 transition-transform group-open:rotate-180 ml-auto",
fill: "none",
viewBox: "0 0 24 24",
stroke: "currentColor",
"aria-hidden": "true",
children: /* @__PURE__ */ jsx(
"path",
{
strokeLinecap: "round",
strokeLinejoin: "round",
strokeWidth: 2,
d: "M19 9l-7 7-7-7"
}
)
}
)
] }) }),
/* @__PURE__ */ jsx("div", { className: "space-y-1", children: passed.map((comparison, index) => /* @__PURE__ */ jsx(
PassedComparison,
{
comparison
},
comparison.id || comparison.signature || `passed-${index}`
)) })
] }) }),
/* @__PURE__ */ jsx("footer", { className: "mt-12 pt-6 border-t border-slate-800/60 text-center", children: /* @__PURE__ */ jsxs("p", { className: "text-sm text-slate-500", children: [
"Visual regression report generated by",
" ",
/* @__PURE__ */ jsx(
"a",
{
href: "https://vizzly.dev",
className: "text-amber-400 hover:text-amber-300 transition-colors",
children: "Vizzly"
}
)
] }) })
] })
] });
}
function renderStaticReport(reportData) {
return renderToString(/* @__PURE__ */ jsx(StaticReportView, { reportData }));
}
export {
renderStaticReport
};