mirror of
https://github.com/praktimarc/kst4contest.git
synced 2026-09-12 04:05:34 +02:00
120 lines
3.7 KiB
JavaScript
120 lines
3.7 KiB
JavaScript
const assert = require("node:assert/strict");
|
|
const fs = require("node:fs");
|
|
const path = require("node:path");
|
|
const test = require("node:test");
|
|
|
|
const {
|
|
formatVisitorCount,
|
|
loadVisitorCount,
|
|
validateVisitorCount
|
|
} = require("../src/assets/js/visitor-count");
|
|
|
|
const VALID_DATA = {
|
|
schemaVersion: 1,
|
|
visits: 1234,
|
|
since: "2026-09-11",
|
|
updatedAt: "2026-09-11T07:00:00Z"
|
|
};
|
|
|
|
function element() {
|
|
return { hidden: true, textContent: "" };
|
|
}
|
|
|
|
test("formats valid data for the English website", async () => {
|
|
const target = element();
|
|
let request;
|
|
const shown = await loadVisitorCount({
|
|
element: target,
|
|
fetchImpl: async (url, options) => {
|
|
request = { url, options };
|
|
return { ok: true, json: async () => VALID_DATA };
|
|
}
|
|
});
|
|
|
|
assert.equal(shown, true);
|
|
assert.equal(target.hidden, false);
|
|
assert.equal(target.textContent, "Visits since 11 September 2026: 1,234");
|
|
assert.equal(request.url, "/visitor-count.json");
|
|
assert.equal(request.options.credentials, "omit");
|
|
});
|
|
|
|
test("rejects unsupported schemas and invalid visit counts", () => {
|
|
for (const changes of [
|
|
{ schemaVersion: 2 },
|
|
{ visits: -1 },
|
|
{ visits: 1.5 },
|
|
{ visits: Number.MAX_SAFE_INTEGER + 1 }
|
|
]) {
|
|
assert.equal(validateVisitorCount({ ...VALID_DATA, ...changes }), null);
|
|
}
|
|
});
|
|
|
|
test("rejects invalid dates and timestamps without timezone shifts", () => {
|
|
for (const changes of [
|
|
{ since: "2026-02-30" },
|
|
{ since: "11-09-2026" },
|
|
{ updatedAt: "2026-09-11" },
|
|
{ updatedAt: "2026-02-30T07:00:00Z" },
|
|
{ updatedAt: "2026-09-11T25:00:00Z" },
|
|
{ updatedAt: "not-a-timestamp" }
|
|
]) {
|
|
assert.equal(formatVisitorCount({ ...VALID_DATA, ...changes }), null);
|
|
}
|
|
});
|
|
|
|
test("keeps the element hidden for HTTP and fetch failures", async () => {
|
|
for (const fetchImpl of [
|
|
async () => ({ ok: false }),
|
|
async () => { throw new Error("network failure"); }
|
|
]) {
|
|
const target = element();
|
|
assert.equal(await loadVisitorCount({ element: target, fetchImpl }), false);
|
|
assert.equal(target.hidden, true);
|
|
assert.equal(target.textContent, "");
|
|
}
|
|
});
|
|
|
|
test("aborts a slow request and keeps the element hidden", async () => {
|
|
const target = element();
|
|
let aborted = false;
|
|
const fetchImpl = (url, options) => new Promise((resolve, reject) => {
|
|
options.signal.addEventListener("abort", () => {
|
|
aborted = true;
|
|
reject(new Error("aborted"));
|
|
});
|
|
});
|
|
|
|
assert.equal(await loadVisitorCount({
|
|
element: target,
|
|
fetchImpl,
|
|
timeoutMs: 5
|
|
}), false);
|
|
assert.equal(aborted, true);
|
|
assert.equal(target.hidden, true);
|
|
});
|
|
|
|
test("uses textContent and loads the script only for the home page", () => {
|
|
const script = fs.readFileSync(path.join(
|
|
__dirname,
|
|
"../src/assets/js/visitor-count.js"
|
|
), "utf8");
|
|
const home = fs.readFileSync(path.join(__dirname, "../src/index.njk"), "utf8");
|
|
const layout = fs.readFileSync(path.join(
|
|
__dirname,
|
|
"../src/_layouts/base.njk"
|
|
), "utf8");
|
|
const sourceRoot = path.join(__dirname, "../src");
|
|
const otherPages = fs.readdirSync(sourceRoot, { recursive: true })
|
|
.filter(entry => entry.endsWith(".njk") && entry !== "index.njk");
|
|
|
|
assert.doesNotMatch(script, /innerHTML/);
|
|
assert.match(script, /textContent/);
|
|
assert.match(home, /^visitorCount: true$/m);
|
|
assert.match(layout, /\{% if visitorCount %\}/);
|
|
assert.equal(
|
|
otherPages.some(entry => fs.readFileSync(path.join(sourceRoot, entry), "utf8")
|
|
.includes("visitorCount: true")),
|
|
false
|
|
);
|
|
});
|