const assert = require('node:assert/strict'); const { spawnSync } = require('node:child_process'); const fs = require('node:fs'); const path = require('node:path'); const test = require('node:test'); const root = path.resolve(__dirname, '..', '..'); const harness = path.join(__dirname, 'user-badges-modal-harness.php'); function render() { const result = spawnSync('php', [harness], { encoding: 'utf8', cwd: root }); assert.equal(result.status, 0, `PHP modal harness failed:\n${result.stderr || result.stdout}`); return JSON.parse(result.stdout); } function assertBefore(html, first, second, message, from = 0) { const firstIndex = html.indexOf(first, from); const secondIndex = html.indexOf(second, from); assert.notEqual(firstIndex, -1, `missing ${first}`); assert.notEqual(secondIndex, -1, `missing ${second}`); assert.ok(firstIndex < secondIndex, message); } test('overview matches the reference header, safely renders labels and places owned badges first', () => { const cases = render(); const html = cases.self.html; assert.match(html, /modal-colorful-header colorful-bg c-blue/); assert.match(html, /class="avatar-img"[^>]*>[\s\S]*?width="38"/); assert.doesNotMatch(html, /]*>\s*]*avatar-img/); assert.match(html, /medal-list-box[^>]*flex[^>]*at[^>]*hh|medal-list-box[^>]*hh[^>]*flex/); const listStart = html.indexOf('mini-scrollbar scroll-y max-vh5'); assertBefore(html, '自动已得', '手动已得', 'owned medals keep the reference category order', listStart); assertBefore(html, '手动已得', '手动未得', 'owned medals render before unearned medals', listStart); assert.match(html, /类别 <svg onload=alert\(1\)>/); assert.match(html, /手动未得 <b>危险<\/b>/); assert.match(cases.grant_admin.html, /说明 <script>bad\(\)<\/script>/); assert.doesNotMatch(html, /