Skip to main content

Testing Extensions

Lumio extensions are React applications with server functions. This guide covers unit testing components, testing server functions, and manual testing against a real overlay before submitting for review.

Testing strategy​

LevelWhat to testTool
UnitPure functions, validators, data transformsVitest or Jest
ComponentRendering logic, UI state, config readsReact Testing Library
IntegrationServer function handlers with mocked storageVitest + hand-rolled mocks
Manual (local)Full flow: editor → layer → eventslumio dev
Manual (staging)Live overlay in browser source with real eventslumio deploy to staging

Setting up Vitest​

pnpm add -D vitest @testing-library/react @testing-library/user-event jsdom

vitest.config.ts:

import { defineConfig } from "vitest/config";

export default defineConfig({
test: {
environment: "jsdom",
globals: true,
},
});

package.json:

{
"scripts": {
"test": "vitest run",
"test:watch": "vitest"
}
}

Testing server function handlers​

Server functions are plain async functions — test the handler directly by constructing a minimal ctx mock.

// server/functions.test.ts
import { describe, it, expect, vi } from "vitest";
import { incrementScore } from "./functions";

function makeCtx(storage: Record<string, unknown> = {}) {
return {
db: {
get: vi.fn(async (_scope, ...keys) => storage[keys.join("/")] ?? null),
set: vi.fn(async (_scope, ...keysAndValue) => {
const value = keysAndValue[keysAndValue.length - 1];
const keys = (keysAndValue.slice(0, -1) as string[]).join("/");
storage[keys] = value;
}),
delete: vi.fn(),
list: vi.fn(async () => []),
},
fetch: vi.fn(),
secrets: { get: vi.fn((k: string) => `mock_${k}`), has: vi.fn(() => true) },
realtime: { push: vi.fn() },
identity: null,
};
}

describe("incrementScore", () => {
it("starts at 0 and increments", async () => {
const ctx = makeCtx();
const result = await incrementScore.handler(ctx, { amount: 5 });
expect(result.score).toBe(5);
});

it("adds to existing score", async () => {
const ctx = makeCtx({ score: 10 });
const result = await incrementScore.handler(ctx, { amount: 3 });
expect(result.score).toBe(13);
});

it("pushes a realtime event", async () => {
const ctx = makeCtx();
await incrementScore.handler(ctx, { amount: 1 });
expect(ctx.realtime.push).toHaveBeenCalledWith("score:updated", { score: 1 });
});
});

Testing React components​

Use React Testing Library to render surfaces and assert on output.

// src/layer.test.tsx
import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import { ScoreDisplay } from "./layer";

// Mock the SDK hooks
vi.mock("@zaflun/lumio-sdk", () => ({
useLumioConfig: () => ({
config: { homeTeam: "LAL", awayTeam: "BOS" },
setConfig: vi.fn(),
loading: false,
}),
useLumioTheme: () => ({ mode: "dark", primaryColor: "#6366f1", fontFamily: "sans-serif", borderRadius: 8 }),
useLumioEvent: () => null,
Box: ({ children, ...props }: React.HTMLAttributes<HTMLDivElement>) => <div {...props}>{children}</div>,
Text: ({ content }: { content: string }) => <span>{content}</span>,
}));

describe("ScoreDisplay", () => {
it("renders team names from config", () => {
render(<ScoreDisplay />);
expect(screen.getByText("LAL")).toBeInTheDocument();
expect(screen.getByText("BOS")).toBeInTheDocument();
});
});

Manual testing checklist​

Before submitting for review, work through this checklist using lumio dev:

Editor panel​

  • All config fields render correctly
  • Changing a setting immediately updates the layer (no page reload needed)
  • Default values display when config is empty
  • Text inputs accept long strings without layout breakage
  • Required fields show a validation hint if empty

Layer​

  • Renders correctly at 1920×1080
  • Renders correctly at 1280×720 (test by resizing the preview browser window)
  • Transparent backgrounds are truly transparent (check against a non-black background)
  • Fonts load correctly (no fallback to system font)
  • Animations play at smooth 60 fps

Event handling​

  • Displays correctly when no event has arrived yet (null state)
  • Responds to the first event within 1 second
  • Handles rapid events without visual glitches or memory leaks
  • Auto-dismiss timers work correctly

Interactive page​

  • Renders on mobile screen sizes (375px wide)
  • Unauthenticated users see an appropriate message or login prompt
  • All user interactions are reflected on the layer within 500ms

Server functions​

  • Actions return expected results
  • Error conditions are handled gracefully (no unhandled promise rejections in the console)
  • External API failures show a user-friendly fallback

Staging deployment​

Deploy to staging to test with real Twitch/YouTube events before submitting:

lumio deploy --version 0.9.0-rc1 --changelog "Release candidate for testing"

Install the extension on a test overlay and go live on Twitch. Trigger real events (follow, subscribe, chat) to verify end-to-end behavior.

Check production logs while testing:

lumio logs --tail --level info