feat: 參考backend

This commit is contained in:
skytek_xinliang
2026-05-12 11:03:46 +08:00
parent 10843227a8
commit 58a5a525d7
9 changed files with 1488 additions and 69 deletions
+137
View File
@@ -1,5 +1,7 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import { buildApiCatalog, matchApiEndpoints } from '../src/lib/api-docs.js'
import { buildMaintenanceContract } from '../src/lib/maintenance.js'
import { buildAppMap, buildAppMapWithGuides, buildPageContract, extractRegions, inferRegionSpec, parsePrototypeGuide, summarizeHtml, validatePageContract } from '../src/lib/html.js'
test('summarizeHtml extracts user-visible contract evidence', () => {
@@ -58,6 +60,37 @@ test('buildPageContract creates page-level UI contract', () => {
assert.ok(contract.vuetifyComponents.includes('VTable'))
})
test('buildPageContract infers legacy table field labels and action semantics', () => {
const html = `
<main>
<form>
<table>
<tr><th>場地</th><td><select name="rom_id"><option>R001 大禮堂</option></select></td></tr>
<tr><th>查詢起日</th><td><input name="startDate" maxlength="7"></td></tr>
</table>
<input type="button" value="查詢">
</form>
<table><tr><th>申請單號</th><th>操作</th></tr><tr><td>A001</td><td><input type="button" value="刪除"></td></tr></table>
</main>
`
const contract = buildPageContract({
page: 'query-room.html',
source: '/prototype/query-room.html',
html,
regions: extractRegions(html),
domSummary: summarizeHtml(html),
screenshotPath: null
})
assert.equal(contract.forms[0].fields[0].label, '場地')
assert.deepEqual(contract.forms[0].fields[0].options, ['R001 大禮堂'])
assert.equal(contract.forms[0].fields[1].maxLength, '7')
assert.equal(contract.actions.find((action) => action.label === '查詢').actionType, 'search')
assert.equal(contract.actions.find((action) => action.label === '刪除').scope, 'rowAction')
assert.equal(contract.tables[0].role, 'searchTable')
assert.equal(contract.tables[1].role, 'resultTable')
})
test('validatePageContract reports evidence mismatches', () => {
const report = validatePageContract({
textSamples: ['Missing'],
@@ -118,6 +151,110 @@ test('buildAppMap enriches routes with prototype markdown guide entries', () =>
assert.equal(route.guide.targetView, 'RoomQueryView.vue')
})
test('parsePrototypeGuide attaches checklist items to guide entries', () => {
const guide = parsePrototypeGuide('venue.md', `
# Venue
| 雛型檔 | 舊 JSP 來源 | 對應 Vue view |
| --- | --- | --- |
| [\`apply-room.html\`](venue/apply-room.html) | \`legacy/apply.jsp\` | \`RoomApplyView.vue\` |
### apply-room.html vs legacy
- [ ] 活動名稱必填
- [ ] 使用節次:14 個 checkbox
`)
assert.deepEqual(guide.entries[0].checklist, ['活動名稱必填', '使用節次:14 個 checkbox'])
})
test('buildApiCatalog parses generic markdown API docs and matches routes', () => {
const catalog = buildApiCatalog([
{
source: 'apps/backend/API.md',
markdown: `
## Orders Module
| 方法 | 路徑 | 說明 | 授權 |
| --- | --- | --- | --- |
| GET | \`/api/v1/orders\` | 查詢訂單 | Authorize |
`
},
{
source: 'apps/backend/API_Manual.md',
markdown: `
## 5. Orders API
### 5.1 新增訂單
\`\`\`text
POST /api/v1/orders
\`\`\`
Request
\`\`\`json
{ "orderName": "Demo", "items": [{ "sku": "A001", "qty": 1 }] }
\`\`\`
Response
\`\`\`json
{ "orderNo": "O001" }
\`\`\`
欄位規則:
| 欄位 | 規則 |
| --- | --- |
| \`orderName\` | 必填 |
`
}
])
assert.ok(catalog.endpoints.some((endpoint) => endpoint.id === 'POST /api/v1/orders'))
assert.equal(catalog.fieldRules[0].field, 'orderName')
const matches = matchApiEndpoints({
prototype: 'orders/apply.html',
page: 'apply.html',
module: 'orders',
title: '訂單申請',
guide: { targetView: 'OrderApplyView.vue' },
evidence: { actions: ['存檔'], textSamples: ['訂單'] }
}, catalog)
assert.equal(matches[0].path, '/api/v1/orders')
})
test('buildMaintenanceContract recommends generic templates from evidence', () => {
const spec = buildSpec('/repo/prototype/orders/apply.html', `
<main>
<h1>訂單申請</h1>
<form>
<table>
<tr><th>訂單名稱</th><td><input name="orderName"></td></tr>
<tr><th>明細</th><td><select name="items"><option>A001</option></select></td></tr>
</table>
<input type="button" value="存檔">
</form>
</main>
`)
const route = {
kind: 'feature-page',
prototype: 'orders/apply.html',
page: 'apply.html',
guide: { targetView: 'OrderApplyView.vue' }
}
const contract = buildMaintenanceContract({
route,
spec,
apiMatches: [{ method: 'POST', path: '/api/v1/orders', description: '新增訂單' }]
})
assert.equal(contract.pageKind, 'application')
assert.equal(contract.recommendedTemplate, 'master-detail-c')
assert.equal(contract.dataModel.primaryEntity, 'OrderApply')
})
function buildSpec(source, html) {
const regions = extractRegions(html)
const domSummary = summarizeHtml(html)