import assert from 'node:assert/strict'; import fs from 'node:fs'; import vm from 'node:vm'; const source = fs.readFileSync(new URL('../www/views/goniometer_rtw.js', import.meta.url), 'utf8'); const functionSource = source.match(/export function resolveGoniometerPersistenceMs[\s\S]*?\n\}/)?.[0]; assert.ok(functionSource, 'persistence resolver must remain testable'); const context = vm.createContext({ Number, String, Math }); vm.runInContext(functionSource.replace('export function', 'function'), context); const markerFunctionSource = source.match(/export function resolveCorrelationNegativeMarker[\s\S]*?\n\}/)?.[0]; assert.ok(markerFunctionSource, 'correlation marker resolver must remain testable'); vm.runInContext(`const CORR_HOLD_MS = 4000;\n${markerFunctionSource.replace('export function', 'function')}`, context); assert.equal(context.resolveGoniometerPersistenceMs({ GONIO_PERSISTENCE_MODE: 'fast' }), 50); assert.equal(context.resolveGoniometerPersistenceMs({ GONIO_PERSISTENCE_MODE: 'medium' }), 150); assert.equal(context.resolveGoniometerPersistenceMs({ GONIO_PERSISTENCE_MODE: 'slow' }), 300); assert.equal(context.resolveGoniometerPersistenceMs({ GONIO_PERSISTENCE_MODE: 'custom', GONIO_LINE_FADE_MS: 470, }), 470); assert.equal(context.resolveGoniometerPersistenceMs({ GONIO_PERSISTENCE_MODE: 'custom', GONIO_LINE_FADE_MS: 900, }), 600); assert.match(source, /Math\.min\(targetPoints, total, MAX_TRACE_POINTS\)/, 'drawing must not upsample invented XY points'); assert.match(source, /while \(trails\.length >= MAX_TRAIL_FRAMES\)/, 'trail history must remain bounded'); assert.match(source, /pool\.pop\(\)/, 'trail buffers must be reused'); assert.doesNotMatch(source, /utils\.correlation\(/, 'browser must not recalculate or smooth backend correlation'); const markerState = { corrNegativeMarkerMode: 'memory', corrHoldPeak: 0, corrHoldUntil: 0, corrResetToken: 0, }; assert.equal(context.resolveCorrelationNegativeMarker( markerState, { CORR_NEGATIVE_MARKER_MODE: 'memory' }, -0.2, -0.8, 1000 ), -0.8, 'memory mode must display the continuous DSP negative peak'); assert.equal(context.resolveCorrelationNegativeMarker( markerState, { CORR_NEGATIVE_MARKER_MODE: 'off' }, -0.9, -0.9, 1100 ), 0, 'off mode must hide the negative marker'); assert.equal(context.resolveCorrelationNegativeMarker( markerState, { CORR_NEGATIVE_MARKER_MODE: 'hold' }, -0.4, -0.9, 2000 ), -0.4, 'hold mode must start from the current correlation'); assert.equal(context.resolveCorrelationNegativeMarker( markerState, { CORR_NEGATIVE_MARKER_MODE: 'hold' }, -0.2, -0.9, 5000 ), -0.4, 'hold mode must retain its negative peak for four seconds'); assert.equal(context.resolveCorrelationNegativeMarker( markerState, { CORR_NEGATIVE_MARKER_MODE: 'hold' }, -0.2, -0.9, 6000 ), -0.2, 'hold mode must release to the current value after four seconds'); assert.equal(context.resolveCorrelationNegativeMarker( markerState, { CORR_NEGATIVE_MARKER_MODE: 'hold', CORR_RESET_TOKEN: 1 }, 0.2, -0.9, 6100 ), 0, 'reset must clear the four-second hold as well'); console.log('goniometer regression tests passed');