From daf88a0385f19188c44a37a437f02d308cbfc71c Mon Sep 17 00:00:00 2001 From: Luuk Brauckmann Date: Wed, 25 Sep 2024 09:50:28 +0200 Subject: [PATCH] chore: component testing (#177) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Changes - Adds unit tests for all components and blocks # Associated issue None # How to test 1. Open project in editor 2. Run in terminal `npm ci` 3. Run `npm run test` 4. All tests should run. # Checklist - [x] I have performed a self-review of my own code - [x] I have made sure that my PR is easy to review (not too big, includes comments) - [x] I have made updated relevant documentation files (in project README, docs/, etc) - [x] I have added a decision log entry if the change affects the architecture or changes a significant technology - [x] I have notified a reviewer --------- Co-authored-by: wesselsmit Co-authored-by: Jasper Moelker Co-authored-by: Jurgen Beliën Co-authored-by: Wessel Smit <45405413+WesselSmit@users.noreply.github.com> --- config/plop/templates/block/Block.astro.hbs | 4 +- config/plop/templates/block/Block.test.hbs | 17 + .../templates/component/Component.test.hbs | 14 + package-lock.json | 435 +++++++++++++++++- package.json | 2 + src/blocks/EmbedBlock/EmbedBlock.astro | 2 +- src/blocks/EmbedBlock/EmbedBlock.test.ts | 167 +++++++ src/blocks/ImageBlock/Image.astro | 8 +- src/blocks/ImageBlock/ImageBlock.astro | 2 +- src/blocks/ImageBlock/ImageBlock.test.ts | 106 +++++ src/blocks/InternalLink/InternalLink.astro | 2 +- src/blocks/InternalLink/InternalLink.test.ts | 24 + .../PagePartialBlock/PagePartialBlock.astro | 4 +- .../PagePartialBlock/PagePartialBlock.test.ts | 238 ++++++++++ src/blocks/TableBlock/TableBlock.astro | 8 +- src/blocks/TableBlock/TableBlock.test.ts | 89 ++++ src/blocks/TextBlock/TextBlock.astro | 2 +- src/blocks/TextBlock/TextBlock.test.ts | 93 ++++ .../TextImageBlock/TextImageBlock.test.ts | 100 ++++ src/blocks/VideoBlock/VideoBlock.astro | 4 +- src/blocks/VideoBlock/VideoBlock.test.ts | 66 +++ .../VideoEmbedBlock/VideoEmbedBlock.astro | 2 +- .../VideoEmbedBlock/VideoEmbedBlock.test.ts | 32 ++ src/components/Accordion/Accordion.test.ts | 10 + src/components/Accordion/AccordionItem.astro | 3 +- .../Accordion/AccordionItem.test.ts | 22 + src/components/Icon/Icon.test.ts | 13 + .../ShareButton/ShareButton.test.ts | 13 + src/components/SkipLink/SkipLink.test.ts | 13 + .../StructuredText/StructuredText.test.ts | 13 + src/components/Tabs/Tabs.test.ts | 13 + src/lib/renderer.ts | 20 + vitest.config.ts | 8 +- 33 files changed, 1516 insertions(+), 33 deletions(-) create mode 100644 config/plop/templates/block/Block.test.hbs create mode 100644 config/plop/templates/component/Component.test.hbs create mode 100644 src/blocks/EmbedBlock/EmbedBlock.test.ts create mode 100644 src/blocks/ImageBlock/ImageBlock.test.ts create mode 100644 src/blocks/InternalLink/InternalLink.test.ts create mode 100644 src/blocks/PagePartialBlock/PagePartialBlock.test.ts create mode 100644 src/blocks/TableBlock/TableBlock.test.ts create mode 100644 src/blocks/TextBlock/TextBlock.test.ts create mode 100644 src/blocks/TextImageBlock/TextImageBlock.test.ts create mode 100644 src/blocks/VideoBlock/VideoBlock.test.ts create mode 100644 src/blocks/VideoEmbedBlock/VideoEmbedBlock.test.ts create mode 100644 src/components/Accordion/Accordion.test.ts create mode 100644 src/components/Accordion/AccordionItem.test.ts create mode 100644 src/components/Icon/Icon.test.ts create mode 100644 src/components/ShareButton/ShareButton.test.ts create mode 100644 src/components/SkipLink/SkipLink.test.ts create mode 100644 src/components/StructuredText/StructuredText.test.ts create mode 100644 src/components/Tabs/Tabs.test.ts create mode 100644 src/lib/renderer.ts diff --git a/config/plop/templates/block/Block.astro.hbs b/config/plop/templates/block/Block.astro.hbs index bb09745..5778c38 100644 --- a/config/plop/templates/block/Block.astro.hbs +++ b/config/plop/templates/block/Block.astro.hbs @@ -1,8 +1,8 @@ --- import type { {{ pascalCase name }}Fragment } from '@lib/types/datocms'; -interface Props { -block: {{ pascalCase name }}Fragment +export interface Props { + block: {{ pascalCase name }}Fragment } const { block } = Astro.props; --- diff --git a/config/plop/templates/block/Block.test.hbs b/config/plop/templates/block/Block.test.hbs new file mode 100644 index 0000000..50e34e4 --- /dev/null +++ b/config/plop/templates/block/Block.test.hbs @@ -0,0 +1,17 @@ +import { renderToFragment } from '@lib/renderer'; +import { describe, expect, test } from 'vitest'; +import {{ pascalCase name }}, { type Props } from './{{ pascalCase name }}.astro'; + +describe('{{ pascalCase name }}', () => { + test('Block is rendered', async () => { + const fragment = await renderToFragment({{ pascalCase name }}, { + props: { + block: {} + } + }); + + expect(fragment).toBeTruthy(); + }); + + // Add more tests here +}); diff --git a/config/plop/templates/component/Component.test.hbs b/config/plop/templates/component/Component.test.hbs new file mode 100644 index 0000000..476c248 --- /dev/null +++ b/config/plop/templates/component/Component.test.hbs @@ -0,0 +1,14 @@ +import { renderToFragment } from '@lib/renderer'; +import { describe, expect, test } from 'vitest'; +import {{ pascalCase name }} from './{{ pascalCase name }}.astro'; + + +describe('{{ pascalCase name }}', () => { + test('Component is rendered', async () => { + const fragment = await renderToFragment({{ pascalCase name }}); + + expect(fragment).toBeTruthy(); + }); + + // Add more tests here +}); diff --git a/package-lock.json b/package-lock.json index 0bba31c..9ef1002 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,6 +43,7 @@ "@types/accept-language-parser": "^1.5.6", "@types/dotenv-safe": "^8.1.4", "@types/eventsource": "^1.1.15", + "@types/jsdom": "^21.1.7", "@typescript-eslint/eslint-plugin": "^6.7.2", "@typescript-eslint/parser": "^6.7.2", "chokidar-cli": "^3.0.0", @@ -52,10 +53,12 @@ "eslint-plugin-astro": "^0.29.0", "eventsource": "^2.0.2", "husky": "^8.0.3", + "jsdom": "^25.0.0", "nano-staged": "^0.8.0", "npm-run-all": "^4.1.5", "plop": "^4.0.0", "svg-sprite": "^2.0.2", + "vite-tsconfig-paths": "^5.0.1", "vitest": "^1.6.0", "wrangler": "^3.23.0" } @@ -5263,6 +5266,18 @@ "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", "dev": true }, + "node_modules/@types/jsdom": { + "version": "21.1.7", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.7.tgz", + "integrity": "sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -5368,6 +5383,13 @@ "@types/node": "*" } }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/triple-beam": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", @@ -6946,6 +6968,13 @@ "integrity": "sha512-KVWlF6WKlUGJA8FOJYVVk7xDm3PxITWXp9hTeDsXMtUPvItQ2x6g2rIeNAa0TtAiiWvHJqhyA3wo+pj0rA7Ldg==", "dev": true }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/at-least-node": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", @@ -8142,6 +8171,19 @@ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/comma-separated-tokens": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", @@ -8375,11 +8417,75 @@ "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", "dev": true }, + "node_modules/cssstyle": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.1.0.tgz", + "integrity": "sha512-h66W1URKpBS5YMI/V8PyXvTMFT8SupJ1IzoIV8IeBC/ji8WVmrO8dGlTi+2dh6whmdk6BiKJLD/ZBkhWbcg6nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "rrweb-cssom": "^0.7.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/data-uri-to-buffer": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz", "integrity": "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==" }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-urls/node_modules/tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-urls/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/data-urls/node_modules/whatwg-url": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz", + "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/dataloader": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.2.2.tgz", @@ -8434,6 +8540,13 @@ "node": ">=0.10.0" } }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "dev": true, + "license": "MIT" + }, "node_modules/decode-named-character-reference": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", @@ -8792,6 +8905,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/dependency-graph": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", @@ -9986,6 +10109,21 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/form-data-encoder": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.9.0.tgz", @@ -10795,6 +10933,19 @@ "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/html-escaper": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", @@ -10994,10 +11145,11 @@ } }, "node_modules/http-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", - "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, + "license": "MIT", "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" @@ -11020,10 +11172,11 @@ } }, "node_modules/https-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", - "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", "dev": true, + "license": "MIT", "dependencies": { "agent-base": "^7.0.2", "debug": "4" @@ -11632,6 +11785,13 @@ "node": ">=0.10.0" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -11927,6 +12087,84 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdom": { + "version": "25.0.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.0.tgz", + "integrity": "sha512-OhoFVT59T7aEq75TVw9xxEfkXgacpqAhQaYgP9y/fDqWQCMB/b1H66RfmPm/MaeaAIU9nDwMOVTlPN51+ao6CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssstyle": "^4.0.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.12", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.7.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.4", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^2.11.2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/jsdom/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/jsdom/node_modules/whatwg-url": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz", + "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -14102,6 +14340,13 @@ "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", "dev": true }, + "node_modules/nwsapi": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.12.tgz", + "integrity": "sha512-qXDmcVlZV4XRtKFzddidpfVP4oMSGhga+xdMc25mv8kaLUHtgzCDhUxkrN8exkGdTlLNaXj7CV3GtON7zuGZ+w==", + "dev": true, + "license": "MIT" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -15114,6 +15359,13 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true, + "license": "MIT" + }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -15150,6 +15402,13 @@ "node": ">=6.0.0" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true, + "license": "MIT" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -15519,6 +15778,13 @@ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -16097,6 +16363,13 @@ "node": ">=8" } }, + "node_modules/rrweb-cssom": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", + "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", + "dev": true, + "license": "MIT" + }, "node_modules/run-applescript": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz", @@ -16319,6 +16592,19 @@ "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==" }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/scuid": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/scuid/-/scuid-1.1.0.tgz", @@ -17066,6 +17352,13 @@ "tslib": "^2.0.3" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, "node_modules/synckit": { "version": "0.8.6", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.6.tgz", @@ -17211,6 +17504,32 @@ "node": ">=8.0" } }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -17833,6 +18152,17 @@ "punycode": "^2.1.0" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/urlpattern-polyfill": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-9.0.0.tgz", @@ -18026,6 +18356,26 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/vite-tsconfig-paths": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-5.0.1.tgz", + "integrity": "sha512-yqwv+LstU7NwPeNqajZzLEBVpUFU6Dugtb2P84FXuvaoYA+/70l9MHE+GYfYAycVyPSDYZ7mjOFuYBRqlEpTig==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "globrex": "^0.1.2", + "tsconfck": "^3.0.3" + }, + "peerDependencies": { + "vite": "*" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, "node_modules/vite/node_modules/@esbuild/aix-ppc64": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", @@ -18701,6 +19051,19 @@ "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==" }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", @@ -18747,6 +19110,42 @@ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", "dev": true }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -19387,9 +19786,10 @@ "dev": true }, "node_modules/ws": { - "version": "8.15.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.15.1.tgz", - "integrity": "sha512-W5OZiCjXEmk0yZ66ZN82beM5Sz7l7coYxpRkzS+p9PP+ToQry8szKh+61eNktr7EA9DOwvFGhfC605jDHbP6QQ==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "license": "MIT", "engines": { "node": ">=10.0.0" }, @@ -19406,6 +19806,23 @@ } } }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, "node_modules/xpath": { "version": "0.0.32", "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.32.tgz", diff --git a/package.json b/package.json index c138dd2..60322d8 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,7 @@ "@types/accept-language-parser": "^1.5.6", "@types/dotenv-safe": "^8.1.4", "@types/eventsource": "^1.1.15", + "@types/jsdom": "^21.1.7", "@typescript-eslint/eslint-plugin": "^6.7.2", "@typescript-eslint/parser": "^6.7.2", "chokidar-cli": "^3.0.0", @@ -82,6 +83,7 @@ "eslint-plugin-astro": "^0.29.0", "eventsource": "^2.0.2", "husky": "^8.0.3", + "jsdom": "^25.0.0", "nano-staged": "^0.8.0", "npm-run-all": "^4.1.5", "plop": "^4.0.0", diff --git a/src/blocks/EmbedBlock/EmbedBlock.astro b/src/blocks/EmbedBlock/EmbedBlock.astro index 7d92d6c..672ed0b 100644 --- a/src/blocks/EmbedBlock/EmbedBlock.astro +++ b/src/blocks/EmbedBlock/EmbedBlock.astro @@ -8,7 +8,7 @@ import TwitterEmbed from './embeds/Twitter.astro'; import VimeoEmbed from './embeds/Vimeo.astro'; import YouTubeEmbed from './embeds/YouTube.astro'; -interface Props { +export interface Props { block: EmbedBlockFragment; } const { block } = Astro.props; diff --git a/src/blocks/EmbedBlock/EmbedBlock.test.ts b/src/blocks/EmbedBlock/EmbedBlock.test.ts new file mode 100644 index 0000000..15a9635 --- /dev/null +++ b/src/blocks/EmbedBlock/EmbedBlock.test.ts @@ -0,0 +1,167 @@ +import { renderToFragment } from '@lib/renderer'; +import { describe, expect, test } from 'vitest'; +import EmbedBlock, { type Props } from './EmbedBlock.astro'; + +describe('EmbedBlock', () => { + test('renders a Twitter embed block correctly', async () => { + const fragment = await renderToFragment(EmbedBlock, { + props: { + block: { + id: '123', + url: 'https://twitter.com/devoorhoede/status/1670853955285565440', + data: { + provider_name: 'Twitter', + url: 'https://twitter.com/devoorhoede/status/1670853955285565440', + author_name: 'De Voorhoede', + author_url: 'https://twitter.com/devoorhoede', + html: '\n\n\n', + width: 550, + height: null, + type: 'rich', + cache_age: '3153600000', + provider_url: 'https://twitter.com', + version: '1.0', + }, + }, + }, + }); + + expect(fragment.querySelector('default-embed')).toBeTruthy(); + expect(fragment.querySelector('[data-provider="Twitter"]')).toBeTruthy(); + }); + + test('renders a Flickr embed block correctly', async () => { + const fragment = await renderToFragment(EmbedBlock, { + props: { + block: { + id: '123', + url: 'https://www.flickr.com/photos/danielcheong/50416691972', + data: { + provider_name: 'Flickr', + url: 'https://live.staticflickr.com/65535/50416691972_d17d04862f_b.jpg', + type: 'photo', + flickr_type: 'photo', + title: 'Fantasy Island', + author_name: 'DanielKHC', + author_url: 'https://www.flickr.com/photos/danielcheong/', + width: 1024, + height: 683, + web_page: 'https://www.flickr.com/photos/danielcheong/50416691972/', + thumbnail_url: 'https://live.staticflickr.com/65535/50416691972_d17d04862f_q.jpg', + thumbnail_width: 150, + thumbnail_height: 150, + web_page_short_url: 'https://flic.kr/p/2jP9J2S', + license: 'All Rights Reserved', + license_id: 0, + html: 'Fantasy Island', + version: 1, + cache_age: 3600, + provider_url: 'https://www.flickr.com/', + }, + }, + }, + }); + + expect(fragment.querySelector('default-embed')).toBeTruthy(); + expect(fragment.querySelector('[data-provider="Flickr"]')).toBeTruthy(); + }); + + test('renders a CodePen embed block correctly', async () => { + const fragment = await renderToFragment(EmbedBlock, { + props: { + block: { + id: '123', + url: 'https://codepen.io/mattdesl/pen/epQNqN', + data: { + provider_name: 'CodePen', + url: 'https://codepen.io/mattdesl/pen/epQNqN', + success: true, + type: 'rich', + version: '1.0', + provider_url: 'https://codepen.io', + title: 'Junk Pile - #codevember', + author_name: 'Matt DesLauriers', + author_url: 'https://codepen.io/mattdesl', + height: '300', + width: '800', + thumbnail_width: '384', + thumbnail_height: '225', + thumbnail_url: 'https://shots.codepen.io/username/pen/epQNqN-512.jpg?version=1447132171', + html: '' + }, + }, + }, + }); + + expect(fragment.querySelector('default-embed')).toBeTruthy(); + expect(fragment.querySelector('[data-provider="CodePen"]')).toBeTruthy(); + }); + + test('renders a YouTube embed block correctly', async () => { + const fragment = await renderToFragment(EmbedBlock, { + props: { + block: { + id: '123', + url: 'https://www.youtube.com/watch?v=feylP4p1-KU&ab_channel=GreenCaravan', + data: { + provider_name: 'YouTube', + url: 'https://www.youtube.com/watch?v=feylP4p1-KU&ab_channel=GreenCaravan', + title: 'Green Caravan pitch (NL)', + author_name: 'Green Caravan', + author_url: 'https://www.youtube.com/@greencaravan6688', + type: 'video', + height: 113, + width: 200, + version: '1.0', + provider_url: 'https://www.youtube.com/', + thumbnail_height: 360, + thumbnail_width: 480, + thumbnail_url: 'https://i.ytimg.com/vi/feylP4p1-KU/hqdefault.jpg', + html: '' + }, + }, + }, + }); + + expect(fragment.querySelector('video-embed')).toBeTruthy(); + expect(fragment.querySelector('[data-provider="youtube"]')).toBeTruthy(); + }); + + test('renders a Vimeo embed block correctly', async () => { + const fragment = await renderToFragment(EmbedBlock, { + props: { + block: { + id: '123', + url: 'https://vimeo.com/641535920', + data: { + provider_name: 'Vimeo', + url: 'https://vimeo.com/641535920', + type: 'video', + version: '1.0', + provider_url: 'https://vimeo.com/', + title: 'Structured text with inline records in DatoCMS', + author_name: 'De Voorhoede', + author_url: 'https://vimeo.com/voorhoede', + is_plus: '0', + account_type: 'pro', + html: '', + width: 372, + height: 360, + duration: 35, + description: 'Companion article: https://www.voorhoede.nl/en/blog/enriching-rich-text-with-inline-components-datocms-react/', + thumbnail_url: 'https://i.vimeocdn.com/video/1290514162-5999b7e95c88cad3fe8cfcff2a3e1bd3a6e6cf4fe322cb95a_295x166', + thumbnail_width: 295, + thumbnail_height: 286, + thumbnail_url_with_play_button: 'https://i.vimeocdn.com/filter/overlay?src0=https%3A%2F%2Fi.vimeocdn.com%2Fvideo%2F1290514162-5999b7e95c88cad3fe8cfcff2a3e1bd3a6e6cf4fe322cb95a_295x166&src1=http%3A%2F%2Ff.vimeocdn.com%2Fp%2Fimages%2Fcrawler_play.png', + upload_date: '2021-11-02 10:17:54', + video_id: 641535920, + uri: '/videos/641535920' + }, + }, + }, + }); + + expect(fragment.querySelector('video-embed')).toBeTruthy(); + expect(fragment.querySelector('[data-provider="vimeo"]')).toBeTruthy(); + }); +}); diff --git a/src/blocks/ImageBlock/Image.astro b/src/blocks/ImageBlock/Image.astro index 9ea289e..3f66ad9 100644 --- a/src/blocks/ImageBlock/Image.astro +++ b/src/blocks/ImageBlock/Image.astro @@ -44,10 +44,10 @@ const imageUnavailableMessage = t('image_unavailable'); data-unavailable={ imageUnavailableMessage } /> : // vector images do not have Imgix generated props, so using as is: - { } { image.title && ( -
- { image.title } -
+
{ image.title }
)} diff --git a/src/blocks/ImageBlock/ImageBlock.astro b/src/blocks/ImageBlock/ImageBlock.astro index 3bdd35b..6d57cb6 100644 --- a/src/blocks/ImageBlock/ImageBlock.astro +++ b/src/blocks/ImageBlock/ImageBlock.astro @@ -2,7 +2,7 @@ import type { ImageBlockFragment } from '@lib/types/datocms'; import Image from './Image.astro'; -interface Props { +export interface Props { block: ImageBlockFragment } diff --git a/src/blocks/ImageBlock/ImageBlock.test.ts b/src/blocks/ImageBlock/ImageBlock.test.ts new file mode 100644 index 0000000..6d9031b --- /dev/null +++ b/src/blocks/ImageBlock/ImageBlock.test.ts @@ -0,0 +1,106 @@ +import { renderToFragment } from '@lib/renderer'; +import { describe, expect, test } from 'vitest'; +import ImageBlock, { type Props } from './ImageBlock.astro'; + +describe('ImageBlock', () => { + test('renders the image with the correct source, alt text, and loading attribute', async () => { + const fragment = await renderToFragment(ImageBlock, { + props: { + block: { + id: '123', + image: { + url: 'https://example.com/test.jpg', + alt: 'A test image', + height: 150, + width: 150 + } + } + }, + }); + + const img = fragment.querySelector('img'); + expect(img).toBeTruthy(); + expect(img?.src).toBe('https://example.com/test.jpg'); + expect(img?.alt).toBe('A test image'); + expect(img?.getAttribute('loading')).toBe('lazy'); + }); + + test('renders the correct aspect ratio style', async () => { + const fragment = await renderToFragment(ImageBlock, { + props: { + block: { + id: '123', + image: { + url: 'https://example.com/test.jpg', + alt: 'A test image', + height: 150, + width: 150 + } + } + }, + }); + + const img = fragment.querySelector('img'); + expect(img?.style.aspectRatio).toBe('150/150'); + }); + + test('renders with a figcaption when title is provided', async () => { + const fragment = await renderToFragment(ImageBlock, { + props: { + block: { + id: '123', + image: { + url: 'https://example.com/test.jpg', + alt: 'A test image', + title: 'See the test image', + height: 150, + width: 150 + } + } + }, + }); + + expect(fragment.querySelector('figcaption')).toBeTruthy(); + expect(fragment.querySelector('figcaption')?.textContent).toBe('See the test image'); + }); + + test('does not render figcaption when no title is provided', async () => { + const fragment = await renderToFragment(ImageBlock, { + props: { + block: { + id: '123', + image: { + url: 'https://example.com/test.jpg', + alt: 'A test image', + height: 150, + width: 150 + } + } + }, + }); + + expect(fragment.querySelector('figcaption')).toBeNull(); + }); + + test('renders responsive image with correct base64 background', async () => { + const fragment = await renderToFragment(ImageBlock, { + props: { + block: { + id: '123', + image: { + url: 'https://example.com/test.jpg', + alt: 'A responsive test image', + responsiveImage: { + aspectRatio: 1.9526315789473685, + base64: 'data:image/jpeg;base64,...' + } + } + } + }, + }); + + const img = fragment.querySelector('img'); + expect(img?.style.aspectRatio).toBe('1.9526315789473685'); + expect(img?.style.backgroundImage).toContain('base64'); + }); +}); diff --git a/src/blocks/InternalLink/InternalLink.astro b/src/blocks/InternalLink/InternalLink.astro index 937dd31..9848ec9 100644 --- a/src/blocks/InternalLink/InternalLink.astro +++ b/src/blocks/InternalLink/InternalLink.astro @@ -6,7 +6,7 @@ import type { } from '@lib/types/datocms'; import { getPagePath } from './index'; -interface Props { +export interface Props { link: InternalLinkFragment; openInNewTab?: boolean; } diff --git a/src/blocks/InternalLink/InternalLink.test.ts b/src/blocks/InternalLink/InternalLink.test.ts new file mode 100644 index 0000000..7e1debc --- /dev/null +++ b/src/blocks/InternalLink/InternalLink.test.ts @@ -0,0 +1,24 @@ +import { renderToFragment } from '@lib/renderer'; +import { describe, expect, test } from 'vitest'; +import InternalLink, { type Props } from './InternalLink.astro'; + +describe('InternalLink', () => { + test('renders an anchor element with the correct href and text when provided with valid link and page properties', async () => { + const fragment = await renderToFragment(InternalLink, { + params: { locale: 'en' }, + props: { + link: { + title: 'A test link', + page: { + __typename: 'HomePageRecord', + title: 'A test page', + } + } + } + }); + + expect(fragment.querySelector('a')).toBeTruthy(); + expect(fragment.querySelector('a')?.href).toBe('/en/'); + expect(fragment.querySelector('a')?.textContent).toBe('A test link'); + }); +}); diff --git a/src/blocks/PagePartialBlock/PagePartialBlock.astro b/src/blocks/PagePartialBlock/PagePartialBlock.astro index fa5360b..9dca605 100644 --- a/src/blocks/PagePartialBlock/PagePartialBlock.astro +++ b/src/blocks/PagePartialBlock/PagePartialBlock.astro @@ -9,7 +9,7 @@ type Item = { title: string; blocks: AnyBlock[]; }; -interface Props { +export interface Props { block: PagePartialBlockFragment } const { block } = Astro.props; @@ -35,7 +35,7 @@ const isLayout = (layout: PagePartialBlockFragment['layout']) => block.layout == { (isLayout('accordion-open') || isLayout('accordion-closed')) && ( { items.map(item => ( - diff --git a/src/blocks/PagePartialBlock/PagePartialBlock.test.ts b/src/blocks/PagePartialBlock/PagePartialBlock.test.ts new file mode 100644 index 0000000..21f73d6 --- /dev/null +++ b/src/blocks/PagePartialBlock/PagePartialBlock.test.ts @@ -0,0 +1,238 @@ +import { renderToFragment } from '@lib/renderer'; +import { describe, expect, test } from 'vitest'; +import PagePartialBlock, { type Props } from './PagePartialBlock.astro'; + +describe('PagePartialBlock', () => { + test('renders a stack layout without a title when layout is "stack-untitled"', async () => { + const fragment = await renderToFragment(PagePartialBlock, { + props: { + block: { + __typename: 'PagePartialBlockRecord', + id: 'ay-D0Z1ZTqWVszeV9ZqfJA', + layout: 'stack-untitled', + items: [ + { + title: 'Partial A', + blocks: [ + { + __typename: 'TextBlockRecord', + text: { + blocks: [], + links: [], + value: { + schema: 'dast', + document: { + type: 'root', + children: [ + { + type: 'paragraph', + children: [ + { + type: 'span', + value: 'This is a test' + }, + ] + } + ] + } + } + } + } + ] + } + ] + } + } + }); + + expect(fragment.querySelector('h2')).toBeFalsy(); + expect(fragment.querySelector('p')).toBeTruthy(); + expect(fragment.querySelector('p')?.textContent).toBe('This is a test'); + }); + + test('renders a stack layout with a title when layout is "stack-titled"', async () => { + const fragment = await renderToFragment(PagePartialBlock, { + props: { + block: { + __typename: 'PagePartialBlockRecord', + id: 'ay-D0Z1ZTqWVszeV9ZqfJA', + layout: 'stack-titled', + items: [ + { + title: 'Partial A', + blocks: [ + { + __typename: 'TextBlockRecord', + text: { + blocks: [], + links: [], + value: { + schema: 'dast', + document: { + type: 'root', + children: [ + { + type: 'paragraph', + children: [ + { + type: 'span', + value: 'This is a test' + }, + ] + } + ] + } + } + } + } + ] + } + ] + } + } + }); + + expect(fragment.querySelector('h2')).toBeTruthy(); + expect(fragment.querySelector('h2')?.textContent).toBe('Partial A'); + expect(fragment.querySelector('p')).toBeTruthy(); + expect(fragment.querySelector('p')?.textContent).toBe('This is a test'); + }); + + test('renders a closed accordion layout when layout is "accordion-closed"', async () => { + const fragment = await renderToFragment(PagePartialBlock, { + props: { + block: { + __typename: 'PagePartialBlockRecord', + id: 'ay-D0Z1ZTqWVszeV9ZqfJA', + layout: 'accordion-closed', + items: [ + { + title: 'Partial A', + blocks: [ + { + __typename: 'TextBlockRecord', + text: { + blocks: [], + links: [], + value: { + schema: 'dast', + document: { + type: 'root', + children: [ + { + type: 'paragraph', + children: [ + { + type: 'span', + value: 'This is a test' + }, + ] + } + ] + } + } + } + } + ] + } + ] + } + } + }); + + expect(fragment.querySelector('details')).toBeTruthy(); + expect(fragment.querySelector('details[open]')).toBeFalsy(); + }); + + test('renders an open accordion layout when layout is "accordion-open"', async () => { + const fragment = await renderToFragment(PagePartialBlock, { + props: { + block: { + __typename: 'PagePartialBlockRecord', + id: 'ay-D0Z1ZTqWVszeV9ZqfJA', + layout: 'accordion-open', + items: [ + { + title: 'Partial A', + blocks: [ + { + __typename: 'TextBlockRecord', + text: { + blocks: [], + links: [], + value: { + schema: 'dast', + document: { + type: 'root', + children: [ + { + type: 'paragraph', + children: [ + { + type: 'span', + value: 'This is a test' + }, + ] + } + ] + } + } + } + } + ] + } + ] + } + } + }); + + expect(fragment.querySelector('details[open]')).toBeTruthy(); + }); + + test('renders a tabs layout with the correct structure and content when layout is "tabs"', async () => { + const fragment = await renderToFragment(PagePartialBlock, { + props: { + block: { + __typename: 'PagePartialBlockRecord', + id: 'ay-D0Z1ZTqWVszeV9ZqfJA', + layout: 'tabs', + items: [ + { + title: 'Partial A', + blocks: [ + { + __typename: 'TextBlockRecord', + text: { + blocks: [], + links: [], + value: { + schema: 'dast', + document: { + type: 'root', + children: [ + { + type: 'paragraph', + children: [ + { + type: 'span', + value: 'This is a test' + }, + ] + } + ] + } + } + } + } + ] + } + ] + } + } + }); + + expect(fragment.querySelector('tabs-component')).toBeTruthy(); + expect(fragment.querySelector('tabs-tab[role="heading"]')?.textContent).toContain('Partial A'); + expect(fragment.querySelector('tabs-panel[role="region"] p')?.textContent).toContain('This is a test'); + }); +}); diff --git a/src/blocks/TableBlock/TableBlock.astro b/src/blocks/TableBlock/TableBlock.astro index 7a03eaf..bc1872f 100644 --- a/src/blocks/TableBlock/TableBlock.astro +++ b/src/blocks/TableBlock/TableBlock.astro @@ -12,8 +12,8 @@ type Table = { type Block = Omit & { table: Table } -type Props = { - block: Block +export type Props = { + block: Block }; const { block } = Astro.props; const { title, table, hasHeaderRow, hasHeaderColumn } = block; @@ -24,7 +24,7 @@ const isNumeric = (value: string) => !isNaN(Number(value.replace('%','')));
{ /* using figcaption instead of table > caption as the latter would scroll horizontally on overflow */ } { title && ( -
{title}
+
{title}
)}
@@ -47,7 +47,7 @@ const isNumeric = (value: string) => !isNaN(Number(value.replace('%',''))); scope={ isHeaderColumn(index) ? 'row' : undefined } > {value} - + ))} ))} diff --git a/src/blocks/TableBlock/TableBlock.test.ts b/src/blocks/TableBlock/TableBlock.test.ts new file mode 100644 index 0000000..af876ab --- /dev/null +++ b/src/blocks/TableBlock/TableBlock.test.ts @@ -0,0 +1,89 @@ +import { renderToFragment } from '@lib/renderer'; +import { describe, expect, test } from 'vitest'; +import TableBlock, { type Props } from './TableBlock.astro'; + +const block = { + _modelApiKey: 'table_block', + id: '123', + title: 'Table Block', + table: { + columns: ['test_1', 'test_2'], + data: [ + { test_1: 'Test 1', test_2: 'Test 2' } + ] + }, +}; + +describe('TableBlock', () => { + test('renders a table with both header row and header column', async () => { + const fragment = await renderToFragment(TableBlock, { + props: { + block: { + ...block, + hasHeaderRow: true, + hasHeaderColumn: true + } + } + }); + + expect(fragment.querySelector('figure')).toBeTruthy(); + expect(fragment.querySelector('figcaption')).toBeTruthy(); + expect(fragment.querySelector('table')).toBeTruthy(); + expect(fragment.querySelector('th[scope="col"]')).toBeTruthy(); + expect(fragment.querySelector('th[scope="row"]')).toBeTruthy(); + }); + + test('renders a table with a header row only', async () => { + const fragment = await renderToFragment(TableBlock, { + props: { + block: { + ...block, + hasHeaderRow: true, + hasHeaderColumn: false + } + } + }); + + expect(fragment.querySelector('figure')).toBeTruthy(); + expect(fragment.querySelector('figcaption')).toBeTruthy(); + expect(fragment.querySelector('table')).toBeTruthy(); + expect(fragment.querySelector('th[scope="col"]')).toBeTruthy(); + expect(fragment.querySelector('th[scope="row"]')).toBeFalsy(); + }); + + test('renders a table with a header column only', async () => { + const fragment = await renderToFragment(TableBlock, { + props: { + block: { + ...block, + hasHeaderRow: false, + hasHeaderColumn: true + } + } + }); + + expect(fragment.querySelector('figure')).toBeTruthy(); + expect(fragment.querySelector('figcaption')).toBeTruthy(); + expect(fragment.querySelector('table')).toBeTruthy(); + expect(fragment.querySelector('th[scope="col"]')).toBeFalsy(); + expect(fragment.querySelector('th[scope="row"]')).toBeTruthy(); + }); + + test('renders a table without header row and header column', async () => { + const fragment = await renderToFragment(TableBlock, { + props: { + block: { + ...block, + hasHeaderRow: false, + hasHeaderColumn: false + } + } + }); + + expect(fragment.querySelector('figure')).toBeTruthy(); + expect(fragment.querySelector('figcaption')).toBeTruthy(); + expect(fragment.querySelector('table')).toBeTruthy(); + expect(fragment.querySelector('th[scope="col"]')).toBeFalsy(); + expect(fragment.querySelector('th[scope="row"]')).toBeFalsy(); + }); +}); diff --git a/src/blocks/TextBlock/TextBlock.astro b/src/blocks/TextBlock/TextBlock.astro index bc0647e..decb097 100644 --- a/src/blocks/TextBlock/TextBlock.astro +++ b/src/blocks/TextBlock/TextBlock.astro @@ -3,7 +3,7 @@ import type { StructuredText } from 'datocms-structured-text-utils'; import type { TextBlockFragment } from '@lib/types/datocms'; import Text from './Text.astro'; -interface Props { +export interface Props { block: TextBlockFragment } const { block } = Astro.props; diff --git a/src/blocks/TextBlock/TextBlock.test.ts b/src/blocks/TextBlock/TextBlock.test.ts new file mode 100644 index 0000000..2fcf05b --- /dev/null +++ b/src/blocks/TextBlock/TextBlock.test.ts @@ -0,0 +1,93 @@ +import { renderToFragment } from '@lib/renderer'; +import { describe, expect, test } from 'vitest'; +import TextBlock, { type Props } from './TextBlock.astro'; + +describe('TextBlock Component', () => { + test('transforms a heading level 1 into heading level 2', async () => { + const fragment = await renderToFragment(TextBlock, { + props: { + block: { + __typename: 'TextBlockRecord', + text: { + blocks: [], + links: [], + value: { + schema: 'dast', + document: { + type: 'root', + children: [ + { + type: 'heading', + level: 1, + children: [ + { + type: 'span', + value: 'This is a test heading' + } + ] + }, + { + type: 'paragraph', + children: [ + { + type: 'span', + value: 'This is a test paragraph' + }, + ] + } + ] + } + } + } + } + } + }); + + expect(fragment.querySelector('h2')?.textContent).toBe('This is a test heading'); + expect(fragment.querySelector('p')?.textContent).toBe('This is a test paragraph'); + }); + + test('renders a heading level 3 and a paragraph with the correct text content', async () => { + const fragment = await renderToFragment(TextBlock, { + props: { + block: { + __typename: 'TextBlockRecord', + text: { + blocks: [], + links: [], + value: { + schema: 'dast', + document: { + type: 'root', + children: [ + { + type: 'heading', + level: 3, + children: [ + { + type: 'span', + value: 'This is a test heading' + } + ] + }, + { + type: 'paragraph', + children: [ + { + type: 'span', + value: 'This is a test paragraph' + }, + ] + } + ] + } + } + } + } + } + }); + + expect(fragment.querySelector('h3')?.textContent).toBe('This is a test heading'); + expect(fragment.querySelector('p')?.textContent).toBe('This is a test paragraph'); + }); +}); diff --git a/src/blocks/TextImageBlock/TextImageBlock.test.ts b/src/blocks/TextImageBlock/TextImageBlock.test.ts new file mode 100644 index 0000000..82588a1 --- /dev/null +++ b/src/blocks/TextImageBlock/TextImageBlock.test.ts @@ -0,0 +1,100 @@ +import { renderToFragment } from '@lib/renderer'; +import type { TextImageBlockFragment } from '@lib/types/datocms'; +import { describe, expect, test } from 'vitest'; +import TextImageBlock from './TextImageBlock.astro'; + +describe('TextImageBlock', () => { + test('renders with "text-image" layout correctly', async () => { + const fragment = await renderToFragment<{ block: TextImageBlockFragment }>(TextImageBlock, { + props: { + block: { + layout: 'text-image', + text: { + blocks: [], + links: [], + value: { + schema: 'dast', + document: { + type: 'root', + children: [ + { + type: 'heading', + level: 1, + children: [ + { + type: 'span', + value: 'This is a test' + } + ] + }, + { + type: 'paragraph', + children: [ + { + type: 'span', + value: 'This is a test' + }, + ] + } + ] + } + } + }, + image: { + url: 'https://via.placeholder.com/300', + alt: 'Placeholder' + } + } + } + }); + + expect(fragment.querySelector('.layout--text-image')).toBeTruthy(); + }); + + test('renders with "image-text" layout correctly', async () => { + const fragment = await renderToFragment<{ block: TextImageBlockFragment }>(TextImageBlock, { + props: { + block: { + layout: 'image-text', + text: { + blocks: [], + links: [], + value: { + schema: 'dast', + document: { + type: 'root', + children: [ + { + type: 'heading', + level: 1, + children: [ + { + type: 'span', + value: 'This is a test' + } + ] + }, + { + type: 'paragraph', + children: [ + { + type: 'span', + value: 'This is a test' + }, + ] + } + ] + } + } + }, + image: { + url: 'https://via.placeholder.com/300', + alt: 'Placeholder' + } + } + } + }); + + expect(fragment.querySelector('.layout--image-text')).toBeTruthy(); + }); +}); diff --git a/src/blocks/VideoBlock/VideoBlock.astro b/src/blocks/VideoBlock/VideoBlock.astro index 3a160f4..d34249c 100644 --- a/src/blocks/VideoBlock/VideoBlock.astro +++ b/src/blocks/VideoBlock/VideoBlock.astro @@ -3,7 +3,7 @@ import type { UploadVideoField, VideoBlockFragment, VideoTextTrackRecord } from import VideoFallback from './VideoFallback.astro'; import VideoTextTracks from './VideoTextTracks.astro'; -interface Props { +export interface Props { block: VideoBlockFragment; } const { block } = Astro.props; @@ -30,7 +30,7 @@ const videoAttributes = { data-streaming-url={ video.streamingUrl } >
- { /* We use 2 video elements: + { /* We use 2 video elements: - The first is enhanced in the client-side script to use streaming with adaptive bitrate. - The second is fallback native video element that works without JS or custom elmenet support. As a JS check is needed for the first video to appear, it's wrapped in a div to reserve space for it. diff --git a/src/blocks/VideoBlock/VideoBlock.test.ts b/src/blocks/VideoBlock/VideoBlock.test.ts new file mode 100644 index 0000000..2a5e802 --- /dev/null +++ b/src/blocks/VideoBlock/VideoBlock.test.ts @@ -0,0 +1,66 @@ +import { renderToFragment } from '@lib/renderer'; +import { describe, expect, test } from 'vitest'; +import VideoBlock, { type Props } from './VideoBlock.astro'; + +describe('VideoBlock', () => { + test('renders a video block with a simple video', async () => { + const fragment = await renderToFragment(VideoBlock, { + props: { + block: { + id: '123', + title: 'A test video', + autoplay: false, + mute: false, + loop: false, + videoAsset: { + video: { + muxAssetId: '123', + muxPlaybackId: '123', + streamingUrl: 'https://example.com/', + thumbnailUrl: 'https://example.com/', + } + }, + tracks: [] + } + } + }); + + expect(fragment.querySelector('figure')).toBeTruthy(); + expect(fragment.querySelector('figcaption')).toBeTruthy(); + expect(fragment.querySelector('video')).toBeTruthy(); + }); + + test('renders a video block with subtitle tracks', async () => { + const fragment = await renderToFragment(VideoBlock, { + props: { + block: { + id: '123', + title: 'A test video', + autoplay: false, + mute: false, + loop: false, + videoAsset: { + video: { + muxAssetId: '123', + muxPlaybackId: '123', + streamingUrl: 'https://example.com/', + thumbnailUrl: 'https://example.com/', + } + }, + tracks: [{ + locale: 'en', + kind: 'mp4', + file: { + url: 'https://example.com/' + } + }] + } + } + }); + + expect(fragment.querySelector('figure')).toBeTruthy(); + expect(fragment.querySelector('figcaption')).toBeTruthy(); + expect(fragment.querySelector('video')).toBeTruthy(); + expect(fragment.querySelector('track[srclang="en"]')).toBeTruthy(); + }); +}); diff --git a/src/blocks/VideoEmbedBlock/VideoEmbedBlock.astro b/src/blocks/VideoEmbedBlock/VideoEmbedBlock.astro index e0336cc..7f50047 100644 --- a/src/blocks/VideoEmbedBlock/VideoEmbedBlock.astro +++ b/src/blocks/VideoEmbedBlock/VideoEmbedBlock.astro @@ -4,7 +4,7 @@ import { t } from '@lib/i18n'; import Icon from '@components/Icon'; import PlayButton from './PlayButton.astro'; -interface Props { +export interface Props { block: VideoEmbedBlockFragment; } const { block } = Astro.props; diff --git a/src/blocks/VideoEmbedBlock/VideoEmbedBlock.test.ts b/src/blocks/VideoEmbedBlock/VideoEmbedBlock.test.ts new file mode 100644 index 0000000..e36f656 --- /dev/null +++ b/src/blocks/VideoEmbedBlock/VideoEmbedBlock.test.ts @@ -0,0 +1,32 @@ +import { renderToFragment } from '@lib/renderer'; +import { describe, expect, test } from 'vitest'; +import VideoEmbedBlock, { type Props } from './VideoEmbedBlock.astro'; + +describe('VideoEmbedBlock', () => { + test('renders a YouTube embed block with the expected elements', async () => { + const fragment = await renderToFragment(VideoEmbedBlock, { + props: { + block: { + id: '123', + video: { + provider: 'youtube', + providerUid: 'feylP4p1-KU', + thumbnailUrl: 'https://i.ytimg.com/vi/feylP4p1-KU/hqdefault.jpg', + title: 'Green Caravan pitch (NL)', + url: 'https://www.youtube.com/watch?v=feylP4p1-KU', + height: 113, + width: 200 + }, + title: '', + autoplay: false, + mute: false, + loop: false + } + } + }); + + expect(fragment.querySelector('[data-provider="youtube"]')).toBeTruthy(); + expect(fragment.querySelector('.play-button__icon')).toBeTruthy(); + expect(fragment.querySelector('.consent-alert')).toBeTruthy(); + }); +}); diff --git a/src/components/Accordion/Accordion.test.ts b/src/components/Accordion/Accordion.test.ts new file mode 100644 index 0000000..53ae6ab --- /dev/null +++ b/src/components/Accordion/Accordion.test.ts @@ -0,0 +1,10 @@ +import { renderToFragment } from '@lib/renderer'; +import { describe, expect, test } from 'vitest'; +import Accordion from './Accordion.astro'; + +describe('Accordion', () => { + test('Component is rendered', async () => { + const fragment = await renderToFragment(Accordion); + expect(fragment.querySelector('section[role="group"]')).toBeTruthy(); + }); +}); diff --git a/src/components/Accordion/AccordionItem.astro b/src/components/Accordion/AccordionItem.astro index 8c4eb03..546e99b 100644 --- a/src/components/Accordion/AccordionItem.astro +++ b/src/components/Accordion/AccordionItem.astro @@ -1,10 +1,11 @@ --- import Icon from '@components/Icon'; -interface Props { +export interface Props { name: string; open?: boolean; } + const { name, open } = Astro.props; const attributes = { name, open }; --- diff --git a/src/components/Accordion/AccordionItem.test.ts b/src/components/Accordion/AccordionItem.test.ts new file mode 100644 index 0000000..1ae44e8 --- /dev/null +++ b/src/components/Accordion/AccordionItem.test.ts @@ -0,0 +1,22 @@ +import { renderToFragment } from '@lib/renderer'; +import { describe, expect, test } from 'vitest'; +import AccordionItem, { type Props } from './AccordionItem.astro'; + +describe('Accordion Item', () => { + test('Component is rendered', async () => { + const fragment = await renderToFragment(AccordionItem, { + slots: { + heading: 'Heading', + body: 'Body' + }, + props: { + name: 'test' + } + }); + + expect(fragment.querySelector('details')).toBeTruthy(); + expect(fragment.querySelector('details')?.getAttribute('name')).toBe('test'); + expect(fragment.querySelector('details')?.textContent).toContain('Body'); + expect(fragment.querySelector('summary')?.textContent).toContain('Heading'); + }); +}); diff --git a/src/components/Icon/Icon.test.ts b/src/components/Icon/Icon.test.ts new file mode 100644 index 0000000..d1545fb --- /dev/null +++ b/src/components/Icon/Icon.test.ts @@ -0,0 +1,13 @@ +import { renderToFragment } from '@lib/renderer'; +import { describe, expect, test } from 'vitest'; +import Icon from './Icon.astro'; + +const fragment = await renderToFragment(Icon); + +describe('Icon', () => { + test('Component is rendered', () => { + expect(fragment).toBeTruthy(); + }); + + // Add more tests here +}); diff --git a/src/components/ShareButton/ShareButton.test.ts b/src/components/ShareButton/ShareButton.test.ts new file mode 100644 index 0000000..8129606 --- /dev/null +++ b/src/components/ShareButton/ShareButton.test.ts @@ -0,0 +1,13 @@ +import { renderToFragment } from '@lib/renderer'; +import { describe, expect, test } from 'vitest'; +import ShareButton from './ShareButton.astro'; + +const fragment = await renderToFragment(ShareButton); + +describe('ShareButton', () => { + test('Component is rendered', () => { + expect(fragment).toBeTruthy(); + }); + + // Add more tests here +}); diff --git a/src/components/SkipLink/SkipLink.test.ts b/src/components/SkipLink/SkipLink.test.ts new file mode 100644 index 0000000..1c876a5 --- /dev/null +++ b/src/components/SkipLink/SkipLink.test.ts @@ -0,0 +1,13 @@ +import { renderToFragment } from '@lib/renderer'; +import { describe, expect, test } from 'vitest'; +import SkipLink from './SkipLink.astro'; + +const fragment = await renderToFragment(SkipLink); + +describe('SkipLink', () => { + test('Component is rendered', () => { + expect(fragment).toBeTruthy(); + }); + + // Add more tests here +}); diff --git a/src/components/StructuredText/StructuredText.test.ts b/src/components/StructuredText/StructuredText.test.ts new file mode 100644 index 0000000..9a59314 --- /dev/null +++ b/src/components/StructuredText/StructuredText.test.ts @@ -0,0 +1,13 @@ +import { renderToFragment } from '@lib/renderer'; +import { describe, expect, test } from 'vitest'; +import StructuredText from './StructuredText.astro'; + +const fragment = await renderToFragment(StructuredText); + +describe('StructuredText', () => { + test('Component is rendered', () => { + expect(fragment).toBeTruthy(); + }); + + // Add more tests here +}); diff --git a/src/components/Tabs/Tabs.test.ts b/src/components/Tabs/Tabs.test.ts new file mode 100644 index 0000000..df92436 --- /dev/null +++ b/src/components/Tabs/Tabs.test.ts @@ -0,0 +1,13 @@ +import { renderToFragment } from '@lib/renderer'; +import { describe, expect, test } from 'vitest'; +import Tabs from './Tabs.astro'; + +const fragment = await renderToFragment(Tabs); + +describe('Tabs', () => { + test('Component is rendered', () => { + expect(fragment).toBeTruthy(); + }); + + // Add more tests here +}); diff --git a/src/lib/renderer.ts b/src/lib/renderer.ts new file mode 100644 index 0000000..ecf7403 --- /dev/null +++ b/src/lib/renderer.ts @@ -0,0 +1,20 @@ +import { experimental_AstroContainer as AstroContainer, type ContainerRenderOptions } from 'astro/container'; +import type { AstroComponentFactory } from 'astro/runtime/server/index.js'; +import { JSDOM } from 'jsdom'; + +export async function renderToString( + component: AstroComponentFactory, + options?: ContainerRenderOptions & { props?: Props } +): Promise { + const container = await AstroContainer.create(); + return container.renderToString(component, options); +} + +export async function renderToFragment( + component: AstroComponentFactory, + options?: ContainerRenderOptions & { props?: Props } +): Promise { + const container = await AstroContainer.create(); + const result = await container.renderToString(component, options); + return JSDOM.fragment(result); +} diff --git a/vitest.config.ts b/vitest.config.ts index 8e3b58b..9bbe387 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,8 +1,10 @@ /// -import { defineConfig } from 'vitest/config'; +import { getViteConfig } from 'astro/config'; -export default defineConfig({ +export default getViteConfig({ test: { + // Vitest configuration options + teardownTimeout: 1000, reporters: process.env.GITHUB_ACTIONS ? ['dot', 'github-actions'] : ['dot'], - }, + } });