import test from 'node:test'
import assert from 'node:assert/strict'
import { buildAppMap, buildAppMapWithGuides, buildPageContract, extractRegions, inferRegionSpec, parsePrototypeGuide, summarizeHtml, validatePageContract } from '../src/lib/html.js'
test('summarizeHtml extracts user-visible contract evidence', () => {
const summary = summarizeHtml(`
Orders
Orders
`)
assert.equal(summary.title, 'Orders')
assert.deepEqual(summary.labels, ['Email'])
assert.deepEqual(summary.buttons, ['Save'])
assert.equal(summary.inputs[0].name, 'email')
assert.equal(summary.inputs[0].required, true)
})
test('extractRegions falls back to a single page region', () => {
const regions = extractRegions('Hello
World
')
assert.equal(regions.length, 1)
assert.equal(regions[0].id, 'page-1')
})
test('inferRegionSpec maps forms to VForm', () => {
const [region] = extractRegions('')
const spec = inferRegionSpec(region)
assert.equal(spec.vuetifyComponent, 'VForm')
assert.deepEqual(spec.uiContract.primaryActions, ['Search'])
})
test('buildPageContract creates page-level UI contract', () => {
const html = `
Orders
`
const regions = extractRegions(html)
const domSummary = summarizeHtml(html)
const contract = buildPageContract({
page: 'orders.html',
source: '/prototype/orders.html',
html,
regions,
domSummary,
screenshotPath: '.ht/cache/prototype/orders/desktop-default.png'
})
assert.equal(contract.page, 'orders.html')
assert.equal(contract.forms[0].fields[0].required, true)
assert.deepEqual(contract.tables[0].headers, ['Status'])
assert.ok(contract.vuetifyComponents.includes('VTable'))
})
test('validatePageContract reports evidence mismatches', () => {
const report = validatePageContract({
textSamples: ['Missing'],
actions: [{ label: 'Save' }],
forms: [],
vuetifyComponents: ['VContainer']
}, {
textSamples: ['Orders'],
buttons: ['Search'],
labels: []
})
assert.equal(report.requiresHumanReview, true)
assert.match(report.warnings.join('\n'), /Missing/)
assert.match(report.warnings.join('\n'), /Save/)
})
test('buildAppMap classifies auth, shell references, and feature pages', () => {
const prototypeDir = '/repo/prototype'
const specs = [
buildSpec('/repo/prototype/portal/login.html', ''),
buildSpec('/repo/prototype/portal/app-layout.html', ''),
buildSpec('/repo/prototype/venue/applications-list.html', '我的申請紀錄
')
]
const appMap = buildAppMap(specs, prototypeDir)
assert.equal(appMap.routes.find((route) => route.prototype === 'portal/login.html').kind, 'auth')
assert.equal(appMap.routes.find((route) => route.prototype === 'portal/login.html').layout, 'template-auth')
assert.equal(buildAppMap([
buildSpec('/repo/prototype/portal/forget-password.html', '忘記密碼
')
], prototypeDir).routes[0].targetRole, 'forgot-password')
assert.equal(appMap.routes.find((route) => route.prototype === 'portal/app-layout.html').kind, 'legacy-shell-reference')
assert.equal(appMap.routes.find((route) => route.prototype === 'portal/app-layout.html').usePrototypeContent, false)
assert.equal(appMap.routes.find((route) => route.prototype === 'venue/applications-list.html').kind, 'feature-page')
assert.equal(appMap.routes.find((route) => route.prototype === 'venue/applications-list.html').layout, 'template-app')
assert.equal(appMap.modules.find((module) => module.name === 'venue').kind, 'feature-module')
})
test('buildAppMap enriches routes with prototype markdown guide entries', () => {
const prototypeDir = '/repo/prototype'
const guide = parsePrototypeGuide('venue.md', `
# Venue 雛型導覽
| 雛型檔 | 舊 JSP 來源 | 舊 PB NVO 來源 | 對應 Vue view(M9) |
| --- | --- | --- | --- |
| [\`query-room.html\`](venue/query-room.html) | \`zte_pro/zte451_02.jsp\` + \`zte451_02_1.jsp\` | \`n_zte451.of_zte451_02\` / \`of_zte451_02_1\` | \`RoomQueryView.vue\` |
`)
const appMap = buildAppMapWithGuides([
buildSpec('/repo/prototype/venue/query-room.html', '全校場地查詢
')
], prototypeDir, [guide])
const route = appMap.routes[0]
assert.equal(appMap.guideSources[0].source, 'venue.md')
assert.equal(route.evidence.prototypeGuide, 'venue.md')
assert.equal(route.guide.legacyJsp, 'zte_pro/zte451_02.jsp + zte451_02_1.jsp')
assert.equal(route.guide.legacyPb, 'n_zte451.of_zte451_02 / of_zte451_02_1')
assert.equal(route.guide.targetView, 'RoomQueryView.vue')
})
function buildSpec(source, html) {
const regions = extractRegions(html)
const domSummary = summarizeHtml(html)
const page = source.split('/').at(-1)
return {
source,
page,
pageContract: buildPageContract({
page,
source,
html,
regions,
domSummary,
screenshotPath: `.ht/cache/prototype/${page.replace(/\.html$/, '')}/desktop-default.png`
})
}
}