From 32b14585c197ad8d527a21811eebc5df42504aad Mon Sep 17 00:00:00 2001
From: Jakob Vogelsang <jakob-vogelsang@posteo.de>
Date: Wed, 18 May 2022 23:23:42 +0200
Subject: [PATCH] feat(wizards/eqsubfunction): add create wizard

---
 src/editors/substation/eq-function-editor.ts  |  65 ++++++++++-
 .../substation/eq-sub-function-editor.ts      |  65 ++++++++++-
 src/wizards/eqsubfunction.ts                  |  56 ++++++++++
 src/wizards/function.ts                       |   4 +-
 src/wizards/wizard-library.ts                 |   3 +-
 .../eq-function-wizarding-editing.test.ts     |  91 +++++++++++++++
 ...-function-editor-wizarding-editing.test.ts |  91 +++++++++++++++
 .../eq-function-editor.test.snap.js           |  70 ++++++++++++
 .../eq-sub-function-editor.test.snap.js       |  70 ++++++++++++
 .../__snapshots__/eqsubfunction.test.snap.js  |  52 +++++++++
 test/unit/wizards/eqsubfunction.test.ts       | 104 ++++++++++++++++++
 11 files changed, 667 insertions(+), 4 deletions(-)
 create mode 100644 src/wizards/eqsubfunction.ts
 create mode 100644 test/integration/editors/substation/eq-function-wizarding-editing.test.ts
 create mode 100644 test/integration/editors/substation/eq-sub-function-editor-wizarding-editing.test.ts
 create mode 100644 test/unit/wizards/__snapshots__/eqsubfunction.test.snap.js
 create mode 100644 test/unit/wizards/eqsubfunction.test.ts

diff --git a/src/editors/substation/eq-function-editor.ts b/src/editors/substation/eq-function-editor.ts
index 204a7d4a3b..f3807ed962 100644
--- a/src/editors/substation/eq-function-editor.ts
+++ b/src/editors/substation/eq-function-editor.ts
@@ -5,11 +5,34 @@ import {
   property,
   customElement,
   state,
+  query,
 } from 'lit-element';
+import { translate } from 'lit-translate';
+
+import '@material/mwc-icon-button';
+import '@material/mwc-list/mwc-list-item';
+import '@material/mwc-menu';
+import { IconButton } from '@material/mwc-icon-button';
+import { ListItem } from '@material/mwc-list/mwc-list-item';
+import { Menu } from '@material/mwc-menu';
 
 import '../../action-pane.js';
 import './eq-sub-function-editor.js';
-import { getChildElementsByTagName } from '../../foundation.js';
+import {
+  getChildElementsByTagName,
+  newWizardEvent,
+  SCLTag,
+  tags,
+} from '../../foundation.js';
+import { emptyWizard, wizards } from '../../wizards/wizard-library.js';
+
+function childTags(element: Element | null | undefined): SCLTag[] {
+  if (!element) return [];
+
+  return tags[<SCLTag>element.tagName].children.filter(
+    child => wizards[child].create !== emptyWizard
+  );
+}
 
 /** Pane rendering `EqFunction` element with its children */
 @customElement('eq-function-editor')
@@ -26,6 +49,19 @@ export class EqFunctionEditor extends LitElement {
     return `${name}${desc ? ` - ${desc}` : ''}${type ? ` (${type})` : ''}`;
   }
 
+  @query('mwc-menu') addMenu!: Menu;
+  @query('mwc-icon-button[icon="playlist_add"]') addButton!: IconButton;
+
+  private openCreateWizard(tagName: string): void {
+    const wizard = wizards[<SCLTag>tagName].create(this.element!);
+
+    if (wizard) this.dispatchEvent(newWizardEvent(wizard));
+  }
+
+  firstUpdated(): void {
+    this.addMenu.anchor = <HTMLElement>this.addButton;
+  }
+
   private renderEqSubFunctions(): TemplateResult {
     const eqSubFunctions = getChildElementsByTagName(
       this.element,
@@ -39,12 +75,39 @@ export class EqFunctionEditor extends LitElement {
     )}`;
   }
 
+  private renderAddButtons(): TemplateResult[] {
+    return childTags(this.element).map(
+      child =>
+        html`<mwc-list-item value="${child}"
+          ><span>${child}</span></mwc-list-item
+        >`
+    );
+  }
+
   render(): TemplateResult {
     return html`<action-pane
       label="${this.header}"
       icon="functions"
       secondary
       highlighted
+      ><abbr
+        slot="action"
+        style="position:relative;"
+        title="${translate('add')}"
+      >
+        <mwc-icon-button
+          icon="playlist_add"
+          @click=${() => (this.addMenu.open = true)}
+        ></mwc-icon-button
+        ><mwc-menu
+          corner="BOTTOM_RIGHT"
+          menuCorner="END"
+          @selected=${(e: Event) => {
+            const tagName = (<ListItem>(<Menu>e.target).selected).value;
+            this.openCreateWizard(tagName);
+          }}
+          >${this.renderAddButtons()}</mwc-menu
+        ></abbr
       >${this.renderEqSubFunctions()}</action-pane
     >`;
   }
diff --git a/src/editors/substation/eq-sub-function-editor.ts b/src/editors/substation/eq-sub-function-editor.ts
index 4981a12260..a80108baa7 100644
--- a/src/editors/substation/eq-sub-function-editor.ts
+++ b/src/editors/substation/eq-sub-function-editor.ts
@@ -5,11 +5,34 @@ import {
   property,
   customElement,
   state,
+  query,
 } from 'lit-element';
 
+import { translate } from 'lit-translate';
+
+import '@material/mwc-icon-button';
+import '@material/mwc-list/mwc-list-item';
+import '@material/mwc-menu';
+import { IconButton } from '@material/mwc-icon-button';
+import { ListItem } from '@material/mwc-list/mwc-list-item';
+import { Menu } from '@material/mwc-menu';
+
 import '../../action-pane.js';
-import { getChildElementsByTagName } from '../../foundation.js';
+import {
+  getChildElementsByTagName,
+  newWizardEvent,
+  SCLTag,
+  tags,
+} from '../../foundation.js';
+import { emptyWizard, wizards } from '../../wizards/wizard-library.js';
+
+function childTags(element: Element | null | undefined): SCLTag[] {
+  if (!element) return [];
 
+  return tags[<SCLTag>element.tagName].children.filter(
+    child => wizards[child].create !== emptyWizard
+  );
+}
 /** Pane rendering `EqSubFunction` element with its children */
 @customElement('eq-sub-function-editor')
 export class EqSubFunctionEditor extends LitElement {
@@ -25,6 +48,19 @@ export class EqSubFunctionEditor extends LitElement {
     return `${name}${desc ? ` - ${desc}` : ''}${type ? ` (${type})` : ''}`;
   }
 
+  @query('mwc-menu') addMenu!: Menu;
+  @query('mwc-icon-button[icon="playlist_add"]') addButton!: IconButton;
+
+  private openCreateWizard(tagName: string): void {
+    const wizard = wizards[<SCLTag>tagName].create(this.element!);
+
+    if (wizard) this.dispatchEvent(newWizardEvent(wizard));
+  }
+
+  firstUpdated(): void {
+    this.addMenu.anchor = <HTMLElement>this.addButton;
+  }
+
   private renderEqSubFunctions(): TemplateResult {
     const eqSubFunctions = getChildElementsByTagName(
       this.element,
@@ -38,8 +74,35 @@ export class EqSubFunctionEditor extends LitElement {
     )}`;
   }
 
+  private renderAddButtons(): TemplateResult[] {
+    return childTags(this.element).map(
+      child =>
+        html`<mwc-list-item value="${child}"
+          ><span>${child}</span></mwc-list-item
+        >`
+    );
+  }
+
   render(): TemplateResult {
     return html`<action-pane label="${this.header}" icon="functions" secondary
+      ><abbr
+        slot="action"
+        style="position:relative;"
+        title="${translate('add')}"
+      >
+        <mwc-icon-button
+          icon="playlist_add"
+          @click=${() => (this.addMenu.open = true)}
+        ></mwc-icon-button
+        ><mwc-menu
+          corner="BOTTOM_RIGHT"
+          menuCorner="END"
+          @selected=${(e: Event) => {
+            const tagName = (<ListItem>(<Menu>e.target).selected).value;
+            this.openCreateWizard(tagName);
+          }}
+          >${this.renderAddButtons()}</mwc-menu
+        ></abbr
       >${this.renderEqSubFunctions()}</action-pane
     >`;
   }
diff --git a/src/wizards/eqsubfunction.ts b/src/wizards/eqsubfunction.ts
new file mode 100644
index 0000000000..4763fdb1c1
--- /dev/null
+++ b/src/wizards/eqsubfunction.ts
@@ -0,0 +1,56 @@
+import { get } from 'lit-translate';
+
+import {
+  createElement,
+  getValue,
+  Wizard,
+  WizardActor,
+  WizardInputElement,
+} from '../foundation.js';
+import { contentFunctionWizard } from './function.js';
+
+function createEqSubFunctionAction(parent: Element): WizardActor {
+  return (inputs: WizardInputElement[]) => {
+    const eqSubFunctionAttrs: Record<string, string | null> = {};
+    const eqSubFunctionKeys = ['name', 'desc', 'type'];
+    eqSubFunctionKeys.forEach(key => {
+      eqSubFunctionAttrs[key] = getValue(inputs.find(i => i.label === key)!);
+    });
+
+    const eqSubFunction = createElement(
+      parent.ownerDocument,
+      'EqSubFunction',
+      eqSubFunctionAttrs
+    );
+
+    return [{ new: { parent, element: eqSubFunction } }];
+  };
+}
+
+export function createEqSubFunctionWizard(parent: Element): Wizard {
+  const name = '';
+  const desc = null;
+  const type = null;
+  const reservedNames = Array.from(
+    parent.querySelectorAll('EqSubFunction')
+  ).map(eqSubFunction => eqSubFunction.getAttribute('name')!);
+
+  return [
+    {
+      title: get('wizard.title.add', { tagName: 'EqSubFunction' }),
+      primary: {
+        icon: 'save',
+        label: get('save'),
+        action: createEqSubFunctionAction(parent),
+      },
+      content: [
+        ...contentFunctionWizard({
+          name,
+          desc,
+          type,
+          reservedNames,
+        }),
+      ],
+    },
+  ];
+}
diff --git a/src/wizards/function.ts b/src/wizards/function.ts
index e1115e358a..a828a8e755 100644
--- a/src/wizards/function.ts
+++ b/src/wizards/function.ts
@@ -16,7 +16,9 @@ interface ContentOptions {
   reservedNames: string[];
 }
 
-function contentFunctionWizard(content: ContentOptions): TemplateResult[] {
+export function contentFunctionWizard(
+  content: ContentOptions
+): TemplateResult[] {
   return [
     html`<wizard-textfield
       label="name"
diff --git a/src/wizards/wizard-library.ts b/src/wizards/wizard-library.ts
index d628e62141..60d4bf74d2 100644
--- a/src/wizards/wizard-library.ts
+++ b/src/wizards/wizard-library.ts
@@ -25,6 +25,7 @@ import { editTrgOpsWizard } from './trgops.js';
 import { createDaWizard } from './da.js';
 import { editDAIWizard } from './dai.js';
 import { createFunctionWizard } from './function.js';
+import { createEqSubFunctionWizard } from './eqsubfunction.js';
 
 type SclElementWizard = (
   element: Element,
@@ -192,7 +193,7 @@ export const wizards: Record<
   },
   EqSubFunction: {
     edit: emptyWizard,
-    create: emptyWizard,
+    create: createEqSubFunctionWizard,
   },
   ExtRef: {
     edit: emptyWizard,
diff --git a/test/integration/editors/substation/eq-function-wizarding-editing.test.ts b/test/integration/editors/substation/eq-function-wizarding-editing.test.ts
new file mode 100644
index 0000000000..eb29d5bba4
--- /dev/null
+++ b/test/integration/editors/substation/eq-function-wizarding-editing.test.ts
@@ -0,0 +1,91 @@
+import { fixture, html, expect } from '@open-wc/testing';
+
+import '../../../mock-wizard-editor.js';
+import { MockWizardEditor } from '../../../mock-wizard-editor.js';
+
+import '../../../../src/editors/substation/eq-function-editor.js';
+import { EqFunctionEditor } from '../../../../src/editors/substation/eq-function-editor.js';
+import { WizardTextField } from '../../../../src/wizard-textfield.js';
+
+describe('eq-function-editor wizarding editing integration', () => {
+  describe('open create wizard for element EqSubFunction', () => {
+    let doc: XMLDocument;
+    let parent: MockWizardEditor;
+    let element: EqFunctionEditor | null;
+
+    let nameField: WizardTextField;
+    let primaryAction: HTMLElement;
+
+    beforeEach(async () => {
+      doc = await fetch('/test/testfiles/zeroline/functions.scd')
+        .then(response => response.text())
+        .then(str => new DOMParser().parseFromString(str, 'application/xml'));
+      parent = <MockWizardEditor>(
+        await fixture(
+          html`<mock-wizard-editor
+            ><eq-function-editor
+              .element=${doc.querySelector(
+                'ConductingEquipment[name="QA1"] > EqFunction'
+              )}
+            ></eq-function-editor
+          ></mock-wizard-editor>`
+        )
+      );
+
+      element = parent.querySelector('eq-function-editor');
+
+      (<HTMLElement>(
+        element?.shadowRoot?.querySelector(
+          'mwc-list-item[value="EqSubFunction"]'
+        )
+      )).click();
+      await parent.updateComplete;
+
+      nameField = <WizardTextField>(
+        parent.wizardUI.dialog?.querySelector('wizard-textfield[label="name"]')
+      );
+
+      primaryAction = <HTMLElement>(
+        parent.wizardUI.dialog?.querySelector(
+          'mwc-button[slot="primaryAction"]'
+        )
+      );
+    });
+
+    it('does not add EqSubFunction if name attribute is not unique', async () => {
+      expect(
+        doc.querySelector(
+          'ConductingEquipment[name="QA1"] EqSubFunction[name="myEqSubFunc"]'
+        )
+      ).to.exist;
+
+      nameField.value = 'myEqSubFunc';
+      primaryAction.click();
+      await parent.updateComplete;
+
+      expect(
+        doc.querySelectorAll(
+          'ConductingEquipment[name="QA1"] EqSubFunction[name="myEqSubFunc"]'
+        ).length
+      ).to.equal(1);
+    });
+
+    it('does add EqFunction if name attribute is unique', async () => {
+      expect(
+        doc.querySelector(
+          'ConductingEquipment[name="QA1"] EqSubFunction[name="someNewEqSubFunction"]'
+        )
+      ).to.not.exist;
+
+      nameField.value = 'someNewEqSubFunction';
+      await parent.updateComplete;
+      primaryAction.click();
+
+      expect(
+        doc.querySelector(
+          'ConductingEquipment[name="QA1"] EqSubFunction[name="someNewEqSubFunction"]'
+        )
+      ).to.exist;
+    });
+  });
+});
diff --git a/test/integration/editors/substation/eq-sub-function-editor-wizarding-editing.test.ts b/test/integration/editors/substation/eq-sub-function-editor-wizarding-editing.test.ts
new file mode 100644
index 0000000000..f4b12ce3f4
--- /dev/null
+++ b/test/integration/editors/substation/eq-sub-function-editor-wizarding-editing.test.ts
@@ -0,0 +1,91 @@
+import { fixture, html, expect } from '@open-wc/testing';
+
+import '../../../mock-wizard-editor.js';
+import { MockWizardEditor } from '../../../mock-wizard-editor.js';
+
+import '../../../../src/editors/substation/eq-sub-function-editor.js';
+import { EqSubFunctionEditor } from '../../../../src/editors/substation/eq-sub-function-editor.js';
+import { WizardTextField } from '../../../../src/wizard-textfield.js';
+
+describe('eq-sub-function-editor wizarding editing integration', () => {
+  describe('open create wizard for element EqSubFunction', () => {
+    let doc: XMLDocument;
+    let parent: MockWizardEditor;
+    let element: EqSubFunctionEditor | null;
+
+    let nameField: WizardTextField;
+    let primaryAction: HTMLElement;
+
+    beforeEach(async () => {
+      doc = await fetch('/test/testfiles/zeroline/functions.scd')
+        .then(response => response.text())
+        .then(str => new DOMParser().parseFromString(str, 'application/xml'));
+      parent = <MockWizardEditor>(
+        await fixture(
+          html`<mock-wizard-editor
+            ><eq-sub-function-editor
+              .element=${doc.querySelector(
+                'ConductingEquipment[name="QA1"] EqSubFunction'
+              )}
+            ></eq-sub-function-editor
+          ></mock-wizard-editor>`
+        )
+      );
+
+      element = parent.querySelector('eq-sub-function-editor');
+
+      (<HTMLElement>(
+        element?.shadowRoot?.querySelector(
+          'mwc-list-item[value="EqSubFunction"]'
+        )
+      )).click();
+      await parent.updateComplete;
+
+      nameField = <WizardTextField>(
+        parent.wizardUI.dialog?.querySelector('wizard-textfield[label="name"]')
+      );
+
+      primaryAction = <HTMLElement>(
+        parent.wizardUI.dialog?.querySelector(
+          'mwc-button[slot="primaryAction"]'
+        )
+      );
+    });
+
+    it('does not add EqSubFunction if name attribute is not unique', async () => {
+      expect(
+        doc.querySelector(
+          'ConductingEquipment[name="QA1"] EqSubFunction[name="myEqSubSubFunction"]'
+        )
+      ).to.exist;
+
+      nameField.value = 'myEqSubSubFunction';
+      primaryAction.click();
+      await parent.updateComplete;
+
+      expect(
+        doc.querySelectorAll(
+          'ConductingEquipment[name="QA1"] EqSubFunction[name="myEqSubSubFunction"]'
+        ).length
+      ).to.equal(1);
+    });
+
+    it('does add EqFunction if name attribute is unique', async () => {
+      expect(
+        doc.querySelector(
+          'ConductingEquipment[name="QA1"] EqSubFunction[name="someNewEqSubFunction"]'
+        )
+      ).to.not.exist;
+
+      nameField.value = 'someNewEqSubFunction';
+      await parent.updateComplete;
+      primaryAction.click();
+
+      expect(
+        doc.querySelector(
+          'ConductingEquipment[name="QA1"] EqSubFunction[name="someNewEqSubFunction"]'
+        )
+      ).to.exist;
+    });
+  });
+});
diff --git a/test/unit/editors/substation/__snapshots__/eq-function-editor.test.snap.js b/test/unit/editors/substation/__snapshots__/eq-function-editor.test.snap.js
index da906462c9..86aba8c6d6 100644
--- a/test/unit/editors/substation/__snapshots__/eq-function-editor.test.snap.js
+++ b/test/unit/editors/substation/__snapshots__/eq-function-editor.test.snap.js
@@ -9,6 +9,41 @@ snapshots["web component rendering EqFunction element with complete attribute se
   secondary=""
   tabindex="0"
 >
+  <abbr
+    slot="action"
+    style="position:relative;"
+    title="[add]"
+  >
+    <mwc-icon-button icon="playlist_add">
+    </mwc-icon-button>
+    <mwc-menu
+      corner="BOTTOM_RIGHT"
+      menucorner="END"
+    >
+      <mwc-list-item
+        aria-disabled="false"
+        mwc-list-item=""
+        role="menuitem"
+        tabindex="0"
+        value="LNode"
+      >
+        <span>
+          LNode
+        </span>
+      </mwc-list-item>
+      <mwc-list-item
+        aria-disabled="false"
+        mwc-list-item=""
+        role="menuitem"
+        tabindex="-1"
+        value="EqSubFunction"
+      >
+        <span>
+          EqSubFunction
+        </span>
+      </mwc-list-item>
+    </mwc-menu>
+  </abbr>
   <eq-sub-function-editor>
   </eq-sub-function-editor>
 </action-pane>
@@ -23,6 +58,41 @@ snapshots["web component rendering EqFunction element with missing desc and type
   secondary=""
   tabindex="0"
 >
+  <abbr
+    slot="action"
+    style="position:relative;"
+    title="[add]"
+  >
+    <mwc-icon-button icon="playlist_add">
+    </mwc-icon-button>
+    <mwc-menu
+      corner="BOTTOM_RIGHT"
+      menucorner="END"
+    >
+      <mwc-list-item
+        aria-disabled="false"
+        mwc-list-item=""
+        role="menuitem"
+        tabindex="0"
+        value="LNode"
+      >
+        <span>
+          LNode
+        </span>
+      </mwc-list-item>
+      <mwc-list-item
+        aria-disabled="false"
+        mwc-list-item=""
+        role="menuitem"
+        tabindex="-1"
+        value="EqSubFunction"
+      >
+        <span>
+          EqSubFunction
+        </span>
+      </mwc-list-item>
+    </mwc-menu>
+  </abbr>
   <eq-sub-function-editor>
   </eq-sub-function-editor>
 </action-pane>
diff --git a/test/unit/editors/substation/__snapshots__/eq-sub-function-editor.test.snap.js b/test/unit/editors/substation/__snapshots__/eq-sub-function-editor.test.snap.js
index 40b9949528..9038e7b37a 100644
--- a/test/unit/editors/substation/__snapshots__/eq-sub-function-editor.test.snap.js
+++ b/test/unit/editors/substation/__snapshots__/eq-sub-function-editor.test.snap.js
@@ -8,6 +8,41 @@ snapshots["web component rendering EqSubFunction element with complete attribute
   secondary=""
   tabindex="0"
 >
+  <abbr
+    slot="action"
+    style="position:relative;"
+    title="[add]"
+  >
+    <mwc-icon-button icon="playlist_add">
+    </mwc-icon-button>
+    <mwc-menu
+      corner="BOTTOM_RIGHT"
+      menucorner="END"
+    >
+      <mwc-list-item
+        aria-disabled="false"
+        mwc-list-item=""
+        role="menuitem"
+        tabindex="0"
+        value="LNode"
+      >
+        <span>
+          LNode
+        </span>
+      </mwc-list-item>
+      <mwc-list-item
+        aria-disabled="false"
+        mwc-list-item=""
+        role="menuitem"
+        tabindex="-1"
+        value="EqSubFunction"
+      >
+        <span>
+          EqSubFunction
+        </span>
+      </mwc-list-item>
+    </mwc-menu>
+  </abbr>
 </action-pane>
 `;
 /* end snapshot web component rendering EqSubFunction element with complete attribute set and existing children looks like the latest snapshot */
@@ -19,6 +54,41 @@ snapshots["web component rendering EqSubFunction element with missing desc and t
   secondary=""
   tabindex="0"
 >
+  <abbr
+    slot="action"
+    style="position:relative;"
+    title="[add]"
+  >
+    <mwc-icon-button icon="playlist_add">
+    </mwc-icon-button>
+    <mwc-menu
+      corner="BOTTOM_RIGHT"
+      menucorner="END"
+    >
+      <mwc-list-item
+        aria-disabled="false"
+        mwc-list-item=""
+        role="menuitem"
+        tabindex="0"
+        value="LNode"
+      >
+        <span>
+          LNode
+        </span>
+      </mwc-list-item>
+      <mwc-list-item
+        aria-disabled="false"
+        mwc-list-item=""
+        role="menuitem"
+        tabindex="-1"
+        value="EqSubFunction"
+      >
+        <span>
+          EqSubFunction
+        </span>
+      </mwc-list-item>
+    </mwc-menu>
+  </abbr>
   <eq-sub-function-editor>
   </eq-sub-function-editor>
 </action-pane>
diff --git a/test/unit/wizards/__snapshots__/eqsubfunction.test.snap.js b/test/unit/wizards/__snapshots__/eqsubfunction.test.snap.js
new file mode 100644
index 0000000000..4453caaea6
--- /dev/null
+++ b/test/unit/wizards/__snapshots__/eqsubfunction.test.snap.js
@@ -0,0 +1,52 @@
+/* @web/test-runner snapshot v1 */
+export const snapshots = {};
+
+snapshots["Wizards for SCL EqSubFunction element define an create wizard that looks like the the latest snapshot"] = 
+`<mwc-dialog
+  defaultaction="close"
+  heading="[wizard.title.add]"
+  open=""
+  style="--mdc-dialog-min-width:calc(100% + 0px)"
+>
+  <div id="wizard-content">
+    <wizard-textfield
+      dialoginitialfocus=""
+      helper="[scl.name]"
+      label="name"
+      required=""
+      validationmessage="[textfield.required]"
+    >
+    </wizard-textfield>
+    <wizard-textfield
+      disabled=""
+      helper="[scl.desc]"
+      label="desc"
+      nullable=""
+    >
+    </wizard-textfield>
+    <wizard-textfield
+      disabled=""
+      helper="[scl.type]"
+      label="type"
+      nullable=""
+    >
+    </wizard-textfield>
+  </div>
+  <mwc-button
+    dialogaction="close"
+    label="[cancel]"
+    slot="secondaryAction"
+    style="--mdc-theme-primary: var(--mdc-theme-error)"
+  >
+  </mwc-button>
+  <mwc-button
+    icon="save"
+    label="[save]"
+    slot="primaryAction"
+    trailingicon=""
+  >
+  </mwc-button>
+</mwc-dialog>
+`;
+/* end snapshot Wizards for SCL EqSubFunction element define an create wizard that looks like the the latest snapshot */
+
diff --git a/test/unit/wizards/eqsubfunction.test.ts b/test/unit/wizards/eqsubfunction.test.ts
new file mode 100644
index 0000000000..2419558b27
--- /dev/null
+++ b/test/unit/wizards/eqsubfunction.test.ts
@@ -0,0 +1,104 @@
+import { expect, fixture, html } from '@open-wc/testing';
+import { SinonSpy, spy } from 'sinon';
+
+import '../../mock-wizard.js';
+import { MockWizard } from '../../mock-wizard.js';
+
+import { WizardTextField } from '../../../src/wizard-textfield.js';
+import {
+  Create,
+  isCreate,
+  WizardInputElement,
+} from '../../../src/foundation.js';
+import { createEqSubFunctionWizard } from '../../../src/wizards/eqsubfunction.js';
+
+describe('Wizards for SCL EqSubFunction element', () => {
+  let doc: XMLDocument;
+  let element: MockWizard;
+  let inputs: WizardInputElement[];
+
+  let primaryAction: HTMLElement;
+
+  let actionEvent: SinonSpy;
+
+  beforeEach(async () => {
+    element = await fixture(html`<mock-wizard></mock-wizard>`);
+    doc = await fetch('test/testfiles/zeroline/functions.scd')
+      .then(response => response.text())
+      .then(str => new DOMParser().parseFromString(str, 'application/xml'));
+
+    actionEvent = spy();
+    window.addEventListener('editor-action', actionEvent);
+  });
+
+  describe('define an create wizard that', () => {
+    beforeEach(async () => {
+      const wizard = createEqSubFunctionWizard(
+        doc.querySelector('EqFunction')!
+      );
+      element.workflow.push(() => wizard);
+      await element.requestUpdate();
+
+      inputs = Array.from(element.wizardUI.inputs);
+
+      primaryAction = <HTMLElement>(
+        element.wizardUI.dialog?.querySelector(
+          'mwc-button[slot="primaryAction"]'
+        )
+      );
+
+      await element.wizardUI.requestUpdate(); // make sure wizard is rendered
+    });
+
+    it('looks like the the latest snapshot', async () =>
+      expect(element.wizardUI.dialog).dom.to.equalSnapshot());
+
+    it('does not accept empty name attribute', async () => {
+      await primaryAction.click();
+
+      expect(actionEvent).to.not.have.been.called;
+    });
+
+    it('triggers simple create action on primary action click', async () => {
+      inputs[0].value = 'someNonEmptyName';
+      await element.requestUpdate();
+      await primaryAction.click();
+
+      expect(actionEvent).to.be.calledOnce;
+      const action = actionEvent.args[0][0].detail.action;
+      expect(action).to.satisfy(isCreate);
+      const createAction = <Create>action;
+
+      expect(createAction.new.element).to.have.attribute(
+        'name',
+        'someNonEmptyName'
+      );
+      expect(createAction.new.element).to.not.have.attribute('desc');
+      expect(createAction.new.element).to.not.have.attribute('type');
+    });
+
+    it('allows to create non required attributes desc and type', async () => {
+      inputs[0].value = 'someNonEmptyName';
+
+      (<WizardTextField>inputs[1]).nullSwitch?.click();
+      (<WizardTextField>inputs[2]).nullSwitch?.click();
+      inputs[1].value = 'SomeDesc';
+      inputs[2].value = 'SomeType';
+
+      await element.requestUpdate();
+      await primaryAction.click();
+
+      expect(actionEvent).to.be.calledOnce;
+      const action = actionEvent.args[0][0].detail.action;
+      expect(action).to.satisfy(isCreate);
+      const createAction = <Create>action;
+
+      expect(createAction.new.element).to.have.attribute(
+        'name',
+        'someNonEmptyName'
+      );
+      expect(createAction.new.element).to.have.attribute('desc', 'SomeDesc');
+      expect(createAction.new.element).to.have.attribute('type', 'SomeType');
+    });
+  });
+});