import assert from 'node:assert/strict'; import { test } from 'node:test'; import { escapeHtml, html, raw } from '../src/render/html.ts'; import { linkify } from '../src/render/text.ts'; import { renderPost } from '../src/render/post.ts'; import { renderError } from '../src/render/error.ts'; import { oneSegment, type Media, type Post } from '../src/types.ts'; test('interpolations are escaped, Raw values are not', () => { assert.equal(html`

${''}

`.value, '

<script>alert(1)</script>

'); assert.equal(html`

${raw('ok')}

`.value, '

ok

'); assert.equal(html`${['a', '']}`.value, 'a<b>'); assert.equal(html`${undefined}${null}${false}`.value, ''); }); test('attribute-breaking characters are escaped', () => { assert.equal(escapeHtml(`" onload='x'`), '" onload='x''); }); test('post text is escaped before links are made', () => { const out = linkify(' https://example.com/a', 'x').value; assert.ok(!out.includes(' { assert.ok(linkify('hi @nasa', 'tiktok').value.includes('https://www.tiktok.com/@nasa')); assert.ok(linkify('hi @nasa', 'bluesky').value.includes('https://bsky.app/profile/nasa')); // Threads has no hashtag pages, so a tag stays plain text there. assert.ok(!linkify('#moon', 'threads').value.includes(' { assert.equal(linkify('a\nb', 'x').value, 'a
b'); }); function post(overrides: Partial = {}): Post { return { platform: 'instagram', platformLabel: 'Instagram', originalUrl: 'https://www.instagram.com/p/ABC/', author: { handle: '@nasa' }, textPosition: 'below', segments: oneSegment({ text: 'caption', media: [{ kind: 'image', url: 'https://cdn/1.jpg' }, { kind: 'image', url: 'https://cdn/2.jpg' }], }), ...overrides, }; } function withMedia(media: Media[], overrides: Partial = {}): Post { return post({ segments: oneSegment({ text: 'caption', media }), ...overrides }); } test('media is proxied, never linked straight at the CDN', () => { const page = renderPost(post()); assert.ok(!page.includes('https://cdn/1.jpg'), 'upstream URLs must not reach the page'); assert.equal((page.match(/src="\/m\//g) ?? []).length, 2); }); test('an HLS video is linked directly, because a proxy cannot rewrite a playlist', () => { const page = renderPost(withMedia([ { kind: 'video', url: 'https://video.bsky.app/x/playlist.m3u8', hls: true, direct: true }, ])); assert.ok(page.includes('https://video.bsky.app/x/playlist.m3u8')); }); test('text sits below the media for Instagram and above it for X', () => { const below = renderPost(post()); assert.ok(below.indexOf('class="media"') < below.indexOf('class="text"')); const above = renderPost(post({ platform: 'x', platformLabel: 'X', textPosition: 'above' })); assert.ok(above.indexOf('class="text"') < above.indexOf('class="media"')); }); test('the layout toggle only appears when there is more than one item', () => { assert.ok(renderPost(post()).includes('data-view="grid"')); assert.ok(!renderPost(withMedia([{ kind: 'image', url: 'https://cdn/1.jpg' }])) .includes('data-view="grid"')); }); test('a video gets native controls and a source, not an iframe', () => { const page = renderPost(withMedia([{ kind: 'video', url: 'https://cdn/v.mp4' }])); assert.ok(page.includes(' { const page = renderPost(post()); assert.ok(page.includes('data-url="https://www.instagram.com/p/ABC/"')); assert.ok(page.includes('https://www.instagram.com/p/ABC/')); }); test('a failure still hands the link back', () => { const page = renderError({ platform: 'tiktok', platformLabel: 'TikTok', originalUrl: 'https://www.tiktok.com/@a/video/1', heading: 'Could not read that TikTok post', detail: 'TikTok showed a verification puzzle instead of the post.', }); assert.ok(page.includes('data-url="https://www.tiktok.com/@a/video/1"')); assert.ok(page.includes('Open on TikTok')); assert.ok(page.includes('verification puzzle')); });