feat(editor): link insertion full UI and flow

This commit is contained in:
Florian Sylvain committed 2026-10-12 17:12:36 +02:00
1 parent 03b39e64f2
commit 6f9ae6207a
16 files changed
+2865 -14

No files matched your search

@@ -0,0 +1,91 @@
import { describe, expect, it } from 'vitest'
import { isStoredHrefSafe, neutralizeUnsafeLinks, normalizeLinkUrl } from '../articleLinkUrl'
describe('normalizeLinkUrl accepts', () => {
it.each([
['https://example.org/a?b=1#c', 'https://example.org/a?b=1#c'],
['HTTP://Example.org', 'HTTP://Example.org'],
['mailto:hello@example.org', 'mailto:hello@example.org'],
['/articles/winter-checklist', '/articles/winter-checklist'],
['#measuring', '#measuring'],
[' /page ', '/page'],
])('%s', (raw, href) => {
expect(normalizeLinkUrl(raw)).toBe(href)
})
})
describe('normalizeLinkUrl rejects', () => {
it.each([
'www.example',
'example.com',
'javascript:alert(1)',
'JaVaScRiPt:alert(1)',
' javascript:alert(1)',
'javascript:alert(1)',
'\u0001javascript:alert(1)',
'java\tscript:alert(1)',
'data:text/html;base64,PHA+',
'vbscript:msgbox(1)',
'file:///etc/passwd',
'ftp://example.org/a',
'tel:+33123456789',
'//evil.example',
'/\\evil.example',
'page.html',
'../x',
'http://',
'https://exa mple.org',
'',
'#',
'https://exa\u0001mple.org',
'/\u0001evil',
'#\u0001',
'/a\u007fb',
'/a\u0085b',
])('%j', (raw) => {
expect(normalizeLinkUrl(raw)).toBeNull()
})
})
describe('isStoredHrefSafe', () => {
it.each([
'/a',
'#a',
'page.html',
'../x',
'https://a.example',
'mailto:a@b.example',
'tel:+3312',
])('keeps %s', (href) => {
expect(isStoredHrefSafe(href)).toBe(true)
})
it.each([
'javascript:alert(1)',
' JavaScript:alert(1)',
'java\nscript:alert(1)',
'\u0001javascript:alert(1)',
'data:text/html,<b>',
'vbscript:msgbox(1)',
'file:///etc/passwd',
'',
])('neutralises %s', (href) => {
expect(isStoredHrefSafe(href)).toBe(false)
})
})
describe('neutralizeUnsafeLinks', () => {
it('unwraps unsafe anchors and keeps their text', () => {
const html = neutralizeUnsafeLinks(
'<p>a <a href="javascript:alert(1)">x</a> <a href="data:text/html,b">y</a> <a href="vbscript:z">w</a></p>',
)
expect(html).toBe('<p>a x y w</p>')
})
it('keeps safe anchors untouched', () => {
const html = neutralizeUnsafeLinks('<p><a href="/a" target="_blank">x</a></p>')
expect(html).toBe('<p><a href="/a" target="_blank">x</a></p>')
})
})