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:
parent
ae4183852f
commit
926290f69e
@ -39,7 +39,7 @@ module.exports = (env, argv, { SRC_DIR, DIST_DIR }) => {
|
||||
|
||||
const config = {
|
||||
mode: isProdBuild ? 'production' : 'development',
|
||||
devtool: isProdBuild ? 'source-map' : 'eval-source-map',
|
||||
devtool: isProdBuild ? 'source-map' : 'cheap-module-source-map',
|
||||
entry: {
|
||||
app: `${SRC_DIR}/index.js`,
|
||||
},
|
||||
|
||||
@ -36,13 +36,13 @@
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-i18next": "^10.11.0",
|
||||
"react-router": "^6.3.0",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"react-router": "^6.8.1",
|
||||
"react-router-dom": "^6.8.1",
|
||||
"webpack": "^5.50.0",
|
||||
"webpack-merge": "^5.7.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.7.6",
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"react-color": "^2.19.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@ -43,7 +43,7 @@
|
||||
"react": "^17.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.16.3",
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"classnames": "^2.3.2",
|
||||
"@cornerstonejs/core": "0.30.1",
|
||||
"@cornerstonejs/tools": "0.39.0"
|
||||
|
||||
@ -42,7 +42,7 @@
|
||||
"lodash": "^4.17.21"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.17.9",
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "0.3.0",
|
||||
"@cornerstonejs/core": "0.30.1",
|
||||
"@cornerstonejs/streaming-image-volume-loader": "^0.11.2",
|
||||
|
||||
@ -77,7 +77,10 @@ export default async function init({
|
||||
|
||||
window.services = servicesManager.services;
|
||||
|
||||
if (!window.crossOriginIsolated) {
|
||||
if (
|
||||
appConfig.showWarningMessageForCrossOrigin &&
|
||||
!window.crossOriginIsolated
|
||||
) {
|
||||
uiNotificationService.show({
|
||||
title: 'Cross Origin Isolation',
|
||||
message:
|
||||
@ -86,7 +89,10 @@ export default async function init({
|
||||
});
|
||||
}
|
||||
|
||||
if (cornerstone.getShouldUseCPURendering()) {
|
||||
if (
|
||||
appConfig.showCPUFallbackMessage &&
|
||||
cornerstone.getShouldUseCPURendering()
|
||||
) {
|
||||
_showCPURenderingModal(uiModalService, hangingProtocolService);
|
||||
}
|
||||
|
||||
|
||||
@ -59,6 +59,7 @@ function callInputDialog(
|
||||
autoFocus
|
||||
className="mt-2 bg-black border-primary-main"
|
||||
type="text"
|
||||
id="annotation"
|
||||
containerClassName="mr-2"
|
||||
value={value.label}
|
||||
onChange={event => {
|
||||
|
||||
@ -58,26 +58,20 @@ export default function toggleMPRHangingProtocol({
|
||||
const viewportDisplaySetInstanceUIDs =
|
||||
viewports[activeViewportIndex].displaySetInstanceUIDs;
|
||||
|
||||
const errorCallback = error => {
|
||||
// Unable to create MPR, so be sure to return to the cached/original protocol.
|
||||
hangingProtocolService.setProtocol(
|
||||
cachedState.protocol.id,
|
||||
viewportMatchDetails,
|
||||
restoreErrorCallback
|
||||
);
|
||||
// What is the current active protocol and stage number to restore later
|
||||
const { protocol, stage } = hangingProtocolService.getActiveProtocol();
|
||||
|
||||
const restoreErrorCallback = error => {
|
||||
console.error(error);
|
||||
uiNotificationService.show({
|
||||
title: 'Multiplanar reconstruction (MPR) ',
|
||||
message:
|
||||
'Cannot create MPR for this DisplaySet since it is not reconstructable.',
|
||||
'Something went wrong while trying to restore the previous layout.',
|
||||
type: 'info',
|
||||
duration: 3000,
|
||||
});
|
||||
};
|
||||
|
||||
// What is the current active protocol and stage number to restore later
|
||||
const { protocol, stage } = hangingProtocolService.getActiveProtocol();
|
||||
|
||||
if (toggledState) {
|
||||
resetCachedState();
|
||||
|
||||
@ -109,6 +103,23 @@ export default function toggleMPRHangingProtocol({
|
||||
getToolGroup
|
||||
);
|
||||
|
||||
const errorCallback = error => {
|
||||
// Unable to create MPR, so be sure to return to the cached/original protocol.
|
||||
hangingProtocolService.setProtocol(
|
||||
cachedState.protocol.id,
|
||||
viewportMatchDetails,
|
||||
restoreErrorCallback
|
||||
);
|
||||
|
||||
uiNotificationService.show({
|
||||
title: 'Multiplanar reconstruction (MPR) ',
|
||||
message:
|
||||
'Cannot create MPR for this DisplaySet since it is not reconstructable.',
|
||||
type: 'info',
|
||||
duration: 3000,
|
||||
});
|
||||
};
|
||||
|
||||
hangingProtocolService.setProtocol(
|
||||
MPR_TOOLGROUP_ID,
|
||||
matchDetails,
|
||||
@ -117,16 +128,6 @@ export default function toggleMPRHangingProtocol({
|
||||
return;
|
||||
}
|
||||
|
||||
const restoreErrorCallback = error => {
|
||||
uiNotificationService.show({
|
||||
title: 'Multiplanar reconstruction (MPR) ',
|
||||
message:
|
||||
'Something went wrong while trying to restore the previous layout.',
|
||||
type: 'info',
|
||||
duration: 3000,
|
||||
});
|
||||
};
|
||||
|
||||
_disableCrosshairs([MPR_TOOLGROUP_ID], getToolGroup);
|
||||
|
||||
const { layoutType, properties } = cachedState.viewportStructure;
|
||||
|
||||
@ -42,7 +42,7 @@
|
||||
"webpack-merge": "^5.7.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.16.3",
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/calculate-suv": "^1.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
@ -176,6 +176,7 @@ export default function PanelMeasurementTable({
|
||||
<div className="p-4 bg-primary-dark">
|
||||
<Input
|
||||
autoFocus
|
||||
id="annotation"
|
||||
className="mt-2 bg-black border-primary-main"
|
||||
type="text"
|
||||
containerClassName="mr-2"
|
||||
|
||||
@ -37,7 +37,7 @@
|
||||
"react": "^17.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.7.6",
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"classnames": "^2.3.2"
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,7 +37,7 @@
|
||||
"react": "^17.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.7.6",
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"classnames": "^2.3.2"
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,7 +44,7 @@
|
||||
"@ohif/ui": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.16.3",
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@ohif/ui": "^2.0.0",
|
||||
"@xstate/react": "^0.8.1",
|
||||
"xstate": "^4.10.0"
|
||||
|
||||
@ -198,6 +198,7 @@ function PanelMeasurementTableTracking({ servicesManager, extensionManager }) {
|
||||
<div className="p-4 bg-primary-dark">
|
||||
<Input
|
||||
autoFocus
|
||||
id="annotation"
|
||||
className="mt-2 bg-black border-primary-main"
|
||||
type="text"
|
||||
containerClassName="mr-2"
|
||||
|
||||
8
extensions/test-extension/.webpack/webpack.dev.js
Normal file
8
extensions/test-extension/.webpack/webpack.dev.js
Normal file
@ -0,0 +1,8 @@
|
||||
const path = require('path');
|
||||
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
|
||||
const SRC_DIR = path.join(__dirname, '../src');
|
||||
const DIST_DIR = path.join(__dirname, '../dist');
|
||||
|
||||
module.exports = (env, argv) => {
|
||||
return webpackCommon(env, argv, { SRC_DIR, DIST_DIR });
|
||||
};
|
||||
43
extensions/test-extension/.webpack/webpack.prod.js
Normal file
43
extensions/test-extension/.webpack/webpack.prod.js
Normal file
@ -0,0 +1,43 @@
|
||||
const webpack = require('webpack');
|
||||
const merge = require('webpack-merge');
|
||||
const path = require('path');
|
||||
const webpackCommon = require('./../../../.webpack/webpack.base.js');
|
||||
const pkg = require('./../package.json');
|
||||
|
||||
const ROOT_DIR = path.join(__dirname, './..');
|
||||
const SRC_DIR = path.join(__dirname, '../src');
|
||||
const DIST_DIR = path.join(__dirname, '../dist');
|
||||
|
||||
module.exports = (env, argv) => {
|
||||
const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR });
|
||||
|
||||
return merge(commonConfig, {
|
||||
stats: {
|
||||
colors: true,
|
||||
hash: true,
|
||||
timings: true,
|
||||
assets: true,
|
||||
chunks: false,
|
||||
chunkModules: false,
|
||||
modules: false,
|
||||
children: false,
|
||||
warnings: true,
|
||||
},
|
||||
optimization: {
|
||||
minimize: true,
|
||||
sideEffects: true,
|
||||
},
|
||||
output: {
|
||||
path: ROOT_DIR,
|
||||
library: 'OHIFExtDICOMSR',
|
||||
libraryTarget: 'umd',
|
||||
libraryExport: 'default',
|
||||
filename: pkg.main,
|
||||
},
|
||||
plugins: [
|
||||
new webpack.optimize.LimitChunkCountPlugin({
|
||||
maxChunks: 1,
|
||||
}),
|
||||
],
|
||||
});
|
||||
};
|
||||
21
extensions/test-extension/LICENSE
Normal file
21
extensions/test-extension/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Open Health Imaging Foundation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
4
extensions/test-extension/README.md
Normal file
4
extensions/test-extension/README.md
Normal file
@ -0,0 +1,4 @@
|
||||
# Test Extension
|
||||
This extension will provide uitlities and module to help with testing. It is not intended to be used in production.
|
||||
For instance, we can use the hanging protocol module to inject a hanging protocol into the core and later
|
||||
use it to test the hanging protocol detection mechanism.
|
||||
1
extensions/test-extension/babel.config.js
Normal file
1
extensions/test-extension/babel.config.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('../../babel.config.js');
|
||||
43
extensions/test-extension/package.json
Normal file
43
extensions/test-extension/package.json
Normal file
@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "@ohif/extension-test",
|
||||
"version": "0.0.1",
|
||||
"description": "OHIF extension used inside e2e testing",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
"repository": "OHIF/Viewers",
|
||||
"main": "dist/index.umd.js",
|
||||
"module": "src/index.tsx",
|
||||
"engines": {
|
||||
"node": ">=14",
|
||||
"npm": ">=6",
|
||||
"yarn": ">=1.16.0"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"README.md"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --debug --output-pathinfo",
|
||||
"build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js",
|
||||
"build:package": "yarn run build",
|
||||
"start": "yarn run dev",
|
||||
"test:unit": "jest --watchAll",
|
||||
"test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "^3.0.0",
|
||||
"@ohif/ui": "^2.0.0",
|
||||
"dcmjs": "0.29.4",
|
||||
"dicom-parser": "^1.8.9",
|
||||
"hammerjs": "^2.0.8",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^17.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"classnames": "^2.3.2"
|
||||
}
|
||||
}
|
||||
5
extensions/test-extension/src/id.js
Normal file
5
extensions/test-extension/src/id.js
Normal file
@ -0,0 +1,5 @@
|
||||
import packageJson from '../package.json';
|
||||
|
||||
const id = packageJson.name;
|
||||
|
||||
export { id };
|
||||
17
extensions/test-extension/src/index.tsx
Normal file
17
extensions/test-extension/src/index.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import { id } from './id';
|
||||
import { Types } from '@ohif/core';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
const testExtension: Types.Extensions.Extension = {
|
||||
/**
|
||||
* Only required property. Should be a unique value across all extensions.
|
||||
*/
|
||||
id,
|
||||
preRegistration() {
|
||||
console.debug('hello from test-extension init.js');
|
||||
},
|
||||
};
|
||||
|
||||
export default testExtension;
|
||||
@ -37,7 +37,7 @@
|
||||
"react": "^17.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.7.6",
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"classnames": "^2.3.2"
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,13 +32,12 @@
|
||||
"@ohif/core": "^3.0.0",
|
||||
"@ohif/extension-default": "^3.0.0",
|
||||
"@ohif/extension-cornerstone": "^3.0.0",
|
||||
"@ohif/extension-cornerstone": "^3.0.0",
|
||||
"@ohif/extension-cornerstone-dicom-sr": "^3.0.0",
|
||||
"@ohif/extension-dicom-pdf": "^3.0.1",
|
||||
"@ohif/extension-dicom-video": "^3.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.13.10"
|
||||
"@babel/runtime": "^7.20.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"webpack": "^5.50.0",
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "mode-basic-test-mode",
|
||||
"name": "@ohif/mode-test",
|
||||
"version": "0.0.1",
|
||||
"description": "Basic mode for testing",
|
||||
"author": "OHIF",
|
||||
@ -38,10 +38,11 @@
|
||||
"@ohif/extension-cornerstone-dicom-sr": "^3.0.0",
|
||||
"@ohif/extension-dicom-pdf": "^3.0.1",
|
||||
"@ohif/extension-dicom-video": "^3.0.1",
|
||||
"@ohif/extension-measurement-tracking": "^3.0.0"
|
||||
"@ohif/extension-measurement-tracking": "^3.0.0",
|
||||
"@ohif/extension-test": "0.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.16.3"
|
||||
"@babel/runtime": "^7.20.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"webpack": "^5.50.0",
|
||||
|
||||
@ -54,6 +54,7 @@ const extensionDependencies = {
|
||||
'@ohif/extension-cornerstone-dicom-seg': '^3.0.0',
|
||||
'@ohif/extension-dicom-pdf': '^3.0.1',
|
||||
'@ohif/extension-dicom-video': '^3.0.1',
|
||||
'@ohif/extension-test': '^0.0.1',
|
||||
};
|
||||
|
||||
function modeFactory() {
|
||||
|
||||
@ -41,7 +41,7 @@
|
||||
"@ohif/extension-measurement-tracking": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.16.3"
|
||||
"@babel/runtime": "^7.20.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"webpack": "^5.50.0",
|
||||
|
||||
@ -41,7 +41,7 @@
|
||||
"@ohif/extension-measurement-tracking": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.16.3"
|
||||
"@babel/runtime": "^7.20.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"webpack": "^5.50.0",
|
||||
|
||||
@ -55,7 +55,7 @@
|
||||
"link-list": "npm ls --depth=0 --link=true"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.16.3",
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@kitware/vtk.js": "26.4.0",
|
||||
"core-js": "^3.2.1"
|
||||
},
|
||||
@ -124,7 +124,7 @@
|
||||
"start-server-and-test": "^1.10.0",
|
||||
"style-loader": "^1.0.0",
|
||||
"terser-webpack-plugin": "^5.1.4",
|
||||
"typescript": "^4.2.3",
|
||||
"typescript": "4.6.4",
|
||||
"unused-webpack-plugin": "2.4.0",
|
||||
"webpack": "^5.50.0",
|
||||
"webpack-cli": "^4.7.2",
|
||||
@ -146,6 +146,6 @@
|
||||
]
|
||||
},
|
||||
"resolutions": {
|
||||
"**/@babel/runtime": "7.16.3"
|
||||
"**/@babel/runtime": "^7.20.13"
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,26 +23,29 @@
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-i18next": "^10.11.0",
|
||||
"react-router": "^6.3.0",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"react-router": "^6.8.1",
|
||||
"react-router-dom": "^6.8.1",
|
||||
"webpack": "^5.50.0",
|
||||
"webpack-merge": "^5.7.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.7.6"
|
||||
"@babel/runtime": "^7.20.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.5.0",
|
||||
"@babel/plugin-proposal-class-properties": "^7.5.0",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.5.5",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
|
||||
"@babel/plugin-transform-arrow-functions": "^7.2.0",
|
||||
"@babel/plugin-transform-regenerator": "^7.4.5",
|
||||
"@babel/plugin-transform-runtime": "^7.5.0",
|
||||
"@babel/core": "^7.17.8",
|
||||
"@babel/plugin-proposal-class-properties": "^7.16.7",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.17.3",
|
||||
"@babel/plugin-proposal-private-methods": "^7.18.6",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/plugin-transform-arrow-functions": "^7.16.7",
|
||||
"@babel/plugin-transform-regenerator": "^7.16.7",
|
||||
"@babel/plugin-transform-runtime": "^7.17.0",
|
||||
"babel-plugin-inline-react-svg": "^2.0.1",
|
||||
"@babel/preset-env": "^7.5.0",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"@babel/plugin-transform-typescript": "^7.13.0",
|
||||
"@babel/preset-env": "^7.16.11",
|
||||
"@babel/preset-react": "^7.16.7",
|
||||
"@babel/preset-typescript": "^7.13.0",
|
||||
|
||||
"babel-eslint": "^8.0.3",
|
||||
"babel-loader": "^8.0.0-beta.4",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
|
||||
@ -20,19 +20,21 @@
|
||||
"@ohif/core": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.7.6"
|
||||
"@babel/runtime": "^7.20.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.5.0",
|
||||
"@babel/plugin-proposal-class-properties": "^7.5.0",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.5.5",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
|
||||
"@babel/plugin-transform-arrow-functions": "^7.2.0",
|
||||
"@babel/plugin-transform-regenerator": "^7.4.5",
|
||||
"@babel/plugin-transform-runtime": "^7.5.0",
|
||||
"@babel/preset-env": "^7.5.0",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"@babel/preset-typescript": "^7.17.12",
|
||||
"@babel/core": "^7.17.8",
|
||||
"@babel/plugin-proposal-class-properties": "^7.16.7",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.17.3",
|
||||
"@babel/plugin-proposal-private-methods": "^7.18.6",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/plugin-transform-arrow-functions": "^7.16.7",
|
||||
"@babel/plugin-transform-regenerator": "^7.16.7",
|
||||
"@babel/plugin-transform-runtime": "^7.17.0",
|
||||
"@babel/plugin-transform-typescript": "^7.13.0",
|
||||
"@babel/preset-env": "^7.16.11",
|
||||
"@babel/preset-react": "^7.16.7",
|
||||
"@babel/preset-typescript": "^7.13.0",
|
||||
"babel-eslint": "^8.0.3",
|
||||
"babel-loader": "^8.0.0-beta.4",
|
||||
"babel-plugin-inline-react-svg": "^2.0.1",
|
||||
|
||||
@ -36,7 +36,7 @@
|
||||
"@ohif/ui": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.16.3",
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"dcmjs": "0.29.4",
|
||||
"dicomweb-client": "^0.6.0",
|
||||
"isomorphic-base64": "^1.0.2",
|
||||
|
||||
@ -35,6 +35,7 @@ export interface ExtensionParams extends ExtensionConstructor {
|
||||
* have more values than this.
|
||||
*/
|
||||
export interface Extension {
|
||||
id: string;
|
||||
preRegistration?: (p: ExtensionParams) => void;
|
||||
}
|
||||
|
||||
@ -160,11 +161,18 @@ export default class ExtensionManager {
|
||||
// but currently since some extensions need to be registered before
|
||||
// others, we need to run them sequentially. We need a postInit hook
|
||||
// to avoid this sequential async registration
|
||||
for (const extension of extensions) {
|
||||
for (let i = 0; i < extensions.length; i++) {
|
||||
const extension = extensions[i];
|
||||
const hasConfiguration = Array.isArray(extension);
|
||||
try {
|
||||
if (hasConfiguration) {
|
||||
const [ohifExtension, configuration] = extension;
|
||||
// Important: for some reason in the line below the type
|
||||
// of extension is not recognized as [ExtensionRegister,
|
||||
// ExtensionConfiguration] by babel DON"T CHANGE IT
|
||||
// Same for the for loop above don't use
|
||||
// for (const extension of extensions)
|
||||
const ohifExtension = extension[0];
|
||||
const configuration = extension[1];
|
||||
await this.registerExtension(
|
||||
ohifExtension,
|
||||
configuration,
|
||||
|
||||
@ -185,7 +185,11 @@ class HangingProtocolService {
|
||||
* @param protocolId - the id of the protocol
|
||||
* @returns protocol - the protocol with the given id
|
||||
*/
|
||||
public getProtocolById(id: string): HangingProtocol.Protocol {
|
||||
public getProtocolById(id: string): HangingProtocol.Protocol | undefined {
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const protocol = this.protocols.get(id);
|
||||
|
||||
if (protocol instanceof Function) {
|
||||
@ -233,7 +237,7 @@ class HangingProtocolService {
|
||||
* If active protocols ids is null right now, then the specified
|
||||
* protocol will become the only active protocol.
|
||||
*/
|
||||
public addActiveProtocol(id: string): void {
|
||||
public addActiveProtocolId(id: string): void {
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
@ -247,17 +251,17 @@ class HangingProtocolService {
|
||||
* Sets the active hanging protocols to use, by name. If the value is empty,
|
||||
* then resets the active protocols to all the named items.
|
||||
*/
|
||||
public setActiveProtocols(hangingProtocol?: string[] | string): void {
|
||||
if (!hangingProtocol || !hangingProtocol.length) {
|
||||
public setActiveProtocolIds(protocolId?: string[] | string): void {
|
||||
if (!protocolId || !protocolId.length) {
|
||||
this.activeProtocolIds = null;
|
||||
console.log('No active protocols, setting all to active');
|
||||
return;
|
||||
}
|
||||
if (typeof hangingProtocol === 'string') {
|
||||
this.setActiveProtocols([hangingProtocol]);
|
||||
if (typeof protocolId === 'string') {
|
||||
this.setActiveProtocolIds([protocolId]);
|
||||
return;
|
||||
}
|
||||
this.activeProtocolIds = [...hangingProtocol];
|
||||
this.activeProtocolIds = [...protocolId];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -272,7 +276,6 @@ class HangingProtocolService {
|
||||
* @param params.displaySets is the list of display sets associated with
|
||||
* the studies to display in viewports.
|
||||
* @param protocol is a specific protocol to apply.
|
||||
* @returns
|
||||
*/
|
||||
public run({ studies, displaySets, activeStudy }, protocolId) {
|
||||
this.studies = [...studies];
|
||||
|
||||
@ -36,7 +36,7 @@
|
||||
"react-i18next": "^10.11.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.16.3",
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"i18next-locize-backend": "^2.0.0",
|
||||
"locize-editor": "^2.0.0",
|
||||
"locize-lastused": "^1.1.0"
|
||||
|
||||
@ -57,6 +57,7 @@ const Input = ({
|
||||
};
|
||||
|
||||
Input.propTypes = {
|
||||
id: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
containerClassName: PropTypes.string,
|
||||
labelClassName: PropTypes.string,
|
||||
@ -70,7 +71,7 @@ Input.propTypes = {
|
||||
readOnly: PropTypes.bool,
|
||||
onKeyPress: PropTypes.func,
|
||||
onKeyDown: PropTypes.func,
|
||||
disabled: PropTypes.bool
|
||||
disabled: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default Input;
|
||||
|
||||
@ -32,6 +32,7 @@ const Modal = ({
|
||||
<Typography
|
||||
variant="h4"
|
||||
className="flex grow text-primary-light font-light"
|
||||
data-cy="modal-header"
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
|
||||
@ -79,6 +79,7 @@ const Select = ({
|
||||
className,
|
||||
'flex flex-col flex-1 customSelect__wrapper'
|
||||
)}
|
||||
data-cy={`input-${id}`}
|
||||
classNamePrefix="customSelect"
|
||||
isDisabled={isDisabled}
|
||||
isClearable={isClearable}
|
||||
|
||||
@ -95,7 +95,11 @@ const ThumbnailTracked = ({
|
||||
>
|
||||
<Icon name={trackedIcon} className="w-4 mb-2 text-primary-light" />
|
||||
</Tooltip>
|
||||
<div className="text-xl leading-tight text-white text-center">
|
||||
|
||||
<div
|
||||
className="text-xl leading-tight text-white text-center"
|
||||
data-cy={'thumbnail-viewport-labels'}
|
||||
>
|
||||
{renderViewportLabels()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -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,
|
||||
|
||||
40
platform/viewer/cypress.config.ts
Normal file
40
platform/viewer/cypress.config.ts
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
6
platform/viewer/cypress/.eslintrc.js
Normal file
6
platform/viewer/cypress/.eslintrc.js
Normal file
@ -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);
|
||||
|
||||
@ -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,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', () => {
|
||||
|
||||
@ -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');
|
||||
});
|
||||
*/
|
||||
});
|
||||
@ -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];
|
||||
|
||||
|
||||
139
platform/viewer/cypress/integration/volume/MPR.spec.js
Normal file
139
platform/viewer/cypress/integration/volume/MPR.spec.js
Normal file
@ -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 };
|
||||
|
||||
17
platform/viewer/src/hooks/useSearchParams.js
Normal file
17
platform/viewer/src/hooks/useSearchParams.js
Normal file
@ -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
|
||||
|
||||
2
testdata
2
testdata
@ -1 +1 @@
|
||||
Subproject commit 1bca96aa2aeae7d90b3c8db73845c85dd27b34d5
|
||||
Subproject commit 4d59660c2883ed749a680e5fb6d4624ab54c9422
|
||||
@ -3,7 +3,8 @@
|
||||
"baseUrl": "./",
|
||||
"checkJs": true,
|
||||
// Ensure that Babel can safely transpile files in the TypeScript project
|
||||
"isolatedModules": false,
|
||||
"isolatedModules": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"jsx": "react",
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
@ -12,14 +13,16 @@
|
||||
"moduleResolution": "node",
|
||||
"outDir": "./dist",
|
||||
"paths": {
|
||||
"@ohif/core": [
|
||||
"platform/core/src"
|
||||
],
|
||||
"@ohif/core": ["platform/core/src"],
|
||||
"@ohif/ui": ["platform/ui/src"],
|
||||
"@ohif/i18n": ["platform/i18n/src"],
|
||||
"@ohif/viewer": ["platform/viewer/src"]
|
||||
}
|
||||
},
|
||||
"include": ["platform/**/src/**/*", "extensions/**/src/**/*", "modes/**/src/**/*"],
|
||||
"include": [
|
||||
"platform/**/src/**/*",
|
||||
"extensions/**/src/**/*",
|
||||
"modes/**/src/**/*"
|
||||
],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user