Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(toolkit): rename NestedWizard class and update arch doc #6229

Merged
merged 2 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 102 additions & 1 deletion docs/arch_develop.md
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,7 @@ await tester.result(items[0].data) // Execute the actions, asserting the final r

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.

### Creating a Wizard (Quick Picks)
### 1. `Wizard` Class

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

### 2. `CompositeWizard` Class

`CompositeWizard` extends `Wizard` to create and manage a collection of nested/child wizards.

Extend this class to create a wizard that contains other wizards as part of a prompter flow.
Use `this.createWizardPrompter()` to use a wizard as a prompter in the `CompositeWizard`.

Example:

```ts

// Child wizard
class ChildWizard extends Wizard<ChildWizardForm> {...}


// Composite wizard
interface SingleNestedWizardForm {
...
singleNestedWizardNestedProp: string
...
}

class SingleNestedWizard extends CompositeWizard<SingleNestedWizardForm> {
constructor() {
super()
...
this.form.singleNestedWizardNestedProp.bindPrompter(() =>
this.createWizardPrompter<ChildWizard, ChildWizardForm>(ChildWizard)
)
...
}
}

```

### Executing

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

### Testing

#### Using `WizardTester`

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`:

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

#### Using `PrompterTester`
justinmk3 marked this conversation as resolved.
Show resolved Hide resolved

Use `PrompterTester` to simulate user behavior (click, input and selection) on prompters to test end-to-end flow of a wizard.

Example:

```ts
// 1. Register PrompterTester handlers
const prompterTester = PrompterTester.init()
.handleInputBox('Input Prompter title 1', (inputBox) => {
// Register Input Prompter handler
inputBox.acceptValue('my-source-bucket-name')
})
.handleQuickPick('Quick Pick Prompter title 2', (quickPick) => {
// Register Quick Pick Prompter handler

// Optional assertion can be added as part of the handler function
assert.strictEqual(quickPick.items.length, 2)
assert.strictEqual(quickPick.items[0].label, 'Specify required parameters and save as defaults')
assert.strictEqual(quickPick.items[1].label, 'Specify required parameters')
// Choose item
quickPick.acceptItem(quickPick.items[0])
})
.handleQuickPick(
'Quick Pick Prompter with various handler behavior title 3',
(() => {
// Register handler with dynamic behavior
const generator = (function* () {
// First call, choose '**'
yield async (picker: TestQuickPick) => {
await picker.untilReady()
assert.strictEqual(picker.items[1].label, '**')
picker.acceptItem(picker.items[1])
}
// Second call, choose BACK button
yield async (picker: TestQuickPick) => {
await picker.untilReady()
picker.pressButton(vscode.QuickInputButtons.Back)
}
// Third and subsequent call
while (true) {
yield async (picker: TestQuickPick) => {
await picker.untilReady()
picker.acceptItem(picker.items[1])
}
}
})()

return (picker: TestQuickPick) => {
const next = generator.next().value
return next(picker)
}
})()
)
.build()

// 2. Run your wizard class
const result = await wizard.run()

// 3. Assert your tests
prompterTester.assertCallAll()
prompterTester.assertCallOrder('Input Prompter title 1', 1)
```

## Module path debugging

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.
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/shared/ui/wizardPrompter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import { Prompter, PromptResult } from './prompter'

/**
* Wraps {@link Wizard} object into its own {@link Prompter}, allowing wizards to use other wizards in their flows.
* This is meant to be used exclusively in createWizardPrompter() method of {@link NestedWizard} class.
* This is meant to be used exclusively in createWizardPrompter() method of {@link CompositeWizard} class.
*
* @remarks
* - The WizardPrompter class should never be instantiated with directly.
* - Use createWizardPrompter() method of {@link NestedWizard} when creating a nested wizard prompter for proper state management.
* - Use createWizardPrompter() method of {@link CompositeWizard} when creating a nested wizard prompter for proper state management.
* - See examples:
* - {@link SingleNestedWizard}
* - {@link DoubleNestedWizard}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { Wizard, WizardOptions } from '../wizards/wizard'
import { Prompter } from './prompter'
import { WizardPrompter } from './wizardPrompter'
import { Wizard, WizardOptions } from './wizard'
import { Prompter } from '../ui/prompter'
import { WizardPrompter } from '../ui/wizardPrompter'
import { createHash } from 'crypto'

/**
* An abstract class that extends the base Wizard class plus the ability to
* use other wizard classes as prompters
*/
export abstract class NestedWizard<T> extends Wizard<T> {
export abstract class CompositeWizard<T> extends Wizard<T> {
/**
* Map to store memoized wizard instances using SHA-256 hashed keys
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/
import * as vscode from 'vscode'
import { createCommonButtons } from '../../../shared/ui/buttons'
import { NestedWizard } from '../../../shared/ui/nestedWizardPrompter'
import { CompositeWizard } from '../../../shared/wizards/compositeWizard'
import { createQuickPick, DataQuickPickItem } from '../../../shared/ui/pickerPrompter'
import * as assert from 'assert'
import { PrompterTester } from './prompterTester'
Expand Down Expand Up @@ -40,7 +40,7 @@ export function createTestPrompter(title: string, itemsString: string[]) {
return createQuickPick(items, { title: title, buttons: createCommonButtons() })
}

class ChildWizard extends NestedWizard<ChildWizardForm> {
class ChildWizard extends CompositeWizard<ChildWizardForm> {
constructor() {
super()
this.form.childWizardProp1.bindPrompter(() =>
Expand All @@ -55,7 +55,7 @@ class ChildWizard extends NestedWizard<ChildWizardForm> {
}
}

class SingleNestedWizard extends NestedWizard<SingleNestedWizardForm> {
class SingleNestedWizard extends CompositeWizard<SingleNestedWizardForm> {
constructor() {
super()

Expand All @@ -74,7 +74,7 @@ class SingleNestedWizard extends NestedWizard<SingleNestedWizardForm> {
}
}

class DoubleNestedWizard extends NestedWizard<DoubleNestedWizardForm> {
class DoubleNestedWizard extends CompositeWizard<DoubleNestedWizardForm> {
constructor() {
super()

Expand Down
Loading