diff --git a/TESTING.md b/TESTING.md new file mode 100644 index 0000000..61269d9 --- /dev/null +++ b/TESTING.md @@ -0,0 +1,188 @@ +# Guide de Tests Astro + +Ce projet utilise **Vitest** avec l'API **AstroContainer** pour tester les composants Astro UI. + +## Configuration + +- **Framework de test** : Vitest +- **API de rendu** : `experimental_AstroContainer` d'Astro +- **Environnement** : Node.js +- **Configuration** : `vitest.config.ts` utilise `getViteConfig` d'Astro + +## Structure des Tests + +``` +src/components/ui/__tests__/ +├── basic.test.ts # Test simple de démonstration +├── Button.test.ts # Tests du composant Button +├── Card.test.ts # Tests du composant Card +├── Alert.test.ts # Tests du composant Alert +├── Badge.test.ts # Tests du composant Badge +├── Input.test.ts # Tests du composant Input +├── Progress.test.ts # Tests du composant Progress +├── Spinner.test.ts # Tests du composant Spinner +├── MovieCard.test.ts # Tests du composant MovieCard +└── ... # Autres tests de composants +``` + +## Exécution des Tests + +```bash +# Exécuter tous les tests +bun run test + +# Exécuter les tests en mode watch +bun run test:watch + +# Exécuter l'interface graphique des tests +bun run test:ui + +# Exécuter des tests spécifiques +bun run test src/components/ui/__tests__/Button.test.ts +``` + +## Structure d'un Test Type + +```typescript +import { experimental_AstroContainer as AstroContainer } from 'astro/container'; +import { describe, it, expect } from 'vitest'; +import MonComposant from '../MonComposant.astro'; + +describe('MonComposant', () => { + it('renders with default props', async () => { + const container = await AstroContainer.create(); + const result = await container.renderToString(MonComposant, { + props: { propriete: 'valeur' }, + slots: { default: 'Contenu du slot' } + }); + + expect(result).toContain('Contenu attendu'); + expect(result).toContain('class="ma-classe"'); + }); +}); +``` + +## Bonnes Pratiques + +### 1. Utilisation d'AstroContainer + +```typescript +// ✅ Correct - Utiliser AstroContainer +const container = await AstroContainer.create(); +const result = await container.renderToString(Component, { + props: { variant: 'primary' }, + slots: { default: 'Content' } +}); + +// ❌ Incorrect - Ancienne méthode DOM +const html = await renderAstroComponent(Component); +document.body.innerHTML = html; +``` + +### 2. Vérifications de Contenu + +```typescript +// ✅ Vérifier le contenu HTML rendu +expect(result).toContain('expected-class'); +expect(result).toContain('Expected text'); +expect(result).toContain('attribute="value"'); + +// ✅ Vérifier la structure HTML +expect(result).toContain('=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ=="], + "vite-node": ["vite-node@3.2.4", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg=="], + "vitefu": ["vitefu@1.1.1", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ=="], + "vitest": ["vitest@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", "@vitest/mocker": "3.2.4", "@vitest/pretty-format": "^3.2.4", "@vitest/runner": "3.2.4", "@vitest/snapshot": "3.2.4", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "debug": "^4.4.1", "expect-type": "^1.2.1", "magic-string": "^0.30.17", "pathe": "^2.0.3", "picomatch": "^4.0.2", "std-env": "^3.9.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.14", "tinypool": "^1.1.1", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", "vite-node": "3.2.4", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@vitest/browser": "3.2.4", "@vitest/ui": "3.2.4", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A=="], + "web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="], "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], + "whatwg-mimetype": ["whatwg-mimetype@3.0.0", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="], + "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], "which-pm-runs": ["which-pm-runs@1.1.0", "", {}, "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA=="], + "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], + "widest-line": ["widest-line@5.0.0", "", { "dependencies": { "string-width": "^7.0.0" } }, "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA=="], "wrap-ansi": ["wrap-ansi@9.0.0", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q=="], @@ -760,20 +858,34 @@ "@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + "@types/fontkit/@types/node": ["@types/node@24.1.0", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w=="], + "ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "astro/aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="], + + "bun-types/@types/node": ["@types/node@24.1.0", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w=="], + "hast-util-to-parse5/property-information": ["property-information@6.5.0", "", {}, "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig=="], "http-errors/statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="], "prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], + "strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], + + "strip-literal/js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="], + + "wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="], + + "@types/fontkit/@types/node/undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="], + "ansi-align/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "ansi-align/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "ansi-align/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "bun-types/@types/node/undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="], } } diff --git a/package.json b/package.json index 9b3702e..cf77ad3 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,10 @@ "dev": "bunx --bun astro dev", "build": "bunx --bun astro build", "preview": "bunx --bun astro preview", - "astro": "bunx --bun astro" + "astro": "bunx --bun astro", + "test": "vitest run", + "test:watch": "vitest", + "test:ui": "vitest --ui" }, "dependencies": { "@astrojs/node": "^9.3.1", @@ -14,6 +17,10 @@ }, "devDependencies": { "@biomejs/biome": "2.1.3", - "@types/bun": "^1.2.19" + "@testing-library/dom": "^10.4.1", + "@types/bun": "^1.2.19", + "@vitest/ui": "^3.2.4", + "happy-dom": "^18.0.1", + "vitest": "^3.2.4" } } diff --git a/src/components/ui/__tests__/Alert.test.ts b/src/components/ui/__tests__/Alert.test.ts new file mode 100644 index 0000000..f4a1c78 --- /dev/null +++ b/src/components/ui/__tests__/Alert.test.ts @@ -0,0 +1,59 @@ +import { experimental_AstroContainer as AstroContainer } from "astro/container"; +import { describe, it, expect } from "vitest"; +import Alert from "../Alert.astro"; + +describe("Alert", () => { + it("renders with default props", async () => { + const container = await AstroContainer.create(); + const result = await container.renderToString(Alert, { + slots: { default: "Alert message" }, + }); + + expect(result).toContain("Alert message"); + expect(result).toContain('class="alert alert-info"'); + expect(result).toContain(" { + const container = await AstroContainer.create(); + const variants = ["success", "warning", "error", "info"] as const; + + for (const variant of variants) { + const result = await container.renderToString(Alert, { + props: { variant }, + slots: { default: "Test message" }, + }); + + expect(result).toContain(`alert-${variant}`); + } + }); + + it("applies custom classes", async () => { + const container = await AstroContainer.create(); + const result = await container.renderToString(Alert, { + props: { class: "custom-alert" }, + slots: { default: "Message" }, + }); + + expect(result).toContain("alert alert-info custom-alert"); + }); + + it("renders as div element", async () => { + const container = await AstroContainer.create(); + const result = await container.renderToString(Alert, { + slots: { default: "Test" }, + }); + + expect(result).toContain('
{ + const container = await AstroContainer.create(); + const result = await container.renderToString(Alert, { + props: { role: "alert" }, + slots: { default: "Important message" }, + }); + + expect(result).toContain('role="alert"'); + }); +}); diff --git a/src/components/ui/__tests__/Badge.test.ts b/src/components/ui/__tests__/Badge.test.ts new file mode 100644 index 0000000..610bd24 --- /dev/null +++ b/src/components/ui/__tests__/Badge.test.ts @@ -0,0 +1,65 @@ +import { experimental_AstroContainer as AstroContainer } from "astro/container"; +import { describe, it, expect } from "vitest"; +import Badge from "../Badge.astro"; + +describe("Badge", () => { + it("renders with default props", async () => { + const container = await AstroContainer.create(); + const result = await container.renderToString(Badge, { + slots: { default: "New" }, + }); + + expect(result).toContain("New"); + expect(result).toContain("badge-primary"); + expect(result).toContain(" { + const container = await AstroContainer.create(); + const variants = [ + "primary", + "secondary", + "success", + "warning", + "error", + ] as const; + + for (const variant of variants) { + const result = await container.renderToString(Badge, { + props: { variant }, + slots: { default: "Badge" }, + }); + + expect(result).toContain(`badge-${variant}`); + } + }); + + it("applies custom classes", async () => { + const container = await AstroContainer.create(); + const result = await container.renderToString(Badge, { + props: { class: "custom-badge" }, + slots: { default: "Test" }, + }); + + expect(result).toContain("badge badge-primary custom-badge"); + }); + + it("renders as span element", async () => { + const container = await AstroContainer.create(); + const result = await container.renderToString(Badge, { + slots: { default: "Test" }, + }); + + expect(result).toContain(' { + const container = await AstroContainer.create(); + const result = await container.renderToString(Badge, { + props: { "data-count": "5" }, + slots: { default: "5" }, + }); + + expect(result).toContain('data-count="5"'); + }); +}); diff --git a/src/components/ui/__tests__/Button.test.ts b/src/components/ui/__tests__/Button.test.ts new file mode 100644 index 0000000..ad70b4d --- /dev/null +++ b/src/components/ui/__tests__/Button.test.ts @@ -0,0 +1,102 @@ +import { experimental_AstroContainer as AstroContainer } from "astro/container"; +import { describe, it, expect } from "vitest"; +import Button from "../Button.astro"; + +describe("Button", () => { + it("renders with default props", async () => { + const container = await AstroContainer.create(); + const result = await container.renderToString(Button, { + slots: { default: "Click me" }, + }); + + expect(result).toContain("Click me"); + expect(result).toContain('class="btn btn-primary"'); + expect(result).toContain('type="button"'); + expect(result).toContain(" { + const container = await AstroContainer.create(); + const variants = ["primary", "secondary", "outline", "ghost"] as const; + + for (const variant of variants) { + const result = await container.renderToString(Button, { + props: { variant }, + slots: { default: "Test" }, + }); + + expect(result).toContain(`btn-${variant}`); + } + }); + + it("renders different sizes", async () => { + const container = await AstroContainer.create(); + const sizes = ["sm", "lg"] as const; + + for (const size of sizes) { + const result = await container.renderToString(Button, { + props: { size }, + slots: { default: "Test" }, + }); + + expect(result).toContain(`btn-${size}`); + } + }); + + it("renders as anchor when href is provided", async () => { + const container = await AstroContainer.create(); + const result = await container.renderToString(Button, { + props: { href: "/test" }, + slots: { default: "Link" }, + }); + + expect(result).toContain(" { + const container = await AstroContainer.create(); + const result = await container.renderToString(Button, { + props: { disabled: true }, + slots: { default: "Disabled" }, + }); + + expect(result).toContain("disabled"); + }); + + it("applies icon styling", async () => { + const container = await AstroContainer.create(); + const result = await container.renderToString(Button, { + props: { icon: true }, + slots: { default: "🔍" }, + }); + + expect(result).toContain("btn-icon"); + }); + + it("applies custom classes", async () => { + const container = await AstroContainer.create(); + const result = await container.renderToString(Button, { + props: { class: "custom-class" }, + slots: { default: "Test" }, + }); + + expect(result).toContain("custom-class"); + }); + + it("handles different button types", async () => { + const container = await AstroContainer.create(); + const types = ["button", "submit", "reset"] as const; + + for (const type of types) { + const result = await container.renderToString(Button, { + props: { type }, + slots: { default: "Test" }, + }); + + expect(result).toContain(`type="${type}"`); + } + }); +}); diff --git a/src/components/ui/__tests__/Card.test.ts b/src/components/ui/__tests__/Card.test.ts new file mode 100644 index 0000000..f664680 --- /dev/null +++ b/src/components/ui/__tests__/Card.test.ts @@ -0,0 +1,45 @@ +import { experimental_AstroContainer as AstroContainer } from "astro/container"; +import { describe, it, expect } from "vitest"; +import Card from "../Card.astro"; + +describe("Card", () => { + it("renders with default props", async () => { + const container = await AstroContainer.create(); + const result = await container.renderToString(Card, { + slots: { default: "Card content" }, + }); + + expect(result).toContain("Card content"); + expect(result).toContain('class="card"'); + expect(result).toContain(" { + const container = await AstroContainer.create(); + const result = await container.renderToString(Card, { + props: { class: "custom-card" }, + slots: { default: "Content" }, + }); + + expect(result).toContain("card custom-card"); + }); + + it("renders as div element", async () => { + const container = await AstroContainer.create(); + const result = await container.renderToString(Card, { + slots: { default: "Test" }, + }); + + expect(result).toContain('
{ + const container = await AstroContainer.create(); + const result = await container.renderToString(Card, { + props: { "data-testid": "test-card" }, + slots: { default: "Test" }, + }); + + expect(result).toContain('data-testid="test-card"'); + }); +}); diff --git a/src/components/ui/__tests__/Col.test.ts b/src/components/ui/__tests__/Col.test.ts new file mode 100644 index 0000000..b9bae02 --- /dev/null +++ b/src/components/ui/__tests__/Col.test.ts @@ -0,0 +1,106 @@ +import { experimental_AstroContainer as AstroContainer } from "astro/container"; +import { describe, it, expect } from "vitest"; +import Col from "../Col.astro"; + +describe("Col", () => { + it("renders with default props", async () => { + const container = await AstroContainer.create(); + const result = await container.renderToString(Col, { + slots: { default: "Column content" }, + }); + + expect(result).toContain("Column content"); + expect(result).toContain('class="col"'); + expect(result).toContain(" { + const container = await AstroContainer.create(); + const result = await container.renderToString(Col, { + props: { + xs: 12, + sm: 6, + md: 4, + lg: 3, + }, + slots: { default: "Content" }, + }); + + expect(result).toContain("col--12"); + expect(result).toContain("col-sm-6"); + expect(result).toContain("col-md-4"); + expect(result).toContain("col-lg-3"); + }); + + it("renders with auto sizing", async () => { + const container = await AstroContainer.create(); + const result = await container.renderToString(Col, { + props: { md: "auto" }, + slots: { default: "Content" }, + }); + + expect(result).toContain("col-md-auto"); + }); + + it("renders with offset classes", async () => { + const container = await AstroContainer.create(); + const result = await container.renderToString(Col, { + props: { + md: 6, + offset: { md: 3, lg: 2 }, + }, + slots: { default: "Content" }, + }); + + expect(result).toContain("col-md-6"); + expect(result).toContain("offset-md-3"); + expect(result).toContain("offset-lg-2"); + }); + + it("renders with xl and xxl breakpoints", async () => { + const container = await AstroContainer.create(); + const result = await container.renderToString(Col, { + props: { + xl: 4, + xxl: 2, + }, + slots: { default: "Content" }, + }); + + expect(result).toContain("col-xl-4"); + expect(result).toContain("col-xxl-2"); + }); + + it("applies custom classes", async () => { + const container = await AstroContainer.create(); + const result = await container.renderToString(Col, { + props: { + class: "custom-col", + md: 6, + }, + slots: { default: "Content" }, + }); + + expect(result).toContain("col-md-6"); + expect(result).toContain("custom-col"); + }); + + it("renders as div element", async () => { + const container = await AstroContainer.create(); + const result = await container.renderToString(Col, { + slots: { default: "Test" }, + }); + + expect(result).toContain('
{ + const container = await AstroContainer.create(); + const result = await container.renderToString(Col, { + props: { "data-column": "main" }, + slots: { default: "Test" }, + }); + + expect(result).toContain('data-column="main"'); + }); +}); diff --git a/src/components/ui/__tests__/Container.test.ts b/src/components/ui/__tests__/Container.test.ts new file mode 100644 index 0000000..32a453c --- /dev/null +++ b/src/components/ui/__tests__/Container.test.ts @@ -0,0 +1,56 @@ +import { experimental_AstroContainer as AstroContainer } from "astro/container"; +import { describe, it, expect } from "vitest"; +import Container from "../Container.astro"; + +describe("Container", () => { + it("renders with default props", async () => { + const container = await AstroContainer.create(); + const result = await container.renderToString(Container, { + slots: { default: "Container content" }, + }); + + expect(result).toContain("Container content"); + expect(result).toContain('class="container"'); + expect(result).toContain(" { + const container = await AstroContainer.create(); + const result = await container.renderToString(Container, { + props: { fluid: true }, + slots: { default: "Fluid content" }, + }); + + expect(result).toContain("Fluid content"); + expect(result).toContain('class="container-fluid"'); + }); + + it("applies custom classes", async () => { + const container = await AstroContainer.create(); + const result = await container.renderToString(Container, { + props: { class: "custom-container" }, + slots: { default: "Content" }, + }); + + expect(result).toContain("container custom-container"); + }); + + it("renders as div element", async () => { + const container = await AstroContainer.create(); + const result = await container.renderToString(Container, { + slots: { default: "Test" }, + }); + + expect(result).toContain('
{ + const container = await AstroContainer.create(); + const result = await container.renderToString(Container, { + props: { "data-section": "main" }, + slots: { default: "Test" }, + }); + + expect(result).toContain('data-section="main"'); + }); +}); diff --git a/src/components/ui/__tests__/FormGroup.test.ts b/src/components/ui/__tests__/FormGroup.test.ts new file mode 100644 index 0000000..7d747e2 --- /dev/null +++ b/src/components/ui/__tests__/FormGroup.test.ts @@ -0,0 +1,44 @@ +import { experimental_AstroContainer as AstroContainer } from "astro/container"; +import { describe, it, expect } from "vitest"; +import FormGroup from "../FormGroup.astro"; + +describe("FormGroup", () => { + it("renders with default props", async () => { + const container = await AstroContainer.create(); + const result = await container.renderToString(FormGroup, { + slots: { default: "" }, + }); + + expect(result).toContain("Form content"); + expect(result).toContain('class="form-group"'); + }); + + it("applies custom classes", async () => { + const container = await AstroContainer.create(); + const result = await container.renderToString(FormGroup, { + props: { class: "custom-form-group" }, + slots: { default: "Content" }, + }); + + expect(result).toContain("form-group custom-form-group"); + }); + + it("renders as div element", async () => { + const container = await AstroContainer.create(); + const result = await container.renderToString(FormGroup, { + slots: { default: "Test" }, + }); + + expect(result).toContain('
{ + const container = await AstroContainer.create(); + const result = await container.renderToString(FormGroup, { + props: { "data-testid": "form-section" }, + slots: { default: "Test" }, + }); + + expect(result).toContain('data-testid="form-section"'); + }); +}); diff --git a/src/components/ui/__tests__/Input.test.ts b/src/components/ui/__tests__/Input.test.ts new file mode 100644 index 0000000..a4d863f --- /dev/null +++ b/src/components/ui/__tests__/Input.test.ts @@ -0,0 +1,103 @@ +import { experimental_AstroContainer as AstroContainer } from "astro/container"; +import { describe, it, expect } from "vitest"; +import Input from "../Input.astro"; + +describe("Input", () => { + it("renders with default props", async () => { + const container = await AstroContainer.create(); + const result = await container.renderToString(Input); + + expect(result).toContain(" { + const container = await AstroContainer.create(); + const types = [ + "text", + "email", + "password", + "number", + "tel", + "url", + "search", + ] as const; + + for (const type of types) { + const result = await container.renderToString(Input, { + props: { type }, + }); + + expect(result).toContain(`type="${type}"`); + } + }); + + it("renders different sizes", async () => { + const container = await AstroContainer.create(); + const sizes = ["sm", "lg"] as const; + + for (const size of sizes) { + const result = await container.renderToString(Input, { + props: { size }, + }); + + expect(result).toContain(`input-${size}`); + } + }); + + it("applies placeholder", async () => { + const container = await AstroContainer.create(); + const result = await container.renderToString(Input, { + props: { placeholder: "Enter text" }, + }); + + expect(result).toContain('placeholder="Enter text"'); + }); + + it("applies value", async () => { + const container = await AstroContainer.create(); + const result = await container.renderToString(Input, { + props: { value: "test value" }, + }); + + expect(result).toContain('value="test value"'); + }); + + it("applies name and id attributes", async () => { + const container = await AstroContainer.create(); + const result = await container.renderToString(Input, { + props: { name: "username", id: "user-input" }, + }); + + expect(result).toContain('name="username"'); + expect(result).toContain('id="user-input"'); + }); + + it("applies disabled state", async () => { + const container = await AstroContainer.create(); + const result = await container.renderToString(Input, { + props: { disabled: true }, + }); + + expect(result).toContain("disabled"); + }); + + it("applies required attribute", async () => { + const container = await AstroContainer.create(); + const result = await container.renderToString(Input, { + props: { required: true }, + }); + + expect(result).toContain("required"); + }); + + it("applies custom classes", async () => { + const container = await AstroContainer.create(); + const result = await container.renderToString(Input, { + props: { class: "custom-input" }, + }); + + expect(result).toContain("custom-input"); + }); +}); diff --git a/src/components/ui/__tests__/Modal.test.ts b/src/components/ui/__tests__/Modal.test.ts new file mode 100644 index 0000000..eefd0e3 --- /dev/null +++ b/src/components/ui/__tests__/Modal.test.ts @@ -0,0 +1,66 @@ +import { experimental_AstroContainer as AstroContainer } from "astro/container"; +import { describe, it, expect } from "vitest"; +import Modal from "../Modal.astro"; + +describe("Modal", () => { + it("renders with required id prop", async () => { + const container = await AstroContainer.create(); + const result = await container.renderToString(Modal, { + props: { id: "test-modal" }, + slots: { default: "Modal content" }, + }); + + expect(result).toContain('class="modal-backdrop"'); + expect(result).toContain('class="modal"'); + expect(result).toContain('id="test-modal"'); + expect(result).toContain("Modal content"); + }); + + it("starts hidden by default", async () => { + const container = await AstroContainer.create(); + const result = await container.renderToString(Modal, { + props: { id: "hidden-modal" }, + slots: { default: "Content" }, + }); + + expect(result).toContain('style="display: none;"'); + }); + + it("applies custom classes", async () => { + const container = await AstroContainer.create(); + const result = await container.renderToString(Modal, { + props: { + id: "custom-modal", + class: "custom-modal-class", + }, + slots: { default: "Content" }, + }); + + expect(result).toContain("modal-backdrop custom-modal-class"); + }); + + it("renders nested modal structure", async () => { + const container = await AstroContainer.create(); + const result = await container.renderToString(Modal, { + props: { id: "nested-modal" }, + slots: { default: "Nested content" }, + }); + + expect(result).toContain('class="modal-backdrop"'); + expect(result).toContain('class="modal"'); + expect(result).toContain("Nested content"); + }); + + it("passes through additional props", async () => { + const container = await AstroContainer.create(); + const result = await container.renderToString(Modal, { + props: { + id: "props-modal", + "data-testid": "modal-test", + }, + slots: { default: "Test" }, + }); + + expect(result).toContain('data-testid="modal-test"'); + }); +}); diff --git a/src/components/ui/__tests__/ModalBody.test.ts b/src/components/ui/__tests__/ModalBody.test.ts new file mode 100644 index 0000000..972a779 --- /dev/null +++ b/src/components/ui/__tests__/ModalBody.test.ts @@ -0,0 +1,44 @@ +import { experimental_AstroContainer as AstroContainer } from "astro/container"; +import { describe, it, expect } from "vitest"; +import ModalBody from "../ModalBody.astro"; + +describe("ModalBody", () => { + it("renders with default props", async () => { + const container = await AstroContainer.create(); + const result = await container.renderToString(ModalBody, { + slots: { default: "Modal body content" }, + }); + + expect(result).toContain("Modal body content"); + expect(result).toContain('class="modal-body"'); + }); + + it("applies custom classes", async () => { + const container = await AstroContainer.create(); + const result = await container.renderToString(ModalBody, { + props: { class: "custom-body" }, + slots: { default: "Content" }, + }); + + expect(result).toContain("modal-body custom-body"); + }); + + it("renders as div element", async () => { + const container = await AstroContainer.create(); + const result = await container.renderToString(ModalBody, { + slots: { default: "Test" }, + }); + + expect(result).toContain('