@planet-matrix/mobius-model 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/README.md +134 -21
- package/dist/index.js +45 -4
- package/dist/index.js.map +186 -11
- package/oxlint.config.ts +6 -0
- package/package.json +16 -10
- package/src/abort/README.md +92 -0
- package/src/abort/abort-manager.ts +278 -0
- package/src/abort/abort-signal-listener-manager.ts +81 -0
- package/src/abort/index.ts +2 -0
- package/src/basic/README.md +69 -117
- package/src/basic/enhance.ts +10 -0
- package/src/basic/function.ts +81 -62
- package/src/basic/index.ts +2 -0
- package/src/basic/is.ts +152 -71
- package/src/basic/object.ts +82 -0
- package/src/basic/promise.ts +29 -8
- package/src/basic/string.ts +2 -33
- package/src/color/README.md +105 -0
- package/src/color/index.ts +3 -0
- package/src/color/internal.ts +42 -0
- package/src/color/rgb/analyze.ts +236 -0
- package/src/color/rgb/construct.ts +130 -0
- package/src/color/rgb/convert.ts +227 -0
- package/src/color/rgb/derive.ts +303 -0
- package/src/color/rgb/index.ts +6 -0
- package/src/color/rgb/internal.ts +208 -0
- package/src/color/rgb/parse.ts +302 -0
- package/src/color/rgb/serialize.ts +144 -0
- package/src/color/types.ts +57 -0
- package/src/color/xyz/analyze.ts +80 -0
- package/src/color/xyz/construct.ts +19 -0
- package/src/color/xyz/convert.ts +71 -0
- package/src/color/xyz/index.ts +3 -0
- package/src/color/xyz/internal.ts +23 -0
- package/src/css/README.md +93 -0
- package/src/css/class.ts +559 -0
- package/src/css/index.ts +1 -0
- package/src/encoding/README.md +92 -0
- package/src/encoding/base64.ts +107 -0
- package/src/encoding/index.ts +1 -0
- package/src/environment/README.md +97 -0
- package/src/environment/basic.ts +26 -0
- package/src/environment/device.ts +311 -0
- package/src/environment/feature.ts +285 -0
- package/src/environment/geo.ts +337 -0
- package/src/environment/index.ts +7 -0
- package/src/environment/runtime.ts +400 -0
- package/src/environment/snapshot.ts +60 -0
- package/src/environment/variable.ts +239 -0
- package/src/event/README.md +90 -0
- package/src/event/class-event-proxy.ts +228 -0
- package/src/event/common.ts +19 -0
- package/src/event/event-manager.ts +203 -0
- package/src/event/index.ts +4 -0
- package/src/event/instance-event-proxy.ts +186 -0
- package/src/event/internal.ts +24 -0
- package/src/exception/README.md +96 -0
- package/src/exception/browser.ts +219 -0
- package/src/exception/index.ts +4 -0
- package/src/exception/nodejs.ts +169 -0
- package/src/exception/normalize.ts +106 -0
- package/src/exception/types.ts +99 -0
- package/src/identifier/README.md +92 -0
- package/src/identifier/id.ts +119 -0
- package/src/identifier/index.ts +2 -0
- package/src/identifier/uuid.ts +187 -0
- package/src/index.ts +18 -1
- package/src/log/README.md +79 -0
- package/src/log/index.ts +5 -0
- package/src/log/log-emitter.ts +72 -0
- package/src/log/log-record.ts +10 -0
- package/src/log/log-scheduler.ts +74 -0
- package/src/log/log-type.ts +8 -0
- package/src/log/logger.ts +543 -0
- package/src/orchestration/README.md +89 -0
- package/src/orchestration/coordination/barrier.ts +214 -0
- package/src/orchestration/coordination/count-down-latch.ts +215 -0
- package/src/orchestration/coordination/errors.ts +98 -0
- package/src/orchestration/coordination/index.ts +16 -0
- package/src/orchestration/coordination/internal/wait-constraints.ts +95 -0
- package/src/orchestration/coordination/internal/wait-queue.ts +109 -0
- package/src/orchestration/coordination/keyed-lock.ts +168 -0
- package/src/orchestration/coordination/mutex.ts +257 -0
- package/src/orchestration/coordination/permit.ts +127 -0
- package/src/orchestration/coordination/read-write-lock.ts +444 -0
- package/src/orchestration/coordination/semaphore.ts +280 -0
- package/src/orchestration/index.ts +1 -0
- package/src/random/README.md +78 -0
- package/src/random/index.ts +1 -0
- package/src/random/string.ts +35 -0
- package/src/reactor/README.md +4 -0
- package/src/reactor/reactor-core/primitive.ts +9 -9
- package/src/reactor/reactor-core/reactive-system.ts +5 -5
- package/src/singleton/README.md +79 -0
- package/src/singleton/factory.ts +55 -0
- package/src/singleton/index.ts +2 -0
- package/src/singleton/manager.ts +204 -0
- package/src/storage/README.md +107 -0
- package/src/storage/index.ts +1 -0
- package/src/storage/table.ts +449 -0
- package/src/timer/README.md +86 -0
- package/src/timer/expiration/expiration-manager.ts +594 -0
- package/src/timer/expiration/index.ts +3 -0
- package/src/timer/expiration/min-heap.ts +208 -0
- package/src/timer/expiration/remaining-manager.ts +241 -0
- package/src/timer/index.ts +1 -0
- package/src/type/README.md +54 -307
- package/src/type/class.ts +2 -2
- package/src/type/index.ts +14 -14
- package/src/type/is.ts +265 -2
- package/src/type/object.ts +37 -0
- package/src/type/string.ts +7 -2
- package/src/type/tuple.ts +6 -6
- package/src/type/union.ts +16 -0
- package/src/web/README.md +77 -0
- package/src/web/capture.ts +35 -0
- package/src/web/clipboard.ts +97 -0
- package/src/web/dom.ts +117 -0
- package/src/web/download.ts +16 -0
- package/src/web/event.ts +46 -0
- package/src/web/index.ts +10 -0
- package/src/web/local-storage.ts +113 -0
- package/src/web/location.ts +28 -0
- package/src/web/permission.ts +172 -0
- package/src/web/script-loader.ts +432 -0
- package/tests/unit/abort/abort-manager.spec.ts +225 -0
- package/tests/unit/abort/abort-signal-listener-manager.spec.ts +62 -0
- package/tests/unit/basic/array.spec.ts +1 -1
- package/tests/unit/basic/object.spec.ts +32 -1
- package/tests/unit/basic/stream.spec.ts +1 -1
- package/tests/unit/basic/string.spec.ts +0 -9
- package/tests/unit/color/rgb/analyze.spec.ts +110 -0
- package/tests/unit/color/rgb/construct.spec.ts +56 -0
- package/tests/unit/color/rgb/convert.spec.ts +60 -0
- package/tests/unit/color/rgb/derive.spec.ts +103 -0
- package/tests/unit/color/rgb/parse.spec.ts +66 -0
- package/tests/unit/color/rgb/serialize.spec.ts +46 -0
- package/tests/unit/color/xyz/analyze.spec.ts +33 -0
- package/tests/unit/color/xyz/construct.spec.ts +10 -0
- package/tests/unit/color/xyz/convert.spec.ts +18 -0
- package/tests/unit/css/class.spec.ts +157 -0
- package/tests/unit/encoding/base64.spec.ts +40 -0
- package/tests/unit/environment/basic.spec.ts +20 -0
- package/tests/unit/environment/device.spec.ts +146 -0
- package/tests/unit/environment/feature.spec.ts +388 -0
- package/tests/unit/environment/geo.spec.ts +111 -0
- package/tests/unit/environment/runtime.spec.ts +364 -0
- package/tests/unit/environment/snapshot.spec.ts +4 -0
- package/tests/unit/environment/variable.spec.ts +190 -0
- package/tests/unit/event/class-event-proxy.spec.ts +225 -0
- package/tests/unit/event/event-manager.spec.ts +246 -0
- package/tests/unit/event/instance-event-proxy.spec.ts +187 -0
- package/tests/unit/exception/browser.spec.ts +213 -0
- package/tests/unit/exception/nodejs.spec.ts +144 -0
- package/tests/unit/exception/normalize.spec.ts +57 -0
- package/tests/unit/identifier/id.spec.ts +71 -0
- package/tests/unit/identifier/uuid.spec.ts +85 -0
- package/tests/unit/log/log-emitter.spec.ts +33 -0
- package/tests/unit/log/log-scheduler.spec.ts +40 -0
- package/tests/unit/log/log-type.spec.ts +7 -0
- package/tests/unit/log/logger.spec.ts +222 -0
- package/tests/unit/orchestration/coordination/barrier.spec.ts +96 -0
- package/tests/unit/orchestration/coordination/count-down-latch.spec.ts +63 -0
- package/tests/unit/orchestration/coordination/errors.spec.ts +29 -0
- package/tests/unit/orchestration/coordination/keyed-lock.spec.ts +109 -0
- package/tests/unit/orchestration/coordination/mutex.spec.ts +132 -0
- package/tests/unit/orchestration/coordination/permit.spec.ts +43 -0
- package/tests/unit/orchestration/coordination/read-write-lock.spec.ts +154 -0
- package/tests/unit/orchestration/coordination/semaphore.spec.ts +135 -0
- package/tests/unit/random/string.spec.ts +11 -0
- package/tests/unit/reactor/alien-signals-effect.spec.ts +11 -10
- package/tests/unit/reactor/preact-signal.spec.ts +1 -2
- package/tests/unit/singleton/singleton.spec.ts +49 -0
- package/tests/unit/storage/table.spec.ts +620 -0
- package/tests/unit/timer/expiration/expiration-manager.spec.ts +464 -0
- package/tests/unit/timer/expiration/min-heap.spec.ts +71 -0
- package/tests/unit/timer/expiration/remaining-manager.spec.ts +234 -0
- package/.oxlintrc.json +0 -5
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import { afterEach, expect, test, vi } from "vitest"
|
|
2
|
+
import {
|
|
3
|
+
ensureCSS,
|
|
4
|
+
ensureClipboard,
|
|
5
|
+
ensureDocument,
|
|
6
|
+
ensureNavigator,
|
|
7
|
+
ensurePermissions,
|
|
8
|
+
ensureProcess,
|
|
9
|
+
getCSS,
|
|
10
|
+
getClipboard,
|
|
11
|
+
getDocument,
|
|
12
|
+
getFeatureFlags,
|
|
13
|
+
getFeatureRegistryItem,
|
|
14
|
+
getNavigator,
|
|
15
|
+
getPermissions,
|
|
16
|
+
getProcess,
|
|
17
|
+
isFeatureRegistered,
|
|
18
|
+
isSatisfiesFeature,
|
|
19
|
+
isSatisfiesFeatures,
|
|
20
|
+
listFeatures,
|
|
21
|
+
registerFeature,
|
|
22
|
+
supportCSS,
|
|
23
|
+
supportClipboard,
|
|
24
|
+
supportDocument,
|
|
25
|
+
supportNavigator,
|
|
26
|
+
supportPermissions,
|
|
27
|
+
supportProcess,
|
|
28
|
+
unregisterFeature,
|
|
29
|
+
useCSS,
|
|
30
|
+
useClipboard,
|
|
31
|
+
useDocument,
|
|
32
|
+
useNavigator,
|
|
33
|
+
usePermissions,
|
|
34
|
+
useProcess,
|
|
35
|
+
} from "#Source/environment/feature.ts"
|
|
36
|
+
|
|
37
|
+
const internalBuiltInFeatures = ["process", "navigator", "clipboard", "permissions", "document", "css"]
|
|
38
|
+
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
for (const featureName of listFeatures()) {
|
|
41
|
+
if (internalBuiltInFeatures.includes(featureName) === false) {
|
|
42
|
+
unregisterFeature(featureName)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
vi.unstubAllGlobals()
|
|
46
|
+
vi.restoreAllMocks()
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test("isFeatureRegistered detects existing and missing feature", () => {
|
|
50
|
+
expect(isFeatureRegistered("process")).toBe(true)
|
|
51
|
+
expect(isFeatureRegistered("custom-feature")).toBe(false)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test("registerFeature stores custom feature detector", () => {
|
|
55
|
+
registerFeature({
|
|
56
|
+
feature: "custom-register",
|
|
57
|
+
detect: () => true,
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
expect(isFeatureRegistered("custom-register")).toBe(true)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test("unregisterFeature removes previously registered feature", () => {
|
|
64
|
+
registerFeature({
|
|
65
|
+
feature: "custom-remove",
|
|
66
|
+
detect: () => true,
|
|
67
|
+
})
|
|
68
|
+
unregisterFeature("custom-remove")
|
|
69
|
+
|
|
70
|
+
expect(isFeatureRegistered("custom-remove")).toBe(false)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test("listFeatures returns all registered feature keys", () => {
|
|
74
|
+
registerFeature({
|
|
75
|
+
feature: "custom-list",
|
|
76
|
+
detect: () => true,
|
|
77
|
+
})
|
|
78
|
+
const result = listFeatures()
|
|
79
|
+
|
|
80
|
+
expect(result).toContain("process")
|
|
81
|
+
expect(result).toContain("custom-list")
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
test("getFeatureRegistryItem returns registry item by key", () => {
|
|
85
|
+
registerFeature({
|
|
86
|
+
feature: "custom-item",
|
|
87
|
+
detect: () => false,
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
const found = getFeatureRegistryItem("custom-item")
|
|
91
|
+
const missing = getFeatureRegistryItem("custom-missing")
|
|
92
|
+
|
|
93
|
+
expect(found?.feature).toBe("custom-item")
|
|
94
|
+
expect(found?.detect()).toBe(false)
|
|
95
|
+
expect(missing).toBeUndefined()
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
test("getFeatureFlags resolves detector outputs", () => {
|
|
99
|
+
registerFeature({
|
|
100
|
+
feature: "custom-flags",
|
|
101
|
+
detect: () => true,
|
|
102
|
+
})
|
|
103
|
+
const flags = getFeatureFlags()
|
|
104
|
+
|
|
105
|
+
expect(flags.process).toBe(true)
|
|
106
|
+
expect(flags["custom-flags"]).toBe(true)
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
test("isSatisfiesFeature checks single feature and throws for missing", () => {
|
|
110
|
+
expect(isSatisfiesFeature("process")).toBe(true)
|
|
111
|
+
expect(() => isSatisfiesFeature("feature-not-found")).toThrow(
|
|
112
|
+
"is not registered",
|
|
113
|
+
)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
test("isSatisfiesFeatures checks multi-feature conditions and throws for missing", () => {
|
|
117
|
+
expect(isSatisfiesFeatures({ process: true })).toBe(true)
|
|
118
|
+
expect(isSatisfiesFeatures({ process: false })).toBe(false)
|
|
119
|
+
expect(() => isSatisfiesFeatures({ "feature-not-found": true })).toThrow(
|
|
120
|
+
"is not registered",
|
|
121
|
+
)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
test("supportProcess reflects process availability", () => {
|
|
125
|
+
expect(supportProcess()).toBe(true)
|
|
126
|
+
vi.stubGlobal("process", undefined)
|
|
127
|
+
expect(supportProcess()).toBe(false)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
test("getProcess returns the global process object", () => {
|
|
131
|
+
expect(getProcess()).toBe(globalThis.process)
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
test("ensureProcess returns process or throws", () => {
|
|
135
|
+
expect(ensureProcess()).toBe(globalThis.process)
|
|
136
|
+
|
|
137
|
+
vi.stubGlobal("process", undefined)
|
|
138
|
+
|
|
139
|
+
expect(() => ensureProcess()).toThrow(
|
|
140
|
+
"The process object is not supported in the current environment.",
|
|
141
|
+
)
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
test("useProcess chooses process or fallback branch", () => {
|
|
145
|
+
const withProcess = useProcess(
|
|
146
|
+
(processObject) => typeof processObject.env,
|
|
147
|
+
() => "fallback",
|
|
148
|
+
)
|
|
149
|
+
vi.stubGlobal("process", undefined)
|
|
150
|
+
const withoutProcess = useProcess(
|
|
151
|
+
(processObject) => typeof processObject.env,
|
|
152
|
+
() => "fallback",
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
expect(withProcess).toBe("object")
|
|
156
|
+
expect(withoutProcess).toBe("fallback")
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
test("supportNavigator reflects navigator availability", () => {
|
|
160
|
+
vi.stubGlobal("navigator", { language: "en-US" })
|
|
161
|
+
expect(supportNavigator()).toBe(true)
|
|
162
|
+
|
|
163
|
+
vi.stubGlobal("navigator", undefined)
|
|
164
|
+
expect(supportNavigator()).toBe(false)
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
test("getNavigator returns the global navigator object", () => {
|
|
168
|
+
const navigatorObject = { language: "en-US" }
|
|
169
|
+
vi.stubGlobal("navigator", navigatorObject)
|
|
170
|
+
|
|
171
|
+
expect(getNavigator()).toBe(navigatorObject)
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
test("ensureNavigator returns navigator or throws", () => {
|
|
175
|
+
const navigatorObject = { language: "en-US" }
|
|
176
|
+
vi.stubGlobal("navigator", navigatorObject)
|
|
177
|
+
|
|
178
|
+
expect(ensureNavigator()).toBe(navigatorObject)
|
|
179
|
+
|
|
180
|
+
vi.stubGlobal("navigator", undefined)
|
|
181
|
+
|
|
182
|
+
expect(() => ensureNavigator()).toThrow(
|
|
183
|
+
"The navigator object is not supported in the current environment.",
|
|
184
|
+
)
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
test("useNavigator chooses navigator or fallback branch", () => {
|
|
188
|
+
vi.stubGlobal("navigator", { language: "en-US", languages: ["en-US"] })
|
|
189
|
+
const withNavigator = useNavigator(
|
|
190
|
+
(navigatorObject) => navigatorObject.language,
|
|
191
|
+
() => "fallback",
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
vi.stubGlobal("navigator", undefined)
|
|
195
|
+
const withoutNavigator = useNavigator(
|
|
196
|
+
(navigatorObject) => navigatorObject.language,
|
|
197
|
+
() => "fallback",
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
expect(withNavigator).toBe("en-US")
|
|
201
|
+
expect(withoutNavigator).toBe("fallback")
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
test("supportClipboard reflects navigator.clipboard availability", () => {
|
|
205
|
+
vi.stubGlobal("navigator", { clipboard: { writeText: vi.fn() } })
|
|
206
|
+
expect(supportClipboard()).toBe(true)
|
|
207
|
+
|
|
208
|
+
vi.stubGlobal("navigator", { language: "en-US" })
|
|
209
|
+
expect(supportClipboard()).toBe(false)
|
|
210
|
+
|
|
211
|
+
vi.stubGlobal("navigator", undefined)
|
|
212
|
+
expect(supportClipboard()).toBe(false)
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
test("getClipboard returns the navigator clipboard object", () => {
|
|
216
|
+
const clipboardObject = { writeText: vi.fn() }
|
|
217
|
+
vi.stubGlobal("navigator", { clipboard: clipboardObject })
|
|
218
|
+
|
|
219
|
+
expect(getClipboard()).toBe(clipboardObject)
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
test("ensureClipboard returns clipboard or throws", () => {
|
|
223
|
+
const clipboardObject = { writeText: vi.fn() }
|
|
224
|
+
vi.stubGlobal("navigator", { clipboard: clipboardObject })
|
|
225
|
+
|
|
226
|
+
expect(ensureClipboard()).toBe(clipboardObject)
|
|
227
|
+
|
|
228
|
+
vi.stubGlobal("navigator", { language: "en-US" })
|
|
229
|
+
|
|
230
|
+
expect(() => ensureClipboard()).toThrow(
|
|
231
|
+
"The clipboard API is not supported in the current environment.",
|
|
232
|
+
)
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
test("useClipboard chooses clipboard or fallback branch", () => {
|
|
236
|
+
vi.stubGlobal("navigator", { clipboard: { writeText: vi.fn() } })
|
|
237
|
+
const withClipboard = useClipboard(
|
|
238
|
+
(clipboardObject) => typeof clipboardObject.writeText === "function",
|
|
239
|
+
() => false,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
vi.stubGlobal("navigator", { language: "en-US" })
|
|
243
|
+
const withoutClipboard = useClipboard(
|
|
244
|
+
(clipboardObject) => typeof clipboardObject.writeText === "function",
|
|
245
|
+
() => false,
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
expect(withClipboard).toBe(true)
|
|
249
|
+
expect(withoutClipboard).toBe(false)
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
test("supportPermissions reflects navigator.permissions availability", () => {
|
|
253
|
+
vi.stubGlobal("navigator", { permissions: { query: vi.fn() } })
|
|
254
|
+
expect(supportPermissions()).toBe(true)
|
|
255
|
+
|
|
256
|
+
vi.stubGlobal("navigator", { language: "en-US" })
|
|
257
|
+
expect(supportPermissions()).toBe(false)
|
|
258
|
+
|
|
259
|
+
vi.stubGlobal("navigator", undefined)
|
|
260
|
+
expect(supportPermissions()).toBe(false)
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
test("getPermissions returns the navigator permissions object", () => {
|
|
264
|
+
const permissionsObject = { query: vi.fn() }
|
|
265
|
+
vi.stubGlobal("navigator", { permissions: permissionsObject })
|
|
266
|
+
|
|
267
|
+
expect(getPermissions()).toBe(permissionsObject)
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
test("ensurePermissions returns permissions or throws", () => {
|
|
271
|
+
const permissionsObject = { query: vi.fn() }
|
|
272
|
+
vi.stubGlobal("navigator", { permissions: permissionsObject })
|
|
273
|
+
|
|
274
|
+
expect(ensurePermissions()).toBe(permissionsObject)
|
|
275
|
+
|
|
276
|
+
vi.stubGlobal("navigator", { language: "en-US" })
|
|
277
|
+
|
|
278
|
+
expect(() => ensurePermissions()).toThrow(
|
|
279
|
+
"The permissions API is not supported in the current environment.",
|
|
280
|
+
)
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
test("usePermissions chooses permissions or fallback branch", () => {
|
|
284
|
+
vi.stubGlobal("navigator", { permissions: { query: vi.fn() } })
|
|
285
|
+
const withPermissions = usePermissions(
|
|
286
|
+
(permissionsObject) => typeof permissionsObject.query === "function",
|
|
287
|
+
() => false,
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
vi.stubGlobal("navigator", { language: "en-US" })
|
|
291
|
+
const withoutPermissions = usePermissions(
|
|
292
|
+
(permissionsObject) => typeof permissionsObject.query === "function",
|
|
293
|
+
() => false,
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
expect(withPermissions).toBe(true)
|
|
297
|
+
expect(withoutPermissions).toBe(false)
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
test("supportDocument reflects document availability", () => {
|
|
301
|
+
vi.stubGlobal("document", { title: "doc" })
|
|
302
|
+
expect(supportDocument()).toBe(true)
|
|
303
|
+
|
|
304
|
+
vi.stubGlobal("document", undefined)
|
|
305
|
+
expect(supportDocument()).toBe(false)
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
test("getDocument returns the global document object", () => {
|
|
309
|
+
const documentObject = { title: "mobius" }
|
|
310
|
+
vi.stubGlobal("document", documentObject)
|
|
311
|
+
|
|
312
|
+
expect(getDocument()).toBe(documentObject)
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
test("ensureDocument returns document or throws", () => {
|
|
316
|
+
const documentObject = { title: "mobius" }
|
|
317
|
+
vi.stubGlobal("document", documentObject)
|
|
318
|
+
|
|
319
|
+
expect(ensureDocument()).toBe(documentObject)
|
|
320
|
+
|
|
321
|
+
vi.stubGlobal("document", undefined)
|
|
322
|
+
|
|
323
|
+
expect(() => ensureDocument()).toThrow(
|
|
324
|
+
"The document object is not supported in the current environment.",
|
|
325
|
+
)
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
test("useDocument chooses document or fallback branch", () => {
|
|
329
|
+
vi.stubGlobal("document", { title: "mobius" })
|
|
330
|
+
const withDocument = useDocument(
|
|
331
|
+
(documentObject) => documentObject.title,
|
|
332
|
+
() => "fallback",
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
vi.stubGlobal("document", undefined)
|
|
336
|
+
const withoutDocument = useDocument(
|
|
337
|
+
(documentObject) => documentObject.title,
|
|
338
|
+
() => "fallback",
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
expect(withDocument).toBe("mobius")
|
|
342
|
+
expect(withoutDocument).toBe("fallback")
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
test("supportCSS reflects CSS availability", () => {
|
|
346
|
+
vi.stubGlobal("CSS", { supports: () => true })
|
|
347
|
+
expect(supportCSS()).toBe(true)
|
|
348
|
+
|
|
349
|
+
vi.stubGlobal("CSS", undefined)
|
|
350
|
+
expect(supportCSS()).toBe(false)
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
test("getCSS returns the global CSS object", () => {
|
|
354
|
+
const cssObject = { supports: vi.fn(() => true) }
|
|
355
|
+
vi.stubGlobal("CSS", cssObject)
|
|
356
|
+
|
|
357
|
+
expect(getCSS()).toBe(cssObject)
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
test("ensureCSS returns CSS or throws", () => {
|
|
361
|
+
const cssObject = { supports: vi.fn(() => true) }
|
|
362
|
+
vi.stubGlobal("CSS", cssObject)
|
|
363
|
+
|
|
364
|
+
expect(ensureCSS()).toBe(cssObject)
|
|
365
|
+
|
|
366
|
+
vi.stubGlobal("CSS", undefined)
|
|
367
|
+
|
|
368
|
+
expect(() => ensureCSS()).toThrow(
|
|
369
|
+
"The CSS object is not supported in the current environment.",
|
|
370
|
+
)
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
test("useCSS chooses CSS or fallback branch", () => {
|
|
374
|
+
vi.stubGlobal("CSS", { supports: () => true })
|
|
375
|
+
const withCSS = useCSS(
|
|
376
|
+
(cssObject) => cssObject.supports("display", "grid"),
|
|
377
|
+
() => false,
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
vi.stubGlobal("CSS", undefined)
|
|
381
|
+
const withoutCSS = useCSS(
|
|
382
|
+
(cssObject) => cssObject.supports("display", "grid"),
|
|
383
|
+
() => false,
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
expect(withCSS).toBe(true)
|
|
387
|
+
expect(withoutCSS).toBe(false)
|
|
388
|
+
})
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { expect, test, vi } from "vitest"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
getGeoInfo,
|
|
5
|
+
getGeoInfoFromCloudflare,
|
|
6
|
+
getGeoInfoFromIpapi,
|
|
7
|
+
getGeoInfoFromIpify,
|
|
8
|
+
} from "#Source/environment/geo.ts"
|
|
9
|
+
|
|
10
|
+
test("getGeoInfoFromCloudflare parses key-value trace response", async () => {
|
|
11
|
+
const mockFetch = async (): Promise<{ text: () => Promise<string> }> => {
|
|
12
|
+
const response = await Promise.resolve({
|
|
13
|
+
text: async (): Promise<string> => {
|
|
14
|
+
return await Promise.resolve("ip=1.2.3.4\nloc=US\nhttp=http/2\n")
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
return response
|
|
18
|
+
}
|
|
19
|
+
vi.stubGlobal("fetch", vi.fn(mockFetch))
|
|
20
|
+
|
|
21
|
+
const geo = await getGeoInfoFromCloudflare()
|
|
22
|
+
|
|
23
|
+
expect(geo.ip).toBe("1.2.3.4")
|
|
24
|
+
expect(geo.loc).toBe("US")
|
|
25
|
+
expect(geo.http).toBe("http/2")
|
|
26
|
+
|
|
27
|
+
vi.unstubAllGlobals()
|
|
28
|
+
vi.restoreAllMocks()
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
test("getGeoInfoFromIpapi returns parsed JSON payload", async () => {
|
|
32
|
+
const mockFetch = async (): Promise<{ json: () => Promise<{ ip: string; country: string }> }> => {
|
|
33
|
+
const response = await Promise.resolve({
|
|
34
|
+
json: async (): Promise<{ ip: string; country: string }> => {
|
|
35
|
+
return await Promise.resolve({ ip: "5.6.7.8", country: "US" })
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
return response
|
|
39
|
+
}
|
|
40
|
+
vi.stubGlobal("fetch", vi.fn(mockFetch))
|
|
41
|
+
|
|
42
|
+
const geo = await getGeoInfoFromIpapi()
|
|
43
|
+
|
|
44
|
+
expect(geo.ip).toBe("5.6.7.8")
|
|
45
|
+
expect(geo.country).toBe("US")
|
|
46
|
+
|
|
47
|
+
vi.unstubAllGlobals()
|
|
48
|
+
vi.restoreAllMocks()
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test("getGeoInfoFromIpify returns public IP payload", async () => {
|
|
52
|
+
const mockFetch = async (): Promise<{ json: () => Promise<{ ip: string }> }> => {
|
|
53
|
+
const response = await Promise.resolve({
|
|
54
|
+
json: async (): Promise<{ ip: string }> => {
|
|
55
|
+
return await Promise.resolve({ ip: "9.9.9.9" })
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
return response
|
|
59
|
+
}
|
|
60
|
+
vi.stubGlobal("fetch", vi.fn(mockFetch))
|
|
61
|
+
|
|
62
|
+
const geo = await getGeoInfoFromIpify()
|
|
63
|
+
|
|
64
|
+
expect(geo.ip).toBe("9.9.9.9")
|
|
65
|
+
|
|
66
|
+
vi.unstubAllGlobals()
|
|
67
|
+
vi.restoreAllMocks()
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test("getGeoInfo aggregates results from all providers", async () => {
|
|
71
|
+
// oxlint-disable-next-line require-await explicit-function-return-type
|
|
72
|
+
const mockFetch = async (input: URL | RequestInfo) => {
|
|
73
|
+
let url: string
|
|
74
|
+
if (input instanceof URL) {
|
|
75
|
+
url = input.toString()
|
|
76
|
+
} else if (typeof input === "string") {
|
|
77
|
+
url = input
|
|
78
|
+
} else {
|
|
79
|
+
url = input.url
|
|
80
|
+
}
|
|
81
|
+
if (url.includes("cloudflare")) {
|
|
82
|
+
return {
|
|
83
|
+
text: async (): Promise<string> => {
|
|
84
|
+
return await Promise.resolve("ip=1.1.1.1\nloc=US\n")
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (url.includes("ipapi")) {
|
|
89
|
+
return {
|
|
90
|
+
json: async (): Promise<{ ip: string; country: string }> => {
|
|
91
|
+
return await Promise.resolve({ ip: "2.2.2.2", country: "US" })
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
json: async (): Promise<{ ip: string }> => {
|
|
97
|
+
return await Promise.resolve({ ip: "3.3.3.3" })
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
vi.stubGlobal("fetch", vi.fn(mockFetch))
|
|
102
|
+
|
|
103
|
+
const geo = await getGeoInfo()
|
|
104
|
+
|
|
105
|
+
expect(geo.cloudflare.ip).toBe("1.1.1.1")
|
|
106
|
+
expect(geo.ipapi.ip).toBe("2.2.2.2")
|
|
107
|
+
expect(geo.ipify.ip).toBe("3.3.3.3")
|
|
108
|
+
|
|
109
|
+
vi.unstubAllGlobals()
|
|
110
|
+
vi.restoreAllMocks()
|
|
111
|
+
})
|