视觉审核完成30%
This commit is contained in:
@@ -33,6 +33,7 @@ $root = Split-Path -Parent $PSScriptRoot
|
||||
$entryPath = Join-Path $root 'pages/auth/a01-entry.vue'
|
||||
$legacyLoginPath = Join-Path $root 'pages/auth/a02-login.vue'
|
||||
$pagesPath = Join-Path $root 'pages.json'
|
||||
$globalStylesPath = Join-Path $root 'styles/global.scss'
|
||||
|
||||
if (-not (Test-Path -LiteralPath $entryPath)) {
|
||||
throw 'Missing the single A01 login page.'
|
||||
@@ -48,19 +49,59 @@ if ($pages.pages[0].path -ne 'pages/auth/a01-entry') {
|
||||
if ($pages.pages.path -contains 'pages/auth/a02-login') {
|
||||
throw 'pages.json must not retain the legacy A02 route.'
|
||||
}
|
||||
if ($pages.pages.Count -ne 53) {
|
||||
throw "Expected 53 final routes after the A02 merge, found $($pages.pages.Count)."
|
||||
if ($pages.pages.Count -ne 52) {
|
||||
throw "Expected 52 active routes after the A02 merge and A06 archive, found $($pages.pages.Count)."
|
||||
}
|
||||
|
||||
$entry = Get-Content -LiteralPath $entryPath -Raw -Encoding utf8
|
||||
$globalStyles = Get-Content -LiteralPath $globalStylesPath -Raw -Encoding utf8
|
||||
|
||||
# Typography has one owner, and the title decoration participates in normal flow.
|
||||
Assert-Contains -Content $globalStyles -Expected 'font-family: "STKaiti", "KaiTi", serif' -Message 'Global styles must own the STKaiti/KaiTi font contract.'
|
||||
Assert-NotContains -Content $entry -Unexpected 'font-family:' -Message 'A01 must inherit the global font contract instead of overriding it locally.'
|
||||
|
||||
$headingRule = [regex]::Match($entry, '(?ms)^\.login-heading\s*\{(?<Body>.*?)^\}')
|
||||
if (-not $headingRule.Success) {
|
||||
throw 'A01 is missing the login heading style rule.'
|
||||
}
|
||||
Assert-Contains -Content $headingRule.Groups['Body'].Value -Expected 'flex-direction: column;' -Message 'A01 login heading must lay out its title and divider vertically in normal flow.'
|
||||
Assert-NotContains -Content $headingRule.Groups['Body'].Value -Unexpected 'position:' -Message 'A01 login heading must not use positioning for ordinary vertical layout.'
|
||||
|
||||
$titleRule = [regex]::Match($entry, '(?ms)^\.login-title\s*\{(?<Body>.*?)^\}')
|
||||
if (-not $titleRule.Success) {
|
||||
throw 'A01 is missing the login title style rule.'
|
||||
}
|
||||
Assert-NotContains -Content $titleRule.Groups['Body'].Value -Unexpected 'position:' -Message 'A01 login title must remain in normal flow.'
|
||||
|
||||
$dividerRule = [regex]::Match($entry, '(?ms)^\.title-divider\s*\{(?<Body>.*?)^\}')
|
||||
if (-not $dividerRule.Success) {
|
||||
throw 'A01 is missing the title divider style rule.'
|
||||
}
|
||||
foreach ($positioning in @('position: absolute', 'left: 50%', 'translateX(')) {
|
||||
Assert-NotContains -Content $dividerRule.Groups['Body'].Value -Unexpected $positioning -Message "A01 title divider must not use manual positioning: $positioning"
|
||||
}
|
||||
Assert-Contains -Content $dividerRule.Groups['Body'].Value -Expected 'width: 360rpx;' -Message 'A01 title divider must be wide enough to remain visible on paper.'
|
||||
Assert-Contains -Content $dividerRule.Groups['Body'].Value -Expected 'height: 60rpx;' -Message 'A01 title divider must retain a legible knot and line weight.'
|
||||
Assert-Contains -Content $dividerRule.Groups['Body'].Value -Expected 'filter: brightness(0.68) saturate(1.5) contrast(1.2);' -Message 'A01 title divider must keep sufficient contrast against the paper background.'
|
||||
|
||||
$brandSealRule = [regex]::Match($entry, '(?ms)^\.brand-seal\s*\{(?<Body>.*?)^\}')
|
||||
if (-not $brandSealRule.Success) {
|
||||
throw 'A01 is missing the brand seal style rule.'
|
||||
}
|
||||
Assert-Contains -Content $brandSealRule.Groups['Body'].Value -Expected 'width: 184rpx;' -Message 'A01 brand seal must use the approved smaller width.'
|
||||
Assert-Contains -Content $brandSealRule.Groups['Body'].Value -Expected 'height: 221rpx;' -Message 'A01 brand seal must preserve its aspect ratio at the approved smaller height.'
|
||||
Assert-Contains -Content $brandSealRule.Groups['Body'].Value -Expected 'top: 64rpx;' -Message 'A01 brand seal must keep its approved vertical anchor.'
|
||||
Assert-Contains -Content $brandSealRule.Groups['Body'].Value -Expected 'left: 50%;' -Message 'A01 brand seal must remain horizontally centered.'
|
||||
|
||||
# Final decorative surfaces must come from real bitmap assets.
|
||||
foreach ($asset in @(
|
||||
'a01-red-hall-ink-backdrop-v1.png',
|
||||
'brand-seal.png',
|
||||
'a01-title-divider-v2.png',
|
||||
'a01-primary-button.png',
|
||||
'a01-secondary-button.png',
|
||||
'a01-vnext-divider-v1.png',
|
||||
'a01-scroll-primary-v3.png',
|
||||
'a01-scroll-secondary-v3.png',
|
||||
'a01-scroll-toast-v3.png',
|
||||
'a01-scroll-dialog-v3.png',
|
||||
'auth-wechat.png',
|
||||
'a01-icon-phone-v1.png',
|
||||
'a01-icon-lock-v1.png',
|
||||
@@ -80,10 +121,24 @@ foreach ($obsoleteVisualAsset in @(
|
||||
'a01-login-scroll-frame-v1.png',
|
||||
'a01-vnext-scroll-v1.png',
|
||||
'auth-ink-scenery.png'
|
||||
'a01-title-divider-v2.png',
|
||||
'a01-primary-button-v2.png',
|
||||
'a01-secondary-button-v2.png',
|
||||
'a02-login-panel.png'
|
||||
)) {
|
||||
Assert-NotContains -Content $entry -Unexpected $obsoleteVisualAsset -Message "A01 must not retain the rejected header, scenery, or scroll asset: $obsoleteVisualAsset"
|
||||
}
|
||||
|
||||
$buttonSkinTags = [regex]::Matches($entry, '<image class="button-skin"[^>]+>')
|
||||
if ($buttonSkinTags.Count -ne 2) {
|
||||
throw "A01 must have exactly two visible login button skins, found $($buttonSkinTags.Count)."
|
||||
}
|
||||
foreach ($buttonSkinTag in $buttonSkinTags) {
|
||||
Assert-Contains -Content $buttonSkinTag.Value -Expected 'mode="aspectFit"' -Message 'A01 button skins must use uniform aspectFit rendering.'
|
||||
Assert-NotContains -Content $buttonSkinTag.Value -Unexpected '/opaque/' -Message 'A01 visible button skins must use cleaned transparent v2 assets.'
|
||||
Assert-NotContains -Content $buttonSkinTag.Value -Unexpected 'mode="scaleToFill"' -Message 'A01 button skins must not distort fixed artwork with scaleToFill.'
|
||||
}
|
||||
|
||||
$requiredCopy = @(
|
||||
'55m75b2V5a626LCx',
|
||||
'5a+G56CB55m75b2V',
|
||||
@@ -137,4 +192,13 @@ Assert-NotContains -Content $entry -Unexpected 'a01-icon-eye-closed-v1.png' -Mes
|
||||
Assert-NotContains -Content $entry -Unexpected 'a01-icon-verification-v1.png' -Message 'A01 SMS field must use a message-code icon instead of a success-state checkmark.'
|
||||
Assert-NotContains -Content $entry -Unexpected 'a01-icon-sms-code-v1.svg' -Message 'A01 SMS field must use the custom generated icon instead of the temporary third-party SVG.'
|
||||
Assert-NotContains -Content $entry -Unexpected '@media (max-height:' -Message 'A01 must scroll naturally on short screens instead of compressing the selected design with height breakpoints.'
|
||||
Assert-NotContains -Content $entry -Unexpected 'feedback-toast__skin' -Message 'A01 feedback Toast must use the dedicated nine-slice border asset instead of an absolutely stretched image.'
|
||||
|
||||
$feedbackToastRule = [regex]::Match($entry, '(?ms)^\.feedback-toast\s*\{(?<Body>.*?)^\}')
|
||||
if (-not $feedbackToastRule.Success) {
|
||||
throw 'A01 is missing the custom feedback Toast style rule.'
|
||||
}
|
||||
Assert-Contains -Content $feedbackToastRule.Groups['Body'].Value -Expected "border-image-source: url('/static/assets/foundation/transparent/a01-scroll-toast-v3.png');" -Message 'A01 feedback Toast must use the approved v3 nine-slice asset.'
|
||||
Assert-Contains -Content $feedbackToastRule.Groups['Body'].Value -Expected 'border-image-slice:' -Message 'A01 feedback Toast must declare fixed decorative slices.'
|
||||
Assert-Contains -Content $feedbackToastRule.Groups['Body'].Value -Expected 'fill' -Message 'A01 feedback Toast nine-slice must fill the paper center.'
|
||||
Write-Output 'A01-LOGIN-MERGE-CONTRACT PASS'
|
||||
|
||||
@@ -18,13 +18,67 @@ $buttons = @(
|
||||
'static/assets/foundation/opaque/a01-primary-button.png',
|
||||
'static/assets/foundation/opaque/a01-secondary-button.png'
|
||||
)
|
||||
$v2Buttons = @(
|
||||
'static/assets/foundation/transparent/a01-primary-button-v2.png',
|
||||
'static/assets/foundation/transparent/a01-secondary-button-v2.png'
|
||||
)
|
||||
$v3Skins = @(
|
||||
'static/assets/foundation/transparent/a01-scroll-primary-v3.png',
|
||||
'static/assets/foundation/transparent/a01-scroll-secondary-v3.png',
|
||||
'static/assets/foundation/transparent/a01-scroll-toast-v3.png',
|
||||
'static/assets/modules/auth/transparent/a01-scroll-dialog-v3.png'
|
||||
)
|
||||
|
||||
foreach ($asset in $backgrounds + $icons + $buttons) {
|
||||
foreach ($asset in $backgrounds + $icons + $buttons + $v2Buttons + $v3Skins) {
|
||||
if (-not (Test-Path -LiteralPath (Join-Path $root $asset))) {
|
||||
throw "Missing A01 asset: $asset"
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($asset in $v3Skins) {
|
||||
$bitmap = [System.Drawing.Bitmap]::FromFile((Join-Path $root $asset))
|
||||
$width = [int]$bitmap.Width
|
||||
$height = [int]$bitmap.Height
|
||||
$corners = @(
|
||||
$bitmap.GetPixel(0, 0).A,
|
||||
$bitmap.GetPixel(($width - 1), 0).A,
|
||||
$bitmap.GetPixel(0, ($height - 1)).A,
|
||||
$bitmap.GetPixel(($width - 1), ($height - 1)).A
|
||||
)
|
||||
$bitmap.Dispose()
|
||||
|
||||
$expectedSize = switch ($asset) {
|
||||
'static/assets/foundation/transparent/a01-scroll-primary-v3.png' { @(1866, 276) }
|
||||
'static/assets/foundation/transparent/a01-scroll-secondary-v3.png' { @(1866, 300) }
|
||||
'static/assets/foundation/transparent/a01-scroll-toast-v3.png' { @(1770, 246) }
|
||||
'static/assets/modules/auth/transparent/a01-scroll-dialog-v3.png' { @(1860, 1560) }
|
||||
}
|
||||
if ($width -ne $expectedSize[0] -or $height -ne $expectedSize[1]) { throw "$asset must be $($expectedSize[0]) x $($expectedSize[1])px." }
|
||||
if (($corners | Where-Object { $_ -ne 0 }).Count -ne 0) { throw "$asset must have transparent outer corners." }
|
||||
Write-Output "PASS $asset transparent corners and exact v3 slot ratio"
|
||||
}
|
||||
|
||||
foreach ($asset in $v2Buttons) {
|
||||
$bitmap = [System.Drawing.Bitmap]::FromFile((Join-Path $root $asset))
|
||||
$width = [int]$bitmap.Width
|
||||
$height = [int]$bitmap.Height
|
||||
$corners = @(
|
||||
$bitmap.GetPixel(0, 0).A,
|
||||
$bitmap.GetPixel(($width - 1), 0).A,
|
||||
$bitmap.GetPixel(0, ($height - 1)).A,
|
||||
$bitmap.GetPixel(($width - 1), ($height - 1)).A
|
||||
)
|
||||
$bitmap.Dispose()
|
||||
|
||||
$expectedSize = switch ($asset) {
|
||||
'static/assets/foundation/transparent/a01-primary-button-v2.png' { @(1866, 276) }
|
||||
'static/assets/foundation/transparent/a01-secondary-button-v2.png' { @(1866, 300) }
|
||||
}
|
||||
if ($width -ne $expectedSize[0] -or $height -ne $expectedSize[1]) { throw "$asset must be $($expectedSize[0]) x $($expectedSize[1])px." }
|
||||
if (($corners | Where-Object { $_ -ne 0 }).Count -ne 0) { throw "$asset must have transparent outer corners." }
|
||||
Write-Output "PASS $asset transparent corners and exact runtime ratio"
|
||||
}
|
||||
|
||||
foreach ($asset in $icons) {
|
||||
$bitmap = [System.Drawing.Bitmap]::FromFile((Join-Path $root $asset))
|
||||
$width = [int]$bitmap.Width
|
||||
|
||||
@@ -56,6 +56,14 @@ const waitFor = async (send, expression, message) => {
|
||||
throw new Error(message)
|
||||
}
|
||||
|
||||
const waitForFreshDocument = async (send, previousTimeOrigin) => {
|
||||
await waitFor(
|
||||
send,
|
||||
`performance.timeOrigin !== ${JSON.stringify(previousTimeOrigin)}`,
|
||||
'A01 navigation did not create a fresh document'
|
||||
)
|
||||
}
|
||||
|
||||
const run = async () => {
|
||||
const { socket, send, exceptions } = await connect()
|
||||
try {
|
||||
@@ -70,7 +78,10 @@ const run = async () => {
|
||||
mobile: true
|
||||
})
|
||||
await send('Page.navigate', { url })
|
||||
await waitFor(send, `location.href === ${JSON.stringify(url)}`, `A01 did not navigate at ${size.width}x${size.height}`)
|
||||
const previousTimeOrigin = await valueOf(send, 'performance.timeOrigin')
|
||||
await send('Page.reload')
|
||||
await waitForFreshDocument(send, previousTimeOrigin)
|
||||
await waitFor(send, "document.querySelectorAll('.login-tab').length === 2", `A01 did not render at ${size.width}x${size.height}`)
|
||||
if (size.width === 320 && size.height === 568) {
|
||||
await waitFor(send, "document.querySelector('.auth-page')?.scrollHeight > document.querySelector('.auth-page')?.clientHeight", 'A01 did not establish natural scrolling at 320x568')
|
||||
@@ -86,11 +97,30 @@ const run = async () => {
|
||||
})`)
|
||||
assert.strictEqual(metrics.innerWidth, size.width, `A01 viewport width mismatch at ${size.width}x${size.height}`)
|
||||
assert(metrics.scrollWidth <= size.width + 1, `A01 has horizontal overflow at ${size.width}x${size.height}: ${metrics.scrollWidth}`)
|
||||
|
||||
const buttonAssets = await valueOf(send, `Array.from(document.querySelectorAll('.button-skin img')).map((image) => ({
|
||||
src: image.currentSrc || image.src,
|
||||
naturalWidth: image.naturalWidth,
|
||||
naturalHeight: image.naturalHeight
|
||||
}))`)
|
||||
assert.strictEqual(buttonAssets.length, 2, `A01 button skins did not render at ${size.width}x${size.height}`)
|
||||
assert(buttonAssets[0].src.includes('a01-scroll-primary-v3.png'), `A01 primary v3 skin is missing at ${size.width}x${size.height}`)
|
||||
assert(buttonAssets[1].src.includes('a01-scroll-secondary-v3.png'), `A01 secondary v3 skin is missing at ${size.width}x${size.height}`)
|
||||
assert.deepStrictEqual(
|
||||
buttonAssets.map(({ naturalWidth, naturalHeight }) => [naturalWidth, naturalHeight]),
|
||||
[[1866, 276], [1866, 300]],
|
||||
`A01 v3 button assets have unexpected runtime dimensions at ${size.width}x${size.height}`
|
||||
)
|
||||
if (size.width === 320 && size.height === 568) {
|
||||
assert(metrics.pageScrollHeight > metrics.pageClientHeight, 'A01 must scroll naturally at 320x568')
|
||||
}
|
||||
}
|
||||
|
||||
await valueOf(send, "document.querySelector('.login-submit').click()")
|
||||
await waitFor(send, "Boolean(document.querySelector('.feedback-toast'))", 'A01 invalid phone Toast did not render')
|
||||
const toastBorderImage = await valueOf(send, "getComputedStyle(document.querySelector('.feedback-toast')).borderImageSource")
|
||||
assert(toastBorderImage.includes('a01-scroll-toast-v3.png'), `A01 Toast did not render the v3 nine-slice asset: ${toastBorderImage}`)
|
||||
|
||||
await valueOf(send, "document.querySelectorAll('.login-tab')[1].click()")
|
||||
await waitFor(send, "document.querySelectorAll('.login-tab')[1].classList.contains('active')", 'A01 SMS tab did not activate')
|
||||
await waitFor(send, "Boolean(document.querySelector('.input-icon--sms img'))", 'A01 SMS field image did not render after tab activation')
|
||||
@@ -117,6 +147,17 @@ const run = async () => {
|
||||
await waitFor(send, "!document.querySelector('.agreement-error')", 'A01 agreement error did not clear after selection')
|
||||
await valueOf(send, "document.querySelector('.login-submit').click()")
|
||||
await waitFor(send, "Boolean(document.querySelector('.verification-layer'))", 'A01 did not open the custom verification layer after local validation')
|
||||
await waitFor(send, "document.querySelector('.verification-dialog__skin img')?.naturalWidth === 1860", 'A01 verification dialog v3 asset did not finish loading')
|
||||
const dialogAsset = await valueOf(send, `(() => {
|
||||
const image = document.querySelector('.verification-dialog__skin img')
|
||||
return image ? { src: image.currentSrc || image.src, naturalWidth: image.naturalWidth, naturalHeight: image.naturalHeight } : null
|
||||
})()`)
|
||||
assert(dialogAsset?.src.includes('a01-scroll-dialog-v3.png'), `A01 verification dialog did not load the v3 asset: ${dialogAsset?.src}`)
|
||||
assert.deepStrictEqual(
|
||||
[dialogAsset.naturalWidth, dialogAsset.naturalHeight],
|
||||
[1860, 1560],
|
||||
'A01 verification dialog loaded unexpected natural dimensions'
|
||||
)
|
||||
assert.deepStrictEqual(exceptions, [], `A01 raised browser exceptions: ${exceptions.join('; ')}`)
|
||||
|
||||
process.stdout.write('A01-RESPONSIVE-RUNTIME-SMOKE PASS\n')
|
||||
|
||||
@@ -4,7 +4,7 @@ $root = Split-Path -Parent $PSScriptRoot
|
||||
$pages = Get-Content -LiteralPath (Join-Path $root 'pages.json') -Raw -Encoding utf8 | ConvertFrom-Json
|
||||
$legacyPage = Join-Path $root 'pages/auth/a02-login.vue'
|
||||
|
||||
if ($pages.pages.Count -ne 53) { throw "Expected 53 routes, found $($pages.pages.Count)." }
|
||||
if ($pages.pages.Count -ne 52) { throw "Expected 52 active routes, found $($pages.pages.Count)." }
|
||||
if ($pages.pages.path -contains 'pages/auth/a02-login') { throw 'Legacy A02 route still exists.' }
|
||||
if (Test-Path -LiteralPath $legacyPage) { throw 'Legacy A02 page file still exists.' }
|
||||
|
||||
|
||||
@@ -8,6 +8,6 @@ $a03File = Join-Path $root 'pages/auth/a03-mobile-verify.vue'
|
||||
if ($pages.pages.path -contains 'pages/auth/a03-mobile-verify') { throw 'A-03 must not remain in pages.json' }
|
||||
if (Test-Path -LiteralPath $a03File) { throw 'A-03 page file must be deleted' }
|
||||
if ($catalog -match '(?m)^\s*a03\s*:') { throw 'A-03 catalog entry must be deleted' }
|
||||
if ($pages.pages.Count -ne 53) { throw "Expected 53 final routes after the approved page-state merges, found $($pages.pages.Count)." }
|
||||
if ($pages.pages.Count -ne 52) { throw "Expected 52 active routes after the approved page-state merges and A06 archive, found $($pages.pages.Count)." }
|
||||
|
||||
Write-Output 'A03-ROUTE-REMOVAL-CONTRACT PASS'
|
||||
|
||||
@@ -20,16 +20,19 @@ $registerPath = Join-Path $root 'pages/auth/a04-register.vue'
|
||||
$pages = Get-Content -LiteralPath (Join-Path $root 'pages.json') -Raw -Encoding utf8 | ConvertFrom-Json
|
||||
$catalog = Get-Content -LiteralPath (Join-Path $root 'data/page-catalog.js') -Raw -Encoding utf8
|
||||
$register = Get-Content -LiteralPath $registerPath -Raw -Encoding utf8
|
||||
$globalStyles = Get-Content -LiteralPath (Join-Path $root 'styles/global.scss') -Raw -Encoding utf8
|
||||
$registerCopy = ConvertFrom-Utf8Base64 '5rOo5YaM6LSm5Y+3'
|
||||
$loginCopy = ConvertFrom-Utf8Base64 '55m75b2V'
|
||||
$sliderPendingCopy = ConvertFrom-Utf8Base64 '5ruR5Yqo6aqM6K+B5b6F5o6l5Y+j5o6l5YWl'
|
||||
|
||||
if (-not ($pages.pages.path -contains 'pages/auth/a04-register')) { throw 'A-04 route must remain declared in pages.json' }
|
||||
Assert-NotContains -Content $register -Unexpected 'ModulePage' -Message 'A-04 must not remain a ModulePage shell'
|
||||
foreach ($required in @(
|
||||
'a01-red-hall-ink-backdrop-v1.png',
|
||||
'brand-seal.png',
|
||||
'a02-login-panel.png',
|
||||
'a01-primary-button.png',
|
||||
'a01-vnext-divider-v1.png',
|
||||
'a01-scroll-primary-v3.png',
|
||||
'a01-scroll-toast-v3.png',
|
||||
'a02-agreement-unchecked.png',
|
||||
'a02-agreement-checked.png',
|
||||
'v-model.trim="phone"',
|
||||
@@ -38,11 +41,10 @@ foreach ($required in @(
|
||||
'const agreed = ref(false)',
|
||||
'const agreementError = ref(false)',
|
||||
'const fieldErrors = ref({',
|
||||
'const verificationVisible = ref(false)',
|
||||
'const toggleAgreement = () =>',
|
||||
'const submitRegister = () =>',
|
||||
'class="feedback-toast"',
|
||||
'class="verification-layer"',
|
||||
$sliderPendingCopy,
|
||||
"const prepareLogin = () => uni.redirectTo({ url: '/pages/auth/a01-entry' })",
|
||||
'if (!/^1\d{10}$/.test(phone.value))',
|
||||
'if (!password.value)',
|
||||
@@ -56,13 +58,28 @@ Assert-Contains -Content $register -Expected $loginCopy -Message 'A-04 must prov
|
||||
Assert-NotContains -Content $catalog -Unexpected 'a04:' -Message 'A-04 must not remain in the temporary page catalog'
|
||||
Assert-NotContains -Content $register -Unexpected 'register-intro' -Message 'A-04 must not retain the redundant registration intro copy'
|
||||
Assert-NotContains -Content $register -Unexpected 'auth-design-scenery-v1.png' -Message 'A-04 must use the shared red-hall ink backdrop instead of the rejected scenery layer'
|
||||
Assert-Contains -Content $globalStyles -Expected 'font-family: "STKaiti", "KaiTi", serif' -Message 'Global styles must own the A04 font contract.'
|
||||
Assert-NotContains -Content $register -Unexpected 'font-family:' -Message 'A04 must inherit the global font contract instead of overriding it locally.'
|
||||
foreach ($obsoleteVisual in @(
|
||||
'a01-primary-button.png',
|
||||
'a01-secondary-button.png',
|
||||
'a02-login-panel.png',
|
||||
'verificationVisible',
|
||||
'verification-layer',
|
||||
'feedback-toast__skin'
|
||||
)) {
|
||||
Assert-NotContains -Content $register -Unexpected $obsoleteVisual -Message "A04 must not retain obsolete visual or placeholder state: $obsoleteVisual"
|
||||
}
|
||||
foreach ($nativeUi in @('uni.showToast', 'uni.showModal', 'uni.showLoading', 'uni.showActionSheet')) {
|
||||
Assert-NotContains -Content $register -Unexpected $nativeUi -Message "A-04 must not use native UniApp feedback: $nativeUi"
|
||||
}
|
||||
foreach ($required in @(
|
||||
'auth-divider-knot.png',
|
||||
'register-divider',
|
||||
'width: 154rpx'
|
||||
'mode="aspectFit"',
|
||||
'width: 184rpx;',
|
||||
'height: 221rpx;',
|
||||
"border-image-source: url('/static/assets/foundation/transparent/a01-scroll-toast-v3.png');",
|
||||
'border-image-slice: 58 280 fill;'
|
||||
)) {
|
||||
Assert-Contains -Content $register -Expected $required -Message "Missing A-04 visual balance contract: $required"
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ const run = async () => {
|
||||
await valueOf(send, "document.querySelector('.register-link').click()")
|
||||
await waitFor(send, "location.href.includes('/pages/auth/a04-register')", 'A01 registration entry did not open A04')
|
||||
|
||||
for (const size of [{ width: 320, height: 568 }, { width: 360, height: 800 }, { width: 412, height: 915 }]) {
|
||||
for (const size of [{ width: 320, height: 568 }, { width: 360, height: 640 }, { width: 360, height: 800 }, { width: 412, height: 915 }]) {
|
||||
await send('Emulation.setDeviceMetricsOverride', { ...size, deviceScaleFactor: 1, mobile: true })
|
||||
await navigate(send, a04Url, '.register-submit')
|
||||
const metrics = await valueOf(send, `({
|
||||
@@ -94,9 +94,11 @@ const run = async () => {
|
||||
document.querySelector('.agreement-row').click()
|
||||
document.querySelector('.register-submit').click()
|
||||
})()`)
|
||||
await waitFor(send, "Boolean(document.querySelector('.verification-layer'))", 'A04 valid submit did not open custom verification')
|
||||
await valueOf(send, "document.querySelector('.verification-action--primary').click()")
|
||||
await waitFor(send, "Boolean(document.querySelector('.feedback-toast'))", 'A04 verification result did not use custom feedback')
|
||||
await waitFor(send, "Boolean(document.querySelector('.feedback-toast'))", 'A04 valid submit did not show slider integration feedback')
|
||||
assert.strictEqual(await valueOf(send, "document.querySelector('.feedback-toast__copy')?.textContent"), '滑动验证待接口接入', 'A04 valid submit showed unexpected feedback')
|
||||
assert.strictEqual(await valueOf(send, "Boolean(document.querySelector('.verification-layer'))"), false, 'A04 must not retain the non-final custom verification placeholder')
|
||||
const toastBorderImage = await valueOf(send, "getComputedStyle(document.querySelector('.feedback-toast')).borderImageSource")
|
||||
assert(toastBorderImage.includes('a01-scroll-toast-v3.png'), `A04 Toast did not render the v3 nine-slice asset: ${toastBorderImage}`)
|
||||
|
||||
await navigate(send, a04Url, '.login-entry__link')
|
||||
await valueOf(send, "document.querySelector('.login-entry__link').click()")
|
||||
|
||||
@@ -24,15 +24,17 @@ $resetCopy = ConvertFrom-Utf8Base64 '6YeN6K6+5a+G56CB'
|
||||
$confirmPasswordCopy = ConvertFrom-Utf8Base64 '56Gu6K6k5paw5a+G56CB'
|
||||
$getCodeCopy = ConvertFrom-Utf8Base64 '6I635Y+W6aqM6K+B56CB'
|
||||
$loginCopy = ConvertFrom-Utf8Base64 '6L+U5Zue55m75b2V'
|
||||
$sliderPendingCopy = ConvertFrom-Utf8Base64 '5ruR5Yqo6aqM6K+B5b6F5o6l5Y+j5o6l5YWl'
|
||||
|
||||
if (-not ($pages.pages.path -contains 'pages/auth/a05-reset-password')) { throw 'A-05 route must remain declared in pages.json' }
|
||||
Assert-NotContains -Content $page -Unexpected 'ModulePage' -Message 'A-05 must not remain a ModulePage shell'
|
||||
foreach ($required in @(
|
||||
'a01-red-hall-ink-backdrop-v1.png',
|
||||
'brand-seal.png',
|
||||
'a02-login-panel.png',
|
||||
'a01-primary-button.png',
|
||||
'auth-divider-knot.png',
|
||||
'a01-vnext-divider-v1.png',
|
||||
'a01-scroll-primary-v3.png',
|
||||
'a01-scroll-toast-v3.png',
|
||||
'a01-scroll-dialog-v3.png',
|
||||
'chevron-right.png',
|
||||
'v-model.trim="phone"',
|
||||
'v-model.trim="verificationCode"',
|
||||
@@ -41,16 +43,18 @@ foreach ($required in @(
|
||||
'const prepareGetCode = () =>',
|
||||
'const submitReset = () =>',
|
||||
'const fieldErrors = ref({',
|
||||
'const verificationVisible = ref(false)',
|
||||
'const successVisible = ref(false)',
|
||||
'class="verification-layer"',
|
||||
'class="feedback-toast"',
|
||||
'class="success-layer"',
|
||||
"const prepareLogin = () => uni.redirectTo({ url: '/pages/auth/a01-entry' })",
|
||||
'if (!/^1\d{10}$/.test(phone.value))',
|
||||
'if (!/^\d{6}$/.test(verificationCode.value))',
|
||||
'if (!password.value)',
|
||||
'if (password.value !== confirmPassword.value)'
|
||||
'if (password.value !== confirmPassword.value)',
|
||||
'width: 184rpx;',
|
||||
'height: 221rpx;',
|
||||
"border-image-source: url('/static/assets/foundation/transparent/a01-scroll-toast-v3.png');",
|
||||
'border-image-slice: 58 280 fill;'
|
||||
)) {
|
||||
Assert-Contains -Content $page -Expected $required -Message "Missing A-05 reset-password contract: $required"
|
||||
}
|
||||
@@ -58,8 +62,27 @@ Assert-Contains -Content $page -Expected $resetCopy -Message 'A-05 must visibly
|
||||
Assert-Contains -Content $page -Expected $confirmPasswordCopy -Message 'A-05 must require password confirmation'
|
||||
Assert-Contains -Content $page -Expected $getCodeCopy -Message 'A-05 must expose the verification-code action'
|
||||
Assert-Contains -Content $page -Expected $loginCopy -Message 'A-05 must provide the A01 return path'
|
||||
Assert-Contains -Content $page -Expected $sliderPendingCopy -Message 'A-05 must defer the real slider to the interface stage'
|
||||
Assert-NotContains -Content $catalog -Unexpected 'a05:' -Message 'A-05 must not remain in the temporary page catalog'
|
||||
Assert-NotContains -Content $page -Unexpected 'auth-design-scenery-v1.png' -Message 'A-05 must use the selected red-hall ink backdrop'
|
||||
foreach ($obsoleteVisual in @(
|
||||
'a01-primary-button.png',
|
||||
'a01-secondary-button.png',
|
||||
'a02-login-panel.png',
|
||||
'auth-divider-knot.png',
|
||||
'feedback-toast__skin',
|
||||
'codeRequested',
|
||||
'verificationVisible',
|
||||
'verification-layer',
|
||||
'closeVerification',
|
||||
'confirmVerification',
|
||||
'font-family:'
|
||||
)) {
|
||||
Assert-NotContains -Content $page -Unexpected $obsoleteVisual -Message "A-05 must not retain obsolete visual or placeholder state: $obsoleteVisual"
|
||||
}
|
||||
|
||||
$primarySkinCount = ([regex]::Matches($page, 'a01-scroll-primary-v3\.png')).Count
|
||||
if ($primarySkinCount -ne 2) { throw "A-05 must use the approved primary skin exactly twice, found $primarySkinCount" }
|
||||
foreach ($nativeUi in @('uni.showToast', 'uni.showModal', 'uni.showLoading', 'uni.showActionSheet')) {
|
||||
Assert-NotContains -Content $page -Unexpected $nativeUi -Message "A-05 must not use native UniApp feedback: $nativeUi"
|
||||
}
|
||||
|
||||
@@ -69,11 +69,13 @@ const run = async () => {
|
||||
await send('Page.enable')
|
||||
await send('Runtime.enable')
|
||||
|
||||
await navigate(send, a01Url, '.forgot-password')
|
||||
await navigate(send, a01Url, '.login-tab')
|
||||
await valueOf(send, "document.querySelectorAll('.login-tab')[0].click()")
|
||||
await waitFor(send, "Boolean(document.querySelector('.forgot-password'))", 'A01 password state did not expose forgot-password entry')
|
||||
await valueOf(send, "document.querySelector('.forgot-password').click()")
|
||||
await waitFor(send, "location.href.includes('/pages/auth/a05-reset-password')", 'A01 forgot-password entry did not open A05')
|
||||
|
||||
for (const size of [{ width: 320, height: 568 }, { width: 360, height: 800 }, { width: 412, height: 915 }]) {
|
||||
for (const size of [{ width: 320, height: 568 }, { width: 360, height: 640 }, { width: 360, height: 800 }, { width: 412, height: 915 }]) {
|
||||
await send('Emulation.setDeviceMetricsOverride', { ...size, deviceScaleFactor: 1, mobile: true })
|
||||
await navigate(send, a05Url, '.reset-submit')
|
||||
const metrics = await valueOf(send, `({
|
||||
@@ -89,16 +91,11 @@ const run = async () => {
|
||||
await waitFor(send, "Boolean(document.querySelector('.field-error'))", 'A05 invalid phone did not show inline error')
|
||||
await valueOf(send, setInputs(['13800138000', '', '', '']))
|
||||
await valueOf(send, "document.querySelector('.get-code').click()")
|
||||
await waitFor(send, "Boolean(document.querySelector('.verification-layer'))", 'A05 code request did not open custom verification')
|
||||
await valueOf(send, "document.querySelector('.verification-action').click()")
|
||||
await waitFor(send, "!document.querySelector('.verification-layer')", 'A05 verification did not close')
|
||||
assert.strictEqual(await valueOf(send, "document.querySelector('.auth-input input').value"), '13800138000', 'A05 verification close cleared the form')
|
||||
|
||||
await valueOf(send, "document.querySelector('.get-code').click()")
|
||||
await waitFor(send, "Boolean(document.querySelector('.verification-layer'))", 'A05 verification did not reopen')
|
||||
await valueOf(send, "document.querySelector('.verification-action--primary').click()")
|
||||
await waitFor(send, "Boolean(document.querySelector('.feedback-toast'))", 'A05 verified code request did not use custom feedback')
|
||||
assert.strictEqual(await valueOf(send, "document.querySelector('.get-code').textContent"), '重新获取', 'A05 code action did not enter requested state')
|
||||
await waitFor(send, "Boolean(document.querySelector('.feedback-toast'))", 'A05 code request did not use custom pending feedback')
|
||||
assert.strictEqual(await valueOf(send, "document.querySelector('.feedback-toast').textContent.trim()"), '滑动验证待接口接入', 'A05 code request showed the wrong pending feedback')
|
||||
assert.strictEqual(await valueOf(send, "Boolean(document.querySelector('.verification-layer'))"), false, 'A05 must not retain the old verification panel')
|
||||
assert.strictEqual(await valueOf(send, "document.querySelector('.auth-input input').value"), '13800138000', 'A05 pending slider feedback cleared the form')
|
||||
assert.strictEqual(await valueOf(send, "document.querySelector('.get-code').textContent.trim()"), '获取验证码', 'A05 must not fake a requested-code state before the real slider and SMS interface')
|
||||
|
||||
await valueOf(send, "document.querySelector('.reset-submit').click()")
|
||||
await waitFor(send, "document.querySelectorAll('.field-error').length === 3", 'A05 incomplete submit did not show three remaining field errors')
|
||||
|
||||
@@ -14,9 +14,12 @@ $root = Split-Path -Parent $PSScriptRoot
|
||||
$page = Get-Content -LiteralPath (Join-Path $root 'pages/auth/a06-auth-status.vue') -Raw -Encoding utf8
|
||||
$pages = Get-Content -LiteralPath (Join-Path $root 'pages.json') -Raw -Encoding utf8 | ConvertFrom-Json
|
||||
$catalog = Get-Content -LiteralPath (Join-Path $root 'data/page-catalog.js') -Raw -Encoding utf8
|
||||
$runtime = Get-Content -LiteralPath (Join-Path $root 'tests/a06-auth-status-runtime-smoke.js') -Raw -Encoding utf8
|
||||
|
||||
if (-not ($pages.pages.path -contains 'pages/auth/a06-auth-status')) { throw 'A-06 route must remain declared in pages.json' }
|
||||
if ($pages.pages.path -contains 'pages/auth/a06-auth-status') { throw 'A-06 is archived and must not remain declared in pages.json' }
|
||||
if (-not (Test-Path -LiteralPath (Join-Path $root 'pages/auth/a06-auth-status.vue'))) { throw 'Archived A-06 source must be retained' }
|
||||
if (-not ($pages.pages.path -contains 'pages/auth/a01-entry')) { throw 'A-06 must retain the A01 return target' }
|
||||
Assert-Contains -Content $runtime -Expected 'A06-AUTH-STATUS-RUNTIME-SMOKE SKIP archived route' -Message 'A-06 runtime smoke must explicitly skip while the route is archived'
|
||||
|
||||
Assert-NotContains -Content $page -Unexpected 'ModulePage' -Message 'A-06 must not remain a ModulePage shell'
|
||||
Assert-NotContains -Content $catalog -Unexpected 'a06:' -Message 'A-06 must not remain in the temporary page catalog'
|
||||
@@ -37,12 +40,15 @@ foreach ($required in @(
|
||||
'const goBack = () =>',
|
||||
'a01-red-hall-ink-backdrop-v1.png',
|
||||
'brand-seal.png',
|
||||
'a02-login-panel.png',
|
||||
'a01-primary-button.png',
|
||||
'auth-divider-knot.png',
|
||||
'a01-vnext-divider-v1.png',
|
||||
'a01-scroll-primary-v3.png',
|
||||
'a01-scroll-dialog-v3.png',
|
||||
'chevron-right.png',
|
||||
'auth-login-outline.png',
|
||||
'class="recovery-layer"'
|
||||
'class="recovery-layer"',
|
||||
'width: 184rpx;',
|
||||
'height: 221rpx;',
|
||||
'mode="aspectFit"'
|
||||
)) {
|
||||
Assert-Contains -Content $page -Expected $required -Message "Missing A-06 auth-status contract: $required"
|
||||
}
|
||||
@@ -54,6 +60,17 @@ foreach ($nativeUi in @('uni.showToast', 'uni.showModal', 'uni.showLoading', 'un
|
||||
Assert-NotContains -Content $page -Unexpected $nativeUi -Message "A-06 must not use native UniApp feedback: $nativeUi"
|
||||
}
|
||||
Assert-NotContains -Content $page -Unexpected 'auth-design-scenery-v1.png' -Message 'A-06 must use the selected red-hall ink backdrop'
|
||||
foreach ($obsoleteVisual in @(
|
||||
'a01-primary-button.png',
|
||||
'a02-login-panel.png',
|
||||
'auth-divider-knot.png',
|
||||
'font-family:'
|
||||
)) {
|
||||
Assert-NotContains -Content $page -Unexpected $obsoleteVisual -Message "A-06 must not retain obsolete visual: $obsoleteVisual"
|
||||
}
|
||||
|
||||
$primarySkinCount = ([regex]::Matches($page, 'a01-scroll-primary-v3\.png')).Count
|
||||
if ($primarySkinCount -ne 2) { throw "A-06 must use the approved primary skin exactly twice, found $primarySkinCount" }
|
||||
|
||||
foreach ($forbidden in @(
|
||||
'(?s)\.status-panel\s*\{[^}]*\bborder\s*:',
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
const assert = require('assert')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
const routeConfig = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'pages.json'), 'utf8'))
|
||||
const isActiveRoute = routeConfig.pages.some((item) => item.path === 'pages/auth/a06-auth-status')
|
||||
if (!isActiveRoute) {
|
||||
process.stdout.write('A06-AUTH-STATUS-RUNTIME-SMOKE SKIP archived route\n')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
const baseUrl = 'http://localhost:5173/#/pages/auth/a06-auth-status'
|
||||
const sleep = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds))
|
||||
@@ -60,7 +69,7 @@ const run = async () => {
|
||||
await send('Page.enable')
|
||||
await send('Runtime.enable')
|
||||
|
||||
for (const size of [{ width: 320, height: 568 }, { width: 360, height: 800 }, { width: 412, height: 915 }]) {
|
||||
for (const size of [{ width: 320, height: 568 }, { width: 360, height: 640 }, { width: 360, height: 800 }, { width: 412, height: 915 }]) {
|
||||
await send('Emulation.setDeviceMetricsOverride', { ...size, deviceScaleFactor: 1, mobile: true })
|
||||
await navigate(send, 'risk', '账号存在安全风险')
|
||||
const metrics = await valueOf(send, `({
|
||||
|
||||
@@ -9,7 +9,7 @@ $root = Split-Path -Parent $PSScriptRoot
|
||||
$pagesPath = Join-Path $root 'pages.json'
|
||||
$pageHeader = ConvertFrom-Utf8Base64 '6aG16Z2i57yW5Y+377ya'
|
||||
$expectedPages = @(
|
||||
'pages/auth/a01-entry', 'pages/auth/a04-register', 'pages/auth/a05-reset-password', 'pages/auth/a06-auth-status',
|
||||
'pages/auth/a01-entry', 'pages/auth/a04-register', 'pages/auth/a05-reset-password',
|
||||
'pages/genealogy/g01-my-genealogies', 'pages/genealogy/g03-create-genealogy', 'pages/genealogy/g05-genealogy-overview', 'pages/genealogy/g06-search-genealogies', 'pages/genealogy/g08-join-application', 'pages/genealogy/g09-my-applications', 'pages/genealogy/g10-application-review', 'pages/genealogy/g11-genealogy-settings', 'pages/genealogy/g12-generation-poems',
|
||||
'pages/tree/t01-tree-overview', 'pages/tree/t03-member-profile', 'pages/tree/t04-add-relative', 'pages/tree/t05-edit-member', 'pages/tree/t06-edit-relationship', 'pages/tree/t07-member-directory', 'pages/tree/t08-member-states',
|
||||
'pages/family/f01-family-feed', 'pages/family/f02-publish-feed', 'pages/family/f03-feed-detail', 'pages/family/f04-article-list', 'pages/family/f05-article-detail', 'pages/family/f06-article-editor', 'pages/family/f07-album-list', 'pages/family/f08-album-detail', 'pages/family/f09-media-upload', 'pages/family/f10-video-list',
|
||||
@@ -20,11 +20,11 @@ $expectedPages = @(
|
||||
|
||||
$pages = Get-Content -LiteralPath $pagesPath -Raw -Encoding utf8 | ConvertFrom-Json
|
||||
$actualPaths = @($pages.pages.path)
|
||||
if ($actualPaths.Count -ne 53) {
|
||||
throw "Expected 53 visual routes after the approved page-state merges, found $($actualPaths.Count)."
|
||||
if ($actualPaths.Count -ne 52) {
|
||||
throw "Expected 52 active visual routes after the approved page-state merges and A06 archive, found $($actualPaths.Count)."
|
||||
}
|
||||
if (Compare-Object -ReferenceObject $expectedPages -DifferenceObject $actualPaths) {
|
||||
throw 'pages.json routes do not match the 53-route visual delivery contract.'
|
||||
throw 'pages.json routes do not match the 52-route active visual delivery contract.'
|
||||
}
|
||||
|
||||
foreach ($page in $expectedPages) {
|
||||
|
||||
@@ -117,6 +117,7 @@ if ($card -notmatch '(?s)\.card-title-row\s*\{.*?justify-content:\s*space-betwee
|
||||
if ($card -notmatch '(?s)\.card-meta\s*\{.*?font-size:\s*26rpx') { throw 'G-01 list metadata is below the approved readable size.' }
|
||||
if ($card -notmatch '(?s)\.card-updated\s*\{.*?font-size:\s*22rpx') { throw 'G-01 list update date is below the approved readable size.' }
|
||||
if ($page -notmatch '(?s)\.create-action\s*>\s*text\s*\{.*?white-space:\s*nowrap') { throw 'G-01 create action text can wrap onto multiple lines.' }
|
||||
if ($page -notmatch '(?s)\.application-record\s*\+\s*\.application-record\s*\{[^}]*margin-top:\s*12rpx') { throw 'G-01 adjacent application cards must keep the approved 12rpx separation.' }
|
||||
|
||||
$manifest = Get-Content -Raw -Encoding UTF8 (Join-Path $root 'manifest.json')
|
||||
if ($manifest -notmatch '"statusbar"\s*:\s*\{\s*"immersed"\s*:\s*"supportedDevice"\s*,\s*"style"\s*:\s*"light"\s*,\s*"background"\s*:\s*"#B52E22"') { throw 'Android status bar is not configured for the cinnabar root header.' }
|
||||
|
||||
Reference in New Issue
Block a user