FEnigma/tests/test_app_accept_proposal.py
Dominik Roth 5a35ea7776 Revert auto-assigned ids back to letters; only detected ids are numeric
Auto-assignment (no real id known: a manual add, or an accepted
proposal with no confident marker-id read) must stay visually
distinct from a genuinely detected id, or a made-up number could
collide with or be mistaken for a real one. Reverts the previous
commit's switch to numeric auto-assignment (_next_free_numeric_id) --
that was wrong, caught by the user immediately. _next_free_id
(letters, rolling over to "AA"/"AB"/... past 26) is back as the
fallback, still scoped per type (that part of the previous change was
correct and stays). Plain numbers are reserved for an id
_accept_proposal is actually confident was read off the marker itself
(Proposal.detected_id), passed straight through and never touching
auto-assignment.

Also fixes detected_id's own collision pre-check in _accept_proposal,
which wasn't scoped per type either -- same bug as the Change ID
popover fix, just in a second place: a detected id could get
needlessly discarded because an unrelated type already used that
number, not because of a real collision.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 19:49:30 +02:00

71 lines
2.7 KiB
Python

"""_accept_proposal: an accepted proposal's entity id should prefer the
marker's own detected "#<N>" id (map_vision.read_marker_id, via
Proposal.detected_id) over an auto-assigned letter, so ids on the board
match what's actually on screen -- falling back to auto-assign (a
letter, deliberately not a number, so it can't collide with or be
mistaken for a real detected id -- see models.py's _next_free_id) only
when there's no detection, or it collides with an id already used for
that type in that group (see _accept_proposal's own docstring).
Needs a real Adw/Gtk init (MainWindow.__new__ skips __init__, so no
window/widgets are actually built, but Adw.init() is still required for
the module import chain), same pattern proven in this repo already for
headlessly exercising GTK-adjacent code.
"""
import gi
gi.require_version("Gtk", "4.0")
gi.require_version("Gdk", "4.0")
gi.require_version("Adw", "1")
from gi.repository import Adw # noqa: E402
Adw.init()
from fenigma.app import MainWindow # noqa: E402
from fenigma.map_import import Proposal # noqa: E402
from fenigma.models import Board # noqa: E402
def _window() -> MainWindow:
win = MainWindow.__new__(MainWindow) # skip __init__: no widgets needed for this
win.board = Board()
return win
def _proposal(detected_id=None, side="hostile", sub_x=0, sub_y=0) -> Proposal:
return Proposal(side=side, label="G8", sub_x=sub_x, sub_y=sub_y, unit=None,
centre=(0, 0), box=(0, 0, 0, 0), detected_id=detected_id)
def test_accept_uses_the_detected_id_when_present():
win = _window()
win._accept_proposal(_proposal(detected_id="8"))
assert win.board.targets[0].id == "8"
def test_accept_falls_back_to_auto_id_with_no_detection():
win = _window()
win._accept_proposal(_proposal(detected_id=None))
assert win.board.targets[0].id == "A"
def test_accept_falls_back_to_auto_id_on_a_detected_id_collision():
win = _window()
win._accept_proposal(_proposal(detected_id="8", sub_x=1))
win._accept_proposal(_proposal(detected_id="8", sub_x=2)) # same detected id, must not collide
ids = [t.id for t in win.board.targets]
assert ids[0] == "8"
assert ids[1] != "8"
def test_accept_keeps_target_and_ally_id_detection_in_separate_namespaces():
"""A detected id colliding with an existing ALLY id shouldn't force a
TARGET accept to fall back -- targets/allies are already a separate
id namespace everywhere else (Board.add_target/add_ally), detected-id
preference shouldn't quietly merge them."""
win = _window()
win._accept_proposal(_proposal(detected_id="8", side="friendly"))
win._accept_proposal(_proposal(detected_id="8", side="hostile"))
assert win.board.allies[0].id == "8"
assert win.board.targets[0].id == "8"