Skip to content

Commit c2c9b93

Browse files
authored
Merge pull request #3 from com-pas/feature/delete-lnode-types
feature: delete lnode types
2 parents 4222b44 + 9d464e0 commit c2c9b93

8 files changed

Lines changed: 673 additions & 45 deletions
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/* eslint-disable no-unused-expressions */
2+
import { fixture, expect, html } from '@open-wc/testing';
3+
import { SinonSpy, spy } from 'sinon';
4+
import { DeleteDialog } from './delete-lnodetype-dialog.js';
5+
6+
customElements.define('delete-dialog', DeleteDialog);
7+
8+
describe('DeleteDialog', () => {
9+
let deleteDialog: DeleteDialog;
10+
let confirmSpy: SinonSpy;
11+
12+
beforeEach(async () => {
13+
confirmSpy = spy();
14+
15+
deleteDialog = await fixture(
16+
html`<delete-dialog
17+
.lnodeTypeId=${'MMXU$oscd$_c53e78191fabefa3'}
18+
.onConfirm=${confirmSpy}
19+
></delete-dialog>`
20+
);
21+
});
22+
23+
afterEach(async () => {
24+
deleteDialog.remove();
25+
});
26+
27+
it('displays the correct LNodeType ID in the message', () => {
28+
deleteDialog.show();
29+
expect(deleteDialog.shadowRoot?.textContent).to.include(
30+
'MMXU$oscd$_c53e78191fabefa3'
31+
);
32+
expect(deleteDialog.shadowRoot?.textContent).to.include('Confirm delete');
33+
expect(deleteDialog.shadowRoot?.textContent).to.include(
34+
'This action may have severe consequences'
35+
);
36+
});
37+
38+
it('should call onConfirm and close when Delete button is clicked', async () => {
39+
deleteDialog.show();
40+
await deleteDialog.updateComplete;
41+
await deleteDialog.dialog.updateComplete;
42+
43+
const buttons =
44+
deleteDialog.shadowRoot?.querySelectorAll('md-outlined-button');
45+
const deleteButton = Array.from(buttons || []).find(
46+
btn => btn.textContent?.trim() === 'Delete'
47+
) as HTMLElement;
48+
49+
expect(deleteButton).to.exist;
50+
deleteButton.click();
51+
await deleteDialog.updateComplete;
52+
await deleteDialog.dialog.updateComplete;
53+
54+
expect(confirmSpy.callCount).to.equal(1);
55+
});
56+
57+
it('should update displayed ID when lnodeTypeId property changes', async () => {
58+
deleteDialog.show();
59+
await deleteDialog.updateComplete;
60+
expect(deleteDialog.shadowRoot?.textContent).to.include(
61+
'MMXU$oscd$_c53e78191fabefa3'
62+
);
63+
64+
deleteDialog.lnodeTypeId = 'LLN0$oscd$_85c7ffbe25d80e63';
65+
await deleteDialog.updateComplete;
66+
expect(deleteDialog.shadowRoot?.textContent).to.include(
67+
'LLN0$oscd$_85c7ffbe25d80e63'
68+
);
69+
expect(deleteDialog.shadowRoot?.textContent).to.not.include(
70+
'MMXU$oscd$_c53e78191fabefa3'
71+
);
72+
});
73+
74+
it('should not call onConfirm when dialog is cancelled', async () => {
75+
deleteDialog.show();
76+
await deleteDialog.updateComplete;
77+
78+
const buttons =
79+
deleteDialog.shadowRoot?.querySelectorAll('md-outlined-button');
80+
const cancelButton = Array.from(buttons || []).find(
81+
btn => btn.textContent?.trim() === 'Cancel'
82+
) as HTMLElement;
83+
84+
cancelButton.click();
85+
await deleteDialog.updateComplete;
86+
87+
expect(confirmSpy.callCount).to.equal(0);
88+
});
89+
});
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/* eslint-disable @typescript-eslint/no-unused-vars */
2+
import { ScopedElementsMixin } from '@open-wc/scoped-elements/lit-element.js';
3+
import { LitElement, html, css } from 'lit';
4+
import { query, property } from 'lit/decorators.js';
5+
import { MdDialog } from '@scopedelement/material-web/dialog/dialog.js';
6+
import { MdOutlinedButton } from '@scopedelement/material-web/button/MdOutlinedButton.js';
7+
8+
export class DeleteDialog extends ScopedElementsMixin(LitElement) {
9+
static scopedElements = {
10+
'md-dialog': MdDialog,
11+
'md-outlined-button': MdOutlinedButton,
12+
};
13+
14+
@property()
15+
onConfirm!: () => void;
16+
17+
@property()
18+
lnodeTypeId: string = '';
19+
20+
@query('md-dialog')
21+
dialog!: MdDialog;
22+
23+
get open() {
24+
return this.dialog?.open ?? false;
25+
}
26+
27+
show() {
28+
this.dialog?.show();
29+
}
30+
31+
close() {
32+
this.dialog?.close();
33+
}
34+
35+
private handleCancel() {
36+
this.close();
37+
}
38+
39+
private handleConfirm() {
40+
this.onConfirm();
41+
this.close();
42+
}
43+
44+
render() {
45+
return html`
46+
<md-dialog>
47+
<div slot="headline">Confirm delete</div>
48+
<div slot="content" class="delete-content">
49+
Are you sure you want to delete Logical Node Type ${this.lnodeTypeId}?
50+
This action may have severe consequences.
51+
</div>
52+
<div slot="actions">
53+
<md-outlined-button class="button close" @click="${this.handleCancel}"
54+
>Cancel</md-outlined-button
55+
>
56+
<md-outlined-button
57+
class="button delete"
58+
@click="${this.handleConfirm}"
59+
>Delete</md-outlined-button
60+
>
61+
</div>
62+
</md-dialog>
63+
`;
64+
}
65+
66+
static styles = css`
67+
* {
68+
--md-sys-color-primary: var(--oscd-primary);
69+
--md-sys-color-secondary: var(--oscd-secondary);
70+
--md-sys-typescale-body-large-font: var(--oscd-theme-text-font);
71+
--md-outlined-text-field-input-text-color: var(--oscd-base01);
72+
73+
--md-sys-color-surface: var(--oscd-base3);
74+
--md-sys-color-on-surface: var(--oscd-base00);
75+
--md-sys-color-on-primary: var(--oscd-base2);
76+
--md-sys-color-on-surface-variant: var(--oscd-base00);
77+
--md-menu-container-color: var(--oscd-base3);
78+
font-family: var(--oscd-theme-text-font);
79+
--md-sys-color-surface-container-highest: var(--oscd-base2);
80+
--md-dialog-container-color: var(--oscd-base3);
81+
font-family: var(--oscd-theme-text-font, 'Roboto');
82+
}
83+
84+
md-outlined-button {
85+
text-transform: uppercase;
86+
}
87+
88+
.delete-content {
89+
display: flex;
90+
flex-direction: column;
91+
gap: 12px;
92+
}
93+
94+
.button.close {
95+
--md-outlined-button-label-text-color: var(--oscd-accent-red);
96+
--md-outlined-button-hover-label-text-color: var(--oscd-accent-red);
97+
}
98+
99+
.button.delete {
100+
--md-outlined-button-label-text-color: var(--oscd-accent-red);
101+
--md-outlined-button-hover-label-text-color: var(--oscd-accent-red);
102+
}
103+
`;
104+
}

foundation/utils.spec.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/* eslint-disable no-unused-expressions */
2+
import { expect } from '@open-wc/testing';
3+
import { removeDOsNotInSelection } from './utils.js';
4+
5+
describe('foundation/utils', () => {
6+
describe('removeDOsNotInSelection', () => {
7+
let lNodeType: Element;
8+
9+
beforeEach(() => {
10+
const doc = new DOMParser().parseFromString(
11+
`<LNodeType id="TestLNodeType" lnClass="MMXU">
12+
<DO name="Beh" type="ENS_1"/>
13+
<DO name="A" type="WYE_1"/>
14+
<DO name="PhV" type="WYE_2"/>
15+
</LNodeType>`,
16+
'application/xml'
17+
);
18+
lNodeType = doc.documentElement;
19+
});
20+
21+
it('removes DOs not in selection', () => {
22+
const selection = {
23+
Beh: {},
24+
PhV: {},
25+
};
26+
27+
const result = removeDOsNotInSelection(lNodeType, selection);
28+
29+
const remainingDOs = Array.from(result.querySelectorAll('DO')).map(
30+
doElement => doElement.getAttribute('name')
31+
);
32+
33+
expect(remainingDOs).to.have.lengthOf(2);
34+
expect(remainingDOs).to.include('Beh');
35+
expect(remainingDOs).to.include('PhV');
36+
expect(remainingDOs).to.not.include('A');
37+
});
38+
39+
it('returns a clone without modifying the original', () => {
40+
const selection = {
41+
Beh: {},
42+
};
43+
44+
const originalDOCount = lNodeType.querySelectorAll('DO').length;
45+
const result = removeDOsNotInSelection(lNodeType, selection);
46+
47+
// Original should be unchanged
48+
expect(lNodeType.querySelectorAll('DO')).to.have.lengthOf(
49+
originalDOCount
50+
);
51+
52+
// Result should have fewer DOs
53+
expect(result.querySelectorAll('DO')).to.have.lengthOf(1);
54+
});
55+
56+
it('removes all DOs when selection is empty', () => {
57+
const selection = {};
58+
59+
const result = removeDOsNotInSelection(lNodeType, selection);
60+
61+
expect(result.querySelectorAll('DO')).to.have.lengthOf(0);
62+
});
63+
64+
it('keeps all DOs when all are in selection', () => {
65+
const selection = {
66+
Beh: {},
67+
A: {},
68+
PhV: {},
69+
};
70+
71+
const result = removeDOsNotInSelection(lNodeType, selection);
72+
73+
expect(result.querySelectorAll('DO')).to.have.lengthOf(3);
74+
});
75+
76+
it('preserves LNodeType attributes', () => {
77+
const selection = {
78+
Beh: {},
79+
};
80+
81+
const result = removeDOsNotInSelection(lNodeType, selection);
82+
83+
expect(result.getAttribute('id')).to.equal('TestLNodeType');
84+
expect(result.getAttribute('lnClass')).to.equal('MMXU');
85+
expect(result.tagName).to.equal('LNodeType');
86+
});
87+
});
88+
});

foundation/utils.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,33 @@ export function filterSelection(
3939

4040
return filteredTree;
4141
}
42+
43+
/**
44+
* Creates a clone of an LNodeType with only the DOs that are present in the selection.
45+
* DOs not included in the selection are removed from the cloned element.
46+
* @param lNodeType - The LNodeType element to filter
47+
* @param selection - The tree selection containing the DO names to keep
48+
* @returns A cloned LNodeType element containing only the selected DOs
49+
*/
50+
export function removeDOsNotInSelection(
51+
lNodeType: Element,
52+
selection: TreeSelection
53+
): Element {
54+
const clonedLNodeType = lNodeType.cloneNode(true) as Element;
55+
56+
const dosToRemove: Element[] = [];
57+
Array.from(clonedLNodeType.querySelectorAll(':scope > DO')).forEach(
58+
doElement => {
59+
const doName = doElement.getAttribute('name');
60+
if (doName && !selection[doName]) {
61+
dosToRemove.push(doElement);
62+
}
63+
}
64+
);
65+
66+
dosToRemove.forEach(doElement => {
67+
clonedLNodeType.removeChild(doElement);
68+
});
69+
70+
return clonedLNodeType;
71+
}

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
2-
"name": "@openscd/oscd-template-generator",
3-
"description": "OpenSCD plugin for creating new DataTypeTemplates",
2+
"name": "@com-pas/scl-template-update",
3+
"description": "OpenSCD plugin for updating DataTypeTemplates",
44
"license": "Apache-2.0",
5-
"author": "oscd-template-generator",
5+
"author": "CoMPAS",
66
"version": "0.0.0",
77
"scripts": {
88
"prepare": "npm run build",

0 commit comments

Comments
 (0)