import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
import test from 'node:test';
import { fileURLToPath } from 'node:url';
const here = path.dirname(fileURLToPath(import.meta.url));
const root = path.resolve(here, '..', '..', '..', '..');
void test('SceneOverlayHost: один dim для materials + npcs в Control и Presentation', () => {
const host = fs.readFileSync(path.join(here, 'SceneOverlayHost.tsx'), 'utf8');
const control = fs.readFileSync(path.join(root, 'app/renderer/control/ControlApp.tsx'), 'utf8');
const presentation = fs.readFileSync(path.join(root, 'app/renderer/shared/PresentationView.tsx'), 'utf8');
const css = fs.readFileSync(
path.join(root, 'app/renderer/shared/materials/MaterialOverlay.module.css'),
'utf8',
);
assert.ok(host.includes('styles.dim'));
assert.ok(host.includes('hostHitThrough'));
// Dim только при active; жёлтая рамка без затемнения.
assert.match(host, /\{active \?
{
const material = fs.readFileSync(
path.join(root, 'app/renderer/shared/materials/MaterialOverlay.tsx'),
'utf8',
);
const npcs = fs.readFileSync(path.join(root, 'app/renderer/shared/npcs/NpcsSceneOverlay.tsx'), 'utf8');
assert.ok(material.includes('embedded'));
assert.ok(material.includes('useSceneOverlayView'));
assert.ok(npcs.includes('embedded'));
assert.ok(npcs.includes('useSceneOverlayView'));
// В embedded-ветке не рисуем второй dim.
assert.match(material, /if \(embedded\) \{\s*return frame;/);
assert.match(npcs, /if \(embedded\) \{\s*return <>\{frames\}<\/>;/);
});
void test('overlays: layout IPC live через rAF coalesce, RO host coalesced', () => {
const host = fs.readFileSync(path.join(here, 'SceneOverlayHost.tsx'), 'utf8');
const material = fs.readFileSync(
path.join(root, 'app/renderer/shared/materials/MaterialOverlay.tsx'),
'utf8',
);
const npcs = fs.readFileSync(path.join(root, 'app/renderer/shared/npcs/NpcsSceneOverlay.tsx'), 'utf8');
assert.ok(host.includes('requestAnimationFrame'));
assert.ok(host.includes('prev.w === w && prev.h === h'));
// Лайв: publishDraft шлёт onLayoutChange внутри rAF (не на каждый pointermove).
assert.ok(material.includes('publishDraft'));
assert.ok(material.includes('onLayoutChangeRef'));
assert.match(material, /requestAnimationFrame\(\(\) => \{[\s\S]*?onLayoutChangeRef\.current\?\.\(pending\)/);
assert.match(
material,
/if \(drag\.mode === 'move'\) \{[\s\S]*?publishDraft\(\{[\s\S]*?return;\s*\}/,
);
assert.ok(npcs.includes('publishDraft'));
assert.ok(npcs.includes('onLayoutChangeRef'));
assert.match(
npcs,
/requestAnimationFrame\(\(\) => \{[\s\S]*?onLayoutChangeRef\.current\?\.\(item\.npcId, pending\)/,
);
assert.match(
npcs,
/if \(drag\.mode === 'move'\) \{[\s\S]*?publishDraft\(\{[\s\S]*?return;\s*\}/,
);
});