From 67edab18b8f8debb7d14986a5312206b654434bb Mon Sep 17 00:00:00 2001 From: Adriaan <1079135+adriaandotcom@users.noreply.github.com> Date: Sat, 14 Jun 2025 01:09:18 +0200 Subject: [PATCH] Add unit tests for auto-events --- test/unit/auto-events.test.js | 67 ++++++++++++++++++++++++ test/unit/helpers/dom-auto-events.js | 78 ++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 test/unit/auto-events.test.js create mode 100644 test/unit/helpers/dom-auto-events.js diff --git a/test/unit/auto-events.test.js b/test/unit/auto-events.test.js new file mode 100644 index 0000000..48beb50 --- /dev/null +++ b/test/unit/auto-events.test.js @@ -0,0 +1,67 @@ +const { expect } = require("chai"); +const { createDOM } = require("./helpers/dom-auto-events"); + +function setupDOM() { + const events = []; + const dom = createDOM({ + beforeRun(vm) { + vm.sa_event = function (name, metadata, cb) { + events.push({ name, metadata }); + if (typeof cb === "function") cb(); + }; + vm.sa_event_loaded = true; + }, + }); + dom.events = events; + return dom; +} + +describe("auto-events", function () { + it("tracks outbound link clicks", function (done) { + const dom = setupDOM(); + const link = dom.window.document.createElement("a"); + link.href = "https://example.org/path"; + link.target = "_blank"; + dom.window.document.body.appendChild(link); + + dom.window.saAutomatedLink(link, "outbound"); + + setTimeout(() => { + expect(dom.events[0]).to.deep.include({ name: "outbound_example_org" }); + expect(dom.events[0].metadata).to.include({ url: link.href }); + done(); + }, 0); + }); + + it("tracks download link clicks", function (done) { + const dom = setupDOM(); + const link = dom.window.document.createElement("a"); + link.href = "https://example.com/file.pdf"; + link.target = "_blank"; + dom.window.document.body.appendChild(link); + + dom.window.saAutomatedLink(link, "download"); + + setTimeout(() => { + expect(dom.events[0]).to.deep.include({ name: "download_file_pdf" }); + expect(dom.events[0].metadata).to.include({ url: link.href }); + done(); + }, 0); + }); + + it("tracks email link clicks", function (done) { + const dom = setupDOM(); + const link = dom.window.document.createElement("a"); + link.href = "mailto:test@example.com"; + link.target = "_blank"; + dom.window.document.body.appendChild(link); + + dom.window.saAutomatedLink(link, "email"); + + setTimeout(() => { + expect(dom.events[0]).to.deep.include({ name: "email_test_example_com" }); + expect(dom.events[0].metadata).to.include({ email: "test@example.com" }); + done(); + }, 0); + }); +}); diff --git a/test/unit/helpers/dom-auto-events.js b/test/unit/helpers/dom-auto-events.js new file mode 100644 index 0000000..eaf7ccc --- /dev/null +++ b/test/unit/helpers/dom-auto-events.js @@ -0,0 +1,78 @@ +const { JSDOM } = require("jsdom"); +const { readFileSync } = require("fs"); +const vm = require("vm"); + +const SCRIPT_PATH = "dist/latest/auto-events.js"; + +/** + * @typedef {"navigate" | "reload" | "back_forward" | "prerender"} NavigationType + */ + +/** @type {Record} */ +const NAVIGATION_TYPES = { + navigate: { name: "navigate", code: 0 }, + reload: { name: "reload", code: 1 }, + back_forward: { name: "back_forward", code: 2 }, + prerender: { name: "prerender", code: 255 }, +}; + +function createDOM(options = {}) { + const { + url = "https://example.com/", + navigationType = "navigate", + settings, + beforeRun, + } = options; + const dom = new JSDOM("", { + url, + runScripts: "outside-only", + pretendToBeVisual: true, + }); + + if (settings) { + vm.runInContext( + `window.sa_settings = ${JSON.stringify(settings)}`, + dom.getInternalVMContext() + ); + } + + if (typeof beforeRun === "function") beforeRun(dom.getInternalVMContext()); + + const sent = []; + dom.window.Image = function () { + return { + set src(value) { + sent.push({ type: "image", url: value }); + }, + }; + }; + dom.window.navigator.sendBeacon = function (url, data) { + sent.push({ type: "beacon", url, data }); + return true; + }; + + Object.defineProperty(dom.window, "performance", { + writable: true, + value: { + getEntriesByType: function (type) { + if (type === "navigation") { + return [{ type: NAVIGATION_TYPES[navigationType].name }]; + } + return []; + }, + navigation: { type: NAVIGATION_TYPES[navigationType].code }, + }, + }); + + const script = readFileSync(SCRIPT_PATH, "utf8"); + vm.runInContext(script, dom.getInternalVMContext()); + + dom.sent = sent; + return dom; +} + +module.exports = { + createDOM, + SCRIPT_PATH, + NAVIGATION_TYPES, +};