diff --git a/src/editors/subscription/later-binding/ext-ref-later-binding-list.ts b/src/editors/subscription/later-binding/ext-ref-later-binding-list.ts index 059982fc0..81e2849e8 100644 --- a/src/editors/subscription/later-binding/ext-ref-later-binding-list.ts +++ b/src/editors/subscription/later-binding/ext-ref-later-binding-list.ts @@ -23,10 +23,14 @@ import { newSubscriptionChangedEvent, styles, updateExtRefElement, + serviceTypes, } from '../foundation.js'; + import { getExtRefElements, getSubscribedExtRefElements, + fcdaSpecification, + inputRestriction, isSubscribed, } from './foundation.js'; @@ -70,19 +74,40 @@ export class ExtRefLaterBindingList extends LitElement { } /** - * The data attribute check using attributes pLN, pDO, pDA and pServT is not supported yet by this plugin. - * To make sure the user does not do anything prohibited, this type of ExtRef cannot be manipulated for the time being. - * (Will be updated in the future). + * Check data consistency of source `FCDA` and sink `ExtRef` based on + * `ExtRef`'s `pLN`, `pDO`, `pDA` and `pServT` attributes. + * Consistent means `CDC` and `bType` of both ExtRef and FCDA is equal. + * In case + * - `pLN`, `pDO`, `pDA` or `pServT` attributes are not present, allow subscribing + * - no CDC or bType can be extracted, do not allow subscribing * - * @param extRefElement - The Ext Ref Element to check. + * @param extRef - The `ExtRef` Element to check against */ - private unsupportedExtRefElement(extRefElement: Element): boolean { - return ( - extRefElement.hasAttribute('pLN') || - extRefElement.hasAttribute('pDO') || - extRefElement.hasAttribute('pDA') || - extRefElement.hasAttribute('pServT') - ); + private unsupportedExtRefElement(extRef: Element): boolean { + // Vendor does not provide data for the check + if ( + !extRef.hasAttribute('pLN') || + !extRef.hasAttribute('pDO') || + !extRef.hasAttribute('pDA') || + !extRef.hasAttribute('pServT') + ) + return false; + + // Not ready for any kind of subscription + if (!this.currentSelectedFcdaElement) return true; + + const fcda = fcdaSpecification(this.currentSelectedFcdaElement); + const input = inputRestriction(extRef); + + if (fcda.cdc === null && input.cdc === null) return true; + if (fcda.bType === null && input.bType === null) return true; + if ( + serviceTypes[this.currentSelectedControlElement?.tagName ?? ''] !== + extRef.getAttribute('pServT') + ) + return true; + + return fcda.cdc !== input.cdc || fcda.bType !== input.bType; } /** diff --git a/src/editors/subscription/later-binding/foundation.ts b/src/editors/subscription/later-binding/foundation.ts index 17cfbe827..0b0a40897 100644 --- a/src/editors/subscription/later-binding/foundation.ts +++ b/src/editors/subscription/later-binding/foundation.ts @@ -1,7 +1,110 @@ import { getSclSchemaVersion } from '../../../foundation.js'; - import { serviceTypes } from '../foundation.js'; +function dataAttributeSpecification( + anyLn: Element, + doName: string, + daName: string +): { cdc: string | null; bType: string | null } { + const doc = anyLn.ownerDocument; + const lNodeType = doc.querySelector( + `LNodeType[id="${anyLn.getAttribute('lnType')}"]` + ); + + const doNames = doName.split('.'); + let leaf: Element | null | undefined = lNodeType; + for (const doName of doNames) { + const dO: Element | null | undefined = leaf?.querySelector( + `DO[name="${doName}"], SDO[name="${doName}"]` + ); + leaf = doc.querySelector(`DOType[id="${dO?.getAttribute('type')}"]`); + } + if (!leaf || !leaf.getAttribute('cdc')) return { cdc: null, bType: null }; + + const cdc = leaf.getAttribute('cdc')!; + + const daNames = daName.split('.'); + for (const daName of daNames) { + const dA: Element | null | undefined = leaf?.querySelector( + `DA[name="${daName}"], BDA[name="${daName}"]` + ); + leaf = + daNames.indexOf(daName) < daNames.length - 1 + ? doc.querySelector(`DAType[id="${dA?.getAttribute('type')}"]`) + : dA; + } + if (!leaf || !leaf.getAttribute('bType')) return { cdc, bType: null }; + + const bType = leaf.getAttribute('bType')!; + + return { bType, cdc }; +} + +/** + * @param fcda - Data attribute reference in a data set + * @returns Data objects `CDC` and data attributes `bType` + */ +export function fcdaSpecification(fcda: Element): { + cdc: string | null; + bType: string | null; +} { + const [doName, daName] = ['doName', 'daName'].map(attr => + fcda.getAttribute(attr) + ); + if (!doName || !daName) return { cdc: null, bType: null }; + + const ied = fcda.closest('IED'); + + const anyLn = Array.from( + ied?.querySelectorAll( + `LDevice[inst="${fcda.getAttribute( + 'ldInst' + )}"] > LN, LDevice[inst="${fcda.getAttribute('inst')}"] LN0` + ) ?? [] + ).find(anyLn => { + return ( + (anyLn.getAttribute('prefix') ?? '') === + (fcda.getAttribute('prefix') ?? '') && + (anyLn.getAttribute('lnClass') ?? '') === + (fcda.getAttribute('lnClass') ?? '') && + (anyLn.getAttribute('inst') ?? '') === (fcda.getAttribute('lnInst') ?? '') + ); + }); + if (!anyLn) return { cdc: null, bType: null }; + + return dataAttributeSpecification(anyLn, doName, daName); +} + +/** + * Edition 2 and later SCL files allow to restrict subscription on + * later binding type inputs (`ExtRef` elements) based on a `CDC` and + * basic type `bType`. + * @param extRef - A later binding type input in the sink IED + * @returns data objects `CDC` and data attribute basic type `bType` or `null` + */ +export function inputRestriction(extRef: Element): { + cdc: string | null; + bType: string | null; +} { + const [pLN, pDO, pDA] = ['pLN', 'pDO', 'pDA'].map(attr => + extRef.getAttribute(attr) + ); + if (!pLN || !pDO || !pDA) return { cdc: null, bType: null }; + + const anyLns = Array.from( + extRef + .closest('IED') + ?.querySelectorAll(`LN[lnClass="${pLN}"],LN0[lnClass="${pLN}"]`) ?? [] + ); + + for (const anyLn of anyLns) { + const dataSpec = dataAttributeSpecification(anyLn, pDO, pDA); + if (dataSpec.cdc !== null && dataSpec.bType !== null) return dataSpec; + } + + return { cdc: null, bType: null }; +} + /** * Simple function to check if the attribute of the Left Side has the same value as the attribute of the Right Element. * diff --git a/test/integration/editors/GooseSubscriberLaterBinding.test.ts b/test/integration/editors/GooseSubscriberLaterBinding.test.ts index 1222de87d..8d41a02ca 100644 --- a/test/integration/editors/GooseSubscriberLaterBinding.test.ts +++ b/test/integration/editors/GooseSubscriberLaterBinding.test.ts @@ -49,7 +49,7 @@ describe('GOOSE Subscribe Later Binding Plugin', () => { expect(getSelectedSubItemValue(fcdaListElement)).to.be.null; expect( extRefListElement['getAvailableExtRefElements']().length - ).to.be.equal(4); + ).to.be.equal(5); (( extRefListElement.shadowRoot!.querySelector( @@ -64,7 +64,7 @@ describe('GOOSE Subscribe Later Binding Plugin', () => { expect(getSelectedSubItemValue(fcdaListElement)).to.have.text('1'); expect( extRefListElement['getAvailableExtRefElements']().length - ).to.be.equal(3); + ).to.be.equal(4); }); it('when unsubscribing a subscribed ExtRef then the lists are changed', async () => { @@ -85,7 +85,7 @@ describe('GOOSE Subscribe Later Binding Plugin', () => { expect(getSelectedSubItemValue(fcdaListElement)).to.have.text('2'); expect( extRefListElement['getAvailableExtRefElements']().length - ).to.be.equal(4); + ).to.be.equal(5); (( extRefListElement.shadowRoot!.querySelector( @@ -100,6 +100,6 @@ describe('GOOSE Subscribe Later Binding Plugin', () => { expect(getSelectedSubItemValue(fcdaListElement)).to.have.text('1'); expect( extRefListElement['getAvailableExtRefElements']().length - ).to.be.equal(5); + ).to.be.equal(6); }); }); diff --git a/test/integration/editors/SMVSubscriberLaterBinding.test.ts b/test/integration/editors/SMVSubscriberLaterBinding.test.ts index eb2903be1..88a865a7d 100644 --- a/test/integration/editors/SMVSubscriberLaterBinding.test.ts +++ b/test/integration/editors/SMVSubscriberLaterBinding.test.ts @@ -50,7 +50,7 @@ describe('SMV Subscribe Later Binding plugin', () => { expect(getSelectedSubItemValue(fcdaListElement)).to.be.null; expect( extRefListElement['getAvailableExtRefElements']().length - ).to.be.equal(8); + ).to.be.equal(9); (( extRefListElement.shadowRoot!.querySelector( @@ -65,7 +65,7 @@ describe('SMV Subscribe Later Binding plugin', () => { expect(getSelectedSubItemValue(fcdaListElement)).to.have.text('1'); expect( extRefListElement['getAvailableExtRefElements']().length - ).to.be.equal(7); + ).to.be.equal(8); }); it('when unsubscribing a subscribed ExtRef then the lists are changed', async () => { @@ -86,7 +86,7 @@ describe('SMV Subscribe Later Binding plugin', () => { expect(getSelectedSubItemValue(fcdaListElement)).to.have.text('3'); expect( extRefListElement['getAvailableExtRefElements']().length - ).to.be.equal(8); + ).to.be.equal(9); (( extRefListElement.shadowRoot!.querySelector( @@ -101,6 +101,6 @@ describe('SMV Subscribe Later Binding plugin', () => { expect(getSelectedSubItemValue(fcdaListElement)).to.have.text('2'); expect( extRefListElement['getAvailableExtRefElements']().length - ).to.be.equal(9); + ).to.be.equal(10); }); }); diff --git a/test/testfiles/editors/LaterBindingGOOSE2007B4.scd b/test/testfiles/editors/LaterBindingGOOSE2007B4.scd index 270e38759..841a1a81b 100644 --- a/test/testfiles/editors/LaterBindingGOOSE2007B4.scd +++ b/test/testfiles/editors/LaterBindingGOOSE2007B4.scd @@ -80,7 +80,8 @@ - + + @@ -344,4 +345,4 @@ process - \ No newline at end of file + diff --git a/test/testfiles/editors/LaterBindingSMV2007B4.scd b/test/testfiles/editors/LaterBindingSMV2007B4.scd index abe23fa35..477c2ddac 100644 --- a/test/testfiles/editors/LaterBindingSMV2007B4.scd +++ b/test/testfiles/editors/LaterBindingSMV2007B4.scd @@ -98,10 +98,17 @@ - + + + + + + + + diff --git a/test/unit/editors/subscription/later-binding/__snapshots__/ext-ref-later-binding-list.test.snap.js b/test/unit/editors/subscription/later-binding/__snapshots__/ext-ref-later-binding-list.test.snap.js index 6d43df73d..547d048f8 100644 --- a/test/unit/editors/subscription/later-binding/__snapshots__/ext-ref-later-binding-list.test.snap.js +++ b/test/unit/editors/subscription/later-binding/__snapshots__/ext-ref-later-binding-list.test.snap.js @@ -52,7 +52,7 @@ snapshots["extref-later-binding-list for Sampled Value Control when SVC has no s aria-disabled="false" noninteractive="" tabindex="-1" - value="MeasPoint.CT2 SMV_Subscriber>>Overvoltage> PTRC 1>AmpSv;TCTR2/AmpSv/instMag.i[0] MeasPoint.CT3 SMV_Subscriber>>Overvoltage> PTRC 1>AmpSv;TCTR3/AmpSv/instMag.i[0] MeasPoint.VT1 SMV_Subscriber>>Overcurrent> PTRC 1>VolSv;TVTR1/VolSv/q[0] MeasPoint.VT2 SMV_Subscriber>>Overcurrent> PTRC 1>VolSv;TVTR2/VolSv/instMag.i[0] MeasPoint.VT1 SMV_Subscriber>>Overcurrent> PTRC 1>VolSv;TVTR2/VolSv/q[0] MeasPoint.VT3 SMV_Subscriber>>Overcurrent> PTRC 1>VolSv;TVTR3/VolSv/instMag.i[0] MeasPoint.VT1 SMV_Subscriber>>Overcurrent> PTRC 1>VolSv;TVTR3/VolSv/q[0] Restricted To AmpSV SMV_Subscriber>>Overcurrent> PTRC 1>someRestrictedExtRef[0]" + value="MeasPoint.CT2 SMV_Subscriber>>Overvoltage> PTRC 1>AmpSv;TCTR2/AmpSv/instMag.i[0] MeasPoint.CT3 SMV_Subscriber>>Overvoltage> PTRC 1>AmpSv;TCTR3/AmpSv/instMag.i[0] MeasPoint.VT1 SMV_Subscriber>>Overcurrent> PTRC 1>VolSv;TVTR1/VolSv/q[0] MeasPoint.VT2 SMV_Subscriber>>Overcurrent> PTRC 1>VolSv;TVTR2/VolSv/instMag.i[0] MeasPoint.VT1 SMV_Subscriber>>Overcurrent> PTRC 1>VolSv;TVTR2/VolSv/q[0] MeasPoint.VT3 SMV_Subscriber>>Overcurrent> PTRC 1>VolSv;TVTR3/VolSv/instMag.i[0] MeasPoint.VT1 SMV_Subscriber>>Overcurrent> PTRC 1>VolSv;TVTR3/VolSv/q[0] Restricted To AmpSV SMV_Subscriber>>Overcurrent> PTRC 1>someRestrictedExtRef[0] Restricted To AmpSV SMV_Subscriber>>Overcurrent> PTRC 1>someRestrictedExtRef[1]" > [subscription.subscriber.availableToSubscribe] @@ -216,6 +216,25 @@ snapshots["extref-later-binding-list for Sampled Value Control when SVC has no s arrow_back + + + someRestrictedExtRef + (Restricted To AmpSV) + + + SMV_Subscriber>>Overcurrent> PTRC 1>someRestrictedExtRef[1] + + + arrow_back + + `; @@ -265,7 +284,7 @@ snapshots["extref-later-binding-list for Sampled Value Control when SVC has a si aria-disabled="false" noninteractive="" tabindex="-1" - value="MeasPoint.CT2 SMV_Subscriber>>Overvoltage> PTRC 1>AmpSv;TCTR2/AmpSv/instMag.i[0] MeasPoint.CT3 SMV_Subscriber>>Overvoltage> PTRC 1>AmpSv;TCTR3/AmpSv/instMag.i[0] MeasPoint.VT1 SMV_Subscriber>>Overcurrent> PTRC 1>VolSv;TVTR1/VolSv/q[0] MeasPoint.VT2 SMV_Subscriber>>Overcurrent> PTRC 1>VolSv;TVTR2/VolSv/instMag.i[0] MeasPoint.VT1 SMV_Subscriber>>Overcurrent> PTRC 1>VolSv;TVTR2/VolSv/q[0] MeasPoint.VT3 SMV_Subscriber>>Overcurrent> PTRC 1>VolSv;TVTR3/VolSv/instMag.i[0] MeasPoint.VT1 SMV_Subscriber>>Overcurrent> PTRC 1>VolSv;TVTR3/VolSv/q[0] Restricted To AmpSV SMV_Subscriber>>Overcurrent> PTRC 1>someRestrictedExtRef[0]" + value="MeasPoint.CT2 SMV_Subscriber>>Overvoltage> PTRC 1>AmpSv;TCTR2/AmpSv/instMag.i[0] MeasPoint.CT3 SMV_Subscriber>>Overvoltage> PTRC 1>AmpSv;TCTR3/AmpSv/instMag.i[0] MeasPoint.VT1 SMV_Subscriber>>Overcurrent> PTRC 1>VolSv;TVTR1/VolSv/q[0] MeasPoint.VT2 SMV_Subscriber>>Overcurrent> PTRC 1>VolSv;TVTR2/VolSv/instMag.i[0] MeasPoint.VT1 SMV_Subscriber>>Overcurrent> PTRC 1>VolSv;TVTR2/VolSv/q[0] MeasPoint.VT3 SMV_Subscriber>>Overcurrent> PTRC 1>VolSv;TVTR3/VolSv/instMag.i[0] MeasPoint.VT1 SMV_Subscriber>>Overcurrent> PTRC 1>VolSv;TVTR3/VolSv/q[0] Restricted To AmpSV SMV_Subscriber>>Overcurrent> PTRC 1>someRestrictedExtRef[0] Restricted To AmpSV SMV_Subscriber>>Overcurrent> PTRC 1>someRestrictedExtRef[1]" > [subscription.subscriber.availableToSubscribe] @@ -429,6 +448,25 @@ snapshots["extref-later-binding-list for Sampled Value Control when SVC has a si arrow_back + + + someRestrictedExtRef + (Restricted To AmpSV) + + + SMV_Subscriber>>Overcurrent> PTRC 1>someRestrictedExtRef[1] + + + arrow_back + + `; @@ -510,7 +548,7 @@ snapshots["extref-later-binding-list when SVC has a multiple subscriptions looks aria-disabled="false" noninteractive="" tabindex="-1" - value="MeasPoint.CT2 SMV_Subscriber>>Overvoltage> PTRC 1>AmpSv;TCTR2/AmpSv/instMag.i[0] MeasPoint.CT3 SMV_Subscriber>>Overvoltage> PTRC 1>AmpSv;TCTR3/AmpSv/instMag.i[0] MeasPoint.VT1 SMV_Subscriber>>Overcurrent> PTRC 1>VolSv;TVTR1/VolSv/q[0] MeasPoint.VT2 SMV_Subscriber>>Overcurrent> PTRC 1>VolSv;TVTR2/VolSv/instMag.i[0] MeasPoint.VT1 SMV_Subscriber>>Overcurrent> PTRC 1>VolSv;TVTR2/VolSv/q[0] MeasPoint.VT3 SMV_Subscriber>>Overcurrent> PTRC 1>VolSv;TVTR3/VolSv/instMag.i[0] MeasPoint.VT1 SMV_Subscriber>>Overcurrent> PTRC 1>VolSv;TVTR3/VolSv/q[0] Restricted To AmpSV SMV_Subscriber>>Overcurrent> PTRC 1>someRestrictedExtRef[0]" + value="MeasPoint.CT2 SMV_Subscriber>>Overvoltage> PTRC 1>AmpSv;TCTR2/AmpSv/instMag.i[0] MeasPoint.CT3 SMV_Subscriber>>Overvoltage> PTRC 1>AmpSv;TCTR3/AmpSv/instMag.i[0] MeasPoint.VT1 SMV_Subscriber>>Overcurrent> PTRC 1>VolSv;TVTR1/VolSv/q[0] MeasPoint.VT2 SMV_Subscriber>>Overcurrent> PTRC 1>VolSv;TVTR2/VolSv/instMag.i[0] MeasPoint.VT1 SMV_Subscriber>>Overcurrent> PTRC 1>VolSv;TVTR2/VolSv/q[0] MeasPoint.VT3 SMV_Subscriber>>Overcurrent> PTRC 1>VolSv;TVTR3/VolSv/instMag.i[0] MeasPoint.VT1 SMV_Subscriber>>Overcurrent> PTRC 1>VolSv;TVTR3/VolSv/q[0] Restricted To AmpSV SMV_Subscriber>>Overcurrent> PTRC 1>someRestrictedExtRef[0] Restricted To AmpSV SMV_Subscriber>>Overcurrent> PTRC 1>someRestrictedExtRef[1]" > [subscription.subscriber.availableToSubscribe] @@ -655,8 +693,7 @@ snapshots["extref-later-binding-list when SVC has a multiple subscriptions looks + + + someRestrictedExtRef + (Restricted To AmpSV) + + + SMV_Subscriber>>Overcurrent> PTRC 1>someRestrictedExtRef[1] + + + arrow_back + + `; @@ -721,7 +778,7 @@ snapshots["extref-later-binding-list for GOOSE Control when GSEControl has no su aria-disabled="false" noninteractive="" tabindex="-1" - value="Interlocking.Input GOOSE_Subscriber>>Earth_Switch> CILO 1>Pos;CSWI1/Pos/stVal[0] Interlocking.Input3 GOOSE_Subscriber>>Earth_Switch> CILO 1>GOOSE:GOOSE2 QB2_Disconnector/ LLN0 GOOSE_Publisher QB1_Disconnector/ CILO 1 EnaCls stVal@Pos;CILO/EnaCls/stVal Interlocking.Input4 GOOSE_Subscriber>>Earth_Switch> CILO 1>GOOSE:GOOSE2 QB2_Disconnector/ LLN0 GOOSE_Publisher QB2_Disconnector/ CILO 1 EnaOpn stVal@Pos;CILO/EnaOpn2/stVal Restricted To Pos GOOSE_Subscriber>>Earth_Switch> CSWI 1>someRestrictedExtRef[0]" + value="Interlocking.Input GOOSE_Subscriber>>Earth_Switch> CILO 1>Pos;CSWI1/Pos/stVal[0] Interlocking.Input3 GOOSE_Subscriber>>Earth_Switch> CILO 1>GOOSE:GOOSE2 QB2_Disconnector/ LLN0 GOOSE_Publisher QB1_Disconnector/ CILO 1 EnaCls stVal@Pos;CILO/EnaCls/stVal Interlocking.Input4 GOOSE_Subscriber>>Earth_Switch> CILO 1>GOOSE:GOOSE2 QB2_Disconnector/ LLN0 GOOSE_Publisher QB2_Disconnector/ CILO 1 EnaOpn stVal@Pos;CILO/EnaOpn2/stVal Restricted To Pos GOOSE_Subscriber>>Earth_Switch> CSWI 1>someRestrictedExtRef[0] Restricted To Pos GOOSE_Subscriber>>Earth_Switch> CSWI 1>someRestrictedExtRef[1]" > [subscription.subscriber.availableToSubscribe] @@ -809,6 +866,25 @@ snapshots["extref-later-binding-list for GOOSE Control when GSEControl has no su arrow_back + + + someRestrictedExtRef + (Restricted To Pos) + + + GOOSE_Subscriber>>Earth_Switch> CSWI 1>someRestrictedExtRef[1] + + + arrow_back + + `; @@ -858,7 +934,7 @@ snapshots["extref-later-binding-list for GOOSE Control when GSEControl has a sin aria-disabled="false" noninteractive="" tabindex="-1" - value="Interlocking.Input GOOSE_Subscriber>>Earth_Switch> CILO 1>Pos;CSWI1/Pos/stVal[0] Interlocking.Input3 GOOSE_Subscriber>>Earth_Switch> CILO 1>GOOSE:GOOSE2 QB2_Disconnector/ LLN0 GOOSE_Publisher QB1_Disconnector/ CILO 1 EnaCls stVal@Pos;CILO/EnaCls/stVal Interlocking.Input4 GOOSE_Subscriber>>Earth_Switch> CILO 1>GOOSE:GOOSE2 QB2_Disconnector/ LLN0 GOOSE_Publisher QB2_Disconnector/ CILO 1 EnaOpn stVal@Pos;CILO/EnaOpn2/stVal Restricted To Pos GOOSE_Subscriber>>Earth_Switch> CSWI 1>someRestrictedExtRef[0]" + value="Interlocking.Input GOOSE_Subscriber>>Earth_Switch> CILO 1>Pos;CSWI1/Pos/stVal[0] Interlocking.Input3 GOOSE_Subscriber>>Earth_Switch> CILO 1>GOOSE:GOOSE2 QB2_Disconnector/ LLN0 GOOSE_Publisher QB1_Disconnector/ CILO 1 EnaCls stVal@Pos;CILO/EnaCls/stVal Interlocking.Input4 GOOSE_Subscriber>>Earth_Switch> CILO 1>GOOSE:GOOSE2 QB2_Disconnector/ LLN0 GOOSE_Publisher QB2_Disconnector/ CILO 1 EnaOpn stVal@Pos;CILO/EnaOpn2/stVal Restricted To Pos GOOSE_Subscriber>>Earth_Switch> CSWI 1>someRestrictedExtRef[0] Restricted To Pos GOOSE_Subscriber>>Earth_Switch> CSWI 1>someRestrictedExtRef[1]" > [subscription.subscriber.availableToSubscribe] @@ -946,6 +1022,25 @@ snapshots["extref-later-binding-list for GOOSE Control when GSEControl has a sin arrow_back + + + someRestrictedExtRef + (Restricted To Pos) + + + GOOSE_Subscriber>>Earth_Switch> CSWI 1>someRestrictedExtRef[1] + + + arrow_back + + `; @@ -1011,7 +1106,7 @@ snapshots["extref-later-binding-list when GSEControl has a multiple subscription aria-disabled="false" noninteractive="" tabindex="-1" - value="Interlocking.Input GOOSE_Subscriber>>Earth_Switch> CILO 1>Pos;CSWI1/Pos/stVal[0] Interlocking.Input3 GOOSE_Subscriber>>Earth_Switch> CILO 1>GOOSE:GOOSE2 QB2_Disconnector/ LLN0 GOOSE_Publisher QB1_Disconnector/ CILO 1 EnaCls stVal@Pos;CILO/EnaCls/stVal Interlocking.Input4 GOOSE_Subscriber>>Earth_Switch> CILO 1>GOOSE:GOOSE2 QB2_Disconnector/ LLN0 GOOSE_Publisher QB2_Disconnector/ CILO 1 EnaOpn stVal@Pos;CILO/EnaOpn2/stVal Restricted To Pos GOOSE_Subscriber>>Earth_Switch> CSWI 1>someRestrictedExtRef[0]" + value="Interlocking.Input GOOSE_Subscriber>>Earth_Switch> CILO 1>Pos;CSWI1/Pos/stVal[0] Interlocking.Input3 GOOSE_Subscriber>>Earth_Switch> CILO 1>GOOSE:GOOSE2 QB2_Disconnector/ LLN0 GOOSE_Publisher QB1_Disconnector/ CILO 1 EnaCls stVal@Pos;CILO/EnaCls/stVal Interlocking.Input4 GOOSE_Subscriber>>Earth_Switch> CILO 1>GOOSE:GOOSE2 QB2_Disconnector/ LLN0 GOOSE_Publisher QB2_Disconnector/ CILO 1 EnaOpn stVal@Pos;CILO/EnaOpn2/stVal Restricted To Pos GOOSE_Subscriber>>Earth_Switch> CSWI 1>someRestrictedExtRef[0] Restricted To Pos GOOSE_Subscriber>>Earth_Switch> CSWI 1>someRestrictedExtRef[1]" > [subscription.subscriber.availableToSubscribe] @@ -1080,8 +1175,7 @@ snapshots["extref-later-binding-list when GSEControl has a multiple subscription + + + someRestrictedExtRef + (Restricted To Pos) + + + GOOSE_Subscriber>>Earth_Switch> CSWI 1>someRestrictedExtRef[1] + + + arrow_back + + `; diff --git a/test/unit/editors/subscription/later-binding/ext-ref-later-binding-list.test.ts b/test/unit/editors/subscription/later-binding/ext-ref-later-binding-list.test.ts index ae04dc17e..21bf6adbf 100644 --- a/test/unit/editors/subscription/later-binding/ext-ref-later-binding-list.test.ts +++ b/test/unit/editors/subscription/later-binding/ext-ref-later-binding-list.test.ts @@ -74,7 +74,7 @@ describe('extref-later-binding-list', () => { }); it('expect the correct number of available elements', () => { - expect(element['getAvailableExtRefElements']().length).to.be.equal(8); + expect(element['getAvailableExtRefElements']().length).to.be.equal(9); }); it('looks like the latest snapshot, when SVC has no subscriptions', async () => { @@ -101,7 +101,7 @@ describe('extref-later-binding-list', () => { }); it('expect the correct number of available elements', () => { - expect(element['getAvailableExtRefElements']().length).to.be.equal(8); + expect(element['getAvailableExtRefElements']().length).to.be.equal(9); }); it('looks like the latest snapshot, ', async () => { @@ -129,7 +129,7 @@ describe('extref-later-binding-list', () => { }); it('expect the correct number of available elements', () => { - expect(element['getAvailableExtRefElements']().length).to.be.equal(8); + expect(element['getAvailableExtRefElements']().length).to.be.equal(9); }); it('looks like the latest snapshot, ', async () => { @@ -188,7 +188,7 @@ describe('extref-later-binding-list', () => { }); it('expect the correct number of available elements', () => { - expect(element['getAvailableExtRefElements']().length).to.be.equal(4); + expect(element['getAvailableExtRefElements']().length).to.be.equal(5); }); it('looks like the latest snapshot, when GSEControl has no subscriptions', async () => { @@ -217,7 +217,7 @@ describe('extref-later-binding-list', () => { }); it('expect the correct number of available elements', () => { - expect(element['getAvailableExtRefElements']().length).to.be.equal(4); + expect(element['getAvailableExtRefElements']().length).to.be.equal(5); }); it('looks like the latest snapshot, ', async () => { @@ -245,7 +245,7 @@ describe('extref-later-binding-list', () => { }); it('expect the correct number of available elements', () => { - expect(element['getAvailableExtRefElements']().length).to.be.equal(4); + expect(element['getAvailableExtRefElements']().length).to.be.equal(5); }); it('looks like the latest snapshot, ', async () => { diff --git a/test/unit/editors/subscription/later-binding/foundation.test.ts b/test/unit/editors/subscription/later-binding/foundation.test.ts index 2a32bcadc..415df13f6 100644 --- a/test/unit/editors/subscription/later-binding/foundation.test.ts +++ b/test/unit/editors/subscription/later-binding/foundation.test.ts @@ -1,6 +1,8 @@ import { expect } from '@open-wc/testing'; import { + fcdaSpecification, + inputRestriction, isSubscribedTo, sameAttributeValue, sameAttributeValueDiffName, @@ -245,3 +247,78 @@ describe('smv-list', () => { }); }); }); + +describe('Input restriction', () => { + let doc: XMLDocument; + let extRef: Element; + beforeEach(async () => { + doc = await fetch('/test/testfiles/editors/LaterBindingSMV2007B4.scd') + .then(response => response.text()) + .then(str => new DOMParser().parseFromString(str, 'application/xml')); + + extRef = doc.querySelector( + 'ExtRef[pLN="TCTR"][pDO="AmpSv"][pDA="instMag.i"]' + )!; + }); + + it('returns null for missing restriction attributes', () => { + extRef.removeAttribute('pLN'); + expect(inputRestriction(extRef)).to.deep.equal({ cdc: null, bType: null }); + }); + + it('returns null for missing logical node connection', () => { + extRef.setAttribute('pLN', 'PDIS'); + expect(inputRestriction(extRef)).to.deep.equal({ cdc: null, bType: null }); + }); + + it('returns null for failed CDC extraction', () => { + extRef.setAttribute('pDO', 'AmpS'); + expect(inputRestriction(extRef)).to.deep.equal({ cdc: null, bType: null }); + }); + + it('returns null for failed bType extraction', () => { + extRef.setAttribute('pDA', 'instMag.f'); + expect(inputRestriction(extRef)).to.deep.equal({ cdc: null, bType: null }); + }); + + it('returns object with extracted CDC and bType', () => + expect(inputRestriction(extRef)).to.deep.equal({ + cdc: 'SAV', + bType: 'INT32', + })); +}); + +describe('FCDA specification', () => { + let doc: XMLDocument; + let fcda: Element; + beforeEach(async () => { + doc = await fetch('/test/testfiles/editors/LaterBindingSMV2007B4.scd') + .then(response => response.text()) + .then(str => new DOMParser().parseFromString(str, 'application/xml')); + + fcda = doc.querySelector( + 'FCDA[lnClass="TCTR"][doName="AmpSv"][daName="instMag.i"]' + )!; + }); + + it('returns null for missing logical node connection', () => { + fcda.setAttribute('lnClass', 'USER'); + expect(fcdaSpecification(fcda)).to.deep.equal({ cdc: null, bType: null }); + }); + + it('returns null for failed CDC extraction', () => { + fcda.setAttribute('doName', 'AmpS'); + expect(fcdaSpecification(fcda)).to.deep.equal({ cdc: null, bType: null }); + }); + + it('returns null for failed bType extraction', () => { + fcda.setAttribute('daName', 'instMag.k'); + expect(fcdaSpecification(fcda)).to.deep.equal({ cdc: 'SAV', bType: null }); + }); + + it('returns object with extracted CDC and bType', () => + expect(fcdaSpecification(fcda)).to.deep.equal({ + cdc: 'SAV', + bType: 'INT32', + })); +});