OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-web-ui / test / render.test.js

147 lines · javascript ref:
import test from 'brittle'
import { renderMarkdown, renderDiff, renderCode, renderPagination, renderBranchSelector, detectLanguage } from '../lib/render.js'

// ── Markdown ──────────────────────────────────────────────────

test('renderMarkdown — headings', (t) => {
  t.ok(renderMarkdown('# Title').includes('<h1>Title</h1>'))
  t.ok(renderMarkdown('## Sub').includes('<h2>Sub</h2>'))
  t.ok(renderMarkdown('### Third').includes('<h3>Third</h3>'))
})

test('renderMarkdown — bold and italic', (t) => {
  t.ok(renderMarkdown('**bold**').includes('<strong>bold</strong>'))
  t.ok(renderMarkdown('*italic*').includes('<em>italic</em>'))
})

test('renderMarkdown — code blocks', (t) => {
  const md = '```js\nconst x = 1\n```'
  const html = renderMarkdown(md)
  t.ok(html.includes('language-js'))
  t.ok(html.includes('const x = 1'))
})

test('renderMarkdown — inline code', (t) => {
  t.ok(renderMarkdown('use `npm install`').includes('<code>npm install</code>'))
})

test('renderMarkdown — links and images', (t) => {
  t.ok(renderMarkdown('[link](http://x.com)').includes('<a href="http://x.com">link</a>'))
  t.ok(renderMarkdown('![alt](img.png)').includes('<img src="img.png"'))
})

test('renderMarkdown — lists', (t) => {
  const html = renderMarkdown('- item 1\n- item 2')
  t.ok(html.includes('<li>item 1</li>'))
  t.ok(html.includes('<ul>'))
})

test('renderMarkdown — empty input', (t) => {
  t.is(renderMarkdown(''), '')
  t.is(renderMarkdown(null), '')
})

test('renderMarkdown — escapes HTML', (t) => {
  const html = renderMarkdown('<script>alert("xss")</script>')
  t.absent(html.includes('<script>'))
  t.ok(html.includes('&lt;script&gt;'))
})

// ── Diff ──────────────────────────────────────────────────────

test('renderDiff — colors additions and deletions', (t) => {
  const patch = '--- a/file.js\n+++ b/file.js\n@@ -1,3 +1,3 @@\n context\n-old line\n+new line'
  const html = renderDiff(patch)
  t.ok(html.includes('diff-file'))
  t.ok(html.includes('diff-hunk'))
  t.ok(html.includes('diff-del'))
  t.ok(html.includes('diff-add'))
  t.ok(html.includes('diff-ctx'))
})

test('renderDiff — empty', (t) => {
  t.is(renderDiff(''), '')
  t.is(renderDiff(null), '')
})

// ── Code with line numbers ───────────────────────────────────

test('renderCode — line numbers', (t) => {
  const html = renderCode('line 1\nline 2\nline 3')
  t.ok(html.includes('data-line="1"'))
  t.ok(html.includes('data-line="3"'))
  t.ok(html.includes('line-num'))
  t.ok(html.includes('line-code'))
  t.ok(html.includes('code-table'))
})

test('renderCode — escapes HTML in code', (t) => {
  const html = renderCode('<div>hello</div>')
  t.ok(html.includes('&lt;div&gt;'))
})

test('renderCode — empty', (t) => {
  t.is(renderCode(''), '')
})

// ── Pagination ───────────────────────────────────────────────

test('renderPagination — single page returns empty', (t) => {
  t.is(renderPagination(10, 1, 20, '/issues'), '')
})

test('renderPagination — multiple pages', (t) => {
  const html = renderPagination(100, 3, 20, '/issues')
  t.ok(html.includes('page-link'))
  t.ok(html.includes('page-current'))
  t.ok(html.includes('Prev'))
  t.ok(html.includes('Next'))
  t.ok(html.includes('page=2'))
  t.ok(html.includes('page=4'))
})

test('renderPagination — first page no prev', (t) => {
  const html = renderPagination(100, 1, 20, '/issues')
  t.absent(html.includes('Prev'))
  t.ok(html.includes('Next'))
})

test('renderPagination — last page no next', (t) => {
  const html = renderPagination(100, 5, 20, '/issues')
  t.ok(html.includes('Prev'))
  t.absent(html.includes('Next'))
})

// ── Branch selector ──────────────────────────────────────────

test('renderBranchSelector — renders options', (t) => {
  const html = renderBranchSelector(['main', 'dev', 'release'], 'dev', '#/repos/foo/tree')
  t.ok(html.includes('<select'))
  t.ok(html.includes('branch-selector'))
  t.ok(html.includes('<option value="main"'))
  t.ok(html.includes('<option value="dev" selected'))
  t.ok(html.includes('<option value="release"'))
})

test('renderBranchSelector — empty branches', (t) => {
  t.is(renderBranchSelector([], 'main', '/'), '')
  t.is(renderBranchSelector(null, 'main', '/'), '')
})

// ── Language detection ───────────────────────────────────────

test('detectLanguage — common extensions', (t) => {
  t.is(detectLanguage('app.js'), 'javascript')
  t.is(detectLanguage('main.py'), 'python')
  t.is(detectLanguage('lib.rs'), 'rust')
  t.is(detectLanguage('style.css'), 'css')
  t.is(detectLanguage('data.json'), 'json')
  t.is(detectLanguage('script.sh'), 'bash')
  t.is(detectLanguage('player.gd'), 'gdscript')
})

test('detectLanguage — unknown', (t) => {
  t.is(detectLanguage('file.xyz'), '')
  t.is(detectLanguage(''), '')
})