-
-
Notifications
You must be signed in to change notification settings - Fork 640
Expand file tree
/
Copy pathvitest.setup.ts
More file actions
153 lines (133 loc) · 5.04 KB
/
Copy pathvitest.setup.ts
File metadata and controls
153 lines (133 loc) · 5.04 KB
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
import { vi } from "vitest"
import "@testing-library/jest-dom"
// Keep test output quiet by default. Individual tests can still spy on these
// methods when they need to assert logging behavior.
// eslint-disable-next-line no-console
console.log = () => {}
// eslint-disable-next-line no-console
console.info = () => {}
console.warn = () => {}
console.error = () => {}
class MemoryStorage implements Storage {
#store = new Map<string, string>()
get length() {
return this.#store.size
}
clear() {
this.#store.clear()
}
getItem(key: string) {
return this.#store.get(key) ?? null
}
key(index: number) {
return [...this.#store.keys()][index] ?? null
}
removeItem(key: string) {
this.#store.delete(key)
}
setItem(key: string, value: string) {
this.#store.set(key, value)
}
}
// Node 22 exposes built-in Web Storage. In worker processes without a configured
// backing file, reading it emits `--localstorage-file` warnings. Replace it with
// an in-memory test double before app modules import Jotai utils.
Object.defineProperty(globalThis, "localStorage", {
configurable: true,
value: new MemoryStorage(),
})
Object.defineProperty(globalThis, "sessionStorage", {
configurable: true,
value: new MemoryStorage(),
})
// Mock the runtime i18n facade so tests resolve keys deterministically (returning the
// dot-key, matching the pre-migration behaviour that test assertions rely on) without
// initializing i18next or touching browser.i18n.
vi.mock("@/utils/i18n", () => ({
i18n: {
t: (key: string) => key,
},
initI18n: async () => {},
setUiLanguage: async () => {},
}))
// LocaleBoundary is a separate module from the mocked facade above and pulls in i18next +
// the bundled YAML resources (which vitest has no plugin for). Stub it to a passthrough so
// no test loads i18next or the .yml files; runtime language switching is not under test here.
vi.mock("@/utils/i18n/locale-boundary", () => ({
LocaleBoundary: ({ children }: { children: unknown }) => children,
}))
// Iconify's <Icon> fetches icon data from api.iconify.design on mount and schedules
// retry timers when that fetch stalls (common in CI). Those Node timers outlive the
// test file's jsdom environment and crash React with "window is not defined" as an
// unhandled error attributed to whichever file runs next. Render an inert placeholder
// instead; no test exercises real icon loading. iconify-internal-api.test.ts opts back
// in via vi.unmock to keep its _api canary pointed at the real package.
vi.mock("@iconify/react", async () => {
const { createElement } = await import("react")
return {
Icon: ({ className, icon }: { className?: string; icon: string }) =>
createElement("span", { "aria-hidden": true, className, "data-icon": icon }),
_api: {
setFetch: () => {},
},
}
})
// Mock the fakeBrowser's i18n.getMessage method which is not implemented in fake-browser
// This is used when WxtVitest plugin replaces browser imports with fake-browser
vi.mock("wxt/testing/fake-browser", async () => {
const actual = await vi.importActual<any>("wxt/testing/fake-browser")
Object.assign(actual.fakeBrowser.i18n, {
getMessage: (key: string) => key.replaceAll("_", "."),
})
Object.assign(actual.fakeBrowser.identity, {
getRedirectURL: () => "https://mock-redirect-url.chromiumapp.org/",
})
Object.assign(actual.fakeBrowser.runtime, {
getManifest: () => ({
manifest_version: 3,
name: "Read Frog",
version: "1.0.0",
description: "Test manifest",
}),
})
return actual
})
// jsdom implements no layout, so it omits Range.getBoundingClientRect entirely
// (Element.getBoundingClientRect it does stub, returning zeros). Every browser
// ships it. Match jsdom's own convention with a zero rect so layout probes
// short-circuit instead of throwing; tests that exercise them spy on this.
// (Guarded: this setup file also runs for node-environment test files.)
if (typeof Range !== "undefined" && typeof Range.prototype.getBoundingClientRect !== "function") {
Range.prototype.getBoundingClientRect = function () {
return {
top: 0,
left: 0,
right: 0,
bottom: 0,
width: 0,
height: 0,
x: 0,
y: 0,
toJSON: () => ({}),
}
}
}
// JSDom + Vitest don't play well with each other. Long story short - default
// TextEncoder produces Uint8Array objects that are _different_ from the global
// Uint8Array objects, so some functions that compare their types explode.
// https://github.com/vitest-dev/vitest/issues/4043#issuecomment-1905172846
class ESBuildAndJSDOMCompatibleTextEncoder extends TextEncoder {
override encode(input: string) {
if (typeof input !== "string") {
throw new TypeError("`input` must be a string")
}
const decodedURI = decodeURIComponent(encodeURIComponent(input))
const arr = new Uint8Array(decodedURI.length)
const chars = decodedURI.split("")
for (let i = 0; i < chars.length; i++) {
arr[i] = decodedURI[i]!.charCodeAt(0)
}
return arr
}
}
globalThis.TextEncoder = ESBuildAndJSDOMCompatibleTextEncoder