fix: target current browser page for grab mode

This commit is contained in:
Neil
2026-05-31 07:21:22 -07:00
committed by GitHub
parent 61b876e308
commit 0f704b95a4
2 changed files with 90 additions and 0 deletions
@@ -0,0 +1,87 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
function createReactHookHarness() {
const refs: { current: unknown }[] = []
const states: unknown[] = []
const effects: { effect: () => void | (() => void); deps: readonly unknown[] | undefined }[] = []
let refIndex = 0
let stateIndex = 0
return {
beginRender: () => {
refIndex = 0
stateIndex = 0
effects.length = 0
},
effects,
react: {
useCallback: <T extends (...args: never[]) => unknown>(callback: T): T => callback,
useEffect: (effect: () => void | (() => void), deps?: readonly unknown[]) => {
effects.push({ effect, deps })
},
useRef: <T>(initialValue: T): { current: T } => {
const index = refIndex
refIndex += 1
refs[index] ??= { current: initialValue }
return refs[index] as { current: T }
},
useState: <T>(initialValue: T): [T, (value: T) => void] => {
const index = stateIndex
stateIndex += 1
states[index] ??= initialValue
return [
states[index] as T,
(value: T) => {
states[index] = value
}
]
}
}
}
}
describe('useGrabMode', () => {
afterEach(() => {
vi.doUnmock('react')
vi.doUnmock('@/hooks/useMountedRef')
vi.resetModules()
vi.unstubAllGlobals()
})
it('uses the latest browser page when toggled before the page-change effect runs', async () => {
const harness = createReactHookHarness()
const setGrabMode = vi.fn(async () => ({ ok: true }))
vi.doMock('react', () => harness.react)
vi.doMock('@/hooks/useMountedRef', () => ({
useMountedRef: () => ({ current: true })
}))
vi.stubGlobal('window', {
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
api: {
browser: {
setGrabMode,
awaitGrabSelection: vi.fn(() => new Promise(() => {})),
cancelGrab: vi.fn()
}
}
})
const { useGrabMode } = await import('./useGrabMode')
const render = (browserPageId: string) => {
harness.beginRender()
// oxlint-disable-next-line react-hooks/rules-of-hooks -- test harness mocks React's hook dispatcher directly.
return useGrabMode(browserPageId)
}
render('page-1')
harness.effects[0]?.effect()
const grab = render('page-2')
grab.toggle()
await Promise.resolve()
expect(setGrabMode).toHaveBeenCalledWith({
browserPageId: 'page-2',
enabled: true
})
})
})
@@ -46,6 +46,9 @@ export function useGrabMode(browserPageId: string): GrabModeHook {
const activeOpIdRef = useRef<string | null>(null)
const grabTabIdRef = useRef<string | null>(null)
const browserTabIdRef = useRef(browserPageId)
// Why: toolbar/key handlers from the latest render can fire before passive
// effects run after a page switch, so keep the target page current in render.
browserTabIdRef.current = browserPageId
const mountedRef = useMountedRef()
// Why: when the browser page changes while grab is active, cancel the