Files
html-transform/test/cli-e2e.test.js
T
2026-05-24 11:14:05 +08:00

100 lines
4.0 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 bdd = await readFile(join(cwd, '.ht/spec/index.bdd.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.match(contract, /BDD Scenarios/)
assert.match(contract, /Capture Artifacts/)
assert.match(contract, /Browser Evidence/)
assert.match(bdd, /Feature: Customer portal entry/)
assert.match(bdd, /Scenario: 使用者填寫必要資料並送出成功/)
assert.doesNotMatch(contract, /Recommended template/)
assert.equal(spec.pageContract.title, null)
assert.equal(spec.bddContract.feature, 'Customer portal entry')
assert.equal(spec.bddContract.scenarios[0].type, 'application-submit')
assert.match(spec.captureArtifacts.domSummary, /\.ht\/cache\/prototype\/index\/dom-summary\.json$/)
assert.match(spec.captureArtifacts.accessibilityTree, /\.ht\/cache\/prototype\/index\/accessibility-tree\.json$/)
assert.equal(Array.isArray(spec.browserEvidence.externalResourceFailures), true)
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
}
)
})