Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/free-ants-turn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@threlte/test': minor
---

`render()` now automatically advances the scheduler once with `delta: 0` after mounting
25 changes: 9 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,29 +63,22 @@ const { frameInvalidated } = advance()
expect(frameInvalidated).toBe(true)
```

#### First advance after render
#### Initial advance on render

The first `advance()` call after `render()` will always return `{ frameInvalidated: true }`. This is expected — Threlte's internal setup (renderer properties, camera, resize detection) calls `invalidate()` during initialization, just as it does in production on the first animation frame.
`render()` automatically calls `advance({ delta: 0 })` once before returning. This ensures that scene matrices (`matrixWorld`) are computed for all objects, so positional assertions are immediately valid without requiring a manual `advance()` call first.

When testing invalidation behavior, call `advance()` once to drain the setup invalidation, then use subsequent calls for your assertions:
There are a few consequences worth knowing:

```ts
const { advance, rerender } = render(MyComponent, {
props: { autoInvalidate: false },
})
**The first user `advance()` is the second scheduler tick.** Any `useTask` with `autoStart: true` will have already run once with `delta: 0` by the time `render()` returns. If your task has side effects that should only run from explicit test code, start the task with `autoStart: false` and control it manually.

// Drain setup invalidation
advance()
**The initial `frameInvalidated` signal is consumed internally.** Any `invalidate()` calls that fire during component initialization are cleared before `render()` returns. You do not need to drain a setup frame before testing invalidation logic — assertions on `frameInvalidated` reflect only what happened during your explicit `advance()` call.

// Now test real invalidation behavior
const { frameInvalidated } = advance()
expect(frameInvalidated).toBe(false)
**Note:** tasks that compute `1 / delta` will receive `Infinity` on this initial tick and should guard against it.

// Trigger invalidation via prop change
await rerender({ someProp: 'newValue' })
The initial delta can be overridden via the third argument to `render()`:

const result = advance()
expect(result.frameInvalidated).toBe(true)
```ts
render(MyComponent, {}, { delta: 16 })
```

### fireEvent
Expand Down
5 changes: 5 additions & 0 deletions src/lib/pure.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { setup } from './setup.js'
* @typedef {{
* container: HTMLElement
* baseElement: HTMLElement
* canvas: HTMLCanvasElement
* camera: import('@threlte/core').CurrentWritable<import('three').Camera>
* scene: import('three').Scene
* context: import('@threlte/core').ThrelteContext<import('three').WebGLRenderer>
Expand All @@ -42,6 +43,7 @@ import { setup } from './setup.js'
* baseElement?: HTMLElement
* canvas?: HTMLCanvasElement
* context?: Record<string, any>
* delta?: number
* }} renderOptions
* @returns {RenderResult<C, Q>} The rendered component and bound testing functions.
*/
Expand All @@ -62,10 +64,13 @@ const render = (Component, options = {}, renderOptions = {}) => {
},
})

component.advance({ delta: renderOptions.delta ?? 0 })

const handlers = component.interactivityContext.handlers

return {
baseElement,
canvas,
camera: component.context.camera,
component: component.ref,
container,
Expand Down
9 changes: 0 additions & 9 deletions src/routes/__tests__/Invalidate.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ describe('<Invalidate>', () => {
},
})

// First advance drains setup invalidation
advance()

const { frameInvalidated } = advance()
expect(frameInvalidated).toBe(false)
})
Expand All @@ -27,8 +24,6 @@ describe('<Invalidate>', () => {
},
})

advance()

const { frameInvalidated } = advance()
expect(frameInvalidated).toBe(true)
})
Expand All @@ -41,8 +36,6 @@ describe('<Invalidate>', () => {
},
})

advance()

const { frameInvalidated } = advance()
expect(frameInvalidated).toBe(false)
})
Expand All @@ -55,8 +48,6 @@ describe('<Invalidate>', () => {
},
})

advance()

const first = advance()
expect(first.frameInvalidated).toBe(false)

Expand Down
Loading