fix(seg hydration): auto-hydrate RT struct on second load with disableConfirmationPrompts (#5875)
This commit is contained in:
parent
0f822aaf48
commit
6f773f9972
@ -94,22 +94,30 @@ function promptHydrationDialog({
|
|||||||
if (type === HydrationType.SEG) {
|
if (type === HydrationType.SEG) {
|
||||||
// SEG needs setTimeout
|
// SEG needs setTimeout
|
||||||
window.setTimeout(async () => {
|
window.setTimeout(async () => {
|
||||||
const isHydrated = await hydrateCallback({
|
try {
|
||||||
segDisplaySet: displaySet,
|
const isHydrated = await hydrateCallback({
|
||||||
viewportId,
|
segDisplaySet: displaySet,
|
||||||
});
|
viewportId,
|
||||||
|
});
|
||||||
resolve(isHydrated);
|
resolve(isHydrated);
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
}, 0);
|
}, 0);
|
||||||
} else if (type === HydrationType.RTSTRUCT) {
|
} else if (type === HydrationType.RTSTRUCT) {
|
||||||
// RT hydration
|
// RT hydration
|
||||||
const isHydrated = await hydrateCallback({
|
window.setTimeout(async () => {
|
||||||
rtDisplaySet: displaySet,
|
try {
|
||||||
viewportId,
|
const isHydrated = await hydrateCallback({
|
||||||
servicesManager,
|
rtDisplaySet: displaySet,
|
||||||
});
|
viewportId,
|
||||||
|
servicesManager,
|
||||||
resolve(isHydrated);
|
});
|
||||||
|
resolve(isHydrated);
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
} else if (type === HydrationType.SR) {
|
} else if (type === HydrationType.SR) {
|
||||||
// SR has a different result structure
|
// SR has a different result structure
|
||||||
const hydrationResult = await hydrateCallback(displaySet);
|
const hydrationResult = await hydrateCallback(displaySet);
|
||||||
|
|||||||
@ -32,12 +32,19 @@ const SegmentationCollapsedHeader = ({ children }: { children: React.ReactNode }
|
|||||||
|
|
||||||
// Dropdown menu component - specifically for dropdown menu content
|
// Dropdown menu component - specifically for dropdown menu content
|
||||||
const SegmentationCollapsedDropdownMenu = ({ children }: { children: React.ReactNode }) => {
|
const SegmentationCollapsedDropdownMenu = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
const { segmentationRepresentationTypes } = useSegmentationTableContext(
|
||||||
|
'SegmentationCollapsedDropdownMenu'
|
||||||
|
);
|
||||||
|
const dataCyTypeSuffix = segmentationRepresentationTypes?.[0]
|
||||||
|
? `-${segmentationRepresentationTypes[0]}`
|
||||||
|
: '';
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
|
data-cy={`segmentation-collapsed-more-btn${dataCyTypeSuffix}`}
|
||||||
>
|
>
|
||||||
<Icons.More className="h-6 w-6" />
|
<Icons.More className="h-6 w-6" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
81
tests/RTHydrationDisableConfirmation.spec.ts
Normal file
81
tests/RTHydrationDisableConfirmation.spec.ts
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import {
|
||||||
|
checkForScreenshot,
|
||||||
|
expect,
|
||||||
|
screenShotPaths,
|
||||||
|
test,
|
||||||
|
visitStudy,
|
||||||
|
addOHIFConfiguration,
|
||||||
|
} from './utils';
|
||||||
|
import { press } from './utils/keyboardUtils';
|
||||||
|
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await addOHIFConfiguration(page, {
|
||||||
|
disableConfirmationPrompts: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const studyInstanceUID = '1.2.840.113619.2.290.3.3767434740.226.1600859119.501';
|
||||||
|
const mode = 'viewer';
|
||||||
|
await visitStudy(page, studyInstanceUID, mode, 2000);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should auto hydrate RT STRUCT on the second load and keep viewport stable after deleting segmentations', async ({
|
||||||
|
page,
|
||||||
|
DOMOverlayPageObject,
|
||||||
|
leftPanelPageObject,
|
||||||
|
rightPanelPageObject,
|
||||||
|
viewportPageObject,
|
||||||
|
}) => {
|
||||||
|
// First load
|
||||||
|
await leftPanelPageObject.loadSeriesByModality('RTSTRUCT');
|
||||||
|
await page.waitForTimeout(5000);
|
||||||
|
|
||||||
|
const loadBadgeCountAfterFirstLoad =
|
||||||
|
await DOMOverlayPageObject.viewport.getModalityLoadBadgeCount();
|
||||||
|
expect(loadBadgeCountAfterFirstLoad).toBe(0);
|
||||||
|
|
||||||
|
await press({ page, key: 'ArrowDown', nTimes: 12 });
|
||||||
|
|
||||||
|
await checkForScreenshot(
|
||||||
|
page,
|
||||||
|
viewportPageObject.active.pane,
|
||||||
|
screenShotPaths.rtHydrationDisableConfirmation.firstLoadPostHydration
|
||||||
|
);
|
||||||
|
|
||||||
|
await rightPanelPageObject.toggle();
|
||||||
|
|
||||||
|
await rightPanelPageObject.noToolsSegmentationPanel.panel.moreMenu.delete();
|
||||||
|
await page.waitForTimeout(2000);
|
||||||
|
|
||||||
|
await checkForScreenshot(
|
||||||
|
page,
|
||||||
|
viewportPageObject.active.pane,
|
||||||
|
screenShotPaths.rtHydrationDisableConfirmation.viewportAfterFirstDelete
|
||||||
|
);
|
||||||
|
|
||||||
|
// Second load
|
||||||
|
await leftPanelPageObject.loadSeriesByModality('RTSTRUCT');
|
||||||
|
|
||||||
|
await page.waitForTimeout(5000);
|
||||||
|
|
||||||
|
const loadBadgeCountAfterSecondLoad =
|
||||||
|
await DOMOverlayPageObject.viewport.getModalityLoadBadgeCount();
|
||||||
|
expect(loadBadgeCountAfterSecondLoad).toBe(0);
|
||||||
|
|
||||||
|
await press({ page, key: 'ArrowDown', nTimes: 12 });
|
||||||
|
|
||||||
|
await checkForScreenshot(
|
||||||
|
page,
|
||||||
|
viewportPageObject.active.pane,
|
||||||
|
screenShotPaths.rtHydrationDisableConfirmation.secondLoadPostHydration
|
||||||
|
);
|
||||||
|
|
||||||
|
await rightPanelPageObject.noToolsSegmentationPanel.panel.moreMenu.delete();
|
||||||
|
|
||||||
|
await page.waitForTimeout(2000);
|
||||||
|
|
||||||
|
await checkForScreenshot(
|
||||||
|
page,
|
||||||
|
viewportPageObject.active.pane,
|
||||||
|
screenShotPaths.rtHydrationDisableConfirmation.viewportAfterSecondDelete
|
||||||
|
);
|
||||||
|
});
|
||||||
@ -11,6 +11,30 @@ export class RightPanelPageObject {
|
|||||||
this.DOMOverlayPageObject = new DOMOverlayPageObject(page);
|
this.DOMOverlayPageObject = new DOMOverlayPageObject(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getCollapsedMoreMenu(typeSuffix?: string) {
|
||||||
|
const page = this.page;
|
||||||
|
const testId = typeSuffix
|
||||||
|
? `segmentation-collapsed-more-btn-${typeSuffix}`
|
||||||
|
: 'segmentation-collapsed-more-btn';
|
||||||
|
const button = page.getByTestId(testId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
button,
|
||||||
|
click: async () => {
|
||||||
|
await button.click();
|
||||||
|
},
|
||||||
|
delete: async () => {
|
||||||
|
await button.click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Delete' }).click();
|
||||||
|
},
|
||||||
|
rename: async (text: string) => {
|
||||||
|
await button.click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Rename' }).click();
|
||||||
|
await this.DOMOverlayPageObject.dialog.input.fillAndSave(text);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private getActionsMenu(row: Locator) {
|
private getActionsMenu(row: Locator) {
|
||||||
const actionsButton = row.getByTestId('actionsMenuTrigger');
|
const actionsButton = row.getByTestId('actionsMenuTrigger');
|
||||||
|
|
||||||
@ -112,12 +136,14 @@ export class RightPanelPageObject {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private get segmentationPanel() {
|
private getSegmentationPanel(typeSuffix?: string) {
|
||||||
const page = this.page;
|
const page = this.page;
|
||||||
const getSegmentByIdx = (index: number) => this.getPanelRowByIdx(index);
|
const getSegmentByIdx = (index: number) => this.getPanelRowByIdx(index);
|
||||||
const getSegmentByText = (text: string) => this.getPanelRowByText(text);
|
const getSegmentByText = (text: string) => this.getPanelRowByText(text);
|
||||||
|
const moreMenu = this.getCollapsedMoreMenu(typeSuffix);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
moreMenu,
|
||||||
getSegmentCount: async () => {
|
getSegmentCount: async () => {
|
||||||
return await page.getByTestId('data-row').count();
|
return await page.getByTestId('data-row').count();
|
||||||
},
|
},
|
||||||
@ -135,7 +161,7 @@ export class RightPanelPageObject {
|
|||||||
get contourSegmentationPanel() {
|
get contourSegmentationPanel() {
|
||||||
const page = this.page;
|
const page = this.page;
|
||||||
const addSegmentationButton = this.addSegmentationButton;
|
const addSegmentationButton = this.addSegmentationButton;
|
||||||
const panel = this.segmentationPanel;
|
const panel = this.getSegmentationPanel('Contour');
|
||||||
const menuButton = page.getByTestId('panelSegmentationWithToolsContour-btn');
|
const menuButton = page.getByTestId('panelSegmentationWithToolsContour-btn');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -150,7 +176,7 @@ export class RightPanelPageObject {
|
|||||||
get labelMapSegmentationPanel() {
|
get labelMapSegmentationPanel() {
|
||||||
const page = this.page;
|
const page = this.page;
|
||||||
const addSegmentationButton = this.addSegmentationButton;
|
const addSegmentationButton = this.addSegmentationButton;
|
||||||
const panel = this.segmentationPanel;
|
const panel = this.getSegmentationPanel('Labelmap');
|
||||||
const menuButton = page.getByTestId('panelSegmentationWithToolsLabelMap-btn');
|
const menuButton = page.getByTestId('panelSegmentationWithToolsLabelMap-btn');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -264,7 +290,7 @@ export class RightPanelPageObject {
|
|||||||
|
|
||||||
get noToolsSegmentationPanel() {
|
get noToolsSegmentationPanel() {
|
||||||
const page = this.page;
|
const page = this.page;
|
||||||
const panel = this.segmentationPanel;
|
const panel = this.getSegmentationPanel();
|
||||||
const menuButton = page.getByTestId(/^panelSegmentation.*-btn$/).first();
|
const menuButton = page.getByTestId(/^panelSegmentation.*-btn$/).first();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
19
tests/utils/OHIFConfiguration.ts
Normal file
19
tests/utils/OHIFConfiguration.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { Page } from 'playwright-test-coverage';
|
||||||
|
|
||||||
|
export async function addOHIFConfiguration(page: Page, configToAdd: Record<string, unknown>) {
|
||||||
|
await page.addInitScript(config => {
|
||||||
|
let _config;
|
||||||
|
Object.defineProperty(window, 'config', {
|
||||||
|
get() {
|
||||||
|
return _config;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
_config = {
|
||||||
|
...value,
|
||||||
|
...config,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
configurable: true,
|
||||||
|
});
|
||||||
|
}, configToAdd);
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import { visitStudy } from './visitStudy';
|
import { visitStudy } from './visitStudy';
|
||||||
|
import { addOHIFConfiguration } from './OHIFConfiguration';
|
||||||
import { checkForScreenshot } from './checkForScreenshot';
|
import { checkForScreenshot } from './checkForScreenshot';
|
||||||
import { screenShotPaths } from './screenShotPaths';
|
import { screenShotPaths } from './screenShotPaths';
|
||||||
import {
|
import {
|
||||||
@ -23,6 +24,7 @@ import { subscribeToMeasurementAdded } from './subscribeToMeasurement';
|
|||||||
|
|
||||||
export {
|
export {
|
||||||
visitStudy,
|
visitStudy,
|
||||||
|
addOHIFConfiguration,
|
||||||
checkForScreenshot,
|
checkForScreenshot,
|
||||||
screenShotPaths,
|
screenShotPaths,
|
||||||
simulateClicksOnElement,
|
simulateClicksOnElement,
|
||||||
|
|||||||
@ -177,6 +177,12 @@ const screenShotPaths = {
|
|||||||
rtPostHydration: 'rtPostHydration.png',
|
rtPostHydration: 'rtPostHydration.png',
|
||||||
rtPreHydration: 'rtPreHydration.png',
|
rtPreHydration: 'rtPreHydration.png',
|
||||||
},
|
},
|
||||||
|
rtHydrationDisableConfirmation: {
|
||||||
|
firstLoadPostHydration: 'firstLoadPostHydration.png',
|
||||||
|
viewportAfterFirstDelete: 'viewportAfterFirstDelete.png',
|
||||||
|
secondLoadPostHydration: 'secondLoadPostHydration.png',
|
||||||
|
viewportAfterSecondDelete: 'viewportAfterSecondDelete.png',
|
||||||
|
},
|
||||||
crosshairs: {
|
crosshairs: {
|
||||||
crosshairsRendered: 'crosshairsRendered.png',
|
crosshairsRendered: 'crosshairsRendered.png',
|
||||||
crosshairsRotated: 'crosshairsRotated.png',
|
crosshairsRotated: 'crosshairsRotated.png',
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user