From d7bec165d2a2542f846c6837adb1bb960e2b5db6 Mon Sep 17 00:00:00 2001 From: NeurArk <197090867+NeurArk@users.noreply.github.com> Date: Thu, 14 May 2026 20:20:35 +0000 Subject: [PATCH 1/4] test: bootstrap monorepo Vitest coverage --- apps/desktop/package.json | 1 + packages/sdk-embed/package.json | 4 +- .../sdk-embed/src/vanilla/cap-embed.test.ts | 37 ++++++++++++ packages/sdk-recorder/package.json | 4 +- .../sdk-recorder/src/core/mime-types.test.ts | 25 +++++++++ packages/sdk-recorder/src/index.ts | 4 +- packages/utils/package.json | 4 +- packages/utils/src/helpers.test.ts | 56 +++++++++++++++++++ packages/web-domain/package.json | 4 +- packages/web-domain/src/Policy.test.ts | 28 ++++++++++ pnpm-lock.yaml | 12 ++++ 11 files changed, 174 insertions(+), 5 deletions(-) create mode 100644 packages/sdk-embed/src/vanilla/cap-embed.test.ts create mode 100644 packages/sdk-recorder/src/core/mime-types.test.ts create mode 100644 packages/utils/src/helpers.test.ts create mode 100644 packages/web-domain/src/Policy.test.ts diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 3e32c06fb03..590158a5ca3 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -7,6 +7,7 @@ "build:sidecar": "node ../../scripts/build-cap-muxer.mjs", "preparescript": "node scripts/prepare.js", "localdev": "dotenv -e ../../.env -- vinxi dev --port 3002", + "test": "vitest run", "build": "vinxi build", "tauri": "tauri", "test:memory": "node scripts/desktop-memory-soak.js", diff --git a/packages/sdk-embed/package.json b/packages/sdk-embed/package.json index 0c732bce52e..150f7992910 100644 --- a/packages/sdk-embed/package.json +++ b/packages/sdk-embed/package.json @@ -23,6 +23,7 @@ "dist" ], "scripts": { + "test": "vitest run", "build": "tsup", "typecheck": "tsc --noEmit" }, @@ -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..1aa1729c4be --- /dev/null +++ b/packages/sdk-embed/src/vanilla/cap-embed.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from "vitest"; +import { createEmbedUrl } from "./cap-embed"; + +describe("createEmbedUrl", () => { + it("builds a default Cap embed URL with SDK and public-key markers", () => { + const url = new URL( + createEmbedUrl({ videoId: "video-123", publicKey: "pk_test" }), + ); + + expect(url.origin).toBe("https://cap.so"); + expect(url.pathname).toBe("/embed/video-123"); + expect(url.searchParams.get("sdk")).toBe("1"); + expect(url.searchParams.get("pk")).toBe("pk_test"); + }); + + it("includes autoplay and branding options when provided", () => { + const url = new URL( + createEmbedUrl({ + videoId: "video-123", + publicKey: "pk_live", + apiBase: "https://app.example.com", + autoplay: true, + branding: { + logoUrl: "https://cdn.example.com/logo.png", + accentColor: "#ff00aa", + }, + }), + ); + + expect(url.origin).toBe("https://app.example.com"); + expect(url.searchParams.get("autoplay")).toBe("1"); + expect(url.searchParams.get("logo")).toBe( + "https://cdn.example.com/logo.png", + ); + expect(url.searchParams.get("accent")).toBe("#ff00aa"); + }); +}); diff --git a/packages/sdk-recorder/package.json b/packages/sdk-recorder/package.json index 7fef2785793..88bd60ba06b 100644 --- a/packages/sdk-recorder/package.json +++ b/packages/sdk-recorder/package.json @@ -19,6 +19,7 @@ "dist" ], "scripts": { + "test": "vitest run", "build": "tsup", "typecheck": "tsc --noEmit" }, @@ -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..d3a8385977e --- /dev/null +++ b/packages/sdk-recorder/src/core/mime-types.test.ts @@ -0,0 +1,25 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { getSupportedMimeType } from "./mime-types"; + +describe("getSupportedMimeType", () => { + afterEach(() => { + vi.unstubAllGlobals(); + }); + + it("returns the first supported preferred MIME type", () => { + vi.stubGlobal("MediaRecorder", { + isTypeSupported: (mimeType: string) => + mimeType === "video/webm;codecs=vp8,opus", + }); + + expect(getSupportedMimeType()).toBe("video/webm;codecs=vp8,opus"); + }); + + it("returns an empty string when the browser supports none of the preferred types", () => { + vi.stubGlobal("MediaRecorder", { + isTypeSupported: () => false, + }); + + expect(getSupportedMimeType()).toBe(""); + }); +}); diff --git a/packages/sdk-recorder/src/index.ts b/packages/sdk-recorder/src/index.ts index 0f7eb10e1fb..8017b277e45 100644 --- a/packages/sdk-recorder/src/index.ts +++ b/packages/sdk-recorder/src/index.ts @@ -84,7 +84,9 @@ export class CapRecorder { const set = this.listeners.get(event); if (set) set.add(handler as EventHandler); return () => { - this.listeners.get(event)?.delete(handler); + this.listeners + .get(event) + ?.delete(handler as EventHandler); }; } diff --git a/packages/utils/package.json b/packages/utils/package.json index 82f842d4c57..ee1e804d5b3 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -5,6 +5,7 @@ ".": "./src/index.ts" }, "scripts": { + "test": "vitest run", "typecheck": "tsc -b", "build": "tsdown" }, @@ -16,7 +17,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" }, "dependencies": { "@aws-sdk/client-s3": "^3.485.0", diff --git a/packages/utils/src/helpers.test.ts b/packages/utils/src/helpers.test.ts new file mode 100644 index 00000000000..a71fd564fa3 --- /dev/null +++ b/packages/utils/src/helpers.test.ts @@ -0,0 +1,56 @@ +import { describe, expect, it } from "vitest"; +import { + calculateStrokeDashoffset, + classNames, + getDisplayProgress, + getProgressCircleConfig, + isEmailAllowedByRestriction, + uuidFormat, + uuidParse, +} from "./helpers"; + +describe("helpers", () => { + it("merges conditional class names and resolves Tailwind conflicts", () => { + expect(classNames("p-2", false && "hidden", "p-4", ["text-sm"])).toBe( + "p-4 text-sm", + ); + }); + + it("round-trips UUIDs between dashed and compact forms", () => { + const dashed = "123e4567-e89b-12d3-a456-426614174000"; + const compact = "123e4567e89b12d3a456426614174000"; + + expect(uuidParse(dashed)).toBe(compact); + expect(uuidFormat(compact)).toBe(dashed); + }); + + it("keeps zero upload progress as the displayed progress", () => { + expect(getDisplayProgress(0, 75)).toBe(0); + expect(getDisplayProgress(undefined, 75)).toBe(75); + }); + + it("calculates circular progress stroke offsets", () => { + const { circumference } = getProgressCircleConfig(); + + expect(calculateStrokeDashoffset(0, circumference)).toBe(circumference); + expect(calculateStrokeDashoffset(100, circumference)).toBe(0); + }); + + it("allows exact emails and domain restrictions case-insensitively", () => { + expect( + isEmailAllowedByRestriction( + "Founders@Cap.so", + "team@example.com, cap.so", + ), + ).toBe(true); + expect( + isEmailAllowedByRestriction( + "team@example.com", + "team@example.com, cap.so", + ), + ).toBe(true); + expect( + isEmailAllowedByRestriction("team@other.com", "team@example.com, cap.so"), + ).toBe(false); + }); +}); diff --git a/packages/web-domain/package.json b/packages/web-domain/package.json index e96e70bc883..bc758a19d9d 100644 --- a/packages/web-domain/package.json +++ b/packages/web-domain/package.json @@ -8,6 +8,7 @@ "main": "./dist/index.js" }, "scripts": { + "test": "vitest run", "build": "tsdown", "generate-openapi": "node scripts/generate-openapi.ts" }, @@ -18,6 +19,7 @@ "effect": "^3.18.4" }, "devDependencies": { - "@effect/platform-node": "^0.98.3" + "@effect/platform-node": "^0.98.3", + "vitest": "~2.1.9" } } diff --git a/packages/web-domain/src/Policy.test.ts b/packages/web-domain/src/Policy.test.ts new file mode 100644 index 00000000000..fa1afc14d87 --- /dev/null +++ b/packages/web-domain/src/Policy.test.ts @@ -0,0 +1,28 @@ +import { Cause, Effect, Option } from "effect"; +import { describe, expect, it } from "vitest"; +import { publicPolicy, withPublicPolicy } from "./Policy"; + +describe("publicPolicy", () => { + it("allows an effect when the public predicate succeeds", async () => { + const result = await Effect.runPromise( + Effect.succeed("allowed").pipe( + withPublicPolicy( + publicPolicy((user) => Effect.succeed(Option.isNone(user))), + ), + ), + ); + + expect(result).toBe("allowed"); + }); + + it("fails with PolicyDeniedError when the public predicate denies access", async () => { + const exit = await Effect.runPromiseExit( + publicPolicy(() => Effect.succeed(false)), + ); + + expect(exit._tag).toBe("Failure"); + if (exit._tag === "Failure") { + expect(Cause.pretty(exit.cause)).toContain("PolicyDenied"); + } + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ce77b06b4f8..117e5bd3abc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1089,6 +1089,9 @@ importers: typescript: specifier: ^5.7.3 version: 5.8.3 + vitest: + specifier: ~2.1.9 + version: 2.1.9(@types/node@22.15.17)(jsdom@26.1.0)(terser@5.44.0) packages/sdk-recorder: dependencies: @@ -1108,6 +1111,9 @@ importers: typescript: specifier: ^5.7.3 version: 5.8.3 + vitest: + specifier: ~2.1.9 + version: 2.1.9(@types/node@22.15.17)(jsdom@26.1.0)(terser@5.44.0) packages/tsconfig: {} @@ -1323,6 +1329,9 @@ importers: typescript: specifier: ^5.8.3 version: 5.8.3 + vitest: + specifier: ~2.1.9 + version: 2.1.9(@types/node@22.15.17)(jsdom@26.1.0)(terser@5.44.0) packages/web-api-contract: dependencies: @@ -1420,6 +1429,9 @@ importers: '@effect/platform-node': specifier: ^0.98.3 version: 0.98.3(@effect/cluster@0.50.4(@effect/platform@0.92.1(effect@3.18.4))(@effect/rpc@0.71.0(@effect/platform@0.92.1(effect@3.18.4))(effect@3.18.4))(@effect/sql@0.44.2(@effect/experimental@0.56.0(@effect/platform@0.92.1(effect@3.18.4))(effect@3.18.4)(ioredis@5.6.1))(@effect/platform@0.92.1(effect@3.18.4))(effect@3.18.4))(@effect/workflow@0.11.3(@effect/platform@0.92.1(effect@3.18.4))(@effect/rpc@0.71.0(@effect/platform@0.92.1(effect@3.18.4))(effect@3.18.4))(effect@3.18.4))(effect@3.18.4))(@effect/platform@0.92.1(effect@3.18.4))(@effect/rpc@0.71.0(@effect/platform@0.92.1(effect@3.18.4))(effect@3.18.4))(@effect/sql@0.44.2(@effect/experimental@0.56.0(@effect/platform@0.92.1(effect@3.18.4))(effect@3.18.4)(ioredis@5.6.1))(@effect/platform@0.92.1(effect@3.18.4))(effect@3.18.4))(effect@3.18.4) + vitest: + specifier: ~2.1.9 + version: 2.1.9(@types/node@22.15.17)(jsdom@26.1.0)(terser@5.44.0) packages: From 14817cbc00b537761722ae4a09ba0e8016970dee Mon Sep 17 00:00:00 2001 From: NeurArk <197090867+NeurArk@users.noreply.github.com> Date: Thu, 14 May 2026 20:25:47 +0000 Subject: [PATCH 2/4] test: narrow bounty scope to new packages --- apps/desktop/package.json | 1 - packages/utils/package.json | 4 +-- packages/utils/src/helpers.test.ts | 56 ------------------------------ pnpm-lock.yaml | 3 -- 4 files changed, 1 insertion(+), 63 deletions(-) delete mode 100644 packages/utils/src/helpers.test.ts diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 590158a5ca3..3e32c06fb03 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -7,7 +7,6 @@ "build:sidecar": "node ../../scripts/build-cap-muxer.mjs", "preparescript": "node scripts/prepare.js", "localdev": "dotenv -e ../../.env -- vinxi dev --port 3002", - "test": "vitest run", "build": "vinxi build", "tauri": "tauri", "test:memory": "node scripts/desktop-memory-soak.js", diff --git a/packages/utils/package.json b/packages/utils/package.json index ee1e804d5b3..82f842d4c57 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -5,7 +5,6 @@ ".": "./src/index.ts" }, "scripts": { - "test": "vitest run", "typecheck": "tsc -b", "build": "tsdown" }, @@ -17,8 +16,7 @@ "react-dom": "^19.1.1", "react-router-dom": "^6.18.0", "tsconfig": "workspace:*", - "typescript": "^5.8.3", - "vitest": "~2.1.9" + "typescript": "^5.8.3" }, "dependencies": { "@aws-sdk/client-s3": "^3.485.0", diff --git a/packages/utils/src/helpers.test.ts b/packages/utils/src/helpers.test.ts deleted file mode 100644 index a71fd564fa3..00000000000 --- a/packages/utils/src/helpers.test.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { - calculateStrokeDashoffset, - classNames, - getDisplayProgress, - getProgressCircleConfig, - isEmailAllowedByRestriction, - uuidFormat, - uuidParse, -} from "./helpers"; - -describe("helpers", () => { - it("merges conditional class names and resolves Tailwind conflicts", () => { - expect(classNames("p-2", false && "hidden", "p-4", ["text-sm"])).toBe( - "p-4 text-sm", - ); - }); - - it("round-trips UUIDs between dashed and compact forms", () => { - const dashed = "123e4567-e89b-12d3-a456-426614174000"; - const compact = "123e4567e89b12d3a456426614174000"; - - expect(uuidParse(dashed)).toBe(compact); - expect(uuidFormat(compact)).toBe(dashed); - }); - - it("keeps zero upload progress as the displayed progress", () => { - expect(getDisplayProgress(0, 75)).toBe(0); - expect(getDisplayProgress(undefined, 75)).toBe(75); - }); - - it("calculates circular progress stroke offsets", () => { - const { circumference } = getProgressCircleConfig(); - - expect(calculateStrokeDashoffset(0, circumference)).toBe(circumference); - expect(calculateStrokeDashoffset(100, circumference)).toBe(0); - }); - - it("allows exact emails and domain restrictions case-insensitively", () => { - expect( - isEmailAllowedByRestriction( - "Founders@Cap.so", - "team@example.com, cap.so", - ), - ).toBe(true); - expect( - isEmailAllowedByRestriction( - "team@example.com", - "team@example.com, cap.so", - ), - ).toBe(true); - expect( - isEmailAllowedByRestriction("team@other.com", "team@example.com, cap.so"), - ).toBe(false); - }); -}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 117e5bd3abc..8e4eff09d56 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1329,9 +1329,6 @@ importers: typescript: specifier: ^5.8.3 version: 5.8.3 - vitest: - specifier: ~2.1.9 - version: 2.1.9(@types/node@22.15.17)(jsdom@26.1.0)(terser@5.44.0) packages/web-api-contract: dependencies: From 9b983cf7d1d431fa595ff2ede60cb9a89790d767 Mon Sep 17 00:00:00 2001 From: NeurArk <197090867+NeurArk@users.noreply.github.com> Date: Thu, 14 May 2026 20:41:00 +0000 Subject: [PATCH 3/4] test: add vitest configs for package suites --- packages/sdk-embed/vitest.config.ts | 9 +++++++++ packages/sdk-recorder/vitest.config.ts | 9 +++++++++ packages/web-domain/vitest.config.ts | 9 +++++++++ 3 files changed, 27 insertions(+) create mode 100644 packages/sdk-embed/vitest.config.ts create mode 100644 packages/sdk-recorder/vitest.config.ts create mode 100644 packages/web-domain/vitest.config.ts diff --git a/packages/sdk-embed/vitest.config.ts b/packages/sdk-embed/vitest.config.ts new file mode 100644 index 00000000000..b9379103131 --- /dev/null +++ b/packages/sdk-embed/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + environment: "node", + include: ["src/**/*.test.ts"], + exclude: ["**/node_modules/**", "**/dist/**"], + }, +}); diff --git a/packages/sdk-recorder/vitest.config.ts b/packages/sdk-recorder/vitest.config.ts new file mode 100644 index 00000000000..b9379103131 --- /dev/null +++ b/packages/sdk-recorder/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + environment: "node", + include: ["src/**/*.test.ts"], + exclude: ["**/node_modules/**", "**/dist/**"], + }, +}); diff --git a/packages/web-domain/vitest.config.ts b/packages/web-domain/vitest.config.ts new file mode 100644 index 00000000000..b9379103131 --- /dev/null +++ b/packages/web-domain/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + environment: "node", + include: ["src/**/*.test.ts"], + exclude: ["**/node_modules/**", "**/dist/**"], + }, +}); From b1d17f2b312eda072ed013511a007c4c29ee1023 Mon Sep 17 00:00:00 2001 From: NeurArk <197090867+NeurArk@users.noreply.github.com> Date: Thu, 14 May 2026 21:02:45 +0000 Subject: [PATCH 4/4] test: cover web api contract schemas --- packages/web-api-contract/package.json | 6 ++ packages/web-api-contract/src/index.test.ts | 110 ++++++++++++++++++++ packages/web-api-contract/vitest.config.ts | 9 ++ pnpm-lock.yaml | 4 + 4 files changed, 129 insertions(+) create mode 100644 packages/web-api-contract/src/index.test.ts create mode 100644 packages/web-api-contract/vitest.config.ts diff --git a/packages/web-api-contract/package.json b/packages/web-api-contract/package.json index 9b22385f99e..49d6a4b7d67 100644 --- a/packages/web-api-contract/package.json +++ b/packages/web-api-contract/package.json @@ -4,8 +4,14 @@ "main": "./src/index.ts", "types": "./src/index.ts", "type": "module", + "scripts": { + "test": "vitest run" + }, "dependencies": { "@ts-rest/core": "^3.52.1", "zod": "^3.25.76" + }, + "devDependencies": { + "vitest": "~2.1.9" } } diff --git a/packages/web-api-contract/src/index.test.ts b/packages/web-api-contract/src/index.test.ts new file mode 100644 index 00000000000..42961da7287 --- /dev/null +++ b/packages/web-api-contract/src/index.test.ts @@ -0,0 +1,110 @@ +import { describe, expect, it } from "vitest"; +import { + contract, + DesktopOrganization, + licenseContract, + Notification, + OrganizationBrandingPatchBody, + orgCustomDomainContract, +} from "./index"; + +describe("desktop contract schemas", () => { + it("parses organization branding payloads with uploaded logos", () => { + const parsed = OrganizationBrandingPatchBody.parse({ + brandColors: { + primary: "#111111", + secondary: null, + accent: "#BADA55", + background: "#ffffff", + }, + logo: { + action: "upload", + contentType: "image/png", + data: "base64-logo", + }, + }); + + expect(parsed.logo?.action).toBe("upload"); + expect( + OrganizationBrandingPatchBody.safeParse({ + brandColors: { + primary: "111111", + secondary: null, + accent: null, + background: null, + }, + }).success, + ).toBe(false); + }); + + it("validates desktop organizations with nullable brand fields", () => { + expect( + DesktopOrganization.parse({ + id: "org_123", + name: "Cap Team", + ownerId: "user_123", + role: "owner", + canEditBrand: true, + iconUrl: null, + brandColors: { + primary: null, + secondary: "#123ABC", + accent: null, + background: "#000000", + }, + }), + ).toMatchObject({ role: "owner", canEditBrand: true }); + }); +}); + +describe("notification contract schema", () => { + it("coerces notification timestamps and rejects unknown notification types", () => { + const parsed = Notification.parse({ + id: "notification_123", + readAt: null, + createdAt: "2026-05-14T20:00:00.000Z", + type: "comment", + videoId: "video_123", + author: { id: "user_123", name: "Ada", avatar: null }, + comment: { id: "comment_123", content: "Looks good" }, + }); + + expect(parsed.createdAt).toBeInstanceOf(Date); + expect(Notification.safeParse({ ...parsed, type: "mention" }).success).toBe( + false, + ); + }); +}); + +describe("route contracts", () => { + it("keeps core video and notification routes stable", () => { + expect(contract.video.getTranscribeStatus).toMatchObject({ + method: "GET", + path: "/video/transcribe/status", + }); + expect(contract.video.delete).toMatchObject({ + method: "DELETE", + path: "/video/delete", + }); + expect(contract.notifications.get).toMatchObject({ + method: "GET", + path: "/notifications", + }); + }); + + it("keeps commercial license and custom domain routes stable", () => { + expect(licenseContract.activateCommercialLicense).toMatchObject({ + method: "POST", + path: "/commercial/activate", + }); + expect( + licenseContract.createCommercialCheckoutUrl.body.safeParse({ + type: "monthly", + }).success, + ).toBe(false); + expect(orgCustomDomainContract.getOrgCustomDomain).toMatchObject({ + method: "GET", + path: "/org-custom-domain", + }); + }); +}); diff --git a/packages/web-api-contract/vitest.config.ts b/packages/web-api-contract/vitest.config.ts new file mode 100644 index 00000000000..b9379103131 --- /dev/null +++ b/packages/web-api-contract/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + environment: "node", + include: ["src/**/*.test.ts"], + exclude: ["**/node_modules/**", "**/dist/**"], + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8e4eff09d56..2b6dbc2cecc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1338,6 +1338,10 @@ importers: zod: specifier: ^3.25.76 version: 3.25.76 + devDependencies: + vitest: + specifier: ~2.1.9 + version: 2.1.9(@types/node@22.15.17)(jsdom@26.1.0)(terser@5.44.0) packages/web-api-contract-effect: dependencies: