1
0
mirror of https://github.com/jcwimer/wrestlingApp synced 2026-05-22 21:20:25 +00:00

New stats page, scoreboard, and live scores pages.

This commit is contained in:
2026-04-13 18:11:21 -04:00
parent 7526148ba5
commit c210b70c95
62 changed files with 10006 additions and 154 deletions

View File

@@ -0,0 +1,160 @@
import { describe, expect, it, vi } from "vitest"
import {
cleanupExpiredLocalStorage,
loadJson,
MATCH_DATA_TTL_MS,
performIfChanged,
removeKey,
saveJson,
SHORT_LIVED_TTL_MS
} from "match-state-transport"
describe("match state transport", () => {
it("loads saved json from storage", () => {
const storage = {
getItem: vi.fn(() => '{"score":3}')
}
expect(loadJson(storage, "match-state:1:1001")).toEqual({ score: 3 })
expect(storage.getItem).toHaveBeenCalledWith("match-state:1:1001")
})
it("returns null when stored json is invalid", () => {
const storage = {
getItem: vi.fn(() => "{not-json")
}
expect(loadJson(storage, "bad")).toBe(null)
})
it("saves and removes json values in storage", () => {
const storage = {
setItem: vi.fn(),
removeItem: vi.fn()
}
expect(saveJson(storage, "key", { score: 5 })).toBe(true)
expect(storage.setItem).toHaveBeenCalledWith("key", '{"score":5}')
expect(removeKey(storage, "key")).toBe(true)
expect(storage.removeItem).toHaveBeenCalledWith("key")
})
it("saves and loads expiring json values", () => {
vi.spyOn(Date, "now").mockReturnValue(1_000)
const storage = {
getItem: vi.fn(),
setItem: vi.fn(),
removeItem: vi.fn()
}
expect(saveJson(storage, "match-state:1:1001", { score: 5 }, { ttlMs: MATCH_DATA_TTL_MS })).toBe(true)
const savedValue = JSON.parse(storage.setItem.mock.calls[0][1])
expect(savedValue.expiresAt).toBe(1_000 + MATCH_DATA_TTL_MS)
storage.getItem.mockReturnValue(JSON.stringify(savedValue))
expect(loadJson(storage, "match-state:1:1001")).toEqual({ score: 5 })
})
it("removes expired json values when loading", () => {
vi.spyOn(Date, "now").mockReturnValue(10_000)
const storage = {
getItem: vi.fn(() => JSON.stringify({
__wrestlingAppStorage: true,
expiresAt: 9_000,
value: { score: 5 }
})),
removeItem: vi.fn()
}
expect(loadJson(storage, "match-state:1:1001")).toBe(null)
expect(storage.removeItem).toHaveBeenCalledWith("match-state:1:1001")
})
it("cleans expired app localStorage keys and leaves unrelated keys alone", () => {
const values = {
"match-state:1:1001": JSON.stringify({
__wrestlingAppStorage: true,
expiresAt: 1_000,
value: { score: 5 }
}),
"mat-selected-bout:1:2": JSON.stringify({
__wrestlingAppStorage: true,
expiresAt: 50_000,
value: { boutNumber: 1001 }
}),
"unrelated": JSON.stringify({
__wrestlingAppStorage: true,
expiresAt: 1_000,
value: "keep"
})
}
const keys = Object.keys(values)
const storage = {
length: keys.length,
key: vi.fn((index) => keys[index]),
getItem: vi.fn((key) => values[key]),
setItem: vi.fn(),
removeItem: vi.fn()
}
cleanupExpiredLocalStorage(storage, 10_000)
expect(storage.removeItem).toHaveBeenCalledWith("match-state:1:1001")
expect(storage.removeItem).not.toHaveBeenCalledWith("mat-selected-bout:1:2")
expect(storage.removeItem).not.toHaveBeenCalledWith("unrelated")
})
it("removes stale legacy stat data using its updated timestamp", () => {
const keys = ["w1-8-1001"]
const storage = {
length: 1,
key: vi.fn((index) => keys[index]),
getItem: vi.fn(() => JSON.stringify({
stats: "old",
updated_at: "2026-04-01T00:00:00.000Z"
})),
setItem: vi.fn(),
removeItem: vi.fn()
}
cleanupExpiredLocalStorage(storage, Date.parse("2026-04-13T00:00:00.000Z"))
expect(storage.removeItem).toHaveBeenCalledWith("w1-8-1001")
})
it("adds expiration to legacy app values that are still valid", () => {
const keys = ["mat-last-match-result:8:3"]
const storage = {
length: 1,
key: vi.fn((index) => keys[index]),
getItem: vi.fn(() => JSON.stringify("Result text")),
setItem: vi.fn(),
removeItem: vi.fn()
}
cleanupExpiredLocalStorage(storage, 2_000)
const wrappedValue = JSON.parse(storage.setItem.mock.calls[0][1])
expect(storage.setItem.mock.calls[0][0]).toBe("mat-last-match-result:8:3")
expect(wrappedValue.value).toBe("Result text")
expect(wrappedValue.expiresAt).toBe(2_000 + SHORT_LIVED_TTL_MS)
})
it("only performs subscription actions when the payload changes", () => {
const subscription = {
perform: vi.fn()
}
const firstSerialized = performIfChanged(subscription, "send_stat", { new_w1_stat: "T3" }, null)
const secondSerialized = performIfChanged(subscription, "send_stat", { new_w1_stat: "T3" }, firstSerialized)
const thirdSerialized = performIfChanged(subscription, "send_stat", { new_w1_stat: "T3 E1" }, secondSerialized)
expect(subscription.perform).toHaveBeenCalledTimes(2)
expect(subscription.perform).toHaveBeenNthCalledWith(1, "send_stat", { new_w1_stat: "T3" })
expect(subscription.perform).toHaveBeenNthCalledWith(2, "send_stat", { new_w1_stat: "T3 E1" })
expect(firstSerialized).toBe('{"new_w1_stat":"T3"}')
expect(secondSerialized).toBe(firstSerialized)
expect(thirdSerialized).toBe('{"new_w1_stat":"T3 E1"}')
})
})