๐Ÿ“ฆ cloudflare / vinext

๐Ÿ“„ dynamic.spec.ts ยท 121 lines
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121/**
 * Next.js Compat E2E: next/dynamic
 *
 * Ported from: https://github.com/vercel/next.js/blob/canary/test/e2e/app-dir/dynamic/dynamic.test.ts
 *
 * Browser-level tests for next/dynamic behavior:
 * - ssr:false components appear after hydration
 * - React.lazy and dynamic() components are interactive after hydration
 * - Named exports work after hydration
 */

import { test, expect } from "@playwright/test";

const BASE = "http://localhost:4174";

async function waitForHydration(page: import("@playwright/test").Page) {
  await expect(async () => {
    const ready = await page.evaluate(
      () => !!(window as any).__VINEXT_RSC_ROOT__,
    );
    expect(ready).toBe(true);
  }).toPass({ timeout: 10_000 });
}

test.describe("Next.js compat: next/dynamic (browser)", () => {
  // Next.js: 'should handle next/dynamic in hydration correctly'
  // Source: https://github.com/vercel/next.js/blob/canary/test/e2e/app-dir/dynamic/dynamic.test.ts#L29-L36
  //
  // After hydration, the ssr:false component should appear in the DOM.
  test("ssr:false component appears after hydration", async ({ page }) => {
    await page.goto(`${BASE}/nextjs-compat/dynamic`);
    await waitForHydration(page);

    // The ssr:false component should now be visible after client-side rendering
    await expect(async () => {
      const text = await page
        .locator("#css-text-dynamic-no-ssr-client")
        .textContent();
      expect(text).toContain("next-dynamic dynamic no ssr on client");
    }).toPass({ timeout: 10_000 });
  });

  // Verify SSR-rendered dynamic components are still present after hydration
  test("dynamic() components remain visible after hydration", async ({
    page,
  }) => {
    await page.goto(`${BASE}/nextjs-compat/dynamic`);
    await waitForHydration(page);

    await expect(page.locator("#css-text-lazy")).toContainText(
      "next-dynamic lazy",
    );
    await expect(page.locator("#css-text-dynamic-server")).toContainText(
      "next-dynamic dynamic on server",
    );
    await expect(page.locator("#css-text-dynamic-client")).toContainText(
      "next-dynamic dynamic on client",
    );
    await expect(
      page.locator("#text-dynamic-server-import-client"),
    ).toContainText("next-dynamic server import client");
  });

  // Next.js: 'should support dynamic import with accessing named exports'
  // Source: https://github.com/vercel/next.js/blob/canary/test/e2e/app-dir/dynamic/dynamic.test.ts#L97-L100
  test("named export via dynamic() renders button after hydration", async ({
    page,
  }) => {
    await page.goto(`${BASE}/nextjs-compat/dynamic/named-export`);
    await waitForHydration(page);

    await expect(page.locator("#client-button")).toHaveText(
      "this is a client button",
    );
  });

  // ssr:false dedicated page โ€” static content present, dynamic appears after hydration
  test("ssr:false page shows dynamic content after hydration", async ({
    page,
  }) => {
    await page.goto(`${BASE}/nextjs-compat/dynamic/ssr-false-only`);

    // Static content should be present immediately
    await expect(page.locator("#static-text")).toHaveText(
      "This is static content",
    );

    await waitForHydration(page);

    // After hydration, the ssr:false component should appear
    await expect(async () => {
      const text = await page
        .locator("#css-text-dynamic-no-ssr-client")
        .textContent();
      expect(text).toContain("next-dynamic dynamic no ssr on client");
    }).toPass({ timeout: 10_000 });
  });

  // ssr:false from a server component โ€” the dynamic shim must be a client
  // module so the RSC serializer emits a client reference instead of
  // executing dynamic() inline and sending null to the client.
  test("ssr:false from server component loads after hydration", async ({
    page,
  }) => {
    await page.goto(`${BASE}/nextjs-compat/dynamic/ssr-false-server`);

    // Server-rendered static content should be present immediately
    await expect(page.locator("#server-text")).toHaveText("Server rendered");

    await waitForHydration(page);

    // After hydration, the ssr:false component should appear
    await expect(async () => {
      const text = await page
        .locator("#css-text-dynamic-no-ssr-client")
        .textContent();
      expect(text).toContain("next-dynamic dynamic no ssr on client");
    }).toPass({ timeout: 10_000 });
  });
});