/** * Tests du plugin slide-dists en dev VitePress (`/slides//` → dist-/). * * Régression couverte (soumission initiale) : * En dev, des requêtes du type `GET /slides//1.md?import&…` recevaient la page HTML * du portail VitePress (Content-Type: text/html) au lieu d’être ignorées ou traitées * comme des modules JS. Le navigateur affichait alors : * « Expected a JavaScript-or-Wasm module script but the server responded with a MIME type of text/html » * (middleware sirv trop large + repli SPA VitePress sur du HTML pour chemins inconnus.) * * Contrat : pour ces URLs « module Vite » sous /slides/, la réponse ne doit **jamais** * être du `text/html` (404 texte ou autre non-HTML est acceptable). */ import { test } from 'node:test' import assert from 'node:assert/strict' import fs from 'node:fs' import http from 'node:http' import path from 'node:path' import { fileURLToPath } from 'node:url' import { execFileSync } from 'node:child_process' import { createServer } from 'vitepress' const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..') const deckIndex = path.join(root, 'dist-kubernetes-operators', 'index.html') if (!fs.existsSync(deckIndex)) { const e = { ...process.env, TALKS_PORTAL_DEV: '1' } delete e.SITE_BASE execFileSync(process.execPath, [path.join(root, 'tools', 'build-deck.mjs'), 'kubernetes-operators'], { cwd: root, stdio: 'inherit', env: e, }) } function get(url, headers = {}) { return new Promise((resolve, reject) => { const u = new URL(url) const opts = { hostname: u.hostname, port: u.port, path: u.pathname + u.search, headers } http .get(opts, (res) => { let body = '' res.on('data', (c) => { body += c }) res.on('end', () => resolve({ status: res.statusCode, body, headers: res.headers })) }) .on('error', reject) }) } /** Lance VitePress en mode dev local (TALKS_PORTAL_DEV), exécute le callback, puis ferme. */ async function withDevPortal(callback) { const prevDev = process.env.TALKS_PORTAL_DEV const prevSite = process.env.SITE_BASE process.env.TALKS_PORTAL_DEV = '1' delete process.env.SITE_BASE const server = await createServer('portal', { server: { strictPort: true, host: true }, }) const port = 28740 + Math.floor(Math.random() * 200) await server.listen(port) try { await callback(port) } finally { await server.close() if (prevDev !== undefined) process.env.TALKS_PORTAL_DEV = prevDev else delete process.env.TALKS_PORTAL_DEV if (prevSite !== undefined) process.env.SITE_BASE = prevSite else delete process.env.SITE_BASE } } test('VitePress dev : racine deck → 302 vers …/1 (première planche Slidev)', async () => { await withDevPortal(async (port) => { const base = `http://127.0.0.1:${port}` for (const path of ['/slides/kubernetes-operators/', '/slides/kubernetes-operators']) { const { status, headers } = await get(`${base}${path}`) assert.equal(status, 302, `${path} → attendu 302`) assert.equal(headers.location, '/slides/kubernetes-operators/1', `Location pour ${path}`) } const q = await get(`${base}/slides/kubernetes-operators/?embedded=1`) assert.equal(q.status, 302) assert.equal(q.headers.location, '/slides/kubernetes-operators/1?embedded=1') }) }) test('VitePress dev : GET /slides/kubernetes-operators/1 → 200 (HTML Slidev)', async () => { await withDevPortal(async (port) => { const { status, body } = await get(`http://127.0.0.1:${port}/slides/kubernetes-operators/1`) assert.equal(status, 200, `attendu 200, reçu ${status}`) assert.match(body, /slidev|Slidev|#app/i, 'corps doit ressembler à une page Slidev') }) }) test('Régression MIME module : *.md?import sous /slides// ne renvoie pas text/html (HAR)', async () => { const paths = [ '/slides/kubernetes-operators/1.md?import&t=1775912428995', '/slides/kubernetes-operators/index.md?import', '/slides/kubernetes-operators/slides.md?import', ] await withDevPortal(async (port) => { for (const p of paths) { const { status, headers, body } = await get(`http://127.0.0.1:${port}${p}`, { Accept: '*/*', 'Sec-Fetch-Dest': 'script', 'Sec-Fetch-Mode': 'cors', }) const ct = String(headers['content-type'] || '') assert.ok( !ct.toLowerCase().includes('text/html'), `${p} : interdit text/html (reçu ${status}, Content-Type: ${ct || '(vide)'}) — régression MIME module`, ) assert.ok( !/^<\s*!DOCTYPE\s+html/i.test(body.trim()), `${p} : le corps ne doit pas être un document HTML (ex. shell VitePress)`, ) } }) }) test('VitePress dev : GET …/2 (route client Slidev) → 200 HTML deck', async () => { await withDevPortal(async (port) => { const { status, body } = await get(`http://127.0.0.1:${port}/slides/kubernetes-operators/2`, { Accept: 'text/html', }) assert.equal(status, 200) assert.match(body, /slidev|Slidev|#app/i) }) })