Refine auth page tests to avoid snapshots (#614)

This commit is contained in:
shenlan 2025-10-31 18:38:36 +08:00 committed by GitHub
parent e65e5078ce
commit fa3ea52e2b
10 changed files with 103 additions and 9 deletions

View File

@ -0,0 +1,13 @@
import type { ReactNode } from 'react'
import { AppShellBypass } from '@lib/appShellBypass'
export default function AuthPagesLayout({ children }: { children: ReactNode }) {
return (
<AppShellBypass>
<div className="flex min-h-screen flex-col bg-slate-50">
{children}
</div>
</AppShellBypass>
)
}

View File

@ -5,13 +5,12 @@ import Link from 'next/link'
import { useRouter, useSearchParams } from 'next/navigation'
import { Github } from 'lucide-react'
import { AskAIButton } from '@components/AskAIButton'
import { AuthLayout, AuthLayoutSocialButton } from '@components/auth/AuthLayout'
import { useLanguage } from '@i18n/LanguageProvider'
import { translations } from '@i18n/translations'
import { getAccountServiceBaseUrl } from '@lib/serviceConfig'
import { WeChatIcon } from '../components/icons/WeChatIcon'
import { WeChatIcon } from '../../components/icons/WeChatIcon'
type LoginContentProps = {
children?: ReactNode
@ -346,7 +345,6 @@ export default function LoginContent({ children }: LoginContentProps) {
>
{formContent}
</AuthLayout>
<AskAIButton />
</>
)
}

View File

@ -5,13 +5,12 @@ import { ChevronDown, Github } from 'lucide-react'
import { FormEvent, useCallback, useEffect, useId, useMemo, useRef, useState } from 'react'
import { useRouter, useSearchParams } from 'next/navigation'
import { AskAIButton } from '@components/AskAIButton'
import { AuthLayout, AuthLayoutSocialButton } from '@components/auth/AuthLayout'
import { useLanguage } from '@i18n/LanguageProvider'
import { translations } from '@i18n/translations'
import { getAccountServiceBaseUrl } from '@lib/serviceConfig'
import { WeChatIcon } from '../components/icons/WeChatIcon'
import { WeChatIcon } from '../../components/icons/WeChatIcon'
type AlertState = { type: 'error' | 'success'; message: string }
@ -498,7 +497,6 @@ export default function RegisterContent() {
</button>
</form>
</AuthLayout>
<AskAIButton />
</>
)
}

View File

@ -1,10 +1,11 @@
import { ComponentType, createElement, type ReactNode } from 'react'
import { ComponentType, createElement, isValidElement, type ReactNode } from 'react'
import { cmsConfig, type ExtensionName, type ThemeName } from './config'
import type { CmsExtension, CmsTheme } from './types'
import { appShellExtension } from './extensions/appShell'
import { markdownSyncExtension } from './extensions/markdownSync'
import defaultTheme from './themes/default'
import { AppShellBypass } from '../lib/appShellBypass'
const themeRegistry: Record<ThemeName, CmsTheme> = {
default: defaultTheme,
@ -30,11 +31,24 @@ export function collectExtensionProviders(): ComponentType<{ children: ReactNode
}
export function applyExtensionLayouts(children: ReactNode): ReactNode {
const { content, skipAppShell } = unwrapAppShellBypass(children)
return getActiveExtensions().reduceRight((acc, extension) => {
if (!extension.Layout) {
return acc
}
if (skipAppShell && extension.name === 'app-shell') {
return acc
}
const LayoutComponent = extension.Layout
return createElement(LayoutComponent, null, acc)
}, children)
}, content)
}
function unwrapAppShellBypass(node: ReactNode): { content: ReactNode; skipAppShell: boolean } {
if (isValidElement(node) && node.type === AppShellBypass) {
return { content: node.props.children, skipAppShell: true }
}
return { content: node, skipAppShell: false }
}

View File

@ -98,7 +98,10 @@ export function AuthLayout({
className="pointer-events-none absolute inset-x-0 -top-1/3 h-1/2 bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] from-sky-100 via-transparent to-transparent"
aria-hidden
/>
<main className="relative flex flex-1 items-center justify-center px-4 py-12 sm:px-6 lg:px-8">
<main
className="relative flex flex-1 items-center justify-center px-4 py-12 sm:px-6 lg:px-8"
data-testid="auth-layout"
>
<div className="w-full max-w-md">
<div className="mb-8 text-center">
<Link href="/" className="text-3xl font-semibold tracking-tight text-slate-900">

View File

@ -0,0 +1,5 @@
import type { ReactNode } from 'react'
export function AppShellBypass({ children }: { children: ReactNode }) {
return <>{children}</>
}

View File

@ -0,0 +1,63 @@
import { Page, expect, test } from '@playwright/test'
async function expectAuthLayoutWithinViewport(page: Page) {
const viewport = page.viewportSize()
if (!viewport) {
throw new Error('Viewport size is not available')
}
const authLayout = page.getByTestId('auth-layout')
await expect(authLayout).toBeVisible()
const boundingBox = await authLayout.evaluate((element) => {
const rect = element.getBoundingClientRect()
return {
top: rect.top,
right: rect.right,
bottom: rect.bottom,
left: rect.left,
}
})
const tolerance = 1
expect.soft(boundingBox.top).toBeGreaterThanOrEqual(-tolerance)
expect.soft(boundingBox.left).toBeGreaterThanOrEqual(-tolerance)
expect.soft(boundingBox.right).toBeLessThanOrEqual(viewport.width + tolerance)
expect.soft(boundingBox.bottom).toBeLessThanOrEqual(viewport.height + tolerance)
await expect(page.locator('nav')).toHaveCount(0)
}
test.describe('Auth pages', () => {
test('login form visible on desktop viewport', async ({ page }) => {
await page.setViewportSize({ width: 1280, height: 720 })
await page.goto('/login')
await page.waitForLoadState('networkidle')
await expectAuthLayoutWithinViewport(page)
})
test('login form visible on mobile viewport', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 812 })
await page.goto('/login')
await page.waitForLoadState('networkidle')
await expectAuthLayoutWithinViewport(page)
})
test('register form visible on desktop viewport', async ({ page }) => {
await page.setViewportSize({ width: 1280, height: 720 })
await page.goto('/register')
await page.waitForLoadState('networkidle')
await expectAuthLayoutWithinViewport(page)
})
test('register form visible on mobile viewport', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 812 })
await page.goto('/register')
await page.waitForLoadState('networkidle')
await expectAuthLayoutWithinViewport(page)
})
})