diff --git a/extensions/cornerstone/src/commandsModule.ts b/extensions/cornerstone/src/commandsModule.ts index f51c6ffe5..a0791411c 100644 --- a/extensions/cornerstone/src/commandsModule.ts +++ b/extensions/cornerstone/src/commandsModule.ts @@ -637,10 +637,22 @@ function commandsModule({ viewportGridService.setActiveViewportId(viewportId); }, - arrowTextCallback: async ({ callback }) => { + arrowTextCallback: async ({ callback, data }) => { const labelConfig = customizationService.getCustomization('measurementLabels'); const renderContent = customizationService.getCustomization('ui.labellingComponent'); + if (!labelConfig) { + const label = await callInputDialog({ + uiDialogService, + title: 'Edit Arrow Text', + placeholder: data?.data?.label || 'Enter new text', + defaultValue: data?.data?.label || '', + }); + + callback?.(label); + return; + } + const value = await callInputDialogAutoComplete({ uiDialogService, labelConfig, diff --git a/extensions/cornerstone/src/initMeasurementService.ts b/extensions/cornerstone/src/initMeasurementService.ts index 3bf36edd2..34e9598a7 100644 --- a/extensions/cornerstone/src/initMeasurementService.ts +++ b/extensions/cornerstone/src/initMeasurementService.ts @@ -409,10 +409,6 @@ const connectMeasurementServiceToTools = ({ setAnnotationLabel(sourceAnnotation, element, label); } - if (metadata.toolName === 'ArrowAnnotate') { - data.text = label; - } - // update the isLocked state annotation.locking.setAnnotationLocked(uid, isLocked); diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/ArrowAnnotate.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/ArrowAnnotate.ts index e3521ec99..5ea0417f6 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/ArrowAnnotate.ts +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/ArrowAnnotate.ts @@ -75,7 +75,7 @@ const ArrowAnnotate = { frameNumber: mappedAnnotations[0]?.frameNumber || 1, toolName: metadata.toolName, displaySetInstanceUID: displaySet.displaySetInstanceUID, - label: data.text, + label: data.label, displayText: displayText, data: data.cachedStats, type: getValueTypeFromToolType(toolName), diff --git a/platform/ui-next/src/components/DataRow/DataRow.tsx b/platform/ui-next/src/components/DataRow/DataRow.tsx index e0b654063..fad5b2988 100644 --- a/platform/ui-next/src/components/DataRow/DataRow.tsx +++ b/platform/ui-next/src/components/DataRow/DataRow.tsx @@ -295,6 +295,7 @@ export const DataRow: React.FC = ({ : 'opacity-0 group-hover:opacity-100' }`} aria-label="Actions" + dataCY="actionsMenuTrigger" onClick={e => e.stopPropagation()} // Prevent row selection on button click > @@ -308,21 +309,41 @@ export const DataRow: React.FC = ({ <> handleAction('Rename', e)}> - Rename + + Rename + handleAction('Delete', e)}> - Delete + + Delete + {onColor && ( handleAction('Color', e)}> - Change Color + + Change Color + )} handleAction('Lock', e)}> - {isLocked ? 'Unlock' : 'Lock'} + + {isLocked ? 'Unlock' : 'Lock'} + diff --git a/platform/ui-next/src/components/OHIFDialogs/InputDialog.tsx b/platform/ui-next/src/components/OHIFDialogs/InputDialog.tsx index a62663c58..ee667375f 100644 --- a/platform/ui-next/src/components/OHIFDialogs/InputDialog.tsx +++ b/platform/ui-next/src/components/OHIFDialogs/InputDialog.tsx @@ -125,6 +125,7 @@ const InputDialogInput = React.forwardRef setValue(e.target.value)} onKeyDown={handleKeyDown} diff --git a/tests/3DFourUp.spec.ts b/tests/3DFourUp.spec.ts index 75991fcb7..b1ac6b055 100644 --- a/tests/3DFourUp.spec.ts +++ b/tests/3DFourUp.spec.ts @@ -24,6 +24,8 @@ test.describe('3D four up Test', async () => { await attemptAction(() => reduce3DViewportSize(page), 10, 100); + await page.waitForTimeout(5000); + await checkForScreenshot( page, page, diff --git a/tests/3DMain.spec.ts b/tests/3DMain.spec.ts index 56db76e2c..dad949c02 100644 --- a/tests/3DMain.spec.ts +++ b/tests/3DMain.spec.ts @@ -22,6 +22,7 @@ test.describe('3D main Test', async () => { .first() .click(); await attemptAction(() => reduce3DViewportSize(page), 10, 100); + await page.waitForTimeout(5000); await checkForScreenshot(page, page, screenShotPaths.threeDMain.threeDMainDisplayedCorrectly); }); }); diff --git a/tests/3DOnly.spec.ts b/tests/3DOnly.spec.ts index b3b890180..472349383 100644 --- a/tests/3DOnly.spec.ts +++ b/tests/3DOnly.spec.ts @@ -22,6 +22,7 @@ test.describe('3D only Test', async () => { .first() .click(); await attemptAction(() => reduce3DViewportSize(page), 10, 100); + await page.waitForTimeout(5000); // Use a 4 percent diff pixel ratio to account for slight color differences in the 3D viewport await checkForScreenshot({ page, diff --git a/tests/3DPrimary.spec.ts b/tests/3DPrimary.spec.ts index 208ee08db..59c791b35 100644 --- a/tests/3DPrimary.spec.ts +++ b/tests/3DPrimary.spec.ts @@ -23,6 +23,7 @@ test.describe('3D primary Test', async () => { .click(); await attemptAction(() => reduce3DViewportSize(page), 10, 100); + await page.waitForTimeout(5000); await checkForScreenshot( page, page, diff --git a/tests/ArrowAnnotate.spec.ts b/tests/ArrowAnnotate.spec.ts new file mode 100644 index 000000000..a73c2f713 --- /dev/null +++ b/tests/ArrowAnnotate.spec.ts @@ -0,0 +1,86 @@ +import { test } from 'playwright-test-coverage'; +import { + visitStudy, + checkForScreenshot, + screenShotPaths, + simulateClicksOnElement, + simulateDoubleClickOnElement, +} from './utils'; + +test.beforeEach(async ({ page }) => { + const studyInstanceUID = '1.3.6.1.4.1.25403.345050719074.3824.20170125095438.5'; + const mode = 'viewer'; + await visitStudy(page, studyInstanceUID, mode, 2000); +}); + +test('should display the arrow tool and allow free-form text to be entered', async ({ page }) => { + await page.getByTestId('trackedMeasurements-btn').click(); + + await page.getByTestId('MeasurementTools-split-button-secondary').click(); + await page.getByTestId('ArrowAnnotate').click(); + + const locator = page.getByTestId('viewport-pane').locator('canvas'); + await simulateClicksOnElement({ + locator, + points: [ + { + x: 164, + y: 234, + }, + { + x: 344, + y: 232, + }, + ], + }); + + await page.getByTestId('dialog-input').fill('Ringo Starr was the drummer for The Beatles'); + await page.getByTestId('input-dialog-save-button').click(); + + await page.getByTestId('prompt-begin-tracking-yes-btn').click(); + + await page.waitForTimeout(2000); + + await checkForScreenshot({ + page, + maxDiffPixelRatio: 0.0075, + screenshotPath: screenShotPaths.arrowAnnotate.arrowAnnotateDisplayedCorrectly0, + }); + + // Now edit the arrow text and the label should not change. + + await simulateDoubleClickOnElement({ + locator, + point: { + x: 164, + y: 234, + }, + }); + + await page.getByTestId('dialog-input').fill('Neil Peart was the drummer for Rush'); + await page.getByTestId('input-dialog-save-button').click(); + + await page.waitForTimeout(2000); + + await checkForScreenshot({ + page, + maxDiffPixelRatio: 0.0075, + screenshotPath: screenShotPaths.arrowAnnotate.arrowAnnotateDisplayedCorrectly1, + }); + + // Now edit the label and the text should not change. + + await page.getByTestId('actionsMenuTrigger').click(); + await page.getByTestId('Rename').click(); + + await page.getByTestId('dialog-input').fill('Drummer annotation arrow'); + await page.getByTestId('input-dialog-save-button').click(); + + await page.waitForTimeout(2000); + + await checkForScreenshot({ + page, + maxDiffPixelRatio: 0.0075, + screenshotPath: screenShotPaths.arrowAnnotate.arrowAnnotateDisplayedCorrectly2, + }); +}); diff --git a/tests/Worklist.spec.ts b/tests/Worklist.spec.ts index e2975997b..38afc0515 100644 --- a/tests/Worklist.spec.ts +++ b/tests/Worklist.spec.ts @@ -14,12 +14,16 @@ test('should render scroll bars with the correct look-and-feel', async ({ page } ); await studyRowHeader.scrollIntoViewIfNeeded(); + await page.waitForTimeout(3000); + await studyRowHeader.click(); const expandedStudyRow = await page.getByTestId( 'studyRow-1.3.6.1.4.1.14519.5.2.1.5099.8010.217836670708542506360829799868' ); + await expandedStudyRow.scrollIntoViewIfNeeded(); + await page.waitForTimeout(3000); await checkForScreenshot({ page, diff --git a/tests/screenshots/chromium/ArrowAnnotate.spec.ts/arrowAnnotateDisplayedCorrectly0.png b/tests/screenshots/chromium/ArrowAnnotate.spec.ts/arrowAnnotateDisplayedCorrectly0.png new file mode 100644 index 000000000..a047ab522 Binary files /dev/null and b/tests/screenshots/chromium/ArrowAnnotate.spec.ts/arrowAnnotateDisplayedCorrectly0.png differ diff --git a/tests/screenshots/chromium/ArrowAnnotate.spec.ts/arrowAnnotateDisplayedCorrectly1.png b/tests/screenshots/chromium/ArrowAnnotate.spec.ts/arrowAnnotateDisplayedCorrectly1.png new file mode 100644 index 000000000..c1b0e1318 Binary files /dev/null and b/tests/screenshots/chromium/ArrowAnnotate.spec.ts/arrowAnnotateDisplayedCorrectly1.png differ diff --git a/tests/screenshots/chromium/ArrowAnnotate.spec.ts/arrowAnnotateDisplayedCorrectly2.png b/tests/screenshots/chromium/ArrowAnnotate.spec.ts/arrowAnnotateDisplayedCorrectly2.png new file mode 100644 index 000000000..434bb82c2 Binary files /dev/null and b/tests/screenshots/chromium/ArrowAnnotate.spec.ts/arrowAnnotateDisplayedCorrectly2.png differ diff --git a/tests/utils/index.ts b/tests/utils/index.ts index abdba595c..8b8334325 100644 --- a/tests/utils/index.ts +++ b/tests/utils/index.ts @@ -3,6 +3,7 @@ import { checkForScreenshot } from './checkForScreenshot'; import { screenShotPaths } from './screenShotPaths'; import { simulateClicksOnElement, + simulateDoubleClickOnElement, simulateNormalizedClickOnElement, simulateNormalizedClicksOnElement, } from './simulateClicksOnElement'; @@ -20,6 +21,7 @@ export { checkForScreenshot, screenShotPaths, simulateClicksOnElement, + simulateDoubleClickOnElement, simulateNormalizedClickOnElement, simulateNormalizedClicksOnElement, reduce3DViewportSize, diff --git a/tests/utils/screenShotPaths.ts b/tests/utils/screenShotPaths.ts index 97641af57..4a8b0e072 100644 --- a/tests/utils/screenShotPaths.ts +++ b/tests/utils/screenShotPaths.ts @@ -2,6 +2,11 @@ * Paths to the screenshots of the tests. */ const screenShotPaths = { + arrowAnnotate: { + arrowAnnotateDisplayedCorrectly0: 'arrowAnnotateDisplayedCorrectly0.png', + arrowAnnotateDisplayedCorrectly1: 'arrowAnnotateDisplayedCorrectly1.png', + arrowAnnotateDisplayedCorrectly2: 'arrowAnnotateDisplayedCorrectly2.png', + }, angle: { angleDisplayedCorrectly: 'angleDisplayedCorrectly.png', }, diff --git a/tests/utils/simulateClicksOnElement.ts b/tests/utils/simulateClicksOnElement.ts index c8dac6714..53ae464df 100644 --- a/tests/utils/simulateClicksOnElement.ts +++ b/tests/utils/simulateClicksOnElement.ts @@ -9,15 +9,28 @@ import { Locator } from 'playwright'; export async function simulateClicksOnElement({ locator, points, + button = 'left', }: { locator: Locator; points: { x: number; y: number }[]; + button?: 'left' | 'right' | 'middle'; }) { for (const { x, y } of points) { - await locator.click({ delay: 100, position: { x, y } }); + await locator.click({ delay: 100, position: { x, y }, button }); } } +export async function simulateDoubleClickOnElement({ + locator, + point, +}: { + locator: Locator; + point: { x: number; y: number }; +}) { + const { x, y } = point; + await locator.dblclick({ delay: 100, position: { x, y } }); +} + /** * Simulates clicks on an element at a normalized point. *