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:
160
test/javascript/match_state/transport.test.js
Normal file
160
test/javascript/match_state/transport.test.js
Normal 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"}')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user