import { assert, expect, test, beforeAll } from 'vitest'; import { addFromTemplate } from '../src/template.mjs'; import { checkAndSetElementChanged, setChangedElement, getData, groupSettingsAdd, groupSettingsDel, initInputKeyValueElement, isChangedElement, setInputValue, countChangedElements, } from '../src/settings.mjs'; beforeAll(() => { document.body.innerHTML += `
Plain kvs
`; for (let name of ['modify', 'append', 'remove']) { document.body.innerHTML += `
`; } document.body.innerHTML += `
`; document.body.innerHTML +=` `; }); /** * @param {import('../src/settings.mjs').ElementValue} lhs * @param {import('../src/settings.mjs').DataValue} rhs */ function expectData(lhs, rhs) { if (typeof lhs === 'boolean') { assert(typeof rhs === 'number'); expect(lhs).toEqual(!!rhs); } else { expect(lhs).toEqual(rhs); } } test('processed data can be gathered back', () => { const PLAIN = { plainText: 'foobar', plainNumber: 12345, plainRange: 74, plainBox: true, }; const CFGS = [ {groupName: 'one', groupValue: 1}, {groupName: 'five', groupValue: 5}, {groupName: 'nine', groupValue: 9}, {groupName: 'fifty-five', groupValue: 55}, {groupName: 'one-hundred', groupValue: 100}, ]; const plain = document.getElementById('gather-plain'); assert(plain instanceof HTMLFieldSetElement); for (let [key, value] of Object.entries(PLAIN)) { initInputKeyValueElement(key, value); } const group = document.getElementById('gather-group'); assert(group instanceof HTMLElement); for (let cfg of CFGS) { addFromTemplate(group, 'gather-group', cfg); } expect(group.childElementCount) .toEqual(CFGS.length); const form = /** @type {HTMLFormElement | null} */ (document.getElementById('gather')); assert(form); const form_data = new FormData(form); expect(form_data.getAll('groupValue')); // retrieves everything, regardless of 'changed' state const data = getData([form], {assumeChanged: true}); expect(data.del.length) .toEqual(0); const dataset = data.set; expect(Object.entries(dataset).length) .toEqual(Object.keys(PLAIN).length + (2 * CFGS.length)); for (let [key, value] of Object.entries(PLAIN)) { expectData(value, dataset[key]); } CFGS.forEach((cfg, index) => { for (let [key, value] of Object.entries(cfg)) { expectData(value, dataset[`${key}${index}`]); } }) }); test('settings group modify', () => { const modify = /** @type {HTMLDivElement | null} */ (document.getElementById('modify-group')); assert(modify); addFromTemplate(modify, 'group', {foo: 'one'}); addFromTemplate(modify, 'group', {foo: 'two'}); addFromTemplate(modify, 'group', {foo: 'three'}); const last = /** @type {HTMLInputElement | null} */ (modify?.lastElementChild?.children[1]); assert(last); setInputValue(last, 'something else'); assert(checkAndSetElementChanged(last)); const first = /** @type {HTMLInputElement | null} */ (modify?.firstElementChild?.children[1]); assert(first); setInputValue(first, 'complete opposite'); assert(checkAndSetElementChanged(first)); const form = /** @type {HTMLFormElement | null} */ (document.getElementById('modify')); assert(form); const data = getData([form]); expect(data.del.length) .toEqual(0); expect(data.set) .toEqual({ foo0: 'complete opposite', foo2: 'something else', }); }); test('settings group append', () => { const append = /** @type {HTMLDivElement | null} */ (document.getElementById('append-group')); assert(append); addFromTemplate(append, 'group', {foo: 'first'}); addFromTemplate(append, 'group', {foo: 'second'}); addFromTemplate(append, 'group', {foo: 'third'}); addFromTemplate(append, 'group', {foo: 'fourth'}); expect(append.children.length).toEqual(4); addFromTemplate(append, 'group', {foo: 'fifth', bar: 'element'}); groupSettingsAdd(append); expect(append.children.length).toEqual(5); const last = /** @type {HTMLFieldSetElement | null} */ (append?.lastElementChild); assert(last); const foo = /** @type {HTMLInputElement | null} */ (last.children[1]); assert(foo); assert(isChangedElement(foo)); setInputValue(foo, 'pending value'); const form = /** @type {HTMLFormElement | null} */ (document.getElementById('append')); assert(form); let data = getData([form]); expect(data.del.length) .toEqual(0); expect(data.set) .toEqual({ foo4: 'pending value', }); groupSettingsDel(append, last); expect(append.children.length).toEqual(4); data = getData([form]); expect(data.del.length) .toEqual(0); expect(data.set) .toEqual({}); }); test('settings group remove', () => { const remove = /** @type {HTMLDivElement | null} */ (document.getElementById('remove-group')); assert(remove); addFromTemplate(remove, 'group', {foo: '1111111'}); addFromTemplate(remove, 'group', {foo: '2222222', bar: 'foobarfoo'}); addFromTemplate(remove, 'group', {foo: '3333333', bar: 'barfoobar'}); addFromTemplate(remove, 'group', {foo: '4444444'}); expect(remove.children.length).toEqual(4); const second = remove.children[1]; assert(second instanceof HTMLFieldSetElement); const form = document.getElementById('remove'); assert(form instanceof HTMLFormElement); let data = getData([form]); expect(data.del.length) .toEqual(0); expect(Object.entries(data.set).length) .toEqual(0); // until now, all rows are expected to be 'unchanged' groupSettingsDel(remove, second); expect(remove.children.length) .toEqual(3); expect(countChangedElements(remove)) .toEqual(4); // 2nd row removal should handle following keys data = getData([form]); expect(data.del) .toEqual(['foo3', 'bar3']); expect(data.set) .toEqual({ bar1: 'barfoobar', bar2: '', foo1: '3333333', foo2: '4444444', }); // extra row is always at the end. because add event was triggered, // make sure that the required fields are in the payload addFromTemplate(remove, 'group', {foo: '5555555', bar: 'ttttttt'}); groupSettingsAdd(remove); // since the new row is on top of the removed one, no need to erase it // non-required data, however, should still be removed when still 'unchanged' data = getData([form]); expect(data.del) .toEqual(['bar3']); expect(data.set) .toEqual({ bar1: 'barfoobar', bar2: '', foo1: '3333333', foo2: '4444444', foo3: '5555555', }); addFromTemplate(remove, 'group', {foo: '6666666', bar: 'yyyyyyy'}); groupSettingsAdd(remove); const last = remove?.lastElementChild; assert(last instanceof HTMLFieldSetElement); const bar = last.querySelector('input[name=bar]'); assert(bar instanceof HTMLInputElement); setChangedElement(bar); const first = remove.children[0]; assert(first instanceof HTMLFieldSetElement); expect(remove.children.length) .toEqual(5); expect(countChangedElements(remove)) .toEqual((3 * 2) + 1); groupSettingsDel(remove, first); expect(remove.children.length) .toEqual(4); expect(countChangedElements(remove)) .toEqual(4 * 2); // substituted row keys should no longer be in del set // resulting data is effectively every element present data = getData([form]); expect(data.del.length) .toEqual(0); expect(data.set) .toEqual({ bar0: 'barfoobar', bar1: '', bar2: 'ttttttt', bar3: 'yyyyyyy', foo0: '3333333', foo1: '4444444', foo2: '5555555', foo3: '6666666', }); while (remove.firstElementChild instanceof HTMLFieldSetElement) { groupSettingsDel(remove, remove.firstElementChild); } // original data removed, extra rows are omitted data = getData([form]); expect(data.del.length) .toEqual(8); expect(data.del) .toEqual(expect.arrayContaining([ 'foo0', 'bar0', 'foo1', 'bar1', 'foo2', 'bar2', 'foo3', 'bar3', ])); expect(data.set) .toEqual({}); }); test('settings group schema remove', () => { const group = document.getElementById('schema-del-group'); assert(group instanceof HTMLDivElement); addFromTemplate(group, 'group', {foo: 'asdasdasd'}); addFromTemplate(group, 'group', {foo: 'foobarfoo', bar: 'barfoobar'}); addFromTemplate(group, 'group', {foo: 'oneoneone', bar: 'twotwotwo'}); expect(group.children.length).toEqual(3); const form = group.parentElement; assert(form instanceof HTMLFormElement); let data = getData([form]); expect(data.set) .toEqual({}); expect(data.del.length) .toEqual(0); while (group.firstElementChild instanceof HTMLFieldSetElement) { groupSettingsDel(group, group.firstElementChild); } data = getData([form]); expect(data.del.length) .toEqual(3); expect(data.del) .toEqual(expect.arrayContaining([ 'foo0', 'foo1', 'foo2', ])); expect(data.set) .toEqual({}); });