UNPKG

@furystack/shades

Version:

A lightweight UI framework for FuryStack with JSX support

219 lines (184 loc) 7.91 kB
import { afterEach, describe, expect, it, vi } from 'vitest' import type { ViewTransitionConfig } from './view-transition.js' import { maybeViewTransition, transitionedValue } from './view-transition.js' describe('maybeViewTransition', () => { afterEach(() => { delete (document as unknown as Record<string, unknown>).startViewTransition }) const mockStartViewTransition = () => { const spy = vi.fn((optionsOrCallback: StartViewTransitionOptions | (() => void)) => { const update = typeof optionsOrCallback === 'function' ? optionsOrCallback : optionsOrCallback.update update?.() return { finished: Promise.resolve(), ready: Promise.resolve(), updateCallbackDone: Promise.resolve(), skipTransition: vi.fn(), } as unknown as ViewTransition }) document.startViewTransition = spy as typeof document.startViewTransition return spy } it('should call update directly when config is undefined', () => { const spy = mockStartViewTransition() const update = vi.fn() const result = maybeViewTransition(undefined, update) expect(update).toHaveBeenCalledTimes(1) expect(spy).not.toHaveBeenCalled() expect(result).toBeUndefined() }) it('should call update directly when config is false', () => { const spy = mockStartViewTransition() const update = vi.fn() const result = maybeViewTransition(false, update) expect(update).toHaveBeenCalledTimes(1) expect(spy).not.toHaveBeenCalled() expect(result).toBeUndefined() }) it('should call update directly when startViewTransition is not available', () => { const update = vi.fn() const result = maybeViewTransition(true, update) expect(update).toHaveBeenCalledTimes(1) expect(result).toBeUndefined() }) it('should call startViewTransition when config is true and API is available', () => { const spy = mockStartViewTransition() const update = vi.fn() const result = maybeViewTransition(true, update) expect(spy).toHaveBeenCalledTimes(1) expect(update).toHaveBeenCalledTimes(1) expect(result).toBeInstanceOf(Promise) }) it('should use callback form when config is true', () => { const spy = mockStartViewTransition() const update = vi.fn() void maybeViewTransition(true, update) expect(spy).toHaveBeenCalledWith(update) }) it('should pass types when config is an object with types', () => { const spy = mockStartViewTransition() const update = vi.fn() const config: ViewTransitionConfig = { types: ['slide', 'fade'] } void maybeViewTransition(config, update) expect(spy).toHaveBeenCalledWith({ update, types: ['slide', 'fade'] }) }) it('should use callback form when config object has empty types array', () => { const spy = mockStartViewTransition() const update = vi.fn() const config: ViewTransitionConfig = { types: [] } void maybeViewTransition(config, update) expect(spy).toHaveBeenCalledWith(update) }) it('should use callback form when config object has no types', () => { const spy = mockStartViewTransition() const update = vi.fn() const config: ViewTransitionConfig = {} void maybeViewTransition(config, update) expect(spy).toHaveBeenCalledWith(update) }) it('should return updateCallbackDone promise when transition is started', async () => { mockStartViewTransition() const update = vi.fn() const result = maybeViewTransition(true, update) expect(result).toBeInstanceOf(Promise) await expect(result).resolves.toBeUndefined() }) }) describe('transitionedValue', () => { afterEach(() => { delete (document as unknown as Record<string, unknown>).startViewTransition }) const mockStartViewTransition = () => { const spy = vi.fn((optionsOrCallback: StartViewTransitionOptions | (() => void)) => { const update = typeof optionsOrCallback === 'function' ? optionsOrCallback : optionsOrCallback.update update?.() return { finished: Promise.resolve(), ready: Promise.resolve(), updateCallbackDone: Promise.resolve(), skipTransition: vi.fn(), } as unknown as ViewTransition }) document.startViewTransition = spy as typeof document.startViewTransition return spy } const createMockUseState = () => { const store = new Map<string, unknown>() const setters = new Map<string, (v: unknown) => void>() const mockUseState = <S>(key: string, initialValue: S): [S, (v: S) => void] => { if (!store.has(key)) { store.set(key, initialValue) } const setValue = (v: S) => { store.set(key, v) } setters.set(key, setValue as (v: unknown) => void) return [store.get(key) as S, setValue] } return { mockUseState, store } } it('should return the value when it equals the displayed value', () => { const { mockUseState } = createMockUseState() const result = transitionedValue(mockUseState, 'key', 'hello', true) expect(result).toBe('hello') }) it('should not call startViewTransition when value has not changed', () => { const spy = mockStartViewTransition() const { mockUseState } = createMockUseState() transitionedValue(mockUseState, 'key', 'hello', true) expect(spy).not.toHaveBeenCalled() }) it('should call startViewTransition when value changes and config is truthy', () => { const spy = mockStartViewTransition() const { mockUseState, store } = createMockUseState() transitionedValue(mockUseState, 'key', 'initial', true) store.set('key', 'initial') transitionedValue(mockUseState, 'key', 'updated', true) expect(spy).toHaveBeenCalledTimes(1) expect(store.get('key')).toBe('updated') }) it('should not call startViewTransition when config is falsy', () => { const spy = mockStartViewTransition() const { mockUseState, store } = createMockUseState() transitionedValue(mockUseState, 'key', 'initial', undefined) store.set('key', 'initial') transitionedValue(mockUseState, 'key', 'updated', undefined) expect(spy).not.toHaveBeenCalled() expect(store.get('key')).toBe('updated') }) it('should not call startViewTransition when shouldTransition returns false', () => { const spy = mockStartViewTransition() const { mockUseState, store } = createMockUseState() transitionedValue(mockUseState, 'key', 'initial', true, () => false) store.set('key', 'initial') transitionedValue(mockUseState, 'key', 'updated', true, () => false) expect(spy).not.toHaveBeenCalled() expect(store.get('key')).toBe('updated') }) it('should call startViewTransition when shouldTransition returns true', () => { const spy = mockStartViewTransition() const { mockUseState, store } = createMockUseState() transitionedValue(mockUseState, 'key', 'initial', true, () => true) store.set('key', 'initial') transitionedValue(mockUseState, 'key', 'updated', true, () => true) expect(spy).toHaveBeenCalledTimes(1) expect(store.get('key')).toBe('updated') }) it('should pass prev and next values to shouldTransition', () => { mockStartViewTransition() const { mockUseState, store } = createMockUseState() const shouldTransition = vi.fn(() => true) transitionedValue(mockUseState, 'key', 'initial', true, shouldTransition) store.set('key', 'initial') transitionedValue(mockUseState, 'key', 'updated', true, shouldTransition) expect(shouldTransition).toHaveBeenCalledWith('initial', 'updated') }) it('should default shouldTransition to always true', () => { const spy = mockStartViewTransition() const { mockUseState, store } = createMockUseState() transitionedValue(mockUseState, 'key', 'a', true) store.set('key', 'a') transitionedValue(mockUseState, 'key', 'b', true) expect(spy).toHaveBeenCalledTimes(1) }) })