Skip to content

Commit

Permalink
feat(3D Knowledge Graph): improve useStore usage and unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
haweston authored and mumanity committed May 26, 2023
1 parent e016ff7 commit f10ffb2
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 94 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import str2ab from 'string-to-arraybuffer';
import flushPromises from 'flush-promises';
import { Object3D, Event, Mesh, MeshBasicMaterial, Color } from 'three';

import { SceneComposerInternal, SceneComposerApi, useSceneComposerApi, COMPOSER_FEATURES, setFeatureConfig } from '..';
import { SceneComposerInternal, useSceneComposerApi, SceneComposerApi, COMPOSER_FEATURES, setFeatureConfig } from '..';
import { testScenes } from '../../tests/testData';
import { useStore } from '../store';

jest.mock('../layouts/StaticLayout', () => ({
StaticLayout: 'StaticLayout',
Expand All @@ -30,55 +31,7 @@ const blueColor = new Color('blue');
const mesh = new Mesh(undefined, new MeshBasicMaterial({ color: redColor }));
object3D.children.push(mesh);

const nodeMap = [
{
name: 'Water Tank',
ref: 'waterTankRef',
components: [
{
ref: 'bindComponentRef',
type: 'DataBinding',
valueDataBinding: {
dataBindingContext: {
entityId: 'WaterTank',
},
},
},
],
},
{
name: 'FlowMeter',
ref: 'flowMeterRef',
components: [
{
ref: 'fakeComponent',
type: 'Camera',
valueDataBinding: {},
},
],
},
];

jest.mock('../store', () => {
const originalModule = jest.requireActual('../store');
return {
...originalModule,
useStore: jest.fn(() => {
return {
...originalModule.useStore(),
getState: jest.fn(() => {
return {
...originalModule.useStore().getState(),
document: {
nodeMap: nodeMap,
},
getObject3DBySceneNodeRef: jest.fn(() => object3D),
};
}),
};
}),
};
});
const sceneComposerId = 'test';

function createSceneLoaderMock(sceneContent: string) {
return {
Expand All @@ -98,20 +51,20 @@ describe('SceneComposerInternal', () => {
it('should return an api object', async () => {
let sut: SceneComposerApi | null = null;

const TestComponent = () => {
const sceneComposerId = 'test';
sut = useSceneComposerApi('test');

return (
<SceneComposerInternal
sceneComposerId={sceneComposerId}
config={{ mode: 'Editing' }}
sceneLoader={createSceneLoaderMock(testScenes.scene1)}
/>
);
};

await act(async () => {
const TestComponent = () => {
const sceneComposerId = 'test';
sut = useSceneComposerApi('test');

return (
<SceneComposerInternal
sceneComposerId={sceneComposerId}
config={{ mode: 'Editing' }}
sceneLoader={createSceneLoaderMock(testScenes.scene1)}
/>
);
};

renderer.create(<TestComponent />);

await flushPromises();
Expand All @@ -122,8 +75,6 @@ describe('SceneComposerInternal', () => {

it('should highlight and clear a scene node', async () => {
const TestComponent = () => {
const sceneComposerId = 'test';

return (
<SceneComposerInternal
sceneComposerId={sceneComposerId}
Expand All @@ -139,6 +90,13 @@ describe('SceneComposerInternal', () => {
await flushPromises();
});

//mocking after the scene loads so this doesn't get overwritten
act(() => {
useStore(sceneComposerId).setState({
getObject3DBySceneNodeRef: () => object3D,
});
});

const composerApi = renderHook(() => useSceneComposerApi('test')).result.current;

act(() => {
Expand Down
32 changes: 18 additions & 14 deletions packages/scene-composer/src/components/SceneComposerInternal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,28 +60,32 @@ export const SceneComposerInternal: React.FC<SceneComposerInternalProps> = ({
};

export function useSceneComposerApi(sceneComposerId: string) {
const store = useStore(sceneComposerId);
const state = store.getState(); //This should likely be a useEffect updated by store instead!
const document = useStore(sceneComposerId)((state) => state.document);
const findSceneNodeRefBy = useStore(sceneComposerId)((state) => state.findSceneNodeRefBy);
const setCameraTarget = useStore(sceneComposerId)((state) => state.setCameraTarget);
const getSceneNodeByRef = useStore(sceneComposerId)((state) => state.getSceneNodeByRef);
const selectedSceneNodeRef = useStore(sceneComposerId)((state) => state.selectedSceneNodeRef);
const setSelectedSceneNodeRef = useStore(sceneComposerId)((state) => state.setSelectedSceneNodeRef);
const getObject3DBySceneNodeRef = useStore(sceneComposerId)((state) => state.getObject3DBySceneNodeRef);
const [materialMaps, dispatch] = useReducer(materialReducer, initialMaterialMaps);

const highlights = useCallback(
(decorations: StyleTarget[]) => {
const bindingComponentTypeFilter = [KnownComponentType.DataBinding];
const nodeList = Object.values(store.getState().document.nodeMap);
const nodeList = Object.values(document.nodeMap);
decorations.forEach((styleTarget) => {
nodeList.forEach((node) => {
const bindingComponent = node.components.find((component) => {
if (bindingComponentTypeFilter.includes(component.type as KnownComponentType)) {
const dataBoundComponent = component as IDataBoundSceneComponentInternal;
//TODO this should get changed to not be an array soon
const boundContext = dataBoundComponent?.valueDataBinding?.dataBindingContext;
return containsMatchingEntityComponent(styleTarget.dataBindingContext, boundContext);
} else {
return false;
}
});
if (bindingComponent) {
const object3D = store.getState().getObject3DBySceneNodeRef(node.ref);
const object3D = getObject3DBySceneNodeRef(node.ref);
if (object3D) {
object3D?.traverse((o) => {
const material = createMaterialFromStyle(o, styleTarget.style);
Expand All @@ -96,13 +100,13 @@ export function useSceneComposerApi(sceneComposerId: string) {
});
});
},
[store, dispatch, materialMaps],
[document, getObject3DBySceneNodeRef, dispatch, materialMaps],
);

const clearHighlights = useCallback(
(dataBindingContexts: unknown[]) => {
const bindingComponentTypeFilter = [KnownComponentType.DataBinding];
const nodeList = Object.values(store.getState().document.nodeMap);
const nodeList = Object.values(document.nodeMap);
dataBindingContexts.forEach((dataBindingContext) => {
nodeList.forEach((node) => {
const bindingComponent = node.components.find((component) => {
Expand All @@ -115,7 +119,7 @@ export function useSceneComposerApi(sceneComposerId: string) {
}
});
if (bindingComponent) {
const object3D = store.getState().getObject3DBySceneNodeRef(node.ref);
const object3D = getObject3DBySceneNodeRef(node.ref);
if (object3D) {
object3D?.traverse((o) => {
removeMaterial(o, 'highlights', materialMaps, dispatch);
Expand All @@ -125,15 +129,15 @@ export function useSceneComposerApi(sceneComposerId: string) {
});
});
},
[[store, dispatch, materialMaps]],
[[document, getObject3DBySceneNodeRef, dispatch, materialMaps]],
);

return {
findSceneNodeRefBy: state.findSceneNodeRefBy,
setCameraTarget: state.setCameraTarget,
getSceneNodeByRef: (ref: string) => cloneDeep(state.getSceneNodeByRef(ref)),
getSelectedSceneNodeRef: () => store.getState().selectedSceneNodeRef,
setSelectedSceneNodeRef: state.setSelectedSceneNodeRef,
findSceneNodeRefBy: findSceneNodeRefBy,
setCameraTarget: setCameraTarget,
getSceneNodeByRef: (ref: string) => cloneDeep(getSceneNodeByRef(ref)),
getSelectedSceneNodeRef: () => selectedSceneNodeRef,
setSelectedSceneNodeRef: setSelectedSceneNodeRef,
highlights: highlights,
clearHighlights: clearHighlights,
};
Expand Down
16 changes: 1 addition & 15 deletions packages/scene-composer/src/reducers/materialReducer.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useReducer } from 'react';
import { act, cleanup, renderHook } from '@testing-library/react-hooks';
import { act, renderHook } from '@testing-library/react-hooks';
import { Mesh, MeshBasicMaterial, Color } from 'three';

import {
Expand All @@ -25,8 +25,6 @@ describe('materialReducer', () => {
});
const backUpColor = materialMaps.original[mesh.uuid].color;
expect(backUpColor.getHex()).toBe(originalColor.getHex());

cleanup();
});

it('should apply a color with rules overwriting highlights', () => {
Expand All @@ -50,8 +48,6 @@ describe('materialReducer', () => {
addMaterial(mesh, newMaterial2, 'rules', materialMaps, dispatch);
});
expect(mesh.material.color.getHex()).toBe(secondTransformColor.getHex());

cleanup();
});

it('should apply a color with highlight not overwriting rules', () => {
Expand All @@ -75,8 +71,6 @@ describe('materialReducer', () => {
addMaterial(mesh, newMaterial2, 'highlights', materialMaps, dispatch);
});
expect(mesh.material.color.getHex()).toBe(transformedColor.getHex());

cleanup();
});

it('should remove colors with highlights returning when rules are removed', () => {
Expand Down Expand Up @@ -105,8 +99,6 @@ describe('materialReducer', () => {
removeMaterial(mesh, 'rules', materialMaps, dispatch);
});
expect(mesh.material.color.getHex()).toBe(transformedColor.getHex());

cleanup();
});

it('should remove highlight colors with original being restored', () => {
Expand All @@ -128,8 +120,6 @@ describe('materialReducer', () => {
removeMaterial(mesh, 'highlights', materialMaps, dispatch);
});
expect(mesh.material.color.getHex()).toBe(originalColor.getHex());

cleanup();
});

it('should remove rules colors with original being restored', () => {
Expand All @@ -151,8 +141,6 @@ describe('materialReducer', () => {
removeMaterial(mesh, 'rules', materialMaps, dispatch);
});
expect(mesh.material.color.getHex()).toBe(originalColor.getHex());

cleanup();
});

it('should remove submodel colors with original being restored', () => {
Expand All @@ -174,7 +162,5 @@ describe('materialReducer', () => {
removeMaterial(mesh, 'subModel', materialMaps, dispatch);
});
expect(mesh.material.color.getHex()).toBe(originalColor.getHex());

cleanup();
});
});

0 comments on commit f10ffb2

Please sign in to comment.