From b3ff80d74ee94dbda177eed233991d726b99b89e Mon Sep 17 00:00:00 2001 From: anti Date: Fri, 8 May 2026 20:27:40 -0400 Subject: [PATCH] test(decnet_web): vitest coverage for Behavioural primitives panel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four tests pin the panel surface: * Empty-state placeholder renders when no observations. * Day-one priority primitives sort to the top of their group: motor.input_modality first in motor; the three cognitive priority primitives in documented order at the top of cognitive. * Each row renders primitive leaf, value, and confidence-percent badge. * Groups follow the canonical domain order (motor / cognitive / temporal / operational / environmental / emotional_valence); unknown domains alphabetise at the end. Mirrors the Orchestrator.test.tsx harness shape (DEBT-043). Live update path (useAttackerStream → setObservations) is exercised indirectly via the static render — the hook is dumb glue and the state mutation is React-side. --- .../AttackerDetail.behaviour_panel.test.tsx | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 decnet_web/src/components/AttackerDetail.behaviour_panel.test.tsx diff --git a/decnet_web/src/components/AttackerDetail.behaviour_panel.test.tsx b/decnet_web/src/components/AttackerDetail.behaviour_panel.test.tsx new file mode 100644 index 00000000..9d94ec6a --- /dev/null +++ b/decnet_web/src/components/AttackerDetail.behaviour_panel.test.tsx @@ -0,0 +1,87 @@ +import { describe, it, expect } from 'vitest'; +import { render, screen } from '@testing-library/react'; + +import { + BehaviouralPrimitivesPanel, + type BehaviouralObservation, +} from './AttackerDetail'; + +const _obs = ( + primitive: string, + value: unknown, + confidence = 0.85, +): BehaviouralObservation => ({ + primitive, + value, + confidence, + ts: 1714521660.456, + source: 'test', +}); + +describe('BehaviouralPrimitivesPanel', () => { + it('renders an empty-state placeholder when no observations', () => { + render(); + expect(screen.getByTestId('behaviour-empty')).toBeInTheDocument(); + }); + + it('places day-one priority primitives at the top of their group', () => { + // Mix priority + non-priority primitives in arbitrary input order. + const observations: BehaviouralObservation[] = [ + _obs('motor.keystroke_cadence', 'steady'), + _obs('cognitive.tool_vocabulary', 'broad'), + _obs('motor.input_modality', 'typed'), // priority #1 + _obs('cognitive.feedback_loop_engagement', 'closed_loop'), // priority #2 + _obs('cognitive.command_branch_diversity', 'adaptive_branching'), // #3 + _obs('cognitive.inter_command_latency_class', 'typing_speed'), // #4 + _obs('motor.error_correction', 'immediate'), + ]; + render(); + + // motor group: input_modality must precede the alphabetised rest. + const motorRows = Array.from( + document.querySelectorAll('[data-testid^="behaviour-row-motor."]'), + ).map((el) => el.getAttribute('data-testid')!); + expect(motorRows[0]).toBe('behaviour-row-motor.input_modality'); + + // cognitive group: the three priority primitives must be in the + // documented order at the top. + const cogRows = Array.from( + document.querySelectorAll('[data-testid^="behaviour-row-cognitive."]'), + ).map((el) => el.getAttribute('data-testid')!); + expect(cogRows.slice(0, 3)).toEqual([ + 'behaviour-row-cognitive.feedback_loop_engagement', + 'behaviour-row-cognitive.command_branch_diversity', + 'behaviour-row-cognitive.inter_command_latency_class', + ]); + }); + + it('renders the primitive value and a confidence badge', () => { + const observations: BehaviouralObservation[] = [ + _obs('motor.input_modality', 'pasted', 0.91), + ]; + render(); + const row = screen.getByTestId('behaviour-row-motor.input_modality'); + expect(row.textContent).toContain('input_modality'); + expect(row.textContent).toContain('pasted'); + expect(row.textContent).toContain('91%'); + }); + + it('groups by top-level domain in the canonical order', () => { + const observations: BehaviouralObservation[] = [ + _obs('emotional_valence.arousal', 'medium_engaged'), + _obs('temporal.session_duration', 'medium'), + _obs('motor.input_modality', 'typed'), + _obs('cognitive.cognitive_load', 'medium'), + ]; + render(); + const groups = Array.from( + document.querySelectorAll('[data-testid^="behaviour-group-"]'), + ).map((el) => el.getAttribute('data-testid')!); + expect(groups).toEqual([ + 'behaviour-group-motor', + 'behaviour-group-cognitive', + 'behaviour-group-temporal', + 'behaviour-group-emotional_valence', + ]); + }); +});