From 1b4d17d0ee1419e9d75e6ba3036e3ed6d49c1df5 Mon Sep 17 00:00:00 2001 From: Robert DeLuca Date: Tue, 17 Feb 2026 21:07:41 -0600 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=90=9B=20Prevent=20stale=20TDD=20imag?= =?UTF-8?q?e=20loads?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cache-bust local /images/* URLs in reporter components using the comparison timestamp so baseline/current/diff re-fetch after each update.\n\nAlso disable caching for image responses in the assets router and add focused tests for URL versioning and cache headers. --- .../comparison/comparison-viewer.jsx | 9 ++-- .../comparison/fullscreen-viewer.jsx | 6 ++- .../comparison/screenshot-display.jsx | 9 ++-- .../components/comparison/screenshot-list.jsx | 6 ++- src/reporter/src/utils/image-url.js | 21 +++++++++ src/server/routers/assets.js | 7 +++ tests/reporter/utils/image-url.test.js | 43 +++++++++++++++++++ tests/server/routers/assets.test.js | 10 +++++ 8 files changed, 101 insertions(+), 10 deletions(-) create mode 100644 src/reporter/src/utils/image-url.js create mode 100644 tests/reporter/utils/image-url.test.js diff --git a/src/reporter/src/components/comparison/comparison-viewer.jsx b/src/reporter/src/components/comparison/comparison-viewer.jsx index abb070bc..2fcf1396 100644 --- a/src/reporter/src/components/comparison/comparison-viewer.jsx +++ b/src/reporter/src/components/comparison/comparison-viewer.jsx @@ -4,6 +4,7 @@ import { ToggleView, } from '@vizzly-testing/observatory'; import { useCallback, useMemo, useState } from 'react'; +import { withImageVersion } from '../../utils/image-url.js'; import { VIEW_MODES } from '../../utils/constants.js'; /** @@ -38,9 +39,9 @@ export default function ComparisonViewer({ comparison, viewMode }) { // Build image URLs - no memoization needed, object creation is cheap const imageUrls = { - current: comparison.current, - baseline: comparison.baseline, - diff: comparison.diff, + current: withImageVersion(comparison.current, comparison.timestamp), + baseline: withImageVersion(comparison.baseline, comparison.timestamp), + diff: withImageVersion(comparison.diff, comparison.timestamp), }; // For new screenshots, just show the current image (no baseline exists yet) @@ -52,7 +53,7 @@ export default function ComparisonViewer({ comparison, viewMode }) { First screenshot - creating new baseline

New baseline screenshot diff --git a/src/reporter/src/components/comparison/fullscreen-viewer.jsx b/src/reporter/src/components/comparison/fullscreen-viewer.jsx index 9f09cb5d..c8e64cc1 100644 --- a/src/reporter/src/components/comparison/fullscreen-viewer.jsx +++ b/src/reporter/src/components/comparison/fullscreen-viewer.jsx @@ -44,6 +44,7 @@ import { ZoomControls, } from '@vizzly-testing/observatory'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { withImageVersion } from '../../utils/image-url.js'; import { VIEW_MODES } from '../../utils/constants.js'; import { ScreenshotDisplay } from './screenshot-display.jsx'; @@ -641,7 +642,10 @@ function FullscreenViewerInner({ onNavigate(item)} /> diff --git a/src/reporter/src/components/comparison/screenshot-display.jsx b/src/reporter/src/components/comparison/screenshot-display.jsx index f134291e..45217ec2 100644 --- a/src/reporter/src/components/comparison/screenshot-display.jsx +++ b/src/reporter/src/components/comparison/screenshot-display.jsx @@ -6,6 +6,7 @@ import { ToggleMode, } from '@vizzly-testing/observatory'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { withImageVersion } from '../../utils/image-url.js'; /** * Unified Screenshot Display Component - matches Observatory architecture @@ -126,9 +127,9 @@ export function ScreenshotDisplay({ // Build image URLs from comparison object - no memoization needed, object creation is cheap const imageUrls = comparison ? { - current: comparison.current, - baseline: comparison.baseline, - diff: comparison.diff, + current: withImageVersion(comparison.current, comparison.timestamp), + baseline: withImageVersion(comparison.baseline, comparison.timestamp), + diff: withImageVersion(comparison.diff, comparison.timestamp), } : {}; @@ -213,7 +214,7 @@ export function ScreenshotDisplay({ > {comparison && ( {comparison.name handleImageLoad(`current-${screenshot?.id}`)} diff --git a/src/reporter/src/components/comparison/screenshot-list.jsx b/src/reporter/src/components/comparison/screenshot-list.jsx index 6435df5f..3ff05934 100644 --- a/src/reporter/src/components/comparison/screenshot-list.jsx +++ b/src/reporter/src/components/comparison/screenshot-list.jsx @@ -7,6 +7,7 @@ import { XCircleIcon, } from '@heroicons/react/24/outline'; import { useMemo } from 'react'; +import { withImageVersion } from '../../utils/image-url.js'; import { Badge, Button } from '../design-system/index.js'; import SmartImage from '../ui/smart-image.jsx'; @@ -238,7 +239,10 @@ function ScreenshotGroupRow({ }) { let { primary, hasChanges, hasNew, maxDiff } = group; let needsAction = hasChanges || hasNew; - let thumbnailSrc = primary.current || primary.baseline; + let thumbnailSrc = withImageVersion( + primary.current || primary.baseline, + primary.timestamp + ); // Generate test ID from primary comparison let testId = `screenshot-group-${(primary.id || primary.signature || group.name).replace(/[^a-zA-Z0-9-]/g, '-')}`; diff --git a/src/reporter/src/utils/image-url.js b/src/reporter/src/utils/image-url.js new file mode 100644 index 00000000..42350367 --- /dev/null +++ b/src/reporter/src/utils/image-url.js @@ -0,0 +1,21 @@ +/** + * Add a version query param for local image URLs so updated screenshots + * are re-fetched when report data changes. + */ +export function withImageVersion(url, version) { + if (!url || typeof url !== 'string') { + return url; + } + + // Only rewrite local TDD image paths. + if (!url.startsWith('/images/')) { + return url; + } + + if (version === null || version === undefined) { + return url; + } + + let separator = url.includes('?') ? '&' : '?'; + return `${url}${separator}v=${encodeURIComponent(String(version))}`; +} diff --git a/src/server/routers/assets.js b/src/server/routers/assets.js index 4b7b3fff..c3b5ad0b 100644 --- a/src/server/routers/assets.js +++ b/src/server/routers/assets.js @@ -84,6 +84,13 @@ export function createAssetsRouter() { if (existsSync(fullImagePath)) { try { const imageData = readFileSync(fullImagePath); + // Images are rewritten in place between TDD runs, so disable browser caching. + res.setHeader( + 'Cache-Control', + 'no-store, no-cache, must-revalidate' + ); + res.setHeader('Pragma', 'no-cache'); + res.setHeader('Expires', '0'); sendFile(res, imageData, 'image/png'); return true; } catch (error) { diff --git a/tests/reporter/utils/image-url.test.js b/tests/reporter/utils/image-url.test.js new file mode 100644 index 00000000..90b55e39 --- /dev/null +++ b/tests/reporter/utils/image-url.test.js @@ -0,0 +1,43 @@ +import assert from 'node:assert'; +import { describe, it } from 'node:test'; +import { withImageVersion } from '../../../src/reporter/src/utils/image-url.js'; + +describe('reporter/utils/image-url', () => { + it('returns original value for non-string urls', () => { + assert.strictEqual(withImageVersion(null, 1), null); + assert.strictEqual(withImageVersion(undefined, 1), undefined); + assert.strictEqual(withImageVersion(42, 1), 42); + }); + + it('returns original url for non-local images', () => { + let url = 'https://cdn.example.com/image.png'; + assert.strictEqual(withImageVersion(url, 123), url); + }); + + it('returns original url when version is missing', () => { + let url = '/images/current/homepage.png'; + assert.strictEqual(withImageVersion(url, null), url); + assert.strictEqual(withImageVersion(url, undefined), url); + }); + + it('appends v query param for local image urls', () => { + assert.strictEqual( + withImageVersion('/images/current/homepage.png', 123), + '/images/current/homepage.png?v=123' + ); + }); + + it('appends v query param using ampersand when query already exists', () => { + assert.strictEqual( + withImageVersion('/images/current/homepage.png?mode=thumb', 456), + '/images/current/homepage.png?mode=thumb&v=456' + ); + }); + + it('encodes non-numeric version values', () => { + assert.strictEqual( + withImageVersion('/images/current/homepage.png', 'run 1'), + '/images/current/homepage.png?v=run%201' + ); + }); +}); diff --git a/tests/server/routers/assets.test.js b/tests/server/routers/assets.test.js index 88b64623..7f72c453 100644 --- a/tests/server/routers/assets.test.js +++ b/tests/server/routers/assets.test.js @@ -148,6 +148,12 @@ describe('server/routers/assets', () => { assert.strictEqual(res.statusCode, 200); assert.strictEqual(res.getHeader('Content-Type'), 'image/png'); + assert.strictEqual( + res.getHeader('Cache-Control'), + 'no-store, no-cache, must-revalidate' + ); + assert.strictEqual(res.getHeader('Pragma'), 'no-cache'); + assert.strictEqual(res.getHeader('Expires'), '0'); }); it('returns 404 for non-existent image', async () => { @@ -177,6 +183,10 @@ describe('server/routers/assets', () => { assert.strictEqual(res.statusCode, 200); assert.strictEqual(res.getHeader('Content-Type'), 'image/png'); + assert.strictEqual( + res.getHeader('Cache-Control'), + 'no-store, no-cache, must-revalidate' + ); }); }); }); From efc5b94034d75f12b8577957ca49a4cd9637b834 Mon Sep 17 00:00:00 2001 From: Robert DeLuca Date: Tue, 17 Feb 2026 21:30:13 -0600 Subject: [PATCH 2/3] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Harden=20cache-bust=20?= =?UTF-8?q?timestamp=20handling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Normalize report/comparison timestamps in reporter data paths so local image cache-busting does not silently fall back when per-comparison timestamps are missing.\n\nRefine tests to focus on observable outcomes and add edge-case coverage for version=0, existing v params, and cache headers. --- src/reporter/src/api/client.js | 5 +- .../comparison/comparison-viewer.jsx | 20 +++++-- .../comparison/screenshot-display.jsx | 28 ++++++--- src/reporter/src/providers/sse-provider.jsx | 12 +++- src/reporter/src/utils/image-url.js | 14 ++++- src/reporter/src/utils/report-data.js | 40 +++++++++++++ tests/reporter/utils/image-url.test.js | 16 ++++- tests/reporter/utils/report-data.test.js | 60 +++++++++++++++++++ tests/server/routers/assets.test.js | 2 + 9 files changed, 176 insertions(+), 21 deletions(-) create mode 100644 src/reporter/src/utils/report-data.js create mode 100644 tests/reporter/utils/report-data.test.js diff --git a/src/reporter/src/api/client.js b/src/reporter/src/api/client.js index b9a99cdf..d24abce5 100644 --- a/src/reporter/src/api/client.js +++ b/src/reporter/src/api/client.js @@ -9,6 +9,8 @@ * - api.auth.* - Authentication */ +import { normalizeReportData } from '../utils/report-data.js'; + /** * Make a JSON API request * @param {string} url - Request URL @@ -45,7 +47,8 @@ export const tdd = { * @returns {Promise} */ async getReportData() { - return fetchJson('/api/report-data'); + let data = await fetchJson('/api/report-data'); + return normalizeReportData(data); }, /** diff --git a/src/reporter/src/components/comparison/comparison-viewer.jsx b/src/reporter/src/components/comparison/comparison-viewer.jsx index 2fcf1396..2ba487dd 100644 --- a/src/reporter/src/components/comparison/comparison-viewer.jsx +++ b/src/reporter/src/components/comparison/comparison-viewer.jsx @@ -37,12 +37,20 @@ export default function ComparisonViewer({ comparison, viewMode }) { [comparison] ); - // Build image URLs - no memoization needed, object creation is cheap - const imageUrls = { - current: withImageVersion(comparison.current, comparison.timestamp), - baseline: withImageVersion(comparison.baseline, comparison.timestamp), - diff: withImageVersion(comparison.diff, comparison.timestamp), - }; + // Build image URLs once per comparison update. + const imageUrls = useMemo( + () => ({ + current: withImageVersion(comparison.current, comparison.timestamp), + baseline: withImageVersion(comparison.baseline, comparison.timestamp), + diff: withImageVersion(comparison.diff, comparison.timestamp), + }), + [ + comparison.current, + comparison.baseline, + comparison.diff, + comparison.timestamp, + ] + ); // For new screenshots, just show the current image (no baseline exists yet) if (comparison.status === 'new' || comparison.status === 'baseline-created') { diff --git a/src/reporter/src/components/comparison/screenshot-display.jsx b/src/reporter/src/components/comparison/screenshot-display.jsx index 45217ec2..61270238 100644 --- a/src/reporter/src/components/comparison/screenshot-display.jsx +++ b/src/reporter/src/components/comparison/screenshot-display.jsx @@ -124,14 +124,26 @@ export function ScreenshotDisplay({ setImageLoadStates(prev => new Map(prev).set(imageKey, 'loaded')); }, []); - // Build image URLs from comparison object - no memoization needed, object creation is cheap - const imageUrls = comparison - ? { - current: withImageVersion(comparison.current, comparison.timestamp), - baseline: withImageVersion(comparison.baseline, comparison.timestamp), - diff: withImageVersion(comparison.diff, comparison.timestamp), - } - : {}; + // Build image URLs once per comparison update. + const imageUrls = useMemo( + () => + comparison + ? { + current: withImageVersion(comparison.current, comparison.timestamp), + baseline: withImageVersion( + comparison.baseline, + comparison.timestamp + ), + diff: withImageVersion(comparison.diff, comparison.timestamp), + } + : {}, + [ + comparison?.current, + comparison?.baseline, + comparison?.diff, + comparison?.timestamp, + ] + ); // Create a screenshot-like object for the comparison modes const screenshot = useMemo(() => { diff --git a/src/reporter/src/providers/sse-provider.jsx b/src/reporter/src/providers/sse-provider.jsx index 999bb21c..fb25eaa3 100644 --- a/src/reporter/src/providers/sse-provider.jsx +++ b/src/reporter/src/providers/sse-provider.jsx @@ -1,6 +1,10 @@ import { useQueryClient } from '@tanstack/react-query'; import { createContext, useEffect, useRef, useState } from 'react'; import { queryKeys } from '../lib/query-keys.js'; +import { + normalizeComparisonUpdate, + normalizeReportData, +} from '../utils/report-data.js'; export let SSE_STATE = { CONNECTING: 'connecting', @@ -57,7 +61,7 @@ export function SSEProvider({ enabled = true, children }) { eventSource.addEventListener('reportData', event => { try { let data = JSON.parse(event.data); - queryClient.setQueryData(queryKeys.reportData(), data); + queryClient.setQueryData(queryKeys.reportData(), normalizeReportData(data)); } catch { // Ignore parse errors } @@ -66,9 +70,13 @@ export function SSEProvider({ enabled = true, children }) { // Incremental: single comparison added or changed eventSource.addEventListener('comparisonUpdate', event => { try { - let comparison = JSON.parse(event.data); + let incomingComparison = JSON.parse(event.data); queryClient.setQueryData(queryKeys.reportData(), old => { if (!old) return old; + let comparison = normalizeComparisonUpdate( + incomingComparison, + old.timestamp + ); let comparisons = old.comparisons || []; let idx = comparisons.findIndex(c => c.id === comparison.id); if (idx >= 0) { diff --git a/src/reporter/src/utils/image-url.js b/src/reporter/src/utils/image-url.js index 42350367..defb3faf 100644 --- a/src/reporter/src/utils/image-url.js +++ b/src/reporter/src/utils/image-url.js @@ -1,14 +1,18 @@ /** * Add a version query param for local image URLs so updated screenshots * are re-fetched when report data changes. + * + * This is a no-op for non-local URLs. */ +export let LOCAL_IMAGE_PREFIX = '/images/'; + export function withImageVersion(url, version) { if (!url || typeof url !== 'string') { return url; } // Only rewrite local TDD image paths. - if (!url.startsWith('/images/')) { + if (!url.startsWith(LOCAL_IMAGE_PREFIX)) { return url; } @@ -16,6 +20,10 @@ export function withImageVersion(url, version) { return url; } - let separator = url.includes('?') ? '&' : '?'; - return `${url}${separator}v=${encodeURIComponent(String(version))}`; + let [path, queryString = ''] = url.split('?'); + let params = new URLSearchParams(queryString); + params.set('v', String(version)); + let query = params.toString(); + + return query ? `${path}?${query}` : path; } diff --git a/src/reporter/src/utils/report-data.js b/src/reporter/src/utils/report-data.js new file mode 100644 index 00000000..f7971fff --- /dev/null +++ b/src/reporter/src/utils/report-data.js @@ -0,0 +1,40 @@ +/** + * Ensure each comparison has a timestamp for image cache-busting. + */ +export function normalizeReportData(reportData) { + if (!reportData || !Array.isArray(reportData.comparisons)) { + return reportData; + } + + let needsNormalization = reportData.comparisons.some( + comparison => comparison && comparison.timestamp == null + ); + + if (!needsNormalization) { + return reportData; + } + + let fallbackTimestamp = reportData.timestamp ?? Date.now(); + let comparisons = reportData.comparisons.map(comparison => + normalizeComparisonUpdate(comparison, fallbackTimestamp) + ); + + return { + ...reportData, + comparisons, + }; +} + +/** + * Ensure a single comparison includes a timestamp. + */ +export function normalizeComparisonUpdate(comparison, fallbackTimestamp) { + if (!comparison || comparison.timestamp != null) { + return comparison; + } + + return { + ...comparison, + timestamp: fallbackTimestamp ?? Date.now(), + }; +} diff --git a/tests/reporter/utils/image-url.test.js b/tests/reporter/utils/image-url.test.js index 90b55e39..ba6136cd 100644 --- a/tests/reporter/utils/image-url.test.js +++ b/tests/reporter/utils/image-url.test.js @@ -27,6 +27,13 @@ describe('reporter/utils/image-url', () => { ); }); + it('supports zero as a valid cache-busting version', () => { + assert.strictEqual( + withImageVersion('/images/current/homepage.png', 0), + '/images/current/homepage.png?v=0' + ); + }); + it('appends v query param using ampersand when query already exists', () => { assert.strictEqual( withImageVersion('/images/current/homepage.png?mode=thumb', 456), @@ -34,10 +41,17 @@ describe('reporter/utils/image-url', () => { ); }); + it('replaces existing v query param instead of duplicating', () => { + assert.strictEqual( + withImageVersion('/images/current/homepage.png?v=old&mode=thumb', 456), + '/images/current/homepage.png?v=456&mode=thumb' + ); + }); + it('encodes non-numeric version values', () => { assert.strictEqual( withImageVersion('/images/current/homepage.png', 'run 1'), - '/images/current/homepage.png?v=run%201' + '/images/current/homepage.png?v=run+1' ); }); }); diff --git a/tests/reporter/utils/report-data.test.js b/tests/reporter/utils/report-data.test.js new file mode 100644 index 00000000..b0986cb0 --- /dev/null +++ b/tests/reporter/utils/report-data.test.js @@ -0,0 +1,60 @@ +import assert from 'node:assert'; +import { describe, it } from 'node:test'; +import { + normalizeComparisonUpdate, + normalizeReportData, +} from '../../../src/reporter/src/utils/report-data.js'; + +describe('reporter/utils/report-data', () => { + describe('normalizeReportData', () => { + it('returns input when report data is nullish', () => { + assert.strictEqual(normalizeReportData(null), null); + assert.strictEqual(normalizeReportData(undefined), undefined); + }); + + it('returns input when comparisons is not an array', () => { + let data = { timestamp: 123 }; + assert.strictEqual(normalizeReportData(data), data); + }); + + it('adds missing comparison timestamps from report timestamp', () => { + let data = { + timestamp: 123, + comparisons: [{ id: 'a' }, { id: 'b', timestamp: 456 }], + }; + + let result = normalizeReportData(data); + + assert.strictEqual(result.comparisons[0].timestamp, 123); + assert.strictEqual(result.comparisons[1].timestamp, 456); + }); + + it('preserves existing comparison timestamps', () => { + let data = { + timestamp: 123, + comparisons: [ + { id: 'a', timestamp: 111 }, + { id: 'b', timestamp: 222 }, + ], + }; + + let result = normalizeReportData(data); + + assert.deepStrictEqual(result.comparisons, data.comparisons); + }); + }); + + describe('normalizeComparisonUpdate', () => { + it('returns input when comparison already has timestamp', () => { + let comparison = { id: 'a', timestamp: 111 }; + assert.strictEqual(normalizeComparisonUpdate(comparison, 999), comparison); + }); + + it('adds fallback timestamp when missing', () => { + let comparison = { id: 'a' }; + let result = normalizeComparisonUpdate(comparison, 999); + + assert.strictEqual(result.timestamp, 999); + }); + }); +}); diff --git a/tests/server/routers/assets.test.js b/tests/server/routers/assets.test.js index 7f72c453..63da92d0 100644 --- a/tests/server/routers/assets.test.js +++ b/tests/server/routers/assets.test.js @@ -187,6 +187,8 @@ describe('server/routers/assets', () => { res.getHeader('Cache-Control'), 'no-store, no-cache, must-revalidate' ); + assert.strictEqual(res.getHeader('Pragma'), 'no-cache'); + assert.strictEqual(res.getHeader('Expires'), '0'); }); }); }); From 1481ed680c2c39ef4e30ca0b4e2cf841b7606136 Mon Sep 17 00:00:00 2001 From: Robert DeLuca Date: Tue, 17 Feb 2026 21:40:54 -0600 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=A7=B9=20Fix=20lint=20issues=20in=20c?= =?UTF-8?q?ache-bust=20follow-up?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apply Biome import/format ordering and update the screenshot image URL memo dependencies to satisfy exhaustive-deps checks. --- .../src/components/comparison/comparison-viewer.jsx | 2 +- .../src/components/comparison/fullscreen-viewer.jsx | 2 +- .../src/components/comparison/screenshot-display.jsx | 7 +------ src/reporter/src/providers/sse-provider.jsx | 5 ++++- src/server/routers/assets.js | 5 +---- tests/reporter/utils/report-data.test.js | 5 ++++- 6 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/reporter/src/components/comparison/comparison-viewer.jsx b/src/reporter/src/components/comparison/comparison-viewer.jsx index 2ba487dd..fd17f74c 100644 --- a/src/reporter/src/components/comparison/comparison-viewer.jsx +++ b/src/reporter/src/components/comparison/comparison-viewer.jsx @@ -4,8 +4,8 @@ import { ToggleView, } from '@vizzly-testing/observatory'; import { useCallback, useMemo, useState } from 'react'; -import { withImageVersion } from '../../utils/image-url.js'; import { VIEW_MODES } from '../../utils/constants.js'; +import { withImageVersion } from '../../utils/image-url.js'; /** * Comparison Viewer for inline card display diff --git a/src/reporter/src/components/comparison/fullscreen-viewer.jsx b/src/reporter/src/components/comparison/fullscreen-viewer.jsx index c8e64cc1..9f02b4d9 100644 --- a/src/reporter/src/components/comparison/fullscreen-viewer.jsx +++ b/src/reporter/src/components/comparison/fullscreen-viewer.jsx @@ -44,8 +44,8 @@ import { ZoomControls, } from '@vizzly-testing/observatory'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { withImageVersion } from '../../utils/image-url.js'; import { VIEW_MODES } from '../../utils/constants.js'; +import { withImageVersion } from '../../utils/image-url.js'; import { ScreenshotDisplay } from './screenshot-display.jsx'; /** diff --git a/src/reporter/src/components/comparison/screenshot-display.jsx b/src/reporter/src/components/comparison/screenshot-display.jsx index 61270238..f5e49f08 100644 --- a/src/reporter/src/components/comparison/screenshot-display.jsx +++ b/src/reporter/src/components/comparison/screenshot-display.jsx @@ -137,12 +137,7 @@ export function ScreenshotDisplay({ diff: withImageVersion(comparison.diff, comparison.timestamp), } : {}, - [ - comparison?.current, - comparison?.baseline, - comparison?.diff, - comparison?.timestamp, - ] + [comparison] ); // Create a screenshot-like object for the comparison modes diff --git a/src/reporter/src/providers/sse-provider.jsx b/src/reporter/src/providers/sse-provider.jsx index fb25eaa3..1a5780b2 100644 --- a/src/reporter/src/providers/sse-provider.jsx +++ b/src/reporter/src/providers/sse-provider.jsx @@ -61,7 +61,10 @@ export function SSEProvider({ enabled = true, children }) { eventSource.addEventListener('reportData', event => { try { let data = JSON.parse(event.data); - queryClient.setQueryData(queryKeys.reportData(), normalizeReportData(data)); + queryClient.setQueryData( + queryKeys.reportData(), + normalizeReportData(data) + ); } catch { // Ignore parse errors } diff --git a/src/server/routers/assets.js b/src/server/routers/assets.js index c3b5ad0b..9023b547 100644 --- a/src/server/routers/assets.js +++ b/src/server/routers/assets.js @@ -85,10 +85,7 @@ export function createAssetsRouter() { try { const imageData = readFileSync(fullImagePath); // Images are rewritten in place between TDD runs, so disable browser caching. - res.setHeader( - 'Cache-Control', - 'no-store, no-cache, must-revalidate' - ); + res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate'); res.setHeader('Pragma', 'no-cache'); res.setHeader('Expires', '0'); sendFile(res, imageData, 'image/png'); diff --git a/tests/reporter/utils/report-data.test.js b/tests/reporter/utils/report-data.test.js index b0986cb0..f786bc94 100644 --- a/tests/reporter/utils/report-data.test.js +++ b/tests/reporter/utils/report-data.test.js @@ -47,7 +47,10 @@ describe('reporter/utils/report-data', () => { describe('normalizeComparisonUpdate', () => { it('returns input when comparison already has timestamp', () => { let comparison = { id: 'a', timestamp: 111 }; - assert.strictEqual(normalizeComparisonUpdate(comparison, 999), comparison); + assert.strictEqual( + normalizeComparisonUpdate(comparison, 999), + comparison + ); }); it('adds fallback timestamp when missing', () => {