Skip to content

Commit d47d9b4

Browse files
authored
Merge pull request #34 from Aljaz-R/styling
Updated pyproject file,added tests with pytest and fixed minor bugs
2 parents d7f5e58 + 80e59c8 commit d47d9b4

8 files changed

Lines changed: 891 additions & 763 deletions

File tree

.github/workflows/test.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ jobs:
3838
run: echo "$APPDATA\Python\Scripts" >> $GITHUB_PATH
3939
- name: Configure poetry
4040
run: poetry config virtualenvs.in-project true
41+
- name: Install system dependencies (for PyQt6)
42+
run: sudo apt-get update && sudo apt-get install -y libegl1
4143
- name: Set up cache
4244
uses: actions/cache@v3
4345
id: cache
@@ -49,5 +51,7 @@ jobs:
4951
run: timeout 10s poetry run pip --version || rm -rf .venv
5052
- name: Install dependencies
5153
run: poetry install
52-
- name: Run tests
53-
run: poetry run pytest
54+
- name: Run tests with Xvfb (for PyQt support)
55+
run: |
56+
export QT_QPA_PLATFORM=offscreen
57+
xvfb-run -a poetry run pytest

niaaml_gui/widgets/pipeline_canvas.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,6 @@ def keyPressEvent(self, event):
238238

239239
else:
240240
super().keyPressEvent(event)
241-
self.pipelineStateChanged.emit()
242241

243242

244243
def progress_start(self, maximum: int | None = None) -> None:
@@ -367,6 +366,9 @@ def _clear_connection_highlights(self):
367366
self._highlighted_circles.clear()
368367

369368
def is_pipeline_ready(self) -> bool:
369+
if not self.block_data:
370+
return False
371+
370372
for block, info in self.block_data.items():
371373
if hasattr(block, "get_value"):
372374
value = block.get_value()
@@ -378,6 +380,7 @@ def is_pipeline_ready(self) -> bool:
378380

379381

380382

383+
381384
class InteractiveConfigBlock(QGraphicsPathItem):
382385
__niaamlFeatureSelectionAlgorithmsMap = (
383386
FeatureSelectionAlgorithmFactory().get_name_to_classname_mapping()
@@ -402,9 +405,11 @@ def __init__(
402405
is_number_input: bool = False,
403406
dropdown_options=None,
404407
icon_path: str | None = None,
408+
is_sidebar=True,
409+
readonly=False
405410
):
406411
super().__init__()
407-
412+
self.readonly = readonly
408413
self.shape = shape.lower()
409414
self.icon_path = icon_path
410415
self.label = label
@@ -494,6 +499,9 @@ def __init__(
494499
self._layout_contents()
495500

496501
def _handle_click_action(self):
502+
503+
if self.readonly:
504+
return
497505
if self.dropdown_options:
498506
return
499507
elif self.label in ["Feature Selection", "Feature Transform", "Classifier"]:
@@ -613,7 +621,8 @@ def _open_csv_editor(self):
613621
if not getattr(self, "value", ""):
614622
QMessageBox.warning(None, "No CSV", "Please pick a CSV file first.")
615623
return
616-
CSVEditorWindow(self.value).show()
624+
self.csv_editor_window = CSVEditorWindow(self.value)
625+
self.csv_editor_window.show()
617626

618627
def getPath(self):
619628
dlg = QFileDialog

niaaml_gui/widgets/sidebar.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def __init__(self, canvas):
7272
lst.setSpacing(2)
7373
lst.setDragEnabled(True)
7474
lst.mouseMoveEvent = self._wrapped_start_drag
75-
lst.itemClicked.connect(self.handle_click)
75+
#lst.itemClicked.connect(self.handle_click)
7676

7777
for label, icon_file in items.items():
7878
icon_path = os.path.join(icon_dir, icon_file)

poetry.lock

Lines changed: 752 additions & 748 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ repository = "https://github.com/firefly-cpp/NiaAML-GUI"
1010
readme = "README.md"
1111

1212
[tool.poetry.dependencies]
13-
python = ">=3.9,<3.13"
13+
python = ">=3.10,<3.13"
1414
niapy = "^2.5.2"
1515
QtAwesome = "^1.2.3"
1616
niaaml = "^2.1.2"
1717
scikit-learn = "^1.6.1"
1818
pyqt6 = "^6.6.0"
19+
pytest-qt = "^4.5.0"
20+
pyqt-feedback-flow = "^0.3.5"
1921

2022
[tool.poetry.group.dev.dependencies]
2123
pytest = "^7.4.3"

tests/test_pipeline.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ def test_pipeline_run_works_fine(self):
8787
has_header=True,
8888
contains_classes=True,
8989
)
90+
9091
pipeline.optimize(
9192
data_reader.get_x(),
9293
data_reader.get_y(),
@@ -95,21 +96,17 @@ def test_pipeline_run_works_fine(self):
9596
"ParticleSwarmAlgorithm",
9697
"Accuracy",
9798
)
98-
predicted = pipeline.run(
99-
pandas.DataFrame(
100-
numpy.random.uniform(
101-
low=0.0, high=15.0, size=(30, data_reader.get_x().shape[1])
102-
)
103-
)
104-
)
10599

106-
self.assertEqual(predicted.shape, (30,))
100+
test_data = data_reader.get_x().iloc[:30]
101+
predicted = pipeline.run(test_data)
107102

103+
self.assertEqual(predicted.shape, (30,))
108104
s1 = set(data_reader.get_y())
109105
s2 = set(predicted)
110106
self.assertTrue(s2.issubset(s1))
111107
self.assertTrue(len(s2) > 0 and len(s2) <= 2)
112108

109+
113110
def test_pipeline_export_works_fine(self):
114111
pipeline = Pipeline(
115112
feature_selection_algorithm=SelectKBest(),

tests/test_pipeline_canvas.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import pytest
2+
from pytestqt.qtbot import QtBot
3+
4+
from niaaml_gui.widgets.pipeline_canvas import PipelineCanvas
5+
6+
7+
@pytest.fixture
8+
def canvas(qtbot: QtBot):
9+
widget = PipelineCanvas()
10+
qtbot.addWidget(widget)
11+
return widget
12+
13+
14+
def test_add_config_block_emits_signal(canvas, qtbot):
15+
with qtbot.waitSignal(canvas.pipelineStateChanged, timeout=1000):
16+
canvas.add_config_block("Select CSV File")
17+
18+
19+
def test_pipeline_ready_false_when_empty(canvas):
20+
canvas.block_data.clear()
21+
canvas.scene.clear()
22+
23+
print("Block data keys:", canvas.block_data.keys())
24+
25+
assert len(canvas.block_data) == 0
26+
assert not canvas.is_pipeline_ready()
27+

tests/test_pipeline_controls.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import pytest
2+
from pytestqt.qtbot import QtBot
3+
from PyQt6.QtCore import Qt
4+
from PyQt6.QtWidgets import QWidget, QVBoxLayout
5+
6+
from niaaml_gui.widgets.pipeline_controls import PipelineControlsWidget
7+
from niaaml_gui.widgets.pipeline_canvas import PipelineCanvas
8+
9+
10+
@pytest.fixture
11+
def controls(qtbot: QtBot):
12+
widget = PipelineControlsWidget()
13+
qtbot.addWidget(widget)
14+
return widget
15+
16+
17+
def test_run_button_initially_disabled(controls):
18+
assert controls.run_button.isEnabled() is False
19+
20+
21+
def test_enable_disable_run_button(controls):
22+
controls.setRunEnabled(True)
23+
assert controls.run_button.isEnabled() is True
24+
25+
controls.setRunEnabled(False)
26+
assert controls.run_button.isEnabled() is False
27+
28+
29+
def test_run_button_emits_signal(controls, qtbot):
30+
with qtbot.waitSignal(controls.runClicked, timeout=1000):
31+
controls.setRunEnabled(True)
32+
qtbot.mouseClick(controls.run_button, Qt.MouseButton.LeftButton)
33+
34+
35+
36+
@pytest.fixture
37+
def full_widget(qtbot: QtBot):
38+
canvas = PipelineCanvas()
39+
controls = PipelineControlsWidget()
40+
41+
container = QWidget()
42+
layout = QVBoxLayout()
43+
layout.addWidget(canvas)
44+
layout.addWidget(controls)
45+
container.setLayout(layout)
46+
47+
qtbot.addWidget(container)
48+
49+
def update_run_button_state():
50+
ready = canvas.is_pipeline_ready()
51+
controls.run_button.setEnabled(ready)
52+
53+
54+
container.canvas = canvas
55+
container.controls = controls
56+
container.update_fn = update_run_button_state
57+
58+
canvas.pipelineStateChanged.connect(update_run_button_state)
59+
60+
return container
61+
62+
63+
def test_update_run_button_state_false_when_incomplete(full_widget):
64+
canvas = full_widget.canvas
65+
controls = full_widget.controls
66+
update_fn = full_widget.update_fn
67+
68+
canvas.add_config_block("Select CSV File")
69+
update_fn()
70+
71+
assert controls.run_button.isEnabled() is False
72+
73+
74+
def test_update_run_button_state_true_when_ready(full_widget):
75+
canvas = full_widget.canvas
76+
controls = full_widget.controls
77+
update_fn = full_widget.update_fn
78+
79+
canvas.add_config_block("Select CSV File")
80+
block = list(canvas.block_data.items())[0][1]
81+
block["path"] = "tests/tests_files/dataset_no_header_no_classes.csv"
82+
83+
update_fn()
84+
85+
assert controls.run_button.isEnabled() is True

0 commit comments

Comments
 (0)