Files
ComfyUI_frontend/src/extensions/core/load3d/load3dRenderLoop.test.ts
Terry Jia b0fa179b87 refactor: extract Load3d render loop to load3dRenderLoop (#11623)
## Summary

Pull the `requestAnimationFrame` loop and its activity-gated tick body
out of `Load3d` into a small `startRenderLoop({ tick, isActive })`
helper. Pure mechanical refactor — no behavior change. First of four
small PRs splitting up the
https://github.com/Comfy-Org/ComfyUI_frontend/pull/11495.

## Changes

- **What**: New `load3dRenderLoop.ts` exports `startRenderLoop` (returns
a `{ stop }` handle). `Load3d.startAnimation()` now constructs a loop
through it; `Load3d.remove()` calls `stop()` instead of
`cancelAnimationFrame`. Field `animationFrameId: number | null` becomes
`renderLoop: RenderLoopHandle | null`.

## Review Focus

- The tick body inside `startAnimation()` is byte-identical to the
previous inline body — only the rAF scheduling has moved.
- `isActive()` is now invoked through a `() => this.isActive()` closure
instead of a direct call inside the inline `animate` function, so the
activity check still fires once per frame and reads the same fields.
- The new helper has 4 unit tests covering: ticks while active, skip
while inactive, stop halts ticks, stop is idempotent.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11623-refactor-extract-Load3d-render-loop-to-load3dRenderLoop-34d6d73d3650815c9c4ec7713e912e37)
by [Unito](https://www.unito.io)
2026-04-25 20:03:01 -04:00

63 lines
1.6 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { startRenderLoop } from './load3dRenderLoop'
describe('startRenderLoop', () => {
beforeEach(() => {
vi.useFakeTimers()
})
afterEach(() => {
vi.useRealTimers()
})
it('runs tick on each frame while isActive returns true', () => {
const tick = vi.fn()
const handle = startRenderLoop({ tick, isActive: () => true })
vi.advanceTimersToNextTimer()
vi.advanceTimersToNextTimer()
vi.advanceTimersToNextTimer()
expect(tick.mock.calls.length).toBeGreaterThanOrEqual(3)
handle.stop()
})
it('skips tick on frames where isActive returns false', () => {
let active = false
const tick = vi.fn()
const handle = startRenderLoop({ tick, isActive: () => active })
vi.advanceTimersToNextTimer()
vi.advanceTimersToNextTimer()
expect(tick).not.toHaveBeenCalled()
active = true
vi.advanceTimersToNextTimer()
expect(tick).toHaveBeenCalledOnce()
handle.stop()
})
it('stop halts further ticks', () => {
const tick = vi.fn()
const handle = startRenderLoop({ tick, isActive: () => true })
vi.advanceTimersToNextTimer()
const callsBeforeStop = tick.mock.calls.length
handle.stop()
vi.advanceTimersToNextTimer()
vi.advanceTimersToNextTimer()
expect(tick.mock.calls.length).toBe(callsBeforeStop)
})
it('is safe to call stop multiple times', () => {
const handle = startRenderLoop({ tick: vi.fn(), isActive: () => true })
handle.stop()
expect(() => handle.stop()).not.toThrow()
})
})