Files
html-transform/test/cli-e2e.test.js
T
skytek_xinliang c8ff825722 feat: derive flow refs and refine maintenance contracts
Parse legacy flow blocks from prototype guides into flowRefs so prompts can rely on extracted evidence instead of hardcoded JSP flow knowledge.

Refine API endpoint matching for special route roles and prototype types, and expand maintenance contracts with row action conditions and checklist-derived business rules. Update README docs to reflect the new contract fields and app-map output.feat: derive flow refs and refine maintenance contracts

Parse legacy flow blocks from prototype guides into flowRefs so prompts can rely on extracted evidence instead of hardcoded JSP flow knowledge.

Refine API endpoint matching for special route roles and prototype types, and expand maintenance contracts with row action conditions and checklist-derived business rules. Update README docs to reflect the new contract fields and app-map output.
2026-05-21 16:05:39 +08:00

89 lines
3.2 KiB
JavaScript

import test from 'node:test'
import assert from 'node:assert/strict'
import { execFile } from 'node:child_process'
import { mkdir, readFile, writeFile } from 'node:fs/promises'
import { join } from 'node:path'
import { tmpdir } from 'node:os'
import { mkdtemp } from 'node:fs/promises'
import { promisify } from 'node:util'
const exec = promisify(execFile)
const cli = new URL('../src/cli.js', import.meta.url).pathname
test('CLI runs doctor and scan against one prototype', async () => {
const cwd = await mkdtemp(join(tmpdir(), 'ht-e2e-'))
await mkdir(join(cwd, 'packages/prototype'), { recursive: true })
await writeFile(join(cwd, 'packages/prototype/index.html'), `
<main>
<h1>Customer Portal</h1>
<form>
<label>Email</label>
<input name="email" required>
<button>Submit</button>
</form>
</main>
`)
await writeFile(join(cwd, 'packages/prototype/portal.md'), `
# Portal Guide
| 雛型檔 | 舊 JSP 來源 | 功能 |
| --- | --- | --- |
| [\`index.html\`](index.html) | \`legacy/index.jsp\` | Customer portal entry |
`)
const doctor = await exec('node', [cli, 'doctor'], { cwd })
await exec('node', [cli, 'scan'], { cwd })
const contract = await readFile(join(cwd, '.ht/spec/index.ui-contract.md'), 'utf8')
const spec = JSON.parse(await readFile(join(cwd, '.ht/spec/index.spec.json'), 'utf8'))
const validation = JSON.parse(await readFile(join(cwd, '.ht/spec/index.validation.json'), 'utf8'))
const appMap = JSON.parse(await readFile(join(cwd, '.ht/app-map.json'), 'utf8'))
assert.match(doctor.stdout, /ok prototype directory/)
assert.match(contract, /Customer Portal/)
assert.doesNotMatch(contract, /Recommended template/)
assert.equal(spec.pageContract.title, null)
assert.deepEqual(pick(spec.pageContract.forms[0].fields[0], ['name', 'label', 'type', 'required']), {
name: 'email',
label: 'Email',
type: 'input',
required: true
})
assert.equal(validation.requiresHumanReview, false)
assert.equal(appMap.routes[0].prototype, 'index.html')
assert.equal(appMap.routes[0].kind, 'feature-page')
assert.equal(appMap.routes[0].layout, 'template-app')
assert.equal(appMap.routes[0].recommendedTemplate, undefined)
assert.equal(appMap.routes[0].evidence.recommendedTemplate, undefined)
assert.equal(appMap.routes[0].guide.legacyJsp, 'legacy/index.jsp')
assert.equal(appMap.routes[0].guide.description, 'Customer portal entry')
assert.equal(appMap.guideSources[0].source, 'portal.md')
})
function pick(object, keys) {
return Object.fromEntries(keys.map((key) => [key, object[key]]))
}
test('CLI help only exposes MVP commands', async () => {
const result = await exec('node', [cli, 'help'])
assert.match(result.stdout, /doctor/)
assert.match(result.stdout, /scan/)
assert.doesNotMatch(result.stdout, /plan/)
assert.doesNotMatch(result.stdout, /run/)
assert.doesNotMatch(result.stdout, /diff/)
assert.doesNotMatch(result.stdout, /verify/)
})
test('CLI rejects removed pipeline commands', async () => {
await assert.rejects(
exec('node', [cli, 'plan']),
(error) => {
assert.equal(error.code, 1)
assert.match(error.stdout, /doctor/)
assert.match(error.stdout, /scan/)
return true
}
)
})