Skip to content

Commit

Permalink
feat(tour): add runElseIf and runElse
Browse files Browse the repository at this point in the history
  • Loading branch information
adenvt committed Nov 24, 2022
1 parent 91e5eac commit 9d20170
Show file tree
Hide file tree
Showing 6 changed files with 191 additions and 36 deletions.
29 changes: 28 additions & 1 deletion src/components/tour/core/step/conditional.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ref } from 'vue-demi'
import { MockStep } from '../../__mocks__/step'
import { Tour } from '../tour'
import StepCondition from './conditional'
import StepCondition, { ConditionalType } from './conditional'

it('should run the subtour when condition is true', async () => {
const tour = new Tour()
Expand Down Expand Up @@ -98,3 +98,30 @@ it('should not run if condition is a function and thrown an error', async () =>
expect(subStep.hit).not.toBeCalled()
expect(lastStep.hit).toBeCalled()
})

it('should run next routine, if first routine false', async () => {
const tour = new Tour()
const subTour = new Tour()
const subStep = new MockStep()
const subTour2 = new Tour()
const subStep2 = new MockStep()
const lastStep = new MockStep()
const step = new StepCondition({
condition: () => false,
tour : subTour.add(subStep),
})

step.chain({
type : ConditionalType.ELSE_IF,
condition: () => true,
tour : subTour2.add(subStep2),
})

tour.add(step).add(lastStep)

await tour.start()

expect(subStep.hit).not.toBeCalled()
expect(subStep2.hit).toBeCalled()
expect(lastStep.hit).not.toBeCalled()
})
81 changes: 68 additions & 13 deletions src/components/tour/core/step/conditional.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,31 @@ export interface ConditionalOptions {
tour: Tour,
}

export enum ConditionalType {
IF = 1,
ELSE_IF = 2,
ELSE = 3,
}

interface Routine extends ConditionalOptions {
type: ConditionalType,
}

export default class StepCondition extends AbstractStep<ConditionalOptions> {
protected routines: Routine[]

constructor (options: ConditionalOptions) {
super(options)

this.routines = [
{
type : ConditionalType.IF,
condition: options.condition,
tour : options.tour,
},
]
}

protected async checkCondition () {
try {
const options = this.getOptions()
Expand All @@ -26,23 +50,54 @@ export default class StepCondition extends AbstractStep<ConditionalOptions> {
}
}

public canChain () {
return this.routines.at(-1).type !== ConditionalType.ELSE
}

public chain (routine: Routine) {
this.routines.push(routine)

return this
}

public getTotalChild () {
return this.getOptions().tour.getTotalChild()
return Math.max(...this.routines.map((routine) => routine.tour.getTotalChild()))
}

protected async run () {
if (await this.checkCondition()) {
const tour = this.getOptions().tour
const index = this.direction === TourDirection.BACKWARD
? tour.getSteps().length - 1
: 0

await tour.setParent(this.parent).start(index, this.direction)

this.onCleanup(async () => {
await tour.stop()
})
} else
let result = false

for (const routine of this.routines) {
const condition = unref(routine.condition)

try {
result = typeof condition === 'function'
? await condition()
: condition
} catch (error) {
if (import.meta.env.DEV)
console.warn(error)

result = false
}

if (result) {
const tour = routine.tour
const index = this.direction === TourDirection.BACKWARD
? tour.getSteps().length - 1
: 0

await tour.setParent(this.parent).start(index, this.direction)

this.onCleanup(async () => {
await tour.stop()
})

break
}
}

if (!result)
await this.ahead()
}
}
21 changes: 21 additions & 0 deletions src/components/tour/core/story.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import StepAction from './step/action'
import StepDelay from './step/delay'
import StepDialog from './step/dialog'
import StepVisit from './step/visit'
import StepCondition, { ConditionalOptions } from './step/conditional'

it('should able to add show step instance using `.dialog()`', () => {
const tour = new TourStory()
Expand Down Expand Up @@ -200,3 +201,23 @@ it('should able to add action step type `visit` using `.visit()`', () => {
expect(step).toBeInstanceOf(StepVisit)
expect(option).toMatchObject({ url: '/redirect-to' })
})

it('should able to add conditional step using `.runIf()`', () => {
const tour = new TourStory()
const condition = () => true
const builder = (tour: TourStory) => {
tour.dialog('#target', 'message')
}

tour.runIf(condition, builder)

const step = tour.getSteps().at(0)
const option = step.getOptions() as ConditionalOptions
const subStep = option.tour.getSteps().at(0)
const subOption = subStep.getOptions()

expect(step).toBeInstanceOf(StepCondition)
expect(option).toMatchObject({ condition })
expect(subStep).toBeInstanceOf(StepDialog)
expect(subOption).toMatchObject({ target: '#target', text: 'message' })
})
44 changes: 40 additions & 4 deletions src/components/tour/core/story.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import StepDialog from './step/dialog'
import StepDelay from './step/delay'
import StepAction from './step/action'
import StepVisit from './step/visit'
import StepCondition from './step/conditional'
import StepCondition, { ConditionalType } from './step/conditional'
import type { ConditionalOptions } from './step/conditional'
import type { DialogOptions } from './step/dialog'
import type { EventType, ParamsOf } from './step/action'

type TourBuilderCallback = ((tour: TourStory) => TourStory) | ((tour: TourStory) => void)

/**
* Tour with additional function to build a tour stories
*/
Expand Down Expand Up @@ -135,20 +137,54 @@ export class TourStory extends Tour {
/**
* Run steps only when condition meet, skip if not
* @param condition Ref or Function to check
* @param buildFn
* @param buildTour
* @example
* tour.runIf(() => window.matchMedia("(max-width: 700px)").matches, (tour) => {
* return tour
* .click('#mobile-only')
* .dialog('#mobile-only', 'Hello World')
* })
*/
runIf (condition: ConditionalOptions['condition'], buildFn: (tour: TourStory) => TourStory | undefined) {
runIf (condition: ConditionalOptions['condition'], buildTour: TourBuilderCallback) {
const tour = new TourStory()

return this.add(new StepCondition({
condition: condition,
tour : buildFn(tour) ?? tour,
tour : buildTour(tour) ?? tour,
}))
}

runElseIf (condition: ConditionalOptions['condition'], buildTour: TourBuilderCallback) {
const step = this.steps.at(-1)

if (!(step instanceof StepCondition) || !step.canChain())
throw new Error('.runElseIf only can be use after .runIf or .runElseIf')

const tour = new TourStory()

step.chain({
type : ConditionalType.ELSE_IF,
condition: condition,
tour : buildTour(tour) ?? tour,
})

return this
}

runElse (buildTour: (tour: TourStory) => TourStory | undefined) {
const step = this.steps.at(-1)

if (!(step instanceof StepCondition) || !step.canChain())
throw new Error('.runElse only can be use after .runIf or .runElseIf')

const tour = new TourStory()

step.chain({
type : ConditionalType.ELSE,
condition: true,
tour : buildTour(tour) ?? tour,
})

return this
}
}
46 changes: 30 additions & 16 deletions src/components/tour/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,21 @@
.visit(withBase('/'))
.dialog('[data-tour="github"]', 'Don\'t forget to hit the star on Github')

const isActive = ref(true)
const number = ref(5)

const tourIf = createTour()
.runIf(() => isActive.value, (tour) => {
.runIf(() => number.value >= 5, (tour) => {
return tour
.dialog('[data-tour="if-dialog-1"]', 'This only run if checkbox was checked')
.dialog('[data-tour="if-more-than-5"]', 'This only run if number >= 5')
})
.runElseIf(() => number.value >= 3, (tour) => {
return tour
.dialog('[data-tour="if-more-than-3"]', 'This only run if number >= 3 and <= 5')
})
.runElse((tour) => {
return tour
.dialog('[data-tour="if-less-than-3"]', 'This only run if number < 3')
})
.dialog('[data-tour="if-dialog-2"]', 'This step 2')
</script>

# Tour
Expand Down Expand Up @@ -251,29 +258,36 @@ const tour = createTour()
.dialog('[data-tour="github"]', 'Don\'t forget to hit the star on Github')
```

## Conditional Run
## Conditional Step

If you want run only on some condition, you can use `.runIf`.
If you want run some steps only on some conditions, you can use `.runIf`, `runElseIf`, and `.runElse`

<p-button class="mt-3" @click="tourIf.start()">
Try It
</p-button>

<preview class="flex-col space-y-2" label="sample">
<p-checkbox v-model="isActive">Need Reset</p-checkbox>

<div class="space-gap-5">
<span data-tour="if-dialog-1">Step 1</span>
<span data-tour="if-dialog-2">Step 2</span>
<div class="space-gap-3">
<p-input v-model="number" />
<span data-tour="if-more-than-5">&ge;5</span>
<span data-tour="if-more-than-3">&ge;3</span>
<span data-tour="if-less-than-3">&lt;3</span>
</div>
</preview>

```ts
const isActive = ref(false)
const tour = createTour()
.runIf(() => isActive.value, (tour) => {
const number = ref(5)
const tour = createTour()
.runIf(() => number.value >= 5, (tour) => {
return tour
.dialog('[data-tour="if-more-than-5"]', 'This only run if number >= 5')
})
.runElseIf(() => number.value >= 3, (tour) => {
return tour
.dialog('[data-tour="if-more-than-3"]', 'This only run if number >= 3 and <= 5')
})
.runElse((tour) => {
return tour
.dialog('[data-tour="if-dialog-1"]', 'This only run if checkbox was checked')
.dialog('[data-tour="if-less-than-3"]', 'This only run if number < 3')
})
.dialog('[data-tour="if-dialog-2"]', 'This step 2')
```
6 changes: 4 additions & 2 deletions src/components/tour/utils/__mocks__/mutation-observer.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { vi } from 'vitest'

const handlers = []

export default class MutationObserver {
constructor (callback: MutationCallback) {
handlers.push(callback)
}

observe () {}
public observe = vi.fn()

disconnect () {}
public disconnect = vi.fn()
}

export function triggerMutation () {
Expand Down

0 comments on commit 9d20170

Please sign in to comment.