From 7d12e3560049025024e2ed8407aa89d010d7956c Mon Sep 17 00:00:00 2001 From: erikgh Date: Tue, 21 Jul 2026 10:10:14 -0600 Subject: [PATCH] feat: primitives (Wordmark, Container, CTA) with tests --- components/primitives/CTA.tsx | 16 +++++++++++++ components/primitives/Container.tsx | 5 ++++ components/primitives/Wordmark.tsx | 23 +++++++++++++++++++ components/primitives/__tests__/CTA.test.tsx | 14 +++++++++++ .../primitives/__tests__/Wordmark.test.tsx | 22 ++++++++++++++++++ package.json | 4 +++- tsconfig.json | 2 +- vitest.config.ts | 13 +++++++++++ vitest.setup.ts | 1 + 9 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 components/primitives/CTA.tsx create mode 100644 components/primitives/Container.tsx create mode 100644 components/primitives/Wordmark.tsx create mode 100644 components/primitives/__tests__/CTA.test.tsx create mode 100644 components/primitives/__tests__/Wordmark.test.tsx create mode 100644 vitest.config.ts create mode 100644 vitest.setup.ts diff --git a/components/primitives/CTA.tsx b/components/primitives/CTA.tsx new file mode 100644 index 0000000..0364901 --- /dev/null +++ b/components/primitives/CTA.tsx @@ -0,0 +1,16 @@ +type Variant = 'light' | 'dark'; +const styles: Record = { + light: 'bg-ink text-base hover:bg-ink/90', + dark: 'bg-gold-bright text-ink hover:bg-gold-bright/90', +}; + +export function CTA({ href, label, variant = 'light', className = '' }: { href: string; label: string; variant?: Variant; className?: string }) { + return ( + + {label} + + ); +} diff --git a/components/primitives/Container.tsx b/components/primitives/Container.tsx new file mode 100644 index 0000000..98d938c --- /dev/null +++ b/components/primitives/Container.tsx @@ -0,0 +1,5 @@ +import type { ElementType, ReactNode } from 'react'; + +export function Container({ as: Tag = 'div', className = '', children }: { as?: ElementType; className?: string; children: ReactNode }) { + return {children}; +} diff --git a/components/primitives/Wordmark.tsx b/components/primitives/Wordmark.tsx new file mode 100644 index 0000000..39abd95 --- /dev/null +++ b/components/primitives/Wordmark.tsx @@ -0,0 +1,23 @@ +import { wordmark } from '@/lib/content'; + +type Size = 'sm' | 'md' | 'lg' | 'xl'; +const sizeClass: Record = { + sm: 'text-xl', + md: 'text-2xl', + lg: 'text-3xl', + xl: 'text-5xl', +}; + +export function Wordmark({ size = 'lg', onDark = false }: { size?: Size; onDark?: boolean }) { + const { before, accent, after, dot } = wordmark.parts; + const baseColor = onDark ? 'text-cream' : 'text-ink'; + const goldColor = onDark ? 'text-gold-bright' : 'text-gold'; + return ( + + {before} + {accent} + {after} + {dot} + + ); +} diff --git a/components/primitives/__tests__/CTA.test.tsx b/components/primitives/__tests__/CTA.test.tsx new file mode 100644 index 0000000..b963b6d --- /dev/null +++ b/components/primitives/__tests__/CTA.test.tsx @@ -0,0 +1,14 @@ +import { render, screen } from '@testing-library/react'; +import { describe, it, expect } from 'vitest'; +import { CTA } from '../CTA'; + +describe('', () => { + it('renders the label with arrow', () => { + render(); + expect(screen.getByRole('link', { name: /define tu siguiente movimiento/i })).toBeInTheDocument(); + }); + it('uses the href', () => { + render(); + expect(screen.getByRole('link').getAttribute('href')).toBe('#contacto'); + }); +}); diff --git a/components/primitives/__tests__/Wordmark.test.tsx b/components/primitives/__tests__/Wordmark.test.tsx new file mode 100644 index 0000000..6eec40d --- /dev/null +++ b/components/primitives/__tests__/Wordmark.test.tsx @@ -0,0 +1,22 @@ +import { render, screen } from '@testing-library/react'; +import { describe, it, expect } from 'vitest'; +import { Wordmark } from '../Wordmark'; + +describe('', () => { + it('renders the wordmark in lowercase', () => { + render(); + const text = screen.getByText(/cre/).closest('span')?.textContent; + expect(text?.toLowerCase()).toBe('creeer.'); + }); + it('renders the accent "e" separately for gold styling', () => { + const { container } = render(); + const accent = container.querySelector('[data-accent="true"]'); + expect(accent).not.toBeNull(); + expect(accent?.textContent).toBe('e'); + }); + it('never renders uppercase CREEER', () => { + const { container } = render(); + expect(container.textContent).not.toMatch(/CREEER/); + expect(container.textContent).not.toMatch(/Creeer/); + }); +}); diff --git a/package.json b/package.json index cb08ddd..d33a136 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,9 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "next lint" + "lint": "next lint", + "test": "vitest run", + "test:watch": "vitest" }, "dependencies": { "framer-motion": "^11.18.2", diff --git a/tsconfig.json b/tsconfig.json index e7ff90f..98203dc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,5 +22,5 @@ } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] + "exclude": ["node_modules", "vitest.config.ts"] } diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..0c7869a --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from 'vitest/config'; +import react from '@vitejs/plugin-react'; +import path from 'path'; + +export default defineConfig({ + plugins: [react()], + test: { + environment: 'jsdom', + globals: true, + setupFiles: ['./vitest.setup.ts'], + }, + resolve: { alias: { '@': path.resolve(__dirname, './') } }, +}); diff --git a/vitest.setup.ts b/vitest.setup.ts new file mode 100644 index 0000000..bb02c60 --- /dev/null +++ b/vitest.setup.ts @@ -0,0 +1 @@ +import '@testing-library/jest-dom/vitest';