|
1 | 1 | import { test, expect } from "@playwright/test";
|
| 2 | +import { PassThrough } from "node:stream"; |
2 | 3 |
|
3 | 4 | import {
|
4 | 5 | createAppFixture,
|
5 | 6 | createFixture,
|
6 | 7 | js,
|
7 | 8 | } from "./helpers/create-fixture.js";
|
8 | 9 | import { PlaywrightFixture } from "./helpers/playwright-fixture.js";
|
| 10 | +import { reactRouterConfig } from "./helpers/vite.js"; |
9 | 11 |
|
10 | 12 | function getFiles() {
|
11 | 13 | return {
|
@@ -118,6 +120,10 @@ test.describe("Fog of War", () => {
|
118 | 120 | let res = await fixture.requestDocument("/");
|
119 | 121 | let html = await res.text();
|
120 | 122 |
|
| 123 | + expect(html).toContain("window.__reactRouterManifest = {"); |
| 124 | + expect(html).not.toContain( |
| 125 | + '<link rel="modulepreload" href="/assets/manifest-' |
| 126 | + ); |
121 | 127 | expect(html).toContain('"root": {');
|
122 | 128 | expect(html).toContain('"routes/_index": {');
|
123 | 129 | expect(html).not.toContain('"routes/a"');
|
@@ -1402,4 +1408,218 @@ test.describe("Fog of War", () => {
|
1402 | 1408 | await app.clickLink("/a");
|
1403 | 1409 | await page.waitForSelector("#a-index");
|
1404 | 1410 | });
|
| 1411 | + |
| 1412 | + test("allows configuration of the manifest path", async ({ page }) => { |
| 1413 | + let fixture = await createFixture({ |
| 1414 | + files: { |
| 1415 | + ...getFiles(), |
| 1416 | + "react-router.config.ts": reactRouterConfig({ |
| 1417 | + routeDiscovery: { mode: "lazy", manifestPath: "/custom-manifest" }, |
| 1418 | + }), |
| 1419 | + }, |
| 1420 | + }); |
| 1421 | + let appFixture = await createAppFixture(fixture); |
| 1422 | + let app = new PlaywrightFixture(appFixture, page); |
| 1423 | + |
| 1424 | + let wrongManifestRequests: string[] = []; |
| 1425 | + let manifestRequests: string[] = []; |
| 1426 | + page.on("request", (req) => { |
| 1427 | + if (req.url().includes("/__manifest")) { |
| 1428 | + wrongManifestRequests.push(req.url()); |
| 1429 | + } |
| 1430 | + if (req.url().includes("/custom-manifest")) { |
| 1431 | + manifestRequests.push(req.url()); |
| 1432 | + } |
| 1433 | + }); |
| 1434 | + |
| 1435 | + await app.goto("/", true); |
| 1436 | + expect( |
| 1437 | + await page.evaluate(() => |
| 1438 | + Object.keys((window as any).__reactRouterManifest.routes) |
| 1439 | + ) |
| 1440 | + ).toEqual(["root", "routes/_index", "routes/a"]); |
| 1441 | + expect(manifestRequests).toEqual([ |
| 1442 | + expect.stringMatching(/\/custom-manifest\?p=%2F&p=%2Fa&version=/), |
| 1443 | + ]); |
| 1444 | + manifestRequests = []; |
| 1445 | + |
| 1446 | + await app.clickLink("/a"); |
| 1447 | + await page.waitForSelector("#a"); |
| 1448 | + expect(await app.getHtml("#a")).toBe(`<h1 id="a">A: A LOADER</h1>`); |
| 1449 | + // Wait for eager discovery to kick off |
| 1450 | + await new Promise((r) => setTimeout(r, 500)); |
| 1451 | + expect(manifestRequests).toEqual([ |
| 1452 | + expect.stringMatching(/\/custom-manifest\?p=%2Fa%2Fb&version=/), |
| 1453 | + ]); |
| 1454 | + |
| 1455 | + expect(wrongManifestRequests).toEqual([]); |
| 1456 | + }); |
| 1457 | + |
| 1458 | + test.describe("routeDiscovery=initial", () => { |
| 1459 | + test("loads full manifest on initial load", async ({ page }) => { |
| 1460 | + let fixture = await createFixture({ |
| 1461 | + files: { |
| 1462 | + ...getFiles(), |
| 1463 | + "react-router.config.ts": reactRouterConfig({ |
| 1464 | + routeDiscovery: { mode: "initial" }, |
| 1465 | + }), |
| 1466 | + "app/entry.client.tsx": js` |
| 1467 | + import { HydratedRouter } from "react-router/dom"; |
| 1468 | + import { startTransition, StrictMode } from "react"; |
| 1469 | + import { hydrateRoot } from "react-dom/client"; |
| 1470 | + startTransition(() => { |
| 1471 | + hydrateRoot( |
| 1472 | + document, |
| 1473 | + <StrictMode> |
| 1474 | + <HydratedRouter discover={"none"} /> |
| 1475 | + </StrictMode> |
| 1476 | + ); |
| 1477 | + }); |
| 1478 | + `, |
| 1479 | + }, |
| 1480 | + }); |
| 1481 | + let appFixture = await createAppFixture(fixture); |
| 1482 | + |
| 1483 | + let manifestRequests: string[] = []; |
| 1484 | + page.on("request", (req) => { |
| 1485 | + if (req.url().includes("/__manifest")) { |
| 1486 | + manifestRequests.push(req.url()); |
| 1487 | + } |
| 1488 | + }); |
| 1489 | + |
| 1490 | + let app = new PlaywrightFixture(appFixture, page); |
| 1491 | + let res = await fixture.requestDocument("/"); |
| 1492 | + let html = await res.text(); |
| 1493 | + |
| 1494 | + expect(html).not.toContain("window.__reactRouterManifest = {"); |
| 1495 | + expect(html).toContain( |
| 1496 | + '<link rel="modulepreload" href="/assets/manifest-' |
| 1497 | + ); |
| 1498 | + |
| 1499 | + // Linking to A succeeds |
| 1500 | + await app.goto("/", true); |
| 1501 | + expect( |
| 1502 | + await page.evaluate(() => |
| 1503 | + Object.keys((window as any).__reactRouterManifest.routes) |
| 1504 | + ) |
| 1505 | + ).toEqual([ |
| 1506 | + "root", |
| 1507 | + "routes/_index", |
| 1508 | + "routes/a", |
| 1509 | + "routes/a.b", |
| 1510 | + "routes/a.b.c", |
| 1511 | + ]); |
| 1512 | + |
| 1513 | + await app.clickLink("/a"); |
| 1514 | + await page.waitForSelector("#a"); |
| 1515 | + expect(await app.getHtml("#a")).toBe(`<h1 id="a">A: A LOADER</h1>`); |
| 1516 | + expect(manifestRequests).toEqual([]); |
| 1517 | + }); |
| 1518 | + |
| 1519 | + test("defaults to `routeDiscovery=initial` when `ssr:false` is set", async ({ |
| 1520 | + page, |
| 1521 | + }) => { |
| 1522 | + let fixture = await createFixture({ |
| 1523 | + spaMode: true, |
| 1524 | + files: { |
| 1525 | + "react-router.config.ts": reactRouterConfig({ |
| 1526 | + ssr: false, |
| 1527 | + }), |
| 1528 | + "app/root.tsx": js` |
| 1529 | + import * as React from "react"; |
| 1530 | + import { Link, Links, Meta, Outlet, Scripts } from "react-router"; |
| 1531 | + export default function Root() { |
| 1532 | + let [showLink, setShowLink] = React.useState(false); |
| 1533 | + return ( |
| 1534 | + <html lang="en"> |
| 1535 | + <head> |
| 1536 | + <Meta /> |
| 1537 | + <Links /> |
| 1538 | + </head> |
| 1539 | + <body> |
| 1540 | + <Link to="/">Home</Link><br/> |
| 1541 | + <Link to="/a">/a</Link><br/> |
| 1542 | + <Outlet /> |
| 1543 | + <Scripts /> |
| 1544 | + </body> |
| 1545 | + </html> |
| 1546 | + ); |
| 1547 | + } |
| 1548 | + `, |
| 1549 | + "app/routes/_index.tsx": js` |
| 1550 | + export default function Index() { |
| 1551 | + return <h1 id="index">Index</h1> |
| 1552 | + } |
| 1553 | + `, |
| 1554 | + |
| 1555 | + "app/routes/a.tsx": js` |
| 1556 | + export function clientLoader({ request }) { |
| 1557 | + return { message: "A LOADER" }; |
| 1558 | + } |
| 1559 | + export default function Index({ loaderData }) { |
| 1560 | + return <h1 id="a">A: {loaderData.message}</h1> |
| 1561 | + } |
| 1562 | + `, |
| 1563 | + }, |
| 1564 | + }); |
| 1565 | + let appFixture = await createAppFixture(fixture); |
| 1566 | + |
| 1567 | + let manifestRequests: string[] = []; |
| 1568 | + page.on("request", (req) => { |
| 1569 | + if (req.url().includes("/__manifest")) { |
| 1570 | + manifestRequests.push(req.url()); |
| 1571 | + } |
| 1572 | + }); |
| 1573 | + |
| 1574 | + let app = new PlaywrightFixture(appFixture, page); |
| 1575 | + let res = await fixture.requestDocument("/"); |
| 1576 | + let html = await res.text(); |
| 1577 | + |
| 1578 | + expect(html).toContain('"routeDiscovery":{"mode":"initial"}'); |
| 1579 | + |
| 1580 | + await app.goto("/", true); |
| 1581 | + await page.waitForSelector("#index"); |
| 1582 | + await app.clickLink("/a"); |
| 1583 | + await page.waitForSelector("#a"); |
| 1584 | + expect(await app.getHtml("#a")).toBe(`<h1 id="a">A: A LOADER</h1>`); |
| 1585 | + expect(manifestRequests).toEqual([]); |
| 1586 | + }); |
| 1587 | + |
| 1588 | + test("Errors if you try to set routeDiscovery=lazy and ssr:false", async () => { |
| 1589 | + let ogConsole = console.error; |
| 1590 | + console.error = () => {}; |
| 1591 | + let buildStdio = new PassThrough(); |
| 1592 | + let err; |
| 1593 | + try { |
| 1594 | + await createFixture({ |
| 1595 | + buildStdio, |
| 1596 | + spaMode: true, |
| 1597 | + files: { |
| 1598 | + ...getFiles(), |
| 1599 | + "react-router.config.ts": reactRouterConfig({ |
| 1600 | + ssr: false, |
| 1601 | + routeDiscovery: { mode: "lazy" }, |
| 1602 | + }), |
| 1603 | + }, |
| 1604 | + }); |
| 1605 | + } catch (e) { |
| 1606 | + err = e; |
| 1607 | + } |
| 1608 | + |
| 1609 | + let chunks: Buffer[] = []; |
| 1610 | + let buildOutput = await new Promise<string>((resolve, reject) => { |
| 1611 | + buildStdio.on("data", (chunk) => chunks.push(Buffer.from(chunk))); |
| 1612 | + buildStdio.on("error", (err) => reject(err)); |
| 1613 | + buildStdio.on("end", () => |
| 1614 | + resolve(Buffer.concat(chunks).toString("utf8")) |
| 1615 | + ); |
| 1616 | + }); |
| 1617 | + |
| 1618 | + expect(err).toEqual(new Error("Build failed, check the output above")); |
| 1619 | + expect(buildOutput).toContain( |
| 1620 | + 'Error: The `routeDiscovery.mode` config cannot be set to "lazy" when setting `ssr:false`' |
| 1621 | + ); |
| 1622 | + console.error = ogConsole; |
| 1623 | + }); |
| 1624 | + }); |
1405 | 1625 | });
|
0 commit comments