import { describe, it, expect, beforeEach, vi } from "vitest"; import { mount } from "@vue/test-utils"; import { createPinia, setActivePinia } from "pinia"; import RunnersOnBase from "~/components/Game/RunnersOnBase.vue"; import RunnerCard from "~/components/Game/RunnerCard.vue"; import { useGameStore } from "~/store/game"; import type { LineupPlayerState, Lineup } from "~/types/game"; describe("RunnersOnBase", () => { let pinia: ReturnType; beforeEach(() => { pinia = createPinia(); setActivePinia(pinia); }); const mockRunnerFirst: LineupPlayerState = { lineup_id: 1, batting_order: 1, position: "LF", card_id: 101, }; const mockRunnerSecond: LineupPlayerState = { lineup_id: 2, batting_order: 2, position: "CF", card_id: 102, }; const mockRunnerThird: LineupPlayerState = { lineup_id: 3, batting_order: 3, position: "RF", card_id: 103, }; const mockCatcher: Lineup = { id: 1, lineup_id: 4, team_id: 1, batting_order: 4, position: "C", is_active: true, player: { id: 104, name: "Buster Posey", image: "https://example.com/posey.jpg", headshot: "https://example.com/posey-headshot.jpg", }, }; const mockFieldingLineup: Lineup[] = [mockCatcher]; describe("component visibility", () => { it("does not render when no runners on base", () => { const wrapper = mount(RunnersOnBase, { global: { plugins: [pinia] }, props: { runners: { first: null, second: null, third: null }, fieldingLineup: mockFieldingLineup, battingTeamColor: "#3b82f6", fieldingTeamColor: "#10b981", battingTeamAbbrev: "BOS", fieldingTeamAbbrev: "NYY", }, }); expect(wrapper.find(".runners-on-base-container").exists()).toBe( false, ); }); it("renders when at least one runner on base", () => { const wrapper = mount(RunnersOnBase, { global: { plugins: [pinia] }, props: { runners: { first: null, second: mockRunnerSecond, third: null, }, fieldingLineup: mockFieldingLineup, battingTeamColor: "#3b82f6", fieldingTeamColor: "#10b981", battingTeamAbbrev: "BOS", fieldingTeamAbbrev: "NYY", }, }); expect(wrapper.find(".runners-on-base-container").exists()).toBe( true, ); }); it("renders when bases loaded", () => { const wrapper = mount(RunnersOnBase, { global: { plugins: [pinia] }, props: { runners: { first: mockRunnerFirst, second: mockRunnerSecond, third: mockRunnerThird, }, fieldingLineup: mockFieldingLineup, battingTeamColor: "#3b82f6", fieldingTeamColor: "#10b981", battingTeamAbbrev: "BOS", fieldingTeamAbbrev: "NYY", }, }); expect(wrapper.find(".runners-on-base-container").exists()).toBe( true, ); }); }); describe("runner cards", () => { it("renders three RunnerCard components", () => { const wrapper = mount(RunnersOnBase, { global: { plugins: [pinia] }, props: { runners: { first: mockRunnerFirst, second: null, third: null, }, fieldingLineup: mockFieldingLineup, battingTeamColor: "#3b82f6", fieldingTeamColor: "#10b981", battingTeamAbbrev: "BOS", fieldingTeamAbbrev: "NYY", }, }); const runnerCards = wrapper.findAllComponents(RunnerCard); expect(runnerCards).toHaveLength(3); }); it("passes correct base labels to RunnerCard components", async () => { const wrapper = mount(RunnersOnBase, { global: { plugins: [pinia] }, props: { runners: { first: mockRunnerFirst, second: null, third: null, }, fieldingLineup: mockFieldingLineup, battingTeamColor: "#3b82f6", fieldingTeamColor: "#10b981", battingTeamAbbrev: "BOS", fieldingTeamAbbrev: "NYY", }, }); await wrapper.vm.$nextTick(); const runnerCards = wrapper.findAllComponents(RunnerCard); expect(runnerCards.length).toBeGreaterThanOrEqual(3); expect(runnerCards[0].props("base")).toBe("1B"); expect(runnerCards[1].props("base")).toBe("2B"); expect(runnerCards[2].props("base")).toBe("3B"); }); it("passes runner data to RunnerCard components", () => { const wrapper = mount(RunnersOnBase, { global: { plugins: [pinia] }, props: { runners: { first: mockRunnerFirst, second: mockRunnerSecond, third: null, }, fieldingLineup: mockFieldingLineup, battingTeamColor: "#3b82f6", fieldingTeamColor: "#10b981", battingTeamAbbrev: "BOS", fieldingTeamAbbrev: "NYY", }, }); const runnerCards = wrapper.findAllComponents(RunnerCard); expect(runnerCards[0].props("runner")).toEqual(mockRunnerFirst); expect(runnerCards[1].props("runner")).toEqual(mockRunnerSecond); expect(runnerCards[2].props("runner")).toBeNull(); }); it("passes team color to RunnerCard components", () => { const wrapper = mount(RunnersOnBase, { global: { plugins: [pinia] }, props: { runners: { first: mockRunnerFirst, second: null, third: null, }, fieldingLineup: mockFieldingLineup, battingTeamColor: "#ff0000", fieldingTeamColor: "#00ff00", battingTeamAbbrev: "BOS", fieldingTeamAbbrev: "NYY", }, }); const runnerCards = wrapper.findAllComponents(RunnerCard); runnerCards.forEach((card) => { expect(card.props("teamColor")).toBe("#ff0000"); }); }); }); describe("catcher display", () => { it("shows collapsed catcher card by default", () => { const wrapper = mount(RunnersOnBase, { global: { plugins: [pinia] }, props: { runners: { first: mockRunnerFirst, second: null, third: null, }, fieldingLineup: mockFieldingLineup, battingTeamColor: "#3b82f6", fieldingTeamColor: "#10b981", battingTeamAbbrev: "BOS", fieldingTeamAbbrev: "NYY", }, }); // Collapsed state shows border-l-4, expanded state shows .matchup-card expect(wrapper.find(".border-l-4.border-gray-600").exists()).toBe( true, ); expect(wrapper.find(".matchup-card").exists()).toBe(false); }); it("displays catcher name", () => { const wrapper = mount(RunnersOnBase, { global: { plugins: [pinia] }, props: { runners: { first: mockRunnerFirst, second: null, third: null, }, fieldingLineup: mockFieldingLineup, battingTeamColor: "#3b82f6", fieldingTeamColor: "#10b981", battingTeamAbbrev: "BOS", fieldingTeamAbbrev: "NYY", }, }); expect(wrapper.text()).toContain("Buster Posey"); }); it('shows "Unknown Catcher" when no catcher in lineup', () => { const wrapper = mount(RunnersOnBase, { global: { plugins: [pinia] }, props: { runners: { first: mockRunnerFirst, second: null, third: null, }, fieldingLineup: [], battingTeamColor: "#3b82f6", fieldingTeamColor: "#10b981", battingTeamAbbrev: "BOS", fieldingTeamAbbrev: "NYY", }, }); expect(wrapper.text()).toContain("Unknown Catcher"); }); }); describe("runner selection", () => { it("expands catcher card when runner is selected", async () => { const gameStore = useGameStore(); gameStore.setGameState({ id: 1, home_team_id: 1, away_team_id: 2, status: "active", inning: 1, half: "top", outs: 0, home_score: 0, away_score: 0, home_team_abbrev: "NYY", away_team_abbrev: "BOS", home_team_dice_color: "3b82f6", current_batter: null, current_pitcher: null, on_first: null, on_second: null, on_third: null, decision_phase: "idle", play_count: 0, }); gameStore.updateLineup(1, [ { id: 1, lineup_id: 1, team_id: 1, batting_order: 1, position: "LF", is_active: true, player: { id: 101, name: "Mike Trout", image: "https://example.com/trout.jpg", }, }, ]); const wrapper = mount(RunnersOnBase, { global: { plugins: [pinia] }, props: { runners: { first: mockRunnerFirst, second: null, third: null, }, fieldingLineup: mockFieldingLineup, battingTeamColor: "#3b82f6", fieldingTeamColor: "#10b981", battingTeamAbbrev: "BOS", fieldingTeamAbbrev: "NYY", }, }); const runnerCards = wrapper.findAllComponents(RunnerCard); await runnerCards[0].trigger("click"); // When runner selected, collapsed state hidden and expanded state shown expect(wrapper.find(".border-l-4.border-gray-600").exists()).toBe( false, ); expect(wrapper.find(".matchup-card").exists()).toBe(true); }); it("collapses catcher card when runner is deselected", async () => { const gameStore = useGameStore(); gameStore.setGameState({ id: 1, home_team_id: 1, away_team_id: 2, status: "active", inning: 1, half: "top", outs: 0, home_score: 0, away_score: 0, home_team_abbrev: "NYY", away_team_abbrev: "BOS", home_team_dice_color: "3b82f6", current_batter: null, current_pitcher: null, on_first: null, on_second: null, on_third: null, decision_phase: "idle", play_count: 0, }); gameStore.updateLineup(1, [ { id: 1, lineup_id: 1, team_id: 1, batting_order: 1, position: "LF", is_active: true, player: { id: 101, name: "Mike Trout", image: "https://example.com/trout.jpg", }, }, ]); const wrapper = mount(RunnersOnBase, { global: { plugins: [pinia] }, props: { runners: { first: mockRunnerFirst, second: null, third: null, }, fieldingLineup: mockFieldingLineup, battingTeamColor: "#3b82f6", fieldingTeamColor: "#10b981", battingTeamAbbrev: "BOS", fieldingTeamAbbrev: "NYY", }, }); const runnerCards = wrapper.findAllComponents(RunnerCard); // Click to expand await runnerCards[0].trigger("click"); expect(wrapper.find(".matchup-card").exists()).toBe(true); // Click again to collapse await runnerCards[0].trigger("click"); expect(wrapper.find(".border-l-4.border-gray-600").exists()).toBe( true, ); expect(wrapper.find(".matchup-card").exists()).toBe(false); }); it("switches selection when clicking different runner", async () => { const gameStore = useGameStore(); gameStore.setGameState({ id: 1, home_team_id: 1, away_team_id: 2, status: "active", inning: 1, half: "top", outs: 0, home_score: 0, away_score: 0, home_team_abbrev: "NYY", away_team_abbrev: "BOS", home_team_dice_color: "3b82f6", current_batter: null, current_pitcher: null, on_first: null, on_second: null, on_third: null, decision_phase: "idle", play_count: 0, }); gameStore.updateLineup(1, [ { id: 1, lineup_id: 1, team_id: 1, batting_order: 1, position: "LF", is_active: true, player: { id: 101, name: "Mike Trout", image: "https://example.com/trout.jpg", }, }, { id: 2, lineup_id: 2, team_id: 1, batting_order: 2, position: "CF", is_active: true, player: { id: 102, name: "Aaron Judge", image: "https://example.com/judge.jpg", }, }, ]); const wrapper = mount(RunnersOnBase, { global: { plugins: [pinia] }, props: { runners: { first: mockRunnerFirst, second: mockRunnerSecond, third: null, }, fieldingLineup: mockFieldingLineup, battingTeamColor: "#3b82f6", fieldingTeamColor: "#10b981", battingTeamAbbrev: "BOS", fieldingTeamAbbrev: "NYY", }, }); const runnerCards = wrapper.findAllComponents(RunnerCard); // Select first runner await runnerCards[0].trigger("click"); expect(runnerCards[0].props("isExpanded")).toBe(true); expect(runnerCards[1].props("isExpanded")).toBe(false); // Select second runner await runnerCards[1].trigger("click"); expect(runnerCards[0].props("isExpanded")).toBe(false); expect(runnerCards[1].props("isExpanded")).toBe(true); }); }); describe("team information", () => { it("displays team abbreviations when provided", async () => { const gameStore = useGameStore(); gameStore.setGameState({ id: 1, home_team_id: 1, away_team_id: 2, status: "active", inning: 1, half: "top", outs: 0, home_score: 0, away_score: 0, home_team_abbrev: "NYY", away_team_abbrev: "BOS", home_team_dice_color: "3b82f6", current_batter: null, current_pitcher: null, on_first: null, on_second: null, on_third: null, decision_phase: "idle", play_count: 0, }); gameStore.updateLineup(1, [ { id: 1, lineup_id: 1, team_id: 1, batting_order: 1, position: "LF", is_active: true, player: { id: 101, name: "Mike Trout", image: "https://example.com/trout.jpg", }, }, ]); const wrapper = mount(RunnersOnBase, { global: { plugins: [pinia] }, props: { runners: { first: mockRunnerFirst, second: null, third: null, }, fieldingLineup: mockFieldingLineup, battingTeamColor: "#3b82f6", fieldingTeamColor: "#10b981", battingTeamAbbrev: "BOS", fieldingTeamAbbrev: "NYY", }, }); // Click runner to expand and show team abbreviation const runnerCards = wrapper.findAllComponents(RunnerCard); await runnerCards[0].trigger("click"); expect(wrapper.text()).toContain("NYY"); }); it("uses default colors when not provided", () => { const wrapper = mount(RunnersOnBase, { global: { plugins: [pinia] }, props: { runners: { first: mockRunnerFirst, second: null, third: null, }, fieldingLineup: mockFieldingLineup, }, }); const runnerCards = wrapper.findAllComponents(RunnerCard); expect(runnerCards[0].props("teamColor")).toBe("#3b82f6"); }); }); });