24 lines
1.9 KiB
JavaScript
24 lines
1.9 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs/promises';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import {readArticles,articleUrl,home,articlePage} from '../scripts/site.mjs';
|
|
async function fixture(t, files) {
|
|
const dir=await fs.mkdtemp(path.join(os.tmpdir(),'truenews-'));
|
|
t.after(()=>fs.rm(dir,{recursive:true,force:true}));
|
|
for(const [name,text] of Object.entries(files)) await fs.writeFile(path.join(dir,name),text);
|
|
return dir;
|
|
}
|
|
test('Markdown supports metadata, nested URLs, fallback titles, and safe HTML', async t=>{
|
|
const dir=await fixture(t,{'first.md':'---\ntitle: "First <story>"\nurl: /world/first\ndate: 2026-09-12\n---\n## Context\n\n**Strong** text\n\n<script>alert(1)</script>\n\n[bad](javascript:alert)\n','second.md':'# Second story\n\nA paragraph.','ignored.txt':'not an article'});
|
|
const articles=await readArticles(dir);
|
|
assert.equal(articles.length,2);assert.equal(articles[0].url,'/world/first/');
|
|
assert.match(articles[0].html,/<strong>Strong<\/strong>/);assert.doesNotMatch(articles[0].html,/<script|javascript:/);
|
|
assert.equal(articles[1].title,'Second story');assert.equal(articles[1].url,'/second/');
|
|
assert.match(home(articles),/First <story>/);assert.match(articlePage(articles[0],articles),/Context/);
|
|
});
|
|
test('rejects unsafe and reserved paths',()=>{for(const url of ['../oops','/assets/x','/404','https://example.com','/a?x=1','/'])assert.throws(()=>articleUrl(url));assert.equal(articleUrl('hello-world'),'/hello-world/');});
|
|
test('duplicate URLs fail visibly',async t=>{const dir=await fixture(t,{'a.md':'---\nurl: /same\n---\na','b.md':'---\nurl: /same/\n---\nb'});await assert.rejects(readArticles(dir),/Duplicate/);});
|
|
test('empty pages directory renders a useful home page',async t=>{assert.match(home(await readArticles(await fixture(t,{}))),/Your next story starts here/);});
|