@times-components/ts-components 1.145.1-b32ea924749bafdf12e3e8515023538fa20e7808.3 → 1.145.1-cfea81c4084e6f91221ea00fec9fc730d5b933cb.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/dist/components/carousel-component/CarouselComponent.stories.js +146 -0
  2. package/dist/components/carousel-component/CarouselItem.d.ts +3 -0
  3. package/dist/components/carousel-component/CarouselItem.js +11 -0
  4. package/dist/components/carousel-component/DefaultNavigationArrow.d.ts +8 -0
  5. package/dist/components/carousel-component/DefaultNavigationArrow.js +6 -0
  6. package/dist/components/carousel-component/DefaultPageDot.d.ts +8 -0
  7. package/dist/components/carousel-component/DefaultPageDot.js +4 -0
  8. package/dist/components/carousel-component/__tests__/CarouselComponent.test.d.ts +1 -0
  9. package/dist/components/carousel-component/__tests__/CarouselComponent.test.js +163 -0
  10. package/dist/components/carousel-component/__tests__/CarouselItem.test.d.ts +1 -0
  11. package/dist/components/carousel-component/__tests__/CarouselItem.test.js +80 -0
  12. package/dist/components/carousel-component/__tests__/DefaultNavigationArrow.test.d.ts +1 -0
  13. package/dist/components/carousel-component/__tests__/DefaultNavigationArrow.test.js +62 -0
  14. package/dist/components/carousel-component/__tests__/DefaultPageDot.test.d.ts +1 -0
  15. package/dist/components/carousel-component/__tests__/DefaultPageDot.test.js +68 -0
  16. package/dist/components/carousel-component/hooks/__tests__/useCarousel.test.d.ts +1 -0
  17. package/dist/components/carousel-component/hooks/__tests__/useCarousel.test.js +314 -0
  18. package/dist/components/carousel-component/hooks/useCarousel.d.ts +2 -0
  19. package/dist/components/carousel-component/hooks/useCarousel.js +140 -0
  20. package/dist/components/carousel-component/index.d.ts +4 -0
  21. package/dist/components/carousel-component/index.js +20 -0
  22. package/dist/components/carousel-component/styles.d.ts +30 -0
  23. package/dist/components/carousel-component/styles.js +120 -0
  24. package/dist/components/carousel-component/types.d.ts +46 -0
  25. package/dist/components/carousel-component/types.js +2 -0
  26. package/dist/components/opta/cricket/scorecard/OptaCricketScorecard.js +2 -10
  27. package/dist/components/opta/cricket/scorecard/__tests__/OptaCricketScorecard.test.js +17 -94
  28. package/dist/components/opta/football/summary/OptaFootballSummary.js +2 -10
  29. package/dist/components/opta/football/summary/__tests__/OptaFootballSummary.test.js +18 -95
  30. package/dist/components/opta/rugby/summary/OptaRugbySummary.js +2 -10
  31. package/dist/components/opta/rugby/summary/__tests__/OptaRugbySummary.test.js +17 -94
  32. package/dist/components/trip-cards/SkeletonCard.d.ts +2 -0
  33. package/dist/components/trip-cards/SkeletonCard.js +21 -0
  34. package/dist/components/trip-cards/TripCard.d.ts +3 -0
  35. package/dist/components/trip-cards/TripCard.js +47 -0
  36. package/dist/components/trip-cards/TripCards.stories.d.ts +1 -0
  37. package/dist/components/trip-cards/TripCards.stories.js +40 -0
  38. package/dist/components/trip-cards/TripCardsLayout.d.ts +3 -0
  39. package/dist/components/trip-cards/TripCardsLayout.js +26 -0
  40. package/dist/components/trip-cards/__tests__/SkeletonCard.test.d.ts +1 -0
  41. package/dist/components/trip-cards/__tests__/SkeletonCard.test.js +139 -0
  42. package/dist/components/trip-cards/__tests__/TripCard.test.d.ts +1 -0
  43. package/dist/components/trip-cards/__tests__/TripCard.test.js +95 -0
  44. package/dist/components/trip-cards/__tests__/TripCardsLayout.test.d.ts +1 -0
  45. package/dist/components/trip-cards/__tests__/TripCardsLayout.test.js +277 -0
  46. package/dist/components/trip-cards/__tests__/assets.test.d.ts +1 -0
  47. package/dist/components/trip-cards/__tests__/assets.test.js +165 -0
  48. package/dist/components/trip-cards/__tests__/helpers.test.d.ts +1 -0
  49. package/dist/components/trip-cards/__tests__/helpers.test.js +135 -0
  50. package/dist/components/trip-cards/__tests__/index.test.d.ts +1 -0
  51. package/dist/components/trip-cards/__tests__/index.test.js +437 -0
  52. package/dist/components/trip-cards/__tests__/mockData.test.d.ts +1 -0
  53. package/dist/components/trip-cards/__tests__/mockData.test.js +57 -0
  54. package/dist/components/trip-cards/__tests__/skeletonStyles.test.d.ts +1 -0
  55. package/dist/components/trip-cards/__tests__/skeletonStyles.test.js +194 -0
  56. package/dist/components/trip-cards/assets/BoatIcon.d.ts +1 -0
  57. package/dist/components/trip-cards/assets/BoatIcon.js +4 -0
  58. package/dist/components/trip-cards/assets/CalendarIcon.d.ts +1 -0
  59. package/dist/components/trip-cards/assets/CalendarIcon.js +4 -0
  60. package/dist/components/trip-cards/assets/ChevronRightIcon.d.ts +1 -0
  61. package/dist/components/trip-cards/assets/ChevronRightIcon.js +4 -0
  62. package/dist/components/trip-cards/assets/LocationIcon.d.ts +1 -0
  63. package/dist/components/trip-cards/assets/LocationIcon.js +4 -0
  64. package/dist/components/trip-cards/assets/MoonIcon.d.ts +1 -0
  65. package/dist/components/trip-cards/assets/MoonIcon.js +4 -0
  66. package/dist/components/trip-cards/assets/index.d.ts +6 -0
  67. package/dist/components/trip-cards/assets/index.js +7 -0
  68. package/dist/components/trip-cards/helpers.d.ts +4 -0
  69. package/dist/components/trip-cards/helpers.js +74 -0
  70. package/dist/components/trip-cards/index.d.ts +4 -0
  71. package/dist/components/trip-cards/index.js +70 -0
  72. package/dist/components/trip-cards/mockData.d.ts +3 -0
  73. package/dist/components/trip-cards/mockData.js +323 -0
  74. package/dist/components/trip-cards/skeletonStyles.d.ts +9 -0
  75. package/dist/components/trip-cards/skeletonStyles.js +37 -0
  76. package/dist/components/trip-cards/styles.d.ts +39 -0
  77. package/dist/components/trip-cards/styles.js +387 -0
  78. package/dist/components/trip-cards/types.d.ts +87 -0
  79. package/dist/components/trip-cards/types.js +2 -0
  80. package/dist/index.d.ts +1 -0
  81. package/dist/index.js +2 -1
  82. package/package.json +3 -3
  83. package/rnw.js +1 -1
  84. package/src/components/carousel-component/CarouselComponent.stories.tsx +220 -0
  85. package/src/components/carousel-component/CarouselItem.tsx +20 -0
  86. package/src/components/carousel-component/DefaultNavigationArrow.tsx +37 -0
  87. package/src/components/carousel-component/DefaultPageDot.tsx +20 -0
  88. package/src/components/carousel-component/__tests__/CarouselComponent.test.tsx +259 -0
  89. package/src/components/carousel-component/__tests__/CarouselItem.test.tsx +140 -0
  90. package/src/components/carousel-component/__tests__/DefaultNavigationArrow.test.tsx +153 -0
  91. package/src/components/carousel-component/__tests__/DefaultPageDot.test.tsx +105 -0
  92. package/src/components/carousel-component/hooks/__tests__/useCarousel.test.ts +438 -0
  93. package/src/components/carousel-component/hooks/useCarousel.ts +187 -0
  94. package/src/components/carousel-component/index.tsx +88 -0
  95. package/src/components/carousel-component/styles.ts +140 -0
  96. package/src/components/carousel-component/types.ts +51 -0
  97. package/src/components/opta/cricket/scorecard/OptaCricketScorecard.tsx +0 -13
  98. package/src/components/opta/cricket/scorecard/__tests__/OptaCricketScorecard.test.tsx +16 -126
  99. package/src/components/opta/cricket/scorecard/__tests__/__snapshots__/OptaCricketScorecard.test.tsx.snap +6 -5
  100. package/src/components/opta/football/summary/OptaFootballSummary.tsx +0 -13
  101. package/src/components/opta/football/summary/__tests__/OptaFootballSummary.test.tsx +18 -127
  102. package/src/components/opta/football/summary/__tests__/__snapshots__/OptaFootballSummary.test.tsx.snap +6 -5
  103. package/src/components/opta/rugby/summary/OptaRugbySummary.tsx +0 -13
  104. package/src/components/opta/rugby/summary/__tests__/OptaRugbySummary.test.tsx +17 -127
  105. package/src/components/opta/rugby/summary/__tests__/__snapshots__/OptaRugbySummary.test.tsx.snap +6 -5
  106. package/src/components/trip-cards/SkeletonCard.tsx +54 -0
  107. package/src/components/trip-cards/TripCard.tsx +135 -0
  108. package/src/components/trip-cards/TripCards.stories.tsx +67 -0
  109. package/src/components/trip-cards/TripCardsLayout.tsx +75 -0
  110. package/src/components/trip-cards/__tests__/SkeletonCard.test.tsx +169 -0
  111. package/src/components/trip-cards/__tests__/TripCard.test.tsx +120 -0
  112. package/src/components/trip-cards/__tests__/TripCardsLayout.test.tsx +532 -0
  113. package/src/components/trip-cards/__tests__/assets.test.tsx +206 -0
  114. package/src/components/trip-cards/__tests__/helpers.test.ts +165 -0
  115. package/src/components/trip-cards/__tests__/index.test.tsx +499 -0
  116. package/src/components/trip-cards/__tests__/mockData.test.ts +67 -0
  117. package/src/components/trip-cards/__tests__/skeletonStyles.test.tsx +256 -0
  118. package/src/components/trip-cards/assets/BoatIcon.tsx +17 -0
  119. package/src/components/trip-cards/assets/CalendarIcon.tsx +17 -0
  120. package/src/components/trip-cards/assets/ChevronRightIcon.tsx +20 -0
  121. package/src/components/trip-cards/assets/LocationIcon.tsx +17 -0
  122. package/src/components/trip-cards/assets/MoonIcon.tsx +17 -0
  123. package/src/components/trip-cards/assets/index.ts +7 -0
  124. package/src/components/trip-cards/helpers.ts +99 -0
  125. package/src/components/trip-cards/index.tsx +104 -0
  126. package/src/components/trip-cards/mockData.ts +351 -0
  127. package/src/components/trip-cards/skeletonStyles.ts +46 -0
  128. package/src/components/trip-cards/styles.ts +426 -0
  129. package/src/components/trip-cards/types.ts +91 -0
  130. package/src/index.ts +2 -0
  131. package/dist/components/opta/utils/__tests__/emitEvent.test.js +0 -264
  132. package/dist/components/opta/utils/emitEvent.d.ts +0 -1
  133. package/dist/components/opta/utils/emitEvent.js +0 -9
  134. package/src/components/opta/utils/__tests__/emitEvent.test.tsx +0 -415
  135. package/src/components/opta/utils/emitEvent.ts +0 -11
  136. /package/dist/components/{opta/utils/__tests__/emitEvent.test.d.ts → carousel-component/CarouselComponent.stories.d.ts} +0 -0
@@ -0,0 +1,153 @@
1
+ import React from 'react';
2
+ import { render, screen, fireEvent } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
4
+ import { DefaultNavigationArrow } from '../DefaultNavigationArrow';
5
+
6
+ describe('DefaultNavigationArrow', () => {
7
+ const mockOnClick = jest.fn();
8
+
9
+ beforeEach(() => {
10
+ mockOnClick.mockClear();
11
+ });
12
+
13
+ it('renders left arrow', () => {
14
+ render(
15
+ <DefaultNavigationArrow
16
+ direction="left"
17
+ onClick={mockOnClick}
18
+ disabled={false}
19
+ />
20
+ );
21
+
22
+ const button = screen.getByLabelText('Previous items');
23
+ expect(button).toBeInTheDocument();
24
+ });
25
+
26
+ it('renders right arrow', () => {
27
+ render(
28
+ <DefaultNavigationArrow
29
+ direction="right"
30
+ onClick={mockOnClick}
31
+ disabled={false}
32
+ />
33
+ );
34
+
35
+ const button = screen.getByLabelText('Next items');
36
+ expect(button).toBeInTheDocument();
37
+ });
38
+
39
+ it('calls onClick when clicked', () => {
40
+ render(
41
+ <DefaultNavigationArrow
42
+ direction="left"
43
+ onClick={mockOnClick}
44
+ disabled={false}
45
+ />
46
+ );
47
+
48
+ const button = screen.getByLabelText('Previous items');
49
+ fireEvent.click(button);
50
+
51
+ expect(mockOnClick).toHaveBeenCalledTimes(1);
52
+ });
53
+
54
+ it('does not call onClick when disabled', () => {
55
+ render(
56
+ <DefaultNavigationArrow
57
+ direction="left"
58
+ onClick={mockOnClick}
59
+ disabled={true}
60
+ />
61
+ );
62
+
63
+ const button = screen.getByLabelText('Previous items');
64
+ fireEvent.click(button);
65
+
66
+ expect(mockOnClick).not.toHaveBeenCalled();
67
+ });
68
+
69
+ it('applies disabled styling when disabled', () => {
70
+ render(
71
+ <DefaultNavigationArrow
72
+ direction="left"
73
+ onClick={mockOnClick}
74
+ disabled={true}
75
+ />
76
+ );
77
+
78
+ const button = screen.getByLabelText('Previous items');
79
+ expect(button).toHaveAttribute('disabled');
80
+ });
81
+
82
+ it('renders SVG icon', () => {
83
+ const { container } = render(
84
+ <DefaultNavigationArrow
85
+ direction="left"
86
+ onClick={mockOnClick}
87
+ disabled={false}
88
+ />
89
+ );
90
+
91
+ const svg = container.querySelector('svg');
92
+ expect(svg).toBeInTheDocument();
93
+ });
94
+
95
+ it('has correct aria-label for accessibility', () => {
96
+ const { rerender } = render(
97
+ <DefaultNavigationArrow
98
+ direction="left"
99
+ onClick={mockOnClick}
100
+ disabled={false}
101
+ />
102
+ );
103
+
104
+ expect(screen.getByLabelText('Previous items')).toBeInTheDocument();
105
+
106
+ rerender(
107
+ <DefaultNavigationArrow
108
+ direction="right"
109
+ onClick={mockOnClick}
110
+ disabled={false}
111
+ />
112
+ );
113
+
114
+ expect(screen.getByLabelText('Next items')).toBeInTheDocument();
115
+ });
116
+
117
+ it('renders as a button element', () => {
118
+ render(
119
+ <DefaultNavigationArrow
120
+ direction="left"
121
+ onClick={mockOnClick}
122
+ disabled={false}
123
+ />
124
+ );
125
+
126
+ const button = screen.getByLabelText('Previous items');
127
+ expect(button.tagName).toBe('BUTTON');
128
+ });
129
+
130
+ it('applies correct styles based on direction', () => {
131
+ const { container, rerender } = render(
132
+ <DefaultNavigationArrow
133
+ direction="left"
134
+ onClick={mockOnClick}
135
+ disabled={false}
136
+ />
137
+ );
138
+
139
+ let button = container.querySelector('button');
140
+ expect(button).toBeInTheDocument();
141
+
142
+ rerender(
143
+ <DefaultNavigationArrow
144
+ direction="right"
145
+ onClick={mockOnClick}
146
+ disabled={false}
147
+ />
148
+ );
149
+
150
+ button = container.querySelector('button');
151
+ expect(button).toBeInTheDocument();
152
+ });
153
+ });
@@ -0,0 +1,105 @@
1
+ import React from 'react';
2
+ import { render, screen, fireEvent } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
4
+ import { DefaultPageDot } from '../DefaultPageDot';
5
+
6
+ describe('DefaultPageDot', () => {
7
+ const mockOnClick = jest.fn();
8
+
9
+ beforeEach(() => {
10
+ mockOnClick.mockClear();
11
+ });
12
+
13
+ it('renders active dot', () => {
14
+ render(<DefaultPageDot active={true} onClick={mockOnClick} index={0} />);
15
+
16
+ const button = screen.getByRole('button');
17
+ expect(button).toBeInTheDocument();
18
+ });
19
+
20
+ it('renders inactive dot', () => {
21
+ render(<DefaultPageDot active={false} onClick={mockOnClick} index={0} />);
22
+
23
+ const button = screen.getByRole('button');
24
+ expect(button).toBeInTheDocument();
25
+ });
26
+
27
+ it('calls onClick when clicked', () => {
28
+ render(<DefaultPageDot active={false} onClick={mockOnClick} index={0} />);
29
+
30
+ const button = screen.getByRole('button');
31
+ fireEvent.click(button);
32
+
33
+ expect(mockOnClick).toHaveBeenCalledTimes(1);
34
+ });
35
+
36
+ it('renders multiple dots with different indices', () => {
37
+ const { rerender } = render(
38
+ <DefaultPageDot active={true} onClick={mockOnClick} index={0} />
39
+ );
40
+
41
+ expect(screen.getByRole('button')).toBeInTheDocument();
42
+
43
+ rerender(<DefaultPageDot active={false} onClick={mockOnClick} index={1} />);
44
+
45
+ expect(screen.getByRole('button')).toBeInTheDocument();
46
+ });
47
+
48
+ it('handles active state changes', () => {
49
+ const { rerender } = render(
50
+ <DefaultPageDot active={false} onClick={mockOnClick} index={0} />
51
+ );
52
+
53
+ const button = screen.getByRole('button');
54
+ expect(button).toBeInTheDocument();
55
+
56
+ rerender(<DefaultPageDot active={true} onClick={mockOnClick} index={0} />);
57
+
58
+ expect(button).toBeInTheDocument();
59
+ });
60
+
61
+ it('applies different styling for active vs inactive state', () => {
62
+ const { container, rerender } = render(
63
+ <DefaultPageDot active={false} onClick={mockOnClick} index={0} />
64
+ );
65
+
66
+ const inactiveButton = container.querySelector('button');
67
+ expect(inactiveButton).toBeInTheDocument();
68
+
69
+ rerender(<DefaultPageDot active={true} onClick={mockOnClick} index={0} />);
70
+
71
+ const activeButton = container.querySelector('button');
72
+ expect(activeButton).toBeInTheDocument();
73
+ });
74
+
75
+ it('renders as a button element', () => {
76
+ render(<DefaultPageDot active={true} onClick={mockOnClick} index={0} />);
77
+
78
+ const button = screen.getByRole('button');
79
+ expect(button.tagName).toBe('BUTTON');
80
+ });
81
+
82
+ it('passes correct index to onClick handler', () => {
83
+ const { rerender } = render(
84
+ <DefaultPageDot active={false} onClick={mockOnClick} index={2} />
85
+ );
86
+
87
+ const button = screen.getByRole('button');
88
+ fireEvent.click(button);
89
+
90
+ expect(mockOnClick).toHaveBeenCalledTimes(1);
91
+
92
+ rerender(<DefaultPageDot active={false} onClick={mockOnClick} index={5} />);
93
+
94
+ fireEvent.click(button);
95
+ expect(mockOnClick).toHaveBeenCalledTimes(2);
96
+ });
97
+
98
+ it('is focusable for keyboard navigation', () => {
99
+ render(<DefaultPageDot active={false} onClick={mockOnClick} index={0} />);
100
+
101
+ const button = screen.getByRole('button');
102
+ button.focus();
103
+ expect(document.activeElement).toBe(button);
104
+ });
105
+ });
@@ -0,0 +1,438 @@
1
+ import React from 'react';
2
+ import { renderHook, act } from '@testing-library/react-hooks';
3
+ import { useCarousel } from '../useCarousel';
4
+
5
+ describe('useCarousel', () => {
6
+ beforeEach(() => {
7
+ jest.useFakeTimers();
8
+ });
9
+
10
+ afterEach(() => {
11
+ jest.runOnlyPendingTimers();
12
+ jest.useRealTimers();
13
+ });
14
+
15
+ it('initializes with correct default values', () => {
16
+ const { result } = renderHook(() => useCarousel(10, { itemsPerPage: 2 }));
17
+
18
+ expect(result.current.currentPage).toBe(0);
19
+ expect(result.current.totalPages).toBe(5);
20
+ expect(result.current.isScrolling).toBe(false);
21
+ });
22
+
23
+ it('calculates correct totalPages', () => {
24
+ const { result } = renderHook(() => useCarousel(10, { itemsPerPage: 3 }));
25
+
26
+ expect(result.current.totalPages).toBe(4);
27
+ });
28
+
29
+ it('provides navigation functions', () => {
30
+ const { result } = renderHook(() => useCarousel(10, { itemsPerPage: 2 }));
31
+
32
+ expect(typeof result.current.handleNext).toBe('function');
33
+ expect(typeof result.current.handlePrevious).toBe('function');
34
+ expect(typeof result.current.scrollToPage).toBe('function');
35
+ });
36
+
37
+ it('provides mouse event handlers for drag functionality', () => {
38
+ const { result } = renderHook(() => useCarousel(10, { itemsPerPage: 2 }));
39
+
40
+ expect(typeof result.current.handleMouseDown).toBe('function');
41
+ expect(typeof result.current.handleMouseMove).toBe('function');
42
+ expect(typeof result.current.handleMouseUp).toBe('function');
43
+ expect(typeof result.current.handleMouseLeave).toBe('function');
44
+ });
45
+
46
+ it('handles single page correctly', () => {
47
+ const { result } = renderHook(() => useCarousel(2, { itemsPerPage: 2 }));
48
+
49
+ expect(result.current.totalPages).toBe(1);
50
+
51
+ act(() => {
52
+ result.current.handleNext();
53
+ });
54
+
55
+ expect(result.current.currentPage).toBe(0);
56
+ });
57
+
58
+ it('handles empty items', () => {
59
+ const { result } = renderHook(() => useCarousel(0, { itemsPerPage: 2 }));
60
+
61
+ expect(result.current.totalPages).toBe(0);
62
+ expect(result.current.currentPage).toBe(0);
63
+ });
64
+
65
+ it('updates totalPages when totalItems changes', () => {
66
+ const { result, rerender } = renderHook(
67
+ ({ total }) => useCarousel(total, { itemsPerPage: 2 }),
68
+ { initialProps: { total: 10 } }
69
+ );
70
+
71
+ expect(result.current.totalPages).toBe(5);
72
+
73
+ rerender({ total: 6 });
74
+
75
+ expect(result.current.totalPages).toBe(3);
76
+ });
77
+
78
+ it('cleans up timeout on unmount', () => {
79
+ const { unmount } = renderHook(() => useCarousel(10, { itemsPerPage: 2 }));
80
+
81
+ unmount();
82
+
83
+ expect(jest.getTimerCount()).toBe(0);
84
+ });
85
+
86
+ it('defaults to itemsPerPage of 2 when not provided', () => {
87
+ const { result } = renderHook(() => useCarousel(10, {}));
88
+
89
+ expect(result.current.totalPages).toBe(5);
90
+ });
91
+
92
+ it('handles itemsPerPage of 1', () => {
93
+ const { result } = renderHook(() => useCarousel(5, { itemsPerPage: 1 }));
94
+
95
+ expect(result.current.totalPages).toBe(5);
96
+ });
97
+
98
+ it('handles large number of items', () => {
99
+ const { result } = renderHook(() =>
100
+ useCarousel(1000, { itemsPerPage: 10 })
101
+ );
102
+
103
+ expect(result.current.totalPages).toBe(100);
104
+ });
105
+
106
+ it('handles odd number of items with even itemsPerPage', () => {
107
+ const { result } = renderHook(() => useCarousel(5, { itemsPerPage: 2 }));
108
+
109
+ expect(result.current.totalPages).toBe(3);
110
+ });
111
+
112
+ it('provides carouselRef', () => {
113
+ const { result } = renderHook(() => useCarousel(10, { itemsPerPage: 2 }));
114
+
115
+ expect(result.current.carouselRef).toBeDefined();
116
+ expect(typeof result.current.carouselRef).toBe('object');
117
+ });
118
+
119
+ it('updates when itemsPerPage option changes', () => {
120
+ const { result, rerender } = renderHook(
121
+ ({ itemsPerPage }) => useCarousel(10, { itemsPerPage }),
122
+ { initialProps: { itemsPerPage: 2 } }
123
+ );
124
+
125
+ expect(result.current.totalPages).toBe(5);
126
+
127
+ rerender({ itemsPerPage: 5 });
128
+
129
+ expect(result.current.totalPages).toBe(2);
130
+ });
131
+
132
+ it('handles itemsPerPage greater than total items', () => {
133
+ const { result } = renderHook(() => useCarousel(3, { itemsPerPage: 10 }));
134
+
135
+ expect(result.current.totalPages).toBe(1);
136
+ });
137
+
138
+ it('calls onPageChange when provided in options', () => {
139
+ const onPageChange = jest.fn();
140
+ const { result } = renderHook(() =>
141
+ useCarousel(10, { itemsPerPage: 2, onPageChange })
142
+ );
143
+
144
+ expect(result.current.currentPage).toBe(0);
145
+ expect(onPageChange).not.toHaveBeenCalled();
146
+ });
147
+
148
+ it('handles mouseDown without carouselRef', () => {
149
+ const { result } = renderHook(() => useCarousel(10, { itemsPerPage: 2 }));
150
+
151
+ const mockEvent = ({
152
+ preventDefault: jest.fn(),
153
+ pageX: 100
154
+ } as unknown) as React.MouseEvent;
155
+
156
+ expect(result.current.carouselRef.current).toBeNull();
157
+ act(() => {
158
+ result.current.handleMouseDown(mockEvent);
159
+ });
160
+
161
+ expect(mockEvent.preventDefault).not.toHaveBeenCalled();
162
+ });
163
+
164
+ it('calls onPageChange when scrolling to a page', () => {
165
+ const onPageChange = jest.fn();
166
+ const { result } = renderHook(() =>
167
+ useCarousel(10, { itemsPerPage: 2, onPageChange })
168
+ );
169
+
170
+ const mockCard1: Partial<HTMLElement> = { offsetLeft: 0 };
171
+ const mockCard2: Partial<HTMLElement> = { offsetLeft: 400 };
172
+ const mockChildren = [
173
+ mockCard1,
174
+ mockCard2,
175
+ mockCard1,
176
+ mockCard2,
177
+ mockCard1
178
+ ];
179
+
180
+ const mockCarousel = {
181
+ scrollLeft: 0,
182
+ children: mockChildren,
183
+ scrollTo: jest.fn(),
184
+ addEventListener: jest.fn(),
185
+ removeEventListener: jest.fn()
186
+ };
187
+
188
+ (result.current.carouselRef as any).current = mockCarousel;
189
+
190
+ act(() => {
191
+ result.current.scrollToPage(1);
192
+ jest.runAllTimers();
193
+ });
194
+ expect(onPageChange).toHaveBeenCalledWith(1);
195
+ expect(mockCarousel.scrollTo).toHaveBeenCalled();
196
+ });
197
+
198
+ it('returns undefined cleanup when carousel is not mounted', () => {
199
+ const { result, unmount } = renderHook(() =>
200
+ useCarousel(10, { itemsPerPage: 2 })
201
+ );
202
+
203
+ expect(result.current.carouselRef.current).toBeNull();
204
+
205
+ unmount();
206
+ });
207
+
208
+ it('handles handleMouseMove when not dragging', () => {
209
+ const { result } = renderHook(() => useCarousel(10, { itemsPerPage: 2 }));
210
+
211
+ const mockEvent = ({
212
+ preventDefault: jest.fn(),
213
+ pageX: 100
214
+ } as unknown) as React.MouseEvent;
215
+
216
+ act(() => {
217
+ result.current.handleMouseMove(mockEvent);
218
+ });
219
+
220
+ expect(mockEvent.preventDefault).not.toHaveBeenCalled();
221
+ });
222
+
223
+ it('handles handleMouseUp when not dragging', () => {
224
+ const { result } = renderHook(() => useCarousel(10, { itemsPerPage: 2 }));
225
+
226
+ const mockEvent = ({
227
+ pageX: 100
228
+ } as unknown) as React.MouseEvent;
229
+
230
+ act(() => {
231
+ result.current.handleMouseUp(mockEvent);
232
+ });
233
+
234
+ expect(result.current.currentPage).toBe(0);
235
+ });
236
+
237
+ it('handles handleMouseLeave when not dragging', () => {
238
+ const { result } = renderHook(() => useCarousel(10, { itemsPerPage: 2 }));
239
+
240
+ act(() => {
241
+ result.current.handleMouseLeave();
242
+ });
243
+
244
+ expect(result.current.currentPage).toBe(0);
245
+ });
246
+
247
+ it('handles complete mouse drag interaction', () => {
248
+ const { result } = renderHook(() => useCarousel(10, { itemsPerPage: 2 }));
249
+
250
+ const mockCarousel = {
251
+ scrollLeft: 0,
252
+ children: [],
253
+ scrollTo: jest.fn(),
254
+ style: { cursor: 'grab' },
255
+ addEventListener: jest.fn(),
256
+ removeEventListener: jest.fn()
257
+ };
258
+
259
+ (result.current.carouselRef as any).current = mockCarousel;
260
+
261
+ const mouseDownEvent = ({
262
+ preventDefault: jest.fn(),
263
+ pageX: 200
264
+ } as unknown) as React.MouseEvent;
265
+
266
+ const mouseMoveEvent = ({
267
+ preventDefault: jest.fn(),
268
+ pageX: 150
269
+ } as unknown) as React.MouseEvent;
270
+
271
+ const mouseUpEvent = ({
272
+ pageX: 100
273
+ } as unknown) as React.MouseEvent;
274
+
275
+ act(() => {
276
+ result.current.handleMouseDown(mouseDownEvent);
277
+ });
278
+
279
+ expect(mouseDownEvent.preventDefault).toHaveBeenCalled();
280
+ expect(mockCarousel.style.cursor).toBe('grabbing');
281
+
282
+ act(() => {
283
+ result.current.handleMouseMove(mouseMoveEvent);
284
+ });
285
+
286
+ expect(mouseMoveEvent.preventDefault).toHaveBeenCalled();
287
+
288
+ act(() => {
289
+ result.current.handleMouseUp(mouseUpEvent);
290
+ });
291
+
292
+ expect(mockCarousel.style.cursor).toBe('grab');
293
+ });
294
+
295
+ it('handles mouseUp with small drag distance', () => {
296
+ const { result } = renderHook(() => useCarousel(10, { itemsPerPage: 2 }));
297
+
298
+ const mockCarousel = {
299
+ scrollLeft: 0,
300
+ children: [],
301
+ scrollTo: jest.fn(),
302
+ style: { cursor: 'grab' },
303
+ addEventListener: jest.fn(),
304
+ removeEventListener: jest.fn()
305
+ };
306
+
307
+ (result.current.carouselRef as any).current = mockCarousel;
308
+
309
+ act(() => {
310
+ result.current.handleMouseDown(({
311
+ preventDefault: jest.fn(),
312
+ pageX: 100
313
+ } as unknown) as React.MouseEvent);
314
+ });
315
+
316
+ act(() => {
317
+ result.current.handleMouseUp(({
318
+ pageX: 90
319
+ } as unknown) as React.MouseEvent);
320
+ });
321
+
322
+ expect(result.current.currentPage).toBe(0);
323
+ });
324
+
325
+ it('handles mouseLeave during active drag', () => {
326
+ const { result } = renderHook(() => useCarousel(10, { itemsPerPage: 2 }));
327
+
328
+ const mockCarousel = {
329
+ style: { cursor: 'grab' },
330
+ addEventListener: jest.fn(),
331
+ removeEventListener: jest.fn()
332
+ };
333
+
334
+ (result.current.carouselRef as any).current = mockCarousel;
335
+
336
+ act(() => {
337
+ result.current.handleMouseDown(({
338
+ preventDefault: jest.fn(),
339
+ pageX: 100
340
+ } as unknown) as React.MouseEvent);
341
+ });
342
+
343
+ expect(mockCarousel.style.cursor).toBe('grabbing');
344
+
345
+ act(() => {
346
+ result.current.handleMouseLeave();
347
+ });
348
+
349
+ expect(mockCarousel.style.cursor).toBe('grab');
350
+ });
351
+
352
+ it('scrollToPage handles case with no children', () => {
353
+ const { result } = renderHook(() => useCarousel(10, { itemsPerPage: 2 }));
354
+
355
+ const mockCarousel = {
356
+ children: [],
357
+ scrollTo: jest.fn(),
358
+ addEventListener: jest.fn(),
359
+ removeEventListener: jest.fn()
360
+ };
361
+
362
+ (result.current.carouselRef as any).current = mockCarousel;
363
+
364
+ act(() => {
365
+ result.current.scrollToPage(1);
366
+ });
367
+
368
+ expect(mockCarousel.scrollTo).not.toHaveBeenCalled();
369
+ });
370
+
371
+ it('handlePrevious and handleNext navigate correctly', () => {
372
+ const { result } = renderHook(() => useCarousel(10, { itemsPerPage: 2 }));
373
+
374
+ const mockCards = Array.from({ length: 5 }, (_, i) => ({
375
+ offsetLeft: i * 200
376
+ })) as HTMLElement[];
377
+
378
+ const mockCarousel = {
379
+ children: mockCards,
380
+ scrollTo: jest.fn(),
381
+ addEventListener: jest.fn(),
382
+ removeEventListener: jest.fn()
383
+ };
384
+
385
+ (result.current.carouselRef as any).current = mockCarousel;
386
+
387
+ act(() => {
388
+ result.current.handleNext();
389
+ jest.runAllTimers();
390
+ });
391
+
392
+ expect(result.current.currentPage).toBe(1);
393
+
394
+ act(() => {
395
+ result.current.handlePrevious();
396
+ jest.runAllTimers();
397
+ });
398
+
399
+ expect(result.current.currentPage).toBe(0);
400
+ });
401
+
402
+ it('drag triggers navigation', () => {
403
+ const { result } = renderHook(() => useCarousel(10, { itemsPerPage: 2 }));
404
+
405
+ const mockCards = Array.from({ length: 5 }, (_, i) => ({
406
+ offsetLeft: i * 200
407
+ })) as HTMLElement[];
408
+
409
+ const mockCarousel = {
410
+ children: mockCards,
411
+ scrollTo: jest.fn(),
412
+ style: { cursor: 'grab' },
413
+ addEventListener: jest.fn(),
414
+ removeEventListener: jest.fn()
415
+ };
416
+
417
+ (result.current.carouselRef as any).current = mockCarousel;
418
+
419
+ act(() => {
420
+ result.current.handleMouseDown(({
421
+ preventDefault: jest.fn(),
422
+ pageX: 200
423
+ } as unknown) as React.MouseEvent);
424
+ });
425
+
426
+ act(() => {
427
+ result.current.handleMouseUp(({
428
+ pageX: 100
429
+ } as unknown) as React.MouseEvent);
430
+ });
431
+
432
+ act(() => {
433
+ jest.runAllTimers();
434
+ });
435
+
436
+ expect(result.current.currentPage).toBe(1);
437
+ });
438
+ });