diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8d79f3d..423d425 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,7 +19,8 @@ jobs: matrix: platform: [ubuntu-latest, windows-latest, macos-latest] python-version: [3.8, 3.9, "3.10"] - + env: + DISPLAY: ':99.0' steps: - name: Checkout Repository uses: actions/checkout@v3 @@ -44,6 +45,8 @@ jobs: python -m pip install --upgrade pip python -m pip install setuptools pip install pytest + pip install pytest-qt + pip install pytest-xvfb pip install coverage pip install -e ".[testing]" working-directory: src/client diff --git a/src/client/dcp_client/gui/main_window.py b/src/client/dcp_client/gui/main_window.py index 76264d4..d4463c6 100644 --- a/src/client/dcp_client/gui/main_window.py +++ b/src/client/dcp_client/gui/main_window.py @@ -132,11 +132,11 @@ def on_item_inprogr_selected(self, item): def on_train_button_clicked(self): message_text = self.app.run_train() - create_warning_box(message_text) + _ = create_warning_box(message_text) def on_run_inference_button_clicked(self): message_text, message_title = self.app.run_inference() - create_warning_box(message_text, message_title) + _ = create_warning_box(message_text, message_title) def on_launch_napari_button_clicked(self): ''' @@ -144,7 +144,7 @@ def on_launch_napari_button_clicked(self): ''' if not self.app.cur_selected_img or '_seg.tiff' in self.app.cur_selected_img: message_text = "Please first select an image you wish to visualise. The selected image must be an original images, not a mask." - create_warning_box(message_text, message_title="Warning") + _ = create_warning_box(message_text, message_title="Warning") else: self.nap_win = NapariWindow(self.app) self.nap_win.show() diff --git a/src/client/dcp_client/gui/napari_window.py b/src/client/dcp_client/gui/napari_window.py index ecef6a8..e0451dd 100644 --- a/src/client/dcp_client/gui/napari_window.py +++ b/src/client/dcp_client/gui/napari_window.py @@ -56,7 +56,7 @@ def on_add_to_curated_button_clicked(self): ''' if self.app.cur_selected_path == str(self.app.train_data_path): message_text = "Image is already in the \'Curated data\' folder and should not be changed again" - utils.create_warning_box(message_text, message_title="Warning") + _ = utils.create_warning_box(message_text, message_title="Warning") return # take the name of the currently selected layer (by the user) @@ -64,7 +64,7 @@ def on_add_to_curated_button_clicked(self): # TODO if more than one item is selected this will break! if '_seg' not in cur_seg_selected: message_text = "Please select the segmenation you wish to save from the layer list" - utils.create_warning_box(message_text, message_title="Warning") + _ = utils.create_warning_box(message_text, message_title="Warning") return seg = self.viewer.layers[cur_seg_selected].data @@ -87,7 +87,7 @@ def on_add_to_inprogress_button_clicked(self): # TODO: Do we allow this? What if they moved it by mistake? User can always manually move from their folders?) if self.app.cur_selected_path == str(self.app.train_data_path): message_text = "Images from '\Curated data'\ folder can not be moved back to \'Curatation in progress\' folder." - utils.create_warning_box(message_text, message_title="Warning") + _ = utils.create_warning_box(message_text, message_title="Warning") return # take the name of the currently selected layer (by the user) @@ -95,7 +95,7 @@ def on_add_to_inprogress_button_clicked(self): # TODO if more than one item is selected this will break! if '_seg' not in cur_seg_selected: message_text = "Please select the segmenation you wish to save from the layer list" - utils.create_warning_box(message_text, message_title="Warning") + _ = utils.create_warning_box(message_text, message_title="Warning") return # Move original image diff --git a/src/client/dcp_client/gui/welcome_window.py b/src/client/dcp_client/gui/welcome_window.py index 8886d7e..5c385a5 100644 --- a/src/client/dcp_client/gui/welcome_window.py +++ b/src/client/dcp_client/gui/welcome_window.py @@ -129,15 +129,15 @@ def start_main(self): self.mw = MainWindow(self.app) else: message_text = "You need to specify a folder both for your uncurated and curated dataset (even if the curated folder is currently empty). Please go back and select folders for both." - create_warning_box(message_text, message_title="Warning") + _ = create_warning_box(message_text, message_title="Warning") def start_upload(self): message_text = ("Your current configurations are set to run some operations on the cloud. \n" "For this we need to upload your data to our server." "We will now upload your data. Click ok to continue. \n" "If you do not agree close the application and contact your software provider.") - create_warning_box(message_text, message_title="Warning") - self.app.upload_data_to_server() + usr_response = create_warning_box(message_text, message_title="Warning", add_cancel_btn=True) + if usr_response: self.app.upload_data_to_server() self.hide() self.mw = MainWindow(self.app) \ No newline at end of file diff --git a/src/client/dcp_client/utils/utils.py b/src/client/dcp_client/utils/utils.py index c7ca5bf..1bf1bc7 100644 --- a/src/client/dcp_client/utils/utils.py +++ b/src/client/dcp_client/utils/utils.py @@ -1,5 +1,5 @@ from PyQt5.QtWidgets import QFileIconProvider, QMessageBox -from PyQt5.QtCore import QSize +from PyQt5.QtCore import QSize, QTimer from PyQt5.QtGui import QPixmap, QIcon from pathlib import Path, PurePath @@ -23,13 +23,26 @@ def icon(self, type: 'QFileIconProvider.IconType'): else: return super().icon(type) -def create_warning_box(message_text, message_title="Warning"): - msg = QMessageBox() +def create_warning_box(message_text, message_title="Warning", add_cancel_btn=False, custom_dialog=None, sim=False): + #setup box + if custom_dialog is None: msg = QMessageBox() + else: msg = custom_dialog msg.setIcon(QMessageBox.Information) msg.setText(message_text) msg.setWindowTitle(message_title) - msg.setStandardButtons(QMessageBox.Ok) - msg.exec() + # if specified add a cancel button else only an ok + if add_cancel_btn: + msg.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) + # simulate button click if specified - workaround used for testing + if sim: QTimer.singleShot(0, msg.button(QMessageBox.Cancel).clicked) + else: + msg.setStandardButtons(QMessageBox.Ok) + # simulate button click if specified - workaround used for testing + if sim: QTimer.singleShot(0, msg.button(QMessageBox.Ok).clicked) + # return if user clicks Ok and False otherwise + usr_response = msg.exec() + if usr_response == QMessageBox.Ok: return True + else: return False def read_config(name, config_path = 'config.cfg') -> dict: """Reads the configuration file diff --git a/src/client/test/test_utils.py b/src/client/test/test_utils.py new file mode 100644 index 0000000..22e3b26 --- /dev/null +++ b/src/client/test/test_utils.py @@ -0,0 +1,46 @@ +import sys +sys.path.append("../") +from qtpy.QtTest import QTest +from qtpy.QtWidgets import QMessageBox +from qtpy.QtCore import Qt, QTimer +from dcp_client.utils import utils + +def test_get_relative_path(): + filepath = '/here/we/are/testing/something.txt' + assert utils.get_relative_path(filepath)== 'something.txt' + +def test_get_path_stem(): + filepath = '/here/we/are/testing/something.txt' + assert utils.get_path_stem(filepath)== 'something' + +def test_get_path_name(): + filepath = '/here/we/are/testing/something.txt' + assert utils.get_path_name(filepath)== 'something.txt' + +def test_get_path_parent(): + filepath = '/here/we/are/testing/something.txt' + assert utils.get_path_parent(filepath)== '/here/we/are/testing' + +def test_join_path(): + filepath = '/here/we/are/testing/something.txt' + path1 = '/here/we/are/testing' + path2 = 'something.txt' + assert utils.join_path(path1, path2) == filepath + +def test_create_warning_box_ok(qtbot): + result = None + def execute_warning_box(): + nonlocal result + box = QMessageBox() + result = utils.create_warning_box("Test Message", custom_dialog=box, sim=True) + qtbot.waitUntil(execute_warning_box, timeout=5000) + assert result is True + +def test_create_warning_box_cancel(qtbot): + result = None + def execute_warning_box(): + nonlocal result + box = QMessageBox() + result = utils.create_warning_box("Test Message", add_cancel_btn=True, custom_dialog=box, sim=True) + qtbot.waitUntil(execute_warning_box, timeout=5000) # Add a timeout for the function to execute + assert result is False \ No newline at end of file