Files
websitedev/scripts/test-content-editor-serializer.js
T
Do Siki 6030c48abb
CI Pipeline with Test Management / 🧪 Run Tests & Generate Reports (push) Waiting to run
CI Pipeline with Test Management / 🐳 Docker Integration Tests (push) Blocked by required conditions
CI Pipeline with Test Management / 🏗️ Build Docker Image (push) Blocked by required conditions
CI Pipeline with Test Management / 📊 Generate Test Summary (push) Blocked by required conditions
Test Reporting & Gherkin Analysis / 🧪 Run Tests & Generate Reports (push) Waiting to run
Test Reporting & Gherkin Analysis / 📊 Analyze Test Coverage (push) Blocked by required conditions
Test Reporting & Gherkin Analysis / 🔄 Sync with Linear (push) Blocked by required conditions
Test Reporting & Gherkin Analysis / ⚡ Performance Monitoring (push) Blocked by required conditions
feat(cms): keyboard shortcuts — Ctrl+S save, Ctrl+P publish, ? help
- Ctrl/Cmd+S saves (native browser save dialog suppressed)
- Ctrl/Cmd+P publishes; Ctrl/Cmd+Shift+V opens the Versions panel
- '?' toggles a shortcuts overlay (Esc/click closes)
- plain typing in inputs never triggers actions (modifiers required;
  '?' only outside editing targets)
- jsdom regression tests run the real client script with dispatched
  KeyboardEvents; the client is evaluated once per suite because each
  eval would stack another keydown listener on the shared document

Closes MITHOME-75
2026-08-19 22:52:09 +02:00

109 lines
5.2 KiB
JavaScript

#!/usr/bin/env node
/**
* Regressziós teszt a Content Editor böngészőben futó serializeréhez.
* A szerver által generált tényleges JavaScriptet futtatja minimális DOM-mal,
* így a szerkesztő és a teszt nem két eltérő implementációt vizsgál.
*/
const assert = require('assert/strict');
const fs = require('fs');
const path = require('path');
const vm = require('vm');
const { createRequire } = require('module');
const source = fs.readFileSync('content-editor.js', 'utf8')
.replace('if (require.main === module) {', 'globalThis.renderContentEditor = HTML;\nif (false) {');
const editorRequire = createRequire(path.join(process.cwd(), 'content-editor.js'));
const serverContext = { require: editorRequire, console, process, Buffer, module: { exports: {} }, __dirname: process.cwd(), globalThis: {} };
vm.createContext(serverContext);
new vm.Script(source).runInContext(serverContext);
const fixture = {
title: 'Teszt',
enabled: true,
limit: 42,
sections: [{ id: 'first', items: ['egy', 'kettő'], settings: { visible: false, weight: 1 } }],
};
const clientJs = fs.readFileSync('scripts/cms-editor-client.js', 'utf8');
const html = serverContext.globalThis.renderContentEditor('home', JSON.stringify(fixture), null, 'csrf-test-token', { common: '⚙️ Közös' }, clientJs, 'hash-test-value');
assert.ok(html.includes('let CONTENT_HASH = "hash-test-value"'));
const browserSource = [...html.matchAll(/<script(?: [^>]*)?>([\s\S]*?)<\/script>/g)].at(-1)[1]
.replace("render(DATA, document.getElementById('editor'));", '')
.replace("const toast = document.querySelector('.toast');", 'const toast = null;');
const fields = [
{ dataset: { path: 'title', type: 'string' }, value: 'Módosított' },
{ dataset: { path: 'enabled', type: 'boolean' }, checked: true },
{ dataset: { path: 'limit', type: 'number' }, value: '99' },
{ dataset: { path: 'sections[0].id', type: 'string' }, value: 'first' },
{ dataset: { path: 'sections[0].items[0]', type: 'string' }, value: 'egy' },
{ dataset: { path: 'sections[0].items[1]', type: 'string' }, value: 'kettő' },
{ dataset: { path: 'sections[0].settings.visible', type: 'boolean' }, checked: false },
{ dataset: { path: 'sections[0].settings.weight', type: 'number' }, value: '3' },
];
const document = {
getElementById: id => id === 'page-data' ? { textContent: JSON.stringify(fixture) } : {},
querySelectorAll: selector => selector === '[data-path]' ? fields : [],
querySelector: () => null,
// Keyboard-shortcut binding in the client script — not under test here.
addEventListener: () => {},
};
const browserContext = { document, console, setTimeout, fetch: async () => ({ json: async () => ({ ok: true }) }) };
vm.createContext(browserContext);
new vm.Script(`${browserSource}\nglobalThis.__collect = collect; globalThis.__reindexItems = reindexItems;`).runInContext(browserContext);
assert.deepStrictEqual(JSON.parse(JSON.stringify(browserContext.__collect())), {
title: 'Módosított',
enabled: true,
limit: 99,
sections: [{ id: 'first', items: ['egy', 'kettő'], settings: { visible: false, weight: 3 } }],
});
// Regression (MITHOME-30): reindexing a NESTED string array (e.g. services[1].specs.items
// after deleting its first item) must rewrite only the item's own trailing index.
// The old "replace first [n]" logic rewrote the OUTER array index and scattered the
// paths across services[0..n], producing sparse arrays and validation errors like
// "$.details.services[1].specs.items[0]: string érték szükséges".
const nestedEls = [1, 2, 3, 4, 5, 6].map(n => ({ dataset: { path: `details.services[1].specs.items[${n}]` } }));
const nestedItems = {
dataset: { arrayItems: 'details.services[1].specs.items' },
children: nestedEls.map(el => ({
querySelectorAll: selector => (selector === '[data-path]' ? [el] : []),
querySelector: () => null,
})),
};
browserContext.__reindexItems(nestedItems);
assert.deepEqual(nestedEls.map(el => el.dataset.path), [
'details.services[1].specs.items[0]',
'details.services[1].specs.items[1]',
'details.services[1].specs.items[2]',
'details.services[1].specs.items[3]',
'details.services[1].specs.items[4]',
'details.services[1].specs.items[5]',
]);
// Reindexing the OUTER services array rewrites only the outer index and keeps
// nested field paths (including nested array item indices) intact.
const cardEls = [
{ dataset: { path: 'details.services[2].icon' } },
{ dataset: { path: 'details.services[2].specs.title' } },
{ dataset: { path: 'details.services[2].specs.items[4]' } },
];
const firstCardEl = { dataset: { path: 'details.services[0].icon' } };
const servicesItems = {
dataset: { arrayItems: 'details.services' },
children: [
{ querySelectorAll: selector => (selector === '[data-path]' ? [firstCardEl] : []), querySelector: () => null },
{ querySelectorAll: selector => (selector === '[data-path]' ? cardEls : []), querySelector: () => null },
],
};
browserContext.__reindexItems(servicesItems);
assert.equal(firstCardEl.dataset.path, 'details.services[0].icon');
assert.deepEqual(cardEls.map(el => el.dataset.path), [
'details.services[1].icon',
'details.services[1].specs.title',
'details.services[1].specs.items[4]',
]);
console.log('Content Editor serializer regression test: OK');