mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
Validate / hacs (push) Failing after 15s
Validate / docs (push) Failing after 20s
Validate / provenance (push) Successful in 54s
Validate / process-gate (push) Failing after 50s
Validate / changes (push) Successful in 46s
Validate / hassfest (push) Failing after 14s
Validate / frontend (push) Successful in 5m54s
Validate / backend (push) Failing after 8m1s
Validate / smoke (push) Failing after 4m25s
Validate / golden (push) Failing after 4m26s
Validate / performance_smoke (push) Failing after 8m39s
Issue: #157 User-Visible: yes
70 lines
3.9 KiB
JavaScript
70 lines
3.9 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { readFileSync } from 'node:fs';
|
|
|
|
const card = readFileSync(new URL('../src/houseplan-card.ts', import.meta.url), 'utf8');
|
|
const staticRender = readFileSync(new URL('../src/space-render.ts', import.meta.url), 'utf8');
|
|
const backend = readFileSync(new URL('../custom_components/houseplan/websocket_api.py', import.meta.url), 'utf8');
|
|
const importer = readFileSync(new URL('../custom_components/houseplan/import_export.py', import.meta.url), 'utf8');
|
|
const en = JSON.parse(readFileSync(new URL('../src/i18n/en.json', import.meta.url)));
|
|
const ru = JSON.parse(readFileSync(new URL('../src/i18n/ru.json', import.meta.url)));
|
|
|
|
test('passage is exposed in the agreed toolbar and dialog order', () => {
|
|
const toolbar = card.slice(card.indexOf("id: 'opening'"), card.indexOf('private _renderEditorGroupLauncher'));
|
|
const order = ['window', 'door', 'passage', 'gate'].map((id) => toolbar.indexOf(`id: '${id}'`));
|
|
assert.equal(order.every((offset) => offset >= 0), true);
|
|
assert.deepEqual([...order].sort((a, b) => a - b), order);
|
|
assert.match(toolbar, /id: 'passage'.*mdi:arch/s);
|
|
|
|
const dialogStart = card.indexOf('private _renderOpeningDialog()');
|
|
const dialog = card.slice(dialogStart, card.indexOf('private _gridLevels()', dialogStart));
|
|
const radioOrder = ['window', 'door', 'passage', 'gate']
|
|
.map((type) => dialog.indexOf(`.checked=\${d.type === '${type}'}`));
|
|
assert.equal(radioOrder.every((offset) => offset >= 0), true);
|
|
assert.deepEqual([...radioOrder].sort((a, b) => a - b), radioOrder);
|
|
assert.match(dialog, /role="status" aria-live="polite"/);
|
|
assert.match(dialog, /d\.type !== 'passage'[\s\S]*opening\.contact_label/);
|
|
assert.match(dialog, /d\.type === 'door' \|\| d\.type === 'gate'/);
|
|
});
|
|
|
|
test('saving passage deletes every inapplicable known field', () => {
|
|
const save = card.slice(card.indexOf('private _saveOpening()'), card.indexOf('private _deleteOpening()'));
|
|
for (const field of ['contact', 'lock', 'invert', 'flip_h', 'flip_v']) {
|
|
assert.match(save, new RegExp(`delete o\\.${field};`));
|
|
}
|
|
assert.match(save, /\.\.\.\(previous \|\| \{\}\)/, 'unknown sibling fields are retained');
|
|
});
|
|
|
|
test('passage bindings cannot reach subscriptions, locks or the info card', () => {
|
|
assert.match(card, /openingEntityReferences\(opening\)/);
|
|
assert.match(card, /\(o\.type === 'door' \|\| o\.type === 'gate'\)[\s\S]*o\.lock/);
|
|
const info = card.slice(card.indexOf('private _renderOpeningInfoCard()'), card.indexOf('private _renderOpeningDialog()'));
|
|
assert.match(info, /o\.type !== 'passage' && o\.contact/);
|
|
assert.match(info, /\(o\.type === 'door' \|\| o\.type === 'gate'\) && o\.lock/);
|
|
});
|
|
|
|
test('static passage cuts and tunnels are passage-only additions', () => {
|
|
assert.match(staticRender, /staticPassageOpenings\(spCfg\.openings, NORM_W\)/);
|
|
assert.match(staticRender, /passages: staticPassages\.map/);
|
|
assert.match(staticRender, /renderOpeningTunnelFills/);
|
|
assert.match(staticRender, /passageDataTunnels/);
|
|
assert.match(staticRender, /passageGlowTunnels/);
|
|
});
|
|
|
|
test('all write/import paths invoke the semantic passage validator', () => {
|
|
assert.equal((backend.match(/validate_opening_passages\(/g) || []).length, 2);
|
|
assert.match(importer, /validate_opening_passages\(merged_config, current_config\)/);
|
|
assert.match(importer, /validate_opening_passages\(incoming_config, validate_all=True\)/);
|
|
});
|
|
|
|
test('passage UI copy is bilingual and carries matching placeholders', () => {
|
|
assert.equal(en['opening.passage'], 'Open passage');
|
|
assert.equal(ru['opening.passage'], 'Открытый проём');
|
|
assert.match(en['opening.passage_binding_warning'], /sensor and lock/i);
|
|
assert.match(ru['opening.passage_binding_warning'], /датчик.*замок/i);
|
|
assert.deepEqual(
|
|
en['opening.invalid_passage_fields'].match(/\{\w+\}/g)?.sort(),
|
|
ru['opening.invalid_passage_fields'].match(/\{\w+\}/g)?.sort(),
|
|
);
|
|
});
|