Skip to content

Commit 64a27d5

Browse files
feat(foundation): allow dynamic wizards (openscd#471)
* feat(foundation): allow dynamic wizards * test: adapt tests
1 parent 3d5c5bd commit 64a27d5

31 files changed

+236
-128
lines changed

src/Wizarding.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import {
33
ifImplemented,
44
LitElementConstructor,
55
Mixin,
6-
Wizard,
76
WizardEvent,
7+
WizardFactory,
88
} from './foundation.js';
99

1010
import './wizard-dialog.js';
@@ -18,13 +18,12 @@ export function Wizarding<TBase extends LitElementConstructor>(Base: TBase) {
1818
class WizardingElement extends Base {
1919
/** FIFO queue of [[`Wizard`]]s to display. */
2020
@internalProperty()
21-
workflow: Wizard[] = [];
21+
workflow: WizardFactory[] = [];
2222

2323
@query('wizard-dialog') wizardUI!: WizardDialog;
2424

2525
private onWizard(we: WizardEvent) {
2626
const wizard = we.detail.wizard;
27-
if (wizard?.length === 0) return;
2827
if (wizard === null) this.workflow.shift();
2928
else if (we.detail.subwizard) this.workflow.unshift(wizard);
3029
else this.workflow.push(wizard);
@@ -49,7 +48,7 @@ export function Wizarding<TBase extends LitElementConstructor>(Base: TBase) {
4948

5049
render(): TemplateResult {
5150
return html`${ifImplemented(super.render())}
52-
<wizard-dialog .wizard=${this.workflow[0] ?? []}></wizard-dialog>`;
51+
<wizard-dialog .wizard=${this.workflow[0]?.() ?? []}></wizard-dialog>`;
5352
}
5453
}
5554

src/foundation.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ export type WizardInput =
142142
| Select
143143
| WizardSelect;
144144

145-
export type WizardAction = EditorAction | (() => Wizard);
145+
export type WizardAction = EditorAction | WizardFactory;
146146

147147
/** @returns [[`EditorAction`]]s to dispatch on [[`WizardDialog`]] commit. */
148148
export type WizardActor = (
@@ -151,10 +151,10 @@ export type WizardActor = (
151151
list?: List | null
152152
) => WizardAction[];
153153

154-
export function isWizard(
155-
wizardAction: WizardAction
156-
): wizardAction is () => Wizard {
157-
return typeof wizardAction === 'function';
154+
export function isWizardFactory(
155+
maybeFactory: WizardAction | Wizard | null
156+
): maybeFactory is WizardFactory {
157+
return typeof maybeFactory === 'function';
158158
}
159159

160160
/** @returns the validity of `input` depending on type. */
@@ -203,17 +203,30 @@ export interface WizardPage {
203203
element?: Element;
204204
}
205205
export type Wizard = WizardPage[];
206+
export type WizardFactory = () => Wizard;
206207

207208
/** If `wizard === null`, close the current wizard, else queue `wizard`. */
208209
export interface WizardDetail {
209-
wizard: Wizard | null;
210+
wizard: WizardFactory | null;
210211
subwizard?: boolean;
211212
}
212213
export type WizardEvent = CustomEvent<WizardDetail>;
213214
export function newWizardEvent(
214-
wizard: Wizard | null = null,
215+
wizardOrFactory?: Wizard | WizardFactory,
215216
eventInitDict?: CustomEventInit<Partial<WizardDetail>>
216217
): WizardEvent {
218+
if (!wizardOrFactory)
219+
return new CustomEvent<WizardDetail>('wizard', {
220+
bubbles: true,
221+
composed: true,
222+
...eventInitDict,
223+
detail: { wizard: null, ...eventInitDict?.detail },
224+
});
225+
226+
const wizard = isWizardFactory(wizardOrFactory)
227+
? wizardOrFactory
228+
: () => wizardOrFactory;
229+
217230
return new CustomEvent<WizardDetail>('wizard', {
218231
bubbles: true,
219232
composed: true,

src/wizard-dialog.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {
2525
newWizardEvent,
2626
WizardActor,
2727
wizardInputSelector,
28-
isWizard,
28+
isWizardFactory,
2929
checkValidity,
3030
reportValidity,
3131
Delete,
@@ -143,8 +143,8 @@ export class WizardDialog extends LitElement {
143143
this.dispatchEvent(newWizardEvent());
144144
}
145145
wizardActions.forEach(wa =>
146-
isWizard(wa)
147-
? this.dispatchEvent(newWizardEvent(wa()))
146+
isWizardFactory(wa)
147+
? this.dispatchEvent(newWizardEvent(wa))
148148
: this.dispatchEvent(newActionEvent(wa))
149149
);
150150
return true;

test/integration/editors/substation/guess-wizarding-editing.test.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ describe('guess-wizard-integration', () => {
1616
.then(str => new DOMParser().parseFromString(str, 'application/xml'));
1717
validSCL.querySelector('Substation')!.innerHTML = '';
1818
element = <MockWizard>await fixture(html`<mock-wizard></mock-wizard>`);
19-
element.workflow.push(guessVoltageLevel(validSCL));
19+
20+
const wizard = guessVoltageLevel(validSCL);
21+
element.workflow.push(() => wizard);
2022
await element.requestUpdate();
2123
});
2224

@@ -78,8 +80,11 @@ describe('guess-wizarding-editing-integration', () => {
7880
element = <MockWizardEditor>(
7981
await fixture(html`<mock-wizard-editor></mock-wizard-editor>`)
8082
);
81-
element.workflow.push(guessVoltageLevel(validSCL));
83+
84+
const wizard = guessVoltageLevel(validSCL);
85+
element.workflow.push(() => wizard);
8286
await element.requestUpdate();
87+
8388
(<HTMLElement>(
8489
element.wizardUI.dialog!.querySelector(
8590
'#ctlModelList > mwc-check-list-item:nth-child(5)'

test/integration/editors/substation/lnodewizard.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ describe('lnodewizard', () => {
2121
element = <MockWizardEditor>(
2222
await fixture(html`<mock-wizard-editor></mock-wizard-editor>`)
2323
);
24-
element.workflow.push(lNodeWizard(doc.querySelector('Bay')!));
24+
const wizard = lNodeWizard(doc.querySelector('Bay')!);
25+
element.workflow.push(() => wizard);
2526
await element.requestUpdate();
2627
});
2728

test/integration/wizards/dataset-wizarding-editing-integration.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ describe('dataset wizards', () => {
2525
const wizard = editDataSetWizard(
2626
doc.querySelector('IED[name="IED2"] DataSet[name="GooseDataSet1"]')!
2727
);
28-
element.workflow.push(wizard);
28+
element.workflow.push(() => wizard);
2929
await element.requestUpdate();
3030
primaryAction = <HTMLElement>(
3131
element.wizardUI.dialog?.querySelector(

test/integration/wizards/fcda-wizarding-editing-integration.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ describe('FCDA editing wizarding integration', () => {
1919
.then(str => new DOMParser().parseFromString(str, 'application/xml'));
2020

2121
const wizard = createFCDAsWizard(doc.querySelector('DataSet')!);
22-
element.workflow.push(wizard!);
22+
element.workflow.push(() => wizard!);
2323
await element.requestUpdate();
2424

2525
finder = element.wizardUI.dialog!.querySelector<FinderList>('finder-list')!;

test/integration/wizards/gse-wizarding-editing-integration.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ describe('gse wizarding editing integration', () => {
2525
const wizard = editGseWizard(
2626
doc.querySelector('GSE[ldInst="CircuitBreaker_CB1"][cbName="GCB"]')!
2727
);
28-
element.workflow.push(wizard);
28+
element.workflow.push(() => wizard);
2929
await element.requestUpdate();
3030
primaryAction = <HTMLElement>(
3131
element.wizardUI.dialog?.querySelector(

test/integration/wizards/gsecontrolwizarding-editing.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ describe('gsecontrol wizarding editing integration', () => {
2929

3030
beforeEach(async () => {
3131
const wizard = selectGseControlWizard(doc.documentElement);
32-
element.workflow.push(wizard);
32+
element.workflow.push(() => wizard);
3333
await element.requestUpdate();
3434
gseControlList = <FilteredList>(
3535
element.wizardUI.dialog?.querySelector('filtered-list')
@@ -66,7 +66,7 @@ describe('gsecontrol wizarding editing integration', () => {
6666
beforeEach(async () => {
6767
gseControl = doc.querySelector('GSEControl[name="GCB"]')!;
6868
const wizard = editGseControlWizard(gseControl);
69-
element.workflow.push(wizard);
69+
element.workflow.push(() => wizard);
7070
await element.requestUpdate();
7171
nameField = element.wizardUI.dialog!.querySelector(
7272
'wizard-textfield[label="name"]'
@@ -157,7 +157,7 @@ describe('gsecontrol wizarding editing integration', () => {
157157
beforeEach(async () => {
158158
gseControl = doc.querySelector('GSEControl[name="GCB2"]')!;
159159
const wizard = editGseControlWizard(gseControl);
160-
element.workflow.push(wizard);
160+
element.workflow.push(() => wizard);
161161
await element.requestUpdate();
162162
});
163163

@@ -177,7 +177,7 @@ describe('gsecontrol wizarding editing integration', () => {
177177
'IED[name="IED2"] GSEControl[name="GCB"]'
178178
)!;
179179
const wizard = editGseControlWizard(gseControl);
180-
element.workflow.push(wizard);
180+
element.workflow.push(() => wizard);
181181
await element.requestUpdate();
182182
});
183183

test/unit/Wizarding.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,23 @@ describe('WizardingElement', () => {
1919
it('shows no wizard-dialog', async () =>
2020
await expect(element).shadowDom.to.be.empty);
2121

22-
it('adds a wizard to the workflow on non-null WizardEvent', () => {
22+
it('adds a wizard factory to the workflow on non-null WizardEvent', () => {
2323
element.dispatchEvent(newWizardEvent([{ title: 'Test Page 1' }]));
2424
expect(element).property('workflow').to.have.lengthOf(1);
2525
});
2626

27-
describe('with a wizard in its workflow', () => {
27+
describe('with a wizard factory in its workflow', () => {
2828
beforeEach(async () => {
2929
element.dispatchEvent(newWizardEvent([{ title: 'Test Page 1' }]));
3030
await element.updateComplete;
3131
});
3232

33-
it('removes the wizard on receiving a null WizardEvent', () => {
33+
it('removes the wizard factory on receiving a null WizardEvent', () => {
3434
element.dispatchEvent(newWizardEvent());
3535
expect(element).property('workflow').to.be.empty;
3636
});
3737

38-
it('removes the wizard on wizard-dialog "close" action', async () => {
38+
it('removes the wizard factory on wizard-dialog "close" action', async () => {
3939
await (<HTMLElement>(
4040
element
4141
.shadowRoot!.querySelector('wizard-dialog')!

0 commit comments

Comments
 (0)