import { afterEach, expect, test, vi } from "vitest" import { Feature } from "#Source/environment/index.ts" const internalBuiltInFeatures = [ "process", "crypto", "navigator", "clipboard", "permissions", "document", "css", ] afterEach(() => { for (const featureName of Feature.listFeatures()) { if (internalBuiltInFeatures.includes(featureName) === false) { Feature.unregisterFeature(featureName) } } vi.unstubAllGlobals() vi.restoreAllMocks() }) test("isFeatureRegistered detects existing and missing feature", () => { expect(Feature.isFeatureRegistered("process")).toBe(true) expect(Feature.isFeatureRegistered("crypto")).toBe(true) expect(Feature.isFeatureRegistered("custom-feature")).toBe(false) }) test("registerFeature stores custom feature detector", () => { Feature.registerFeature({ feature: "custom-register", detect: () => true, }) expect(Feature.isFeatureRegistered("custom-register")).toBe(true) }) test("unregisterFeature removes previously registered feature", () => { Feature.registerFeature({ feature: "custom-remove", detect: () => true, }) Feature.unregisterFeature("custom-remove") expect(Feature.isFeatureRegistered("custom-remove")).toBe(false) }) test("listFeatures returns all registered feature keys", () => { Feature.registerFeature({ feature: "custom-list", detect: () => true, }) const result = Feature.listFeatures() expect(result).toContain("process") expect(result).toContain("custom-list") }) test("getFeatureRegistryItem returns registry item by key", () => { Feature.registerFeature({ feature: "custom-item", detect: () => false, }) const found = Feature.getFeatureRegistryItem("custom-item") const missing = Feature.getFeatureRegistryItem("custom-missing") expect(found?.feature).toBe("custom-item") expect(found?.detect()).toBe(false) expect(missing).toBeUndefined() }) test("getFeatureFlags resolves detector outputs", () => { Feature.registerFeature({ feature: "custom-flags", detect: () => true, }) const flags = Feature.getFeatureFlags() expect(flags.process).toBe(true) expect(flags["custom-flags"]).toBe(true) }) test("isSatisfiesFeature checks single feature and throws for missing", () => { expect(Feature.isSatisfiesFeature("process")).toBe(true) expect(() => Feature.isSatisfiesFeature("feature-not-found")).toThrow("is not registered") }) test("isSatisfiesFeatures checks multi-feature conditions and throws for missing", () => { expect(Feature.isSatisfiesFeatures({ process: true })).toBe(true) expect(Feature.isSatisfiesFeatures({ process: false })).toBe(false) expect(() => Feature.isSatisfiesFeatures({ "feature-not-found": true })).toThrow( "is not registered", ) }) test("supportProcess reflects process availability", () => { expect(Feature.supportProcess()).toBe(true) vi.stubGlobal("process", undefined) expect(Feature.supportProcess()).toBe(false) }) test("getProcess returns the global process object", () => { expect(Feature.getProcess()).toBe(globalThis.process) }) test("ensureProcess returns process or throws", () => { expect(Feature.ensureProcess()).toBe(globalThis.process) vi.stubGlobal("process", undefined) expect(() => Feature.ensureProcess()).toThrow( "The process object is not supported in the current environment.", ) }) test("useProcess chooses process or fallback branch", () => { const withProcess = Feature.useProcess( (processObject) => typeof processObject.env, () => "fallback", ) vi.stubGlobal("process", undefined) const withoutProcess = Feature.useProcess( (processObject) => typeof processObject.env, () => "fallback", ) expect(withProcess).toBe("object") expect(withoutProcess).toBe("fallback") }) test("supportCrypto reflects crypto availability", () => { vi.stubGlobal("crypto", { randomUUID: vi.fn(() => "uuid") }) expect(Feature.supportCrypto()).toBe(true) vi.stubGlobal("crypto", undefined) expect(Feature.supportCrypto()).toBe(false) }) test("getCrypto returns the global crypto object", () => { const cryptoObject = { randomUUID: vi.fn(() => "uuid") } vi.stubGlobal("crypto", cryptoObject) expect(Feature.getCrypto()).toBe(cryptoObject) }) test("ensureCrypto returns crypto or throws", () => { const cryptoObject = { randomUUID: vi.fn(() => "uuid") } vi.stubGlobal("crypto", cryptoObject) expect(Feature.ensureCrypto()).toBe(cryptoObject) vi.stubGlobal("crypto", undefined) expect(() => Feature.ensureCrypto()).toThrow( "The crypto object is not supported in the current environment.", ) }) test("useCrypto chooses crypto or fallback branch", () => { vi.stubGlobal("crypto", { randomUUID: vi.fn(() => "uuid") }) const withCrypto = Feature.useCrypto( (cryptoObject) => typeof cryptoObject.randomUUID === "function", () => false, ) vi.stubGlobal("crypto", undefined) const withoutCrypto = Feature.useCrypto( (cryptoObject) => typeof cryptoObject.randomUUID === "function", () => false, ) expect(withCrypto).toBe(true) expect(withoutCrypto).toBe(false) }) test("supportNavigator reflects navigator availability", () => { vi.stubGlobal("navigator", { language: "en-US" }) expect(Feature.supportNavigator()).toBe(true) vi.stubGlobal("navigator", undefined) expect(Feature.supportNavigator()).toBe(false) }) test("getNavigator returns the global navigator object", () => { const navigatorObject = { language: "en-US" } vi.stubGlobal("navigator", navigatorObject) expect(Feature.getNavigator()).toBe(navigatorObject) }) test("ensureNavigator returns navigator or throws", () => { const navigatorObject = { language: "en-US" } vi.stubGlobal("navigator", navigatorObject) expect(Feature.ensureNavigator()).toBe(navigatorObject) vi.stubGlobal("navigator", undefined) expect(() => Feature.ensureNavigator()).toThrow( "The navigator object is not supported in the current environment.", ) }) test("useNavigator chooses navigator or fallback branch", () => { vi.stubGlobal("navigator", { language: "en-US", languages: ["en-US"] }) const withNavigator = Feature.useNavigator( (navigatorObject) => navigatorObject.language, () => "fallback", ) vi.stubGlobal("navigator", undefined) const withoutNavigator = Feature.useNavigator( (navigatorObject) => navigatorObject.language, () => "fallback", ) expect(withNavigator).toBe("en-US") expect(withoutNavigator).toBe("fallback") }) test("supportClipboard reflects navigator.clipboard availability", () => { vi.stubGlobal("navigator", { clipboard: { writeText: vi.fn() } }) expect(Feature.supportClipboard()).toBe(true) vi.stubGlobal("navigator", { language: "en-US" }) expect(Feature.supportClipboard()).toBe(false) vi.stubGlobal("navigator", undefined) expect(Feature.supportClipboard()).toBe(false) }) test("getClipboard returns the navigator clipboard object", () => { const clipboardObject = { writeText: vi.fn() } vi.stubGlobal("navigator", { clipboard: clipboardObject }) expect(Feature.getClipboard()).toBe(clipboardObject) }) test("ensureClipboard returns clipboard or throws", () => { const clipboardObject = { writeText: vi.fn() } vi.stubGlobal("navigator", { clipboard: clipboardObject }) expect(Feature.ensureClipboard()).toBe(clipboardObject) vi.stubGlobal("navigator", { language: "en-US" }) expect(() => Feature.ensureClipboard()).toThrow( "The clipboard API is not supported in the current environment.", ) }) test("useClipboard chooses clipboard or fallback branch", () => { vi.stubGlobal("navigator", { clipboard: { writeText: vi.fn() } }) const withClipboard = Feature.useClipboard( (clipboardObject) => typeof clipboardObject.writeText === "function", () => false, ) vi.stubGlobal("navigator", { language: "en-US" }) const withoutClipboard = Feature.useClipboard( (clipboardObject) => typeof clipboardObject.writeText === "function", () => false, ) expect(withClipboard).toBe(true) expect(withoutClipboard).toBe(false) }) test("supportPermissions reflects navigator.permissions availability", () => { vi.stubGlobal("navigator", { permissions: { query: vi.fn() } }) expect(Feature.supportPermissions()).toBe(true) vi.stubGlobal("navigator", { language: "en-US" }) expect(Feature.supportPermissions()).toBe(false) vi.stubGlobal("navigator", undefined) expect(Feature.supportPermissions()).toBe(false) }) test("getPermissions returns the navigator permissions object", () => { const permissionsObject = { query: vi.fn() } vi.stubGlobal("navigator", { permissions: permissionsObject }) expect(Feature.getPermissions()).toBe(permissionsObject) }) test("ensurePermissions returns permissions or throws", () => { const permissionsObject = { query: vi.fn() } vi.stubGlobal("navigator", { permissions: permissionsObject }) expect(Feature.ensurePermissions()).toBe(permissionsObject) vi.stubGlobal("navigator", { language: "en-US" }) expect(() => Feature.ensurePermissions()).toThrow( "The permissions API is not supported in the current environment.", ) }) test("usePermissions chooses permissions or fallback branch", () => { vi.stubGlobal("navigator", { permissions: { query: vi.fn() } }) const withPermissions = Feature.usePermissions( (permissionsObject) => typeof permissionsObject.query === "function", () => false, ) vi.stubGlobal("navigator", { language: "en-US" }) const withoutPermissions = Feature.usePermissions( (permissionsObject) => typeof permissionsObject.query === "function", () => false, ) expect(withPermissions).toBe(true) expect(withoutPermissions).toBe(false) }) test("supportDocument reflects document availability", () => { vi.stubGlobal("document", { title: "doc" }) expect(Feature.supportDocument()).toBe(true) vi.stubGlobal("document", undefined) expect(Feature.supportDocument()).toBe(false) }) test("getDocument returns the global document object", () => { const documentObject = { title: "mobius" } vi.stubGlobal("document", documentObject) expect(Feature.getDocument()).toBe(documentObject) }) test("ensureDocument returns document or throws", () => { const documentObject = { title: "mobius" } vi.stubGlobal("document", documentObject) expect(Feature.ensureDocument()).toBe(documentObject) vi.stubGlobal("document", undefined) expect(() => Feature.ensureDocument()).toThrow( "The document object is not supported in the current environment.", ) }) test("useDocument chooses document or fallback branch", () => { vi.stubGlobal("document", { title: "mobius" }) const withDocument = Feature.useDocument( (documentObject) => documentObject.title, () => "fallback", ) vi.stubGlobal("document", undefined) const withoutDocument = Feature.useDocument( (documentObject) => documentObject.title, () => "fallback", ) expect(withDocument).toBe("mobius") expect(withoutDocument).toBe("fallback") }) test("supportCSS reflects CSS availability", () => { vi.stubGlobal("CSS", { supports: () => true }) expect(Feature.supportCSS()).toBe(true) vi.stubGlobal("CSS", undefined) expect(Feature.supportCSS()).toBe(false) }) test("getCSS returns the global CSS object", () => { const cssObject = { supports: vi.fn(() => true) } vi.stubGlobal("CSS", cssObject) expect(Feature.getCSS()).toBe(cssObject) }) test("ensureCSS returns CSS or throws", () => { const cssObject = { supports: vi.fn(() => true) } vi.stubGlobal("CSS", cssObject) expect(Feature.ensureCSS()).toBe(cssObject) vi.stubGlobal("CSS", undefined) expect(() => Feature.ensureCSS()).toThrow( "The CSS object is not supported in the current environment.", ) }) test("useCSS chooses CSS or fallback branch", () => { vi.stubGlobal("CSS", { supports: () => true }) const withCSS = Feature.useCSS( (cssObject) => cssObject.supports("display", "grid"), () => false, ) vi.stubGlobal("CSS", undefined) const withoutCSS = Feature.useCSS( (cssObject) => cssObject.supports("display", "grid"), () => false, ) expect(withCSS).toBe(true) expect(withoutCSS).toBe(false) })