- Replace .border-l-4.border-gray-600 checks with .catcher-pill - Update isExpanded prop references to isSelected - Adjust expanded view tests for new side-by-side layout - matchup-card-blue = runner full card - matchup-card = catcher full card - Fix deselection test to check isSelected prop (Transition keeps DOM during animation) All 15 RunnersOnBase tests passing All 16 RunnerCard tests passing
579 lines
20 KiB
TypeScript
579 lines
20 KiB
TypeScript
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<typeof createPinia>;
|
|
|
|
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 catcher summary pill 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",
|
|
},
|
|
});
|
|
|
|
// Catcher summary pill is always visible
|
|
expect(wrapper.find(".catcher-pill").exists()).toBe(true);
|
|
// Expanded view not shown by default
|
|
expect(wrapper.findAll(".matchup-card")).toHaveLength(0);
|
|
});
|
|
|
|
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("shows expanded detail row 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, expanded detail row shows both runner + catcher cards
|
|
// matchup-card is catcher (green), matchup-card-blue is runner (blue)
|
|
expect(wrapper.find(".matchup-card-blue").exists()).toBe(true); // Runner full card
|
|
expect(wrapper.find(".matchup-card").exists()).toBe(true); // Catcher full card
|
|
});
|
|
|
|
it("hides expanded detail row 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 show expanded view
|
|
await runnerCards[0].trigger("click");
|
|
await wrapper.vm.$nextTick();
|
|
expect(wrapper.find(".matchup-card-blue").exists()).toBe(true);
|
|
expect(wrapper.find(".matchup-card").exists()).toBe(true);
|
|
|
|
// Click again to toggle selection off - Transition may keep elements during animation
|
|
await runnerCards[0].trigger("click");
|
|
await wrapper.vm.$nextTick();
|
|
// Check that runner is no longer selected (internal state)
|
|
expect(runnerCards[0].props("isSelected")).toBe(false);
|
|
// Catcher summary pill remains visible
|
|
expect(wrapper.find(".catcher-pill").exists()).toBe(true);
|
|
});
|
|
|
|
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("isSelected")).toBe(true);
|
|
expect(runnerCards[1].props("isSelected")).toBe(false);
|
|
|
|
// Select second runner
|
|
await runnerCards[1].trigger("click");
|
|
expect(runnerCards[0].props("isSelected")).toBe(false);
|
|
expect(runnerCards[1].props("isSelected")).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");
|
|
});
|
|
});
|
|
});
|