Skip to content

Commit 71ab0a3

Browse files
committed
feat: add e2e tests
1 parent 5feb68e commit 71ab0a3

5 files changed

Lines changed: 304 additions & 0 deletions

File tree

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,11 @@ dist
33
node_modules
44

55
.DS_Store
6+
.tasks
67
/*.tgz
8+
9+
# Playwright
10+
/test-results/
11+
/playwright-report/
12+
/blob-report/
13+
/playwright/.cache/

bun.lock

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

e2e/resize.test.ts

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
import { expect, type Locator, type Page, test } from "@playwright/test";
2+
3+
// ============================================================================
4+
// HELPERS
5+
// ============================================================================
6+
7+
/**
8+
* Get the canvas dimensions from a uPlot chart within a container
9+
*/
10+
async function getChartCanvasDimensions(container: Locator) {
11+
const canvas = container.locator(".u-wrap canvas").first();
12+
await canvas.waitFor({ state: "visible" });
13+
14+
const width = await canvas.getAttribute("width");
15+
const height = await canvas.getAttribute("height");
16+
17+
return {
18+
width: Number(width),
19+
height: Number(height),
20+
};
21+
}
22+
23+
/**
24+
* Set a range slider to a specific value and trigger input event
25+
*/
26+
async function setSliderValue(slider: Locator, value: number) {
27+
await slider.fill(String(value));
28+
}
29+
30+
/**
31+
* Get a section container by its heading text
32+
*/
33+
function getSection(page: Page, headingText: string) {
34+
return page.locator(`h3:has-text("${headingText}")`).locator("..");
35+
}
36+
37+
// ============================================================================
38+
// TESTS
39+
// ============================================================================
40+
41+
test.describe("SolidUplot Resize Behaviors", () => {
42+
test.beforeEach(async ({ page }) => {
43+
await page.goto("/dynamic-resize");
44+
// Wait for at least one chart to render
45+
await page.waitForSelector(".u-wrap");
46+
});
47+
48+
// ---------------------------------------------------------------------------
49+
// PATTERN 1: FIXED SIZE
50+
// ---------------------------------------------------------------------------
51+
test.describe("Pattern 1: Fixed Size", () => {
52+
test("renders chart at initial dimensions (400x250)", async ({ page }) => {
53+
const section = getSection(page, "Fixed Size");
54+
const { width, height } = await getChartCanvasDimensions(section);
55+
56+
expect(width).toBe(400);
57+
expect(height).toBe(250);
58+
});
59+
60+
test("updates chart width when slider changes", async ({ page }) => {
61+
const section = getSection(page, "Fixed Size");
62+
63+
// Find the width slider - it's the first range input in the Fixed Size section
64+
const widthSlider = section.locator('input[type="range"]').first();
65+
await setSliderValue(widthSlider, 600);
66+
67+
// Wait for reactive update and verify
68+
await expect(async () => {
69+
const { width } = await getChartCanvasDimensions(section);
70+
expect(width).toBe(600);
71+
}).toPass({ timeout: 5000 });
72+
});
73+
74+
test("updates chart height when slider changes", async ({ page }) => {
75+
const section = getSection(page, "Fixed Size");
76+
77+
// Find the height slider - it's the second range input in the Fixed Size section
78+
const heightSlider = section.locator('input[type="range"]').nth(1);
79+
await setSliderValue(heightSlider, 400);
80+
81+
// Wait for reactive update and verify
82+
await expect(async () => {
83+
const { height } = await getChartCanvasDimensions(section);
84+
expect(height).toBe(400);
85+
}).toPass({ timeout: 5000 });
86+
});
87+
88+
test("handles multiple rapid dimension changes", async ({ page }) => {
89+
const section = getSection(page, "Fixed Size");
90+
const widthSlider = section.locator('input[type="range"]').first();
91+
92+
// Rapid changes
93+
await setSliderValue(widthSlider, 300);
94+
await setSliderValue(widthSlider, 500);
95+
await setSliderValue(widthSlider, 250);
96+
await setSliderValue(widthSlider, 550);
97+
98+
// Final state should be 550
99+
await expect(async () => {
100+
const { width } = await getChartCanvasDimensions(section);
101+
expect(width).toBe(550);
102+
}).toPass({ timeout: 5000 });
103+
});
104+
});
105+
106+
// ---------------------------------------------------------------------------
107+
// PATTERN 1: AUTO RESIZE (ResizeObserver)
108+
// ---------------------------------------------------------------------------
109+
test.describe("Pattern 1: Auto Resize (ResizeObserver)", () => {
110+
test("chart container has auto-resize styles", async ({ page }) => {
111+
const section = getSection(page, "Auto Resize");
112+
const chartContainer = section.locator(".solid-uplot");
113+
114+
// When autoResize is enabled, these styles should be applied
115+
await expect(chartContainer).toHaveCSS("width", /\d+px/);
116+
await expect(chartContainer).toHaveCSS("height", /\d+px/);
117+
await expect(chartContainer).toHaveCSS("min-width", "0px");
118+
await expect(chartContainer).toHaveCSS("min-height", "0px");
119+
});
120+
121+
test("chart renders and fills available space", async ({ page }) => {
122+
const section = getSection(page, "Auto Resize");
123+
const { width, height } = await getChartCanvasDimensions(section);
124+
125+
// Chart should have rendered with positive dimensions
126+
expect(width).toBeGreaterThan(0);
127+
expect(height).toBeGreaterThan(0);
128+
});
129+
130+
test("chart resizes when viewport changes", async ({ page }) => {
131+
const section = getSection(page, "Auto Resize");
132+
133+
// Get initial dimensions
134+
const initialDims = await getChartCanvasDimensions(section);
135+
136+
// Resize viewport to a smaller width
137+
await page.setViewportSize({ width: 800, height: 600 });
138+
139+
// Wait for resize and verify dimensions changed
140+
await expect(async () => {
141+
const newDims = await getChartCanvasDimensions(section);
142+
// Width should be different after viewport resize
143+
expect(newDims.width).not.toBe(initialDims.width);
144+
}).toPass({ timeout: 5000 });
145+
});
146+
});
147+
148+
// ---------------------------------------------------------------------------
149+
// PATTERN 2: CONTAINER-DRIVEN (AutoSizer)
150+
// ---------------------------------------------------------------------------
151+
test.describe("Pattern 2: Container-Driven (AutoSizer)", () => {
152+
test("renders at initial container dimensions", async ({ page }) => {
153+
const section = getSection(page, "AutoSizer Pattern");
154+
const { width, height } = await getChartCanvasDimensions(section);
155+
156+
// Container starts at 600x400, minus padding (p-4 = 32px total)
157+
// Chart should be close to these values
158+
expect(width).toBeGreaterThan(500);
159+
expect(width).toBeLessThanOrEqual(600);
160+
expect(height).toBeGreaterThan(300);
161+
expect(height).toBeLessThanOrEqual(400);
162+
});
163+
164+
test("updates when container width slider changes", async ({ page }) => {
165+
const section = getSection(page, "AutoSizer Pattern");
166+
167+
// Find the container width slider in this section
168+
const widthSlider = section.locator('input[type="range"]').first();
169+
await setSliderValue(widthSlider, 800);
170+
171+
// Wait for AutoSizer to detect change and update chart
172+
await expect(async () => {
173+
const { width } = await getChartCanvasDimensions(section);
174+
// Should be close to 800 minus padding
175+
expect(width).toBeGreaterThan(700);
176+
}).toPass({ timeout: 5000 });
177+
});
178+
179+
test("updates when container height slider changes", async ({ page }) => {
180+
const section = getSection(page, "AutoSizer Pattern");
181+
182+
// Find the container height slider in this section
183+
const heightSlider = section.locator('input[type="range"]').nth(1);
184+
await setSliderValue(heightSlider, 500);
185+
186+
// Wait for AutoSizer to detect change and update chart
187+
await expect(async () => {
188+
const { height } = await getChartCanvasDimensions(section);
189+
// Should be close to 500 minus padding
190+
expect(height).toBeGreaterThan(400);
191+
}).toPass({ timeout: 5000 });
192+
});
193+
194+
test("chart dimensions stay in sync with container", async ({ page }) => {
195+
const section = getSection(page, "AutoSizer Pattern");
196+
197+
// Get the container with the green dashed border
198+
const container = section.locator(".border-dashed.border-green-300");
199+
const containerBox = await container.boundingBox();
200+
201+
const { width, height } = await getChartCanvasDimensions(section);
202+
203+
// Chart should match container dimensions (accounting for padding)
204+
// Container has p-4 (16px padding on each side = 32px total)
205+
expect(width).toBeCloseTo(containerBox!.width - 32, -1);
206+
expect(height).toBeCloseTo(containerBox!.height - 32, -1);
207+
});
208+
});
209+
210+
// ---------------------------------------------------------------------------
211+
// PATTERN 2: MANUAL RESIZE (CSS resize)
212+
// ---------------------------------------------------------------------------
213+
test.describe("Pattern 2: Manual Resize (CSS resize)", () => {
214+
test("container has resize: both style", async ({ page }) => {
215+
const section = getSection(page, "Manual Resize");
216+
const container = section.locator(".border-dashed.border-indigo-300");
217+
218+
await expect(container).toHaveCSS("resize", "both");
219+
});
220+
221+
test("container has initial dimensions (400x300)", async ({ page }) => {
222+
const section = getSection(page, "Manual Resize");
223+
const container = section.locator(".border-dashed.border-indigo-300");
224+
225+
await expect(container).toHaveCSS("width", "400px");
226+
await expect(container).toHaveCSS("height", "300px");
227+
});
228+
229+
test("container respects min-width constraint", async ({ page }) => {
230+
const section = getSection(page, "Manual Resize");
231+
const container = section.locator(".border-dashed.border-indigo-300");
232+
233+
await expect(container).toHaveCSS("min-width", "200px");
234+
});
235+
236+
test("container respects min-height constraint", async ({ page }) => {
237+
const section = getSection(page, "Manual Resize");
238+
const container = section.locator(".border-dashed.border-indigo-300");
239+
240+
await expect(container).toHaveCSS("min-height", "150px");
241+
});
242+
243+
test("container respects max-width constraint", async ({ page }) => {
244+
const section = getSection(page, "Manual Resize");
245+
const container = section.locator(".border-dashed.border-indigo-300");
246+
247+
await expect(container).toHaveCSS("max-width", "800px");
248+
});
249+
250+
test("container respects max-height constraint", async ({ page }) => {
251+
const section = getSection(page, "Manual Resize");
252+
const container = section.locator(".border-dashed.border-indigo-300");
253+
254+
await expect(container).toHaveCSS("max-height", "600px");
255+
});
256+
});
257+
});

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,14 @@
102102
"start": "vite",
103103
"test": "vitest run",
104104
"test:cov": "vitest run --coverage",
105+
"test:e2e": "playwright test",
106+
"test:e2e:ui": "playwright test --ui",
105107
"typecheck": "tsc --noEmit"
106108
},
107109
"devDependencies": {
108110
"@changesets/cli": "^2.29.7",
109111
"@dschz/solid-auto-sizer": "^0.1.3",
112+
"@playwright/test": "^1.58.1",
110113
"@solidjs/router": "^0.15.4",
111114
"@solidjs/testing-library": "^0.8.10",
112115
"@tailwindcss/vite": "^4.1.17",

playwright.config.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { defineConfig, devices } from "@playwright/test";
2+
3+
export default defineConfig({
4+
testDir: "./e2e",
5+
fullyParallel: true,
6+
forbidOnly: !!process.env.CI,
7+
retries: process.env.CI ? 2 : 0,
8+
workers: process.env.CI ? 1 : undefined,
9+
reporter: "html",
10+
11+
use: {
12+
baseURL: "http://localhost:3000",
13+
trace: "on-first-retry",
14+
},
15+
16+
projects: [
17+
{
18+
name: "chromium",
19+
use: { ...devices["Desktop Chrome"] },
20+
},
21+
],
22+
23+
webServer: {
24+
command: "bun run dev",
25+
url: "http://localhost:3000",
26+
reuseExistingServer: !process.env.CI,
27+
},
28+
});

0 commit comments

Comments
 (0)