A 5e storytelling engine with an LLM DM
1"""Tests for the gender morphology layer."""
2
3import random
4
5import pytest
6
7from storied.names.forge import Morphology
8from storied.names.morphology import _join_with_suffix, apply_morphology
9
10
11@pytest.fixture
12def gendered() -> Morphology:
13 return Morphology(
14 applies=True,
15 female_suffixes=["a", "ina"],
16 male_suffixes=["us", "or"],
17 neutral_suffixes=["en"],
18 )
19
20
21@pytest.fixture
22def unmarked() -> Morphology:
23 return Morphology(
24 applies=False,
25 female_suffixes=[],
26 male_suffixes=[],
27 neutral_suffixes=[],
28 )
29
30
31class TestApplyMorphology:
32 def test_unmarked_returns_root_unchanged(self, unmarked: Morphology):
33 rng = random.Random(42)
34 result = apply_morphology("kara", unmarked, "female", rng)
35 assert result == "kara"
36
37 def test_bare_probability_can_skip_suffix(self, gendered: Morphology):
38 # bare_probability=1.0 → always skip
39 rng = random.Random(42)
40 result = apply_morphology(
41 "kara",
42 gendered,
43 "female",
44 rng,
45 bare_probability=1.0,
46 )
47 assert result == "kara"
48
49 def test_female_suffix_applied(self, gendered: Morphology):
50 rng = random.Random(42)
51 result = apply_morphology(
52 "kar",
53 gendered,
54 "female",
55 rng,
56 bare_probability=0.0,
57 )
58 assert result.startswith("kar")
59 assert any(result.endswith(s) for s in ["a", "ina"])
60
61 def test_male_suffix_applied(self, gendered: Morphology):
62 rng = random.Random(42)
63 result = apply_morphology(
64 "kar",
65 gendered,
66 "male",
67 rng,
68 bare_probability=0.0,
69 )
70 assert any(result.endswith(s) for s in ["us", "or"])
71
72 def test_neutral_when_gender_is_none(self, gendered: Morphology):
73 rng = random.Random(42)
74 result = apply_morphology(
75 "kar",
76 gendered,
77 None,
78 rng,
79 bare_probability=0.0,
80 )
81 assert result.endswith("en")
82
83 def test_returns_root_when_no_neutral_suffixes(self):
84 rng = random.Random(42)
85 morph = Morphology(
86 applies=True,
87 female_suffixes=["a"],
88 male_suffixes=["us"],
89 neutral_suffixes=[],
90 )
91 result = apply_morphology(
92 "kar",
93 morph,
94 None,
95 rng,
96 bare_probability=0.0,
97 )
98 assert result == "kar"
99
100 def test_empty_suffix_returns_root(self):
101 rng = random.Random(42)
102 morph = Morphology(
103 applies=True,
104 female_suffixes=[""],
105 male_suffixes=[""],
106 neutral_suffixes=[""],
107 )
108 result = apply_morphology(
109 "kar",
110 morph,
111 "female",
112 rng,
113 bare_probability=0.0,
114 )
115 assert result == "kar"
116
117
118class TestJoinWithSuffix:
119 def test_simple_concat(self):
120 assert _join_with_suffix("kar", "us") == "karus"
121
122 def test_drops_duplicate_at_boundary(self):
123 # root ends in 'a', suffix starts with 'a' → drop one
124 assert _join_with_suffix("mara", "a") == "mara"
125 assert _join_with_suffix("mara", "an") == "maran"
126
127 def test_empty_suffix(self):
128 assert _join_with_suffix("kar", "") == "kar"
129
130 def test_empty_root(self):
131 assert _join_with_suffix("", "us") == "us"
132
133 def test_case_insensitive_match(self):
134 assert _join_with_suffix("Mara", "An") == "Maran"