• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

prabhuignoto / react-chrono / #92

18 Jun 2025 10:08AM UTC coverage: 90.727% (+0.9%) from 89.791%
#92

push

web-flow
Minor cleanup and expanding test coverage (#548)

* refactor: rename project to React Chrono UI and update related files

* fix: update tsconfig to reference the correct entry file for React Chrono UI

* feat: enhance styles with vendor prefixes and improve cross-browser support

* Add tests for useNewScrollPosition hook and TimelineHorizontal component

- Implement comprehensive tests for the useNewScrollPosition hook covering horizontal, vertical, and edge cases.
- Create a new test file for the TimelineHorizontal component, ensuring it renders correctly and handles various props and states.
- Update snapshots for timeline control and vertical components to reflect recent changes in class names.
- Modify vitest configuration to include all test files in the src directory.

* refactor: simplify transform handling in timeline styles

* refactor: clean up test imports and remove unused styles in timeline components

1783 of 2112 branches covered (84.42%)

Branch coverage included in aggregate %.

670 of 674 new or added lines in 12 files covered. (99.41%)

400 existing lines in 29 files now uncovered.

10564 of 11497 relevant lines covered (91.88%)

10.09 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.98
/src/hooks/__tests__/useTimelineNavigation.test.ts
1
import { renderHook, act } from '@testing-library/react';
1✔
2
import { describe, expect, it, vi, beforeEach } from 'vitest';
1✔
3
import { useTimelineNavigation } from '../useTimelineNavigation';
1✔
4

5
// Mock the findTimelineElement utility
6
vi.mock('../utils/timelineUtils', () => ({
1✔
7
  findTimelineElement: vi.fn(() => ({
×
8
    scrollIntoView: vi.fn(),
×
9
  })),
×
10
}));
1✔
11

12
// Helper function to create mock keyboard events
13
const createMockKeyboardEvent = (key: string): React.KeyboardEvent<HTMLDivElement> => ({
1✔
14
  key,
9✔
15
  preventDefault: vi.fn(),
9✔
16
} as unknown as React.KeyboardEvent<HTMLDivElement>);
9✔
17

18
describe('useTimelineNavigation', () => {
1✔
19
  const mockItems = [
1✔
20
    { id: '1', title: 'First Item' },
1✔
21
    { id: '2', title: 'Second Item' },
1✔
22
    { id: '3', title: 'Third Item' },
1✔
23
  ];
1✔
24

25
  const mockOnTimelineUpdated = vi.fn();
1✔
26
  const mockOnNext = vi.fn();
1✔
27
  const mockOnPrevious = vi.fn();
1✔
28
  const mockOnFirst = vi.fn();
1✔
29
  const mockOnLast = vi.fn();
1✔
30

31
  beforeEach(() => {
1✔
32
    vi.clearAllMocks();
15✔
33
  });
1✔
34

35
  it('should initialize with correct state', () => {
1✔
36
    const { result } = renderHook(() =>
1✔
37
      useTimelineNavigation({
1✔
38
        items: mockItems,
1✔
39
        mode: 'VERTICAL',
1✔
40
        timelineId: 'test-timeline',
1✔
41
        hasFocus: true,
1✔
42
        onTimelineUpdated: mockOnTimelineUpdated,
1✔
43
        onNext: mockOnNext,
1✔
44
        onPrevious: mockOnPrevious,
1✔
45
        onFirst: mockOnFirst,
1✔
46
        onLast: mockOnLast,
1✔
47
      }),
1✔
48
    );
1✔
49

50
    expect(result.current.activeItemIndex.current).toBe(0);
1✔
51
  });
1✔
52

53
  it('should handle timeline item click', () => {
1✔
54
    const { result } = renderHook(() =>
1✔
55
      useTimelineNavigation({
1✔
56
        items: mockItems,
1✔
57
        mode: 'VERTICAL',
1✔
58
        timelineId: 'test-timeline',
1✔
59
        hasFocus: true,
1✔
60
        onTimelineUpdated: mockOnTimelineUpdated,
1✔
61
      }),
1✔
62
    );
1✔
63

64
    act(() => {
1✔
65
      result.current.handleTimelineItemClick('2');
1✔
66
    });
1✔
67

68
    expect(result.current.activeItemIndex.current).toBe(1);
1✔
69
    expect(mockOnTimelineUpdated).toHaveBeenCalledWith(1);
1✔
70
  });
1✔
71

72
  it('should handle timeline item elapsed', () => {
1✔
73
    const { result } = renderHook(() =>
1✔
74
      useTimelineNavigation({
1✔
75
        items: mockItems,
1✔
76
        mode: 'VERTICAL',
1✔
77
        timelineId: 'test-timeline',
1✔
78
        hasFocus: true,
1✔
79
        onTimelineUpdated: mockOnTimelineUpdated,
1✔
80
      }),
1✔
81
    );
1✔
82

83
    act(() => {
1✔
84
      result.current.handleTimelineItemElapsed('2');
1✔
85
    });
1✔
86

87
    expect(result.current.activeItemIndex.current).toBe(1);
1✔
88
    expect(mockOnTimelineUpdated).toHaveBeenCalledWith(2); // isSlideShow is true
1✔
89
  });
1✔
90

91
  it('should handle next navigation', () => {
1✔
92
    const { result } = renderHook(() =>
1✔
93
      useTimelineNavigation({
1✔
94
        items: mockItems,
1✔
95
        mode: 'VERTICAL',
1✔
96
        timelineId: 'test-timeline',
1✔
97
        hasFocus: true,
1✔
98
        onNext: mockOnNext,
1✔
99
      }),
1✔
100
    );
1✔
101

102
    act(() => {
1✔
103
      result.current.handleNext();
1✔
104
    });
1✔
105

106
    expect(result.current.activeItemIndex.current).toBe(1);
1✔
107
    expect(mockOnNext).toHaveBeenCalled();
1✔
108
  });
1✔
109

110
  it('should handle previous navigation', () => {
1✔
111
    const { result } = renderHook(() =>
1✔
112
      useTimelineNavigation({
1✔
113
        items: mockItems,
1✔
114
        mode: 'VERTICAL',
1✔
115
        timelineId: 'test-timeline',
1✔
116
        hasFocus: true,
1✔
117
        onPrevious: mockOnPrevious,
1✔
118
      }),
1✔
119
    );
1✔
120

121
    // First move to the second item
122
    act(() => {
1✔
123
      result.current.handleNext();
1✔
124
    });
1✔
125

126
    // Then move back
127
    act(() => {
1✔
128
      result.current.handlePrevious();
1✔
129
    });
1✔
130

131
    expect(result.current.activeItemIndex.current).toBe(0);
1✔
132
    expect(mockOnPrevious).toHaveBeenCalled();
1✔
133
  });
1✔
134

135
  it('should handle first navigation', () => {
1✔
136
    const { result } = renderHook(() =>
1✔
137
      useTimelineNavigation({
1✔
138
        items: mockItems,
1✔
139
        mode: 'VERTICAL',
1✔
140
        timelineId: 'test-timeline',
1✔
141
        hasFocus: true,
1✔
142
        onFirst: mockOnFirst,
1✔
143
      }),
1✔
144
    );
1✔
145

146
    // First move to the last item
147
    act(() => {
1✔
148
      result.current.handleLast();
1✔
149
    });
1✔
150

151
    // Then move to first
152
    act(() => {
1✔
153
      result.current.handleFirst();
1✔
154
    });
1✔
155

156
    expect(result.current.activeItemIndex.current).toBe(0);
1✔
157
    expect(mockOnFirst).toHaveBeenCalled();
1✔
158
  });
1✔
159

160
  it('should handle last navigation', () => {
1✔
161
    const { result } = renderHook(() =>
1✔
162
      useTimelineNavigation({
1✔
163
        items: mockItems,
1✔
164
        mode: 'VERTICAL',
1✔
165
        timelineId: 'test-timeline',
1✔
166
        hasFocus: true,
1✔
167
        onLast: mockOnLast,
1✔
168
      }),
1✔
169
    );
1✔
170

171
    act(() => {
1✔
172
      result.current.handleLast();
1✔
173
    });
1✔
174

175
    expect(result.current.activeItemIndex.current).toBe(2);
1✔
176
    expect(mockOnLast).toHaveBeenCalled();
1✔
177
  });
1✔
178

179
  it('should handle keyboard navigation in vertical mode', () => {
1✔
180
    const { result } = renderHook(() =>
1✔
181
      useTimelineNavigation({
1✔
182
        items: mockItems,
1✔
183
        mode: 'VERTICAL',
1✔
184
        timelineId: 'test-timeline',
1✔
185
        hasFocus: true,
1✔
186
        onNext: mockOnNext,
1✔
187
        onPrevious: mockOnPrevious,
1✔
188
        onFirst: mockOnFirst,
1✔
189
        onLast: mockOnLast,
1✔
190
      }),
1✔
191
    );
1✔
192

193
    // Test arrow down
194
    act(() => {
1✔
195
      result.current.handleKeySelection(createMockKeyboardEvent('ArrowDown'));
1✔
196
    });
1✔
197

198
    expect(result.current.activeItemIndex.current).toBe(1);
1✔
199
    expect(mockOnNext).toHaveBeenCalled();
1✔
200

201
    // Test arrow up
202
    act(() => {
1✔
203
      result.current.handleKeySelection(createMockKeyboardEvent('ArrowUp'));
1✔
204
    });
1✔
205

206
    expect(result.current.activeItemIndex.current).toBe(0);
1✔
207
    expect(mockOnPrevious).toHaveBeenCalled();
1✔
208

209
    // Move to a different position first, then test Home key
210
    act(() => {
1✔
211
      result.current.handleKeySelection(createMockKeyboardEvent('ArrowDown'));
1✔
212
    });
1✔
213
    
214
    act(() => {
1✔
215
      result.current.handleKeySelection(createMockKeyboardEvent('Home'));
1✔
216
    });
1✔
217

218
    expect(result.current.activeItemIndex.current).toBe(0);
1✔
219
    expect(mockOnFirst).toHaveBeenCalled();
1✔
220

221
    // Test End key
222
    act(() => {
1✔
223
      result.current.handleKeySelection(createMockKeyboardEvent('End'));
1✔
224
    });
1✔
225

226
    expect(result.current.activeItemIndex.current).toBe(mockItems.length - 1);
1✔
227
    expect(mockOnLast).toHaveBeenCalled();
1✔
228
  });
1✔
229

230
  it('should handle keyboard navigation in horizontal mode', () => {
1✔
231
    const { result } = renderHook(() =>
1✔
232
      useTimelineNavigation({
1✔
233
        items: mockItems,
1✔
234
        mode: 'HORIZONTAL',
1✔
235
        timelineId: 'test-timeline',
1✔
236
        hasFocus: true,
1✔
237
        onNext: mockOnNext,
1✔
238
        onPrevious: mockOnPrevious,
1✔
239
      }),
1✔
240
    );
1✔
241

242
    // Test arrow right
243
    act(() => {
1✔
244
      result.current.handleKeySelection(createMockKeyboardEvent('ArrowRight'));
1✔
245
    });
1✔
246

247
    expect(result.current.activeItemIndex.current).toBe(1);
1✔
248
    expect(mockOnNext).toHaveBeenCalled();
1✔
249

250
    // Test arrow left
251
    act(() => {
1✔
252
      result.current.handleKeySelection(createMockKeyboardEvent('ArrowLeft'));
1✔
253
    });
1✔
254

255
    expect(result.current.activeItemIndex.current).toBe(0);
1✔
256
    expect(mockOnPrevious).toHaveBeenCalled();
1✔
257
  });
1✔
258

259
  it('should handle flipped layout in horizontal mode', () => {
1✔
260
    const { result } = renderHook(() =>
1✔
261
      useTimelineNavigation({
1✔
262
        items: mockItems,
1✔
263
        mode: 'HORIZONTAL',
1✔
264
        timelineId: 'test-timeline',
1✔
265
        hasFocus: true,
1✔
266
        flipLayout: true,
1✔
267
        onNext: mockOnNext,
1✔
268
        onPrevious: mockOnPrevious,
1✔
269
      }),
1✔
270
    );
1✔
271

272
    // Move to index 1 first to enable testing both directions
273
    act(() => {
1✔
274
      result.current.handleKeySelection(createMockKeyboardEvent('ArrowLeft'));
1✔
275
    });
1✔
276

277
    expect(result.current.activeItemIndex.current).toBe(1);
1✔
278
    expect(mockOnNext).toHaveBeenCalled();
1✔
279

280
    // Test arrow right (should go previous in flipped layout)
281
    act(() => {
1✔
282
      result.current.handleKeySelection(createMockKeyboardEvent('ArrowRight'));
1✔
283
    });
1✔
284

285
    expect(result.current.activeItemIndex.current).toBe(0);
1✔
286
    expect(mockOnPrevious).toHaveBeenCalled();
1✔
287
  });
1✔
288

289
  it('should not handle navigation when hasFocus is false', () => {
1✔
290
    const { result } = renderHook(() =>
1✔
291
      useTimelineNavigation({
1✔
292
        items: mockItems,
1✔
293
        mode: 'VERTICAL',
1✔
294
        timelineId: 'test-timeline',
1✔
295
        hasFocus: false,
1✔
296
        onNext: mockOnNext,
1✔
297
        onPrevious: mockOnPrevious,
1✔
298
        onFirst: mockOnFirst,
1✔
299
        onLast: mockOnLast,
1✔
300
      }),
1✔
301
    );
1✔
302

303
    act(() => {
1✔
304
      result.current.handleNext();
1✔
305
      result.current.handlePrevious();
1✔
306
      result.current.handleFirst();
1✔
307
      result.current.handleLast();
1✔
308
    });
1✔
309

310
    expect(mockOnNext).not.toHaveBeenCalled();
1✔
311
    expect(mockOnPrevious).not.toHaveBeenCalled();
1✔
312
    expect(mockOnFirst).not.toHaveBeenCalled();
1✔
313
    expect(mockOnLast).not.toHaveBeenCalled();
1✔
314
  });
1✔
315

316
  it('should not handle invalid item clicks', () => {
1✔
317
    const { result } = renderHook(() =>
1✔
318
      useTimelineNavigation({
1✔
319
        items: mockItems,
1✔
320
        mode: 'VERTICAL',
1✔
321
        timelineId: 'test-timeline',
1✔
322
        hasFocus: true,
1✔
323
        onTimelineUpdated: mockOnTimelineUpdated,
1✔
324
      }),
1✔
325
    );
1✔
326

327
    act(() => {
1✔
328
      result.current.handleTimelineItemClick('invalid-id');
1✔
329
    });
1✔
330

331
    expect(mockOnTimelineUpdated).not.toHaveBeenCalled();
1✔
332
  });
1✔
333

334
  it('should disable card alignment scrolling in horizontal mode when slideshow is running', () => {
1✔
335
    const mockScrollIntoView = vi.fn();
1✔
336
    
337
    // Mock document.getElementById to return an element with scrollIntoView method
338
    const originalGetElementById = document.getElementById;
1✔
339
    document.getElementById = vi.fn(() => ({
1✔
UNCOV
340
      scrollIntoView: mockScrollIntoView,
×
341
    })) as any;
1✔
342

343
    const { result } = renderHook(() =>
1✔
344
      useTimelineNavigation({
1✔
345
        items: mockItems,
1✔
346
        mode: 'HORIZONTAL',
1✔
347
        timelineId: 'test-timeline',
1✔
348
        hasFocus: true,
1✔
349
        slideShowRunning: true,
1✔
350
        onTimelineUpdated: mockOnTimelineUpdated,
1✔
351
      }),
1✔
352
    );
1✔
353

354
    act(() => {
1✔
355
      result.current.handleTimelineItemClick('2');
1✔
356
    });
1✔
357

358
    // Should update timeline position but not call scrollIntoView when slideshow is running
359
    expect(result.current.activeItemIndex.current).toBe(1);
1✔
360
    expect(mockOnTimelineUpdated).toHaveBeenCalledWith(1);
1✔
361
    expect(mockScrollIntoView).not.toHaveBeenCalled();
1✔
362

363
    // Restore original function
364
    document.getElementById = originalGetElementById;
1✔
365
  });
1✔
366

367
  it('should allow card alignment scrolling in horizontal mode when slideshow is not running', async () => {
1✔
368
    const mockScrollIntoView = vi.fn();
1✔
369
    
370
    // Mock document.getElementById to return an element with scrollIntoView method and closest method
371
    const originalGetElementById = document.getElementById;
1✔
372
    document.getElementById = vi.fn(() => ({
1✔
373
      scrollIntoView: mockScrollIntoView,
1✔
374
      closest: vi.fn(() => null), // Mock closest method
1✔
375
      parentElement: null,
1✔
376
    })) as any;
1✔
377

378
    const { result } = renderHook(() =>
1✔
379
      useTimelineNavigation({
1✔
380
        items: mockItems,
1✔
381
        mode: 'HORIZONTAL',
1✔
382
        timelineId: 'test-timeline',
1✔
383
        hasFocus: true,
1✔
384
        slideShowRunning: false,
1✔
385
        onTimelineUpdated: mockOnTimelineUpdated,
1✔
386
      }),
1✔
387
    );
1✔
388

389
    act(() => {
1✔
390
      result.current.handleTimelineItemClick('2');
1✔
391
    });
1✔
392

393
    // Wait for requestAnimationFrame to complete
394
    await act(async () => {
1✔
395
      await new Promise(resolve => requestAnimationFrame(resolve));
1✔
396
    });
1✔
397

398
    // Should update timeline position and call scrollIntoView when slideshow is not running
399
    expect(result.current.activeItemIndex.current).toBe(1);
1✔
400
    expect(mockOnTimelineUpdated).toHaveBeenCalledWith(1);
1✔
401
    expect(mockScrollIntoView).toHaveBeenCalled();
1✔
402

403
    // Restore original function
404
    document.getElementById = originalGetElementById;
1✔
405
  });
1✔
406

407
  it('should allow card alignment scrolling in vertical mode even when slideshow is running', () => {
1✔
408
    const { result } = renderHook(() =>
1✔
409
      useTimelineNavigation({
1✔
410
        items: mockItems,
1✔
411
        mode: 'VERTICAL',
1✔
412
        timelineId: 'test-timeline',
1✔
413
        hasFocus: true,
1✔
414
        slideShowRunning: true,
1✔
415
        onTimelineUpdated: mockOnTimelineUpdated,
1✔
416
      }),
1✔
417
    );
1✔
418

419
    act(() => {
1✔
420
      result.current.handleTimelineItemClick('2');
1✔
421
    });
1✔
422

423
    // Should update timeline position and the mocked scrollIntoView should be called
424
    expect(result.current.activeItemIndex.current).toBe(1);
1✔
425
    expect(mockOnTimelineUpdated).toHaveBeenCalledWith(1);
1✔
426
    // In vertical mode, slideshow running should not affect scrolling behavior
427
  });
1✔
428
});
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc