fix(hotkeyBindings): consolidate Escape hotkey behavior for contour drawing tools (#6104)

* fix(hotkeyBindings): consolidate Escape hotkey behavior for contour drawing tools

* PR feedback.

* PR feedback.
This commit is contained in:
Joe Boccanfuso authored and GitHub committed 2026-06-26 08:20:22 -04:00
1 parent b7c8b865dd
commit 661ecb5b2e
12 files changed
+442 -12

No files matched your search

+47
View File
@@ -0,0 +1,47 @@
import { expect, test, visitStudy } from './utils';
import { press } from './utils/keyboardUtils';
test.beforeEach(async ({ page }) => {
const studyInstanceUID = '1.3.12.2.1107.5.2.32.35162.30000015050317233592200000046';
const mode = 'segmentation';
await visitStudy(page, studyInstanceUID, mode, 2000);
});
test('should cancel an in-progress B-Spline contour segmentation via Escape', async ({
page,
rightPanelPageObject,
viewportPageObject,
}) => {
const contourPanel = rightPanelPageObject.contourSegmentationPanel;
// Create a Contour-type segmentation so the contour drawing tools become enabled.
// Creating it adds a default "Segment 1"; wait for that row before drawing.
await contourPanel.addSegmentation();
await expect(contourPanel.panel.rows).toHaveCount(1);
// Activate the Spline Contour tool, then switch to the B-Spline variant.
await contourPanel.tools.splineContour.click();
await contourPanel.tools.splineContour.selectType('bSpline');
const activeViewport = await viewportPageObject.active;
await activeViewport.clickAt([
{ x: 380, y: 299 },
{ x: 420, y: 236 },
{ x: 523, y: 232 },
]);
// Ensure the three points clicked above are rendered in the DOM before pressing Escape
await expect(activeViewport.svg('circle')).toHaveCount(3);
await press({ page, key: 'Escape' });
// Pressing Escape should cancel the in-progress B-Spline contour
await expect(activeViewport.nthAnnotation(0).locator).toBeHidden();
// Draw again to verify the contour tool is still interactive after cancellation
await activeViewport.clickAt([
{ x: 380, y: 299 },
{ x: 420, y: 236 },
{ x: 523, y: 232 },
]);
await expect(activeViewport.svg('circle')).toHaveCount(3);
});
@@ -0,0 +1,47 @@
import { expect, test, visitStudy } from './utils';
import { press } from './utils/keyboardUtils';
test.beforeEach(async ({ page }) => {
const studyInstanceUID = '1.3.12.2.1107.5.2.32.35162.30000015050317233592200000046';
const mode = 'segmentation';
await visitStudy(page, studyInstanceUID, mode, 2000);
});
test('should cancel an in-progress Catmull-Rom spline contour segmentation via Escape', async ({
page,
rightPanelPageObject,
viewportPageObject,
}) => {
const contourPanel = rightPanelPageObject.contourSegmentationPanel;
// Create a Contour-type segmentation so the contour drawing tools become enabled.
// Creating it adds a default "Segment 1"; wait for that row before drawing.
await contourPanel.addSegmentation();
await expect(contourPanel.panel.rows).toHaveCount(1);
// Activate the Spline Contour tool, then arm the Catmull-Rom spline variant.
await contourPanel.tools.splineContour.click();
await contourPanel.tools.splineContour.selectType('catmullRom');
const activeViewport = await viewportPageObject.active;
await activeViewport.clickAt([
{ x: 380, y: 299 },
{ x: 420, y: 236 },
{ x: 523, y: 232 },
]);
// Ensure the three points clicked above are rendered in the DOM before pressing Escape
await expect(activeViewport.svg('circle')).toHaveCount(3);
await press({ page, key: 'Escape' });
// Pressing Escape should cancel the in-progress Catmull-Rom spline contour
await expect(activeViewport.nthAnnotation(0).locator).toBeHidden();
// Draw again to verify the contour tool is still interactive after cancellation
await activeViewport.clickAt([
{ x: 380, y: 299 },
{ x: 420, y: 236 },
{ x: 523, y: 232 },
]);
await expect(activeViewport.svg('circle')).toHaveCount(3);
});
@@ -0,0 +1,47 @@
import { expect, test, visitStudy } from './utils';
import { press } from './utils/keyboardUtils';
test.beforeEach(async ({ page }) => {
const studyInstanceUID = '1.3.12.2.1107.5.2.32.35162.30000015050317233592200000046';
const mode = 'segmentation';
await visitStudy(page, studyInstanceUID, mode, 2000);
});
test('should cancel an in-progress Linear spline contour segmentation via Escape', async ({
page,
rightPanelPageObject,
viewportPageObject,
}) => {
const contourPanel = rightPanelPageObject.contourSegmentationPanel;
// Create a Contour-type segmentation so the contour drawing tools become enabled.
// Creating it adds a default "Segment 1"; wait for that row before drawing.
await contourPanel.addSegmentation();
await expect(contourPanel.panel.rows).toHaveCount(1);
// Activate the Spline Contour tool, then switch to the Linear spline variant.
await contourPanel.tools.splineContour.click();
await contourPanel.tools.splineContour.selectType('linear');
const activeViewport = await viewportPageObject.active;
await activeViewport.clickAt([
{ x: 380, y: 299 },
{ x: 420, y: 236 },
{ x: 523, y: 232 },
]);
// Ensure the three points clicked above are rendered in the DOM before pressing Escape
await expect(activeViewport.svg('circle')).toHaveCount(3);
await press({ page, key: 'Escape' });
// Pressing Escape should cancel the in-progress Linear spline contour
await expect(activeViewport.nthAnnotation(0).locator).toBeHidden();
// Draw again to verify the contour tool is still interactive after cancellation
await activeViewport.clickAt([
{ x: 380, y: 299 },
{ x: 420, y: 236 },
{ x: 523, y: 232 },
]);
await expect(activeViewport.svg('circle')).toHaveCount(3);
});
+34
View File
@@ -84,6 +84,40 @@ test('should restore viewport interactivity after deleting an in-progress Livewi
await expect(activeViewport.nthAnnotation(0).locator).toBeVisible();
});
test('should cancel an in-progress Livewire annotation via Escape', async ({
page,
DOMOverlayPageObject,
mainToolbarPageObject,
viewportPageObject,
}) => {
await mainToolbarPageObject.measurementTools.livewireContour.click();
const activeViewport = await viewportPageObject.active;
await activeViewport.clickAt([
{ x: 380, y: 299 },
{ x: 420, y: 236 },
{ x: 523, y: 232 },
]);
// Ensure the three points clicked above are rendered in the DOM before pressing Escape
await expect(activeViewport.svg('circle')).toHaveCount(3);
await press({ page, key: 'Escape' });
// Pressing Escape should cancel the in-progress Livewire annotation
await expect(activeViewport.nthAnnotation(0).locator).toBeHidden();
// Draw and complete a new Livewire annotation to verify interactivity is restored
await activeViewport.clickAt([
{ x: 380, y: 299 },
{ x: 420, y: 236 },
{ x: 523, y: 232 },
{ x: 581, y: 287 },
{ x: 482, y: 333 },
{ x: 383, y: 301 },
]);
await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
await expect(activeViewport.nthAnnotation(0).locator).toBeVisible();
});
test('should restore viewport interactivity after deleting an in-progress Livewire annotation via Backspace', async ({
page,
DOMOverlayPageObject,
+46
View File
@@ -0,0 +1,46 @@
import { expect, test, visitStudy } from './utils';
import { press } from './utils/keyboardUtils';
test.beforeEach(async ({ page }) => {
const studyInstanceUID = '1.3.12.2.1107.5.2.32.35162.30000015050317233592200000046';
const mode = 'segmentation';
await visitStudy(page, studyInstanceUID, mode, 2000);
});
test('should cancel an in-progress Livewire contour segmentation via Escape', async ({
page,
rightPanelPageObject,
viewportPageObject,
}) => {
const contourPanel = rightPanelPageObject.contourSegmentationPanel;
// Create a Contour-type segmentation so the contour drawing tools become enabled.
// Creating it adds a default "Segment 1"; wait for that row before drawing.
await contourPanel.addSegmentation();
await expect(contourPanel.panel.rows).toHaveCount(1);
// Activate the Livewire Contour tool (a plain click arms it; no variants).
await contourPanel.tools.livewireContour.click();
const activeViewport = await viewportPageObject.active;
await activeViewport.clickAt([
{ x: 380, y: 299 },
{ x: 420, y: 236 },
{ x: 523, y: 232 },
]);
// Ensure the three points clicked above are rendered in the DOM before pressing Escape
await expect(activeViewport.svg('circle')).toHaveCount(3);
await press({ page, key: 'Escape' });
// Pressing Escape should cancel the in-progress Livewire contour
await expect(activeViewport.nthAnnotation(0).locator).toBeHidden();
// Draw again to verify the contour tool is still interactive after cancellation
await activeViewport.clickAt([
{ x: 380, y: 299 },
{ x: 420, y: 236 },
{ x: 523, y: 232 },
]);
await expect(activeViewport.svg('circle')).toHaveCount(3);
});
+35
View File
@@ -84,6 +84,41 @@ test('should restore viewport interactivity after deleting an in-progress Spline
await expect(activeViewport.nthAnnotation(0).locator).toBeVisible();
});
test('should cancel an in-progress Spline annotation via Escape', async ({
page,
DOMOverlayPageObject,
mainToolbarPageObject,
viewportPageObject,
}) => {
await mainToolbarPageObject.measurementTools.splineROI.click();
const activeViewport = await viewportPageObject.active;
await activeViewport.clickAt([
{ x: 380, y: 299 },
{ x: 420, y: 236 },
{ x: 523, y: 232 },
]);
// Ensure the three points clicked above are rendered in the DOM before pressing Escape
await expect(activeViewport.svg('circle')).toHaveCount(3);
await press({ page, key: 'Escape' });
// Pressing Escape should cancel the in-progress Spline annotation
await expect(activeViewport.nthAnnotation(0).locator).toBeHidden();
// Draw and complete a new Spline annotation to verify interactivity is restored
await activeViewport.clickAt([
{ x: 380, y: 299 },
{ x: 420, y: 236 },
{ x: 523, y: 232 },
{ x: 581, y: 287 },
{ x: 482, y: 333 },
{ x: 383, y: 301 },
]);
await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
await expect(activeViewport.nthAnnotation(0).locator).toBeVisible();
});
test('should restore viewport interactivity after deleting an in-progress Spline annotation via Backspace', async ({
page,
DOMOverlayPageObject,
+51
View File
@@ -344,6 +344,57 @@ export class RightPanelPageObject {
select: async () => {
await menuButton.click();
},
// Switches to the contour tab and creates a new Contour-type segmentation,
// which enables the contour drawing tools (Spline / Livewire / Freehand).
addSegmentation: async () => {
await menuButton.click();
await addSegmentationButton.click();
},
tools: {
get splineContour() {
const button = page.getByTestId('SplineContourSegmentationTool');
// Maps a friendly spline name to the underlying cornerstone tool name,
// which is also the data-cy of its option in the Spline Type dropdown.
const splineTypeToolNames = {
catmullRom: 'CatmullRomSplineROI',
linear: 'LinearSplineROI',
bSpline: 'BSplineROI',
} as const;
return {
button,
// Activates the Spline Contour tool, arming the spline variant currently
// selected in the Spline Type dropdown. This defaults to Catmull-Rom until
// selectType is used to switch it.
click: async () => {
await button.click();
},
// Opens the Spline Type dropdown (rendered once the tool is active) and
// switches to the requested spline variant.
selectType: async (type: keyof typeof splineTypeToolNames) => {
await page.getByTestId('splineTypeSelect').getByRole('combobox').click();
await page.getByTestId(splineTypeToolNames[type]).click();
},
};
},
get livewireContour() {
const button = page.getByTestId('LivewireContourSegmentationTool');
return {
button,
click: async () => {
await button.click();
},
};
},
get freehandContour() {
const button = page.getByTestId('PlanarFreehandContourSegmentationTool');
return {
button,
click: async () => {
await button.click();
},
};
},
},
};
}
get labelMapSegmentationPanel() {