2026-07-30 09:43:08 +08:00
|
|
|
import assert from "node:assert/strict";
|
|
|
|
|
import test from "node:test";
|
|
|
|
|
import { FrameCache } from "../src/host/frame-cache.ts";
|
|
|
|
|
|
|
|
|
|
test("retains opted-in iframe identities across navigation", () => {
|
|
|
|
|
const cache = new FrameCache("services", {
|
2026-07-30 15:16:02 +08:00
|
|
|
inactiveTtlMs: 1_000,
|
2026-07-30 09:43:08 +08:00
|
|
|
shouldKeepAlive: (view) => view === "services" || view === "settings",
|
|
|
|
|
});
|
|
|
|
|
const services = cache.snapshot()[0];
|
|
|
|
|
|
|
|
|
|
const away = cache.activate("overview");
|
|
|
|
|
assert.deepEqual(
|
|
|
|
|
away.entries.map((entry) => entry.view),
|
|
|
|
|
["services", "overview"],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const back = cache.activate("services");
|
|
|
|
|
assert.equal(back.activated, services);
|
|
|
|
|
assert.equal(back.activated.instance, services.instance);
|
|
|
|
|
assert.deepEqual(
|
|
|
|
|
back.removed.map((entry) => entry.view),
|
|
|
|
|
["overview"],
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("recreates non-keep-alive documents after leaving them", () => {
|
|
|
|
|
const cache = new FrameCache("overview", {
|
2026-07-30 15:16:02 +08:00
|
|
|
inactiveTtlMs: 1_000,
|
2026-07-30 09:43:08 +08:00
|
|
|
shouldKeepAlive: () => false,
|
|
|
|
|
});
|
|
|
|
|
const firstInstance = cache.snapshot()[0].instance;
|
|
|
|
|
|
|
|
|
|
cache.activate("activity");
|
|
|
|
|
const transition = cache.activate("overview");
|
|
|
|
|
|
|
|
|
|
assert.notEqual(transition.activated.instance, firstInstance);
|
|
|
|
|
assert.deepEqual(
|
|
|
|
|
transition.removed.map((entry) => entry.view),
|
|
|
|
|
["activity"],
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2026-07-30 15:16:02 +08:00
|
|
|
test("expires a keep-alive document after its continuous hidden TTL", () => {
|
|
|
|
|
let now = 5_000;
|
2026-07-30 09:43:08 +08:00
|
|
|
const cache = new FrameCache("services", {
|
2026-07-30 15:16:02 +08:00
|
|
|
inactiveTtlMs: 1_000,
|
2026-07-30 09:43:08 +08:00
|
|
|
shouldKeepAlive: () => true,
|
2026-07-30 15:16:02 +08:00
|
|
|
now: () => now,
|
2026-07-30 09:43:08 +08:00
|
|
|
});
|
|
|
|
|
const servicesInstance = cache.snapshot()[0].instance;
|
|
|
|
|
|
2026-07-30 15:16:02 +08:00
|
|
|
// Time spent visible does not consume the inactive TTL.
|
|
|
|
|
now = 50_000;
|
2026-07-30 09:43:08 +08:00
|
|
|
cache.activate("settings");
|
2026-07-30 15:16:02 +08:00
|
|
|
now = 50_999;
|
|
|
|
|
assert.equal(cache.timeUntilExpiration(), 1);
|
|
|
|
|
assert.deepEqual(cache.expireInactive().removed, []);
|
2026-07-30 09:43:08 +08:00
|
|
|
|
2026-07-30 15:16:02 +08:00
|
|
|
now = 51_000;
|
|
|
|
|
const expiration = cache.expireInactive();
|
2026-07-30 09:43:08 +08:00
|
|
|
assert.deepEqual(
|
2026-07-30 15:16:02 +08:00
|
|
|
expiration.removed.map((entry) => entry.view),
|
2026-07-30 09:43:08 +08:00
|
|
|
["services"],
|
|
|
|
|
);
|
|
|
|
|
assert.deepEqual(
|
2026-07-30 15:16:02 +08:00
|
|
|
expiration.entries.map((entry) => entry.view),
|
|
|
|
|
["settings"],
|
2026-07-30 09:43:08 +08:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const recreated = cache.activate("services");
|
|
|
|
|
assert.notEqual(recreated.activated.instance, servicesInstance);
|
2026-07-30 15:16:02 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("revisiting a hidden page before its TTL preserves and resets it", () => {
|
|
|
|
|
let now = 0;
|
|
|
|
|
const cache = new FrameCache("services", {
|
|
|
|
|
inactiveTtlMs: 1_000,
|
|
|
|
|
shouldKeepAlive: () => true,
|
|
|
|
|
now: () => now,
|
|
|
|
|
});
|
|
|
|
|
const services = cache.snapshot()[0];
|
|
|
|
|
|
|
|
|
|
cache.activate("settings");
|
|
|
|
|
now = 999;
|
|
|
|
|
const revisited = cache.activate("services");
|
|
|
|
|
assert.equal(revisited.activated, services);
|
|
|
|
|
|
|
|
|
|
now = 1_500;
|
|
|
|
|
assert.deepEqual(cache.expireInactive().removed, []);
|
|
|
|
|
assert.equal(cache.timeUntilExpiration(), 499);
|
|
|
|
|
|
|
|
|
|
now = 1_999;
|
|
|
|
|
const expiration = cache.expireInactive();
|
2026-07-30 09:43:08 +08:00
|
|
|
assert.deepEqual(
|
2026-07-30 15:16:02 +08:00
|
|
|
expiration.removed.map((entry) => entry.view),
|
2026-07-30 09:43:08 +08:00
|
|
|
["settings"],
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2026-07-30 15:16:02 +08:00
|
|
|
test("rejects invalid inactive TTL values", () => {
|
|
|
|
|
for (const inactiveTtlMs of [0, -1, Number.POSITIVE_INFINITY, Number.NaN]) {
|
|
|
|
|
assert.throws(
|
|
|
|
|
() =>
|
|
|
|
|
new FrameCache("overview", {
|
|
|
|
|
inactiveTtlMs,
|
|
|
|
|
shouldKeepAlive: () => true,
|
|
|
|
|
}),
|
|
|
|
|
RangeError,
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-07-30 09:43:08 +08:00
|
|
|
});
|