diff --git a/src/bricks/readers/ElementReader.test.ts b/src/bricks/readers/ElementReader.test.ts index 95334c3e37..a6f90e5b1c 100644 --- a/src/bricks/readers/ElementReader.test.ts +++ b/src/bricks/readers/ElementReader.test.ts @@ -17,10 +17,19 @@ import { ElementReader } from "@/bricks/readers/ElementReader"; import { validateUUID } from "@/types/helpers"; +import { rectFactory } from "@/testUtils/factories/domFactories"; const reader = new ElementReader(); describe("ElementReader", () => { + beforeEach(() => { + // `jsdom` does not implement full layout engine + // https://github.com/jsdom/jsdom#unimplemented-parts-of-the-web-platform + (Element.prototype.getBoundingClientRect as any) = jest.fn(() => + rectFactory(), + ); + }); + test("it produces valid element reference", async () => { const div = document.createElement("div"); const { ref } = await reader.read(div); @@ -44,4 +53,29 @@ describe("ElementReader", () => { const { isVisible } = await reader.read(div); expect(isVisible).toBe(true); }); + + test("isInViewport: true for element in document", async () => { + const div = document.createElement("div"); + div.innerHTML = "
Some text
"; + document.body.append(div); + + const { isInViewport } = await reader.read(div); + expect(isInViewport).toBe(true); + }); + + test("isInViewport: false for element partially outside of document", async () => { + (Element.prototype.getBoundingClientRect as any) = jest.fn(() => + rectFactory({ + width: window.innerWidth + 1, + right: window.innerWidth + 1, + }), + ); + + const div = document.createElement("div"); + div.innerHTML = "Some text
"; + document.body.append(div); + + const { isInViewport } = await reader.read(div); + expect(isInViewport).toBe(false); + }); }); diff --git a/src/bricks/readers/ElementReader.ts b/src/bricks/readers/ElementReader.ts index e0b0314fe4..9ce9deadac 100644 --- a/src/bricks/readers/ElementReader.ts +++ b/src/bricks/readers/ElementReader.ts @@ -20,7 +20,7 @@ import { getReferenceForElement } from "@/contentScript/elementReference"; import { ReaderABC } from "@/types/bricks/readerTypes"; import { type SelectorRoot } from "@/types/runtimeTypes"; import { type Schema } from "@/types/schemaTypes"; -import { isVisible } from "@/utils/domUtils"; +import { isInViewport, isVisible } from "@/utils/domUtils"; /** * Read attributes, text, etc. from an HTML element. @@ -49,6 +49,7 @@ export class ElementReader extends ReaderABC { return { ref: getReferenceForElement(element), isVisible: isVisible(element), + isInViewport: isInViewport(element), tagName: element.tagName, attrs: Object.fromEntries( Object.values(element.attributes).map((x) => [x.name, x.value]), @@ -89,8 +90,20 @@ export class ElementReader extends ReaderABC { type: "boolean", description: "True if the element is visible", }, + isInViewport: { + type: "boolean", + description: "True if element is completely in the viewport", + }, }, - required: ["tagName", "attrs", "data", "text", "ref", "isVisible"], + required: [ + "tagName", + "attrs", + "data", + "text", + "ref", + "isVisible", + "isInViewport", + ], additionalProperties: false, }; diff --git a/src/utils/domUtils.ts b/src/utils/domUtils.ts index 0649513890..14ecd07564 100644 --- a/src/utils/domUtils.ts +++ b/src/utils/domUtils.ts @@ -132,11 +132,40 @@ export async function waitForBody(): Promise