fix(cms): reindex nested array paths correctly in Content Editor
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
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
reindexItems replaced the FIRST [n] index in a data-path, which for nested arrays (e.g. services[1].specs.items) rewrote the OUTER array index instead of the item's own index. Deleting or adding an item scattered paths across services[0..n], produced sparse arrays (null items) and schema errors like '$.details.services[1].specs.items[0]: string érték szükséges'. Rewrite only the index directly following the reindexed array's path prefix; drop the now-redundant str-item special case. Closes MITHOME-30
This commit is contained in:
+10
-8
@@ -298,21 +298,23 @@ function makeObjCard(obj, idx, path) {
|
|||||||
function reindexItems(itemsEl) {
|
function reindexItems(itemsEl) {
|
||||||
if (!itemsEl) return;
|
if (!itemsEl) return;
|
||||||
const path = itemsEl.dataset.arrayItems;
|
const path = itemsEl.dataset.arrayItems;
|
||||||
|
// WHY: rewrite only the index that directly follows THIS array's own path prefix.
|
||||||
|
// A generic "replace first [n]" rule corrupts nested arrays (e.g. deleting from
|
||||||
|
// services[1].specs.items rewrites the OUTER services index and scatters paths
|
||||||
|
// across services[0..n], producing sparse arrays and schema errors).
|
||||||
|
const prefix = path + '[';
|
||||||
Array.from(itemsEl.children).forEach((child, i) => {
|
Array.from(itemsEl.children).forEach((child, i) => {
|
||||||
// Update all data-path in this child
|
|
||||||
child.querySelectorAll('[data-path]').forEach(el => {
|
child.querySelectorAll('[data-path]').forEach(el => {
|
||||||
const old = el.dataset.path;
|
const old = el.dataset.path;
|
||||||
// Replace the array index part: path[old_i] → path[new_i]
|
if (typeof old !== 'string' || !old.startsWith(prefix)) return;
|
||||||
el.dataset.path = old.replace(/^(.+?)\[(\d+)\]/, (_,p) => p + '[' + i + ']');
|
const rest = old.slice(prefix.length);
|
||||||
|
const bracketEnd = rest.indexOf(']');
|
||||||
|
const suffix = bracketEnd === -1 ? '' : rest.slice(bracketEnd);
|
||||||
|
el.dataset.path = prefix + i + suffix;
|
||||||
});
|
});
|
||||||
// Update card header
|
// Update card header
|
||||||
const hdr = child.querySelector('.card-header');
|
const hdr = child.querySelector('.card-header');
|
||||||
if (hdr) hdr.textContent = path + '[' + i + ']';
|
if (hdr) hdr.textContent = path + '[' + i + ']';
|
||||||
// Update str-item textarea
|
|
||||||
const ta = child.querySelector('textarea[data-path]');
|
|
||||||
if (ta && !ta.closest('.obj-card')) {
|
|
||||||
ta.dataset.path = path + '[' + i + ']';
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ const document = {
|
|||||||
};
|
};
|
||||||
const browserContext = { document, console, setTimeout, fetch: async () => ({ json: async () => ({ ok: true }) }) };
|
const browserContext = { document, console, setTimeout, fetch: async () => ({ json: async () => ({ ok: true }) }) };
|
||||||
vm.createContext(browserContext);
|
vm.createContext(browserContext);
|
||||||
new vm.Script(`${browserSource}\nglobalThis.__collect = collect;`).runInContext(browserContext);
|
new vm.Script(`${browserSource}\nglobalThis.__collect = collect; globalThis.__reindexItems = reindexItems;`).runInContext(browserContext);
|
||||||
|
|
||||||
assert.deepStrictEqual(JSON.parse(JSON.stringify(browserContext.__collect())), {
|
assert.deepStrictEqual(JSON.parse(JSON.stringify(browserContext.__collect())), {
|
||||||
title: 'Módosított',
|
title: 'Módosított',
|
||||||
@@ -54,4 +54,51 @@ assert.deepStrictEqual(JSON.parse(JSON.stringify(browserContext.__collect())), {
|
|||||||
limit: 99,
|
limit: 99,
|
||||||
sections: [{ id: 'first', items: ['egy', 'kettő'], settings: { visible: false, weight: 3 } }],
|
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');
|
console.log('Content Editor serializer regression test: OK');
|
||||||
|
|||||||
Reference in New Issue
Block a user