diff --git a/apps/desktop/package.json b/apps/desktop/package.json
index 3e32c06fb03..645d35a54c2 100644
--- a/apps/desktop/package.json
+++ b/apps/desktop/package.json
@@ -9,6 +9,7 @@
"localdev": "dotenv -e ../../.env -- vinxi dev --port 3002",
"build": "vinxi build",
"tauri": "tauri",
+ "test": "vitest run",
"test:memory": "node scripts/desktop-memory-soak.js",
"test:memory:unit": "vitest run scripts/desktop-memory-soak.test.js"
},
diff --git a/apps/storybook/package.json b/apps/storybook/package.json
index 4ea3d148eef..89dada1d62e 100644
--- a/apps/storybook/package.json
+++ b/apps/storybook/package.json
@@ -4,7 +4,8 @@
"type": "module",
"scripts": {
"dev:storybook": "storybook dev -p 6006",
- "build:storybook": "storybook build"
+ "build:storybook": "storybook build",
+ "test": "vitest run"
},
"dependencies": {
"@cap/ui-solid": "workspace:*",
@@ -25,6 +26,7 @@
"storybook-solidjs-vite": "^1.0.0-beta.2",
"typescript": "^5.8.3",
"vite": "^6.3.5",
- "vite-plugin-solid": "^2.10.2"
+ "vite-plugin-solid": "^2.10.2",
+ "vitest": "~2.1.9"
}
}
diff --git a/apps/storybook/storybook-vite.test.ts b/apps/storybook/storybook-vite.test.ts
new file mode 100644
index 00000000000..8c50f7ee338
--- /dev/null
+++ b/apps/storybook/storybook-vite.test.ts
@@ -0,0 +1,9 @@
+import { describe, expect, it } from "vitest";
+import config from "./vite.config";
+
+describe("storybook Vite config", () => {
+ it("loads Solid and Cap UI plugins", () => {
+ expect(Array.isArray(config.plugins)).toBe(true);
+ expect(config.plugins).toHaveLength(2);
+ });
+});
diff --git a/apps/storybook/vitest.config.ts b/apps/storybook/vitest.config.ts
new file mode 100644
index 00000000000..ce63ad84542
--- /dev/null
+++ b/apps/storybook/vitest.config.ts
@@ -0,0 +1,8 @@
+import { defineConfig } from "vitest/config";
+
+export default defineConfig({
+ test: {
+ environment: "node",
+ include: ["**/*.test.ts"],
+ },
+});
diff --git a/apps/web-cluster/package.json b/apps/web-cluster/package.json
index 5c30d80a6ce..2e586a6d162 100644
--- a/apps/web-cluster/package.json
+++ b/apps/web-cluster/package.json
@@ -4,7 +4,8 @@
"scripts": {
"dev_": "pnpm dotenv -e ../../.env -- concurrently \"deno run --allow-all --watch ./src/runner/index.ts\" \"deno run --allow-all --watch ./src/shard-manager.ts\"",
"build": "pnpm run --filter @cap/web-cluster^... build",
- "build:docker": "cd ../.. && docker build -f apps/web-cluster/Dockerfile -t ghcr.io/brendonovich/cap-web-cluster:latest ."
+ "build:docker": "cd ../.. && docker build -f apps/web-cluster/Dockerfile -t ghcr.io/brendonovich/cap-web-cluster:latest .",
+ "test": "vitest run"
},
"dependencies": {
"@cap/web-backend": "workspace:*",
@@ -26,6 +27,7 @@
},
"devDependencies": {
"concurrently": "^9.2.1",
- "dotenv-cli": "^10.0.0"
+ "dotenv-cli": "^10.0.0",
+ "vitest": "~2.1.9"
}
}
diff --git a/apps/web-cluster/src/cluster/container-metadata.test.ts b/apps/web-cluster/src/cluster/container-metadata.test.ts
new file mode 100644
index 00000000000..e7181d8dd0d
--- /dev/null
+++ b/apps/web-cluster/src/cluster/container-metadata.test.ts
@@ -0,0 +1,47 @@
+import { Effect } from "effect";
+import { afterEach, describe, expect, it, vi } from "vitest";
+import { ContainerMetadata } from "./container-metadata";
+
+const originalEnv = { ...process.env };
+
+afterEach(() => {
+ process.env = { ...originalEnv };
+ vi.unstubAllGlobals();
+});
+
+function getContainerMetadata() {
+ return Effect.gen(function* () {
+ return yield* ContainerMetadata;
+ }).pipe(Effect.provide(ContainerMetadata.Default));
+}
+
+describe("ContainerMetadata", () => {
+ it("uses local defaults when ECS metadata is unavailable", async () => {
+ delete process.env.ECS_CONTAINER_METADATA_URI_V4;
+ delete process.env.PORT;
+
+ const metadata = await Effect.runPromise(getContainerMetadata());
+
+ expect(metadata).toEqual({ ipAddress: "0.0.0.0", port: 42069 });
+ });
+
+ it("reads container IP and port from the runtime environment", async () => {
+ process.env.ECS_CONTAINER_METADATA_URI_V4 = "http://metadata.local";
+ process.env.PORT = "5173";
+ const fetchMock = vi.fn(async () => ({
+ json: async () => ({
+ Containers: [
+ {
+ Networks: [{ IPv4Addresses: ["10.0.0.42"] }],
+ },
+ ],
+ }),
+ }));
+ vi.stubGlobal("fetch", fetchMock);
+
+ const metadata = await Effect.runPromise(getContainerMetadata());
+
+ expect(fetchMock).toHaveBeenCalledWith("http://metadata.local/task");
+ expect(metadata).toEqual({ ipAddress: "10.0.0.42", port: 5173 });
+ });
+});
diff --git a/apps/web-cluster/vitest.config.ts b/apps/web-cluster/vitest.config.ts
new file mode 100644
index 00000000000..cfbd4c3fcac
--- /dev/null
+++ b/apps/web-cluster/vitest.config.ts
@@ -0,0 +1,8 @@
+import { defineConfig } from "vitest/config";
+
+export default defineConfig({
+ test: {
+ environment: "node",
+ include: ["src/**/*.test.ts"],
+ },
+});
diff --git a/packages/config/package.json b/packages/config/package.json
index e05ced6a042..d0e7721690b 100644
--- a/packages/config/package.json
+++ b/packages/config/package.json
@@ -5,6 +5,9 @@
"./*": "./*",
"./vite": "./vite"
},
+ "scripts": {
+ "test": "vitest run"
+ },
"devDependencies": {
"@types/node": "^20.4.5",
"@typescript-eslint/eslint-plugin": "^5.59.6",
@@ -17,7 +20,8 @@
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-tailwindcss": "^3.12.0",
- "eslint-utils": "^3.0.0"
+ "eslint-utils": "^3.0.0",
+ "vitest": "~2.1.9"
},
"dependencies": {
"@vitejs/plugin-react": "^4.0.3",
diff --git a/packages/config/vite/relativeAliasResolver.test.ts b/packages/config/vite/relativeAliasResolver.test.ts
new file mode 100644
index 00000000000..fe68dd0adff
--- /dev/null
+++ b/packages/config/vite/relativeAliasResolver.test.ts
@@ -0,0 +1,32 @@
+import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { afterEach, describe, expect, it } from "vitest";
+import resolver from "./relativeAliasResolver";
+
+let testRoot: string | undefined;
+
+afterEach(async () => {
+ if (testRoot) await rm(testRoot, { recursive: true, force: true });
+ testRoot = undefined;
+});
+
+describe("relativeAliasResolver", () => {
+ it("resolves ~/ imports relative to a package src directory", async () => {
+ testRoot = await mkdtemp(join(tmpdir(), "cap-alias-"));
+ await mkdir(join(testRoot, "src", "nested"), { recursive: true });
+ await writeFile(join(testRoot, "package.json"), "{}");
+ await writeFile(join(testRoot, "src", "nested", "target.ts"), "");
+
+ const customResolver = resolver.customResolver;
+ if (!customResolver) throw new Error("customResolver is not configured");
+
+ const resolved = await customResolver(
+ "~/nested/target",
+ join(testRoot, "src", "importer.ts"),
+ {},
+ );
+
+ expect(resolved).toBe(join(testRoot, "src", "nested", "target.ts"));
+ });
+});
diff --git a/packages/config/vitest.config.ts b/packages/config/vitest.config.ts
new file mode 100644
index 00000000000..ce63ad84542
--- /dev/null
+++ b/packages/config/vitest.config.ts
@@ -0,0 +1,8 @@
+import { defineConfig } from "vitest/config";
+
+export default defineConfig({
+ test: {
+ environment: "node",
+ include: ["**/*.test.ts"],
+ },
+});
diff --git a/packages/database/crypto.test.ts b/packages/database/crypto.test.ts
new file mode 100644
index 00000000000..b9785e5bbe7
--- /dev/null
+++ b/packages/database/crypto.test.ts
@@ -0,0 +1,21 @@
+import { describe, expect, it } from "vitest";
+import { hashPassword, verifyPassword } from "./crypto";
+
+describe("password hashing", () => {
+ it("verifies matching passwords and rejects mismatches", async () => {
+ const hash = await hashPassword("correct horse battery staple");
+
+ await expect(
+ verifyPassword(hash, "correct horse battery staple"),
+ ).resolves.toBe(true);
+ await expect(verifyPassword(hash, "wrong password")).resolves.toBe(false);
+ });
+
+ it("rejects empty stored hashes or password input", async () => {
+ await expect(verifyPassword("", "password")).resolves.toBe(false);
+ await expect(verifyPassword("stored", "")).resolves.toBe(false);
+ await expect(hashPassword("")).rejects.toThrow(
+ "Cannot hash empty or null password",
+ );
+ });
+});
diff --git a/packages/database/package.json b/packages/database/package.json
index a221a469a38..9f8f2ef9880 100644
--- a/packages/database/package.json
+++ b/packages/database/package.json
@@ -13,7 +13,8 @@
"db:studio": "drizzle-kit studio --config=drizzle.config.ts",
"db:check-integrity": "node scripts/check-migration-integrity.js",
"drizzle-kit": "pnpm dotenv -e ../../.env drizzle-kit --config=drizzle.config.ts",
- "build": "tsdown"
+ "build": "tsdown",
+ "test": "vitest run"
},
"dependencies": {
"@cap/env": "workspace:*",
@@ -50,7 +51,8 @@
"react-dom": "^19.1.1",
"react-router-dom": "^6.18.0",
"tsconfig": "workspace:*",
- "typescript": "^5.8.3"
+ "typescript": "^5.8.3",
+ "vitest": "~2.1.9"
},
"engines": {
"node": ">=20"
diff --git a/packages/database/vitest.config.ts b/packages/database/vitest.config.ts
new file mode 100644
index 00000000000..ce63ad84542
--- /dev/null
+++ b/packages/database/vitest.config.ts
@@ -0,0 +1,8 @@
+import { defineConfig } from "vitest/config";
+
+export default defineConfig({
+ test: {
+ environment: "node",
+ include: ["**/*.test.ts"],
+ },
+});
diff --git a/packages/env/build.test.ts b/packages/env/build.test.ts
new file mode 100644
index 00000000000..13433eb6cd0
--- /dev/null
+++ b/packages/env/build.test.ts
@@ -0,0 +1,32 @@
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+
+const originalEnv = { ...process.env };
+
+beforeEach(() => {
+ process.env = { ...originalEnv };
+ vi.resetModules();
+});
+
+afterEach(() => {
+ process.env = { ...originalEnv };
+});
+
+describe("buildEnv", () => {
+ it("uses WEB_URL as the public web URL when present", async () => {
+ process.env.WEB_URL = "https://web-url.example";
+ process.env.NEXT_PUBLIC_WEB_URL = "https://next-public.example";
+
+ const { buildEnv } = await import("./build");
+
+ expect(buildEnv.NEXT_PUBLIC_WEB_URL).toBe("https://web-url.example");
+ });
+
+ it("falls back to NEXT_PUBLIC_WEB_URL when WEB_URL is absent", async () => {
+ delete process.env.WEB_URL;
+ process.env.NEXT_PUBLIC_WEB_URL = "https://next-public.example";
+
+ const { buildEnv } = await import("./build");
+
+ expect(buildEnv.NEXT_PUBLIC_WEB_URL).toBe("https://next-public.example");
+ });
+});
diff --git a/packages/env/package.json b/packages/env/package.json
index 926c67f70b7..635593920e8 100644
--- a/packages/env/package.json
+++ b/packages/env/package.json
@@ -5,7 +5,8 @@
"main": "./index.ts",
"types": "./index.ts",
"scripts": {
- "build": "tsdown"
+ "build": "tsdown",
+ "test": "vitest run"
},
"publishConfig": {
"main": "./dist/index.js"
@@ -15,6 +16,7 @@
"zod": "^3.25.76"
},
"devDependencies": {
- "@types/node": "^22.15.14"
+ "@types/node": "^22.15.14",
+ "vitest": "~2.1.9"
}
}
diff --git a/packages/env/vitest.config.ts b/packages/env/vitest.config.ts
new file mode 100644
index 00000000000..ce63ad84542
--- /dev/null
+++ b/packages/env/vitest.config.ts
@@ -0,0 +1,8 @@
+import { defineConfig } from "vitest/config";
+
+export default defineConfig({
+ test: {
+ environment: "node",
+ include: ["**/*.test.ts"],
+ },
+});
diff --git a/packages/sdk-embed/package.json b/packages/sdk-embed/package.json
index 0c732bce52e..e694855cbe7 100644
--- a/packages/sdk-embed/package.json
+++ b/packages/sdk-embed/package.json
@@ -24,6 +24,7 @@
],
"scripts": {
"build": "tsup",
+ "test": "vitest run",
"typecheck": "tsc --noEmit"
},
"dependencies": {},
@@ -39,6 +40,7 @@
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"tsup": "^8.0.0",
- "typescript": "^5.7.3"
+ "typescript": "^5.7.3",
+ "vitest": "~2.1.9"
}
}
diff --git a/packages/sdk-embed/src/vanilla/cap-embed.test.ts b/packages/sdk-embed/src/vanilla/cap-embed.test.ts
new file mode 100644
index 00000000000..b4fc33bee52
--- /dev/null
+++ b/packages/sdk-embed/src/vanilla/cap-embed.test.ts
@@ -0,0 +1,30 @@
+import { describe, expect, it } from "vitest";
+import { createEmbedUrl } from "./cap-embed";
+
+describe("createEmbedUrl", () => {
+ it("builds the default Cap embed URL", () => {
+ const url = createEmbedUrl({
+ videoId: "video_123",
+ publicKey: "pk_test",
+ });
+
+ expect(url).toBe("https://cap.so/embed/video_123?sdk=1&pk=pk_test");
+ });
+
+ it("includes optional base, playback, and branding parameters", () => {
+ const url = createEmbedUrl({
+ videoId: "video_123",
+ publicKey: "pk_test",
+ apiBase: "https://app.example",
+ autoplay: true,
+ branding: {
+ logoUrl: "https://cdn.example/logo.png",
+ accentColor: "#abcdef",
+ },
+ });
+
+ expect(url).toBe(
+ "https://app.example/embed/video_123?sdk=1&pk=pk_test&autoplay=1&logo=https%3A%2F%2Fcdn.example%2Flogo.png&accent=%23abcdef",
+ );
+ });
+});
diff --git a/packages/sdk-embed/vitest.config.ts b/packages/sdk-embed/vitest.config.ts
new file mode 100644
index 00000000000..cfbd4c3fcac
--- /dev/null
+++ b/packages/sdk-embed/vitest.config.ts
@@ -0,0 +1,8 @@
+import { defineConfig } from "vitest/config";
+
+export default defineConfig({
+ test: {
+ environment: "node",
+ include: ["src/**/*.test.ts"],
+ },
+});
diff --git a/packages/sdk-recorder/package.json b/packages/sdk-recorder/package.json
index 7fef2785793..dcd16b9acad 100644
--- a/packages/sdk-recorder/package.json
+++ b/packages/sdk-recorder/package.json
@@ -20,6 +20,7 @@
],
"scripts": {
"build": "tsup",
+ "test": "vitest run",
"typecheck": "tsc --noEmit"
},
"dependencies": {},
@@ -35,6 +36,7 @@
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"tsup": "^8.0.0",
- "typescript": "^5.7.3"
+ "typescript": "^5.7.3",
+ "vitest": "~2.1.9"
}
}
diff --git a/packages/sdk-recorder/src/core/mime-types.test.ts b/packages/sdk-recorder/src/core/mime-types.test.ts
new file mode 100644
index 00000000000..b92649d84fe
--- /dev/null
+++ b/packages/sdk-recorder/src/core/mime-types.test.ts
@@ -0,0 +1,24 @@
+import { afterEach, describe, expect, it, vi } from "vitest";
+import { getSupportedMimeType } from "./mime-types";
+
+afterEach(() => {
+ vi.unstubAllGlobals();
+});
+
+describe("getSupportedMimeType", () => {
+ it("selects the first supported MIME type by priority", () => {
+ vi.stubGlobal("MediaRecorder", {
+ isTypeSupported: vi.fn((mimeType: string) => mimeType.includes("vp8")),
+ });
+
+ expect(getSupportedMimeType()).toBe("video/webm;codecs=vp8,opus");
+ });
+
+ it("returns an empty string when no preferred type is supported", () => {
+ vi.stubGlobal("MediaRecorder", {
+ isTypeSupported: vi.fn(() => false),
+ });
+
+ expect(getSupportedMimeType()).toBe("");
+ });
+});
diff --git a/packages/sdk-recorder/vitest.config.ts b/packages/sdk-recorder/vitest.config.ts
new file mode 100644
index 00000000000..cfbd4c3fcac
--- /dev/null
+++ b/packages/sdk-recorder/vitest.config.ts
@@ -0,0 +1,8 @@
+import { defineConfig } from "vitest/config";
+
+export default defineConfig({
+ test: {
+ environment: "node",
+ include: ["src/**/*.test.ts"],
+ },
+});
diff --git a/packages/tsconfig/base.test.ts b/packages/tsconfig/base.test.ts
new file mode 100644
index 00000000000..80509e2d1e3
--- /dev/null
+++ b/packages/tsconfig/base.test.ts
@@ -0,0 +1,18 @@
+import { describe, expect, it } from "vitest";
+import base from "./base.json";
+import nextjs from "./nextjs.json";
+import reactLibrary from "./react-library.json";
+
+describe("shared TypeScript configs", () => {
+ it("keeps strictness enabled in the base config", () => {
+ expect(base.compilerOptions.strict).toBe(true);
+ expect(base.compilerOptions.forceConsistentCasingInFileNames).toBe(true);
+ });
+
+ it("extends the base config for React and Next.js presets", () => {
+ expect(reactLibrary.extends).toBe("./base.json");
+ expect(reactLibrary.compilerOptions.jsx).toBe("react-jsx");
+ expect(nextjs.extends).toBe("./base.json");
+ expect(nextjs.compilerOptions.jsx).toBe("preserve");
+ });
+});
diff --git a/packages/tsconfig/package.json b/packages/tsconfig/package.json
index 92ef9e4c59b..fc048323746 100644
--- a/packages/tsconfig/package.json
+++ b/packages/tsconfig/package.json
@@ -3,6 +3,12 @@
"version": "0.0.0",
"private": true,
"license": "MIT",
+ "scripts": {
+ "test": "vitest run"
+ },
+ "devDependencies": {
+ "vitest": "~2.1.9"
+ },
"publishConfig": {
"access": "public"
}
diff --git a/packages/tsconfig/vitest.config.ts b/packages/tsconfig/vitest.config.ts
new file mode 100644
index 00000000000..5aee5c5b672
--- /dev/null
+++ b/packages/tsconfig/vitest.config.ts
@@ -0,0 +1,8 @@
+import { defineConfig } from "vitest/config";
+
+export default defineConfig({
+ test: {
+ environment: "node",
+ include: ["*.test.ts"],
+ },
+});
diff --git a/packages/ui-solid/package.json b/packages/ui-solid/package.json
index c8dbd63d5a1..5998a98e961 100644
--- a/packages/ui-solid/package.json
+++ b/packages/ui-solid/package.json
@@ -10,6 +10,9 @@
"./vite": "./vite.js",
"./types": "./src/types.d.ts"
},
+ "scripts": {
+ "test": "vitest run"
+ },
"dependencies": {
"@kobalte/core": "^0.13.7",
"cva": "npm:class-variance-authority@^0.7.0",
@@ -30,7 +33,10 @@
"tailwindcss-animate": "^1.0.6",
"unplugin-auto-import": "^0.18.2",
"unplugin-fonts": "^1.1.1",
- "unplugin-icons": "^0.19.2"
+ "unplugin-icons": "^0.19.2",
+ "vite": "^6.3.5",
+ "vite-plugin-solid": "^2.10.2",
+ "vitest": "~2.1.9"
},
"peerDependencies": {
"@fontsource/geist-sans": "^5.0.3"
diff --git a/packages/ui-solid/src/ProgressCircle.test.tsx b/packages/ui-solid/src/ProgressCircle.test.tsx
new file mode 100644
index 00000000000..e7ecb2a1d9e
--- /dev/null
+++ b/packages/ui-solid/src/ProgressCircle.test.tsx
@@ -0,0 +1,32 @@
+import { renderToString } from "solid-js/web";
+import { describe, expect, it } from "vitest";
+import { ProgressCircle } from "./ProgressCircle";
+
+describe("ProgressCircle", () => {
+ it("renders accessible progress values", () => {
+ const markup = renderToString(() => (
+
+ ));
+
+ expect(markup).toContain('role="progressbar"');
+ expect(markup).toContain('aria-valuenow="42"');
+ expect(markup).toContain('aria-valuemin="0"');
+ expect(markup).toContain('aria-valuemax="100"');
+ expect(markup).toContain("stroke-blue-10");
+ expect(markup).toContain("stroke-blue-5");
+ });
+
+ it("clamps progress before rendering aria state and stroke offset", () => {
+ const overMaxMarkup = renderToString(() => (
+
+ ));
+ const belowMinMarkup = renderToString(() => (
+
+ ));
+
+ expect(overMaxMarkup).toContain('aria-valuenow="100"');
+ expect(overMaxMarkup).toContain('stroke-dashoffset="0"');
+ expect(belowMinMarkup).toContain('aria-valuenow="0"');
+ expect(belowMinMarkup).toContain('stroke-dashoffset="37.69911184307752"');
+ });
+});
diff --git a/packages/ui-solid/src/jest-dom.setup.ts b/packages/ui-solid/src/jest-dom.setup.ts
new file mode 100644
index 00000000000..cb0ff5c3b54
--- /dev/null
+++ b/packages/ui-solid/src/jest-dom.setup.ts
@@ -0,0 +1 @@
+export {};
diff --git a/packages/ui-solid/vitest.config.ts b/packages/ui-solid/vitest.config.ts
new file mode 100644
index 00000000000..e1d45e76998
--- /dev/null
+++ b/packages/ui-solid/vitest.config.ts
@@ -0,0 +1,11 @@
+import solid from "vite-plugin-solid";
+import { defineConfig } from "vitest/config";
+
+export default defineConfig({
+ plugins: [solid({ ssr: true })],
+ test: {
+ environment: "node",
+ include: ["src/**/*.test.{ts,tsx}"],
+ setupFiles: ["./src/jest-dom.setup.ts"],
+ },
+});
diff --git a/packages/ui/package.json b/packages/ui/package.json
index b76a0f4b1e8..4bcf18973ce 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -10,6 +10,7 @@
"./style": "./style/styles.css"
},
"scripts": {
+ "test": "vitest run",
"typecheck": "tsc -b"
},
"devDependencies": {
@@ -32,7 +33,8 @@
"tsconfig": "workspace:*",
"typescript": "^5.8.3",
"vite": "^6.3.5",
- "vite-tsconfig-paths": "^4.2.1"
+ "vite-tsconfig-paths": "^4.2.1",
+ "vitest": "~2.1.9"
},
"dependencies": {
"@cap/utils": "workspace:*",
diff --git a/packages/ui/src/components/Button.test.tsx b/packages/ui/src/components/Button.test.tsx
new file mode 100644
index 00000000000..572a1c63fc9
--- /dev/null
+++ b/packages/ui/src/components/Button.test.tsx
@@ -0,0 +1,31 @@
+import { renderToStaticMarkup } from "react-dom/server";
+import { describe, expect, it } from "vitest";
+import { Button } from "./Button";
+
+describe("Button", () => {
+ it("renders default button attributes and content", () => {
+ const markup = renderToStaticMarkup();
+
+ expect(markup).toContain("