Skip to content

Commit c5ce31c

Browse files
committed
test: add whatwg cross-tests with browsers
1 parent 931a8b1 commit c5ce31c

3 files changed

Lines changed: 29434 additions & 5 deletions

File tree

tests/whatwg.browser.test.js

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import '@exodus/bytes/encoding.js'
2+
import { percentEncodeAfterEncoding } from '@exodus/bytes/whatwg.js'
3+
import { describe, test, before, after } from 'node:test'
4+
import { labels } from './encoding/fixtures/encodings.cjs'
5+
6+
// The test uses https:// URL query, which is special
7+
const specialquery = ' "#\'<>' // https://url.spec.whatwg.org/#special-query-percent-encode-set
8+
9+
const invalid = new Set(['replacement', 'utf-16le', 'utf-16be']) // https://encoding.spec.whatwg.org/#get-an-encoder
10+
11+
const { window, document } = globalThis
12+
13+
const range = (length, start) => Array.from({ length }, (_, i) => String.fromCodePoint(start + i))
14+
const strings = [
15+
...range(256, 0x20).filter((x) => x !== ' ' && x !== '#'), // we directly set to href
16+
...range(256, 0)
17+
.filter((x) => x !== '#' && x !== '\t' && x !== '\n' && x !== '\r')
18+
.map((x) => `${x}*`),
19+
...range(256, 0)
20+
.filter((x) => x !== '#' && x !== '\t' && x !== '\n' && x !== '\r')
21+
.map((x) => `*${x}*`),
22+
23+
String.fromCodePoint(0xfe_ff),
24+
String.fromCodePoint(0xff_fd),
25+
String.fromCodePoint(0xff_fe),
26+
String.fromCodePoint(0xff_ff),
27+
String.fromCodePoint(0x1_00_00),
28+
String.fromCodePoint(0x2_f8_a6), // max big5
29+
String.fromCodePoint(0x2_f8_a7),
30+
String.fromCodePoint(0x1_10_00),
31+
32+
String.fromCodePoint(42, 0x1_00_00, 0x1_10_00, 42),
33+
String.fromCodePoint(42, 0x1_00_00, 44, 0x1_10_00, 42),
34+
String.fromCodePoint(42, 0x1_00_00, 0x1_10_00, 42),
35+
String.fromCodePoint(42, 0x1_00_00, 44, 0x1_10_00, 42),
36+
37+
String.fromCharCode(0x20, 0x22, 0x3c, 0x3e, 0x60),
38+
String.fromCharCode(0x20, 0x22, 0x24, 0x3c, 0x3e),
39+
String.fromCharCode(0x3f, 0x5e, 0x60, 0x7b, 0x7d),
40+
String.fromCharCode(0x2f, 0x3a, 0x3b, 0x3d, 0x40, 0x5b, 0x5c, 0x5d, 0x7c),
41+
String.fromCharCode(0x24, 0x25, 0x26, 0x2b, 0x2c),
42+
String.fromCharCode(0x21, 0x27, 0x28, 0x29, 0x7e),
43+
44+
String.fromCharCode(0x61, 0x62, 0xd8_00, 0x77, 0x78),
45+
String.fromCharCode(0xd8_00, 0xd8_00),
46+
String.fromCharCode(0x61, 0x62, 0xdf_ff, 0x77, 0x78),
47+
String.fromCharCode(0xdf_ff, 0xd8_00),
48+
49+
range(0x2_00, 0x24).join(''), // from # + 1
50+
range(0x2_00, 0xf6_00).join(''), // user-defined
51+
range(0x2_00, 0xff_00).join(''),
52+
range(0x20_00, 0x24).join(''),
53+
range(0x20_00, 0xf0_00).join(''),
54+
range(0x20_00, 0xf_f0_00).join(''),
55+
'hello' + range(0x20_00, 0xf0_00).join('') + 'abc',
56+
]
57+
58+
// Passes on Chromium, Servo. Webkit is incorrect. Firefox somewhy fails on CI only
59+
const skip =
60+
!document ||
61+
!window ||
62+
process.env.EXODUS_TEST_PLATFORM === 'webkit' ||
63+
process.env.EXODUS_TEST_PLATFORM === 'firefox'
64+
65+
describe('percent-encode after encoding matches browser', { skip }, () => {
66+
let handle
67+
const onmessage = (event) => handle(event.data)
68+
const iframe = document.createElement('iframe')
69+
70+
before(() => {
71+
window.addEventListener('message', onmessage)
72+
document.body.append(iframe)
73+
})
74+
75+
after(() => {
76+
window.removeEventListener('message', onmessage)
77+
iframe.remove()
78+
})
79+
80+
for (const encoding of labels) {
81+
if (invalid.has(encoding)) continue
82+
test(encoding, async (t) => {
83+
let ok = 0
84+
const loaded = new Promise((resolve) => (handle = resolve))
85+
const html = `
86+
<!DOCTYPE html>
87+
<script>
88+
var a = document.createElement('a');
89+
window.parent.postMessage('', '*');
90+
window.addEventListener('message', (e) => {
91+
a.href = 'https://example.com/?' + e.data
92+
window.parent.postMessage(a.search.slice(1), '*')
93+
})
94+
</script>`
95+
iframe.src = `data:text/html;charset=${encoding},${encodeURI(html)}`
96+
await loaded
97+
98+
for (const str of strings) {
99+
const promise = new Promise((resolve) => (handle = resolve))
100+
iframe.contentWindow.postMessage(str, '*')
101+
const actual = percentEncodeAfterEncoding(encoding, str, specialquery)
102+
t.assert.strictEqual(actual, await promise, `${encoding} #${ok + 1}`)
103+
ok++
104+
}
105+
106+
t.assert.strictEqual(ok, strings.length)
107+
})
108+
}
109+
})
110+
111+
// Ensures that behavior mathches everywhere with snapshots
112+
// Combined with the above check, we know that snapshots match reference browser platforms
113+
describe('percent-encode after encoding matches snapshot', () => {
114+
for (const encoding of labels) {
115+
if (invalid.has(encoding)) continue
116+
test(encoding, async (t) => {
117+
const res = []
118+
for (const str of strings) res.push(percentEncodeAfterEncoding(encoding, str, specialquery))
119+
if (t.assert.snapshot) {
120+
t.assert.snapshot(res)
121+
} else {
122+
t.skip('Snapshots are not supported')
123+
}
124+
})
125+
}
126+
})

0 commit comments

Comments
 (0)