test(02-01): add failing tests for AUTH-01 through AUTH-04 and install shadcn Alert
- Install shadcn Alert component (alert.tsx) - LoginPage.test.tsx: 4 test cases for gradient bg, wordmark, Alert error, SVG icon - RegisterPage.test.tsx: 2 test cases for gradient bg, wordmark - All tests fail as expected (features not yet implemented)
This commit is contained in:
76
frontend/src/components/ui/alert.tsx
Normal file
76
frontend/src/components/ui/alert.tsx
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const alertVariants = cva(
|
||||||
|
"group/alert relative grid w-full gap-0.5 rounded-lg border px-2.5 py-2 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "bg-card text-card-foreground",
|
||||||
|
destructive:
|
||||||
|
"bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
function Alert({
|
||||||
|
className,
|
||||||
|
variant,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="alert"
|
||||||
|
role="alert"
|
||||||
|
className={cn(alertVariants({ variant }), className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="alert-title"
|
||||||
|
className={cn(
|
||||||
|
"font-medium group-has-[>svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function AlertDescription({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="alert-description"
|
||||||
|
className={cn(
|
||||||
|
"text-sm text-balance text-muted-foreground md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function AlertAction({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="alert-action"
|
||||||
|
className={cn("absolute top-2 right-2", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Alert, AlertTitle, AlertDescription, AlertAction }
|
||||||
65
frontend/src/pages/LoginPage.test.tsx
Normal file
65
frontend/src/pages/LoginPage.test.tsx
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { render, screen, act } from '@testing-library/react'
|
||||||
|
import { describe, it, expect, vi } from 'vitest'
|
||||||
|
import { LoginPage } from './LoginPage'
|
||||||
|
|
||||||
|
vi.mock('react-i18next', () => ({
|
||||||
|
useTranslation: () => ({ t: (key: string) => key }),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const mockAuth = {
|
||||||
|
user: null,
|
||||||
|
loading: false,
|
||||||
|
login: vi.fn(),
|
||||||
|
register: vi.fn(),
|
||||||
|
logout: vi.fn(),
|
||||||
|
token: null,
|
||||||
|
refetch: vi.fn(),
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('LoginPage branding', () => {
|
||||||
|
it('AUTH-01: wrapper div has a linear-gradient background', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<LoginPage auth={mockAuth} onToggle={vi.fn()} />
|
||||||
|
)
|
||||||
|
const wrapper = container.firstElementChild as HTMLElement
|
||||||
|
expect(wrapper?.style.background).toContain('linear-gradient')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('AUTH-02: renders a wordmark element with a gradient background style', () => {
|
||||||
|
render(<LoginPage auth={mockAuth} onToggle={vi.fn()} />)
|
||||||
|
const wordmark = screen.getByTestId('wordmark')
|
||||||
|
expect(wordmark).toBeTruthy()
|
||||||
|
expect(wordmark.style.background).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('AUTH-04: renders role="alert" when an error is present', async () => {
|
||||||
|
const failingLogin = vi.fn().mockRejectedValue(new Error('Invalid credentials'))
|
||||||
|
const authWithError = { ...mockAuth, login: failingLogin }
|
||||||
|
|
||||||
|
render(<LoginPage auth={authWithError} onToggle={vi.fn()} />)
|
||||||
|
|
||||||
|
const form = document.querySelector('form')!
|
||||||
|
await act(async () => {
|
||||||
|
form.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }))
|
||||||
|
})
|
||||||
|
|
||||||
|
const alertEl = await screen.findByRole('alert')
|
||||||
|
expect(alertEl).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('AUTH-04: the alert contains an SVG icon (AlertCircle)', async () => {
|
||||||
|
const failingLogin = vi.fn().mockRejectedValue(new Error('Invalid credentials'))
|
||||||
|
const authWithError = { ...mockAuth, login: failingLogin }
|
||||||
|
|
||||||
|
render(<LoginPage auth={authWithError} onToggle={vi.fn()} />)
|
||||||
|
|
||||||
|
const form = document.querySelector('form')!
|
||||||
|
await act(async () => {
|
||||||
|
form.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }))
|
||||||
|
})
|
||||||
|
|
||||||
|
const alertEl = await screen.findByRole('alert')
|
||||||
|
const svgEl = alertEl.querySelector('svg')
|
||||||
|
expect(svgEl).toBeTruthy()
|
||||||
|
})
|
||||||
|
})
|
||||||
33
frontend/src/pages/RegisterPage.test.tsx
Normal file
33
frontend/src/pages/RegisterPage.test.tsx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { render, screen } from '@testing-library/react'
|
||||||
|
import { describe, it, expect, vi } from 'vitest'
|
||||||
|
import { RegisterPage } from './RegisterPage'
|
||||||
|
|
||||||
|
vi.mock('react-i18next', () => ({
|
||||||
|
useTranslation: () => ({ t: (key: string) => key }),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const mockAuth = {
|
||||||
|
user: null,
|
||||||
|
loading: false,
|
||||||
|
login: vi.fn(),
|
||||||
|
register: vi.fn(),
|
||||||
|
logout: vi.fn(),
|
||||||
|
token: null,
|
||||||
|
refetch: vi.fn(),
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('RegisterPage branding', () => {
|
||||||
|
it('AUTH-03: wrapper div has a linear-gradient background', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<RegisterPage auth={mockAuth} onToggle={vi.fn()} />
|
||||||
|
)
|
||||||
|
const wrapper = container.firstElementChild as HTMLElement
|
||||||
|
expect(wrapper?.style.background).toContain('linear-gradient')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('AUTH-03: renders a wordmark element with data-testid="wordmark"', () => {
|
||||||
|
render(<RegisterPage auth={mockAuth} onToggle={vi.fn()} />)
|
||||||
|
const wordmark = screen.getByTestId('wordmark')
|
||||||
|
expect(wordmark).toBeTruthy()
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user