Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/improsocial/impro
An extensible Bluesky client for web impro.social
Something went wrong. Try again.
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173import { JSDOM } from "jsdom";
import { register } from "node:module";
// Register the loader so we can use the / prefix to load files from the src directoryregister(new URL("./loader.js", import.meta.url));
// Enable JSDOMconst dom = new JSDOM("", { url: "http://localhost/" });globalThis.window = dom.window;globalThis.window.scrollTo = () => {};globalThis.document = dom.window.document;globalThis.HTMLElement = dom.window.HTMLElement;globalThis.customElements = dom.window.customElements;globalThis.CustomEvent = dom.window.CustomEvent;globalThis.Node = dom.window.Node;globalThis.DocumentFragment = dom.window.DocumentFragment;globalThis.Event = dom.window.Event;globalThis.KeyboardEvent = dom.window.KeyboardEvent;globalThis.MouseEvent = dom.window.MouseEvent;globalThis.MutationObserver = dom.window.MutationObserver;globalThis.FileReader = dom.window.FileReader;globalThis.Blob = dom.window.Blob;globalThis.getComputedStyle = dom.window.getComputedStyle;
// Matches head.html templateglobalThis.window.env = { hostName: "localhost", gitCommit: "test", environment: "test", version: "0.0.0", pluginSdkFileName: "pluginSdk.test.js", oauthScopes: "atproto", oauthOptionalScopes: "", playwright: false, useConfidentialOauth: false,};
// Node has its own global navigator; alias JSDOM's so code under test and// tests (which mock properties on window.navigator) see the same object.Object.defineProperty(globalThis, "navigator", { value: dom.window.navigator, configurable: true, writable: true,});
// Mock requestAnimationFrameconst realSetTimeout = globalThis.setTimeout;globalThis.requestAnimationFrame = (callback) => realSetTimeout(callback, 0);globalThis.window.requestAnimationFrame = globalThis.requestAnimationFrame;
// Mock matchMediaglobalThis.window.matchMedia = (query) => ({ matches: false, media: query, onchange: null, addListener: () => {}, removeListener: () => {}, addEventListener: () => {}, removeEventListener: () => {}, dispatchEvent: () => {},});
class IntersectionObserver { observe() {} unobserve() {} disconnect() {}}globalThis.IntersectionObserver = IntersectionObserver;
globalThis.window.HTMLDialogElement.prototype.showModal = function () { this.setAttribute("open", "");};globalThis.window.HTMLDialogElement.prototype.close = function () { this.removeAttribute("open"); this.dispatchEvent(new globalThis.Event("close"));};
globalThis.window.HTMLElement.prototype.showPopover = function () {};globalThis.window.HTMLElement.prototype.hidePopover = function () {};
globalThis.window.HTMLMediaElement.prototype.play = function () { return Promise.resolve();};globalThis.window.HTMLMediaElement.prototype.pause = function () {};globalThis.window.HTMLMediaElement.prototype.load = function () {};
// Prevent network requests. We can mock this in individual tests as needed.delete globalThis.fetch;
// JSDOM's window.crypto lacks subtle; swap in node's webcrypto.if (!globalThis.window.crypto?.subtle) { Object.defineProperty(globalThis.window, "crypto", { value: globalThis.crypto, configurable: true, writable: true, });}
class LocalStorageStub { constructor() { this._store = {}; } getItem(key) { return key in this._store ? this._store[key] : null; } setItem(key, value) { this._store[key] = String(value); } removeItem(key) { delete this._store[key]; } get length() { return Object.keys(this._store).length; } key(index) { return Object.keys(this._store)[index] ?? null; } clear() { this._store = {}; }}
globalThis.localStorage = new LocalStorageStub();
globalThis.Element = globalThis.window.Element;
// Components exercise scroll locking without the app bootstrap, so supply the// same active-page boundary through the test fixture.const { scrollLocks } = await import("/js/scrollLocks.js");scrollLocks.setContainerProvider(() => document.querySelector(".page-visible"));
class HighlightStub { constructor(...ranges) { this._ranges = new Set(ranges); } add(range) { this._ranges.add(range); } delete(range) { return this._ranges.delete(range); } clear() { this._ranges.clear(); } get size() { return this._ranges.size; }}globalThis.Highlight = HighlightStub;globalThis.window.Highlight = HighlightStub;if (typeof globalThis.window.CSS === "undefined") { globalThis.window.CSS = {};}globalThis.window.CSS.highlights = new Map();globalThis.CSS = globalThis.window.CSS;
// Real emoji element makes a fetch / uses indexeddb, so stub itclass EmojiPickerStub extends globalThis.window.HTMLElement {}if (!globalThis.customElements.get("emoji-picker")) { globalThis.customElements.define("emoji-picker", EmojiPickerStub);}
// Global per-test cleanup: emptying the body disconnects custom elements// (whose disconnectedCallback clears their timers) and cleanupToasts// cancels toast timers, so no test leaves timers pending on the event loop.const { afterEach } = await import("node:test");const { cleanupToasts } = await import("/js/toasts.js");afterEach(() => { document.body.replaceChildren(); cleanupToasts();});