import { describe, it, expect, beforeEach } from "vitest"; import { mount } from "@vue/test-utils"; import { createPinia, setActivePinia } from "pinia"; import RunnerCard from "~/components/Game/RunnerCard.vue"; import { useGameStore } from "~/store/game"; import type { LineupPlayerState } from "~/types/game"; describe("RunnerCard", () => { let pinia: ReturnType; beforeEach(() => { pinia = createPinia(); setActivePinia(pinia); }); const mockRunner: LineupPlayerState = { lineup_id: 1, batting_order: 1, position: "LF", card_id: 101, }; describe("empty base state", () => { it("renders empty state when no runner provided", () => { const wrapper = mount(RunnerCard, { global: { plugins: [pinia] }, props: { base: "1B", runner: null, isExpanded: false, teamColor: "#3b82f6", }, }); expect(wrapper.find(".runner-card.empty").exists()).toBe(true); expect(wrapper.text()).toContain("Empty"); }); it("displays base label for empty base", () => { const wrapper = mount(RunnerCard, { global: { plugins: [pinia] }, props: { base: "2B", runner: null, isExpanded: false, teamColor: "#3b82f6", }, }); expect(wrapper.text()).toContain("2B"); }); it("shows hollow circle for empty base", () => { const wrapper = mount(RunnerCard, { global: { plugins: [pinia] }, props: { base: "3B", runner: null, isExpanded: false, teamColor: "#3b82f6", }, }); const circle = wrapper.find(".rounded-full.border-dashed"); expect(circle.exists()).toBe(true); }); it("does not emit click event for empty base", async () => { const wrapper = mount(RunnerCard, { global: { plugins: [pinia] }, props: { base: "1B", runner: null, isExpanded: false, teamColor: "#3b82f6", }, }); await wrapper.trigger("click"); expect(wrapper.emitted("click")).toBeUndefined(); }); }); describe("occupied base state", () => { beforeEach(() => { const gameStore = useGameStore(); // Set game state first so updateLineup knows the team ID 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", headshot: "https://example.com/trout-headshot.jpg", }, }, ]); }); it("renders occupied state when runner provided", () => { const wrapper = mount(RunnerCard, { global: { plugins: [pinia] }, props: { base: "1B", runner: mockRunner, isExpanded: false, teamColor: "#3b82f6", }, }); expect(wrapper.find(".runner-card.occupied").exists()).toBe(true); expect(wrapper.find(".runner-card.empty").exists()).toBe(false); }); it("displays runner name", () => { const wrapper = mount(RunnerCard, { global: { plugins: [pinia] }, props: { base: "1B", runner: mockRunner, isExpanded: false, teamColor: "#3b82f6", }, }); expect(wrapper.text()).toContain("Mike Trout"); }); it("displays base label", () => { const wrapper = mount(RunnerCard, { global: { plugins: [pinia] }, props: { base: "2B", runner: mockRunner, isExpanded: false, teamColor: "#3b82f6", }, }); expect(wrapper.text()).toContain("2B"); }); it("displays runner number based on lineup_id", () => { const wrapper = mount(RunnerCard, { global: { plugins: [pinia] }, props: { base: "1B", runner: mockRunner, isExpanded: false, teamColor: "#3b82f6", }, }); expect(wrapper.text()).toContain("#01"); }); it("displays player headshot when available", () => { const wrapper = mount(RunnerCard, { global: { plugins: [pinia] }, props: { base: "1B", runner: mockRunner, isExpanded: false, teamColor: "#3b82f6", }, }); const img = wrapper.find('img[alt="Mike Trout"]'); expect(img.exists()).toBe(true); expect(img.attributes("src")).toBe( "https://example.com/trout-headshot.jpg", ); }); it("applies team color to border", () => { const wrapper = mount(RunnerCard, { global: { plugins: [pinia] }, props: { base: "1B", runner: mockRunner, isExpanded: false, teamColor: "#ff0000", }, }); const avatar = wrapper.find(".rounded-full.border-2"); expect(avatar.attributes("style")).toContain( "border-color: #ff0000", ); }); it("shows chevron icon when occupied", () => { const wrapper = mount(RunnerCard, { global: { plugins: [pinia] }, props: { base: "1B", runner: mockRunner, isExpanded: false, teamColor: "#3b82f6", }, }); const chevron = wrapper.find("svg"); expect(chevron.exists()).toBe(true); }); it("emits click event when clicked", async () => { const wrapper = mount(RunnerCard, { global: { plugins: [pinia] }, props: { base: "1B", runner: mockRunner, isExpanded: false, teamColor: "#3b82f6", }, }); await wrapper.trigger("click"); expect(wrapper.emitted("click")).toHaveLength(1); }); }); describe("expanded state", () => { beforeEach(() => { const gameStore = useGameStore(); gameStore.setGameState({ id: 1, home_team_id: 1, away_team_id: 2, status: "active", inning: 1, half: "top", outs: 0, on_base_code: 0, home_team: { id: 1, name: "Home Team", abbreviation: "HOME", dice_color: "3b82f6", }, away_team: { id: 2, name: "Away Team", abbreviation: "AWAY", dice_color: "10b981", }, }); 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-card.jpg", headshot: "https://example.com/trout-headshot.jpg", }, }, ]); }); it("does not show expanded view when collapsed", () => { const wrapper = mount(RunnerCard, { global: { plugins: [pinia] }, props: { base: "1B", runner: mockRunner, isExpanded: false, teamColor: "#3b82f6", }, }); expect(wrapper.find(".runner-expanded").exists()).toBe(false); }); it("shows expanded view when isExpanded is true", () => { const wrapper = mount(RunnerCard, { global: { plugins: [pinia] }, props: { base: "1B", runner: mockRunner, isExpanded: true, teamColor: "#3b82f6", }, }); expect(wrapper.find(".runner-expanded").exists()).toBe(true); }); it("displays full player card image when expanded", () => { const wrapper = mount(RunnerCard, { global: { plugins: [pinia] }, props: { base: "1B", runner: mockRunner, isExpanded: true, teamColor: "#3b82f6", }, }); const cardImg = wrapper.find( '.runner-expanded img[alt="Mike Trout card"]', ); expect(cardImg.exists()).toBe(true); expect(cardImg.attributes("src")).toBe( "https://example.com/trout-card.jpg", ); }); it("shows player initials when no card image available", () => { const gameStore = useGameStore(); gameStore.setGameState({ id: 1, home_team_id: 1, away_team_id: 2, status: "active", inning: 1, half: "top", outs: 0, on_base_code: 0, home_team: { id: 1, name: "Home Team", abbreviation: "HOME", dice_color: "3b82f6", }, away_team: { id: 2, name: "Away Team", abbreviation: "AWAY", dice_color: "10b981", }, }); 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: "", headshot: "", }, }, ]); const wrapper = mount(RunnerCard, { global: { plugins: [pinia] }, props: { base: "1B", runner: mockRunner, isExpanded: true, teamColor: "#3b82f6", }, }); expect(wrapper.text()).toContain("MT"); }); it('displays "RUNNER" label in expanded header', () => { const wrapper = mount(RunnerCard, { global: { plugins: [pinia] }, props: { base: "2B", runner: mockRunner, isExpanded: true, teamColor: "#3b82f6", }, }); expect(wrapper.find(".runner-expanded").text()).toContain("RUNNER"); }); it("applies expanded class when isExpanded is true", () => { const wrapper = mount(RunnerCard, { global: { plugins: [pinia] }, props: { base: "1B", runner: mockRunner, isExpanded: true, teamColor: "#3b82f6", }, }); expect(wrapper.find(".runner-card.expanded").exists()).toBe(true); }); it("rotates chevron when expanded", () => { const wrapper = mount(RunnerCard, { global: { plugins: [pinia] }, props: { base: "1B", runner: mockRunner, isExpanded: true, teamColor: "#3b82f6", }, }); const chevron = wrapper.find("svg"); expect(chevron.classes()).toContain("rotate-90"); }); }); describe("player name handling", () => { it('shows "Unknown Runner" when player not found in store', () => { const wrapper = mount(RunnerCard, { global: { plugins: [pinia] }, props: { base: "1B", runner: mockRunner, isExpanded: false, teamColor: "#3b82f6", }, }); expect(wrapper.text()).toContain("Unknown Runner"); }); it("extracts initials from first and last name", () => { const gameStore = useGameStore(); gameStore.setGameState({ id: 1, home_team_id: 1, away_team_id: 2, status: "active", inning: 1, half: "top", outs: 0, on_base_code: 0, home_team: { id: 1, name: "Home Team", abbreviation: "HOME", dice_color: "3b82f6", }, away_team: { id: 2, name: "Away Team", abbreviation: "AWAY", dice_color: "10b981", }, }); gameStore.updateLineup(1, [ { id: 1, lineup_id: 1, team_id: 1, batting_order: 1, position: "LF", is_active: true, player: { id: 101, name: "Aaron Donald Judge", image: "", }, }, ]); const wrapper = mount(RunnerCard, { global: { plugins: [pinia] }, props: { base: "1B", runner: mockRunner, isExpanded: true, teamColor: "#3b82f6", }, }); // Should use first and last name (A + J) expect(wrapper.text()).toContain("AJ"); }); it("handles single-word names", () => { const gameStore = useGameStore(); gameStore.setGameState({ id: 1, home_team_id: 1, away_team_id: 2, status: "active", inning: 1, half: "top", outs: 0, on_base_code: 0, home_team: { id: 1, name: "Home Team", abbreviation: "HOME", dice_color: "3b82f6", }, away_team: { id: 2, name: "Away Team", abbreviation: "AWAY", dice_color: "10b981", }, }); gameStore.updateLineup(1, [ { id: 1, lineup_id: 1, team_id: 1, batting_order: 1, position: "LF", is_active: true, player: { id: 101, name: "Pele", image: "", }, }, ]); const wrapper = mount(RunnerCard, { global: { plugins: [pinia] }, props: { base: "1B", runner: mockRunner, isExpanded: true, teamColor: "#3b82f6", }, }); expect(wrapper.text()).toContain("PE"); }); }); describe("base label variations", () => { it("displays 1B correctly", () => { const wrapper = mount(RunnerCard, { global: { plugins: [pinia] }, props: { base: "1B", runner: null, isExpanded: false, teamColor: "#3b82f6", }, }); expect(wrapper.text()).toContain("1B"); }); it("displays 2B correctly", () => { const wrapper = mount(RunnerCard, { global: { plugins: [pinia] }, props: { base: "2B", runner: null, isExpanded: false, teamColor: "#3b82f6", }, }); expect(wrapper.text()).toContain("2B"); }); it("displays 3B correctly", () => { const wrapper = mount(RunnerCard, { global: { plugins: [pinia] }, props: { base: "3B", runner: null, isExpanded: false, teamColor: "#3b82f6", }, }); expect(wrapper.text()).toContain("3B"); }); }); });