|
1 | 1 | import { describe } from 'vitest'
|
2 |
| -import { candidate, css, fetchStyles, js, json, retryAssertion, test } from '../utils' |
| 2 | +import { candidate, css, fetchStyles, js, json, jsx, retryAssertion, test, txt } from '../utils' |
3 | 3 |
|
4 | 4 | test(
|
5 | 5 | 'production build',
|
@@ -356,3 +356,131 @@ test(
|
356 | 356 | })
|
357 | 357 | },
|
358 | 358 | )
|
| 359 | + |
| 360 | +test( |
| 361 | + 'changes to `public/` should not trigger an infinite loop', |
| 362 | + { |
| 363 | + fs: { |
| 364 | + 'package.json': json` |
| 365 | + { |
| 366 | + "dependencies": { |
| 367 | + "react": "^18", |
| 368 | + "react-dom": "^18", |
| 369 | + "next": "^15", |
| 370 | + "@ducanh2912/next-pwa": "^10.2.9" |
| 371 | + }, |
| 372 | + "devDependencies": { |
| 373 | + "@tailwindcss/postcss": "workspace:^", |
| 374 | + "tailwindcss": "workspace:^" |
| 375 | + } |
| 376 | + } |
| 377 | + `, |
| 378 | + '.gitignore': txt` |
| 379 | + .next/ |
| 380 | + public/workbox-*.js |
| 381 | + public/sw.js |
| 382 | + `, |
| 383 | + 'postcss.config.mjs': js` |
| 384 | + export default { |
| 385 | + plugins: { |
| 386 | + '@tailwindcss/postcss': {}, |
| 387 | + }, |
| 388 | + } |
| 389 | + `, |
| 390 | + 'next.config.mjs': js` |
| 391 | + import withPWA from '@ducanh2912/next-pwa' |
| 392 | +
|
| 393 | + const pwaConfig = { |
| 394 | + dest: 'public', |
| 395 | + register: true, |
| 396 | + skipWaiting: true, |
| 397 | + reloadOnOnline: false, |
| 398 | + cleanupOutdatedCaches: true, |
| 399 | + clientsClaim: true, |
| 400 | + maximumFileSizeToCacheInBytes: 20 * 1024 * 1024, |
| 401 | + } |
| 402 | +
|
| 403 | + const nextConfig = {} |
| 404 | +
|
| 405 | + const configWithPWA = withPWA(pwaConfig)(nextConfig) |
| 406 | +
|
| 407 | + export default configWithPWA |
| 408 | + `, |
| 409 | + 'app/layout.js': js` |
| 410 | + import './globals.css' |
| 411 | +
|
| 412 | + export default function RootLayout({ children }) { |
| 413 | + return ( |
| 414 | + <html> |
| 415 | + <body>{children}</body> |
| 416 | + </html> |
| 417 | + ) |
| 418 | + } |
| 419 | + `, |
| 420 | + 'app/page.js': js` |
| 421 | + export default function Page() { |
| 422 | + return <div className="flex"></div> |
| 423 | + } |
| 424 | + `, |
| 425 | + 'app/globals.css': css` |
| 426 | + @import 'tailwindcss/theme'; |
| 427 | + @import 'tailwindcss/utilities'; |
| 428 | + `, |
| 429 | + }, |
| 430 | + }, |
| 431 | + async ({ spawn, fs, expect }) => { |
| 432 | + let process = await spawn('pnpm next dev') |
| 433 | + |
| 434 | + let url = '' |
| 435 | + await process.onStdout((m) => { |
| 436 | + let match = /Local:\s*(http.*)/.exec(m) |
| 437 | + if (match) url = match[1] |
| 438 | + return Boolean(url) |
| 439 | + }) |
| 440 | + |
| 441 | + await process.onStdout((m) => m.includes('Ready in')) |
| 442 | + |
| 443 | + await retryAssertion(async () => { |
| 444 | + let css = await fetchStyles(url) |
| 445 | + expect(css).toContain(candidate`flex`) |
| 446 | + expect(css).not.toContain(candidate`underline`) |
| 447 | + }) |
| 448 | + |
| 449 | + await fs.write( |
| 450 | + 'app/page.js', |
| 451 | + jsx` |
| 452 | + export default function Page() { |
| 453 | + return <div className="flex underline"></div> |
| 454 | + } |
| 455 | + `, |
| 456 | + ) |
| 457 | + await process.onStdout((m) => m.includes('Compiled in')) |
| 458 | + |
| 459 | + await retryAssertion(async () => { |
| 460 | + let css = await fetchStyles(url) |
| 461 | + expect(css).toContain(candidate`flex`) |
| 462 | + expect(css).toContain(candidate`underline`) |
| 463 | + }) |
| 464 | + // Flush all existing messages in the queue |
| 465 | + process.flush() |
| 466 | + |
| 467 | + // Fetch the styles one more time, to ensure we see the latest version of |
| 468 | + // the CSS |
| 469 | + await fetchStyles(url) |
| 470 | + |
| 471 | + // At this point, no changes should triger a compile step. If we see any |
| 472 | + // changes, there is an infinite loop because we (the user) didn't write any |
| 473 | + // files to disk. |
| 474 | + // |
| 475 | + // Ensure there are no more changes in stdout (signaling no infinite loop) |
| 476 | + let result = await Promise.race([ |
| 477 | + // If this succeeds, it means that it saw another change which indicates |
| 478 | + // an infinite loop. |
| 479 | + process.onStdout((m) => m.includes('Compiled in')).then(() => 'infinite loop detected'), |
| 480 | + |
| 481 | + // There should be no changes in stdout |
| 482 | + new Promise((resolve) => setTimeout(() => resolve('no infinite loop detected'), 2_000)), |
| 483 | + ]) |
| 484 | + expect(result).toBe('no infinite loop detected') |
| 485 | + }, |
| 486 | +) |
0 commit comments