import React, { useRef } from 'react'; import ReactDOM from 'react-dom'; import { act } from 'react-dom/test-utils'; import '@testing-library/jest-dom'; import { useAddFixtureNavigation } from '../useAddFixtureNavigation'; class MockMutationObserver { observe = jest.fn(); disconnect = jest.fn(); takeRecords = jest.fn().mockReturnValue([]); constructor(public callback: MutationCallback) {} } (global as any).MutationObserver = MockMutationObserver; describe('useAddFixtureNavigation', () => { const Component: React.FC<{ sport: string; competition: string; season: string; isApp?: boolean; children?: React.ReactNode; }> = ({ sport, competition, season, isApp, children }) => { const ref = useRef(null); useAddFixtureNavigation({ ref, sport, competition, season, isApp }); return
{children}
; }; let container: HTMLDivElement; const baseProps = { sport: 'football', competition: '8', season: '2025' }; beforeEach(() => { jest.useFakeTimers(); container = document.createElement('div'); document.body.appendChild(container); }); afterEach(() => { ReactDOM.unmountComponentAtNode(container); container.remove(); jest.useRealTimers(); delete (window as any).webkit; delete (window as any).interactiveSliceEvent; }); const renderFixtures = (html: string) => { act(() => { ReactDOM.render(
, container ); }); }; const renderFixturesWithProps = ( html: string, props: Partial ) => { act(() => { ReactDOM.render(
, container ); }); }; it('does nothing when ref is missing and clears after 26 ticks', () => { act(() => { ReactDOM.render(, container); }); act(() => { jest.advanceTimersByTime(25 * 500); }); const clearSpy = jest.spyOn(window, 'clearInterval'); act(() => { jest.advanceTimersByTime(500); }); expect(clearSpy).toHaveBeenCalled(); clearSpy.mockRestore(); }); it('marks clickable fixtures with data-has-fixtures-page-link', () => { const html = `
`; renderFixtures(html); act(() => { jest.advanceTimersByTime(500); }); const fixture = container.querySelector('.Opta-fixture') as any; expect(fixture).toBeTruthy(); expect(fixture.getAttribute('data-has-fixtures-page-link')).toBe('true'); }); it('navigates via iOS webkit message handler when available', () => { (window as any).webkit = { messageHandlers: { interactiveSliceEvent: { postMessage: jest.fn() } } }; const html = `
`; renderFixtures(html); act(() => { jest.advanceTimersByTime(500); }); const fixture = container.querySelector('.Opta-fixture') as HTMLElement; act(() => { fixture.click(); }); expect( (window as any).webkit.messageHandlers.interactiveSliceEvent.postMessage ).toHaveBeenCalledTimes(1); }); it('navigates via Android postMessage when available', () => { (window as any).interactiveSliceEvent = { postMessage: jest.fn() }; const html = `
`; renderFixtures(html); act(() => { jest.advanceTimersByTime(500); }); const fixture = container.querySelector('.Opta-fixture') as HTMLElement; act(() => { fixture.click(); }); expect( (window as any).interactiveSliceEvent.postMessage ).toHaveBeenCalledTimes(1); }); it('falls back to window.location.href navigation in web', () => { const html = `
`; renderFixtures(html); act(() => { jest.advanceTimersByTime(500); }); const originalLocation = window.location; const originalHref = window.location.href; delete (window as any).location; (window as any).location = { href: originalHref }; const fixture = container.querySelector('.Opta-fixture') as HTMLElement; act(() => { fixture.click(); }); expect(window.location.href).not.toBe(originalHref); expect(window.location.href).toContain('/sport/football/match-stats'); expect(window.location.href).toContain('competition=8'); expect(window.location.href).toContain('homeTeam=123'); expect(window.location.href).toContain('awayTeam=456'); expect(fixture.classList.contains('fixture-selected')).toBe(true); (window as any).location = originalLocation; }); it('derives the World Cup match stats path from competition id', () => { const html = `
`; renderFixturesWithProps(html, { competition: '4' }); act(() => { jest.advanceTimersByTime(500); }); const originalLocation = window.location; const originalHref = window.location.href; delete (window as any).location; (window as any).location = { href: originalHref }; const fixture = container.querySelector('.Opta-fixture') as HTMLElement; act(() => { fixture.click(); }); expect(window.location.href).toContain( '/sport/football/world-cup/match-stats' ); expect(window.location.href).toContain('competition=4'); (window as any).location = originalLocation; }); it('returns early when required attributes are missing', () => { const html = `
`; renderFixtures(html); act(() => { jest.advanceTimersByTime(500); }); const fixture = container.querySelector('.Opta-fixture') as any; expect(fixture.getAttribute('data-has-fixtures-page-link')).toBeNull(); }); it('returns early when team ids cannot be extracted (no Opta-Team- classes)', () => { const html = `
`; renderFixtures(html); act(() => { jest.advanceTimersByTime(500); }); const fixture = container.querySelector('.Opta-fixture') as any; expect(fixture.getAttribute('data-has-fixtures-page-link')).toBeNull(); }); it('returns early when only one team id is present', () => { const html = `
`; renderFixtures(html); act(() => { jest.advanceTimersByTime(500); }); const fixture = container.querySelector('.Opta-fixture') as any; expect(fixture.getAttribute('data-has-fixtures-page-link')).toBeNull(); }); it('extracts team ids from classList using getTeamIdFromClassList', () => { const html = `
`; renderFixtures(html); act(() => { jest.advanceTimersByTime(500); }); const fixture = container.querySelector('.Opta-fixture') as any; expect(fixture.getAttribute('data-has-fixtures-page-link')).toBe('true'); }); it('returns early on click when isApp is true and no native handlers exist', () => { const html = `
`; act(() => { ReactDOM.render(
, container ); }); act(() => { jest.advanceTimersByTime(500); }); const originalLocation = window.location; const originalHref = window.location.href; delete (window as any).location; (window as any).location = { href: originalHref }; const fixture = container.querySelector('.Opta-fixture') as HTMLElement; act(() => { fixture.click(); }); // Should NOT navigate — isApp early return prevents window.location.href assignment expect(window.location.href).toBe(originalHref); // Should NOT add fixture-selected class when isApp and no native handlers expect(fixture.classList.contains('fixture-selected')).toBe(false); (window as any).location = originalLocation; }); it('does not bind click handler for PreMatch or Abandoned fixtures', () => { const html = `
`; renderFixtures(html); act(() => { jest.advanceTimersByTime(500); }); const fixtures = container.querySelectorAll('.Opta-fixture'); expect( (fixtures[0] as any).getAttribute('data-has-fixtures-page-link') ).toBeNull(); expect( (fixtures[1] as any).getAttribute('data-has-fixtures-page-link') ).toBeNull(); }); });