diff --git a/devbox.json b/devbox.json index b06eea7..7b8ecbc 100644 --- a/devbox.json +++ b/devbox.json @@ -1,14 +1,10 @@ { - "$schema": "https://raw.githubusercontent.com/jetify-com/devbox/0.12.0/.schema/devbox.schema.json", + "$schema": "https://raw.githubusercontent.com/jetify-com/devbox/0.12.0/.schema/devbox.schema.json", "packages": ["nodejs@22"], "shell": { - "init_hook": [ - "echo 'Welcome to devbox!' > /dev/null" - ], + "init_hook": ["echo 'Welcome to devbox!' > /dev/null"], "scripts": { - "test": [ - "echo \"Error: no test specified\" && exit 1" - ] + "test": ["npm test"] } } } diff --git a/package-lock.json b/package-lock.json index f67d902..4eb0ece 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.5.5", "globals": "^17.4.0", + "happy-dom": "^20.8.3", "postcss": "^8.5.8", "postcss-preset-env": "^11.2.0", "prettier": "^3.8.1", @@ -2845,6 +2846,33 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "25.3.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.3.tgz", + "integrity": "sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/whatwg-mimetype": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz", + "integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@vitest/expect": { "version": "4.0.18", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz", @@ -3322,6 +3350,19 @@ "node": ">=10.13.0" } }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es-module-lexer": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", @@ -3772,6 +3813,24 @@ "dev": true, "license": "ISC" }, + "node_modules/happy-dom": { + "version": "20.8.3", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.8.3.tgz", + "integrity": "sha512-lMHQRRwIPyJ70HV0kkFT7jH/gXzSI7yDkQFe07E2flwmNDFoWUTRMKpW2sglsnpeA7b6S2TJPp98EbQxai8eaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": ">=20.0.0", + "@types/whatwg-mimetype": "^3.0.2", + "@types/ws": "^8.18.1", + "entities": "^7.0.1", + "whatwg-mimetype": "^3.0.0", + "ws": "^8.18.3" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -5323,6 +5382,13 @@ "node": ">= 0.8.0" } }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", @@ -5524,6 +5590,16 @@ } } }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -5567,6 +5643,28 @@ "node": ">=0.10.0" } }, + "node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 692f886..8085203 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.5.5", "globals": "^17.4.0", + "happy-dom": "^20.8.3", "postcss": "^8.5.8", "postcss-preset-env": "^11.2.0", "prettier": "^3.8.1", diff --git a/test/build.test.js b/test/build.test.js index 6092785..e371a19 100644 --- a/test/build.test.js +++ b/test/build.test.js @@ -1,7 +1,7 @@ import { execSync } from "node:child_process"; import { readFileSync, readdirSync } from "node:fs"; import { resolve } from "node:path"; -import { beforeAll, describe, expect, test } from "vitest"; +import { beforeAll, beforeEach, describe, expect, test } from "vitest"; const distDir = resolve(import.meta.dirname, "../dist"); @@ -13,22 +13,33 @@ describe("production build", () => { }); }); - test("outputs index.html", () => { + beforeEach(() => { const html = readFileSync(resolve(distDir, "index.html"), "utf-8"); - expect(html.toLowerCase()).toContain(""); + document.documentElement.innerHTML = html; + }); + + test("outputs index.html", () => { + expect(document.querySelector("html")).not.toBeNull(); + expect(document.querySelector("head")).not.toBeNull(); + expect(document.querySelector("body")).not.toBeNull(); }); test("html contains expected page structure", () => { - const html = readFileSync(resolve(distDir, "index.html"), "utf-8"); - expect(html).toContain("Tailwind CSS Template"); - expect(html).toMatch(/Tailwind CSS Template\s*<\/h1>/); - expect(html).toContain("plain-old-HTML template using Tailwind CSS"); + expect(document.querySelector("title").textContent).toBe( + "Tailwind CSS Template" + ); + expect(document.querySelector("h1").textContent).toContain( + "Tailwind CSS Template" + ); + expect(document.body.textContent).toContain( + "plain-old-HTML template using Tailwind CSS" + ); }); test("html links to a compiled css file", () => { - const html = readFileSync(resolve(distDir, "index.html"), "utf-8"); - const cssLinkMatch = html.match(/href="\.\/assets\/[^"]+\.css"/); - expect(cssLinkMatch).not.toBeNull(); + const link = document.querySelector('link[rel="stylesheet"]'); + expect(link).not.toBeNull(); + expect(link.getAttribute("href")).toMatch(/\.\/assets\/.*\.css$/); }); test("compiled css contains tailwind utility styles", () => { @@ -37,7 +48,6 @@ describe("production build", () => { expect(cssFile).toBeDefined(); const css = readFileSync(resolve(distDir, "assets", cssFile), "utf-8"); - // Tailwind should have compiled these utilities used in index.html expect(css).toContain("font-bold"); expect(css).toContain("text-center"); expect(css.length).toBeGreaterThan(100); diff --git a/vite.config.js b/vite.config.js index 0b01a3f..839654b 100644 --- a/vite.config.js +++ b/vite.config.js @@ -6,6 +6,15 @@ export default { emptyOutDir: true }, test: { - root: "." + root: ".", + environment: "happy-dom", + environmentOptions: { + happyDOM: { + settings: { + disableCSSFileLoading: true, + handleDisabledFileLoadingAsSuccess: true + } + } + } } };