import { describe, expect, test } from 'bun:test' import { DestinationOpener } from '../main/opening' import { githubOpening } from './github' import { destinationKey, openedDestination, openedDestinationKey, openedSubject, parseDestinationUrl, type UrlOpening } from './systems' // Opening by URL (integrations §3): GitHub's declaration, and the core's use // of declarations without naming a system (a GitLab-like stand-in). /** A merge request system shaped like GitLab's, only for these tests. */ const mergeRequests: UrlOpening = { system: 'mrs', names: { system: 'Mrs', noun: 'merge request', kind: 'mr', example: 'https://mrs.test/g/p/-/merge_requests/1' }, short: (locator) => `!${locator.split('/').pop()}`, parseUrl(text) { const m = /^https:\/\/(mrs\.test)\/([\w/-]+)\/-\/merge_requests\/(\d+)(?:[/?#].*)?$/.exec(text.trim()) return m ? { host: m[1]!, locator: `${m[2]!.toLowerCase()}/-/merge_requests/${m[3]}` } : null }, canonicalUrl: (host, locator) => `https://${host}/${locator}`, cloneUrl: (host, locator) => `https://${host}/${locator.split('/-/')[0]}.git`, isRepository: (host, locator, url) => url === `https://${host}/${locator.split('/-/')[0]}.git`, refs: { head: (locator) => ({ ref: `refs/merge-requests/${locator.split('/').pop()}/head`, tracking: `merge-requests/${locator.split('/').pop()}` }), base: (branch) => ({ ref: `refs/heads/${branch}`, tracking: branch }), headOf: (tracking) => { const n = /^merge-requests\/(\d+)$/.exec(tracking)?.[1] return n ? `refs/merge-requests/${n}/head` : null } }, destinationOf(url, tracking) { const repo = /^https:\/\/mrs\.test\/(.+)\.git$/.exec(url)?.[1] const n = /^merge-requests\/(\d+)$/.exec(tracking)?.[1] return repo && n ? { host: 'mrs.test', locator: `${repo}/-/merge_requests/${n}` } : null } } const both = [githubOpening, mergeRequests] describe("GitHub's declaration (github.md §1)", () => { test('a pull request URL, with anything after it ignored, names a canonical locator', () => { for (const url of ['https://github.com/Acme/Code-Browser/pull/212', 'github.com/Acme/Code-Browser/pull/212/files#x', 'https://github.com/acme/code-browser.git/pull/212?w=1']) expect(parseDestinationUrl(url)).toEqual({ system: 'github', host: 'github.com', locator: 'acme/code-browser/pull/212' }) expect(parseDestinationUrl('https://ghe.github.corp.example/o/r/pull/3')).toEqual({ system: 'github', host: 'ghe.github.corp.example', locator: 'o/r/pull/3' }) for (const url of ['https://gitlab.com/o/r/pull/3', 'https://github.com/o/r/issues/3', 'https://github.com/o/r/pull/0', 'https://github.com/../r/pull/3', 'https://github.com/o/r/pull/3x']) expect(parseDestinationUrl(url)).toBeNull() }) test('canonical URL, change key, names, clone URL and refs', () => { const d = { system: 'github', host: 'github.com', locator: 'acme/code-browser/pull/212' } expect(githubOpening.canonicalUrl(d.host, d.locator)).toBe('https://github.com/acme/code-browser/pull/212') // The key format §11.2 has had since cb-g4u: reviews keyed before this keep their key. expect(destinationKey(parseDestinationUrl('github.com/Acme/Code-Browser/pull/212/files#x')!)).toBe('https://github.com/acme/code-browser/pull/212') expect(githubOpening.short(d.locator)).toBe('#212') expect(githubOpening.names).toMatchObject({ system: 'GitHub', noun: 'pull request', kind: 'pr' }) expect(githubOpening.cloneUrl(d.host, d.locator)).toBe('https://github.com/acme/code-browser.git') expect(githubOpening.refs.head(d.locator)).toEqual({ ref: 'refs/pull/212/head', tracking: 'pull/212' }) expect(githubOpening.refs.base('release/1.2')).toEqual({ ref: 'refs/heads/release/1.2', tracking: 'release/1.2' }) expect(githubOpening.refs.headOf('pull/212')).toBe('refs/pull/212/head') expect(githubOpening.refs.headOf('main')).toBeNull() }) test('a remote is the repository whatever its form', () => { for (const url of ['git@github.com:Acme/Code-Browser.git', 'https://github.com/acme/code-browser', 'ssh://git@ssh.github.com:443/Acme/Code-Browser.git']) expect(githubOpening.isRepository('github.com', 'acme/code-browser/pull/1', url)).toBe(true) expect(githubOpening.isRepository('github.com', 'acme/code-browser/pull/1', 'git@github.com:acme/other.git')).toBe(false) expect(githubOpening.isRepository('github.com', 'acme/code-browser/pull/1', 'git@gitlab.com:acme/code-browser.git')).toBe(false) }) }) describe('the core uses only declarations', () => { test('each syntax is tried; a subject is traced back through its remote', () => { expect(parseDestinationUrl('https://mrs.test/G/p/-/merge_requests/342/diffs', both)).toEqual({ system: 'mrs', host: 'mrs.test', locator: 'g/p/-/merge_requests/342' }) expect(parseDestinationUrl('https://github.com/o/r/pull/3', both)?.system).toBe('github') const remotes = [ { remote: 'origin', url: 'https://mrs.test/g/p.git' }, { remote: 'gh', url: 'git@github.com:o/r.git' } ] const mr = { headLabel: 'origin/merge-requests/342', baseLabel: 'merge-base(origin/main)' } expect(openedSubject(mr, both)).toEqual({ system: 'mrs', remote: 'origin', tracking: 'merge-requests/342', head: 'refs/merge-requests/342/head', base: 'main' }) expect(openedDestination({ remotes, subject: mr }, both)).toEqual({ system: 'mrs', host: 'mrs.test', locator: 'g/p/-/merge_requests/342' }) expect(openedDestinationKey({ remotes, subject: mr }, both)).toBe('https://mrs.test/g/p/-/merge_requests/342') expect(openedDestinationKey({ remotes, subject: { headLabel: 'gh/pull/9' } }, both)).toBe('https://github.com/o/r/pull/9') // Not a declared layout, a remote that isn't there, or one of another system. expect(openedDestinationKey({ remotes, subject: { headLabel: 'origin/feature' } }, both)).toBeUndefined() expect(openedDestinationKey({ remotes, subject: { headLabel: 'fork/pull/9' } }, both)).toBeUndefined() expect(openedDestinationKey({ remotes, subject: { headLabel: 'origin/pull/9' } }, both)).toBeUndefined() }) test('opening a URL: the system is asked, then the repository opens the declared destination', async () => { const calls: unknown[] = [] const repos = { openDestination: async (d: unknown, baseRef: string | null) => { calls.push([d, baseRef]) return { info: { id: 'r' } as never, subject: { repoId: 'r' } as never, remote: 'origin', baseRef: baseRef ?? 'main' } } } const opener = new DestinationOpener(repos, { mrs: async () => ({ title: 'Speed up', baseRef: 'develop' }) }, both) const o = await opener.open('https://mrs.test/g/p/-/merge_requests/342') expect(calls).toEqual([[{ system: 'mrs', host: 'mrs.test', locator: 'g/p/-/merge_requests/342' }, 'develop']]) expect(o.opened).toEqual({ system: 'mrs', host: 'mrs.test', locator: 'g/p/-/merge_requests/342', title: 'Speed up', baseRef: 'develop', remote: 'origin' }) // A system that can't be asked opens against the default branch. expect((await opener.open('https://github.com/o/r/pull/1')).opened.baseRef).toBe('main') await expect(opener.open('https://example.com/o/r')).rejects.toThrow('Not a URL this app can open') // The opened subject is attached; a failed attach (a read-only store) still opens it. const attached: unknown[] = [] const attaching = new DestinationOpener(repos, {}, both, async (subject, d) => void attached.push([subject, d.locator])) await attaching.open('https://mrs.test/g/p/-/merge_requests/342') expect(attached).toEqual([[{ repoId: 'r' }, 'g/p/-/merge_requests/342']]) const failing = new DestinationOpener(repos, {}, both, async () => { throw new Error('read-only') }) expect((await failing.open('https://mrs.test/g/p/-/merge_requests/342')).subject).toEqual({ repoId: 'r' } as never) }) })