diff --git a/package.json b/package.json
index a9ed12a92e..6d1daed335 100644
--- a/package.json
+++ b/package.json
@@ -74,6 +74,7 @@
"jest": "^29.3.1",
"jest-cli": "^27.5.1",
"lint-staged": "^12.3.7",
+ "node-fetch": "2",
"prettier": "^2.6.1",
"pretty-quick": "^3.1.3",
"react": "^18.0.0",
diff --git a/packages/fiber/tests/core/renderer.test.tsx b/packages/fiber/tests/core/renderer.test.tsx
index 0ac0a83ed3..95e119de25 100644
--- a/packages/fiber/tests/core/renderer.test.tsx
+++ b/packages/fiber/tests/core/renderer.test.tsx
@@ -1,5 +1,7 @@
import * as React from 'react'
import * as THREE from 'three'
+import * as Stdlib from 'three-stdlib'
+import { TextEncoder } from 'util'
import { createCanvas } from '@react-three/test-renderer/src/createTestCanvas'
import {
@@ -11,6 +13,7 @@ import {
ReactThreeFiber,
useThree,
createPortal,
+ useLoader,
} from '../../src/index'
import { UseBoundStore } from 'zustand'
import { privateKeys, RootState } from '../../src/core/store'
@@ -1062,4 +1065,49 @@ describe('renderer', () => {
expect(store.getState().camera.top).toBe(0)
expect(store.getState().camera.bottom).toBe(0)
})
+
+ it('should load a model with GLTFLoader', async () => {
+ // 1. Create minimal GLB buffer
+ const jsonString = '{"asset":{"version":"2.0"},"scenes":[{"nodes":[0]}],"nodes":[{}]}'
+ const jsonBuffer = new TextEncoder().encode(jsonString)
+ const jsonChunkLength = jsonBuffer.length
+ const totalLength = 12 + 8 + jsonChunkLength
+ const glbBuffer = new ArrayBuffer(totalLength)
+ const dataView = new DataView(glbBuffer)
+ dataView.setUint32(0, 0x46546c67, true) // 'glTF'
+ dataView.setUint32(4, 2, true) // version
+ dataView.setUint32(8, totalLength, true) // total length
+ dataView.setUint32(12, jsonChunkLength, true) // chunk length
+ dataView.setUint32(16, 0x4e4f534a, true) // 'JSON'
+ new Uint8Array(glbBuffer).set(jsonBuffer, 20)
+
+ // 2. Mock fetch
+ const mockFetch = jest.spyOn(global, 'fetch').mockImplementation(async () => {
+ return new Response(glbBuffer)
+ })
+
+ // 3. The component that uses the loader
+ const Component = () => {
+ const gltf = useLoader(Stdlib.GLTFLoader, '/model.glb')
+ return
+ }
+
+ // 4. Render and assert
+ let scene: THREE.Scene = null!
+ await act(async () => {
+ scene = root
+ .render(
+
+
+ ,
+ )
+ .getState().scene
+ })
+
+ expect(scene.children[0]).toBeInstanceOf(THREE.Group)
+ expect(scene.children[0].name).toBe('')
+
+ // 5. Restore fetch
+ mockFetch.mockRestore()
+ })
})
diff --git a/packages/shared/setupTests.ts b/packages/shared/setupTests.ts
index 7b0e01e629..2b0d41ae15 100644
--- a/packages/shared/setupTests.ts
+++ b/packages/shared/setupTests.ts
@@ -19,3 +19,8 @@ global.console.error = (...args: any[]) => {
if (args.join('').startsWith('Warning')) return
return logError(...args)
}
+
+const { default: fetch, Request, Response } = require('node-fetch')
+global.fetch = fetch
+global.Request = Request
+global.Response = Response
diff --git a/yarn.lock b/yarn.lock
index 78c25d47de..1a472e8434 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -8074,6 +8074,13 @@ node-dir@^0.1.17:
dependencies:
minimatch "^3.0.2"
+node-fetch@2:
+ version "2.7.0"
+ resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d"
+ integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
+ dependencies:
+ whatwg-url "^5.0.0"
+
node-fetch@^2.2.0, node-fetch@^2.6.0:
version "2.6.7"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"