@vizzly-testing/cli 0.29.2 → 0.29.4

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.
@@ -1,5 +1,8 @@
1
1
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
2
  import { renderToString } from "react-dom/server";
3
+ function isNewComparisonStatus(status) {
4
+ return status === "new" || status === "baseline-created";
5
+ }
3
6
  const statusConfig = {
4
7
  failed: {
5
8
  label: "Changed",
@@ -430,11 +433,11 @@ function StaticReportView({ reportData }) {
430
433
  total: comparisons.length,
431
434
  passed: comparisons.filter((c) => c.status === "passed").length,
432
435
  failed: comparisons.filter((c) => c.status === "failed").length,
433
- new: comparisons.filter((c) => c.status === "new").length,
436
+ new: comparisons.filter((c) => isNewComparisonStatus(c.status)).length,
434
437
  error: comparisons.filter((c) => c.status === "error").length
435
438
  };
436
439
  let failed = comparisons.filter((c) => c.status === "failed");
437
- let newItems = comparisons.filter((c) => c.status === "new");
440
+ let newItems = comparisons.filter((c) => isNewComparisonStatus(c.status));
438
441
  let passed = comparisons.filter((c) => c.status === "passed");
439
442
  let errors = comparisons.filter((c) => c.status === "error");
440
443
  return /* @__PURE__ */ jsxs("div", { className: "min-h-screen bg-slate-900 text-white", children: [
@@ -58,6 +58,78 @@ export function createEventsRouter(context) {
58
58
  res.write(`event: ${eventType}\n`);
59
59
  res.write(`data: ${JSON.stringify(data)}\n\n`);
60
60
  };
61
+
62
+ /**
63
+ * Build a lookup map from comparisons array keyed by id
64
+ */
65
+ const buildComparisonMap = comparisons => {
66
+ let map = new Map();
67
+ for (let c of comparisons) {
68
+ map.set(c.id, c);
69
+ }
70
+ return map;
71
+ };
72
+ const comparisonChanged = (oldComp, newComp) => {
73
+ return JSON.stringify(oldComp) !== JSON.stringify(newComp);
74
+ };
75
+
76
+ /**
77
+ * Extract summary fields (everything except comparisons) for diffing
78
+ */
79
+ const extractSummary = data => {
80
+ let {
81
+ comparisons: _c,
82
+ ...summary
83
+ } = data;
84
+ return summary;
85
+ };
86
+
87
+ /**
88
+ * Check if summary-level fields changed between old and new data
89
+ */
90
+ const summaryChanged = (oldData, newData) => {
91
+ let oldSummary = extractSummary(oldData);
92
+ let newSummary = extractSummary(newData);
93
+ return JSON.stringify(oldSummary) !== JSON.stringify(newSummary);
94
+ };
95
+
96
+ /**
97
+ * Send incremental updates by diffing old vs new report data.
98
+ * Returns true if any events were sent.
99
+ */
100
+ const sendIncrementalUpdates = (res, oldData, newData) => {
101
+ let sent = false;
102
+ let oldComparisons = oldData.comparisons || [];
103
+ let newComparisons = newData.comparisons || [];
104
+ let oldMap = buildComparisonMap(oldComparisons);
105
+ let newMap = buildComparisonMap(newComparisons);
106
+
107
+ // New or changed comparisons — sends the full comparison object, not a partial delta
108
+ for (let [id, newComp] of newMap) {
109
+ let oldComp = oldMap.get(id);
110
+ if (!oldComp || comparisonChanged(oldComp, newComp)) {
111
+ sendEvent(res, 'comparisonUpdate', newComp);
112
+ sent = true;
113
+ }
114
+ }
115
+
116
+ // Removed comparisons
117
+ for (let [id] of oldMap) {
118
+ if (!newMap.has(id)) {
119
+ sendEvent(res, 'comparisonRemoved', {
120
+ id
121
+ });
122
+ sent = true;
123
+ }
124
+ }
125
+
126
+ // Summary-level changes (total, passed, failed, etc.)
127
+ if (summaryChanged(oldData, newData)) {
128
+ sendEvent(res, 'summaryUpdate', extractSummary(newData));
129
+ sent = true;
130
+ }
131
+ return sent;
132
+ };
61
133
  return async function handleEventsRoute(req, res, pathname) {
62
134
  if (req.method !== 'GET' || pathname !== '/api/events') {
63
135
  return false;
@@ -71,20 +143,28 @@ export function createEventsRouter(context) {
71
143
  'X-Accel-Buffering': 'no' // Disable nginx buffering
72
144
  });
73
145
 
74
- // Send initial data immediately
75
- const initialData = readReportData();
76
- if (initialData) {
77
- sendEvent(res, 'reportData', initialData);
146
+ // Send initial full data immediately
147
+ let lastSentData = readReportData();
148
+ if (lastSentData) {
149
+ sendEvent(res, 'reportData', lastSentData);
78
150
  }
79
151
 
80
152
  // Debounce file change events (fs.watch can fire multiple times)
81
153
  let debounceTimer = null;
82
154
  let watcher = null;
83
155
  const sendUpdate = () => {
84
- const data = readReportData();
85
- if (data) {
86
- sendEvent(res, 'reportData', data);
156
+ const newData = readReportData();
157
+ if (!newData) return;
158
+ if (!lastSentData) {
159
+ // No previous data — send full payload
160
+ sendEvent(res, 'reportData', newData);
161
+ } else {
162
+ // Diff and send incremental updates
163
+ let sent = sendIncrementalUpdates(res, lastSentData, newData);
164
+ // If nothing changed, skip (no event needed)
165
+ if (!sent) return;
87
166
  }
167
+ lastSentData = newData;
88
168
  };
89
169
 
90
170
  // Watch for file changes
@@ -1038,9 +1038,9 @@ export class TddService {
1038
1038
  }
1039
1039
 
1040
1040
  // Baseline exists - compare
1041
+ let effectiveThreshold = typeof validatedProperties.threshold === 'number' && validatedProperties.threshold >= 0 ? validatedProperties.threshold : this.threshold;
1042
+ let effectiveMinClusterSize = Number.isInteger(validatedProperties.minClusterSize) && validatedProperties.minClusterSize >= 1 ? validatedProperties.minClusterSize : this.minClusterSize;
1041
1043
  try {
1042
- let effectiveThreshold = typeof validatedProperties.threshold === 'number' && validatedProperties.threshold >= 0 ? validatedProperties.threshold : this.threshold;
1043
- let effectiveMinClusterSize = Number.isInteger(validatedProperties.minClusterSize) && validatedProperties.minClusterSize >= 1 ? validatedProperties.minClusterSize : this.minClusterSize;
1044
1044
  let honeydiffResult = await compareImages(baselineImagePath, currentImagePath, diffImagePath, {
1045
1045
  threshold: effectiveThreshold,
1046
1046
  minClusterSize: effectiveMinClusterSize
@@ -1088,29 +1088,25 @@ export class TddService {
1088
1088
  }
1089
1089
  } catch (error) {
1090
1090
  if (isDimensionMismatchError(error)) {
1091
- output.debug('comparison', `${sanitizedName}: dimension mismatch, creating new baseline`);
1092
- saveBaseline(this.baselinePath, filename, imageBuffer);
1093
- if (!this.baselineData) {
1094
- this.baselineData = createEmptyBaselineMetadata({
1095
- threshold: this.threshold,
1096
- signatureProperties: this.signatureProperties
1097
- });
1098
- }
1099
- let screenshotEntry = {
1100
- name: sanitizedName,
1101
- properties: validatedProperties,
1102
- path: baselineImagePath,
1103
- signature
1104
- };
1105
- upsertScreenshotInMetadata(this.baselineData, screenshotEntry, signature);
1106
- saveBaselineMetadata(this.baselinePath, this.baselineData);
1107
- let result = buildNewComparison({
1091
+ output.debug('comparison', `${sanitizedName}: dimension mismatch, marking comparison as failed`, {
1092
+ error: error.message
1093
+ });
1094
+ let result = buildFailedComparison({
1108
1095
  name: sanitizedName,
1109
1096
  signature,
1110
1097
  baselinePath: baselineImagePath,
1111
1098
  currentPath: currentImagePath,
1112
- properties: validatedProperties
1099
+ diffPath: null,
1100
+ properties: validatedProperties,
1101
+ threshold: effectiveThreshold,
1102
+ minClusterSize: effectiveMinClusterSize,
1103
+ honeydiffResult: {
1104
+ isDifferent: true,
1105
+ diffClusters: []
1106
+ }
1113
1107
  });
1108
+ result.reason = 'dimension-mismatch';
1109
+ result.error = error.message;
1114
1110
  this._upsertComparison(result);
1115
1111
  return result;
1116
1112
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vizzly-testing/cli",
3
- "version": "0.29.2",
3
+ "version": "0.29.4",
4
4
  "description": "Visual review platform for UI developers and designers",
5
5
  "keywords": [
6
6
  "visual-testing",
@@ -122,6 +122,7 @@
122
122
  "@vitejs/plugin-react": "^5.0.3",
123
123
  "@vizzly-testing/observatory": "^0.3.3",
124
124
  "autoprefixer": "^10.4.21",
125
+ "babel-plugin-react-compiler": "^1.0.0",
125
126
  "babel-plugin-transform-remove-console": "^6.9.4",
126
127
  "postcss": "^8.5.6",
127
128
  "react": "^19.1.1",