Automerge backed Markdown editor for the Atmosphere
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286import { describe, it, expect, vi, beforeEach } from 'vitest'import { GitHubForge, findForge, isForgeUrl } from '../plugin-forge'
describe('GitHubForge', () => { let forge: GitHubForge
beforeEach(() => { forge = new GitHubForge() })
describe('canHandle', () => { it('returns true for github.com URLs', () => { expect(forge.canHandle('https://github.com/owner/repo')).toBe(true) expect(forge.canHandle('https://www.github.com/owner/repo')).toBe(true) expect(forge.canHandle('http://github.com/owner/repo')).toBe(true) })
it('returns false for non-GitHub URLs', () => { expect(forge.canHandle('https://gitlab.com/owner/repo')).toBe(false) expect(forge.canHandle('https://example.com/plugin')).toBe(false) expect(forge.canHandle('not-a-url')).toBe(false) }) })
describe('parseRepoUrl', () => { it('parses simple repo URL', () => { const result = forge.parseRepoUrl('https://github.com/liamcain/obsidian-calendar-plugin') expect(result).toEqual({ owner: 'liamcain', repo: 'obsidian-calendar-plugin' }) })
it('parses URL with trailing slash', () => { const result = forge.parseRepoUrl('https://github.com/owner/repo/') expect(result).toEqual({ owner: 'owner', repo: 'repo' }) })
it('parses URL with subpaths', () => { const result = forge.parseRepoUrl('https://github.com/owner/repo/tree/main') expect(result).toEqual({ owner: 'owner', repo: 'repo' }) })
it('returns null for invalid URLs', () => { expect(forge.parseRepoUrl('https://github.com/')).toBe(null) expect(forge.parseRepoUrl('https://github.com/owner')).toBe(null) expect(forge.parseRepoUrl('not-a-url')).toBe(null) })
it('returns null for non-GitHub URLs', () => { expect(forge.parseRepoUrl('https://gitlab.com/owner/repo')).toBe(null) }) })
describe('fetchLatestRelease', () => { it('fetches latest non-prerelease with required assets', async () => { vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ ok: true, json: async () => [ { tag_name: 'v1.6.0', prerelease: true, draft: false, assets: [ { name: 'manifest.json', browser_download_url: 'https://example.com/manifest.json' }, { name: 'main.js', browser_download_url: 'https://example.com/main.js' }, ], }, { tag_name: '1.5.10', prerelease: false, draft: false, assets: [ { name: 'manifest.json', browser_download_url: 'https://example.com/v1.5.10/manifest.json' }, { name: 'main.js', browser_download_url: 'https://example.com/v1.5.10/main.js' }, ], }, ], }))
const release = await forge.fetchLatestRelease('liamcain', 'obsidian-calendar-plugin')
expect(release.version).toBe('1.5.10') expect(release.tag).toBe('1.5.10') expect(release.prerelease).toBe(false) expect(release.assets.manifestUrl).toBe('https://example.com/v1.5.10/manifest.json') expect(release.assets.mainJsUrl).toBe('https://example.com/v1.5.10/main.js') })
it('strips leading v from version', async () => { vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ ok: true, json: async () => [ { tag_name: 'v1.5.10', prerelease: false, draft: false, assets: [ { name: 'manifest.json', browser_download_url: 'https://example.com/manifest.json' }, { name: 'main.js', browser_download_url: 'https://example.com/main.js' }, ], }, ], }))
const release = await forge.fetchLatestRelease('owner', 'repo')
expect(release.version).toBe('1.5.10') expect(release.tag).toBe('v1.5.10') })
it('throws when no suitable release found', async () => { vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ ok: true, json: async () => [ { tag_name: '1.0.0', prerelease: false, draft: false, assets: [ // Missing main.js { name: 'manifest.json', browser_download_url: 'https://example.com/manifest.json' }, ], }, ], }))
await expect(forge.fetchLatestRelease('owner', 'repo')).rejects.toThrow( /no suitable release found/i ) })
it('throws when repository not found', async () => { vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ ok: false, status: 404, statusText: 'Not Found', }))
await expect(forge.fetchLatestRelease('owner', 'nonexistent')).rejects.toThrow( /repository not found/i ) })
it('skips draft releases', async () => { vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ ok: true, json: async () => [ { tag_name: 'v2.0.0', prerelease: false, draft: true, // Draft! assets: [ { name: 'manifest.json', browser_download_url: 'https://example.com/v2/manifest.json' }, { name: 'main.js', browser_download_url: 'https://example.com/v2/main.js' }, ], }, { tag_name: 'v1.0.0', prerelease: false, draft: false, assets: [ { name: 'manifest.json', browser_download_url: 'https://example.com/v1/manifest.json' }, { name: 'main.js', browser_download_url: 'https://example.com/v1/main.js' }, ], }, ], }))
const release = await forge.fetchLatestRelease('owner', 'repo') expect(release.version).toBe('1.0.0') }) })
describe('downloadManifest', () => { it('downloads and parses manifest', async () => { const mockManifest = { id: 'test-plugin', name: 'Test Plugin', version: '1.0.0', minAppVersion: '0.0.0', author: 'Test', description: 'A test plugin', }
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ ok: true, json: async () => mockManifest, }))
const result = await forge.downloadManifest({ version: '1.0.0', tag: 'v1.0.0', prerelease: false, assets: { manifestUrl: 'https://example.com/manifest.json', mainJsUrl: 'https://example.com/main.js', }, })
expect(result).toEqual(mockManifest) })
it('throws on download failure', async () => { vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ ok: false, status: 404, }))
await expect( forge.downloadManifest({ version: '1.0.0', tag: 'v1.0.0', prerelease: false, assets: { manifestUrl: 'https://example.com/manifest.json', mainJsUrl: 'https://example.com/main.js', }, }) ).rejects.toThrow(/failed to download manifest/i) }) })
describe('downloadMainJs', () => { it('downloads main.js source', async () => { const mockSource = 'export default class MyPlugin extends Plugin {}'
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ ok: true, text: async () => mockSource, }))
const result = await forge.downloadMainJs({ version: '1.0.0', tag: 'v1.0.0', prerelease: false, assets: { manifestUrl: 'https://example.com/manifest.json', mainJsUrl: 'https://example.com/main.js', }, })
expect(result).toBe(mockSource) })
it('throws on download failure', async () => { vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ ok: false, status: 404, }))
await expect( forge.downloadMainJs({ version: '1.0.0', tag: 'v1.0.0', prerelease: false, assets: { manifestUrl: 'https://example.com/manifest.json', mainJsUrl: 'https://example.com/main.js', }, }) ).rejects.toThrow(/failed to download main\.js/i) }) })})
describe('findForge', () => { it('returns GitHubForge for GitHub URLs', () => { const forge = findForge('https://github.com/owner/repo') expect(forge).not.toBeNull() expect(forge!.name).toBe('GitHub') })
it('returns null for non-forge URLs', () => { const forge = findForge('https://example.com/plugin') expect(forge).toBeNull() })})
describe('isForgeUrl', () => { it('returns true for GitHub URLs', () => { expect(isForgeUrl('https://github.com/owner/repo')).toBe(true) })
it('returns false for non-forge URLs', () => { expect(isForgeUrl('https://example.com/plugin')).toBe(false) })})