@omicronenergy/oscd-scl-dialogs 0.0.14 → 0.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/custom-elements.json +23 -0
- package/dist/wizards/address.js +1 -1
- package/dist/wizards/address.js.map +1 -1
- package/dist/wizards/connectedap.spec.d.ts +1 -0
- package/dist/wizards/connectedap.spec.js +132 -0
- package/dist/wizards/connectedap.spec.js.map +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.0.15](https://github.com/OMICRONEnergyOSS/oscd-scl-dialogs/compare/oscd-scl-dialogs-v0.0.14...oscd-scl-dialogs-v0.0.15) (2026-04-22)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* edits to the connectedAP should affect the correct address ([e9318e5](https://github.com/OMICRONEnergyOSS/oscd-scl-dialogs/commit/e9318e5464508d7e5c29e3f1c0c5764451c46228))
|
|
9
|
+
|
|
3
10
|
## [0.0.14](https://github.com/OMICRONEnergyOSS/oscd-scl-dialogs/compare/oscd-scl-dialogs-v0.0.13...oscd-scl-dialogs-v0.0.14) (2026-04-20)
|
|
4
11
|
|
|
5
12
|
|
package/custom-elements.json
CHANGED
|
@@ -2048,6 +2048,29 @@
|
|
|
2048
2048
|
}
|
|
2049
2049
|
]
|
|
2050
2050
|
},
|
|
2051
|
+
{
|
|
2052
|
+
"kind": "javascript-module",
|
|
2053
|
+
"path": "wizards/connectedap.spec.ts",
|
|
2054
|
+
"declarations": [],
|
|
2055
|
+
"exports": [
|
|
2056
|
+
{
|
|
2057
|
+
"kind": "custom-element-definition",
|
|
2058
|
+
"name": "scl-text-field",
|
|
2059
|
+
"declaration": {
|
|
2060
|
+
"name": "SclTextField",
|
|
2061
|
+
"package": "@openenergytools/scl-text-field"
|
|
2062
|
+
}
|
|
2063
|
+
},
|
|
2064
|
+
{
|
|
2065
|
+
"kind": "custom-element-definition",
|
|
2066
|
+
"name": "scl-checkbox",
|
|
2067
|
+
"declaration": {
|
|
2068
|
+
"name": "SclCheckbox",
|
|
2069
|
+
"package": "@openenergytools/scl-checkbox"
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
]
|
|
2073
|
+
},
|
|
2051
2074
|
{
|
|
2052
2075
|
"kind": "javascript-module",
|
|
2053
2076
|
"path": "wizards/connectedap.ts",
|
package/dist/wizards/address.js
CHANGED
|
@@ -32,7 +32,7 @@ export function createAddressElement(parent, inputs, instType) {
|
|
|
32
32
|
export function updateAddress(parent, inputs, instType) {
|
|
33
33
|
const actions = [];
|
|
34
34
|
const newAddress = createAddressElement(parent, inputs, instType);
|
|
35
|
-
const oldAddress = parent.querySelector('Address');
|
|
35
|
+
const oldAddress = parent.querySelector(':scope > Address');
|
|
36
36
|
if (oldAddress !== null && existDiff(oldAddress, newAddress)) {
|
|
37
37
|
actions.push({
|
|
38
38
|
node: oldAddress,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"address.js","sourceRoot":"","sources":["../../wizards/address.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAkB,MAAM,KAAK,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAIzD,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEhD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAO/D,SAAS,WAAW,CAAC,MAAe,EAAE,IAAmB;IACvD,OAAO,CACL,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,sBAAsB,CAAC,CAAC,CAAC,IAAI,CAC9D,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,KAAK,IAAI,CACrC,IAAI,IAAI,CACV,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,OAAgB,EAAE,OAAgB;IAC1D,IACE,OAAO,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,MAAM;QACpC,OAAO,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,MAAM,EACpC,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CACnD,KAAK,CAAC,EAAE,CACN,WAAW,CAAC,OAAO,EAAE,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW;QAC7D,KAAK,CAAC,WAAW,CACpB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,oBAAoB,CAClC,MAAe,EACf,MAAqC,EACrC,QAAiB;IAEjB,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,aAAa,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC;IAEnE,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;SAEnB,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,IAAI,CAAC;SACtC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE;QACxB,MAAM,IAAI,GAAG,GAAG,CAAC;QACjB,MAAM,KAAK,GAAG,aAAa,CAAC,MAAM,CAAC,aAAa,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;QACjE,IAAI,QAAQ,EAAE,CAAC;YACb,KAAK,CAAC,cAAc,CAClB,2CAA2C,EAC3C,UAAU,EACV,MAAM,GAAG,EAAE,CACZ,CAAC;QACJ,CAAC;QACD,KAAK,CAAC,WAAW,GAAG,KAAK,CAAC;QAC1B,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;IAEL,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,aAAa,CAC3B,MAAe,EACf,MAAqC,EACrC,QAAiB;IAEjB,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,MAAM,UAAU,GAAG,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;IAClE,MAAM,UAAU,GAAG,MAAM,CAAC,aAAa,CAAC,
|
|
1
|
+
{"version":3,"file":"address.js","sourceRoot":"","sources":["../../wizards/address.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, TemplateResult } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\n\nimport { EditV2 } from '@openscd/oscd-api';\n\nimport { getReference } from '@openscd/scl-lib';\n\nimport { typePattern } from './patterns.js';\n\nimport { createElement, typeNullable } from '../foundation.js';\n\ninterface AddressContentOptions {\n element: Element;\n types: string[];\n}\n\nfunction getPElement(parent: Element, type: string | null): Element | null {\n return (\n Array.from(parent.querySelectorAll(':scope > Address > P')).find(\n p => p.getAttribute('type') === type,\n ) ?? null\n );\n}\n\nexport function existDiff(oldAddr: Element, newAddr: Element): boolean {\n if (\n oldAddr.querySelectorAll('P').length !==\n newAddr.querySelectorAll('P').length\n ) {\n return true;\n }\n return Array.from(oldAddr.querySelectorAll('P')).some(\n pType =>\n getPElement(newAddr, pType.getAttribute('type'))?.textContent !==\n pType.textContent,\n );\n}\n\nexport function createAddressElement(\n parent: Element,\n inputs: Record<string, string | null>,\n instType: boolean,\n): Element {\n const address = createElement(parent.ownerDocument, 'Address', {});\n\n Object.entries(inputs)\n\n .filter(([_, value]) => value !== null)\n .forEach(([key, value]) => {\n const type = key;\n const child = createElement(parent.ownerDocument, 'P', { type });\n if (instType) {\n child.setAttributeNS(\n 'http://www.w3.org/2001/XMLSchema-instance',\n 'xsi:type',\n `tP_${key}`,\n );\n }\n child.textContent = value;\n address.appendChild(child);\n });\n\n return address;\n}\n\nexport function updateAddress(\n parent: Element,\n inputs: Record<string, string | null>,\n instType: boolean,\n): EditV2[] {\n const actions: EditV2[] = [];\n\n const newAddress = createAddressElement(parent, inputs, instType);\n const oldAddress = parent.querySelector(':scope > Address');\n\n if (oldAddress !== null && existDiff(oldAddress, newAddress)) {\n actions.push({\n node: oldAddress,\n });\n actions.push({\n parent,\n node: newAddress,\n reference: oldAddress.nextSibling,\n });\n } else if (oldAddress === null) {\n actions.push({\n parent,\n node: newAddress,\n reference: getReference(parent, 'Address'),\n });\n }\n\n return actions;\n}\n\nexport function hasTypeRestriction(element: Element): boolean {\n return Array.from(element.querySelectorAll('Address > P')).some(pType =>\n pType.getAttribute('xsi:type'),\n );\n}\n\nexport function contentAddress(\n content: AddressContentOptions,\n): TemplateResult[] {\n const pChildren: Record<string, string | null> = {};\n\n content.types.forEach(type => {\n if (!pChildren[type]) {\n pChildren[type] =\n getPElement(content.element, type)?.textContent?.trim() ?? null;\n }\n });\n\n return [\n html`<scl-checkbox\n label=\"Add XMLSchema-instance type\"\n id=\"instType\"\n .value=\"${hasTypeRestriction(content.element) ? 'true' : 'false'}\"\n ></scl-checkbox>`,\n html`${Object.entries(pChildren).map(\n ([key, value]) =>\n html`<scl-text-field\n label=\"${key}\"\n ?nullable=${typeNullable[key]}\n .value=${value}\n pattern=\"${ifDefined(typePattern[key])}\"\n required\n ></scl-text-field>`,\n )}`,\n ];\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unused-expressions */
|
|
2
|
+
import { expect, fixture } from '@open-wc/testing';
|
|
3
|
+
import { html } from 'lit';
|
|
4
|
+
import { editConnectedApWizard } from './connectedap.js';
|
|
5
|
+
import { XMLEditor } from '@openscd/oscd-editor';
|
|
6
|
+
import { SclTextField } from '@openenergytools/scl-text-field';
|
|
7
|
+
import { SclCheckbox } from '@openenergytools/scl-checkbox';
|
|
8
|
+
if (!customElements.get('scl-text-field'))
|
|
9
|
+
customElements.define('scl-text-field', SclTextField);
|
|
10
|
+
if (!customElements.get('scl-checkbox'))
|
|
11
|
+
customElements.define('scl-checkbox', SclCheckbox);
|
|
12
|
+
async function setSclTextFieldValue(field, value) {
|
|
13
|
+
if (field.nullable && !field.nullSwitch?.selected) {
|
|
14
|
+
field.nullSwitch?.click();
|
|
15
|
+
await field.updateComplete;
|
|
16
|
+
}
|
|
17
|
+
field.value = value;
|
|
18
|
+
await field.updateComplete;
|
|
19
|
+
field.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
|
|
20
|
+
await field.updateComplete;
|
|
21
|
+
}
|
|
22
|
+
const xmlParser = new DOMParser();
|
|
23
|
+
function createSclDoc() {
|
|
24
|
+
return xmlParser.parseFromString(`<?xml version="1.0" encoding="UTF-8"?>
|
|
25
|
+
<SCL xmlns="http://www.iec.ch/61850/2003/SCL"
|
|
26
|
+
version="2007"
|
|
27
|
+
revision="B"
|
|
28
|
+
release="4">
|
|
29
|
+
<Substation name="S1">
|
|
30
|
+
<VoltageLevel name="V1">
|
|
31
|
+
<Bay name="PUB"/>
|
|
32
|
+
<Bay name="SUB"/>
|
|
33
|
+
</VoltageLevel>
|
|
34
|
+
</Substation>
|
|
35
|
+
<Communication>
|
|
36
|
+
<SubNetwork name="StationBus" type="8-MMS">
|
|
37
|
+
<BitRate unit="b/s" multiplier="M">100</BitRate>
|
|
38
|
+
<ConnectedAP iedName="PUB_A" apName="AP1">
|
|
39
|
+
<GSE ldInst="LD_A" cbName="GCB_A">
|
|
40
|
+
<Address>
|
|
41
|
+
<P type="MAC-Address">01-0C-CD-01-00-01</P>
|
|
42
|
+
<P type="APPID">0001</P>
|
|
43
|
+
</Address>
|
|
44
|
+
<MinTime unit="s" multiplier="m">10</MinTime>
|
|
45
|
+
<MaxTime unit="s" multiplier="m">1000</MaxTime>
|
|
46
|
+
</GSE>
|
|
47
|
+
</ConnectedAP>
|
|
48
|
+
</SubNetwork>
|
|
49
|
+
</Communication>
|
|
50
|
+
<IED name="PUB_A" manufacturer="OpenSCD">
|
|
51
|
+
<AccessPoint name="AP1">
|
|
52
|
+
<Server>
|
|
53
|
+
<Authentication/>
|
|
54
|
+
<LDevice inst="LD_A">
|
|
55
|
+
<LN0 lnClass="LLN0" inst="" lnType="LLN0_Test">
|
|
56
|
+
<DataSet name="DS_A">
|
|
57
|
+
<FCDA ldInst="LD_A" prefix="" lnClass="TCTR" lnInst="1" doName="Beh" daName="stVal" fc="ST"/>
|
|
58
|
+
<FCDA ldInst="LD_A" prefix="" lnClass="TCTR" lnInst="1" doName="Beh" daName="q" fc="ST"/>
|
|
59
|
+
</DataSet>
|
|
60
|
+
<GSEControl name="GCB_A" type="GOOSE" appID="0001" confRev="1" datSet="DS_A"/>
|
|
61
|
+
</LN0>
|
|
62
|
+
<LN lnClass="TCTR" inst="1" lnType="TCTR_Test">
|
|
63
|
+
<DOI name="Beh">
|
|
64
|
+
<DAI name="stVal">
|
|
65
|
+
<Val>on</Val>
|
|
66
|
+
</DAI>
|
|
67
|
+
</DOI>
|
|
68
|
+
<DOI name="HzRtg"/>
|
|
69
|
+
<DOI name="ARtg"/>
|
|
70
|
+
</LN>
|
|
71
|
+
</LDevice>
|
|
72
|
+
</Server>
|
|
73
|
+
</AccessPoint>
|
|
74
|
+
</IED>
|
|
75
|
+
</SCL>`, 'application/xml');
|
|
76
|
+
}
|
|
77
|
+
describe('ConnectedAP wizard', () => {
|
|
78
|
+
let xmlEditor;
|
|
79
|
+
beforeEach(() => {
|
|
80
|
+
xmlEditor = new XMLEditor();
|
|
81
|
+
});
|
|
82
|
+
it('Editing a ConnectedAP to add an IP/Subnet does not affect GSE elements contained within', async () => {
|
|
83
|
+
const doc = createSclDoc();
|
|
84
|
+
const connectedAP = doc.querySelector('ConnectedAP');
|
|
85
|
+
expect(connectedAP).to.exist;
|
|
86
|
+
// Verify GSE > Address exists before editing
|
|
87
|
+
const gseBefore = connectedAP.querySelector('GSE');
|
|
88
|
+
expect(gseBefore).to.exist;
|
|
89
|
+
const gseAddressBefore = gseBefore.querySelector('Address');
|
|
90
|
+
expect(gseAddressBefore).to.exist;
|
|
91
|
+
expect(gseAddressBefore.querySelectorAll('P').length).to.equal(2);
|
|
92
|
+
// Create the edit wizard
|
|
93
|
+
const wizard = editConnectedApWizard(connectedAP);
|
|
94
|
+
expect(wizard).to.exist;
|
|
95
|
+
expect(wizard.primary).to.exist;
|
|
96
|
+
// Render the wizard content
|
|
97
|
+
const content = await fixture(html `<form>${wizard.content}</form>`);
|
|
98
|
+
// Find the IP field and set a value
|
|
99
|
+
const ipField = content.querySelector('scl-text-field[label="IP"]');
|
|
100
|
+
expect(ipField).to.exist;
|
|
101
|
+
await setSclTextFieldValue(ipField, '192.168.0.1');
|
|
102
|
+
// Find the IP-SUBNET field and set a value
|
|
103
|
+
const subnetField = content.querySelector('scl-text-field[label="IP-SUBNET"]');
|
|
104
|
+
expect(subnetField).to.exist;
|
|
105
|
+
await setSclTextFieldValue(subnetField, '255.255.255.0');
|
|
106
|
+
// Gather inputs (all scl-text-field elements)
|
|
107
|
+
const inputs = Array.from(content.querySelectorAll('scl-text-field'));
|
|
108
|
+
// Execute the update action
|
|
109
|
+
const edits = wizard.primary.action(inputs, content);
|
|
110
|
+
expect(edits).to.exist;
|
|
111
|
+
expect(edits.length).to.be.greaterThan(0);
|
|
112
|
+
// Apply edits
|
|
113
|
+
xmlEditor.commit(edits);
|
|
114
|
+
// After applying edits, verify the GSE > Address still exists and is intact
|
|
115
|
+
const gseAfter = connectedAP.querySelector('GSE');
|
|
116
|
+
expect(gseAfter, 'GSE element should still exist').to.exist;
|
|
117
|
+
const gseAddressAfter = gseAfter.querySelector('Address');
|
|
118
|
+
expect(gseAddressAfter, 'GSE > Address element should still exist after editing ConnectedAP address').to.exist;
|
|
119
|
+
const gsePElements = gseAddressAfter.querySelectorAll('P');
|
|
120
|
+
expect(gsePElements.length, 'GSE > Address should still have 2 P elements').to.equal(2);
|
|
121
|
+
const macAddress = Array.from(gsePElements).find(p => p.getAttribute('type') === 'MAC-Address');
|
|
122
|
+
expect(macAddress, 'GSE MAC-Address should be preserved').to.exist;
|
|
123
|
+
expect(macAddress.textContent).to.equal('01-0C-CD-01-00-01');
|
|
124
|
+
const appId = Array.from(gsePElements).find(p => p.getAttribute('type') === 'APPID');
|
|
125
|
+
expect(appId, 'GSE APPID should be preserved').to.exist;
|
|
126
|
+
expect(appId.textContent).to.equal('0001');
|
|
127
|
+
// Also verify that the ConnectedAP now has its own direct Address child
|
|
128
|
+
const connApAddress = connectedAP.querySelector(':scope > Address');
|
|
129
|
+
expect(connApAddress, 'ConnectedAP should have a direct Address child with IP info').to.exist;
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
//# sourceMappingURL=connectedap.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connectedap.spec.js","sourceRoot":"","sources":["../../wizards/connectedap.spec.ts"],"names":[],"mappings":"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","sourcesContent":["/* eslint-disable @typescript-eslint/no-unused-expressions */\nimport { expect, fixture } from '@open-wc/testing';\nimport { html } from 'lit';\n\nimport { editConnectedApWizard } from './connectedap.js';\nimport { EditV2 } from '@openscd/oscd-api';\nimport { XMLEditor } from '@openscd/oscd-editor';\nimport { SclTextField } from '@openenergytools/scl-text-field';\nimport { SclCheckbox } from '@openenergytools/scl-checkbox';\nimport { WizardInputElement } from '../foundation.js';\n\nif (!customElements.get('scl-text-field'))\n customElements.define('scl-text-field', SclTextField);\nif (!customElements.get('scl-checkbox'))\n customElements.define('scl-checkbox', SclCheckbox);\n\nasync function setSclTextFieldValue(\n field: SclTextField,\n value: string,\n): Promise<void> {\n if (field.nullable && !field.nullSwitch?.selected) {\n field.nullSwitch?.click();\n await field.updateComplete;\n }\n\n field.value = value;\n await field.updateComplete;\n field.dispatchEvent(new Event('input', { bubbles: true, composed: true }));\n await field.updateComplete;\n}\n\nconst xmlParser = new DOMParser();\nfunction createSclDoc(): XMLDocument {\n return xmlParser.parseFromString(\n `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<SCL xmlns=\"http://www.iec.ch/61850/2003/SCL\"\n version=\"2007\"\n revision=\"B\"\n release=\"4\">\n <Substation name=\"S1\">\n <VoltageLevel name=\"V1\">\n <Bay name=\"PUB\"/>\n <Bay name=\"SUB\"/>\n </VoltageLevel>\n </Substation>\n <Communication>\n <SubNetwork name=\"StationBus\" type=\"8-MMS\">\n <BitRate unit=\"b/s\" multiplier=\"M\">100</BitRate>\n <ConnectedAP iedName=\"PUB_A\" apName=\"AP1\">\n <GSE ldInst=\"LD_A\" cbName=\"GCB_A\">\n <Address>\n <P type=\"MAC-Address\">01-0C-CD-01-00-01</P>\n <P type=\"APPID\">0001</P>\n </Address>\n <MinTime unit=\"s\" multiplier=\"m\">10</MinTime>\n <MaxTime unit=\"s\" multiplier=\"m\">1000</MaxTime>\n </GSE>\n </ConnectedAP>\n </SubNetwork>\n </Communication>\n <IED name=\"PUB_A\" manufacturer=\"OpenSCD\">\n <AccessPoint name=\"AP1\">\n <Server>\n <Authentication/>\n <LDevice inst=\"LD_A\">\n <LN0 lnClass=\"LLN0\" inst=\"\" lnType=\"LLN0_Test\">\n <DataSet name=\"DS_A\">\n <FCDA ldInst=\"LD_A\" prefix=\"\" lnClass=\"TCTR\" lnInst=\"1\" doName=\"Beh\" daName=\"stVal\" fc=\"ST\"/>\n <FCDA ldInst=\"LD_A\" prefix=\"\" lnClass=\"TCTR\" lnInst=\"1\" doName=\"Beh\" daName=\"q\" fc=\"ST\"/>\n </DataSet>\n <GSEControl name=\"GCB_A\" type=\"GOOSE\" appID=\"0001\" confRev=\"1\" datSet=\"DS_A\"/>\n </LN0>\n <LN lnClass=\"TCTR\" inst=\"1\" lnType=\"TCTR_Test\">\n <DOI name=\"Beh\">\n <DAI name=\"stVal\">\n <Val>on</Val>\n </DAI>\n </DOI>\n <DOI name=\"HzRtg\"/>\n <DOI name=\"ARtg\"/>\n </LN>\n </LDevice>\n </Server>\n </AccessPoint>\n </IED>\n</SCL>`,\n 'application/xml',\n );\n}\n\ndescribe('ConnectedAP wizard', () => {\n let xmlEditor: XMLEditor;\n\n beforeEach(() => {\n xmlEditor = new XMLEditor();\n });\n\n it('Editing a ConnectedAP to add an IP/Subnet does not affect GSE elements contained within', async () => {\n const doc = createSclDoc();\n const connectedAP = doc.querySelector('ConnectedAP')!;\n expect(connectedAP).to.exist;\n\n // Verify GSE > Address exists before editing\n const gseBefore = connectedAP.querySelector('GSE');\n expect(gseBefore).to.exist;\n const gseAddressBefore = gseBefore!.querySelector('Address');\n expect(gseAddressBefore).to.exist;\n expect(gseAddressBefore!.querySelectorAll('P').length).to.equal(2);\n\n // Create the edit wizard\n const wizard = editConnectedApWizard(connectedAP);\n expect(wizard).to.exist;\n expect(wizard.primary).to.exist;\n\n // Render the wizard content\n const content = await fixture(html`<form>${wizard.content}</form>`);\n\n // Find the IP field and set a value\n const ipField = content.querySelector(\n 'scl-text-field[label=\"IP\"]',\n ) as SclTextField;\n expect(ipField).to.exist;\n await setSclTextFieldValue(ipField, '192.168.0.1');\n\n // Find the IP-SUBNET field and set a value\n const subnetField = content.querySelector(\n 'scl-text-field[label=\"IP-SUBNET\"]',\n ) as SclTextField;\n expect(subnetField).to.exist;\n await setSclTextFieldValue(subnetField, '255.255.255.0');\n\n // Gather inputs (all scl-text-field elements)\n const inputs = Array.from(\n content.querySelectorAll('scl-text-field'),\n ) as WizardInputElement[];\n\n // Execute the update action\n const edits = wizard.primary!.action(inputs, content) as EditV2[];\n expect(edits).to.exist;\n expect(edits.length).to.be.greaterThan(0);\n\n // Apply edits\n xmlEditor.commit(edits);\n\n // After applying edits, verify the GSE > Address still exists and is intact\n const gseAfter = connectedAP.querySelector('GSE');\n expect(gseAfter, 'GSE element should still exist').to.exist;\n\n const gseAddressAfter = gseAfter!.querySelector('Address');\n expect(\n gseAddressAfter,\n 'GSE > Address element should still exist after editing ConnectedAP address',\n ).to.exist;\n\n const gsePElements = gseAddressAfter!.querySelectorAll('P');\n expect(\n gsePElements.length,\n 'GSE > Address should still have 2 P elements',\n ).to.equal(2);\n\n const macAddress = Array.from(gsePElements).find(\n p => p.getAttribute('type') === 'MAC-Address',\n );\n expect(macAddress, 'GSE MAC-Address should be preserved').to.exist;\n expect(macAddress!.textContent).to.equal('01-0C-CD-01-00-01');\n\n const appId = Array.from(gsePElements).find(\n p => p.getAttribute('type') === 'APPID',\n );\n expect(appId, 'GSE APPID should be preserved').to.exist;\n expect(appId!.textContent).to.equal('0001');\n\n // Also verify that the ConnectedAP now has its own direct Address child\n const connApAddress = connectedAP.querySelector(':scope > Address');\n expect(\n connApAddress,\n 'ConnectedAP should have a direct Address child with IP info',\n ).to.exist;\n });\n});\n"]}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@omicronenergy/oscd-scl-dialogs",
|
|
3
3
|
"description": "Provides a resuable dialog for adding and editing SCL elements in OpenSCD.",
|
|
4
4
|
"displayName": "OpenSCD Edit Dialog",
|
|
5
|
-
"version": "0.0.
|
|
5
|
+
"version": "0.0.15",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git+https://github.com/OMICRONEnergyOSS/oscd-scl-dialogs.git"
|