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

Status
Pending
` 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` }) } }