From 230cee8fcde771d6fe0becc1b355f53f6405c645 Mon Sep 17 00:00:00 2001 From: MarcvanRaalte Date: Tue, 7 Mar 2023 09:30:59 +0100 Subject: [PATCH 01/21] feat(line-editor.ts):unit_test_added --- src/editors/substation/line-editor.ts | 103 ++++++++ src/editors/substation/zeroline-pane.ts | 27 +- test/testfiles/editors/substation/Line.scd | 235 ++++++++++++++++++ .../__snapshots__/line-editor.test.snap.js | 56 +++++ .../editors/substation/line-editor.test.ts | 68 +++++ 5 files changed, 487 insertions(+), 2 deletions(-) create mode 100644 src/editors/substation/line-editor.ts create mode 100644 test/testfiles/editors/substation/Line.scd create mode 100644 test/unit/editors/substation/__snapshots__/line-editor.test.snap.js create mode 100644 test/unit/editors/substation/line-editor.test.ts diff --git a/src/editors/substation/line-editor.ts b/src/editors/substation/line-editor.ts new file mode 100644 index 000000000..459c200e6 --- /dev/null +++ b/src/editors/substation/line-editor.ts @@ -0,0 +1,103 @@ +import { + customElement, + html, + LitElement, + TemplateResult, + property, + state, +} from 'lit-element'; + +import './conducting-equipment-editor.js'; +import './function-editor.js'; +import './general-equipment-editor.js'; +import './l-node-editor.js'; +import { getChildElementsByTagName } from '../../foundation.js'; + +@customElement('line-editor') +export class LineEditor extends LitElement { + /** The document being edited as provided to editor by [[`Zeroline`]]. */ + @property({ attribute: false }) + doc!: XMLDocument; + /** SCL element Line */ + @property({ attribute: false }) + element!: Element; + /** Whether `Function` and `LNode` are rendered */ + @property({ type: Boolean }) + showfunctions = false; + + @state() + get header(): string { + const name = this.element.getAttribute('name') ?? ''; + const desc = this.element.getAttribute('desc'); + + return `${name} ${desc ? `—${desc}` : ''}`; + } + + private renderConductingEquipments(): TemplateResult { + const ConductingEquipments = getChildElementsByTagName( + this.element, + 'ConductingEquipment' + ); + return html` ${ConductingEquipments.map( + ConductingEquipment => + html`` + )}`; + } + + private renderGeneralEquipments(): TemplateResult { + const GeneralEquipments = getChildElementsByTagName( + this.element, + 'GeneralEquipment' + ); + return html` ${GeneralEquipments.map( + GeneralEquipment => + html`` + )}`; + } + + private renderFunctions(): TemplateResult { + if (!this.showfunctions) return html``; + + const Functions = getChildElementsByTagName(this.element, 'Function'); + return html` ${Functions.map( + Function => + html`` + )}`; + } + + private renderLNodes(): TemplateResult { + if (!this.showfunctions) return html``; + + const lNodes = getChildElementsByTagName(this.element, 'LNode'); + return lNodes.length + ? html`
+ ${lNodes.map( + lNode => + html`` + )} +
` + : html``; + } + + render(): TemplateResult { + return html` ${this.renderConductingEquipments()}${this.renderGeneralEquipments()}${this.renderFunctions()}${this.renderLNodes()} + `; + } +} diff --git a/src/editors/substation/zeroline-pane.ts b/src/editors/substation/zeroline-pane.ts index 918f0077b..da2d237e7 100644 --- a/src/editors/substation/zeroline-pane.ts +++ b/src/editors/substation/zeroline-pane.ts @@ -14,6 +14,7 @@ import '@material/mwc-icon-button-toggle'; import { IconButton } from '@material/mwc-icon-button'; import { IconButtonToggle } from '@material/mwc-icon-button-toggle'; +import './line-editor.js'; import './substation-editor.js'; import './ied-editor.js'; import { communicationMappingWizard } from '../../wizards/commmap-wizards.js'; @@ -113,11 +114,33 @@ export class ZerolinePane extends LitElement { return ieds.length ? html`
- ${ieds.map(ied => html``)} + ${ieds.map( + ied => + html`` + )}
` : html``; } + renderLines(): TemplateResult { + return this.doc?.querySelector(':root > Line') + ? html`
+ ${Array.from(this.doc.querySelectorAll('Line') ?? []) + .filter(isPublic) + .map( + line => + html`` + )} +
` + : html``; + } + render(): TemplateResult { return html`

`}`; + `}${this.renderLines()}`; } static styles = css` diff --git a/test/testfiles/editors/substation/Line.scd b/test/testfiles/editors/substation/Line.scd new file mode 100644 index 000000000..3cbdb667b --- /dev/null +++ b/test/testfiles/editors/substation/Line.scd @@ -0,0 +1,235 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + status-only + + + + + + + + + + + + + + + + + + sbo-with-enhanced-security + + + 30000 + + + 600 + + + + + + + + + + + + + + 1000 + + + direct-with-enhanced-security + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + sbo-with-enhanced-security + + + 30000 + + + 600 + + + + + + + + + + + + IEC 61850-8-1:2003 + + + + + + + + + IEC 61850-8-1:2003 + + + + + + + + + + + + + + IEC 61850-8-1:2003 + + + + + + + + + IEC 61850-8-1:2003 + + + + + + + + IEC 61850-8-1:2003 + + + + + + + + + IEC 61850-8-1:2003 + + + + + + + status-only + + + pulse + persistent + persistent-feedback + + + Ok + Warning + Alarm + + + status-only + direct-with-normal-security + sbo-with-normal-security + direct-with-enhanced-security + sbo-with-enhanced-security + + + on + blocked + test + test/blocked + off + + + not-supported + bay-control + station-control + remote-control + automatic-bay + automatic-station + automatic-remote + maintenance + process + + + + + + + + + 110.0 + + + + + + + + + + + + + + 380.0 + + + + + diff --git a/test/unit/editors/substation/__snapshots__/line-editor.test.snap.js b/test/unit/editors/substation/__snapshots__/line-editor.test.snap.js new file mode 100644 index 000000000..ae6dbfe89 --- /dev/null +++ b/test/unit/editors/substation/__snapshots__/line-editor.test.snap.js @@ -0,0 +1,56 @@ +/* @web/test-runner snapshot v1 */ +export const snapshots = {}; + +snapshots["web component rendering Line element rendering LNode and Function children looks like the latest snapshot"] = +` + + + + +
+ + +
+
+`; +/* end snapshot web component rendering Line element rendering LNode and Function children looks like the latest snapshot */ + +snapshots["web component rendering Line element rendering ConductingEquipment looks like the latest snapshot"] = +` + + + + +
+ + +
+
+`; +/* end snapshot web component rendering Line element rendering ConductingEquipment looks like the latest snapshot */ + +snapshots["web component rendering Line element rendering GeneralEquipment looks like the latest snapshot"] = +` + + + + + + +
+ + +
+
+`; +/* end snapshot web component rendering Line element rendering GeneralEquipment looks like the latest snapshot */ + diff --git a/test/unit/editors/substation/line-editor.test.ts b/test/unit/editors/substation/line-editor.test.ts new file mode 100644 index 000000000..31e6ef9e8 --- /dev/null +++ b/test/unit/editors/substation/line-editor.test.ts @@ -0,0 +1,68 @@ +import { fixture, html, expect } from '@open-wc/testing'; + +import '../../../../src/editors/substation/line-editor.js'; +import { LineEditor } from '../../../../src/editors/substation/line-editor.js'; + +describe('web component rendering Line element', () => { + let element: LineEditor; + let doc: XMLDocument; + + describe('rendering LNode and Function children', () => { + beforeEach(async () => { + doc = await fetch('/test/testfiles/editors/substation/Line.scd') + .then(response => response.text()) + .then(str => new DOMParser().parseFromString(str, 'application/xml')); + element = ( + await fixture( + html`` + ) + ); + element.showfunctions = true; + await element.updateComplete; + }); + it('looks like the latest snapshot', async () => { + await expect(element).shadowDom.to.equalSnapshot(); + }); + }); + + describe('rendering ConductingEquipment', () => { + beforeEach(async () => { + doc = await fetch('/test/testfiles/editors/substation/Line.scd') + .then(response => response.text()) + .then(str => new DOMParser().parseFromString(str, 'application/xml')); + element = ( + await fixture( + html`` + ) + ); + element.showfunctions = true; + await element.updateComplete; + }); + it('looks like the latest snapshot', async () => { + await expect(element).shadowDom.to.equalSnapshot(); + }); + }); + describe('rendering GeneralEquipment', () => { + beforeEach(async () => { + doc = await fetch('/test/testfiles/editors/substation/Line.scd') + .then(response => response.text()) + .then(str => new DOMParser().parseFromString(str, 'application/xml')); + element = ( + await fixture( + html`` + ) + ); + element.showfunctions = true; + await element.updateComplete; + }); + it('looks like the latest snapshot', async () => { + await expect(element).shadowDom.to.equalSnapshot(); + }); + }); +}); From 68ad7420913465fccc532630f97f11a7db371242 Mon Sep 17 00:00:00 2001 From: MarcvanRaalte Date: Mon, 13 Mar 2023 15:40:26 +0100 Subject: [PATCH 02/21] feat(line-editor):add_edit_wizard --- src/editors/substation/line-editor.ts | 25 +++- src/editors/substation/tapchanger-editor.ts | 48 ++++---- src/translations/en.ts | 15 +++ src/wizards/line.ts | 119 ++++++++++++++++++++ src/wizards/wizard-library.ts | 3 +- 5 files changed, 180 insertions(+), 30 deletions(-) create mode 100644 src/wizards/line.ts diff --git a/src/editors/substation/line-editor.ts b/src/editors/substation/line-editor.ts index 459c200e6..18f7e0f61 100644 --- a/src/editors/substation/line-editor.ts +++ b/src/editors/substation/line-editor.ts @@ -7,11 +7,17 @@ import { state, } from 'lit-element'; +import { translate } from 'lit-translate'; + +import '@material/mwc-icon'; +import '@material/mwc-icon-button'; + import './conducting-equipment-editor.js'; import './function-editor.js'; import './general-equipment-editor.js'; import './l-node-editor.js'; -import { getChildElementsByTagName } from '../../foundation.js'; +import { getChildElementsByTagName, newWizardEvent } from '../../foundation.js'; +import { wizards } from '../../wizards/wizard-library.js'; @customElement('line-editor') export class LineEditor extends LitElement { @@ -33,6 +39,11 @@ export class LineEditor extends LitElement { return `${name} ${desc ? `—${desc}` : ''}`; } + private openEditWizard(): void { + const wizard = wizards['Line'].edit(this.element); + if (wizard) this.dispatchEvent(newWizardEvent(wizard)); + } + private renderConductingEquipments(): TemplateResult { const ConductingEquipments = getChildElementsByTagName( this.element, @@ -95,9 +106,13 @@ export class LineEditor extends LitElement { } render(): TemplateResult { - return html` ${this.renderConductingEquipments()}${this.renderGeneralEquipments()}${this.renderFunctions()}${this.renderLNodes()} - `; + return html` + + this.openEditWizard()} + > ${this.renderConductingEquipments()}${this.renderGeneralEquipments()}${this.renderFunctions()}${this.renderLNodes()} + `; } } diff --git a/src/editors/substation/tapchanger-editor.ts b/src/editors/substation/tapchanger-editor.ts index bca1a0043..aadd903ba 100644 --- a/src/editors/substation/tapchanger-editor.ts +++ b/src/editors/substation/tapchanger-editor.ts @@ -64,7 +64,7 @@ export class TapChangerEditor extends LitElement { @query('mwc-menu') addMenu!: Menu; @query('mwc-icon-button[icon="playlist_add"]') addButton!: IconButton; - openEditWizard(): void { + private openEditWizard(): void { const wizard = wizards['TapChanger'].edit(this.element); if (wizard) this.dispatchEvent(newWizardEvent(wizard)); } @@ -147,8 +147,8 @@ export class TapChangerEditor extends LitElement { } render(): TemplateResult { - return html` - + return html` + this.openEditWizard()} @@ -161,27 +161,27 @@ export class TapChangerEditor extends LitElement { > - (this.addMenu.open = true)} - > { - const tagName = ((e.target).selected).value; - this.openCreateWizard(tagName); - }} - >${this.renderAddButtons()} - ${this.renderLNodes()} - ${this.renderEqFunctions()} ${this.renderSubEquipments()} - `; + slot="action" + style="position:relative;" + title="${translate('add')}" + > + (this.addMenu.open = true)} + > { + const tagName = ((e.target).selected).value; + this.openCreateWizard(tagName); + }} + >${this.renderAddButtons()} + ${this.renderLNodes()} ${this.renderEqFunctions()} + ${this.renderSubEquipments()} + `; } static styles = css` diff --git a/src/translations/en.ts b/src/translations/en.ts index e5e64fb97..b21754ff4 100644 --- a/src/translations/en.ts +++ b/src/translations/en.ts @@ -285,6 +285,21 @@ export const en = { updateVoltagelevel: 'Edited voltagelevel "{{name}}"', }, }, + line: { + name: 'Line', + wizard: { + nameHelper: 'Line name', + descHelper: 'Line description', + typeHelper: 'Line type', + title: { + add: 'Add line', + edit: 'Edit line', + }, + }, + action: { + updateLine: 'Edited line "{{name}}"', + }, + }, bay: { name: 'Bay', wizard: { diff --git a/src/wizards/line.ts b/src/wizards/line.ts new file mode 100644 index 000000000..5a02d7d94 --- /dev/null +++ b/src/wizards/line.ts @@ -0,0 +1,119 @@ +import { html, TemplateResult } from 'lit-html'; +import { get, translate } from 'lit-translate'; + +import '../wizard-textfield.js'; +import { + cloneElement, + ComplexAction, + createElement, + EditorAction, + getMultiplier, + getValue, + patterns, + SimpleAction, + Wizard, + WizardActor, + WizardInputElement, +} from '../foundation.js'; + +import { updateReferences } from './foundation/references.js'; + +const initial = { + nomFreq: '50', + numPhases: '3', + Voltage: '110', + multiplier: 'k', +}; + +function render( + name: string, + desc: string | null, + type: string | null, + nomFreq: string | null, + numPhases: string | null +): TemplateResult[] { + return [ + html``, + html``, + html``, + html``, + html``, + ]; +} + +function updateAction(element: Element): WizardActor { + return (inputs: WizardInputElement[]): SimpleAction[] => { + const lineAttrs: Record = {}; + const lineKeys = ['name', 'desc', 'type', 'nomFreq', 'numPhases']; + lineKeys.forEach(key => { + lineAttrs[key] = getValue(inputs.find(i => i.label === key)!); + }); + + if (lineKeys.some(key => lineAttrs[key] !== element.getAttribute(key))) { + const newElement = cloneElement(element, lineAttrs); + return [ + { + old: { element }, + new: { element: newElement }, + }, + ]; + } + return []; + }; +} + +export function editLineWizard(element: Element): Wizard { + return [ + { + title: get('line.wizard.title.edit'), + element, + primary: { + icon: 'edit', + label: get('save'), + action: updateAction(element), + }, + content: render( + element.getAttribute('name') ?? '', + element.getAttribute('desc'), + element.getAttribute('type'), + element.getAttribute('nomFreq'), + element.getAttribute('numPhases') + ), + }, + ]; +} diff --git a/src/wizards/wizard-library.ts b/src/wizards/wizard-library.ts index e0203ec7e..94a3b3d28 100644 --- a/src/wizards/wizard-library.ts +++ b/src/wizards/wizard-library.ts @@ -49,6 +49,7 @@ import { editTransformerWindingWizard, } from './transformerWinding.js'; import { createTapChangerWizard, editTapChangerWizard } from './tapchanger.js'; +import { editLineWizard } from './line.js'; type SclElementWizard = ( element: Element, @@ -335,7 +336,7 @@ export const wizards: Record< create: emptyWizard, }, Line: { - edit: emptyWizard, + edit: editLineWizard, create: emptyWizard, }, Log: { From 1d8a0fdc054b4c13bf7c4f6319a02af18dbdd66f Mon Sep 17 00:00:00 2001 From: MarcvanRaalte Date: Tue, 14 Mar 2023 09:58:56 +0100 Subject: [PATCH 03/21] feat(line-editor-wizard-editing.test):part-of-test --- src/wizards/line.ts | 8 +- .../line-editor-wizard-editing.test.ts | 83 +++++++++++++++++++ .../tapchanger-editor-wizard-editing.test.ts | 2 +- .../__snapshots__/line-editor.test.snap.js | 21 +++++ 4 files changed, 106 insertions(+), 8 deletions(-) create mode 100644 test/integration/editors/substation/line-editor-wizard-editing.test.ts diff --git a/src/wizards/line.ts b/src/wizards/line.ts index 5a02d7d94..47cedd17c 100644 --- a/src/wizards/line.ts +++ b/src/wizards/line.ts @@ -4,10 +4,6 @@ import { get, translate } from 'lit-translate'; import '../wizard-textfield.js'; import { cloneElement, - ComplexAction, - createElement, - EditorAction, - getMultiplier, getValue, patterns, SimpleAction, @@ -16,8 +12,6 @@ import { WizardInputElement, } from '../foundation.js'; -import { updateReferences } from './foundation/references.js'; - const initial = { nomFreq: '50', numPhases: '3', @@ -50,7 +44,7 @@ function render( html``, html` { + let doc: XMLDocument; + let parent: MockWizardEditor; + let element: LineEditor | null; + + describe('edit wizard', () => { + let nameField: WizardTextField; + let descField: WizardTextField; + let typeField: WizardTextField; + let nomFreqField: WizardTextField; + let numPhasesField: WizardTextField; + let primaryAction: HTMLElement; + let secondaryAction: HTMLElement; + + beforeEach(async () => { + doc = await fetch('/test/testfiles/editors/substation/Line.scd') + .then(response => response.text()) + .then(str => new DOMParser().parseFromString(str, 'application/xml')); + parent = ( + await fixture( + html`` + ) + ); + element = parent.querySelector('line-editor'); + await (( + element?.shadowRoot?.querySelector('mwc-icon-button[icon="edit"]') + )).click(); + await parent.updateComplete; + + nameField = ( + parent.wizardUI.dialog?.querySelector('wizard-textfield[label="name"]') + ); + descField = ( + parent.wizardUI.dialog?.querySelector('wizard-textfield[label="desc"]') + ); + typeField = ( + parent.wizardUI.dialog?.querySelector('wizard-textfield[label="type"]') + ); + nomFreqField = ( + parent.wizardUI.dialog?.querySelector( + 'wizard-textfield[label="nomFreq"]' + ) + ); + numPhasesField = ( + parent.wizardUI.dialog?.querySelector( + 'wizard-textfield[label="numPhases"]' + ) + ); + + secondaryAction = ( + parent.wizardUI.dialog?.querySelector( + 'mwc-button[slot="secondaryAction"]' + ) + ); + primaryAction = ( + parent.wizardUI.dialog?.querySelector( + 'mwc-button[slot="primaryAction"]' + ) + ); + }); + it('closes on secondary action', async () => { + secondaryAction.click(); + await new Promise(resolve => setTimeout(resolve, 100)); // await animation + expect(parent.wizardUI.dialog).to.not.exist; + }); + }); +}); diff --git a/test/integration/editors/substation/tapchanger-editor-wizard-editing.test.ts b/test/integration/editors/substation/tapchanger-editor-wizard-editing.test.ts index fba8dbf9d..63c3a9b2f 100644 --- a/test/integration/editors/substation/tapchanger-editor-wizard-editing.test.ts +++ b/test/integration/editors/substation/tapchanger-editor-wizard-editing.test.ts @@ -4,7 +4,7 @@ import '../../../mock-wizard-editor.js'; import { MockWizardEditor } from '../../../mock-wizard-editor.js'; import { ListItemBase } from '@material/mwc-list/mwc-list-item-base'; -import '../../../../src/editors/substation/tapchanger-editor'; +import '../../../../src/editors/substation/tapchanger-editor.js'; import { TapChangerEditor } from '../../../../src/editors/substation/tapchanger-editor.js'; import { WizardTextField } from '../../../../src/wizard-textfield.js'; import { WizardCheckbox } from '../../../../src/wizard-checkbox.js'; diff --git a/test/unit/editors/substation/__snapshots__/line-editor.test.snap.js b/test/unit/editors/substation/__snapshots__/line-editor.test.snap.js index ae6dbfe89..1dab9893c 100644 --- a/test/unit/editors/substation/__snapshots__/line-editor.test.snap.js +++ b/test/unit/editors/substation/__snapshots__/line-editor.test.snap.js @@ -6,6 +6,13 @@ snapshots["web component rendering Line element rendering LNode and Function chi label="Berlin " tabindex="0" > + + + + @@ -23,6 +30,13 @@ snapshots["web component rendering Line element rendering ConductingEquipment lo label="Berlin " tabindex="0" > + + + + @@ -40,6 +54,13 @@ snapshots["web component rendering Line element rendering GeneralEquipment looks label="Munich " tabindex="0" > + + + + From 879d2fba49bfffeb22740f2706463dc5a2b6f9e6 Mon Sep 17 00:00:00 2001 From: MarcvanRaalte Date: Tue, 14 Mar 2023 13:31:50 +0100 Subject: [PATCH 04/21] feat(line-editor_wizard_editing)_continue --- .../line-editor-wizard-editing.test.ts | 19 +++++++++++++++++++ test/testfiles/editors/substation/Line.scd | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/test/integration/editors/substation/line-editor-wizard-editing.test.ts b/test/integration/editors/substation/line-editor-wizard-editing.test.ts index 632928867..6c9f07ad2 100644 --- a/test/integration/editors/substation/line-editor-wizard-editing.test.ts +++ b/test/integration/editors/substation/line-editor-wizard-editing.test.ts @@ -79,5 +79,24 @@ describe('line-editor wizarding editing integration', () => { await new Promise(resolve => setTimeout(resolve, 100)); // await animation expect(parent.wizardUI.dialog).to.not.exist; }); + + it('does not change name attribute if not unique within parent element', async () => { + const oldName = nameField.value; + nameField.value = 'Munich'; + primaryAction.click(); + await parent.updateComplete; + expect( + doc.querySelector('Line[name="Berlin"]')?.getAttribute('name') + ).to.equal(oldName); + }); + + it('changes type attribute on primary action', async () => { + parent.wizardUI.inputs[1].value = 'newDesc'; + primaryAction.click(); + await parent.updateComplete; + expect( + doc.querySelector('Line[name="Berlin"]')?.getAttribute('desc') + ).to.equal('newDesc'); + }); }); }); diff --git a/test/testfiles/editors/substation/Line.scd b/test/testfiles/editors/substation/Line.scd index 3cbdb667b..dc1b5d346 100644 --- a/test/testfiles/editors/substation/Line.scd +++ b/test/testfiles/editors/substation/Line.scd @@ -207,7 +207,7 @@ process - + From 98702df45cc7daee7b3795668b8b5745e5bb127b Mon Sep 17 00:00:00 2001 From: MarcvanRaalte Date: Wed, 15 Mar 2023 12:47:38 +0100 Subject: [PATCH 05/21] feat(line.test,etc):editing_and_testing --- src/editors/substation/zeroline-pane.ts | 44 +++--- src/translations/de.ts | 15 ++ .../line-editor-wizard-editing.test.ts | 50 ++++--- test/testfiles/editors/substation/Line.scd | 2 +- .../wizards/__snapshots__/line.test.snap.js | 70 +++++++++ test/unit/wizards/line.test.ts | 136 ++++++++++++++++++ 6 files changed, 275 insertions(+), 42 deletions(-) create mode 100644 test/unit/wizards/__snapshots__/line.test.snap.js create mode 100644 test/unit/wizards/line.test.ts diff --git a/src/editors/substation/zeroline-pane.ts b/src/editors/substation/zeroline-pane.ts index da2d237e7..557b22d79 100644 --- a/src/editors/substation/zeroline-pane.ts +++ b/src/editors/substation/zeroline-pane.ts @@ -122,6 +122,29 @@ export class ZerolinePane extends LitElement { : html``; } + renderSubstation(): TemplateResult { + return this.doc?.querySelector(':root > Substation') + ? html`
+ ${Array.from(this.doc.querySelectorAll('Substation') ?? []) + .filter(isPublic) + .map( + substation => + html`` + )} +
` + : html`

+ ${translate('substation.missing')} +

`; + } + renderLines(): TemplateResult { return this.doc?.querySelector(':root > Line') ? html`
@@ -202,26 +225,7 @@ export class ZerolinePane extends LitElement { ${this.renderIedContainer()} - ${this.doc?.querySelector(':root > Substation') - ? html`
- ${Array.from(this.doc.querySelectorAll('Substation') ?? []) - .filter(isPublic) - .map( - substation => - html`` - )} -
` - : html`

- ${translate('substation.missing')} -

`}${this.renderLines()}`; + ${this.renderSubstation()}${this.renderLines()}`; } static styles = css` diff --git a/src/translations/de.ts b/src/translations/de.ts index 0aae589f2..68ad8b93a 100644 --- a/src/translations/de.ts +++ b/src/translations/de.ts @@ -288,6 +288,21 @@ export const de: Translations = { updateVoltagelevel: 'Spannungsebene "{{name}}" bearbeitet', }, }, + line: { + name: 'Linie', + wizard: { + nameHelper: 'Liniename', + descHelper: 'Beschreibung des Linies', + typeHelper: 'Type des Linies', + title: { + add: 'Linie hinzufügen', + edit: 'Linie bearbeiten', + }, + }, + action: { + updateLine: 'Edited line "{{name}}"', + }, + }, bay: { name: 'Feld', wizard: { diff --git a/test/integration/editors/substation/line-editor-wizard-editing.test.ts b/test/integration/editors/substation/line-editor-wizard-editing.test.ts index 6c9f07ad2..127bbf132 100644 --- a/test/integration/editors/substation/line-editor-wizard-editing.test.ts +++ b/test/integration/editors/substation/line-editor-wizard-editing.test.ts @@ -17,10 +17,7 @@ describe('line-editor wizarding editing integration', () => { describe('edit wizard', () => { let nameField: WizardTextField; - let descField: WizardTextField; - let typeField: WizardTextField; - let nomFreqField: WizardTextField; - let numPhasesField: WizardTextField; + let primaryAction: HTMLElement; let secondaryAction: HTMLElement; @@ -46,22 +43,6 @@ describe('line-editor wizarding editing integration', () => { nameField = ( parent.wizardUI.dialog?.querySelector('wizard-textfield[label="name"]') ); - descField = ( - parent.wizardUI.dialog?.querySelector('wizard-textfield[label="desc"]') - ); - typeField = ( - parent.wizardUI.dialog?.querySelector('wizard-textfield[label="type"]') - ); - nomFreqField = ( - parent.wizardUI.dialog?.querySelector( - 'wizard-textfield[label="nomFreq"]' - ) - ); - numPhasesField = ( - parent.wizardUI.dialog?.querySelector( - 'wizard-textfield[label="numPhases"]' - ) - ); secondaryAction = ( parent.wizardUI.dialog?.querySelector( @@ -90,7 +71,7 @@ describe('line-editor wizarding editing integration', () => { ).to.equal(oldName); }); - it('changes type attribute on primary action', async () => { + it('changes desc attribute on primary action', async () => { parent.wizardUI.inputs[1].value = 'newDesc'; primaryAction.click(); await parent.updateComplete; @@ -98,5 +79,32 @@ describe('line-editor wizarding editing integration', () => { doc.querySelector('Line[name="Berlin"]')?.getAttribute('desc') ).to.equal('newDesc'); }); + + it('changes type attribute on primary action', async () => { + parent.wizardUI.inputs[2].value = 'newType'; + primaryAction.click(); + await parent.updateComplete; + expect( + doc.querySelector('Line[name="Berlin"]')?.getAttribute('type') + ).to.equal('newType'); + }); + + it('changes nomFreq attribute on primary action', async () => { + parent.wizardUI.inputs[3].value = '50'; + primaryAction.click(); + await parent.updateComplete; + expect( + doc.querySelector('Line[name="Berlin"]')?.getAttribute('nomFreq') + ).to.equal('50'); + }); + + it('changes numPhases attribute on primary action', async () => { + parent.wizardUI.inputs[4].value = '3'; + primaryAction.click(); + await parent.updateComplete; + expect( + doc.querySelector('Line[name="Berlin"]')?.getAttribute('numPhases') + ).to.equal('3'); + }); }); }); diff --git a/test/testfiles/editors/substation/Line.scd b/test/testfiles/editors/substation/Line.scd index dc1b5d346..34b7904fc 100644 --- a/test/testfiles/editors/substation/Line.scd +++ b/test/testfiles/editors/substation/Line.scd @@ -207,7 +207,7 @@ process - + diff --git a/test/unit/wizards/__snapshots__/line.test.snap.js b/test/unit/wizards/__snapshots__/line.test.snap.js new file mode 100644 index 000000000..52ee88afc --- /dev/null +++ b/test/unit/wizards/__snapshots__/line.test.snap.js @@ -0,0 +1,70 @@ +/* @web/test-runner snapshot v1 */ +export const snapshots = {}; + +snapshots["Wizards for SCL Line element define an edit wizard that looks like the the latest snapshot"] = +` +
+ + + + + + + + + + +
+ + + + +
+`; +/* end snapshot Wizards for SCL Line element define an edit wizard that looks like the the latest snapshot */ + diff --git a/test/unit/wizards/line.test.ts b/test/unit/wizards/line.test.ts new file mode 100644 index 000000000..c810a7689 --- /dev/null +++ b/test/unit/wizards/line.test.ts @@ -0,0 +1,136 @@ +import { expect, fixture, html } from '@open-wc/testing'; +import '../../mock-wizard.js'; +import { MockWizard } from '../../mock-wizard.js'; + +import { WizardTextField } from '../../../src/wizard-textfield.js'; +import { SinonSpy, spy } from 'sinon'; + +import { + Create, + isCreate, + isReplace, + Replace, + WizardInputElement, +} from '../../../src/foundation.js'; +import { editLineWizard } from '../../../src/wizards/line.js'; +import { WizardCheckbox } from '../../../src/wizard-checkbox.js'; + +describe('Wizards for SCL Line element', () => { + let doc: XMLDocument; + let element: MockWizard; + let inputs: WizardInputElement[]; + + let primaryAction: HTMLElement; + let actionEvent: SinonSpy; + + beforeEach(async () => { + element = await fixture(html``); + doc = await fetch('/test/testfiles/editors/substation/Line.scd') + .then(response => response.text()) + .then(str => new DOMParser().parseFromString(str, 'application/xml')); + + actionEvent = spy(); + window.addEventListener('editor-action', actionEvent); + }); + describe('define an edit wizard that', () => { + beforeEach(async () => { + const wizard = editLineWizard(doc.querySelector('Line[name="Berlin"]')!); + 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 the latest snapshot', async () => + await expect(element.wizardUI.dialog).dom.to.equalSnapshot()); + + it('does not accept empty name attribute', async () => { + inputs[0].value = ''; + await element.requestUpdate(); + await primaryAction.click(); + expect(actionEvent).to.not.have.been.called; + }); + it('triggers simple edit action on primary action click', async () => { + inputs[0].value = 'someNonEmptyName'; + + await element.requestUpdate(); + await primaryAction.click(); + + expect(actionEvent).to.be.calledOnce; + const action = actionEvent.args[0][0].detail.action; + expect(action).to.satisfy(isReplace); + const editAction = action; + + expect(editAction.new.element).to.have.attribute( + 'name', + 'someNonEmptyName' + ); + }); + it('allows to create non required attribute desc', async () => { + inputs[1].value = 'someNonEmptyDesc'; + + await element.requestUpdate(); + await primaryAction.click(); + + expect(actionEvent).to.be.calledOnce; + const action = actionEvent.args[0][0].detail.action; + expect(action).to.satisfy(isReplace); + const editAction = action; + + expect(editAction.new.element).to.have.attribute( + 'desc', + 'someNonEmptyDesc' + ); + }); + it('allows to create non required attribute type', async () => { + inputs[2].value = 'someNonEmptyType'; + + await element.requestUpdate(); + await primaryAction.click(); + + expect(actionEvent).to.be.calledOnce; + const action = actionEvent.args[0][0].detail.action; + expect(action).to.satisfy(isReplace); + const editAction = action; + + expect(editAction.new.element).to.have.attribute( + 'type', + 'someNonEmptyType' + ); + }); + it('allows to create non required attribute nomFreq', async () => { + inputs[3].value = '50'; + + await element.requestUpdate(); + await primaryAction.click(); + + expect(actionEvent).to.be.calledOnce; + const action = actionEvent.args[0][0].detail.action; + expect(action).to.satisfy(isReplace); + const editAction = action; + + expect(editAction.new.element).to.have.attribute('nomFreq', '50'); + }); + it('allows to create non required attribute numPhases', async () => { + inputs[4].value = '3'; + + await element.requestUpdate(); + await primaryAction.click(); + + expect(actionEvent).to.be.calledOnce; + const action = actionEvent.args[0][0].detail.action; + expect(action).to.satisfy(isReplace); + const editAction = action; + + expect(editAction.new.element).to.have.attribute('numPhases', '3'); + }); + }); +}); From 1e22861afefd483c361118d4561ea209a4d6b8ef Mon Sep 17 00:00:00 2001 From: MarcvanRaalte Date: Thu, 16 Mar 2023 11:05:33 +0100 Subject: [PATCH 06/21] feat(line.ts):createLineWizard_added --- src/wizards/line.ts | 32 ++++++++++++++++++++++++++++++++ src/wizards/wizard-library.ts | 4 ++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/wizards/line.ts b/src/wizards/line.ts index 47cedd17c..5cb196466 100644 --- a/src/wizards/line.ts +++ b/src/wizards/line.ts @@ -4,6 +4,7 @@ import { get, translate } from 'lit-translate'; import '../wizard-textfield.js'; import { cloneElement, + createElement, getValue, patterns, SimpleAction, @@ -69,7 +70,19 @@ function render( >`, ]; } +function createLineAction(parent: Element): WizardActor { + return (inputs: WizardInputElement[]) => { + const lineAttrs: Record = {}; + const lineKeys = ['name', 'desc', 'type', 'nomFreq', 'numPhases']; + lineKeys.forEach(key => { + lineAttrs[key] = getValue(inputs.find(i => i.label === key)!); + }); + const line = createElement(parent.ownerDocument, 'Line', lineAttrs); + + return [{ new: { parent, element: line } }]; + }; +} function updateAction(element: Element): WizardActor { return (inputs: WizardInputElement[]): SimpleAction[] => { const lineAttrs: Record = {}; @@ -91,6 +104,25 @@ function updateAction(element: Element): WizardActor { }; } +export function createLineWizard(parent: Element): Wizard { + const name = ''; + const desc = ''; + const type = ''; + const nomFreq = ''; + const numPhases = ''; + return [ + { + title: get('wizard.title.add', { tagName: 'Line' }), + primary: { + icon: 'save', + label: get('save'), + action: createLineAction(parent), + }, + content: [...render(name, desc, type, nomFreq, numPhases)], + }, + ]; +} + export function editLineWizard(element: Element): Wizard { return [ { diff --git a/src/wizards/wizard-library.ts b/src/wizards/wizard-library.ts index 94a3b3d28..035bc22b6 100644 --- a/src/wizards/wizard-library.ts +++ b/src/wizards/wizard-library.ts @@ -49,7 +49,7 @@ import { editTransformerWindingWizard, } from './transformerWinding.js'; import { createTapChangerWizard, editTapChangerWizard } from './tapchanger.js'; -import { editLineWizard } from './line.js'; +import { createLineWizard, editLineWizard } from './line.js'; type SclElementWizard = ( element: Element, @@ -337,7 +337,7 @@ export const wizards: Record< }, Line: { edit: editLineWizard, - create: emptyWizard, + create: createLineWizard, }, Log: { edit: emptyWizard, From d45a537e40b76c1476ec904295ab92b75157f331 Mon Sep 17 00:00:00 2001 From: MarcvanRaalte Date: Thu, 16 Mar 2023 16:32:07 +0100 Subject: [PATCH 07/21] feat(zeroline-pane):create_Line_added --- src/editors/substation/zeroline-pane.ts | 60 ++++++++++++---- .../editors/substation/zeroline-pane.test.ts | 21 ------ .../__snapshots__/zeroline-pane.test.snap.js | 68 ++++++++++++++++--- 3 files changed, 106 insertions(+), 43 deletions(-) diff --git a/src/editors/substation/zeroline-pane.ts b/src/editors/substation/zeroline-pane.ts index 557b22d79..a6870a1d7 100644 --- a/src/editors/substation/zeroline-pane.ts +++ b/src/editors/substation/zeroline-pane.ts @@ -12,7 +12,9 @@ import { translate } from 'lit-translate'; import '@material/mwc-icon-button'; import '@material/mwc-icon-button-toggle'; import { IconButton } from '@material/mwc-icon-button'; +import { ListItem } from '@material/mwc-list/mwc-list-item'; import { IconButtonToggle } from '@material/mwc-icon-button-toggle'; +import { Menu } from '@material/mwc-menu'; import './line-editor.js'; import './substation-editor.js'; @@ -21,12 +23,14 @@ import { communicationMappingWizard } from '../../wizards/commmap-wizards.js'; import { gooseIcon, smvIcon, reportIcon } from '../../icons/icons.js'; import { isPublic, newWizardEvent } from '../../foundation.js'; import { selectGseControlWizard } from '../../wizards/gsecontrol.js'; -import { wizards } from '../../wizards/wizard-library.js'; +import { emptyWizard, wizards } from '../../wizards/wizard-library.js'; import { getAttachedIeds } from './foundation.js'; import { selectSampledValueControlWizard } from '../../wizards/sampledvaluecontrol.js'; import { Settings } from '../../Setting.js'; import { selectReportControlWizard } from '../../wizards/reportcontrol.js'; +import { SCLTag, tags } from '../../foundation.js'; + function shouldShowIEDs(): boolean { return localStorage.getItem('showieds') === 'on'; } @@ -43,6 +47,14 @@ function setShowFunctions(value: 'on' | 'off') { localStorage.setItem('showfunctions', value); } +function childTags(element: Element | null | undefined): SCLTag[] { + if (!element) return []; + + return tags[element.tagName].children.filter( + child => wizards[child].create !== emptyWizard + ); +} + /** [[`Zeroline`]] pane for displaying `Substation` and/or `IED` sections. */ @customElement('zeroline-pane') export class ZerolinePane extends LitElement { @@ -63,17 +75,14 @@ export class ZerolinePane extends LitElement { @query('#reportcontrol') reportcontrol!: IconButton; @query('#createsubstation') createsubstation!: IconButton; + @query('mwc-menu') addMenu!: Menu; + @query('mwc-icon-button[icon="playlist_add"]') addButton!: IconButton; + openCommunicationMapping(): void { const wizard = communicationMappingWizard(this.doc); if (wizard) this.dispatchEvent(newWizardEvent(wizard)); } - /** Opens a [[`WizardDialog`]] for creating a new `Substation` element. */ - openCreateSubstationWizard(): void { - const wizard = wizards['Substation'].create(this.doc.documentElement); - if (wizard) this.dispatchEvent(newWizardEvent(wizard)); - } - openReportControlSelection(): void { this.dispatchEvent( newWizardEvent(() => selectReportControlWizard(this.doc.documentElement)) @@ -164,16 +173,43 @@ export class ZerolinePane extends LitElement { : html``; } + private openCreateWizard(tagName: string): void { + const wizard = wizards[tagName].create(this.doc.documentElement); + + if (wizard) this.dispatchEvent(newWizardEvent(wizard)); + } + + private renderAddButtons(): TemplateResult[] { + return childTags(this.doc.documentElement).map( + child => + html`${child}` + ); + } + + updated(): void { + if (this.addMenu && this.addButton) + this.addMenu.anchor = this.addButton; + } + render(): TemplateResult { return html`