All checks were successful
Adds a «POI» checkbox to the terrain popup that toggles the poi-circles and poi-labels layers via map.setLayoutProperty. The choice is persisted in localStorage (key `poi-visible`) and restored on page load and after style changes, kept consistent with the runtime layerState.poi per ADR-0001. Tests: behavioral JS unit tests (TP-01..TP-04) run via `node --test`, wrapped by tests/unit/test_poi_toggle.py with static structure checks so they execute under the existing `pytest tests/` CI step. Refs: ET-002 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
168 lines
6.8 KiB
JavaScript
168 lines
6.8 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* ET-002 — поведенческие unit-тесты чекбокса видимости POI.
|
|
*
|
|
* Покрывают TP-01..TP-04 из docs/work-items/ET-002/04-test-plan.yaml.
|
|
*
|
|
* Тесты исполняют РЕАЛЬНЫЙ код из src/web/app.js: блок POI извлекается
|
|
* по маркерам `>>> ET-002 POI visibility block` и оборачивается в
|
|
* фабрику через `new Function`, которой передаются мок-зависимости
|
|
* (window, document, localStorage, layerState, layerGroups). Так
|
|
* монолитный browser-скрипт проверяется без полной загрузки в Node.
|
|
*
|
|
* Запуск: `node --test tests/unit/poi_toggle.test.js`
|
|
* (в CI оборачивается pytest-тестом tests/unit/test_poi_toggle.py).
|
|
*/
|
|
|
|
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
|
|
const APP_JS = path.join(__dirname, '..', '..', 'src', 'web', 'app.js');
|
|
|
|
/**
|
|
* Извлекает блок POI-логики из app.js и собирает из него модуль,
|
|
* подставляя переданные зависимости.
|
|
*/
|
|
function loadPoiModule(deps) {
|
|
const src = fs.readFileSync(APP_JS, 'utf8');
|
|
const m = src.match(
|
|
/\/\/ >>> ET-002 POI visibility block[^\n]*\n([\s\S]*?)\/\/ <<< ET-002 POI visibility block/
|
|
);
|
|
assert.ok(m, 'POI-блок не найден в app.js (маркеры ET-002 отсутствуют)');
|
|
const factory = new Function(
|
|
'layerState', 'layerGroups', 'window', 'document', 'localStorage',
|
|
m[1] + '\nreturn { applyPoiVisibility, onPoiCheckbox, restorePoiState };'
|
|
);
|
|
return factory(
|
|
deps.layerState, deps.layerGroups, deps.window, deps.document, deps.localStorage
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Готовит изолированное мок-окружение для одного теста.
|
|
* @param {object} [opts]
|
|
* @param {string} [opts.stored] - значение ключа poi-visible в localStorage
|
|
* (если не указан — ключ отсутствует).
|
|
* @param {boolean} [opts.layerExists] - что возвращает map.getLayer().
|
|
*/
|
|
function makeEnv({ stored, layerExists = true } = {}) {
|
|
const calls = { setLayoutProperty: [], setItem: [] };
|
|
const store = {};
|
|
if (stored !== undefined) store['poi-visible'] = stored;
|
|
|
|
const localStorage = {
|
|
getItem: (k) => (k in store ? store[k] : null),
|
|
setItem: (k, v) => { store[k] = String(v); calls.setItem.push([k, String(v)]); },
|
|
};
|
|
const map = {
|
|
getLayer: () => layerExists,
|
|
setLayoutProperty: (id, prop, val) => calls.setLayoutProperty.push([id, prop, val]),
|
|
};
|
|
const checkbox = { checked: true };
|
|
const document = {
|
|
getElementById: (id) => (id === 'poi-visible-cb' ? checkbox : null),
|
|
};
|
|
const layerState = { tracks: true, paths: true, poi: true, basemap: true };
|
|
const layerGroups = { poi: ['poi-circles', 'poi-labels'] };
|
|
const win = { _map: map };
|
|
|
|
const mod = loadPoiModule({
|
|
layerState, layerGroups, window: win, document, localStorage,
|
|
});
|
|
return { mod, calls, checkbox, layerState, store };
|
|
}
|
|
|
|
// ── TP-01: onPoiCheckbox() скрывает слои при checked=false ───────────────
|
|
test('TP-01: снятый чекбокс скрывает слои POI и сохраняет 0', () => {
|
|
const env = makeEnv();
|
|
env.checkbox.checked = false;
|
|
|
|
env.mod.onPoiCheckbox();
|
|
|
|
assert.deepEqual(env.calls.setLayoutProperty, [
|
|
['poi-circles', 'visibility', 'none'],
|
|
['poi-labels', 'visibility', 'none'],
|
|
]);
|
|
assert.deepEqual(env.calls.setItem, [['poi-visible', '0']]);
|
|
assert.equal(env.layerState.poi, false);
|
|
});
|
|
|
|
// ── TP-02: onPoiCheckbox() показывает слои при checked=true ──────────────
|
|
test('TP-02: установленный чекбокс показывает слои POI и сохраняет 1', () => {
|
|
const env = makeEnv();
|
|
env.checkbox.checked = true;
|
|
|
|
env.mod.onPoiCheckbox();
|
|
|
|
assert.deepEqual(env.calls.setLayoutProperty, [
|
|
['poi-circles', 'visibility', 'visible'],
|
|
['poi-labels', 'visibility', 'visible'],
|
|
]);
|
|
assert.deepEqual(env.calls.setItem, [['poi-visible', '1']]);
|
|
assert.equal(env.layerState.poi, true);
|
|
});
|
|
|
|
// ── TP-03: восстановление состояния — POI скрыты ─────────────────────────
|
|
test('TP-03: restorePoiState() при poi-visible=0 скрывает POI', () => {
|
|
const env = makeEnv({ stored: '0' });
|
|
|
|
env.mod.restorePoiState();
|
|
|
|
assert.equal(env.checkbox.checked, false);
|
|
assert.equal(env.layerState.poi, false);
|
|
assert.deepEqual(env.calls.setLayoutProperty, [
|
|
['poi-circles', 'visibility', 'none'],
|
|
['poi-labels', 'visibility', 'none'],
|
|
]);
|
|
// restore не должен переписывать localStorage
|
|
assert.deepEqual(env.calls.setItem, []);
|
|
});
|
|
|
|
// ── TP-04: восстановление состояния — POI видны (default) ────────────────
|
|
test('TP-04: restorePoiState() без ключа включает POI по умолчанию', () => {
|
|
const env = makeEnv();
|
|
|
|
env.mod.restorePoiState();
|
|
|
|
assert.equal(env.checkbox.checked, true);
|
|
assert.equal(env.layerState.poi, true);
|
|
assert.deepEqual(env.calls.setLayoutProperty, [
|
|
['poi-circles', 'visibility', 'visible'],
|
|
['poi-labels', 'visibility', 'visible'],
|
|
]);
|
|
});
|
|
|
|
// ── Доп.: значение '1' восстанавливает видимость явно ────────────────────
|
|
test('restorePoiState() при poi-visible=1 показывает POI', () => {
|
|
const env = makeEnv({ stored: '1' });
|
|
|
|
env.mod.restorePoiState();
|
|
|
|
assert.equal(env.checkbox.checked, true);
|
|
assert.equal(env.layerState.poi, true);
|
|
});
|
|
|
|
// ── Доп.: POI-логика не трогает чужие слои (дух TP-08) ───────────────────
|
|
test('onPoiCheckbox() меняет только слои poi-circles и poi-labels', () => {
|
|
const env = makeEnv();
|
|
env.checkbox.checked = false;
|
|
|
|
env.mod.onPoiCheckbox();
|
|
|
|
const touched = env.calls.setLayoutProperty.map((c) => c[0]);
|
|
assert.deepEqual([...touched].sort(), ['poi-circles', 'poi-labels']);
|
|
});
|
|
|
|
// ── Доп.: layerState синхронизируется даже без слоёв на карте ────────────
|
|
test('applyPoiVisibility() обновляет layerState даже если слой ещё не добавлен', () => {
|
|
const env = makeEnv({ layerExists: false });
|
|
|
|
env.mod.applyPoiVisibility(false);
|
|
|
|
assert.equal(env.layerState.poi, false);
|
|
assert.deepEqual(env.calls.setLayoutProperty, []);
|
|
});
|