From 8a6a6712e7554f110b5ef951f270d88fd010e040 Mon Sep 17 00:00:00 2001 From: Bertrand Yuan Date: Thu, 26 Mar 2026 00:02:16 +0800 Subject: add more tests --- src/components/ui/alert.test.tsx | 30 +++++++++++++++ src/components/ui/button-variants.test.ts | 28 ++++++++++++++ src/components/ui/card.test.tsx | 40 ++++++++++++++++++++ src/components/ui/input.test.tsx | 22 +++++++++++ src/components/ui/label.test.tsx | 15 ++++++++ src/components/ui/pagination.test.tsx | 61 +++++++++++++++++++++++++++++++ src/components/ui/skeleton.test.tsx | 16 ++++++++ 7 files changed, 212 insertions(+) create mode 100644 src/components/ui/alert.test.tsx create mode 100644 src/components/ui/button-variants.test.ts create mode 100644 src/components/ui/card.test.tsx create mode 100644 src/components/ui/input.test.tsx create mode 100644 src/components/ui/label.test.tsx create mode 100644 src/components/ui/pagination.test.tsx create mode 100644 src/components/ui/skeleton.test.tsx (limited to 'src/components/ui') diff --git a/src/components/ui/alert.test.tsx b/src/components/ui/alert.test.tsx new file mode 100644 index 0000000..ded0356 --- /dev/null +++ b/src/components/ui/alert.test.tsx @@ -0,0 +1,30 @@ +import { render, screen } from '@testing-library/react'; +import { describe, expect, test } from 'vitest'; +import { Alert, AlertDescription, AlertTitle } from './alert'; + +describe('Alert', () => { + test('renders with role and slot attributes', () => { + render( + + Heads up + Something happened. + , + ); + + const alert = screen.getByRole('alert'); + expect(alert).toHaveAttribute('data-slot', 'alert'); + expect(screen.getByText('Heads up')).toHaveAttribute('data-slot', 'alert-title'); + expect(screen.getByText('Something happened.')).toHaveAttribute( + 'data-slot', + 'alert-description', + ); + }); + + test('applies destructive variant classes', () => { + render(Danger); + + const alert = screen.getByRole('alert'); + expect(alert.className).toContain('text-destructive'); + }); +}); + diff --git a/src/components/ui/button-variants.test.ts b/src/components/ui/button-variants.test.ts new file mode 100644 index 0000000..5090703 --- /dev/null +++ b/src/components/ui/button-variants.test.ts @@ -0,0 +1,28 @@ +import { describe, expect, test } from 'vitest'; +import { buttonVariants } from './button'; + +describe('buttonVariants', () => { + test('returns default variant and size classes when no args are provided', () => { + const classes = buttonVariants(); + + expect(classes).toContain('bg-primary'); + expect(classes).toContain('h-9'); + }); + + test('returns destructive and icon classes for explicit options', () => { + const classes = buttonVariants({ + variant: 'destructive', + size: 'icon', + }); + + expect(classes).toContain('bg-destructive'); + expect(classes).toContain('size-9'); + }); + + test('includes caller-provided className', () => { + const classes = buttonVariants({ className: 'w-full' }); + + expect(classes).toContain('w-full'); + }); +}); + diff --git a/src/components/ui/card.test.tsx b/src/components/ui/card.test.tsx new file mode 100644 index 0000000..aba3d00 --- /dev/null +++ b/src/components/ui/card.test.tsx @@ -0,0 +1,40 @@ +import { render, screen } from '@testing-library/react'; +import { describe, expect, test } from 'vitest'; +import { + Card, + CardAction, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from './card'; + +describe('Card', () => { + test('renders all card slots', () => { + render( + + + Card Title + Card Description + Action + + Content + Footer + , + ); + + expect(screen.getByText('Card Title')).toHaveAttribute( + 'data-slot', + 'card-title', + ); + expect(screen.getByText('Card Description')).toHaveAttribute( + 'data-slot', + 'card-description', + ); + expect(screen.getByText('Action')).toHaveAttribute('data-slot', 'card-action'); + expect(screen.getByText('Content')).toHaveAttribute('data-slot', 'card-content'); + expect(screen.getByText('Footer')).toHaveAttribute('data-slot', 'card-footer'); + }); +}); + diff --git a/src/components/ui/input.test.tsx b/src/components/ui/input.test.tsx new file mode 100644 index 0000000..aabe76f --- /dev/null +++ b/src/components/ui/input.test.tsx @@ -0,0 +1,22 @@ +import { render, screen } from '@testing-library/react'; +import { describe, expect, test } from 'vitest'; +import { Input } from './input'; + +describe('Input', () => { + test('renders an input with the expected slot attribute', () => { + render(); + + const input = screen.getByLabelText('Email'); + expect(input).toBeInTheDocument(); + expect(input).toHaveAttribute('data-slot', 'input'); + }); + + test('supports input type and disabled props', () => { + render(); + + const input = screen.getByLabelText('Password'); + expect(input).toHaveAttribute('type', 'password'); + expect(input).toBeDisabled(); + }); +}); + diff --git a/src/components/ui/label.test.tsx b/src/components/ui/label.test.tsx new file mode 100644 index 0000000..08031b6 --- /dev/null +++ b/src/components/ui/label.test.tsx @@ -0,0 +1,15 @@ +import { render, screen } from '@testing-library/react'; +import { describe, expect, test } from 'vitest'; +import { Label } from './label'; + +describe('Label', () => { + test('renders label text and exposes data-slot', () => { + render(); + + const label = screen.getByText('Email'); + expect(label).toBeInTheDocument(); + expect(label).toHaveAttribute('for', 'email'); + expect(label).toHaveAttribute('data-slot', 'label'); + }); +}); + diff --git a/src/components/ui/pagination.test.tsx b/src/components/ui/pagination.test.tsx new file mode 100644 index 0000000..77309b8 --- /dev/null +++ b/src/components/ui/pagination.test.tsx @@ -0,0 +1,61 @@ +import { render, screen } from '@testing-library/react'; +import { describe, expect, test } from 'vitest'; +import { + Pagination, + PaginationContent, + PaginationEllipsis, + PaginationItem, + PaginationLink, + PaginationNext, + PaginationPrevious, +} from './pagination'; + +describe('Pagination UI', () => { + test('renders pagination container and list slots', () => { + render( + + + + 1 + + + , + ); + + const nav = screen.getByLabelText('pagination'); + expect(nav).toHaveAttribute('data-slot', 'pagination'); + expect(screen.getByRole('link', { name: '1' })).toHaveAttribute( + 'data-slot', + 'pagination-link', + ); + }); + + test('sets active page attributes', () => { + render( + + 2 + , + ); + + const link = screen.getByRole('link', { name: '2' }); + expect(link).toHaveAttribute('aria-current', 'page'); + expect(link).toHaveAttribute('data-active', 'true'); + }); + + test('renders previous, next, and ellipsis affordances', () => { + render( +
+ + + +
, + ); + + expect( + screen.getByRole('link', { name: 'Go to previous page' }), + ).toBeInTheDocument(); + expect(screen.getByRole('link', { name: 'Go to next page' })).toBeInTheDocument(); + expect(screen.getByText('More pages')).toBeInTheDocument(); + }); +}); + diff --git a/src/components/ui/skeleton.test.tsx b/src/components/ui/skeleton.test.tsx new file mode 100644 index 0000000..4459d77 --- /dev/null +++ b/src/components/ui/skeleton.test.tsx @@ -0,0 +1,16 @@ +import { render, screen } from '@testing-library/react'; +import { describe, expect, test } from 'vitest'; +import { Skeleton } from './skeleton'; + +describe('Skeleton', () => { + test('renders with default and custom classes', () => { + render(); + + const skeleton = screen.getByTestId('skeleton'); + expect(skeleton).toHaveAttribute('data-slot', 'skeleton'); + expect(skeleton.className).toContain('animate-pulse'); + expect(skeleton.className).toContain('h-4'); + expect(skeleton.className).toContain('w-20'); + }); +}); + -- cgit v1.2.3 From cd3c4bc89c169616b38bdb7443bb4eb7571b020c Mon Sep 17 00:00:00 2001 From: Bertrand Yuan Date: Thu, 26 Mar 2026 00:15:40 +0800 Subject: fix defects in pr #11 --- src/components/json-ld.test.tsx | 2 +- src/components/json-ld.tsx | 2 +- src/components/ui/button.test.tsx | 15 +++++++++++---- vite.config.ts | 11 ++++++++--- 4 files changed, 21 insertions(+), 9 deletions(-) (limited to 'src/components/ui') diff --git a/src/components/json-ld.test.tsx b/src/components/json-ld.test.tsx index c96e5c3..6a81414 100644 --- a/src/components/json-ld.test.tsx +++ b/src/components/json-ld.test.tsx @@ -46,7 +46,7 @@ describe('json-ld components', () => { }); test('returns null when post is not provided', () => { - const { container } = render(); + const { container } = render(); expect(container.firstChild).toBeNull(); }); diff --git a/src/components/json-ld.tsx b/src/components/json-ld.tsx index 58cb0ba..bbfad33 100644 --- a/src/components/json-ld.tsx +++ b/src/components/json-ld.tsx @@ -4,7 +4,7 @@ import { baseUrl } from '@/lib/constants'; import type { BlogPost } from '@/lib/payload-posts'; import type { BlogPosting, BreadcrumbList, Graph } from 'schema-dts'; -export const PostJsonLd = ({ post }: { post: BlogPost }) => { +export const PostJsonLd = ({ post }: { post: BlogPost | null | undefined }) => { if (!post) { return null; } diff --git a/src/components/ui/button.test.tsx b/src/components/ui/button.test.tsx index 314e9bf..f8bd3a9 100644 --- a/src/components/ui/button.test.tsx +++ b/src/components/ui/button.test.tsx @@ -14,9 +14,16 @@ describe('Button', () => { // Test buttons with different variants test('renders button with different variants', () => { - const variants = ['default', 'destructive', 'outline', 'secondary', 'ghost', 'link']; + const variants = [ + 'default', + 'destructive', + 'outline', + 'secondary', + 'ghost', + 'link', + ] as const; variants.forEach((variant) => { - render(); + render(); const button = screen.getByText(`${variant} Variant`); expect(button).toBeInTheDocument(); }); @@ -24,9 +31,9 @@ describe('Button', () => { // Test buttons with different sizes test('renders button with different sizes', () => { - const sizes = ['default', 'sm', 'lg', 'icon']; + const sizes = ['default', 'sm', 'lg', 'icon'] as const; sizes.forEach((size) => { - render(); + render(); const button = screen.getByText(`${size} Size`); expect(button).toBeInTheDocument(); }); diff --git a/vite.config.ts b/vite.config.ts index ec36e8c..507d61a 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -7,9 +7,14 @@ export default defineConfig({ test: { environment: 'jsdom', setupFiles: './src/test/setup.ts', - include: ['**/*.test.tsx', '**/*.test.ts'], - alias: { - '@': path.resolve(__dirname, './src'), + include: ['**/*.{test,spec}.{ts,tsx,js,jsx}'], + coverage: { + include: [ + 'src/components/**/*.{ts,tsx}', + 'src/hooks/**/*.{ts,tsx}', + 'src/lib/**/*.{ts,tsx}', + ], + exclude: ['src/**/*.test.{ts,tsx,js,jsx}', 'src/test/**'], }, }, resolve: { -- cgit v1.2.3