mirror of
https://github.com/rajnandan1/kener.git
synced 2026-08-07 07:14:56 +00:00
test: add Vitest browser-mode client project with CopyButton test
This commit is contained in:
Generated
+169
-1
@@ -103,6 +103,7 @@
|
|||||||
"@types/mustache": "^4.2.6",
|
"@types/mustache": "^4.2.6",
|
||||||
"@types/node": "^25.0.3",
|
"@types/node": "^25.0.3",
|
||||||
"@types/nodemailer": "^7.0.4",
|
"@types/nodemailer": "^7.0.4",
|
||||||
|
"@vitest/browser-playwright": "^4.1.10",
|
||||||
"autoprefixer": "^10.4.22",
|
"autoprefixer": "^10.4.22",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"concurrently": "^9.2.1",
|
"concurrently": "^9.2.1",
|
||||||
@@ -127,7 +128,8 @@
|
|||||||
"vite": "^7.2.2",
|
"vite": "^7.2.2",
|
||||||
"vite-node": "^5.3.0",
|
"vite-node": "^5.3.0",
|
||||||
"vite-plugin-devtools-json": "^1.0.0",
|
"vite-plugin-devtools-json": "^1.0.0",
|
||||||
"vitest": "^4.1.10"
|
"vitest": "^4.1.10",
|
||||||
|
"vitest-browser-svelte": "^3.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.0.0"
|
"node": ">=20.0.0"
|
||||||
@@ -158,6 +160,13 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@blazediff/core": {
|
||||||
|
"version": "1.9.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@blazediff/core/-/core-1.9.1.tgz",
|
||||||
|
"integrity": "sha512-ehg3jIkYKulZh+8om/O25vkvSsXXwC+skXmyA87FFx6A/45eqOkZsBltMw/TVteb0mloiGT8oGRTcjRAz66zaA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@codemirror/autocomplete": {
|
"node_modules/@codemirror/autocomplete": {
|
||||||
"version": "6.20.0",
|
"version": "6.20.0",
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.0.tgz",
|
||||||
@@ -2646,6 +2655,19 @@
|
|||||||
"vite": "^5.2.0 || ^6 || ^7"
|
"vite": "^5.2.0 || ^6 || ^7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@testing-library/svelte-core": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@testing-library/svelte-core/-/svelte-core-1.1.3.tgz",
|
||||||
|
"integrity": "sha512-KkMAvXeWorxN2Yn0kdC1lfoAItxpoj4uOWzxK5leDrNxonLvS5nwBFvztrroyTszQ0Wf/EU6iLT8JhY5qcn22g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"svelte": "^3 || ^4 || ^5 || ^5.0.0-next.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/bcrypt": {
|
"node_modules/@types/bcrypt": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-6.0.0.tgz",
|
||||||
@@ -2919,6 +2941,63 @@
|
|||||||
"@codemirror/view": ">=6.0.0"
|
"@codemirror/view": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@vitest/browser": {
|
||||||
|
"version": "4.1.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.1.10.tgz",
|
||||||
|
"integrity": "sha512-UDwuWGwXj646CBx/bQHOaJSX7np0I8JL/UKQYa1e4QrVHH8VdWtx8eaOuf8sy0ShwDgR6NjJAsp5eF6vjF6qng==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@blazediff/core": "1.9.1",
|
||||||
|
"@vitest/mocker": "4.1.10",
|
||||||
|
"@vitest/utils": "4.1.10",
|
||||||
|
"magic-string": "^0.30.21",
|
||||||
|
"pngjs": "^7.0.0",
|
||||||
|
"sirv": "^3.0.2",
|
||||||
|
"tinyrainbow": "^3.1.0",
|
||||||
|
"ws": "^8.19.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vitest": "4.1.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vitest/browser-playwright": {
|
||||||
|
"version": "4.1.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.1.10.tgz",
|
||||||
|
"integrity": "sha512-nMoXGEiRpT7m3W7NsbvrM2aKNwiNHZf+zEpUCvMteGjZFvfT96Q9fh7QyB98dvDWXiKvrLxA7bJ1mCOOv+JQPw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vitest/browser": "4.1.10",
|
||||||
|
"@vitest/mocker": "4.1.10",
|
||||||
|
"tinyrainbow": "^3.1.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"playwright": "*",
|
||||||
|
"vitest": "4.1.10"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"playwright": {
|
||||||
|
"optional": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vitest/browser/node_modules/pngjs": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.19.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@vitest/expect": {
|
"node_modules/@vitest/expect": {
|
||||||
"version": "4.1.10",
|
"version": "4.1.10",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz",
|
||||||
@@ -8470,6 +8549,56 @@
|
|||||||
"node": ">=22.0.0"
|
"node": ">=22.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/playwright": {
|
||||||
|
"version": "1.61.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz",
|
||||||
|
"integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"playwright-core": "1.61.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"playwright": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"fsevents": "2.3.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/playwright-core": {
|
||||||
|
"version": "1.61.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz",
|
||||||
|
"integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
|
"bin": {
|
||||||
|
"playwright-core": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/playwright/node_modules/fsevents": {
|
||||||
|
"version": "2.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||||
|
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/pngjs": {
|
"node_modules/pngjs": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz",
|
||||||
@@ -11046,6 +11175,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vitest-browser-svelte": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vitest-browser-svelte/-/vitest-browser-svelte-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-BKM9iBGNsEQzigWuSZFAiw6AJDqQqMttApoD8gM8LTI6vxfd423rDMN8GqUHJyGbhH3XcOo9N9u/4hu47Nxhsw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@testing-library/svelte-core": "^1.1.3"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"svelte": "^3 || ^4 || ^5 || ^5.0.0-next.0",
|
||||||
|
"vitest": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/w3c-keyname": {
|
"node_modules/w3c-keyname": {
|
||||||
"version": "2.2.8",
|
"version": "2.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
||||||
@@ -11221,6 +11367,28 @@
|
|||||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/ws": {
|
||||||
|
"version": "8.21.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz",
|
||||||
|
"integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==",
|
||||||
|
"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/xmlbuilder": {
|
"node_modules/xmlbuilder": {
|
||||||
"version": "8.2.2",
|
"version": "8.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz",
|
||||||
|
|||||||
+4
-1
@@ -50,6 +50,7 @@
|
|||||||
"seed": "npx knex seed:run",
|
"seed": "npx knex seed:run",
|
||||||
"start": "node build/main.js",
|
"start": "node build/main.js",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
|
"test:client": "vitest run --project=client",
|
||||||
"test:server": "vitest run --project=server",
|
"test:server": "vitest run --project=server",
|
||||||
"test:watch": "vitest"
|
"test:watch": "vitest"
|
||||||
},
|
},
|
||||||
@@ -72,6 +73,7 @@
|
|||||||
"@types/mustache": "^4.2.6",
|
"@types/mustache": "^4.2.6",
|
||||||
"@types/node": "^25.0.3",
|
"@types/node": "^25.0.3",
|
||||||
"@types/nodemailer": "^7.0.4",
|
"@types/nodemailer": "^7.0.4",
|
||||||
|
"@vitest/browser-playwright": "^4.1.10",
|
||||||
"autoprefixer": "^10.4.22",
|
"autoprefixer": "^10.4.22",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"concurrently": "^9.2.1",
|
"concurrently": "^9.2.1",
|
||||||
@@ -96,7 +98,8 @@
|
|||||||
"vite": "^7.2.2",
|
"vite": "^7.2.2",
|
||||||
"vite-node": "^5.3.0",
|
"vite-node": "^5.3.0",
|
||||||
"vite-plugin-devtools-json": "^1.0.0",
|
"vite-plugin-devtools-json": "^1.0.0",
|
||||||
"vitest": "^4.1.10"
|
"vitest": "^4.1.10",
|
||||||
|
"vitest-browser-svelte": "^3.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.0.0"
|
"node": ">=20.0.0"
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import { render } from "vitest-browser-svelte";
|
||||||
|
import CopyButton from "./CopyButton.svelte";
|
||||||
|
|
||||||
|
// The clipboard is spied on rather than read back: headless Chromium does not
|
||||||
|
// grant clipboard-read permission, and the component's contract is "calls
|
||||||
|
// navigator.clipboard.writeText and flips to the copied state".
|
||||||
|
describe("CopyButton", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("copies the text prop and shows the copied state on click", async () => {
|
||||||
|
const writeText = vi.spyOn(navigator.clipboard, "writeText").mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
const screen = await render(CopyButton, { text: "copy-me" });
|
||||||
|
const copiedTooltip = screen.getByText("Copied");
|
||||||
|
|
||||||
|
// Hidden before the click (class-based visibility)
|
||||||
|
await expect.element(copiedTooltip).toHaveClass(/opacity-0/);
|
||||||
|
|
||||||
|
await screen.getByRole("button").click();
|
||||||
|
|
||||||
|
expect(writeText).toHaveBeenCalledExactlyOnceWith("copy-me");
|
||||||
|
await expect.element(copiedTooltip).toHaveClass(/scale-100/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("invokes the onclick callback", async () => {
|
||||||
|
vi.spyOn(navigator.clipboard, "writeText").mockResolvedValue(undefined);
|
||||||
|
const onclick = vi.fn();
|
||||||
|
|
||||||
|
const screen = await render(CopyButton, { text: "x", onclick });
|
||||||
|
await screen.getByRole("button").click();
|
||||||
|
|
||||||
|
expect(onclick).toHaveBeenCalledOnce();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not touch the clipboard when no text is provided", async () => {
|
||||||
|
const writeText = vi.spyOn(navigator.clipboard, "writeText").mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
const screen = await render(CopyButton, {});
|
||||||
|
await screen.getByRole("button").click();
|
||||||
|
|
||||||
|
expect(writeText).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -4,6 +4,7 @@ import { sveltekit } from "@sveltejs/kit/vite";
|
|||||||
import version from "vite-plugin-package-version";
|
import version from "vite-plugin-package-version";
|
||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
import devtoolsJson from "vite-plugin-devtools-json";
|
import devtoolsJson from "vite-plugin-devtools-json";
|
||||||
|
import { playwright } from "@vitest/browser-playwright";
|
||||||
|
|
||||||
import * as dotenv from "dotenv";
|
import * as dotenv from "dotenv";
|
||||||
|
|
||||||
@@ -68,6 +69,23 @@ export default defineConfig(({ mode }) => {
|
|||||||
exclude: ["src/**/*.svelte.{test,spec}.{js,ts}"],
|
exclude: ["src/**/*.svelte.{test,spec}.{js,ts}"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
extends: "./vite.config.ts",
|
||||||
|
test: {
|
||||||
|
name: "client",
|
||||||
|
browser: {
|
||||||
|
enabled: true,
|
||||||
|
headless: true,
|
||||||
|
provider: playwright(),
|
||||||
|
instances: [{ browser: "chromium" }],
|
||||||
|
// Vitest's default browser API port (63315) can fall inside Windows
|
||||||
|
// Hyper-V excluded TCP port ranges; pin below the ephemeral range.
|
||||||
|
api: { port: 5180 },
|
||||||
|
},
|
||||||
|
include: ["src/**/*.svelte.{test,spec}.{js,ts}"],
|
||||||
|
setupFiles: ["./vitest-setup-client.ts"],
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { vi } from "vitest";
|
||||||
|
|
||||||
|
// Shared stubs for SvelteKit runtime modules used by components under test.
|
||||||
|
// Values are minimal but realistic; individual tests can override with their
|
||||||
|
// own vi.mock(...) when they need different page data.
|
||||||
|
vi.mock("$app/state", () => ({
|
||||||
|
page: {
|
||||||
|
data: {
|
||||||
|
siteStatusColors: { UP: "#22c55e", DOWN: "#ef4444", DEGRADED: "#eab308", MAINTENANCE: "#3b82f6" },
|
||||||
|
dateAndTimeFormat: { dateOnly: "yyyy-MM-dd", timeOnly: "HH:mm" },
|
||||||
|
nowAtTz: 1768478400, // 2026-01-15T12:00:00Z
|
||||||
|
},
|
||||||
|
url: new URL("http://localhost/"),
|
||||||
|
params: {},
|
||||||
|
route: { id: null },
|
||||||
|
status: 200,
|
||||||
|
error: null,
|
||||||
|
form: null,
|
||||||
|
state: {},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("$app/paths", () => ({
|
||||||
|
base: "",
|
||||||
|
assets: "",
|
||||||
|
resolve: (path: string) => path,
|
||||||
|
}));
|
||||||
Reference in New Issue
Block a user