From 06c4640c668af4a3065e75deaa6fa2f8cb368c63 Mon Sep 17 00:00:00 2001 From: Jakob Vogelsang Date: Sun, 23 Jan 2022 20:39:18 +0100 Subject: [PATCH 1/7] feat(translation): add/fix SCL terms --- src/translations/de.ts | 6 +++++- src/translations/en.ts | 6 +++++- src/wizards/gsecontrol.ts | 5 +++-- test/unit/wizards/__snapshots__/gsecontrol.test.snap.js | 4 ++-- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/translations/de.ts b/src/translations/de.ts index ab3c12776e..50fff20963 100644 --- a/src/translations/de.ts +++ b/src/translations/de.ts @@ -31,11 +31,15 @@ export const de: Translations = { qchg: 'Qualitätsanderung ist Auslöser', dupd: 'Datenupdate ist Auslöser', fixedOffs: 'Fester Offset', - securityEnabled: 'Aktive Sicherungsmaßnahmen', + securityEnable: 'Aktive Sicherungsmaßnahmen', DataSet: 'Datensetz', Communication: 'Kommunikation', TrgOps: 'Triggerbedingungen', OptFields: 'Optionale felder', + multicast: 'SMV nach IEC 61850 9-2', + smpMod: 'Abtast-Art', + smpRate: 'Abtastrate', + nofASDU: 'Abtastpunkte pro Datenpacket', }, settings: { title: 'Einstellungen', diff --git a/src/translations/en.ts b/src/translations/en.ts index 2ed2471057..8c23605f83 100644 --- a/src/translations/en.ts +++ b/src/translations/en.ts @@ -29,11 +29,15 @@ export const en = { qchg: 'Trigger on quality change', dupd: 'Trigger on data update', fixedOffs: 'Fixed offset', - securityEnabled: 'Security enabled', + securityEnable: 'Security enabled', DataSet: 'Dataset', Communication: 'Communication', TrgOps: 'Trigger options', OptFields: 'Optional fields', + multicast: 'SMV acc. to IEC 61850 9-2', + smpMod: 'Sample mode', + smpRate: 'Sample rate', + nofASDU: 'Samples per paket', }, settings: { title: 'Settings', diff --git a/src/wizards/gsecontrol.ts b/src/wizards/gsecontrol.ts index 28de53deb1..fe2116d0c6 100644 --- a/src/wizards/gsecontrol.ts +++ b/src/wizards/gsecontrol.ts @@ -28,6 +28,7 @@ import { import { maxLength, patterns } from './foundation/limits.js'; import { editDataSetWizard } from './dataset.js'; import { editGseWizard } from './gse.js'; +import { securityEnableEnum } from './foundation/enums.js'; function getGSE(element: Element): Element | null | undefined { const cbName = element.getAttribute('name'); @@ -100,8 +101,8 @@ export function renderGseAttributes( .maybeValue=${securityEnabled} nullable required - helper="${translate('scl.securityEnabled')}" - >${['None', 'Signature', 'SignatureAndEncryption'].map( + helper="${translate('scl.securityEnable')}" + >${securityEnableEnum.map( type => html`${type}` )}`, diff --git a/test/unit/wizards/__snapshots__/gsecontrol.test.snap.js b/test/unit/wizards/__snapshots__/gsecontrol.test.snap.js index 595f084864..e07091db13 100644 --- a/test/unit/wizards/__snapshots__/gsecontrol.test.snap.js +++ b/test/unit/wizards/__snapshots__/gsecontrol.test.snap.js @@ -144,7 +144,7 @@ snapshots["gsecontrol wizards renderGseAttribute looks like the latest snapshot" Date: Sun, 23 Jan 2022 21:51:23 +0100 Subject: [PATCH 2/7] feat(wizards/sampledvaluecontrol): add edit wizard --- src/wizards/foundation/enums.ts | 8 + src/wizards/sampledvaluecontrol.ts | 207 ++++++++++++++- .../testfiles/wizards/sampledvaluecontrol.scd | 2 +- .../sampledvaluecontrol.test.snap.js | 161 ++++++++++++ test/unit/wizards/sampledvaluecontrol.test.ts | 247 +++++++++++++++++- 5 files changed, 620 insertions(+), 5 deletions(-) diff --git a/src/wizards/foundation/enums.ts b/src/wizards/foundation/enums.ts index 98990027fa..845792783c 100644 --- a/src/wizards/foundation/enums.ts +++ b/src/wizards/foundation/enums.ts @@ -53,3 +53,11 @@ export const predefinedBasicTypeEnum = [ ]; export const valKindEnum = ['Spec', 'Conf', 'RO', 'Set']; + +export const smpModEnum = ['SmpPerPeriod', 'SmpPerSec', 'SecPerSmp']; + +export const securityEnableEnum = [ + 'None', + 'Signature', + 'SignatureAndEncryption', +]; diff --git a/src/wizards/sampledvaluecontrol.ts b/src/wizards/sampledvaluecontrol.ts index 54036fd0d0..a6c13b3b53 100644 --- a/src/wizards/sampledvaluecontrol.ts +++ b/src/wizards/sampledvaluecontrol.ts @@ -1,7 +1,195 @@ -import { html } from 'lit-element'; -import { get } from 'lit-translate'; +import { List } from '@material/mwc-list'; +import { SingleSelectedEvent } from '@material/mwc-list/mwc-list-foundation'; +import { ListItemBase } from '@material/mwc-list/mwc-list-item-base'; +import { html, TemplateResult } from 'lit-element'; +import { get, translate } from 'lit-translate'; -import { identity, isPublic, Wizard } from '../foundation.js'; +import { + cloneElement, + EditorAction, + getValue, + identity, + isPublic, + newSubWizardEvent, + selector, + Wizard, + WizardActor, + WizardInput, +} from '../foundation.js'; +import { securityEnableEnum, smpModEnum } from './foundation/enums.js'; +import { maxLength, patterns } from './foundation/limits.js'; + +interface ContentOptions { + name: string | null; + desc: string | null; + multicast: string | null; + smvID: string | null; + smpMod: string | null; + smpRate: string | null; + nofASDU: string | null; + securityEnable: string | null; +} + +function contentSampledValueControlWizard( + options: ContentOptions +): TemplateResult[] { + return [ + html``, + html``, + html`${['true', 'false'].map( + option => + html`${option}` + )}`, + html``, + html`${smpModEnum.map( + option => + html`${option}` + )}`, + html``, + html``, + html`${securityEnableEnum.map( + option => + html`${option}` + )}`, + ]; +} + +function updateSampledValueControlAction(element: Element): WizardActor { + return (inputs: WizardInput[]): EditorAction[] => { + const name = inputs.find(i => i.label === 'name')!.value!; + const desc = getValue(inputs.find(i => i.label === 'desc')!); + const multicast = getValue(inputs.find(i => i.label === 'multicast')!); + const smvID = getValue(inputs.find(i => i.label === 'smvID')!)!; + const smpMod = getValue(inputs.find(i => i.label === 'smpMod')!); + const smpRate = getValue(inputs.find(i => i.label === 'smpRate')!); + const nofASDU = getValue(inputs.find(i => i.label === 'nofASDU')!); + const securityEnable = getValue( + inputs.find(i => i.label === 'securityEnable')! + ); + + let sampledValueControlAction: EditorAction | null; + if ( + name === element.getAttribute('name') && + desc === element.getAttribute('desc') && + multicast === element.getAttribute('multicast') && + smvID === element.getAttribute('smvID') && + smpMod === element.getAttribute('smpMod') && + smpRate === element.getAttribute('smpRate') && + nofASDU === element.getAttribute('nofASDU') && + securityEnable === element.getAttribute('securityEnable') + ) { + sampledValueControlAction = null; + } else { + const newElement = cloneElement(element, { + name, + desc, + multicast, + smvID, + smpMod, + smpRate, + nofASDU, + securityEnable, + }); + sampledValueControlAction = { + old: { element }, + new: { element: newElement }, + }; + } + + const actions: EditorAction[] = []; + if (sampledValueControlAction) actions.push(sampledValueControlAction); + return actions; + }; +} + +export function editSampledValueControlWizard(element: Element): Wizard { + const name = element.getAttribute('name'); + const desc = element.getAttribute('desc'); + const multicast = element.getAttribute('multicast'); + const smvID = element.getAttribute('smvID'); + const smpMod = element.getAttribute('smpMod'); + const smpRate = element.getAttribute('smpRate'); + const nofASDU = element.getAttribute('nofASDU'); + const securityEnable = element.getAttribute('securityEnabled'); + + return [ + { + title: get('wizard.title.edit', { tagName: element.tagName }), + element, + primary: { + icon: 'save', + label: get('save'), + action: updateSampledValueControlAction(element), + }, + content: [ + ...contentSampledValueControlWizard({ + name, + desc, + multicast, + smvID, + smpMod, + smpRate, + nofASDU, + securityEnable, + }), + ], + }, + ]; +} export function selectSampledValueControlWizard(element: Element): Wizard { const smvControls = Array.from( @@ -13,6 +201,19 @@ export function selectSampledValueControlWizard(element: Element): Wizard { title: get('wizard.title.select', { tagName: 'SampledValueControl' }), content: [ html` { + const identity = ((e.target).selected).value; + const sampledValueControl = element.querySelector( + selector('SampledValueControl', identity) + ); + if (!sampledValueControl) return; + + e.target?.dispatchEvent( + newSubWizardEvent(() => + editSampledValueControlWizard(sampledValueControl) + ) + ); + }} >${smvControls.map( smvControl => html` - + diff --git a/test/unit/wizards/__snapshots__/sampledvaluecontrol.test.snap.js b/test/unit/wizards/__snapshots__/sampledvaluecontrol.test.snap.js index 4506ae2c61..3b37eed5f9 100644 --- a/test/unit/wizards/__snapshots__/sampledvaluecontrol.test.snap.js +++ b/test/unit/wizards/__snapshots__/sampledvaluecontrol.test.snap.js @@ -50,3 +50,164 @@ snapshots["Wizards for SCL element SampledValueControl define a select wizard th `; /* end snapshot Wizards for SCL element SampledValueControl define a select wizard that looks like the latest snapshot */ +snapshots["Wizards for SCL element SampledValueControl define an edit wizard that looks like the latest snapshot"] = +` +
+ + + + + + + true + + + false + + + + + + + SmpPerPeriod + + + SmpPerSec + + + SecPerSmp + + + + + + + + + None + + + Signature + + + SignatureAndEncryption + + +
+ + + + +
+`; +/* end snapshot Wizards for SCL element SampledValueControl define an edit wizard that looks like the latest snapshot */ + diff --git a/test/unit/wizards/sampledvaluecontrol.test.ts b/test/unit/wizards/sampledvaluecontrol.test.ts index 2a25464cd0..b001a441b4 100644 --- a/test/unit/wizards/sampledvaluecontrol.test.ts +++ b/test/unit/wizards/sampledvaluecontrol.test.ts @@ -4,11 +4,21 @@ import { SinonSpy, spy } from 'sinon'; import '../../mock-wizard.js'; import { MockWizard } from '../../mock-wizard.js'; -import { selectSampledValueControlWizard } from '../../../src/wizards/sampledvaluecontrol.js'; +import { isUpdate, Update, WizardInput } from '../../../src/foundation.js'; +import { + editSampledValueControlWizard, + selectSampledValueControlWizard, +} from '../../../src/wizards/sampledvaluecontrol.js'; +import fc, { integer } from 'fast-check'; +import { inverseRegExp, regExp, regexString } from '../../foundation.js'; +import { WizardTextField } from '../../../src/wizard-textfield.js'; describe('Wizards for SCL element SampledValueControl', () => { let doc: XMLDocument; let element: MockWizard; + let inputs: WizardInput[]; + + let primaryAction: HTMLElement; let actionEvent: SinonSpy; @@ -22,6 +32,241 @@ describe('Wizards for SCL element SampledValueControl', () => { window.addEventListener('editor-action', actionEvent); }); + describe('define an edit wizard that', () => { + beforeEach(async () => { + const wizard = editSampledValueControlWizard( + doc.querySelector('SampledValueControl')! + ); + element.workflow.push(() => wizard); + await element.requestUpdate(); + + inputs = Array.from(element.wizardUI.inputs); + + primaryAction = ( + element.wizardUI.dialog?.querySelector( + 'mwc-button[slot="primaryAction"]' + ) + ); + + await element.wizardUI.requestUpdate(); // make sure wizard is rendered + }); + + it('looks like the latest snapshot', async () => { + await expect(element.wizardUI.dialog).dom.to.equalSnapshot(); + }).timeout(5000); + + it('edits name attribute only for valid inputs', async () => { + await fc.assert( + fc.asyncProperty(regexString(regExp.tAsciName, 1, 32), async name => { + inputs[0].value = name; + await (inputs[0]).requestUpdate(); + expect(inputs[0].checkValidity()).to.be.true; + }) + ); + }); + + it('rejects name attribute starting with decimals', async () => { + await fc.assert( + fc.asyncProperty(regexString(regExp.decimal, 1, 1), async name => { + inputs[0].value = name; + await (inputs[0]).requestUpdate(); + expect(inputs[0].checkValidity()).to.be.false; + }) + ); + }); + + it('edits smpRate attribute only for valid inputs', async () => { + await fc.assert( + fc.asyncProperty( + integer({ min: 0 }).map(num => `${num}`), + async smpRate => { + inputs[5].value = smpRate; + await (inputs[5]).requestUpdate(); + expect(inputs[5].checkValidity()).to.be.true; + } + ) + ); + }); + + it('rejects smpRate attribute in case input is not unsigned int', async () => { + await fc.assert( + fc.asyncProperty(regexString(inverseRegExp.uint, 1), async smpRate => { + inputs[5].value = smpRate; + await (inputs[5]).requestUpdate(); + expect(inputs[5].checkValidity()).to.be.false; + }) + ); + }); + + it('edits nofASDU attribute only for valid inputs', async () => { + const input = inputs[6]; + input.nullSwitch?.click(); + await input.requestUpdate(); + + await fc.assert( + fc.asyncProperty( + integer({ min: 0 }).map(num => `${num}`), + async nofASDU => { + input.value = nofASDU; + await input.requestUpdate(); + expect(input.checkValidity()).to.be.true; + } + ) + ); + }); + + it('rejects nofASDU attribute in case input is not unsigned int', async () => { + const input = inputs[6]; + input.nullSwitch?.click(); + await input.requestUpdate(); + + await fc.assert( + fc.asyncProperty(regexString(inverseRegExp.uint, 1), async nofASDU => { + input.value = nofASDU; + await input.requestUpdate(); + expect(input.checkValidity()).to.be.false; + }) + ); + }); + + it('does not update the SampledValueControl element when no attribute has changed', async () => { + primaryAction.click(); + await element.requestUpdate(); + expect(actionEvent.notCalled).to.be.true; + }); + + it('update a SampledValueControl element when only name attribute changed', async () => { + const input = inputs[0]; + input.value = 'myNewCbName'; + await input.requestUpdate(); + + primaryAction.click(); + await element.requestUpdate(); + expect(actionEvent).to.be.calledOnce; + + const action = actionEvent.args[0][0].detail.action; + expect(action).to.satisfy(isUpdate); + + const updateAction = action; + expect(updateAction.old.element).to.have.attribute('name', 'MSVCB01'); + expect(updateAction.new.element).to.have.attribute('name', 'myNewCbName'); + }); + + it('update a SampledValueControl element when only desc attribute changed', async () => { + const input = inputs[1]; + input.nullSwitch?.click(); + input.value = 'myDesc'; + await input.requestUpdate(); + + primaryAction.click(); + await element.requestUpdate(); + expect(actionEvent).to.be.calledOnce; + + const action = actionEvent.args[0][0].detail.action; + expect(action).to.satisfy(isUpdate); + + const updateAction = action; + expect(updateAction.old.element).to.not.have.attribute('desc'); + expect(updateAction.new.element).to.have.attribute('desc', 'myDesc'); + }); + + it('update a SampledValueControl element when smvID attribute changed', async () => { + const input = inputs[3]; + input.value = 'myNewType/ID'; + await input.requestUpdate(); + + primaryAction.click(); + await element.requestUpdate(); + expect(actionEvent).to.be.calledOnce; + + const action = actionEvent.args[0][0].detail.action; + expect(action).to.satisfy(isUpdate); + + const updateAction = action; + expect(updateAction.old.element).to.have.attribute( + 'smvID', + 'some/reference' + ); + expect(updateAction.new.element).to.have.attribute( + 'smvID', + 'myNewType/ID' + ); + }); + + it('update a SampledValueControl element when smpMod attribute changed', async () => { + const input = inputs[4]; + input.nullSwitch?.click(); + input.value = 'SmpPerSec'; + await input.requestUpdate(); + + primaryAction.click(); + await element.requestUpdate(); + expect(actionEvent).to.be.calledOnce; + + const action = actionEvent.args[0][0].detail.action; + expect(action).to.satisfy(isUpdate); + + const updateAction = action; + expect(updateAction.old.element).to.not.have.attribute('smpMod'); + expect(updateAction.new.element).to.have.attribute('smpMod', 'SmpPerSec'); + }); + + it('update a SampledValueControl element when smpRate attribute changed', async () => { + const input = inputs[5]; + input.value = '4000'; + await input.requestUpdate(); + + primaryAction.click(); + await element.requestUpdate(); + expect(actionEvent).to.be.calledOnce; + + const action = actionEvent.args[0][0].detail.action; + expect(action).to.satisfy(isUpdate); + + const updateAction = action; + expect(updateAction.old.element).to.have.attribute('smpRate', '80'); + expect(updateAction.new.element).to.have.attribute('smpRate', '4000'); + }); + + it('update a SampledValueControl element when nofASDU attribute changed', async () => { + const input = inputs[6]; + input.value = '2'; + await input.requestUpdate(); + + primaryAction.click(); + await element.requestUpdate(); + expect(actionEvent).to.be.calledOnce; + + const action = actionEvent.args[0][0].detail.action; + expect(action).to.satisfy(isUpdate); + + const updateAction = action; + expect(updateAction.old.element).to.have.attribute('nofASDU', '1'); + expect(updateAction.new.element).to.have.attribute('nofASDU', '2'); + }); + + it('updates the SampledValueEnable element when securityEnable changed', async () => { + const input = inputs[7]; + input.nullSwitch?.click(); + input.value = 'Signature'; + await input.requestUpdate(); + + primaryAction.click(); + await element.requestUpdate(); + expect(actionEvent).to.be.calledOnce; + + const action = actionEvent.args[0][0].detail.action; + expect(action).to.satisfy(isUpdate); + + const updateAction = action; + expect(updateAction.old.element).to.not.have.attribute('securityEnable'); + expect(updateAction.new.element).to.have.attribute( + 'securityEnable', + 'Signature' + ); + }); + }); + describe('define a select wizard that', () => { beforeEach(async () => { const wizard = selectSampledValueControlWizard(doc.documentElement); From c5aa52095172b45a716ebe0b444bdb15c25cd72d Mon Sep 17 00:00:00 2001 From: Jakob Vogelsang Date: Mon, 24 Jan 2022 10:29:29 +0100 Subject: [PATCH 3/7] feat(zeroline): start sampledvaluecontrol as subwizard --- src/zeroline-pane.ts | 7 +++++-- src/zeroline/ied-editor.ts | 5 +++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/zeroline-pane.ts b/src/zeroline-pane.ts index 4dad806043..f5a7284187 100644 --- a/src/zeroline-pane.ts +++ b/src/zeroline-pane.ts @@ -79,8 +79,11 @@ export class ZerolinePane extends LitElement { } openSampledValueControlSelection(): void { - const wizard = selectSampledValueControlWizard(this.doc.documentElement); - if (wizard) this.dispatchEvent(newWizardEvent(wizard)); + this.dispatchEvent( + newSubWizardEvent(() => + selectSampledValueControlWizard(this.doc.documentElement) + ) + ); } toggleShowIEDs(): void { diff --git a/src/zeroline/ied-editor.ts b/src/zeroline/ied-editor.ts index 8baf78cba1..81f4b071a8 100644 --- a/src/zeroline/ied-editor.ts +++ b/src/zeroline/ied-editor.ts @@ -52,8 +52,9 @@ export class IedEditor extends LitElement { } private openSmvControlSelection(): void { - const wizard = selectSampledValueControlWizard(this.element); - if (wizard) this.dispatchEvent(newWizardEvent(wizard)); + this.dispatchEvent( + newSubWizardEvent(() => selectSampledValueControlWizard(this.element)) + ); } private openCommunicationMapping(): void { From 6f694077db369a9c4aa26dabb6cd5266d1b09e8a Mon Sep 17 00:00:00 2001 From: Jakob Vogelsang Date: Mon, 24 Jan 2022 10:30:26 +0100 Subject: [PATCH 4/7] refactor(wizards/sampledvaluecontrol): import statements and update action --- src/wizards/sampledvaluecontrol.ts | 60 +++++------- .../sampledvaluecontrol.test.snap.js | 98 +++++++++---------- 2 files changed, 75 insertions(+), 83 deletions(-) diff --git a/src/wizards/sampledvaluecontrol.ts b/src/wizards/sampledvaluecontrol.ts index a6c13b3b53..ca61cbac56 100644 --- a/src/wizards/sampledvaluecontrol.ts +++ b/src/wizards/sampledvaluecontrol.ts @@ -1,9 +1,14 @@ -import { List } from '@material/mwc-list'; -import { SingleSelectedEvent } from '@material/mwc-list/mwc-list-foundation'; -import { ListItemBase } from '@material/mwc-list/mwc-list-item-base'; import { html, TemplateResult } from 'lit-element'; import { get, translate } from 'lit-translate'; +import '@material/mwc-list/mwc-list-item.js'; +import { List } from '@material/mwc-list'; +import { ListItemBase } from '@material/mwc-list/mwc-list-item-base'; +import { SingleSelectedEvent } from '@material/mwc-list/mwc-list-foundation'; + +import '../filtered-list.js'; +import '../wizard-select.js'; +import '../wizard-textfield.js'; import { cloneElement, EditorAction, @@ -110,40 +115,27 @@ function contentSampledValueControlWizard( function updateSampledValueControlAction(element: Element): WizardActor { return (inputs: WizardInput[]): EditorAction[] => { - const name = inputs.find(i => i.label === 'name')!.value!; - const desc = getValue(inputs.find(i => i.label === 'desc')!); - const multicast = getValue(inputs.find(i => i.label === 'multicast')!); - const smvID = getValue(inputs.find(i => i.label === 'smvID')!)!; - const smpMod = getValue(inputs.find(i => i.label === 'smpMod')!); - const smpRate = getValue(inputs.find(i => i.label === 'smpRate')!); - const nofASDU = getValue(inputs.find(i => i.label === 'nofASDU')!); - const securityEnable = getValue( - inputs.find(i => i.label === 'securityEnable')! - ); + const attributes: Record = {}; + const attributeKeys = [ + 'name', + 'desc', + 'multicast', + 'smvID', + 'smpMod', + 'smpRate', + 'nofASDU', + 'securityEnable', + ]; + + attributeKeys.forEach(key => { + attributes[key] = getValue(inputs.find(i => i.label === key)!); + }); - let sampledValueControlAction: EditorAction | null; + let sampledValueControlAction: EditorAction | null = null; if ( - name === element.getAttribute('name') && - desc === element.getAttribute('desc') && - multicast === element.getAttribute('multicast') && - smvID === element.getAttribute('smvID') && - smpMod === element.getAttribute('smpMod') && - smpRate === element.getAttribute('smpRate') && - nofASDU === element.getAttribute('nofASDU') && - securityEnable === element.getAttribute('securityEnable') + attributeKeys.some(key => attributes[key] !== element.getAttribute(key)) ) { - sampledValueControlAction = null; - } else { - const newElement = cloneElement(element, { - name, - desc, - multicast, - smvID, - smpMod, - smpRate, - nofASDU, - securityEnable, - }); + const newElement = cloneElement(element, attributes); sampledValueControlAction = { old: { element }, new: { element: newElement }, diff --git a/test/unit/wizards/__snapshots__/sampledvaluecontrol.test.snap.js b/test/unit/wizards/__snapshots__/sampledvaluecontrol.test.snap.js index 3b37eed5f9..a72da24e28 100644 --- a/test/unit/wizards/__snapshots__/sampledvaluecontrol.test.snap.js +++ b/test/unit/wizards/__snapshots__/sampledvaluecontrol.test.snap.js @@ -1,55 +1,6 @@ /* @web/test-runner snapshot v1 */ export const snapshots = {}; -snapshots["Wizards for SCL element SampledValueControl define a select wizard that looks like the latest snapshot"] = -` -
- - - - MSVCB01 - - - IED2>>CBSW>MSVCB01 - - - - - MSVCB01 - - - IED3>>MU01>MSVCB01 - - - -
- - -
-`; -/* end snapshot Wizards for SCL element SampledValueControl define a select wizard that looks like the latest snapshot */ - snapshots["Wizards for SCL element SampledValueControl define an edit wizard that looks like the latest snapshot"] = ` +
+ + + + MSVCB01 + + + IED2>>CBSW>MSVCB01 + + + + + MSVCB01 + + + IED3>>MU01>MSVCB01 + + + +
+ + +
+`; +/* end snapshot Wizards for SCL element SampledValueControl define a select wizard that looks like the latest snapshot */ + From 11b923eca54765c021ba5af8fe2640694993a45f Mon Sep 17 00:00:00 2001 From: Jakob Vogelsang Date: Mon, 24 Jan 2022 10:30:59 +0100 Subject: [PATCH 5/7] test(wizards/sampledvaluecontrol): add integration tests --- ...pledvaluecontrol-wizarding-editing.test.ts | 173 ++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 test/integration/wizards/sampledvaluecontrol-wizarding-editing.test.ts diff --git a/test/integration/wizards/sampledvaluecontrol-wizarding-editing.test.ts b/test/integration/wizards/sampledvaluecontrol-wizarding-editing.test.ts new file mode 100644 index 0000000000..41554180f7 --- /dev/null +++ b/test/integration/wizards/sampledvaluecontrol-wizarding-editing.test.ts @@ -0,0 +1,173 @@ +import { expect, fixture, html } from '@open-wc/testing'; + +import '../../mock-wizard-editor.js'; +import { MockWizardEditor } from '../../mock-wizard-editor.js'; + +import { ListItemBase } from '@material/mwc-list/mwc-list-item-base'; + +import { FilteredList } from '../../../src/filtered-list.js'; +import { WizardTextField } from '../../../src/wizard-textfield.js'; +import { selectSampledValueControlWizard } from '../../../src/wizards/sampledvaluecontrol.js'; + +describe('Wizards for SCL element SampledValueControl', () => { + let doc: XMLDocument; + let element: MockWizardEditor; + + beforeEach(async () => { + element = await fixture(html``); + doc = await fetch('/test/testfiles/wizards/sampledvaluecontrol.scd') + .then(response => response.text()) + .then(str => new DOMParser().parseFromString(str, 'application/xml')); + }); + + describe('define a select wizards that ', () => { + let sampledValueControlList: FilteredList; + + beforeEach(async () => { + const wizard = selectSampledValueControlWizard(doc.documentElement); + element.workflow.push(() => wizard); + await element.requestUpdate(); + + sampledValueControlList = ( + element.wizardUI.dialog?.querySelector('filtered-list') + ); + await sampledValueControlList.updateComplete; + }); + + it('shows all SampledValueControl elements within a project', () => + expect(sampledValueControlList.items.length).to.equal( + doc.querySelectorAll('SampledValueControl').length + )); + + it('allows to filter SampledValueControl elements per IED', async () => { + const wizard = selectSampledValueControlWizard(doc.querySelector('IED')!); + element.workflow.pop(); + element.workflow.push(() => wizard); + await element.requestUpdate(); + + sampledValueControlList = ( + element.wizardUI.dialog?.querySelector('filtered-list') + ); + await sampledValueControlList.updateComplete; + + expect(sampledValueControlList.items.length).to.equal( + doc.querySelector('IED')!.querySelectorAll('SampledValueControl').length + ); + }); + + it('opens edit wizard for selected SampledValueControl element on click', async () => { + const reportItem = sampledValueControlList.items[0]; + reportItem.click(); + await new Promise(resolve => setTimeout(resolve, 20)); // await animation + + const nameField = ( + element.wizardUI.dialog?.querySelector('wizard-textfield[label="name"]') + ); + await nameField.requestUpdate(); + + expect(nameField.value).to.equal( + doc.querySelectorAll('SampledValueControl')[0].getAttribute('name') + ); + }); + }); + + describe('defines an edit wizard that', () => { + let nameField: WizardTextField; + let secondaryAction: HTMLElement; + let primaryAction: HTMLElement; + let parentIED: Element; + + beforeEach(async () => { + element.workflow.length = 0; // remove all wizard from FIFO queue + parentIED = doc.querySelectorAll('IED')[1]; + element.workflow.push(() => selectSampledValueControlWizard(parentIED)); + await element.requestUpdate(); + await new Promise(resolve => setTimeout(resolve, 20)); // await animation + + const sampledValueControlBlock = ( + (element.wizardUI.dialog?.querySelector('filtered-list')) + .items[0] + ); + sampledValueControlBlock.click(); + await new Promise(resolve => setTimeout(resolve, 20)); // await animation + + nameField = element.wizardUI.dialog!.querySelector( + 'wizard-textfield[label="name"]' + )!; + primaryAction = ( + element.wizardUI.dialog?.querySelector( + 'mwc-button[slot="primaryAction"]' + ) + ); + secondaryAction = ( + element.wizardUI.dialog?.querySelector( + 'mwc-button[slot="secondaryAction"]' + ) + ); + await nameField.updateComplete; + }); + + it('rejects name attribute starting with decimals', async () => { + expect( + parentIED + .querySelectorAll('SampledValueControl')[0] + ?.getAttribute('name') + ).to.not.equal('4adsasd'); + + nameField.value = '4adsasd'; + await element.requestUpdate(); + primaryAction.click(); + + expect( + parentIED + .querySelectorAll('SampledValueControl')[0] + ?.getAttribute('name') + ).to.not.equal('4adsasd'); + }); + + it('edits name attribute on primary action', async () => { + expect( + parentIED + .querySelectorAll('SampledValueControl')[0] + ?.getAttribute('name') + ).to.not.equal('myNewName'); + + nameField.value = 'myNewName'; + await element.requestUpdate(); + primaryAction.click(); + + expect( + parentIED + .querySelectorAll('SampledValueControl')[0] + ?.getAttribute('name') + ).to.equal('myNewName'); + }); + + it('dynamically updates wizards after attribute change', async () => { + nameField.value = 'myNewName'; + primaryAction.click(); + + await new Promise(resolve => setTimeout(resolve, 20)); // await animation + + const sampledValueControlBlock = ( + (element.wizardUI.dialog?.querySelector('filtered-list')) + .items[0] + ); + + expect(sampledValueControlBlock.innerHTML).to.contain('myNewName'); + }); + + it('returns back to its starting wizard on secondary action', async () => { + secondaryAction.click(); + + await new Promise(resolve => setTimeout(resolve, 100)); // await animation + + const sampledValueControlBlock = ( + (element.wizardUI.dialog?.querySelector('filtered-list')) + .items[0] + ); + + expect(sampledValueControlBlock.innerHTML).to.contain('MSVCB01'); + }); + }); +}); From 2047eb4909e2e32271688824fd23706de3094733 Mon Sep 17 00:00:00 2001 From: Jakob Vogelsang Date: Tue, 25 Jan 2022 16:15:52 +0100 Subject: [PATCH 6/7] fix(zeroline): remove incorrect SubWizard trigger --- src/zeroline-pane.ts | 10 ++++------ src/zeroline/ied-editor.ts | 8 ++++---- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/zeroline-pane.ts b/src/zeroline-pane.ts index f5a7284187..88a4ef73ee 100644 --- a/src/zeroline-pane.ts +++ b/src/zeroline-pane.ts @@ -19,7 +19,7 @@ import './zeroline/ied-editor.js'; import { Settings } from './Setting.js'; import { communicationMappingWizard } from './wizards/commmap-wizards.js'; import { gooseIcon, smvIcon, reportIcon } from './icons.js'; -import { isPublic, newSubWizardEvent, newWizardEvent } from './foundation.js'; +import { isPublic, newWizardEvent } from './foundation.js'; import { selectGseControlWizard } from './wizards/gsecontrol.js'; import { wizards } from './wizards/wizard-library.js'; import { getAttachedIeds } from './zeroline/foundation.js'; @@ -66,21 +66,19 @@ export class ZerolinePane extends LitElement { openReportControlSelection(): void { this.dispatchEvent( - newSubWizardEvent(() => - selectReportControlWizard(this.doc.documentElement) - ) + newWizardEvent(() => selectReportControlWizard(this.doc.documentElement)) ); } openGseControlSelection(): void { this.dispatchEvent( - newSubWizardEvent(() => selectGseControlWizard(this.doc.documentElement)) + newWizardEvent(() => selectGseControlWizard(this.doc.documentElement)) ); } openSampledValueControlSelection(): void { this.dispatchEvent( - newSubWizardEvent(() => + newWizardEvent(() => selectSampledValueControlWizard(this.doc.documentElement) ) ); diff --git a/src/zeroline/ied-editor.ts b/src/zeroline/ied-editor.ts index 81f4b071a8..d9a13b23ef 100644 --- a/src/zeroline/ied-editor.ts +++ b/src/zeroline/ied-editor.ts @@ -15,7 +15,7 @@ import '../action-icon.js'; import { createClientLnWizard } from '../wizards/clientln.js'; import { gooseIcon, smvIcon, reportIcon } from '../icons.js'; import { wizards } from '../wizards/wizard-library.js'; -import { newSubWizardEvent, newWizardEvent } from '../foundation.js'; +import { newWizardEvent } from '../foundation.js'; import { selectGseControlWizard } from '../wizards/gsecontrol.js'; import { selectSampledValueControlWizard } from '../wizards/sampledvaluecontrol.js'; import { selectReportControlWizard } from '../wizards/reportcontrol.js'; @@ -41,19 +41,19 @@ export class IedEditor extends LitElement { private openReportControlSelection(): void { this.dispatchEvent( - newSubWizardEvent(() => selectReportControlWizard(this.element)) + newWizardEvent(() => selectReportControlWizard(this.element)) ); } private openGseControlSelection(): void { this.dispatchEvent( - newSubWizardEvent(() => selectGseControlWizard(this.element)) + newWizardEvent(() => selectGseControlWizard(this.element)) ); } private openSmvControlSelection(): void { this.dispatchEvent( - newSubWizardEvent(() => selectSampledValueControlWizard(this.element)) + newWizardEvent(() => selectSampledValueControlWizard(this.element)) ); } From ced93ad9ed488db7eb5706323430a746cb74a3aa Mon Sep 17 00:00:00 2001 From: Jakob Vogelsang Date: Tue, 25 Jan 2022 16:18:38 +0100 Subject: [PATCH 7/7] fix(wizards/sampledvaluecontrol): add missing pattern --- src/wizards/sampledvaluecontrol.ts | 1 + test/unit/wizards/__snapshots__/sampledvaluecontrol.test.snap.js | 1 + 2 files changed, 2 insertions(+) diff --git a/src/wizards/sampledvaluecontrol.ts b/src/wizards/sampledvaluecontrol.ts index ca61cbac56..003be8e3a0 100644 --- a/src/wizards/sampledvaluecontrol.ts +++ b/src/wizards/sampledvaluecontrol.ts @@ -53,6 +53,7 @@ function contentSampledValueControlWizard( label="desc" .maybeValue=${options.desc} nullable + pattern="${patterns.normalizedString}" helper="${translate('scl.desc')}" >`, html`