feat: add more e2e tests for MPR and add test mode and extension (#3180)
* tests: add various e2e tests for MPR and measurements wip add cypress config feat: add mode and extension for testing add hp applied through search params add MPR tests apply review comments add more e2e tests update yarn lock * fix unit tests failing
This commit is contained in:
1 parent
ae4183852f
commit
926290f69e
83 files changed
+6286
-6860
No files matched your search
@@ -29,18 +29,18 @@ const copyPluginFromExtensions = writePluginImportFile(SRC_DIR, DIST_DIR);
|
||||
|
||||
const setHeaders = (res, path) => {
|
||||
if (path.indexOf('.gz') !== -1) {
|
||||
res.setHeader('Content-Encoding', 'gzip')
|
||||
res.setHeader('Content-Encoding', 'gzip');
|
||||
} else if (path.indexOf('.br') !== -1) {
|
||||
res.setHeader('Content-Encoding', 'br')
|
||||
res.setHeader('Content-Encoding', 'br');
|
||||
}
|
||||
if (path.indexOf('.pdf') !== -1) {
|
||||
res.setHeader('Content-Type', 'application/pdf');
|
||||
} else if (path.indexOf('/frames') !== -1) {
|
||||
res.setHeader('Content-Type', 'multipart/related')
|
||||
res.setHeader('Content-Type', 'multipart/related');
|
||||
} else {
|
||||
res.setHeader('Content-Type', 'application/json')
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = (env, argv) => {
|
||||
const baseConfig = webpackBase(env, argv, { SRC_DIR, DIST_DIR });
|
||||
@@ -55,7 +55,7 @@ module.exports = (env, argv) => {
|
||||
path: DIST_DIR,
|
||||
filename: isProdBuild ? '[name].bundle.[chunkhash].js' : '[name].js',
|
||||
publicPath: PUBLIC_URL, // Used by HtmlWebPackPlugin for asset prefix
|
||||
devtoolModuleFilenameTemplate: function (info) {
|
||||
devtoolModuleFilenameTemplate: function(info) {
|
||||
if (isProdBuild) {
|
||||
return `webpack:///${info.resourcePath}`;
|
||||
} else {
|
||||
@@ -146,12 +146,12 @@ module.exports = (env, argv) => {
|
||||
proxy: {
|
||||
'/dicomweb': 'http://localhost:5000',
|
||||
},
|
||||
'static': [
|
||||
static: [
|
||||
{
|
||||
directory: '../../testdata',
|
||||
staticOptions: {
|
||||
extensions: ['gz', 'br'],
|
||||
index: "index.json.gz",
|
||||
index: ['index.json.gz', 'index.mht.gz'],
|
||||
redirect: true,
|
||||
setHeaders,
|
||||
},
|
||||
|
||||
@@ -17,8 +17,9 @@ function constructLines(input, categoryName) {
|
||||
if (!input) return lines;
|
||||
|
||||
input.forEach(entry => {
|
||||
const packageName = entry.packageName;
|
||||
if (entry.default === false) return;
|
||||
|
||||
const packageName = entry.packageName;
|
||||
const defaultImportName = `${categoryName}${pluginCount}`;
|
||||
|
||||
lines.importLines.push(
|
||||
@@ -66,7 +67,23 @@ function getRuntimeLoadModesExtensions() {
|
||||
' for(const modeFactory of modesFactory) {\n' +
|
||||
' const newModes = await modeFactory(modes,extensions);\n' +
|
||||
' newModes.forEach(newMode => modes.push(newMode));\n' +
|
||||
'}\n}\n'
|
||||
'}\n}\n\n\n' +
|
||||
'async function loadRuntimeImports(config) {\n' +
|
||||
' if (config && config.modes && config.modes.length) {\n' +
|
||||
' for (const modeName of config.modes) {\n' +
|
||||
' const existingMode = modes.find(mode => mode.id === modeName);\n' +
|
||||
' if (!existingMode) {\n' +
|
||||
' if (modeName === `@ohif/mode-test`) {\n' +
|
||||
' const mode = await import(`@ohif/mode-test`);\n' +
|
||||
' window.modes.push(mode.default);\n' +
|
||||
' const extension = await import(`@ohif/extension-test`);\n' +
|
||||
' window.extensions.push(extension.default);\n' +
|
||||
' }\n' +
|
||||
' }\n' +
|
||||
' };\n' +
|
||||
' }\n' +
|
||||
'}\n' +
|
||||
'export { loadRuntimeImports };\n\n'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -201,11 +218,12 @@ function writePluginImportsFile(SRC_DIR, DIST_DIR) {
|
||||
'dist'
|
||||
);
|
||||
|
||||
console.log('copy plugins', [...copyPluginPublicToDistBuild,
|
||||
...copyPluginPublicToDistLink,
|
||||
...copyPluginDistToDistBuild,
|
||||
...copyPluginDistToDistLink,
|
||||
])
|
||||
console.log('copy plugins', [
|
||||
...copyPluginPublicToDistBuild,
|
||||
...copyPluginPublicToDistLink,
|
||||
...copyPluginDistToDistBuild,
|
||||
...copyPluginDistToDistLink,
|
||||
]);
|
||||
return [
|
||||
...copyPluginPublicToDistBuild,
|
||||
...copyPluginPublicToDistLink,
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { defineConfig } from 'cypress';
|
||||
|
||||
export default defineConfig({
|
||||
chromeWebSecurity: false,
|
||||
e2e: {
|
||||
experimentalRunAllSpecs: true,
|
||||
supportFile: 'cypress/support/index.js',
|
||||
setupNodeEvents(on, config) {
|
||||
on('before:browser:launch', (browser, launchOptions) => {
|
||||
// `args` is an array of all the arguments that will
|
||||
// be passed to browsers when it launches
|
||||
|
||||
console.log(launchOptions.args); // print all current args
|
||||
|
||||
console.log('***', browser.family, browser.name, '***');
|
||||
if (browser.family === 'chromium' && browser.name !== 'electron') {
|
||||
// auto open devtools
|
||||
launchOptions.args.push('--enable-features=SharedArrayBuffer');
|
||||
}
|
||||
|
||||
// whatever you return here becomes the launchOptions
|
||||
return launchOptions;
|
||||
});
|
||||
},
|
||||
baseUrl: 'http://localhost:3000',
|
||||
waitForAnimations: true,
|
||||
chromeWebSecurity: false,
|
||||
defaultCommandTimeout: 10000,
|
||||
requestTimeout: 10000,
|
||||
responseTimeout: 10000,
|
||||
specPattern: 'cypress/integration/**/*.spec.[jt]s',
|
||||
projectId: '4oe38f',
|
||||
video: false,
|
||||
reporter: 'junit',
|
||||
reporterOptions: {
|
||||
mochaFile: 'cypress/results/test-output.xml',
|
||||
toConsole: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"baseUrl": "http://localhost:3000",
|
||||
"chromeWebSecurity": false,
|
||||
"waitForAnimations": true,
|
||||
"defaultCommandTimeout": 10000,
|
||||
"requestTimeout": 10000,
|
||||
"responseTimeout": 10000,
|
||||
"projectId": "4oe38f",
|
||||
"video": false,
|
||||
"reporter": "junit",
|
||||
"reporterOptions": {
|
||||
"mochaFile": "cypress/results/test-output.xml",
|
||||
"toConsole": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: ['cypress'],
|
||||
env: {
|
||||
'cypress/globals': true,
|
||||
},
|
||||
};
|
||||
@@ -1,18 +1,13 @@
|
||||
describe('OHIF PDF Display', function () {
|
||||
before(() => {
|
||||
cy.openStudyInViewer(
|
||||
'2.25.317377619501274872606137091638706705333'
|
||||
);
|
||||
});
|
||||
describe('OHIF PDF Display', function() {
|
||||
beforeEach(function() {
|
||||
cy.openStudyInViewer('2.25.317377619501274872606137091638706705333');
|
||||
|
||||
beforeEach(function () {
|
||||
cy.resetViewport().wait(50);
|
||||
});
|
||||
|
||||
it('checks if series thumbnails are being displayed', function () {
|
||||
it('checks if series thumbnails are being displayed', function() {
|
||||
cy.get('[data-cy="study-browser-thumbnail"]')
|
||||
.its('length')
|
||||
.should('be.gt', 0);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,15 +1,10 @@
|
||||
describe('OHIF Video Display', function () {
|
||||
before(() => {
|
||||
cy.openStudyInViewer(
|
||||
'2.25.96975534054447904995905761963464388233'
|
||||
);
|
||||
});
|
||||
|
||||
beforeEach(function () {
|
||||
describe('OHIF Video Display', function() {
|
||||
beforeEach(function() {
|
||||
cy.openStudyInViewer('2.25.96975534054447904995905761963464388233');
|
||||
cy.resetViewport().wait(50);
|
||||
});
|
||||
|
||||
it('checks if series thumbnails are being displayed', function () {
|
||||
it('checks if series thumbnails are being displayed', function() {
|
||||
cy.get('[data-cy="study-browser-thumbnail"]')
|
||||
.its('length')
|
||||
.should('be.gt', 1);
|
||||
|
||||
+13
-26
@@ -1,5 +1,5 @@
|
||||
describe('OHIF Cornerstone Hotkeys', () => {
|
||||
before(() => {
|
||||
beforeEach(() => {
|
||||
cy.checkStudyRouteInViewer(
|
||||
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
|
||||
);
|
||||
@@ -15,12 +15,8 @@ describe('OHIF Cornerstone Hotkeys', () => {
|
||||
});
|
||||
|
||||
cy.expectMinimumThumbnails(3);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.initCornerstoneToolsAliases();
|
||||
cy.initCommonElementsAliases();
|
||||
cy.resetViewport();
|
||||
});
|
||||
|
||||
it('checks if hotkeys "R" and "L" can rotate the image', () => {
|
||||
@@ -45,11 +41,11 @@ describe('OHIF Cornerstone Hotkeys', () => {
|
||||
|
||||
it('checks if hotkeys "V" and "H" can flip the image', () => {
|
||||
// Hotkey H
|
||||
cy.get('body').type('H');
|
||||
cy.get('body').type('h');
|
||||
cy.get('@viewportInfoMidLeft').should('contains.text', 'L');
|
||||
cy.get('@viewportInfoMidTop').should('contains.text', 'A');
|
||||
// Hotkey V
|
||||
cy.get('body').type('V');
|
||||
cy.get('body').type('v');
|
||||
cy.get('@viewportInfoMidLeft').should('contains.text', 'L');
|
||||
cy.get('@viewportInfoMidTop').should('contains.text', 'P');
|
||||
});
|
||||
@@ -73,26 +69,17 @@ describe('OHIF Cornerstone Hotkeys', () => {
|
||||
// cy.get('@viewportInfoTopLeft').should('contains.text', 'Zoom:1.67x');
|
||||
// });
|
||||
|
||||
// it('checks if hotkey "SPACEBAR" can reset the image', () => {
|
||||
// //Click on button and verify if icon is active on toolbar
|
||||
// cy.get('@zoomBtn')
|
||||
// .click()
|
||||
// .then($zoomBtn => {
|
||||
// cy.wrap($zoomBtn).should('have.class', 'active');
|
||||
// });
|
||||
it('checks if hotkey "SPACEBAR" can reset the image', () => {
|
||||
// Press multiples hotkeys
|
||||
cy.get('body').type('v+++i');
|
||||
cy.get('@viewportInfoMidLeft').should('contains.text', 'R');
|
||||
cy.get('@viewportInfoMidTop').should('contains.text', 'P');
|
||||
|
||||
// // Press multiples hotkeys
|
||||
// cy.get('body').type('V+++I');
|
||||
// cy.get('@viewportInfoMidLeft').should('contains.text', 'L');
|
||||
// cy.get('@viewportInfoMidTop').should('contains.text', 'A');
|
||||
// cy.get('@viewportInfoTopLeft').should('contains.text', 'Zoom:2.30x');
|
||||
|
||||
// // Hotkey SPACEBAR
|
||||
// cy.get('body').type(' ');
|
||||
// cy.get('@viewportInfoMidLeft').should('contains.text', 'R');
|
||||
// cy.get('@viewportInfoMidTop').should('contains.text', 'A');
|
||||
// cy.get('@viewportInfoTopLeft').should('contains.text', 'Zoom:1.67x');
|
||||
// });
|
||||
// Hotkey SPACEBAR
|
||||
cy.get('body').type(' ');
|
||||
cy.get('@viewportInfoMidLeft').should('contains.text', 'R');
|
||||
cy.get('@viewportInfoMidTop').should('contains.text', 'A');
|
||||
});
|
||||
|
||||
/*
|
||||
// TODO: Pretty sure this is not implemented yet
|
||||
|
||||
+1
-6
@@ -1,12 +1,9 @@
|
||||
describe('OHIF Cornerstone Toolbar', () => {
|
||||
before(() => {
|
||||
beforeEach(() => {
|
||||
cy.checkStudyRouteInViewer(
|
||||
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
|
||||
);
|
||||
cy.expectMinimumThumbnails(3);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.initCornerstoneToolsAliases();
|
||||
cy.initCommonElementsAliases();
|
||||
|
||||
@@ -16,8 +13,6 @@ describe('OHIF Cornerstone Toolbar', () => {
|
||||
|
||||
//const expectedText = 'Ser: 1';
|
||||
//cy.get('@viewportInfoBottomLeft').should('contains.text', expectedText);
|
||||
|
||||
cy.resetViewport();
|
||||
});
|
||||
|
||||
it('checks if all primary buttons are being displayed', () => {
|
||||
|
||||
+14
-31
@@ -1,32 +1,17 @@
|
||||
describe('OHIF Download Snapshot File', () => {
|
||||
before(() => {
|
||||
beforeEach(() => {
|
||||
cy.checkStudyRouteInViewer(
|
||||
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
|
||||
);
|
||||
cy.expectMinimumThumbnails(3);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.openDownloadImageModal();
|
||||
});
|
||||
|
||||
/**
|
||||
afterEach(() => {
|
||||
// Close modal
|
||||
|
||||
// TODO: Modal.tsx currently uses Icon instead of IconButton, so
|
||||
// it's not as easy to give it the cypress data attribute.
|
||||
// We should fix that first
|
||||
cy.get('[data-cy="close-button"]')
|
||||
.scrollIntoView()
|
||||
.click();
|
||||
});*/
|
||||
|
||||
/*it('checks displayed information for Desktop experience', function() {
|
||||
it('checks displayed information for Desktop experience', function() {
|
||||
// Set Desktop resolution
|
||||
cy.viewport(1750, 720);
|
||||
// cy.viewport(1750, 720);
|
||||
// Visual comparison
|
||||
cy.screenshot('Download Image Modal - Desktop experience');
|
||||
// cy.screenshot('Download Image Modal - Desktop experience');
|
||||
//Check if all elements are displayed
|
||||
|
||||
// TODO: need to add this attribute to the modal
|
||||
@@ -36,19 +21,19 @@ describe('OHIF Download Snapshot File', () => {
|
||||
|
||||
// Check input fields
|
||||
// TODO: select2
|
||||
cy.get('[data-cy="file-type"]')
|
||||
.select('png')
|
||||
.should('have.value', 'png')
|
||||
.select('jpg')
|
||||
.should('have.value', 'jpg');
|
||||
// cy.get('[data-cy="file-type"]')
|
||||
// .select('png')
|
||||
// .should('have.value', 'png')
|
||||
// .select('jpg')
|
||||
// .should('have.value', 'jpg');
|
||||
|
||||
// Check image preview
|
||||
cy.get('[data-cy="image-preview"]').should('contain.text', 'Image preview');
|
||||
|
||||
TODO: This is a canvas now, not an img with src
|
||||
cy.get('[data-cy="viewport-preview-img"]')
|
||||
.should('have.attr', 'src')
|
||||
.and('include', 'data:image');
|
||||
//TODO: This is a canvas now, not an img with src
|
||||
// cy.get('[data-cy="viewport-preview-img"]')
|
||||
// .should('have.attr', 'src')
|
||||
// .and('include', 'data:image');
|
||||
|
||||
// Check buttons
|
||||
cy.get('[data-cy="cancel-btn"]')
|
||||
@@ -58,7 +43,6 @@ describe('OHIF Download Snapshot File', () => {
|
||||
.scrollIntoView()
|
||||
.should('be.visible');
|
||||
});
|
||||
*/
|
||||
|
||||
/*it('cancel changes on download modal', function() {
|
||||
//Change Image Width, Filename and File Type
|
||||
@@ -88,8 +72,7 @@ describe('OHIF Download Snapshot File', () => {
|
||||
// TO-DO once issue is fixed: https://github.com/OHIF/Viewers/issues/1217
|
||||
// it('checks error messages for empty fields', function() {
|
||||
// //Clear fields Image Width and Filename
|
||||
// cy.get('[data-cy="image-width"]')
|
||||
// .clear();
|
||||
// cy.get('[data-cy="image-width"]').clear();
|
||||
// cy.get('[data-cy="file-name"]').clear();
|
||||
|
||||
// //Click on Download button
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
describe('OHIF Study Viewer Page', function() {
|
||||
beforeEach(function() {
|
||||
cy.checkStudyRouteInViewer(
|
||||
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
|
||||
);
|
||||
|
||||
cy.expectMinimumThumbnails(3);
|
||||
cy.initCommonElementsAliases();
|
||||
cy.initCornerstoneToolsAliases();
|
||||
});
|
||||
|
||||
it('scrolls series stack using scrollbar', function() {
|
||||
cy.scrollToIndex(13);
|
||||
|
||||
cy.get('@viewportInfoTopRight').should('contains.text', '14');
|
||||
});
|
||||
|
||||
it('performs right click to zoom', function() {
|
||||
// This is not used to activate the tool, it is used to ensure the
|
||||
// top left viewport info shows the zoom values (it only shows up
|
||||
// when the zoom tool is active)
|
||||
cy.get('@zoomBtn')
|
||||
.click()
|
||||
.then($zoomBtn => {
|
||||
cy.wrap($zoomBtn).should('have.class', 'active');
|
||||
});
|
||||
|
||||
const zoomLevelInitial = cy
|
||||
.get('@viewportInfoTopLeft')
|
||||
.then($viewportInfo => {
|
||||
return $viewportInfo.text().substring(6, 9);
|
||||
});
|
||||
|
||||
//Right click on viewport
|
||||
cy.get('@viewport')
|
||||
.trigger('mousedown', 'top', { buttons: 2 })
|
||||
.trigger('mousemove', 'center', { buttons: 2 })
|
||||
.trigger('mouseup');
|
||||
|
||||
// make sure the new zoom level is less than the initial
|
||||
cy.get('@viewportInfoTopLeft').then($viewportInfo => {
|
||||
const zoomLevelFinal = $viewportInfo.text().substring(6, 9);
|
||||
expect(zoomLevelFinal < zoomLevelInitial).to.eq(true);
|
||||
});
|
||||
});
|
||||
|
||||
/*it('performs middle click to pan', function() {
|
||||
//Get image position from cornerstone and check if y axis was modified
|
||||
let cornerstone;
|
||||
let currentPan;
|
||||
|
||||
// TO DO: Replace the cornerstone pan check by Percy snapshot comparison
|
||||
cy.window()
|
||||
.its('cornerstone')
|
||||
.then(c => {
|
||||
cornerstone = c;
|
||||
currentPan = () =>
|
||||
cornerstone.getEnabledElements()[0].viewport.translation;
|
||||
});
|
||||
|
||||
//pan image with middle click
|
||||
cy.get('@viewport')
|
||||
.trigger('mousedown', 'center', { buttons: 3 })
|
||||
.trigger('mousemove', 'bottom', { buttons: 3 })
|
||||
.trigger('mouseup', 'bottom')
|
||||
.then(() => {
|
||||
expect(currentPan().y > 0).to.eq(true);
|
||||
});
|
||||
});*/
|
||||
|
||||
/*it('opens About modal and verify the displayed information', function() {
|
||||
cy.get('[data-cy="options-dropdown"]')
|
||||
.first()
|
||||
.click();
|
||||
cy.get('[data-cy="about-modal"]')
|
||||
.as('aboutOverlay')
|
||||
.should('be.visible');
|
||||
|
||||
//check buttons and links
|
||||
cy.get('[data-cy="about-modal"]')
|
||||
.should('contains.text', 'Visit the forum')
|
||||
.and('contains.text', 'Report an issue')
|
||||
.and('contains.text', 'https://github.com/OHIF/Viewers/');
|
||||
|
||||
//check version number
|
||||
cy.get('[data-cy="about-modal"]').then($modal => {
|
||||
cy.get('[data-cy="header-version-info"]').should($headerVersionNumber => {
|
||||
$headerVersionNumber = $headerVersionNumber.text().substring(1);
|
||||
expect($modal).to.contain($headerVersionNumber);
|
||||
});
|
||||
});
|
||||
|
||||
//close modal
|
||||
cy.get('[data-cy="close-button"]').click();
|
||||
cy.get('@aboutOverlay').should('not.exist');
|
||||
});
|
||||
*/
|
||||
});
|
||||
+213
@@ -0,0 +1,213 @@
|
||||
describe('OHIF Measurement Panel', function() {
|
||||
beforeEach(function() {
|
||||
cy.checkStudyRouteInViewer(
|
||||
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
|
||||
);
|
||||
|
||||
cy.expectMinimumThumbnails(3);
|
||||
cy.initCommonElementsAliases();
|
||||
cy.initCornerstoneToolsAliases();
|
||||
cy.resetViewport().wait(50);
|
||||
});
|
||||
|
||||
it('checks if Measurements right panel can be hidden/displayed', function() {
|
||||
cy.get('@measurementsPanel').should('exist');
|
||||
cy.get('@measurementsPanel').should('be.visible');
|
||||
|
||||
cy.get('@RightCollapseBtn').click();
|
||||
cy.get('@measurementsPanel').should('not.exist');
|
||||
|
||||
cy.get('@RightCollapseBtn').click();
|
||||
cy.get('@measurementsPanel').should('exist');
|
||||
cy.get('@measurementsPanel').should('be.visible');
|
||||
});
|
||||
|
||||
it('checks if measurement item can be Relabeled under Measurements panel', function() {
|
||||
// Add length measurement
|
||||
cy.addLengthMeasurement();
|
||||
cy.get('[data-cy="measurement-tracking-prompt-begin-tracking"]').should(
|
||||
'exist'
|
||||
);
|
||||
cy.get('[data-cy="measurement-tracking-prompt-begin-tracking"]').should(
|
||||
'be.visible'
|
||||
);
|
||||
cy.get('[data-cy="prompt-begin-tracking-yes"]').click();
|
||||
cy.get('[data-cy="measurement-item"]').click();
|
||||
|
||||
cy.get('[data-cy="measurement-item"]')
|
||||
.find('svg')
|
||||
.click();
|
||||
|
||||
// enter Bone label
|
||||
cy.get('[data-cy="input-annotation"]').should('exist');
|
||||
cy.get('[data-cy="input-annotation"]').should('be.visible');
|
||||
cy.get('[data-cy="input-annotation"]').type('Bone{enter}');
|
||||
|
||||
// Verify if 'Bone' label was added
|
||||
cy.get('[data-cy="measurement-item"]').should('contain.text', 'Bone');
|
||||
});
|
||||
|
||||
it('checks if image would jump when clicked on a measurement item', function() {
|
||||
// Add length measurement
|
||||
cy.addLengthMeasurement();
|
||||
cy.get('[data-cy="prompt-begin-tracking-yes"]').click();
|
||||
|
||||
cy.scrollToIndex(13);
|
||||
|
||||
cy.addLengthMeasurement([100, 100], [200, 200]); //Adding measurement in the viewport
|
||||
|
||||
cy.get('@viewportInfoTopRight').should('contains.text', '(14/');
|
||||
|
||||
// Click on first measurement item
|
||||
cy.get('[data-cy="measurement-item"]').click();
|
||||
|
||||
cy.get('@viewportInfoTopRight').should('contains.text', '(1/');
|
||||
cy.get('@viewportInfoTopRight').should('not.contains.text', '(14/');
|
||||
});
|
||||
|
||||
/*
|
||||
TODO: Not sure why this is failing
|
||||
it('checks if Description can be added to measurement item under Measurements panel', () => {
|
||||
cy.addLengthMeasurement(); //Adding measurement in the viewport
|
||||
cy.get('@measurementsBtn').click();
|
||||
cy.get('.measurementItem').click();
|
||||
|
||||
// Click "Description"
|
||||
cy.get('.btnAction')
|
||||
.contains('Description')
|
||||
.click();
|
||||
|
||||
// Enter description text
|
||||
const descriptionText = 'Adding text for description test';
|
||||
cy.get('#description').type(descriptionText);
|
||||
|
||||
// Confirm
|
||||
cy.get('.btn-confirm').click();
|
||||
|
||||
//Verify if descriptionText was added
|
||||
cy.get('.measurementLocation').should('contain.text', descriptionText);
|
||||
|
||||
// Remove the measurement we just added
|
||||
cy.get('.btnAction')
|
||||
.last()
|
||||
.contains('Delete')
|
||||
.click()
|
||||
|
||||
// Close panel
|
||||
cy.get('@measurementsBtn').click();
|
||||
cy.get('@measurementsPanel').should('not.be.enabled');
|
||||
});
|
||||
*/
|
||||
|
||||
/*it('checks if measurement item can be deleted through the context menu on the viewport', function() {
|
||||
cy.addLengthMeasurement([100, 100], [200, 100]); //Adding measurement in the viewport
|
||||
|
||||
//Right click on measurement annotation
|
||||
const [x1, y1] = [150, 100];
|
||||
cy.get('@viewport')
|
||||
.trigger('mousedown', x1, y1, {
|
||||
which: 3,
|
||||
})
|
||||
.trigger('mouseup', x1, y1, {
|
||||
which: 3,
|
||||
})
|
||||
.wait(300)
|
||||
.then(() => {
|
||||
//Contextmenu is visible
|
||||
cy.get('.ToolContextMenu').should('be.visible');
|
||||
});
|
||||
|
||||
//Click "Delete measurement"
|
||||
cy.get('.form-action')
|
||||
.contains('Delete measurement')
|
||||
.click();
|
||||
|
||||
//Open measurements menu
|
||||
cy.get('@measurementsBtn').click();
|
||||
|
||||
//Verify measurements was removed from panel
|
||||
cy.get('.measurementItem')
|
||||
.should('not.exist')
|
||||
.log('Annotation successfully removed');
|
||||
|
||||
//Close panel
|
||||
cy.get('@measurementsBtn').click();
|
||||
cy.get('@measurementsPanel').should('not.exist');
|
||||
});*/
|
||||
|
||||
/*it('adds relabel and description to measurement item through the context menu on the viewport', function() {
|
||||
cy.addLengthMeasurement([100, 100], [200, 100]); //Adding measurement in the viewport
|
||||
|
||||
// Relabel
|
||||
// Right click on measurement annotation
|
||||
const [x1, y1] = [150, 100];
|
||||
cy.get('@viewport')
|
||||
.trigger('mousedown', x1, y1, {
|
||||
which: 3,
|
||||
})
|
||||
.trigger('mouseup', x1, y1, {
|
||||
which: 3,
|
||||
});
|
||||
|
||||
// Contextmenu is visible
|
||||
cy.get('.ToolContextMenu').should('be.visible');
|
||||
|
||||
// Click "Relabel"
|
||||
cy.get('.form-action')
|
||||
.contains('Relabel')
|
||||
.click();
|
||||
|
||||
// Search for "Brain"
|
||||
cy.get('.searchInput').type('Brain');
|
||||
|
||||
// Select "Brain" Result
|
||||
cy.get('.treeInputs > .wrapperLabel')
|
||||
.contains('Brain')
|
||||
.click();
|
||||
|
||||
// Confirm Selection
|
||||
cy.get('.checkIconWrapper').click();
|
||||
|
||||
// Description
|
||||
// Right click on measurement annotation
|
||||
cy.get('@viewport')
|
||||
.trigger('mousedown', x1, y1, {
|
||||
which: 3,
|
||||
})
|
||||
.trigger('mouseup', x1, y1, {
|
||||
which: 3,
|
||||
});
|
||||
|
||||
// Contextmenu is visible
|
||||
cy.get('.ToolContextMenu').should('be.visible');
|
||||
|
||||
// Click "Description"
|
||||
cy.get('.form-action')
|
||||
.contains('Add Description')
|
||||
.click();
|
||||
|
||||
// Enter description text
|
||||
const descriptionText = 'Adding text for description test';
|
||||
cy.get('#description').type(descriptionText);
|
||||
|
||||
// Confirm
|
||||
cy.get('.btn-confirm').click();
|
||||
|
||||
//Open measurements menu
|
||||
cy.get('@measurementsBtn').click();
|
||||
|
||||
// Verify if label was added
|
||||
cy.get('.measurementLocation')
|
||||
.should('contain.text', 'Brain')
|
||||
.log('Relabel added with success');
|
||||
|
||||
//Verify if descriptionText was added
|
||||
cy.get('.measurementLocation')
|
||||
.should('contain.text', descriptionText)
|
||||
.log('Description added with success');
|
||||
|
||||
// Close panel
|
||||
cy.get('@measurementsBtn').click();
|
||||
cy.get('@measurementsPanel').should('not.exist');
|
||||
});*/
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
describe('OHIF Study Viewer Page', function() {
|
||||
beforeEach(function() {
|
||||
cy.checkStudyRouteInViewer(
|
||||
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
|
||||
);
|
||||
|
||||
cy.expectMinimumThumbnails(3);
|
||||
cy.initCommonElementsAliases();
|
||||
cy.initCornerstoneToolsAliases();
|
||||
cy.resetViewport().wait(50);
|
||||
});
|
||||
|
||||
it('checks if series thumbnails are being displayed', function() {
|
||||
cy.get('[data-cy="study-browser-thumbnail"]')
|
||||
.its('length')
|
||||
.should('be.gt', 1);
|
||||
});
|
||||
|
||||
it('drags and drop a series thumbnail into viewport', function() {
|
||||
cy.get('[data-cy="study-browser-thumbnail"]:nth-child(2)') //element to be dragged
|
||||
.drag('.cornerstone-canvas'); //dropzone element
|
||||
|
||||
//const expectedText =
|
||||
// 'Ser: 2Img: 1 1/13512 x 512Loc: -17.60 mm Thick: 3.00 mm';
|
||||
//cy.get('@viewportInfoBottomLeft').should('contain.text', expectedText);
|
||||
});
|
||||
|
||||
it('checks if Series left panel can be hidden/displayed', function() {
|
||||
cy.get('@seriesPanel').should('exist');
|
||||
cy.get('@seriesPanel').should('be.visible');
|
||||
|
||||
cy.get('@seriesBtn').click();
|
||||
cy.get('@seriesPanel').should('not.exist');
|
||||
|
||||
cy.get('@seriesBtn').click();
|
||||
cy.get('@seriesPanel').should('exist');
|
||||
cy.get('@seriesPanel').should('be.visible');
|
||||
});
|
||||
|
||||
it('performs double-click to load thumbnail in active viewport', () => {
|
||||
cy.get('[data-cy="study-browser-thumbnail"]:nth-child(2)').dblclick();
|
||||
|
||||
//cy.get('@viewportInfoBottomLeft').should('contains.text', expectedText);
|
||||
});
|
||||
});
|
||||
@@ -1,339 +0,0 @@
|
||||
describe('OHIF Study Viewer Page', function() {
|
||||
before(() => {
|
||||
cy.checkStudyRouteInViewer(
|
||||
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
|
||||
);
|
||||
|
||||
cy.expectMinimumThumbnails(3);
|
||||
});
|
||||
|
||||
beforeEach(function() {
|
||||
cy.initCommonElementsAliases();
|
||||
cy.initCornerstoneToolsAliases();
|
||||
cy.resetViewport().wait(50);
|
||||
});
|
||||
|
||||
it('checks if series thumbnails are being displayed', function() {
|
||||
cy.get('[data-cy="study-browser-thumbnail"]')
|
||||
.its('length')
|
||||
.should('be.gt', 1);
|
||||
});
|
||||
|
||||
it('drags and drop a series thumbnail into viewport', function() {
|
||||
cy.get('[data-cy="study-browser-thumbnail"]:nth-child(2)') //element to be dragged
|
||||
.drag('.cornerstone-canvas'); //dropzone element
|
||||
|
||||
//const expectedText =
|
||||
// 'Ser: 2Img: 1 1/13512 x 512Loc: -17.60 mm Thick: 3.00 mm';
|
||||
//cy.get('@viewportInfoBottomLeft').should('contain.text', expectedText);
|
||||
});
|
||||
|
||||
it('checks if Series left panel can be hidden/displayed', function() {
|
||||
cy.get('@seriesPanel').should('exist');
|
||||
cy.get('@seriesPanel').should('be.visible');
|
||||
|
||||
cy.get('@seriesBtn').click();
|
||||
cy.get('@seriesPanel').should('not.exist');
|
||||
|
||||
cy.get('@seriesBtn').click();
|
||||
cy.get('@seriesPanel').should('exist');
|
||||
cy.get('@seriesPanel').should('be.visible');
|
||||
});
|
||||
|
||||
it('checks if Measurements right panel can be hidden/displayed', function() {
|
||||
cy.get('@measurementsPanel').should('exist');
|
||||
cy.get('@measurementsPanel').should('be.visible');
|
||||
|
||||
cy.get('@RightCollapseBtn').click();
|
||||
cy.get('@measurementsPanel').should('not.exist');
|
||||
|
||||
cy.get('@RightCollapseBtn').click();
|
||||
cy.get('@measurementsPanel').should('exist');
|
||||
cy.get('@measurementsPanel').should('be.visible');
|
||||
});
|
||||
|
||||
/*it('checks if measurement item can be Relabeled under Measurements panel', function() {
|
||||
// Add length measurement
|
||||
cy.addLengthMeasurement();
|
||||
cy.get('[data-cy="measurement-tracking-prompt-begin-tracking"]').should('exist')
|
||||
cy.get('[data-cy="measurement-tracking-prompt-begin-tracking"]').should('be.visible')
|
||||
cy.get('[data-cy="prompt-begin-tracking-yes"]').click();
|
||||
cy.get('.measurementItem').click();
|
||||
|
||||
// Click "Relabel"
|
||||
cy.get('.btnAction', { timeout: 10000 })
|
||||
.first()
|
||||
.contains('Relabel')
|
||||
.click().should('be.visible');
|
||||
|
||||
// Search for "Bone"
|
||||
cy.get('.searchInput').type('Bone');
|
||||
|
||||
// Select "Bone" Result
|
||||
cy.get('.treeInputs > .wrapperLabel')
|
||||
.contains('Bone')
|
||||
.click();
|
||||
|
||||
// Confirm Selection
|
||||
cy.get('.checkIconWrapper').click();
|
||||
|
||||
// Verify if 'Bone' label was added
|
||||
cy.get('.measurementLocation').should('contain.text', 'Bone');
|
||||
|
||||
// Remove the measurement we just added
|
||||
cy.get('.btnAction')
|
||||
.last()
|
||||
.contains('Delete')
|
||||
.click()
|
||||
|
||||
// Close panel
|
||||
cy.get('@measurementsBtn').click();
|
||||
cy.get('@measurementsPanel').should('not.exist');
|
||||
});*/
|
||||
|
||||
/*
|
||||
TODO: Not sure why this is failing
|
||||
it('checks if Description can be added to measurement item under Measurements panel', () => {
|
||||
cy.addLengthMeasurement(); //Adding measurement in the viewport
|
||||
cy.get('@measurementsBtn').click();
|
||||
cy.get('.measurementItem').click();
|
||||
|
||||
// Click "Description"
|
||||
cy.get('.btnAction')
|
||||
.contains('Description')
|
||||
.click();
|
||||
|
||||
// Enter description text
|
||||
const descriptionText = 'Adding text for description test';
|
||||
cy.get('#description').type(descriptionText);
|
||||
|
||||
// Confirm
|
||||
cy.get('.btn-confirm').click();
|
||||
|
||||
//Verify if descriptionText was added
|
||||
cy.get('.measurementLocation').should('contain.text', descriptionText);
|
||||
|
||||
// Remove the measurement we just added
|
||||
cy.get('.btnAction')
|
||||
.last()
|
||||
.contains('Delete')
|
||||
.click()
|
||||
|
||||
// Close panel
|
||||
cy.get('@measurementsBtn').click();
|
||||
cy.get('@measurementsPanel').should('not.be.enabled');
|
||||
});
|
||||
*/
|
||||
|
||||
/*it('checks if measurement item can be deleted through the context menu on the viewport', function() {
|
||||
cy.addLengthMeasurement([100, 100], [200, 100]); //Adding measurement in the viewport
|
||||
|
||||
//Right click on measurement annotation
|
||||
const [x1, y1] = [150, 100];
|
||||
cy.get('@viewport')
|
||||
.trigger('mousedown', x1, y1, {
|
||||
which: 3,
|
||||
})
|
||||
.trigger('mouseup', x1, y1, {
|
||||
which: 3,
|
||||
})
|
||||
.wait(300)
|
||||
.then(() => {
|
||||
//Contextmenu is visible
|
||||
cy.get('.ToolContextMenu').should('be.visible');
|
||||
});
|
||||
|
||||
//Click "Delete measurement"
|
||||
cy.get('.form-action')
|
||||
.contains('Delete measurement')
|
||||
.click();
|
||||
|
||||
//Open measurements menu
|
||||
cy.get('@measurementsBtn').click();
|
||||
|
||||
//Verify measurements was removed from panel
|
||||
cy.get('.measurementItem')
|
||||
.should('not.exist')
|
||||
.log('Annotation successfully removed');
|
||||
|
||||
//Close panel
|
||||
cy.get('@measurementsBtn').click();
|
||||
cy.get('@measurementsPanel').should('not.exist');
|
||||
});*/
|
||||
|
||||
/*it('adds relabel and description to measurement item through the context menu on the viewport', function() {
|
||||
cy.addLengthMeasurement([100, 100], [200, 100]); //Adding measurement in the viewport
|
||||
|
||||
// Relabel
|
||||
// Right click on measurement annotation
|
||||
const [x1, y1] = [150, 100];
|
||||
cy.get('@viewport')
|
||||
.trigger('mousedown', x1, y1, {
|
||||
which: 3,
|
||||
})
|
||||
.trigger('mouseup', x1, y1, {
|
||||
which: 3,
|
||||
});
|
||||
|
||||
// Contextmenu is visible
|
||||
cy.get('.ToolContextMenu').should('be.visible');
|
||||
|
||||
// Click "Relabel"
|
||||
cy.get('.form-action')
|
||||
.contains('Relabel')
|
||||
.click();
|
||||
|
||||
// Search for "Brain"
|
||||
cy.get('.searchInput').type('Brain');
|
||||
|
||||
// Select "Brain" Result
|
||||
cy.get('.treeInputs > .wrapperLabel')
|
||||
.contains('Brain')
|
||||
.click();
|
||||
|
||||
// Confirm Selection
|
||||
cy.get('.checkIconWrapper').click();
|
||||
|
||||
// Description
|
||||
// Right click on measurement annotation
|
||||
cy.get('@viewport')
|
||||
.trigger('mousedown', x1, y1, {
|
||||
which: 3,
|
||||
})
|
||||
.trigger('mouseup', x1, y1, {
|
||||
which: 3,
|
||||
});
|
||||
|
||||
// Contextmenu is visible
|
||||
cy.get('.ToolContextMenu').should('be.visible');
|
||||
|
||||
// Click "Description"
|
||||
cy.get('.form-action')
|
||||
.contains('Add Description')
|
||||
.click();
|
||||
|
||||
// Enter description text
|
||||
const descriptionText = 'Adding text for description test';
|
||||
cy.get('#description').type(descriptionText);
|
||||
|
||||
// Confirm
|
||||
cy.get('.btn-confirm').click();
|
||||
|
||||
//Open measurements menu
|
||||
cy.get('@measurementsBtn').click();
|
||||
|
||||
// Verify if label was added
|
||||
cy.get('.measurementLocation')
|
||||
.should('contain.text', 'Brain')
|
||||
.log('Relabel added with success');
|
||||
|
||||
//Verify if descriptionText was added
|
||||
cy.get('.measurementLocation')
|
||||
.should('contain.text', descriptionText)
|
||||
.log('Description added with success');
|
||||
|
||||
// Close panel
|
||||
cy.get('@measurementsBtn').click();
|
||||
cy.get('@measurementsPanel').should('not.exist');
|
||||
});*/
|
||||
|
||||
it('scrolls series stack using scrollbar', function() {
|
||||
// Workaround implemented based on Cypress issue:
|
||||
// https://github.com/cypress-io/cypress/issues/1570#issuecomment-450966053
|
||||
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
|
||||
window.HTMLInputElement.prototype,
|
||||
'value'
|
||||
).set;
|
||||
|
||||
cy.get('input.imageSlider[type=range]').then($range => {
|
||||
// get the DOM node
|
||||
const range = $range[0];
|
||||
// set the value manually
|
||||
nativeInputValueSetter.call(range, 13);
|
||||
// now dispatch the event
|
||||
range.dispatchEvent(new Event('change', { value: 13, bubbles: true }));
|
||||
});
|
||||
|
||||
/*const expectedText =
|
||||
'Ser: 2Img: 13 13/13512 x 512Loc: 18.40 mm Thick: 3.00 mm'; //'Img: 13 13/13';
|
||||
cy.get('@viewportInfoBottomLeft').should('contains.text', expectedText);*/
|
||||
});
|
||||
|
||||
it('performs double-click to load thumbnail in active viewport', () => {
|
||||
cy.get('[data-cy="study-browser-thumbnail"]:nth-child(2)').dblclick();
|
||||
|
||||
//const expectedText = 'Ser: 3';
|
||||
//cy.get('@viewportInfoBottomLeft').should('contains.text', expectedText);
|
||||
});
|
||||
|
||||
// it('performs right click to zoom', function() {
|
||||
// // This is not used to activate the tool, it is used to ensure the
|
||||
// // top left viewport info shows the zoom values (it only shows up
|
||||
// // when the zoom tool is active)
|
||||
// cy.get('@zoomBtn')
|
||||
// .click()
|
||||
// .then($zoomBtn => {
|
||||
// cy.wrap($zoomBtn).should('have.class', 'active');
|
||||
// });
|
||||
|
||||
// //Right click on viewport
|
||||
// cy.get('@viewport')
|
||||
// .trigger('mousedown', 'top', { buttons: 2 })
|
||||
// .trigger('mousemove', 'center', { buttons: 2 })
|
||||
// .trigger('mouseup');
|
||||
|
||||
// const expectedText = 'Zoom:6.65x';
|
||||
// cy.get('@viewportInfoTopLeft').should('contains.text', expectedText);
|
||||
// });
|
||||
|
||||
/*it('performs middle click to pan', function() {
|
||||
//Get image position from cornerstone and check if y axis was modified
|
||||
let cornerstone;
|
||||
let currentPan;
|
||||
|
||||
// TO DO: Replace the cornerstone pan check by Percy snapshot comparison
|
||||
cy.window()
|
||||
.its('cornerstone')
|
||||
.then(c => {
|
||||
cornerstone = c;
|
||||
currentPan = () =>
|
||||
cornerstone.getEnabledElements()[0].viewport.translation;
|
||||
});
|
||||
|
||||
//pan image with middle click
|
||||
cy.get('@viewport')
|
||||
.trigger('mousedown', 'center', { buttons: 3 })
|
||||
.trigger('mousemove', 'bottom', { buttons: 3 })
|
||||
.trigger('mouseup', 'bottom')
|
||||
.then(() => {
|
||||
expect(currentPan().y > 0).to.eq(true);
|
||||
});
|
||||
});*/
|
||||
|
||||
/*it('opens About modal and verify the displayed information', function() {
|
||||
cy.get('[data-cy="options-dropdown"]')
|
||||
.first()
|
||||
.click();
|
||||
cy.get('[data-cy="about-modal"]')
|
||||
.as('aboutOverlay')
|
||||
.should('be.visible');
|
||||
|
||||
//check buttons and links
|
||||
cy.get('[data-cy="about-modal"]')
|
||||
.should('contains.text', 'Visit the forum')
|
||||
.and('contains.text', 'Report an issue')
|
||||
.and('contains.text', 'https://github.com/OHIF/Viewers/');
|
||||
|
||||
//check version number
|
||||
cy.get('[data-cy="about-modal"]').then($modal => {
|
||||
cy.get('[data-cy="header-version-info"]').should($headerVersionNumber => {
|
||||
$headerVersionNumber = $headerVersionNumber.text().substring(1);
|
||||
expect($modal).to.contain($headerVersionNumber);
|
||||
});
|
||||
});
|
||||
|
||||
//close modal
|
||||
cy.get('[data-cy="close-button"]').click();
|
||||
cy.get('@aboutOverlay').should('not.exist');
|
||||
});*/
|
||||
});
|
||||
@@ -1,12 +1,10 @@
|
||||
//We are keeping the hardcoded results values for the study list tests
|
||||
//this is intended to be running in a controled docker environment with test data.
|
||||
describe('OHIF Study List', function () {
|
||||
context('Desktop resolution', function () {
|
||||
before(function () {
|
||||
describe('OHIF Study List', function() {
|
||||
context('Desktop resolution', function() {
|
||||
beforeEach(function() {
|
||||
cy.openStudyList();
|
||||
});
|
||||
|
||||
beforeEach(function () {
|
||||
cy.viewport(1750, 720);
|
||||
cy.initStudyListAliasesOnDesktop();
|
||||
//Clear all text fields
|
||||
@@ -14,14 +12,9 @@ describe('OHIF Study List', function () {
|
||||
cy.get('@MRN').clear();
|
||||
cy.get('@AccessionNumber').clear();
|
||||
cy.get('@StudyDescription').clear();
|
||||
|
||||
// TODO: Find out how to clear this input
|
||||
// select-2 / react-select has some specific ways
|
||||
// to trigger things
|
||||
//cy.get('@modalities')
|
||||
});
|
||||
|
||||
it('Displays several studies initially', function () {
|
||||
it('Displays several studies initially', function() {
|
||||
cy.waitStudyList();
|
||||
cy.get('@searchResult2').should($list => {
|
||||
expect($list.length).to.be.greaterThan(1);
|
||||
@@ -30,7 +23,7 @@ describe('OHIF Study List', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('searches Patient Name with exact string', function () {
|
||||
it('searches Patient Name with exact string', function() {
|
||||
cy.get('@PatientName').type('Juno');
|
||||
//Wait result list to be displayed
|
||||
cy.waitStudyList();
|
||||
@@ -40,8 +33,7 @@ describe('OHIF Study List', function () {
|
||||
});
|
||||
});
|
||||
|
||||
/* TODO - re-enable this once the test server is fixed to allow searching by mrn
|
||||
it('searches MRN with exact string', function () {
|
||||
it('searches MRN with exact string', function() {
|
||||
cy.get('@MRN').type('0000003');
|
||||
//Wait result list to be displayed
|
||||
cy.waitStudyList();
|
||||
@@ -50,31 +42,17 @@ describe('OHIF Study List', function () {
|
||||
expect($list).to.contain('0000003');
|
||||
});
|
||||
});
|
||||
*/
|
||||
|
||||
it('searches Accession with exact string', function () {
|
||||
cy.get('@AccessionNumber').type('0000155811');
|
||||
it('searches Accession with exact string', function() {
|
||||
cy.get('@AccessionNumber').type('321');
|
||||
//Wait result list to be displayed
|
||||
cy.waitStudyList();
|
||||
cy.get('@searchResult2').should($list => {
|
||||
expect($list.length).to.be.eq(1);
|
||||
expect($list).to.contain('0000155811');
|
||||
expect($list).to.contain('321');
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: need to be able to programmatically change react-select
|
||||
/*it('searches Modality with camel case', function() {
|
||||
cy.get('@modalities').type('Ct');
|
||||
//Wait result list to be displayed
|
||||
cy.waitStudyList();
|
||||
cy.get('@searchResult2').should($list => {
|
||||
expect($list.length).to.be.greaterThan(1);
|
||||
expect($list).to.contain('CT');
|
||||
});
|
||||
});*/
|
||||
|
||||
/*
|
||||
TODO: Currently broken in dicomweb-server
|
||||
it('searches Description with exact string', function() {
|
||||
cy.get('@StudyDescription').type('PETCT');
|
||||
//Wait result list to be displayed
|
||||
@@ -84,10 +62,19 @@ describe('OHIF Study List', function () {
|
||||
expect($list).to.contain('PETCT');
|
||||
});
|
||||
});
|
||||
*/
|
||||
|
||||
// TODO: need to be able to programmatically change react-select
|
||||
/*it('changes Rows per page and checks the study count', function() {
|
||||
/* Todo: fix react select
|
||||
it('searches Modality with camel case', function() {
|
||||
cy.get('@modalities').type('Ct');
|
||||
// Wait result list to be displayed
|
||||
cy.waitStudyList();
|
||||
cy.get('@searchResult2').should($list => {
|
||||
expect($list.length).to.be.greaterThan(1);
|
||||
expect($list).to.contain('CT');
|
||||
});
|
||||
});
|
||||
|
||||
it('changes Rows per page and checks the study count', function() {
|
||||
//Show Rows per page options
|
||||
const pageRows = [25, 50, 100];
|
||||
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
describe('OHIF MPR', () => {
|
||||
beforeEach(() => {
|
||||
cy.checkStudyRouteInViewer(
|
||||
'1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1'
|
||||
);
|
||||
cy.expectMinimumThumbnails(3);
|
||||
cy.initCornerstoneToolsAliases();
|
||||
cy.initCommonElementsAliases();
|
||||
});
|
||||
|
||||
it('should not go MPR for non reconstructible displaySets', () => {
|
||||
cy.get('[data-cy="MPR"]').click();
|
||||
cy.get('.cornerstone-canvas').should('have.length', 1);
|
||||
});
|
||||
|
||||
it('should go MPR for reconstructible displaySets and come back', () => {
|
||||
cy.wait(1000);
|
||||
cy.get(':nth-child(3) > [data-cy="study-browser-thumbnail"]').dblclick();
|
||||
|
||||
cy.get('[data-cy="MPR"]').click();
|
||||
|
||||
cy.get('.cornerstone-canvas').should('have.length', 3);
|
||||
|
||||
cy.get('[data-cy="MPR"]').click();
|
||||
|
||||
cy.get('.cornerstone-canvas').should('have.length', 1);
|
||||
});
|
||||
|
||||
it('should render correctly the MPR', () => {
|
||||
cy.wait(1000);
|
||||
|
||||
cy.get(':nth-child(3) > [data-cy="study-browser-thumbnail"]').dblclick();
|
||||
cy.get('[data-cy="MPR"]').click();
|
||||
|
||||
cy.get('[data-cy="thumbnail-viewport-labels"]').should('have.length', 3);
|
||||
|
||||
cy.get('.cornerstone-canvas').should('have.length', 3);
|
||||
|
||||
cy.get('[data-cy="thumbnail-viewport-labels"]')
|
||||
.eq(2)
|
||||
.find('div')
|
||||
.should('have.length', 3)
|
||||
.each(($div, index) => {
|
||||
const text = $div.text();
|
||||
switch (index) {
|
||||
case 0:
|
||||
expect(text).to.equal('A');
|
||||
break;
|
||||
case 1:
|
||||
expect(text).to.equal('B');
|
||||
break;
|
||||
case 2:
|
||||
expect(text).to.equal('C');
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unexpected div found with text: ${text}`);
|
||||
}
|
||||
});
|
||||
|
||||
// check cornerstone to see if each has images
|
||||
// we can later do visual testing to match the images with a baseline
|
||||
cy.window()
|
||||
.its('cornerstone')
|
||||
.then(cornerstone => {
|
||||
const viewports = cornerstone.getRenderingEngines()[0].getViewports();
|
||||
|
||||
const imageData1 = viewports[0].getImageData();
|
||||
const imageData2 = viewports[1].getImageData();
|
||||
const imageData3 = viewports[2].getImageData();
|
||||
|
||||
// for some reason map doesn't work here
|
||||
cy.wrap(imageData1).should('not.be', undefined);
|
||||
cy.wrap(imageData2).should('not.be', undefined);
|
||||
cy.wrap(imageData3).should('not.be', undefined);
|
||||
|
||||
cy.wrap(imageData1.dimensions).should(
|
||||
'deep.equal',
|
||||
imageData2.dimensions
|
||||
);
|
||||
|
||||
cy.wrap(imageData1.origin).should('deep.equal', imageData2.origin);
|
||||
});
|
||||
|
||||
cy.get('[data-cy="MPR"]').click();
|
||||
|
||||
cy.get('.cornerstone-canvas').should('have.length', 1);
|
||||
|
||||
// should not have any div under it
|
||||
cy.get('[data-cy="thumbnail-viewport-labels"]')
|
||||
.eq(2)
|
||||
.find('div')
|
||||
.should('have.length', 0);
|
||||
});
|
||||
|
||||
it('should correctly render Crosshairs for MPR', () => {
|
||||
cy.wait(1000);
|
||||
|
||||
cy.get('[data-cy="Crosshairs"]').click();
|
||||
cy.window()
|
||||
.its('cornerstoneTools')
|
||||
.then(cornerstoneTools => {
|
||||
const state = cornerstoneTools.annotation.state.getDefaultAnnotationManager();
|
||||
|
||||
const fORMap = state.annotations;
|
||||
// it should not have crosshairs yet
|
||||
expect(Object.keys(fORMap)).to.have.length(0);
|
||||
});
|
||||
|
||||
cy.get(':nth-child(3) > [data-cy="study-browser-thumbnail"]').dblclick();
|
||||
cy.get('[data-cy="MPR"]').click();
|
||||
cy.get('[data-cy="Crosshairs"]').click();
|
||||
|
||||
cy.wait(1000);
|
||||
|
||||
// check cornerstone to see if each has crosshairs
|
||||
// we can later do visual testing to match the images with a baseline
|
||||
cy.window()
|
||||
.its('cornerstoneTools')
|
||||
.then(cornerstoneTools => {
|
||||
const state = cornerstoneTools.annotation.state.getDefaultAnnotationManager();
|
||||
|
||||
const fORMap = state.annotations;
|
||||
const fOR = Object.keys(fORMap)[0];
|
||||
const fORAnnotation = fORMap[fOR];
|
||||
|
||||
// it should have crosshairs as the only key
|
||||
expect(Object.keys(fORAnnotation)).to.have.length(1);
|
||||
|
||||
const crosshairs = fORAnnotation.Crosshairs;
|
||||
|
||||
// it should have three
|
||||
expect(crosshairs).to.have.length(3);
|
||||
|
||||
expect(crosshairs[0].data.handles.toolCenter).to.deep.equal(
|
||||
crosshairs[1].data.handles.toolCenter
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -18,6 +18,7 @@ export function initCornerstoneToolsAliases() {
|
||||
cy.get('[data-cy="MoreTools-split-button-secondary"]').as('moreBtnSecondary');
|
||||
cy.get('[data-cy="Layout"]').as('layoutBtn');
|
||||
cy.get('.cornerstone-viewport-element').as('viewport');
|
||||
cy.get('[data-cy="MPR"]').as('mprBtn');
|
||||
}
|
||||
|
||||
//Creating aliases for Common page elements
|
||||
@@ -49,9 +50,16 @@ export function initCommonElementsAliases() {
|
||||
|
||||
//Creating aliases for Routes
|
||||
export function initRouteAliases() {
|
||||
cy.server();
|
||||
cy.route('GET', '**/series**').as('getStudySeries');
|
||||
cy.route('GET', '**/studies**').as('getStudies');
|
||||
cy.intercept('GET', '**/series**', { statusCode: 200, body: [] }).as(
|
||||
'getStudySeries'
|
||||
);
|
||||
|
||||
// Todo: for some reason cypress does not redirect to the correct url
|
||||
// so we intercept the request and redirect it to the correct url
|
||||
cy.intercept('/studies?limit*', req => {
|
||||
const url = req.url.replace(/\/studies\?/, '/studies/?limit');
|
||||
req.url = url;
|
||||
});
|
||||
}
|
||||
|
||||
//Creating aliases for Study List page elements on Desktop experience
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
initCornerstoneToolsAliases,
|
||||
initCommonElementsAliases,
|
||||
initRouteAliases,
|
||||
initVTKToolsAliases,
|
||||
initStudyListAliasesOnDesktop,
|
||||
initStudyListAliasesOnTablet,
|
||||
initPreferencesModalAliases,
|
||||
@@ -46,7 +45,9 @@ import {
|
||||
Cypress.Commands.add('openStudy', PatientName => {
|
||||
cy.openStudyList();
|
||||
cy.get('#filter-patientNameOrId').type(PatientName);
|
||||
cy.wait('@getStudies');
|
||||
// cy.get('@getStudies').then(() => {
|
||||
cy.wait(1000);
|
||||
|
||||
cy.get('[data-cy="study-list-results"]', { timeout: 5000 })
|
||||
.contains(PatientName)
|
||||
.first()
|
||||
@@ -56,15 +57,16 @@ Cypress.Commands.add('openStudy', PatientName => {
|
||||
Cypress.Commands.add('checkStudyRouteInViewer', StudyInstanceUID => {
|
||||
cy.location('pathname').then($url => {
|
||||
cy.log($url);
|
||||
if ($url == 'blank' || !$url.includes(`/viewer/${StudyInstanceUID}`)) {
|
||||
if ($url == 'blank' || !$url.includes(`/basic-test/${StudyInstanceUID}`)) {
|
||||
cy.openStudyInViewer(StudyInstanceUID);
|
||||
cy.waitDicomImage();
|
||||
cy.wait(2000);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('openStudyInViewer', StudyInstanceUID => {
|
||||
cy.visit(`/viewer?StudyInstanceUIDs=${StudyInstanceUID}`);
|
||||
cy.visit(`/basic-test?StudyInstanceUIDs=${StudyInstanceUID}`);
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -89,16 +91,20 @@ Cypress.Commands.add('openStudyModality', Modality => {
|
||||
/**
|
||||
* Command to wait and check if a new page was loaded
|
||||
*
|
||||
* @param {string} url - part of the expected url. Default value is /viewer
|
||||
* @param {string} url - part of the expected url. Default value is /basic-test
|
||||
*/
|
||||
Cypress.Commands.add('isPageLoaded', (url = '/viewer') => {
|
||||
Cypress.Commands.add('isPageLoaded', (url = '/basic-test') => {
|
||||
return cy.location('pathname', { timeout: 60000 }).should('include', url);
|
||||
});
|
||||
|
||||
Cypress.Commands.add('openStudyList', () => {
|
||||
cy.initRouteAliases();
|
||||
cy.visit('/');
|
||||
cy.wait('@getStudies');
|
||||
|
||||
// For some reason cypress 12.x does not like to stub the network request
|
||||
// so we just wait herer for 1 second
|
||||
// cy.wait('@getStudies');
|
||||
cy.wait(1000);
|
||||
});
|
||||
|
||||
Cypress.Commands.add('waitStudyList', () => {
|
||||
@@ -450,6 +456,29 @@ Cypress.Commands.add('openPreferences', () => {
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('scrollToIndex', index => {
|
||||
// Workaround implemented based on Cypress issue:
|
||||
// https://github.com/cypress-io/cypress/issues/1570#issuecomment-450966053
|
||||
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
|
||||
window.HTMLInputElement.prototype,
|
||||
'value'
|
||||
).set;
|
||||
|
||||
cy.get('input.imageSlider[type=range]').then($range => {
|
||||
// get the DOM node
|
||||
const range = $range[0];
|
||||
// set the value manually
|
||||
nativeInputValueSetter.call(range, index);
|
||||
// now dispatch the event
|
||||
range.dispatchEvent(
|
||||
new Event('change', {
|
||||
value: index,
|
||||
bubbles: true,
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('closePreferences', () => {
|
||||
cy.log('Close User Preferences Modal');
|
||||
|
||||
@@ -596,3 +625,14 @@ Cypress.Commands.add('setLanguage', (language, save = true) => {
|
||||
.click();
|
||||
});
|
||||
});
|
||||
|
||||
// hide noisy logs
|
||||
// https://github.com/cypress-io/cypress/issues/7362
|
||||
// uncomment this if you really need the network logs
|
||||
const origLog = Cypress.log;
|
||||
Cypress.log = function(opts, ...other) {
|
||||
if (opts.displayName === 'script' || opts.name === 'request') {
|
||||
return;
|
||||
}
|
||||
return origLog(opts, ...other);
|
||||
};
|
||||
@@ -46,7 +46,7 @@
|
||||
"README.md"
|
||||
],
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.16.3",
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@ohif/core": "^3.0.0",
|
||||
"@ohif/extension-cornerstone": "^3.0.0",
|
||||
"@ohif/extension-cornerstone-dicom-seg": "^3.0.0",
|
||||
@@ -54,8 +54,10 @@
|
||||
"@ohif/extension-default": "^3.0.0",
|
||||
"@ohif/extension-dicom-pdf": "^3.0.1",
|
||||
"@ohif/extension-dicom-video": "^3.0.1",
|
||||
"@ohif/extension-test": "0.0.1",
|
||||
"@ohif/i18n": "^1.0.0",
|
||||
"@ohif/mode-basic-dev-mode": "^3.0.0",
|
||||
"@ohif/mode-test": "^0.0.1",
|
||||
"@ohif/mode-longitudinal": "^3.0.0",
|
||||
"@ohif/ui": "^2.0.0",
|
||||
"@types/react": "^17.0.38",
|
||||
@@ -82,13 +84,13 @@
|
||||
"react-dropzone": "^10.1.7",
|
||||
"react-i18next": "^10.11.0",
|
||||
"react-resize-detector": "^6.7.6",
|
||||
"react-router": "^6.3.0",
|
||||
"react-router-dom": "^6.3.0"
|
||||
"react-router": "^6.8.1",
|
||||
"react-router-dom": "^6.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-private-methods": "^7.18.6",
|
||||
"@percy/cypress": "^3.1.1",
|
||||
"cypress": "^9.5.4",
|
||||
"cypress": "^12.6.0",
|
||||
"cypress-file-upload": "^3.5.3",
|
||||
"glob": "^8.0.3",
|
||||
"identity-obj-proxy": "3.0.x",
|
||||
|
||||
@@ -6,6 +6,8 @@ window.config = {
|
||||
showStudyList: true,
|
||||
// below flag is for performance reasons, but it might not work for all servers
|
||||
omitQuotationForMultipartRequest: true,
|
||||
showWarningMessageForCrossOrigin: true,
|
||||
showCPUFallbackMessage: true,
|
||||
showLoadingIndicator: true,
|
||||
// filterQueryParam: false,
|
||||
dataSources: [
|
||||
|
||||
@@ -12,6 +12,8 @@ window.config = {
|
||||
maxNumberOfWebWorkers: 3,
|
||||
// below flag is for performance reasons, but it might not work for all servers
|
||||
omitQuotationForMultipartRequest: true,
|
||||
showWarningMessageForCrossOrigin: true,
|
||||
showCPUFallbackMessage: true,
|
||||
showLoadingIndicator: true,
|
||||
maxNumRequests: {
|
||||
interaction: 100,
|
||||
|
||||
@@ -4,6 +4,8 @@ window.config = {
|
||||
showStudyList: true,
|
||||
// below flag is for performance reasons, but it might not work for all servers
|
||||
omitQuotationForMultipartRequest: true,
|
||||
showWarningMessageForCrossOrigin: true,
|
||||
showCPUFallbackMessage: true,
|
||||
servers: {
|
||||
dicomWeb: [
|
||||
{
|
||||
|
||||
@@ -6,6 +6,8 @@ window.config = {
|
||||
showStudyList: true,
|
||||
// below flag is for performance reasons, but it might not work for all servers
|
||||
omitQuotationForMultipartRequest: true,
|
||||
showWarningMessageForCrossOrigin: true,
|
||||
showCPUFallbackMessage: true,
|
||||
showLoadingIndicator: true,
|
||||
// filterQueryParam: false,
|
||||
dataSources: [
|
||||
|
||||
@@ -7,6 +7,8 @@ window.config = {
|
||||
maxNumberOfWebWorkers: 3,
|
||||
// below flag is for performance reasons, but it might not work for all servers
|
||||
omitQuotationForMultipartRequest: true,
|
||||
showWarningMessageForCrossOrigin: true,
|
||||
showCPUFallbackMessage: true,
|
||||
showLoadingIndicator: true,
|
||||
// filterQueryParam: false,
|
||||
dataSources: [
|
||||
|
||||
@@ -5,6 +5,8 @@ window.config = {
|
||||
modes: [],
|
||||
// below flag is for performance reasons, but it might not work for all servers
|
||||
omitQuotationForMultipartRequest: true,
|
||||
showWarningMessageForCrossOrigin: true,
|
||||
showCPUFallbackMessage: true,
|
||||
showLoadingIndicator: true,
|
||||
dataSources: [
|
||||
{
|
||||
|
||||
@@ -3,6 +3,8 @@ window.config = {
|
||||
showStudyList: true,
|
||||
// below flag is for performance reasons, but it might not work for all servers
|
||||
omitQuotationForMultipartRequest: true,
|
||||
showWarningMessageForCrossOrigin: true,
|
||||
showCPUFallbackMessage: true,
|
||||
showLoadingIndicator: true,
|
||||
servers: {
|
||||
// This is an array, but we'll only use the first entry for now
|
||||
|
||||
@@ -5,6 +5,8 @@ window.config = {
|
||||
modes: [],
|
||||
// below flag is for performance reasons, but it might not work for all servers
|
||||
omitQuotationForMultipartRequest: true,
|
||||
showWarningMessageForCrossOrigin: true,
|
||||
showCPUFallbackMessage: true,
|
||||
showLoadingIndicator: true,
|
||||
dataSources: [
|
||||
{
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
window.config = {
|
||||
routerBasename: '/',
|
||||
// whiteLabelling: {},
|
||||
extensions: [],
|
||||
modes: [],
|
||||
modes: ['@ohif/mode-test'],
|
||||
showStudyList: true,
|
||||
// below flag is for performance reasons, but it might not work for all servers
|
||||
omitQuotationForMultipartRequest: true,
|
||||
showWarningMessageForCrossOrigin: false,
|
||||
showCPUFallbackMessage: false,
|
||||
// filterQueryParam: false,
|
||||
dataSources: [
|
||||
{
|
||||
@@ -13,7 +14,9 @@ window.config = {
|
||||
namespace: '@ohif/extension-default.dataSourcesModule.dicomweb',
|
||||
sourceName: 'dicomweb',
|
||||
configuration: {
|
||||
name: 'DCM4CHEE',
|
||||
// The most important field to set for static WADO
|
||||
staticWado: true,
|
||||
name: 'StaticWADO',
|
||||
wadoUriRoot: '/viewer-testdata',
|
||||
qidoRoot: '/viewer-testdata',
|
||||
wadoRoot: '/viewer-testdata',
|
||||
@@ -24,7 +27,6 @@ window.config = {
|
||||
enableStudyLazyLoad: true,
|
||||
supportsFuzzyMatching: false,
|
||||
supportsWildcard: true,
|
||||
staticWado: true,
|
||||
singlepart: 'video,thumbnail,pdf',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -3,6 +3,8 @@ window.config = {
|
||||
enableGoogleCloudAdapter: false,
|
||||
// below flag is for performance reasons, but it might not work for all servers
|
||||
omitQuotationForMultipartRequest: true,
|
||||
showWarningMessageForCrossOrigin: true,
|
||||
showCPUFallbackMessage: true,
|
||||
showLoadingIndicator: true,
|
||||
// This is an array, but we'll only use the first entry for now
|
||||
oidc: [
|
||||
|
||||
@@ -3,6 +3,8 @@ window.config = {
|
||||
enableGoogleCloudAdapter: true,
|
||||
// below flag is for performance reasons, but it might not work for all servers
|
||||
omitQuotationForMultipartRequest: true,
|
||||
showWarningMessageForCrossOrigin: true,
|
||||
showCPUFallbackMessage: true,
|
||||
showLoadingIndicator: true,
|
||||
servers: {
|
||||
// This is an array, but we'll only use the first entry for now
|
||||
|
||||
@@ -5,6 +5,8 @@ window.config = {
|
||||
modes: [],
|
||||
// below flag is for performance reasons, but it might not work for all servers
|
||||
omitQuotationForMultipartRequest: true,
|
||||
showWarningMessageForCrossOrigin: true,
|
||||
showCPUFallbackMessage: true,
|
||||
showLoadingIndicator: true,
|
||||
dataSources: [
|
||||
{
|
||||
|
||||
@@ -6,6 +6,8 @@ window.config = {
|
||||
showStudyList: true,
|
||||
maxNumberOfWebWorkers: 3,
|
||||
showLoadingIndicator: true,
|
||||
showWarningMessageForCrossOrigin: true,
|
||||
showCPUFallbackMessage: true,
|
||||
// filterQueryParam: false,
|
||||
dataSources: [
|
||||
{
|
||||
|
||||
@@ -9,6 +9,8 @@ window.config = {
|
||||
maxNumberOfWebWorkers: 4,
|
||||
// below flag is for performance reasons, but it might not work for all servers
|
||||
omitQuotationForMultipartRequest: true,
|
||||
showWarningMessageForCrossOrigin: true,
|
||||
showCPUFallbackMessage: true,
|
||||
showLoadingIndicator: true,
|
||||
// filterQueryParam: false,
|
||||
dataSources: [
|
||||
|
||||
@@ -14,6 +14,8 @@ window.config = {
|
||||
maxNumberOfWebWorkers: 4,
|
||||
// below flag is for performance reasons, but it might not work for all servers
|
||||
omitQuotationForMultipartRequest: true,
|
||||
showWarningMessageForCrossOrigin: true,
|
||||
showCPUFallbackMessage: true,
|
||||
showLoadingIndicator: true,
|
||||
// filterQueryParam: false,
|
||||
dataSources: [
|
||||
|
||||
@@ -6,6 +6,8 @@ window.config = {
|
||||
showStudyList: true,
|
||||
// below flag is for performance reasons, but it might not work for all servers
|
||||
omitQuotationForMultipartRequest: true,
|
||||
showWarningMessageForCrossOrigin: true,
|
||||
showCPUFallbackMessage: true,
|
||||
showLoadingIndicator: true,
|
||||
// filterQueryParam: false,
|
||||
dataSources: [
|
||||
|
||||
@@ -3,6 +3,8 @@ window.config = {
|
||||
showStudyList: true,
|
||||
// below flag is for performance reasons, but it might not work for all servers
|
||||
omitQuotationForMultipartRequest: true,
|
||||
showWarningMessageForCrossOrigin: true,
|
||||
showCPUFallbackMessage: true,
|
||||
showLoadingIndicator: true,
|
||||
servers: {
|
||||
dicomWeb: [
|
||||
|
||||
@@ -96,6 +96,11 @@ async function appInit(appConfigOrFunc, defaultExtensions, defaultModes) {
|
||||
appConfig.modes.push(mode);
|
||||
}
|
||||
|
||||
// remove modes that are not objects, or have no id
|
||||
appConfig.modes = appConfig.modes.filter(
|
||||
mode => typeof mode === 'object' && mode.id
|
||||
);
|
||||
|
||||
return {
|
||||
appConfig,
|
||||
commandsManager,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import useDebounce from './useDebounce.js';
|
||||
import useQuery from './useQuery.js';
|
||||
import useSearchParams from './useSearchParams.js';
|
||||
|
||||
export { useDebounce, useQuery };
|
||||
export { useDebounce, useQuery, useSearchParams };
|
||||
@@ -0,0 +1,17 @@
|
||||
import useQuery from './useQuery';
|
||||
|
||||
/**
|
||||
* It returns a Map of the query parameters in the URL, where the keys are
|
||||
* lowercase
|
||||
* @returns A function that returns a Map of the query parameters.
|
||||
*/
|
||||
export default function useSearchParams() {
|
||||
const query = useQuery();
|
||||
// make query params case-insensitive
|
||||
const searchParams = new Map();
|
||||
for (const [key, value] of query) {
|
||||
searchParams.set(key.toLowerCase(), value);
|
||||
}
|
||||
|
||||
return searchParams;
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import 'regenerator-runtime/runtime';
|
||||
import App from './App';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
/**
|
||||
* EXTENSIONS AND MODES
|
||||
* =================
|
||||
@@ -15,21 +14,23 @@ import ReactDOM from 'react-dom';
|
||||
* pluginImports.js imports all of the modes and extensions and adds them
|
||||
* to the window for processing.
|
||||
*/
|
||||
import loadDynamicImports from './pluginImports.js';
|
||||
import loadDynamicImports, { loadRuntimeImports } from './pluginImports.js';
|
||||
|
||||
loadDynamicImports().then(() => {
|
||||
/**
|
||||
* Combine our appConfiguration with installed extensions and modes.
|
||||
* In the future appConfiguration may contain modes added at runtime.
|
||||
* */
|
||||
const appProps = {
|
||||
config: window ? window.config : {},
|
||||
defaultExtensions: window.extensions,
|
||||
defaultModes: window.modes,
|
||||
};
|
||||
loadRuntimeImports(window.config).then(() => {
|
||||
/**
|
||||
* Combine our appConfiguration with installed extensions and modes.
|
||||
* In the future appConfiguration may contain modes added at runtime.
|
||||
* */
|
||||
const appProps = {
|
||||
config: window ? window.config : {},
|
||||
defaultExtensions: window.extensions,
|
||||
defaultModes: window.modes,
|
||||
};
|
||||
|
||||
/** Create App */
|
||||
const app = React.createElement(App, appProps, null);
|
||||
/** Render */
|
||||
ReactDOM.render(app, document.getElementById('root'));
|
||||
/** Create App */
|
||||
const app = React.createElement(App, appProps, null);
|
||||
/** Render */
|
||||
ReactDOM.render(app, document.getElementById('root'));
|
||||
});
|
||||
});
|
||||
@@ -3,9 +3,9 @@ import { useParams, useLocation } from 'react-router';
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
// TODO: DicomMetadataStore should be injected?
|
||||
import { DicomMetadataStore } from '@ohif/core';
|
||||
import { DicomMetadataStore, ServicesManager } from '@ohif/core';
|
||||
import { DragAndDropProvider, ImageViewerProvider } from '@ohif/ui';
|
||||
import { useQuery } from '@hooks';
|
||||
import { useQuery, useSearchParams } from '@hooks';
|
||||
import ViewportGrid from '@components/ViewportGrid';
|
||||
import Compose from './Compose';
|
||||
|
||||
@@ -20,7 +20,7 @@ import Compose from './Compose';
|
||||
*/
|
||||
function defaultRouteInit(
|
||||
{ servicesManager, studyInstanceUIDs, dataSource, filters },
|
||||
hangingProtocol
|
||||
hangingProtocolId
|
||||
) {
|
||||
const {
|
||||
displaySetService,
|
||||
@@ -86,7 +86,7 @@ function defaultRouteInit(
|
||||
// hanging protocol in the mode configuration
|
||||
hangingProtocolService.run(
|
||||
{ studies, activeStudy, displaySets },
|
||||
hangingProtocol
|
||||
hangingProtocolId
|
||||
);
|
||||
});
|
||||
|
||||
@@ -105,7 +105,9 @@ export default function ModeRoute({
|
||||
const location = useLocation();
|
||||
const query = useQuery();
|
||||
const params = useParams();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
const runTimeHangingProtocolId = searchParams.get('hangingprotocolid');
|
||||
const [studyInstanceUIDs, setStudyInstanceUIDs] = useState();
|
||||
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
@@ -121,7 +123,7 @@ export default function ModeRoute({
|
||||
const {
|
||||
displaySetService,
|
||||
hangingProtocolService,
|
||||
} = servicesManager.services;
|
||||
} = (servicesManager as ServicesManager).services;
|
||||
|
||||
const { extensions, sopClassHandlers, hotkeys, hangingProtocol } = mode;
|
||||
|
||||
@@ -249,22 +251,36 @@ export default function ModeRoute({
|
||||
extensionManager,
|
||||
commandsManager,
|
||||
});
|
||||
|
||||
// use the URL hangingProtocolId if it exists, otherwise use the one
|
||||
// defined in the mode configuration
|
||||
const hangingProtocolIdToUse = hangingProtocolService.getProtocolById(
|
||||
runTimeHangingProtocolId
|
||||
)
|
||||
? runTimeHangingProtocolId
|
||||
: hangingProtocol;
|
||||
|
||||
// Sets the active hanging protocols - if hangingProtocol is undefined,
|
||||
// resets to default. Done before the onModeEnter to allow the onModeEnter
|
||||
// to perform custom hanging protocol actions
|
||||
hangingProtocolService.setActiveProtocols(hangingProtocol);
|
||||
mode?.onModeEnter({ servicesManager, extensionManager, commandsManager });
|
||||
hangingProtocolService.setActiveProtocolIds(hangingProtocolIdToUse);
|
||||
|
||||
mode?.onModeEnter({
|
||||
servicesManager,
|
||||
extensionManager,
|
||||
commandsManager,
|
||||
});
|
||||
|
||||
const setupRouteInit = async () => {
|
||||
/**
|
||||
* The next line should get all the query parameters provided by the URL
|
||||
* - except the StudyInstaceUIDs - and create an object called filters
|
||||
* - except the StudyInstanceUIDs - and create an object called filters
|
||||
* used to filtering the study as the user wants otherwise it will return
|
||||
* a empty object.
|
||||
*
|
||||
* Example:
|
||||
* const filters = {
|
||||
* seriesInstaceUID: 1.2.276.0.7230010.3.1.3.1791068887.5412.1620253993.114611
|
||||
* seriesInstanceUID: 1.2.276.0.7230010.3.1.3.1791068887.5412.1620253993.114611
|
||||
* }
|
||||
*/
|
||||
const filters =
|
||||
@@ -272,7 +288,10 @@ export default function ModeRoute({
|
||||
(acc: Record<string, string>, val: string) => {
|
||||
if (val !== 'StudyInstanceUIDs') {
|
||||
if (['seriesInstanceUID', 'SeriesInstanceUID'].includes(val)) {
|
||||
return { ...acc, seriesInstanceUID: query.get(val) };
|
||||
return {
|
||||
...acc,
|
||||
seriesInstanceUID: query.get(val),
|
||||
};
|
||||
}
|
||||
|
||||
return { ...acc, [val]: query.get(val) };
|
||||
@@ -291,7 +310,7 @@ export default function ModeRoute({
|
||||
dataSource,
|
||||
filters,
|
||||
},
|
||||
hangingProtocol
|
||||
hangingProtocolIdToUse
|
||||
);
|
||||
}
|
||||
|
||||
@@ -302,7 +321,7 @@ export default function ModeRoute({
|
||||
dataSource,
|
||||
filters,
|
||||
},
|
||||
hangingProtocol
|
||||
hangingProtocolIdToUse
|
||||
);
|
||||
};
|
||||
|
||||
@@ -316,7 +335,10 @@ export default function ModeRoute({
|
||||
// information, and must be in a try/catch to ensure subscriptions
|
||||
// are unsubscribed.
|
||||
try {
|
||||
mode?.onModeExit?.({ servicesManager, extensionManager });
|
||||
mode?.onModeExit?.({
|
||||
servicesManager,
|
||||
extensionManager,
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn('mode exit failure', e);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { useTranslation } from 'react-i18next';
|
||||
//
|
||||
import filtersMeta from './filtersMeta.js';
|
||||
import { useAppConfig } from '@state';
|
||||
import { useDebounce, useQuery } from '@hooks';
|
||||
import { useDebounce, useSearchParams } from '@hooks';
|
||||
import { utils, hotkeys } from '@ohif/core';
|
||||
|
||||
import {
|
||||
@@ -54,10 +54,10 @@ function WorkList({
|
||||
// ~ Modes
|
||||
const [appConfig] = useAppConfig();
|
||||
// ~ Filters
|
||||
const query = useQuery();
|
||||
const searchParams = useSearchParams();
|
||||
const navigate = useNavigate();
|
||||
const STUDIES_LIMIT = 101;
|
||||
const queryFilterValues = _getQueryFilterValues(query);
|
||||
const queryFilterValues = _getQueryFilterValues(searchParams);
|
||||
const [filterValues, _setFilterValues] = useState({
|
||||
...defaultFilterValues,
|
||||
...queryFilterValues,
|
||||
@@ -503,7 +503,7 @@ const defaultFilterValues = {
|
||||
|
||||
function _tryParseInt(str, defaultValue) {
|
||||
let retValue = defaultValue;
|
||||
if (str !== null) {
|
||||
if (str != null) {
|
||||
if (str.length > 0) {
|
||||
if (!isNaN(str)) {
|
||||
retValue = parseInt(str);
|
||||
@@ -513,24 +513,24 @@ function _tryParseInt(str, defaultValue) {
|
||||
return retValue;
|
||||
}
|
||||
|
||||
function _getQueryFilterValues(query) {
|
||||
function _getQueryFilterValues(params) {
|
||||
const queryFilterValues = {
|
||||
patientName: query.get('patientName'),
|
||||
mrn: query.get('mrn'),
|
||||
patientName: params.get('patientname'),
|
||||
mrn: params.get('mrn'),
|
||||
studyDate: {
|
||||
startDate: query.get('startDate'),
|
||||
endDate: query.get('endDate'),
|
||||
startDate: params.get('startdate') || null,
|
||||
endDate: params.get('enddate') || null,
|
||||
},
|
||||
description: query.get('description'),
|
||||
modalities: query.get('modalities')
|
||||
? query.get('modalities').split(',')
|
||||
description: params.get('description'),
|
||||
modalities: params.get('modalities')
|
||||
? params.get('modalities').split(',')
|
||||
: [],
|
||||
accession: query.get('accession'),
|
||||
sortBy: query.get('sortBy'),
|
||||
sortDirection: query.get('sortDirection'),
|
||||
pageNumber: _tryParseInt(query.get('pageNumber'), undefined),
|
||||
resultsPerPage: _tryParseInt(query.get('resultsPerPage'), undefined),
|
||||
datasources: query.get('datasources'),
|
||||
accession: params.get('accession'),
|
||||
sortBy: params.get('sortby'),
|
||||
sortDirection: params.get('sortdirection'),
|
||||
pageNumber: _tryParseInt(params.get('pagenumber'), undefined),
|
||||
resultsPerPage: _tryParseInt(params.get('resultsperpage'), undefined),
|
||||
datasources: params.get('datasources'),
|
||||
};
|
||||
|
||||
// Delete null/undefined keys
|
||||
|
||||
Reference in new issue
Block a user