109 lines
3.2 KiB
TypeScript
109 lines
3.2 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, 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,
|
|
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,
|
|
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]);
|
|
});
|
|
});
|