feat(walkthrough): add Getting Started tour with first-run invite
Bespoke spotlight walkthrough (no new deps) that guides new users through the main features. A version-stamped invite bubble offers the tour to new users and re-offers it once to existing users after an upgrade; restartable from Settings. - Multi-hole SVG-mask overlay keeps a ringed control and any open modal lit - Steps: scan, scan history, inventory, nodes, edit, text/zone, grouping, style, imports, and a closing recap with a GitHub link - Scan/history/inventory/import steps inject demo data (no backend calls) and are filtered out in standalone, which ends on a full-mode recap - Persisted invite state via localStorage (utils/walkthrough.ts), live tour state via zustand (stores/walkthroughStore.ts) ha-relevant: maybe
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
import {
|
||||
WALKTHROUGH_VERSION,
|
||||
readWalkthrough,
|
||||
writeWalkthrough,
|
||||
subscribeWalkthrough,
|
||||
shouldOfferWalkthrough,
|
||||
markWalkthroughSeen,
|
||||
resetWalkthrough,
|
||||
} from '../walkthrough'
|
||||
|
||||
beforeEach(() => localStorage.clear())
|
||||
|
||||
describe('readWalkthrough', () => {
|
||||
it('defaults to pending / unseen when nothing is stored', () => {
|
||||
expect(readWalkthrough()).toEqual({ seenVersion: null, status: 'pending' })
|
||||
})
|
||||
|
||||
it('round-trips written state', () => {
|
||||
writeWalkthrough({ seenVersion: 3, status: 'completed' })
|
||||
expect(readWalkthrough()).toEqual({ seenVersion: 3, status: 'completed' })
|
||||
})
|
||||
|
||||
it('falls back to defaults on malformed json', () => {
|
||||
localStorage.setItem('homelable.walkthrough', '{not json')
|
||||
expect(readWalkthrough()).toEqual({ seenVersion: null, status: 'pending' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('shouldOfferWalkthrough', () => {
|
||||
it('offers a brand-new user (seenVersion null)', () => {
|
||||
expect(shouldOfferWalkthrough()).toBe(true)
|
||||
})
|
||||
|
||||
it('offers an upgrader whose seen version is behind', () => {
|
||||
writeWalkthrough({ seenVersion: WALKTHROUGH_VERSION - 1, status: 'completed' })
|
||||
expect(shouldOfferWalkthrough()).toBe(true)
|
||||
})
|
||||
|
||||
it('stops offering once the current version is stamped', () => {
|
||||
markWalkthroughSeen('completed')
|
||||
expect(shouldOfferWalkthrough()).toBe(false)
|
||||
expect(readWalkthrough().seenVersion).toBe(WALKTHROUGH_VERSION)
|
||||
})
|
||||
})
|
||||
|
||||
describe('resetWalkthrough', () => {
|
||||
it('re-offers after a reset', () => {
|
||||
markWalkthroughSeen('skipped')
|
||||
expect(shouldOfferWalkthrough()).toBe(false)
|
||||
resetWalkthrough()
|
||||
expect(shouldOfferWalkthrough()).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('subscribeWalkthrough', () => {
|
||||
it('notifies listeners on write and unsubscribes cleanly', () => {
|
||||
const spy = vi.fn()
|
||||
const unsub = subscribeWalkthrough(spy)
|
||||
writeWalkthrough({ seenVersion: 1, status: 'completed' })
|
||||
expect(spy).toHaveBeenCalledWith({ seenVersion: 1, status: 'completed' })
|
||||
unsub()
|
||||
writeWalkthrough({ seenVersion: 2, status: 'skipped' })
|
||||
expect(spy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user