-
-
Notifications
You must be signed in to change notification settings - Fork 164
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(controllers): Refactored how Controllers interact w/ three (#…
…294) This a big refactor of controller and how they interact with three counterparts. The flow of events was very confusing, to say the least. This PR makes this flow much direct and does some other notable changes. --------- Co-authored-by: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com>
- Loading branch information
1 parent
d6acce1
commit 1031262
Showing
32 changed files
with
1,343 additions
and
322 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
import * as React from 'react' | ||
import { describe, it, expect, vi } from 'vitest' | ||
import { createStoreMock, createStoreProvider } from './mocks/storeMock' | ||
import { render } from './testUtils/testUtilsThree' | ||
import { Controllers } from './Controllers' | ||
import { XRControllerMock } from './mocks/XRControllerMock' | ||
import { XRControllerModel } from './XRControllerModel' | ||
import { XRControllerModelFactoryMock } from './mocks/XRControllerModelFactoryMock' | ||
import { XRInputSourceMock } from './mocks/XRInputSourceMock' | ||
import { act } from '@react-three/test-renderer' | ||
|
||
vi.mock('./XRControllerModelFactory', async () => { | ||
const { XRControllerModelFactoryMock } = await vi.importActual<typeof import('./mocks/XRControllerModelFactoryMock')>( | ||
'./mocks/XRControllerModelFactoryMock' | ||
) | ||
return { XRControllerModelFactory: XRControllerModelFactoryMock } | ||
}) | ||
|
||
describe('Controllers', () => { | ||
it('should not render anything if controllers in state are empty', async () => { | ||
const store = createStoreMock() | ||
const xrControllerMock = new XRControllerMock(0) | ||
store.setState({ controllers: [] }) | ||
|
||
const { renderer } = await render(<Controllers />, { wrapper: createStoreProvider(store) }) | ||
|
||
// We aren't rendering anything as a direct children, only in portals | ||
const graph = renderer.toGraph() | ||
expect(graph).toHaveLength(0) | ||
// Checking portals | ||
expect(xrControllerMock.grip.children).toHaveLength(0) | ||
expect(xrControllerMock.controller.children).toHaveLength(0) | ||
}) | ||
|
||
it('should render one xr controller model and one ray given one controller in state', async () => { | ||
const store = createStoreMock() | ||
const xrControllerMock = new XRControllerMock(0) | ||
store.setState({ controllers: [xrControllerMock] }) | ||
|
||
await render(<Controllers />, { wrapper: createStoreProvider(store) }) | ||
|
||
// Checking portals | ||
expect(xrControllerMock.grip.children).toHaveLength(1) | ||
expect(xrControllerMock.grip.children[0]).toBeInstanceOf(XRControllerModel) | ||
expect(xrControllerMock.controller.children).toHaveLength(1) | ||
expect(xrControllerMock.controller.children[0].type).toBe('Line') | ||
}) | ||
|
||
it('should render two xr controller models and two rays given one controller in state', async () => { | ||
const store = createStoreMock() | ||
const xrControllerMockLeft = new XRControllerMock(0) | ||
const xrControllerMockRight = new XRControllerMock(1) | ||
store.setState({ controllers: [xrControllerMockLeft, xrControllerMockRight] }) | ||
|
||
await render(<Controllers />, { wrapper: createStoreProvider(store) }) | ||
|
||
// Checking portals | ||
// left | ||
expect(xrControllerMockLeft.grip.children).toHaveLength(1) | ||
expect(xrControllerMockLeft.grip.children[0]).toBeInstanceOf(XRControllerModel) | ||
expect(xrControllerMockLeft.controller.children).toHaveLength(1) | ||
expect(xrControllerMockLeft.controller.children[0].type).toBe('Line') | ||
// right | ||
expect(xrControllerMockRight.grip.children).toHaveLength(1) | ||
expect(xrControllerMockRight.grip.children[0]).toBeInstanceOf(XRControllerModel) | ||
expect(xrControllerMockRight.controller.children).toHaveLength(1) | ||
expect(xrControllerMockRight.controller.children[0].type).toBe('Line') | ||
}) | ||
|
||
it('should remove xr controller model when controller is removed from state', async () => { | ||
const store = createStoreMock() | ||
const xrControllerMock = new XRControllerMock(0) | ||
xrControllerMock.inputSource = new XRInputSourceMock() | ||
store.setState({ controllers: [xrControllerMock] }) | ||
|
||
const { renderer } = await render(<Controllers />, { wrapper: createStoreProvider(store) }) | ||
|
||
await act(async () => { | ||
store.setState({ controllers: [] }) | ||
}) | ||
|
||
// We aren't rendering anything as a direct children, only in portals | ||
const graph = renderer.toGraph() | ||
expect(graph).toHaveLength(0) | ||
// Checking portals | ||
expect(xrControllerMock.grip.children).toHaveLength(0) | ||
expect(xrControllerMock.controller.children).toHaveLength(0) | ||
}) | ||
|
||
it('should handle xr controller model given one controller in state', async () => { | ||
const store = createStoreMock() | ||
const xrControllerMock = new XRControllerMock(0) | ||
xrControllerMock.inputSource = new XRInputSourceMock() | ||
store.setState({ controllers: [xrControllerMock] }) | ||
|
||
await render(<Controllers />, { wrapper: createStoreProvider(store) }) | ||
|
||
const xrControllerModelFactory = XRControllerModelFactoryMock.instance | ||
expect(xrControllerModelFactory).toBeDefined() | ||
expect(xrControllerMock.xrControllerModel).toBeInstanceOf(XRControllerModel) | ||
expect(xrControllerModelFactory?.initializeControllerModel).toBeCalled() | ||
}) | ||
|
||
it('should handle xr controller model when controller is removed from state', async () => { | ||
const store = createStoreMock() | ||
const xrControllerMock = new XRControllerMock(0) | ||
xrControllerMock.inputSource = new XRInputSourceMock() | ||
store.setState({ controllers: [xrControllerMock] }) | ||
|
||
await render(<Controllers />, { wrapper: createStoreProvider(store) }) | ||
|
||
const xrControllerModel = xrControllerMock.xrControllerModel | ||
const disconnectSpy = vi.spyOn(xrControllerModel!, 'disconnect') | ||
|
||
await act(async () => { | ||
store.setState({ controllers: [] }) | ||
}) | ||
|
||
const xrControllerModelFactory = XRControllerModelFactoryMock.instance | ||
expect(xrControllerModelFactory).toBeDefined() | ||
expect(xrControllerMock.xrControllerModel).toBeNull() | ||
expect(disconnectSpy).toBeCalled() | ||
}) | ||
|
||
it('should not reconnect when component is rerendered', async () => { | ||
const store = createStoreMock() | ||
const xrControllerMock = new XRControllerMock(0) | ||
xrControllerMock.inputSource = new XRInputSourceMock() | ||
store.setState({ controllers: [xrControllerMock] }) | ||
|
||
const { rerender } = await render(<Controllers />, { wrapper: createStoreProvider(store) }) | ||
|
||
const xrControllerModel = xrControllerMock.xrControllerModel | ||
const disconnectSpy = vi.spyOn(xrControllerModel!, 'disconnect') | ||
|
||
await rerender(<Controllers />) | ||
|
||
const xrControllerModelFactory = XRControllerModelFactoryMock.instance | ||
expect(xrControllerModelFactory).toBeDefined() | ||
expect(xrControllerMock.xrControllerModel).not.toBeNull() | ||
expect(disconnectSpy).not.toBeCalled() | ||
expect(xrControllerModelFactory?.initializeControllerModel).toBeCalledTimes(1) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import { describe, it, expect, vi } from 'vitest' | ||
import { render } from './testUtils/testUtilsThree' | ||
import * as React from 'react' | ||
import { createStoreMock, createStoreProvider } from './mocks/storeMock' | ||
import { InteractionManager, Interactive } from './Interactions' | ||
import { XRControllerMock } from './mocks/XRControllerMock' | ||
import { act } from '@react-three/test-renderer' | ||
import { XRInputSourceMock } from './mocks/XRInputSourceMock' | ||
import { Intersection } from '@react-three/fiber' | ||
import { Vector3 } from 'three' | ||
|
||
describe('Interactions', () => { | ||
it('should call onSelect when select event is dispatched', async () => { | ||
const store = createStoreMock() | ||
const xrControllerMock = new XRControllerMock(0) | ||
const xrInputSourceMock = new XRInputSourceMock({ handedness: 'right' }) | ||
xrControllerMock.inputSource = xrInputSourceMock | ||
const rightHoverState = new Map() | ||
store.setState({ | ||
controllers: [xrControllerMock], | ||
hoverState: { | ||
none: new Map(), | ||
left: new Map(), | ||
right: rightHoverState | ||
} | ||
}) | ||
|
||
const selectSpy = vi.fn() | ||
const { renderer } = await render( | ||
<InteractionManager> | ||
<Interactive onSelect={selectSpy}> | ||
<mesh position={[0, 0, -1]}> | ||
<planeGeometry args={[1, 1]} /> | ||
</mesh> | ||
</Interactive> | ||
</InteractionManager>, | ||
{ wrapper: createStoreProvider(store) } | ||
) | ||
|
||
const mesh = renderer.scene.findByType('Mesh').instance | ||
const interactiveGroup = renderer.scene.findByType('Group').instance | ||
expect(mesh).toBeDefined() | ||
expect(interactiveGroup).toBeDefined() | ||
const intersection: Intersection = { | ||
eventObject: mesh, | ||
distance: 1, | ||
point: new Vector3(0, 0, 0), | ||
object: mesh | ||
} | ||
|
||
rightHoverState.set(mesh, intersection) | ||
rightHoverState.set(interactiveGroup, intersection) | ||
|
||
await act(async () => { | ||
xrControllerMock.controller.dispatchEvent({ type: 'select', data: {} }) | ||
}) | ||
|
||
expect(selectSpy).toBeCalled() | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.