Files
DndGamePlayer/app/renderer/control/controlApp.effectsPanel.test.ts
T
Ivan Fontosh 20c838da7d feat(effects): вода, облако яда, луч света; пульт и окна демонстрации
- Поле: вода (сплошная заливка по штриху, превью кистью), туман/огонь/дождь без изменений логики.

- Действия: облако яда (частицы, круглая текстура, звук oblako-yada.mp3, длительность как у трека), луч света и заморозка со звуками из public/.

- Пульт: инструменты воды и яда, синхрон SFX, тесты панели и ластика.

- Окно управления: дочернее от окна просмотра (Z-order).

- Типы эффектов, effectsStore prune, hit-test ластика.

Made-with: Cursor
2026-04-20 11:03:57 +08:00

97 lines
4.3 KiB
TypeScript

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));
function readControlApp(): string {
return fs.readFileSync(path.join(here, 'ControlApp.tsx'), 'utf8');
}
function readControlAppCss(): string {
return fs.readFileSync(path.join(here, 'ControlApp.module.css'), 'utf8');
}
void test('ControlApp: звук молнии (public/molniya.mp3)', () => {
const src = readControlApp();
assert.ok(src.includes('molniya.mp3'));
assert.ok(src.includes('playLightningEffectSound'));
});
void test('ControlApp: звук заморозки (public/zamorozka.mp3)', () => {
const src = readControlApp();
assert.ok(src.includes('zamorozka.mp3'));
assert.ok(src.includes('playFreezeEffectSound'));
});
void test('ControlApp: звук луча света (public/luch_sveta.mp3)', () => {
const appSrc = readControlApp();
const sfxSrc = fs.readFileSync(path.join(here, 'sunbeamSfx.ts'), 'utf8');
assert.ok(appSrc.includes('playSunbeamEffectSound'));
assert.ok(appSrc.includes('getSunbeamEffectLifeMs'));
assert.ok(sfxSrc.includes('luch_sveta.mp3'));
assert.ok(sfxSrc.includes('playbackRate'));
assert.ok(sfxSrc.includes('SUNBEAM_PLAYBACK_RATE'));
});
void test('ControlApp: звук облака яда (public/oblako-yada.mp3)', () => {
const appSrc = readControlApp();
const sfxSrc = fs.readFileSync(path.join(here, 'poisonCloudSfx.ts'), 'utf8');
assert.ok(appSrc.includes('getPoisonCloudEffectLifeMs'));
assert.ok(appSrc.includes('playPoisonCloudEffectSound'));
assert.ok(sfxSrc.includes('oblako-yada.mp3'));
assert.ok(sfxSrc.includes('playbackRate'));
});
void test('ControlApp: эффекты в пульте, иконки с тултипами и подписью для a11y', () => {
const src = readControlApp();
assert.ok(src.includes('ЭФФЕКТЫ'));
assert.ok(src.includes('Инструменты'));
assert.ok(src.includes('Эффекты поля'));
assert.ok(src.includes('Эффекты действий'));
assert.ok(src.includes('Луч света'));
assert.ok(src.includes('title="Вода"'));
assert.ok(src.includes('title="Облако яда"'));
assert.ok(src.includes('title="Туман"'));
assert.ok(src.includes('ariaLabel="Туман"'));
assert.ok(src.includes('iconOnly'));
assert.ok(src.includes('title="Очистить эффекты"'));
assert.ok(src.includes('ariaLabel="Очистить эффекты"'));
assert.ok(src.includes('#e5484d'));
const fx = src.indexOf('ЭФФЕКТЫ');
const story = src.indexOf('СЮЖЕТНАЯ ЛИНИЯ');
assert.ok(fx !== -1 && story !== -1 && fx < story, 'Блок эффектов должен быть выше сюжетной линии');
});
void test('ControlApp: сюжетная линия — колонка сверху вниз и фон как у карточек ветвления', () => {
const src = readControlApp();
const css = readControlAppCss();
const story = src.indexOf('СЮЖЕТНАЯ ЛИНИЯ');
assert.ok(story !== -1);
assert.ok(src.includes('className={styles.storyScroll}'));
assert.match(css, /\.storyScroll[\s\S]*?justify-content:\s*flex-start/);
assert.match(css, /\.storyScroll[\s\S]*?background:\s*var\(--color-overlay-dark-2\)/);
assert.match(css, /\.branchCard[\s\S]*?background:\s*var\(--color-overlay-dark-2\)/);
});
void test('ControlApp: слой кисти не использует курсор not-allowed (ластик тоже crosshair)', () => {
const src = readControlApp();
const css = readControlAppCss();
assert.ok(!src.includes("tool.tool === 'eraser' ? 'not-allowed'"));
assert.ok(src.includes('className={styles.brushLayer}'));
assert.match(css, /\.brushLayer[\s\S]*?cursor:\s*crosshair/);
});
void test('ControlApp: радиус кисти не в блоке предпросмотра', () => {
const src = readControlApp();
const previewLabel = src.indexOf('Предпросмотр экрана');
const radius = src.indexOf('Радиус кисти');
assert.ok(previewLabel !== -1 && radius !== -1);
assert.ok(
radius < previewLabel,
'Слайдер радиуса должен быть в пульте (файл: выше заголовка предпросмотра)',
);
});