diff --git a/src/action-icon.ts b/src/action-icon.ts new file mode 100644 index 0000000000..ee8acbedb0 --- /dev/null +++ b/src/action-icon.ts @@ -0,0 +1,201 @@ +import { + css, + customElement, + html, + LitElement, + property, + TemplateResult, +} from 'lit-element'; +import { nothing } from 'lit-html'; + +import '@material/mwc-icon'; + +/** + * A responsive container rendering actions in a header. + * + * The "action" slot may contain up to eight icon buttons. + * The "icon" slot, if filled overrides the icon property. + * The default slot will be rendered into the pane body in a single column. + */ +@customElement('action-icon') +export class ActionIcon extends LitElement { + /** caption text, displayed in the header */ + @property({ type: String }) + label?: string; + /** icon name, displayed unless the "icon" slot is filled */ + @property({ type: String }) + icon?: string; + /** color header with secondary theme color while focus is within */ + @property({ type: Boolean }) + secondary = false; + /** highlight pane with dotted outline */ + @property({ type: Boolean }) + highlighted = false; + + async firstUpdated(): Promise { + this.tabIndex = 0; + } + + private renderIcon(): TemplateResult { + return html` + ${this.icon ? html`${this.icon}` : nothing} `; + } + + render(): TemplateResult { + return html`
${this.label ?? nothing}
+
${this.renderIcon()}
+ `; + } + + static styles = css` + :host { + display: flex; + flex-direction: column; + outline: none; + } + + section { + align-self: center; + } + + ::slotted([slot='icon']), + mwc-icon { + display: block; + color: var(--mdc-theme-on-surface); + transition: transform 150ms linear, box-shadow 200ms linear; + outline-color: var(--mdc-theme-primary); + outline-style: solid; + margin: 0px; + outline-width: 0px; + width: 64px; + height: 64px; + --mdc-icon-size: 64px; + } + + :host(:focus-within) ::slotted([slot='icon']), + :host(:focus-within) mwc-icon { + outline-style: solid; + outline-width: 4px; + transform: scale(0.8); + transition: all 250ms linear; + box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14), + 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.2); + } + + :host([secondary]) ::slotted([slot='icon']), + :host([secondary]) mwc-icon { + outline-color: var(--mdc-theme-secondary); + } + + :host([highlighted]) ::slotted([slot='icon']), + :host([highlighted]) mwc-icon { + outline-style: dotted; + outline-width: 2px; + } + + ::slotted([slot='icon']:hover), + mwc-icon:hover { + outline-style: dashed; + outline-width: 2px; + transition: transform 200ms linear, box-shadow 250ms linear; + } + + ::slotted([slot='action']) { + color: var(--mdc-theme-on-surface); + transition: transform 200ms cubic-bezier(0.4, 0, 0.2, 1), + opacity 200ms linear; + position: absolute; + pointer-events: none; + z-index: 1; + opacity: 0; + margin-top: -56px; + margin-left: 8px; + } + + :host(:focus-within) ::slotted([slot='action']) { + transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1), + opacity 250ms linear; + pointer-events: auto; + opacity: 1; + } + + :host(:focus-within) ::slotted([slot='action']:nth-of-type(1)) { + transform: translate(0px, -52px); + } + :host(:focus-within) ::slotted([slot='action']:nth-of-type(2)) { + transform: translate(0px, 52px); + } + :host(:focus-within) ::slotted([slot='action']:nth-of-type(3)) { + transform: translate(52px, 0px); + } + :host(:focus-within) ::slotted([slot='action']:nth-of-type(4)) { + transform: translate(-52px, 0px); + } + :host(:focus-within) ::slotted([slot='action']:nth-of-type(5)) { + transform: translate(52px, -52px); + } + :host(:focus-within) ::slotted([slot='action']:nth-of-type(6)) { + transform: translate(-52px, 52px); + } + :host(:focus-within) ::slotted([slot='action']:nth-of-type(7)) { + transform: translate(-52px, -52px); + } + :host(:focus-within) ::slotted([slot='action']:nth-of-type(8)) { + transform: translate(52px, 52px); + } + + footer { + color: var(--mdc-theme-on-surface); + font-family: 'Roboto', sans-serif; + font-weight: 300; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + margin: 0px; + text-align: center; + align-self: center; + max-width: 64px; + direction: rtl; + } + + header { + color: var(--mdc-theme-on-primary); + background-color: var(--mdc-theme-primary); + font-family: 'Roboto', sans-serif; + font-weight: 500; + font-size: 1.2em; + position: absolute; + text-align: center; + align-self: center; + max-width: 100vw; + padding: 4px 8px; + border-radius: 4px; + opacity: 0; + transition: transform 200ms cubic-bezier(0.4, 0, 0.2, 1), + opacity 200ms linear; + } + + :host(:hover) header { + position: absolute; + opacity: 1; + transform: translate(0, -40px); + box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14), + 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.2); + transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1), + opacity 250ms linear; + } + + :host(:focus-within) header { + position: absolute; + opacity: 1; + transform: translate(0, -80px); + box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14), + 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.2); + transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1), + opacity 250ms linear; + } + `; +} diff --git a/src/editors/communication/connectedap-editor.ts b/src/editors/communication/connectedap-editor.ts index 547cfc8c7a..454f1b088a 100644 --- a/src/editors/communication/connectedap-editor.ts +++ b/src/editors/communication/connectedap-editor.ts @@ -4,7 +4,6 @@ import { customElement, html, property, - css, } from 'lit-element'; import { ifDefined } from 'lit-html/directives/if-defined'; import { translate, get } from 'lit-translate'; @@ -19,6 +18,7 @@ import { Checkbox } from '@material/mwc-checkbox'; import { List } from '@material/mwc-list'; import { ListItemBase } from '@material/mwc-list/mwc-list-item-base'; +import '../../action-icon.js'; import '../../wizard-textfield.js'; import '../../filtered-list.js'; import { @@ -276,15 +276,16 @@ function editConnectedApWizard(element: Element): Wizard { /** [[`Communication`]] subeditor for a `ConnectedAP` element. */ @customElement('connectedap-editor') export class ConnectedAPEditor extends LitElement { - @property() + /** SCL element ConnectedAP */ + @property({ attribute: false }) element!: Element; - - @property() - get apName(): string | null { - return this.element.getAttribute('apName') ?? null; + /** ConductingEquipment apName attribute */ + @property({ type: String }) + get apName(): string { + return this.element.getAttribute('apName') ?? 'UNDEFINED'; } - openEditWizard(): void { + private openEditWizard(): void { this.dispatchEvent(newWizardEvent(editConnectedApWizard(this.element))); } @@ -303,116 +304,20 @@ export class ConnectedAPEditor extends LitElement { render(): TemplateResult { return html` -
- settings_input_hdmi - -
-

${this.apName}

+ > `; } - - static styles = css` - #container { - color: var(--mdc-theme-on-surface); - width: 64px; - height: 64px; - margin: auto; - position: relative; - transition: all 200ms linear; - } - - #container:focus { - outline: none; - } - - .fancy { - color: var(--mdc-theme-on-surface); - --mdc-icon-size: 64px; - transition: transform 150ms linear, box-shadow 200ms linear; - outline-color: var(--mdc-theme-primary); - outline-style: solid; - outline-width: 0px; - } - - #container:focus > .fancy { - box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14), - 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.2); - } - - #container:hover > .fancy { - outline: 2px dashed var(--mdc-theme-primary); - transition: transform 200ms linear, box-shadow 250ms linear; - } - - #container:focus-within > .fancy { - outline: 2px solid var(--mdc-theme-primary); - background: var(--mdc-theme-on-primary); - transform: scale(0.8); - transition: transform 200ms linear, box-shadow 250ms linear; - } - - .menu-item { - color: var(--mdc-theme-on-surface); - transition: transform 200ms cubic-bezier(0.4, 0, 0.2, 1), - opacity 200ms linear; - position: absolute; - top: 8px; - left: 8px; - pointer-events: none; - z-index: 1; - opacity: 0; - } - - #container:focus-within > .menu-item { - transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1), - opacity 250ms linear; - pointer-events: auto; - opacity: 1; - } - - #container:focus-within > .menu-item.up { - transform: translate(0px, -52px); - } - - #container:focus-within > .menu-item.down { - transform: translate(0px, 52px); - } - - #container:focus-within > .menu-item.right { - transform: translate(52px, 0px); - } - - #container:focus-within > .menu-item.left { - transform: translate(-52px, 0px); - } - - h4 { - color: var(--mdc-theme-on-surface); - font-family: 'Roboto', sans-serif; - font-weight: 300; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - margin: 0px; - opacity: 1; - transition: opacity 200ms linear; - text-align: center; - } - - :host(.moving) #container, - :host(.moving) h4 { - opacity: 0.3; - } - `; } diff --git a/src/zeroline/conducting-equipment-editor.ts b/src/zeroline/conducting-equipment-editor.ts index ef5184178a..ef4880a322 100644 --- a/src/zeroline/conducting-equipment-editor.ts +++ b/src/zeroline/conducting-equipment-editor.ts @@ -9,32 +9,30 @@ import { import '@material/mwc-fab'; -import { BayEditor } from './bay-editor.js'; +import '../action-icon.js'; import { startMove, getIcon } from './foundation.js'; import { newActionEvent, newWizardEvent } from '../foundation.js'; +import { BayEditor } from './bay-editor.js'; import { wizards } from '../wizards/wizard-library.js'; /** [[`SubstationEditor`]] subeditor for a `ConductingEquipment` element. */ @customElement('conducting-equipment-editor') export class ConductingEquipmentEditor extends LitElement { - @property({ type: Element }) + /** SCL element ConductingEquipment */ + @property({ attribute: false }) element!: Element; - - @property({ type: Boolean }) - readonly = false; - + /** ConductingEquipment name attribute */ @property({ type: String }) get name(): string { return this.element.getAttribute('name') ?? ''; } - openEditWizard(): void { + private openEditWizard(): void { const wizard = wizards['ConductingEquipment'].edit(this.element); if (wizard) this.dispatchEvent(newWizardEvent(wizard)); } - /** Opens a [[`WizardDialog`]] for editing `LNode` connections. */ - openLNodeWizard(): void { + private openLNodeWizard(): void { const wizard = wizards['LNode'].edit(this.element); if (wizard) this.dispatchEvent(newWizardEvent(wizard)); } @@ -53,132 +51,37 @@ export class ConductingEquipmentEditor extends LitElement { } render(): TemplateResult { - return html` -
- ${getIcon(this.element)} - ${this.readonly - ? html`` - : html` - - - `} -
-

${this.name}

- `; + return html` + ${getIcon(this.element)} + + + + + `; } static styles = css` - #container { - color: var(--mdc-theme-on-surface); - width: 64px; - height: 64px; - margin: auto; - position: relative; - transition: all 200ms linear; - } - - #container:focus { - outline: none; - } - - #container > svg { - color: var(--mdc-theme-on-surface); - width: 64px; - height: 64px; - transition: transform 150ms linear, box-shadow 200ms linear; - outline-color: var(--mdc-theme-primary); - outline-style: solid; - outline-width: 0px; - } - - #container:focus > svg { - box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14), - 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.2); - } - - #container:hover > svg { - outline: 2px dashed var(--mdc-theme-primary); - transition: transform 200ms linear, box-shadow 250ms linear; - } - - #container:focus-within > svg { - outline: 2px solid var(--mdc-theme-primary); - background: var(--mdc-theme-on-primary); - transform: scale(0.8); - transition: transform 200ms linear, box-shadow 250ms linear; - } - - .menu-item { - color: var(--mdc-theme-on-surface); - transition: transform 200ms cubic-bezier(0.4, 0, 0.2, 1), - opacity 200ms linear; - position: absolute; - top: 8px; - left: 8px; - pointer-events: none; - z-index: 1; - opacity: 0; - } - - #container:focus-within > .menu-item { - transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1), - opacity 250ms linear; - pointer-events: auto; - opacity: 1; - } - - #container:focus-within > .menu-item.up { - transform: translate(0px, -52px); - } - - #container:focus-within > .menu-item.down { - transform: translate(0px, 52px); - } - - #container:focus-within > .menu-item.right { - transform: translate(52px, 0px); - } - - #container:focus-within > .menu-item.left { - transform: translate(-52px, 0px); - } - - h4 { - color: var(--mdc-theme-on-surface); - font-family: 'Roboto', sans-serif; - font-weight: 300; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - margin: 0px; - opacity: 1; - transition: opacity 200ms linear; - text-align: center; - } - - :host(.moving) #container, - :host(.moving) h4 { + :host(.moving) { opacity: 0.3; } `; diff --git a/src/zeroline/ied-editor.ts b/src/zeroline/ied-editor.ts index 02ed1ef2bc..bdff48db51 100644 --- a/src/zeroline/ied-editor.ts +++ b/src/zeroline/ied-editor.ts @@ -1,5 +1,4 @@ import { - css, customElement, html, LitElement, @@ -12,25 +11,27 @@ import '@material/mwc-fab'; import '@material/mwc-icon'; import { Fab } from '@material/mwc-fab'; +import '../action-icon.js'; import { createClientLnWizard } from '../wizards/clientln.js'; import { gooseIcon } from '../icons.js'; import { newWizardEvent } from '../foundation.js'; import { selectGseControlWizard } from '../wizards/gsecontrol.js'; -/** [[`SubstationEditor`]] subeditor for a `ConductingEquipment` element. */ +/** [[`SubstationEditor`]] subeditor for a child-less `IED` element. */ @customElement('ied-editor') export class IedEditor extends LitElement { - @property({ type: Element }) + /** SCL element IED */ + @property({ attribute: false }) element!: Element; - + /** IED name attribute */ @property({ type: String }) get name(): string { - return this.element.getAttribute('name') ?? ''; + return this.element.getAttribute('name') ?? 'UNDEFINED'; } - @query('#connectreport') connectReport!: Fab; + @query('.connectreport') connectReport!: Fab; - openCommunicationMapping(): void { + private openCommunicationMapping(): void { const sendingIeds = Array.from( this.element.closest('SCL')?.querySelectorAll('IED') ?? [] ); @@ -38,139 +39,27 @@ export class IedEditor extends LitElement { if (wizard) this.dispatchEvent(newWizardEvent(wizard)); } - openGseControlSelection(): void { + private openGseControlSelection(): void { const wizard = selectGseControlWizard(this.element); if (wizard) this.dispatchEvent(newWizardEvent(wizard)); } render(): TemplateResult { - return html` -
- - developer_board - - ${gooseIcon} -
-

${this.name}

- `; + return html` + ${gooseIcon} `; } - - static styles = css` - #container { - color: var(--mdc-theme-on-surface); - width: 50px; - height: 50px; - margin: auto; - position: relative; - transition: all 200ms linear; - user-select: none; - } - - #container:focus { - outline: none; - } - - .icon { - color: var(--mdc-theme-on-surface); - --mdc-icon-size: 50px; - transition: transform 150ms linear, box-shadow 200ms linear; - outline-color: var(--mdc-theme-primary); - outline-style: solid; - outline-width: 0px; - } - - #container > .icon { - color: var(--mdc-theme-on-surface); - width: 50px; - height: 50px; - transition: transform 150ms linear, box-shadow 200ms linear; - outline-color: var(--mdc-theme-primary); - outline-style: solid; - outline-width: 0px; - } - - #container:focus > .icon { - box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14), - 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.2); - } - - #container:hover > .icon { - outline: 2px dashed var(--mdc-theme-primary); - transition: transform 200ms linear, box-shadow 250ms linear; - } - - #container:focus-within > .icon { - outline: 2px solid var(--mdc-theme-primary); - background: var(--mdc-theme-on-primary); - transform: scale(0.8); - transition: transform 200ms linear, box-shadow 250ms linear; - } - - .menu-item { - color: var(--mdc-theme-on-surface); - transition: transform 200ms cubic-bezier(0.4, 0, 0.2, 1), - opacity 200ms linear; - position: absolute; - top: 2px; - left: 2px; - pointer-events: none; - z-index: 1; - opacity: 0; - } - - #container:focus-within > .menu-item { - transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1), - opacity 250ms linear; - pointer-events: auto; - opacity: 1; - } - - #container:focus-within > .menu-item.up { - transform: translate(0px, -60px); - } - - #container:focus-within > .menu-item.down { - transform: translate(0px, 60px); - } - - #container:focus-within > .menu-item.right { - transform: translate(60px, 0px); - } - - #container:focus-within > .menu-item.left { - transform: translate(-60px, 0px); - } - - h4 { - color: var(--mdc-theme-on-surface); - font-family: 'Roboto', sans-serif; - font-weight: 300; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - margin: 0px; - opacity: 1; - transition: opacity 200ms linear; - text-align: center; - direction: rtl; - } - - :host(.moving) #container, - :host(.moving) h4 { - opacity: 0.3; - } - `; } diff --git a/test/unit/__snapshots__/action-icon.test.snap.js b/test/unit/__snapshots__/action-icon.test.snap.js new file mode 100644 index 0000000000..3ece266a8d --- /dev/null +++ b/test/unit/__snapshots__/action-icon.test.snap.js @@ -0,0 +1,38 @@ +/* @web/test-runner snapshot v1 */ +export const snapshots = {}; + +snapshots["Basic component action-icon with icon property set looks like the latest snapshot"] = +`
+
+
+ + + + + + +
+ +`; +/* end snapshot Basic component action-icon with icon property set looks like the latest snapshot */ + +snapshots["Basic component action-icon with unset icon property looks like the latest snapshot"] = +`
+
+
+ + + + edit + + + + + +
+ +`; +/* end snapshot Basic component action-icon with unset icon property looks like the latest snapshot */ + diff --git a/test/unit/action-icon.test.ts b/test/unit/action-icon.test.ts new file mode 100644 index 0000000000..0467821121 --- /dev/null +++ b/test/unit/action-icon.test.ts @@ -0,0 +1,30 @@ +import { expect, fixture, html } from '@open-wc/testing'; + +import '../../src/action-icon.js'; +import { ActionIcon } from '../../src/action-icon.js'; + +describe('Basic component action-icon', () => { + let element: ActionIcon; + + beforeEach(async () => { + element = await fixture( + html`` + ); + await element.updateComplete; + }); + describe('with icon property set', () => { + it('looks like the latest snapshot', async () => { + await expect(element).shadowDom.to.equalSnapshot(); + }); + }); + + describe('with unset icon property ', () => { + beforeEach(async () => { + element.icon = 'edit'; + await element.requestUpdate(); + }); + it('looks like the latest snapshot', async () => { + await expect(element).shadowDom.to.equalSnapshot(); + }); + }); +}); diff --git a/test/unit/editors/communication/__snapshots__/conductingap-editor.test.snap.js b/test/unit/editors/communication/__snapshots__/conductingap-editor.test.snap.js index 94ee7a36db..532ea49c4c 100644 --- a/test/unit/editors/communication/__snapshots__/conductingap-editor.test.snap.js +++ b/test/unit/editors/communication/__snapshots__/conductingap-editor.test.snap.js @@ -1,30 +1,25 @@ /* @web/test-runner snapshot v1 */ export const snapshots = {}; -snapshots["connectedap-editor looks like the latest snapshot"] = -`
- - settings_input_hdmi - -
-

- P1 -

+ `; -/* end snapshot connectedap-editor looks like the latest snapshot */ +/* end snapshot A component to visualize SCL element ConnectedAP looks like the latest snapshot */ diff --git a/test/unit/editors/communication/conductingap-editor.test.ts b/test/unit/editors/communication/conductingap-editor.test.ts index c9fb986243..1099c301bc 100644 --- a/test/unit/editors/communication/conductingap-editor.test.ts +++ b/test/unit/editors/communication/conductingap-editor.test.ts @@ -1,12 +1,17 @@ import { fixture, html, expect } from '@open-wc/testing'; +import { SinonSpy, spy } from 'sinon'; import '../../../../src/editors/communication/connectedap-editor.js'; import { ConnectedAPEditor } from '../../../../src/editors/communication/connectedap-editor.js'; +import { isDelete } from '../../../../src/foundation.js'; -describe('connectedap-editor', () => { +describe('A component to visualize SCL element ConnectedAP', () => { let element: ConnectedAPEditor; let validSCL: XMLDocument; + let wizardEvent: SinonSpy; + let actionEvent: SinonSpy; + beforeEach(async () => { validSCL = await fetch('/test/testfiles/valid2007B4.scd') .then(response => response.text()) @@ -20,17 +25,45 @@ describe('connectedap-editor', () => { >` ) ); - }); - it('has a apName property', () => - expect(element).to.have.property( - 'apName', - validSCL - .querySelector('SubNetwork[name="StationBus"] > ConnectedAP') - ?.getAttribute('apName') - )); + wizardEvent = spy(); + window.addEventListener('wizard', wizardEvent); + actionEvent = spy(); + window.addEventListener('editor-action', actionEvent); + }); it('looks like the latest snapshot', async () => { await expect(element).shadowDom.to.equalSnapshot(); }); + + it('renders label UNDEFINED in case ConnectedAP apName attribute is missing', async () => { + const connAp = validSCL.querySelector('ConnectedAP'); + connAp?.removeAttribute('apName'); + await element.requestUpdate(); + + expect(element).to.have.property('apName', 'UNDEFINED'); + }); + + it('triggers edit wizard for ConnectedAP element on action button click', async () => { + (( + element.shadowRoot?.querySelector('mwc-fab[icon="edit"]') + )).click(); + + await element.requestUpdate(); + + expect(wizardEvent).to.have.be.calledOnce; + expect(wizardEvent.args[0][0].detail.wizard[0].title).to.contain('edit'); + }); + + it('triggers remove action on action button click', async () => { + (( + element.shadowRoot?.querySelector('mwc-fab[icon="delete"]') + )).click(); + + await element.requestUpdate(); + + expect(wizardEvent).to.not.have.been.called; + expect(actionEvent).to.have.been.calledOnce; + expect(actionEvent.args[0][0].detail.action).to.satisfy(isDelete); + }); }); diff --git a/test/unit/zeroline/ConductingEquipmentEditor.test.ts b/test/unit/zeroline/ConductingEquipmentEditor.test.ts deleted file mode 100644 index 943e6036d5..0000000000 --- a/test/unit/zeroline/ConductingEquipmentEditor.test.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { fixture, html, expect } from '@open-wc/testing'; - -import '../../../src/wizard-textfield.js'; -import { WizardInput, isCreate, isUpdate } from '../../../src/foundation.js'; -import { createAction } from '../../../src/wizards/conductingequipment.js'; -import { updateNamingAction } from '../../../src/wizards/foundation/actions.js'; - -describe('ConductingEquipmentEditor', () => { - const noOp = () => { - return; - }; - const newWizard = (done = noOp) => { - const element = document.createElement('mwc-dialog'); - element.close = done; - return element; - }; - - let inputs: WizardInput[]; - beforeEach(async () => { - inputs = await Promise.all( - ['name', 'desc', 'type'].map( - label => - >( - fixture(html``) - ) - ) - ); - inputs[2] = await fixture( - html`"Circuit Breaker"` - ); - }); - - describe('createAction', () => { - let parent: Element; - beforeEach(() => { - parent = new DOMParser().parseFromString( - '', - 'application/xml' - ).documentElement; - }); - - it('returns a WizardAction which returns a Create EditorAction', () => { - const wizardAction = createAction(parent); - expect(wizardAction(inputs, newWizard())[0]).to.satisfy(isCreate); - }); - }); - - describe('updateAction', () => { - let element: Element; - beforeEach(() => { - element = new DOMParser().parseFromString( - '', - 'application/xml' - ).documentElement; - }); - - it('returns a WizardAction which retruns one EditorActions', () => { - const wizardAction = updateNamingAction(element); - expect(wizardAction(inputs, newWizard()).length).to.equal(1); - }); - - it('returns a WizardAction with returned EditorAction beeing an Update', () => { - const wizardAction = updateNamingAction(element); - expect(wizardAction(inputs, newWizard())[0]).to.satisfy(isUpdate); - }); - - describe('with no change in ConductingEquipement', () => { - let element: Element; - beforeEach(() => { - element = new DOMParser().parseFromString( - '', - 'application/xml' - ).documentElement; - }); - - it('returns a WizardAction with an empty EditorActions array', () => { - const wizardAction = updateNamingAction(element); - expect(wizardAction(inputs, newWizard()).length).to.equal(0); - }); - }); - }); -}); diff --git a/test/unit/zeroline/__snapshots__/conducting-equipment-editor.test.snap.js b/test/unit/zeroline/__snapshots__/conducting-equipment-editor.test.snap.js index af78840396..b6c01f6cbd 100644 --- a/test/unit/zeroline/__snapshots__/conducting-equipment-editor.test.snap.js +++ b/test/unit/zeroline/__snapshots__/conducting-equipment-editor.test.snap.js @@ -2,50 +2,37 @@ export const snapshots = {}; snapshots["conducting-equipment-editor looks like the latest snapshot"] = -`
+ + -
-

- QA1 -

+ `; /* end snapshot conducting-equipment-editor looks like the latest snapshot */ -snapshots["conducting-equipment-editor with readonly property looks like the latest snapshot"] = -`
-
-

- QA1 -

-`; -/* end snapshot conducting-equipment-editor with readonly property looks like the latest snapshot */ - diff --git a/test/unit/zeroline/__snapshots__/ied-editor.test.snap.js b/test/unit/zeroline/__snapshots__/ied-editor.test.snap.js new file mode 100644 index 0000000000..16b24bc053 --- /dev/null +++ b/test/unit/zeroline/__snapshots__/ied-editor.test.snap.js @@ -0,0 +1,28 @@ +/* @web/test-runner snapshot v1 */ +export const snapshots = {}; + +snapshots["A component to visualize SCL element IED looks like the latest snapshot"] = +` + + + + + + + +`; +/* end snapshot A component to visualize SCL element IED looks like the latest snapshot */ + diff --git a/test/unit/zeroline/conducting-equipment-editor.test.ts b/test/unit/zeroline/conducting-equipment-editor.test.ts index 3067981bb1..e6603b74a3 100644 --- a/test/unit/zeroline/conducting-equipment-editor.test.ts +++ b/test/unit/zeroline/conducting-equipment-editor.test.ts @@ -1,16 +1,22 @@ import { fixture, html, expect } from '@open-wc/testing'; +import { SinonSpy, spy } from 'sinon'; import '../../../src/zeroline/conducting-equipment-editor.js'; import { ConductingEquipmentEditor } from '../../../src/zeroline/conducting-equipment-editor.js'; +import { isDelete } from '../../../src/foundation.js'; describe('conducting-equipment-editor', () => { let element: ConductingEquipmentEditor; let validSCL: XMLDocument; + let wizardEvent: SinonSpy; + let actionEvent: SinonSpy; + beforeEach(async () => { validSCL = await fetch('/test/testfiles/valid2007B4.scd') .then(response => response.text()) .then(str => new DOMParser().parseFromString(str, 'application/xml')); + element = ( await fixture( html` { >` ) ); + + wizardEvent = spy(); + window.addEventListener('wizard', wizardEvent); + actionEvent = spy(); + window.addEventListener('editor-action', actionEvent); }); it('looks like the latest snapshot', async () => { await expect(element).shadowDom.to.equalSnapshot(); }); - describe('with readonly property', () => { - beforeEach(async () => { - element.readonly = true; - await element.requestUpdate(); - }); - it('looks like the latest snapshot', async () => { - await expect(element).shadowDom.to.equalSnapshot(); - }); + it('renders empty string in case ConductingEquipment name attribute is missing', async () => { + const condEq = validSCL.querySelector('ConductingEquipment'); + condEq?.removeAttribute('name'); + await element.requestUpdate(); + + expect(element).to.have.property('name', ''); + }); + + it('triggers edit wizard for LNode element on action button click', async () => { + (( + element.shadowRoot?.querySelector('mwc-fab[icon="account_tree"]') + )).click(); + + await element.requestUpdate(); + + expect(wizardEvent).to.have.be.calledOnce; + expect(wizardEvent.args[0][0].detail.wizard[0].title).to.contain('lnode'); + }); + + it('triggers edit wizard for ConductingEquipment element on action button click', async () => { + (( + element.shadowRoot?.querySelector('mwc-fab[icon="edit"]') + )).click(); + + await element.requestUpdate(); + + expect(wizardEvent).to.have.be.calledOnce; + expect(wizardEvent.args[0][0].detail.wizard[0].title).to.contain('edit'); + }); + + it('triggers remove action on action button click', async () => { + (( + element.shadowRoot?.querySelector('mwc-fab[icon="delete"]') + )).click(); + + await element.requestUpdate(); + + expect(wizardEvent).to.not.have.been.called; + expect(actionEvent).to.have.been.calledOnce; + expect(actionEvent.args[0][0].detail.action).to.satisfy(isDelete); }); }); diff --git a/test/unit/zeroline/ied-editor.test.ts b/test/unit/zeroline/ied-editor.test.ts new file mode 100644 index 0000000000..34aca2e04b --- /dev/null +++ b/test/unit/zeroline/ied-editor.test.ts @@ -0,0 +1,82 @@ +import { fixture, html, expect } from '@open-wc/testing'; +import { SinonSpy, spy } from 'sinon'; + +import '../../../src/zeroline/ied-editor.js'; +import { IedEditor } from '../../../src/zeroline/ied-editor.js'; + +describe('A component to visualize SCL element IED', () => { + let element: IedEditor; + let validSCL: XMLDocument; + + let wizardEvent: SinonSpy; + + beforeEach(async () => { + validSCL = await fetch('/test/testfiles/valid2007B4.scd') + .then(response => response.text()) + .then(str => new DOMParser().parseFromString(str, 'application/xml')); + + element = ( + await fixture( + html`` + ) + ); + + wizardEvent = spy(); + window.addEventListener('wizard', wizardEvent); + }); + + it('looks like the latest snapshot', async () => { + await expect(element).shadowDom.to.equalSnapshot(); + }); + + it('renders label UNDEFINED in case IED name attribute is missing', async () => { + const condEq = validSCL.querySelector('IED'); + condEq?.removeAttribute('name'); + await element.requestUpdate(); + + expect(element).to.have.property('name', 'UNDEFINED'); + }); + + it('triggers select wizard for GSEControl element on action button click', async () => { + (( + element.shadowRoot?.querySelector('mwc-fab[class="selectgse"]') + )).click(); + + await element.requestUpdate(); + + expect(wizardEvent).to.have.be.calledOnce; + expect(wizardEvent.args[0][0].detail.wizard[0].title).to.contain('select'); + }); + + it('triggers create wizard for ClientLN element on action button click', async () => { + (( + element.shadowRoot?.querySelector('mwc-fab[class="connectreport"]') + )).click(); + + await element.requestUpdate(); + + expect(wizardEvent).to.have.be.calledOnce; + expect(wizardEvent.args[0][0].detail.wizard[0].title).to.contain( + 'connectToIED' + ); + }); + + it('still triggers create wizard for ClientLN element with missing parent', async () => { + const copyElement: Element = element.cloneNode(true); + element.element = copyElement; + await element.requestUpdate(); + + (( + element.shadowRoot?.querySelector('mwc-fab[class="connectreport"]') + )).click(); + + await element.requestUpdate(); + + expect(wizardEvent).to.have.been.calledOnce; + expect(wizardEvent.args[0][0].detail.wizard[0].title).to.contain( + 'connectToIED' + ); + }); +});