-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(react-components): LayersButton component (#5009)
* interim commit * fix the type error * refractored Layers button to use MVVM and fixed cyclic dependency * added test file before merging master * refratored to use context * removed unnecessary memozied * lint fix * updated test file * removed unused file * removed madge package * added test for LayerButton component * added basic test for useModelHandler and iseSyncExternalLayersState hook * added test for updateViewerFromExternalState * reverted search storybook example changes * updated test files to use provider for sdk, renderTarget * added testing-library/jest-dom to test DOM element in test files * removed @testing-library/jest-dom and used existing happy-dom instead for test react component * removed setUpTest.ts entry from tsconfig * lint fix * added most of the test in layers button * removed unused element.ts file * fixed type error in test * removed unsafe type conversion in test file * addressed review comment from risk-review-team * added viewer and viewer.models as context
- Loading branch information
1 parent
9da4014
commit 2434b36
Showing
30 changed files
with
978 additions
and
136 deletions.
There are no files selected for viewing
25 changes: 25 additions & 0 deletions
25
react-components/src/components/RevealToolbar/LayersButton/LayersButton.context.tsx
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,25 @@ | ||
/*! | ||
* Copyright 2025 Cognite AS | ||
*/ | ||
import { createContext } from 'react'; | ||
import { useModelHandlers } from './hooks/useModelHandlers'; | ||
import { useSyncExternalLayersState } from './hooks/useSyncExternalLayersState'; | ||
import { ModelLayerSelection } from './components/ModelLayerSelection'; | ||
import { useReveal } from '../../RevealCanvas'; | ||
import { use3dModels } from '../../../hooks/use3dModels'; | ||
|
||
export type LayersButtonDependencies = { | ||
useModelHandlers: typeof useModelHandlers; | ||
useSyncExternalLayersState: typeof useSyncExternalLayersState; | ||
ModelLayerSelection: typeof ModelLayerSelection; | ||
useReveal: typeof useReveal; | ||
use3dModels: typeof use3dModels; | ||
}; | ||
|
||
export const LayersButtonContext = createContext<LayersButtonDependencies>({ | ||
useModelHandlers, | ||
useSyncExternalLayersState, | ||
ModelLayerSelection, | ||
useReveal, | ||
use3dModels | ||
}); |
113 changes: 113 additions & 0 deletions
113
react-components/src/components/RevealToolbar/LayersButton/LayersButton.test.tsx
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,113 @@ | ||
/*! | ||
* Copyright 2025 Cognite AS | ||
*/ | ||
import { render } from '@testing-library/react'; | ||
import { describe, expect, test, vi } from 'vitest'; | ||
import type { ReactElement, ReactNode } from 'react'; | ||
import { LayersButton } from './LayersButton'; | ||
import type { LayersButtonProps } from './LayersButton'; | ||
import { LayersButtonContext, type LayersButtonDependencies } from './LayersButton.context'; | ||
import { | ||
createCadHandlerMock, | ||
createPointCloudHandlerMock, | ||
createImage360HandlerMock | ||
} from '../../../../tests/tests-utilities/fixtures/modelHandler'; | ||
import { type ModelLayerHandlers } from './types'; | ||
import { cadMock } from '../../../../tests/tests-utilities/fixtures/cadModel'; | ||
import { viewerMock } from '../../../../tests/tests-utilities/fixtures/viewer'; | ||
|
||
describe(LayersButton.name, () => { | ||
const mockCadHandler = createCadHandlerMock(); | ||
const mockPointCloudHandler = createPointCloudHandlerMock(); | ||
const mockImage360Handler = createImage360HandlerMock(); | ||
const defaultProps: LayersButtonProps = { | ||
layersState: { | ||
cadLayers: [{ revisionId: 456, applied: true, index: 0 }], | ||
pointCloudLayers: [{ revisionId: 123, applied: true, index: 0 }], | ||
image360Layers: [{ siteId: 'site-id', applied: true }] | ||
}, | ||
setLayersState: vi.fn(), | ||
defaultLayerConfiguration: undefined | ||
}; | ||
|
||
const defaultDependencies: LayersButtonDependencies = { | ||
useModelHandlers: vi.fn((): [ModelLayerHandlers, () => void] => [ | ||
{ | ||
cadHandlers: [mockCadHandler], | ||
pointCloudHandlers: [mockPointCloudHandler], | ||
image360Handlers: [mockImage360Handler] | ||
}, | ||
vi.fn() | ||
]), | ||
useReveal: vi.fn(() => viewerMock), | ||
use3dModels: vi.fn(() => [cadMock, cadMock]), | ||
useSyncExternalLayersState: vi.fn(), | ||
ModelLayerSelection: vi.fn(({ label }) => <div>{label}</div>) | ||
}; | ||
|
||
const wrapper = (props: { | ||
children: ReactNode; | ||
dependencies?: LayersButtonDependencies; | ||
}): ReactElement => { | ||
const { children, dependencies = defaultDependencies } = props; | ||
return ( | ||
<LayersButtonContext.Provider value={dependencies}>{children}</LayersButtonContext.Provider> | ||
); | ||
}; | ||
|
||
test('renders without crashing', () => { | ||
const { getByRole } = render(<LayersButton {...defaultProps} />, { | ||
wrapper: ({ children }: { children: ReactNode }) => wrapper({ children }) | ||
}); | ||
|
||
// Validate the presence of specific UI elements | ||
expect( | ||
getByRole('button', { name: 'Filter 3D resource layers' }).className.includes('cogs-button') | ||
).toBe(true); | ||
}); | ||
|
||
test('should update viewer models visibility when layersState changes', () => { | ||
const ModelLayerSelection = vi.fn(({ label }) => <div>{label}</div>); | ||
const newProps: LayersButtonDependencies & { | ||
setLayersState: typeof defaultProps.setLayersState; | ||
} = { | ||
setLayersState: defaultProps.setLayersState, | ||
...defaultDependencies, | ||
useModelHandlers: vi.fn((): [ModelLayerHandlers, () => void] => [ | ||
{ | ||
cadHandlers: [mockCadHandler], | ||
pointCloudHandlers: [mockPointCloudHandler], | ||
image360Handlers: [mockImage360Handler] | ||
}, | ||
() => {} | ||
]), | ||
useSyncExternalLayersState: vi.fn(), | ||
ModelLayerSelection | ||
}; | ||
|
||
const { rerender } = render(<LayersButton {...defaultProps} />, { | ||
wrapper: ({ children }) => wrapper({ children, dependencies: newProps }) | ||
}); | ||
|
||
// Change layersState | ||
const newLayersState = { | ||
cadLayers: [{ revisionId: 456, applied: false, index: 0 }], | ||
pointCloudLayers: [{ revisionId: 123, applied: true, index: 0 }], | ||
image360Layers: [{ siteId: 'site-id', applied: false }] | ||
}; | ||
if (newProps.setLayersState !== undefined) { | ||
newProps.setLayersState(newLayersState); | ||
} | ||
|
||
// Re-render with the updated state | ||
rerender(<LayersButton {...defaultProps} layersState={newLayersState} />); | ||
|
||
mockCadHandler.setVisibility(newLayersState.cadLayers[0].applied); | ||
mockPointCloudHandler.setVisibility(newLayersState.pointCloudLayers[0].applied); | ||
mockImage360Handler.setVisibility(newLayersState.image360Layers[0].applied); | ||
|
||
expect(mockCadHandler.visible()).toBe(false); | ||
expect(mockPointCloudHandler.visible()).toBe(true); | ||
expect(mockImage360Handler.visible()).toBe(false); | ||
}); | ||
}); |
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
59 changes: 59 additions & 0 deletions
59
react-components/src/components/RevealToolbar/LayersButton/LayersButton.viewmodel.tsx
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,59 @@ | ||
/*! | ||
* Copyright 2025 Cognite AS | ||
*/ | ||
import { type ReactElement, useContext, type Dispatch, type SetStateAction } from 'react'; | ||
import { LayersButtonContext } from './LayersButton.context'; | ||
import { | ||
type ModelLayerHandlers, | ||
type DefaultLayersConfiguration, | ||
type LayersUrlStateParam | ||
} from './types'; | ||
import { type ModelHandler } from './ModelHandler'; | ||
|
||
type UpdateCallback = () => void; | ||
|
||
type ModelLayerSelectionProps = { | ||
label: string; | ||
modelLayerHandlers: ModelHandler[]; | ||
update: UpdateCallback; | ||
}; | ||
|
||
type UseLayersButtonViewModelResult = { | ||
modelLayerHandlers: ModelLayerHandlers; | ||
updateCallback: UpdateCallback; | ||
ModelLayerSelection: (props: ModelLayerSelectionProps) => ReactElement; | ||
}; | ||
|
||
export function useLayersButtonViewModel( | ||
setExternalLayersState: Dispatch<SetStateAction<LayersUrlStateParam | undefined>> | undefined, | ||
defaultLayerConfiguration: DefaultLayersConfiguration | undefined, | ||
externalLayersState: LayersUrlStateParam | undefined | ||
): UseLayersButtonViewModelResult { | ||
const { | ||
useModelHandlers, | ||
useSyncExternalLayersState, | ||
ModelLayerSelection, | ||
use3dModels, | ||
useReveal | ||
} = useContext(LayersButtonContext); | ||
|
||
const [modelLayerHandlers, update] = useModelHandlers( | ||
setExternalLayersState, | ||
defaultLayerConfiguration, | ||
useReveal(), | ||
use3dModels() | ||
); | ||
|
||
useSyncExternalLayersState( | ||
modelLayerHandlers, | ||
externalLayersState, | ||
setExternalLayersState, | ||
update | ||
); | ||
|
||
return { | ||
modelLayerHandlers, | ||
updateCallback: update, | ||
ModelLayerSelection | ||
}; | ||
} |
Oops, something went wrong.