import React from 'react';
import { render } from '@testing-library/react';
import '@testing-library/jest-dom';
jest.mock('@times-components/image', () => ({
Placeholder: () =>
Placeholder
}));
jest.mock('../../useFetchFeed', () => ({
useFetchFeed: jest.fn()
}));
import { useFetchFeed } from '../../useFetchFeed';
import { OptaMatchStatsWinProbability } from '../OptaMatchStatsWinProbability';
const mockUseFetchFeed = useFetchFeed as jest.Mock;
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
const createFeed = (overrides: Record = {}) => ({
matchInfo: {
id: 'match-1',
description: 'Home FC vs Away United',
contestant: [
{
id: 'team-home',
name: 'Home FC',
shortName: 'Home',
officialName: 'Home Football Club',
code: 'HFC',
position: 'home' as const
},
{
id: 'team-away',
name: 'Away United',
shortName: 'Away',
officialName: 'Away United FC',
code: 'AUF',
position: 'away' as const
}
]
},
liveData: {
matchDetails: { matchStatus: 'Fixture' },
preMatchPredictions: [
{
type: 'Win' as const,
prediction: [
{ type: 'Home' as const, probability: '59.622' },
{ type: 'Away' as const, probability: '19.263' },
{ type: 'Draw' as const, probability: '21.115' }
]
}
]
},
...overrides
});
const createLiveFeed = (
homePct: string,
drawPct: string,
awayPct: string,
overrides: Record = {}
) =>
createFeed({
liveData: {
matchDetails: { matchStatus: 'Playing', periodId: 1 },
overallLivePredictions: [
{
type: 'Win' as const,
prediction: [
{ type: 'Home' as const, probability: homePct },
{ type: 'Draw' as const, probability: drawPct },
{ type: 'Away' as const, probability: awayPct }
]
}
],
...overrides
}
});
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
describe('OptaMatchStatsWinProbability', () => {
beforeEach(() => {
mockUseFetchFeed.mockReturnValue({
data: undefined,
loading: false,
error: undefined
});
});
afterEach(() => {
jest.clearAllMocks();
});
// ── loading / error states ────────────────────────────────────────────────
it('shows placeholder skeleton while loading', () => {
mockUseFetchFeed.mockReturnValue({
data: undefined,
loading: true,
error: undefined
});
const { getByTestId } = render(
);
expect(getByTestId('wp-loading')).toBeInTheDocument();
expect(getByTestId('wp-placeholder')).toBeInTheDocument();
});
it('shows "No data found." on error', () => {
mockUseFetchFeed.mockReturnValue({
data: undefined,
loading: false,
error: 'Network error'
});
const { getByTestId } = render(
);
expect(getByTestId('wp-error')).toHaveTextContent('No data found.');
});
// ── data-driven states ───────────────────────────────────────────────────
it('renders nothing when no feed is provided and no URL given', () => {
const { container } = render();
expect(container.firstChild).toBeNull();
});
it('renders the title', () => {
const feed = createFeed();
const { getByText } = render(
);
expect(getByText('Win Probability')).toBeInTheDocument();
});
it('renders pre-match probability values', () => {
const feed = createFeed();
const { getByTestId } = render(
);
expect(getByTestId('wp-label-home')).toHaveTextContent('59.6%');
expect(getByTestId('wp-label-draw')).toHaveTextContent('21.1%');
expect(getByTestId('wp-label-away')).toHaveTextContent('19.3%');
});
it('prefers live predictions over pre-match predictions', () => {
const feed = createLiveFeed('46.036', '16.150', '37.814');
const { getByTestId } = render(
);
expect(getByTestId('wp-label-home')).toHaveTextContent('46.0%');
expect(getByTestId('wp-label-draw')).toHaveTextContent('16.1%');
expect(getByTestId('wp-label-away')).toHaveTextContent('37.8%');
});
it('renders wrapper with correct test id', () => {
const feed = createFeed();
const { getByTestId } = render(
);
expect(getByTestId('win-probability-wrapper')).toBeInTheDocument();
});
it('renders three bar segments (home, draw, away)', () => {
const feed = createFeed();
const { getByTestId } = render(
);
expect(getByTestId('wp-bar-home')).toBeInTheDocument();
expect(getByTestId('wp-bar-draw')).toBeInTheDocument();
expect(getByTestId('wp-bar-away')).toBeInTheDocument();
});
it('renders nothing when contestants are missing', () => {
const feed = {
matchInfo: { id: 'match-1' },
liveData: {
preMatchPredictions: [
{
type: 'Win' as const,
prediction: [
{ type: 'Home' as const, probability: '50' },
{ type: 'Away' as const, probability: '30' },
{ type: 'Draw' as const, probability: '20' }
]
}
]
}
};
const { container } = render(
);
expect(container.firstChild).toBeNull();
});
it('renders nothing when predictions are absent', () => {
const feed = {
matchInfo: {
id: 'match-1',
contestant: [
{
id: 'h',
name: 'Home',
shortName: 'Home',
officialName: 'Home FC',
code: 'HFC',
position: 'home' as const
},
{
id: 'a',
name: 'Away',
shortName: 'Away',
officialName: 'Away FC',
code: 'AFC',
position: 'away' as const
}
]
},
liveData: {}
};
const { container } = render(
);
expect(container.firstChild).toBeNull();
});
it('formats probabilities to 1 decimal place', () => {
const feed = createLiveFeed('46.036', '16.150', '37.814');
const { getByTestId } = render(
);
expect(getByTestId('wp-label-home').textContent).toMatch(/46\.0%/);
expect(getByTestId('wp-label-draw').textContent).toMatch(/16\.1%/);
expect(getByTestId('wp-label-away').textContent).toMatch(/37\.8%/);
});
it('renders a snapshot for pre-match', () => {
const feed = createFeed();
const { asFragment } = render(
);
expect(asFragment()).toMatchSnapshot();
});
it('renders a snapshot for live match', () => {
const feed = createLiveFeed('46.036', '16.150', '37.814');
const { asFragment } = render(
);
expect(asFragment()).toMatchSnapshot();
});
});