From 9563773c89f32a93f46b7a94d43f36497d056845 Mon Sep 17 00:00:00 2001 From: zhengjynicolas Date: Fri, 15 May 2026 04:23:52 +0800 Subject: [PATCH 1/2] test: add package-level vitest examples --- packages/sdk-embed/package.json | 4 +- .../sdk-embed/src/vanilla/cap-embed.test.ts | 42 +++++++++++++ packages/sdk-recorder/package.json | 4 +- .../sdk-recorder/src/core/mime-types.test.ts | 33 ++++++++++ packages/web-domain/package.json | 4 +- packages/web-domain/src/ImageUpload.test.ts | 44 +++++++++++++ packages/web-domain/src/Video.test.ts | 63 +++++++++++++++++++ pnpm-lock.yaml | 9 +++ 8 files changed, 200 insertions(+), 3 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/web-domain/src/ImageUpload.test.ts create mode 100644 packages/web-domain/src/Video.test.ts diff --git a/packages/sdk-embed/package.json b/packages/sdk-embed/package.json index 0c732bce52e..095bcae4b2b 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": "^3.2.0" } } 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..694430ffed0 --- /dev/null +++ b/packages/sdk-embed/src/vanilla/cap-embed.test.ts @@ -0,0 +1,42 @@ +import { describe, expect, it } from "vitest"; +import { createEmbedUrl } from "./cap-embed"; + +describe("createEmbedUrl", () => { + it("builds a default Cap embed URL", () => { + const url = new URL( + createEmbedUrl({ + videoId: "video_123", + publicKey: "pk_test_123", + }), + ); + + 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_123"); + expect(url.searchParams.has("autoplay")).toBe(false); + }); + + it("includes optional playback and branding parameters", () => { + const url = new URL( + createEmbedUrl({ + apiBase: "https://app.example.com", + videoId: "video_456", + publicKey: "pk_live_456", + autoplay: true, + branding: { + logoUrl: "https://cdn.example.com/logo.svg", + accentColor: "#ff3366", + }, + }), + ); + + expect(url.origin).toBe("https://app.example.com"); + expect(url.pathname).toBe("/embed/video_456"); + expect(url.searchParams.get("autoplay")).toBe("1"); + expect(url.searchParams.get("logo")).toBe( + "https://cdn.example.com/logo.svg", + ); + expect(url.searchParams.get("accent")).toBe("#ff3366"); + }); +}); diff --git a/packages/sdk-recorder/package.json b/packages/sdk-recorder/package.json index 7fef2785793..fb2c24620dd 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": "^3.2.0" } } 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..c84a6f92890 --- /dev/null +++ b/packages/sdk-recorder/src/core/mime-types.test.ts @@ -0,0 +1,33 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { getSupportedMimeType } from "./mime-types"; + +const originalMediaRecorder = globalThis.MediaRecorder; + +afterEach(() => { + if (originalMediaRecorder) { + globalThis.MediaRecorder = originalMediaRecorder; + } else { + delete (globalThis as { MediaRecorder?: typeof MediaRecorder }) + .MediaRecorder; + } +}); + +const setSupportedMimeTypes = (mimeTypes: ReadonlyArray) => { + globalThis.MediaRecorder = { + isTypeSupported: vi.fn((mimeType: string) => mimeTypes.includes(mimeType)), + } as unknown as typeof MediaRecorder; +}; + +describe("getSupportedMimeType", () => { + it("returns the first supported MIME type by preference order", () => { + setSupportedMimeTypes(["video/webm;codecs=vp8,opus", "video/webm"]); + + expect(getSupportedMimeType()).toBe("video/webm;codecs=vp8,opus"); + }); + + it("falls back to an empty string when no preferred types are supported", () => { + setSupportedMimeTypes([]); + + expect(getSupportedMimeType()).toBe(""); + }); +}); diff --git a/packages/web-domain/package.json b/packages/web-domain/package.json index e96e70bc883..c229e4219b8 100644 --- a/packages/web-domain/package.json +++ b/packages/web-domain/package.json @@ -9,6 +9,7 @@ }, "scripts": { "build": "tsdown", + "test": "vitest run", "generate-openapi": "node scripts/generate-openapi.ts" }, "dependencies": { @@ -18,6 +19,7 @@ "effect": "^3.18.4" }, "devDependencies": { - "@effect/platform-node": "^0.98.3" + "@effect/platform-node": "^0.98.3", + "vitest": "^3.2.0" } } diff --git a/packages/web-domain/src/ImageUpload.test.ts b/packages/web-domain/src/ImageUpload.test.ts new file mode 100644 index 00000000000..e8374826e5d --- /dev/null +++ b/packages/web-domain/src/ImageUpload.test.ts @@ -0,0 +1,44 @@ +import { Option } from "effect"; +import { describe, expect, it } from "vitest"; +import { extractFileKey } from "./ImageUpload"; + +const unwrap = (option: Option.Option) => + Option.isSome(option) ? option.value : undefined; + +describe("extractFileKey", () => { + it("keeps existing image keys unchanged", () => { + expect(unwrap(extractFileKey("users/user-1/avatar.png", false))).toBe( + "users/user-1/avatar.png", + ); + }); + + it("extracts keys from virtual-hosted S3 URLs", () => { + expect( + unwrap( + extractFileKey("https://assets.cap.so/users/user-1/avatar.png", false), + ), + ).toBe("users/user-1/avatar.png"); + }); + + it("drops the bucket segment for path-style S3 URLs", () => { + expect( + unwrap( + extractFileKey( + "https://s3.us-east-1.amazonaws.com/cap-bucket/users/user-1/avatar.png", + true, + ), + ), + ).toBe("users/user-1/avatar.png"); + }); + + it("ignores Google profile image URLs", () => { + expect( + Option.isNone( + extractFileKey( + "https://lh3.googleusercontent.com/a/profile-image", + false, + ), + ), + ).toBe(true); + }); +}); diff --git a/packages/web-domain/src/Video.test.ts b/packages/web-domain/src/Video.test.ts new file mode 100644 index 00000000000..29fd689d386 --- /dev/null +++ b/packages/web-domain/src/Video.test.ts @@ -0,0 +1,63 @@ +import { describe, expect, it } from "vitest"; +import { + M3U8Source, + Mp4Source, + normalizeSegmentEntry, + SegmentsSource, +} from "./Video"; + +describe("video source file keys", () => { + it("builds deterministic keys for MP4 sources", () => { + const source = new Mp4Source({ ownerId: "owner-1", videoId: "video-1" }); + + expect(source.getFileKey()).toBe("owner-1/video-1/result.mp4"); + }); + + it("builds deterministic keys for HLS playlist sources", () => { + const source = new M3U8Source({ + ownerId: "owner-1", + videoId: "video-1", + subpath: "combined-source/stream.m3u8", + }); + + expect(source.getPlaylistFileKey()).toBe( + "owner-1/video-1/combined-source/stream.m3u8", + ); + }); + + it("builds deterministic keys for segmented desktop recordings", () => { + const source = new SegmentsSource({ + ownerId: "owner-1", + videoId: "video-1", + }); + + expect(source.getManifestKey()).toBe( + "owner-1/video-1/segments/manifest.json", + ); + expect(source.getVideoInitKey()).toBe( + "owner-1/video-1/segments/video/init.mp4", + ); + expect(source.getAudioInitKey()).toBe( + "owner-1/video-1/segments/audio/init.mp4", + ); + expect(source.getVideoSegmentKey(7)).toBe( + "owner-1/video-1/segments/video/segment_007.m4s", + ); + expect(source.getAudioSegmentKey(12)).toBe( + "owner-1/video-1/segments/audio/segment_012.m4s", + ); + }); +}); + +describe("normalizeSegmentEntry", () => { + it("uses the legacy default duration for numeric manifest entries", () => { + expect(normalizeSegmentEntry(4)).toEqual({ index: 4, duration: 3.0 }); + }); + + it("preserves explicit segment durations", () => { + expect(normalizeSegmentEntry({ index: 2, duration: 1.5 })).toEqual({ + index: 2, + duration: 1.5, + }); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ce77b06b4f8..01bb749dfe6 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: ^3.2.0 + version: 3.2.4(@types/debug@4.1.12)(@types/node@20.17.43)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@26.1.0)(terser@5.44.0)(yaml@2.8.1) packages/sdk-recorder: dependencies: @@ -1108,6 +1111,9 @@ importers: typescript: specifier: ^5.7.3 version: 5.8.3 + vitest: + specifier: ^3.2.0 + version: 3.2.4(@types/debug@4.1.12)(@types/node@20.17.43)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@26.1.0)(terser@5.44.0)(yaml@2.8.1) packages/tsconfig: {} @@ -1420,6 +1426,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: ^3.2.0 + version: 3.2.4(@types/debug@4.1.12)(@types/node@20.17.43)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@26.1.0)(terser@5.44.0)(yaml@2.8.1) packages: From e2bfbe8725c380ec2cbd4a23baf6afcf3cdaf86e Mon Sep 17 00:00:00 2001 From: zhengjynicolas Date: Fri, 15 May 2026 12:27:55 +0800 Subject: [PATCH 2/2] test: cover review gaps --- packages/sdk-embed/src/vanilla/cap-embed.test.ts | 2 ++ packages/sdk-recorder/src/core/mime-types.test.ts | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/packages/sdk-embed/src/vanilla/cap-embed.test.ts b/packages/sdk-embed/src/vanilla/cap-embed.test.ts index 694430ffed0..3a5b9dc6853 100644 --- a/packages/sdk-embed/src/vanilla/cap-embed.test.ts +++ b/packages/sdk-embed/src/vanilla/cap-embed.test.ts @@ -33,6 +33,8 @@ describe("createEmbedUrl", () => { expect(url.origin).toBe("https://app.example.com"); expect(url.pathname).toBe("/embed/video_456"); + expect(url.searchParams.get("sdk")).toBe("1"); + expect(url.searchParams.get("pk")).toBe("pk_live_456"); expect(url.searchParams.get("autoplay")).toBe("1"); expect(url.searchParams.get("logo")).toBe( "https://cdn.example.com/logo.svg", diff --git a/packages/sdk-recorder/src/core/mime-types.test.ts b/packages/sdk-recorder/src/core/mime-types.test.ts index c84a6f92890..d321fddc6b4 100644 --- a/packages/sdk-recorder/src/core/mime-types.test.ts +++ b/packages/sdk-recorder/src/core/mime-types.test.ts @@ -25,6 +25,15 @@ describe("getSupportedMimeType", () => { expect(getSupportedMimeType()).toBe("video/webm;codecs=vp8,opus"); }); + it("prefers vp9,opus over vp8,opus when both are supported", () => { + setSupportedMimeTypes([ + "video/webm;codecs=vp9,opus", + "video/webm;codecs=vp8,opus", + ]); + + expect(getSupportedMimeType()).toBe("video/webm;codecs=vp9,opus"); + }); + it("falls back to an empty string when no preferred types are supported", () => { setSupportedMimeTypes([]);