Files
openscreen/docs/tests/writing-tests.md
T
2026-05-07 23:22:32 -04:00

4.1 KiB
Raw Blame History

Writing Tests

This project uses Vitest for both unit/integration tests and browser tests. There are two separate configs — each targets a different set of files.

Unit tests

Config: vitest.config.ts
Runs in: jsdom (simulated DOM, no real browser)
File pattern: src/**/*.test.ts — anything that does not end in .browser.test.ts
CI command: npm run test

Use unit tests for pure logic, utility functions, data transformations, and anything that doesn't need real browser APIs (Canvas, WebCodecs, MediaRecorder, etc.).

File placement

Co-locate the test file next to the source file, or put it in a __tests__/ folder in the same directory.

src/lib/compositeLayout.ts
src/lib/compositeLayout.test.ts        # co-located

src/i18n/__tests__/tutorialHelpTranslations.test.ts  # grouped

Example

import { describe, expect, it } from "vitest";
import { computeCompositeLayout } from "./compositeLayout";

describe("computeCompositeLayout", () => {
  it("anchors the overlay in the lower-right corner", () => {
    const layout = computeCompositeLayout({
      canvasSize: { width: 1920, height: 1080 },
      screenSize: { width: 1920, height: 1080 },
      webcamSize: { width: 1280, height: 720 },
    });

    expect(layout).not.toBeNull();
    expect(layout!.webcamRect!.x).toBeGreaterThan(1920 / 2);
    expect(layout!.webcamRect!.y).toBeGreaterThan(1080 / 2);
  });
});

Path aliases

The @/ alias resolves to src/. Use it for imports that would otherwise need long relative paths.

import { SUPPORTED_LOCALES } from "@/i18n/config";

Running locally

npm run test          # run once
npm run test:watch    # watch mode

Browser tests

Config: vitest.browser.config.ts
Runs in: real Chromium via Playwright (headless)
File pattern: src/**/*.browser.test.ts
CI commands: npm run test:browser:install then npm run test:browser

Use browser tests when the code under test depends on real browser APIs that jsdom doesn't implement: VideoDecoder, VideoEncoder, MediaRecorder, OffscreenCanvas, WebGL, etc.

File placement

Name the file <subject>.browser.test.ts and place it next to the source file.

src/lib/exporter/videoExporter.ts
src/lib/exporter/videoExporter.browser.test.ts

Loading fixture assets

Static assets (video files, images) live in tests/fixtures/. Import them with Vite's ?url suffix so Vite serves them through the dev server.

import sampleVideoUrl from "../../../tests/fixtures/sample.webm?url";

Example

import { describe, expect, it } from "vitest";
import sampleVideoUrl from "../../../tests/fixtures/sample.webm?url";
import { VideoExporter } from "./videoExporter";

describe("VideoExporter (real browser)", () => {
  it("exports a valid MP4 blob from a real video", async () => {
    const exporter = new VideoExporter({
      videoUrl: sampleVideoUrl,
      width: 320,
      height: 180,
      frameRate: 15,
      bitrate: 1_000_000,
      wallpaper: "#1a1a2e",
      zoomRegions: [],
      showShadow: false,
      shadowIntensity: 0,
      showBlur: false,
      cropRegion: { x: 0, y: 0, width: 1, height: 1 },
    });

    const result = await exporter.export();

    expect(result.success, result.error).toBe(true);
    expect(result.blob).toBeInstanceOf(Blob);
  });
});

Timeouts

Browser tests have a default timeout of 120 seconds per test and 30 seconds per hook (set in vitest.browser.config.ts). Export operations are slow — prefer small fixture dimensions (320×180) and low bitrates to keep tests fast.

Running locally

First install the browser (one-time):

npm run test:browser:install

Then run the tests:

npm run test:browser

Choosing the right type

Situation Use
Pure function / data transformation Unit test
i18n key coverage Unit test
React hook logic (no real browser APIs) Unit test
VideoDecoder / VideoEncoder / MediaRecorder Browser test
OffscreenCanvas / WebGL / Pixi.js rendering Browser test
File export producing a real Blob Browser test