File size: 5,437 Bytes
cd8bd0a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
// @vitest-environment jsdom
import React, { act } from "react";
import { createRoot } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { DEFAULT_DISPLAY_BASE_URL } from "../useDisplayBaseUrl";

const cleanupCallbacks: Array<() => void> = [];

function makeContainer(): HTMLElement {
  const container = document.createElement("div");
  document.body.appendChild(container);
  cleanupCallbacks.push(() => {
    container.remove();
  });
  return container;
}

describe("useDisplayBaseUrl", () => {
  beforeEach(() => {
    (
      globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }
    ).IS_REACT_ACT_ENVIRONMENT = true;
  });

  afterEach(() => {
    while (cleanupCallbacks.length > 0) {
      cleanupCallbacks.pop()?.();
    }
    document.body.innerHTML = "";
    vi.unstubAllEnvs();
    vi.unstubAllGlobals();
  });

  it("returns env value on first render and after mount when NEXT_PUBLIC_BASE_URL is set", async () => {
    vi.stubEnv("NEXT_PUBLIC_BASE_URL", "https://example.com");

    const { useDisplayBaseUrl } = await import("../useDisplayBaseUrl");

    const container = makeContainer();
    const root = createRoot(container);

    function C() {
      const url = useDisplayBaseUrl();
      return <span data-testid="value">{url}</span>;
    }

    // Synchronous act: commits render and flushes synchronous effects.
    // The queueMicrotask in useEffect has not yet fired.
    act(() => {
      root.render(<C />);
    });

    // Env set: first render shows env value (useEffect no-ops when envValue is set)
    expect(container.querySelector('[data-testid="value"]')?.textContent).toBe(
      "https://example.com"
    );

    // Flush microtasks and any remaining async work
    await act(async () => {});

    // Env still wins after mount
    expect(container.querySelector('[data-testid="value"]')?.textContent).toBe(
      "https://example.com"
    );
  });

  it("returns DEFAULT_DISPLAY_BASE_URL on first render and origin after mount when env unset", async () => {
    vi.stubEnv("NEXT_PUBLIC_BASE_URL", "");

    const { useDisplayBaseUrl } = await import("../useDisplayBaseUrl");

    const container = makeContainer();
    const root = createRoot(container);

    function C() {
      const url = useDisplayBaseUrl();
      return <span data-testid="value">{url}</span>;
    }

    // Synchronous act commits render. useEffect fires but queueMicrotask
    // schedules setState for after this act() call returns.
    act(() => {
      root.render(<C />);
    });

    // Pre-microtask: DOM still shows the initial state (DEFAULT_DISPLAY_BASE_URL)
    expect(container.querySelector('[data-testid="value"]')?.textContent).toBe(
      DEFAULT_DISPLAY_BASE_URL
    );

    // Flush queueMicrotask callback (setState) and resulting re-render
    await act(async () => {});

    // After mount: swaps to window.location.origin
    expect(container.querySelector('[data-testid="value"]')?.textContent).toBe(
      window.location.origin
    );
  });

  it("trims and strips trailing slash from env value", async () => {
    vi.stubEnv("NEXT_PUBLIC_BASE_URL", "  https://x.com/  ");

    const { useDisplayBaseUrl } = await import("../useDisplayBaseUrl");

    const container = makeContainer();
    const root = createRoot(container);

    function C() {
      const url = useDisplayBaseUrl();
      return <span data-testid="value">{url}</span>;
    }

    await act(async () => {
      root.render(<C />);
    });

    expect(container.querySelector('[data-testid="value"]')?.textContent).toBe("https://x.com");
  });

  it("strips trailing slash from window.location.origin after mount", async () => {
    vi.stubEnv("NEXT_PUBLIC_BASE_URL", "");

    // Stub window.location with trailing slash on origin
    vi.stubGlobal("location", { origin: "http://192.168.13.62:20128/" });

    const { useDisplayBaseUrl } = await import("../useDisplayBaseUrl");

    const container = makeContainer();
    const root = createRoot(container);

    function C() {
      const url = useDisplayBaseUrl();
      return <span data-testid="value">{url}</span>;
    }

    // Render and flush all effects including queueMicrotask
    await act(async () => {
      root.render(<C />);
    });

    expect(container.querySelector('[data-testid="value"]')?.textContent).toBe(
      "http://192.168.13.62:20128"
    );
  });

  it("treats empty-string env as unset and falls through to origin after mount", async () => {
    vi.stubEnv("NEXT_PUBLIC_BASE_URL", "");

    const { useDisplayBaseUrl } = await import("../useDisplayBaseUrl");

    const container = makeContainer();
    const root = createRoot(container);

    function C() {
      const url = useDisplayBaseUrl();
      return <span data-testid="value">{url}</span>;
    }

    // Synchronous act: render committed, useEffect fired, microtask queued but not yet run
    act(() => {
      root.render(<C />);
    });

    // Empty env treated as unset → initial state is DEFAULT_DISPLAY_BASE_URL
    expect(container.querySelector('[data-testid="value"]')?.textContent).toBe(
      DEFAULT_DISPLAY_BASE_URL
    );

    // Flush queueMicrotask + re-render
    await act(async () => {});

    // After mount: resolves to origin
    const result = container.querySelector('[data-testid="value"]')?.textContent;
    expect(result).toBe(window.location.origin.replace(/\/+$/, ""));
  });
});