// @vitest-environment happy-dom import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { nextTick } from 'vue' import BrowserExtensionCard from '../../src/components/settings/BrowserExtensionCard.vue' import { freshPinia, mountComponent } from '../support/mountComponent.js' // useApi is a thin fetch wrapper, so the seam is fetch itself (same shape as // showcase.spec.js) rather than a module mock. function stubApi(manifest) { globalThis.fetch = vi.fn(async (url) => { const payload = String(url).includes('/api/extension/manifest') ? manifest : { key: 'test-key' } return { ok: true, status: 200, statusText: '200', text: async () => JSON.stringify(payload), } }) } async function mountCard(manifest) { stubApi(manifest) const w = mountComponent(BrowserExtensionCard, { pinia: freshPinia() }) // onMounted fires two fetches (manifest + key) and each resolves through a // chain of microtasks. Yielding to a macrotask drains the whole queue, which // a fixed number of nextTicks would only do by luck. await new Promise((resolve) => setTimeout(resolve, 0)) await nextTick() return w } const INSTALLED = { installed: true, version: '1.0.3499884', xpi_url: '/extension/fabledcurator-1.0.3499884.xpi', latest_url: '/extension/fabledcurator-latest.xpi', sha256: 'abc', } describe('BrowserExtensionCard — channel', () => { beforeEach(() => { vi.restoreAllMocks() }) afterEach(() => { delete globalThis.fetch }) it('names the channel the instance reports', async () => { // The point of the whole channel scheme: an operator can tell a dev // instance from a main one without installing anything. const w = await mountCard({ ...INSTALLED, channel: 'dev' }) expect(w.text()).toContain('dev') }) it('shows the version and the channel as SEPARATE text, never merged', async () => { // Regression guard with teeth: the tempting shortcut is a `-dev` version // suffix, and that is precisely what breaks the extension's comparator — // it parses each dotted segment with parseInt, so a suffixed segment reads // as 0 and every dev build compares equal to every other. If someone ever // "simplifies" by folding the channel into the version, the version text // stops being the bare derived number and this fails. const w = await mountCard({ ...INSTALLED, channel: 'dev' }) expect(w.text()).toContain('v1.0.3499884') expect(w.text()).not.toContain('1.0.3499884-dev') }) it('renders no channel when the instance declares none', async () => { // A locally-built image, or one predating the field. The card must read // exactly as it did before the channel existed rather than inventing an // "unknown" badge — absence is a normal answer here, not a fault. const w = await mountCard(INSTALLED) expect(w.text()).toContain('v1.0.3499884') expect(w.findAll('v-chip')).toHaveLength(0) }) })