Files
jiapuapp/tests/a01-a02-ui-contract.ps1
T
2026-07-14 17:36:43 +08:00

210 lines
10 KiB
PowerShell

$ErrorActionPreference = 'Stop'
function ConvertFrom-Utf8Base64 {
param([string]$Value)
return [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($Value))
}
function Assert-Contains {
param(
[string]$Content,
[string]$Expected,
[string]$Message
)
if ($Content -notmatch [regex]::Escape($Expected)) {
throw $Message
}
}
function Assert-NotContains {
param(
[string]$Content,
[string]$Unexpected,
[string]$Message
)
if ($Content -match [regex]::Escape($Unexpected)) {
throw $Message
}
}
$root = Split-Path -Parent $PSScriptRoot
$entryPath = Join-Path $root 'pages/auth/a01-entry.vue'
$loginPath = Join-Path $root 'pages/auth/a02-login.vue'
$pagesPath = Join-Path $root 'pages.json'
if (-not (Test-Path -LiteralPath $entryPath)) {
throw 'Missing A-01 entry page: pages/auth/a01-entry.vue'
}
$pages = Get-Content -LiteralPath $pagesPath -Raw -Encoding utf8 | ConvertFrom-Json
if ($pages.pages[0].path -ne 'pages/auth/a01-entry') {
throw 'A-01 entry page must be the first route in pages.json'
}
$entry = Get-Content -LiteralPath $entryPath -Raw -Encoding utf8
$login = Get-Content -LiteralPath $loginPath -Raw -Encoding utf8
$accountPasswordLogin = ConvertFrom-Utf8Base64 '6LSm5Y+35a+G56CB55m75b2V'
$wechatLogin = ConvertFrom-Utf8Base64 '5b6u5L+h55m75b2V'
$registerPrompt = ConvertFrom-Utf8Base64 '6L+Y5rKh5pyJ6LSm5Y+377yf'
$registerEntry = ConvertFrom-Utf8Base64 '5rOo5YaM6LSm5Y+3'
$loginEntry = ConvertFrom-Utf8Base64 '55m75b2V'
$dividerGlyph = ConvertFrom-Utf8Base64 '4oyY'
$agreementRequired = ConvertFrom-Utf8Base64 '6K+35YWI6ZiF6K+75bm25ZCM5oSP55u45YWz5Y2P6K6u'
$wechatPending = ConvertFrom-Utf8Base64 '5b6u5L+h55m75b2V5Yqf6IO95YeG5aSH5Lit'
$registerPending = ConvertFrom-Utf8Base64 '5rOo5YaM6aG16Z2i5YeG5aSH5Lit'
$agreementPending = ConvertFrom-Utf8Base64 '5Y2P6K6u6aG16Z2i5YeG5aSH5Lit'
$smsLogin = ConvertFrom-Utf8Base64 '5omL5py66aqM6K+B56CB55m75b2V'
$forgotPassword = ConvertFrom-Utf8Base64 '5b+Y6K6w5a+G56CB'
$getCode = ConvertFrom-Utf8Base64 '6I635Y+W6aqM6K+B56CB'
$loginPending = ConvertFrom-Utf8Base64 '55m75b2V5o6l5Y+j5b6F5o6l5YWl'
$forgotPasswordPending = ConvertFrom-Utf8Base64 '5b+Y6K6w5a+G56CB6aG16Z2i5YeG5aSH5Lit'
$getCodePending = ConvertFrom-Utf8Base64 '6I635Y+W6aqM6K+B56CB5Yqf6IO95b6F5o6l5YWl'
$implicitAgreement = ConvertFrom-Utf8Base64 '55m75b2V5Y2z6KGo56S65ZCM5oSP'
$agreementConfirm = ConvertFrom-Utf8Base64 '5oiR5bey6ZiF6K+75bm25ZCM5oSP'
$agreementGuard = ConvertFrom-Utf8Base64 '6K+35YWI6ZiF6K+75bm25ZCM5oSP55u45YWz5Y2P6K6u'
$legacyAgreementReminder = ConvertFrom-Utf8Base64 '6K+35YWI5Zyo55m75b2V5byV5a+86aG16Z2i5YeG5aSH5Lit6ZiF6K+75bm25ZCM5oSP5Y2P6K6u'
foreach ($requiredEntryCopy in @($wechatLogin, $registerPrompt, $registerEntry, $agreementRequired, $wechatPending, $registerPending, $agreementPending)) {
Assert-Contains -Content $entry -Expected $requiredEntryCopy -Message "Missing A-01 copy: $requiredEntryCopy"
}
Assert-Contains -Content $entry -Expected "url: '/pages/auth/a02-login?agreed=1'" -Message 'A-01 account login must navigate with agreed=1'
Assert-Contains -Content $entry -Expected "<text>$loginEntry</text>" -Message 'A-01 primary entry copy must be 登录'
foreach ($requiredClass in @('auth-header', 'brand-intro', 'entry-actions', 'agreement-row')) {
Assert-Contains -Content $entry -Expected $requiredClass -Message "Missing A-01 page structure: $requiredClass"
}
foreach ($requiredVisualAnchor in @(
'brand-divider--top',
'brand-divider--bottom',
'entry-button--primary',
'entry-button--secondary',
'height: 253rpx',
'padding: 150rpx 56rpx 0',
'width: 426rpx',
'margin: 95rpx 110rpx 0',
'height: 118rpx',
'margin-top: 51rpx',
'margin-top: 98rpx',
'margin-top: 141rpx',
'top: 70rpx',
'width: 64rpx',
'entry-button__content',
'translateX(-20rpx)',
'auth-divider-knot.png',
'auth-title-cloud.png',
'a01-primary-button.png',
'a01-secondary-button.png',
'entry-button__skin',
'align-items: center',
'white-space: nowrap',
'font-size: 20rpx',
'width: 30rpx',
'height: 30rpx'
)) {
Assert-Contains -Content $entry -Expected $requiredVisualAnchor -Message "Missing A-01 calibrated visual anchor: $requiredVisualAnchor"
}
foreach ($rejectedVisualConstruction in @('link-line', 'button-corner--legacy', 'class="auth-button primary-button"', 'auth-button-corner.png', 'button-corner-asset', '.entry-button::after')) {
Assert-NotContains -Content $entry -Unexpected $rejectedVisualConstruction -Message "A-01 retains rejected visual construction: $rejectedVisualConstruction"
}
foreach ($forbiddenClass in @('entryGuideVisual', 'guide-art', 'entry-hit', 'width: 750rpx', 'min-height: 1625rpx')) {
Assert-NotContains -Content $entry -Unexpected $forbiddenClass -Message "A-01 must not render the whole design image with $forbiddenClass"
}
Assert-NotContains -Content $entry -Unexpected '/static/assets/icons/action/create-cloud-v2.png' -Message 'A-01 must not reuse the mismatched create-cloud asset for its title or divider ornaments'
Assert-Contains -Content $entry -Expected 'filter: brightness(0) invert(1)' -Message 'A-01 primary login icon must render in white'
Assert-NotContains -Content $entry -Unexpected $dividerGlyph -Message 'A-01 divider must not use a text glyph as ornament'
if (([regex]::Matches($entry, 'class="entry-button__skin"')).Count -ne 2) {
throw 'A-01 must render one complete opaque skin image for each entry button'
}
if (([regex]::Matches($entry, 'auth-divider-knot\.png')).Count -ne 2) {
throw 'A-01 must render the dedicated divider-knot asset above and below the subtitle'
}
if ($entry -match '(?s)\.entry-button\s*\{[^}]*\bborder\s*:') {
throw 'A-01 entry buttons must not draw their final border with CSS'
}
if ($entry -match '(?s)\.entry-button--(?:primary|secondary)\s*\{[^}]*\bbackground\s*:') {
throw 'A-01 entry buttons must not draw their final surface with CSS'
}
if ($entry -notmatch '(?s)\.agreement-row\s*\{[^}]*align-items:\s*center') {
throw 'A-01 agreement checkbox and copy must align on one horizontal center line'
}
if ($entry -notmatch '(?s)\.agreement-copy\s*\{[^}]*white-space:\s*nowrap') {
throw 'A-01 agreement copy must remain on a single line'
}
if ($entry -match '(?s)\.auth-page\s*\{[^}]*overflow\s*:\s*hidden') {
throw 'A-01 must allow vertical scrolling on small screens'
}
foreach ($requiredLoginCopy in @($accountPasswordLogin, $smsLogin, $forgotPassword, $getCode, $loginPending, $agreementConfirm, $agreementGuard, $getCodePending)) {
Assert-Contains -Content $login -Expected $requiredLoginCopy -Message "Missing A-02 copy: $requiredLoginCopy"
}
Assert-Contains -Content $login -Expected "url: '/pages/auth/a05-reset-password'" -Message 'A-02 forgot-password entry must navigate to A-05'
foreach ($requiredLoginVisualAnchor in @(
'auth-title-cloud.png',
'login-title-cloud',
'a02-login-panel.png',
'login-panel__skin',
'a01-primary-button.png',
'login-submit__skin',
'login-submit__content',
'agreement-row',
'agreement-icon',
'a02-agreement-unchecked.png',
'a02-agreement-checked.png'
)) {
Assert-Contains -Content $login -Expected $requiredLoginVisualAnchor -Message "Missing A-02 calibrated visual anchor: $requiredLoginVisualAnchor"
}
if (([regex]::Matches($login, 'class="login-submit__skin"')).Count -ne 1) {
throw 'A-02 must render one complete opaque skin image for its login button'
}
if (([regex]::Matches($login, 'class="login-panel__skin"')).Count -ne 1) {
throw 'A-02 must render one complete opaque skin image for its login panel'
}
if ($login -match '(?s)\.login-panel\s*\{[^}]*\bborder\s*:') {
throw 'A-02 login panel must not draw its final frame with CSS'
}
if ($login -match '(?s)\.login-panel\s*\{[^}]*\bbackground\s*:') {
throw 'A-02 login panel must not draw its final paper surface with CSS'
}
if ($login -match '(?s)\.login-submit\s*\{[^}]*\bborder\s*:') {
throw 'A-02 login button must not draw its final border with CSS'
}
if ($login -match '(?s)\.login-submit\s*\{[^}]*\bbackground\s*:') {
throw 'A-02 login button must not draw its final surface with CSS'
}
if ($login -notmatch '<view class="agreement-row" @click="toggleAgreement">') {
throw 'A-02 must provide one directly clickable agreement row'
}
if (([regex]::Matches($login, '@click\.stop="prepareAgreement"')).Count -ne 2) {
throw 'A-02 agreement row must retain two non-toggling agreement links'
}
foreach ($requiredAgreementState in @('const agreed = ref(false)', 'const isAgreedRoute = () =>', "if (typeof location !== 'undefined') {", "new URLSearchParams(location.hash.split('?')[1] || '')", 'const pages = getCurrentPages()', 'const currentPage = pages[pages.length - 1]', "return currentPage?.options?.agreed === '1'", 'const syncAgreementFromRoute = () =>', 'agreed.value = isAgreedRoute()', 'onShow(() => {', 'syncAgreementFromRoute()', 'const toggleAgreement = () =>', 'if (agreed.value) return true')) {
Assert-Contains -Content $login -Expected $requiredAgreementState -Message "Missing A-02 agreement state: $requiredAgreementState"
}
Assert-NotContains -Content $login -Unexpected 'onLoad((options) =>' -Message 'A-02 must not leave agreement state initialized only on first load'
Assert-NotContains -Content $login -Unexpected $legacyAgreementReminder -Message 'A-02 must not retain its passive return-to-entry agreement reminder'
Assert-NotContains -Content $login -Unexpected 'agreedFromEntry' -Message 'A-02 must use its local agreed state as the submit gate'
if ($login -match '(?s)\.agreement-icon\s*\{[^}]*\bborder\s*:') {
throw 'A-02 agreement icon must not draw its final circle with CSS'
}
if ($login -match '(?s)\.agreement-icon\s*\{[^}]*\bbackground\s*:') {
throw 'A-02 agreement icon must not draw its final state with CSS'
}
if ($login -match '\.agreement-icon::(?:before|after)') {
throw 'A-02 agreement icon must not draw its final state with a pseudo element'
}
if ($login -match '(?s)\.auth-page\s*\{[^}]*overflow\s*:\s*hidden') {
throw 'A-02 must allow vertical scrolling on small screens'
}
foreach ($forbidden in @('appApi', 'calcMD5', 'loginWithPassword', 'uni.reLaunch', $implicitAgreement)) {
Assert-NotContains -Content $entry -Unexpected $forbidden -Message "A-01 must not contain legacy auth behavior: $forbidden"
Assert-NotContains -Content $login -Unexpected $forbidden -Message "A-02 must not contain legacy auth behavior: $forbidden"
}
Write-Output 'A01-A02-UI-CONTRACT PASS'