8ebc1da9e4
- Astro statique, EN par defaut + /fr/ + /nl/, detection langue navigateur - i18n par fichiers JSON, ajouter une langue = ajouter des traductions - Contenu original porte a l'identique (diff d'inventaire par langue) - Chronologie legislative corrigee sur sources primaires (PE, Conseil, votes) - Timeline 23 precedents + observatoire 35 items + annuaire 66 outils FOSS, chaque affirmation verifiee et sourcee - Zero requete tierce (teste), lisible JS coupe, axe WCAG AA x2 themes - Version offline monofichier par langue a chaque build - Docker multi-stage vers nginx + CSP stricte auto-generee - CI GitHub Actions (SHA-pinnees) + verification hebdo des liens - CONTRIBUTING : divulgation d'affiliation obligatoire, allowlist de domaines testee en CI
59 lines
2.2 KiB
TypeScript
59 lines
2.2 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { escapeHtml, mdToHtml } from '../../src/lib/md'
|
|
|
|
// The md micro-renderer is the ONLY place dataset prose becomes HTML
|
|
// (set:html in Timeline/Observatory/Directory/Checklist). It must escape
|
|
// first and only ever emit <strong>, <br /> and Ext-policy anchors.
|
|
|
|
describe('escapeHtml', () => {
|
|
it('escapes all five HTML special characters', () => {
|
|
expect(escapeHtml(`<a href="x" title='y'> & </a>`)).toBe(
|
|
'<a href="x" title='y'> & </a>',
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('mdToHtml', () => {
|
|
it('escapes HTML before rendering anything (no injection from overlays)', () => {
|
|
expect(mdToHtml('<script>alert("x")</script>')).toBe(
|
|
'<script>alert("x")</script>',
|
|
)
|
|
expect(mdToHtml('<img src=x onerror=alert(1)>')).toBe('<img src=x onerror=alert(1)>')
|
|
})
|
|
|
|
it('renders **bold** as <strong>', () => {
|
|
expect(mdToHtml('a **very bold** claim')).toBe('a <strong>very bold</strong> claim')
|
|
})
|
|
|
|
it('renders [text](https://url) with the Ext.astro rel policy', () => {
|
|
expect(mdToHtml('see [the ruling](https://example.org/a?b=c&d=e)')).toBe(
|
|
'see <a href="https://example.org/a?b=c&d=e" target="_blank" rel="noopener noreferrer">the ruling</a>',
|
|
)
|
|
})
|
|
|
|
it('never links non-https URLs (they stay literal, escaped text)', () => {
|
|
expect(mdToHtml('[x](http://insecure.example)')).toBe('[x](http://insecure.example)')
|
|
expect(mdToHtml('[x](javascript:alert(1))')).toBe('[x](javascript:alert(1))')
|
|
})
|
|
|
|
it('escapes markup inside link labels', () => {
|
|
expect(mdToHtml('[<b>hi</b>](https://example.org)')).toBe(
|
|
'<a href="https://example.org" target="_blank" rel="noopener noreferrer"><b>hi</b></a>',
|
|
)
|
|
})
|
|
|
|
it('renders bold inside a link label', () => {
|
|
expect(mdToHtml('[**Blaze**, 1994](https://example.org)')).toBe(
|
|
'<a href="https://example.org" target="_blank" rel="noopener noreferrer"><strong>Blaze</strong>, 1994</a>',
|
|
)
|
|
})
|
|
|
|
it('converts line breaks to <br />', () => {
|
|
expect(mdToHtml('one\ntwo')).toBe('one<br />two')
|
|
})
|
|
|
|
it('renders NOTHING else from markdown', () => {
|
|
expect(mdToHtml('# heading * item _em_ `code`')).toBe('# heading * item _em_ `code`')
|
|
})
|
|
})
|