import { afterEach, describe, expect, it, vi } from 'vitest' import { defineComponent, nextTick } from 'vue' import { mount } from '@vue/test-utils' import { useRuntimeAlignment } from '../../composables/useRuntimeAlignment' class MemoryStorage implements Pick { private readonly values = new Map() getItem(key: string): string | null { return this.values.get(key) ?? null } setItem(key: string, value: string): void { this.values.set(key, value) } } function mountRuntime(options: Parameters[0]) { let runtime: ReturnType | undefined const wrapper = mount(defineComponent({ setup() { runtime = useRuntimeAlignment(options) return () => null }, })) return { wrapper, get runtime() { return runtime! } } } describe('useRuntimeAlignment', () => { afterEach(() => vi.restoreAllMocks()) it('should offer a reload when a visible tab observes a different revision', async () => { const replace = vi.fn() const { runtime } = mountRuntime({ currentRevision: 'sha256:old', visibility: () => 'visible', replace, storage: new MemoryStorage(), }) runtime.observe({ frontend_revision: 'sha256:new' }) await nextTick() expect(runtime.updateAvailable.value).toBe(true) }) it('should reload a hidden stale tab automatically', () => { const replace = vi.fn() const { runtime } = mountRuntime({ currentRevision: 'sha256:old', visibility: () => 'hidden', replace, storage: new MemoryStorage(), currentUrl: () => 'https://mint.example/leaf/analysis', }) runtime.observe({ frontend_revision: 'sha256:new' }) expect(replace).toHaveBeenCalledWith( 'https://mint.example/leaf/analysis?__mint_revision=sha256%3Anew', ) }) it('should reload before reuse when a tab became stale while hidden', () => { const replace = vi.fn() let state: DocumentVisibilityState = 'visible' let visibilityListener: EventListener | undefined const runtime = mountRuntime({ currentRevision: 'sha256:before-hide', visibility: () => state, replace, storage: new MemoryStorage(), currentUrl: () => 'https://mint.example/leaf/analysis', visibilityTarget: { addEventListener: (_type, listener) => { visibilityListener = listener }, removeEventListener: () => {}, }, }).runtime state = 'hidden' visibilityListener?.(new Event('visibilitychange')) state = 'visible' runtime.observe({ frontend_revision: 'sha256:after-hide' }) expect(replace).toHaveBeenCalledTimes(1) }) it('should make only one recovery reload for the same missing chunk', () => { const replace = vi.fn() const storage = new MemoryStorage() const first = mountRuntime({ currentRevision: 'sha256:old', visibility: () => 'visible', replace, storage, currentUrl: () => 'https://mint.example/', }) const second = mountRuntime({ currentRevision: 'sha256:old', visibility: () => 'visible', replace, storage, currentUrl: () => 'https://mint.example/', }) first.runtime.recoverMissingChunk() second.runtime.recoverMissingChunk() expect(replace).toHaveBeenCalledTimes(1) }) it('should make only one recovery reload when session storage is unavailable', () => { const replace = vi.fn() const runtime = mountRuntime({ currentRevision: 'sha256:no-storage', visibility: () => 'visible', replace, storage: null, currentUrl: () => 'https://mint.example/', }).runtime runtime.recoverMissingChunk() runtime.recoverMissingChunk() expect(replace).toHaveBeenCalledTimes(1) }) it('should not repeat chunk recovery after the cache-busted page reloads', () => { const replace = vi.fn() const runtime = mountRuntime({ currentRevision: 'sha256:already-reloaded', visibility: () => 'visible', replace, storage: null, currentUrl: () => ( 'https://mint.example/?__mint_revision=sha256%3Aalready-reloaded' ), }).runtime runtime.recoverMissingChunk() expect(replace).not.toHaveBeenCalled() }) })