// URL normalization: the non-ASCII and malformed-percent cases that made // v7 routing crash or mismatch. See lib/router.js header for the rules. import { test } from 'node:test' import assert from 'node:assert' import html from '@uhhm/buuh-html' import choo from '@uhhm/buuh' function view (state) { return html`
` } test('a literal % in the path does not crash routing', () => { const app = choo() let seen app.route('/deals/:label', (state) => { seen = state return view(state) }) app.toString('/deals/50%off') assert.strictEqual(seen.params.label, '50%off', 'raw segment kept when not decodable') assert.strictEqual(seen.href, '/deals/50%off', 'href kept raw when not decodable') }) test('percent-encoded UTF-8 params decode exactly once', () => { const app = choo() let seen app.route('/user/:name', (state) => { seen = state return view(state) }) app.toString('/user/%F0%9F%9A%82') assert.strictEqual(seen.params.name, '🚂', 'param decoded') assert.strictEqual(seen.href, '/user/🚂', 'href decoded for humans') }) test('double-encoded input is not double-decoded', () => { const app = choo() let seen app.route('/user/:name', (state) => { seen = state return view(state) }) // %2540 is '%40' encoded once; v7 double-decoded it all the way to '@' app.toString('/user/%2540') assert.strictEqual(seen.params.name, '%40', 'decoded exactly once') }) test('non-ASCII literal routes match their encoded locations', () => { const app = choo() let calls = 0 app.route('/café', (state) => { calls++ return view(state) }) app.toString('/caf%C3%A9') assert.strictEqual(calls, 1, 'encoded location matched unencoded route') }) test('NFD input matches an NFC route definition', () => { const app = choo() let calls = 0 app.route('/café', (state) => { // composed é calls++ return view(state) }) app.toString('/café') // decomposed e + combining acute assert.strictEqual(calls, 1, 'unicode-normalized before matching') }) test('unencoded non-ASCII locations route fine', () => { const app = choo() let seen app.route('/user/:name', (state) => { seen = state return view(state) }) app.toString('/user/日本語') assert.strictEqual(seen.params.name, '日本語') }) test('wildcards decode per segment', () => { const app = choo() let seen app.route('/files/*', (state) => { seen = state return view(state) }) app.toString('/files/caf%C3%A9/na%C3%AFve.txt') assert.strictEqual(seen.params.wildcard, 'café/naïve.txt') }) test('hash routing survives multiple hashes', () => { const app = choo({ hash: true }) let calls = 0 app.route('/docs/api/intro', (state) => { calls++ return view(state) }) app.toString('/docs#api#intro') assert.strictEqual(calls, 1, 'every hash became a slash') }) test('query strings decode + and percent-encoding via URLSearchParams', () => { const app = choo() let seen app.route('/', (state) => { seen = state return view(state) }) app.toString('/?q=caf%C3%A9+au+lait&tags=a&tags=b') assert.deepStrictEqual(seen.query, { q: 'café au lait', tags: ['a', 'b'] }) })