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 | /**
* The minimum browser surface chat's modules touch at import time and in the
* code paths the vectors cover: localStorage, IndexedDB (fake-indexeddb),
* `document` (i18n's hydrateHtml runs at module load), `window`, `navigator`.
*
* Deliberately tiny and honest: every stub either behaves like the real thing
* for the operations under test, or throws. A stub that silently returns
* `undefined` where the browser returns data is how a fixture ends up encoding
* the harness's bug (dev/docs/CLJS.md).
*/
// Pinned before the first Date use: app/rooms.ts's default room label goes
// through Intl (`fmtDate`), so an unpinned zone would make the fixtures depend
// on the machine that generated them.
process.env.TZ = "UTC";
import "fake-indexeddb/auto";
class MemStorage {
constructor() {
this._m = new Map();
}
get length() {
return this._m.size;
}
key(i) {
return [...this._m.keys()][i] ?? null;
}
getItem(k) {
return this._m.has(String(k)) ? this._m.get(String(k)) : null;
}
setItem(k, v) {
this._m.set(String(k), String(v));
}
removeItem(k) {
this._m.delete(String(k));
}
clear() {
this._m.clear();
}
snapshot() {
return Object.fromEntries([...this._m.entries()].sort(([a], [b]) => (a < b ? -1 : 1)));
}
}
/** Node's `querySelectorAll` needs to exist and return nothing (i18n hydrate). */
const emptyList = [];
const fakeEl = {
lang: "en",
hidden: false,
className: "",
textContent: "",
style: { setProperty() {} },
dataset: {},
classList: { add() {}, remove() {}, toggle() {}, contains: () => false },
querySelectorAll: () => emptyList,
querySelector: () => null,
appendChild(x) {
return x;
},
setAttribute() {},
getAttribute: () => null,
addEventListener() {},
remove() {},
};
export const localStorageStub = new MemStorage();
export function installDom() {
const g = globalThis;
g.localStorage = localStorageStub;
g.sessionStorage = new MemStorage();
if (!g.document) {
g.document = {
documentElement: { ...fakeEl, style: { setProperty() {} }, classList: fakeEl.classList },
head: { appendChild() {} },
body: { appendChild() {}, classList: fakeEl.classList },
title: "sueta",
visibilityState: "visible",
hasFocus: () => true,
querySelectorAll: () => emptyList,
querySelector: () => null,
getElementById: () => null,
createElement: () => ({ ...fakeEl, style: { setProperty() {} } }),
createDocumentFragment: () => ({ appendChild() {} }),
createTextNode: (t) => ({ nodeValue: t }),
addEventListener() {},
removeEventListener() {},
};
}
if (!g.window) {
g.window = g;
g.window.addEventListener = () => {};
g.window.removeEventListener = () => {};
g.window.visualViewport = null;
g.window.setTimeout = setTimeout;
}
if (!g.navigator) g.navigator = {};
if (!g.navigator.languages) {
Object.defineProperty(g.navigator, "languages", { value: ["en-US", "en"], configurable: true });
}
if (!g.navigator.language) {
Object.defineProperty(g.navigator, "language", { value: "en-US", configurable: true });
}
if (!g.matchMedia) g.matchMedia = () => ({ matches: false });
if (!g.requestAnimationFrame) g.requestAnimationFrame = (fn) => setTimeout(() => fn(0), 0);
// node-forge (reached through helia -> auto-tls -> acme-client) does
// `new URL(window.location.href)` at module init, so `href` must be a real
// absolute URL — an incomplete location stub throws ERR_INVALID_URL and the
// whole harness dies before a single fixture is written.
if (!g.location) {
g.location = {
href: "https://chat.ardegazu.ro/",
origin: "https://chat.ardegazu.ro",
protocol: "https:",
host: "chat.ardegazu.ro",
hostname: "chat.ardegazu.ro",
pathname: "/",
search: "",
hash: "",
reload() {},
replace() {},
};
}
if (!g.history) g.history = { replaceState() {}, pushState() {} };
return { localStorage: localStorageStub };
}
|