Skip to content

Commit f60bdf3

Browse files
authored
Merge pull request #1682 from thunderstore-io/12-23-refactor_streamline_beta_switch_initialization_and_add_tests
refactor: streamline beta switch initialization and add tests
2 parents 10e0a7f + b7d5b88 commit f60bdf3

File tree

4 files changed

+270
-120
lines changed

4 files changed

+270
-120
lines changed

apps/cyberstorm-remix/public/cyberstorm-static/scripts/beta-switch.js

Lines changed: 74 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,3 @@
1-
const legacyProd = {
2-
protocol: "https://",
3-
hostname: "thunderstore.io",
4-
port: "",
5-
tld: "io",
6-
};
7-
const betaProd = {
8-
protocol: "https://",
9-
hostname: "new.thunderstore.io",
10-
port: "",
11-
tld: "io",
12-
};
13-
const legacyQA = {
14-
protocol: "https://",
15-
hostname: "thunderstore.dev",
16-
port: "",
17-
tld: "dev",
18-
};
19-
const betaQA = {
20-
protocol: "https://",
21-
hostname: "new.thunderstore.dev",
22-
port: "",
23-
tld: "dev",
24-
};
25-
const legacyDev = {
26-
protocol: "http://",
27-
hostname: "thunderstore.temp",
28-
port: "",
29-
tld: "temp",
30-
};
31-
const betaDev = {
32-
protocol: "http://",
33-
hostname: "new.thunderstore.temp",
34-
port: "",
35-
tld: "temp",
36-
};
371
async function checkBetaRedirect(legacy, beta, goToBetaRoR2) {
382
const legacyOnlyPages = [
393
"/settings",
@@ -124,25 +88,79 @@ async function insertSwitchButton(legacy, beta) {
12488
}
12589
}
12690
}
127-
const legacy = window.location.hostname.endsWith(legacyProd.tld)
128-
? legacyProd
129-
: window.location.hostname.endsWith(legacyQA.tld)
130-
? legacyQA
131-
: legacyDev;
132-
const beta = window.location.hostname.endsWith(betaProd.tld)
133-
? betaProd
134-
: window.location.hostname.endsWith(betaQA.tld)
135-
? betaQA
136-
: betaDev;
137-
async function insertSwitchButtonListener() {
138-
insertSwitchButton(legacy, beta);
139-
document.removeEventListener("DOMContentLoaded", insertSwitchButtonListener);
91+
function hasBrowserGlobals() {
92+
return typeof window !== "undefined" && typeof document !== "undefined";
14093
}
141-
if (
142-
document.readyState === "complete" ||
143-
document.readyState === "interactive"
144-
) {
145-
insertSwitchButton(legacy, beta);
146-
} else {
147-
document.addEventListener("DOMContentLoaded", insertSwitchButtonListener);
94+
export function initBetaSwitch() {
95+
if (!hasBrowserGlobals()) {
96+
return;
97+
}
98+
const globalWindow = window;
99+
const initFlag = "__thunderstore_beta_switch_initialized__";
100+
if (globalWindow[initFlag]) {
101+
return;
102+
}
103+
globalWindow[initFlag] = true;
104+
const legacyProd = {
105+
protocol: "https://",
106+
hostname: "thunderstore.io",
107+
port: "",
108+
tld: "io",
109+
};
110+
const betaProd = {
111+
protocol: "https://",
112+
hostname: "new.thunderstore.io",
113+
port: "",
114+
tld: "io",
115+
};
116+
const legacyQA = {
117+
protocol: "https://",
118+
hostname: "thunderstore.dev",
119+
port: "",
120+
tld: "dev",
121+
};
122+
const betaQA = {
123+
protocol: "https://",
124+
hostname: "new.thunderstore.dev",
125+
port: "",
126+
tld: "dev",
127+
};
128+
const legacyDev = {
129+
protocol: "http://",
130+
hostname: "thunderstore.localhost",
131+
port: "",
132+
tld: "localhost",
133+
};
134+
const betaDev = {
135+
protocol: "http://",
136+
hostname: "new.thunderstore.localhost",
137+
port: "",
138+
tld: "localhost",
139+
};
140+
const legacy = window.location.hostname.endsWith(legacyProd.tld)
141+
? legacyProd
142+
: window.location.hostname.endsWith(legacyQA.tld)
143+
? legacyQA
144+
: legacyDev;
145+
const beta = window.location.hostname.endsWith(betaProd.tld)
146+
? betaProd
147+
: window.location.hostname.endsWith(betaQA.tld)
148+
? betaQA
149+
: betaDev;
150+
async function insertSwitchButtonListener() {
151+
insertSwitchButton(legacy, beta);
152+
document.removeEventListener(
153+
"DOMContentLoaded",
154+
insertSwitchButtonListener
155+
);
156+
}
157+
if (
158+
document.readyState === "complete" ||
159+
document.readyState === "interactive"
160+
) {
161+
insertSwitchButton(legacy, beta);
162+
} else {
163+
document.addEventListener("DOMContentLoaded", insertSwitchButtonListener);
164+
}
148165
}
166+
initBetaSwitch();
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { describe, expect, it, vi } from "vitest";
2+
3+
type TestWindow = {
4+
location: {
5+
hostname: string;
6+
pathname: string;
7+
assign: (url: string) => void;
8+
};
9+
};
10+
11+
type TestElement = {
12+
tag: string;
13+
attributes: Map<string, string>;
14+
setAttribute: (key: string, value: string) => void;
15+
onclick?: (() => void) | null;
16+
innerHTML: string;
17+
cloneNode: (deep?: boolean) => TestElement;
18+
};
19+
20+
type TestDocument = {
21+
readyState: "complete" | "interactive" | "loading";
22+
createElement: (tag: string) => TestElement;
23+
addEventListener: (type: string, listener: () => void) => void;
24+
removeEventListener: (type: string, listener: () => void) => void;
25+
querySelector: (
26+
selector: string
27+
) => { appendChild: (child: TestElement) => void } | null;
28+
};
29+
30+
describe("@thunderstore/beta-switch", () => {
31+
it("can be imported without window/document (SSR-safe)", async () => {
32+
// Ensure SSR-like environment
33+
const g = globalThis as unknown as { window?: unknown; document?: unknown };
34+
delete g.window;
35+
delete g.document;
36+
37+
await expect(import("../index")).resolves.toBeTruthy();
38+
});
39+
40+
it("inserts switch button into #nimbusBeta when container exists", async () => {
41+
const appended: TestElement[] = [];
42+
43+
const desktopContainer = {
44+
appendChild: (child: TestElement) => appended.push(child),
45+
};
46+
47+
const createElement = (tag: string) => {
48+
const element: TestElement = {
49+
tag,
50+
attributes: new Map<string, string>(),
51+
setAttribute: (k: string, v: string) => element.attributes.set(k, v),
52+
onclick: undefined,
53+
innerHTML: "",
54+
cloneNode: () => ({
55+
...element,
56+
attributes: new Map(element.attributes),
57+
}),
58+
};
59+
return element;
60+
};
61+
62+
const addEventListener = vi.fn();
63+
const removeEventListener = vi.fn();
64+
65+
const g = globalThis as unknown as { window?: unknown; document?: unknown };
66+
67+
const w: TestWindow = {
68+
location: {
69+
hostname: "thunderstore.temp",
70+
pathname: "/",
71+
assign: vi.fn(),
72+
},
73+
};
74+
75+
const d: TestDocument = {
76+
readyState: "complete",
77+
createElement,
78+
addEventListener:
79+
addEventListener as unknown as TestDocument["addEventListener"],
80+
removeEventListener:
81+
removeEventListener as unknown as TestDocument["removeEventListener"],
82+
querySelector: (selector: string) =>
83+
selector === "#nimbusBeta" ? desktopContainer : null,
84+
};
85+
86+
g.window = w;
87+
g.document = d;
88+
89+
const mod: typeof import("../index") = await import("../index");
90+
mod.initBetaSwitch();
91+
92+
expect(appended.length).toBe(1);
93+
expect(appended[0].tag).toBe("button");
94+
});
95+
});

packages/beta-switch/src/index.ts

Lines changed: 86 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -7,48 +7,6 @@ type UrlStructure = {
77
tld: string;
88
};
99

10-
const legacyProd: UrlStructure = {
11-
protocol: "https://",
12-
hostname: "thunderstore.io",
13-
port: "",
14-
tld: "io",
15-
};
16-
17-
const betaProd: UrlStructure = {
18-
protocol: "https://",
19-
hostname: "new.thunderstore.io",
20-
port: "",
21-
tld: "io",
22-
};
23-
24-
const legacyQA: UrlStructure = {
25-
protocol: "https://",
26-
hostname: "thunderstore.dev",
27-
port: "",
28-
tld: "dev",
29-
};
30-
31-
const betaQA: UrlStructure = {
32-
protocol: "https://",
33-
hostname: "new.thunderstore.dev",
34-
port: "",
35-
tld: "dev",
36-
};
37-
38-
const legacyDev: UrlStructure = {
39-
protocol: "http://",
40-
hostname: "thunderstore.temp",
41-
port: "",
42-
tld: "temp",
43-
};
44-
45-
const betaDev: UrlStructure = {
46-
protocol: "http://",
47-
hostname: "new.thunderstore.temp",
48-
port: "",
49-
tld: "temp",
50-
};
51-
5210
async function checkBetaRedirect(
5311
legacy: UrlStructure,
5412
beta: UrlStructure,
@@ -178,28 +136,92 @@ async function insertSwitchButton(legacy: UrlStructure, beta: UrlStructure) {
178136
}
179137
}
180138

181-
const legacy = window.location.hostname.endsWith(legacyProd.tld)
182-
? legacyProd
183-
: window.location.hostname.endsWith(legacyQA.tld)
184-
? legacyQA
185-
: legacyDev;
186-
const beta = window.location.hostname.endsWith(betaProd.tld)
187-
? betaProd
188-
: window.location.hostname.endsWith(betaQA.tld)
189-
? betaQA
190-
: betaDev;
191-
192-
async function insertSwitchButtonListener() {
193-
insertSwitchButton(legacy, beta);
194-
document.removeEventListener("DOMContentLoaded", insertSwitchButtonListener);
139+
function hasBrowserGlobals(): boolean {
140+
return typeof window !== "undefined" && typeof document !== "undefined";
195141
}
196142

197-
// Run above code
198-
if (
199-
document.readyState === "complete" ||
200-
document.readyState === "interactive"
201-
) {
202-
insertSwitchButton(legacy, beta);
203-
} else {
204-
document.addEventListener("DOMContentLoaded", insertSwitchButtonListener);
143+
export function initBetaSwitch() {
144+
if (!hasBrowserGlobals()) {
145+
return;
146+
}
147+
148+
const globalWindow = window as unknown as Record<string, unknown>;
149+
const initFlag = "__thunderstore_beta_switch_initialized__";
150+
if (globalWindow[initFlag]) {
151+
return;
152+
}
153+
globalWindow[initFlag] = true;
154+
155+
const legacyProd: UrlStructure = {
156+
protocol: "https://",
157+
hostname: "thunderstore.io",
158+
port: "",
159+
tld: "io",
160+
};
161+
162+
const betaProd: UrlStructure = {
163+
protocol: "https://",
164+
hostname: "new.thunderstore.io",
165+
port: "",
166+
tld: "io",
167+
};
168+
169+
const legacyQA: UrlStructure = {
170+
protocol: "https://",
171+
hostname: "thunderstore.dev",
172+
port: "",
173+
tld: "dev",
174+
};
175+
176+
const betaQA: UrlStructure = {
177+
protocol: "https://",
178+
hostname: "new.thunderstore.dev",
179+
port: "",
180+
tld: "dev",
181+
};
182+
183+
const legacyDev: UrlStructure = {
184+
protocol: "http://",
185+
hostname: "thunderstore.localhost",
186+
port: "",
187+
tld: "localhost",
188+
};
189+
190+
const betaDev: UrlStructure = {
191+
protocol: "http://",
192+
hostname: "new.thunderstore.localhost",
193+
port: "",
194+
tld: "localhost",
195+
};
196+
197+
const legacy = window.location.hostname.endsWith(legacyProd.tld)
198+
? legacyProd
199+
: window.location.hostname.endsWith(legacyQA.tld)
200+
? legacyQA
201+
: legacyDev;
202+
const beta = window.location.hostname.endsWith(betaProd.tld)
203+
? betaProd
204+
: window.location.hostname.endsWith(betaQA.tld)
205+
? betaQA
206+
: betaDev;
207+
208+
async function insertSwitchButtonListener() {
209+
insertSwitchButton(legacy, beta);
210+
document.removeEventListener(
211+
"DOMContentLoaded",
212+
insertSwitchButtonListener
213+
);
214+
}
215+
216+
if (
217+
document.readyState === "complete" ||
218+
document.readyState === "interactive"
219+
) {
220+
insertSwitchButton(legacy, beta);
221+
} else {
222+
document.addEventListener("DOMContentLoaded", insertSwitchButtonListener);
223+
}
205224
}
225+
226+
// Backwards compatible: importing the module in the browser initializes it.
227+
initBetaSwitch();

0 commit comments

Comments
 (0)