/** * Invariant du dépôt : chaque slides//meta.json a une entrée et des fichiers générés. * Exécuter après `npm run sync` (la CI lance sync avant les tests). */ import { test, describe, before } from 'node:test' import assert from 'node:assert' import fs from 'node:fs' import path from 'node:path' import { fileURLToPath } from 'node:url' import { spawnSync } from 'node:child_process' const __dirname = path.dirname(fileURLToPath(import.meta.url)) const repoRoot = path.resolve(__dirname, '..') before(() => { const r = spawnSync(process.execPath, [path.join(repoRoot, 'tools', 'sync-portal.mjs')], { cwd: repoRoot, encoding: 'utf8', }) assert.strictEqual(r.status, 0, r.stderr || r.stdout) }) describe('Cohérence slides ↔ portail (après sync)', () => { test('presentations.json existe et liste les dossiers slides', () => { const jsonPath = path.join(repoRoot, 'portal', '.vitepress', 'data', 'presentations.json') assert.ok(fs.existsSync(jsonPath), 'presentations.json manquant') const data = JSON.parse(fs.readFileSync(jsonPath, 'utf8')) assert.ok(Array.isArray(data)) assert.ok(data.length >= 1, 'au moins une présentation attendue dans ce dépôt') const slideDirs = fs .readdirSync(path.join(repoRoot, 'slides'), { withFileTypes: true }) .filter((d) => d.isDirectory() && !d.name.startsWith('_')) .map((d) => d.name) .filter((name) => fs.existsSync(path.join(repoRoot, 'slides', name, 'meta.json'))) assert.strictEqual( data.length, slideDirs.length, 'nombre d’entrées JSON = nombre de dossiers slides avec meta.json', ) const slugs = new Set(data.map((p) => p.slug)) for (const dir of slideDirs) { assert.ok(slugs.has(dir), `slug manquant pour le dossier slides/${dir}`) } }) test('chaque entrée a title, description, slug', () => { const jsonPath = path.join(repoRoot, 'portal', '.vitepress', 'data', 'presentations.json') const data = JSON.parse(fs.readFileSync(jsonPath, 'utf8')) for (const p of data) { assert.ok(typeof p.title === 'string' && p.title.length > 0, `title requis: ${p.slug}`) assert.ok(typeof p.description === 'string', `description requise: ${p.slug}`) assert.ok(typeof p.slug === 'string' && p.slug.length > 0) } }) test('chaque deck a un global-bottom.vue (pied de page portail)', () => { const data = JSON.parse( fs.readFileSync(path.join(repoRoot, 'portal', '.vitepress', 'data', 'presentations.json'), 'utf8'), ) for (const p of data) { const gb = path.join(repoRoot, 'slides', p.slug, 'global-bottom.vue') assert.ok(fs.existsSync(gb), `slides/${p.slug}/global-bottom.vue manquant (pied de page)`) const raw = fs.readFileSync(gb, 'utf8') assert.ok( raw.includes('_shared/DeckFooter'), `global-bottom doit importer le pied de page partagé: ${p.slug}`, ) } }) test('fiche et liste générées pour chaque slug', () => { const data = JSON.parse( fs.readFileSync(path.join(repoRoot, 'portal', '.vitepress', 'data', 'presentations.json'), 'utf8'), ) const listMd = path.join(repoRoot, 'portal', 'presentations', 'index.md') const listContent = fs.readFileSync(listMd, 'utf8') for (const p of data) { const detail = path.join(repoRoot, 'portal', 'presentations', p.slug, 'index.md') assert.ok(fs.existsSync(detail), `fiche manquante: presentations/${p.slug}/index.md`) const body = fs.readFileSync(detail, 'utf8') assert.match(body, new RegExp(`slides/${p.slug}/`, ''), 'lien deck dans la fiche') assert.match(body, /