Skip to content

Commit

Permalink
feat(sub-equipment-editor): edit wizard (openscd#1063)
Browse files Browse the repository at this point in the history
* Added edit wizard for sub-equipment

* Removed dist folders
Signed-off-by: Pascal Wilbrink <pascal.wilbrink@alliander.com>

Signed-off-by: Pascal Wilbrink <pascal.wilbrink@alliander.com>

* Removed dist folder
Signed-off-by: Pascal Wilbrink <pascal.wilbrink@alliander.com>

Signed-off-by: Pascal Wilbrink <pascal.wilbrink@alliander.com>

* Added integration tests

* Fixed review comments
Signed-off-by: Pascal Wilbrink <pascal.wilbrink@alliander.com>

Signed-off-by: Pascal Wilbrink <pascal.wilbrink@alliander.com>

Signed-off-by: Pascal Wilbrink <pascal.wilbrink@alliander.com>
  • Loading branch information
pascalwilbrink authored Nov 21, 2022
1 parent 2963c27 commit 4778e7d
Show file tree
Hide file tree
Showing 10 changed files with 544 additions and 2 deletions.
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
**/*.js
**/*.ts.hbs
13 changes: 12 additions & 1 deletion src/editors/substation/sub-equipment-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
property,
TemplateResult,
} from 'lit-element';
import { translate } from 'lit-translate';

import '@material/mwc-fab';
import '@material/mwc-icon';
Expand All @@ -16,7 +17,8 @@ import '../../action-icon.js';
import '../../action-pane.js';

import { styles } from './foundation.js';
import { getChildElementsByTagName } from '../../foundation.js';
import { getChildElementsByTagName, newWizardEvent } from '../../foundation.js';
import { wizards } from '../../wizards/wizard-library.js';

/** [[`SubstationEditor`]] subeditor for a child-less `SubEquipment` element. */
@customElement('sub-equipment-editor')
Expand Down Expand Up @@ -81,8 +83,17 @@ export class SubEquipmentEditor extends LitElement {
: html``;
}

private openEditWizard(): void {
const wizard = wizards['SubEquipment'].edit(this.element);
if (wizard) this.dispatchEvent(newWizardEvent(wizard));
}

render(): TemplateResult {
return html`<action-pane label="${this.label}">
<abbr slot="action" title="${translate('edit')}">
<mwc-icon-button icon="edit" @click=${() => this.openEditWizard()}>
</mwc-icon-button>
</abbr>
${this.renderLNodes()} ${this.renderEqFunctions()}
</action-pane> `;
}
Expand Down
1 change: 1 addition & 0 deletions src/translations/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export const de: Translations = {
ldInst: 'Referenziertes logisches Gerät',
prefix: 'Präfix des logischen Knotens',
lnInst: 'Instanz des logischen Knotens',
virtual: 'Virtuell',
},
settings: {
title: 'Einstellungen',
Expand Down
1 change: 1 addition & 0 deletions src/translations/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export const en = {
ldInst: 'Referenced Logical Device',
prefix: 'Prefix of the Logical Node',
lnInst: 'Instance of the Logical Node',
virtual: 'Virtual',
},
settings: {
title: 'Settings',
Expand Down
125 changes: 125 additions & 0 deletions src/wizards/subequipment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { html, TemplateResult } from 'lit-html';
import { get, translate } from 'lit-translate';

import {
cloneElement,
getChildElementsByTagName,
getValue,
SimpleAction,
Wizard,
WizardActor,
WizardInputElement,
} from '../foundation.js';

import '../wizard-textfield.js';
import '../wizard-select.js';

interface ContentOptions {
name: string | null;
desc: string | null;
phase: string | null;
virtual: string | null;
reservedNames: string[];
}

export function contentFunctionWizard(
content: ContentOptions
): TemplateResult[] {
return [
html`<wizard-textfield
label="name"
.maybeValue=${content.name}
.reservedValues=${content.reservedNames}
helper="${translate('scl.name')}"
required
validationMessage="${translate('textfield.required')}"
dialogInitialFocus
></wizard-textfield>`,
html`<wizard-textfield
label="desc"
.maybeValue=${content.desc}
nullable
helper="${translate('scl.desc')}"
></wizard-textfield>`,
html`<wizard-select
label="phase"
fixedMenuPosition
.maybeValue=${content.phase}
nullable
helper="${translate('scl.phase')}"
>
${['A', 'B', 'C', 'N', 'all', 'none', 'AB', 'BC', 'CA'].map(
value =>
html`<mwc-list-item value="${value}">
${value.charAt(0).toUpperCase() + value.slice(1)}
</mwc-list-item>`
)}
</wizard-select> `,
html`<wizard-checkbox
label="virtual"
.maybeValue=${content.virtual}
nullable
helper="${translate('scl.virtual')}"
></wizard-checkbox>`,
];
}

function updateSubEquipmentAction(element: Element): WizardActor {
return (inputs: WizardInputElement[]): SimpleAction[] => {
const subfunctionAttrs: Record<string, string | null> = {};
const subFunctionKeys = ['name', 'desc', 'phase', 'virtual'];
subFunctionKeys.forEach(key => {
subfunctionAttrs[key] = getValue(inputs.find(i => i.label === key)!);
});

if (
subFunctionKeys.some(
key => subfunctionAttrs[key] !== element.getAttribute(key)
)
) {
const newElement = cloneElement(element, subfunctionAttrs);
return [
{
old: { element },
new: { element: newElement },
},
];
}

return [];
};
}

export function editSubEquipmentWizard(element: Element): Wizard {
const name = element.getAttribute('name');
const desc = element.getAttribute('desc');
const phase = element.getAttribute('phase');
const virtual = element.getAttribute('virtual');

const reservedNames: string[] = getChildElementsByTagName(
element.parentElement!,
'SubEquipment'
)
.filter(sibling => sibling !== element)
.map(sibling => sibling.getAttribute('name')!);

return [
{
title: get('wizard.title.edit', { tagName: 'SubEquipment' }),
primary: {
icon: 'save',
label: get('save'),
action: updateSubEquipmentAction(element),
},
content: [
...contentFunctionWizard({
name,
desc,
phase,
virtual,
reservedNames,
}),
],
},
];
}
3 changes: 2 additions & 1 deletion src/wizards/wizard-library.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
editSubFunctionWizard,
} from './subfunction.js';
import { editSampledValueControlWizard } from './sampledvaluecontrol.js';
import { editSubEquipmentWizard } from './subequipment.js';

type SclElementWizard = (
element: Element,
Expand Down Expand Up @@ -482,7 +483,7 @@ export const wizards: Record<
create: emptyWizard,
},
SubEquipment: {
edit: emptyWizard,
edit: editSubEquipmentWizard,
create: emptyWizard,
},
SubFunction: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { fixture, html, expect } from '@open-wc/testing';

import '../../../mock-wizard-editor.js';
import { MockWizardEditor } from '../../../mock-wizard-editor.js';

import '../../../../src/editors/substation/sub-equipment-editor.js';
import { WizardTextField } from '../../../../src/wizard-textfield.js';
import { SubEquipmentEditor } from '../../../../src/editors/substation/sub-equipment-editor.js';
import { WizardCheckbox } from '../../../../src/wizard-checkbox.js';

describe('sub-equipment-editor wizarding editing integration', () => {
let doc: XMLDocument;
let parent: MockWizardEditor;
let element: SubEquipmentEditor | null;

beforeEach(async () => {
doc = await fetch('/test/testfiles/SubEquipment.scd')
.then(response => response.text())
.then(str => new DOMParser().parseFromString(str, 'application/xml'));
parent = <MockWizardEditor>(
await fixture(
html`<mock-wizard-editor
><sub-equipment-editor
.element=${doc.querySelector('SubEquipment[name="subque"]')}
></sub-equipment-editor
></mock-wizard-editor>`
)
);

element = parent.querySelector('sub-equipment-editor');
});

describe('open edit wizard', () => {
let nameField: WizardTextField;
let virtualField: WizardCheckbox;
let primaryAction: HTMLElement;

beforeEach(async () => {
(<HTMLElement>(
element?.shadowRoot?.querySelector('mwc-icon-button[icon="edit"]')
)).click();
await parent.updateComplete;

nameField = <WizardTextField>(
parent.wizardUI.dialog?.querySelector('wizard-textfield[label="name"]')
);

virtualField = <WizardCheckbox>(
parent.wizardUI.dialog?.querySelector(
'wizard-checkbox[label="virtual"]'
)
);

primaryAction = <HTMLElement>(
parent.wizardUI.dialog?.querySelector(
'mwc-button[slot="primaryAction"]'
)
);
});

it('does not update SubEquipment if name attribute is not unique', async () => {
nameField.value = 'addEqi';
primaryAction.click();
await parent.updateComplete;

expect(doc.querySelectorAll('SubEquipment[name="addEqi"]')).to.lengthOf(
1
);
});

it('does update SubEquipment if name attribute is unique', async () => {
nameField.value = 'addEqi2';
await parent.updateComplete;
primaryAction.click();

expect(doc.querySelector('SubEquipment[name="addEqi2"]')).to.exist;
expect(doc.querySelector('SubEquipment[name="subque"]')).to.not.exist;
});

it('does update SubEquipment when virtual is checked', async () => {
expect(virtualField.nullSwitch).to.exist;

virtualField.nullSwitch?.click();

virtualField.maybeValue = 'true';

await parent.updateComplete;
primaryAction.click();

expect(
doc
.querySelector('SubEquipment[name="subque"]')
?.hasAttribute('virtual')
);
expect(
doc
.querySelector('SubEquipment[name="subque"]')
?.getAttribute('virtual')
).to.equal('true');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ snapshots["sub-equipment-editor looks like the latest snapshot"] =
label="subque - somedesc (all)"
tabindex="0"
>
<abbr
slot="action"
title="[edit]"
>
<mwc-icon-button icon="edit">
</mwc-icon-button>
</abbr>
</action-pane>
`;
/* end snapshot sub-equipment-editor looks like the latest snapshot */
Expand All @@ -15,6 +22,13 @@ snapshots["sub-equipment-editor With children looks like the latest snapshot"] =
label="addEqi - somedesc (none)"
tabindex="0"
>
<abbr
slot="action"
title="[edit]"
>
<mwc-icon-button icon="edit">
</mwc-icon-button>
</abbr>
<div class="container lnode">
<l-node-editor>
</l-node-editor>
Expand All @@ -30,6 +44,13 @@ snapshots["sub-equipment-editor without description and state looks like the lat
label="other"
tabindex="0"
>
<abbr
slot="action"
title="[edit]"
>
<mwc-icon-button icon="edit">
</mwc-icon-button>
</abbr>
<div class="container lnode">
<l-node-editor>
</l-node-editor>
Expand Down
Loading

0 comments on commit 4778e7d

Please sign in to comment.