From 29fb756a5c4fcb590f20ca02da343129c03e97d2 Mon Sep 17 00:00:00 2001 From: Said Turksever Date: Mon, 19 Oct 2026 16:50:21 +0100 Subject: [PATCH] test(cctv): cover the focus de-emphasis icon colors refreshCctvFocusStyles passes cameraColorFor as baseColorFor, but only refreshCoverageStyles was tested. The new rendering.test.mjs runs the focus pass through createRendering with stubbed focus services and checks that an idle video camera fades from violet, a still from cyan and the active camera from amber. It fails if the focus callback goes back to the old active/idle check. Also adds the missing semicolon on the policy.js import in cctv.test.mjs. Co-Authored-By: Claude Opus 5.5 --- scripts/format-scope.json | 1 + src/data/cctv.test.mjs | 2 +- src/layers/cctv/rendering.test.mjs | 73 ++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 src/layers/cctv/rendering.test.mjs diff --git a/scripts/format-scope.json b/scripts/format-scope.json index 4230c23ae..2d2ddb361 100644 --- a/scripts/format-scope.json +++ b/scripts/format-scope.json @@ -557,6 +557,7 @@ "src/data/cctvNswSource.test.mjs", "src/data/cctvTxdotSource.test.mjs", "src/layers/cctv/source.test.mjs", + "src/layers/cctv/rendering.test.mjs", "src/data/alprCameras.js", "src/data/alprCameras.test.mjs", "src/data/dataCredits.test.mjs", diff --git a/src/data/cctv.test.mjs b/src/data/cctv.test.mjs index e102b081d..dafaeb5f6 100644 --- a/src/data/cctv.test.mjs +++ b/src/data/cctv.test.mjs @@ -81,7 +81,7 @@ import { ACTIVE_CAMERA_COLOR, IDLE_CAMERA_COLOR, LIVE_CAMERA_COLOR, -} from '../layers/cctv/policy.js' +} from '../layers/cctv/policy.js'; const CCTV_PRESENTATION_SOURCE = fs.readFileSync( path.join( diff --git a/src/layers/cctv/rendering.test.mjs b/src/layers/cctv/rendering.test.mjs new file mode 100644 index 000000000..c4b490dd2 --- /dev/null +++ b/src/layers/cctv/rendering.test.mjs @@ -0,0 +1,73 @@ +// src/layers/cctv/rendering.test.mjs +// Icon colors under focus de-emphasis (#1200): the focus pass fades each icon +// from the same base color the coverage refresh assigns, so a video-feed camera +// stays violet while it dims instead of falling back to the idle cyan. +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { createRendering } from './rendering.js'; +import { + ACTIVE_CAMERA_COLOR, + IDLE_CAMERA_COLOR, + LIVE_CAMERA_COLOR, +} from './policy.js'; +import { isVideoFeedType } from '../../sources/cctvTypes.js'; + +const FOCUS_FACTOR = 0.5; + +function assertFadedFrom(actual, base, label) { + assert.ok(actual, `${label}: color written`); + assert.deepEqual( + [actual.red, actual.green, actual.blue], + [base.red, base.green, base.blue], + `${label}: hue`, + ); + assert.equal(actual.alpha, base.alpha * FOCUS_FACTOR, `${label}: alpha`); +} + +test('focus de-emphasis fades each icon from its own color', () => { + const record = (id, feedType) => ({ + camera: { id, feedType }, + // No position, so the pass skips screen projection; the focus stub below + // decides the factor on its own. + billboard: { show: true }, + }); + const idleVideo = record('idle-hls', 'hls'); + const idleStill = record('idle-image', 'image'); + const activeVideo = record('active-hls', 'hls'); + const state = { + _enabled: true, + _viewer: { scene: {}, camera: {} }, + _records: [idleVideo, idleStill, activeVideo], + _activeCameraId: activeVideo.camera.id, + _activeFocusStyleCount: 0, + _lastFocusStyleAt: -Infinity, + }; + const focus = { + focusNowMs: (nowMs) => nowMs, + getFocusTarget: () => ({}), + focusPassIsNeeded: () => true, + advanceSpriteFocus: () => ({ + factor: FOCUS_FACTOR, + active: true, + transitioning: false, + }), + focusAlphaNeedsWrite: () => true, + }; + const { refreshCctvFocusStyles } = createRendering({ + state, + services: { focus }, + parts: { model: { isVideoFeedType } }, + source: {}, + }); + + refreshCctvFocusStyles(1000); + + assertFadedFrom(idleVideo.billboard.color, LIVE_CAMERA_COLOR, 'idle video'); + assertFadedFrom(idleStill.billboard.color, IDLE_CAMERA_COLOR, 'idle still'); + assertFadedFrom( + activeVideo.billboard.color, + ACTIVE_CAMERA_COLOR, + 'active video', + ); + assert.equal(state._activeFocusStyleCount, 3); +});