diff --git a/activity_browser/actions/__init__.py b/activity_browser/actions/__init__.py index a4035dea9..e80a3aa0a 100644 --- a/activity_browser/actions/__init__.py +++ b/activity_browser/actions/__init__.py @@ -28,7 +28,8 @@ from .calculation_setup.cs_open import CSOpen from .database.database_open import DatabaseOpen -from .database.database_export import DatabaseExport +from .database.database_export_excel import DatabaseExportExcel +from .database.database_export_bw2package import DatabaseExportBW2Package from .database.database_new import DatabaseNew from .database.database_delete import DatabaseDelete from .database.database_duplicate import DatabaseDuplicate diff --git a/activity_browser/actions/database/database_delete.py b/activity_browser/actions/database/database_delete.py index 26e6d4a9d..2e88505ac 100644 --- a/activity_browser/actions/database/database_delete.py +++ b/activity_browser/actions/database/database_delete.py @@ -1,3 +1,5 @@ +from typing import List + from qtpy import QtCore, QtWidgets import bw2data as bd @@ -12,72 +14,87 @@ class DatabaseDelete(ABAction): """ - Deletes a specified database from the project after user confirmation. + Deletes one or more databases from the project after user confirmation. This method performs the following steps: - - Displays a confirmation dialog to the user with the database name and record count. - - If the user confirms, deletes the database, its upstream exchanges, and associated parameters. - - Removes the database from the project settings. + - Displays a confirmation dialog to the user with the database name(s) and total record count. + - If the user confirms, deletes the database(s), their upstream exchanges, and associated parameters. + - Removes the database(s) from the project settings. Args: - db_name (str): The name of the database to be deleted. + db_names (List[str]): The name(s) of the database(s) to be deleted. Steps: - Set the cursor to a waiting state while gathering data for large databases. - - Retrieve the record count for the specified database. - - Construct a warning message with the database name and record count. + - Retrieve the record count for the specified database(s). + - Construct a warning message with the database name(s) and record count. - Display a confirmation dialog to the user. - If the user cancels, exit the method. - Set the cursor to a waiting state while performing the deletion. - - Delete upstream exchanges associated with the database. - - Remove the database from the Brightway2 project. + - Delete upstream exchanges associated with the database(s). + - Remove the database(s) from the Brightway2 project. - Delete database parameters. - - Remove the database from the project settings. + - Remove the database(s) from the project settings. - Restore the cursor to its default state. """ icon = qicons.delete - text = "Delete database" - tool_tip = "Delete this database from the project" + text = "Delete databases" + tool_tip = "Delete database(s) from the project" @staticmethod @exception_dialogs - def run(db_name: str): + def run(db_names: List[str]): # gathering data will take time for large databases QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - # get the record count from the database controller - db_name = db_name - n_records = AB_metadata.dataframe[AB_metadata.dataframe["database"] == db_name].shape[0] + # get the total record count from all databases + total_records = 0 + for db_name in db_names: + n_records = AB_metadata.dataframe[AB_metadata.dataframe["database"] == db_name].shape[0] + total_records += n_records # construct warning text - text = f"Are you sure you want to delete database '{db_name}'?" - if n_records: - text += f" It contains {n_records} activities" + if len(db_names) == 1: + text = f"Are you sure you want to delete database '{db_names[0]}'?" + if total_records: + text += f" It contains {total_records} activities." + else: + text = f"Are you sure you want to delete {len(db_names)} databases?" + if total_records: + text += f" They contain {total_records} activities in total." # ask the user for confirmation QtWidgets.QApplication.restoreOverrideCursor() response = QtWidgets.QMessageBox.question( - application.main_window, "Delete database?", text + application.main_window, build_title(db_names), text ) # return if the user cancels - if response != response.Yes: + if response != QtWidgets.QMessageBox.Yes: return # deleting data will take time for large databases QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - # delete upstream exchanges - ExchangeDataset.delete().where(ExchangeDataset.input_database == db_name).execute() + for db_name in db_names: + # delete upstream exchanges + ExchangeDataset.delete().where(ExchangeDataset.input_database == db_name).execute() - # instruct the DatabaseController to delete the database from the project. - del bd.databases[db_name] + # instruct the DatabaseController to delete the database from the project. + del bd.databases[db_name] - # delete database parameters - Group.delete().where(Group.name == db_name).execute() + # delete database parameters + Group.delete().where(Group.name == db_name).execute() - # remove database from project settings - settings.project_settings.remove_db(db_name) + # remove database from project settings + settings.project_settings.remove_db(db_name) QtWidgets.QApplication.restoreOverrideCursor() + + +def build_title(db_names: List[str]) -> str: + """Build an appropriate title for the confirmation dialog.""" + if len(db_names) == 1: + return "Delete database?" + return "Delete databases?" diff --git a/activity_browser/actions/database/database_export.py b/activity_browser/actions/database/database_export.py deleted file mode 100644 index 928710e5a..000000000 --- a/activity_browser/actions/database/database_export.py +++ /dev/null @@ -1,20 +0,0 @@ -from qtpy import QtWidgets - -from activity_browser import application -from activity_browser.actions.base import ABAction, exception_dialogs -from activity_browser.ui.wizards.db_export_wizard import DatabaseExportWizard - - -class DatabaseExport(ABAction): - """ - ABAction to open the DatabaseExportWizard. - """ - - icon = application.style().standardIcon(QtWidgets.QStyle.SP_DriveHDIcon) - text = "Export database..." - tool_tip = "Export a database from this project" - - @staticmethod - @exception_dialogs - def run(): - DatabaseExportWizard(application.main_window).show() diff --git a/activity_browser/actions/database/database_export_bw2package.py b/activity_browser/actions/database/database_export_bw2package.py new file mode 100644 index 000000000..354e11e85 --- /dev/null +++ b/activity_browser/actions/database/database_export_bw2package.py @@ -0,0 +1,100 @@ +from logging import getLogger +from typing import List + +from qtpy import QtWidgets + +from activity_browser import application +from activity_browser.actions.base import ABAction, exception_dialogs +from activity_browser.ui import widgets, threading +from activity_browser.bwutils import exporters + +log = getLogger(__name__) + + +class DatabaseExportBW2Package(ABAction): + """ + ABAction to export database(s) to BW2Package format (.bw2package). + """ + + # icon = icons.qicons.export_db + text = "Export to .bw2package" + tool_tip = "Export database(s) to BW2Package format" + + @classmethod + @exception_dialogs + def run(cls, db_names: List[str] = None): + if db_names is None: + import bw2data as bd + dialog = widgets.ABDatabaseSelectionDialog( + parent=application.main_window, + databases=sorted(bd.databases), + title="Select databases to export to BW2Package" + ) + if dialog.exec_() == QtWidgets.QDialog.Accepted: + db_names = dialog.get_selected_databases() + else: + return + + # Get export directory or file from user + if len(db_names) == 1: + # Single database - suggest a filename + suggested_name = f"{db_names[0]}.bw2package" + path, _ = QtWidgets.QFileDialog.getSaveFileName( + parent=application.main_window, + caption=f'Export database "{db_names[0]}" to BW2Package', + directory=suggested_name, + filter='Brightway2 Database Package (*.bw2package);; All files (*.*)' + ) + else: + # Multiple databases - ask for directory + path = QtWidgets.QFileDialog.getExistingDirectory( + parent=application.main_window, + caption=f'Select directory to export {len(db_names)} databases', + ) + + if not path: + return + + # Show export dialog + context = { + "db_names": db_names, + "path": path, + } + export_dialog = ExportBW2PackageSetup( + parent=application.main_window, + title="Export to BW2Package", + context=context + ) + export_dialog.show() + + +class ExportBW2PackageSetup(widgets.ABWizard): + """Wizard for exporting databases to BW2Package format.""" + + class ExportPage(widgets.ABThreadedWizardPage): + """Wizard page to export the selected database(s) to BW2Package.""" + title = "Exporting Database(s)" + subtitle = "Exporting database(s) to .bw2package file(s)" + + class Thread(threading.ABThread): + """Thread to handle the export process.""" + + def run_safely(self, db_names: List[str], path: str): + """Export the database(s) to BW2Package.""" + for db_name in db_names: + try: + success = exporters.store_database_as_package(db_name, path) + if success: + log.info(f"Successfully exported database '{db_name}' to BW2Package") + else: + log.error(f"Failed to export database '{db_name}'") + raise RuntimeError(f"Database '{db_name}' not found") + except Exception as e: + log.error(f"Failed to export database '{db_name}': {e}") + raise + + def initializePage(self, context: dict): + """Start the export thread.""" + self.thread.start(context["db_names"], context["path"]) + + pages = [ExportPage] diff --git a/activity_browser/actions/database/database_export_excel.py b/activity_browser/actions/database/database_export_excel.py new file mode 100644 index 000000000..dcc81ed75 --- /dev/null +++ b/activity_browser/actions/database/database_export_excel.py @@ -0,0 +1,97 @@ +from logging import getLogger +from typing import List + +from qtpy import QtWidgets + +from activity_browser import application +from activity_browser.actions.base import ABAction, exception_dialogs +from activity_browser.ui import widgets, threading +from activity_browser.bwutils import exporters + + +log = getLogger(__name__) + + +class DatabaseExportExcel(ABAction): + """ + ABAction to export database(s) to Excel format (.xlsx). + """ + + icon = application.style().standardIcon(QtWidgets.QStyle.SP_DriveHDIcon) + text = "Export to Excel (.xlsx)" + tool_tip = "Export database(s) to Excel format" + + @classmethod + @exception_dialogs + def run(cls, db_names: List[str] = None): + if db_names is None: + import bw2data as bd + dialog = widgets.ABDatabaseSelectionDialog( + parent=application.main_window, + databases=sorted(bd.databases), + title="Select databases to export to Excel" + ) + if dialog.exec_() == QtWidgets.QDialog.Accepted: + db_names = dialog.get_selected_databases() + else: + return + + # Get export directory or file from user + if len(db_names) == 1: + # Single database - suggest a filename + suggested_name = f"lci-{db_names[0]}.xlsx" + path, _ = QtWidgets.QFileDialog.getSaveFileName( + parent=application.main_window, + caption=f'Export database "{db_names[0]}" to Excel', + directory=suggested_name, + filter='Excel spreadsheet (*.xlsx);; All files (*.*)' + ) + else: + # Multiple databases - ask for directory + path = QtWidgets.QFileDialog.getExistingDirectory( + parent=application.main_window, + caption=f'Select directory to export {len(db_names)} databases', + ) + + if not path: + return + + # Show export dialog + context = { + "db_names": db_names, + "path": path, + } + export_dialog = ExportExcelSetup( + parent=application.main_window, + title="Export to Excel", + context=context + ) + export_dialog.show() + + +class ExportExcelSetup(widgets.ABWizard): + """Wizard for exporting databases to Excel format.""" + + class ExportPage(widgets.ABThreadedWizardPage): + """Wizard page to export the selected database(s) to Excel.""" + title = "Exporting Database(s)" + subtitle = "Exporting database(s) to Excel file(s)" + + class Thread(threading.ABThread): + """Thread to handle the export process.""" + + def run_safely(self, db_names: List[str], path: str): + """Export the database(s) to Excel.""" + for db_name in db_names: + try: + exporters.write_lci_excel(db_name, path) + log.info(f"Successfully exported database '{db_name}' to Excel") + except Exception as e: + log.error(f"Failed to export database '{db_name}': {e}") + raise + + def initializePage(self, context: dict): + """Start the export thread.""" + self.thread.start(context["db_names"], context["path"]) + + pages = [ExportPage] diff --git a/activity_browser/layouts/panes/database_products.py b/activity_browser/layouts/panes/database_products.py index c4e30b8b5..8bcccb59e 100644 --- a/activity_browser/layouts/panes/database_products.py +++ b/activity_browser/layouts/panes/database_products.py @@ -53,7 +53,7 @@ def __init__(self, parent, db_name: str): self.model = ProductModel(self) # Create the QTableView and set the model - self.table_view = ProductView(self) + self.table_view = ProductView(self, db_name=db_name) self.table_view.setModel(self.model) self.model.setDataFrame(self.build_df()) @@ -255,27 +255,27 @@ class ContextMenu(ui.widgets.ABMenu): enable=len(p.selected_activities) > 0, ), lambda m: m.addSeparator(), - lambda m, p: m.add(actions.ActivityNewProcess, m.database_name, - enable=not database_is_locked(m.database_name), + lambda m, p: m.add(actions.ActivityNewProcess, p.db_name, + enable=not database_is_locked(p.db_name), ), lambda m, p: m.add(actions.ActivityDuplicate, p.selected_activities, text="Duplicate process" if len(p.selected_activities) == 1 else "Duplicate processes", - enable=len(p.selected_activities) > 0 and not database_is_locked(m.database_name), + enable=len(p.selected_activities) > 0 and not database_is_locked(p.db_name), ), lambda m, p: m.add(actions.ActivityDuplicateToDB, p.selected_activities, text="Move process" if len(p.selected_activities) == 1 else "Move processes", - enable=len(p.selected_activities) > 0 and not database_is_locked(m.database_name), + enable=len(p.selected_activities) > 0 and not database_is_locked(p.db_name), ), lambda m: m.addSeparator(), lambda m, p: m.add(actions.ActivityDelete, p.selected_activities, text="Delete process" if len(p.selected_activities) == 1 else "Delete processes", - enable=len(p.selected_activities) > 0 and not database_is_locked(m.database_name), + enable=len(p.selected_activities) > 0 and not database_is_locked(p.db_name), ), lambda m, p: m.add(actions.ActivityDelete, p.selected_products, text="Delete product" if len(p.selected_products) == 1 else "Delete products", enable=len(p.selected_products) > 0 and not - database_is_locked(m.database_name) and not - database_is_legacy(m.database_name), + database_is_locked(p.db_name) and not + database_is_legacy(p.db_name), ), lambda m: m.addSeparator(), lambda m, p: m.add(actions.CSNew, @@ -295,16 +295,13 @@ def get_functional_unit_amount(key): exc = excs[0] if len(excs) == 1 else {} return exc.get("amount", 1.0) - @property - def database_name(self): - return self.parent().parent().database.name - - def __init__(self, parent: DatabaseProductsPane): + def __init__(self, parent: DatabaseProductsPane, db_name: str): """ Initializes the ProductView. Args: parent (DatabaseProductsPane): The parent widget. + db_name (str): The name of the database. """ super().__init__(parent) self.setSortingEnabled(True) @@ -313,6 +310,8 @@ def __init__(self, parent: DatabaseProductsPane): self.setSelectionBehavior(ui.widgets.ABTreeView.SelectionBehavior.SelectRows) self.setSelectionMode(ui.widgets.ABTreeView.SelectionMode.ExtendedSelection) + self.db_name = db_name + self.propertyDelegate = delegates.PropertyDelegate(self) def setDefaultColumnDelegates(self): diff --git a/activity_browser/layouts/panes/databases.py b/activity_browser/layouts/panes/databases.py index 622ed73d8..10af03971 100644 --- a/activity_browser/layouts/panes/databases.py +++ b/activity_browser/layouts/panes/databases.py @@ -108,13 +108,29 @@ class DatabasesView(widgets.ABTreeView): "modified": delegates.DateTimeDelegate, } + class ExportDatabaseContextMenu(widgets.ABMenu): + menuSetup = [ + lambda m: m.setTitle("Export database" if len(m.parent().selected_databases) == 1 else "Export databases"), + lambda m, p: m.add(actions.DatabaseExportExcel, p.selected_databases if p.selected_databases else [], + enable=len(p.selected_databases) >= 1, + text="to .xlsx", + ), + lambda m, p: m.add(actions.DatabaseExportBW2Package, p.selected_databases if p.selected_databases else [], + enable=len(p.selected_databases) >= 1, + text="to .bw2package", + ), + ] + class ContextMenu(widgets.ABMenu): menuSetup = [ lambda m, p: m.add(actions.DatabaseNew), lambda m: m.addMenu(ImportDatabaseMenu(m)), + lambda m, p: m.addMenu(DatabasesView.ExportDatabaseContextMenu(parent=p)), lambda m: m.addSeparator(), - lambda m, p: m.add(actions.DatabaseDelete, p.selected_databases[0] if p.selected_databases else None, - enable=len(p.selected_databases) == 1), + lambda m, p: m.add(actions.DatabaseDelete, p.selected_databases if p.selected_databases else [], + enable=len(p.selected_databases) >= 1, + text="Delete databases" if len(p.selected_databases) > 1 else "Delete database", + ), lambda m, p: m.add(actions.DatabaseDuplicate, p.selected_databases[0] if p.selected_databases else None, enable=len(p.selected_databases) == 1), lambda m, p: m.add(actions.DatabaseProcess, p.selected_databases[0] if p.selected_databases else None, @@ -168,6 +184,20 @@ def mouseDoubleClickEvent(self, event: QtGui.QMouseEvent): actions.DatabaseOpen.run([db_name]) + def keyPressEvent(self, event: QtGui.QKeyEvent): + """ + Handles key press events. Specifically handles the Delete key to delete selected databases. + + Args: + event (QtGui.QKeyEvent): The key press event. + """ + if event.key() == Qt.Key_Delete: + if self.selected_databases: + actions.DatabaseDelete.run(self.selected_databases) + return + + super().keyPressEvent(event) + @property def selected_databases(self) -> list: """ diff --git a/activity_browser/ui/menu_bar.py b/activity_browser/ui/menu_bar.py index cd99df9a9..a70eafb3d 100644 --- a/activity_browser/ui/menu_bar.py +++ b/activity_browser/ui/menu_bar.py @@ -46,8 +46,6 @@ def __init__(self, parent=None) -> None: self.import_proj_action = actions.ProjectImport.get_QAction() self.export_proj_action = actions.ProjectExport.get_QAction() - self.export_db_action = actions.DatabaseExport.get_QAction() - self.manage_settings_action = actions.SettingsWizardOpen.get_QAction() self.manage_projects_action = actions.ProjectManagerOpen.get_QAction() @@ -60,7 +58,7 @@ def __init__(self, parent=None) -> None: self.addAction(self.export_proj_action) self.addSeparator() self.addMenu(ImportDatabaseMenu(self)) - self.addAction(self.export_db_action) + self.addMenu(ExportDatabaseMenu(self)) self.addSeparator() self.addMenu(ImportICMenu(self)) self.addSeparator() @@ -286,6 +284,21 @@ def __init__(self, parent=None) -> None: self.addAction(self.import_from_ecoinvent_action) +class ExportDatabaseMenu(QtWidgets.QMenu): + def __init__(self, parent=None) -> None: + super().__init__(parent=parent) + self.setTitle("Export database") + + self.export_to_excel_action = actions.DatabaseExportExcel.get_QAction() + self.export_to_bw2package_action = actions.DatabaseExportBW2Package.get_QAction() + + self.export_to_excel_action.setText("to .xlsx") + self.export_to_bw2package_action.setText("to .bw2package") + + self.addAction(self.export_to_excel_action) + self.addAction(self.export_to_bw2package_action) + + class ImportICMenu(QtWidgets.QMenu): def __init__(self, parent=None) -> None: super().__init__(parent=parent) diff --git a/activity_browser/ui/widgets/__init__.py b/activity_browser/ui/widgets/__init__.py index 9031f6e35..d92fe1773 100644 --- a/activity_browser/ui/widgets/__init__.py +++ b/activity_browser/ui/widgets/__init__.py @@ -22,4 +22,5 @@ from .central import CentralTabWidget from .menu import ABMenu from .list_edit_dialog import ABListEditDialog -from .drop_overlay import ABDropOverlay \ No newline at end of file +from .drop_overlay import ABDropOverlay +from .database_selection_dialog import ABDatabaseSelectionDialog diff --git a/activity_browser/ui/widgets/database_selection_dialog.py b/activity_browser/ui/widgets/database_selection_dialog.py new file mode 100644 index 000000000..ab3c0af26 --- /dev/null +++ b/activity_browser/ui/widgets/database_selection_dialog.py @@ -0,0 +1,35 @@ +from typing import List + +from qtpy import QtWidgets + + +class ABDatabaseSelectionDialog(QtWidgets.QDialog): + """Dialog to select one or more databases for export.""" + + def __init__(self, parent=None, databases=None, title="Select databases"): + super().__init__(parent=parent) + self.setWindowTitle(title) + self.setModal(True) + self.resize(400, 300) + + layout = QtWidgets.QVBoxLayout(self) + + self.db_list_widget = QtWidgets.QListWidget(self) + self.db_list_widget.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection) + for db_name in databases: + item = QtWidgets.QListWidgetItem(db_name) + self.db_list_widget.addItem(item) + layout.addWidget(self.db_list_widget) + + button_box = QtWidgets.QDialogButtonBox( + QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel, + parent=self + ) + button_box.accepted.connect(self.accept) + button_box.rejected.connect(self.reject) + layout.addWidget(button_box) + + def get_selected_databases(self) -> List[str]: + """Return the list of selected database names.""" + selected_items = self.db_list_widget.selectedItems() + return [item.text() for item in selected_items] diff --git a/activity_browser/ui/widgets/menu.py b/activity_browser/ui/widgets/menu.py index 329e7a7e8..52f332749 100644 --- a/activity_browser/ui/widgets/menu.py +++ b/activity_browser/ui/widgets/menu.py @@ -7,7 +7,7 @@ class ABMenu(QtWidgets.QMenu): menuSetup: list[Callable[["ABMenu", Optional[QtWidgets.QWidget]], None]] title: str = None - def __init__(self, pos, parent=None): + def __init__(self, pos=None, parent=None, title: str = None): super().__init__(parent) for item in self.menuSetup: diff --git a/tests/actions/test_database_actions.py b/tests/actions/test_database_actions.py index 5e4ef93ad..5914362ac 100644 --- a/tests/actions/test_database_actions.py +++ b/tests/actions/test_database_actions.py @@ -11,7 +11,7 @@ def test_database_delete(monkeypatch, basic_database): staticmethod(lambda *args, **kwargs: QtWidgets.QMessageBox.Yes), ) - actions.DatabaseDelete.run(basic_database.name) + actions.DatabaseDelete.run([basic_database.name]) assert basic_database.name not in bd.databases @@ -39,15 +39,60 @@ def test_database_duplicate(monkeypatch, qtbot, basic_database): assert dup_db in bd.databases -def test_database_export(main_window): - # TODO: implement when we've redone the export wizard and actions - from activity_browser.ui.wizards.db_export_wizard import DatabaseExportWizard +def test_database_export_excel(monkeypatch, qtbot, basic_database, tmp_path): + """Test exporting a database to Excel format.""" + from activity_browser.actions.database.database_export_excel import ExportExcelSetup + + # Mock the file dialog to return a path + test_path = str(tmp_path / "test_export.xlsx") + monkeypatch.setattr( + QtWidgets.QFileDialog, + "getSaveFileName", + staticmethod(lambda *args, **kwargs: (test_path, "")), + ) + + # Call the action + actions.DatabaseExportExcel.run([basic_database.name]) + + # Find the wizard dialog and wait for the export thread to finish + wizard = application.main_window.findChild(ExportExcelSetup) + assert wizard is not None + + # Wait for the export thread to finish + export_page = wizard.currentPage() + with qtbot.waitSignal(export_page.thread.finished, timeout=10 * 1000): + pass + + # Close the wizard + wizard.close() - actions.DatabaseExport.run() - wizard = main_window.findChild(DatabaseExportWizard) - assert wizard.isVisible() - wizard.destroy() +def test_database_export_bw2package(monkeypatch, qtbot, basic_database, tmp_path): + """Test exporting a database to BW2Package format.""" + from activity_browser.actions.database.database_export_bw2package import ExportBW2PackageSetup + + # Mock the file dialog to return a path + test_path = str(tmp_path / "test_export.bw2package") + monkeypatch.setattr( + QtWidgets.QFileDialog, + "getSaveFileName", + staticmethod(lambda *args, **kwargs: (test_path, "")), + ) + + # Call the action + actions.DatabaseExportBW2Package.run([basic_database.name]) + + # Find the wizard dialog and wait for the export thread to finish + wizard = application.main_window.findChild(ExportBW2PackageSetup) + assert wizard is not None + + # Wait for the export thread to finish + export_page = wizard.currentPage() + with qtbot.waitSignal(export_page.thread.finished, timeout=10 * 1000): + pass + + # Close the wizard + wizard.close() def test_database_new(monkeypatch, basic_database): @@ -76,6 +121,42 @@ def test_database_new(monkeypatch, basic_database): actions.DatabaseNew.run() assert db_number == len(bd.databases) + + +def test_database_delete_multiple(monkeypatch, basic_database): + """Test that multiple databases can be deleted at once.""" + from activity_browser.actions.database.database_new import NewDatabaseDialog + + # Create two additional databases + db2 = "test_db_2" + db3 = "test_db_3" + + for db_name in [db2, db3]: + monkeypatch.setattr( + NewDatabaseDialog, + "get_new_database_data", + staticmethod(lambda *args, db=db_name, **kwargs: (db, "functional_sqlite", True)), + ) + monkeypatch.setattr( + QtWidgets.QMessageBox, "information", staticmethod(lambda *args, **kwargs: True) + ) + actions.DatabaseNew.run() + + assert db2 in bd.databases + assert db3 in bd.databases + + # Mock the confirmation dialog + monkeypatch.setattr( + QtWidgets.QMessageBox, + "question", + staticmethod(lambda *args, **kwargs: QtWidgets.QMessageBox.Yes), + ) + + # Delete both databases at once + actions.DatabaseDelete.run([db2, db3]) + + assert db2 not in bd.databases + assert db3 not in bd.databases # # # def test_database_relink(ab_app, monkeypatch): @@ -106,3 +187,4 @@ def test_database_new(monkeypatch, basic_database): # assert to_db in databases # assert from_db not in Database(db).find_dependents() # assert to_db in Database(db).find_dependents() + diff --git a/tests_old/__init__.py b/tests_old/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests_old/actions/test_activity_actions.py b/tests_old/actions/test_activity_actions.py deleted file mode 100644 index 0e7194087..000000000 --- a/tests_old/actions/test_activity_actions.py +++ /dev/null @@ -1,154 +0,0 @@ -from bw2data import Database -from bw2data.errors import BW2Exception -from bw2data.project import projects -from bw2data.utils import get_activity - -import pytest -from qtpy import QtWidgets - -from activity_browser import actions -from activity_browser.ui.widgets.dialog import (ActivityLinkingDialog, - LocationLinkingDialog) -from activity_browser.ui.widgets.new_node_dialog import NewNodeDialog - - -def test_activity_delete(ab_app, monkeypatch): - key = ("activity_tests", "330b935a46bc4ad39530ab7df012f38b") - - monkeypatch.setattr( - QtWidgets.QMessageBox, - "warning", - staticmethod(lambda *args, **kwargs: QtWidgets.QMessageBox.Yes), - ) - - assert projects.current == "default" - assert get_activity(key) - - actions.ActivityDelete.run([key]) - - with pytest.raises(BW2Exception): - get_activity(key) - - -def test_activity_duplicate(ab_app): - key = ("activity_tests", "dd4e2393573c49248e7299fbe03a169c") - dup_key = ("activity_tests", "dd4e2393573c49248e7299fbe03a169c_copy1") - - assert projects.current == "default" - assert get_activity(key) - with pytest.raises(BW2Exception): - get_activity(dup_key) - - actions.ActivityDuplicate.run([key]) - - assert get_activity(key) - assert get_activity(dup_key) - - -def test_activity_duplicate_to_db(ab_app, monkeypatch): - key = ("activity_tests", "dd4e2393573c49248e7299fbe03a169c") - dup_key = ("db_to_duplicate_to", "dd4e2393573c49248e7299fbe03a169c_copy1") - - monkeypatch.setattr( - QtWidgets.QInputDialog, - "getItem", - staticmethod(lambda *args, **kwargs: ("db_to_duplicate_to", True)), - ) - - assert projects.current == "default" - assert get_activity(key) - with pytest.raises(BW2Exception): - get_activity(dup_key) - - actions.ActivityDuplicateToDB.run([key]) - - assert get_activity(key) - assert get_activity(dup_key) - - -def test_activity_duplicate_to_loc(ab_app, monkeypatch): - key = ("activity_tests", "dd4e2393573c49248e7299fbe03a169c") - dup_key = ("activity_tests", "dd4e2393573c49248e7299fbe03a169c_copy2") - - monkeypatch.setattr( - LocationLinkingDialog, "exec_", staticmethod(lambda *args, **kwargs: True) - ) - - monkeypatch.setattr(LocationLinkingDialog, "relink", {"MOON": "GLO"}) - - assert projects.current == "default" - assert get_activity(key).as_dict()["location"] == "MOON" - with pytest.raises(BW2Exception): - get_activity(dup_key) - - actions.ActivityDuplicateToLoc.run(key) - - assert get_activity(key).as_dict()["location"] == "MOON" - assert get_activity(dup_key).as_dict()["location"] == "GLO" - - -def test_activity_graph(ab_app): - key = ("activity_tests", "3fcde3e3bf424e97b32cf29347ac7f33") - panel = ab_app.main_window.right_panel.tabs["Graph Explorer"] - - assert projects.current == "default" - assert get_activity(key) - assert key not in panel.tabs - - actions.ActivityGraph.run([key]) - - assert key in panel.tabs - - -def test_activity_new(ab_app, monkeypatch): - database_name = "activity_tests" - records = len(Database(database_name)) - - monkeypatch.setattr( - NewNodeDialog, "exec_", staticmethod(lambda *args, **kwargs: True) - ) - - monkeypatch.setattr( - NewNodeDialog, - "get_new_process_data", - staticmethod(lambda *args, **kwargs: ("check", "check", "check", "check")) - ) - - actions.ActivityNewProcess.run(database_name) - - assert records < len(Database(database_name)) - - -def test_activity_open(ab_app): - key = ("activity_tests", "3fcde3e3bf424e97b32cf29347ac7f33") - panel = ab_app.main_window.right_panel.tabs["Activity Details"] - - assert projects.current == "default" - assert get_activity(key) - assert key not in panel.tabs - - actions.ActivityOpen.run([key]) - - assert key in panel.tabs - assert panel.isVisible() - - -def test_activity_relink(ab_app, monkeypatch, qtbot): - key = ("activity_tests", "834c9010dff24c138c8ffa19924e5935") - from_key = ("db_to_relink_from", "6a98a991da90495ea599e35b3d3602ab") - to_key = ("db_to_relink_to", "0d4d83e3baee4b7e865c34a16a63f03e") - - monkeypatch.setattr( - ActivityLinkingDialog, "exec_", staticmethod(lambda *args, **kwargs: True) - ) - - monkeypatch.setattr( - ActivityLinkingDialog, "relink", {"db_to_relink_from": "db_to_relink_to"} - ) - - assert projects.current == "default" - assert list(get_activity(key).exchanges())[1].input.key == from_key - - actions.ActivityRelink.run([key]) - - assert list(get_activity(key).exchanges())[1].input.key == to_key diff --git a/tests_old/actions/test_calculation_setup_actions.py b/tests_old/actions/test_calculation_setup_actions.py deleted file mode 100644 index de6945fe5..000000000 --- a/tests_old/actions/test_calculation_setup_actions.py +++ /dev/null @@ -1,84 +0,0 @@ -from bw2data.project import projects -from bw2data.meta import calculation_setups - -from qtpy import QtWidgets - -from activity_browser import actions - - -def test_cs_delete(ab_app, monkeypatch): - cs = "cs_to_delete" - - monkeypatch.setattr( - QtWidgets.QMessageBox, "warning", staticmethod(lambda *args, **kwargs: True) - ) - - monkeypatch.setattr( - QtWidgets.QMessageBox, "information", staticmethod(lambda *args, **kwargs: True) - ) - - assert projects.current == "default" - assert cs in calculation_setups - - actions.CSDelete.run(cs) - - assert cs not in calculation_setups - - -def test_cs_duplicate(ab_app, monkeypatch): - cs = "cs_to_duplicate" - dup_cs = "cs_that_is_duplicated" - - monkeypatch.setattr( - QtWidgets.QInputDialog, - "getText", - staticmethod(lambda *args, **kwargs: ("cs_that_is_duplicated", True)), - ) - - assert projects.current == "default" - assert cs in calculation_setups - assert dup_cs not in calculation_setups - - actions.CSDuplicate.run(cs) - - assert cs in calculation_setups - assert dup_cs in calculation_setups - - -def test_cs_new(ab_app, monkeypatch): - new_cs = "cs_that_is_new" - - monkeypatch.setattr( - QtWidgets.QInputDialog, - "getText", - staticmethod(lambda *args, **kwargs: ("cs_that_is_new", True)), - ) - - assert projects.current == "default" - assert new_cs not in calculation_setups - - actions.CSNew.run() - - assert new_cs in calculation_setups - - return - - -def test_cs_rename(ab_app, monkeypatch): - cs = "cs_to_rename" - renamed_cs = "cs_that_is_renamed" - - monkeypatch.setattr( - QtWidgets.QInputDialog, - "getText", - staticmethod(lambda *args, **kwargs: ("cs_that_is_renamed", True)), - ) - - assert projects.current == "default" - assert cs in calculation_setups - assert renamed_cs not in calculation_setups - - actions.CSRename.run(cs) - - assert cs not in calculation_setups - assert renamed_cs in calculation_setups diff --git a/tests_old/actions/test_database_actions.py b/tests_old/actions/test_database_actions.py deleted file mode 100644 index 04e0b5ee2..000000000 --- a/tests_old/actions/test_database_actions.py +++ /dev/null @@ -1,129 +0,0 @@ -from bw2data.meta import databases -from bw2data.project import projects -from bw2data.database import Database - -from qtpy import QtWidgets - -from activity_browser import actions, application -from activity_browser.actions.database.database_duplicate import DuplicateDatabaseDialog -from activity_browser.ui.widgets.dialog import DatabaseLinkingDialog -from activity_browser.ui.wizards.db_export_wizard import DatabaseExportWizard -from activity_browser.ui.wizards.db_import_wizard import DatabaseImportWizard - - -def test_database_delete(ab_app, monkeypatch): - db = "db_to_delete" - - monkeypatch.setattr( - QtWidgets.QMessageBox, - "question", - staticmethod(lambda *args, **kwargs: QtWidgets.QMessageBox.Yes), - ) - - assert projects.current == "default" - assert db in databases - - actions.DatabaseDelete.run(db) - - assert db not in databases - - -def test_database_duplicate(ab_app, monkeypatch, qtbot): - db = "db_to_duplicate" - dup_db = "db_that_is_duplicated" - - monkeypatch.setattr( - QtWidgets.QInputDialog, - "getText", - staticmethod(lambda *args, **kwargs: ("db_that_is_duplicated", True)), - ) - - assert projects.current == "default" - assert db in databases - assert dup_db not in databases - - actions.DatabaseDuplicate.run(db) - - dialog = application.main_window.findChild(DuplicateDatabaseDialog) - with qtbot.waitSignal(dialog.dup_thread.finished, timeout=60 * 1000): - pass - - assert db in databases - assert dup_db in databases - - -def test_database_export(ab_app): - # TODO: implement when we've redone the export wizard and actions - actions.DatabaseExport.run() - - wizard = application.main_window.findChild(DatabaseExportWizard) - assert wizard.isVisible() - wizard.destroy() - return - - -def test_database_import(ab_app): - # TODO: implement when we've redone the import wizard and actions - actions.DatabaseImport.run() - - wizard = application.main_window.findChild(DatabaseImportWizard) - assert wizard.isVisible() - wizard.destroy() - return - - -def test_database_new(ab_app, monkeypatch): - new_db = "db_that_is_new" - - monkeypatch.setattr( - QtWidgets.QInputDialog, - "getText", - staticmethod(lambda *args, **kwargs: ("db_that_is_new", True)), - ) - - monkeypatch.setattr( - QtWidgets.QMessageBox, "information", staticmethod(lambda *args, **kwargs: True) - ) - - assert projects.current == "default" - assert new_db not in databases - - actions.DatabaseNew.run() - - assert new_db in databases - - db_number = len(databases) - - actions.DatabaseNew.run() - - assert db_number == len(databases) - - -def test_database_relink(ab_app, monkeypatch): - db = "db_to_relink" - from_db = "db_to_relink_from" - to_db = "db_to_relink_to" - - monkeypatch.setattr( - DatabaseLinkingDialog, - "exec_", - staticmethod(lambda *args, **kwargs: DatabaseLinkingDialog.Accepted), - ) - - monkeypatch.setattr( - DatabaseLinkingDialog, "relink", {"db_to_relink_from": "db_to_relink_to"} - ) - - assert db in databases - assert from_db in databases - assert to_db in databases - assert from_db in Database(db).find_dependents() - assert to_db not in Database(db).find_dependents() - - actions.DatabaseRelink.run(db) - - assert db in databases - assert from_db in databases - assert to_db in databases - assert from_db not in Database(db).find_dependents() - assert to_db in Database(db).find_dependents() diff --git a/tests_old/actions/test_exchange_actions.py b/tests_old/actions/test_exchange_actions.py deleted file mode 100644 index 7d55bba85..000000000 --- a/tests_old/actions/test_exchange_actions.py +++ /dev/null @@ -1,163 +0,0 @@ -import platform - -from bw2data.utils import get_activity -from bw2data.project import projects - -import pytest -from qtpy import QtGui -from stats_arrays.distributions import NormalUncertainty, UndefinedUncertainty - -from activity_browser import actions, application -from activity_browser.ui.wizards import UncertaintyWizard - - -def test_exchange_copy_sdf(ab_app): - # this test will always fail on the linux automated test because it doesn't have a clipboard - if platform.system() == "Linux": - return - - key = ("exchange_tests", "186cdea4c3214479b931428591ab2021") - from_key = ("exchange_tests", "77780c6ab87d4e8785172f107877d6ed") - exchange = [ - exchange - for exchange in get_activity(key).exchanges() - if exchange.input.key == from_key - ] - - clipboard = QtGui.QClipboard() - clipboard.setText("FAILED") - - assert projects.current == "default" - assert len(exchange) == 1 - assert clipboard.text() == "FAILED" - - actions.ExchangeCopySDF.run(exchange) - - assert clipboard.text() != "FAILED" - - return - - -def test_exchange_delete(ab_app): - key = ("exchange_tests", "186cdea4c3214479b931428591ab2021") - from_key = ("exchange_tests", "3b86eaea74ff40d69e9e6bec137a8f0c") - exchange = [ - exchange - for exchange in get_activity(key).exchanges() - if exchange.input.key == from_key - ] - - assert projects.current == "default" - assert len(exchange) == 1 - assert exchange[0].as_dict() in [ - exchange.as_dict() for exchange in get_activity(key).exchanges() - ] - - actions.ExchangeDelete.run(exchange) - - assert exchange[0].as_dict() not in [ - exchange.as_dict() for exchange in get_activity(key).exchanges() - ] - - -def test_exchange_formula_remove(ab_app): - key = ("exchange_tests", "186cdea4c3214479b931428591ab2021") - from_key = ("exchange_tests", "19437c81de6545ad8d017ee2e2fa32e6") - exchange = [ - exchange - for exchange in get_activity(key).exchanges() - if exchange.input.key == from_key - ] - - assert projects.current == "default" - assert len(exchange) == 1 - assert exchange[0].as_dict()["formula"] - - actions.ExchangeFormulaRemove.run(exchange) - - with pytest.raises(KeyError): - assert exchange[0].as_dict()["formula"] - - -def test_exchange_modify(ab_app): - key = ("exchange_tests", "186cdea4c3214479b931428591ab2021") - from_key = ("exchange_tests", "0e1dc99927284e45af17d546414a3ccd") - exchange = [ - exchange - for exchange in get_activity(key).exchanges() - if exchange.input.key == from_key - ] - - new_data = {"amount": 200} - - assert projects.current == "default" - assert len(exchange) == 1 - assert exchange[0].amount == 1.0 - - actions.ExchangeModify.run(exchange[0], new_data) - - assert exchange[0].amount == 200.0 - - -def test_exchange_new(ab_app): - key = ("exchange_tests", "186cdea4c3214479b931428591ab2021") - from_key = ("activity_tests", "be8fb2776c354aa7ad61d8348828f3af") - - assert projects.current == "default" - assert not [ - exchange - for exchange in get_activity(key).exchanges() - if exchange.input.key == from_key - ] - - actions.ExchangeNew.run([from_key], key, "production") - - assert ( - len( - [ - exchange - for exchange in get_activity(key).exchanges() - if exchange.input.key == from_key - ] - ) - == 1 - ) - - -def test_exchange_uncertainty_modify(ab_app): - key = ("exchange_tests", "186cdea4c3214479b931428591ab2021") - from_key = ("exchange_tests", "5ad223731bd244e997623b0958744017") - exchange = [ - exchange - for exchange in get_activity(key).exchanges() - if exchange.input.key == from_key - ] - - assert projects.current == "default" - assert len(exchange) == 1 - - actions.ExchangeUncertaintyModify.run(exchange) - - wizard = application.main_window.findChild(UncertaintyWizard) - - assert wizard.isVisible() - - wizard.destroy() - - -def test_exchange_uncertainty_remove(ab_app): - key = ("exchange_tests", "186cdea4c3214479b931428591ab2021") - from_key = ("exchange_tests", "4e28577e29a346e3aef6aeafb6d5eb65") - exchange = [ - exchange - for exchange in get_activity(key).exchanges() - if exchange.input.key == from_key - ] - - assert projects.current == "default" - assert len(exchange) == 1 - assert exchange[0].uncertainty_type == NormalUncertainty - - actions.ExchangeUncertaintyRemove.run(exchange) - - assert exchange[0].uncertainty_type == UndefinedUncertainty diff --git a/tests_old/actions/test_method_actions.py b/tests_old/actions/test_method_actions.py deleted file mode 100644 index b15ed7e7f..000000000 --- a/tests_old/actions/test_method_actions.py +++ /dev/null @@ -1,170 +0,0 @@ -from bw2data import methods -from bw2data.method import Method -from bw2data.project import projects - -from qtpy import QtWidgets -from stats_arrays.distributions import ( - NormalUncertainty, - UndefinedUncertainty, - UniformUncertainty, -) - -from activity_browser import actions, application -from activity_browser.ui.widgets.dialog import TupleNameDialog -from activity_browser.ui.wizards import UncertaintyWizard - - -def test_cf_amount_modify(ab_app): - method = ("A_methods", "methods", "method") - key = ("biosphere3", "595f08d9-6304-497e-bb7d-48b6d2d8bff3") - act_id = 2192 - cf = [cf for cf in Method(method).load() if cf[0] == act_id] - - assert projects.current == "default" - assert len(cf) == 1 - assert cf[0][1] == 1.0 or cf[0][1]["amount"] == 1.0 - - actions.CFAmountModify.run(method, cf, 200) - - cf = [cf for cf in Method(method).load() if cf[0] == act_id] - assert cf[0][1] == 200.0 or cf[0][1]["amount"] == 200.0 - - -def test_cf_new(ab_app): - method = ("A_methods", "methods", "method") - key = ("biosphere3", "0d9f52b2-f2d5-46a3-90a3-e22ef252cc37") - act_id = 2084 - cf = [cf for cf in Method(method).load() if cf[0] == act_id] - - assert projects.current == "default" - assert len(cf) == 0 - - actions.CFNew.run(method, [key]) - - cf = [cf for cf in Method(method).load() if cf[0] == act_id] - - assert len(cf) == 1 - assert cf[0][1] == 0.0 - - -def test_cf_remove(ab_app, monkeypatch): - method = ("A_methods", "methods", "method") - key = ("biosphere3", "075e433b-4be4-448e-9510-9a5029c1ce94") - act_id = 3772 - cf = [cf for cf in Method(method).load() if cf[0] == act_id] - - monkeypatch.setattr( - QtWidgets.QMessageBox, - "warning", - staticmethod(lambda *args, **kwargs: QtWidgets.QMessageBox.Yes), - ) - - assert projects.current == "default" - assert len(cf) == 1 - - actions.CFRemove.run(method, cf) - - cf = [cf for cf in Method(method).load() if cf[0] == act_id] - assert len(cf) == 0 - - -def test_cf_uncertainty_modify(ab_app): - method = ("A_methods", "methods", "method") - key = ("biosphere3", "da5e6be3-ed71-48ac-9397-25bac666c7b7") - act_id = 2188 - cf = [cf for cf in Method(method).load() if cf[0] == act_id] - new_cf_tuple = ( - act_id, - {"amount": 5.5}, - ) - uncertainty = { - "loc": float("nan"), - "maximum": 10.0, - "minimum": 1.0, - "negative": False, - "scale": float("nan"), - "shape": float("nan"), - "uncertainty type": UniformUncertainty.id, - } - - assert projects.current == "default" - assert len(cf) == 1 - assert cf[0][1].get("uncertainty type") == NormalUncertainty.id - - actions.CFUncertaintyModify.run(method, cf) - - wizard = application.main_window.findChild(UncertaintyWizard) - - assert wizard.isVisible() - - wizard.destroy() - actions.CFUncertaintyModify.wizard_done(method, new_cf_tuple, uncertainty) - - cf = [cf for cf in Method(method).load() if cf[0] == act_id] - - assert cf[0][1].get("uncertainty type") == UniformUncertainty.id - assert cf[0][1].get("amount") == 5.5 - - -def test_cf_uncertainty_remove(ab_app): - method = ("A_methods", "methods", "method") - key = ("biosphere3", "2a7b68ff-f12a-44c6-8b31-71ec91d29889") - act_id = 2164 - cf = [cf for cf in Method(method).load() if cf[0] == act_id] - - assert projects.current == "default" - assert len(cf) == 1 - assert cf[0][1].get("uncertainty type") == NormalUncertainty.id - - actions.CFUncertaintyRemove.run(method, cf) - - cf = [cf for cf in Method(method).load() if cf[0] == act_id] - assert ( - cf[0][1] == 1.0 or cf[0][1].get("uncertainty type") == UndefinedUncertainty.id - ) - - -def test_method_delete(ab_app, monkeypatch): - method = ("A_methods", "methods", "method_to_delete") - dual_method_1 = ("A_methods", "methods_to_delete", "method_to_delete_one") - dual_method_2 = ("A_methods", "methods_to_delete", "method_to_delete_two") - - monkeypatch.setattr( - QtWidgets.QMessageBox, - "warning", - staticmethod(lambda *args, **kwargs: QtWidgets.QMessageBox.Yes), - ) - - assert projects.current == "default" - assert method in methods - assert dual_method_1 in methods - assert dual_method_2 in methods - - actions.MethodDelete.run([method]) - actions.MethodDelete.run([dual_method_1, dual_method_2]) - - assert method not in methods - assert dual_method_1 not in methods - assert dual_method_2 not in methods - - -def test_method_duplicate(ab_app, monkeypatch): - method = ("A_methods", "methods", "method_to_duplicate") - result = ("A_methods", "duplicated_methods") - duplicated_method = ("A_methods", "duplicated_methods", "method_to_duplicate") - - monkeypatch.setattr( - TupleNameDialog, - "exec_", - staticmethod(lambda *args, **kwargs: TupleNameDialog.Accepted), - ) - - monkeypatch.setattr(TupleNameDialog, "result_tuple", result) - - assert method in methods - assert duplicated_method not in methods - - actions.MethodDuplicate.run([method], "leaf") - - assert method in methods - assert duplicated_method in methods diff --git a/tests_old/actions/test_parameter_actions.py b/tests_old/actions/test_parameter_actions.py deleted file mode 100644 index 890a14409..000000000 --- a/tests_old/actions/test_parameter_actions.py +++ /dev/null @@ -1,179 +0,0 @@ -from bw2data.project import projects -from bw2data.utils import get_activity - -from bw2data.parameters import ActivityParameter, DatabaseParameter, ProjectParameter -from qtpy import QtWidgets - -from activity_browser import actions -from activity_browser.actions.parameter.parameter_new import ParameterWizard - - -class TestParameterNew: - def test_parameter_new_project(self, ab_app, monkeypatch): - key = ("", "") - param_data = {"name": "project_parameter_to_be_created", "amount": "1.0"} - - monkeypatch.setattr( - ParameterWizard, - "exec_", - staticmethod(lambda *args, **kwargs: ParameterWizard.Accepted), - ) - monkeypatch.setattr(ParameterWizard, "selected", 0) - monkeypatch.setattr(ParameterWizard, "param_data", param_data) - - assert projects.current == "default" - assert "project_parameter_to_be_created" not in ProjectParameter.load().keys() - - actions.ParameterNew.run(key) - - assert "project_parameter_to_be_created" in ProjectParameter.load().keys() - - def test_parameter_new_database(self, ab_app, monkeypatch): - key = ("db", "") - param_data = { - "name": "database_parameter_to_be_created", - "database": "activity_tests", - "amount": "1.0", - } - - monkeypatch.setattr( - ParameterWizard, - "exec_", - staticmethod(lambda *args, **kwargs: ParameterWizard.Accepted), - ) - monkeypatch.setattr(ParameterWizard, "selected", 1) - monkeypatch.setattr(ParameterWizard, "param_data", param_data) - - assert projects.current == "default" - assert ( - "database_parameter_to_be_created" - not in DatabaseParameter.load("activity_tests").keys() - ) - - actions.ParameterNew.run(key) - - assert ( - "database_parameter_to_be_created" - in DatabaseParameter.load("activity_tests").keys() - ) - - def test_parameter_new_activity(self, ab_app, monkeypatch): - key = ("activity_tests", "3fcde3e3bf424e97b32cf29347ac7f33") - group = "activity_group" - param_data = { - "name": "activity_parameter_to_be_created", - "database": key[0], - "code": key[1], - "group": group, - "amount": "1.0", - } - - monkeypatch.setattr( - ParameterWizard, - "exec_", - staticmethod(lambda *args, **kwargs: ParameterWizard.Accepted), - ) - monkeypatch.setattr(ParameterWizard, "selected", 2) - monkeypatch.setattr(ParameterWizard, "param_data", param_data) - - assert projects.current == "default" - assert ( - "activity_parameter_to_be_created" - not in ActivityParameter.load(group).keys() - ) - - actions.ParameterNew.run(key) - - assert ( - "activity_parameter_to_be_created" in ActivityParameter.load(group).keys() - ) - - def test_parameter_new_wizard_project(self, ab_app): - key = ("", "") - param_data = {"name": "parameter_test", "amount": "1.0"} - wizard = ParameterWizard(key) - - assert not wizard.isVisible() - wizard.show() - assert wizard.isVisible() - assert wizard.pages[0].isVisible() - assert wizard.pages[0].selected == 0 - wizard.next() - assert wizard.pages[1].isVisible() - assert wizard.pages[1].database.isHidden() - wizard.pages[1].name.setText("parameter_test") - wizard.done(1) - assert not wizard.isVisible() - assert wizard.param_data == param_data - - def test_parameter_new_wizard_parameter(self, ab_app): - key = ("db", "") - param_data = { - "name": "parameter_test", - "database": "activity_tests", - "amount": "1.0", - } - wizard = ParameterWizard(key) - - assert not wizard.isVisible() - wizard.show() - assert wizard.isVisible() - assert wizard.pages[0].isVisible() - assert wizard.pages[0].selected == 1 - wizard.next() - assert wizard.pages[1].isVisible() - assert not wizard.pages[1].database.isHidden() - wizard.pages[1].name.setText("parameter_test") - wizard.pages[1].database.setCurrentText("activity_tests") - wizard.done(1) - assert not wizard.isVisible() - assert wizard.param_data == param_data - - def test_parameter_new_wizard_activity(self, ab_app): - key = ("activity_tests", "be8fb2776c354aa7ad61d8348828f3af") - group = get_activity(key)._document.id - - assert "dummy_parameter" not in ActivityParameter.load(group).keys() - - param_data = { - "name": "parameter_test", - "database": "activity_tests", - "code": "be8fb2776c354aa7ad61d8348828f3af", - "group": "4748", - "amount": "1.0", - } - wizard = ParameterWizard(key) - - assert not wizard.isVisible() - wizard.show() - assert wizard.isVisible() - assert wizard.pages[0].isVisible() - assert wizard.pages[0].selected == 2 - wizard.next() - assert wizard.pages[1].isVisible() - assert wizard.pages[1].database.isHidden() - wizard.pages[1].name.setText("parameter_test") - wizard.done(1) - assert not wizard.isVisible() - assert wizard.param_data == param_data - assert "dummy_parameter" in ActivityParameter.load(group).keys() - - -def test_parameter_rename(ab_app, monkeypatch): - parameter = list( - ProjectParameter.select().where(ProjectParameter.name == "parameter_to_rename") - )[0] - - monkeypatch.setattr( - QtWidgets.QInputDialog, - "getText", - staticmethod(lambda *args, **kwargs: ("renamed_parameter", True)), - ) - - assert projects.current == "default" - assert "renamed_parameter" not in ProjectParameter.load().keys() - - actions.ParameterRename.run(parameter) - - assert "parameter_to_rename" not in ProjectParameter.load().keys() - assert "renamed_parameter" in ProjectParameter.load().keys() diff --git a/tests_old/actions/test_project_actions.py b/tests_old/actions/test_project_actions.py deleted file mode 100644 index b7409d9c4..000000000 --- a/tests_old/actions/test_project_actions.py +++ /dev/null @@ -1,86 +0,0 @@ -from bw2data.project import projects - -from qtpy import QtWidgets - -from activity_browser import actions -from activity_browser.actions.project.project_delete import ProjectDeletionDialog - - -def test_project_delete(ab_app, monkeypatch): - project_name = "project_to_delete" - projects.set_current(project_name) - - monkeypatch.setattr( - ProjectDeletionDialog, - "exec_", - staticmethod(lambda *args, **kwargs: ProjectDeletionDialog.Accepted), - ) - - monkeypatch.setattr( - QtWidgets.QMessageBox, "information", staticmethod(lambda *args, **kwargs: True) - ) - - assert projects.current == project_name - - actions.ProjectDelete.run() - - assert projects.current == ab_settings.startup_project - assert project_name not in projects - - actions.ProjectDelete.run() - - assert projects.current == ab_settings.startup_project - - -def test_project_duplicate(ab_app, monkeypatch): - project_name = "project_to_duplicate" - dup_project_name = "duplicated_project" - projects.set_current(project_name) - - monkeypatch.setattr( - QtWidgets.QInputDialog, - "getText", - staticmethod(lambda *args, **kwargs: (dup_project_name, True)), - ) - monkeypatch.setattr( - QtWidgets.QMessageBox, "information", staticmethod(lambda *args, **kwargs: True) - ) - - assert projects.current == project_name - assert dup_project_name not in projects - - actions.ProjectDuplicate.run() - - assert projects.current == dup_project_name - assert project_name in projects - - projects_number = len(projects) - - actions.ProjectDuplicate.run() - - assert len(projects) == projects_number - - -def test_project_new(ab_app, monkeypatch): - project_name = "project_that_is_new" - - monkeypatch.setattr( - QtWidgets.QInputDialog, - "getText", - staticmethod(lambda *args, **kwargs: (project_name, True)), - ) - monkeypatch.setattr( - QtWidgets.QMessageBox, "information", staticmethod(lambda *args, **kwargs: True) - ) - - assert project_name not in projects - - actions.ProjectNew.run() - - assert project_name in projects - - projects_number = len(projects) - - actions.ProjectNew.run() - - assert len(projects) == projects_number diff --git a/tests_old/actions/test_various_actions.py b/tests_old/actions/test_various_actions.py deleted file mode 100644 index 576c7303c..000000000 --- a/tests_old/actions/test_various_actions.py +++ /dev/null @@ -1,92 +0,0 @@ -from activity_browser import actions, application -from activity_browser.ui.wizards.settings_wizard import SettingsWizard - -####### DEPRECATED IN BW25 -# -# @pytest.mark.skipif( -# os.environ.get("TEST_FAST", False), reason="Skipped for faster testing" -# ) -# def test_default_install(ab_app, monkeypatch, qtbot): -# project_name = "biosphere_project" -# projects.set_current(project_name) -# -# monkeypatch.setattr( -# EcoinventVersionDialog, -# "exec_", -# staticmethod(lambda *args, **kwargs: EcoinventVersionDialog.Accepted), -# ) -# monkeypatch.setattr( -# QtWidgets.QComboBox, "currentText", staticmethod(lambda *args, **kwargs: "3.9") -# ) -# -# assert projects.current == project_name -# assert "biosphere3" not in databases -# assert not application.main_window.findChild(DefaultBiosphereDialog) -# -# actions.DefaultInstall.run(check_patches=False) -# -# dialog = application.main_window.findChild(DefaultBiosphereDialog) -# with qtbot.waitSignal(dialog.finished, timeout=5 * 60 * 1000): -# pass -# # TODO: look into why AB_metadata not being populated -# # qtbot.waitUntil(lambda: len(AB_metadata.dataframe) == 4709) -# -# assert "biosphere3" in databases -# assert len(Database("biosphere3")) == 4709 -# assert len(methods) == 762 -# -# -# @pytest.mark.skipif( -# os.environ.get("TEST_FAST", False), reason="Skipped for faster testing" -# ) -# def test_biosphere_update(ab_app, monkeypatch, qtbot): -# project_name = "biosphere_project" -# projects.set_current(project_name) -# -# monkeypatch.setattr( -# QtWidgets.QMessageBox, -# "question", -# staticmethod(lambda *args, **kwargs: QtWidgets.QMessageBox.Ok), -# ) -# monkeypatch.setattr( -# EcoinventVersionDialog, -# "exec_", -# staticmethod(lambda *args, **kwargs: EcoinventVersionDialog.Accepted), -# ) -# monkeypatch.setattr( -# QtWidgets.QComboBox, -# "currentText", -# staticmethod(lambda *args, **kwargs: "3.9.1"), -# ) -# -# assert projects.current == project_name -# assert "biosphere3" in databases -# assert len(Database("biosphere3")) == 4709 -# -# actions.BiosphereUpdate.run() -# -# dialog = application.main_window.findChild(BiosphereUpdater) -# with qtbot.waitSignal(dialog.finished, timeout=5 * 60 * 1000): -# pass -# -# assert len(Database("biosphere3")) == 4718 - - -def test_plugin_wizard_open(ab_app): - assert not application.main_window.findChild(PluginsManagerWizard) - - actions.PluginWizardOpen.run() - - assert application.main_window.findChild(PluginsManagerWizard).isVisible() - - application.main_window.findChild(PluginsManagerWizard).destroy() - - -def test_settings_wizard_open(ab_app): - assert not application.main_window.findChild(SettingsWizard) - - actions.SettingsWizardOpen.run() - - assert application.main_window.findChild(SettingsWizard).isVisible() - - application.main_window.findChild(SettingsWizard).destroy() diff --git a/tests_old/conftest.py b/tests_old/conftest.py deleted file mode 100644 index 333a3a25c..000000000 --- a/tests_old/conftest.py +++ /dev/null @@ -1,71 +0,0 @@ -# -*- coding: utf-8 -*- -import os -import shutil -import tempfile -from pathlib import Path -from typing import Optional - -from bw2data import projects, config -import bw2io as bi -import pytest - -from activity_browser import MainWindow, application - - -def create_temp_dirs(temp_dir: Optional[Path] = None): - """ - Create temporary directories for testing - """ - temp_dir = temp_dir or Path(tempfile.mkdtemp()) - dir_base_data = temp_dir / "data" - dir_base_data.mkdir(parents=True, exist_ok=True) - dir_base_logs = temp_dir / "logs" - dir_base_logs.mkdir(parents=True, exist_ok=True) - return dir_base_data, dir_base_logs - - -@pytest.fixture(scope="session") -def ab_app(): - """Initialize the application and yield it. Cleanup the 'test' project - after session is complete. - """ - dir_base_data, dir_base_logs = create_temp_dirs() - projects.change_base_directories(dir_base_data, dir_base_logs) - - bi.restore_project_directory( - os.path.join(os.path.dirname(os.path.abspath(__file__)), "pytest_base.gz"), - "default", - overwrite_existing=True, - ) - - application.main_window = MainWindow(application) - application.show() - projects.set_current("default") - yield application - application.close() - - -@pytest.fixture() -def bw2test(): - """Similar to `bw2test` from bw2data.tests, but makes use of pytest - fixture setup/teardown mechanics. - - Allows tests to be performed in a perfectly clean project instead - of the test project. - """ - config.dont_warn = True - config.is_test = True - config.cache = {} - current_data_dir = projects._base_data_dir - current_log_dir = projects._base_logs_dir - tempdir = Path(tempfile.mkdtemp()) - dir_base_data, dir_base_logs = create_temp_dirs(tempdir) - projects.change_base_directories(dir_base_data, dir_base_logs) - - yield tempdir - - projects.change_base_directories(current_data_dir, current_log_dir) - # Make the jump back to the pytest_project if it exists - if "pytest_project" in projects: - projects.set_current("pytest_project", update=False) - shutil.rmtree(tempdir) diff --git a/tests_old/legacy/test_settings.py b/tests_old/legacy/test_settings.py deleted file mode 100644 index 0ffec63d4..000000000 --- a/tests_old/legacy/test_settings.py +++ /dev/null @@ -1,89 +0,0 @@ -# -*- coding: utf-8 -*- -import os - -import pytest - -from activity_browser.settings import ABSettings, BaseSettings, ProjectSettings - - -@pytest.fixture() -def ab_settings(qtbot, ab_app): - """Remove the test settings file after finishing the tests.""" - with qtbot.waitExposed(ab_app.main_window): - settings = ABSettings("test_ab.json") - yield settings - if os.path.isfile(settings.settings_file): - os.remove(settings.settings_file) - - -@pytest.fixture() -def project_settings(qtbot, ab_app): - """No cleanup needed as the entire project is removed after testing.""" - with qtbot.waitExposed(ab_app.main_window): - settings = ProjectSettings("test_project.json") - yield settings - - -def test_base_class(): - """Test that the base class raises an error on initialization""" - current_path = os.path.dirname(os.path.abspath(__file__)) - with pytest.raises(NotImplementedError): - settings = BaseSettings(current_path) - - -def test_ab_default_keys(ab_settings): - """Test that default setting are only created for the given keys.""" - defaults = ab_settings.get_default_settings() - assert not { - "current_bw_dir", - "custom_bw_dirs", - "startup_project", - }.symmetric_difference(defaults) - - -def test_ab_default_settings(ab_settings): - assert ABSettings.get_default_directory() == ab_settings.custom_bw_dir[0] - assert ABSettings.get_default_project_name() == ab_settings.startup_project - - -def test_ab_edit_settings(ab_settings): - current_path = os.path.dirname(os.path.abspath(__file__)) - ab_settings.custom_bw_dir = current_path - assert ab_settings.custom_bw_dir != ABSettings.get_default_directory() - - -def test_ab_unknown_startup(ab_settings): - """Alter the startup project with an unknown project, assert that it - was not altered because the project does not exist. - """ - ab_settings.startup_project = "unknown_project" - assert ab_settings.startup_project == ABSettings.get_default_project_name() - - -def test_project_default_keys(project_settings): - defaults = project_settings.get_default_settings() - assert not {"plugins_list", "read-only-databases"}.symmetric_difference(defaults) - - -def test_project_add_dbs(project_settings): - project_settings.add_db("fakedb") - project_settings.add_db("fake_readabledb", False) - assert project_settings.db_is_readonly("fakedb") is True - assert project_settings.db_is_readonly("fake_readabledb") is False - - -def test_project_modify_db(project_settings): - assert project_settings.db_is_readonly("fakedb") is True - project_settings.modify_db("fakedb", False) - assert project_settings.db_is_readonly("fakedb") is False - - -def test_project_editable_dbs(project_settings): - editable_dbs = {"fakedb", "fake_readabledb"} - assert all(db in editable_dbs for db in project_settings.get_editable_databases()) - - -def test_project_remove_db(project_settings): - project_settings.remove_db("fakedb") - # If db cannot be found, return True - assert project_settings.db_is_readonly("fakedb") is True diff --git a/tests_old/legacy/test_uncertainty.py b/tests_old/legacy/test_uncertainty.py deleted file mode 100644 index 94f4de3ac..000000000 --- a/tests_old/legacy/test_uncertainty.py +++ /dev/null @@ -1,85 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Use the existing parameters to look at the uncertainty and edit it in -multiple ways -""" -from bw2data.project import projects -from bw2data.database import Database -from bw2data.configuration import config -from bw2data.utils import get_activity -from bw2data import Method, methods - -import pytest -from stats_arrays.distributions import UndefinedUncertainty, UniformUncertainty - -from activity_browser.bwutils.uncertainty import ( - CFUncertaintyInterface, - ExchangeUncertaintyInterface, - get_uncertainty_interface, -) - - -def test_exchange_interface(qtbot, ab_app): - projects.set_current("default") - flow = Database(config.biosphere).random() - db = Database("testdb") - act_key = ("testdb", "act_unc") - db.write( - { - act_key: { - "name": "act_unc", - "unit": "kilogram", - "exchanges": [ - {"input": act_key, "amount": 1, "type": "production"}, - {"input": flow.key, "amount": 2, "type": "biosphere"}, - ], - } - } - ) - - act = get_activity(act_key) - exc = next(e for e in act.biosphere()) - interface = get_uncertainty_interface(exc) - assert isinstance(interface, ExchangeUncertaintyInterface) - assert interface.amount == 2 - assert interface.uncertainty_type == UndefinedUncertainty - assert interface.uncertainty == {} - - -@pytest.mark.xfail(reason="Selected CF was already uncertain") -def test_cf_interface(qtbot, ab_app): - key = methods.random() - method = Method(key).load() - cf = next(f for f in method) - - assert isinstance(cf, tuple) - if isinstance(cf[-1], dict): - cf = method[1] - assert isinstance(cf[-1], float) - amount = cf[-1] # last value in the CF should be the amount. - - interface = get_uncertainty_interface(cf) - assert isinstance(interface, CFUncertaintyInterface) - assert not interface.is_uncertain # CF should not be uncertain. - assert interface.amount == amount - assert interface.uncertainty_type == UndefinedUncertainty - assert interface.uncertainty == {} - - # Now add uncertainty. - uncertainty = { - "minimum": 1, - "maximum": 18, - "uncertainty type": UniformUncertainty.id, - } - uncertainty["amount"] = amount - cf = (cf[0], uncertainty) - interface = get_uncertainty_interface(cf) - assert isinstance(interface, CFUncertaintyInterface) - assert interface.is_uncertain # It is uncertain now! - assert interface.amount == amount - assert interface.uncertainty_type == UniformUncertainty - assert interface.uncertainty == { - "uncertainty type": UniformUncertainty.id, - "minimum": 1, - "maximum": 18, - } diff --git a/tests_old/legacy/test_utils.py b/tests_old/legacy/test_utils.py deleted file mode 100644 index 242205136..000000000 --- a/tests_old/legacy/test_utils.py +++ /dev/null @@ -1,12 +0,0 @@ -from activity_browser.utils import sort_semantic_versions - - -def test_semantic_sort(): - """Test the semantic sorting function.""" - test_versions = ["1.2.3", "2.0.0", "1.1.1", "2.1.0", "1.0.0"] - - sorted_dsc = sort_semantic_versions(test_versions) - assert sorted_dsc == ["2.1.0", "2.0.0", "1.2.3", "1.1.1", "1.0.0"] - - sorted_asc = sort_semantic_versions(test_versions, highest_to_lowest=False) - assert sorted_asc == ["1.0.0", "1.1.1", "1.2.3", "2.0.0", "2.1.0"] diff --git a/tests_old/legacy/test_widgets.py b/tests_old/legacy/test_widgets.py deleted file mode 100644 index 927e129f6..000000000 --- a/tests_old/legacy/test_widgets.py +++ /dev/null @@ -1,91 +0,0 @@ -# -*- coding: utf-8 -*- -from qtpy.QtCore import Qt -from qtpy.QtWidgets import QDialogButtonBox, QMessageBox, QWidget - -from activity_browser.ui.widgets import (BiosphereUpdater, CutoffMenu, - ForceInputDialog, SwitchComboBox, - parameter_save_errorbox, - simple_warning_box) - -# NOTE: No way of testing the BiosphereUpdater class without causing the -# ab_app fixture to flip its lid and fail to clean itself up. - - -def test_comparison_switch_empty(qtbot): - parent = QWidget() - parent.has_scenarios = False - qtbot.addWidget(parent) - box = SwitchComboBox(parent) - box.configure(False, False) - size = box.count() - assert size == 0 - assert not box.isVisible() - - -def test_comparison_switch_no_scenarios(qtbot): - parent = QWidget() - parent.has_scenarios = False - qtbot.addWidget(parent) - box = SwitchComboBox(parent) - box.configure() - size = box.count() - assert size == 2 - # assert box.isVisible() # Box fails to be visible, except it definitely is? - - -def test_comparison_switch_all(qtbot): - parent = QWidget() - parent.has_scenarios = True - qtbot.addWidget(parent) - box = SwitchComboBox(parent) - box.configure() - size = box.count() - assert size == 3 - - -def test_cutoff_slider_toggle(qtbot): - slider = CutoffMenu() - qtbot.addWidget(slider) - with qtbot.waitSignal(slider.buttons.number.toggled, timeout=800): - slider.buttons.number.click() - assert slider.limit_type == "number" - - -def test_input_dialog(qtbot): - """Test the various thing about the dialog widget.""" - parent = QWidget() - qtbot.addWidget(parent) - dialog = ForceInputDialog.get_text( - parent, "Early in the morning", "What should we do with a drunken sailor" - ) - assert dialog.output == "" - assert not dialog.buttons.button(QDialogButtonBox.Ok).isEnabled() - - existing = ForceInputDialog.get_text( - parent, "Existence", "is a nightmare", "and here is why" - ) - assert existing.output == "and here is why" - # Text in dialog MUST be changed before Ok button is enabled. - assert not dialog.buttons.button(QDialogButtonBox.Ok).isEnabled() - with qtbot.waitSignal(dialog.input.textChanged, timeout=100): - dialog.input.setText("Now it works.") - assert dialog.buttons.button(QDialogButtonBox.Ok).isEnabled() - - -def test_parameter_errorbox(qtbot, monkeypatch): - """Not truly used anymore in favour of not saving invalid values.""" - parent = QWidget() - qtbot.addWidget(parent) - - monkeypatch.setattr(QMessageBox, "exec_", lambda *args: QMessageBox.Cancel) - result = parameter_save_errorbox(parent, "got an error") - assert result == QMessageBox.Cancel - - -def test_simple_warning_box(qtbot, monkeypatch): - parent = QWidget() - qtbot.addWidget(parent) - - monkeypatch.setattr(QMessageBox, "warning", lambda *args: QMessageBox.Ok) - result = simple_warning_box(parent, "Warning title", "This is a warning") - assert result == QMessageBox.Ok diff --git a/tests_old/pytest.ini b/tests_old/pytest.ini deleted file mode 100644 index d53dac08f..000000000 --- a/tests_old/pytest.ini +++ /dev/null @@ -1,6 +0,0 @@ -[pytest] -testpaths=tests -qt_api=pyside2 -log_level=INFO -env = - AB_SIMPLE_CONSOLE=1 \ No newline at end of file diff --git a/tests_old/pytest_base.gz b/tests_old/pytest_base.gz deleted file mode 100644 index 0c33d9c52..000000000 Binary files a/tests_old/pytest_base.gz and /dev/null differ diff --git a/tests_old/widgets/test_custom_allocation_editor.py b/tests_old/widgets/test_custom_allocation_editor.py deleted file mode 100644 index a51ec7408..000000000 --- a/tests_old/widgets/test_custom_allocation_editor.py +++ /dev/null @@ -1,214 +0,0 @@ -from typing import Union -from unittest.mock import patch -from uuid import uuid4 -from pytestqt.qtbot import QtBot -from pytest import fixture, mark -from qtpy.QtCore import Qt - -from activity_browser.ui.widgets.custom_allocation_editor import CustomAllocationEditor -from activity_browser.mod import bw2data as bd -from bw_functional import allocation_strategies -from bw_functional.custom_allocation import MessageType, PropertyMessage - -TEST_PROPERTIES =[ - ("prop1", MessageType.ALL_VALID), - ("second prop", MessageType.NONNUMERIC_PROPERTY), - ("prop3", MessageType.MISSING_PROPERTY), - ("mass", MessageType.ALL_VALID), - ("fifth prop", MessageType.NONNUMERIC_PROPERTY), - ("prop5", MessageType.MISSING_PROPERTY) - ] - -PROPERTY_CHECK_VALUE1 = [ - PropertyMessage(1, 1, 1, MessageType.ALL_VALID, "Very good property") -] - -PROPERTY_CHECK_VALUE2 = [ - PropertyMessage(1, 1, 1, MessageType.MISSING_PROPERTY, "One missing property"), - PropertyMessage(1, 1, 1, MessageType.NONNUMERIC_PROPERTY, "One bad property"), - PropertyMessage(1, 1, 1, MessageType.MISSING_PROPERTY, "Other missing") -] - -PROPERTY_CHECK_VALUE3 = True -# -# -# def test_fill_empty(qtbot: QtBot): -# # Test the startup with empty property list -# with patch("activity_browser.ui.widgets.custom_allocation_editor.list_available_properties") as mock_list_prop: -# mock_list_prop.return_value = [] -# ed = CustomAllocationEditor("", "test_db") -# qtbot.add_widget(ed) -# ed.show() -# -# assert ed._property_table.rowCount() == 0 -# assert ed._save_button.isEnabled() == False -# -# def _check_property_list(ed: CustomAllocationEditor, -# properties: list[tuple[str, MessageType]]): -# sorted_props = sorted(properties) -# for i in range(ed._property_table.rowCount()): -# assert (ed._property_table.item(i, 0).data(Qt.ItemDataRole.DisplayRole) -# == sorted_props[i][0]) -# assert (ed._property_table.item(i, 1).data(Qt.ItemDataRole.DisplayRole) -# == sorted_props[i][1].value) -# -# @mark.parametrize("old_value, check_value", [ -# ("", []), -# ("prop3", PROPERTY_CHECK_VALUE1) -# ]) -# def test_fill_for_db(qtbot: QtBot, old_value: str, check_value: list[PropertyMessage]): -# # Test the filling of the table for a database -# with (patch("activity_browser.ui.widgets.custom_allocation_editor.list_available_properties") as mock_list_prop, -# patch("activity_browser.ui.widgets.custom_allocation_editor.check_property_for_allocation") as mock_check -# ): -# mock_list_prop.return_value = TEST_PROPERTIES -# mock_check.return_value = PROPERTY_CHECK_VALUE1 -# ed = CustomAllocationEditor(old_value, "test_db") -# qtbot.add_widget(ed) -# ed.show() -# -# mock_list_prop.assert_called_with("test_db", None) -# assert ed._property_table.rowCount() == len(TEST_PROPERTIES) -# assert ed._save_button.isEnabled() == (old_value != "") -# if old_value != "": -# mock_check.assert_called_with("test_db", old_value) -# for prop_message in check_value: -# assert prop_message.message in ed._status_text.toPlainText() -# else: -# assert ed._status_text.toPlainText() == "" -# mock_check.assert_not_called() -# assert ed.selected_property() == old_value -# _check_property_list(ed, TEST_PROPERTIES) -# -# @fixture -# def new_db(bw2test): -# db = bd.Database("test_db") -# db.register() -# return db -# -# @fixture -# def new_act(new_db): -# data = { -# "name": "new act", -# "unit": "unit", -# "type": "process", -# } -# act = new_db.new_activity(code=uuid4().hex, **data) -# act.save() -# return act -# -# @mark.parametrize("old_value, check_value", [ -# ("", []), -# ("prop3", PROPERTY_CHECK_VALUE1) -# ]) -# def test_fill_for_process(qtbot, new_act, -# old_value: str, check_value: list[PropertyMessage]): -# # Test the filling of the table for a specific activity -# with (patch("activity_browser.ui.widgets.custom_allocation_editor.list_available_properties") as mock_list_prop, -# patch("activity_browser.ui.widgets.custom_allocation_editor.check_property_for_process_allocation") as mock_check -# ): -# mock_list_prop.return_value = TEST_PROPERTIES -# mock_check.return_value = PROPERTY_CHECK_VALUE1 -# ed = CustomAllocationEditor(old_value, new_act) -# qtbot.add_widget(ed) -# ed.show() -# mock_list_prop.assert_called_with("test_db", new_act) -# assert ed._property_table.rowCount() == len(TEST_PROPERTIES) -# assert ed._save_button.isEnabled() == (old_value != "") -# if old_value != "": -# mock_check.assert_called_with(new_act, old_value) -# for prop_message in check_value: -# assert prop_message.message in ed._status_text.toPlainText() -# else: -# assert ed._status_text.toPlainText() == "" -# mock_check.assert_not_called() -# assert ed.selected_property() == old_value -# _check_property_list(ed, TEST_PROPERTIES) -# -# @mark.parametrize("old_value, check_old, click_row, check_new", [ -# ("", [], 4, PROPERTY_CHECK_VALUE2), -# ("prop3", PROPERTY_CHECK_VALUE1, 5, PROPERTY_CHECK_VALUE3) -# ]) -# def test_click_property(qtbot: QtBot, -# old_value: str, check_old: list[PropertyMessage], -# click_row: int, check_new: Union[bool, list[PropertyMessage]]): -# # Test that clicking a property updates the status text with the detailed analyses -# # of the respective property -# with (patch("activity_browser.ui.widgets.custom_allocation_editor.list_available_properties") as mock_list_prop, -# patch("activity_browser.ui.widgets.custom_allocation_editor.check_property_for_allocation") as mock_check -# ): -# mock_list_prop.return_value = TEST_PROPERTIES -# mock_check.return_value = check_old -# ed = CustomAllocationEditor(old_value, "test_db") -# qtbot.add_widget(ed) -# ed.show() -# -# mock_check.return_value = check_new -# click_index = ed._property_table.model().index(click_row, 0) -# assert click_index.isValid() -# item_rect = ed._property_table.visualRect(click_index) -# qtbot.mouseClick(ed._property_table.viewport(), Qt.MouseButton.LeftButton, pos = item_rect.center()) -# -# sorted_properties = sorted(TEST_PROPERTIES) -# assert ed._get_current_property() == sorted_properties[click_row][0] -# mock_check.assert_called_with("test_db", sorted_properties[click_row][0]) -# if isinstance(check_new, bool): -# assert ed._status_text.toPlainText() == "All good!" -# else: -# for prop_message in check_new: -# assert prop_message.message in ed._status_text.toPlainText() -# -# @mark.parametrize("old_value, click_rows, cancel", [ -# ("", [2, 4], False), -# ("prop3", [3, 1], False), # Make sure that 'mass' (index 1 after sort) is also selected once -# ("", [0, 5], True), -# ("prop3", [2, 2], True), -# ("", [], False), # No clicks, no previous selection -# ("prop3", [], False), # No clicks, only selection -# ]) -# def test_select_property(qtbot: QtBot, old_value: str, click_rows: list[int], cancel: bool): -# # Test the saving of the selected property -# with (patch("activity_browser.ui.widgets.custom_allocation_editor.list_available_properties") as mock_list_prop, -# patch("activity_browser.ui.widgets.custom_allocation_editor.check_property_for_allocation") as mock_check, -# patch("activity_browser.ui.widgets.custom_allocation_editor.add_custom_property_allocation_to_project") as mock_add_alloc -# ): -# mock_list_prop.return_value = TEST_PROPERTIES -# mock_check.return_value = [] -# ed = CustomAllocationEditor(old_value, "test_db") -# qtbot.add_widget(ed) -# ed.show() -# -# sorted_properties = sorted(TEST_PROPERTIES) -# -# # Click a couple of rows -# for row in click_rows: -# click_index = ed._property_table.model().index(row, 0) -# assert click_index.isValid() -# item_rect = ed._property_table.visualRect(click_index) -# qtbot.mouseClick(ed._property_table.viewport(), Qt.MouseButton.LeftButton, pos = item_rect.center()) -# -# assert ed._get_current_property() == sorted_properties[row][0] -# -# if cancel: -# # If the dialog is cancelled, it should return the old value -# qtbot.mouseClick(ed._cancel_button, Qt.MouseButton.LeftButton) -# assert ed.selected_property() == old_value -# mock_add_alloc.assert_not_called() -# else: -# qtbot.mouseClick(ed._save_button, Qt.MouseButton.LeftButton) -# selected = sorted_properties[click_rows[-1]][0] if click_rows else old_value -# # If there is anything selected -# if selected: -# assert not ed.isVisible() -# # If the dialog is closed with select, it should return the new value -# already_defined = selected in allocation_strategies -# assert ed.selected_property() == selected -# if already_defined: -# # add_custom_property_allocation_to_project should not be called for -# # existing properties -# mock_add_alloc.assert_not_called() -# else: -# mock_add_alloc.assert_called_with(selected) -# else: -# # No selection, dialog should be still open -# assert ed.isVisible() diff --git a/tests_old/widgets/test_property_editor.py b/tests_old/widgets/test_property_editor.py deleted file mode 100644 index 097d41ad4..000000000 --- a/tests_old/widgets/test_property_editor.py +++ /dev/null @@ -1,254 +0,0 @@ -from enum import Enum -from math import isnan, nan -from pytest import mark -from pytestqt.qtbot import QtBot -from qtpy import QtCore - -from activity_browser.ui.widgets.property_editor import PropertyEditor -from activity_browser.ui.style import style_item - - -class RowState(Enum): - NORMAL = 0 - MODIFIED = 1 - NEW = 2 - DELETED = 3 - DUPLICATED = 4 - LAST = 5 - -STATE_COLORS = { - RowState.NORMAL: None, - RowState.MODIFIED: style_item.brushes.get("modified"), - RowState.NEW: style_item.brushes.get("new"), - RowState.DELETED: style_item.brushes.get("deleted"), - RowState.DUPLICATED: style_item.brushes.get("duplicate"), - RowState.LAST: style_item.brushes.get("deleted"), -} - -def _check_row(ped: PropertyEditor, read_only: bool, row: int, key: str, value: float, state: RowState): - key_idx = ped._editor_table.model().index(row, 0) # Key - val_idx = ped._editor_table.model().index(row, 1) # Value - del_idx = ped._editor_table.model().index(row, 2) # Delete button - # check values - assert ped._editor_table.model().data(key_idx) == key - if isnan(value): - assert isnan(ped._editor_table.model().data(val_idx)) - else: - assert ped._editor_table.model().data(val_idx) == value - # check the delete button - assert del_idx.isValid() == (not read_only) - if del_idx.isValid(): - assert ped._editor_table.isPersistentEditorOpen(del_idx) == (state != RowState.LAST) - # check deleted strikthrough - font_key = ped._editor_table.model().data(key_idx, QtCore.Qt.ItemDataRole.FontRole) - font_val = ped._editor_table.model().data(val_idx, QtCore.Qt.ItemDataRole.FontRole) - if state == RowState.DELETED or key == "": - assert font_key.strikeOut() - assert font_val.strikeOut() - # check colors - key_color = ped._editor_table.model().data(key_idx, QtCore.Qt.ItemDataRole.ForegroundRole) - val_color = ped._editor_table.model().data(val_idx, QtCore.Qt.ItemDataRole.ForegroundRole) - if key != "": - assert key_color == val_color == STATE_COLORS[state] - else: - assert key_color == val_color == STATE_COLORS[RowState.DELETED] - - -def _check_last_row(ped: PropertyEditor, read_only: bool) -> bool: - last_row = ped._editor_table.model().rowCount() - 1 - _check_row(ped, read_only, last_row, "", nan, RowState.LAST) - -def _check_values(ped: PropertyEditor, read_only: bool, rows: list[tuple[str, float]], - state: list[RowState]): - assert len(rows) == len(state) - assert ped._editor_table.model().rowCount() == len(rows) + 1 - for i in range(len(rows)): - _check_row(ped, read_only, i, rows[i][0], rows[i][1], state[i]) - -@mark.parametrize("read_only", [(True,), (False,)]) -def test_show_empty(qtbot: QtBot, read_only: bool): - ped = PropertyEditor({}, read_only) - qtbot.add_widget(ped) - ped.show() - if read_only: - assert ped._message_label.text() == PropertyEditor.MESSAGE_READ_ONLY - else: - assert ped._message_label.text() == PropertyEditor.MESSAGE_NO_CHANGES_YET - assert ped._editor_table.model().rowCount() == 1 - - assert ped._save_button.isEnabled() == False - assert ped._cancel_button.isEnabled() == True - _check_last_row(ped, read_only) - -@mark.parametrize("values, read_only", [ - ({"one": 1, "two": 2.2, "three": 3.331}, True), - ({"one": 1, "two": 2.2, "three": 3.331}, False) - ]) -def test_show_with_values(qtbot: QtBot, values: dict[str, float], read_only: bool): - ped = PropertyEditor(values, read_only) - qtbot.add_widget(ped) - ped.show() - if read_only: - assert ped._message_label.text() == PropertyEditor.MESSAGE_READ_ONLY - else: - assert ped._message_label.text() == PropertyEditor.MESSAGE_NO_CHANGES_YET - - assert ped._editor_table.model().rowCount() == len(values) + 1 - _check_values(ped, read_only, list(values.items()), [RowState.NORMAL, RowState.NORMAL, RowState.NORMAL]) - - assert ped._save_button.isEnabled() == False - assert ped._cancel_button.isEnabled() == True - _check_last_row(ped, read_only) - -def test_delete(qtbot: QtBot): - ped = PropertyEditor({"one": 1, "two": 2.2, "three": 3.331}, read_only = False) - table = ped._editor_table - qtbot.add_widget(ped) - ped.show() - # Can not click on the delete button using QtBot, as the clicks are received - # by the viewport - # Delete one item - two_btn_idx = table.model().index(1, 2) - table._model.handle_delete_request(two_btn_idx) - _check_values(ped, False, [("one", 1), ("two", 2.2), ("three", 3.331)], - [RowState.NORMAL, RowState.DELETED, RowState.NORMAL]) - assert ped.properties() == {"one": 1, "three": 3.331} - assert ped._message_label.text() == PropertyEditor.MESSAGE_MODIFIED - assert ped._save_button.isEnabled() == True - - # Delete one more item - one_btn_idx = table.model().index(0, 2) - table._model.handle_delete_request(one_btn_idx) - _check_values(ped, False, [("one", 1), ("two", 2.2), ("three", 3.331)], - [RowState.DELETED, RowState.DELETED, RowState.NORMAL]) - assert ped.properties() == {"three": 3.331} - assert ped._message_label.text() == PropertyEditor.MESSAGE_MODIFIED - assert ped._save_button.isEnabled() == True - - # Delete the last item - three_btn_idx = table.model().index(2, 2) - table._model.handle_delete_request(three_btn_idx) - _check_values(ped, False, [("one", 1), ("two", 2.2), ("three", 3.331)], - [RowState.DELETED, RowState.DELETED, RowState.DELETED]) - assert ped.properties() == {} - assert ped._message_label.text() == PropertyEditor.MESSAGE_MODIFIED - assert ped._save_button.isEnabled() == True - - # Undelete one item - two_btn_idx = table.model().index(1, 2) - table._model.handle_delete_request(two_btn_idx) - _check_values(ped, False, [("one", 1), ("two", 2.2), ("three", 3.331)], - [RowState.DELETED, RowState.NORMAL, RowState.DELETED]) - assert ped.properties() == {"two": 2.2} - assert ped._message_label.text() == PropertyEditor.MESSAGE_MODIFIED - assert ped._save_button.isEnabled() == True - -def test_modify(qtbot: QtBot): - ped = PropertyEditor({"one": 1, "two": 2.2, "three": 3.331}, read_only = False) - table = ped._editor_table - qtbot.add_widget(ped) - ped.show() - mod_val_idx = table.model().index(1, 1) - # Can not do proper UI testing because of the delegates - table._model.setData(mod_val_idx, 5.7, QtCore.Qt.ItemDataRole.EditRole) - _check_values(ped, False, [("one", 1), ("two", 5.7), ("three", 3.331)], - [RowState.NORMAL, RowState.MODIFIED, RowState.NORMAL]) - assert ped.properties() == {"one": 1, "two": 5.7, "three": 3.331} - assert ped._message_label.text() == PropertyEditor.MESSAGE_MODIFIED - assert ped._save_button.isEnabled() == True - -def test_new(qtbot: QtBot): - ped = PropertyEditor({"one": 1, "two": 2.2, "three": 3.331}, read_only = False) - table = ped._editor_table - qtbot.add_widget(ped) - ped.show() - new_key_idx = table.model().index(3, 0) - # Can not do proper UI testing because of the delegates - table._model.setData(new_key_idx, "new", QtCore.Qt.ItemDataRole.EditRole) - _check_values(ped, False, [("one", 1), ("two", 2.2), ("three", 3.331), ("new", 0)], - [RowState.NORMAL, RowState.NORMAL, RowState.NORMAL, RowState.NEW]) - assert ped.properties() == {"one": 1, "two": 2.2, "three": 3.331, "new": 0,} - assert ped._message_label.text() == PropertyEditor.MESSAGE_MODIFIED - assert ped._save_button.isEnabled() == True - - new_val_idx = table.model().index(3, 1) - table._model.setData(new_val_idx, 5.2, QtCore.Qt.ItemDataRole.EditRole) - _check_values(ped, False, [("one", 1), ("two", 2.2), ("three", 3.331), ("new", 5.2)], - [RowState.NORMAL, RowState.NORMAL, RowState.NORMAL, RowState.NEW]) - assert ped.properties() == {"one": 1, "two": 2.2, "three": 3.331, "new": 5.2,} - assert ped._message_label.text() == PropertyEditor.MESSAGE_MODIFIED - assert ped._save_button.isEnabled() == True - -def test_duplicates(qtbot: QtBot): - ped = PropertyEditor({"one": 1, "two": 2.2, "three": 3.331}, read_only = False) - table = ped._editor_table - qtbot.add_widget(ped) - ped.show() - new_key_idx = table.model().index(3, 0) - # Can not do proper UI testing because of the delegates - # Create one duplicate - table._model.setData(new_key_idx, "one", QtCore.Qt.ItemDataRole.EditRole) - _check_values(ped, False, [("one", 1), ("two", 2.2), ("three", 3.331), ("one", 0)], - [RowState.DUPLICATED, RowState.NORMAL, RowState.NORMAL, RowState.DUPLICATED]) - assert ped.properties() == {"one": 0, "two": 2.2, "three": 3.331} - assert ped._message_label.text() == PropertyEditor.MESSAGE_DUPLICATES - assert ped._save_button.isEnabled() == False - # Create one more duplicate - two_key_idx = table.model().index(1, 0) - table._model.setData(two_key_idx, "one", QtCore.Qt.ItemDataRole.EditRole) - _check_values(ped, False, [("one", 1), ("one", 2.2), ("three", 3.331), ("one", 0)], - [RowState.DUPLICATED, RowState.DUPLICATED, RowState.NORMAL, RowState.DUPLICATED]) - assert ped.properties() == {"one": 0, "three": 3.331, } - assert ped._message_label.text() == PropertyEditor.MESSAGE_DUPLICATES - assert ped._save_button.isEnabled() == False - # Delete two duplicates - one_btn_idx = table.model().index(0, 2) - table._model.handle_delete_request(one_btn_idx) - new_btn_idx = table.model().index(3, 2) - table._model.handle_delete_request(new_btn_idx) - _check_values(ped, False, [("one", 1), ("one", 2.2), ("three", 3.331), ("one", 0)], - [RowState.DELETED, RowState.MODIFIED, RowState.NORMAL, RowState.DELETED]) - assert ped.properties() == {"one": 2.2, "three": 3.331, } - assert ped._message_label.text() == PropertyEditor.MESSAGE_MODIFIED - assert ped._save_button.isEnabled() == True - -def test_read_only_no_edit(qtbot: QtBot): - ped = PropertyEditor({"one": 1, "two": 2.2, "three": 3.331}, read_only = True) - table = ped._editor_table - qtbot.add_widget(ped) - ped.show() - - # Can not do proper UI testing because of the delegates - # The model itself is editable, but the UI is not - - two_key_idx = table.model().index(1, 0) - assert table._model.flags(two_key_idx) & QtCore.Qt.ItemFlag.ItemIsEditable == 0 - - three_val_idx = table.model().index(2, 1) - assert table._model.flags(three_val_idx) & QtCore.Qt.ItemFlag.ItemIsEditable == 0 - - assert ped._message_label.text() == PropertyEditor.MESSAGE_READ_ONLY - assert ped._save_button.isEnabled() == False - -def test_modify_undo(qtbot: QtBot): - ped = PropertyEditor({"one": 1, "two": 2.2, "three": 3.331}, read_only = False) - table = ped._editor_table - qtbot.add_widget(ped) - ped.show() - assert ped._message_label.text() == PropertyEditor.MESSAGE_NO_CHANGES_YET - # Can not do proper UI testing because of the delegates - # Modify a value - mod_val_idx = table.model().index(1, 1) - table._model.setData(mod_val_idx, 5.7, QtCore.Qt.ItemDataRole.EditRole) - _check_values(ped, False, [("one", 1), ("two", 5.7), ("three", 3.331)], - [RowState.NORMAL, RowState.MODIFIED, RowState.NORMAL]) - assert ped.properties() == {"one": 1, "two": 5.7, "three": 3.331} - assert ped._message_label.text() == PropertyEditor.MESSAGE_MODIFIED - assert ped._save_button.isEnabled() == True - # Undo the change - table._model.setData(mod_val_idx, 2.2, QtCore.Qt.ItemDataRole.EditRole) - _check_values(ped, False, [("one", 1), ("two", 2.2), ("three", 3.331)], - [RowState.NORMAL, RowState.NORMAL, RowState.NORMAL]) - assert ped.properties() == {"one": 1, "two": 2.2, "three": 3.331} - assert ped._message_label.text() == PropertyEditor.MESSAGE_NO_CHANGES - assert ped._save_button.isEnabled() == False diff --git a/tests_old/widgets/test_tag_editor.py b/tests_old/widgets/test_tag_editor.py deleted file mode 100644 index e6729176a..000000000 --- a/tests_old/widgets/test_tag_editor.py +++ /dev/null @@ -1,206 +0,0 @@ -from unittest.mock import MagicMock - -import pytest -from qtpy import QtWidgets, QtCore, QtGui -from pytestqt.qtbot import QtBot - -from activity_browser.ui.delegates import JSONDelegate, ComboBoxDelegate -from activity_browser.ui.tables.tags import TagTable, TagDelegate -from activity_browser.ui.widgets import TagEditor - - -@pytest.fixture -def tag_table(request, qtbot: QtBot): - """Fixture for initializing TagTable""" - tags = {"tag1": "value1", "tag2": "value2"} - read_only = getattr(request, "param", False) - tag_table = TagTable(tags, "test_database", read_only=read_only) - qtbot.addWidget(tag_table) - return tag_table - - -@pytest.fixture -def tag_editor(request, qtbot): - """Fixture for creating the TagEditor dialog""" - target = {"database": "test_db", "tags": {"tag1": "value1"}} - read_only = getattr(request, "param", False) - tag_editor = TagEditor(target=target, read_only=read_only) - qtbot.addWidget(tag_editor) - return tag_editor - - -def test_add_tag(tag_table, qtbot): - """Test adding a new tag to the model.""" - initial_row_count = tag_table.model.rowCount() - - # Simulate clicking the 'Add Tag' button - with qtbot.waitSignal(tag_table.model.rowsInserted, timeout=10): - tag_table.add_tag_button.trigger() - - # Check if the row count has increased - assert tag_table.model.rowCount() == initial_row_count + 1 - - -def test_remove_tag(tag_table, qtbot): - """Test removing a tag from the model.""" - initial_row_count = tag_table.model.rowCount() - - # Select the first tag - index = tag_table.proxy_model.index(0, 0) - tag_table.setCurrentIndex(index) - - # Simulate clicking the 'Remove Tag' button - with qtbot.waitSignal(tag_table.model.rowsRemoved, timeout=10): - tag_table.remove_tag_button.trigger() - - # Check if the row count has decreased - assert tag_table.model.rowCount() == initial_row_count - 1 - - -@pytest.mark.parametrize( - "tag_table, read_only, on_element", - [ - pytest.param(True, True, True, id="read_only on element"), - pytest.param(True, True, False, id="read_only off element"), - pytest.param(False, False, True, id="editable on element"), - pytest.param(False, False, False, id="editable on element"), - ], - indirect=["tag_table"], -) -def test_context_menu(tag_table, read_only, on_element, qtbot: QtBot): - """Test that the context menu is shown and contains the correct actions.""" - index = tag_table.proxy_model.index(0, 0) - tag_table.setCurrentIndex(index) - - if on_element: - position = tag_table.visualRect(index).center() - expected_actions = [tag_table.remove_tag_button, tag_table.add_tag_button] - else: - position = tag_table.viewport().rect().center() - expected_actions = [tag_table.add_tag_button] - if read_only: - expected_actions = list( - filter(lambda x: x is not tag_table.add_tag_button, expected_actions) - ) - # The context menu is shown using QtWidgets.QMenu.exec_, and this method - # does not return, so the test gets blocked. Patching it did not work, - # probably due to the same named static method. - def hide_menu(): - menu = None - def check_menu(): - nonlocal menu - assert (menu := tag_table.findChild(QtWidgets.QMenu)) is not None - qtbot.waitUntil(check_menu) - # close the menu before the assertions, as a failing assertion will - # prevent the menu from closing, thus failing further test execution - menu.close() - assert isinstance(menu, QtWidgets.QMenu) - actions = menu.actions() - assert set(actions) == set(expected_actions) - - # Use a timer to execute code after the menu is shown - QtCore.QTimer.singleShot(200, hide_menu) - - # Simulate a right-click to open the context menu - tag_table.contextMenuEvent( - QtGui.QContextMenuEvent(QtGui.QContextMenuEvent.Reason.Mouse, position) - ) - - # Checks are done in the hide_menu, which will block until the menu - # is created - - -def test_update_proxy_model(tag_table): - """Test if the proxy model is set up and sorting works.""" - tag_table.update_proxy_model() - - assert isinstance(tag_table.proxy_model, QtCore.QSortFilterProxyModel) - - # Check sorting by the first column - tag_table.sortByColumn(0, QtCore.Qt.AscendingOrder) - first_tag = tag_table.proxy_model.index(0, 0).data() - assert first_tag == "tag1" - - tag_table.sortByColumn(0, QtCore.Qt.DescendingOrder) - first_tag = tag_table.proxy_model.index(0, 0).data() - assert first_tag == "tag2" - - -@pytest.mark.parametrize( - "tag_table", - [ - pytest.param(False, id="editable"), - pytest.param(True, id="read_only"), - ], - indirect=["tag_table"], -) -def test_delegate_for_columns(tag_table): - """Test if the correct delegates are set for each column.""" - delegate_col_0 = tag_table.itemDelegateForColumn(0) - delegate_col_1 = tag_table.itemDelegateForColumn(1) - delegate_col_2 = tag_table.itemDelegateForColumn(2) - - if not tag_table.read_only: - assert isinstance(delegate_col_0, TagDelegate) - assert isinstance(delegate_col_1, JSONDelegate) - assert isinstance(delegate_col_2, ComboBoxDelegate) - else: - assert all( - [x is None for x in [delegate_col_0, delegate_col_1, delegate_col_2]] - ) - - -def test_initialization(tag_editor): - """Test that the TagEditor initializes correctly.""" - assert tag_editor.windowTitle() == "Tag Editor" - assert tag_editor._save_button.isEnabled() is False - assert tag_editor._message_label.text() == "No changes yet" - assert isinstance(tag_editor._tag_table, QtWidgets.QTableView) - - -def test_read_only_mode(qtbot): - """Test the TagEditor in read-only mode.""" - target = {"database": "test_db", "tags": {"tag1": "value1"}} - tag_editor = TagEditor(target=target, read_only=True) - - assert tag_editor._save_button.isEnabled() is False - assert tag_editor._message_label.text() == "Read only" - - -def test_save_button_enabled_on_change(tag_editor, qtbot): - """Test that the save button is enabled when the tag data changes.""" - tag_editor._tag_table.add_tag_button.trigger() - - assert tag_editor._message_label.text() == "Modified" - assert tag_editor._save_button.isEnabled() is True - - -def test_save_button_disabled_on_duplicate_keys(tag_editor, qtbot): - """Test that the save button is disabled if there are duplicate keys.""" - tag_editor._tag_table.add_tag_button.trigger() - tag_editor._tag_table.add_tag_button.trigger() - - assert tag_editor._message_label.text() == "Error: there are duplicate tag names" - assert tag_editor._save_button.isEnabled() is False - - -def test_static_edit_method(qtbot): - """Test the static edit method behavior.""" - target = {"database": "test_db", "tags": {"tag1": "value1"}} - - # Mock the dialog exec_ method to simulate user accepting the changes - TagEditor.exec_ = MagicMock(return_value=QtWidgets.QDialog.Accepted) - TagEditor.tags = MagicMock(return_value={"tag1": "new_value"}) - - result = TagEditor.edit(target, read_only=False) - - assert result is True - assert target["tags"] == {"tag1": "new_value"} - - # Now simulate the user cancelling the dialog - TagEditor.exec_ = MagicMock(return_value=QtWidgets.QDialog.Rejected) - - result = TagEditor.edit(target, read_only=False) - - assert result is False - assert target["tags"] == {"tag1": "new_value"} # No changes after cancel diff --git a/tests_old/wizards/test_export_wizard.py b/tests_old/wizards/test_export_wizard.py deleted file mode 100644 index e3f7e83fc..000000000 --- a/tests_old/wizards/test_export_wizard.py +++ /dev/null @@ -1,37 +0,0 @@ -from qtpy import QtCore, QtWidgets - -from activity_browser.ui.wizards.db_export_wizard import DatabaseExportWizard - -# TODO: Add fixture with small database to export. - - -# def test_trigger_export_wizard(qtbot, ab_app, monkeypatch): -# """Test the triggers for the export wizard.""" -# assert bw.projects.current == 'pytest_project' -# qtbot.waitForWindowShown(ab_app.main_window) -# -# menu_bar = ab_app.main_window.menu_bar -# -# monkeypatch.setattr( -# DatabaseController, "export_database_wizard", lambda *args: None -# ) -# # Trigger the action for export database. -# with qtbot.waitSignal(signals.export_database, timeout=500): -# menu_bar.export_db_action.trigger() - - -def test_open_export_wizard(ab_app, qtbot): - """Actually open the export wizard.""" - with qtbot.waitExposed(ab_app.main_window): - wizard = DatabaseExportWizard(ab_app.main_window) - qtbot.addWidget(wizard) - wizard.show() - - # The initial field for the database does not allow 'finish' - assert wizard.field("database_choice") == "-----" - assert not wizard.button(DatabaseExportWizard.FinishButton).isEnabled() - - # And close it down - qtbot.mouseClick( - wizard.button(QtWidgets.QWizard.CancelButton), QtCore.Qt.LeftButton - ) diff --git a/tests_old/wizards/test_import_wizard.py b/tests_old/wizards/test_import_wizard.py deleted file mode 100644 index 8083ebf47..000000000 --- a/tests_old/wizards/test_import_wizard.py +++ /dev/null @@ -1,34 +0,0 @@ -# -*- coding: utf-8 -*- -from qtpy import QtCore, QtWidgets - -from activity_browser.ui.wizards.db_import_wizard import DatabaseImportWizard - -# -# -# def test_open_db_wizard_button(qtbot, ab_app, monkeypatch): -# """Show that the signals and slots works for importing.""" -# assert bw.projects.current == 'pytest_project' -# qtbot.waitForWindowShown(ab_app.main_window) -# project_tab = ab_app.main_window.left_panel.tabs['Project'] -# -# # Monkeypatch the 'import_database_wizard' method in the controller -# monkeypatch.setattr( -# DatabaseController, "import_database_wizard", lambda *args: None -# ) -# with qtbot.waitSignal(signals.import_database, timeout=500): -# qtbot.mouseClick( -# project_tab.databases_widget.import_database_button, -# QtCore.Qt.LeftButton -# ) - - -def test_open_db_wizard(ab_app, qtbot): - """Open the wizard itself.""" - with qtbot.waitExposed(ab_app.main_window): - wizard = DatabaseImportWizard(ab_app.main_window) - qtbot.addWidget(wizard) - wizard.show() - - qtbot.mouseClick( - wizard.button(QtWidgets.QWizard.CancelButton), QtCore.Qt.LeftButton - ) diff --git a/tests_old/wizards/test_uncertainty_wizard.py b/tests_old/wizards/test_uncertainty_wizard.py deleted file mode 100644 index 33fd690f6..000000000 --- a/tests_old/wizards/test_uncertainty_wizard.py +++ /dev/null @@ -1,227 +0,0 @@ -# -*- coding: utf-8 -*- -import logging -import sys - -import numpy as np -import pytest -from bw2data.parameters import ProjectParameter -from qtpy.QtWidgets import QMessageBox, QWizard -from stats_arrays.distributions import (LognormalUncertainty, - TriangularUncertainty, - UndefinedUncertainty, - UniformUncertainty) - -from activity_browser import signals -from activity_browser.ui.wizards import UncertaintyWizard - -""" -Mess around with the uncertainty wizard. -""" - - -@pytest.mark.skipif(sys.platform == "darwin", reason="tests segfaults on osx") -def test_wizard_fail(ab_app, qtbot): - """Can't create a wizard if no uncertainty interface exists.""" - mystery_box = ["Hello", "My", "Name", "Is", "Error"] # Type is list. - with pytest.raises(TypeError): - UncertaintyWizard(mystery_box) - - -@pytest.mark.skipif(sys.platform == "darwin", reason="tests segfaults on osx") -def test_uncertainty_wizard_simple(ab_app, qtbot, caplog): - """Use extremely simple text to open the wizard and go to all the pages.""" - caplog.set_level(logging.INFO) - param = ProjectParameter.create(name="test1", amount=3) - wizard = UncertaintyWizard(param, None) - qtbot.addWidget(wizard) - wizard.show() - - assert "uncertainty type" in wizard.uncertainty_info - wizard.extract_uncertainty() - wizard.extract_lognormal_loc() - - # Go to the pedigree page - with qtbot.waitSignal(wizard.currentIdChanged, timeout=100): - wizard.type.pedigree.click() - - # Pedigree is empty, so complaint is issued. - captured = caplog.text - assert "Could not extract pedigree data" in captured - - # Now go back for giggles. - with qtbot.waitSignal(wizard.currentIdChanged, timeout=100): - wizard.button(QWizard.BackButton).click() - assert not wizard.using_pedigree - - -@pytest.mark.skipif(sys.platform == "darwin", reason="tests segfaults on osx") -def test_graph_rebuild(ab_app, qtbot): - """Test that the graph is correctly built and rebuilt, ensure - that the 'finish' button is enabled and disabled at the correct - times. - """ - param = ProjectParameter.create(name="test2", amount=3) - wizard = UncertaintyWizard(param, None) - qtbot.addWidget(wizard) - wizard.show() - - # Check that the graph exists and distribution is 'unknown' - assert wizard.type.plot.isVisible() - assert wizard.type.distribution.currentIndex() == UndefinedUncertainty.id - assert wizard.button(QWizard.FinishButton).isEnabled() - # Select an uncertainty distribution, fill out numbers. - with qtbot.waitSignal(wizard.type.distribution.currentIndexChanged, timeout=100): - wizard.type.distribution.setCurrentIndex(UniformUncertainty.id) - assert not wizard.type.complete # Missing values for valid uncertainty. - assert not wizard.button(QWizard.FinishButton).isEnabled() - - # When programmatically changing values, no textEdited signal is emitted. - with qtbot.assertNotEmitted(wizard.type.minimum.textEdited): - wizard.type.minimum.setText("1") - wizard.type.generate_plot() - assert not wizard.type.complete # Still missing 'maximum' - assert not wizard.button(QWizard.FinishButton).isEnabled() - - with qtbot.assertNotEmitted(wizard.type.minimum.textEdited): - wizard.type.maximum.setText("5") - wizard.type.generate_plot() - assert wizard.type.complete - assert wizard.button(QWizard.FinishButton).isEnabled() - - -@pytest.mark.skipif(sys.platform == "darwin", reason="tests segfaults on osx") -def test_update_uncertainty(ab_app, qtbot): - """Using the signal/controller setup, update the uncertainty of a parameter""" - param = ProjectParameter.create(name="uc1", amount=3) - wizard = UncertaintyWizard(param, None) - qtbot.addWidget(wizard) - wizard.show() - - wizard.type.distribution.setCurrentIndex(TriangularUncertainty.id) - wizard.type.minimum.setText("1") - wizard.type.maximum.setText("5") - wizard.type.generate_plot() - assert wizard.type.complete - - # Now trigger a 'finish' action - with qtbot.waitSignal(signals.parameter.changed, timeout=100): - wizard.button(QWizard.FinishButton).click() - - # Reload param - param = ProjectParameter.get(name="uc1") - assert "loc" in param.data and param.data["loc"] == 3 - - -@pytest.mark.skipif(sys.platform == "darwin", reason="tests segfaults on osx") -def test_update_alter_mean(qtbot, monkeypatch, ab_app): - param = ProjectParameter.create(name="uc2", amount=1) - wizard = UncertaintyWizard(param, None) - qtbot.addWidget(wizard) - wizard.show() - - # Select the lognormal distribution and set 'loc' and 'scale' fields. - wizard.type.distribution.setCurrentIndex(LognormalUncertainty.id) - wizard.type.loc.setText("1") - wizard.type.scale.setText("0.3") - wizard.type.generate_plot() - assert wizard.type.complete - - # Now, monkeypatch Qt to ensure a 'yes' is selected for updating. - monkeypatch.setattr( - QMessageBox, "question", staticmethod(lambda *args: QMessageBox.Yes) - ) - # Now trigger a 'finish' action - with qtbot.waitSignal(signals.parameter.changed, timeout=100): - wizard.button(QWizard.FinishButton).click() - - # Reload param and check that the amount is changed. - param = ProjectParameter.get(name="uc2") - assert "loc" in param.data and param.amount != 1 - loc = param.data["loc"] - assert loc == 1 - assert np.isclose(np.log(param.amount), loc) - - -@pytest.mark.skipif(sys.platform == "darwin", reason="tests segfaults on osx") -def test_lognormal_mean_balance(qtbot, bw2test, ab_app): - uncertain = { - "loc": 2, - "scale": 0.2, - "uncertainty type": 2, - } - param = ProjectParameter.create(name="uc1", amount=3, data=uncertain) - wizard = UncertaintyWizard(param, None) - qtbot.addWidget(wizard) - wizard.show() - - # Compare loc with mean, - loc, mean = float(wizard.type.loc.text()), float(wizard.type.mean.text()) - assert np.isclose(np.exp(loc), mean) - wizard.type.check_negative() - assert not wizard.field("negative") - - # Alter mean and loc fields in turn to show balancing methods - with qtbot.assertNotEmitted(wizard.type.mean.textEdited): - wizard.type.mean.setText("") - wizard.type.balance_loc_with_mean() - wizard.type.check_negative() - assert wizard.type.loc.text() == "nan" - # Setting the mean to a negative number will still return the same loc - # value, but it will alter the 'negative' field. - with qtbot.assertNotEmitted(wizard.type.mean.textEdited): - wizard.type.mean.setText("-5") - wizard.type.balance_loc_with_mean() - wizard.type.check_negative() - assert np.isclose(np.exp(float(wizard.type.loc.text())), 5) - assert wizard.field("negative") - - -@pytest.mark.skipif(sys.platform == "darwin", reason="tests segfaults on osx") -def test_pedigree(qtbot, bw2test, ab_app): - """Configure uncertainty using the pedigree page of the wizard.""" - uncertain = { - "loc": 2, - "scale": 0.2, - "uncertainty type": 2, - "pedigree": { - "reliability": 1, - "completeness": 2, - "temporal correlation": 2, - "geographical correlation": 2, - "further technological correlation": 3, - }, - } - param = ProjectParameter.create(name="uc1", amount=3, data=uncertain) - wizard = UncertaintyWizard(param, None) - qtbot.addWidget(wizard) - wizard.show() - - # Uncertainty data has pedigree in it. - assert "pedigree" in wizard.obj.uncertainty - - # Go to the pedigree page - with qtbot.waitSignal(wizard.currentIdChanged, timeout=100): - wizard.type.pedigree.click() - assert wizard.using_pedigree # Uncertainty/Pedigree data is valid - - loc, mean = float(wizard.pedigree.loc.text()), float(wizard.pedigree.mean.text()) - assert np.isclose(np.exp(loc), mean) - # The uncertainty should be positive - assert not wizard.field("negative") - wizard.pedigree.check_negative() - assert not wizard.field("negative") - - # Alter mean and loc fields in turn to show balancing methods - with qtbot.assertNotEmitted(wizard.pedigree.mean.textEdited): - wizard.pedigree.mean.setText("") - wizard.pedigree.balance_loc_with_mean() - wizard.pedigree.check_negative() - assert wizard.pedigree.loc.text() == "nan" - # Setting the mean to a negative number will still return the same loc - # value, but it will alter the 'negative' field. - with qtbot.assertNotEmitted(wizard.pedigree.mean.textEdited): - wizard.pedigree.mean.setText("-5") - wizard.pedigree.balance_loc_with_mean() - wizard.pedigree.check_negative() - assert np.isclose(np.exp(float(wizard.pedigree.loc.text())), 5) - assert wizard.field("negative")