1
0
mirror of https://github.com/jcwimer/wrestlingApp synced 2026-04-11 16:01:56 +00:00
Files
wrestlingdev.com/test/javascript/controllers/match_scoreboard_controller.test.js

356 lines
13 KiB
JavaScript

import { beforeEach, describe, expect, it, vi } from "vitest"
import MatchScoreboardController from "../../../app/assets/javascripts/controllers/match_scoreboard_controller.js"
function makeTarget() {
return {
textContent: "",
innerHTML: "",
style: {}
}
}
function buildController() {
const controller = new MatchScoreboardController()
controller.sourceModeValue = "localstorage"
controller.displayModeValue = "fullscreen"
controller.initialBoutNumberValue = 1001
controller.matchIdValue = 22
controller.matIdValue = 3
controller.tournamentIdValue = 8
controller.hasRedSectionTarget = true
controller.hasCenterSectionTarget = true
controller.hasGreenSectionTarget = true
controller.hasEmptyStateTarget = true
controller.hasRedNameTarget = true
controller.hasRedSchoolTarget = true
controller.hasRedScoreTarget = true
controller.hasRedTimerIndicatorTarget = true
controller.hasGreenNameTarget = true
controller.hasGreenSchoolTarget = true
controller.hasGreenScoreTarget = true
controller.hasGreenTimerIndicatorTarget = true
controller.hasClockTarget = true
controller.hasPeriodLabelTarget = true
controller.hasWeightLabelTarget = true
controller.hasBoutLabelTarget = true
controller.hasTimerBannerTarget = true
controller.hasTimerBannerLabelTarget = true
controller.hasTimerBannerClockTarget = true
controller.hasRedStatsTarget = true
controller.hasGreenStatsTarget = true
controller.hasLastMatchResultTarget = true
controller.redSectionTarget = makeTarget()
controller.centerSectionTarget = makeTarget()
controller.greenSectionTarget = makeTarget()
controller.emptyStateTarget = makeTarget()
controller.redNameTarget = makeTarget()
controller.redSchoolTarget = makeTarget()
controller.redScoreTarget = makeTarget()
controller.redTimerIndicatorTarget = makeTarget()
controller.greenNameTarget = makeTarget()
controller.greenSchoolTarget = makeTarget()
controller.greenScoreTarget = makeTarget()
controller.greenTimerIndicatorTarget = makeTarget()
controller.clockTarget = makeTarget()
controller.periodLabelTarget = makeTarget()
controller.weightLabelTarget = makeTarget()
controller.boutLabelTarget = makeTarget()
controller.timerBannerTarget = makeTarget()
controller.timerBannerLabelTarget = makeTarget()
controller.timerBannerClockTarget = makeTarget()
controller.redStatsTarget = makeTarget()
controller.greenStatsTarget = makeTarget()
controller.lastMatchResultTarget = makeTarget()
return controller
}
describe("match scoreboard controller", () => {
beforeEach(() => {
vi.restoreAllMocks()
vi.spyOn(Date, "now").mockReturnValue(1_000)
global.window = {
localStorage: {
getItem: vi.fn(() => null)
},
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
setInterval: vi.fn(() => 123),
clearInterval: vi.fn()
}
})
it("connects in localstorage mode using the extracted connection plan", () => {
const controller = buildController()
controller.setupMatSubscription = vi.fn()
controller.setupMatchSubscription = vi.fn()
controller.loadSelectedBoutNumber = vi.fn()
controller.loadStateFromLocalStorage = vi.fn()
controller.render = vi.fn()
controller.connect()
expect(window.addEventListener).toHaveBeenCalledWith("storage", controller.storageListener)
expect(controller.loadSelectedBoutNumber).toHaveBeenCalledTimes(1)
expect(controller.loadStateFromLocalStorage).toHaveBeenCalledTimes(1)
expect(controller.setupMatSubscription).toHaveBeenCalledTimes(1)
expect(controller.setupMatchSubscription).not.toHaveBeenCalled()
expect(controller.render).toHaveBeenCalledTimes(1)
})
it("connects with populated localStorage state before any timer snapshot exists", () => {
const controller = buildController()
const persistedState = {
metadata: {
boutNumber: 1001,
ruleset: "folkstyle_usa",
bracketPosition: "Bracket Round of 64",
w1Name: "Alpha",
w2Name: "Bravo"
},
assignment: { w1: "green", w2: "red" },
participantScores: { w1: 0, w2: 0 },
phaseIndex: 0,
clocksByPhase: {
period_1: { remainingSeconds: 120, running: false, startedAt: null }
},
timers: {
w1: { blood: { remainingSeconds: 300, running: false, startedAt: null } },
w2: { blood: { remainingSeconds: 300, running: false, startedAt: null } }
}
}
window.localStorage.getItem = vi.fn((key) => {
if (key === "match-state:8:1001") return JSON.stringify(persistedState)
return null
})
controller.setupMatSubscription = vi.fn()
expect(() => controller.connect()).not.toThrow()
expect(controller.previousTimerSnapshot).toEqual({
"w1:blood": false,
"w2:blood": false
})
controller.disconnect()
})
it("renders populated scoreboard targets from state", () => {
const controller = buildController()
controller.currentBoutNumber = 1001
controller.liveMatchData = { w1_stat: "T3", w2_stat: "E1" }
controller.lastMatchResult = "Previous result"
controller.state = {
metadata: {
ruleset: "folkstyle_usa",
bracketPosition: "Bracket Round of 64",
weightLabel: "106",
w1Name: "Alpha",
w2Name: "Bravo",
w1School: "School A",
w2School: "School B"
},
assignment: { w1: "green", w2: "red" },
participantScores: { w1: 3, w2: 1 },
phaseIndex: 0,
clocksByPhase: {
period_1: { remainingSeconds: 120, running: false, startedAt: null }
},
timers: { w1: {}, w2: {} }
}
controller.timerBannerState = null
controller.render()
expect(controller.emptyStateTarget.style.display).toBe("none")
expect(controller.redNameTarget.textContent).toBe("Bravo")
expect(controller.redSchoolTarget.textContent).toBe("School B")
expect(controller.redScoreTarget.textContent).toBe("1")
expect(controller.greenNameTarget.textContent).toBe("Alpha")
expect(controller.greenSchoolTarget.textContent).toBe("School A")
expect(controller.greenScoreTarget.textContent).toBe("3")
expect(controller.clockTarget.textContent).toBe("2:00")
expect(controller.periodLabelTarget.textContent).toBe("Period 1")
expect(controller.weightLabelTarget.textContent).toBe("Weight 106")
expect(controller.boutLabelTarget.textContent).toBe("Bout 1001")
expect(controller.redStatsTarget.textContent).toBe("E1")
expect(controller.greenStatsTarget.textContent).toBe("T3")
expect(controller.lastMatchResultTarget.textContent).toBe("Previous result")
expect(controller.redSectionTarget.style.background).toBe("#c91f1f")
expect(controller.greenSectionTarget.style.background).toBe("#1cab2d")
})
it("renders empty scoreboard targets when there is no match state", () => {
const controller = buildController()
controller.currentBoutNumber = 1005
controller.lastMatchResult = "Last result"
controller.state = null
controller.render()
expect(controller.emptyStateTarget.style.display).toBe("block")
expect(controller.redNameTarget.textContent).toBe("NO MATCH")
expect(controller.greenNameTarget.textContent).toBe("NO MATCH")
expect(controller.clockTarget.textContent).toBe("-")
expect(controller.periodLabelTarget.textContent).toBe("No Match")
expect(controller.boutLabelTarget.textContent).toBe("Bout 1005")
expect(controller.lastMatchResultTarget.textContent).toBe("Last result")
expect(controller.redSectionTarget.style.background).toBe("#000")
expect(controller.greenSectionTarget.style.background).toBe("#000")
})
it("renders a visible timer banner when the match clock is not running", () => {
const controller = buildController()
controller.config = {
phaseSequence: [{ key: "period_1", type: "period" }],
timers: { blood: { label: "Blood", maxSeconds: 300 } }
}
controller.state = {
phaseIndex: 0,
metadata: { w1Name: "Alpha" },
assignment: { w1: "green", w2: "red" },
clocksByPhase: { period_1: { running: false, remainingSeconds: 120, startedAt: null } },
timers: {
w1: { blood: { running: true, remainingSeconds: 300, startedAt: 1_000 } },
w2: {}
}
}
controller.timerBannerState = { participantKey: "w1", timerKey: "blood", expiresAt: null }
controller.renderTimerBanner()
expect(controller.timerBannerTarget.style.display).toBe("block")
expect(controller.timerBannerLabelTarget.textContent).toBe("Green Alpha Blood Running")
expect(controller.timerBannerClockTarget.textContent).toBe("0:00")
})
it("hides an expiring timer banner when the main clock is running", () => {
const controller = buildController()
controller.config = {
phaseSequence: [{ key: "period_1", type: "period" }],
timers: { blood: { label: "Blood", maxSeconds: 300 } }
}
controller.state = {
phaseIndex: 0,
metadata: { w1Name: "Alpha" },
assignment: { w1: "green", w2: "red" },
clocksByPhase: { period_1: { running: true, remainingSeconds: 120, startedAt: 1_000 } },
timers: {
w1: { blood: { running: false, remainingSeconds: 280, startedAt: null } },
w2: {}
}
}
controller.timerBannerState = { participantKey: "w1", timerKey: "blood", expiresAt: 5_000 }
controller.renderTimerBanner()
expect(controller.timerBannerState).toBe(null)
expect(controller.timerBannerTarget.style.display).toBe("none")
})
it("handles mat payload changes by switching match subscriptions and rendering", () => {
const controller = buildController()
controller.sourceModeValue = "mat_websocket"
controller.currentQueueBoutNumber = 1001
controller.currentBoutNumber = 1001
controller.currentMatchId = 10
controller.liveMatchData = { w1_stat: "Old" }
controller.state = { metadata: { boutNumber: 1001 } }
controller.setupMatchSubscription = vi.fn()
controller.unsubscribeMatchSubscription = vi.fn()
controller.loadSelectedBoutNumber = vi.fn()
controller.loadStateFromLocalStorage = vi.fn()
controller.resetTimerBannerState = vi.fn()
controller.render = vi.fn()
controller.handleMatPayload({
queue1_bout_number: 1001,
queue1_match_id: 10,
selected_bout_number: 1002,
selected_match_id: 11,
last_match_result: "Result"
})
expect(controller.currentMatchId).toBe(11)
expect(controller.currentBoutNumber).toBe(1002)
expect(controller.lastMatchResult).toBe("Result")
expect(controller.resetTimerBannerState).toHaveBeenCalledTimes(1)
expect(controller.setupMatchSubscription).toHaveBeenCalledWith(11)
expect(controller.render).toHaveBeenCalledTimes(1)
})
it("loads selected mat payload state from localStorage when the selected-bout key is missing", () => {
const controller = buildController()
const selectedState = {
metadata: {
boutNumber: 1002,
ruleset: "folkstyle_usa",
bracketPosition: "Bracket Round of 64"
},
matchResult: { finished: false }
}
window.localStorage.getItem = vi.fn((key) => {
if (key === "match-state:8:1002") return JSON.stringify(selectedState)
return null
})
controller.render = vi.fn()
controller.handleMatPayload({
queue1_bout_number: 1001,
selected_bout_number: 1002,
last_match_result: ""
})
expect(controller.currentBoutNumber).toBe(1002)
expect(controller.state).toEqual(selectedState)
expect(controller.render).toHaveBeenCalledTimes(1)
})
it("reloads selected bout and local state on selected-bout storage changes", () => {
const controller = buildController()
controller.currentQueueBoutNumber = 1001
controller.currentBoutNumber = 1001
controller.loadSelectedBoutNumber = vi.fn()
controller.loadStateFromLocalStorage = vi.fn()
controller.render = vi.fn()
controller.handleStorageChange({ key: "mat-selected-bout:8:3" })
expect(controller.loadSelectedBoutNumber).toHaveBeenCalledTimes(1)
expect(controller.loadStateFromLocalStorage).toHaveBeenCalledTimes(1)
expect(controller.render).toHaveBeenCalledTimes(1)
})
it("applies match websocket payloads into live match data and scoreboard state", () => {
const controller = buildController()
controller.currentBoutNumber = 1001
controller.liveMatchData = { w1_stat: "Old" }
controller.state = null
controller.handleMatchPayload({
scoreboard_state: {
metadata: { boutNumber: 1002 },
matchResult: { finished: true }
},
w1_stat: "T3",
w2_stat: "E1",
finished: 1
})
expect(controller.currentBoutNumber).toBe(1002)
expect(controller.finished).toBe(true)
expect(controller.liveMatchData).toEqual({
w1_stat: "T3",
w2_stat: "E1",
finished: 1
})
expect(controller.state).toEqual({
metadata: { boutNumber: 1002 },
matchResult: { finished: true }
})
})
})