A tool for people curious about the React Server Components protocol rscexplorer.dev/
rsc react

add ci and unflake tests

+384 -108
+23
.github/workflows/ci.yml
···
··· 1 + name: CI 2 + 3 + on: 4 + push: 5 + branches: [main] 6 + pull_request: 7 + branches: [main] 8 + 9 + jobs: 10 + test: 11 + runs-on: ubuntu-latest 12 + steps: 13 + - uses: actions/checkout@v4 14 + - uses: actions/setup-node@v4 15 + with: 16 + node-version: 22 17 + cache: npm 18 + - run: npm install 19 + - run: npm run lint 20 + - run: npm run format:check 21 + - run: npx playwright install --with-deps chromium 22 + - run: npm test 23 + - run: npm run build:versions
+1 -1
package.json
··· 10 "build:versions": "node scripts/build-version.js --all", 11 "dev": "vite", 12 "preview": "vite preview", 13 - "deploy": "npm install && npm test && npm run build:versions && wrangler pages deploy dist --project-name=rscexplorer", 14 "test": "vitest run --reporter=verbose", 15 "lint": "eslint .", 16 "format": "prettier --write .",
··· 10 "build:versions": "node scripts/build-version.js --all", 11 "dev": "vite", 12 "preview": "vite preview", 13 + "deploy": "npm install && npm run lint && npm test && npm run build:versions && wrangler pages deploy dist --project-name=rscexplorer", 14 "test": "vitest run --reporter=verbose", 15 "lint": "eslint .", 16 "format": "prettier --write .",
+1 -1
src/client/samples.js
··· 248 borderRadius: 8, 249 textAlign: 'center' 250 }}> 251 - <p style={{ fontFamily: 'monospace', fontSize: 32, margin: 0 }}>{seconds}s</p> 252 </div> 253 ) 254 }
··· 248 borderRadius: 8, 249 textAlign: 'center' 250 }}> 251 + <p style={{ fontFamily: 'monospace', fontSize: 32, margin: 0 }}>Timer: {seconds}s</p> 252 </div> 253 ) 254 }
+12 -5
tests/async.spec.js
··· 1 import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 - import { chromium } from "playwright"; 3 - import { createHelpers } from "./helpers.js"; 4 5 let browser, page, h; 6 7 beforeAll(async () => { 8 - browser = await chromium.launch(); 9 page = await browser.newPage(); 10 h = createHelpers(page); 11 }); ··· 32 </Suspense> 33 </div>" 34 `); 35 - expect(await h.preview()).toMatchInlineSnapshot(`"Async Component Loading..."`); 36 37 // Tree changes when async data resolves 38 expect(await h.stepAll()).toMatchInlineSnapshot(` ··· 45 </Suspense> 46 </div>" 47 `); 48 - expect(await h.preview()).toMatchInlineSnapshot(`"Async Component Loading..."`); 49 });
··· 1 import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 + import { createHelpers, launchBrowser } from "./helpers.js"; 3 4 let browser, page, h; 5 6 beforeAll(async () => { 7 + browser = await launchBrowser(); 8 page = await browser.newPage(); 9 h = createHelpers(page); 10 }); ··· 31 </Suspense> 32 </div>" 33 `); 34 + expect(await h.preview("Loading")).toMatchInlineSnapshot(` 35 + "Async Component 36 + 37 + Loading..." 38 + `); 39 40 // Tree changes when async data resolves 41 expect(await h.stepAll()).toMatchInlineSnapshot(` ··· 48 </Suspense> 49 </div>" 50 `); 51 + expect(await h.preview("Data loaded")).toMatchInlineSnapshot(` 52 + "Async Component 53 + 54 + Data loaded!" 55 + `); 56 });
+62 -15
tests/bound.spec.js
··· 1 import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 - import { chromium } from "playwright"; 3 - import { createHelpers } from "./helpers.js"; 4 5 let browser, page, h; 6 7 beforeAll(async () => { 8 - browser = await chromium.launch(); 9 page = await browser.newPage(); 10 h = createHelpers(page); 11 }); ··· 31 <Greeter action={[Function: bound greet]} /> 32 </div>" 33 `); 34 - expect(await h.preview()).toMatchInlineSnapshot( 35 - `"Bound Actions Same action, different bound greetings: Greet Greet Greet"`, 36 ); 37 }); 38 ··· 49 <Greeter action={[Function: bound greet]} /> 50 </div>" 51 `); 52 - expect(await h.preview()).toMatchInlineSnapshot( 53 - `"Bound Actions Same action, different bound greetings: Greet Greet Greet"`, 54 ); 55 56 // Fill all three inputs and submit all three forms ··· 67 68 // First action pending 69 expect(await h.stepAll()).toMatchInlineSnapshot(`"Pending"`); 70 - expect(await h.preview()).toMatchInlineSnapshot( 71 - `"Bound Actions Same action, different bound greetings: Greet Greet Greet"`, 72 ); 73 74 // First action resolves - Hello greeting (second still pending) 75 expect(await h.stepAll()).toMatchInlineSnapshot(`"Pending"`); 76 - expect(await h.preview()).toMatchInlineSnapshot( 77 - `"Bound Actions Same action, different bound greetings: GreetHello, Alice! Greet Greet"`, 78 ); 79 80 // Second action resolves - Howdy greeting (third still pending) 81 expect(await h.stepAll()).toMatchInlineSnapshot(`"Pending"`); 82 - expect(await h.preview()).toMatchInlineSnapshot( 83 - `"Bound Actions Same action, different bound greetings: GreetHello, Alice! GreetHowdy, Bob! Greet"`, 84 ); 85 86 // Third action resolves - Hey greeting 87 expect(await h.stepAll()).toMatchInlineSnapshot(`""Hey, Charlie!""`); 88 - expect(await h.preview()).toMatchInlineSnapshot( 89 - `"Bound Actions Same action, different bound greetings: GreetHello, Alice! GreetHowdy, Bob! GreetHey, Charlie!"`, 90 ); 91 });
··· 1 import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 + import { createHelpers, launchBrowser } from "./helpers.js"; 3 4 let browser, page, h; 5 6 beforeAll(async () => { 7 + browser = await launchBrowser(); 8 page = await browser.newPage(); 9 h = createHelpers(page); 10 }); ··· 30 <Greeter action={[Function: bound greet]} /> 31 </div>" 32 `); 33 + expect(await h.preview("bound greetings")).toMatchInlineSnapshot( 34 + ` 35 + "Bound Actions 36 + 37 + Same action, different bound greetings: 38 + 39 + Greet 40 + Greet 41 + Greet" 42 + `, 43 ); 44 }); 45 ··· 56 <Greeter action={[Function: bound greet]} /> 57 </div>" 58 `); 59 + expect(await h.preview("bound greetings")).toMatchInlineSnapshot( 60 + ` 61 + "Bound Actions 62 + 63 + Same action, different bound greetings: 64 + 65 + Greet 66 + Greet 67 + Greet" 68 + `, 69 ); 70 71 // Fill all three inputs and submit all three forms ··· 82 83 // First action pending 84 expect(await h.stepAll()).toMatchInlineSnapshot(`"Pending"`); 85 + expect(await h.preview("bound greetings")).toMatchInlineSnapshot( 86 + ` 87 + "Bound Actions 88 + 89 + Same action, different bound greetings: 90 + 91 + Greet 92 + Greet 93 + Greet" 94 + `, 95 ); 96 97 // First action resolves - Hello greeting (second still pending) 98 expect(await h.stepAll()).toMatchInlineSnapshot(`"Pending"`); 99 + expect(await h.preview("Hello, Alice")).toMatchInlineSnapshot( 100 + ` 101 + "Bound Actions 102 + 103 + Same action, different bound greetings: 104 + 105 + GreetHello, Alice! 106 + Greet 107 + Greet" 108 + `, 109 ); 110 111 // Second action resolves - Howdy greeting (third still pending) 112 expect(await h.stepAll()).toMatchInlineSnapshot(`"Pending"`); 113 + expect(await h.preview("Howdy, Bob")).toMatchInlineSnapshot( 114 + ` 115 + "Bound Actions 116 + 117 + Same action, different bound greetings: 118 + 119 + GreetHello, Alice! 120 + GreetHowdy, Bob! 121 + Greet" 122 + `, 123 ); 124 125 // Third action resolves - Hey greeting 126 expect(await h.stepAll()).toMatchInlineSnapshot(`""Hey, Charlie!""`); 127 + expect(await h.preview("Hey, Charlie")).toMatchInlineSnapshot( 128 + ` 129 + "Bound Actions 130 + 131 + Same action, different bound greetings: 132 + 133 + GreetHello, Alice! 134 + GreetHowdy, Bob! 135 + GreetHey, Charlie!" 136 + `, 137 ); 138 });
+9 -4
tests/clientref.spec.js
··· 1 import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 - import { chromium } from "playwright"; 3 - import { createHelpers } from "./helpers.js"; 4 5 let browser, page, h; 6 7 beforeAll(async () => { 8 - browser = await chromium.launch(); 9 page = await browser.newPage(); 10 h = createHelpers(page); 11 }); ··· 37 </div> 38 </div>" 39 `); 40 - expect(await h.preview()).toMatchInlineSnapshot(`"Client Reference Dark theme Light theme"`); 41 });
··· 1 import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 + import { createHelpers, launchBrowser } from "./helpers.js"; 3 4 let browser, page, h; 5 6 beforeAll(async () => { 7 + browser = await launchBrowser(); 8 page = await browser.newPage(); 9 h = createHelpers(page); 10 }); ··· 36 </div> 37 </div>" 38 `); 39 + expect(await h.preview("Dark theme")).toMatchInlineSnapshot( 40 + ` 41 + "Client Reference 42 + Dark theme 43 + Light theme" 44 + `, 45 + ); 46 });
+18 -5
tests/counter.spec.js
··· 1 import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 - import { chromium } from "playwright"; 3 - import { createHelpers } from "./helpers.js"; 4 5 let browser, page, h; 6 7 beforeAll(async () => { 8 - browser = await chromium.launch(); 9 page = await browser.newPage(); 10 h = createHelpers(page); 11 }); ··· 27 <Counter initialCount={0} /> 28 </div>" 29 `); 30 - expect(await h.preview()).toMatchInlineSnapshot(`"Counter Count: 0 − +"`); 31 32 // Client interactivity works 33 await h.frame().locator(".preview-container button").last().click(); 34 - expect(await h.preview()).toContain("Count: 1"); 35 });
··· 1 import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 + import { createHelpers, launchBrowser } from "./helpers.js"; 3 4 let browser, page, h; 5 6 beforeAll(async () => { 7 + browser = await launchBrowser(); 8 page = await browser.newPage(); 9 h = createHelpers(page); 10 }); ··· 26 <Counter initialCount={0} /> 27 </div>" 28 `); 29 + expect(await h.preview("Count: 0")).toMatchInlineSnapshot(` 30 + "Counter 31 + 32 + Count: 0 33 + 34 + 35 + +" 36 + `); 37 38 // Client interactivity works 39 await h.frame().locator(".preview-container button").last().click(); 40 + expect(await h.preview("Count: 1")).toMatchInlineSnapshot(` 41 + "Counter 42 + 43 + Count: 1 44 + 45 + 46 + +" 47 + `); 48 });
+15 -6
tests/errors.spec.js
··· 1 import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 - import { chromium } from "playwright"; 3 - import { createHelpers } from "./helpers.js"; 4 5 let browser, page, h; 6 7 beforeAll(async () => { 8 - browser = await chromium.launch(); 9 page = await browser.newPage(); 10 h = createHelpers(page); 11 }); ··· 44 </ErrorBoundary> 45 </div>" 46 `); 47 - expect(await h.preview()).toContain("Error Handling"); 48 49 // After async resolves with error, error boundary catches it 50 expect(await h.stepAll()).toMatchInlineSnapshot(` ··· 69 </ErrorBoundary> 70 </div>" 71 `); 72 - expect(await h.preview()).toContain("Failed to load user"); 73 - expect(await h.preview()).toContain("Please try again later"); 74 });
··· 1 import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 + import { createHelpers, launchBrowser } from "./helpers.js"; 3 4 let browser, page, h; 5 6 beforeAll(async () => { 7 + browser = await launchBrowser(); 8 page = await browser.newPage(); 9 h = createHelpers(page); 10 }); ··· 43 </ErrorBoundary> 44 </div>" 45 `); 46 + expect(await h.preview("Loading user")).toMatchInlineSnapshot(` 47 + "Error Handling 48 + 49 + Loading user..." 50 + `); 51 52 // After async resolves with error, error boundary catches it 53 expect(await h.stepAll()).toMatchInlineSnapshot(` ··· 72 </ErrorBoundary> 73 </div>" 74 `); 75 + expect(await h.preview("Failed to load user")).toMatchInlineSnapshot( 76 + ` 77 + "Error Handling 78 + Failed to load user 79 + 80 + Please try again later." 81 + `, 82 + ); 83 });
+18 -6
tests/form.spec.js
··· 1 import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 - import { chromium } from "playwright"; 3 - import { createHelpers } from "./helpers.js"; 4 5 let browser, page, h; 6 7 beforeAll(async () => { 8 - browser = await chromium.launch(); 9 page = await browser.newPage(); 10 h = createHelpers(page); 11 }); ··· 27 <Form greetAction={[Function: greet]} /> 28 </div>" 29 `); 30 - expect(await h.preview()).toMatchInlineSnapshot(`"Form Action Greet"`); 31 32 // Submit form 33 await h.frame().locator('.preview-container input[name="name"]').fill("World"); 34 await h.frame().locator(".preview-container button").click(); 35 - expect(await h.preview()).toMatchInlineSnapshot(`"Form Action Sending..."`); 36 37 // Action response 38 expect(await h.stepAll()).toMatchInlineSnapshot(`"{ message: "Hello, World!", error: null }"`); 39 - expect(await h.preview()).toMatchInlineSnapshot(`"Form Action Greet Hello, World!"`); 40 });
··· 1 import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 + import { createHelpers, launchBrowser } from "./helpers.js"; 3 4 let browser, page, h; 5 6 beforeAll(async () => { 7 + browser = await launchBrowser(); 8 page = await browser.newPage(); 9 h = createHelpers(page); 10 }); ··· 26 <Form greetAction={[Function: greet]} /> 27 </div>" 28 `); 29 + expect(await h.preview("Greet")).toMatchInlineSnapshot(` 30 + "Form Action 31 + Greet" 32 + `); 33 34 // Submit form 35 await h.frame().locator('.preview-container input[name="name"]').fill("World"); 36 await h.frame().locator(".preview-container button").click(); 37 + expect(await h.preview("Sending")).toMatchInlineSnapshot(` 38 + "Form Action 39 + Sending..." 40 + `); 41 42 // Action response 43 expect(await h.stepAll()).toMatchInlineSnapshot(`"{ message: "Hello, World!", error: null }"`); 44 + expect(await h.preview("Hello, World")).toMatchInlineSnapshot( 45 + ` 46 + "Form Action 47 + Greet 48 + 49 + Hello, World!" 50 + `, 51 + ); 52 });
+29 -27
tests/globalSetup.js
··· 1 - import { spawn, execSync } from "child_process"; 2 - 3 - let devServer; 4 - 5 - export async function setup() { 6 - await new Promise((resolve, reject) => { 7 - devServer = spawn("npm", ["run", "dev", "--", "--port", "5599"], { 8 - stdio: ["ignore", "pipe", "pipe"], 9 - detached: false, 10 - }); 11 12 - devServer.stdout.on("data", (data) => { 13 - if (data.toString().includes("Local:")) { 14 - resolve(); 15 - } 16 - }); 17 18 - devServer.stderr.on("data", (data) => { 19 - console.error(data.toString()); 20 - }); 21 22 - devServer.on("error", reject); 23 24 - // Timeout after 30s 25 - setTimeout(() => reject(new Error("Dev server failed to start")), 30000); 26 }); 27 } 28 29 export async function teardown() { 30 - if (devServer) { 31 - devServer.kill("SIGTERM"); 32 - // Also kill any process on the port 33 - try { 34 - execSync("lsof -ti:5599 | xargs kill -9 2>/dev/null || true"); 35 - } catch {} 36 } 37 }
··· 1 + import { spawn } from "child_process"; 2 3 + let server; 4 5 + async function waitForServer(url, timeout = 30000) { 6 + const start = Date.now(); 7 + while (Date.now() - start < timeout) { 8 + try { 9 + const res = await fetch(url); 10 + if (res.ok) return; 11 + } catch { 12 + // Server not ready yet 13 + } 14 + await new Promise((r) => setTimeout(r, 100)); 15 + } 16 + throw new Error("Test server start timeout"); 17 + } 18 19 + export async function setup() { 20 + server = spawn("npx", ["vite", "--port", "5599", "--strictPort"], { 21 + stdio: "inherit", 22 + shell: true, 23 + }); 24 25 + server.on("close", (code) => { 26 + if (code === 1) { 27 + console.error("\n\x1b[31mTest server failed to start (port 5599 in use?)\x1b[0m\n"); 28 + process.exit(1); 29 + } 30 }); 31 + 32 + await waitForServer("http://localhost:5599"); 33 } 34 35 export async function teardown() { 36 + if (server) { 37 + server.kill(); 38 } 39 }
+3 -4
tests/hello.spec.js
··· 1 import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 - import { chromium } from "playwright"; 3 - import { createHelpers } from "./helpers.js"; 4 5 let browser, page, h; 6 7 beforeAll(async () => { 8 - browser = await chromium.launch(); 9 page = await browser.newPage(); 10 h = createHelpers(page); 11 }); ··· 22 await h.load("hello"); 23 24 expect(await h.stepAll()).toMatchInlineSnapshot(`"<h1>Hello World</h1>"`); 25 - expect(await h.preview()).toMatchInlineSnapshot(`"Hello World"`); 26 });
··· 1 import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 + import { createHelpers, launchBrowser } from "./helpers.js"; 3 4 let browser, page, h; 5 6 beforeAll(async () => { 7 + browser = await launchBrowser(); 8 page = await browser.newPage(); 9 h = createHelpers(page); 10 }); ··· 21 await h.load("hello"); 22 23 expect(await h.stepAll()).toMatchInlineSnapshot(`"<h1>Hello World</h1>"`); 24 + expect(await h.preview("Hello World")).toMatchInlineSnapshot(`"Hello World"`); 25 });
+12 -2
tests/helpers.js
··· 1 import { expect } from "vitest"; 2 3 let prevRowTexts = []; 4 let prevStatuses = []; ··· 25 } 26 27 async function getPreviewText() { 28 - return (await frameRef.locator(".preview-container").innerText()).trim().replace(/\s+/g, " "); 29 } 30 31 async function doStep() { ··· 130 return await tree(); 131 } 132 133 - async function preview() { 134 const current = await getPreviewText(); 135 if (current !== prevPreview) { 136 prevPreview = current;
··· 1 import { expect } from "vitest"; 2 + import { chromium } from "playwright"; 3 + 4 + export async function launchBrowser() { 5 + const executablePath = process.env.CHROMIUM_PATH; 6 + return chromium.launch(executablePath ? { executablePath } : undefined); 7 + } 8 9 let prevRowTexts = []; 10 let prevStatuses = []; ··· 31 } 32 33 async function getPreviewText() { 34 + return (await frameRef.locator(".preview-container").innerText()).trim(); 35 } 36 37 async function doStep() { ··· 136 return await tree(); 137 } 138 139 + async function preview(waitFor) { 140 + if (waitFor) { 141 + // Wait for preview to contain the marker 142 + await expect.poll(() => getPreviewText(), { timeout: 10000 }).toContain(waitFor); 143 + } 144 const current = await getPreviewText(); 145 if (current !== prevPreview) { 146 prevPreview = current;
+62 -6
tests/kitchensink.spec.js
··· 1 import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 - import { chromium } from "playwright"; 3 - import { createHelpers } from "./helpers.js"; 4 5 let browser, page, h; 6 7 beforeAll(async () => { 8 - browser = await chromium.launch(); 9 page = await browser.newPage(); 10 h = createHelpers(page); 11 }); ··· 36 </Suspense> 37 </div>" 38 `); 39 - expect(await h.preview()).toMatchInlineSnapshot(`"Kitchen Sink Loading..."`); 40 41 // Step to resolve async content (with delayed promise still pending) 42 expect(await h.stepAll()).toMatchInlineSnapshot(` ··· 133 </Suspense> 134 </div>" 135 `); 136 - expect(await h.preview()).toMatchInlineSnapshot(`"Kitchen Sink Loading..."`); 137 138 // Step to resolve delayed promise 139 expect(await h.stepAll()).toMatchInlineSnapshot(` ··· 230 </Suspense> 231 </div>" 232 `); 233 - expect(await h.preview()).toMatchInlineSnapshot(`"Kitchen Sink Loading..."`); 234 });
··· 1 import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 + import { createHelpers, launchBrowser } from "./helpers.js"; 3 4 let browser, page, h; 5 6 beforeAll(async () => { 7 + browser = await launchBrowser(); 8 page = await browser.newPage(); 9 h = createHelpers(page); 10 }); ··· 35 </Suspense> 36 </div>" 37 `); 38 + expect(await h.preview("Loading...")).toMatchInlineSnapshot(` 39 + "Kitchen Sink 40 + 41 + Loading..." 42 + `); 43 44 // Step to resolve async content (with delayed promise still pending) 45 expect(await h.stepAll()).toMatchInlineSnapshot(` ··· 136 </Suspense> 137 </div>" 138 `); 139 + expect(await h.preview("Loading...")).toMatchInlineSnapshot(` 140 + "Kitchen Sink 141 + 142 + Loading..." 143 + `); 144 145 // Step to resolve delayed promise 146 expect(await h.stepAll()).toMatchInlineSnapshot(` ··· 237 </Suspense> 238 </div>" 239 `); 240 + expect(await h.preview("hello world")).toMatchInlineSnapshot( 241 + ` 242 + "Kitchen Sink 243 + primitives 244 + null: null 245 + true: true 246 + false: false 247 + int: 42 248 + float: 3.14159 249 + string: hello world 250 + empty: 251 + dollar: $special 252 + unicode: Hello 世界 🌍 253 + special 254 + negZero: 0 255 + inf: Infinity 256 + negInf: -Infinity 257 + nan: NaN 258 + types 259 + date: 2024-01-15T12:00:00.000Z 260 + bigint: 12345678901234567890n 261 + symbol: Symbol(mySymbol) 262 + collections 263 + map: Map(2) 264 + set: Set(3) 265 + formData: FormData 266 + blob: Blob(5) 267 + arrays 268 + simple: [3 items] 269 + sparse: [4 items] 270 + nested: [2 items] 271 + objects 272 + simple: {...} 273 + nested: {...} 274 + elements 275 + div: {...} 276 + fragment: [2 items] 277 + suspense: {...} 278 + promises 279 + resolved: {...} 280 + delayed: {...} 281 + iterators 282 + sync: {...} 283 + refs 284 + dup: {...} 285 + cyclic: {...} 286 + action 287 + Call serverAction" 288 + `, 289 + ); 290 });
+105 -13
tests/pagination.spec.js
··· 1 import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 - import { chromium } from "playwright"; 3 - import { createHelpers } from "./helpers.js"; 4 5 let browser, page, h; 6 7 beforeAll(async () => { 8 - browser = await chromium.launch(); 9 page = await browser.newPage(); 10 h = createHelpers(page); 11 }); ··· 32 </Suspense> 33 </div>" 34 `); 35 - expect(await h.preview()).toMatchInlineSnapshot(`"Pagination Loading recipes..."`); 36 37 // Then resolves to Paginator with initial items 38 expect(await h.stepAll()).toMatchInlineSnapshot(` ··· 76 </Suspense> 77 </div>" 78 `); 79 - expect(await h.preview()).toMatchInlineSnapshot(`"Pagination Loading recipes..."`); 80 81 // First Load More 82 await h.frame().locator(".preview-container button").click(); 83 - expect(await h.preview()).toMatchInlineSnapshot( 84 - `"Pagination Pasta Carbonara 25 min · Medium Grilled Cheese 10 min · Easy Loading..."`, 85 ); 86 87 // Action returns new items ··· 123 hasMore: true 124 }" 125 `); 126 - expect(await h.preview()).toMatchInlineSnapshot( 127 - `"Pagination Pasta Carbonara 25 min · Medium Grilled Cheese 10 min · Easy Chicken Stir Fry 20 min · Easy Beef Tacos 30 min · Medium Load More"`, 128 ); 129 130 // Second Load More 131 await h.frame().locator(".preview-container button").click(); 132 - expect(await h.preview()).toMatchInlineSnapshot( 133 - `"Pagination Pasta Carbonara 25 min · Medium Grilled Cheese 10 min · Easy Chicken Stir Fry 20 min · Easy Beef Tacos 30 min · Medium Loading..."`, 134 ); 135 136 // Final items, hasMore: false ··· 172 hasMore: false 173 }" 174 `); 175 - expect(await h.preview()).toMatchInlineSnapshot( 176 - `"Pagination Pasta Carbonara 25 min · Medium Grilled Cheese 10 min · Easy Chicken Stir Fry 20 min · Easy Beef Tacos 30 min · Medium Caesar Salad 15 min · Easy Mushroom Risotto 45 min · Hard"`, 177 ); 178 179 // No more items - button should be gone
··· 1 import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 + import { createHelpers, launchBrowser } from "./helpers.js"; 3 4 let browser, page, h; 5 6 beforeAll(async () => { 7 + browser = await launchBrowser(); 8 page = await browser.newPage(); 9 h = createHelpers(page); 10 }); ··· 31 </Suspense> 32 </div>" 33 `); 34 + expect(await h.preview("Loading recipes")).toMatchInlineSnapshot( 35 + ` 36 + "Pagination 37 + 38 + Loading recipes..." 39 + `, 40 + ); 41 42 // Then resolves to Paginator with initial items 43 expect(await h.stepAll()).toMatchInlineSnapshot(` ··· 81 </Suspense> 82 </div>" 83 `); 84 + expect(await h.preview("Grilled Cheese")).toMatchInlineSnapshot( 85 + ` 86 + "Pagination 87 + Pasta Carbonara 88 + 89 + 25 min · Medium 90 + 91 + Grilled Cheese 92 + 93 + 10 min · Easy 94 + 95 + Load More" 96 + `, 97 + ); 98 99 // First Load More 100 await h.frame().locator(".preview-container button").click(); 101 + expect(await h.preview("Loading...")).toMatchInlineSnapshot( 102 + ` 103 + "Pagination 104 + Pasta Carbonara 105 + 106 + 25 min · Medium 107 + 108 + Grilled Cheese 109 + 110 + 10 min · Easy 111 + 112 + Loading..." 113 + `, 114 ); 115 116 // Action returns new items ··· 152 hasMore: true 153 }" 154 `); 155 + expect(await h.preview("Beef Tacos")).toMatchInlineSnapshot( 156 + ` 157 + "Pagination 158 + Pasta Carbonara 159 + 160 + 25 min · Medium 161 + 162 + Grilled Cheese 163 + 164 + 10 min · Easy 165 + 166 + Chicken Stir Fry 167 + 168 + 20 min · Easy 169 + 170 + Beef Tacos 171 + 172 + 30 min · Medium 173 + 174 + Load More" 175 + `, 176 ); 177 178 // Second Load More 179 await h.frame().locator(".preview-container button").click(); 180 + expect(await h.preview("Loading...")).toMatchInlineSnapshot( 181 + ` 182 + "Pagination 183 + Pasta Carbonara 184 + 185 + 25 min · Medium 186 + 187 + Grilled Cheese 188 + 189 + 10 min · Easy 190 + 191 + Chicken Stir Fry 192 + 193 + 20 min · Easy 194 + 195 + Beef Tacos 196 + 197 + 30 min · Medium 198 + 199 + Loading..." 200 + `, 201 ); 202 203 // Final items, hasMore: false ··· 239 hasMore: false 240 }" 241 `); 242 + expect(await h.preview("Mushroom Risotto")).toMatchInlineSnapshot( 243 + ` 244 + "Pagination 245 + Pasta Carbonara 246 + 247 + 25 min · Medium 248 + 249 + Grilled Cheese 250 + 251 + 10 min · Easy 252 + 253 + Chicken Stir Fry 254 + 255 + 20 min · Easy 256 + 257 + Beef Tacos 258 + 259 + 30 min · Medium 260 + 261 + Caesar Salad 262 + 263 + 15 min · Easy 264 + 265 + Mushroom Risotto 266 + 267 + 45 min · Hard" 268 + `, 269 ); 270 271 // No more items - button should be gone
+13 -12
tests/refresh.spec.js
··· 1 import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 - import { chromium } from "playwright"; 3 - import { createHelpers } from "./helpers.js"; 4 5 let browser, page, h; 6 7 beforeAll(async () => { 8 - browser = await chromium.launch(); 9 page = await browser.newPage(); 10 h = createHelpers(page); 11 }); ··· 33 </Suspense> 34 </div>" 35 `); 36 - expect(await h.preview()).toMatchInlineSnapshot( 37 - `"Router Refresh Client state persists across server navigations Loading..."`, 38 ); 39 40 // Step to resolve async Timer content (color is random hsl value) ··· 42 expect(tree).toMatch(/Router Refresh/); 43 expect(tree).toMatch(/<Timer color="hsl\(\d+, 70%, 85%\)" \/>/); 44 45 - // Wait for preview to render the timer (shows "0s" or similar) 46 - await h.waitFor( 47 - () => /\d+s/.test(document.querySelector(".preview-container")?.textContent || ""), 48 - { timeout: 5000 }, 49 - ); 50 - const preview = await h.preview(); 51 - expect(preview).toMatch(/Router Refresh.*\d+s.*Refresh/); 52 });
··· 1 import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 + import { createHelpers, launchBrowser } from "./helpers.js"; 3 4 let browser, page, h; 5 6 beforeAll(async () => { 7 + browser = await launchBrowser(); 8 page = await browser.newPage(); 9 h = createHelpers(page); 10 }); ··· 32 </Suspense> 33 </div>" 34 `); 35 + expect(await h.preview("Loading...")).toMatchInlineSnapshot( 36 + ` 37 + "Router Refresh 38 + 39 + Client state persists across server navigations 40 + 41 + Loading..." 42 + `, 43 ); 44 45 // Step to resolve async Timer content (color is random hsl value) ··· 47 expect(tree).toMatch(/Router Refresh/); 48 expect(tree).toMatch(/<Timer color="hsl\(\d+, 70%, 85%\)" \/>/); 49 50 + // Wait for preview to render the timer 51 + const preview = await h.preview("Timer:"); 52 + expect(preview).toMatch(/Timer: \d+s[\s\S]*Refresh/); 53 });
+1 -1
vitest.config.js
··· 2 3 export default defineConfig({ 4 test: { 5 - testTimeout: 15000, 6 fileParallelism: true, 7 globalSetup: "./tests/globalSetup.js", 8 },
··· 2 3 export default defineConfig({ 4 test: { 5 + testTimeout: 30000, 6 fileParallelism: true, 7 globalSetup: "./tests/globalSetup.js", 8 },