Skip to content

Commit 6bbf49a

Browse files
committed
Add modular unit tests and helpers
1 parent fb045ce commit 6bbf49a

File tree

7 files changed

+204
-128
lines changed

7 files changed

+204
-128
lines changed

dist/latest/latest.dev.js

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Simple Analytics - Privacy friendly analytics (docs.simpleanalytics.com/script; 2023-05-03; ed1a; v11) */
1+
/* Simple Analytics - Privacy-first analytics (docs.simpleanalytics.com/script; 2025-05-29; 36a5; v12) */
22
/* eslint-env browser */
33

44
(function (
@@ -110,8 +110,8 @@
110110
return Array.isArray(csv)
111111
? csv
112112
: isString(csv) && csv.length
113-
? csv.split(/, ?/)
114-
: [];
113+
? csv.split(/, ?/)
114+
: [];
115115
};
116116

117117
var isObject = function (object) {
@@ -432,12 +432,13 @@
432432
// PAYLOAD FOR BOTH PAGE VIEWS AND EVENTS
433433
//
434434

435+
var phantom = window.phantom;
435436
var bot =
436437
nav.webdriver ||
437438
window.__nightmare ||
438439
window.callPhantom ||
439440
window._phantom ||
440-
window.phantom ||
441+
(phantom && !phantom.solana) ||
441442
window.__polypane ||
442443
window._bot ||
443444
isBotAgent ||
@@ -512,8 +513,12 @@
512513
var lastSendPath;
513514

514515
var getReferrer = function () {
516+
// Customers can overwrite their referrer, here we check for that
517+
var overwrittenReferrer =
518+
overwriteOptions.referrer || attr(scriptElement, "referrer");
519+
515520
return (
516-
(doc.referrer || "")
521+
(overwrittenReferrer || doc.referrer || "")
517522
.replace(locationHostname, definedHostname)
518523
.replace(/^https?:\/\/((m|l|w{2,3}([0-9]+)?)\.)?([^?#]+)(.*)$/, "$4")
519524
.replace(/^([^/]+)$/, "$1") || undefinedVar
@@ -738,7 +743,10 @@
738743
: falseVar;
739744

740745
// We set unique variable based on pushstate or back navigation, if no match we check the referrer
741-
page.unique = isPushState || userNavigated ? falseVar : !sameSite;
746+
page.unique =
747+
/__cf_/.test(getReferrer()) || isPushState || userNavigated
748+
? falseVar
749+
: !sameSite;
742750

743751
metadata = appendMetadata(metadata, {
744752
type: pageviewText,
@@ -841,11 +849,11 @@
841849
}
842850

843851
if (autoCollect) pageview();
844-
else {
845-
window.sa_pageview = function (path, metadata) {
846-
pageview(0, path, metadata);
847-
};
848-
}
852+
853+
window.sa_pageview = function (path, metadata) {
854+
pageview(0, path, metadata);
855+
};
856+
849857

850858
/////////////////////
851859
// EVENTS
@@ -938,6 +946,6 @@
938946
{},
939947
"simpleanalyticscdn.com",
940948
"queue.",
941-
"cdn_latest_dev_11",
949+
"cdn_latest_dev_12",
942950
"sa"
943951
);

test/unit/default.test.js

Lines changed: 0 additions & 116 deletions
This file was deleted.

test/unit/helpers/dom.js

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
const { JSDOM } = require("jsdom");
2+
const { readFileSync } = require("fs");
3+
const vm = require("vm");
4+
5+
const SCRIPT_PATH = "dist/latest/latest.dev.js";
6+
7+
/**
8+
* @typedef {"navigate" | "reload" | "back_forward" | "prerender"} NavigationType
9+
*/
10+
11+
/** @type {Record<NavigationType, {name: NavigationType, code: number}>} */
12+
const NAVIGATION_TYPES = {
13+
navigate: { name: "navigate", code: 0 },
14+
reload: { name: "reload", code: 1 },
15+
back_forward: { name: "back_forward", code: 2 },
16+
prerender: { name: "prerender", code: 255 },
17+
};
18+
19+
function createDOM(options = {}) {
20+
const {
21+
url = "https://example.com/",
22+
navigationType = "navigate",
23+
settings,
24+
beforeRun,
25+
} = options;
26+
const dom = new JSDOM("<!doctype html><html><body></body></html>", {
27+
url,
28+
runScripts: "outside-only",
29+
pretendToBeVisual: true,
30+
});
31+
32+
if (settings) {
33+
vm.runInContext(
34+
`window.sa_settings = ${JSON.stringify(settings)}`,
35+
dom.getInternalVMContext()
36+
);
37+
}
38+
39+
if (typeof beforeRun === "function") beforeRun(dom.getInternalVMContext());
40+
41+
const sent = [];
42+
dom.window.Image = function () {
43+
return {
44+
set src(value) {
45+
sent.push({ type: "image", url: value });
46+
},
47+
};
48+
};
49+
dom.window.navigator.sendBeacon = function (url, data) {
50+
sent.push({ type: "beacon", url, data });
51+
return true;
52+
};
53+
54+
Object.defineProperty(dom.window, "performance", {
55+
writable: true,
56+
value: {
57+
getEntriesByType: function (type) {
58+
if (type === "navigation") {
59+
return [{ type: NAVIGATION_TYPES[navigationType].name }];
60+
}
61+
return [];
62+
},
63+
navigation: { type: NAVIGATION_TYPES[navigationType].code },
64+
},
65+
});
66+
67+
const script = readFileSync(SCRIPT_PATH, "utf8");
68+
vm.runInContext(script, dom.getInternalVMContext());
69+
70+
dom.sent = sent;
71+
return dom;
72+
}
73+
74+
module.exports = {
75+
createDOM,
76+
SCRIPT_PATH,
77+
NAVIGATION_TYPES,
78+
};

test/unit/helpers/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require("./dom");

test/unit/ignore-pages.test.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
const { expect } = require("chai");
2+
const { createDOM } = require("./helpers/dom");
3+
4+
describe("ignore pages", function () {
5+
it("does not send a request for ignored paths", function (done) {
6+
const dom = createDOM({
7+
settings: { autoCollect: false, ignorePages: "/ignore" },
8+
});
9+
10+
dom.window.sa_pageview("/ignore");
11+
dom.window.sa_pageview("/allowed");
12+
13+
setTimeout(() => {
14+
const ignoreReq = dom.sent.find(
15+
(r) => r.type === "image" && /path=%2Fignore/.test(r.url)
16+
);
17+
const allowedReq = dom.sent.find(
18+
(r) => r.type === "image" && /path=%2Fallowed/.test(r.url)
19+
);
20+
21+
expect(ignoreReq, "request for ignored path").to.not.exist;
22+
expect(allowedReq, "request for allowed path").to.exist;
23+
done();
24+
}, 10);
25+
});
26+
});

test/unit/metadata.test.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
const { expect } = require("chai");
2+
const { createDOM } = require("./helpers/dom");
3+
4+
describe("metadata", function () {
5+
it("collects metadata from global object and collector", function (done) {
6+
const dom = createDOM({
7+
settings: { autoCollect: false, metadataCollector: "collector" },
8+
beforeRun(vmContext) {
9+
const { runInContext } = require("vm");
10+
runInContext(
11+
"window.sa_metadata = { fromGlobal: true };" +
12+
"window.collector = function(data){ return { fromCollector: true, path: data.path }; };",
13+
vmContext
14+
);
15+
},
16+
});
17+
18+
const { runInContext } = require("vm");
19+
runInContext(
20+
"window.manualMeta = { manual: true };",
21+
dom.getInternalVMContext()
22+
);
23+
dom.window.sa_pageview("/meta", dom.window.manualMeta);
24+
25+
setTimeout(() => {
26+
const req = dom.sent.find(
27+
(r) => r.type === "image" && /path=%2Fmeta/.test(r.url)
28+
);
29+
expect(req, "pageview request").to.exist;
30+
const url = new URL(req.url);
31+
const meta = JSON.parse(
32+
decodeURIComponent(url.searchParams.get("metadata"))
33+
);
34+
expect(meta).to.include({
35+
manual: true,
36+
fromGlobal: true,
37+
fromCollector: true,
38+
});
39+
done();
40+
}, 10);
41+
});
42+
});

test/unit/pageview.test.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
const { expect } = require("chai");
2+
const { createDOM } = require("./helpers/dom");
3+
4+
describe("pageview", function () {
5+
it("sends pageview, event and beacon requests", function (done) {
6+
const dom = createDOM({ navigationType: "reload" });
7+
8+
dom.window.sa_event("unit_test");
9+
10+
if (!("onpagehide" in dom.window)) {
11+
dom.window.document.hidden = true;
12+
dom.window.document.dispatchEvent(
13+
new dom.window.Event("visibilitychange")
14+
);
15+
} else {
16+
dom.window.dispatchEvent(new dom.window.Event("pagehide"));
17+
}
18+
19+
setTimeout(() => {
20+
const gif = dom.sent.find(
21+
(r) => r.type === "image" && /simple\.gif/.test(r.url)
22+
);
23+
const eventReq = dom.sent.find(
24+
(r) => r.type === "image" && /event=unit_test/.test(r.url)
25+
);
26+
const beacon = dom.sent.find((r) => r.type === "beacon");
27+
28+
expect(gif, "pageview gif request").to.exist;
29+
expect(eventReq, "event gif request").to.exist;
30+
expect(beacon, "append beacon request").to.exist;
31+
expect(beacon.url).to.match(/\/append$/);
32+
expect(beacon.data).to.include('"type":"append"');
33+
34+
done();
35+
}, 10);
36+
});
37+
});

0 commit comments

Comments
 (0)