import { test, describe } from 'node:test' import assert from 'node:assert' import fs from 'node:fs' import os from 'node:os' import path from 'node:path' import { escHtml, escMd, pickFeatured, buildFeaturedHtml, readAllMeta, generateListPageMarkdown, generateDetailPageMarkdown, patchIndexFeaturedContent, FEATURED_START, FEATURED_END, } from '../tools/presentation-contracts.mjs' function mockSitePath(p) { return p.startsWith('/') ? p : `/${p}` } describe('escHtml / escMd', () => { test('escHtml neutralise < > & "', () => { assert.strictEqual(escHtml(''), '<a href="x">') }) test('escMd échappe les pipes tableaux markdown', () => { assert.strictEqual(escMd('a|b'), 'a\\|b') }) }) describe('pickFeatured', () => { test('priorité aux entrées featured puis order', () => { const items = [ { slug: 'c', title: 'C', order: 3, featured: false }, { slug: 'a', title: 'A', order: 1, featured: true }, { slug: 'b', title: 'B', order: 2, featured: true }, ] const got = pickFeatured(items) assert.deepStrictEqual( got.map((x) => x.slug), ['a', 'b'], ) }) test('sans featured : trois premiers par order', () => { const items = [ { slug: 'z', title: 'Z', order: 10 }, { slug: 'a', title: 'A', order: 1 }, { slug: 'b', title: 'B', order: 2 }, { slug: 'c', title: 'C', order: 3 }, ] const got = pickFeatured(items) assert.deepStrictEqual( got.map((x) => x.slug), ['a', 'b', 'c'], ) }) test('liste vide', () => { assert.deepStrictEqual(pickFeatured([]), []) }) }) describe('buildFeaturedHtml', () => { test('génère des liens et échappe le titre', () => { const html = buildFeaturedHtml( [{ slug: 'x', title: '', description: 'd & co', order: 1 }], mockSitePath, ) assert.match(html, /href="\/presentations\/x\/"/) assert.match(html, /<evil>/) assert.match(html, /d & co/) }) test('aucune présentation : commentaire', () => { assert.strictEqual(buildFeaturedHtml([], mockSitePath), '') }) }) describe('readAllMeta (répertoire temporaire)', () => { test('ignore dossier sans meta.json', () => { const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'talks-slides-')) fs.mkdirSync(path.join(dir, 'empty'), { recursive: true }) const okDir = path.join(dir, 'ok') fs.mkdirSync(okDir, { recursive: true }) fs.writeFileSync(path.join(okDir, 'meta.json'), JSON.stringify({ title: 'T', description: '', order: 1 })) const list = readAllMeta(dir) assert.strictEqual(list.length, 1) assert.strictEqual(list[0].slug, 'ok') assert.strictEqual(list[0].title, 'T') }) }) describe('generateListPageMarkdown / generateDetailPageMarkdown', () => { const p = { slug: 'demo', title: 'Démo', description: 'Desc', tags: ['a', 'b'], order: 1, } test('liste contient fiche + slides + tags', () => { const md = generateListPageMarkdown([p], mockSitePath) assert.match(md, /href="\/presentations\/demo\/"/) assert.match(md, /href="\/slides\/demo\/1"/) assert.match(md, /vp-raw/) assert.match(md, /`a` · `b`/) }) test('détail contient QrEncode et boutons', () => { const md = generateDetailPageMarkdown(p, mockSitePath) assert.match(md, //) assert.match(md, /class="vp-button vp-raw"/) assert.match(md, /Voir les slides/) assert.match(md, /## Partager les slides/) }) test('détail sans tags : pas de ligne Tags', () => { const md = generateDetailPageMarkdown({ ...p, tags: [] }, mockSitePath) assert.ok(!md.includes('**Tags :**')) }) }) describe('patchIndexFeaturedContent', () => { test('remplace la zone entre marqueurs', () => { const raw = `head\n${FEATURED_START}\nold\n${FEATURED_END}\ntail` const { ok, content } = patchIndexFeaturedContent( raw, [{ slug: 's', title: 'T', description: 'D', order: 1 }], mockSitePath, ) assert.strictEqual(ok, true) assert.ok(content.includes('talk-card')) assert.ok(content.includes('head')) assert.ok(content.includes('tail')) assert.ok(!content.includes('old')) }) test('marqueurs absents : ok false', () => { const { ok } = patchIndexFeaturedContent('nope', [], mockSitePath) assert.strictEqual(ok, false) }) })