Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 2m56s
169 lines
5.1 KiB
TypeScript
169 lines
5.1 KiB
TypeScript
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
|
|
import {
|
|
shouldShowStep,
|
|
clearChainProgress,
|
|
tutorialEnabled,
|
|
tutorialProgress,
|
|
sessionSkipped,
|
|
maybeShow,
|
|
dismissActive,
|
|
activeStep,
|
|
} from '../controller'
|
|
|
|
function setKebabAnchorHtml() {
|
|
document.body.innerHTML = `
|
|
<div class="kebab-menu"></div>
|
|
<button data-tutorial="chore-edit-points kebab-edit-points-cost">Edit Points</button>
|
|
<button data-tutorial="chore-schedule">Schedule</button>
|
|
`
|
|
}
|
|
|
|
describe('tutorial controller - shouldShowStep', () => {
|
|
beforeEach(() => {
|
|
tutorialEnabled.value = true
|
|
tutorialProgress.value = {}
|
|
sessionSkipped.value = false
|
|
activeStep.value = null
|
|
document.body.innerHTML = ''
|
|
global.fetch = vi.fn().mockResolvedValue({ ok: true })
|
|
})
|
|
|
|
afterEach(() => {
|
|
activeStep.value = null
|
|
document.body.innerHTML = ''
|
|
vi.restoreAllMocks()
|
|
})
|
|
|
|
it('returns true when tips are enabled, the step is unseen, and the session is not skipped', () => {
|
|
expect(shouldShowStep('create-chore')).toBe(true)
|
|
})
|
|
|
|
it('returns false when tips are disabled for a normal step', () => {
|
|
tutorialEnabled.value = false
|
|
expect(shouldShowStep('create-penalty')).toBe(false)
|
|
})
|
|
|
|
it('returns true for a locally-cleared step even when tips are disabled', () => {
|
|
tutorialEnabled.value = false
|
|
clearChainProgress('create-reward')
|
|
expect(shouldShowStep('create-reward')).toBe(true)
|
|
})
|
|
|
|
it('still blocks locally-cleared steps when the session has been skipped', () => {
|
|
tutorialEnabled.value = false
|
|
sessionSkipped.value = true
|
|
clearChainProgress('create-kindness')
|
|
expect(shouldShowStep('create-kindness')).toBe(false)
|
|
})
|
|
|
|
it('returns false when the step has already been seen', () => {
|
|
tutorialProgress.value = { 'notification-click': true }
|
|
expect(shouldShowStep('notification-click')).toBe(false)
|
|
})
|
|
|
|
it('ignores the enabled flag when ignoreEnabled is true', () => {
|
|
tutorialEnabled.value = false
|
|
expect(shouldShowStep('status-pending', true)).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('tutorial controller - maybeShow', () => {
|
|
beforeEach(() => {
|
|
tutorialEnabled.value = true
|
|
tutorialProgress.value = {}
|
|
sessionSkipped.value = false
|
|
activeStep.value = null
|
|
setKebabAnchorHtml()
|
|
global.fetch = vi.fn().mockResolvedValue({ ok: true })
|
|
})
|
|
|
|
afterEach(() => {
|
|
activeStep.value = null
|
|
document.body.innerHTML = ''
|
|
vi.restoreAllMocks()
|
|
})
|
|
|
|
it('promotes a step when the anchor is present', () => {
|
|
maybeShow('chore-kebab-menu')
|
|
expect(activeStep.value?.def.id).toBe('chore-kebab-menu')
|
|
})
|
|
|
|
it('does not promote a step that has already been seen', () => {
|
|
tutorialProgress.value = { 'chore-kebab-menu': true }
|
|
maybeShow('chore-kebab-menu')
|
|
expect(activeStep.value).toBeNull()
|
|
})
|
|
|
|
it('is idempotent for the currently active step', () => {
|
|
maybeShow('chore-kebab-menu')
|
|
expect(activeStep.value?.def.id).toBe('chore-kebab-menu')
|
|
|
|
maybeShow('chore-kebab-menu')
|
|
expect(activeStep.value?.def.id).toBe('chore-kebab-menu')
|
|
})
|
|
|
|
it('drops a step whose anchor is missing', () => {
|
|
document.body.innerHTML = ''
|
|
maybeShow('chore-kebab-menu')
|
|
expect(activeStep.value).toBeNull()
|
|
})
|
|
})
|
|
|
|
describe('tutorial controller - dismissActive', () => {
|
|
beforeEach(() => {
|
|
tutorialEnabled.value = true
|
|
tutorialProgress.value = {}
|
|
sessionSkipped.value = false
|
|
activeStep.value = null
|
|
setKebabAnchorHtml()
|
|
global.fetch = vi.fn().mockResolvedValue({ ok: true })
|
|
})
|
|
|
|
afterEach(() => {
|
|
activeStep.value = null
|
|
document.body.innerHTML = ''
|
|
vi.restoreAllMocks()
|
|
})
|
|
|
|
it('marks the active step seen and chains the next step', () => {
|
|
maybeShow('chore-kebab-menu')
|
|
expect(activeStep.value?.def.id).toBe('chore-kebab-menu')
|
|
|
|
dismissActive(true)
|
|
expect(tutorialProgress.value['chore-kebab-menu']).toBe(true)
|
|
expect(activeStep.value?.def.id).toBe('chore-edit-points')
|
|
})
|
|
|
|
it('does not duplicate a chained step that is already queued', () => {
|
|
// Set up: open the chore kebab menu, then queue the next step early.
|
|
maybeShow('chore-kebab-menu')
|
|
maybeShow('chore-edit-points')
|
|
expect(activeStep.value?.def.id).toBe('chore-kebab-menu')
|
|
|
|
// Dismissing the menu should chain to chore-edit-points only once.
|
|
dismissActive(true)
|
|
expect(activeStep.value?.def.id).toBe('chore-edit-points')
|
|
|
|
// If a duplicate had been added, we would still be on chore-edit-points.
|
|
dismissActive(true)
|
|
expect(activeStep.value?.def.id).toBe('chore-schedule')
|
|
})
|
|
|
|
it('does not re-chain a step that is currently active', () => {
|
|
maybeShow('chore-kebab-menu')
|
|
dismissActive(true)
|
|
expect(activeStep.value?.def.id).toBe('chore-edit-points')
|
|
|
|
// Dismissing chore-edit-points should move to chore-schedule, not loop.
|
|
dismissActive(true)
|
|
expect(activeStep.value?.def.id).toBe('chore-schedule')
|
|
})
|
|
|
|
it('does not mark the step seen when markSeen is false', () => {
|
|
maybeShow('chore-kebab-menu')
|
|
dismissActive(false)
|
|
expect(tutorialProgress.value['chore-kebab-menu']).toBeUndefined()
|
|
expect(activeStep.value).toBeNull()
|
|
})
|
|
})
|