oxynote/web/app/utils/load-progress.test.ts
2026-09-13 23:40:19 +02:00

134 lines
3.7 KiB
TypeScript

import { beforeEach, describe, it, vi, type MockInstance } from "vitest"
// the module keeps the running load's baseline and its throttle window in
// module scope, so every case re-imports it to start from a clean slate
async function loadModule() {
vi.resetModules()
return await import("./load-progress")
}
type ConsoleSpy = MockInstance<(...args: unknown[]) => void>
function lines(log: ConsoleSpy): string[] {
return log.mock.calls.map((call) => String(call[0]))
}
// the cases share the console spy and the module registry, so they cannot
// interleave
describe("load-progress", { concurrent: false }, () => {
let log: ConsoleSpy
beforeEach(() => {
log = vi.spyOn(console, "log").mockImplementation(() => undefined)
})
describe("logSectionReady", { concurrent: false }, () => {
it("names the section and how long it took", async ({ expect }) => {
const { logSectionReady, startLoadProgress } = await loadModule()
startLoadProgress()
logSectionReady("sidebar")
expect(lines(log)).toEqual([
expect.stringMatching(/^\[page-load\] sidebar ready in \d+\.\d\ds$/),
])
})
})
describe("logAsyncBlockProgress", { concurrent: false }, () => {
it("draws how many blocks rendered and how many are waiting", async ({
expect,
}) => {
const { logAsyncBlockProgress, startLoadProgress } = await loadModule()
startLoadProgress()
logAsyncBlockProgress(2, 4)
expect(lines(log)).toEqual([
"[page-load] async blocks ████████░░░░░░░░ 2/4 rendered, 2 waiting",
])
})
it("holds back an update that follows another too closely", async ({
expect,
}) => {
const { logAsyncBlockProgress, startLoadProgress } = await loadModule()
startLoadProgress()
logAsyncBlockProgress(1, 4)
logAsyncBlockProgress(2, 4)
expect(lines(log)).toEqual([
expect.stringContaining("1/4 rendered, 3 waiting"),
])
})
it("always draws the last block, however fast it arrives", async ({
expect,
}) => {
const { logAsyncBlockProgress, startLoadProgress } = await loadModule()
startLoadProgress()
logAsyncBlockProgress(1, 2)
logAsyncBlockProgress(2, 2)
expect(lines(log)).toEqual([
expect.stringContaining("1/2 rendered, 1 waiting"),
"[page-load] async blocks ████████████████ 2/2 rendered, 0 waiting",
])
})
})
describe("startLoadProgress", { concurrent: false }, () => {
it("times the first load from the moment the page opened", async ({
expect,
}) => {
const { logSectionReady, startLoadProgress } = await loadModule()
startLoadProgress()
logSectionReady("sidebar")
const seconds = Number(
/ready in (\d+\.\d\d)s$/.exec(lines(log)[0] ?? "")?.[1],
)
expect(seconds).toBeCloseTo(performance.now() / 1000, 1)
})
it("times a later load from its own start", async ({ expect }) => {
const { logSectionReady, startLoadProgress } = await loadModule()
startLoadProgress()
startLoadProgress()
logSectionReady("document data")
expect(lines(log)).toEqual(["[doc-switch] document data ready in 0.00s"])
})
it("marks a later load as a document switch rather than a page load", async ({
expect,
}) => {
const { logAsyncBlockProgress, startLoadProgress } = await loadModule()
startLoadProgress()
startLoadProgress()
logAsyncBlockProgress(1, 2)
expect(lines(log)).toEqual([
expect.stringMatching(/^\[doc-switch\] async blocks /),
])
})
it("reopens the throttle window for the next load", async ({ expect }) => {
const { logAsyncBlockProgress, startLoadProgress } = await loadModule()
startLoadProgress()
logAsyncBlockProgress(1, 4)
startLoadProgress()
logAsyncBlockProgress(2, 4)
expect(lines(log)).toHaveLength(2)
})
})
})