完成50%
This commit is contained in:
@@ -1,23 +1,140 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$root = Split-Path -Parent $PSScriptRoot
|
||||
function Read-Page([string]$path) { Get-Content -LiteralPath (Join-Path $root $path) -Raw -Encoding UTF8 }
|
||||
|
||||
$contracts = [ordered]@{
|
||||
'pages/records/r03-gift-list.vue' = @('giftBooks','openGiftBook','createGift','gift-state--empty','gift-state--loading','gift-state--error','/pages/records/r04-gift-editor?mode=create','/pages/records/r04-gift-editor?mode=view&giftId=')
|
||||
'pages/records/r04-gift-editor.vue' = @('giftForm','validateGift','saveGift','confirmDelete','giftId','gift-editor-state--saving','gift-editor-state--error','saveResult','/pages/records/r03-gift-list')
|
||||
'pages/records/r05-ritual-list.vue' = @('rituals','openRitual','createRitual','ritual-state--empty','ritual-state--loading','ritual-state--error','/pages/records/r06-ritual-detail?ritualId=','/pages/records/r07-ritual-editor?mode=create')
|
||||
'pages/records/r06-ritual-detail.vue' = @('ritualDetail','participants','editRitual','ritual-state--expired','ritual-state--privacy','ritual-state--error','/pages/records/r07-ritual-editor?mode=edit&ritualId=')
|
||||
'pages/records/r07-ritual-editor.vue' = @('ritualForm','validateRitual','saveRitual','confirmDelete','ritualId','ritual-editor-state--saving','ritual-editor-state--error','saveResult','/pages/records/r05-ritual-list')
|
||||
'pages/records/r08-growth-journal.vue' = @('growthRecords','recordGrowth','personName','timeline-state--empty','timeline-state--loading','timeline-state--error','growthForm','saveGrowth')
|
||||
'pages/records/r09-life-events.vue' = @('lifeEvents','createLifeEvent','personName','timeline-state--empty','timeline-state--loading','timeline-state--error','lifeEventForm','saveLifeEvent')
|
||||
'pages/records/r10-memo-list.vue' = @('memos','toggleMemo','createMemo','memo-state--empty','memo-state--loading','memo-state--error','memoForm','saveMemo')
|
||||
'pages/records/r11-merit-records.vue' = @('meritRecords','createMerit','totalContribution','merit-state--empty','merit-state--loading','merit-state--error','meritForm','saveMerit')
|
||||
'pages/records/r03-gift-list.vue' = @('listRelativeRecordFixtures','relativeRecords','openRelative','createRelativePreview','relative-state--empty','openPage(','"R04",','relativeId')
|
||||
'pages/records/r04-gift-editor.vue' = @('findRelativeRecordFixture','relativeForm','validateRelative','localRelativePreview','relativeId','relativeName','eventName','giftAmount','requestBack')
|
||||
'pages/records/r05-ritual-list.vue' = @('listCeremonyFixtures','ceremonies','openCeremony','createCeremonyPreview','ceremony-state--empty','openPage(','"R06",','"R07",')
|
||||
'pages/records/r06-ritual-detail.vue' = @('findCeremonyFixture','listTreeMemberPresentationFixtures','ceremonyDetail','invitees','inviteeUserId','memberOptions','editCeremony','ceremonyId','openPage(','"R07",','ceremony-state--expired')
|
||||
'pages/records/r07-ritual-editor.vue' = @('findCeremonyFixture','ceremonyForm','validateCeremony','localCeremonyPreview','ceremonyId','ceremonyType','ceremonyTitle','requestBack')
|
||||
'pages/records/r08-growth-journal.vue' = @('listGrowthRecordFixtures','findTreeMemberPresentationFixture','growthRecords','localGrowthPreview','recordGrowth','requestBack','timeline-state--empty')
|
||||
'pages/records/r09-life-events.vue' = @('findTreeMemberPresentationFixture','人生事件接口尚未开放','serviceState','requestBack')
|
||||
'pages/records/r10-memo-list.vue' = @('listMemoFixtures','memos','createMemoPreview','localMemoPreview','memoTitle','requestBack','memo-state--empty')
|
||||
'pages/records/r11-merit-records.vue' = @('listMeritRecordFixtures','meritRecords','createMeritPreview','localMeritPreview','donorName','meritTitle','totalContribution','requestBack')
|
||||
}
|
||||
|
||||
foreach ($entry in $contracts.GetEnumerator()) {
|
||||
$source = Read-Page $entry.Key
|
||||
foreach ($token in $entry.Value) { if (-not $source.Contains($token)) { throw "$($entry.Key) missing R business anchor: $token" } }
|
||||
foreach ($shared in @('ModulePageBackground','PageHeader','AppButton','AppLoading')) { if (-not $source.Contains($shared)) { throw "$($entry.Key) missing shared primitive: $shared" } }
|
||||
foreach ($token in $entry.Value) {
|
||||
if (-not $source.Contains($token)) { throw "$($entry.Key) missing R contract: $token" }
|
||||
}
|
||||
foreach ($shared in @('ModulePageBackground','PageHeader','AppButton')) {
|
||||
if (-not $source.Contains($shared)) { throw "$($entry.Key) missing shared primitive: $shared" }
|
||||
}
|
||||
foreach ($forbidden in @(
|
||||
'uni.navigateTo', 'uni.navigateBack', 'uni.redirectTo', 'uni.reLaunch',
|
||||
'/pages/records/', 'saveResult', 'finishPage(', 'Date.now()', '.unshift(',
|
||||
'giftId', 'ritualId', 'personName=', '已保存', '已删除'
|
||||
)) {
|
||||
if ($source.Contains($forbidden)) { throw "$($entry.Key) retains forbidden fake-write or old-route contract: $forbidden" }
|
||||
}
|
||||
if ($source -match '<ModulePage(?:\s|/|>)|import\s+ModulePage\s+from') { throw "$($entry.Key) must own its business page" }
|
||||
if ($source -match 'uni\.(showToast|showModal|showLoading|showActionSheet)') { throw "$($entry.Key) must use project feedback primitives" }
|
||||
if ($source -match '(?im)(?<![-\w])position\s*:|overflow\s*:\s*hidden') { throw "$($entry.Key) contains forbidden document-flow declaration" }
|
||||
}
|
||||
|
||||
foreach ($pageAndTarget in @(
|
||||
@{ Path = 'pages/records/r04-gift-editor.vue'; Target = 'R03' },
|
||||
@{ Path = 'pages/records/r06-ritual-detail.vue'; Target = 'R05' },
|
||||
@{ Path = 'pages/records/r07-ritual-editor.vue'; Target = 'R05' }
|
||||
)) {
|
||||
$source = Read-Page $pageAndTarget.Path
|
||||
$safeReturnPattern = '(?s)genealogyId\.value\s*\?\s*returnTo\("{0}".*?:\s*goBack\(\)' -f $pageAndTarget.Target
|
||||
if ($source -notmatch $safeReturnPattern) {
|
||||
throw "$($pageAndTarget.Path) invalid-entry CTA must fall back to goBack without genealogyId"
|
||||
}
|
||||
}
|
||||
|
||||
$r03 = Read-Page 'pages/records/r03-gift-list.vue'
|
||||
$r04 = Read-Page 'pages/records/r04-gift-editor.vue'
|
||||
$r06 = Read-Page 'pages/records/r06-ritual-detail.vue'
|
||||
foreach ($forbiddenCurrency in @('¥', '¥')) {
|
||||
if ($r03.Contains($forbiddenCurrency) -or $r04.Contains($forbiddenCurrency)) {
|
||||
throw "R03/R04 must not invent a currency symbol absent from the backend contract: $forbiddenCurrency"
|
||||
}
|
||||
}
|
||||
foreach ($optionalRelativeField in @('relationName', 'eventName', 'eventTime')) {
|
||||
if ($r04 -match "relativeErrors\.$optionalRelativeField\s*=\s*relativeForm\.$optionalRelativeField\.trim\(\)") {
|
||||
throw "R04 must not make optional backend field required: $optionalRelativeField"
|
||||
}
|
||||
}
|
||||
foreach ($invalidAmountRule in @('amount >= 0', 'String(Number(relativeForm.giftAmount))')) {
|
||||
if ($r04.Contains($invalidAmountRule)) {
|
||||
throw "R04 must preserve optional numeric amount semantics without inventing a boundary or string body: $invalidAmountRule"
|
||||
}
|
||||
}
|
||||
foreach ($inventedInvitationField in @('inviteStatusLabel', 'userId:', 'name:', 'relation:')) {
|
||||
if ($r06.Contains($inventedInvitationField)) {
|
||||
throw "R06 must join inviteeUserId to scoped member options and must not retain invented invitation fields: $inventedInvitationField"
|
||||
}
|
||||
}
|
||||
foreach ($previewGuard in @(
|
||||
@{ Path = 'pages/records/r04-gift-editor.vue'; Pattern = 'editorState\.value\s*===\s*"preview"' },
|
||||
@{ Path = 'pages/records/r07-ritual-editor.vue'; Pattern = 'editorState\.value\s*===\s*"preview"' },
|
||||
@{ Path = 'pages/records/r08-growth-journal.vue'; Pattern = 'dirty:\s*Boolean\(localGrowthPreview\.value\)' },
|
||||
@{ Path = 'pages/records/r10-memo-list.vue'; Pattern = 'dirty:\s*Boolean\(localMemoPreview\.value\)' },
|
||||
@{ Path = 'pages/records/r11-merit-records.vue'; Pattern = 'dirty:\s*Boolean\(localMeritPreview\.value\)' }
|
||||
)) {
|
||||
$source = Read-Page $previewGuard.Path
|
||||
if ($source -notmatch $previewGuard.Pattern) {
|
||||
throw "$($previewGuard.Path) must guard page back while a local preview remains unsaved"
|
||||
}
|
||||
}
|
||||
foreach ($dialogPage in @(
|
||||
'pages/records/r04-gift-editor.vue',
|
||||
'pages/records/r07-ritual-editor.vue',
|
||||
'pages/records/r08-growth-journal.vue',
|
||||
'pages/records/r10-memo-list.vue',
|
||||
'pages/records/r11-merit-records.vue'
|
||||
)) {
|
||||
$source = Read-Page $dialogPage
|
||||
$dialogs = [regex]::Matches($source, '(?s)<AppDialog\b.*?>')
|
||||
if ($dialogs.Count -eq 0 -or ($dialogs | Where-Object { $_.Value -notmatch ':close-on-mask="false"' })) {
|
||||
throw "$dialogPage dialogs must ignore mask taps"
|
||||
}
|
||||
}
|
||||
$r11 = Read-Page 'pages/records/r11-merit-records.vue'
|
||||
foreach ($amountContract in @(
|
||||
'type="digit"',
|
||||
'Number.isFinite(Number(amountInput))',
|
||||
'amount: amountInput ? Number(amountInput) : null',
|
||||
'金额数值(单位待确认)',
|
||||
'localMeritPreview.meritType'
|
||||
)) {
|
||||
if (-not $r11.Contains($amountContract)) {
|
||||
throw "R11 optional numeric amount or preview visibility contract missing: $amountContract"
|
||||
}
|
||||
}
|
||||
|
||||
$r10 = Read-Page 'pages/records/r10-memo-list.vue'
|
||||
foreach ($forbidden in @('toggleMemo', 'memo.done =', '点击标记完成', '点击恢复待办')) {
|
||||
if ($r10.Contains($forbidden)) { throw "R10 must not fake completed-state mutation: $forbidden" }
|
||||
}
|
||||
$r09 = Read-Page 'pages/records/r09-life-events.vue'
|
||||
foreach ($forbidden in @('lifeEvents', 'createLifeEvent', 'saveLifeEvent', 'growth-records', 'LIFE_EVENT')) {
|
||||
if ($r09.Contains($forbidden)) { throw "R09 must remain hard closed without a backend contract: $forbidden" }
|
||||
}
|
||||
|
||||
$runtimeSources = @(
|
||||
'tests/r-business-flow-runtime-smoke.js',
|
||||
'tests/r02-person-detail-runtime-smoke.js',
|
||||
'tests/r02-background-runtime-smoke.js',
|
||||
'tests/module-page-runtime-smoke.js',
|
||||
'tests/module-series-responsive-runtime-smoke.js'
|
||||
) | ForEach-Object { Read-Page $_ }
|
||||
$runtimeText = $runtimeSources -join "`n"
|
||||
foreach ($retired in @('giftId', 'ritualId', 'count=50', 'r02-person-detail?personId=1', 'r08-growth-journal",".timeline-card')) {
|
||||
if ($runtimeText.Contains($retired)) { throw "R runtime smoke retains retired route or fake-volume contract: $retired" }
|
||||
}
|
||||
foreach ($requiredRuntimeFact in @(
|
||||
'genealogyId=1001&mode=view&relativeId=301&sourceKey=R03',
|
||||
'R08 local preview must not enter the official timeline',
|
||||
'R11 local preview must not change the official count',
|
||||
'personId=103',
|
||||
'preview back guard missing'
|
||||
)) {
|
||||
if (-not $runtimeText.Contains($requiredRuntimeFact)) { throw "R runtime smoke misses current contract: $requiredRuntimeFact" }
|
||||
}
|
||||
|
||||
Write-Output 'R-BUSINESS-FLOW-CONTRACT PASS'
|
||||
|
||||
Reference in New Issue
Block a user