mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-08-09 00:47:59 +08:00
### What problem does this PR solve? This code allows user chat to auto-scroll down when entered, but if user scrolls up away from the generative feedback, autoscroll is disabled. Close #9062 ### Type of change - [x] Bug Fix (non-breaking change which fixes an issue) --------- Co-authored-by: Charles Copley <ccopley@ancera.com>
128 lines
4.0 KiB
TypeScript
128 lines
4.0 KiB
TypeScript
jest.mock('eventsource-parser/stream', () => ({}));
|
|
|
|
import { act, renderHook } from '@testing-library/react';
|
|
import { useScrollToBottom } from '../logic-hooks';
|
|
|
|
function createMockContainer({ atBottom = true } = {}) {
|
|
const scrollTop = atBottom ? 100 : 0;
|
|
const clientHeight = 100;
|
|
const scrollHeight = 200;
|
|
const listeners = {};
|
|
return {
|
|
current: {
|
|
scrollTop,
|
|
clientHeight,
|
|
scrollHeight,
|
|
addEventListener: jest.fn((event, cb) => {
|
|
listeners[event] = cb;
|
|
}),
|
|
removeEventListener: jest.fn(),
|
|
},
|
|
listeners,
|
|
} as any;
|
|
}
|
|
|
|
// Helper to flush all timers and microtasks
|
|
async function flushAll() {
|
|
jest.runAllTimers();
|
|
// Flush microtasks
|
|
await Promise.resolve();
|
|
// Sometimes, effects queue more timers, so run again
|
|
jest.runAllTimers();
|
|
await Promise.resolve();
|
|
}
|
|
|
|
describe('useScrollToBottom', () => {
|
|
beforeEach(() => {
|
|
jest.useFakeTimers();
|
|
});
|
|
afterEach(() => {
|
|
jest.useRealTimers();
|
|
});
|
|
|
|
it('should set isAtBottom true when user is at bottom', () => {
|
|
const containerRef = createMockContainer({ atBottom: true });
|
|
const { result } = renderHook(() => useScrollToBottom([], containerRef));
|
|
expect(result.current.isAtBottom).toBe(true);
|
|
});
|
|
|
|
it('should set isAtBottom false when user is not at bottom', () => {
|
|
const containerRef = createMockContainer({ atBottom: false });
|
|
const { result } = renderHook(() => useScrollToBottom([], containerRef));
|
|
expect(result.current.isAtBottom).toBe(false);
|
|
});
|
|
|
|
it('should scroll to bottom when isAtBottom is true and messages change', async () => {
|
|
const containerRef = createMockContainer({ atBottom: true });
|
|
const mockScroll = jest.fn();
|
|
|
|
function useTestScrollToBottom(messages: any, containerRef: any) {
|
|
const hook = useScrollToBottom(messages, containerRef);
|
|
hook.scrollRef.current = { scrollIntoView: mockScroll } as any;
|
|
return hook;
|
|
}
|
|
|
|
const { rerender } = renderHook(
|
|
({ messages }) => useTestScrollToBottom(messages, containerRef),
|
|
{ initialProps: { messages: [] } },
|
|
);
|
|
|
|
rerender({ messages: ['msg1'] });
|
|
await flushAll();
|
|
|
|
expect(mockScroll).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should NOT scroll to bottom when isAtBottom is false and messages change', async () => {
|
|
const containerRef = createMockContainer({ atBottom: false });
|
|
const mockScroll = jest.fn();
|
|
|
|
function useTestScrollToBottom(messages: any, containerRef: any) {
|
|
const hook = useScrollToBottom(messages, containerRef);
|
|
hook.scrollRef.current = { scrollIntoView: mockScroll } as any;
|
|
console.log('HOOK: isAtBottom:', hook.isAtBottom);
|
|
return hook;
|
|
}
|
|
|
|
const { result, rerender } = renderHook(
|
|
({ messages }) => useTestScrollToBottom(messages, containerRef),
|
|
{ initialProps: { messages: [] } },
|
|
);
|
|
|
|
// Simulate user scrolls up before messages change
|
|
await act(async () => {
|
|
containerRef.current.scrollTop = 0;
|
|
containerRef.current.addEventListener.mock.calls[0][1]();
|
|
await flushAll();
|
|
// Advance fake timers by 10ms instead of real setTimeout
|
|
jest.advanceTimersByTime(10);
|
|
console.log('AFTER SCROLL: isAtBottom:', result.current.isAtBottom);
|
|
});
|
|
|
|
rerender({ messages: ['msg1'] });
|
|
await flushAll();
|
|
|
|
console.log('AFTER RERENDER: isAtBottom:', result.current.isAtBottom);
|
|
|
|
expect(mockScroll).not.toHaveBeenCalled();
|
|
|
|
// Optionally, flush again after the assertion to see if it gets called late
|
|
await flushAll();
|
|
});
|
|
|
|
it('should indicate button should appear when user is not at bottom', () => {
|
|
const containerRef = createMockContainer({ atBottom: false });
|
|
const { result } = renderHook(() => useScrollToBottom([], containerRef));
|
|
// The button should appear in the UI when isAtBottom is false
|
|
expect(result.current.isAtBottom).toBe(false);
|
|
});
|
|
});
|
|
|
|
const originalRAF = global.requestAnimationFrame;
|
|
beforeAll(() => {
|
|
global.requestAnimationFrame = (cb) => setTimeout(cb, 0);
|
|
});
|
|
afterAll(() => {
|
|
global.requestAnimationFrame = originalRAF;
|
|
});
|