Files
ohif-viewer/extensions/cornerstone/src/utils/toggleVOISliceSync.test.ts
T

607 lines
16 KiB
TypeScript

import toggleVOISliceSync from './toggleVOISliceSync';
describe('toggleVOISliceSync', () => {
const mockSyncGroupService = {
getSynchronizersForViewport: jest.fn(),
addViewportToSyncGroup: jest.fn(),
removeViewportFromSyncGroup: jest.fn(),
};
const mockViewportGridService = {
getState: jest.fn(),
};
const mockDisplaySetService = {
getDisplaySetByUID: jest.fn(),
};
const mockCornerstoneViewportService = {
getCornerstoneViewport: jest.fn(),
};
const mockServicesManager = {
services: {
syncGroupService: mockSyncGroupService,
viewportGridService: mockViewportGridService,
displaySetService: mockDisplaySetService,
cornerstoneViewportService: mockCornerstoneViewportService,
},
};
const mockViewport = {
id: 'viewport-1',
getRenderingEngine: jest.fn(() => ({ id: 'rendering-engine-1' })),
};
const mockViewport2 = {
id: 'viewport-2',
getRenderingEngine: jest.fn(() => ({ id: 'rendering-engine-2' })),
};
const mockGridViewport = {
viewportOptions: {
viewportId: 'viewport-1',
},
displaySetInstanceUIDs: ['displaySet-1'],
};
const mockDisplaySet = {
Modality: 'CT',
};
const defaultParameters = {
servicesManager: mockServicesManager as unknown as AppTypes.ServicesManager,
viewports: null,
syncId: null,
};
beforeEach(() => {
jest.clearAllMocks();
mockCornerstoneViewportService.getCornerstoneViewport.mockReturnValue(mockViewport);
mockDisplaySetService.getDisplaySetByUID.mockReturnValue(mockDisplaySet);
});
it('should enable sync when no sync exists for single modality', () => {
const viewports = {
CT: [mockGridViewport],
};
mockSyncGroupService.getSynchronizersForViewport.mockReturnValue([]);
toggleVOISliceSync({
...defaultParameters,
viewports,
});
expect(mockSyncGroupService.getSynchronizersForViewport).toHaveBeenCalledWith('viewport-1');
expect(mockSyncGroupService.addViewportToSyncGroup).toHaveBeenCalledWith(
'viewport-1',
'rendering-engine-1',
{
type: 'voi',
id: 'VOI_SYNC_CT',
source: true,
target: true,
}
);
});
it('should disable sync when sync already exists', () => {
const viewports = {
CT: [mockGridViewport],
};
const mockSyncState = {
id: 'VOI_SYNC_CT',
};
mockSyncGroupService.getSynchronizersForViewport.mockReturnValue([mockSyncState]);
toggleVOISliceSync({
...defaultParameters,
viewports,
});
expect(mockSyncGroupService.removeViewportFromSyncGroup).toHaveBeenCalledWith(
'viewport-1',
'rendering-engine-1',
'VOI_SYNC_CT'
);
expect(mockSyncGroupService.addViewportToSyncGroup).not.toHaveBeenCalled();
});
it('should use custom syncId when provided', () => {
const viewports = {
CT: [mockGridViewport],
};
mockSyncGroupService.getSynchronizersForViewport.mockReturnValue([]);
toggleVOISliceSync({
...defaultParameters,
viewports,
syncId: 'CUSTOM_SYNC_ID',
});
expect(mockSyncGroupService.addViewportToSyncGroup).toHaveBeenCalledWith(
'viewport-1',
'rendering-engine-1',
{
type: 'voi',
id: 'CUSTOM_SYNC_ID',
source: true,
target: true,
}
);
});
it('should handle multiple viewports in same modality', () => {
const mockGridViewport2 = {
viewportOptions: {
viewportId: 'viewport-2',
},
displaySetInstanceUIDs: ['displaySet-2'],
};
const viewports = {
CT: [mockGridViewport, mockGridViewport2],
};
mockCornerstoneViewportService.getCornerstoneViewport
.mockReturnValueOnce(mockViewport)
.mockReturnValueOnce(mockViewport2);
mockSyncGroupService.getSynchronizersForViewport.mockReturnValue([]);
toggleVOISliceSync({
...defaultParameters,
viewports,
});
expect(mockSyncGroupService.addViewportToSyncGroup).toHaveBeenCalledTimes(2);
expect(mockSyncGroupService.addViewportToSyncGroup).toHaveBeenNthCalledWith(
1,
'viewport-1',
'rendering-engine-1',
{
type: 'voi',
id: 'VOI_SYNC_CT',
source: true,
target: true,
}
);
expect(mockSyncGroupService.addViewportToSyncGroup).toHaveBeenNthCalledWith(
2,
'viewport-2',
'rendering-engine-2',
{
type: 'voi',
id: 'VOI_SYNC_CT',
source: true,
target: true,
}
);
});
it('should handle multiple modalities separately', () => {
const mockGridViewportMR = {
viewportOptions: {
viewportId: 'viewport-mr',
},
displaySetInstanceUIDs: ['displaySet-mr'],
};
const mockViewportMR = {
id: 'viewport-mr',
getRenderingEngine: jest.fn(() => ({ id: 'rendering-engine-mr' })),
};
const viewports = {
CT: [mockGridViewport],
MR: [mockGridViewportMR],
};
mockCornerstoneViewportService.getCornerstoneViewport
.mockReturnValueOnce(mockViewport)
.mockReturnValueOnce(mockViewportMR);
mockSyncGroupService.getSynchronizersForViewport.mockReturnValue([]);
toggleVOISliceSync({
...defaultParameters,
viewports,
});
expect(mockSyncGroupService.addViewportToSyncGroup).toHaveBeenCalledTimes(2);
expect(mockSyncGroupService.addViewportToSyncGroup).toHaveBeenNthCalledWith(
1,
'viewport-1',
'rendering-engine-1',
{
type: 'voi',
id: 'VOI_SYNC_CT',
source: true,
target: true,
}
);
expect(mockSyncGroupService.addViewportToSyncGroup).toHaveBeenNthCalledWith(
2,
'viewport-mr',
'rendering-engine-mr',
{
type: 'voi',
id: 'VOI_SYNC_MR',
source: true,
target: true,
}
);
});
it('should skip viewport when cornerstone viewport is not found', () => {
const viewports = {
CT: [mockGridViewport],
};
mockCornerstoneViewportService.getCornerstoneViewport.mockReturnValue(null);
mockSyncGroupService.getSynchronizersForViewport.mockReturnValue([]);
toggleVOISliceSync({
...defaultParameters,
viewports,
});
expect(mockSyncGroupService.addViewportToSyncGroup).not.toHaveBeenCalled();
});
it('should generate viewports from grid service when not provided', () => {
const mockViewportsMap = new Map([
[
'viewport-1',
{
...mockGridViewport,
},
],
]);
const mockState = {
viewports: mockViewportsMap,
};
mockViewportGridService.getState.mockReturnValue(mockState);
mockSyncGroupService.getSynchronizersForViewport.mockReturnValue([]);
toggleVOISliceSync({
...defaultParameters,
viewports: null,
});
expect(mockViewportGridService.getState).toHaveBeenCalled();
expect(mockDisplaySetService.getDisplaySetByUID).toHaveBeenCalledWith('displaySet-1');
expect(mockSyncGroupService.addViewportToSyncGroup).toHaveBeenCalledWith(
'viewport-1',
'rendering-engine-1',
{
type: 'voi',
id: 'VOI_SYNC_CT',
source: true,
target: true,
}
);
});
it('should handle sync state with different id not matching', () => {
const viewports = {
CT: [mockGridViewport],
};
const mockSyncState = {
id: 'DIFFERENT_SYNC_ID',
};
mockSyncGroupService.getSynchronizersForViewport.mockReturnValue([mockSyncState]);
toggleVOISliceSync({
...defaultParameters,
viewports,
});
expect(mockSyncGroupService.addViewportToSyncGroup).toHaveBeenCalledWith(
'viewport-1',
'rendering-engine-1',
{
type: 'voi',
id: 'VOI_SYNC_CT',
source: true,
target: true,
}
);
expect(mockSyncGroupService.removeViewportFromSyncGroup).not.toHaveBeenCalled();
});
it('should handle mixed sync states where some match and some do not', () => {
const mockGridViewport2 = {
viewportOptions: {
viewportId: 'viewport-2',
},
displaySetInstanceUIDs: ['displaySet-2'],
};
const viewports = {
CT: [mockGridViewport, mockGridViewport2],
};
const mockSyncState = {
id: 'VOI_SYNC_CT',
};
mockSyncGroupService.getSynchronizersForViewport
.mockReturnValueOnce([mockSyncState])
.mockReturnValueOnce([]);
mockCornerstoneViewportService.getCornerstoneViewport
.mockReturnValueOnce(mockViewport)
.mockReturnValueOnce(mockViewport2);
toggleVOISliceSync({
...defaultParameters,
viewports,
});
expect(mockSyncGroupService.removeViewportFromSyncGroup).toHaveBeenCalledWith(
'viewport-1',
'rendering-engine-1',
'VOI_SYNC_CT'
);
expect(mockSyncGroupService.removeViewportFromSyncGroup).toHaveBeenCalledWith(
'viewport-2',
'rendering-engine-2',
'VOI_SYNC_CT'
);
});
it('should disable sync for multiple viewports when sync exists', () => {
const mockGridViewport2 = {
viewportOptions: {
viewportId: 'viewport-2',
},
displaySetInstanceUIDs: ['displaySet-2'],
};
const mockViewport2 = {
id: 'viewport-2',
getRenderingEngine: jest.fn(() => ({ id: 'rendering-engine-2' })),
};
const viewports = {
CT: [mockGridViewport, mockGridViewport2],
};
const mockSyncState = {
id: 'VOI_SYNC_CT',
};
mockCornerstoneViewportService.getCornerstoneViewport
.mockReturnValueOnce(mockViewport)
.mockReturnValueOnce(mockViewport2);
mockSyncGroupService.getSynchronizersForViewport.mockReturnValue([mockSyncState]);
toggleVOISliceSync({
...defaultParameters,
viewports,
});
expect(mockSyncGroupService.removeViewportFromSyncGroup).toHaveBeenCalledTimes(2);
expect(mockSyncGroupService.removeViewportFromSyncGroup).toHaveBeenNthCalledWith(
1,
'viewport-1',
'rendering-engine-1',
'VOI_SYNC_CT'
);
expect(mockSyncGroupService.removeViewportFromSyncGroup).toHaveBeenNthCalledWith(
2,
'viewport-2',
'rendering-engine-2',
'VOI_SYNC_CT'
);
});
it('should skip viewport during disable when cornerstone viewport is not found', () => {
const mockGridViewport2 = {
viewportOptions: {
viewportId: 'viewport-2',
},
displaySetInstanceUIDs: ['displaySet-2'],
};
const viewports = {
CT: [mockGridViewport, mockGridViewport2],
};
const mockSyncState = {
id: 'VOI_SYNC_CT',
};
mockCornerstoneViewportService.getCornerstoneViewport
.mockReturnValueOnce(mockViewport)
.mockReturnValueOnce(null);
mockSyncGroupService.getSynchronizersForViewport.mockReturnValue([mockSyncState]);
toggleVOISliceSync({
...defaultParameters,
viewports,
});
expect(mockSyncGroupService.removeViewportFromSyncGroup).toHaveBeenCalledTimes(1);
expect(mockSyncGroupService.removeViewportFromSyncGroup).toHaveBeenCalledWith(
'viewport-1',
'rendering-engine-1',
'VOI_SYNC_CT'
);
});
it('should handle empty viewports object', () => {
const viewports = {};
toggleVOISliceSync({
...defaultParameters,
viewports,
});
expect(mockSyncGroupService.getSynchronizersForViewport).not.toHaveBeenCalled();
expect(mockSyncGroupService.addViewportToSyncGroup).not.toHaveBeenCalled();
expect(mockSyncGroupService.removeViewportFromSyncGroup).not.toHaveBeenCalled();
});
it('should handle modality with empty viewport array', () => {
const viewports = {
CT: [],
};
toggleVOISliceSync({
...defaultParameters,
viewports,
});
expect(mockSyncGroupService.getSynchronizersForViewport).not.toHaveBeenCalled();
expect(mockSyncGroupService.addViewportToSyncGroup).not.toHaveBeenCalled();
expect(mockSyncGroupService.removeViewportFromSyncGroup).not.toHaveBeenCalled();
});
it('should handle multiple displaySetInstanceUIDs by using first one', () => {
const mockGridViewportMultipleDisplaySets = {
viewportOptions: {
viewportId: 'viewport-1',
},
displaySetInstanceUIDs: ['displaySet-1', 'displaySet-2', 'displaySet-3'],
};
mockViewportGridService.getState.mockReturnValue({
viewports: new Map([['viewport-1', mockGridViewportMultipleDisplaySets]]),
});
mockSyncGroupService.getSynchronizersForViewport.mockReturnValue([]);
mockDisplaySetService.getDisplaySetByUID.mockReturnValue(mockDisplaySet);
toggleVOISliceSync({
...defaultParameters,
});
expect(mockDisplaySetService.getDisplaySetByUID).toHaveBeenCalledWith('displaySet-1');
expect(mockSyncGroupService.addViewportToSyncGroup).toHaveBeenCalledWith(
'viewport-1',
'rendering-engine-1',
{
type: 'voi',
id: 'VOI_SYNC_CT',
source: true,
target: true,
}
);
});
it('should group viewports by modality correctly when generating from grid', () => {
const mockViewportsMap = new Map([
[
'viewport-ct-1',
{
viewportOptions: {
viewportId: 'viewport-ct-1',
},
displaySetInstanceUIDs: ['displaySet-ct-1'],
},
],
[
'viewport-ct-2',
{
viewportOptions: {
viewportId: 'viewport-ct-2',
},
displaySetInstanceUIDs: ['displaySet-ct-2'],
},
],
[
'viewport-mr-1',
{
viewportOptions: {
viewportId: 'viewport-mr-1',
},
displaySetInstanceUIDs: ['displaySet-mr-1'],
},
],
]);
const mockState = {
viewports: mockViewportsMap,
};
const mockDisplaySetCT = { Modality: 'CT' };
const mockDisplaySetMR = { Modality: 'MR' };
const mockViewportCT1 = {
id: 'viewport-ct-1',
getRenderingEngine: jest.fn(() => ({ id: 'rendering-engine-ct-1' })),
};
const mockViewportCT2 = {
id: 'viewport-ct-2',
getRenderingEngine: jest.fn(() => ({ id: 'rendering-engine-ct-2' })),
};
const mockViewportMR1 = {
id: 'viewport-mr-1',
getRenderingEngine: jest.fn(() => ({ id: 'rendering-engine-mr-1' })),
};
mockViewportGridService.getState.mockReturnValue(mockState);
mockDisplaySetService.getDisplaySetByUID
.mockReturnValueOnce(mockDisplaySetCT)
.mockReturnValueOnce(mockDisplaySetCT)
.mockReturnValueOnce(mockDisplaySetMR);
mockCornerstoneViewportService.getCornerstoneViewport
.mockReturnValueOnce(mockViewportCT1)
.mockReturnValueOnce(mockViewportCT2)
.mockReturnValueOnce(mockViewportMR1);
mockSyncGroupService.getSynchronizersForViewport.mockReturnValue([]);
toggleVOISliceSync({
...defaultParameters,
viewports: null,
});
expect(mockSyncGroupService.addViewportToSyncGroup).toHaveBeenCalledTimes(3);
expect(mockSyncGroupService.addViewportToSyncGroup).toHaveBeenNthCalledWith(
1,
'viewport-ct-1',
'rendering-engine-ct-1',
{
type: 'voi',
id: 'VOI_SYNC_CT',
source: true,
target: true,
}
);
expect(mockSyncGroupService.addViewportToSyncGroup).toHaveBeenNthCalledWith(
2,
'viewport-ct-2',
'rendering-engine-ct-2',
{
type: 'voi',
id: 'VOI_SYNC_CT',
source: true,
target: true,
}
);
expect(mockSyncGroupService.addViewportToSyncGroup).toHaveBeenNthCalledWith(
3,
'viewport-mr-1',
'rendering-engine-mr-1',
{
type: 'voi',
id: 'VOI_SYNC_MR',
source: true,
target: true,
}
);
});
});