mirror of
https://github.com/stablyai/orca.git
synced 2026-11-14 01:47:24 +00:00
refactor(native-chat): tidy the rail wake hook and cover it with tests (#39477)
Store absolute deadlines instead of two last-seen timestamps, and let scroll events with no wake pending return before reading the clock. Behaviour is unchanged.
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { renderHook } from '@testing-library/react'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { useNativeChatRailWake } from './use-native-chat-rail-wake'
|
||||
|
||||
function setup() {
|
||||
const hook = renderHook(() => useNativeChatRailWake())
|
||||
const rail = document.createElement('div')
|
||||
hook.result.current.railRef.current = rail
|
||||
return { ...hook.result.current, rail, unmount: hook.unmount }
|
||||
}
|
||||
|
||||
describe('useNativeChatRailWake', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
})
|
||||
afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
it('lights on input and goes dark after the wake window', () => {
|
||||
const { rail, wake } = setup()
|
||||
wake()
|
||||
expect(rail.dataset.awake).toBe('')
|
||||
vi.advanceTimersByTime(299)
|
||||
expect('awake' in rail.dataset).toBe(true)
|
||||
vi.advanceTimersByTime(1)
|
||||
expect('awake' in rail.dataset).toBe(false)
|
||||
})
|
||||
|
||||
it('stays lit while scroll events follow a recent input', () => {
|
||||
const { rail, wake, sustain } = setup()
|
||||
wake()
|
||||
for (let i = 0; i < 10; i++) {
|
||||
vi.advanceTimersByTime(100)
|
||||
sustain()
|
||||
}
|
||||
expect('awake' in rail.dataset).toBe(true)
|
||||
vi.advanceTimersByTime(300)
|
||||
expect('awake' in rail.dataset).toBe(false)
|
||||
})
|
||||
|
||||
it('stops sustaining once the input is too old', () => {
|
||||
const { rail, wake, sustain } = setup()
|
||||
wake()
|
||||
for (let i = 0; i < 20; i++) {
|
||||
vi.advanceTimersByTime(100)
|
||||
sustain()
|
||||
}
|
||||
// 1.5 s after input, scroll events no longer extend the wake.
|
||||
expect('awake' in rail.dataset).toBe(false)
|
||||
})
|
||||
|
||||
it('ignores scroll events with no wake pending', () => {
|
||||
const { rail, sustain } = setup()
|
||||
sustain()
|
||||
vi.advanceTimersByTime(1000)
|
||||
expect('awake' in rail.dataset).toBe(false)
|
||||
})
|
||||
|
||||
it('clears its timer on unmount', () => {
|
||||
const { wake, unmount } = setup()
|
||||
wake()
|
||||
unmount()
|
||||
expect(vi.getTimerCount()).toBe(0)
|
||||
})
|
||||
})
|
||||
@@ -17,12 +17,15 @@ export function useNativeChatRailWake(): {
|
||||
} {
|
||||
const railRef = useRef<HTMLDivElement | null>(null)
|
||||
const timer = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||
const lastWake = useRef(0)
|
||||
const lastInput = useRef(0)
|
||||
/** When the rail may go dark again. */
|
||||
const sleepAt = useRef(0)
|
||||
/** Scroll events before this still belong to the reader's last input. */
|
||||
const sustainUntil = useRef(0)
|
||||
|
||||
const wake = useCallback(() => {
|
||||
lastWake.current = performance.now()
|
||||
lastInput.current = lastWake.current
|
||||
const now = performance.now()
|
||||
sleepAt.current = now + NATIVE_CHAT_RAIL_WAKE_MS
|
||||
sustainUntil.current = now + NATIVE_CHAT_RAIL_SUSTAIN_MS
|
||||
const rail = railRef.current
|
||||
// Why: a gesture fires wheel events every frame; write the DOM and arm a timer once per gesture.
|
||||
if (rail && !('awake' in rail.dataset)) {
|
||||
@@ -32,7 +35,7 @@ export function useNativeChatRailWake(): {
|
||||
return
|
||||
}
|
||||
const settle = (): void => {
|
||||
const remaining = lastWake.current + NATIVE_CHAT_RAIL_WAKE_MS - performance.now()
|
||||
const remaining = sleepAt.current - performance.now()
|
||||
if (remaining > 0) {
|
||||
timer.current = setTimeout(settle, remaining)
|
||||
return
|
||||
@@ -46,9 +49,13 @@ export function useNativeChatRailWake(): {
|
||||
}, [])
|
||||
|
||||
const sustain = useCallback(() => {
|
||||
// Why: most scroll events (streaming, auto-follow) arrive with no wake pending; skip the clock read.
|
||||
if (timer.current === null) {
|
||||
return
|
||||
}
|
||||
const now = performance.now()
|
||||
if (timer.current !== null && now - lastInput.current < NATIVE_CHAT_RAIL_SUSTAIN_MS) {
|
||||
lastWake.current = now
|
||||
if (now < sustainUntil.current) {
|
||||
sleepAt.current = now + NATIVE_CHAT_RAIL_WAKE_MS
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user