github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/web/src/raf.tsx (about) 1 import React, { useContext } from "react" 2 3 // requestAnimationFrame (RAF) is a general browser primitive 4 // for scheduling things that are only necessary for rendering. 5 // 6 // The advantage of using a RAF callback is: 7 // - They're paused on background tabs when the browser isn't rendering. 8 // - They allow you to "yield" the CPU so that if you have some long-running 9 // rendering task, it doesn't make animation jittery. 10 // 11 // RafContext is a way for us to use RAF callbcks in tests. 12 13 export type RafContext = { 14 requestAnimationFrame(callback: () => void): number 15 cancelAnimationFrame(id: number): void 16 } 17 18 const rafContext = React.createContext<RafContext>({ 19 // By default, use the normal window schedulers. 20 requestAnimationFrame: (callback: () => void) => 21 window.requestAnimationFrame(callback), 22 cancelAnimationFrame: (id: number) => window.cancelAnimationFrame(id), 23 }) 24 25 export function useRaf(): RafContext { 26 return useContext(rafContext) 27 } 28 29 // Inject a RAF provider that runs all callbacks synchronously. 30 export function SyncRafProvider(props: React.PropsWithChildren<{}>) { 31 let context = { 32 requestAnimationFrame: (callback: () => void) => { 33 callback() 34 return 0 35 }, 36 cancelAnimationFrame: () => {}, 37 } 38 return ( 39 <rafContext.Provider value={context}>{props.children}</rafContext.Provider> 40 ) 41 } 42 43 export let RafProvider = rafContext.Provider 44 45 export type TestRafContext = { 46 callbacks: { [key: string]: () => void } 47 invoke: jest.Mock<void, [id: number]> 48 requestAnimationFrame: jest.Mock<number, [callback: () => void]> 49 cancelAnimationFrame: jest.Mock<void, [id: number]> 50 } 51 52 // Returns a scheduler that pauses callbacks 53 // until they're invoked manually by ID. 54 export function newFakeRaf(): TestRafContext { 55 let callbacks: any = {} 56 let callbackCount = 0 57 return { 58 callbacks: callbacks, 59 invoke: jest.fn((id: number) => { 60 callbacks[id].call() 61 delete callbacks[id] 62 }), 63 requestAnimationFrame: jest.fn((callback: () => void) => { 64 let id = ++callbackCount 65 callbacks[id] = callback 66 return id 67 }), 68 cancelAnimationFrame: jest.fn((id: number) => { 69 delete callbacks[id] 70 }), 71 } 72 }