@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
|
|
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
|
|
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
|
-
|
|
76
|
-
if (
|
|
77
|
-
sendEvent(res, 'reportData',
|
|
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
|
|
85
|
-
if (
|
|
86
|
-
|
|
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
|
package/dist/tdd/tdd-service.js
CHANGED
|
@@ -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,
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
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
|
-
|
|
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.
|
|
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",
|