diff --git a/src/editors/substation/process-editor.ts b/src/editors/substation/process-editor.ts
index b6d34c94ed..b80fe4a967 100644
--- a/src/editors/substation/process-editor.ts
+++ b/src/editors/substation/process-editor.ts
@@ -8,6 +8,8 @@ import {
state,
} from 'lit-element';
+import { translate } from 'lit-translate';
+
import '@material/mwc-icon';
import '@material/mwc-icon-button';
import '@material/mwc-menu';
@@ -22,7 +24,9 @@ import './substation-editor.js';
import './process-editor.js';
import { styles } from './foundation.js';
-import { getChildElementsByTagName } from '../../foundation.js';
+import { newWizardEvent, getChildElementsByTagName } from '../../foundation.js';
+
+import { wizards } from '../../wizards/wizard-library.js';
@customElement('process-editor')
export class ProcessEditor extends LitElement {
@@ -44,6 +48,11 @@ export class ProcessEditor extends LitElement {
return `${name} ${desc ? `—${desc}` : ''}`;
}
+ private openEditWizard(): void {
+ const wizard = wizards['Process'].edit(this.element);
+ if (wizard) this.dispatchEvent(newWizardEvent(wizard));
+ }
+
private renderConductingEquipments(): TemplateResult {
const ConductingEquipments = getChildElementsByTagName(
this.element,
@@ -143,6 +152,12 @@ export class ProcessEditor extends LitElement {
render(): TemplateResult {
return html`
+
+ this.openEditWizard()}
+ >
+
${this.renderConductingEquipments()}${this.renderGeneralEquipments()}${this.renderFunctions()}${this.renderLNodes()}
${this.renderLines()} ${this.renderSubstations()}${this.renderProcesses()}
`;
diff --git a/src/wizards/process.ts b/src/wizards/process.ts
new file mode 100644
index 0000000000..8ad928891a
--- /dev/null
+++ b/src/wizards/process.ts
@@ -0,0 +1,103 @@
+import { html, TemplateResult } from 'lit-element';
+import { get, translate } from 'lit-translate';
+
+import {
+ cloneElement,
+ createElement,
+ getChildElementsByTagName,
+ getValue,
+ SimpleAction,
+ Wizard,
+ WizardActor,
+ WizardInputElement,
+} from '../foundation.js';
+
+function updateProcessAction(element: Element): WizardActor {
+ return (inputs: WizardInputElement[]): SimpleAction[] => {
+ const tapProcessAttrs: Record = {};
+ const tapProcessKeys = ['name', 'desc', 'type'];
+ tapProcessKeys.forEach(key => {
+ tapProcessAttrs[key] = getValue(inputs.find(i => i.label === key)!);
+ });
+
+ if (
+ tapProcessKeys.some(
+ key => tapProcessAttrs[key] !== element.getAttribute(key)
+ )
+ ) {
+ const newElement = cloneElement(element, tapProcessAttrs);
+ return [
+ {
+ old: { element },
+ new: { element: newElement },
+ },
+ ];
+ }
+ return [];
+ };
+}
+
+interface ContentOptions {
+ name: string | null;
+ desc: string | null;
+ type: string | null;
+ reservedNames: string[];
+}
+
+export function contentProcessWizard(
+ content: ContentOptions
+): TemplateResult[] {
+ return [
+ html``,
+ html``,
+ html``,
+ ];
+}
+
+export function editProcessWizard(element: Element): Wizard {
+ const name = element.getAttribute('name');
+ const desc = element.getAttribute('desc');
+ const type = element.getAttribute('type');
+ const reservedNames: string[] = getChildElementsByTagName(
+ element.parentElement!,
+ 'Process'
+ )
+ .filter(sibling => sibling !== element)
+ .map(sibling => sibling.getAttribute('name')!);
+ return [
+ {
+ title: get('wizard.title.edit', { tagName: 'Process' }),
+ primary: {
+ icon: 'save',
+ label: get('save'),
+ action: updateProcessAction(element),
+ },
+ content: [
+ ...contentProcessWizard({
+ name,
+ desc,
+ type,
+ reservedNames,
+ }),
+ ],
+ },
+ ];
+}
diff --git a/src/wizards/wizard-library.ts b/src/wizards/wizard-library.ts
index 035bc22b6e..8729ec7373 100644
--- a/src/wizards/wizard-library.ts
+++ b/src/wizards/wizard-library.ts
@@ -50,6 +50,7 @@ import {
} from './transformerWinding.js';
import { createTapChangerWizard, editTapChangerWizard } from './tapchanger.js';
import { createLineWizard, editLineWizard } from './line.js';
+import { editProcessWizard } from './process.js';
type SclElementWizard = (
element: Element,
@@ -388,7 +389,7 @@ export const wizards: Record<
create: emptyWizard,
},
Process: {
- edit: emptyWizard,
+ edit: editProcessWizard,
create: emptyWizard,
},
ProtNs: {
diff --git a/test/integration/editors/substation/process-editor-wizard-editing.test.ts b/test/integration/editors/substation/process-editor-wizard-editing.test.ts
new file mode 100644
index 0000000000..22d0f06076
--- /dev/null
+++ b/test/integration/editors/substation/process-editor-wizard-editing.test.ts
@@ -0,0 +1,133 @@
+import { expect, fixture, html } from '@open-wc/testing';
+
+import '../../../mock-wizard-editor.js';
+import { MockWizardEditor } from '../../../mock-wizard-editor.js';
+
+import '../../../../src/editors/substation/process-editor.js';
+import { ProcessEditor } from '../../../../src/editors/substation/process-editor.js';
+import { WizardTextField } from '../../../../src/wizard-textfield.js';
+
+describe('process-editor wizarding editing integration', () => {
+ let doc: XMLDocument;
+ let parent: MockWizardEditor;
+ let element: ProcessEditor | null;
+
+ describe('edit wizard', () => {
+ let nameField: WizardTextField;
+ let descField: WizardTextField;
+ let typeField: WizardTextField;
+
+ let primaryAction: HTMLElement;
+ let secondaryAction: HTMLElement;
+
+ beforeEach(async () => {
+ doc = await fetch('/test/testfiles/editors/substation/Process.scd')
+ .then(response => response.text())
+ .then(str => new DOMParser().parseFromString(str, 'application/xml'));
+ parent = (
+ await fixture(
+ html``
+ )
+ );
+ element = parent.querySelector('process-editor');
+ await ((
+ element?.shadowRoot?.querySelector('mwc-icon-button[icon="edit"]')
+ )).click();
+ await parent.updateComplete;
+
+ nameField = (
+ parent.wizardUI.dialog?.querySelector('wizard-textfield[label="name"]')
+ );
+
+ typeField = (
+ parent.wizardUI.dialog?.querySelector('wizard-textfield[label="type"]')
+ );
+
+ 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;
+ });
+
+ it('does not change name attribute if not unique within parent element', async () => {
+ const oldName = nameField.value;
+ nameField.value = 'ProcProcSubAA1';
+ primaryAction.click();
+ await parent.updateComplete;
+ expect(
+ doc
+ .querySelector('Process[name="ProcessGenConduct"]')
+ ?.getAttribute('name')
+ ).to.equal(oldName);
+ });
+
+ it('changes name attribute on primary action', async () => {
+ nameField.value = 'newName';
+ primaryAction.click();
+ await parent.updateComplete;
+ expect(doc.querySelector('Process')?.getAttribute('name')).to.equal(
+ 'newName'
+ );
+ });
+
+ it('changes desc attribute on primary action', async () => {
+ descField = (
+ parent.wizardUI.dialog?.querySelector('wizard-textfield[label="desc"]')
+ );
+ await new Promise(resolve => setTimeout(resolve, 100)); // await animation
+ descField.nullSwitch!.click();
+ await parent.updateComplete;
+ descField.value = 'newDesc';
+ console.log(descField.value);
+ primaryAction.click();
+ await parent.updateComplete;
+ expect(
+ doc
+ .querySelector('Process[name="ProcessGenConduct"]')
+ ?.getAttribute('desc')
+ ).to.equal('newDesc');
+ });
+
+ it('deletes desc attribute if wizard-textfield is deactivated', async () => {
+ await new Promise(resolve => setTimeout(resolve, 100)); // await animation
+ descField.nullSwitch!.click();
+ await parent.updateComplete;
+ await primaryAction.click();
+ await parent.updateComplete;
+ expect(
+ doc
+ .querySelector('Process[name="ProcessGenConduct"]')
+ ?.getAttribute('desc')
+ ).to.be.null;
+ });
+
+ it('changes type attribute on primary action', async () => {
+ await new Promise(resolve => setTimeout(resolve, 100));
+ typeField.nullSwitch!.click();
+ await parent.updateComplete;
+ typeField.value = 'newType';
+ primaryAction.click();
+ await parent.updateComplete;
+ expect(
+ doc
+ .querySelector('Process[name="ProcessGenConduct"]')
+ ?.getAttribute('type')
+ ).to.equal('newType');
+ });
+ });
+});
diff --git a/test/integration/editors/substation/zeroline-pane.test.ts b/test/integration/editors/substation/zeroline-pane.test.ts
index b85b741215..aa36444cfa 100644
--- a/test/integration/editors/substation/zeroline-pane.test.ts
+++ b/test/integration/editors/substation/zeroline-pane.test.ts
@@ -6,7 +6,6 @@ import { MockWizardEditor } from '../../../mock-wizard-editor.js';
import '../../../../src/editors/substation/zeroline-pane.js';
import { FilteredList } from '../../../../src/filtered-list.js';
import { ZerolinePane } from '../../../../src/editors/substation/zeroline-pane.js';
-
import { WizardTextField } from '../../../../src/wizard-textfield.js';
import { IconButton } from '@material/mwc-icon-button';
import { ListItem } from '@material/mwc-list/mwc-list-item';
diff --git a/test/unit/editors/substation/__snapshots__/process-editor.test.snap.js b/test/unit/editors/substation/__snapshots__/process-editor.test.snap.js
index 8b76e7c934..cf67d15110 100644
--- a/test/unit/editors/substation/__snapshots__/process-editor.test.snap.js
+++ b/test/unit/editors/substation/__snapshots__/process-editor.test.snap.js
@@ -6,6 +6,13 @@ snapshots["web component rendering Process element rendering LNode, GeneralEquip
label="ProcessGenConduct "
tabindex="0"
>
+
+
+
+
@@ -25,6 +32,13 @@ snapshots["web component rendering Process element hides LNode and Function chil
label="ProcessGenConduct "
tabindex="0"
>
+
+
+
+
@@ -38,6 +52,13 @@ snapshots["web component rendering Process element rendering Substation and Proc
label="ProcProcSubAA1 "
tabindex="0"
>
+
+
+
+
@@ -51,6 +72,13 @@ snapshots["web component rendering Process element rendering a Line child looks
label="ProcessLine "
tabindex="0"
>
+
+
+
+
diff --git a/test/unit/wizards/__snapshots__/process.test.snap.js b/test/unit/wizards/__snapshots__/process.test.snap.js
new file mode 100644
index 0000000000..b54631785d
--- /dev/null
+++ b/test/unit/wizards/__snapshots__/process.test.snap.js
@@ -0,0 +1,52 @@
+/* @web/test-runner snapshot v1 */
+export const snapshots = {};
+
+snapshots["Wizards for SCL Process element define an edit wizard that looks like the the latest snapshot"] =
+`
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+/* end snapshot Wizards for SCL Process element define an edit wizard that looks like the the latest snapshot */
+
diff --git a/test/unit/wizards/process.test.ts b/test/unit/wizards/process.test.ts
new file mode 100644
index 0000000000..93507a036c
--- /dev/null
+++ b/test/unit/wizards/process.test.ts
@@ -0,0 +1,103 @@
+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 {
+ isReplace,
+ Replace,
+ WizardInputElement,
+} from '../../../src/foundation.js';
+import { editProcessWizard } from '../../../src/wizards/process.js';
+
+describe('Wizards for SCL Process 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/Process.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 = editProcessWizard(
+ doc.querySelector('Process[name="ProcessGenConduct"]')!
+ );
+ 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('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]).nullSwitch?.click();
+ inputs[1].value = 'someDesc';
+
+ 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', 'someDesc');
+ });
+ it('allows to create non required attribute type', async () => {
+ (inputs[2]).nullSwitch?.click();
+ 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'
+ );
+ });
+ });
+});