Files
houseplan-card/test/header-menu.test.mjs
T
Claude 255330b9bc Шапка на телефоне: одна строка — вкладки, масштаб и шестерёнка с меню остального (#616)
На окне не шире 480 px шапка карточки и страницы бокового меню занимает одну
строку ≤ 56 px: вкладки пространств (без шестерёнок и «+», прокрутка вбок,
текущая вкладка докручивается в видимую область), масштаб, одна шестерёнка
«Действия и настройки» и у админа слот крестика #647. Заголовок скрыт.

Меню шестерёнки (src/header-menu.ts): редакторы, настройки и добавление
пространства, общие настройки, PDF, помощь, сводная панель и объёмный вид
(alpha) — в фиксированном порядке и с теми же условиями и действиями, что у
прежних кнопок. Пункт закрывает меню; Escape возвращает фокус на шестерёнку;
касание вне меню поглощается прозрачной подложкой. На > 480 px шапка прежняя,
в киоске меню нет.

- три кнопки шапки вынесены в renderHeaderActions — шапка карточки не растёт;
- сводная панель отдаёт свои пункты методом menuItems();
- i18n: title.header_menu (en/ru/de/fr);
- потолок initial View 291 000 → 292 600 (замер 291 635), база bundleBytes;
- смок smoke_mobile_view_header, юнит header-menu, 10 мутантов; смоки
  support_feedback, gear_tabs, modes, toolbar_stable_width переведены на
  меню на ≤ 480 px; подпись вкладки в span.tabtitle без pointer-events;
- USER-GUIDE.ru, UX-MODES, TOUCH-SUPPORT, STYLING-HOOKS, data-hp-contract,
  оба changelog.

Issue: #616
User-Visible: yes
2026-09-25 14:22:36 +03:00

67 lines
4.2 KiB
JavaScript

// #616: состав меню шестерёнки телефонной шапки — ТЗ п.4. Порядок фиксирован,
// условия те же, что у прежних кнопок: редакторы и настройки — только с правом
// записи, «Добавить пространство» — без фиксированного пространства, сводная
// панель и объёмный вид — только в просмотре, в киоске меню нет.
import assert from 'node:assert/strict';
import test from 'node:test';
import { headerMenuItems } from '../test-build/header-menu.js';
const calls = [];
const actions = {
setMode: (m) => calls.push(`mode:${m}`), configureSpace: () => calls.push('configure'),
addSpace: () => calls.push('add'), settings: () => calls.push('settings'), pdf: () => calls.push('pdf'),
support: () => calls.push('support'), projection: (p) => calls.push(`projection:${p}`),
};
const summary = [{ id: 'summary-settings', label: 's', run: () => calls.push('summary-settings') },
{ id: 'summary-toggle', label: 't', pressed: false, run: () => calls.push('summary-toggle') }];
const input = (over = {}) => ({
canEdit: true, kiosk: false, mode: 'view', hasFixedFloor: false, labsIso: false, iso: false,
summary, t: (key) => `«${key}»`, actions, ...over,
});
const ids = (over) => headerMenuItems(input(over)).map((item) => item.id);
const ADMIN = ['mode-plan', 'mode-devices', 'mode-decor', 'space-settings', 'space-add', 'settings', 'pdf', 'support'];
test('#616 админ в просмотре: редакторы · пространство · приложение · сводная панель — в этом порядке', () => {
assert.deepEqual(ids(), [...ADMIN, 'summary-settings', 'summary-toggle']);
});
test('#616 не-админ: только сводная панель; без неё меню пусто — кнопки нет', () => {
assert.deepEqual(ids({ canEdit: false }), ['summary-settings', 'summary-toggle']);
assert.deepEqual(ids({ canEdit: false, summary: [] }), []);
});
test('#616 редактор: сводной панели и объёмного вида нет, текущий редактор отмечен', () => {
const items = headerMenuItems(input({ mode: 'devices', labsIso: true }));
assert.deepEqual(items.map((i) => i.id), ADMIN);
assert.deepEqual(items.filter((i) => i.current).map((i) => i.id), ['mode-devices']);
});
test('#616 фиксированное пространство убирает «Добавить пространство», киоск — всё меню', () => {
assert.ok(!ids({ hasFixedFloor: true }).includes('space-add'));
assert.ok(ids({ hasFixedFloor: true }).includes('space-settings'));
assert.deepEqual(ids({ kiosk: true }), []);
assert.deepEqual(ids({ kiosk: true, canEdit: false }), []);
});
test('#616 объёмный вид — только с hp_alpha и в просмотре; пункт — переключатель', () => {
assert.ok(!ids().includes('projection'));
const flat = headerMenuItems(input({ labsIso: true })).at(-1);
assert.deepEqual([flat.id, flat.pressed, flat.label], ['projection', false, '«view.volumetric»']);
const iso = headerMenuItems(input({ labsIso: true, iso: true, canEdit: false })).at(-1);
assert.deepEqual([iso.id, iso.pressed, iso.label], ['projection', true, '«view.flat»']);
});
test('#616 каждый пункт вызывает то же действие, что прежняя кнопка', () => {
calls.length = 0;
for (const item of headerMenuItems(input({ labsIso: true }))) item.run();
assert.deepEqual(calls, ['mode:plan', 'mode:devices', 'mode:decor', 'configure', 'add', 'settings', 'pdf', 'support',
'summary-settings', 'summary-toggle', 'projection:iso']);
});
test('#616 подписи — существующие ключи интерфейса', () => {
assert.deepEqual(headerMenuItems(input({ summary: [] })).map((i) => i.label), [
'«mode.plan»', '«mode.devices»', '«mode.decor»', '«title.configure_space»', '«title.add_space»',
'«title.general_settings»', '«title.export_pdf»', '«support.title»',
]);
});