Automerge backed Markdown editor for the Atmosphere
Something went wrong. Try again.
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223import { describe, it, expect, beforeEach, vi } from 'vitest'import { ExternalPluginStore } from '../external-plugin-store'vi.mock('../../../app/capabilities', async importOriginal => ({ ...await importOriginal<typeof import('../../../app/capabilities')>(), areThirdPartyPluginsEnabled: () => true,}))import { PluginRegistry } from '../plugin-registry'import { createTestManifest, createMockLocalStorage } from './test-utils'
// Mock URL.createObjectURL / revokeObjectURL for Node environmentconst blobUrls = new Map<string, string>()let blobCounter = 0
beforeEach(() => { blobUrls.clear() blobCounter = 0 vi.stubGlobal('URL', { ...URL, createObjectURL: vi.fn((_blob: Blob) => { const url = `blob:test-${blobCounter++}` blobUrls.set(url, 'blob') return url }), revokeObjectURL: vi.fn((url: string) => { blobUrls.delete(url) }), })})
describe('ExternalPluginStore', () => { let store: ExternalPluginStore let mockStorage: ReturnType<typeof createMockLocalStorage>
beforeEach(() => { mockStorage = createMockLocalStorage() vi.stubGlobal('localStorage', mockStorage) store = new ExternalPluginStore() })
describe('install', () => { it('stores manifest and main.js source', () => { const manifest = createTestManifest({ id: 'ext-plugin' }) store.install(manifest, 'export default class extends Plugin {}', 'https://example.com/plugins/test')
expect(store.isExternal('ext-plugin')).toBe(true) expect(store.getAll()).toHaveLength(1) expect(store.getAll()[0].manifest.id).toBe('ext-plugin') })
it('persists to localStorage', () => { const manifest = createTestManifest({ id: 'ext-plugin' }) store.install(manifest, 'export default class {}')
const stored = mockStorage._storage['reams:external-plugins'] expect(stored).toBeDefined() const parsed = JSON.parse(stored) expect(parsed['ext-plugin']).toBeDefined() expect(parsed['ext-plugin'].mainJs).toBe('export default class {}') })
it('creates a blob URL for the main.js source', () => { const manifest = createTestManifest({ id: 'ext-plugin' }) store.install(manifest, 'export default class {}')
const entry = store.get('ext-plugin') expect(entry).not.toBeNull() expect(entry!.blobUrl).toMatch(/^blob:/) })
it('throws on duplicate plugin ID', () => { const manifest = createTestManifest({ id: 'ext-plugin' }) store.install(manifest, 'export default class {}')
expect(() => store.install(manifest, 'other code')).toThrow(/already installed/) })
it('records sourceUrl when provided', () => { const manifest = createTestManifest({ id: 'ext-plugin' }) store.install(manifest, 'code', 'https://example.com/plugin')
const entry = store.get('ext-plugin') expect(entry!.sourceUrl).toBe('https://example.com/plugin') })
it('emits change event on install', () => { const cb = vi.fn() store.on('change', cb)
const manifest = createTestManifest({ id: 'ext-plugin' }) store.install(manifest, 'code')
expect(cb).toHaveBeenCalledTimes(1) }) })
describe('uninstall', () => { it('removes the plugin entry', () => { const manifest = createTestManifest({ id: 'ext-plugin' }) store.install(manifest, 'code') store.uninstall('ext-plugin')
expect(store.isExternal('ext-plugin')).toBe(false) expect(store.getAll()).toHaveLength(0) })
it('revokes the blob URL', () => { const manifest = createTestManifest({ id: 'ext-plugin' }) store.install(manifest, 'code') store.uninstall('ext-plugin')
expect(URL.revokeObjectURL).toHaveBeenCalled() })
it('removes from localStorage', () => { const manifest = createTestManifest({ id: 'ext-plugin' }) store.install(manifest, 'code') store.uninstall('ext-plugin')
const stored = JSON.parse(mockStorage._storage['reams:external-plugins'] ?? '{}') expect(stored['ext-plugin']).toBeUndefined() })
it('is a no-op for unknown plugins', () => { expect(() => store.uninstall('unknown')).not.toThrow() })
it('emits change event on uninstall', () => { const manifest = createTestManifest({ id: 'ext-plugin' }) store.install(manifest, 'code')
const cb = vi.fn() store.on('change', cb) store.uninstall('ext-plugin')
expect(cb).toHaveBeenCalledTimes(1) }) })
describe('restoreAll', () => { it('re-registers all persisted plugins with a PluginRegistry', () => { const manifest = createTestManifest({ id: 'ext-plugin' }) store.install(manifest, 'export default class {}')
// Create a fresh store (simulating page reload) const store2 = new ExternalPluginStore() const registry = new PluginRegistry() store2.restoreAll(registry)
expect(registry.has('ext-plugin')).toBe(true) expect(store2.isExternal('ext-plugin')).toBe(true) })
it('creates new blob URLs for restored plugins', () => { const manifest = createTestManifest({ id: 'ext-plugin' }) store.install(manifest, 'code') const originalBlobUrl = store.get('ext-plugin')!.blobUrl
const store2 = new ExternalPluginStore() const registry = new PluginRegistry() store2.restoreAll(registry)
const restoredBlobUrl = store2.get('ext-plugin')!.blobUrl expect(restoredBlobUrl).toMatch(/^blob:/) // New blob URL should be different from original (different session) expect(restoredBlobUrl).not.toBe(originalBlobUrl) })
it('skips plugins that are already registered (idempotent)', () => { const manifest = createTestManifest({ id: 'ext-plugin' }) store.install(manifest, 'code')
const store2 = new ExternalPluginStore() const registry = new PluginRegistry() store2.restoreAll(registry) // Calling again should not throw expect(() => store2.restoreAll(registry)).not.toThrow() })
it('handles corrupted localStorage gracefully', () => { mockStorage._storage['reams:external-plugins'] = 'not valid json'
const store2 = new ExternalPluginStore() const registry = new PluginRegistry() expect(() => store2.restoreAll(registry)).not.toThrow() expect(store2.getAll()).toHaveLength(0) }) })
describe('isExternal', () => { it('returns false for unknown plugins', () => { expect(store.isExternal('unknown')).toBe(false) })
it('returns true for installed external plugins', () => { const manifest = createTestManifest({ id: 'ext-plugin' }) store.install(manifest, 'code') expect(store.isExternal('ext-plugin')).toBe(true) }) })
describe('getAll', () => { it('returns all installed external plugins', () => { store.install(createTestManifest({ id: 'a' }), 'code-a') store.install(createTestManifest({ id: 'b' }), 'code-b')
const all = store.getAll() expect(all).toHaveLength(2) expect(all.map(e => e.manifest.id).sort()).toEqual(['a', 'b']) }) })
describe('cleanup', () => { it('revokes all blob URLs', () => { store.install(createTestManifest({ id: 'a' }), 'code-a') store.install(createTestManifest({ id: 'b' }), 'code-b')
store.cleanup()
expect(URL.revokeObjectURL).toHaveBeenCalledTimes(2) }) })})