117 lines
4.5 KiB
JavaScript
117 lines
4.5 KiB
JavaScript
import test from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import { buildAppMap, buildPageContract, extractRegions, inferRegionSpec, summarizeHtml, validatePageContract } from '../src/lib/html.js'
|
|
|
|
test('summarizeHtml extracts user-visible contract evidence', () => {
|
|
const summary = summarizeHtml(`
|
|
<title>Orders</title>
|
|
<main>
|
|
<h1>Orders</h1>
|
|
<form><label>Email</label><input name="email" required><button>Save</button></form>
|
|
</main>
|
|
`)
|
|
|
|
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('<h1>Hello</h1><p>World</p>')
|
|
|
|
assert.equal(regions.length, 1)
|
|
assert.equal(regions[0].id, 'page-1')
|
|
})
|
|
|
|
test('inferRegionSpec maps forms to VForm', () => {
|
|
const [region] = extractRegions('<main><form><input name="q"><button>Search</button></form></main>')
|
|
const spec = inferRegionSpec(region)
|
|
|
|
assert.equal(spec.vuetifyComponent, 'VForm')
|
|
assert.deepEqual(spec.uiContract.primaryActions, ['Search'])
|
|
})
|
|
|
|
test('buildPageContract creates page-level UI contract', () => {
|
|
const html = `
|
|
<main>
|
|
<h1>Orders</h1>
|
|
<form><label>Email</label><input name="email" required><button>Search</button></form>
|
|
<table><thead><tr><th>Status</th></tr></thead><tbody><tr><td>Pending</td></tr></tbody></table>
|
|
</main>
|
|
`
|
|
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', '<main><input type="password" name="pwd"><button>登入</button></main>'),
|
|
buildSpec('/repo/prototype/portal/app-layout.html', '<main><button>隱藏選單</button><button>登 出</button></main>'),
|
|
buildSpec('/repo/prototype/venue/applications-list.html', '<main><h1>我的申請紀錄</h1><table><tr><th>申請單號</th></tr></table><button>查詢</button></main>')
|
|
]
|
|
|
|
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', '<main><h1>忘記密碼</h1><input name="idno"><button>發送至信箱</button></main>')
|
|
], 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')
|
|
})
|
|
|
|
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`
|
|
})
|
|
}
|
|
}
|