๐Ÿ“ฆ cloudflare / vinext

๐Ÿ“„ error-boundary.test.ts ยท 138 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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138/**
 * Error boundary unit tests.
 *
 * Tests the ErrorBoundary and NotFoundBoundary components that handle
 * error.tsx and not-found.tsx rendering in the App Router. Verifies
 * correct digest handling, error propagation, and the reset mechanism.
 *
 * These test the same digest-based error routing that Next.js uses
 * to distinguish between notFound(), redirect(), forbidden(), and
 * genuine application errors.
 */
import { describe, it, expect, beforeAll, vi } from "vitest";

// Mock next/navigation since it's a virtual module provided by the vinext plugin.
// We only need usePathname for the NotFoundBoundary wrapper, not for the static
// getDerivedStateFromError methods we're testing.
vi.mock("next/navigation", () => ({
  usePathname: () => "/",
}));
// The error boundary is primarily a client-side component.

// Test the digest detection patterns used by the boundaries
describe("ErrorBoundary digest patterns", () => {
  it("NEXT_NOT_FOUND digest matches legacy not-found pattern", () => {
    const error = new Error("Not Found");
    (error as any).digest = "NEXT_NOT_FOUND";

    // The ErrorBoundary re-throws errors with these digests
    const digest = (error as any).digest;
    expect(digest === "NEXT_NOT_FOUND").toBe(true);
  });

  it("NEXT_HTTP_ERROR_FALLBACK;404 matches new not-found pattern", () => {
    const error = new Error("Not Found");
    (error as any).digest = "NEXT_HTTP_ERROR_FALLBACK;404";

    const digest = (error as any).digest;
    expect(digest.startsWith("NEXT_HTTP_ERROR_FALLBACK;")).toBe(true);
    expect(digest).toBe("NEXT_HTTP_ERROR_FALLBACK;404");
  });

  it("NEXT_HTTP_ERROR_FALLBACK;403 matches forbidden pattern", () => {
    const error = new Error("Forbidden");
    (error as any).digest = "NEXT_HTTP_ERROR_FALLBACK;403";

    const digest = (error as any).digest;
    expect(digest.startsWith("NEXT_HTTP_ERROR_FALLBACK;")).toBe(true);
  });

  it("NEXT_HTTP_ERROR_FALLBACK;401 matches unauthorized pattern", () => {
    const error = new Error("Unauthorized");
    (error as any).digest = "NEXT_HTTP_ERROR_FALLBACK;401";

    const digest = (error as any).digest;
    expect(digest.startsWith("NEXT_HTTP_ERROR_FALLBACK;")).toBe(true);
  });

  it("NEXT_REDIRECT digest matches redirect pattern", () => {
    const error = new Error("Redirect");
    (error as any).digest = "NEXT_REDIRECT;replace;/login;307;";

    const digest = (error as any).digest;
    expect(digest.startsWith("NEXT_REDIRECT;")).toBe(true);
  });

  it("regular errors (no digest) are caught by ErrorBoundary", () => {
    const error = new Error("Something broke");
    // No digest property โ€” this is a normal error
    expect("digest" in error).toBe(false);
  });

  it("errors with non-special digests are caught by ErrorBoundary", () => {
    const error = new Error("Custom error");
    (error as any).digest = "SOME_CUSTOM_DIGEST";

    const digest = (error as any).digest;
    // These should NOT be re-thrown โ€” they should be caught
    expect(digest === "NEXT_NOT_FOUND").toBe(false);
    expect(digest.startsWith("NEXT_HTTP_ERROR_FALLBACK;")).toBe(false);
    expect(digest.startsWith("NEXT_REDIRECT;")).toBe(false);
  });
});

// Test the actual ErrorBoundary.getDerivedStateFromError classification.
// The real method THROWS for digest errors (re-throwing them past the boundary)
// and returns { error } for regular errors (catching them).
describe("ErrorBoundary digest classification (actual class)", () => {
  let ErrorBoundary: any;

  beforeAll(async () => {
    const mod = await import("../packages/vinext/src/shims/error-boundary.js");
    ErrorBoundary = mod.ErrorBoundary;
  });

  it("rethrows NEXT_NOT_FOUND", () => {
    const e = Object.assign(new Error(), { digest: "NEXT_NOT_FOUND" });
    expect(() => ErrorBoundary.getDerivedStateFromError(e)).toThrow(e);
  });

  it("rethrows NEXT_HTTP_ERROR_FALLBACK;404", () => {
    const e = Object.assign(new Error(), { digest: "NEXT_HTTP_ERROR_FALLBACK;404" });
    expect(() => ErrorBoundary.getDerivedStateFromError(e)).toThrow(e);
  });

  it("rethrows NEXT_HTTP_ERROR_FALLBACK;403", () => {
    const e = Object.assign(new Error(), { digest: "NEXT_HTTP_ERROR_FALLBACK;403" });
    expect(() => ErrorBoundary.getDerivedStateFromError(e)).toThrow(e);
  });

  it("rethrows NEXT_HTTP_ERROR_FALLBACK;401", () => {
    const e = Object.assign(new Error(), { digest: "NEXT_HTTP_ERROR_FALLBACK;401" });
    expect(() => ErrorBoundary.getDerivedStateFromError(e)).toThrow(e);
  });

  it("rethrows NEXT_REDIRECT", () => {
    const e = Object.assign(new Error(), { digest: "NEXT_REDIRECT;replace;/login;307;" });
    expect(() => ErrorBoundary.getDerivedStateFromError(e)).toThrow(e);
  });

  it("catches regular errors (no digest)", () => {
    const e = new Error("oops");
    const state = ErrorBoundary.getDerivedStateFromError(e);
    expect(state).toEqual({ error: e });
  });

  it("catches errors with unknown digest", () => {
    const e = Object.assign(new Error(), { digest: "CUSTOM_ERROR" });
    const state = ErrorBoundary.getDerivedStateFromError(e);
    expect(state).toEqual({ error: e });
  });

  it("catches errors with empty digest", () => {
    const e = Object.assign(new Error(), { digest: "" });
    const state = ErrorBoundary.getDerivedStateFromError(e);
    expect(state).toEqual({ error: e });
  });
});