import pytest import gettext import locale import threading import sys from pytestqt.plugin import qapp import time import pyautogui as pag import logging from qtpy import QtTest, QtCore, QtGui, QtWidgets, QT6 from os import path, remove from gnuradio import gr from grc.gui_qt import properties from grc.gui_qt.grc import Application from grc.gui_qt.components.window import MainWindow from grc.gui_qt.Platform import Platform log = logging.getLogger("grc") @pytest.fixture(scope="session") def qapp_cls_(): settings = properties.Properties([]) settings.argv = [""] """ Translation Support """ # Try to get the current locale. Always add English lc, encoding = locale.getlocale() if lc: languages = [lc] languages += settings.DEFAULT_LANGUAGE log.debug("Using locale - %s" % str(languages)) # Still run even if the english translation isn't found language = gettext.translation( settings.APP_NAME, settings.path.LANGUAGE, languages=languages, fallback=True ) if type(language) == gettext.NullTranslations: log.error("Unable to find any translation") log.error("Default English translation missing") else: log.info("Using translation - %s" % language.info()["language"]) # Still need to install null translation to let the system handle calls to _() language.install() model = Platform( version=gr.version(), version_parts=(gr.major_version(), gr.api_version(), gr.minor_version()), prefs=gr.prefs(), install_prefix=gr.prefix(), ) model.build_library() app = Application(settings, model, []) app.MainWindow.showMaximized() return app def global_pos(block, view): scene_pos = block.mapToScene(block.boundingRect().center()) global_pos = view.viewport().mapToGlobal(view.mapFromScene(scene_pos)) return global_pos def type_text(qtbot, app, keys): for key in keys: # Each sequence contains a single key. # That's why we use the first element keycode = QtGui.QKeySequence(key)[0] if QT6: qtbot.keyClick(app.focusWidget(), keycode.key(), QtCore.Qt.NoModifier) else: qtbot.keyClick(app.focusWidget(), keycode, QtCore.Qt.NoModifier) def keystroke(qtbot, app, key): qtbot.keyClick(app.focusWidget(), key, QtCore.Qt.NoModifier) qtbot.wait(100) def ctrl_keystroke(qtbot, app, key): qtbot.keyClick(app.focusWidget(), key, QtCore.Qt.ControlModifier) qtbot.wait(100) def gather_menu_items(menu): ret = {} for act in menu.actions(): ret[act.text()] = act return ret def add_block_from_query(qtbot, app, query): qtbot.keyClick(app.focusWidget(), QtCore.Qt.Key_F, QtCore.Qt.ControlModifier) type_text(qtbot, app, query) qtbot.wait(10) pag.press('down') qtbot.wait(10) pag.press('enter') qtbot.wait(10) def find_blocks(flowgraph, block_type): blocks = [] for b in flowgraph.blocks: if b.key == block_type: blocks.append(b) if len(blocks) == 0: return None if len(blocks) == 1: return blocks[0] return blocks def click_on(qtbot, app, item, button="left"): scaling = app.MainWindow.screen().devicePixelRatio() view = app.MainWindow.currentView click_pos = scaling * global_pos(item.gui, view) pag.click(click_pos.x(), click_pos.y(), button=button) qtbot.wait(100) def undo(qtbot, app): qtbot.keyClick(app.focusWidget(), QtCore.Qt.Key_Z, QtCore.Qt.ControlModifier) qtbot.wait(100) def redo(qtbot, app): qtbot.keyClick( app.focusWidget(), QtCore.Qt.Key_Z, QtCore.Qt.ControlModifier | QtCore.Qt.ShiftModifier, ) qtbot.wait(100) def delete_block(qtbot, app, block): view = app.MainWindow.currentView scaling = app.MainWindow.screen().devicePixelRatio() click_pos = scaling * global_pos(block.gui, view) pag.click(click_pos.x(), click_pos.y(), button="left") qtbot.wait(100) qtbot.keyClick(app.focusWidget(), QtCore.Qt.Key_Delete) qtbot.wait(100) def menu_shortcut(qtbot, app, menu_name, menu_key, shortcut_key): menu = app.MainWindow.menus[menu_name] qtbot.keyClick(app.focusWidget(), menu_key, QtCore.Qt.AltModifier) qtbot.wait(100) qtbot.keyClick(menu, shortcut_key) qtbot.wait(100) # Start by closing the flowgraph that pops up on start def test_file_close_init(qtbot, qapp_cls_, monkeypatch): win = qapp_cls_.MainWindow monkeypatch.setattr( QtWidgets.QMessageBox, "question", lambda *args: QtWidgets.QMessageBox.Discard, ) qtbot.wait(100) assert win.tabWidget.count() == 1 menu_shortcut(qtbot, qapp_cls_, "file", QtCore.Qt.Key_F, QtCore.Qt.Key_L) assert win.tabWidget.count() == 1 def test_delete_block(qtbot, qapp_cls_): qtbot.wait(100) var = find_blocks(qapp_cls_.MainWindow.currentFlowgraph, "variable") assert var is not None delete_block(qtbot, qapp_cls_, var) qtbot.wait(100) assert len(qapp_cls_.MainWindow.currentFlowgraph.blocks) == 1 undo(qtbot, qapp_cls_) assert len(qapp_cls_.MainWindow.currentFlowgraph.blocks) == 2 def test_add_null_sink(qtbot, qapp_cls_): qtbot.wait(100) add_block_from_query(qtbot, qapp_cls_, "null sin") n_sink = find_blocks(qapp_cls_.MainWindow.currentFlowgraph, "blocks_null_sink") assert n_sink is not None delete_block(qtbot, qapp_cls_, n_sink) def test_add_null_source(qtbot, qapp_cls_): qtbot.wait(100) add_block_from_query(qtbot, qapp_cls_, "null sou") n_sou = find_blocks(qapp_cls_.MainWindow.currentFlowgraph, "blocks_null_source") assert n_sou is not None delete_block(qtbot, qapp_cls_, n_sou) def test_add_throttle(qtbot, qapp_cls_): qtbot.wait(100) add_block_from_query(qtbot, qapp_cls_, "throttle") throttle = find_blocks(qapp_cls_.MainWindow.currentFlowgraph, "blocks_throttle") assert throttle is not None delete_block(qtbot, qapp_cls_, throttle) def test_right_click(qtbot, qapp_cls_): qtbot.wait(100) add_block_from_query(qtbot, qapp_cls_, "throttle") throttle = find_blocks(qapp_cls_.MainWindow.currentFlowgraph, "blocks_throttle") assert throttle is not None qtbot.wait(100) def close(): qtbot.keyClick(throttle.gui.right_click_menu, QtCore.Qt.Key_Escape) QtCore.QTimer.singleShot(200, close) click_on(qtbot, qapp_cls_, throttle, button="right") qtbot.wait(100) delete_block(qtbot, qapp_cls_, throttle) def test_errors(qtbot, qapp_cls_): menu = qapp_cls_.MainWindow.menus["build"] def assert_and_close(): assert qapp_cls_.activeWindow() != qapp_cls_.MainWindow qtbot.keyClick(qapp_cls_.activeWindow(), QtCore.Qt.Key_Escape) qtbot.wait(100) add_block_from_query(qtbot, qapp_cls_, "throttle") qtbot.wait(100) qtbot.keyClick(qapp_cls_.focusWidget(), QtCore.Qt.Key_B, QtCore.Qt.AltModifier) qtbot.wait(100) QtCore.QTimer.singleShot(200, assert_and_close) # qtbot.keyClick(menu, QtCore.Qt.Key_E) # Not necessary since it's already selected (it's the first item) qtbot.keyClick(menu, QtCore.Qt.Key_Enter) qtbot.wait(300) throttle = find_blocks(qapp_cls_.MainWindow.currentFlowgraph, "blocks_throttle") assert throttle is not None delete_block(qtbot, qapp_cls_, throttle) def test_open_properties(qtbot, qapp_cls_): qtbot.wait(100) qtbot.mouseDClick( qapp_cls_.MainWindow.currentView.viewport(), QtCore.Qt.LeftButton, pos=qapp_cls_.MainWindow.currentView.mapFromScene( qapp_cls_.MainWindow.currentFlowgraph.options_block.gui.pos() + QtCore.QPointF(15.0, 15.0) ), ) qtbot.wait(100) assert qapp_cls_.MainWindow.currentFlowgraph.options_block.gui.props_dialog.isVisible() keystroke(qtbot, qapp_cls_, QtCore.Qt.Key_Escape) assert ( not qapp_cls_.MainWindow.currentFlowgraph.options_block.gui.props_dialog.isVisible() ) def test_change_id(qtbot, qapp_cls_): opts = find_blocks(qapp_cls_.MainWindow.currentFlowgraph, "options") assert opts.params["title"].value == "Not titled yet" qtbot.mouseDClick( qapp_cls_.MainWindow.currentView.viewport(), QtCore.Qt.LeftButton, pos=qapp_cls_.MainWindow.currentView.mapFromScene( opts.gui.pos() + QtCore.QPointF(15.0, 15.0) ), ) qtbot.wait(100) qtbot.mouseDClick( opts.gui.props_dialog.edit_params[1], QtCore.Qt.LeftButton, ) type_text(qtbot, qapp_cls_, "changed") qtbot.wait(100) keystroke(qtbot, qapp_cls_, QtCore.Qt.Key_Enter) assert opts.params["title"].value == "Not changed yet" qtbot.wait(100) keystroke(qtbot, qapp_cls_, QtCore.Qt.Key_Enter) qtbot.wait(100) undo(qtbot, qapp_cls_) assert opts.params["title"].value == "Not titled yet" redo(qtbot, qapp_cls_) assert opts.params["title"].value == "Not changed yet" def test_rotate_block(qtbot, qapp_cls_): qtbot.wait(100) opts = find_blocks(qapp_cls_.MainWindow.currentFlowgraph, "options") # Still running into issues with what has focus depending on the order of tests run. # This is a problem! Tests should be able to run independently without affecting other # tests. Need to track this down, but for those tests that are failing for this reason, # something like below seems to be a workaround click_on(qtbot, qapp_cls_, opts) qtbot.wait(400) click_on(qtbot, qapp_cls_, opts) old_rotation = opts.states["rotation"] keystroke(qtbot, qapp_cls_, QtCore.Qt.Key_Left) new_rotation = opts.states["rotation"] assert new_rotation == (old_rotation - 90) % 360 undo(qtbot, qapp_cls_) new_rotation = opts.states["rotation"] assert new_rotation == old_rotation keystroke(qtbot, qapp_cls_, QtCore.Qt.Key_Right) new_rotation = opts.states["rotation"] assert new_rotation == old_rotation + 90 undo(qtbot, qapp_cls_) new_rotation = opts.states["rotation"] assert new_rotation == old_rotation def test_disable_enable(qtbot, qapp_cls_): qtbot.wait(100) var = find_blocks(qapp_cls_.MainWindow.currentFlowgraph, "variable") click_on(qtbot, qapp_cls_, var) assert var is not None assert var.state == "enabled" keystroke(qtbot, qapp_cls_, QtCore.Qt.Key_D) assert var.state == "disabled" keystroke(qtbot, qapp_cls_, QtCore.Qt.Key_E) assert var.state == "enabled" def test_move_blocks(qtbot, qapp_cls_): fg = qapp_cls_.MainWindow.currentFlowgraph view = qapp_cls_.MainWindow.currentView scaling = qapp_cls_.MainWindow.screen().devicePixelRatio() qtbot.wait(100) add_block_from_query(qtbot, qapp_cls_, "throttle") throttle = find_blocks(fg, "blocks_throttle") variable = find_blocks(fg, "variable") assert throttle is not None click_on(qtbot, qapp_cls_, variable) qtbot.wait(100) start_throttle = scaling * global_pos(throttle.gui, view) start_variable = scaling * global_pos(variable.gui, view) pag.moveTo(start_throttle.x(), start_throttle.y()) pag.mouseDown() def drag(): for i in range(20): pag.move(0, scaling * 10) drag_t = threading.Thread(target=drag) drag_t.start() while drag_t.is_alive(): qtbot.wait(50) pag.mouseUp() qtbot.wait(100) assert scaling * global_pos(throttle.gui, view) != start_throttle undo(qtbot, qapp_cls_) assert scaling * global_pos(throttle.gui, view) == start_throttle redo(qtbot, qapp_cls_) assert scaling * global_pos(throttle.gui, view) != start_throttle # Variable shouldn't move assert scaling * global_pos(variable.gui, view) == start_variable delete_block(qtbot, qapp_cls_, throttle) def test_connection(qtbot, qapp_cls_): fg = qapp_cls_.MainWindow.currentFlowgraph view = qapp_cls_.MainWindow.currentView scaling = qapp_cls_.MainWindow.screen().devicePixelRatio() qtbot.wait(100) for block in ["null sou", "null sin"]: add_block_from_query(qtbot, qapp_cls_, block) n_src = find_blocks(fg, "blocks_null_source") n_sink = find_blocks(fg, "blocks_null_sink") assert len(fg.connections) == 0 start = scaling * global_pos(n_sink.gui, view) pag.moveTo(start.x(), start.y()) pag.mouseDown() def drag(): for i in range(20): pag.move(scaling * 10, 0) drag_t = threading.Thread(target=drag) drag_t.start() while drag_t.is_alive(): qtbot.wait(50) pag.mouseUp() click_on(qtbot, qapp_cls_, n_src.sources[0]) click_on(qtbot, qapp_cls_, n_sink.sinks[0]) assert len(fg.connections) == 1 undo(qtbot, qapp_cls_) assert len(fg.connections) == 0 redo(qtbot, qapp_cls_) assert len(fg.connections) == 1 connection = next(iter(fg.connections)) # get a connection without removing it # delete connection with delete key press event assert len(fg.connections) == 1 click_on(qtbot, qapp_cls_, connection) keystroke(qtbot, qapp_cls_, QtCore.Qt.Key_Delete) assert len(fg.connections) == 0 qtbot.wait(100) undo(qtbot, qapp_cls_) # delete connection with double click click_on(qtbot, qapp_cls_, connection) click_on(qtbot, qapp_cls_, connection) assert len(fg.connections) == 0 qtbot.wait(100) for block in [n_src, n_sink]: delete_block(qtbot, qapp_cls_, block) def test_num_inputs(qtbot, qapp_cls_): fg = qapp_cls_.MainWindow.currentFlowgraph view = qapp_cls_.MainWindow.currentView scaling = qapp_cls_.MainWindow.screen().devicePixelRatio() qtbot.wait(100) for block in ["null sou", "null sin"]: add_block_from_query(qtbot, qapp_cls_, block) n_src = find_blocks(fg, "blocks_null_source") n_sink = find_blocks(fg, "blocks_null_sink") assert len(n_sink.sinks) == 1 start = scaling * global_pos(n_sink.gui, view) pag.moveTo(start.x(), start.y()) pag.mouseDown() def drag(): for i in range(20): pag.move(scaling * 10, 0) drag_t = threading.Thread(target=drag) drag_t.start() while drag_t.is_alive(): qtbot.wait(50) pag.mouseUp() click_on(qtbot, qapp_cls_, n_src.sources[0]) click_on(qtbot, qapp_cls_, n_sink.sinks[0]) qtbot.wait(100) click_pos = scaling * global_pos(n_sink.gui, view) pag.doubleClick(click_pos.x(), click_pos.y(), button="left") qtbot.wait(100) param_index = 0 for i in range(len(n_sink.gui.props_dialog.edit_params)): if n_sink.gui.props_dialog.edit_params[i].param.key == 'num_inputs': param_index = i qtbot.mouseDClick(n_sink.gui.props_dialog.edit_params[param_index], QtCore.Qt.LeftButton) type_text(qtbot, qapp_cls_, "2") qtbot.wait(100) keystroke(qtbot, qapp_cls_, QtCore.Qt.Key_Enter) assert len(n_sink.sinks) == 2 assert len(fg.connections) == 1 click_pos = scaling * global_pos(n_sink.gui, view) pag.doubleClick(click_pos.x(), click_pos.y(), button="left") qtbot.wait(100) qtbot.mouseDClick(n_sink.gui.props_dialog.edit_params[param_index], QtCore.Qt.LeftButton) type_text(qtbot, qapp_cls_, "1") qtbot.wait(100) keystroke(qtbot, qapp_cls_, QtCore.Qt.Key_Enter) qtbot.wait(100) keystroke(qtbot, qapp_cls_, QtCore.Qt.Key_Enter) qtbot.wait(100) assert len(n_sink.sinks) == 1 assert len(fg.connections) == 1 # I think loses focus makes delete_fail the first time. This makes it work, but is a hack click_on(qtbot, qapp_cls_, n_src) pag.click(click_pos.x() + 50, click_pos.y() + 50, button="left") for block in [n_src, n_sink]: delete_block(qtbot, qapp_cls_, block) qtbot.wait(100) assert len(fg.blocks) == 2 def test_bus(qtbot, qapp_cls_): fg = qapp_cls_.MainWindow.currentFlowgraph view = qapp_cls_.MainWindow.currentView scaling = qapp_cls_.MainWindow.screen().devicePixelRatio() qtbot.wait(100) add_block_from_query(qtbot, qapp_cls_, "null sin") n_sink = find_blocks(fg, "blocks_null_sink") assert len(n_sink.sinks) == 1 click_pos = scaling * global_pos(n_sink.gui, view) pag.doubleClick(click_pos.x(), click_pos.y(), button="left") qtbot.wait(100) param_index = 0 for i in range(len(n_sink.gui.props_dialog.edit_params)): if n_sink.gui.props_dialog.edit_params[i].param.key == 'num_inputs': param_index = i qtbot.mouseDClick(n_sink.gui.props_dialog.edit_params[param_index], QtCore.Qt.LeftButton) type_text(qtbot, qapp_cls_, "2") qtbot.wait(100) keystroke(qtbot, qapp_cls_, QtCore.Qt.Key_Enter) qtbot.wait(100) keystroke(qtbot, qapp_cls_, QtCore.Qt.Key_Enter) qtbot.wait(100) assert len(n_sink.sinks) == 2 # Enable bus port qtbot.wait(100) more_menu = qapp_cls_.MainWindow.menus["more"] menu_shortcut(qtbot, qapp_cls_, "edit", QtCore.Qt.Key_E, QtCore.Qt.Key_M) qtbot.wait(100) qtbot.keyClick(more_menu, QtCore.Qt.Key_Up) qtbot.wait(100) qtbot.keyClick(more_menu, QtCore.Qt.Key_Enter) qtbot.wait(100) assert len(n_sink.sinks) == 3 assert n_sink.sinks[2].dtype == 'bus' # Disable bus port qtbot.wait(100) more_menu = qapp_cls_.MainWindow.menus["more"] menu_shortcut(qtbot, qapp_cls_, "edit", QtCore.Qt.Key_E, QtCore.Qt.Key_M) qtbot.wait(100) qtbot.keyClick(more_menu, QtCore.Qt.Key_Up) qtbot.wait(100) qtbot.keyClick(more_menu, QtCore.Qt.Key_Enter) qtbot.wait(100) assert len(n_sink.sinks) == 2 # Test undo undo(qtbot, qapp_cls_) qtbot.wait(100) assert len(n_sink.sinks) == 3 qtbot.wait(100) undo(qtbot, qapp_cls_) qtbot.wait(100) assert len(n_sink.sinks) == 2 delete_block(qtbot, qapp_cls_, n_sink) qtbot.wait(100) def test_bypass(qtbot, qapp_cls_): scaling = qapp_cls_.MainWindow.screen().devicePixelRatio() fg = qapp_cls_.MainWindow.currentFlowgraph view = qapp_cls_.MainWindow.currentView qtbot.wait(100) for block in ["null sou", "throttle"]: add_block_from_query(qtbot, qapp_cls_, block) n_src = find_blocks(fg, "blocks_null_source") throttle = find_blocks(fg, "blocks_throttle") # Bypass the throttle block click_on(qtbot, qapp_cls_, throttle) keystroke(qtbot, qapp_cls_, QtCore.Qt.Key_B) assert throttle.state == "bypassed" undo(qtbot, qapp_cls_) assert throttle.state == "enabled" redo(qtbot, qapp_cls_) assert throttle.state == "bypassed" qtbot.wait(100) keystroke(qtbot, qapp_cls_, QtCore.Qt.Key_E) assert throttle.state == "enabled" # Try to bypass the null source, this shouldn't work click_on(qtbot, qapp_cls_, n_src) keystroke(qtbot, qapp_cls_, QtCore.Qt.Key_B) assert n_src.state == "enabled" for block in [throttle, n_src]: delete_block(qtbot, qapp_cls_, block) def test_file_save(qtbot, qapp_cls_, monkeypatch, tmp_path): fg_path = tmp_path / "test_save.grc" monkeypatch.setattr( QtWidgets.QFileDialog, "selectedFiles", lambda *args, **kargs: (str(fg_path), "") ) monkeypatch.setattr( QtWidgets.QFileDialog, "exec_", lambda *args: QtWidgets.QFileDialog.Accepted ) assert not fg_path.exists(), "File/Save (setup): File already exists" ctrl_keystroke(qtbot, qapp_cls_, QtCore.Qt.Key_S) assert fg_path.exists(), "File/Save: Could not save file" def test_file_save_as(qtbot, qapp_cls_, monkeypatch, tmp_path): fg_path = tmp_path / "test.grc" monkeypatch.setattr( QtWidgets.QFileDialog, "selectedFiles", lambda *args, **kargs: (str(fg_path), "") ) monkeypatch.setattr( QtWidgets.QFileDialog, "exec_", lambda *args: QtWidgets.QFileDialog.Accepted ) qtbot.wait(100) menu_shortcut(qtbot, qapp_cls_, "file", QtCore.Qt.Key_F, QtCore.Qt.Key_A) assert fg_path.exists() def test_file_save_copy(qtbot, qapp_cls_, monkeypatch, tmp_path): fg_path = tmp_path / "test_copy.grc" monkeypatch.setattr( QtWidgets.QFileDialog, "selectedFiles", lambda *args, **kargs: (str(fg_path), "") ) monkeypatch.setattr( QtWidgets.QFileDialog, "exec_", lambda *args: QtWidgets.QFileDialog.Accepted ) qtbot.wait(100) assert not fg_path.exists(), "File/Save Copy (setup): File already exists" menu_shortcut(qtbot, qapp_cls_, "file", QtCore.Qt.Key_F, QtCore.Qt.Key_Y) assert fg_path.exists(), "File/Save Copy: Could not save file" # TODO: File/Open def test_file_screen_capture_pdf(qtbot, qapp_cls_, monkeypatch, tmp_path): fg_path = tmp_path / "test.pdf" monkeypatch.setattr( QtWidgets.QFileDialog, "getSaveFileName", lambda *args, **kargs: (str(fg_path), "") ) qtbot.wait(100) assert not fg_path.exists(), "File/Screen Capture (setup): PDF already exists" ctrl_keystroke(qtbot, qapp_cls_, QtCore.Qt.Key_P) assert fg_path.exists(), "File/Screen Capture: Could not create PDF" def test_file_screen_capture_png(qtbot, qapp_cls_, monkeypatch, tmp_path): fg_path = tmp_path / "test.png" assert not fg_path.exists() monkeypatch.setattr( QtWidgets.QFileDialog, "getSaveFileName", lambda *args, **kargs: (str(fg_path), "") ) qtbot.wait(100) assert not fg_path.exists(), "File/Screen Capture (setup): PNG already exists" ctrl_keystroke(qtbot, qapp_cls_, QtCore.Qt.Key_P) assert fg_path.exists(), "File/Screen Capture: Could not create PNG" def test_file_screen_capture_svg(qtbot, qapp_cls_, monkeypatch, tmp_path): fg_path = tmp_path / "test.svg" assert not fg_path.exists() monkeypatch.setattr( QtWidgets.QFileDialog, "getSaveFileName", lambda *args, **kargs: (str(fg_path), "") ) qtbot.wait(100) assert not fg_path.exists(), "File/Screen Capture (setup): SVG already exists" ctrl_keystroke(qtbot, qapp_cls_, QtCore.Qt.Key_P) assert fg_path.exists(), "File/Screen Capture: Could not create SVG" def test_file_preferences(qtbot, qapp_cls_): menu = qapp_cls_.MainWindow.menus["file"] items = gather_menu_items(menu) def assert_and_close(): assert qapp_cls_.activeWindow() != qapp_cls_.MainWindow qtbot.keyClick(qapp_cls_.activeWindow(), QtCore.Qt.Key_Enter) qtbot.keyClick(qapp_cls_.focusWidget(), QtCore.Qt.Key_F, QtCore.Qt.AltModifier) qtbot.wait(100) QtCore.QTimer.singleShot(200, assert_and_close) qtbot.keyClick(menu, QtCore.Qt.Key_F) qtbot.wait(600) assert qapp_cls_.activeWindow() == qapp_cls_.MainWindow qtbot.wait(100) def test_file_examples(qtbot, qapp_cls_): menu = qapp_cls_.MainWindow.menus["file"] items = gather_menu_items(menu) def assert_and_close(): assert qapp_cls_.activeWindow() != qapp_cls_.MainWindow qtbot.keyClick(qapp_cls_.activeWindow(), QtCore.Qt.Key_Escape) qtbot.keyClick(qapp_cls_.focusWidget(), QtCore.Qt.Key_F, QtCore.Qt.AltModifier) qtbot.wait(100) QtCore.QTimer.singleShot(200, assert_and_close) qtbot.keyClick(menu, QtCore.Qt.Key_E) qtbot.wait(600) assert qapp_cls_.activeWindow() == qapp_cls_.MainWindow qtbot.wait(100) def test_edit_actions(qtbot, qapp_cls_): pass def test_edit_select_all(qtbot, qapp_cls_): qtbot.keyClick(qapp_cls_.focusWidget(), QtCore.Qt.Key_A, QtCore.Qt.ControlModifier) qtbot.wait(100) def test_edit_cut_paste(qtbot, qapp_cls_): fg = qapp_cls_.MainWindow.currentFlowgraph qtbot.wait(100) var = find_blocks(fg, "variable") assert var is not None, "Edit/Cut and paste (setup): Could not find variable block" click_on(qtbot, qapp_cls_, var) ctrl_keystroke(qtbot, qapp_cls_, QtCore.Qt.Key_X) qtbot.wait(100) var = find_blocks(fg, "variable") assert var is None, "Edit/Cut and paste: Could not cut variable block" ctrl_keystroke(qtbot, qapp_cls_, QtCore.Qt.Key_V) qtbot.wait(100) var = find_blocks(fg, "variable") assert var is not None, "Edit/Cut and paste: Could not paste variable block" qtbot.wait(100) def test_edit_copy_paste(qtbot, qapp_cls_): fg = qapp_cls_.MainWindow.currentFlowgraph qtbot.wait(100) var = find_blocks(fg, "variable") assert var is not None, "Edit/Copy and paste (setup): Could not find variable block" click_on(qtbot, qapp_cls_, var) ctrl_keystroke(qtbot, qapp_cls_, QtCore.Qt.Key_C) qtbot.wait(100) ctrl_keystroke(qtbot, qapp_cls_, QtCore.Qt.Key_V) vars = find_blocks(fg, "variable") assert isinstance(vars, list), "Edit/Copy and paste: Could not paste variable block" assert len(vars) == 2, "Edit/Copy and paste: Could not paste variable block" assert ( vars[0].name != vars[1].name ), "Edit/Copy and paste: Newly pasted variable block's ID is the same as the original block's ID" delete_block(qtbot, qapp_cls_, vars[1]) def test_view_actions(qtbot, qapp_cls_): pass def test_build_actions(qtbot, qapp_cls_): pass def test_tools_actions(qtbot, qapp_cls_): pass def test_tools_oot_browser(qtbot, qapp_cls_): menu = qapp_cls_.MainWindow.menus["tools"] items = gather_menu_items(menu) def assert_open(): assert qapp_cls_.activeWindow() != qapp_cls_.MainWindow qtbot.keyClick(qapp_cls_.focusWidget(), QtCore.Qt.Key_T, QtCore.Qt.AltModifier) qtbot.wait(100) QtCore.QTimer.singleShot(100, assert_open) qtbot.keyClick(menu, QtCore.Qt.Key_O) qtbot.wait(200) qtbot.keyClick(qapp_cls_.activeWindow(), QtCore.Qt.Key_Escape) qtbot.wait(200) def test_reports_actions(qtbot, qapp_cls_): pass def test_help_windows(qtbot, qapp_cls_): def assert_and_close(): assert qapp_cls_.activeWindow() != qapp_cls_.MainWindow qtbot.keyClick(qapp_cls_.activeWindow(), QtCore.Qt.Key_Enter) def test_help_window(menu_key): qtbot.keyClick(qapp_cls_.focusWidget(), QtCore.Qt.Key_H, QtCore.Qt.AltModifier) qtbot.wait(300) QtCore.QTimer.singleShot(200, assert_and_close) qtbot.keyClick(menu, menu_key) qtbot.wait(600) assert qapp_cls_.activeWindow() == qapp_cls_.MainWindow qtbot.wait(100) menu = qapp_cls_.MainWindow.menus["help"] qtbot.wait(100) for key in [ QtCore.Qt.Key_H, QtCore.Qt.Key_T, QtCore.Qt.Key_K, QtCore.Qt.Key_G, QtCore.Qt.Key_A, QtCore.Qt.Key_Q, ]: test_help_window(key) def test_file_new_close(qtbot, qapp_cls_, monkeypatch): win = qapp_cls_.MainWindow monkeypatch.setattr( QtWidgets.QMessageBox, "question", lambda *args: QtWidgets.QMessageBox.Discard, ) qtbot.wait(100) menu_shortcut(qtbot, qapp_cls_, "file", QtCore.Qt.Key_F, QtCore.Qt.Key_N) assert win.tabWidget.count() == 2, "File/New" for i in range(3, 5): ctrl_keystroke(qtbot, qapp_cls_, QtCore.Qt.Key_N) assert win.tabWidget.count() == i, "File/New" for i in range(1, 4): ctrl_keystroke(qtbot, qapp_cls_, QtCore.Qt.Key_W) assert win.tabWidget.count() == 4 - i, "File/Close" def test_generate(qtbot, qapp_cls_, monkeypatch, tmp_path): fg = qapp_cls_.MainWindow.currentFlowgraph view = qapp_cls_.MainWindow.currentView scaling = qapp_cls_.MainWindow.screen().devicePixelRatio() fg_path = tmp_path / "test_generate.grc" py_path = tmp_path / "default.py" monkeypatch.setattr( QtWidgets.QFileDialog, "selectedFiles", lambda *args, **kargs: (str(fg_path), "") ) monkeypatch.setattr( QtWidgets.QFileDialog, "exec_", lambda *args: QtWidgets.QFileDialog.Accepted ) qtbot.wait(100) for block in ["null sou", "null sin"]: add_block_from_query(qtbot, qapp_cls_, block) n_src = find_blocks(fg, "blocks_null_source") n_sink = find_blocks(fg, "blocks_null_sink") assert len(fg.connections) == 0 start = scaling * global_pos(n_sink.gui, view) pag.moveTo(start.x(), start.y()) pag.mouseDown() def drag(): for i in range(20): pag.move(scaling * 10, 0) drag_t = threading.Thread(target=drag) drag_t.start() while drag_t.is_alive(): qtbot.wait(50) pag.mouseUp() click_on(qtbot, qapp_cls_, n_src.sources[0]) click_on(qtbot, qapp_cls_, n_sink.sinks[0]) assert not fg_path.exists(), "File/Save (setup): .grc file already exists" assert not py_path.exists(), "File/Save (setup): Python file already exists" menu_shortcut(qtbot, qapp_cls_, "build", QtCore.Qt.Key_B, QtCore.Qt.Key_G) qtbot.wait(500) assert fg_path.exists(), "File/Save: Could not save .grc file" assert py_path.exists(), "File/Save: Could not save Python file" def test_file_close_all(qtbot, qapp_cls_, monkeypatch): win = qapp_cls_.MainWindow monkeypatch.setattr( QtWidgets.QMessageBox, "question", lambda *args: QtWidgets.QMessageBox.Discard, ) qtbot.wait(100) for i in range(1, 4): ctrl_keystroke(qtbot, qapp_cls_, QtCore.Qt.Key_N) assert win.tabWidget.count() == 4, "File/Close All" menu_shortcut(qtbot, qapp_cls_, "file", QtCore.Qt.Key_F, QtCore.Qt.Key_L) assert win.tabWidget.count() == 1, "File/Close All" def test_quit(qtbot, qapp_cls_, monkeypatch): monkeypatch.setattr( QtWidgets.QMessageBox, "question", lambda *args: QtWidgets.QMessageBox.Discard, ) qapp_cls_.MainWindow.actions["exit"].trigger() assert True time.sleep(1)