diff --git a/packages/openscd/test/unit/Editor.test.ts b/packages/openscd/test/unit/Editor.test.ts index 45c7926c8..3bdbf15de 100644 --- a/packages/openscd/test/unit/Editor.test.ts +++ b/packages/openscd/test/unit/Editor.test.ts @@ -3,6 +3,7 @@ import { html, fixture, expect } from '@open-wc/testing'; import '../../src/addons/Editor.js'; import { OscdEditor } from '../../src/addons/Editor.js'; import { Insert, newEditEvent, Remove, Update } from '@openscd/core'; +import { CommitDetail, LogDetail } from '@openscd/core/foundation/deprecated/history.js'; describe('OSCD-Editor', () => { @@ -14,12 +15,17 @@ describe('OSCD-Editor', () => { let voltageLevel2: Element; let bay1: Element; let bay2: Element; + let bay4: Element; + let bay5: Element; let lnode1: Element; let lnode2: Element; + const nsXsi = 'urn:example.com'; + const nsTd = 'urn:typedesigner.com'; + beforeEach(async () => { scd = new DOMParser().parseFromString( - ` + ` @@ -27,7 +33,11 @@ describe('OSCD-Editor', () => { - + + + + + `, @@ -42,6 +52,8 @@ describe('OSCD-Editor', () => { voltageLevel2 = scd.querySelector('VoltageLevel[name="v2"]')!; bay1 = scd.querySelector('Bay[name="b1"]')!; bay2 = scd.querySelector('Bay[name="b2"]')!; + bay4 = scd.querySelector('Bay[name="b4"]')!; + bay5 = scd.querySelector('Bay[name="b5"]')!; lnode1 = scd.querySelector('LNode[name="l1"]')!; lnode2 = scd.querySelector('LNode[name="l2"]')!; }); @@ -174,13 +186,270 @@ describe('OSCD-Editor', () => { }); describe('namespaced attributes', () => { - // TODO + it('should update attribute with namespace', () => { + const update: Update = { + element: lnode1, + attributes: { + type: { value: 'newType', namespaceURI: 'xsi' } + } + }; + + host.dispatchEvent(newEditEvent(update)); + + expect(lnode1.getAttributeNS('xsi', 'type')).to.equal('newType'); + }); + + it('should handle multiple namespaces', () => { + const update: Update = { + element: lnode1, + attributes: { + type: { value: 'newTypeXSI', namespaceURI: nsXsi } + } + }; + + host.dispatchEvent(newEditEvent(update)); + + const update2: Update = { + element: lnode1, + attributes: { + type: { value: 'newTypeTD', namespaceURI: nsTd } + } + }; + + host.dispatchEvent(newEditEvent(update2)); + + expect(lnode1.getAttributeNS(nsXsi, 'type')).to.equal('newTypeXSI'); + expect(lnode1.getAttributeNS(nsTd, 'type')).to.equal('newTypeTD'); + }); + + it('should remove namespaced attribute', () => { + const update: Update = { + element: lnode2, + attributes: { + type: { value: null, namespaceURI: nsXsi } + } + }; + + host.dispatchEvent(newEditEvent(update)); + + expect(lnode2.getAttributeNS(nsXsi, 'type')).to.be.null; + expect(lnode2.getAttributeNS(nsTd, 'type')).to.equal('typeTD'); + }); + + it('should add and remove multiple normal and namespaced attributes', () => { + const update: Update = { + element: lnode2, + attributes: { + type: { value: null, namespaceURI: nsXsi }, + kind: { value: 'td-kind', namespaceURI: nsTd }, + normalAttribute: 'normalValue', + lnClass: null + } + }; + + host.dispatchEvent(newEditEvent(update)); + + expect(lnode2.getAttributeNS(nsXsi, 'type')).to.be.null; + expect(lnode2.getAttributeNS(nsTd, 'kind')).to.equal('td-kind'); + expect(lnode2.getAttribute('normalAttribute')).to.equal('normalValue'); + expect(lnode2.getAttribute('lnClass')).to.be.null; + }); + }); + + describe('Complex action', () => { + it('should apply each edit from a complex edit', () => { + const newNode = scd.createElement('Bay'); + newNode.setAttribute('name', 'b3'); + + const insert: Insert = { + parent: voltageLevel1, + node: newNode, + reference: bay1 + }; + + const remove: Remove = { + node: bay2 + }; + + const update: Update = { + element: bay1, + attributes: { + desc: 'new description' + } + }; + + host.dispatchEvent(newEditEvent([insert, remove, update])); + + expect(scd.querySelector('VoltageLevel[name="v1"] > Bay[name="b3"]')).to.deep.equal(newNode); + expect(scd.querySelector('VoltageLevel[name="v2"] > Bay[name="b2"]')).to.be.null; + expect(scd.querySelector('Bay[name="b1"]')?.getAttribute('desc')).to.equal('new description'); + }); + }); + + describe('log edits', () => { + let log: LogDetail[] = []; + beforeEach(() => { + log = []; + + element.addEventListener('log', (e: CustomEvent) => { + log.push(e.detail); + }); + }); + + it('should log edit for user event', () => { + const remove: Remove = { + node: bay2, + }; + + host.dispatchEvent(newEditEvent(remove, 'user')); + + expect(log).to.have.lengthOf(1); + const logEntry = log[0] as CommitDetail; + expect(logEntry.kind).to.equal('action'); + expect(logEntry.title).to.equal('[editing.deleted]'); + expect(logEntry.redo).to.deep.equal(remove); + }); + + it('should not log edit for undo, redo or system event', () => { + const remove: Remove = { + node: bay2, + }; + + host.dispatchEvent(newEditEvent(remove, 'redo')); + host.dispatchEvent(newEditEvent(remove, 'undo')); + host.dispatchEvent(newEditEvent(remove, 'system')); + + expect(log).to.have.lengthOf(0); + }); }); }); }); describe('Undo/Redo', () => { - // TODO + let log: CommitDetail[] = []; + beforeEach(() => { + log = []; + + element.addEventListener('log', (e: CustomEvent) => { + log.push(e.detail as CommitDetail); + }); + }); + + it('should undo insert', () => { + const newNode = scd.createElement('Bay'); + newNode.setAttribute('name', 'b3'); + + const insert: Insert = { + parent: voltageLevel1, + node: newNode, + reference: null + }; + + host.dispatchEvent(newEditEvent(insert)); + + const undoInsert = log[0].undo as Remove; + + host.dispatchEvent(newEditEvent(undoInsert)); + + expect(scd.querySelector('VoltageLevel[name="v1"] > Bay[name="b3"]')).to.be.null; + }); + + it('should undo remove', () => { + const remove: Remove = { + node: bay4 + }; + + host.dispatchEvent(newEditEvent(remove)); + + const undoRemove = log[0].undo as Insert; + + host.dispatchEvent(newEditEvent(undoRemove)); + + const bay4FromScd = scd.querySelector('VoltageLevel[name="v2"] > Bay[name="b4"]'); + expect(bay4FromScd).to.deep.equal(bay4); + }); + + it('should undo update', () => { + const update: Update = { + element: bay1, + attributes: { + desc: 'new description', + kind: 'superbay' + } + }; + + host.dispatchEvent(newEditEvent(update)); + + const undoUpdate = log[0].undo as Update; + + host.dispatchEvent(newEditEvent(undoUpdate)); + + expect(bay1.getAttribute('desc')).to.be.null; + expect(bay1.getAttribute('kind')).to.equal('bay'); + }); + + it('should redo previously undone action', () => { + const newNode = scd.createElement('Bay'); + newNode.setAttribute('name', 'b3'); + + const insert: Insert = { + parent: voltageLevel1, + node: newNode, + reference: null + }; + + host.dispatchEvent(newEditEvent(insert)); + + const undoIsert = log[0].undo; + const redoInsert = log[0].redo; + + host.dispatchEvent(newEditEvent(undoIsert)); + + expect(scd.querySelector('VoltageLevel[name="v1"] > Bay[name="b3"]')).to.be.null; + + host.dispatchEvent(newEditEvent(redoInsert)); + + expect(scd.querySelector('VoltageLevel[name="v1"] > Bay[name="b3"]')).to.deep.equal(newNode); + }); + + it('should undo and redo complex edit', () => { + const newNode = scd.createElement('Bay'); + newNode.setAttribute('name', 'b3'); + + const insert: Insert = { + parent: voltageLevel1, + node: newNode, + reference: bay1 + }; + + const remove: Remove = { + node: bay2 + }; + + const update: Update = { + element: bay1, + attributes: { + desc: 'new description' + } + }; + + host.dispatchEvent(newEditEvent([insert, remove, update])); + + const undoComplex = log[0].undo; + const redoComplex = log[0].redo; + + host.dispatchEvent(newEditEvent(undoComplex)); + + expect(scd.querySelector('VoltageLevel[name="v1"] > Bay[name="b3"]')).to.be.null; + expect(scd.querySelector('VoltageLevel[name="v2"] > Bay[name="b2"]')).to.deep.equal(bay2); + expect(bay1.getAttribute('desc')).to.be.null; + + host.dispatchEvent(newEditEvent(redoComplex)); + + expect(scd.querySelector('VoltageLevel[name="v1"] > Bay[name="b3"]')).to.deep.equal(newNode); + expect(scd.querySelector('VoltageLevel[name="v2"] > Bay[name="b2"]')).to.be.null; + expect(bay1.getAttribute('desc')).to.equal('new description'); + }); }); });