import assert from 'node:assert/strict'; import { readFile } from 'node:fs/promises'; import test from 'node:test'; import { scheduleSlotActionLabel } from '../admin/js/views/schedule.js'; const readFrontendFile = relativePath => readFile( new URL(`../${relativePath}`, import.meta.url), 'utf8' ); test('одиночный слот очищается, а несколько слотов удаляются', () => { assert.equal(scheduleSlotActionLabel(1), 'Очистить'); assert.equal(scheduleSlotActionLabel(2), 'Удалить'); assert.equal(scheduleSlotActionLabel(5), 'Удалить'); }); test('в общих заголовках админ-панели и настроек нет бургер-меню', async () => { const files = await Promise.all([ readFrontendFile('admin/index.html'), readFrontendFile('admin/js/main.js'), readFrontendFile('admin/settings/index.html'), readFrontendFile('admin/settings/js/main.js') ]); files.forEach(content => assert.doesNotMatch(content, /menu-toggle/)); }); test('заголовок вкладки соответствует конструктору правил', async () => { const mainScript = await readFrontendFile('admin/js/main.js'); assert.match(mainScript, /schedule:\s*\{\s*title:\s*'Конструктор правил'/); }); test('основная кнопка показа находится в заголовке блока параметров', async () => { const scheduleView = await readFrontendFile('admin/views/schedule-view.html'); const controlHeaderIndex = scheduleView.indexOf('schedule-view-control-header'); const controlGridIndex = scheduleView.indexOf('schedule-view-control-grid'); const loadButtonIndex = scheduleView.indexOf('id="schedule-view-load"'); assert.ok(controlHeaderIndex >= 0); assert.ok(loadButtonIndex > controlHeaderIndex); assert.ok(loadButtonIndex < controlGridIndex); assert.equal(scheduleView.match(/id="schedule-view-load"/g)?.length, 1); }); test('пустое состояние расписания содержит собственную кнопку обновления', async () => { const scheduleScript = await readFrontendFile('admin/js/views/schedule-view.js'); assert.match(scheduleScript, /SCHEDULE_REFRESH_PROMPT\s*=\s*'Нажмите «Показать», чтобы обновить расписание'/); assert.match(scheduleScript, /data-schedule-view-reload/); assert.match(scheduleScript, /closest\('\[data-schedule-view-reload\]'\)[\s\S]*?loadData\(\)/); assert.match(scheduleScript, /setTableState\(message,\s*message === SCHEDULE_REFRESH_PROMPT\)/); }); test('действия слотов имеют размер соседнего поля', async () => { const [components, scheduleScript] = await Promise.all([ readFrontendFile('admin/css/components.css'), readFrontendFile('admin/js/views/schedule.js') ]); assert.match(components, /\.schedule-slot-remove\s*\{[^}]*min-width:\s*125px[^}]*min-height:\s*44px/s); assert.match(scheduleScript, /class="btn btn-md \$\{actionClass\} schedule-slot-remove"/); }); test('все варианты системных кнопок имеют видимую обводку', async () => { const components = await readFrontendFile('admin/css/components.css'); assert.match(components, /\.btn\s*\{[^}]*border:\s*1px solid var\(--button-secondary-border\)/s); assert.match(components, /\.btn-primary\s*\{[^}]*border-color:\s*var\(--button-primary-border\)/s); assert.match(components, /\.btn-ghost\s*\{[^}]*border-color:\s*var\(--button-ghost-border\)/s); assert.match(components, /\.btn-danger\s*\{[^}]*border-color:\s*var\(--button-danger-border\)/s); }); test('подсказка календарной сетки получает позицию напрямую от курсора', async () => { const calendarScript = await readFrontendFile('admin/js/views/academic-calendar.js'); assert.match(calendarScript, /translate3d\(\$\{left\}px, \$\{top\}px, 0\) scale\(1\)/); assert.match(calendarScript, /\{ duration: 1, fill: 'forwards' \}/); assert.doesNotMatch(calendarScript, /\.style\.(?:left|top)\s*=/); }); test('светлая тема задаёт контрастные пункты и заголовки левого меню', async () => { const [layout, index] = await Promise.all([ readFrontendFile('admin/css/layout.css'), readFrontendFile('admin/index.html') ]); assert.match(layout, /\[data-theme="light"\] \.sidebar \.nav-item\s*\{[^}]*color:\s*#334155/s); assert.match(layout, /\[data-theme="light"\] \.sidebar-section-title\s*\{[^}]*color:\s*#64748b !important/s); assert.match(index, />Расписание<\/div>/); }); test('таблица календарных графиков не содержит отдельный столбец названия', async () => { const [calendarView, calendarScript] = await Promise.all([ readFrontendFile('admin/views/academic-calendar.html'), readFrontendFile('admin/js/views/academic-calendar.js') ]); const bodyIndex = calendarView.indexOf('id="academic-calendars-tbody"'); const tableStart = calendarView.lastIndexOf('