deadhead-space-freight-tycoon/apps/web/test/presence.test.ts

117 lines
3.3 KiB
TypeScript

import { afterEach, describe, expect, it } from "vitest";
import {
browserPresence,
presenceAtStart,
presenceChange,
startPresence,
usePresence,
type PresenceHost,
} from "../src/platform/presence";
const T = 1_000_000;
/** A host whose visibility the test flips. */
function fakeHost(here = true) {
let listener: ((here: boolean) => void) | null = null;
const host: PresenceHost = {
isHere: () => here,
onChange(fn) {
listener = fn;
return () => (listener = null);
},
};
return {
host,
set(value: boolean) {
here = value;
listener?.(value);
},
get listening() {
return listener !== null;
},
};
}
let visibility: DocumentVisibilityState = "visible";
Object.defineProperty(document, "visibilityState", {
configurable: true,
get: () => visibility,
});
afterEach(() => {
visibility = "visible";
});
describe("presence (Step 28.2, decision 0.5.160)", () => {
it("records when the player went away and came back", () => {
let state = presenceAtStart(true, T, null);
expect(state).toEqual({
here: true,
hereSince: T,
awaySince: null,
lastReturn: null,
returns: 0,
});
state = presenceChange(state, false, T + 1_000);
expect(state).toMatchObject({ here: false, awaySince: T + 1_000 });
// Hidden again (a second event) changes nothing.
expect(presenceChange(state, false, T + 2_000)).toBe(state);
state = presenceChange(state, true, T + 301_000);
expect(state).toEqual({
here: true,
hereSince: T + 301_000,
awaySince: null,
lastReturn: { from: T + 1_000, to: T + 301_000 },
returns: 1,
});
});
it("counts a cold start as coming back from the save's last time seen", () => {
expect(presenceAtStart(true, T, T - 3_600_000)).toEqual({
here: true,
hereSince: T,
awaySince: null,
lastReturn: { from: T - 3_600_000, to: T },
returns: 1,
});
// Opened in a background tab: away since then, until it's shown.
const hidden = presenceAtStart(false, T, T - 3_600_000);
expect(presenceChange(hidden, true, T + 5_000).lastReturn).toEqual({
from: T - 3_600_000,
to: T + 5_000,
});
});
it("follows the host into the store, with times from the game clock", () => {
const fake = fakeHost();
let now = T;
const stop = startPresence(fake.host, () => now, T - 60_000);
expect(usePresence.getState().lastReturn).toEqual({ from: T - 60_000, to: T });
now = T + 10_000;
fake.set(false);
expect(usePresence.getState()).toMatchObject({ here: false, awaySince: T + 10_000 });
now = T + 40_000;
fake.set(true);
expect(usePresence.getState()).toMatchObject({
here: true,
lastReturn: { from: T + 10_000, to: T + 40_000 },
returns: 2,
});
stop();
expect(fake.listening).toBe(false);
});
it("reads the browser tab's visibility", () => {
const seen: boolean[] = [];
const stop = browserPresence.onChange((here) => seen.push(here));
visibility = "hidden";
document.dispatchEvent(new Event("visibilitychange"));
expect(browserPresence.isHere()).toBe(false);
visibility = "visible";
document.dispatchEvent(new Event("visibilitychange"));
stop();
document.dispatchEvent(new Event("visibilitychange"));
expect(seen).toEqual([false, true]);
});
});