| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import { describe, it, expect } from 'vitest'; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| function isScrollAtBottom( |
| scrollTop: number, |
| scrollHeight: number, |
| clientHeight: number, |
| threshold = 50 |
| ): boolean { |
| const distanceFromBottom = scrollHeight - scrollTop - clientHeight; |
| return distanceFromBottom < threshold; |
| } |
|
|
| describe('Summary Auto-Scroll Detection Logic', () => { |
| describe('basic scroll position detection', () => { |
| it('should return true when scrolled to exact bottom', () => { |
| |
| |
| const result = isScrollAtBottom(500, 1000, 500); |
| expect(result).toBe(true); |
| }); |
|
|
| it('should return true when near bottom (within threshold)', () => { |
| |
| const result = isScrollAtBottom(451, 1000, 500); |
| expect(result).toBe(true); |
| }); |
|
|
| it('should return true when exactly at threshold boundary (49px)', () => { |
| |
| const result = isScrollAtBottom(451, 1000, 500); |
| expect(result).toBe(true); |
| }); |
|
|
| it('should return false when just outside threshold (51px)', () => { |
| |
| const result = isScrollAtBottom(449, 1000, 500); |
| expect(result).toBe(false); |
| }); |
|
|
| it('should return false when scrolled to top', () => { |
| const result = isScrollAtBottom(0, 1000, 500); |
| expect(result).toBe(false); |
| }); |
|
|
| it('should return false when scrolled to middle', () => { |
| const result = isScrollAtBottom(250, 1000, 500); |
| expect(result).toBe(false); |
| }); |
| }); |
|
|
| describe('edge cases with small content', () => { |
| it('should return true when content fits in viewport (no scroll needed)', () => { |
| |
| const result = isScrollAtBottom(0, 300, 500); |
| expect(result).toBe(true); |
| }); |
|
|
| it('should return true when content exactly fits viewport', () => { |
| const result = isScrollAtBottom(0, 500, 500); |
| expect(result).toBe(true); |
| }); |
|
|
| it('should return true when content slightly exceeds viewport (within threshold)', () => { |
| |
| |
| const result = isScrollAtBottom(0, 540, 500); |
| expect(result).toBe(true); |
| }); |
| }); |
|
|
| describe('large content scenarios', () => { |
| it('should correctly detect bottom in very long content', () => { |
| |
| |
| const result = isScrollAtBottom(9500, 10000, 500); |
| expect(result).toBe(true); |
| }); |
|
|
| it('should correctly detect non-bottom in very long content', () => { |
| |
| const result = isScrollAtBottom(5000, 10000, 500); |
| expect(result).toBe(false); |
| }); |
|
|
| it('should detect when user scrolls up from bottom', () => { |
| |
| const result = isScrollAtBottom(9400, 10000, 500); |
| expect(result).toBe(false); |
| }); |
| }); |
|
|
| describe('custom threshold values', () => { |
| it('should work with larger threshold (100px)', () => { |
| |
| const result = isScrollAtBottom(425, 1000, 500, 100); |
| expect(result).toBe(true); |
| }); |
|
|
| it('should work with smaller threshold (10px)', () => { |
| |
| const result = isScrollAtBottom(485, 1000, 500, 10); |
| expect(result).toBe(false); |
| }); |
|
|
| it('should work with zero threshold (exact match only)', () => { |
| |
| |
| const result = isScrollAtBottom(500, 1000, 500, 0); |
| expect(result).toBe(false); |
|
|
| |
| const result2 = isScrollAtBottom(499, 1000, 500, 0); |
| expect(result2).toBe(false); |
|
|
| |
| |
| const result3 = isScrollAtBottom(501, 1000, 500, 0); |
| expect(result3).toBe(true); |
| }); |
| }); |
|
|
| describe('pipeline summary scrolling scenarios', () => { |
| it('should enable auto-scroll when new content arrives while at bottom', () => { |
| |
| |
| |
|
|
| |
| |
|
|
| |
| const newScrollTop = 1500 - 500; |
| const result = isScrollAtBottom(newScrollTop, 1500, 500); |
| expect(result).toBe(true); |
| }); |
|
|
| it('should not auto-scroll when user is reading earlier summaries', () => { |
| |
| |
| |
|
|
| |
| |
| |
| const result = isScrollAtBottom(200, 1500, 500); |
| expect(result).toBe(false); |
| }); |
|
|
| it('should re-enable auto-scroll when user scrolls back to bottom', () => { |
| |
| |
| const result = isScrollAtBottom(1450, 1500, 500); |
| expect(result).toBe(true); |
| }); |
| }); |
|
|
| describe('decimal scroll values', () => { |
| it('should handle fractional scroll positions', () => { |
| |
| const result = isScrollAtBottom(499.5, 1000, 500); |
| expect(result).toBe(true); |
| }); |
|
|
| it('should handle fractional scroll heights', () => { |
| const result = isScrollAtBottom(450.7, 1000.3, 500); |
| expect(result).toBe(true); |
| }); |
| }); |
|
|
| describe('negative and invalid inputs', () => { |
| it('should handle negative scrollTop (bounce scroll)', () => { |
| |
| const result = isScrollAtBottom(-10, 1000, 500); |
| expect(result).toBe(false); |
| }); |
|
|
| it('should handle zero scrollHeight', () => { |
| |
| const result = isScrollAtBottom(0, 0, 500); |
| expect(result).toBe(true); |
| }); |
|
|
| it('should handle zero clientHeight', () => { |
| |
| |
| |
| const result = isScrollAtBottom(0, 1000, 0); |
| expect(result).toBe(false); |
| }); |
| }); |
|
|
| describe('real-world accumulated summary dimensions', () => { |
| it('should handle typical 3-step pipeline summary dimensions', () => { |
| |
| |
| const result = isScrollAtBottom(2000, 2400, 400); |
| expect(result).toBe(true); |
| }); |
|
|
| it('should handle large 10-step pipeline summary dimensions', () => { |
| |
| |
| const result = isScrollAtBottom(7600, 8000, 400); |
| expect(result).toBe(true); |
| }); |
|
|
| it('should detect scroll to top of large summary', () => { |
| |
| const result = isScrollAtBottom(0, 8000, 400); |
| expect(result).toBe(false); |
| }); |
| }); |
| }); |
|
|