import { beforeEach, afterEach, describe, expect, expectTypeOf, it, vi } from 'vitest' const unmountedCallbacks: Array<() => void> = [] type MessageHandler = (event: MessageEvent) => void let messageHandler: MessageHandler | null = null vi.mock('vue', async () => { const actual = await vi.importActual('vue') return { ...actual, onMounted: vi.fn((cb: () => void) => cb()), onUnmounted: vi.fn((cb: () => void) => { unmountedCallbacks.push(cb) }), } }) import { usePlatformContext } from '../../composables/usePlatformContext' import type { PlatformContext } from '../../types' type InjectedPlatformWindow = Window & { __MINT_PLATFORM__?: PlatformContext } function sendThemeMessage(origin: string, theme: 'light' | 'dark' | 'system'): void { if (!messageHandler) { throw new Error('Message handler was not registered') } messageHandler({ source: window.parent, origin, data: { type: 'mint:theme-changed', payload: theme, }, } as MessageEvent) } describe('usePlatformContext', () => { it('uses integer IDs for injected users', () => { expectTypeOf['id']>().toEqualTypeOf() }) beforeEach(() => { unmountedCallbacks.length = 0 messageHandler = null delete (window as InjectedPlatformWindow).__MINT_PLATFORM__ vi.spyOn(window, 'addEventListener').mockImplementation((type, listener) => { if (type === 'message') { messageHandler = listener as MessageHandler } }) vi.spyOn(window, 'removeEventListener').mockImplementation(() => {}) }) afterEach(() => { while (unmountedCallbacks.length > 0) { const callback = unmountedCallbacks.pop() callback?.() } delete (window as InjectedPlatformWindow).__MINT_PLATFORM__ vi.restoreAllMocks() }) it('preserves the injected plugin settings API capability', () => { const injectedContext: PlatformContext = { isIntegrated: true, theme: 'light', platformApiUrl: '/api', plugin: { id: 'test-plugin', name: 'test-plugin', version: '1.0.0', route_prefix: '/test-plugin', api_prefix: '/api/test-plugin', settings_api: 'plugin', }, } ;(window as InjectedPlatformWindow).__MINT_PLATFORM__ = injectedContext const context = usePlatformContext() expect(context.plugin.value?.settings_api).toBe('plugin') }) it('clears allowAnyOrigin policy after last consumer unmounts', () => { const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) const first = usePlatformContext({ allowAnyOrigin: true }) sendThemeMessage('https://evil.example', 'dark') expect(first.theme.value).toBe('dark') expect(unmountedCallbacks).toHaveLength(1) unmountedCallbacks[0]!() unmountedCallbacks.length = 0 const second = usePlatformContext() sendThemeMessage('https://evil.example', 'light') expect(second.theme.value).toBe('system') expect( warnSpy.mock.calls.some(([msg]) => String(msg).includes('Rejected postMessage')), ).toBe(true) }) it('does not leak explicit allowed origins to new consumers', () => { const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) const first = usePlatformContext({ allowedOrigins: ['https://trusted.example'] }) sendThemeMessage('https://trusted.example', 'dark') expect(first.theme.value).toBe('dark') expect(unmountedCallbacks).toHaveLength(1) unmountedCallbacks[0]!() unmountedCallbacks.length = 0 const second = usePlatformContext() sendThemeMessage('https://trusted.example', 'light') expect(second.theme.value).toBe('system') expect( warnSpy.mock.calls.some(([msg]) => String(msg).includes('Rejected postMessage')), ).toBe(true) }) it('removes allowAnyOrigin scope when that consumer unmounts while others remain', () => { const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) usePlatformContext({ allowAnyOrigin: true }) const second = usePlatformContext() sendThemeMessage('https://evil.example', 'dark') expect(second.theme.value).toBe('dark') expect(unmountedCallbacks).toHaveLength(2) unmountedCallbacks[0]!() sendThemeMessage('https://evil.example', 'light') expect(second.theme.value).toBe('dark') expect( warnSpy.mock.calls.some(([msg]) => String(msg).includes('Rejected postMessage')), ).toBe(true) unmountedCallbacks[1]!() unmountedCallbacks.length = 0 }) })