// Tier-1 media handling in the repo file viewer: images, SVG, and CSV/TSV.
// renderRepoBlob is pure SSR — we drive it with a stub sync service so the
// test needs no daemon, swarm, or real repo.
import test from 'brittle'
import { renderRepoBlob, renderCsvTable } from '../lib/web/pages/repo-browser.js'
function stubSvc (overrides = {}) {
const sync = {
isBinary: async () => false,
getBranches: async () => [{ name: 'main', current: true }],
getTags: async () => [],
readFile: async () => 'col1,col2\nx,y',
...overrides
}
return { _sync: sync }
}
const CTX = { prefix: '', user: null }
test('renderCsvTable: header row, escaping, quoted fields', (t) => {
const csv = 'name,score\n"a,b",10\n"q""x",<script>'
const html = renderCsvTable(csv, ',')
t.ok(html.includes('<th>name</th>'), 'first row becomes <th>')
t.ok(html.includes('<td>a,b</td>'), 'quoted field keeps embedded comma')
t.ok(html.includes('q"x') || html.includes('q"x'), 'escaped "" quote')
t.ok(html.includes('<script>'), 'cell HTML is escaped')
t.absent(html.includes('<script>'), 'no raw script tag leaks through')
})
test('renderCsvTable: TSV delimiter', (t) => {
const html = renderCsvTable('a\tb\n1\t2', '\t')
t.ok(html.includes('<th>a</th>') && html.includes('<th>b</th>'), 'tab split')
t.ok(html.includes('<td>1</td>') && html.includes('<td>2</td>'), 'tab data row')
})
test('renderCsvTable: row cap reports true total', (t) => {
let csv = 'h\n'
for (let i = 0; i < 1500; i++) csv += i + '\n'
const html = renderCsvTable(csv, ',')
t.ok(html.includes('Showing first 1000 of 1501 rows'), 'caps display, counts all')
})
test('renderRepoBlob: raster image renders as <img> to /raw, no byte read', async (t) => {
let readCalled = false
const svc = stubSvc({ readFile: async () => { readCalled = true; return 'x' } })
const out = await renderRepoBlob(svc, CTX, 'demo', 'data/pixel.png', 'HEAD')
t.ok(/<img src="[^"]*\/repos\/demo\/raw\/data\/pixel\.png"/.test(out), 'img points at raw route')
t.absent(readCalled, 'image bytes are not loaded into a string')
t.ok(out.includes('view raw'), 'offers a raw link')
})
test('renderRepoBlob: svg renders as <img> (not source)', async (t) => {
const out = await renderRepoBlob(svc(), CTX, 'demo', 'logo.svg', 'HEAD')
t.ok(out.includes('/repos/demo/raw/logo.svg'), 'svg uses raw route')
t.ok(out.includes('<img'), 'svg shown as image, not highlighted source')
function svc () { return stubSvc() }
})
test('renderRepoBlob: csv renders a table', async (t) => {
const svc = stubSvc({ readFile: async () => 'col1,col2\nx,y' })
const out = await renderRepoBlob(svc, CTX, 'demo', 'data/t.csv', 'HEAD')
t.ok(out.includes('csv-table'), 'uses csv table')
t.ok(out.includes('<th>col1</th>'), 'csv header rendered')
})
test('renderRepoBlob: ref carried into raw url', async (t) => {
const out = await renderRepoBlob(stubSvc(), CTX, 'demo', 'a.png', 'v1.0')
t.ok(out.includes('/repos/demo/raw/a.png?ref=v1.0'), 'non-HEAD ref appended')
})