164 lines
9.0 KiB
JavaScript
164 lines
9.0 KiB
JavaScript
import assert from 'node:assert/strict';
|
||
import { readFile } from 'node:fs/promises';
|
||
import test from 'node:test';
|
||
|
||
import {
|
||
scheduleSlotActionLabel,
|
||
shouldMarkRuleFormDirty
|
||
} 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('конструктор считает изменениями только действия пользователя', () => {
|
||
assert.equal(shouldMarkRuleFormDirty({ programmatic: true, type: 'change' }), false);
|
||
assert.equal(shouldMarkRuleFormDirty({ type: 'input', fromAsyncCombobox: true }), false);
|
||
assert.equal(shouldMarkRuleFormDirty({ type: 'change' }), true);
|
||
assert.equal(shouldMarkRuleFormDirty({ type: 'input' }), true);
|
||
});
|
||
|
||
test('поисковые списки просмотра расписаний загружаются свёрнутыми', async () => {
|
||
const scheduleView = await readFrontendFile('admin/js/views/schedule-view.js');
|
||
|
||
assert.match(scheduleView, /combo\.preload\(''\)/);
|
||
assert.doesNotMatch(scheduleView, /combo\.search\(''\)/);
|
||
});
|
||
|
||
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('<table>', bodyIndex);
|
||
const tableEnd = calendarView.indexOf('</table>', bodyIndex);
|
||
const calendarTable = calendarView.slice(tableStart, tableEnd);
|
||
|
||
assert.doesNotMatch(calendarTable, /<th>Название<\/th>/);
|
||
assert.match(calendarTable, /colspan="6"/);
|
||
assert.doesNotMatch(calendarScript, /title:\s*buildCalendarTitleFromForm/);
|
||
});
|
||
|
||
test('конструктор правил показывает готовую компоновку без скачков колонок', async () => {
|
||
const [scheduleView, scheduleScript, components] = await Promise.all([
|
||
readFrontendFile('admin/views/schedule.html'),
|
||
readFrontendFile('admin/js/views/schedule.js'),
|
||
readFrontendFile('admin/css/components.css')
|
||
]);
|
||
|
||
assert.match(scheduleView, /id="schedule-initial-state"/);
|
||
assert.match(scheduleView, /id="schedule-initial-content"[^>]*class="schedule-initial-content is-loading"|class="schedule-initial-content is-loading"[^>]*id="schedule-initial-content"/);
|
||
assert.match(scheduleView, /<table id="schedule-table">\s*<colgroup>/s);
|
||
assert.match(scheduleScript, /finally\s*\{[\s\S]*initialContent\?\.classList\.remove\('is-loading'\)/);
|
||
assert.match(components, /#schedule-table\s*\{[^}]*table-layout:\s*fixed/s);
|
||
assert.match(components, /#schedule-table tbody tr\s*\{[^}]*animation:\s*none/s);
|
||
});
|
||
|
||
test('форма учебного года расположена после рабочих блоков календарного графика', async () => {
|
||
const calendarView = await readFrontendFile('admin/views/academic-calendar.html');
|
||
const calendarsRegistryIndex = calendarView.indexOf('id="academic-calendars-tbody"');
|
||
const academicYearBuilderIndex = calendarView.indexOf('academic-year-builder-card');
|
||
const managePanelEndIndex = calendarView.indexOf('</section>', academicYearBuilderIndex);
|
||
|
||
assert.ok(calendarsRegistryIndex >= 0);
|
||
assert.ok(academicYearBuilderIndex > calendarsRegistryIndex);
|
||
assert.ok(managePanelEndIndex > academicYearBuilderIndex);
|
||
});
|
||
|
||
test('поисковые и диалоговые списки имеют размытие и светлую тему', async () => {
|
||
const [components, absences, absenceView] = await Promise.all([
|
||
readFrontendFile('admin/css/components.css'),
|
||
readFrontendFile('admin/css/teacher-absences.css'),
|
||
readFrontendFile('admin/views/teacher-absences.html')
|
||
]);
|
||
|
||
assert.match(absenceView, /id="teacher-absence-teacher"[^>]*data-native-select="true"/);
|
||
assert.match(components, /\.async-combobox-menu\s*\{[^}]*backdrop-filter:\s*blur\(20px\)/s);
|
||
assert.match(components, /\[data-theme="light"\] \.async-combobox-menu\s*\{[^}]*background:\s*rgba\(255, 255, 255, 0\.96\)/s);
|
||
assert.match(absences, /\.teacher-dialogue-toolbar \.custom-select-menu\s*\{[^}]*backdrop-filter:\s*blur\(20px\)/s);
|
||
assert.match(absences, /\[data-theme="light"\] \.teacher-dialogue-toolbar \.custom-select-menu/);
|
||
});
|