dom.spec.ts 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. import { describe, expect, it } from "vitest";
  2. import { addGlobalStyle, addParent, getSiblingsFrame, getUnsafeWindow, interceptWindowEvent, isDomLoaded, observeElementProp, onDomLoad, openInNewTab, preloadImages, probeElementStyle, setInnerHtmlUnsafe } from "./dom.js";
  3. import { PlatformError } from "./errors.js";
  4. //#region getUnsafeWindow
  5. describe("dom/getUnsafeWindow", () => {
  6. it("Returns the correct window objects", () => {
  7. expect(getUnsafeWindow()).toBe(window);
  8. var unsafeWindow = window;
  9. expect(getUnsafeWindow()).toBe(unsafeWindow);
  10. });
  11. });
  12. //#region addParent
  13. describe("dom/addParent", () => {
  14. it("Adds a parent to an element", () => {
  15. const container = document.createElement("div");
  16. container.id = "container";
  17. const child = document.createElement("div");
  18. child.id = "child";
  19. document.body.appendChild(child);
  20. addParent(child, container);
  21. expect(child.parentNode).toBe(container);
  22. container.remove();
  23. });
  24. });
  25. //#region addGlobalStyle
  26. describe("dom/addGlobalStyle", () => {
  27. it("Adds a global style to the document", () => {
  28. const el = addGlobalStyle(`body { background-color: red; }`);
  29. el.id = "test-style";
  30. expect(document.querySelector("head #test-style")).toBe(el);
  31. });
  32. });
  33. //#region preloadImages
  34. //TODO:FIXME: no workis
  35. describe.skip("dom/preloadImages", () => {
  36. it("Preloads images", async () => {
  37. const res = await preloadImages(["https://picsum.photos/50/50"]);
  38. expect(Array.isArray(res)).toBe(true);
  39. expect(res.every(r => r.status === "fulfilled")).toBe(true);
  40. });
  41. });
  42. //#region openInNewTab
  43. describe("dom/openInNewTab", () => {
  44. it("Via GM.openInTab", () => {
  45. let link = "", bg;
  46. // @ts-expect-error
  47. window.GM = {
  48. openInTab(href: string, background?: boolean) {
  49. link = href;
  50. bg = background;
  51. }
  52. };
  53. openInNewTab("https://example.org", true);
  54. expect(link).toBe("https://example.org");
  55. expect(bg).toBe(true);
  56. // @ts-expect-error
  57. window.GM = {
  58. openInTab(_href: string, _background?: boolean) {
  59. throw new Error("Error");
  60. }
  61. }
  62. openInNewTab("https://example.org", true);
  63. expect(document.querySelector(".userutils-open-in-new-tab")).not.toBeNull();
  64. // @ts-expect-error
  65. delete window.GM;
  66. });
  67. });
  68. //#region interceptWindowEvent
  69. describe("dom/interceptWindowEvent", () => {
  70. it("Intercepts a window event", () => {
  71. let amount = 0;
  72. const inc = () => amount++;
  73. window.addEventListener("foo", inc);
  74. Error.stackTraceLimit = NaN;
  75. // @ts-expect-error
  76. interceptWindowEvent("foo", () => true);
  77. window.addEventListener("foo", inc);
  78. window.dispatchEvent(new Event("foo"));
  79. expect(amount).toBe(1);
  80. window.removeEventListener("foo", inc);
  81. });
  82. it("Throws when GM platform is FireMonkey", () => {
  83. // @ts-expect-error
  84. window.GM = { info: { scriptHandler: "FireMonkey" } };
  85. // @ts-expect-error
  86. expect(() => interceptWindowEvent("foo", () => true)).toThrow(PlatformError);
  87. // @ts-expect-error
  88. delete window.GM;
  89. });
  90. });
  91. //#region observeElementProp
  92. //TODO:FIXME: no workio
  93. describe.skip("dom/observeElementProp", () => {
  94. it("Observes an element property", () => {
  95. const el = document.createElement("input");
  96. el.type = "text";
  97. document.body.appendChild(el);
  98. let newVal = "";
  99. observeElementProp(el, "value", (_oldVal, newVal) => {
  100. newVal = newVal;
  101. });
  102. el.value = "foo";
  103. expect(newVal).toBe("foo");
  104. });
  105. });
  106. //#region getSiblingsFrame
  107. describe("dom/getSiblingsFrame", () => {
  108. it("Returns the correct frame", () => {
  109. const container = document.createElement("div");
  110. for(let i = 0; i < 10; i++) {
  111. const el = document.createElement("div");
  112. el.id = `e${i}`;
  113. container.appendChild(el);
  114. }
  115. const cntrEl = container.querySelector<HTMLElement>("#e5")!;
  116. expect(getSiblingsFrame(cntrEl, 2).map(e => e.id)).toEqual(["e5", "e6"]);
  117. expect(getSiblingsFrame(cntrEl, 2, "top", false).map(e => e.id)).toEqual(["e6", "e7"]);
  118. expect(getSiblingsFrame(cntrEl, 2, "bottom", false).map(e => e.id)).toEqual(["e3", "e4"]);
  119. expect(getSiblingsFrame(cntrEl, 2, "center-top", false).map(e => e.id)).toEqual(["e4", "e6"]);
  120. expect(getSiblingsFrame(cntrEl, 3, "center-top", true).map(e => e.id)).toEqual(["e4", "e5", "e6"]);
  121. expect(getSiblingsFrame(cntrEl, 4, "center-top", true).map(e => e.id)).toEqual(["e4", "e5", "e6", "e7"]);
  122. expect(getSiblingsFrame(cntrEl, 4, "center-bottom", true).map(e => e.id)).toEqual(["e3", "e4", "e5", "e6"]);
  123. // @ts-expect-error
  124. expect(getSiblingsFrame(cntrEl, 2, "invalid")).toHaveLength(0);
  125. });
  126. });
  127. //#region setInnerHtmlUnsafe
  128. describe("dom/setInnerHtmlUnsafe", () => {
  129. it("Sets inner HTML", () => {
  130. // @ts-expect-error
  131. window.trustedTypes = {
  132. createPolicy: (_name: string, opts: { createHTML: (html: string) => string }) => ({
  133. createHTML: opts.createHTML,
  134. }),
  135. };
  136. const el = document.createElement("div");
  137. setInnerHtmlUnsafe(el, "<div>foo</div>");
  138. expect(el.querySelector("div")?.textContent).toBe("foo");
  139. });
  140. });
  141. //#region probeElementStyle
  142. //TODO:FIXME: no workiong
  143. describe.skip("dom/probeElementStyle", () => {
  144. it("Resolves a CSS variable", async () => {
  145. addGlobalStyle(`:root { --foo: #f00; --bar: var(--foo, #00f); }`);
  146. const tryResolveCol = (i = 0) => new Promise<string>((res, rej) => {
  147. if(i > 100)
  148. return rej(new Error("Could not resolve color after 100 tries"));
  149. const probedCol = probeElementStyle(
  150. (style) => style.backgroundColor,
  151. () => {
  152. const elem = document.createElement("span");
  153. elem.style.backgroundColor = "var(--foo, #000)";
  154. return elem;
  155. },
  156. true,
  157. );
  158. if(probedCol.length === 0 || probedCol.match(/^rgba?\((?:(?:255,\s?255,\s?255)|(?:0,\s?0,\s?0))/) || probedCol.match(/^#(?:fff(?:fff)?|000(?:000)?)/))
  159. return setTimeout(async () => res(await tryResolveCol(++i)), 100);
  160. return res(probedCol);
  161. });
  162. const val = await tryResolveCol();
  163. expect(val).toBe("rgb(255, 0, 0)");
  164. });
  165. });
  166. //#region onDomLoad & isDomLoaded
  167. describe("dom/onDomLoad", () => {
  168. it("Resolves when the DOM is loaded", async () => {
  169. let cb = false;
  170. const res = onDomLoad(() => cb = true);
  171. document.dispatchEvent(new Event("DOMContentLoaded"));
  172. await res;
  173. expect(cb).toBe(true);
  174. expect(isDomLoaded()).toBe(true);
  175. cb = false;
  176. onDomLoad(() => cb = true);
  177. document.dispatchEvent(new Event("DOMContentLoaded"));
  178. expect(cb).toBe(true);
  179. });
  180. });