from buddy_fusion.edit_parser import parse_edit, match_part def test_generic_noun_picks_generic_part(): assert match_part("give it wings") == "wings" assert match_part("add a tail") == "tail" assert match_part("add horns") == "horns" def test_specific_phrase_beats_generic(): assert match_part("add bat wings") == "wing_bat" assert match_part("a bushy fox tail") == "tail_fox" assert match_part("put a wizard hat on it") == "wizard_hat" def test_no_match_returns_none(): assert match_part("asdfghjkl zzz") is None assert parse_edit("asdfghjkl zzz") is None def test_count_from_number_word_and_digit(): assert parse_edit("add five arms").count == 5 assert parse_edit("give it 3 horns").count == 3 assert parse_edit("add a tail").count == 1 # article is not a multi-count def test_scale_from_size_adjective(): assert parse_edit("add big wings").scale == 1.5 assert parse_edit("a huge tail").scale == 2.0 assert parse_edit("tiny ears").scale == 0.5 assert parse_edit("add wings").scale == 1.0 def test_anchor_override(): assert parse_edit("add a house behind it").anchor == "rear" assert parse_edit("put a hat on top").anchor == "top" assert parse_edit("wings under it").anchor == "bottom" def test_anchor_word_does_not_fire_inside_part_name(): # "backpack" contains "back" but must not be parsed as a rear anchor cue spec = parse_edit("add a backpack") assert spec.part == "backpack" def test_rotation_keyword(): assert parse_edit("add horns upside down").rotation == ("x", 2) assert parse_edit("a tail sideways").rotation == ("z", 1) assert parse_edit("add wings").rotation is None def test_color_override(): assert parse_edit("add red horns").color == "#ef4444" assert parse_edit("add horns").color is None def test_combined_instruction(): spec = parse_edit("add five big bat wings behind it") assert spec.part == "wing_bat" assert spec.count == 5 assert spec.scale == 1.5 assert spec.anchor == "rear"