import { unref } from 'vue'; import { DataOverlaysActions, FileManagementActions, FrontEndInterfaces, InteractivityActions, LayoutActions, MarkupsActions, McadActions, NavigationCubeActions, PresetsActions, ScanMovementActions, ScanOrientationActions, SlidersActions, ViewSelectionActions, } from '@3cr/types-ts'; import { currentColourPreset, previousLayout, transactionStarted, } from '@/models/scanState'; import { useViewer3cr } from '@/composables/useViewer3cr'; import { Viewer3crServiceImpl } from '@/services/viewer-3cr.service'; import { executePayload } from '@3cr/sdk-browser'; import { flushPromises } from '@vue/test-utils'; import { Mock, MockInstance } from '@vitest/spy'; import { storeToRefs } from 'pinia'; import { useViewerStore } from '@/stores/viewer.store'; describe('Viewer3cr tests', () => { const viewer3cr = useViewer3cr() as Viewer3crServiceImpl; let sendPayload: MockInstance; beforeEach(() => { vi.clearAllMocks(); sendPayload = vi.spyOn(viewer3cr, 'sendPayload').mockResolvedValue(); }); it('should create instance', () => { expect(viewer3cr).toBeTruthy(); }); describe('data overlay actions', () => { it('should toggle 2d annotation', async () => { await viewer3cr.setAnnotation2dVisibility({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.data_overlay, DataOverlaysActions.do03, expect.any(Object), ); }); it('should set size 2d annotation', async () => { await viewer3cr.setAnnotation2dSize({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.data_overlay, DataOverlaysActions.do04, expect.any(Object), ); }); it('should set colour 2d annotation', async () => { await viewer3cr.setAnnotation2dColour({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.data_overlay, DataOverlaysActions.do05, expect.any(Object), ); }); it('should set Icon annotation', async () => { await viewer3cr.setAnnotationIcon({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.data_overlay, DataOverlaysActions.do06, expect.any(Object), ); }); it('should set size 3d annotation', async () => { await viewer3cr.setAnnotation3dSize({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.data_overlay, DataOverlaysActions.do08, expect.any(Object), ); }); it('should set size 3d annotation', async () => { await viewer3cr.setAnnotation3dColour({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.data_overlay, DataOverlaysActions.do09, expect.any(Object), ); }); it('should toggle 3d annotation', async () => { await viewer3cr.setAnnotation3dVisibility({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.data_overlay, DataOverlaysActions.do11, expect.any(Object), ); }); }); describe('interactivity actions', () => { it('should set mouse input', async () => { await viewer3cr.setMouseInput({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.interactivity, InteractivityActions.in01, expect.any(Object), ); }); it('should enable/disable unity inputs', async () => { await viewer3cr.setKeyboardInput({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.interactivity, InteractivityActions.in02, expect.any(Object), ); }); }); describe('file management actions', () => { it('should set loadScan', async () => { await viewer3cr.loadScan({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.file_management, FileManagementActions.fm01, expect.any(Object), ); }); it('should set loadScanSession', async () => { await viewer3cr.loadScanSession({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.file_management, FileManagementActions.fm03, expect.any(Object), ); }); it('should set setDataOverlaySession', async () => { await viewer3cr.setDataOverlaySession({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.file_management, FileManagementActions.fm05, expect.any(Object), ); }); it('should set addDataOverlaySession', async () => { await viewer3cr.addDataOverlaySession({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.file_management, FileManagementActions.fm06, expect.any(Object), ); }); it('should set loadExternalMcadObject', async () => { await viewer3cr.loadExternalMcadObject({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.file_management, FileManagementActions.fm07, expect.any(Object), ); }); it('should set loadExternalMcadObject', async () => { await viewer3cr.loadLocalMcadObject({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.file_management, FileManagementActions.fm08, expect.any(Object), ); }); it('should set loadMcadObjects', async () => { await viewer3cr.loadMcadObjects({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.file_management, FileManagementActions.fm09, expect.any(Object), ); }); }); describe('layout actions', () => { const { defaultLayout2x2 } = storeToRefs(useViewerStore()); beforeEach(() => { previousLayout.value = defaultLayout2x2.value; }); it('should layouts', async () => { const action = LayoutActions.lo01; expect(sendPayload).not.toHaveBeenCalled(); await viewer3cr.layouts(action); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.layout, action, expect.any(Object), ); }); it('should set prevLayout', async () => { const action = LayoutActions.lo02; expect(sendPayload).not.toHaveBeenCalled(); await viewer3cr.layouts(action); expect(unref(previousLayout)).toEqual(defaultLayout2x2.value); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.layout, action, expect.any(Object), ); }); }); describe('mcad actions', () => { it('should set mcad object visibility', async () => { await viewer3cr.setMcadObjectVisibility({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.mcad, McadActions.mc03, expect.any(Object), ); }); it('should set mcad object colour', async () => { await viewer3cr.setMcadObjectColour({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.mcad, McadActions.mc04, expect.any(Object), ); }); it('should toggle mcad object movement', async () => { await viewer3cr.moveMcadObject({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.mcad, McadActions.mc05, expect.any(Object), ); }); it('should toggle rotateMcadObject', async () => { await viewer3cr.rotateMcadObject({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.mcad, McadActions.mc06, expect.any(Object), ); }); it('should toggle lockMcad', async () => { await viewer3cr.lockMcad({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.mcad, McadActions.mc09, expect.any(Object), ); }); it('should toggle groupMcad', async () => { await viewer3cr.groupMcad({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.mcad, McadActions.mc10, expect.any(Object), ); }); it('should toggle ungroupMcad', async () => { await viewer3cr.ungroupMcad({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.mcad, McadActions.mc11, expect.any(Object), ); }); it('should toggle mcad inversion', async () => { await viewer3cr.invertMcadObject({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.mcad, McadActions.mc16, expect.any(Object), ); }); it('should toggle removeMcadObject', async () => { await viewer3cr.removeMcadObject({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.mcad, McadActions.mc17, expect.any(Object), ); }); it('should toggle setMcadObjectTitle', async () => { await viewer3cr.setMcadObjectTitle({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.mcad, McadActions.mc18, expect.any(Object), ); }); }); describe('measurement actions', () => { it('should set createAnnotation2dTool', async () => { await viewer3cr.createAnnotation2dTool({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.markups, MarkupsActions.mu01, expect.any(Object), ); }); it('should set createAnnotation3dTool', async () => { await viewer3cr.createAnnotation3dTool({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.markups, MarkupsActions.mu02, expect.any(Object), ); }); it('should set createAnnotation', async () => { await viewer3cr.createAnnotation({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.markups, MarkupsActions.mu03, expect.any(Object), ); }); it('should set moveAnnotation2dTool', async () => { await viewer3cr.moveAnnotation2dTool({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.markups, MarkupsActions.mu04, expect.any(Object), ); }); it('should set moveAnnotation3dTool', async () => { await viewer3cr.moveAnnotation3dTool({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.markups, MarkupsActions.mu05, expect.any(Object), ); }); it('should set moveAnnotation', async () => { await viewer3cr.moveAnnotation({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.markups, MarkupsActions.mu06, expect.any(Object), ); }); it('should set removeAnnotation', async () => { await viewer3cr.removeAnnotation({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.markups, MarkupsActions.mu07, expect.any(Object), ); }); it('should set updateAnnotationTitle', async () => { await viewer3cr.updateAnnotationTitle({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.markups, MarkupsActions.mu08, expect.any(Object), ); }); it('should set updateAnnotationDescription', async () => { await viewer3cr.updateAnnotationDescription({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.markups, MarkupsActions.mu09, expect.any(Object), ); }); it('should set updateAnnotationActions', async () => { await viewer3cr.updateAnnotationActions({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.markups, MarkupsActions.mu10, expect.any(Object), ); }); it('should set measurement visibility', async () => { await viewer3cr.setMeasurementVisibility({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.markups, MarkupsActions.mu16, expect.any(Object), ); }); it('should toggle length tool', async () => { await viewer3cr.lengthTool({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.markups, MarkupsActions.mu20, expect.any(Object), ); }); it('should toggle polygon tool', async () => { await viewer3cr.polygonTool({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.markups, MarkupsActions.mu21, expect.any(Object), ); }); it('should toggle angle tool', async () => { await viewer3cr.angleTool({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.markups, MarkupsActions.mu22, expect.any(Object), ); }); }); describe('navigation actions', () => { it('should set nav cube position', async () => { await viewer3cr.setNavCubePositionSize({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.navigation_cube, NavigationCubeActions.nc01, expect.any(Object), ); }); it('should set nav cube visibility', async () => { await viewer3cr.setNavCubeVisibility({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.navigation_cube, NavigationCubeActions.nc02, expect.any(Object), ); }); it('should set nav cube interactivity', async () => { await viewer3cr.setNavCubeInteractivity({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.navigation_cube, NavigationCubeActions.nc03, expect.any(Object), ); }); it('should set nav cube colour/opacity', async () => { await viewer3cr.setNavCubeColourOpacity({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.navigation_cube, NavigationCubeActions.nc05, expect.any(Object), ); }); it('should set nav cube highlight colour/opacity', async () => { await viewer3cr.setNavCubeHighlightColourOpacity({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.navigation_cube, NavigationCubeActions.nc07, expect.any(Object), ); }); it('should set nav cube setNavCubeGraphics', async () => { await viewer3cr.setNavCubeGraphics({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.navigation_cube, NavigationCubeActions.nc08, expect.any(Object), ); }); }); describe('preset actions', () => { it('should set setPreset', async () => { const action = PresetsActions.pr01; const data = { asd: '123' } as any; expect(sendPayload).not.toHaveBeenCalled(); expect(unref(currentColourPreset)).not.toBe(data); await viewer3cr.setPreset(action, data); expect(unref(currentColourPreset)).not.toBe(data); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.presets, action, { message: data }, ); }); it('should set setPreset change colour', async () => { const action = PresetsActions.pr02; const data = { asd: '123' } as any; expect(sendPayload).not.toHaveBeenCalled(); expect(unref(currentColourPreset)).not.toBe(data); await viewer3cr.setPreset(action, data); expect(unref(currentColourPreset)).toStrictEqual(data); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.presets, action, { message: data }, ); }); }); describe('scan movement actions', () => { it('should scanMovementHandler', async () => { const action = ScanMovementActions.sm05; const value = 100; expect(sendPayload).not.toHaveBeenCalled(); await viewer3cr.scanMovementHandler(action, value); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.scan_movement, action, { message: { Version: expect.any(String), Value: value, }, }, ); }); it('should not scanMovementHandler transactionStarted', async () => { transactionStarted.value = true; const action = ScanMovementActions.sm05; const value = 100; expect(sendPayload).not.toHaveBeenCalled(); await viewer3cr.scanMovementHandler(action, value); expect(sendPayload).not.toHaveBeenCalledWith( FrontEndInterfaces.scan_movement, action, { message: { Version: expect.any(String), Value: value, }, }, ); }); it('should pan left', async () => { await viewer3cr.panLeft({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.scan_movement, ScanMovementActions.sm13, expect.any(Object), ); }); it('should pan right', async () => { await viewer3cr.panRight({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.scan_movement, ScanMovementActions.sm14, expect.any(Object), ); }); it('should pan up', async () => { await viewer3cr.panUp({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.scan_movement, ScanMovementActions.sm15, expect.any(Object), ); }); it('should pan down', async () => { await viewer3cr.panDown({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.scan_movement, ScanMovementActions.sm16, expect.any(Object), ); }); it('should zoom in', async () => { await viewer3cr.zoomIn({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.scan_movement, ScanMovementActions.sm17, expect.any(Object), ); }); it('should zoom out', async () => { await viewer3cr.zoomOut({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.scan_movement, ScanMovementActions.sm18, expect.any(Object), ); }); it('should pan', async () => { await viewer3cr.pan({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.scan_movement, ScanMovementActions.sm26, expect.any(Object), ); }); }); describe('scan orientation actions', () => { it('should rotateByDeg', async () => { await viewer3cr.rotateByDeg({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.scan_orientation, ScanOrientationActions.so01, expect.any(Object), ); }); it('should flip 2d view horizontally', async () => { await viewer3cr.flipHorizontally({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.scan_orientation, ScanOrientationActions.so02, expect.any(Object), ); }); it('should flip 2d view vertically', async () => { await viewer3cr.flipVertically({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.scan_orientation, ScanOrientationActions.so03, expect.any(Object), ); }); it('should invert transform', async () => { await viewer3cr.invertTransform({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.scan_orientation, ScanOrientationActions.so04, expect.any(Object), ); }); }); describe('view selection actions', () => { for (const action of Object.keys(ViewSelectionActions)) { it('should viewSelection', async () => { expect(sendPayload).not.toHaveBeenCalled(); await viewer3cr.viewSelection(action as ViewSelectionActions); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.view_selection, action, { message: { Version: expect.any(String), }, }, ); }); } it('should viewSelectionToggleView', async () => { await viewer3cr.viewSelectionToggleView({} as any); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.view_selection, ViewSelectionActions.vs07, expect.any(Object), ); }); }); describe('slider actions', () => { beforeEach(() => { transactionStarted.value = false; }); it('should sliderHandler', async () => { const action = SlidersActions.sl08; const value = 100; expect(sendPayload).not.toHaveBeenCalled(); await viewer3cr.sliderHandler(action, value); expect(sendPayload).toHaveBeenCalledWith( FrontEndInterfaces.sliders, action, { message: { Version: expect.any(String), Value: value, }, }, ); }); it('should not sliderHandler transactionStarted', async () => { transactionStarted.value = true; const action = SlidersActions.sl08; const value = 100; expect(sendPayload).not.toHaveBeenCalled(); await viewer3cr.sliderHandler(action, value); expect(sendPayload).not.toHaveBeenCalledWith( FrontEndInterfaces.sliders, action, { message: { Version: expect.any(String), Value: value, }, }, ); }); }); }); describe('Viewer3CR payloads', () => { const viewer3cr = new Viewer3crServiceImpl(); const execMockRef = executePayload as Mock; beforeEach(() => { execMockRef.mockClear(); execMockRef.mockResolvedValue(); }); it('should sendPayload', async () => { const iface = FrontEndInterfaces.sliders; const action = SlidersActions.sl08; await viewer3cr.sendPayload(iface, action, {} as any); await flushPromises(); expect(execMockRef).toHaveBeenCalledWith({ Version: '0.0.1', Interface: iface, Action: action, Message: expect.toBeTypeOrNull(String), ReturnChannel: expect.toBeTypeOrNull(String), ReturnTo: expect.toBeTypeOrNull(String), }); }); it('should sendPayload with message JSON', async () => { const iface = FrontEndInterfaces.sliders; const action = SlidersActions.sl08; const messageJSON = { Value: '123', }; await viewer3cr.sendPayload(iface, action, { message: messageJSON, } as any); await flushPromises(); expect(execMockRef).toHaveBeenCalledWith({ Version: '0.0.1', Interface: iface, Action: action, Message: JSON.stringify(messageJSON), ReturnChannel: expect.toBeTypeOrNull(String), ReturnTo: expect.toBeTypeOrNull(String), }); }); it('should sendPayload with returnChannel JSON', async () => { const iface = FrontEndInterfaces.sliders; const action = SlidersActions.sl08; const returnChannelJSON = { Value: '123', }; await viewer3cr.sendPayload(iface, action, { returnChannel: returnChannelJSON, } as any); await flushPromises(); expect(execMockRef).toHaveBeenCalledWith({ Version: '0.0.1', Interface: iface, Action: action, Message: expect.toBeTypeOrNull(String), ReturnChannel: JSON.stringify(returnChannelJSON), ReturnTo: expect.toBeTypeOrNull(String), }); }); }); describe('Viewer3CR callbacks', () => { let viewer3cr = new Viewer3crServiceImpl(); const execMockRef = executePayload as Mock; const interfaceCallback = vi.fn(); const actionCallback = vi.fn(); beforeEach(() => { execMockRef.mockClear(); execMockRef.mockResolvedValue(); interfaceCallback.mockClear(); actionCallback.mockClear(); viewer3cr = new Viewer3crServiceImpl(); }); for (const iface of Object.keys(FrontEndInterfaces)) { it(`should call interface callbacks onPayload [${iface}]`, async () => { const ifaceTyped: FrontEndInterfaces = iface as FrontEndInterfaces; const action = SlidersActions.sl08; viewer3cr.addInterfaceHandler(ifaceTyped, interfaceCallback); const payload = { Version: '0.0.1', Interface: ifaceTyped, Action: action, Message: null, ReturnChannel: null, ReturnTo: null, }; await viewer3cr.onPayload(payload); await flushPromises(); expect(interfaceCallback).toHaveBeenCalledWith( expect.toBeTypeOrNull(String), action, expect.toBeTypeOrNull(String), expect.toBeTypeOrNull(String), ); }); it(`should call interface callbacks onPayload [${iface}] and not on removal`, async () => { const ifaceTyped: FrontEndInterfaces = iface as FrontEndInterfaces; const action = SlidersActions.sl08; viewer3cr.addInterfaceHandler(ifaceTyped, interfaceCallback); const payload = { Version: '0.0.1', Interface: ifaceTyped, Action: action, Message: null, ReturnChannel: null, ReturnTo: null, }; await viewer3cr.onPayload(payload); await flushPromises(); expect(interfaceCallback).toHaveBeenCalledWith( expect.toBeTypeOrNull(String), action, expect.toBeTypeOrNull(String), expect.toBeTypeOrNull(String), ); interfaceCallback.mockClear(); viewer3cr.removeInterfaceHandler(ifaceTyped, interfaceCallback); await viewer3cr.onPayload(payload); await flushPromises(); expect(interfaceCallback).not.toHaveBeenCalledWith( expect.toBeTypeOrNull(String), action, expect.toBeTypeOrNull(String), expect.toBeTypeOrNull(String), ); }); } for (const actionSet of [ DataOverlaysActions, FileManagementActions, InteractivityActions, LayoutActions, MarkupsActions, McadActions, NavigationCubeActions, PresetsActions, ScanMovementActions, ScanOrientationActions, SlidersActions, ViewSelectionActions, ]) { for (const action of Object.keys(actionSet)) { const ifaceTyped: FrontEndInterfaces = FrontEndInterfaces.file_management; it(`should call action callbacks onPayload [${action}]`, async () => { viewer3cr.addActionHandler(action, actionCallback); const payload = { Version: '0.0.1', Interface: ifaceTyped, Action: action, Message: null, ReturnChannel: null, ReturnTo: null, }; await viewer3cr.onPayload(payload); await flushPromises(); expect(actionCallback).toHaveBeenCalledWith( expect.toBeTypeOrNull(String), expect.toBeTypeOrNull(String), expect.toBeTypeOrNull(String), ); }); it(`should call action callbacks onPayload [${action}] and not on removal`, async () => { viewer3cr.addActionHandler(action, actionCallback); const payload = { Version: '0.0.1', Interface: ifaceTyped, Action: action, Message: null, ReturnChannel: null, ReturnTo: null, }; await viewer3cr.onPayload(payload); await flushPromises(); expect(actionCallback).toHaveBeenCalledWith( expect.toBeTypeOrNull(String), expect.toBeTypeOrNull(String), expect.toBeTypeOrNull(String), ); actionCallback.mockClear(); viewer3cr.removeActionHandler(action, actionCallback); await viewer3cr.onPayload(payload); await flushPromises(); expect(actionCallback).not.toHaveBeenCalledWith( expect.toBeTypeOrNull(String), expect.toBeTypeOrNull(String), expect.toBeTypeOrNull(String), ); }); } } });