Skip to content

Commit dccf114

Browse files
replace jsdom with vitest browser mode (#176)
* Swap from jsdom to browser * Create clever-toys-travel.md * Add missing packages * Attempt more test fixes * Update testing matrix * Update test.yml * Update test.yml
1 parent cdd4d0b commit dccf114

File tree

11 files changed

+249
-113
lines changed

11 files changed

+249
-113
lines changed

.changeset/clever-toys-travel.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"ai-elements": patch
3+
---
4+
5+
replace jsdom with Vitest Browser Mode

.github/workflows/test.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515

1616
strategy:
1717
matrix:
18-
node-version: [18, 20, 22]
18+
node-version: [20, 22, 24]
1919

2020
steps:
2121
- name: Checkout Repo
@@ -32,5 +32,8 @@ jobs:
3232
- name: Install Dependencies
3333
run: pnpm i
3434

35+
- name: Install Playwright Browsers
36+
run: pnpm --filter @repo/elements exec playwright install --with-deps chromium
37+
3538
- name: Run Tests
3639
run: pnpm test

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ node_modules
1414

1515
# Testing
1616
coverage
17+
**/__screenshots__
1718

1819
# Turbo
1920
.turbo

package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,16 @@
1515
"devDependencies": {
1616
"@biomejs/biome": "2.3.1",
1717
"@changesets/cli": "^2.29.7",
18+
"@vitest/browser-playwright": "^4.0.4",
19+
"@vitest/browser-preview": "^4.0.4",
1820
"turbo": "^2.5.8",
1921
"typescript": "5.9.3",
20-
"ultracite": "6.0.4"
22+
"ultracite": "6.0.4",
23+
"vitest": "^4.0.4"
2124
},
2225
"packageManager": "[email protected]",
2326
"engines": {
24-
"node": ">=18"
27+
"node": ">=20.12"
2528
},
2629
"pnpm": {
2730
"overrides": {

packages/elements/__tests__/code-block.test.tsx

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,6 @@ import userEvent from "@testing-library/user-event";
33
import { describe, expect, it, vi } from "vitest";
44
import { CodeBlock, CodeBlockCopyButton } from "../src/code-block";
55

6-
// Mock clipboard API
7-
Object.assign(navigator, {
8-
clipboard: {
9-
writeText: vi.fn(() => Promise.resolve()),
10-
},
11-
});
12-
136
describe("CodeBlock", () => {
147
it("renders code content", async () => {
158
const { container } = render(

packages/elements/__tests__/connection.test.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import { render } from "@testing-library/react";
2-
import { describe, expect, it } from "vitest";
2+
import { describe, expect, it, vi } from "vitest";
33
import { Connection } from "../src/connection";
44

55
describe("Connection", () => {
66
it("renders with basic props", () => {
7+
// Mock console.error to suppress React SVG warnings from @xyflow/react
8+
const consoleError = vi.spyOn(console, "error").mockImplementation(() => {});
9+
710
const props = {
811
fromX: 0,
912
fromY: 0,
@@ -20,6 +23,8 @@ describe("Connection", () => {
2023

2124
expect(path).toBeInTheDocument();
2225
expect(circle).toBeInTheDocument();
26+
27+
consoleError.mockRestore();
2328
});
2429

2530
it("renders path with correct coordinates", () => {

packages/elements/__tests__/prompt-input.test.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ import {
2626

2727
// Mock URL.createObjectURL and URL.revokeObjectURL for tests
2828
beforeEach(() => {
29-
global.URL.createObjectURL = vi.fn(
29+
window.URL.createObjectURL = vi.fn(
3030
(blob) => `blob:mock-url-${Math.random()}`
3131
);
32-
global.URL.revokeObjectURL = vi.fn();
32+
window.URL.revokeObjectURL = vi.fn();
3333

3434
// Mock fetch for blob URL conversion
35-
global.fetch = vi.fn((url: string) => {
35+
window.fetch = vi.fn((url: string) => {
3636
if (url.startsWith("blob:")) {
3737
const blob = new Blob(["test content"], { type: "text/plain" });
3838
return Promise.resolve({
@@ -43,7 +43,7 @@ beforeEach(() => {
4343
});
4444

4545
// Mock FileReader
46-
global.FileReader = vi.fn(function (this: FileReader) {
46+
window.FileReader = vi.fn(function (this: FileReader) {
4747
this.readAsDataURL = vi.fn(function (this: FileReader, blob: Blob) {
4848
// Simulate async file reading
4949
setTimeout(() => {
@@ -1069,16 +1069,16 @@ describe("PromptInputSpeechButton", () => {
10691069
};
10701070

10711071
// @ts-expect-error - Mocking browser API
1072-
global.window.SpeechRecognition = function () {
1072+
window.SpeechRecognition = function () {
10731073
return mockRecognition;
10741074
};
10751075
});
10761076

10771077
afterEach(() => {
10781078
// @ts-expect-error - Cleaning up mock
1079-
delete global.window.SpeechRecognition;
1079+
delete window.SpeechRecognition;
10801080
// @ts-expect-error - Cleaning up mock
1081-
delete global.window.webkitSpeechRecognition;
1081+
delete window.webkitSpeechRecognition;
10821082
});
10831083

10841084
it("renders button component", () => {
@@ -1185,9 +1185,9 @@ describe("PromptInputSpeechButton", () => {
11851185

11861186
it("is disabled when speech recognition not available", () => {
11871187
// @ts-expect-error - Removing mock
1188-
delete global.window.SpeechRecognition;
1188+
delete window.SpeechRecognition;
11891189

1190-
const { container } = render(<PromptInputSpeechButton />);
1190+
const { container} = render(<PromptInputSpeechButton />);
11911191
const button = container.querySelector("button");
11921192

11931193
expect(button).toBeDisabled();

packages/elements/__tests__/setup.ts

Lines changed: 6 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -3,74 +3,15 @@ import { cleanup } from "@testing-library/react";
33
import { afterEach, vi } from "vitest";
44
import failOnConsole from "vitest-fail-on-console";
55

6-
// Mock CSS imports
7-
vi.mock("*.css", () => ({}));
8-
vi.mock("katex/dist/katex.min.css", () => ({}));
9-
10-
// Mock ResizeObserver
11-
global.ResizeObserver = vi.fn(function (this: ResizeObserver) {
12-
this.observe = vi.fn();
13-
this.unobserve = vi.fn();
14-
this.disconnect = vi.fn();
15-
return this;
16-
}) as unknown as typeof ResizeObserver;
17-
18-
// Mock IntersectionObserver
19-
global.IntersectionObserver = vi.fn(function (this: IntersectionObserver) {
20-
this.observe = vi.fn();
21-
this.unobserve = vi.fn();
22-
this.disconnect = vi.fn();
23-
this.root = null;
24-
this.rootMargin = "";
25-
this.thresholds = [];
26-
this.takeRecords = vi.fn(() => []);
27-
return this;
28-
}) as unknown as typeof IntersectionObserver;
29-
30-
// Mock matchMedia
31-
Object.defineProperty(window, "matchMedia", {
6+
// Mock clipboard API for browser environment
7+
Object.defineProperty(navigator, "clipboard", {
8+
value: {
9+
writeText: vi.fn(() => Promise.resolve()),
10+
},
3211
writable: true,
33-
value: vi.fn().mockImplementation((query) => ({
34-
matches: false,
35-
media: query,
36-
onchange: null,
37-
addListener: vi.fn(),
38-
removeListener: vi.fn(),
39-
addEventListener: vi.fn(),
40-
removeEventListener: vi.fn(),
41-
dispatchEvent: vi.fn(),
42-
})),
12+
configurable: true,
4313
});
4414

45-
// Patch createElement to handle SVG elements properly in jsdom
46-
const SVG_TAGS = new Set([
47-
"svg",
48-
"path",
49-
"circle",
50-
"g",
51-
"rect",
52-
"line",
53-
"polyline",
54-
"polygon",
55-
"ellipse",
56-
"text",
57-
"tspan",
58-
"defs",
59-
"clipPath",
60-
]);
61-
62-
const originalCreateElement = document.createElement.bind(document);
63-
// @ts-expect-error - Overriding createElement signature for test environment
64-
document.createElement = (
65-
tagName: string,
66-
options?: ElementCreationOptions
67-
) => {
68-
if (SVG_TAGS.has(tagName.toLowerCase())) {
69-
return document.createElementNS("http://www.w3.org/2000/svg", tagName);
70-
}
71-
return originalCreateElement(tagName, options);
72-
};
73-
7415
// Fail the test if there are any console logs during test execution
7516
failOnConsole({
7617
shouldFailOnAssert: true,

packages/elements/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"motion": "^12.23.24",
2222
"nanoid": "^5.1.6",
2323
"react": "19.2.0",
24+
"react-dom": "19.2.0",
2425
"shiki": "3.14.0",
2526
"streamdown": "^1.4.0",
2627
"tokenlens": "^1.3.1",
@@ -34,8 +35,10 @@
3435
"@types/hast": "^3.0.4",
3536
"@types/react": "19.2.2",
3637
"@vitejs/plugin-react": "^5.1.0",
38+
"@vitest/browser": "^4.0.4",
39+
"@vitest/browser-playwright": "^4.0.4",
3740
"@vitest/coverage-v8": "^4.0.4",
38-
"jsdom": "^26.0.0",
41+
"playwright": "^1.56.1",
3942
"typescript": "^5.9.3",
4043
"vitest": "^4.0.4",
4144
"vitest-fail-on-console": "^0.10.1"

packages/elements/vitest.config.mts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
1+
import path from "node:path";
12
import react from "@vitejs/plugin-react";
2-
import path from "path";
3+
import { playwright } from "@vitest/browser-playwright";
34
import { defineConfig } from "vitest/config";
45

56
export default defineConfig({
67
plugins: [react()],
78
test: {
89
globals: true,
9-
environment: "jsdom",
10+
browser: {
11+
enabled: true,
12+
provider: playwright(),
13+
headless: true,
14+
instances: [{ browser: "chromium" }],
15+
},
1016
setupFiles: ["./__tests__/setup.ts"],
1117
include: ["__tests__/**/*.test.{ts,tsx}"],
1218
server: {

0 commit comments

Comments
 (0)