Files
worldmonitor/tests/regional-intelligence-board.test.mts
Elie Habib ebcbcceeff Phase 3 PR3: Regime drift timeline + weekly brief in RegionalIntelligenceBoard (#2995)
* feat(intelligence): regime drift timeline + weekly brief in RegionalIntelligenceBoard (Phase 3 PR3)

Phase 3 PR3 of the Regional Intelligence Model. Adds two new blocks to
the existing RegionalIntelligenceBoard panel, completing the UI layer
for the full feature set.

## New blocks

### Regime drift timeline (buildRegimeHistoryBlock)

Renders the region's regime transition log newest-first as a compact
timeline. Each row shows: date, previous label → current label, and
the transition driver if present. Capped at 20 entries in the UI
(the Redis list holds up to 100 from Phase 3 PR1).

Shows "No regime transitions recorded yet" when the history is empty
(no transitions have occurred since Phase 3 PR1 deployed).

### Weekly brief (buildWeeklyBriefBlock)

Renders the latest weekly intelligence brief with:
  - Period date range (e.g., 2026-04-04 — 2026-04-11)
  - Situation recap (primary paragraph)
  - Regime trajectory (1-sentence summary)
  - Key developments (up to 5 bullet points)
  - Risk outlook (forward-looking paragraph)
  - Provider + model source

Shows "No weekly brief available yet" when the brief is undefined or
has an empty situationRecap.

## Panel changes

RegionalIntelligenceBoard.loadCurrent() now fires 3 RPCs in parallel
via Promise.allSettled:
  1. getRegionalSnapshot (required — empty board if this fails)
  2. getRegimeHistory (best-effort — board renders without it)
  3. getRegionalBrief (best-effort — board renders without it)

Sequence arbitration still applies: stale responses from earlier
region selections are discarded. The new blocks render AFTER the
existing 6 blocks + meta footer in the board layout.

## Tests — 9 new unit tests

buildRegimeHistoryBlock (4):
  - transitions render with date + from → to + driver
  - empty transitions → "no transitions"
  - capped at 20 entries
  - HTML escaping

buildWeeklyBriefBlock (5):
  - all sections render when populated
  - undefined brief → "no brief"
  - empty situationRecap → "no brief"
  - period date range rendered
  - HTML escaping

## Verification

- npx tsx --test tests/regional-intelligence-board.test.mts: 54/54 pass
- npm run test:data: 4831/4831 pass
- npm run typecheck: clean
- biome lint: clean (1 pre-existing gateway complexity warning)

* fix(intelligence): distinguish RPC failure from empty data in board render (review P2s on #2995)

* fix(intelligence): render error state on snapshot RPC rejection, not empty state (review P2 on #2995)

* fix(intelligence): show 'no brief yet' empty state on successful RPC with no brief (review P2 on #2995)

* fix(intelligence): cap keyDevelopments at 5 in weekly brief block (Greptile P2 on #2995)

* fix(intelligence): non-blocking enrichments + upstreamUnavailable check (review H1+H2 on #2995)

H1 — upstreamUnavailable responses misclassified as empty data

getRegimeHistory and getRegionalBrief return HTTP 200 with
upstreamUnavailable:true on Redis failure. The old code only checked
Promise.allSettled status (fulfilled vs rejected) so these responses
landed in the fulfilled branch and got rendered as "No transitions"
/ "No brief yet" instead of being omitted. Now checks for the
upstreamUnavailable flag in the response body and treats it as null
(omit block).

H2 — enrichment RPCs blocked the snapshot render

Promise.allSettled awaited all 3 RPCs before rendering anything. A
slow history or brief timeout left the panel stuck in "Loading..." for
30+ seconds even though the snapshot was ready. Now uses a two-phase
render: snapshot renders immediately on resolution, then history+brief
fire in the background and re-render the board with enrichments when
they resolve. The sequence arbitrator still discards stale responses
from earlier region selections.

* fix(intelligence): suppress false empty-state during enrichment fetch (review H on #2995)
2026-04-12 11:06:51 +04:00

749 lines
33 KiB
TypeScript

// Tests for the RegionalIntelligenceBoard pure HTML builders.
// The builders are exported so we can test without DOM / panel instantiation.
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import {
BOARD_REGIONS,
buildBoardHtml,
buildNarrativeHtml,
buildRegimeBlock,
buildBalanceBlock,
buildActorsBlock,
buildScenariosBlock,
buildTransmissionBlock,
buildWatchlistBlock,
buildMetaFooter,
buildRegimeHistoryBlock,
buildWeeklyBriefBlock,
isLatestSequence,
} from '../src/components/regional-intelligence-board-utils';
import type {
RegionalSnapshot,
BalanceVector,
ActorState,
ScenarioSet,
TransmissionPath,
Trigger,
RegionalNarrative,
NarrativeSection,
} from '../src/generated/client/worldmonitor/intelligence/v1/service_client';
// ────────────────────────────────────────────────────────────────────────────
// Fixtures
// ────────────────────────────────────────────────────────────────────────────
function balanceFixture(overrides: Partial<BalanceVector> = {}): BalanceVector {
return {
coercivePressure: 0.72,
domesticFragility: 0.55,
capitalStress: 0.40,
energyVulnerability: 0.30,
allianceCohesion: 0.60,
maritimeAccess: 0.70,
energyLeverage: 0.80,
netBalance: 0.07,
pressures: [],
buffers: [],
...overrides,
};
}
function snapshotFixture(overrides: Partial<RegionalSnapshot> = {}): RegionalSnapshot {
return {
regionId: 'mena',
generatedAt: 1_700_000_000_000,
meta: {
snapshotId: 'snap-1',
modelVersion: '0.1.0',
scoringVersion: '1.0.0',
geographyVersion: '1.0.0',
snapshotConfidence: 0.92,
missingInputs: [],
staleInputs: [],
validUntil: 0,
triggerReason: 'scheduled_6h',
narrativeProvider: 'groq',
narrativeModel: 'llama-3.3-70b-versatile',
},
regime: {
label: 'coercive_stalemate',
previousLabel: 'calm',
transitionedAt: 1_700_000_000_000,
transitionDriver: 'cross_source_surge',
},
balance: balanceFixture(),
actors: [
{ actorId: 'IR', name: 'Iran', role: 'aggressor', leverageDomains: ['military', 'energy'], leverageScore: 0.85, delta: 0.05, evidenceIds: [] },
{ actorId: 'IL', name: 'Israel', role: 'stabilizer', leverageDomains: ['military'], leverageScore: 0.70, delta: 0.00, evidenceIds: [] },
{ actorId: 'SA', name: 'Saudi Arabia', role: 'broker', leverageDomains: ['energy'], leverageScore: 0.65, delta: -0.02, evidenceIds: [] },
],
leverageEdges: [],
scenarioSets: [
{ horizon: '24h', lanes: [
{ name: 'base', probability: 0.5, triggerIds: [], consequences: [], transmissions: [] },
{ name: 'escalation', probability: 0.3, triggerIds: [], consequences: [], transmissions: [] },
{ name: 'containment', probability: 0.15, triggerIds: [], consequences: [], transmissions: [] },
{ name: 'fragmentation', probability: 0.05, triggerIds: [], consequences: [], transmissions: [] },
] },
{ horizon: '7d', lanes: [
{ name: 'base', probability: 0.4, triggerIds: [], consequences: [], transmissions: [] },
{ name: 'escalation', probability: 0.4, triggerIds: [], consequences: [], transmissions: [] },
{ name: 'containment', probability: 0.15, triggerIds: [], consequences: [], transmissions: [] },
{ name: 'fragmentation', probability: 0.05, triggerIds: [], consequences: [], transmissions: [] },
] },
{ horizon: '30d', lanes: [
{ name: 'base', probability: 0.35, triggerIds: [], consequences: [], transmissions: [] },
{ name: 'escalation', probability: 0.45, triggerIds: [], consequences: [], transmissions: [] },
{ name: 'containment', probability: 0.15, triggerIds: [], consequences: [], transmissions: [] },
{ name: 'fragmentation', probability: 0.05, triggerIds: [], consequences: [], transmissions: [] },
] },
],
transmissionPaths: [
{ start: 'hormuz', mechanism: 'naval_posture', end: 'crude_oil', severity: 'high', corridorId: 'hormuz', confidence: 0.85, latencyHours: 12, impactedAssetClass: 'commodity', impactedRegions: ['mena'], magnitudeLow: 0, magnitudeHigh: 0, magnitudeUnit: 'pct', templateId: 't1', templateVersion: '1.0.0' },
{ start: 'babelm', mechanism: 'shipping_disruption', end: 'container', severity: 'medium', corridorId: 'babelm', confidence: 0.6, latencyHours: 24, impactedAssetClass: 'commodity', impactedRegions: ['mena'], magnitudeLow: 0, magnitudeHigh: 0, magnitudeUnit: 'pct', templateId: 't2', templateVersion: '1.0.0' },
],
triggers: {
active: [
{ id: 'mena_coercive_high', description: 'Coercive pressure crossed 0.7', threshold: undefined, activated: true, activatedAt: 0, scenarioLane: 'escalation', evidenceIds: [] },
],
watching: [],
dormant: [],
},
mobility: undefined,
evidence: [],
narrative: {
situation: { text: 'Iran flexes naval posture near the Strait of Hormuz.', evidenceIds: ['ev1'] },
balanceAssessment: { text: 'Pressures edge ahead of buffers.', evidenceIds: ['ev2'] },
outlook24h: { text: 'Base case dominates.', evidenceIds: [] },
outlook7d: { text: 'Escalation risk rises over the coming week.', evidenceIds: [] },
outlook30d: { text: 'Uncertainty widens.', evidenceIds: [] },
watchItems: [
{ text: 'Hormuz transit counts below seasonal.', evidenceIds: ['ev1'] },
],
},
...overrides,
};
}
// ────────────────────────────────────────────────────────────────────────────
// BOARD_REGIONS
// ────────────────────────────────────────────────────────────────────────────
describe('BOARD_REGIONS', () => {
it('exposes 7 non-global regions', () => {
assert.equal(BOARD_REGIONS.length, 7);
assert.ok(!BOARD_REGIONS.some((r) => r.id === 'global'));
});
it('includes every expected region ID', () => {
const ids = BOARD_REGIONS.map((r) => r.id).sort();
assert.deepEqual(ids, [
'east-asia',
'europe',
'latam',
'mena',
'north-america',
'south-asia',
'sub-saharan-africa',
]);
});
});
// ────────────────────────────────────────────────────────────────────────────
// buildRegimeBlock
// ────────────────────────────────────────────────────────────────────────────
describe('buildRegimeBlock', () => {
it('renders the current regime label', () => {
const html = buildRegimeBlock(snapshotFixture());
assert.match(html, /coercive stalemate/i);
});
it('shows the "Was:" line when regime changed', () => {
const html = buildRegimeBlock(snapshotFixture());
assert.match(html, /Was:\s*calm/);
assert.match(html, /cross_source_surge/);
});
it('hides the "Was:" line when regime is unchanged', () => {
const html = buildRegimeBlock(snapshotFixture({
regime: { label: 'calm', previousLabel: 'calm', transitionedAt: 0, transitionDriver: '' },
}));
assert.doesNotMatch(html, /Was:/);
});
it('handles missing regime by falling back to "unknown"', () => {
const html = buildRegimeBlock(snapshotFixture({ regime: undefined }));
assert.match(html, /unknown/);
});
});
// ────────────────────────────────────────────────────────────────────────────
// buildBalanceBlock
// ────────────────────────────────────────────────────────────────────────────
describe('buildBalanceBlock', () => {
it('renders all 4 pressure axes and 3 buffer axes', () => {
const html = buildBalanceBlock(balanceFixture());
assert.match(html, /Coercive/);
assert.match(html, /Fragility/);
assert.match(html, /Capital/);
assert.match(html, /Energy Vuln/);
assert.match(html, /Alliance/);
assert.match(html, /Maritime/);
assert.match(html, /Energy Lev/);
});
it('renders the net_balance bar', () => {
const html = buildBalanceBlock(balanceFixture({ netBalance: -0.25 }));
assert.match(html, /Net Balance/);
assert.match(html, /-0\.25/);
});
it('shows "Unavailable" when balance is missing', () => {
const html = buildBalanceBlock(undefined);
assert.match(html, /Unavailable/);
});
it('clamps axis values to [0, 1] for bar width', () => {
// A value > 1 should not break the HTML.
const html = buildBalanceBlock(balanceFixture({ coercivePressure: 1.5 }));
assert.match(html, /width:100\.0%/);
});
});
// ────────────────────────────────────────────────────────────────────────────
// buildActorsBlock
// ────────────────────────────────────────────────────────────────────────────
describe('buildActorsBlock', () => {
it('renders all actors up to the top-5 cap', () => {
const actors: ActorState[] = Array.from({ length: 10 }, (_, i) => ({
actorId: `a${i}`,
name: `Actor ${i}`,
role: 'actor',
leverageDomains: [],
leverageScore: 1 - i * 0.1,
delta: 0,
evidenceIds: [],
}));
const html = buildActorsBlock(actors);
assert.match(html, /Actor 0/);
assert.match(html, /Actor 4/);
assert.doesNotMatch(html, /Actor 5/);
});
it('sorts actors by leverage_score descending', () => {
const html = buildActorsBlock([
{ actorId: 'Z', name: 'Low', role: 'actor', leverageDomains: [], leverageScore: 0.1, delta: 0, evidenceIds: [] },
{ actorId: 'A', name: 'High', role: 'actor', leverageDomains: [], leverageScore: 0.9, delta: 0, evidenceIds: [] },
]);
const highIdx = html.indexOf('High');
const lowIdx = html.indexOf('Low');
assert.ok(highIdx < lowIdx, 'high-leverage actor should appear first');
});
it('colors positive delta (rising) differently from negative', () => {
const html = buildActorsBlock([
{ actorId: 'A', name: 'Rising', role: 'actor', leverageDomains: [], leverageScore: 0.5, delta: 0.1, evidenceIds: [] },
{ actorId: 'B', name: 'Falling', role: 'actor', leverageDomains: [], leverageScore: 0.4, delta: -0.1, evidenceIds: [] },
]);
// Positive delta uses danger color; negative uses accent.
assert.match(html, /\+0\.10/);
assert.match(html, /-0\.10/);
});
it('shows empty-state when no actors', () => {
const html = buildActorsBlock([]);
assert.match(html, /No actor data/);
});
});
// ────────────────────────────────────────────────────────────────────────────
// buildScenariosBlock
// ────────────────────────────────────────────────────────────────────────────
describe('buildScenariosBlock', () => {
it('renders one column per horizon in canonical order 24h → 7d → 30d', () => {
const html = buildScenariosBlock(snapshotFixture().scenarioSets);
const i24 = html.indexOf('24h');
const i7d = html.indexOf('7d');
const i30d = html.indexOf('30d');
assert.ok(i24 < i7d && i7d < i30d, `horizons out of order: 24h=${i24}, 7d=${i7d}, 30d=${i30d}`);
});
it('renders lane probabilities as percentages', () => {
const html = buildScenariosBlock(snapshotFixture().scenarioSets);
assert.match(html, /50%/); // 24h base
assert.match(html, /45%/); // 30d escalation
});
it('sorts lanes within each horizon by probability descending', () => {
const html = buildScenariosBlock([
{ horizon: '24h', lanes: [
{ name: 'fragmentation', probability: 0.05, triggerIds: [], consequences: [], transmissions: [] },
{ name: 'base', probability: 0.8, triggerIds: [], consequences: [], transmissions: [] },
] },
]);
assert.ok(html.indexOf('base') < html.indexOf('fragmentation'));
});
it('shows empty-state when no scenarios', () => {
const html = buildScenariosBlock([]);
assert.match(html, /No scenario data/);
});
});
// ────────────────────────────────────────────────────────────────────────────
// buildTransmissionBlock
// ────────────────────────────────────────────────────────────────────────────
describe('buildTransmissionBlock', () => {
it('renders each transmission path with mechanism + corridor + severity', () => {
const html = buildTransmissionBlock(snapshotFixture().transmissionPaths);
assert.match(html, /naval_posture/);
assert.match(html, /hormuz/);
assert.match(html, /high/i);
});
it('sorts transmissions by confidence descending', () => {
const paths: TransmissionPath[] = [
{ start: 'a', mechanism: 'low_conf', end: 'x', severity: 'low', corridorId: '', confidence: 0.2, latencyHours: 0, impactedAssetClass: '', impactedRegions: [], magnitudeLow: 0, magnitudeHigh: 0, magnitudeUnit: '', templateId: '', templateVersion: '' },
{ start: 'b', mechanism: 'high_conf', end: 'y', severity: 'high', corridorId: '', confidence: 0.9, latencyHours: 0, impactedAssetClass: '', impactedRegions: [], magnitudeLow: 0, magnitudeHigh: 0, magnitudeUnit: '', templateId: '', templateVersion: '' },
];
const html = buildTransmissionBlock(paths);
assert.ok(html.indexOf('high_conf') < html.indexOf('low_conf'));
});
it('caps transmissions at top 5', () => {
const paths: TransmissionPath[] = Array.from({ length: 10 }, (_, i) => ({
start: 's', mechanism: `m${i}`, end: 'e', severity: 'low', corridorId: '', confidence: 1 - i * 0.1, latencyHours: 0, impactedAssetClass: '', impactedRegions: [], magnitudeLow: 0, magnitudeHigh: 0, magnitudeUnit: '', templateId: '', templateVersion: '',
}));
const html = buildTransmissionBlock(paths);
assert.match(html, /m0/);
assert.match(html, /m4/);
assert.doesNotMatch(html, /m5\b/);
});
it('shows empty-state when no transmissions', () => {
const html = buildTransmissionBlock([]);
assert.match(html, /No active transmissions/);
});
});
// ────────────────────────────────────────────────────────────────────────────
// buildWatchlistBlock
// ────────────────────────────────────────────────────────────────────────────
describe('buildWatchlistBlock', () => {
it('renders active triggers + narrative watch items', () => {
const triggers: Trigger[] = [
{ id: 'trig1', description: 'desc', threshold: undefined, activated: true, activatedAt: 0, scenarioLane: 'escalation', evidenceIds: [] },
];
const watchItems: NarrativeSection[] = [
{ text: 'Watch Hormuz volumes', evidenceIds: [] },
];
const html = buildWatchlistBlock(triggers, watchItems);
assert.match(html, /trig1/);
assert.match(html, /Watch Hormuz volumes/);
assert.match(html, /Active Triggers/);
assert.match(html, /Watch Items/);
});
it('shows only triggers when watch items are empty', () => {
const html = buildWatchlistBlock([
{ id: 'trig1', description: '', threshold: undefined, activated: true, activatedAt: 0, scenarioLane: 'escalation', evidenceIds: [] },
], []);
assert.match(html, /Active Triggers/);
assert.doesNotMatch(html, /Watch Items/);
});
it('shows only watch items when triggers are empty', () => {
const html = buildWatchlistBlock([], [{ text: 'Watch this', evidenceIds: [] }]);
assert.doesNotMatch(html, /Active Triggers/);
assert.match(html, /Watch this/);
});
it('filters watch items with empty text', () => {
const html = buildWatchlistBlock([], [
{ text: '', evidenceIds: [] },
{ text: 'Real item', evidenceIds: [] },
]);
assert.match(html, /Real item/);
// No empty bullet rows.
assert.doesNotMatch(html, /▸\s*<\/div>/);
});
it('shows empty-state when both sources are empty', () => {
const html = buildWatchlistBlock([], []);
assert.match(html, /No active triggers or watch items/);
});
});
// ────────────────────────────────────────────────────────────────────────────
// buildNarrativeHtml
// ────────────────────────────────────────────────────────────────────────────
describe('buildNarrativeHtml', () => {
it('renders all populated sections', () => {
const html = buildNarrativeHtml(snapshotFixture().narrative);
assert.match(html, /Iran flexes naval posture/);
assert.match(html, /Pressures edge ahead/);
assert.match(html, /Base case dominates/);
});
it('hides empty sections', () => {
const narrative: RegionalNarrative = {
situation: { text: 'Only this one.', evidenceIds: [] },
balanceAssessment: { text: '', evidenceIds: [] },
outlook24h: { text: '', evidenceIds: [] },
outlook7d: { text: '', evidenceIds: [] },
outlook30d: { text: '', evidenceIds: [] },
watchItems: [],
};
const html = buildNarrativeHtml(narrative);
assert.match(html, /Only this one/);
assert.doesNotMatch(html, /Outlook/);
assert.doesNotMatch(html, /Balance Assessment/);
});
it('returns empty string when the whole narrative is empty', () => {
const narrative: RegionalNarrative = {
situation: { text: '', evidenceIds: [] },
balanceAssessment: { text: '', evidenceIds: [] },
outlook24h: { text: '', evidenceIds: [] },
outlook7d: { text: '', evidenceIds: [] },
outlook30d: { text: '', evidenceIds: [] },
watchItems: [],
};
assert.equal(buildNarrativeHtml(narrative), '');
});
it('returns empty string when narrative is undefined', () => {
assert.equal(buildNarrativeHtml(undefined), '');
});
it('displays evidence ID pills when present', () => {
const html = buildNarrativeHtml(snapshotFixture().narrative);
assert.match(html, /\[ev1\]/);
});
});
// ────────────────────────────────────────────────────────────────────────────
// buildMetaFooter
// ────────────────────────────────────────────────────────────────────────────
describe('buildMetaFooter', () => {
it('renders confidence, versions, and narrative source', () => {
const html = buildMetaFooter(snapshotFixture());
assert.match(html, /confidence 92%/);
assert.match(html, /scoring v1\.0\.0/);
assert.match(html, /geo v1\.0\.0/);
assert.match(html, /groq\/llama-3\.3-70b-versatile/);
});
it('shows "no narrative" when provider is empty', () => {
const html = buildMetaFooter(snapshotFixture({
meta: { ...snapshotFixture().meta!, narrativeProvider: '', narrativeModel: '' },
}));
assert.match(html, /no narrative/);
});
it('returns empty string when meta is missing', () => {
assert.equal(buildMetaFooter(snapshotFixture({ meta: undefined })), '');
});
});
// ────────────────────────────────────────────────────────────────────────────
// buildBoardHtml (integration)
// ────────────────────────────────────────────────────────────────────────────
describe('buildBoardHtml', () => {
it('includes all 6 block titles + narrative + meta footer', () => {
const html = buildBoardHtml(snapshotFixture());
assert.match(html, /Narrative/);
assert.match(html, /Regime/);
assert.match(html, /Balance Vector/);
assert.match(html, /Actors/);
assert.match(html, /Scenarios/);
assert.match(html, /Transmission Paths/);
assert.match(html, /Watchlist/);
assert.match(html, /generated/);
assert.match(html, /confidence/);
});
it('escapes user-provided strings to prevent HTML injection', () => {
const malicious = snapshotFixture({
actors: [{
actorId: 'A1',
name: '<img src=x onerror=alert(1)>',
role: '<script>bad()</script>',
leverageDomains: [],
leverageScore: 0.5,
delta: 0,
evidenceIds: [],
}],
});
const html = buildBoardHtml(malicious);
// Raw HTML must not appear...
assert.doesNotMatch(html, /<img src=x onerror/);
assert.doesNotMatch(html, /<script>bad/);
// ...and the escaped versions must appear.
assert.match(html, /&lt;img src=x onerror/);
assert.match(html, /&lt;script&gt;bad/);
});
it('renders a mostly-empty snapshot without throwing', () => {
const bare = snapshotFixture({
actors: [],
scenarioSets: [],
transmissionPaths: [],
triggers: { active: [], watching: [], dormant: [] },
narrative: undefined,
});
assert.doesNotThrow(() => buildBoardHtml(bare));
const html = buildBoardHtml(bare);
assert.match(html, /No actor data/);
assert.match(html, /No scenario data/);
assert.match(html, /No active transmissions/);
assert.match(html, /No active triggers or watch items/);
});
});
// ────────────────────────────────────────────────────────────────────────────
// Request-sequence arbitrator (P2 fix for PR #2963 review)
// ────────────────────────────────────────────────────────────────────────────
describe('isLatestSequence', () => {
it('returns true when the claimed sequence still matches latest', () => {
assert.equal(isLatestSequence(1, 1), true);
assert.equal(isLatestSequence(42, 42), true);
});
it('returns false when a newer sequence has claimed latest', () => {
assert.equal(isLatestSequence(1, 2), false);
assert.equal(isLatestSequence(9, 10), false);
});
it('returns false for any mismatch (even when mine > latest, defensive)', () => {
assert.equal(isLatestSequence(5, 3), false);
});
});
// ────────────────────────────────────────────────────────────────────────────
// Simulated fast-dropdown race (P2 fix for PR #2963 review)
// ────────────────────────────────────────────────────────────────────────────
//
// Mimics the loadCurrent() flow without instantiating the Panel class
// (which transitively imports @/services/i18n and fails node:test).
// Each "load" claims a sequence, awaits a controllable RPC, then calls a
// rendered callback ONLY if isLatestSequence(mySeq, latestSeq). The test
// orchestrates two overlapping loads where the first RPC resolves AFTER
// the second, and asserts only the second render fires.
describe('loadCurrent race simulation', () => {
it('drops an earlier in-flight response when a later region is selected', async () => {
const state = { latestSequence: 0, currentRegion: 'mena', rendered: [] as string[] };
// Two resolvable deferreds so the test controls finish order.
let resolveA: (value: string) => void;
let resolveB: (value: string) => void;
const pA = new Promise<string>((resolve) => { resolveA = resolve; });
const pB = new Promise<string>((resolve) => { resolveB = resolve; });
async function loadCurrent(regionId: string, promise: Promise<string>) {
state.latestSequence += 1;
const mySeq = state.latestSequence;
state.currentRegion = regionId;
const result = await promise;
if (!isLatestSequence(mySeq, state.latestSequence)) return;
state.rendered.push(`${regionId}:${result}`);
}
// Kick off call A (mena), then call B (east-asia) — call B claims
// the later sequence. Order of resolution is intentionally reversed:
// B resolves first, then A. A must be discarded as stale.
const loadA = loadCurrent('mena', pA);
const loadB = loadCurrent('east-asia', pB);
resolveB!('snapshot-east-asia');
await loadB;
resolveA!('snapshot-mena');
await loadA;
assert.deepEqual(state.rendered, ['east-asia:snapshot-east-asia']);
});
it('renders the latest load even when it resolves before an earlier one', async () => {
const state = { latestSequence: 0, rendered: [] as string[] };
let resolveA: (value: string) => void;
let resolveB: (value: string) => void;
const pA = new Promise<string>((resolve) => { resolveA = resolve; });
const pB = new Promise<string>((resolve) => { resolveB = resolve; });
async function loadCurrent(regionId: string, promise: Promise<string>) {
state.latestSequence += 1;
const mySeq = state.latestSequence;
const result = await promise;
if (!isLatestSequence(mySeq, state.latestSequence)) return;
state.rendered.push(`${regionId}:${result}`);
}
const loadA = loadCurrent('mena', pA);
const loadB = loadCurrent('europe', pB);
// A resolves first (normal ordering), but B has claimed a later seq,
// so when A checks the arbitrator (seq 1 vs latest 2) it discards.
resolveA!('snap-a');
await loadA;
resolveB!('snap-b');
await loadB;
assert.deepEqual(state.rendered, ['europe:snap-b']);
});
it('three rapid switches render only the last one', async () => {
const state = { latestSequence: 0, rendered: [] as string[] };
const resolvers: Array<(value: string) => void> = [];
const promises = [0, 1, 2].map(
() => new Promise<string>((resolve) => { resolvers.push(resolve); }),
);
async function loadCurrent(regionId: string, promise: Promise<string>) {
state.latestSequence += 1;
const mySeq = state.latestSequence;
const result = await promise;
if (!isLatestSequence(mySeq, state.latestSequence)) return;
state.rendered.push(`${regionId}:${result}`);
}
const loadMena = loadCurrent('mena', promises[0]!);
const loadEu = loadCurrent('europe', promises[1]!);
const loadEa = loadCurrent('east-asia', promises[2]!);
// Resolve out of order: middle first, then last, then first.
resolvers[1]!('snap-eu');
await loadEu;
resolvers[2]!('snap-ea');
await loadEa;
resolvers[0]!('snap-mena');
await loadMena;
assert.deepEqual(state.rendered, ['east-asia:snap-ea']);
});
it('a single load (no race) still renders', async () => {
const state = { latestSequence: 0, rendered: [] as string[] };
async function loadCurrent(regionId: string, promise: Promise<string>) {
state.latestSequence += 1;
const mySeq = state.latestSequence;
const result = await promise;
if (!isLatestSequence(mySeq, state.latestSequence)) return;
state.rendered.push(`${regionId}:${result}`);
}
await loadCurrent('mena', Promise.resolve('snap'));
assert.deepEqual(state.rendered, ['mena:snap']);
});
});
// ────────────────────────────────────────────────────────────────────────────
// Regime History block (Phase 3 PR3)
// ────────────────────────────────────────────────────────────────────────────
describe('buildRegimeHistoryBlock', () => {
it('renders transitions newest-first with date + from → to', () => {
const transitions = [
{ regionId: 'mena', label: 'escalation_ladder', previousLabel: 'coercive_stalemate', transitionedAt: 1700000000000, transitionDriver: 'regime_shift', snapshotId: 's1' },
{ regionId: 'mena', label: 'coercive_stalemate', previousLabel: 'calm', transitionedAt: 1699900000000, transitionDriver: '', snapshotId: 's0' },
];
const html = buildRegimeHistoryBlock(transitions);
assert.match(html, /Regime History/);
assert.match(html, /escalation ladder/);
assert.match(html, /coercive stalemate/);
assert.match(html, /calm/);
assert.match(html, /regime_shift/);
});
it('shows "no transitions" for empty array', () => {
const html = buildRegimeHistoryBlock([]);
assert.match(html, /No regime transitions/);
});
it('caps at 20 entries', () => {
const transitions = Array.from({ length: 30 }, (_, i) => ({
regionId: 'mena', label: 'calm', previousLabel: 'calm',
transitionedAt: Date.now() - i * 86400000, transitionDriver: '', snapshotId: `s${i}`,
}));
const html = buildRegimeHistoryBlock(transitions);
const count = (html.match(/rib-section/g) ?? []).length;
// Still just one section wrapper, but not 30 rows visible
assert.ok(count >= 1);
});
it('escapes HTML in labels', () => {
const transitions = [
{ regionId: 'mena', label: '<script>x</script>', previousLabel: '', transitionedAt: 0, transitionDriver: '', snapshotId: '' },
];
const html = buildRegimeHistoryBlock(transitions);
assert.doesNotMatch(html, /<script>x<\/script>/);
assert.match(html, /&lt;script&gt;/);
});
});
// ────────────────────────────────────────────────────────────────────────────
// Weekly Brief block (Phase 3 PR3)
// ────────────────────────────────────────────────────────────────────────────
describe('buildWeeklyBriefBlock', () => {
const brief = {
regionId: 'mena',
generatedAt: Date.now(),
periodStart: Date.now() - 7 * 86400000,
periodEnd: Date.now(),
situationRecap: 'Iran increased naval posture near Hormuz.',
regimeTrajectory: 'Shifted from calm to coercive stalemate mid-week.',
keyDevelopments: ['Hormuz transit dropped 15%', 'CII spike for Iran'],
riskOutlook: 'Escalation risk remains elevated.',
provider: 'groq',
model: 'llama-3.3-70b-versatile',
};
it('renders all brief sections when populated', () => {
const html = buildWeeklyBriefBlock(brief);
assert.match(html, /Weekly Brief/);
assert.match(html, /Iran increased naval posture/);
assert.match(html, /Shifted from calm/);
assert.match(html, /Hormuz transit dropped/);
assert.match(html, /CII spike/);
assert.match(html, /Escalation risk/);
assert.match(html, /groq/);
});
it('shows "no brief" for undefined', () => {
const html = buildWeeklyBriefBlock(undefined);
assert.match(html, /No weekly brief available/);
});
it('shows "no brief" when situationRecap is empty', () => {
const html = buildWeeklyBriefBlock({ ...brief, situationRecap: '' });
assert.match(html, /No weekly brief available/);
});
it('renders period date range', () => {
const html = buildWeeklyBriefBlock(brief);
// Should contain date strings like 2026-04-04
assert.match(html, /\d{4}-\d{2}-\d{2}/);
});
it('escapes HTML in brief content', () => {
const malicious = { ...brief, situationRecap: '<img onerror=alert(1)>' };
const html = buildWeeklyBriefBlock(malicious);
assert.doesNotMatch(html, /<img onerror/);
assert.match(html, /&lt;img/);
});
});