diff --git a/extensions/cornerstone/src/utils/promptHydrationDialog.ts b/extensions/cornerstone/src/utils/promptHydrationDialog.ts index 0a2872419..d821371f3 100644 --- a/extensions/cornerstone/src/utils/promptHydrationDialog.ts +++ b/extensions/cornerstone/src/utils/promptHydrationDialog.ts @@ -94,22 +94,30 @@ function promptHydrationDialog({ if (type === HydrationType.SEG) { // SEG needs setTimeout window.setTimeout(async () => { - const isHydrated = await hydrateCallback({ - segDisplaySet: displaySet, - viewportId, - }); - - resolve(isHydrated); + try { + const isHydrated = await hydrateCallback({ + segDisplaySet: displaySet, + viewportId, + }); + resolve(isHydrated); + } catch (error) { + reject(error); + } }, 0); } else if (type === HydrationType.RTSTRUCT) { // RT hydration - const isHydrated = await hydrateCallback({ - rtDisplaySet: displaySet, - viewportId, - servicesManager, - }); - - resolve(isHydrated); + window.setTimeout(async () => { + try { + const isHydrated = await hydrateCallback({ + rtDisplaySet: displaySet, + viewportId, + servicesManager, + }); + resolve(isHydrated); + } catch (error) { + reject(error); + } + }, 0); } else if (type === HydrationType.SR) { // SR has a different result structure const hydrationResult = await hydrateCallback(displaySet); diff --git a/platform/ui-next/src/components/SegmentationTable/SegmentationCollapsed.tsx b/platform/ui-next/src/components/SegmentationTable/SegmentationCollapsed.tsx index bd38314a6..f9eb3669c 100644 --- a/platform/ui-next/src/components/SegmentationTable/SegmentationCollapsed.tsx +++ b/platform/ui-next/src/components/SegmentationTable/SegmentationCollapsed.tsx @@ -32,12 +32,19 @@ const SegmentationCollapsedHeader = ({ children }: { children: React.ReactNode } // Dropdown menu component - specifically for dropdown menu content const SegmentationCollapsedDropdownMenu = ({ children }: { children: React.ReactNode }) => { + const { segmentationRepresentationTypes } = useSegmentationTableContext( + 'SegmentationCollapsedDropdownMenu' + ); + const dataCyTypeSuffix = segmentationRepresentationTypes?.[0] + ? `-${segmentationRepresentationTypes[0]}` + : ''; return ( diff --git a/tests/RTHydrationDisableConfirmation.spec.ts b/tests/RTHydrationDisableConfirmation.spec.ts new file mode 100644 index 000000000..d2c6c9564 --- /dev/null +++ b/tests/RTHydrationDisableConfirmation.spec.ts @@ -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 + ); +}); diff --git a/tests/pages/RightPanelPageObject.ts b/tests/pages/RightPanelPageObject.ts index 3a7def123..d41bf254d 100644 --- a/tests/pages/RightPanelPageObject.ts +++ b/tests/pages/RightPanelPageObject.ts @@ -11,6 +11,30 @@ export class RightPanelPageObject { 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) { const actionsButton = row.getByTestId('actionsMenuTrigger'); @@ -112,12 +136,14 @@ export class RightPanelPageObject { }; } - private get segmentationPanel() { + private getSegmentationPanel(typeSuffix?: string) { const page = this.page; const getSegmentByIdx = (index: number) => this.getPanelRowByIdx(index); const getSegmentByText = (text: string) => this.getPanelRowByText(text); + const moreMenu = this.getCollapsedMoreMenu(typeSuffix); return { + moreMenu, getSegmentCount: async () => { return await page.getByTestId('data-row').count(); }, @@ -135,7 +161,7 @@ export class RightPanelPageObject { get contourSegmentationPanel() { const page = this.page; const addSegmentationButton = this.addSegmentationButton; - const panel = this.segmentationPanel; + const panel = this.getSegmentationPanel('Contour'); const menuButton = page.getByTestId('panelSegmentationWithToolsContour-btn'); return { @@ -150,7 +176,7 @@ export class RightPanelPageObject { get labelMapSegmentationPanel() { const page = this.page; const addSegmentationButton = this.addSegmentationButton; - const panel = this.segmentationPanel; + const panel = this.getSegmentationPanel('Labelmap'); const menuButton = page.getByTestId('panelSegmentationWithToolsLabelMap-btn'); return { @@ -264,7 +290,7 @@ export class RightPanelPageObject { get noToolsSegmentationPanel() { const page = this.page; - const panel = this.segmentationPanel; + const panel = this.getSegmentationPanel(); const menuButton = page.getByTestId(/^panelSegmentation.*-btn$/).first(); return { diff --git a/tests/screenshots/chromium/RTHydrationDisableConfirmation.spec.ts/firstLoadPostHydration.png b/tests/screenshots/chromium/RTHydrationDisableConfirmation.spec.ts/firstLoadPostHydration.png new file mode 100644 index 000000000..07f0c4f5f Binary files /dev/null and b/tests/screenshots/chromium/RTHydrationDisableConfirmation.spec.ts/firstLoadPostHydration.png differ diff --git a/tests/screenshots/chromium/RTHydrationDisableConfirmation.spec.ts/secondLoadPostHydration.png b/tests/screenshots/chromium/RTHydrationDisableConfirmation.spec.ts/secondLoadPostHydration.png new file mode 100644 index 000000000..5b8e51197 Binary files /dev/null and b/tests/screenshots/chromium/RTHydrationDisableConfirmation.spec.ts/secondLoadPostHydration.png differ diff --git a/tests/screenshots/chromium/RTHydrationDisableConfirmation.spec.ts/viewportAfterFirstDelete.png b/tests/screenshots/chromium/RTHydrationDisableConfirmation.spec.ts/viewportAfterFirstDelete.png new file mode 100644 index 000000000..c9ea47000 Binary files /dev/null and b/tests/screenshots/chromium/RTHydrationDisableConfirmation.spec.ts/viewportAfterFirstDelete.png differ diff --git a/tests/screenshots/chromium/RTHydrationDisableConfirmation.spec.ts/viewportAfterSecondDelete.png b/tests/screenshots/chromium/RTHydrationDisableConfirmation.spec.ts/viewportAfterSecondDelete.png new file mode 100644 index 000000000..6f1eae8fe Binary files /dev/null and b/tests/screenshots/chromium/RTHydrationDisableConfirmation.spec.ts/viewportAfterSecondDelete.png differ diff --git a/tests/utils/OHIFConfiguration.ts b/tests/utils/OHIFConfiguration.ts new file mode 100644 index 000000000..429e40f96 --- /dev/null +++ b/tests/utils/OHIFConfiguration.ts @@ -0,0 +1,19 @@ +import { Page } from 'playwright-test-coverage'; + +export async function addOHIFConfiguration(page: Page, configToAdd: Record) { + await page.addInitScript(config => { + let _config; + Object.defineProperty(window, 'config', { + get() { + return _config; + }, + set(value) { + _config = { + ...value, + ...config, + }; + }, + configurable: true, + }); + }, configToAdd); +} diff --git a/tests/utils/index.ts b/tests/utils/index.ts index 50b6ba1c3..7ed55bade 100644 --- a/tests/utils/index.ts +++ b/tests/utils/index.ts @@ -1,4 +1,5 @@ import { visitStudy } from './visitStudy'; +import { addOHIFConfiguration } from './OHIFConfiguration'; import { checkForScreenshot } from './checkForScreenshot'; import { screenShotPaths } from './screenShotPaths'; import { @@ -23,6 +24,7 @@ import { subscribeToMeasurementAdded } from './subscribeToMeasurement'; export { visitStudy, + addOHIFConfiguration, checkForScreenshot, screenShotPaths, simulateClicksOnElement, diff --git a/tests/utils/screenShotPaths.ts b/tests/utils/screenShotPaths.ts index 1de6fbeb4..2946b73d6 100644 --- a/tests/utils/screenShotPaths.ts +++ b/tests/utils/screenShotPaths.ts @@ -177,6 +177,12 @@ const screenShotPaths = { rtPostHydration: 'rtPostHydration.png', rtPreHydration: 'rtPreHydration.png', }, + rtHydrationDisableConfirmation: { + firstLoadPostHydration: 'firstLoadPostHydration.png', + viewportAfterFirstDelete: 'viewportAfterFirstDelete.png', + secondLoadPostHydration: 'secondLoadPostHydration.png', + viewportAfterSecondDelete: 'viewportAfterSecondDelete.png', + }, crosshairs: { crosshairsRendered: 'crosshairsRendered.png', crosshairsRotated: 'crosshairsRotated.png',