From f801c96c968c5adc629f2386efb1bd48e830e8f6 Mon Sep 17 00:00:00 2001 From: qpismont Date: Fri, 1 Aug 2025 16:41:58 +0000 Subject: [PATCH] Enhance testing framework and add UI component tests. Integrated Vitest for testing with AstroContainer API. Updated package.json and bun.lock to include testing dependencies. Added comprehensive test cases for various UI components including Button, Alert, Badge, and more, ensuring proper rendering and functionality. --- TESTING.md | 188 ++++++++++++++++++ bun.lock | 124 +++++++++++- package.json | 11 +- src/components/ui/__tests__/Alert.test.ts | 59 ++++++ src/components/ui/__tests__/Badge.test.ts | 65 ++++++ src/components/ui/__tests__/Button.test.ts | 102 ++++++++++ src/components/ui/__tests__/Card.test.ts | 45 +++++ src/components/ui/__tests__/Col.test.ts | 106 ++++++++++ src/components/ui/__tests__/Container.test.ts | 56 ++++++ src/components/ui/__tests__/FormGroup.test.ts | 44 ++++ src/components/ui/__tests__/Input.test.ts | 103 ++++++++++ src/components/ui/__tests__/Modal.test.ts | 66 ++++++ src/components/ui/__tests__/ModalBody.test.ts | 44 ++++ .../ui/__tests__/ModalFooter.test.ts | 44 ++++ .../ui/__tests__/ModalHeader.test.ts | 54 +++++ src/components/ui/__tests__/MovieCard.test.ts | 113 +++++++++++ src/components/ui/__tests__/NavLink.test.ts | 85 ++++++++ src/components/ui/__tests__/Navbar.test.ts | 81 ++++++++ .../ui/__tests__/NavbarBrand.test.ts | 60 ++++++ src/components/ui/__tests__/Progress.test.ts | 91 +++++++++ src/components/ui/__tests__/Row.test.ts | 54 +++++ src/components/ui/__tests__/SearchBar.test.ts | 94 +++++++++ src/components/ui/__tests__/Select.test.ts | 71 +++++++ src/components/ui/__tests__/Spinner.test.ts | 71 +++++++ src/components/ui/__tests__/Textarea.test.ts | 78 ++++++++ src/components/ui/__tests__/basic.test.ts | 15 ++ vitest.config.ts | 7 + 27 files changed, 1923 insertions(+), 8 deletions(-) create mode 100644 TESTING.md create mode 100644 src/components/ui/__tests__/Alert.test.ts create mode 100644 src/components/ui/__tests__/Badge.test.ts create mode 100644 src/components/ui/__tests__/Button.test.ts create mode 100644 src/components/ui/__tests__/Card.test.ts create mode 100644 src/components/ui/__tests__/Col.test.ts create mode 100644 src/components/ui/__tests__/Container.test.ts create mode 100644 src/components/ui/__tests__/FormGroup.test.ts create mode 100644 src/components/ui/__tests__/Input.test.ts create mode 100644 src/components/ui/__tests__/Modal.test.ts create mode 100644 src/components/ui/__tests__/ModalBody.test.ts create mode 100644 src/components/ui/__tests__/ModalFooter.test.ts create mode 100644 src/components/ui/__tests__/ModalHeader.test.ts create mode 100644 src/components/ui/__tests__/MovieCard.test.ts create mode 100644 src/components/ui/__tests__/NavLink.test.ts create mode 100644 src/components/ui/__tests__/Navbar.test.ts create mode 100644 src/components/ui/__tests__/NavbarBrand.test.ts create mode 100644 src/components/ui/__tests__/Progress.test.ts create mode 100644 src/components/ui/__tests__/Row.test.ts create mode 100644 src/components/ui/__tests__/SearchBar.test.ts create mode 100644 src/components/ui/__tests__/Select.test.ts create mode 100644 src/components/ui/__tests__/Spinner.test.ts create mode 100644 src/components/ui/__tests__/Textarea.test.ts create mode 100644 src/components/ui/__tests__/basic.test.ts create mode 100644 vitest.config.ts 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('