#!/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(/]*)?>([\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, }; 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');