diff --git a/public/js/plugins.js b/public/js/plugins.js index c342d8e314..a1e4e11c67 100644 --- a/public/js/plugins.js +++ b/public/js/plugins.js @@ -20,6 +20,13 @@ export const officialPlugins = [ default: false, kind: 'editor', }, + { + name: 'Subscription', + src: '/src/editors/Subscription.js', + icon: 'link', + default: true, + kind: 'editor', + }, { name: 'Sampled Values Subscriber', src: '/src/editors/SampledValues.js', diff --git a/src/editors/SampledValues.ts b/src/editors/SampledValues.ts index e71f101013..54edfeca4d 100644 --- a/src/editors/SampledValues.ts +++ b/src/editors/SampledValues.ts @@ -2,7 +2,7 @@ import { LitElement, html, TemplateResult, property, css } from 'lit-element'; import '@material/mwc-fab'; -import './sampledvalues/subscriber-ied-list.js'; +import './sampledvalues/subscriber-ied-list-smv.js'; import './sampledvalues/sampled-values-list.js'; /** An editor [[`plugin`]] for subscribing IEDs to Sampled Values. */ @@ -18,7 +18,7 @@ export default class SampledValuesPlugin extends LitElement {
- +
`; } diff --git a/src/editors/Subscription.ts b/src/editors/Subscription.ts new file mode 100644 index 0000000000..4c2b7baef8 --- /dev/null +++ b/src/editors/Subscription.ts @@ -0,0 +1,49 @@ +import { LitElement, html, TemplateResult, property, css } from 'lit-element'; + +import '@material/mwc-fab'; + +import './subscription/subscriber-ied-list-goose.js'; +import './subscription/publisher-goose-list.js'; + +/** An editor [[`plugin`]] for subscribing IEDs to GOOSE messages using the ABB subscription method. */ +export default class SubscriptionABBPlugin extends LitElement { + /** The document being edited as provided to plugins by [[`OpenSCD`]]. */ + @property() + doc!: XMLDocument; + + render(): TemplateResult { + return html` +
+
+ +
+
+ +
+
`; + } + + static styles = css` + :host { + width: 100vw; + } + + section { + width: 49vw; + } + + #containerTemplates { + display: grid; + grid-gap: 12px; + padding: 8px 12px 16px; + box-sizing: border-box; + grid-template-columns: repeat(auto-fit, minmax(316px, auto)); + } + + @media (max-width: 387px) { + #containerTemplates { + grid-template-columns: repeat(auto-fit, minmax(196px, auto)); + } + } + `; +} diff --git a/src/editors/sampledvalues/elements/ied-element.ts b/src/editors/sampledvalues/elements/ied-element-smv.ts similarity index 92% rename from src/editors/sampledvalues/elements/ied-element.ts rename to src/editors/sampledvalues/elements/ied-element-smv.ts index a940bdc799..c282443ae3 100644 --- a/src/editors/sampledvalues/elements/ied-element.ts +++ b/src/editors/sampledvalues/elements/ied-element-smv.ts @@ -11,8 +11,8 @@ import '@material/mwc-list/mwc-list-item'; import { newIEDSampledValuesSubscriptionEvent, SubscribeStatus } from '../foundation.js'; -@customElement('ied-element') -export class IEDElement extends LitElement { +@customElement('ied-element-smv') +export class IEDElementSmv extends LitElement { /** Holding the IED element */ @property({ attribute: false }) element!: Element; diff --git a/src/editors/sampledvalues/subscriber-ied-list.ts b/src/editors/sampledvalues/subscriber-ied-list-smv.ts similarity index 96% rename from src/editors/sampledvalues/subscriber-ied-list.ts rename to src/editors/sampledvalues/subscriber-ied-list-smv.ts index 5ed0f258d2..159b4cb1a2 100644 --- a/src/editors/sampledvalues/subscriber-ied-list.ts +++ b/src/editors/sampledvalues/subscriber-ied-list-smv.ts @@ -13,7 +13,7 @@ import '@material/mwc-icon'; import '@material/mwc-list'; import '@material/mwc-list/mwc-list-item'; -import './elements/ied-element.js'; +import './elements/ied-element-smv.js'; import { Create, createElement, @@ -97,8 +97,8 @@ const localState: State = { }; /** An sub element for subscribing and unsubscribing IEDs to Sampled Values messages. */ -@customElement('subscriber-ied-list') -export class SubscriberIEDList extends LitElement { +@customElement('subscriber-ied-list-smv') +export class SubscriberIEDListSmv extends LitElement { @property({ attribute: false }) doc!: XMLDocument; @@ -128,6 +128,7 @@ export class SubscriberIEDList extends LitElement { * @param event - Incoming event. */ private async onSampledValuesDataSetEvent(event: SampledValuesSelectEvent) { + console.log('onSMVSelect') localState.currentSampledValuesControl = event.detail.sampledValuesControl; localState.currentDataset = event.detail.dataset; localState.currentSampledValuesIEDName = localState.currentSampledValuesControl @@ -195,6 +196,7 @@ export class SubscriberIEDList extends LitElement { * @param event - Incoming event. */ private async onIEDSubscriptionEvent(event: IEDSampledValuesSubscriptionEvent) { + console.log('onSMVIEDSub') switch (event.detail.subscribeStatus) { case SubscribeStatus.Full: { this.unsubscribe(event.detail.ied); @@ -377,10 +379,10 @@ export class SubscriberIEDList extends LitElement { ${localState.subscribedIeds.length > 0 ? localState.subscribedIeds.map( ied => - html`` + >` ) : html` ${translate('sampledvalues.none')} @@ -398,10 +400,10 @@ export class SubscriberIEDList extends LitElement { ${partialSubscribedIeds.length > 0 ? partialSubscribedIeds.map( ied => - html`` + >` ) : html` ${translate('sampledvalues.none')} @@ -419,10 +421,10 @@ export class SubscriberIEDList extends LitElement { ${availableIeds.length > 0 ? availableIeds.map( ied => - html`` + >` ) : html` ${translate('sampledvalues.none')} diff --git a/src/editors/subscription/elements/goose-message.ts b/src/editors/subscription/elements/goose-message.ts new file mode 100644 index 0000000000..3a693a3864 --- /dev/null +++ b/src/editors/subscription/elements/goose-message.ts @@ -0,0 +1,35 @@ +import { + customElement, + html, + LitElement, + property, + TemplateResult, +} from 'lit-element'; + +import '@material/mwc-icon'; +import '@material/mwc-list/mwc-list-item'; + +import { newGOOSESelectEvent } from '../foundation.js'; +import { gooseIcon } from '../../../icons/icons.js'; + +@customElement('goose-message') +export class GOOSEMessage extends LitElement { + /** Holding the GSEControl element */ + @property({ attribute: false }) + element!: Element; + + private onGooseSelect = () => { + const ln = this.element.parentElement; + const dataset = ln?.querySelector( + `DataSet[name=${this.element.getAttribute('datSet')}]` + ); + this.dispatchEvent(newGOOSESelectEvent(this.element, dataset!)); + }; + + render(): TemplateResult { + return html` + ${this.element.getAttribute('name')} + ${gooseIcon} + `; + } +} diff --git a/src/editors/subscription/elements/ied-element-goose.ts b/src/editors/subscription/elements/ied-element-goose.ts new file mode 100644 index 0000000000..3771727c5a --- /dev/null +++ b/src/editors/subscription/elements/ied-element-goose.ts @@ -0,0 +1,43 @@ +import { + customElement, + html, + LitElement, + property, + TemplateResult, +} from 'lit-element'; + +import '@material/mwc-icon'; +import '@material/mwc-list/mwc-list-item'; + +import { newIEDSubscriptionEvent, SubscribeStatus } from '../foundation.js'; + +@customElement('ied-element-goose') +export class IEDElementGoose extends LitElement { + /** Holding the IED element */ + @property({ attribute: false }) + element!: Element; + + @property({ attribute: false }) + status!: SubscribeStatus; + + private onIedSelect = () => { + this.dispatchEvent( + newIEDSubscriptionEvent(this.element, this.status ?? SubscribeStatus.None) + ); + }; + + render(): TemplateResult { + return html` + ${this.element.getAttribute('name')} + ${this.status == SubscribeStatus.Full + ? html`clear` + : html`add`} + `; + } +} diff --git a/src/editors/subscription/foundation.ts b/src/editors/subscription/foundation.ts new file mode 100644 index 0000000000..c1445a7115 --- /dev/null +++ b/src/editors/subscription/foundation.ts @@ -0,0 +1,115 @@ +import { css } from 'lit-element'; + +/** + * Enumeration stating the Subscribe status of a IED to a GOOSE. + */ +export enum SubscribeStatus { + Full, + Partial, + None, +} + +export interface GOOSESelectDetail { + gseControl: Element; + dataset: Element; +} +export type GOOSESelectEvent = CustomEvent; +export function newGOOSESelectEvent( + gseControl: Element, + dataset: Element, + eventInitDict?: CustomEventInit +): GOOSESelectEvent { + return new CustomEvent('goose-dataset', { + bubbles: true, + composed: true, + ...eventInitDict, + detail: { gseControl, dataset, ...eventInitDict?.detail }, + }); +} + +export interface IEDSubscriptionDetail { + element: Element; + subscribeStatus: SubscribeStatus; +} +export type IEDSubscriptionEvent = CustomEvent; +export function newIEDSubscriptionEvent( + element: Element, + subscribeStatus: SubscribeStatus +): IEDSubscriptionEvent { + return new CustomEvent('ied-subscription', { + bubbles: true, + composed: true, + detail: { element, subscribeStatus }, + }); +} + +/** Common `CSS` styles used by DataTypeTemplate subeditors */ +export const styles = css` + :host(.moving) section { + opacity: 0.3; + } + + section { + background-color: var(--mdc-theme-surface); + transition: all 200ms linear; + outline-color: var(--mdc-theme-primary); + outline-style: solid; + outline-width: 0px; + opacity: 1; + } + + section:focus { + 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); + } + + section:focus-within { + outline-width: 2px; + transition: all 250ms linear; + } + + h1, + h2, + h3 { + color: var(--mdc-theme-on-surface); + font-family: 'Roboto', sans-serif; + font-weight: 300; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + margin: 0px; + line-height: 48px; + padding-left: 0.3em; + transition: background-color 150ms linear; + } + + section:focus-within > h1, + section:focus-within > h2, + section:focus-within > h3 { + color: var(--mdc-theme-surface); + background-color: var(--mdc-theme-primary); + transition: background-color 200ms linear; + } + + h1 > nav, + h2 > nav, + h3 > nav, + h1 > abbr > mwc-icon-button, + h2 > abbr > mwc-icon-button, + h3 > abbr > mwc-icon-button { + float: right; + } + + abbr[title] { + border-bottom: none !important; + cursor: inherit !important; + text-decoration: none !important; + } +`; + +declare global { + interface ElementEventMap { + ['goose-dataset']: GOOSESelectEvent; + ['ied-subscription']: IEDSubscriptionEvent; + } +} diff --git a/src/editors/subscription/publisher-goose-list.ts b/src/editors/subscription/publisher-goose-list.ts new file mode 100644 index 0000000000..146229b78e --- /dev/null +++ b/src/editors/subscription/publisher-goose-list.ts @@ -0,0 +1,81 @@ +import { + css, + customElement, + html, + LitElement, + property, + TemplateResult, +} from 'lit-element'; +import { translate } from 'lit-translate'; + +import '@material/mwc-icon'; +import '@material/mwc-list'; +import '@material/mwc-list/mwc-list-item'; + +import './elements/goose-message.js'; +import { compareNames, getNameAttribute } from '../../foundation.js'; +import { styles } from './foundation.js'; + +/** An sub element for showing all published GOOSE messages per IED. */ +@customElement('publisher-goose-list') +export class PublisherGOOSEList extends LitElement { + @property({ attribute: false }) + doc!: XMLDocument; + + private get ieds(): Element[] { + return this.doc + ? Array.from(this.doc.querySelectorAll(':root > IED')).sort((a, b) => + compareNames(a, b) + ) + : []; + } + + /** + * Get all the published GOOSE messages. + * @param ied - The IED to search through. + * @returns All the published GOOSE messages of this specific IED. + */ + private getGSEControls(ied: Element): Element[] { + return Array.from( + ied.querySelectorAll( + ':scope > AccessPoint > Server > LDevice > LN0 > GSEControl' + ) + ); + } + + render(): TemplateResult { + return html`
+

${translate('subscription.publisherGoose.title')}

+ + ${this.ieds.map(ied => + ied.querySelector('GSEControl') + ? html` + + ${getNameAttribute(ied)} + developer_board + +
  • + ${this.getGSEControls(ied).map( + control => + html`` + )} + ` + : `` + )} +
    +
    `; + } + + static styles = css` + ${styles} + + mwc-list { + height: 100vh; + overflow-y: scroll; + } + + .iedListTitle { + font-weight: bold; + } + `; +} diff --git a/src/editors/subscription/subscriber-ied-list-goose.ts b/src/editors/subscription/subscriber-ied-list-goose.ts new file mode 100644 index 0000000000..ef837f1e0e --- /dev/null +++ b/src/editors/subscription/subscriber-ied-list-goose.ts @@ -0,0 +1,466 @@ +import { + css, + customElement, + html, + LitElement, + property, + query, + TemplateResult, +} from 'lit-element'; +import { translate } from 'lit-translate'; + +import '@material/mwc-icon'; +import '@material/mwc-list'; +import '@material/mwc-list/mwc-list-item'; + +import './elements/ied-element-goose.js'; +import { + Create, + createElement, + Delete, + identity, + newActionEvent, + selector, +} from '../../foundation.js'; +import { + GOOSESelectEvent, + IEDSubscriptionEvent, + newGOOSESelectEvent, + styles, + SubscribeStatus, +} from './foundation.js'; + +/** + * An IED within this IED list has 2 properties: + * - The IED element itself. + * - A 'partial' property indicating if the GOOSE is fully initialized or partially. + */ +interface IED { + element: Element; + partial?: boolean; +} + +/** + * All available FCDA references that are used to link ExtRefs. + */ +const fcdaReferences = [ + 'ldInst', + 'lnClass', + 'lnInst', + 'prefix', + 'doName', + 'daName', +]; + +/** + * Get all the FCDA attributes containing values from a specific element. + * @param elementContainingFcdaReferences - The element to use + * @returns FCDA references + */ +function getFcdaReferences(elementContainingFcdaReferences: Element): string { + return fcdaReferences + .map(fcdaRef => + elementContainingFcdaReferences.getAttribute(fcdaRef) + ? `[${fcdaRef}="${elementContainingFcdaReferences.getAttribute(fcdaRef)}"]` + : '' + ) + .join(''); +} + +/** + * Internal persistent state, so it's not lost when + * subscribing / unsubscribing. + */ +interface State { + /** Current selected GSEControl element */ + currentGseControl: Element | undefined; + + /** The current selected dataset */ + currentDataset: Element | undefined; + + /** The name of the IED belonging to the current selected GOOSE */ + currentGooseIEDName: string | undefined | null; + + /** List holding all current subscribed IEDs. */ + subscribedIeds: IED[]; + + /** List holding all current avaialble IEDs which are not subscribed. */ + availableIeds: IED[]; +} + +const localState: State = { + currentGseControl: undefined, + currentDataset: undefined, + currentGooseIEDName: undefined, + subscribedIeds: [], + availableIeds: [], +}; + +/** An sub element for subscribing and unsubscribing IEDs to GOOSE messages. */ +@customElement('subscriber-ied-list-goose') +export class SubscriberIEDListGoose extends LitElement { + @property({ attribute: false }) + doc!: XMLDocument; + + @query('div') subscriberWrapper!: Element; + + constructor() { + super(); + this.onGOOSEDataSetEvent = this.onGOOSEDataSetEvent.bind(this); + this.onIEDSubscriptionEvent = this.onIEDSubscriptionEvent.bind(this); + + const parentDiv = this.closest('div[id="containerTemplates"]'); + if (parentDiv) { + parentDiv.addEventListener( + 'goose-dataset', + this.onGOOSEDataSetEvent + ); + parentDiv.addEventListener( + 'ied-subscription', + this.onIEDSubscriptionEvent + ); + } + } + + /** + * When a GOOSEDataSetEvent is received, check for all IEDs if + * all FCDAs are covered, or partly FCDAs are covered. + * @param event - Incoming event. + */ + private async onGOOSEDataSetEvent(event: GOOSESelectEvent) { + console.log('onGOOSESelect') + localState.currentGseControl = event.detail.gseControl; + localState.currentDataset = event.detail.dataset; + localState.currentGooseIEDName = localState.currentGseControl + .closest('IED') + ?.getAttribute('name'); + + localState.subscribedIeds = []; + localState.availableIeds = []; + + Array.from(this.doc.querySelectorAll(':root > IED')) + .filter(ied => ied.getAttribute('name') != localState.currentGooseIEDName) + .forEach(ied => { + const inputElements = ied.querySelectorAll(`LN0 > Inputs, LN > Inputs`); + + let numberOfLinkedExtRefs = 0; + + /** + * If no Inputs element is found, we can safely say it's not subscribed. + */ + if (!inputElements) { + localState.availableIeds.push({ element: ied }); + return; + } + + /** + * Count all the linked ExtRefs. + */ + localState.currentDataset!.querySelectorAll('FCDA').forEach(fcda => { + inputElements.forEach(inputs => { + if ( + inputs.querySelector( + `ExtRef[iedName=${localState.currentGooseIEDName}]` + + `${getFcdaReferences(fcda)}` + ) + ) { + numberOfLinkedExtRefs++; + } + }); + }); + + /** + * Make a distinction between not subscribed at all, + * partially subscribed and fully subscribed. + */ + if (numberOfLinkedExtRefs == 0) { + localState.availableIeds.push({ element: ied }); + return; + } + + if ( + numberOfLinkedExtRefs >= + localState.currentDataset!.querySelectorAll('FCDA').length + ) { + localState.subscribedIeds.push({ element: ied }); + } else { + localState.availableIeds.push({ element: ied, partial: true }); + } + }); + + this.requestUpdate(); + } + + /** + * When a IEDSubscriptionEvent is received, check if + * @param event - Incoming event. + */ + private async onIEDSubscriptionEvent(event: IEDSubscriptionEvent) { + console.log('onGOOSEIEDSub') + switch (event.detail.subscribeStatus) { + case SubscribeStatus.Full: { + this.unsubscribe(event.detail.element); + break; + } + case SubscribeStatus.Partial: { + this.subscribe(event.detail.element); + break; + } + case SubscribeStatus.None: { + this.subscribe(event.detail.element); + break; + } + } + } + + /** + * Full subscribe a given IED to the current dataset. + * @param ied - Given IED to subscribe. + */ + private async subscribe(ied: Element): Promise { + if (!ied.querySelector('LN0')) return; + + let inputsElement = ied.querySelector('LN0 > Inputs'); + if (!inputsElement) + inputsElement = createElement(ied.ownerDocument, 'Inputs', {}); + + const actions: Create[] = []; + localState.currentDataset!.querySelectorAll('FCDA').forEach(fcda => { + if ( + !inputsElement!.querySelector( + `ExtRef[iedName=${localState.currentGooseIEDName}]` + + `${getFcdaReferences(fcda)}` + ) + ) { + const extRef = createElement(ied.ownerDocument, 'ExtRef', { + iedName: localState.currentGooseIEDName!, + serviceType: 'GOOSE', + ldInst: fcda.getAttribute('ldInst') ?? '', + lnClass: fcda.getAttribute('lnClass') ?? '', + lnInst: fcda.getAttribute('lnInst') ?? '', + prefix: fcda.getAttribute('prefix') ?? '', + doName: fcda.getAttribute('doName') ?? '', + daName: fcda.getAttribute('daName') ?? '', + }); + + if (inputsElement?.parentElement) + actions.push({ new: { parent: inputsElement!, element: extRef } }); + else inputsElement?.appendChild(extRef); + } + }); + + /** If the IED doesn't have a Inputs element, just append it to the first LN0 element. */ + const title = 'Connect'; + if (inputsElement.parentElement) { + this.dispatchEvent(newActionEvent({ title, actions })); + } else { + const inputAction: Create = { + new: { parent: ied.querySelector('LN0')!, element: inputsElement }, + }; + this.dispatchEvent(newActionEvent({ title, actions: [inputAction] })); + } + + this.dispatchEvent( + newGOOSESelectEvent( + localState.currentGseControl!, + localState.currentDataset! + ) + ); + } + + /** + * Unsubscribing a given IED to the current dataset. + * @param ied - Given IED to unsubscribe. + */ + private unsubscribe(ied: Element): void { + const actions: Delete[] = []; + ied.querySelectorAll('LN0 > Inputs, LN > Inputs').forEach(inputs => { + localState.currentDataset!.querySelectorAll('FCDA').forEach(fcda => { + const extRef = inputs.querySelector( + `ExtRef[iedName=${localState.currentGooseIEDName}]` + + `${getFcdaReferences(fcda)}` + ); + + if (extRef) actions.push({ old: { parent: inputs, element: extRef } }); + }); + + + }); + + this.dispatchEvent( + newActionEvent({ + title: 'Disconnect', + actions: this.extendDeleteActions(actions), + }) + ); + + this.dispatchEvent( + newGOOSESelectEvent( + localState.currentGseControl!, + localState.currentDataset! + ) + ); + } + + /** + * Creating Delete actions in case Inputs elements are empty. + * @param extRefDeleteActions - All Delete actions for ExtRefs. + * @returns Possible delete actions for empty Inputs elements. + */ + private extendDeleteActions(extRefDeleteActions: Delete[]): Delete[] { + if (!extRefDeleteActions.length) return []; + + // Initialize with the already existing ExtRef Delete actions. + const extendedDeleteActions: Delete[] = extRefDeleteActions; + const inputsMap: Record = {}; + + for (const extRefDeleteAction of extRefDeleteActions) { + const extRef = extRefDeleteAction.old.element; + const inputsElement = extRefDeleteAction.old.parent; + + const id = identity(inputsElement); + if (!inputsMap[id]) inputsMap[id] = (inputsElement.cloneNode(true)); + + const linkedExtRef = inputsMap[id].querySelector(`ExtRef[iedName=${extRef.getAttribute('iedName')}]` + + `${getFcdaReferences(extRef)}`); + + if (linkedExtRef) inputsMap[id].removeChild(linkedExtRef); + } + + // create delete action for each empty inputs + Object.entries(inputsMap).forEach(([key, value]) => { + if (value.children.length ! == 0) { + const doc = extRefDeleteActions[0].old.parent.ownerDocument!; + const inputs = doc.querySelector(selector('Inputs', key)); + + if (inputs && inputs.parentElement) { + extendedDeleteActions.push({ + old: { parent: inputs.parentElement, element: inputs }, + }); + } + } + }); + + return extendedDeleteActions; + } + + protected updated(): void { + if (this.subscriberWrapper) { + this.subscriberWrapper.scrollTo(0, 0); + } + } + + render(): TemplateResult { + const partialSubscribedIeds = localState.availableIeds.filter( + ied => ied.partial + ); + const availableIeds = localState.availableIeds.filter( + ied => !ied.partial + ); + const gseControlName = + localState.currentGseControl?.getAttribute('name') ?? undefined; + + return html` +
    +

    + ${translate('subscription.subscriberIed.title', { + selected: gseControlName + ? localState.currentGooseIEDName + ' > ' + gseControlName + : 'IED', + })} +

    + ${localState.currentGseControl + ? html`
    + + + ${translate('subscription.subscriberIed.subscribed')} + +
  • + ${localState.subscribedIeds.length > 0 + ? localState.subscribedIeds.map( + ied => + html`` + ) + : html` + ${translate('subscription.none')} + `} +
    + + + ${translate( + 'subscription.subscriberIed.partiallySubscribed' + )} + +
  • + ${partialSubscribedIeds.length > 0 + ? partialSubscribedIeds.map( + ied => + html`` + ) + : html` + ${translate('subscription.none')} + `} +
    + + + ${translate( + 'subscription.subscriberIed.availableToSubscribe' + )} + +
  • + ${availableIeds.length > 0 + ? availableIeds.map( + ied => + html`` + ) + : html` + ${translate('subscription.none')} + `} +
    +
    ` + : html` + + ${translate( + 'subscription.subscriberIed.noGooseMessageSelected' + )} + + + `} +
    + `; + } + + static styles = css` + ${styles} + + h1 { + overflow: unset; + white-space: unset; + text-overflow: unset; + } + + .subscriberWrapper { + height: 100vh; + overflow-y: scroll; + } + + .iedListTitle { + font-weight: bold; + } + `; +} diff --git a/src/translations/de.ts b/src/translations/de.ts index b05573fed6..3310787e61 100644 --- a/src/translations/de.ts +++ b/src/translations/de.ts @@ -290,6 +290,19 @@ export const de: Translations = { missing: 'DataTypeTemplates fehlen', add: 'DataTypeTemplates hinzufügen', }, + subscription: { + none: 'Keine Verbindung vorhanden', + publisherGoose: { + title: 'GOOSE-Publizierer' + }, + subscriberIed: { + title: 'Verbunden mit {{ selected }}', + subscribed: 'Verbunden', + availableToSubscribe: 'Kann verbunden werden', + partiallySubscribed: 'Teilweise verbunden', + noGooseMessageSelected: 'Keine GOOSE ausgewählt' + } + }, sampledvalues: { none: 'Keine Verbindung vorhanden', sampledValuesList: { diff --git a/src/translations/en.ts b/src/translations/en.ts index 5e6d8cab82..3182a72387 100644 --- a/src/translations/en.ts +++ b/src/translations/en.ts @@ -286,6 +286,19 @@ export const en = { missing: 'DataTypeTemplates missing', add: 'Add DataTypeTemplates', }, + subscription: { + none: 'None', + publisherGoose: { + title: 'GOOSE publisher' + }, + subscriberIed: { + title: 'Subscriber of {{ selected }}', + subscribed: 'Subscribed', + availableToSubscribe: 'Available to subscribe', + partiallySubscribed: 'Partially subscribed', + noGooseMessageSelected: 'No GOOSE message selected' + } + }, sampledvalues: { none: 'none', sampledValuesList: { diff --git a/test/integration/__snapshots__/open-scd.test.snap.js b/test/integration/__snapshots__/open-scd.test.snap.js index a576dac150..1729df09d5 100644 --- a/test/integration/__snapshots__/open-scd.test.snap.js +++ b/test/integration/__snapshots__/open-scd.test.snap.js @@ -604,6 +604,22 @@ snapshots["open-scd looks like its snapshot"] = Single Line Diagram + + + link + + Subscription + { }); it('the IED list looks like the latest snapshot', async () => { - await expect(element.shadowRoot?.querySelector('subscriber-ied-list')).shadowDom.to.equalSnapshot(); + await expect(element.shadowRoot?.querySelector('subscriber-ied-list-smv')).shadowDom.to.equalSnapshot(); }); }); @@ -39,39 +39,39 @@ describe('Sampled Values Plugin', () => { it('the list on the right will initially show the subscribed / partially subscribed / not subscribed IEDs', async () => { await element.updateComplete; - expect(element.shadowRoot?.querySelector('subscriber-ied-list')).shadowDom.to.equalSnapshot(); + expect(element.shadowRoot?.querySelector('subscriber-ied-list-smv')).shadowDom.to.equalSnapshot(); }); describe('and you subscribe a non-subscribed IED', () => { it('it looks like the latest snapshot', async () => { - const ied = element.shadowRoot?.querySelector('subscriber-ied-list') - ?.shadowRoot?.querySelectorAll('ied-element')[2].shadowRoot?.querySelector('mwc-list-item'); + const ied = element.shadowRoot?.querySelector('subscriber-ied-list-smv') + ?.shadowRoot?.querySelectorAll('ied-element-smv')[2].shadowRoot?.querySelector('mwc-list-item'); ((ied)).click(); await element.updateComplete; - expect(element.shadowRoot?.querySelector('subscriber-ied-list')).shadowDom.to.equalSnapshot(); + expect(element.shadowRoot?.querySelector('subscriber-ied-list-smv')).shadowDom.to.equalSnapshot(); }); }); describe('and you unsubscribe a subscribed IED', () => { it('it looks like the latest snapshot', async () => { - const ied = element.shadowRoot?.querySelector('subscriber-ied-list') - ?.shadowRoot?.querySelectorAll('ied-element')[0].shadowRoot?.querySelector('mwc-list-item'); + const ied = element.shadowRoot?.querySelector('subscriber-ied-list-smv') + ?.shadowRoot?.querySelectorAll('ied-element-smv')[0].shadowRoot?.querySelector('mwc-list-item'); ((ied)).click(); await element.updateComplete; - expect(element.shadowRoot?.querySelector('subscriber-ied-list')).shadowDom.to.equalSnapshot(); + expect(element.shadowRoot?.querySelector('subscriber-ied-list-smv')).shadowDom.to.equalSnapshot(); }); }); describe('and you subscribe a partially subscribed IED', () => { it('it looks like the latest snapshot', async () => { - const ied = element.shadowRoot?.querySelector('subscriber-ied-list') - ?.shadowRoot?.querySelectorAll('ied-element')[1].shadowRoot?.querySelector('mwc-list-item'); + const ied = element.shadowRoot?.querySelector('subscriber-ied-list-smv') + ?.shadowRoot?.querySelectorAll('ied-element-smv')[1].shadowRoot?.querySelector('mwc-list-item'); ((ied)).click(); await element.updateComplete; - expect(element.shadowRoot?.querySelector('subscriber-ied-list')).shadowDom.to.equalSnapshot(); + expect(element.shadowRoot?.querySelector('subscriber-ied-list-smv')).shadowDom.to.equalSnapshot(); }); }); }); diff --git a/test/integration/editors/subscription/Subscription.test.ts b/test/integration/editors/subscription/Subscription.test.ts new file mode 100644 index 0000000000..f64df1ece0 --- /dev/null +++ b/test/integration/editors/subscription/Subscription.test.ts @@ -0,0 +1,78 @@ +import { html, fixture, expect } from '@open-wc/testing'; + +import '../../../mock-wizard.js'; + +import Subscription from '../../../../src/editors/Subscription.js'; +import { Editing } from '../../../../src/Editing.js'; +import { Wizarding } from '../../../../src/Wizarding.js'; + +describe('Subscription Plugin', () => { + customElements.define('subscription-plugin', Wizarding(Editing(Subscription))); + let element: Subscription; + let doc: XMLDocument; + + beforeEach(async () => { + doc = await fetch('/test/testfiles/valid2007B4ForSubscription.scd') + .then(response => response.text()) + .then(str => new DOMParser().parseFromString(str, 'application/xml')); + + element = await fixture(html``); + }); + + describe('initially', () => { + it('the GOOSE list looks like the latest snapshot', async () => { + await expect(element.shadowRoot?.querySelector('publisher-goose-list')).shadowDom.to.equalSnapshot(); + }); + + it('the IED list looks like the latest snapshot', async () => { + await expect(element.shadowRoot?.querySelector('subscriber-ied-list-goose')).shadowDom.to.equalSnapshot(); + }); + }); + + describe('when selecting a GOOSE message', () => { + beforeEach(async () => { + const gseMsg = element.shadowRoot?.querySelector('publisher-goose-list') + ?.shadowRoot?.querySelectorAll('goose-message')[2].shadowRoot?.querySelector('mwc-list-item'); + + ((gseMsg)).click(); + }); + + it('the list on the right will initially show the subscribed / partially subscribed / not subscribed IEDs', async () => { + await element.updateComplete; + expect(element.shadowRoot?.querySelector('subscriber-ied-list-goose')).shadowDom.to.equalSnapshot(); + }); + + describe('and you subscribe a non-subscribed IED', () => { + it('it looks like the latest snapshot', async () => { + const ied = element.shadowRoot?.querySelector('subscriber-ied-list-goose') + ?.shadowRoot?.querySelectorAll('ied-element-goose')[2].shadowRoot?.querySelector('mwc-list-item'); + + ((ied)).click(); + await element.updateComplete; + expect(element.shadowRoot?.querySelector('subscriber-ied-list-goose')).shadowDom.to.equalSnapshot(); + }); + }); + + describe('and you unsubscribe a subscribed IED', () => { + it('it looks like the latest snapshot', async () => { + const ied = element.shadowRoot?.querySelector('subscriber-ied-list-goose') + ?.shadowRoot?.querySelectorAll('ied-element-goose')[0].shadowRoot?.querySelector('mwc-list-item'); + + ((ied)).click(); + await element.updateComplete; + expect(element.shadowRoot?.querySelector('subscriber-ied-list-goose')).shadowDom.to.equalSnapshot(); + }); + }); + + describe('and you subscribe a partially subscribed IED', () => { + it('it looks like the latest snapshot', async () => { + const ied = element.shadowRoot?.querySelector('subscriber-ied-list-goose') + ?.shadowRoot?.querySelectorAll('ied-element-goose')[1].shadowRoot?.querySelector('mwc-list-item'); + + ((ied)).click(); + await element.updateComplete; + expect(element.shadowRoot?.querySelector('subscriber-ied-list-goose')).shadowDom.to.equalSnapshot(); + }); + }); + }); +}); diff --git a/test/integration/editors/subscription/__snapshots__/Subscription.test.snap.js b/test/integration/editors/subscription/__snapshots__/Subscription.test.snap.js new file mode 100644 index 0000000000..9fdce293ca --- /dev/null +++ b/test/integration/editors/subscription/__snapshots__/Subscription.test.snap.js @@ -0,0 +1,388 @@ +/* @web/test-runner snapshot v1 */ +export const snapshots = {}; + +snapshots["Subscription Plugin initially the GOOSE list looks like the latest snapshot"] = +`
    +

    + [subscription.publisherGoose.title] +

    + + + + IED1 + + + developer_board + + +
  • +
  • + + + + + + + IED2 + + + developer_board + + +
  • +
  • + + + + + IED4 + + + developer_board + + +
  • +
  • + + + + +
    +
    +`; +/* end snapshot Subscription Plugin initially the GOOSE list looks like the latest snapshot */ + +snapshots["Subscription Plugin initially the IED list looks like the latest snapshot"] = +`
    +

    + [subscription.subscriberIed.title] +

    + + + + [subscription.subscriberIed.noGooseMessageSelected] + + + +
    +`; +/* end snapshot Subscription Plugin initially the IED list looks like the latest snapshot */ + +snapshots["Subscription Plugin when selecting a GOOSE message the list on the right will initially show the subscribed / partially subscribed / not subscribed IEDs"] = +`
    +

    + [subscription.subscriberIed.title] +

    +
    + + + + [subscription.subscriberIed.subscribed] + + +
  • +
  • + + +
    + + + + [subscription.subscriberIed.partiallySubscribed] + + +
  • +
  • + + +
    + + + + [subscription.subscriberIed.availableToSubscribe] + + +
  • +
  • + + +
    +
    +
    +`; +/* end snapshot Subscription Plugin when selecting a GOOSE message the list on the right will initially show the subscribed / partially subscribed / not subscribed IEDs */ + +snapshots["Subscription Plugin when selecting a GOOSE message and you subscribe a non-subscribed IED it looks like the latest snapshot"] = +`
    +

    + [subscription.subscriberIed.title] +

    +
    + + + + [subscription.subscriberIed.subscribed] + + +
  • +
  • + + + + +
    + + + + [subscription.subscriberIed.partiallySubscribed] + + +
  • +
  • + + +
    + + + + [subscription.subscriberIed.availableToSubscribe] + + +
  • +
  • + + + [subscription.none] + + +
    +
    +
    +`; +/* end snapshot Subscription Plugin when selecting a GOOSE message and you subscribe a non-subscribed IED it looks like the latest snapshot */ + +snapshots["Subscription Plugin when selecting a GOOSE message and you unsubscribe a subscribed IED it looks like the latest snapshot"] = +`
    +

    + [subscription.subscriberIed.title] +

    +
    + + + + [subscription.subscriberIed.subscribed] + + +
  • +
  • + + + [subscription.none] + + +
    + + + + [subscription.subscriberIed.partiallySubscribed] + + +
  • +
  • + + +
    + + + + [subscription.subscriberIed.availableToSubscribe] + + +
  • +
  • + + + + +
    +
    +
    +`; +/* end snapshot Subscription Plugin when selecting a GOOSE message and you unsubscribe a subscribed IED it looks like the latest snapshot */ + +snapshots["Subscription Plugin when selecting a GOOSE message and you subscribe a partially subscribed IED it looks like the latest snapshot"] = +`
    +

    + [subscription.subscriberIed.title] +

    +
    + + + + [subscription.subscriberIed.subscribed] + + +
  • +
  • + + + + +
    + + + + [subscription.subscriberIed.partiallySubscribed] + + +
  • +
  • + + + [subscription.none] + + +
    + + + + [subscription.subscriberIed.availableToSubscribe] + + +
  • +
  • + + +
    +
    +
    +`; +/* end snapshot Subscription Plugin when selecting a GOOSE message and you subscribe a partially subscribed IED it looks like the latest snapshot */ + diff --git a/test/testfiles/valid2007B4ForSubscription.scd b/test/testfiles/valid2007B4ForSubscription.scd new file mode 100644 index 0000000000..b9ea199caa --- /dev/null +++ b/test/testfiles/valid2007B4ForSubscription.scd @@ -0,0 +1,787 @@ + + +
    + TrainingIEC61850 + + + +
    + + + 110.0 + + + + + + + + + + + + + + + + + + + + + + + + + 20 + + + + + + + 100.0 + +
    +

    192.168.210.111

    +

    255.255.255.0

    +

    192.168.210.1

    +

    1,3,9999,23

    +

    23

    +

    00000001

    +

    0001

    +

    0001

    +
    + +
    +

    01-0C-CD-01-00-10

    +

    005

    +

    4

    +

    0010

    +
    +
    + +

    RJ45

    +
    +
    +
    + + +
    +

    192.168.0.112

    +

    255.255.255.0

    +

    192.168.210.1

    +

    1,3,9999,23

    +

    23

    +

    00000001

    +

    0001

    +

    0001

    +
    +
    + +
    +

    192.168.0.113

    +

    255.255.255.0

    +

    192.168.210.1

    +

    1,3,9999,23

    +

    23

    +

    00000001

    +

    0001

    +

    0001

    +
    + +
    +

    01-0C-CD-04-00-20

    +

    007

    +

    4

    +

    4002

    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IED2 + + + + + + + status-only + + + + + + + sbo-with-enhanced-security + + + + + + + status-only + + + + + + + + 1 + + + + sbo-with-enhanced-security + + + + + + + + + + status-only + + + + + + + + + + + + + + + + status-only + + + + + + + + + direct-with-normal-security + + + + + + + sbo-with-normal-security + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + status-only + + + + + + + + + + + + + + + + + + status-only + + + + + + + + + + + + + + + + + + + + + + status-only + + + + + + + status-only + + + + + + + + + + + status-only + + + + + + + direct-with-enhanced-security + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IED2 + + + + + + + status-only + + + + + + + sbo-with-enhanced-security + + + + + + + status-only + + + + + + + + 1 + + + + sbo-with-enhanced-security + + + + + + + + + + status-only + + + + + + + + + + + + + + status-only + + + + + + + + + direct-with-normal-security + + + + + + + sbo-with-normal-security + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + sbo-with-enhanced-security + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IEC 61850-8-1:2003 + + + + + + + + + + + + + + + + + + + + + + + + + status-only + direct-with-normal-security + sbo-with-normal-security + direct-with-enhanced-security + sbo-with-enhanced-security + + + on + blocked + test + test/blocked + off + + + Ok + Warning + Alarm + + + not-supported + bay-control + station-control + remote-control + automatic-bay + automatic-station + automatic-remote + maintenance + process + + +
    \ No newline at end of file diff --git a/test/unit/Plugging.test.ts b/test/unit/Plugging.test.ts index f95ff03fb8..2f0f276f5f 100644 --- a/test/unit/Plugging.test.ts +++ b/test/unit/Plugging.test.ts @@ -22,7 +22,7 @@ describe('PluggingElement', () => { }); it('stores default plugins on load', () => - expect(element).property('editors').to.have.lengthOf(4)); + expect(element).property('editors').to.have.lengthOf(5)); describe('plugin manager dialog', () => { let firstEditorPlugin: HTMLElement; @@ -49,7 +49,7 @@ describe('PluggingElement', () => { it('disables deselected plugins', async () => { firstEditorPlugin.click(); await element.updateComplete; - expect(element).property('editors').to.have.lengthOf(3); + expect(element).property('editors').to.have.lengthOf(4); }); it('enables selected plugins', async () => { @@ -57,7 +57,7 @@ describe('PluggingElement', () => { await element.updateComplete; (element.pluginList.firstElementChild).click(); await element.updateComplete; - expect(element).property('editors').to.have.lengthOf(4); + expect(element).property('editors').to.have.lengthOf(5); }); it('resets plugins to default on reset button click', async () => { @@ -65,7 +65,7 @@ describe('PluggingElement', () => { await element.updateComplete; resetAction.click(); await element.updateComplete; - expect(element).property('editors').to.have.lengthOf(4); + expect(element).property('editors').to.have.lengthOf(5); }); it('opens the custom plugin dialog on add button click', async () => { @@ -139,7 +139,7 @@ describe('PluggingElement', () => { await name.updateComplete; primaryAction.click(); await element.updateComplete; - expect(element.editors).to.have.lengthOf(5); + expect(element.editors).to.have.lengthOf(6); }); it('adds a new menu kind plugin on add button click', async () => { const lengthMenuKindPlugins = element.menuEntries.length; diff --git a/test/unit/editors/subscription/__snapshots__/goose-message.test.snap.js b/test/unit/editors/subscription/__snapshots__/goose-message.test.snap.js new file mode 100644 index 0000000000..9e2e07de5d --- /dev/null +++ b/test/unit/editors/subscription/__snapshots__/goose-message.test.snap.js @@ -0,0 +1,19 @@ +/* @web/test-runner snapshot v1 */ +export const snapshots = {}; + +snapshots["goose-message looks like the latest snapshot"] = +` + + GCB + + + + +`; +/* end snapshot goose-message looks like the latest snapshot */ + diff --git a/test/unit/editors/subscription/__snapshots__/publisher-goose-list.test.snap.js b/test/unit/editors/subscription/__snapshots__/publisher-goose-list.test.snap.js new file mode 100644 index 0000000000..7bf81ed850 --- /dev/null +++ b/test/unit/editors/subscription/__snapshots__/publisher-goose-list.test.snap.js @@ -0,0 +1,67 @@ +/* @web/test-runner snapshot v1 */ +export const snapshots = {}; + +snapshots["publisher-goose-list looks like the latest snapshot"] = +`
    +

    + [subscription.publisherGoose.title] +

    + + + + IED1 + + + developer_board + + +
  • +
  • + + + + + + + IED2 + + + developer_board + + +
  • +
  • + + +
    +
    +`; +/* end snapshot publisher-goose-list looks like the latest snapshot */ + +snapshots["publisher-goose-list looks like the latest snapshot without a doc loaded"] = +`
    +

    + [subscription.publisherGoose.title] +

    + + +
    +`; +/* end snapshot publisher-goose-list looks like the latest snapshot without a doc loaded */ + diff --git a/test/unit/editors/subscription/__snapshots__/subscriber-ied-list.test.snap.js b/test/unit/editors/subscription/__snapshots__/subscriber-ied-list.test.snap.js new file mode 100644 index 0000000000..a3105d42a7 --- /dev/null +++ b/test/unit/editors/subscription/__snapshots__/subscriber-ied-list.test.snap.js @@ -0,0 +1,23 @@ +/* @web/test-runner snapshot v1 */ +export const snapshots = {}; + +snapshots["subscriber-ied-list initially looks like the latest snapshot"] = +`
    +

    + [subscription.subscriberIed.title] +

    + + + + [subscription.subscriberIed.noGooseMessageSelected] + + + +
    +`; +/* end snapshot subscriber-ied-list initially looks like the latest snapshot */ + diff --git a/test/unit/editors/subscription/elements/__snapshots__/goose-message.test.snap.js b/test/unit/editors/subscription/elements/__snapshots__/goose-message.test.snap.js new file mode 100644 index 0000000000..9e2e07de5d --- /dev/null +++ b/test/unit/editors/subscription/elements/__snapshots__/goose-message.test.snap.js @@ -0,0 +1,19 @@ +/* @web/test-runner snapshot v1 */ +export const snapshots = {}; + +snapshots["goose-message looks like the latest snapshot"] = +` + + GCB + + + + +`; +/* end snapshot goose-message looks like the latest snapshot */ + diff --git a/test/unit/editors/subscription/elements/__snapshots__/ied-element.test.snap.js b/test/unit/editors/subscription/elements/__snapshots__/ied-element.test.snap.js new file mode 100644 index 0000000000..f61bc9b17a --- /dev/null +++ b/test/unit/editors/subscription/elements/__snapshots__/ied-element.test.snap.js @@ -0,0 +1,21 @@ +/* @web/test-runner snapshot v1 */ +export const snapshots = {}; + +snapshots["ied-element looks like the latest snapshot"] = +` + + IED2 + + + add + + +`; +/* end snapshot ied-element looks like the latest snapshot */ + diff --git a/test/unit/editors/subscription/elements/goose-message.test.ts b/test/unit/editors/subscription/elements/goose-message.test.ts new file mode 100644 index 0000000000..2e4614149c --- /dev/null +++ b/test/unit/editors/subscription/elements/goose-message.test.ts @@ -0,0 +1,41 @@ +import { html, fixture, expect } from '@open-wc/testing'; +import { spy } from 'sinon'; + +import '../../../../../src/editors/subscription/elements/goose-message.js' +import { GOOSEMessage } from '../../../../../src/editors/subscription/elements/goose-message.js'; + +describe('goose-message', () => { + let element: GOOSEMessage; + let validSCL: XMLDocument; + + let gseControl: Element; + + beforeEach(async () => { + validSCL = await fetch('/test/testfiles/valid2007B4.scd') + .then(response => response.text()) + .then(str => new DOMParser().parseFromString(str, 'application/xml')); + + gseControl = validSCL.querySelector('GSEControl[name="GCB"]')!; + + element = await fixture(html``); + }); + + it('a newGOOSESelectEvent is fired when clicking the goose message element', async () => { + const newGOOSESelectEvent = spy(); + window.addEventListener('goose-dataset', newGOOSESelectEvent); + + const listItem = (element.shadowRoot!.querySelector('mwc-list-item')); + listItem.click(); + + await element.requestUpdate(); + expect(newGOOSESelectEvent).to.have.been.called; + expect(newGOOSESelectEvent.args[0][0].detail['gseControl']).to.eql(gseControl); + expect(newGOOSESelectEvent.args[0][0].detail['dataset']).to.eql(gseControl.closest('IED')?.querySelector('DataSet[name="GooseDataSet1"]')); + }); + + it('looks like the latest snapshot', async () => { + await expect(element).shadowDom.to.equalSnapshot(); + }); +}); diff --git a/test/unit/editors/subscription/elements/ied-element.test.ts b/test/unit/editors/subscription/elements/ied-element.test.ts new file mode 100644 index 0000000000..185d857855 --- /dev/null +++ b/test/unit/editors/subscription/elements/ied-element.test.ts @@ -0,0 +1,42 @@ +import { html, fixture, expect } from '@open-wc/testing'; +import { spy } from 'sinon'; + +import '../../../../../src/editors/subscription/elements/ied-element-goose.js' +import { IEDElementGoose } from '../../../../../src/editors/subscription/elements/ied-element-goose.js'; +import { SubscribeStatus } from '../../../../../src/editors/subscription/foundation.js'; + +describe('ied-element', () => { + let element: IEDElementGoose; + let validSCL: XMLDocument; + + let iedElement: Element; + + beforeEach(async () => { + validSCL = await fetch('/test/testfiles/valid2007B4.scd') + .then(response => response.text()) + .then(str => new DOMParser().parseFromString(str, 'application/xml')); + + iedElement = validSCL.querySelector('IED[name="IED2"]')!; + + element = await fixture(html``); + }); + + it('a newGOOSESelectEvent is fired when clicking the goose message element', async () => { + const newIEDSubscriptionEvent = spy(); + window.addEventListener('ied-subscription', newIEDSubscriptionEvent); + + const listItem = (element.shadowRoot!.querySelector('mwc-list-item')); + listItem.click(); + + await element.requestUpdate(); + expect(newIEDSubscriptionEvent).to.have.been.called; + expect(newIEDSubscriptionEvent.args[0][0].detail['element']).to.eql(iedElement); + expect(newIEDSubscriptionEvent.args[0][0].detail['subscribeStatus']).to.eql(SubscribeStatus.None); + }); + + it('looks like the latest snapshot', async () => { + await expect(element).shadowDom.to.equalSnapshot(); + }); +}); diff --git a/test/unit/editors/subscription/publisher-goose-list.test.ts b/test/unit/editors/subscription/publisher-goose-list.test.ts new file mode 100644 index 0000000000..19d7a87254 --- /dev/null +++ b/test/unit/editors/subscription/publisher-goose-list.test.ts @@ -0,0 +1,29 @@ +import { html, fixture, expect } from '@open-wc/testing'; + +import '../../../../src/editors/subscription/publisher-goose-list.js' +import { PublisherGOOSEList } from '../../../../src/editors/subscription/publisher-goose-list.js'; + +describe('publisher-goose-list', () => { + let element: PublisherGOOSEList; + let validSCL: XMLDocument; + + 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``); + }); + + it('looks like the latest snapshot', async () => { + await expect(element).shadowDom.to.equalSnapshot(); + }); + + it('looks like the latest snapshot without a doc loaded', async () => { + element = await fixture(html``); + + await expect(element).shadowDom.to.equalSnapshot(); + }); +}); diff --git a/test/unit/editors/subscription/subscriber-ied-list.test.ts b/test/unit/editors/subscription/subscriber-ied-list.test.ts new file mode 100644 index 0000000000..f5b8dca64f --- /dev/null +++ b/test/unit/editors/subscription/subscriber-ied-list.test.ts @@ -0,0 +1,23 @@ +import { html, fixture, expect } from '@open-wc/testing'; + +import '../../../../src/editors/subscription/subscriber-ied-list-goose.js' +import { SubscriberIEDListGoose } from '../../../../src/editors/subscription/subscriber-ied-list-goose.js'; + +describe('subscriber-ied-list', () => { + let element: SubscriberIEDListGoose; + let validSCL: XMLDocument; + + 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``); + }); + + it('initially looks like the latest snapshot', async () => { + await expect(element).shadowDom.to.equalSnapshot(); + }); +});