Skip to content

Commit 0609e5b

Browse files
authored
refactor(wizard): rename NestedWizard, update docs #6229
ref #6166 (comment)
1 parent 96ce5ae commit 0609e5b

File tree

4 files changed

+112
-11
lines changed

4 files changed

+112
-11
lines changed

docs/arch_develop.md

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,7 @@ await tester.result(items[0].data) // Execute the actions, asserting the final r
444444

445445
Abstractly, a 'wizard' is a collection of discrete, linear steps (subroutines), where each step can potentially be dependent on prior steps, that results in some final state. Wizards are extremely common in top-level flows such as creating a new resource, deployments, or confirmation messages. For these kinds of flows, we have a shared `Wizard` class that handles the bulk of control flow and state management logic for us.
446446

447-
### Creating a Wizard (Quick Picks)
447+
### 1. `Wizard` Class
448448

449449
Create a new wizard by extending the base `Wizard` class, using the template type to specify the
450450
shape of the wizard state. All wizards have an internal `form` property that is used to assign
@@ -482,6 +482,41 @@ class ExampleWizard extends Wizard<ExampleState> {
482482
}
483483
```
484484

485+
### 2. `CompositeWizard` Class
486+
487+
`CompositeWizard` extends `Wizard` to create and manage a collection of nested/child wizards.
488+
489+
Extend this class to create a wizard that contains other wizards as part of a prompter flow.
490+
Use `this.createWizardPrompter()` to use a wizard as a prompter in the `CompositeWizard`.
491+
492+
Example:
493+
494+
```ts
495+
496+
// Child wizard
497+
class ChildWizard extends Wizard<ChildWizardForm> {...}
498+
499+
500+
// Composite wizard
501+
interface SingleNestedWizardForm {
502+
...
503+
singleNestedWizardNestedProp: string
504+
...
505+
}
506+
507+
class SingleNestedWizard extends CompositeWizard<SingleNestedWizardForm> {
508+
constructor() {
509+
super()
510+
...
511+
this.form.singleNestedWizardNestedProp.bindPrompter(() =>
512+
this.createWizardPrompter<ChildWizard, ChildWizardForm>(ChildWizard)
513+
)
514+
...
515+
}
516+
}
517+
518+
```
519+
485520
### Executing
486521

487522
Wizards can be ran by calling the async `run` method:
@@ -495,6 +530,8 @@ Note that all wizards can potentially return `undefined` if the workflow was can
495530

496531
### Testing
497532

533+
#### Using `WizardTester`
534+
498535
Use `createWizardTester` on an instance of a wizard. Tests can then be constructed by asserting both the user-defined and internal state. Using the above `ExampleWizard`:
499536

500537
```ts
@@ -505,6 +542,70 @@ tester.foo.applyInput('Hello, world!') // Manipulate 'user' state
505542
tester.bar.assertShow() // True since 'foo' has a defined value
506543
```
507544

545+
#### Using `PrompterTester`
546+
547+
Use `PrompterTester` to simulate user behavior (click, input and selection) on prompters to test end-to-end flow of a wizard.
548+
549+
Example:
550+
551+
```ts
552+
// 1. Register PrompterTester handlers
553+
const prompterTester = PrompterTester.init()
554+
.handleInputBox('Input Prompter title 1', (inputBox) => {
555+
// Register Input Prompter handler
556+
inputBox.acceptValue('my-source-bucket-name')
557+
})
558+
.handleQuickPick('Quick Pick Prompter title 2', (quickPick) => {
559+
// Register Quick Pick Prompter handler
560+
561+
// Optional assertion can be added as part of the handler function
562+
assert.strictEqual(quickPick.items.length, 2)
563+
assert.strictEqual(quickPick.items[0].label, 'Specify required parameters and save as defaults')
564+
assert.strictEqual(quickPick.items[1].label, 'Specify required parameters')
565+
// Choose item
566+
quickPick.acceptItem(quickPick.items[0])
567+
})
568+
.handleQuickPick(
569+
'Quick Pick Prompter with various handler behavior title 3',
570+
(() => {
571+
// Register handler with dynamic behavior
572+
const generator = (function* () {
573+
// First call, choose '**'
574+
yield async (picker: TestQuickPick) => {
575+
await picker.untilReady()
576+
assert.strictEqual(picker.items[1].label, '**')
577+
picker.acceptItem(picker.items[1])
578+
}
579+
// Second call, choose BACK button
580+
yield async (picker: TestQuickPick) => {
581+
await picker.untilReady()
582+
picker.pressButton(vscode.QuickInputButtons.Back)
583+
}
584+
// Third and subsequent call
585+
while (true) {
586+
yield async (picker: TestQuickPick) => {
587+
await picker.untilReady()
588+
picker.acceptItem(picker.items[1])
589+
}
590+
}
591+
})()
592+
593+
return (picker: TestQuickPick) => {
594+
const next = generator.next().value
595+
return next(picker)
596+
}
597+
})()
598+
)
599+
.build()
600+
601+
// 2. Run your wizard class
602+
const result = await wizard.run()
603+
604+
// 3. Assert your tests
605+
prompterTester.assertCallAll()
606+
prompterTester.assertCallOrder('Input Prompter title 1', 1)
607+
```
608+
508609
## Module path debugging
509610

510611
Node has an environment variable `NODE_DEBUG=module` that helps to debug module imports. This can be helpful on windows, which can load node modules into uppercase or lower case drive letters, depending on the drive letter of the parent module.

packages/core/src/shared/ui/wizardPrompter.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ import { Prompter, PromptResult } from './prompter'
99

1010
/**
1111
* Wraps {@link Wizard} object into its own {@link Prompter}, allowing wizards to use other wizards in their flows.
12-
* This is meant to be used exclusively in createWizardPrompter() method of {@link NestedWizard} class.
12+
* This is meant to be used exclusively in createWizardPrompter() method of {@link CompositeWizard} class.
1313
*
1414
* @remarks
1515
* - The WizardPrompter class should never be instantiated with directly.
16-
* - Use createWizardPrompter() method of {@link NestedWizard} when creating a nested wizard prompter for proper state management.
16+
* - Use createWizardPrompter() method of {@link CompositeWizard} when creating a nested wizard prompter for proper state management.
1717
* - See examples:
1818
* - {@link SingleNestedWizard}
1919
* - {@link DoubleNestedWizard}

packages/core/src/shared/ui/nestedWizardPrompter.ts renamed to packages/core/src/shared/wizards/compositeWizard.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import { Wizard, WizardOptions } from '../wizards/wizard'
7-
import { Prompter } from './prompter'
8-
import { WizardPrompter } from './wizardPrompter'
6+
import { Wizard, WizardOptions } from './wizard'
7+
import { Prompter } from '../ui/prompter'
8+
import { WizardPrompter } from '../ui/wizardPrompter'
99
import { createHash } from 'crypto'
1010

1111
/**
1212
* An abstract class that extends the base Wizard class plus the ability to
1313
* use other wizard classes as prompters
1414
*/
15-
export abstract class NestedWizard<T> extends Wizard<T> {
15+
export abstract class CompositeWizard<T> extends Wizard<T> {
1616
/**
1717
* Map to store memoized wizard instances using SHA-256 hashed keys
1818
*/

packages/core/src/test/shared/wizards/nestedWizard.test.ts renamed to packages/core/src/test/shared/wizards/compositWizard.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55
import * as vscode from 'vscode'
66
import { createCommonButtons } from '../../../shared/ui/buttons'
7-
import { NestedWizard } from '../../../shared/ui/nestedWizardPrompter'
7+
import { CompositeWizard } from '../../../shared/wizards/compositeWizard'
88
import { createQuickPick, DataQuickPickItem } from '../../../shared/ui/pickerPrompter'
99
import * as assert from 'assert'
1010
import { PrompterTester } from './prompterTester'
@@ -40,7 +40,7 @@ export function createTestPrompter(title: string, itemsString: string[]) {
4040
return createQuickPick(items, { title: title, buttons: createCommonButtons() })
4141
}
4242

43-
class ChildWizard extends NestedWizard<ChildWizardForm> {
43+
class ChildWizard extends CompositeWizard<ChildWizardForm> {
4444
constructor() {
4545
super()
4646
this.form.childWizardProp1.bindPrompter(() =>
@@ -55,7 +55,7 @@ class ChildWizard extends NestedWizard<ChildWizardForm> {
5555
}
5656
}
5757

58-
class SingleNestedWizard extends NestedWizard<SingleNestedWizardForm> {
58+
class SingleNestedWizard extends CompositeWizard<SingleNestedWizardForm> {
5959
constructor() {
6060
super()
6161

@@ -74,7 +74,7 @@ class SingleNestedWizard extends NestedWizard<SingleNestedWizardForm> {
7474
}
7575
}
7676

77-
class DoubleNestedWizard extends NestedWizard<DoubleNestedWizardForm> {
77+
class DoubleNestedWizard extends CompositeWizard<DoubleNestedWizardForm> {
7878
constructor() {
7979
super()
8080

0 commit comments

Comments
 (0)