Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
f4d310b
test(renderer): failing repro for foreign Material attach (isMaterial…
bigmistqke May 26, 2026
2ee594a
fix(create-three): drop mergeProps when calling useSceneGraph
bigmistqke May 26, 2026
5d27743
test(hooks): re-enable useLoader integration tests under Suspense
bigmistqke May 26, 2026
b78b048
test(use-loader-suspense): assert scene contents during pending and a…
bigmistqke May 26, 2026
068b8a4
fix(props): duck-type Material/Object3D/Fog/BufferGeometry attach checks
bigmistqke May 26, 2026
addab6f
refactor(create-three): use createResource for renderer init
bigmistqke May 26, 2026
3c488ce
refactor(utils): consolidate isRenderer and isWritable into utils.ts
bigmistqke May 26, 2026
dbbe477
test: cover createEntity, meta/getMeta/hasMeta, useProps, load, Resou…
bigmistqke May 26, 2026
4ac1ac7
fix(Resource): tag loaded resource with meta so parent attach works
bigmistqke May 26, 2026
33cb357
docs(spec): port-to-next-solid-2 design + analysis
bigmistqke May 26, 2026
3a384c0
docs(plan): port-next-to-next-solid-2 implementation plan
bigmistqke May 26, 2026
54e94ab
fix(xr): support WebGPURenderer XR by driving the loop from the renderer
bigmistqke May 26, 2026
b0833c7
fix(canvas): drop non-null assertions on ref bindings
bigmistqke May 26, 2026
bb2e29c
chore(test): add vitest browser-mode config alongside jsdom
bigmistqke May 26, 2026
890e983
chore(test): consolidate vitest configs into projects, add jsdom cont…
bigmistqke May 26, 2026
4270ea2
ci: install Playwright Chromium before running tests
bigmistqke May 26, 2026
f835cf1
docs(plan): correct Task 6/12/14 for Solid 2.x API delta
bigmistqke May 26, 2026
c33c062
test: switch fully to real-browser testing, drop jsdom
bigmistqke May 26, 2026
0bed88f
fix(create-three): swap canvas element when recreating default WebGLR…
bigmistqke May 26, 2026
4f61a89
refactor(canvas): replace gl tuple form with flat r3f-style object + …
bigmistqke May 26, 2026
ece7abb
docs: document the new flat gl prop and warn behavior
bigmistqke May 26, 2026
51e85bc
docs(canvas): update gl prop JSDoc to match the flat-form API
bigmistqke May 26, 2026
56d8d04
fix(test): use playwright(launchOptions) instead of invalid instance.…
bigmistqke May 26, 2026
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
3 changes: 3 additions & 0 deletions .github/workflows/pkg-pr-new.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ jobs:
- name: Install dependencies
run: pnpm install

- name: Install Playwright Chromium
run: pnpm exec playwright install --with-deps chromium

- name: Typecheck
run: pnpm lint:types

Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ types
node_modules
demo/node_modules
packed/
.vitest-attachments/
tests/**/__screenshots__/
.claude/worktrees/
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ The `Canvas` component initializes the `three.js` rendering context and acts as

- **camera**: Configures the camera used in the scene. Can be partial props for a camera or an existing Camera instance.
- **fallback**: Element to render while the main content is loading asynchronously.
- **gl**: Defines options for the default WebGLRenderer, a factory returning any renderer (`WebGLRenderer`, `WebGPURenderer`, `SVGRenderer`, `CSS2D/3DRenderer`, custom), or a pre-built renderer instance. See [Custom renderers](#custom-renderers) for narrowing the accepted renderer type project-wide.
- **gl**: A flat object mixing `WebGLRenderer` constructor params (e.g. `antialias`, `alpha`) and instance-writable props (e.g. `toneMapping`) — solid-three splits them internally; ctor args are baked at construction, instance props stay reactive. Reactively changing a ctor-only key logs a warning (WebGL contexts are immutable; remount `<Canvas>` to swap). Also accepts a factory returning any renderer (`WebGLRenderer`, `WebGPURenderer`, `SVGRenderer`, `CSS2D/3DRenderer`, custom), or a pre-built renderer instance. See [Custom renderers](#custom-renderers) for narrowing the accepted renderer type project-wide.
- **scene**: Provides custom settings for the Scene instance or an existing Scene.
- **raycaster**: Configures the Raycaster for mouse and pointer events.
- **shadows**: Enables and configures shadows in the scene with various shadow mapping techniques.
Expand All @@ -126,7 +126,7 @@ interface CanvasProps {
camera?: Partial<PerspectiveCamera | OrthographicCamera> | Camera
fallback?: JSX.Element
gl?:
| Partial<WebGLRenderer>
| Partial<WebGLRenderer & WebGLRendererParameters>
| ((canvas: HTMLCanvasElement) => ResolvedRenderer)
| ResolvedRenderer
scene?: Partial<Scene> | Scene
Expand Down Expand Up @@ -221,7 +221,7 @@ function Scene() {
<Canvas gl={canvas => new WebGPURenderer({ canvas })}> {/* ✓ */}
<Canvas gl={canvas => new WebGLRenderer({ canvas })}> {/* ✗ type error */}
<Canvas gl={{ toneMapping: ACESFilmicToneMapping }}> {/* ✗ type error —
the config shorthand
the flat-object form
only builds a default
WebGLRenderer */}
```
Expand Down
115 changes: 58 additions & 57 deletions TESTING.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,31 @@
## Testing

`solid-three` provides a comprehensive testing framework for unit testing 3D components. The testing utilities are available as a separate export.
`solid-three` ships a small testing API (`solid-three/testing`) that mounts a real `<canvas>` and creates a `solid-three` root against it. It is **browser-only** — run your tests in a real browser (e.g. [vitest browser mode](https://vitest.dev/guide/browser/) with Playwright + Chromium). jsdom is not supported.

### Setup and Basic Testing

```tsx
import { test, TestCanvas } from "solid-three/testing"
import { test, TestCanvas, cleanup } from "solid-three/testing"
import { render } from "@solidjs/testing-library"
import { afterEach } from "vitest"

test("renders a mesh", () => {
const { canvas, scene, unmount, waitTillNextFrame } = test(() => (
// Browsers cap concurrent WebGL contexts (~16 in Chromium). Wire `cleanup()`
// into `afterEach` so each test frees its renderer.
afterEach(() => cleanup())

it("renders a mesh", () => {
const scene = test(() => (
<T.Mesh>
<T.BoxGeometry />
<T.MeshBasicMaterial />
</T.Mesh>
))

expect(scene.children).toHaveLength(1)
expect(scene.children[0]).toBeDefined()

// Clean up
unmount()
expect(scene.scene.children).toHaveLength(1)
})

// Using TestCanvas for JSX-based testing
test("renders with TestCanvas", () => {
// Or use TestCanvas as JSX
it("renders with TestCanvas", () => {
render(() => (
<TestCanvas camera={{ position: [0, 0, 5] }}>
<T.Mesh>
Expand All @@ -33,45 +34,28 @@ test("renders with TestCanvas", () => {
</T.Mesh>
</TestCanvas>
))

// TestCanvas automatically handles the canvas setup
})
```

### Mock WebGL Context

The testing framework includes a mock WebGL2RenderingContext for environments without GPU support:

```tsx
import { WebGL2RenderingContext } from "solid-three/testing"

// Automatically used when real WebGL is unavailable
// Provides all WebGL methods as no-ops for testing
```

### Testing Events

Dispatch real `MouseEvent`/`PointerEvent`s. The test canvas is mounted at the
top-left of the document body, so `clientX`/`clientY` map 1:1 to canvas
`offsetX`/`offsetY` (which `CursorRaycaster` reads).

```tsx
import { fireEvent } from "@solidjs/testing-library"
import { test } from "solid-three/testing"

test("handles click events", () => {
it("handles click events", () => {
let clicked = false

const { canvas } = test(() => (
<T.Mesh onClick={() => (clicked = true)}>
<T.BoxGeometry />
<T.BoxGeometry args={[2, 2]} />
<T.MeshBasicMaterial />
</T.Mesh>
))

// Create a mock click event on the canvas
const clickEvent = new MouseEvent("click")
Object.defineProperty(clickEvent, "offsetX", { get: () => 640 })
Object.defineProperty(clickEvent, "offsetY", { get: () => 400 })

fireEvent(canvas, clickEvent)

fireEvent(canvas, new MouseEvent("click", { clientX: 640, clientY: 400, bubbles: true }))
expect(clicked).toBe(true)
})
```
Expand All @@ -82,26 +66,18 @@ test("handles click events", () => {
import { test } from "solid-three/testing"
import { useThree } from "solid-three"

test("useThree returns context", () => {
it("useThree returns context", () => {
let context

const TestComponent = () => {
context = useThree()
return (
<T.Mesh>
<T.BoxGeometry />
<T.MeshBasicMaterial />
</T.Mesh>
)
return <T.Mesh><T.BoxGeometry /><T.MeshBasicMaterial /></T.Mesh>
}

const { unmount } = test(() => <TestComponent />)
test(() => <TestComponent />)

expect(context.camera).toBeDefined()
expect(context.gl).toBeDefined()
expect(context.scene).toBeDefined()

unmount()
})
```

Expand All @@ -111,22 +87,47 @@ test("useThree returns context", () => {
import { test } from "solid-three/testing"
import { useFrame } from "solid-three"

test("animates on frame", async () => {
it("animates on frame", async () => {
let rotation = 0

const AnimatedBox = () => {
useFrame(() => {
rotation += 0.01
})

useFrame(() => { rotation += 0.01 })
return <T.Mesh />
}

const { waitTillNextFrame } = test(() => <AnimatedBox />, { frameloop: "always" })

// Wait for animation frame using test utility
await waitTillNextFrame()

const scene = test(() => <AnimatedBox />, { frameloop: "always" })
await scene.waitTillNextFrame()
expect(rotation).toBeGreaterThan(0)
})
```

### Recommended vitest browser config

This repo's own `vitest.config.ts` is a good reference. Key bits:

```ts
import { playwright } from "@vitest/browser-playwright"

export default defineConfig({
test: {
setupFiles: ["./tests/setup.ts"],
// Real WebGL contexts are GPU-process-limited (~16 in Chromium).
// Running test files in parallel exhausts the cap and hangs the browser.
fileParallelism: false,
browser: {
enabled: true,
provider: playwright(),
headless: true,
instances: [
{
browser: "chromium",
// SwiftShader = software WebGL. Removes Chromium's GPU-process
// context cap so renderer-heavy suites don't crash mid-run.
launch: {
args: ["--use-gl=swiftshader", "--enable-unsafe-swiftshader"],
},
},
],
},
},
})
```
Loading
Loading