"""Offscreen tests for the BOM window's drag/drop path (no display needed — QGraphicsScene is pure Qt). Guards the placement-id vs cut-item-id crash.""" import os import pytest os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") pytest.importorskip("PySide6") from PySide6.QtWidgets import QApplication # noqa: E402 from woodshop.cutplan import find_placement # noqa: E402 from woodshop.gui.bom_window import BomWindow, _Piece # noqa: E402 from woodshop.gui.controller import Controller # noqa: E402 _app = QApplication.instance() or QApplication([]) def _pieces(w): return sorted((it for it in w.scene.items() if isinstance(it, _Piece)), key=lambda it: it.pos().x()) def test_drop_overlap_reverts_without_crashing(tmp_path): c = Controller(str(tmp_path / "s.json")) c.place("2x4", 30) c.place("2x4", 30) # one stick, two pieces w = BomWindow(c) first, second = _pieces(w)[:2] home = (second.sp_id, second.pos().x(), second.pos().y()) second.setPos(0, second.pos().y()) # drop on top of the first -> overlap w._drop_piece(second, home) # must not raise (was StopIteration) assert "revert" in w._status.text().lower() def test_drop_onto_incompatible_stock_reverts(tmp_path): c = Controller(str(tmp_path / "s.json")) c.place("2x4", 24) c.place("ply-3/4", 24, width_in=24) w = BomWindow(c) lumber = next(it for it in _pieces(w) if not find_placement(w._plan, it.pid)[0].is_sheet) sheet_y = next(y0 for y0, _y1, sp in w._rows if sp.is_sheet) home = (lumber.sp_id, lumber.pos().x(), lumber.pos().y()) lumber.setPos(10, sheet_y + 5) # drag the 2x4 onto the plywood sheet w._drop_piece(lumber, home) # must not raise assert "can't go" in w._status.text() def test_best_of_n_button_keeps_valid_plan(tmp_path): from woodshop.cutplan import validate_cut_plan c = Controller(str(tmp_path / "s.json")) for ln in (50, 46, 40, 30): c.place("2x4", ln) w = BomWindow(c) w._best_of_n() # no locks -> best of 100 assert validate_cut_plan(w._plan) == [] assert "best" in w._status.text().lower() def test_best_of_n_with_lock_runs_and_validates(tmp_path): from woodshop.cutplan import validate_cut_plan c = Controller(str(tmp_path / "s.json")) for ln in (50, 46, 40, 30): c.place("2x4", ln) w = BomWindow(c) p = next(p for sp in w._plan.stock_pieces for p in sp.placements) p.locked = True w._best_of_n() # locked path: strategies + shuffles assert validate_cut_plan(w._plan) == [] assert "locked" in w._status.text() def test_valid_move_commits(tmp_path): c = Controller(str(tmp_path / "s.json")) c.place("2x4", 20) c.place("2x4", 20) w = BomWindow(c) second = _pieces(w)[1] home = (second.sp_id, second.pos().x(), second.pos().y()) second.setPos(50 * w._px, second.pos().y()) # slide it right, still clear w._drop_piece(second, home) assert "placed" in w._status.text().lower()