import { afterEach, describe, expect, it, vi } from 'vitest' import { ensureOffscreenDocument, hasOffscreenDocument } from './offscreen' afterEach(() => { vi.unstubAllGlobals() }) const PARAMS = { url: 'offscreen.html', reasons: ['LOCAL_STORAGE' as chrome.offscreen.Reason], justification: 'test', } /** * A chrome stub shaped like one of the version windows the manifest supports. * `getContexts` arrived in Chrome 116; `hasDocument` has been callable since * the offscreen API shipped in 109 but is undocumented, and has been * discussed for removal — so "neither" is a real shape too. */ function stubChrome(opts: { getContexts?: number | undefined hasDocument?: boolean | undefined createRejects?: unknown }) { const createDocument = vi.fn(async (_p: chrome.offscreen.CreateParameters) => { if (opts.createRejects !== undefined) throw opts.createRejects }) const stub = { runtime: { ContextType: { OFFSCREEN_DOCUMENT: 'OFFSCREEN_DOCUMENT' }, ...(opts.getContexts === undefined ? {} : { getContexts: vi.fn(async () => Array.from({ length: opts.getContexts! }, () => ({}))) }), }, offscreen: { Reason: { LOCAL_STORAGE: 'LOCAL_STORAGE' }, createDocument, ...(opts.hasDocument === undefined ? {} : { hasDocument: vi.fn(async () => opts.hasDocument!) }), }, } vi.stubGlobal('chrome', stub) return { createDocument, stub } } describe('hasOffscreenDocument', () => { it('prefers getContexts where it exists (Chrome 116+)', async () => { const { stub } = stubChrome({ getContexts: 1, hasDocument: false }) await expect(hasOffscreenDocument()).resolves.toBe(true) // The documented API wins even when the undocumented one disagrees. expect(stub.offscreen.hasDocument).not.toHaveBeenCalled() }) it('reports absence when getContexts returns nothing', async () => { stubChrome({ getContexts: 0 }) await expect(hasOffscreenDocument()).resolves.toBe(false) }) // The 110-115 window the manifest still supports: no getContexts yet. it('falls back to hasDocument when getContexts is missing', async () => { const { stub } = stubChrome({ hasDocument: true }) await expect(hasOffscreenDocument()).resolves.toBe(true) expect(stub.offscreen.hasDocument).toHaveBeenCalled() }) // Not "no": a caller that read undefined as false would create a second // document and throw. it('answers undefined when neither existence api is available', async () => { stubChrome({}) await expect(hasOffscreenDocument()).resolves.toBeUndefined() }) }) describe('ensureOffscreenDocument', () => { it('creates the document when there is none', async () => { const { createDocument } = stubChrome({ getContexts: 0 }) await ensureOffscreenDocument(PARAMS) expect(createDocument).toHaveBeenCalledWith(PARAMS) }) it('does not create a second document when one exists', async () => { const { createDocument } = stubChrome({ getContexts: 1 }) await ensureOffscreenDocument(PARAMS) expect(createDocument).not.toHaveBeenCalled() }) // With no existence check available it must still try, and the attempt is // what tells it the answer. it('tries to create when existence is unknown', async () => { const { createDocument } = stubChrome({}) await ensureOffscreenDocument(PARAMS) expect(createDocument).toHaveBeenCalled() }) // Chrome's real message. Reaching this means the document exists, which is // exactly what the caller asked for — sign-in must not fail here. it('treats "only a single offscreen document" as success', async () => { stubChrome({ getContexts: 0, createRejects: new Error('Only a single offscreen document may be created.'), }) await expect(ensureOffscreenDocument(PARAMS)).resolves.toBeUndefined() }) it('survives that error with no existence api at all', async () => { stubChrome({ createRejects: new Error('Only a single offscreen document may be created.') }) await expect(ensureOffscreenDocument(PARAMS)).resolves.toBeUndefined() }) it('propagates any other creation failure', async () => { stubChrome({ getContexts: 0, createRejects: new Error('No such file: offscreen.html') }) await expect(ensureOffscreenDocument(PARAMS)).rejects.toThrow(/No such file/) }) })