aboutsummaryrefslogtreecommitdiffstats
path: root/tests/test_selectui.py
blob: 991a0654db00c6b2be3e356b06fad7fba04bae70 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
from selectui import SelectItem, SelectUI


class TestSelectUIInitialization:
    """Test SelectUI initialization."""

    def test_init_with_dict_items(self, sample_items):
        """Test initialization with dictionary items."""
        app = SelectUI(items=sample_items)

        assert len(app.items) == 4
        assert app.items[0]["title"] == "Python"
        assert app.oneshot is False
        assert app.fuzzy_search is False
        assert app.case_sensitive is False

    def test_init_with_pydantic_items(self):
        """Test initialization with SelectItem instances."""
        items = [
            SelectItem(title="Python", subtitle="Language"),
            SelectItem(title="Rust", subtitle="Systems"),
        ]

        app = SelectUI(items=items)

        assert len(app.items) == 2
        assert app.items[0]["title"] == "Python"

    def test_init_empty(self):
        """Test initialization with no items."""
        app = SelectUI()

        assert app.items == []
        assert app.filtered_items == []

    def test_init_with_oneshot(self, sample_items):
        """Test initialization with oneshot mode."""
        app = SelectUI(items=sample_items, oneshot=True)

        assert app.oneshot is True

    def test_init_with_custom_keys(self, sample_items):
        """Test initialization with custom key configuration."""
        app = SelectUI(
            items=sample_items,
            title_key="name",
            subtitle_key="desc",
            info_key="year"
        )

        assert app.title_key == "name"
        assert app.subtitle_key == "desc"
        assert app.info_key == "year"

    def test_init_with_events_mode(self, sample_items):
        """Test initialization with events mode."""
        app = SelectUI(items=sample_items, events_mode=True)

        assert app.events_mode is True

    def test_init_with_command_template(self):
        """Test initialization with command template."""
        app = SelectUI(command_template="echo {}")

        assert app.command_template == "echo {}"
        assert app.input_mode is True


class TestSelectUIFiltering:
    """Test SelectUI filtering functionality."""

    def test_filter_items_exact_match(self, sample_items):
        """Test exact string filtering."""
        app = SelectUI(items=sample_items)
        app.fuzzy_search = False
        app.case_sensitive = False

        # Simulate filtering for "python"
        filtered = []
        query = "python"
        for item in app.items:
            title = str(item.get("title", "")).lower()
            subtitle = str(item.get("subtitle", "")).lower()
            if query in title or query in subtitle:
                filtered.append(item)

        assert len(filtered) == 1
        assert filtered[0]["title"] == "Python"

    def test_filter_items_case_sensitive(self, sample_items):
        """Test case-sensitive filtering."""
        app = SelectUI(items=sample_items)

        # Lowercase query should not match "Python" in case-sensitive mode
        filtered = []
        query = "python"
        for item in app.items:
            title = str(item.get("title", ""))
            subtitle = str(item.get("subtitle", ""))
            if query in title or query in subtitle:
                filtered.append(item)

        assert len(filtered) == 0

        # Correct case should match
        filtered = []
        query = "Python"
        for item in app.items:
            title = str(item.get("title", ""))
            subtitle = str(item.get("subtitle", ""))
            if query in title or query in subtitle:
                filtered.append(item)

        assert len(filtered) == 1

    def test_filter_by_subtitle(self, sample_items):
        """Test filtering by subtitle."""
        app = SelectUI(items=sample_items)

        filtered = []
        query = "language"
        for item in app.items:
            title = str(item.get("title", "")).lower()
            subtitle = str(item.get("subtitle", "")).lower()
            if query in title or query in subtitle:
                filtered.append(item)

        assert len(filtered) == 4  # All items have "language" in subtitle


class TestSelectUINormalization:
    """Test item normalization in SelectUI."""

    def test_normalize_dict_items(self, sample_items):
        """Test that dict items are normalized correctly."""
        app = SelectUI(items=sample_items)

        assert isinstance(app.items, list)
        assert all(isinstance(item, dict) for item in app.items)

    def test_normalize_pydantic_items(self):
        """Test that Pydantic items are converted to dicts."""
        items = [
            SelectItem(title="Python", subtitle="Language", info="1991"),
            SelectItem(title="Rust", subtitle="Systems", info="2010"),
        ]

        app = SelectUI(items=items)

        assert isinstance(app.items, list)
        assert all(isinstance(item, dict) for item in app.items)
        assert app.items[0]["title"] == "Python"
        assert app.items[1]["title"] == "Rust"

    def test_normalize_with_custom_keys(self):
        """Test normalization preserves custom key mappings."""
        items = [
            SelectItem(title="Alice", subtitle="Engineer", info="Backend"),
        ]

        app = SelectUI(
            items=items,
            title_key="name",
            subtitle_key="role",
            info_key="team"
        )

        # Should have both standard and custom keys
        assert app.items[0]["title"] == "Alice"
        assert app.items[0]["name"] == "Alice"
        assert app.items[0]["subtitle"] == "Engineer"
        assert app.items[0]["role"] == "Engineer"


class TestSelectUIState:
    """Test SelectUI state management."""

    def test_initial_state(self, sample_items):
        """Test initial state after initialization."""
        app = SelectUI(items=sample_items)

        assert app.selected_item is None
        assert app.fuzzy_search is False
        assert app.case_sensitive is False
        assert app._stream_complete is False
        assert app._refresh_timer_running is False

    def test_filtered_items_initial(self, sample_items):
        """Test that filtered_items initially equals items."""
        app = SelectUI(items=sample_items)

        assert app.filtered_items == app.items

    def test_command_mode_state(self):
        """Test state in command mode."""
        app = SelectUI(command_template="echo {}")

        assert app.input_mode is True
        assert app.command_template == "echo {}"
        assert app._command_running is False

    def test_stdin_mode_state(self):
        """Test state when stdin_fd is not provided."""
        app = SelectUI()

        assert app.stdin_fd is None
        assert app._stream_complete is False


class TestSelectUIEdgeCases:
    """Test edge cases and error handling."""

    def test_empty_items_list(self):
        """Test with empty items list."""
        app = SelectUI(items=[])

        assert app.items == []
        assert app.filtered_items == []

    def test_none_items(self):
        """Test with None items."""
        app = SelectUI(items=None)

        assert app.items == []
        assert app.filtered_items == []

    def test_items_without_required_keys(self):
        """Test items missing title key."""
        items = [{"name": "Test"}]  # Missing 'title' key

        app = SelectUI(items=items, title_key="name")

        assert len(app.items) == 1

    def test_mixed_item_types(self):
        """Test that dict items work after Pydantic items."""
        pydantic_items = [SelectItem(title="A")]
        dict_items = [{"title": "B"}]

        app1 = SelectUI(items=pydantic_items)
        app2 = SelectUI(items=dict_items)

        assert app1.items[0]["title"] == "A"
        assert app2.items[0]["title"] == "B"


class TestSelectUICustomKeys:
    """Test custom key configuration."""

    def test_custom_title_key(self, custom_key_items):
        """Test using custom title key."""
        app = SelectUI(items=custom_key_items, title_key="name")

        assert app.title_key == "name"

    def test_custom_subtitle_key(self, custom_key_items):
        """Test using custom subtitle key."""
        app = SelectUI(items=custom_key_items, subtitle_key="role")

        assert app.subtitle_key == "role"

    def test_custom_info_key(self, custom_key_items):
        """Test using custom info key."""
        app = SelectUI(items=custom_key_items, info_key="team")

        assert app.info_key == "team"

    def test_no_subtitle_key(self, single_line_items):
        """Test with subtitle_key=None for single-line display."""
        app = SelectUI(items=single_line_items, subtitle_key=None)

        assert app.subtitle_key is None