import test from 'node:test'; import assert from 'node:assert/strict'; import { pHashVerdict, mergeAiVisualVerdict } from './visual-diff-verdict.js'; test('pHashVerdict: IDENTICAL maps to NONE/APPROVE', () => { const v = pHashVerdict(0, 'IDENTICAL', []); assert.equal(v.changeType, 'NONE'); assert.equal(v.recommendation, 'APPROVE'); }); test('pHashVerdict: MODERATE change level maps to SIGNIFICANT changeType', () => { const v = pHashVerdict(12, 'MODERATE', []); assert.equal(v.changeType, 'SIGNIFICANT'); }); test('pHashVerdict: SIGNIFICANT change level maps to CRITICAL changeType (naming overlap between phash and VisualDiff scales)', () => { const v = pHashVerdict(35, 'SIGNIFICANT', []); assert.equal(v.changeType, 'CRITICAL'); }); test('pHashVerdict: recommendation escalates with distance regardless of changeLevel', () => { assert.equal(pHashVerdict(0, 'MINOR', []).recommendation, 'APPROVE'); assert.equal(pHashVerdict(10, 'MINOR', []).recommendation, 'REVIEW'); assert.equal(pHashVerdict(25, 'MINOR', []).recommendation, 'FLAG'); }); test('mergeAiVisualVerdict: AI downgrading pHash CRITICAL to MINOR (anti-aliasing noise) produces no bug report draft', () => { const pHash = pHashVerdict(35, 'SIGNIFICANT', [{ location: 'header', severity: 'Cosmetic', description: 'pixel shift' }]); const merged = mergeAiVisualVerdict( pHash, { changeType: 'MINOR', recommendation: 'APPROVE', changes: [], diffScore: 3 }, 'Login Screen', ); assert.equal(merged.changeType, 'MINOR'); assert.equal(merged.recommendation, 'APPROVE'); assert.equal(merged.bugReportDraft, undefined, 'a downgraded verdict must not carry a stale bug report draft'); }); test('mergeAiVisualVerdict: AI confirming CRITICAL produces a bug report draft with the AI diff score, not the pHash one', () => { const pHash = pHashVerdict(35, 'SIGNIFICANT', []); const merged = mergeAiVisualVerdict( pHash, { changeType: 'CRITICAL', recommendation: 'FLAG', changes: [{ location: 'nav', severity: 'Functional', description: 'Logo missing' }], diffScore: 62.4 }, 'Dashboard', ); assert.equal(merged.changeType, 'CRITICAL'); assert.ok(merged.bugReportDraft); assert.equal(merged.bugReportDraft!.severity, 'High'); assert.match(merged.bugReportDraft!.actual, /62\.4%/); assert.equal(merged.changes.length, 1); assert.equal(merged.changes[0].description, 'Logo missing'); }); test('mergeAiVisualVerdict: SIGNIFICANT (not just CRITICAL) also gets a bug report draft, with Medium severity', () => { const pHash = pHashVerdict(20, 'MODERATE', []); const merged = mergeAiVisualVerdict( pHash, { changeType: 'SIGNIFICANT', recommendation: 'REVIEW', changes: [], diffScore: 18 }, 'Checkout', ); assert.ok(merged.bugReportDraft); assert.equal(merged.bugReportDraft!.severity, 'Medium'); }); test('mergeAiVisualVerdict: empty AI changes array keeps the pHash-derived changes instead of wiping them', () => { const pHash = pHashVerdict(35, 'SIGNIFICANT', [{ location: 'footer', severity: 'Cosmetic', description: 'layout shift' }]); const merged = mergeAiVisualVerdict( pHash, { changeType: 'CRITICAL', recommendation: 'FLAG', changes: [], diffScore: 40 }, 'Settings', ); assert.equal(merged.changes.length, 1); assert.equal(merged.changes[0].description, 'layout shift'); }); test('mergeAiVisualVerdict: malformed AI result (no changeType) falls back to the pHash verdict untouched', () => { const pHash = pHashVerdict(35, 'SIGNIFICANT', []); const merged = mergeAiVisualVerdict(pHash, { changeType: '', recommendation: '', changes: [], diffScore: 0 }, 'Home'); assert.deepEqual(merged, pHash); });