import type { BobbinClient } from '@/lib/api'
import { BOBBIN_CLIENT_PROVIDER, BobbinError } from '@/lib/api'
import RepositorySourcePage from '@/views/RepositorySourcePage.vue'
import { flushPromises } from '@vue/test-utils'
import { describe, expect, test, vi } from 'vitest'
import { mountIonicRoute } from './support/mount'
const repo = 'at://did:plc:owner/sh.tangled.repo/twisted'
const repoDid = 'did:plc:repository'
const routes = [
{ path: '/repositories/:repo', name: 'repository', component: { template: '
' } },
{ path: '/repositories/:repo/source', name: 'repository-source', component: RepositorySourcePage },
{ path: '/repositories/:repo/commits', name: 'repository-commits', component: { template: '' } },
{ path: '/repositories/:repo/branches', name: 'repository-branches', component: { template: '' } },
{ path: '/repositories/:repo/tags', name: 'repository-tags', component: { template: '' } },
]
function client(overrides: Record = {}) {
return {
getRepo: vi
.fn()
.mockResolvedValue({
uri: repo,
value: {
$type: 'sh.tangled.repo',
createdAt: '2026-08-01T00:00:00Z',
knot: 'knot.example',
name: 'Twisted',
repoDid,
},
}),
getRepositoryDefaultBranch: vi
.fn()
.mockResolvedValue({ name: 'main', hash: 'a'.repeat(40), when: '2026-08-01T00:00:00Z' }),
listRepositoryBranches: vi
.fn()
.mockResolvedValue({ items: [{ name: 'main', hash: 'a'.repeat(40), isDefault: true }] }),
listRepositoryTags: vi.fn().mockResolvedValue({ items: [] }),
getRepositoryTree: vi.fn().mockResolvedValue({ ref: 'main', files: [] }),
getRepositoryBlob: vi
.fn()
.mockResolvedValue({
path: 'src/main.ts',
ref: 'main',
size: 20,
encoding: 'utf-8',
content: 'const one = 1\nconst two = 2',
}),
repositoryBlobUrl: vi.fn(() => 'https://api.example/raw'),
...overrides,
} as unknown as BobbinClient
}
describe('RepositorySourcePage', () => {
test('renders shareable source lines, anchors, wrapping, and download links', async () => {
const fakeClient = client()
const url = `/repositories/${encodeURIComponent(repo)}/source?ref=main&path=src%2Fmain.ts&view=blob`
const wrapper = await mountIonicRoute(RepositorySourcePage, url, routes, {
[BOBBIN_CLIENT_PROVIDER]: () => fakeClient,
navManager: { handleNavigateBack: vi.fn() },
})
await flushPromises()
await flushPromises()
expect(wrapper.findAll('.code-lines li')).toHaveLength(2)
expect(wrapper.get('#L2 a').attributes('href')).toBe('#L2')
expect(wrapper.get('.blob-toolbar a[download]').attributes('href')).toBe('https://api.example/raw')
await wrapper.get('.blob-toolbar button').trigger('click')
expect(wrapper.get('.code-lines').classes()).toContain('code-lines--wrap')
expect(fakeClient.getRepositoryBlob).toHaveBeenCalledWith(repo, 'main', 'src/main.ts', expect.any(Object))
})
test('keeps repository navigation available when a ref cannot be opened', async () => {
const fakeClient = client({
getRepositoryTree: vi.fn().mockRejectedValue(new BobbinError('not-found', 'missing ref')),
})
const wrapper = await mountIonicRoute(
RepositorySourcePage,
`/repositories/${encodeURIComponent(repo)}/source?ref=missing`,
routes,
{ [BOBBIN_CLIENT_PROVIDER]: () => fakeClient, navManager: { handleNavigateBack: vi.fn() } },
)
await flushPromises()
await flushPromises()
expect(wrapper.findAll('.repository-navigation a')).toHaveLength(5)
expect(wrapper.text()).toContain('Record not found')
})
})