From c6d51028cafa06f59405cfae6738d7f1d11f0b77 Mon Sep 17 00:00:00 2001 From: Dennis Labordus Date: Mon, 9 May 2022 07:45:54 +0200 Subject: [PATCH] bug(wizards): Update attributes of Terminal when updating Voltage Level/Bay name. (#712) * Update attributes of Terminal when updating VoltageLevel/Bay name. * Added extra reference tests. * Small change. * Improved version that will include parent names when needed to search for matching references. * Fixed review comment. --- src/translations/de.ts | 6 + src/translations/en.ts | 6 + src/wizards/bay.ts | 4 +- src/wizards/conductingequipment.ts | 4 +- src/wizards/foundation/actions.ts | 30 +- src/wizards/foundation/references.ts | 169 ++++++++-- src/wizards/powertransformer.ts | 4 +- src/wizards/voltagelevel.ts | 25 +- test/testfiles/wizards/references.scd | 296 ++++++++++++++++++ .../singlelinediagram/wizards/bay.test.ts | 2 +- .../wizards/conductingequipment.test.ts | 2 +- .../wizards/powertransformer.test.ts | 2 +- .../substation/SubstationEditor.test.ts | 84 ----- .../BayEditor.test.ts => wizards/bay.test.ts} | 30 +- test/unit/wizards/dai.test.ts | 2 +- .../wizards/foundation/references.test.ts | 94 +++++- test/unit/wizards/ied.test.ts | 2 +- test/unit/wizards/powertransformer.test.ts | 10 +- test/unit/wizards/substation.test.ts | 4 +- .../{foundation.ts => test-support.ts} | 0 .../voltagelevel.test.ts} | 115 ++++--- 21 files changed, 677 insertions(+), 214 deletions(-) create mode 100644 test/testfiles/wizards/references.scd delete mode 100644 test/unit/editors/substation/SubstationEditor.test.ts rename test/unit/{editors/substation/BayEditor.test.ts => wizards/bay.test.ts} (62%) rename test/unit/wizards/{foundation.ts => test-support.ts} (100%) rename test/unit/{editors/substation/VoltageLevelEditor.test.ts => wizards/voltagelevel.test.ts} (67%) diff --git a/src/translations/de.ts b/src/translations/de.ts index 184af68273..fcdc76e097 100644 --- a/src/translations/de.ts +++ b/src/translations/de.ts @@ -258,6 +258,9 @@ export const de: Translations = { edit: 'Spannungsebene bearbeiten', }, }, + action: { + updateVoltagelevel: 'Spannungsebene "{{name}}" bearbeitet', + }, }, bay: { name: 'Feld', @@ -269,6 +272,9 @@ export const de: Translations = { edit: 'Feld bearbeiten', }, }, + action: { + updateBay: 'Feld "{{name}}" bearbeitet', + }, }, conductingequipment: { name: 'Primärelement', diff --git a/src/translations/en.ts b/src/translations/en.ts index 364dd70568..f22128d12b 100644 --- a/src/translations/en.ts +++ b/src/translations/en.ts @@ -255,6 +255,9 @@ export const en = { edit: 'Edit voltage level', }, }, + action: { + updateVoltagelevel: 'Edited voltagelevel "{{name}}"', + }, }, bay: { name: 'Bay', @@ -266,6 +269,9 @@ export const en = { edit: 'Edit bay', }, }, + action: { + updateBay: 'Edited bay "{{name}}"', + }, }, conductingequipment: { name: 'Conducting Equipment', diff --git a/src/wizards/bay.ts b/src/wizards/bay.ts index 8acc8bf5d8..8cbfd99c00 100644 --- a/src/wizards/bay.ts +++ b/src/wizards/bay.ts @@ -10,7 +10,7 @@ import { WizardActor, WizardInputElement, } from '../foundation.js'; -import { updateNamingAction } from './foundation/actions.js'; +import { replaceNamingAttributeWithReferencesAction } from './foundation/actions.js'; export function renderBayWizard(name: string | null, desc: string | null): TemplateResult[] { return [ @@ -74,7 +74,7 @@ export function editBayWizard(element: Element): Wizard { primary: { icon: 'edit', label: get('save'), - action: updateNamingAction(element), + action: replaceNamingAttributeWithReferencesAction(element, 'bay.action.updateBay'), }, content: renderBayWizard( element.getAttribute('name'), diff --git a/src/wizards/conductingequipment.ts b/src/wizards/conductingequipment.ts index ebe5cdc0b9..48f117a5bf 100644 --- a/src/wizards/conductingequipment.ts +++ b/src/wizards/conductingequipment.ts @@ -15,7 +15,7 @@ import { WizardActor, WizardInputElement, } from '../foundation.js'; -import { updateNamingAction } from './foundation/actions.js'; +import { replaceNamingAction } from './foundation/actions.js'; const types: Partial> = { // standard @@ -313,7 +313,7 @@ export function editConductingEquipmentWizard(element: Element): Wizard { primary: { icon: 'edit', label: get('save'), - action: updateNamingAction(element), + action: replaceNamingAction(element), }, content: renderConductingEquipmentWizard( element.getAttribute('name'), diff --git a/src/wizards/foundation/actions.ts b/src/wizards/foundation/actions.ts index c3d2d6f594..15eec3ad60 100644 --- a/src/wizards/foundation/actions.ts +++ b/src/wizards/foundation/actions.ts @@ -10,7 +10,7 @@ import { import { get } from "lit-translate"; import { updateReferences } from "./references.js"; -export function updateNamingAction(element: Element): WizardActor { +export function replaceNamingAction(element: Element): WizardActor { return (inputs: WizardInputElement[]): EditorAction[] => { const name = getValue(inputs.find(i => i.label === 'name')!)!; const desc = getValue(inputs.find(i => i.label === 'desc')!); @@ -28,6 +28,34 @@ export function updateNamingAction(element: Element): WizardActor { }; } +export function replaceNamingAttributeWithReferencesAction( + element: Element, + messageTitleKey: string +): WizardActor { + return (inputs: WizardInputElement[]): EditorAction[] => { + const newName = getValue(inputs.find(i => i.label === 'name')!)!; + const oldName = element.getAttribute('name'); + const newDesc = getValue(inputs.find(i => i.label === 'desc')!); + + if ( + newName === oldName && + newDesc === element.getAttribute('desc') + ) { + return []; + } + + const newElement = cloneElement(element, { name: newName, desc: newDesc }); + + const complexAction: ComplexAction = { + actions: [], + title: get(messageTitleKey, {name: newName}), + }; + complexAction.actions.push({ old: { element }, new: { element: newElement } }); + complexAction.actions.push(...updateReferences(element, oldName, newName)); + return complexAction.actions.length ? [complexAction] : []; + }; +} + export function updateNamingAttributeWithReferencesAction( element: Element, messageTitleKey: string diff --git a/src/wizards/foundation/references.ts b/src/wizards/foundation/references.ts index 86523659da..8606ab011e 100644 --- a/src/wizards/foundation/references.ts +++ b/src/wizards/foundation/references.ts @@ -1,54 +1,132 @@ -import {isPublic, SimpleAction} from "../../foundation.js"; +import { + isPublic, + Replace +} from "../../foundation.js"; -const referenceInfoTags = ['IED', 'Substation'] as const; +const referenceInfoTags = ['IED', 'Substation', 'VoltageLevel', 'Bay'] as const; type ReferencesInfoTag = typeof referenceInfoTags[number]; +type FilterFunction = (element: Element, attributeName: string | null, oldName: string | null) => string; + /* * For every supported tag a list of information about which elements to search for and which attribute value * to replace with the new value typed in the screen by the user. This is used to update references to a name * of an element by other elements. - * If the attribute is null the text content of the found element will be replaced. + * If the attributeName is null the text content of the found element will be replaced. */ const referenceInfos: Record< ReferencesInfoTag, { - elementQuery: string; - attribute: string | null; + attributeName: string | null; + filter: FilterFunction; }[] > = { IED: [{ - elementQuery: `Association`, - attribute: 'iedName' + attributeName: 'iedName', + filter: simpleAttributeFilter(`Association`) }, { - elementQuery: `ClientLN`, - attribute: 'iedName' + attributeName: 'iedName', + filter: simpleAttributeFilter(`ClientLN`) }, { - elementQuery: `ConnectedAP`, - attribute: 'iedName' + attributeName: 'iedName', + filter: simpleAttributeFilter(`ConnectedAP`) }, { - elementQuery: `ExtRef`, - attribute: 'iedName' + attributeName: 'iedName', + filter: simpleAttributeFilter(`ExtRef`) }, { - elementQuery: `KDC`, - attribute: 'iedName' + attributeName: 'iedName', + filter: simpleAttributeFilter(`KDC`) }, { - elementQuery: `LNode`, - attribute: 'iedName' + attributeName: 'iedName', + filter: simpleAttributeFilter(`LNode`) }, { - elementQuery: `GSEControl > IEDName`, - attribute: null + attributeName: null, + filter: simpleTextContentFilter(`GSEControl > IEDName`) }, { - elementQuery: `SampledValueControl > IEDName`, - attribute: null + attributeName: null, + filter: simpleTextContentFilter(`SampledValueControl > IEDName`) }], Substation: [{ - elementQuery: `Terminal`, - attribute: 'substationName' - }] + attributeName: 'substationName', + filter: simpleAttributeFilter(`Terminal`) + }], + VoltageLevel: + [{ + attributeName: 'voltageLevelName', + filter: attributeFilterWithParentNameAttribute(`Terminal`, + {'Substation': 'substationName'}) + }], + Bay: + [{ + attributeName: 'bayName', + filter: attributeFilterWithParentNameAttribute(`Terminal`, + {'Substation': 'substationName', 'VoltageLevel': 'voltageLevelName'}) + }], } +/** + * Simple function to create a filter to find Elements where the value of an attribute equals the old name. + * + * @param tagName - The tagName of the elements to search for. + */ +function simpleAttributeFilter(tagName: string) { + return function filter(element: Element, attributeName: string | null, oldName: string | null): string { + return `${tagName}[${attributeName}="${oldName}"]`; + } +} + +/** + * Simple function to search for Elements for which the text content may contain the old name. + * Because the text content of an element can't be search for in a CSS Selector this is done afterwards. + * + * @param elementQuery - The CSS Query to search for the Elements. + */ +function simpleTextContentFilter(elementQuery: string) { + return function filter(): string { + return `${elementQuery}`; + } +} + +/** + * More complex function to search for elements for which the value of an attribute needs to be updated. + * To find the correct element the name of a parent element also needs to be included in the search. + * + * For instance when the name of a Bay is updated only the terminals need to be updated where of course + * the old name of the bay is the value of the attribute 'bayName', but also the voltage level and substation + * name need to be included, because the name of the bay is only unique within the voltage level. + * The query will then become + * `Terminal[substationName=""][voltageLevelName=""][bayName=""]` + * + * @param tagName - The tagName of the elements to search for. + * @param parentInfo - The records of parent to search for, the key is the tagName of the parent, the value + * is the name of the attribuet to use in the query. + */ +function attributeFilterWithParentNameAttribute(tagName: string, parentInfo: Record) { + return function filter(element: Element, attributeName: string | null, oldName: string | null): string { + return `${tagName}${Object.entries(parentInfo) + .map(([parentTag, parentAttribute]) => { + const parentElement = element.closest(parentTag); + if (parentElement && parentElement.hasAttribute('name')) { + const name = parentElement.getAttribute('name'); + return `[${parentAttribute}="${name}"]`; + } + return null; + }).join('') // Join the strings to 1 string without a separator. + }[${attributeName}="${oldName}"]`; + } +} + +/** + * Clone an element with the attribute name passed and process the new value. If the new value + * is null the attribute will be removed otherwise the value of the attribute is updated. + * + * @param element - The element to clone. + * @param attributeName - The name of the attribute to copy. + * @param value - The value to set on the cloned element or if null remove the attribute. + * @returns Returns the cloned element. + */ function cloneElement(element: Element, attributeName: string, value: string | null): Element { const newElement = element.cloneNode(false); if (value === null) { @@ -59,14 +137,33 @@ function cloneElement(element: Element, attributeName: string, value: string | n return newElement; } +/** + * Clone an element and set the value as text content on the cloned element. + * + * @param element - The element to clone. + * @param value - The value to set. + * @returns Returns the cloned element. + */ function cloneElementAndTextContent(element: Element, value: string | null): Element { const newElement = element.cloneNode(false); newElement.textContent = value; return newElement; } -export function updateReferences(element: Element, oldValue: string | null, newValue: string): SimpleAction[] { - if (oldValue === newValue) { +/** + * Function to create Replace actions to update reference which point to the name of the element being updated. + * For instance the IED Name is used in other SCL Elements as attribute 'iedName' to reference the IED. + * These attribute values need to be updated if the name of the IED changes. + * + * An empty array will be returned if the old and new value are the same or no references need to be updated. + * + * @param element - The element for which the name is updated. + * @param oldName - The old name of the element. + * @param newName - The new name of the element. + * @returns Returns a list of Replace Actions that can be added to a Complex Action or returned directly for execution. + */ +export function updateReferences(element: Element, oldName: string | null, newName: string): Replace[] { + if (oldName === newName) { return []; } @@ -75,21 +172,27 @@ export function updateReferences(element: Element, oldValue: string | null, newV return []; } - const actions: SimpleAction[] = []; + const actions: Replace[] = []; referenceInfo.forEach(info => { - if (info.attribute !== null) { - Array.from(element.ownerDocument.querySelectorAll(`${info.elementQuery}[${info.attribute}="${oldValue}"]`)) + // Depending on if an attribute value needs to be updated or the text content of an element + // different scenarios need to be executed. + if (info.attributeName) { + const filter = info.filter(element, info.attributeName, oldName); + Array.from(element.ownerDocument.querySelectorAll(`${filter}`)) .filter(isPublic) .forEach(element => { - const newElement = cloneElement(element, info.attribute!, newValue); + const newElement = cloneElement(element, info.attributeName!, newName); actions.push({old: {element}, new: {element: newElement}}); }) } else { - Array.from(element.ownerDocument.querySelectorAll(`${info.elementQuery}`)) - .filter(element => element.textContent === oldValue) + // If the text content needs to be updated, filter on the text content can't be done in a CSS Selector. + // So we query all elements the may need to be updated and filter them afterwards. + const filter = info.filter(element, info.attributeName, oldName); + Array.from(element.ownerDocument.querySelectorAll(`${filter}`)) + .filter(element => element.textContent === oldName) .filter(isPublic) .forEach(element => { - const newElement = cloneElementAndTextContent(element, newValue); + const newElement = cloneElementAndTextContent(element, newName); actions.push({old: {element}, new: {element: newElement}}); }) } diff --git a/src/wizards/powertransformer.ts b/src/wizards/powertransformer.ts index 85239c2b45..ee87956049 100644 --- a/src/wizards/powertransformer.ts +++ b/src/wizards/powertransformer.ts @@ -11,7 +11,7 @@ import { WizardInputElement, } from '../foundation.js'; -import { updateNamingAction } from "./foundation/actions.js"; +import { replaceNamingAction } from "./foundation/actions.js"; const defaultPowerTransformerType = 'PTR'; @@ -103,7 +103,7 @@ export function editPowerTransformerWizard(element: Element): Wizard { primary: { icon: 'edit', label: get('save'), - action: updateNamingAction(element), + action: replaceNamingAction(element), }, content: renderPowerTransformerWizard( element.getAttribute('name'), diff --git a/src/wizards/voltagelevel.ts b/src/wizards/voltagelevel.ts index 3a8258be42..5e5d1c5f94 100644 --- a/src/wizards/voltagelevel.ts +++ b/src/wizards/voltagelevel.ts @@ -4,16 +4,20 @@ import { get, translate } from 'lit-translate'; import '../wizard-textfield.js'; import { cloneElement, + ComplexAction, createElement, EditorAction, getMultiplier, getValue, patterns, + SimpleAction, Wizard, WizardActor, WizardInputElement, } from '../foundation.js'; +import { updateReferences } from "./foundation/references.js"; + const initial = { nomFreq: '50', numPhases: '3', @@ -144,7 +148,7 @@ function getVoltageAction( Voltage: string | null, multiplier: string | null, voltageLevel: Element -): EditorAction { +): SimpleAction { if (oldVoltage === null) { const element = createElement(voltageLevel.ownerDocument, 'Voltage', { unit: 'V', @@ -187,8 +191,8 @@ export function updateAction(element: Element): WizardActor { const Voltage = getValue(inputs.find(i => i.label === 'Voltage')!); const multiplier = getMultiplier(inputs.find(i => i.label === 'Voltage')!); - let voltageLevelAction: EditorAction | null; - let voltageAction: EditorAction | null; + let voltageLevelAction: SimpleAction | null; + let voltageAction: SimpleAction | null; if ( name === element.getAttribute('name') && @@ -226,10 +230,17 @@ export function updateAction(element: Element): WizardActor { ); } - const actions: EditorAction[] = []; - if (voltageLevelAction) actions.push(voltageLevelAction); - if (voltageAction) actions.push(voltageAction); - return actions; + const complexAction: ComplexAction = { + actions: [], + title: get('voltagelevel.action.updateVoltagelevel', {name}), + }; + if (voltageLevelAction) complexAction.actions.push(voltageLevelAction); + if (voltageAction) complexAction.actions.push(voltageAction); + complexAction.actions.push(...updateReferences( + element, + element.getAttribute('name'), + name)); + return complexAction.actions.length ? [complexAction] : []; }; } diff --git a/test/testfiles/wizards/references.scd b/test/testfiles/wizards/references.scd new file mode 100644 index 0000000000..32395ee963 --- /dev/null +++ b/test/testfiles/wizards/references.scd @@ -0,0 +1,296 @@ + + +
+ + + 20 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 20 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/unit/editors/singlelinediagram/wizards/bay.test.ts b/test/unit/editors/singlelinediagram/wizards/bay.test.ts index 8465316b18..6a089dc6f2 100644 --- a/test/unit/editors/singlelinediagram/wizards/bay.test.ts +++ b/test/unit/editors/singlelinediagram/wizards/bay.test.ts @@ -7,7 +7,7 @@ import { expectWizardNoUpdateAction, fetchDoc, setWizardTextFieldValue, -} from '../../../wizards/foundation.js'; +} from '../../../wizards/test-support.js'; import { WizardTextField } from '../../../../../src/wizard-textfield.js'; import { WizardInputElement } from '../../../../../src/foundation.js'; diff --git a/test/unit/editors/singlelinediagram/wizards/conductingequipment.test.ts b/test/unit/editors/singlelinediagram/wizards/conductingequipment.test.ts index 4db18547af..4d47c010f0 100644 --- a/test/unit/editors/singlelinediagram/wizards/conductingequipment.test.ts +++ b/test/unit/editors/singlelinediagram/wizards/conductingequipment.test.ts @@ -7,7 +7,7 @@ import { expectWizardNoUpdateAction, fetchDoc, setWizardTextFieldValue, -} from '../../../wizards/foundation.js'; +} from '../../../wizards/test-support.js'; import { WizardTextField } from '../../../../../src/wizard-textfield.js'; import { WizardInputElement } from '../../../../../src/foundation.js'; diff --git a/test/unit/editors/singlelinediagram/wizards/powertransformer.test.ts b/test/unit/editors/singlelinediagram/wizards/powertransformer.test.ts index 7ca33d7436..c78a94db46 100644 --- a/test/unit/editors/singlelinediagram/wizards/powertransformer.test.ts +++ b/test/unit/editors/singlelinediagram/wizards/powertransformer.test.ts @@ -7,7 +7,7 @@ import { expectWizardNoUpdateAction, fetchDoc, setWizardTextFieldValue, -} from '../../../wizards/foundation.js'; +} from '../../../wizards/test-support.js'; import { WizardTextField } from '../../../../../src/wizard-textfield.js'; import { WizardInputElement } from '../../../../../src/foundation.js'; diff --git a/test/unit/editors/substation/SubstationEditor.test.ts b/test/unit/editors/substation/SubstationEditor.test.ts deleted file mode 100644 index 674e6dcd23..0000000000 --- a/test/unit/editors/substation/SubstationEditor.test.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { fixture, html, expect } from '@open-wc/testing'; - -import '../../../../src/wizard-textfield.js'; -import { - WizardInputElement, - isCreate, - isReplace, -} from '../../../../src/foundation.js'; -import { updateNamingAction } from '../../../../src/wizards/foundation/actions.js'; -import { createAction } from '../../../../src/wizards/substation.js'; - -describe('SubstationEditor', () => { - const noOp = () => { - return; - }; - const newWizard = (done = noOp) => { - const element = document.createElement('mwc-dialog'); - element.close = done; - return element; - }; - - let inputs: WizardInputElement[]; - beforeEach(async () => { - inputs = await Promise.all( - ['name', 'desc'].map( - label => - >( - fixture(html``) - ) - ) - ); - }); - - 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 EditorAction', () => { - const wizardAction = updateNamingAction(element); - expect(wizardAction(inputs, newWizard()).length).to.equal(1); - }); - - it('returns a WizardAction which returns an Update EditorAction', () => { - const wizardAction = updateNamingAction(element); - expect(wizardAction(inputs, newWizard())[0]).to.satisfy(isReplace); - }); - - describe('with no change in element Substation', () => { - let element: Element; - beforeEach(() => { - element = new DOMParser().parseFromString( - ` - `, - 'application/xml' - ).documentElement; - }); - - it('returns a WizardAction which returns empty EditorAction array', () => { - const wizardAction = updateNamingAction(element); - expect(wizardAction(inputs, newWizard()).length).to.equal(0); - }); - }); - }); -}); diff --git a/test/unit/editors/substation/BayEditor.test.ts b/test/unit/wizards/bay.test.ts similarity index 62% rename from test/unit/editors/substation/BayEditor.test.ts rename to test/unit/wizards/bay.test.ts index 99cf2fd3d5..20c788dfa7 100644 --- a/test/unit/editors/substation/BayEditor.test.ts +++ b/test/unit/wizards/bay.test.ts @@ -4,11 +4,14 @@ import { WizardInputElement, isCreate, isReplace, -} from '../../../../src/foundation.js'; + WizardActor, + ComplexAction, + isSimple +} from '../../../src/foundation.js'; -import '../../../../src/wizard-textfield.js'; -import { createAction } from '../../../../src/wizards/bay.js'; -import { updateNamingAction } from '../../../../src/wizards/foundation/actions.js'; +import '../../../src/wizard-textfield.js'; +import { createAction } from '../../../src/wizards/bay.js'; +import { replaceNamingAttributeWithReferencesAction } from '../../../src/wizards/foundation/actions.js'; describe('BayEditor', () => { const noOp = () => { @@ -32,6 +35,13 @@ describe('BayEditor', () => { ); }); + function getAndValidComplexAction(wizardActor: WizardActor): ComplexAction { + const editorActions = wizardActor(inputs, newWizard()); + expect(editorActions.length).to.equal(1); + expect(editorActions[0]).to.not.satisfy(isSimple); + return editorActions[0]; + } + describe('createAction', () => { let parent: Element; beforeEach(() => { @@ -57,13 +67,15 @@ describe('BayEditor', () => { }); it('returns a WizardAction which retruns one EditorAction', () => { - const wizardAction = updateNamingAction(element); - expect(wizardAction(inputs, newWizard()).length).to.equal(1); + const wizardAction = replaceNamingAttributeWithReferencesAction(element, 'bay.action.updateBay'); + const complexAction = getAndValidComplexAction(wizardAction); + expect(complexAction.actions.length).to.equal(1); }); it('returns a WizardAction which returns an Update EditorAction', () => { - const wizardAction = updateNamingAction(element); - expect(wizardAction(inputs, newWizard())[0]).to.satisfy(isReplace); + const wizardAction = replaceNamingAttributeWithReferencesAction(element, 'bay.action.updateBay'); + const complexAction = getAndValidComplexAction(wizardAction); + expect(complexAction.actions[0]).to.satisfy(isReplace); }); describe('with no change in element Bay', () => { @@ -77,7 +89,7 @@ describe('BayEditor', () => { }); it('returns a WizardAction which returns empty EditorAction array', () => { - const wizardAction = updateNamingAction(element); + const wizardAction = replaceNamingAttributeWithReferencesAction(element, 'bay.action.updateBay'); expect(wizardAction(inputs, newWizard()).length).to.equal(0); }); }); diff --git a/test/unit/wizards/dai.test.ts b/test/unit/wizards/dai.test.ts index 6924bbdbe2..eb3d25f815 100644 --- a/test/unit/wizards/dai.test.ts +++ b/test/unit/wizards/dai.test.ts @@ -15,7 +15,7 @@ import { fetchDoc, newWizard, setWizardTextFieldValue, -} from './foundation.js'; +} from './test-support.js'; import { editDAIWizard, updateValue } from '../../../src/wizards/dai.js'; describe('Wizards for SCL element DAI', () => { diff --git a/test/unit/wizards/foundation/references.test.ts b/test/unit/wizards/foundation/references.test.ts index bf408b60c1..1bffb5a5ca 100644 --- a/test/unit/wizards/foundation/references.test.ts +++ b/test/unit/wizards/foundation/references.test.ts @@ -2,7 +2,7 @@ import { expectReplaceAction, expectUpdateTextValue, fetchDoc, -} from '../foundation.js'; +} from '../test-support.js'; import { updateReferences } from '../../../../src/wizards/foundation/references.js'; import { expect } from '@open-wc/testing'; import { Replace } from '../../../../src/foundation.js'; @@ -10,12 +10,12 @@ import { Replace } from '../../../../src/foundation.js'; describe('Update reference for ', () => { let doc: XMLDocument; - describe('IED ', () => { + describe('IED', () => { beforeEach(async () => { doc = await fetchDoc('/test/testfiles/wizards/ied.scd'); }); - it('will update all references to IED1', async function () { + it('will update all references to IED IED1', async function () { const oldName = 'IED1'; const newName = 'NewIED1'; const ied = doc.querySelector(`IED[name="${oldName}"]`)!; @@ -60,7 +60,7 @@ describe('Update reference for ', () => { ); }); - it('will update all references to IED2', async function () { + it('will update all references to IED IED2', async function () { const oldName = 'IED2'; const newName = 'NewIED2'; const ied = doc.querySelector(`IED[name="${oldName}"]`)!; @@ -89,4 +89,90 @@ describe('Update reference for ', () => { ); }); }); + + describe('Substation', () => { + beforeEach(async () => { + doc = await fetchDoc('/test/testfiles/wizards/references.scd'); + }); + + it('will update all references to Substation AA1', async function () { + const oldName = 'AA1'; + const newName = 'NewAA1'; + const substation = doc.querySelector(`Substation[name="${oldName}"]`)!; + + const updateActions = updateReferences(substation, oldName, newName); + expect(updateActions.length).to.equal(48); + + expectReplaceAction( + updateActions[0], + 'Terminal', + 'substationName', + oldName, + newName + ); + }); + }); + + describe('VoltageLevel', () => { + beforeEach(async () => { + doc = await fetchDoc('/test/testfiles/wizards/references.scd'); + }); + + it('will update all references to VoltageLevel "J1"', async function () { + const oldName = 'J1'; + const newName = 'J1 UPD'; + const voltageLevel = doc.querySelector(`VoltageLevel[name="${oldName}"]`)!; + + const updateActions = updateReferences(voltageLevel, oldName, newName); + expect(updateActions.length).to.equal(48); + + expectReplaceAction( + updateActions[0], + 'Terminal', + 'voltageLevelName', + oldName, + newName + ); + }); + }); + + describe('Bay', () => { + beforeEach(async () => { + doc = await fetchDoc('/test/testfiles/wizards/references.scd'); + }); + + it('will update all references to BusBar "BusBar A"', async function () { + const oldName = 'BusBar A'; + const newName = 'BusBar A UPD'; + const bay = doc.querySelector(`Bay[name="${oldName}"]`)!; + + const updateActions = updateReferences(bay, oldName, newName); + expect(updateActions.length).to.equal(6); + + expectReplaceAction( + updateActions[0], + 'Terminal', + 'bayName', + oldName, + newName + ); + }); + + it('will update all references to Bay "Bay A"', async function () { + const oldName = 'Bay A'; + const newName = 'Bay A UPD'; + const bay = doc.querySelector(`Bay[name="${oldName}"]`)!; + + const updateActions = updateReferences(bay, oldName, newName); + expect(updateActions.length).to.equal(8); + + expectReplaceAction( + updateActions[0], + 'Terminal', + 'bayName', + oldName, + newName + ); + }); + }); }); diff --git a/test/unit/wizards/ied.test.ts b/test/unit/wizards/ied.test.ts index 0f58281689..c5ef050026 100644 --- a/test/unit/wizards/ied.test.ts +++ b/test/unit/wizards/ied.test.ts @@ -18,7 +18,7 @@ import { fetchDoc, newWizard, setWizardTextFieldValue, -} from './foundation.js'; +} from './test-support.js'; import { updateNamingAttributeWithReferencesAction } from "../../../src/wizards/foundation/actions.js"; describe('Wizards for SCL element IED', () => { diff --git a/test/unit/wizards/powertransformer.test.ts b/test/unit/wizards/powertransformer.test.ts index 0f5de0bf5f..52fb5efc71 100644 --- a/test/unit/wizards/powertransformer.test.ts +++ b/test/unit/wizards/powertransformer.test.ts @@ -5,7 +5,7 @@ import { MockWizard } from '../../mock-wizard.js'; import { WizardTextField } from '../../../src/wizard-textfield.js'; import { WizardInputElement } from '../../../src/foundation.js'; -import { updateNamingAction } from '../../../src/wizards/foundation/actions.js'; +import { replaceNamingAction } from '../../../src/wizards/foundation/actions.js'; import { createAction, @@ -19,7 +19,7 @@ import { expectWizardNoUpdateAction, fetchDoc, setWizardTextFieldValue, -} from './foundation.js'; +} from './test-support.js'; describe('Wizards for SCL element Power Transformer', () => { let doc: XMLDocument; @@ -43,7 +43,7 @@ describe('Wizards for SCL element Power Transformer', () => { await setWizardTextFieldValue(inputs[0], 'OtherTA1'); const updateAction = executeWizardReplaceAction( - updateNamingAction(powerTransformer), + replaceNamingAction(powerTransformer), inputs ); expect(updateAction.old.element).to.have.attribute('name', 'TA1'); @@ -57,7 +57,7 @@ describe('Wizards for SCL element Power Transformer', () => { ); const updateAction = executeWizardReplaceAction( - updateNamingAction(powerTransformer), + replaceNamingAction(powerTransformer), inputs ); expect(updateAction.old.element).to.not.have.attribute('desc'); @@ -68,7 +68,7 @@ describe('Wizards for SCL element Power Transformer', () => { }); it('when no fields changed there will be no update action', async function () { - expectWizardNoUpdateAction(updateNamingAction(powerTransformer), inputs); + expectWizardNoUpdateAction(replaceNamingAction(powerTransformer), inputs); }); it('looks like the latest snapshot', async () => { diff --git a/test/unit/wizards/substation.test.ts b/test/unit/wizards/substation.test.ts index 8e0bd99cec..ffee80e965 100644 --- a/test/unit/wizards/substation.test.ts +++ b/test/unit/wizards/substation.test.ts @@ -6,7 +6,7 @@ import { MockWizard } from '../../mock-wizard.js'; import { WizardTextField } from '../../../src/wizard-textfield.js'; import { ComplexAction, - isSimple, Replace, + isSimple, WizardInputElement, } from '../../../src/foundation.js'; @@ -18,7 +18,7 @@ import { fetchDoc, newWizard, setWizardTextFieldValue, -} from './foundation.js'; +} from './test-support.js'; import { updateNamingAttributeWithReferencesAction } from "../../../src/wizards/foundation/actions.js"; import { createAction, diff --git a/test/unit/wizards/foundation.ts b/test/unit/wizards/test-support.ts similarity index 100% rename from test/unit/wizards/foundation.ts rename to test/unit/wizards/test-support.ts diff --git a/test/unit/editors/substation/VoltageLevelEditor.test.ts b/test/unit/wizards/voltagelevel.test.ts similarity index 67% rename from test/unit/editors/substation/VoltageLevelEditor.test.ts rename to test/unit/wizards/voltagelevel.test.ts index d2c8bfc354..1d7c6e5996 100644 --- a/test/unit/editors/substation/VoltageLevelEditor.test.ts +++ b/test/unit/wizards/voltagelevel.test.ts @@ -1,42 +1,52 @@ import { fixture, html, expect } from '@open-wc/testing'; -import '../../../../src/wizard-textfield.js'; +import '../../../src/wizard-textfield.js'; import { WizardInputElement, isCreate, isReplace, isDelete, -} from '../../../../src/foundation.js'; + isSimple, + ComplexAction, + WizardActor, +} from '../../../src/foundation.js'; import { createAction, updateAction, -} from '../../../../src/wizards/voltagelevel.js'; +} from '../../../src/wizards/voltagelevel.js'; describe('VoltageLevelEditor', () => { - describe('with no nulled properties', () => { - const noOp = () => { - return; - }; - const newWizard = (done = noOp) => { - const element = document.createElement('mwc-dialog'); - element.close = done; - return element; - }; - - let inputs: WizardInputElement[]; - beforeEach(async () => { - inputs = await Promise.all( - ['name', 'desc', 'nomFreq', 'numPhases', 'Voltage'].map( - label => - >( - fixture( - html`` - ) + const noOp = () => { + return; + }; + const newWizard = (done = noOp) => { + const element = document.createElement('mwc-dialog'); + element.close = done; + return element; + }; + + let inputs: WizardInputElement[]; + beforeEach(async () => { + inputs = await Promise.all( + ['name', 'desc', 'nomFreq', 'numPhases', 'Voltage'].map( + label => + >( + fixture( + html`` ) - ) - ); - }); + ) + ) + ); + }); + + function getAndValidComplexAction(wizardActor: WizardActor): ComplexAction { + const editorActions = wizardActor(inputs, newWizard()); + expect(editorActions.length).to.equal(1); + expect(editorActions[0]).to.not.satisfy(isSimple); + return editorActions[0]; + } + describe('with no nulled properties', () => { describe('has a createAction that', () => { let parent: Element; beforeEach(() => { @@ -64,17 +74,20 @@ describe('VoltageLevelEditor', () => { it('returns a WizardAction which returns two EditorActions', () => { const wizardAction = updateAction(element); - expect(wizardAction(inputs, newWizard()).length).to.equal(2); + const complexAction = getAndValidComplexAction(wizardAction); + expect(complexAction.actions.length).to.equal(2); }); it('returns a WizardAction with the first returned EditorAction being an Update', () => { const wizardAction = updateAction(element); - expect(wizardAction(inputs, newWizard())[0]).to.satisfy(isReplace); + const complexAction = getAndValidComplexAction(wizardAction); + expect(complexAction.actions[0]).to.satisfy(isReplace); }); it('returns a WizardAction with the second returned EditorAction being a Create', () => { const wizardAction = updateAction(element); - expect(wizardAction(inputs, newWizard())[1]).to.satisfy(isCreate); + const complexAction = getAndValidComplexAction(wizardAction); + expect(complexAction.actions[1]).to.satisfy(isCreate); }); }); @@ -91,17 +104,20 @@ describe('VoltageLevelEditor', () => { it('returns a WizardAction which returns two EditorActions', () => { const wizardAction = updateAction(element); - expect(wizardAction(inputs, newWizard()).length).to.equal(2); + const complexAction = getAndValidComplexAction(wizardAction); + expect(complexAction.actions.length).to.equal(2); }); it('returns a WizardAction with the first returned EditorAction being an Update', () => { const wizardAction = updateAction(element); - expect(wizardAction(inputs, newWizard())[0]).to.satisfy(isReplace); + const complexAction = getAndValidComplexAction(wizardAction); + expect(complexAction.actions[0]).to.satisfy(isReplace); }); it('returns a WizardAction with the second returned EditorAction being a Update', () => { const wizardAction = updateAction(element); - expect(wizardAction(inputs, newWizard())[1]).to.satisfy(isReplace); + const complexAction = getAndValidComplexAction(wizardAction); + expect(complexAction.actions[1]).to.satisfy(isReplace); }); }); @@ -118,12 +134,14 @@ describe('VoltageLevelEditor', () => { it('returns a WizardAction which returns one EditorActions', () => { const wizardAction = updateAction(element); - expect(wizardAction(inputs, newWizard()).length).to.equal(1); + const complexAction = getAndValidComplexAction(wizardAction); + expect(complexAction.actions.length).to.equal(1); }); it('returns a WizardAction with the first returned EditorAction beeing an Update', () => { const wizardAction = updateAction(element); - expect(wizardAction(inputs, newWizard())[0]).to.satisfy(isReplace); + const complexAction = getAndValidComplexAction(wizardAction); + expect(complexAction.actions[0]).to.satisfy(isReplace); }); }); @@ -145,28 +163,6 @@ describe('VoltageLevelEditor', () => { }); describe('with nulled properties', () => { - const noOp = () => { - return; - }; - const newWizard = (done = noOp) => { - const element = document.createElement('mwc-dialog'); - element.close = done; - return element; - }; - - let inputs: WizardInputElement[]; - beforeEach(async () => { - inputs = await Promise.all( - ['name', 'desc', 'nomFreq', 'numPhases', 'Voltage'].map( - label => - >( - fixture( - html`` - ) - ) - ) - ); - }); describe('has an updateAction that', () => { describe('with present child element Voltage', () => { @@ -191,17 +187,20 @@ describe('VoltageLevelEditor', () => { it('returns a WizardAction which returns two EditorActions', () => { const wizardAction = updateAction(element); - expect(wizardAction(inputs, newWizard()).length).to.equal(2); + const complexAction = getAndValidComplexAction(wizardAction); + expect(complexAction.actions.length).to.equal(2); }); it('returns a WizardAction with the first returned EditorAction beeing an Update', () => { const wizardAction = updateAction(element); - expect(wizardAction(inputs, newWizard())[0]).to.satisfy(isReplace); + const complexAction = getAndValidComplexAction(wizardAction); + expect(complexAction.actions[0]).to.satisfy(isReplace); }); it('returns a WizardAction with the second returned EditorAction beeing a Delete', () => { const wizardAction = updateAction(element); - expect(wizardAction(inputs, newWizard())[1]).to.satisfy(isDelete); + const complexAction = getAndValidComplexAction(wizardAction); + expect(complexAction.actions[1]).to.satisfy(isDelete); }); }); });