Skip to content

Commit 63e3cd1

Browse files
committed
test: add more test
1 parent 56c6292 commit 63e3cd1

File tree

1 file changed

+308
-2
lines changed

1 file changed

+308
-2
lines changed

test_easymotion.py

Lines changed: 308 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
1-
from easymotion import (generate_hints, get_char_width, get_string_width,
2-
get_true_position)
1+
import os
2+
3+
import pytest
4+
5+
from easymotion import (
6+
PaneInfo,
7+
assign_hints_by_distance,
8+
find_matches,
9+
generate_hints,
10+
get_char_width,
11+
get_string_width,
12+
get_true_position,
13+
)
314

415

516
def test_get_char_width():
@@ -110,3 +121,298 @@ def test_generate_hints_distribution():
110121
assert all(len(hint) == 2 for hint in hints)
111122
# For all double chars case, just ensure no duplicate combinations
112123
assert len(hints) == len(set(hints))
124+
125+
126+
# ============================================================================
127+
# Fixtures for reusable test data
128+
# ============================================================================
129+
130+
@pytest.fixture
131+
def simple_pane():
132+
"""Single pane with basic ASCII content"""
133+
pane = PaneInfo(
134+
pane_id='%1', active=True, start_y=0, height=10, start_x=0, width=80
135+
)
136+
pane.lines = ['hello world', 'foo bar baz', 'test line']
137+
return pane
138+
139+
140+
@pytest.fixture
141+
def wide_char_pane():
142+
"""Pane with CJK (wide) characters"""
143+
pane = PaneInfo(
144+
pane_id='%2', active=True, start_y=0, height=10, start_x=0, width=80
145+
)
146+
pane.lines = ['こんにちは world', '你好 hello', 'test 테스트']
147+
return pane
148+
149+
150+
@pytest.fixture
151+
def multi_pane():
152+
"""Multiple panes for cross-pane testing"""
153+
pane1 = PaneInfo(
154+
pane_id='%1', active=True, start_y=0, height=10, start_x=0, width=40
155+
)
156+
pane1.lines = ['left pane', 'aaa bbb']
157+
158+
pane2 = PaneInfo(
159+
pane_id='%2', active=False, start_y=0, height=10, start_x=40, width=40
160+
)
161+
pane2.lines = ['right pane', 'ccc ddd']
162+
163+
return [pane1, pane2]
164+
165+
166+
# ============================================================================
167+
# Tests for find_matches()
168+
# ============================================================================
169+
170+
@pytest.mark.parametrize("search_char,expected_min_count", [
171+
('o', 4), # 'o' in "hello", "world", "foo"
172+
('l', 3), # 'l' in "hello", "world"
173+
('b', 2), # 'b' in "bar", "baz"
174+
('x', 0), # no matches
175+
])
176+
def test_find_matches_basic(simple_pane, search_char, expected_min_count):
177+
"""Test basic character matching with various characters"""
178+
matches = find_matches([simple_pane], search_char)
179+
assert len(matches) >= expected_min_count
180+
181+
182+
def test_find_matches_case_insensitive(simple_pane):
183+
"""Test case-insensitive matching (default behavior)"""
184+
# Mock the CASE_SENSITIVE environment variable
185+
import easymotion
186+
original_case_sensitive = easymotion.CASE_SENSITIVE
187+
188+
try:
189+
easymotion.CASE_SENSITIVE = False
190+
191+
# Add a line with uppercase
192+
simple_pane.lines = ['Hello World']
193+
194+
# Should match both 'h' and 'H'
195+
matches_lower = find_matches([simple_pane], 'h')
196+
matches_upper = find_matches([simple_pane], 'H')
197+
198+
# Both should find the 'H' in "Hello"
199+
assert len(matches_lower) >= 1
200+
assert len(matches_upper) >= 1
201+
202+
finally:
203+
easymotion.CASE_SENSITIVE = original_case_sensitive
204+
205+
206+
def test_find_matches_smartsign():
207+
"""Test SMARTSIGN feature - searching ',' also finds '<'"""
208+
import easymotion
209+
original_smartsign = easymotion.SMARTSIGN
210+
211+
try:
212+
pane = PaneInfo(
213+
pane_id='%1', active=True, start_y=0, height=10, start_x=0, width=80
214+
)
215+
pane.lines = ['hello, world < test']
216+
217+
# With SMARTSIGN enabled, searching ',' should also find '<'
218+
easymotion.SMARTSIGN = True
219+
matches = find_matches([pane], ',')
220+
# Should find both ',' and '<'
221+
assert len(matches) >= 2
222+
223+
# Without SMARTSIGN, should only find ','
224+
easymotion.SMARTSIGN = False
225+
matches = find_matches([pane], ',')
226+
assert len(matches) == 1
227+
228+
finally:
229+
easymotion.SMARTSIGN = original_smartsign
230+
231+
232+
def test_find_matches_wide_characters(wide_char_pane):
233+
"""Test matching with wide characters and correct visual position"""
234+
matches = find_matches([wide_char_pane], 'w')
235+
236+
# Should find 'w' in "world" on first line
237+
assert len(matches) >= 1
238+
239+
# Check that visual column accounts for wide characters
240+
# 'こんにちは' = 5 chars * 2 width = 10, plus 1 space = 11
241+
pane, line_num, visual_col = matches[0]
242+
assert line_num == 0
243+
assert visual_col == 11 # After wide chars and space
244+
245+
246+
def test_find_matches_multiple_panes(multi_pane):
247+
"""Test finding matches across multiple panes"""
248+
matches = find_matches(multi_pane, 'a')
249+
250+
# Should find 'a' in both panes: "pane" (twice), "aaa" (3 times) = 5+ total
251+
assert len(matches) >= 5
252+
253+
# Verify matches come from both panes
254+
pane_ids = {match[0].pane_id for match in matches}
255+
assert '%1' in pane_ids
256+
assert '%2' in pane_ids
257+
258+
259+
def test_find_matches_edge_cases():
260+
"""Test edge cases: empty pane, no matches"""
261+
# Empty pane
262+
empty_pane = PaneInfo(
263+
pane_id='%1', active=True, start_y=0, height=10, start_x=0, width=80
264+
)
265+
empty_pane.lines = []
266+
267+
matches = find_matches([empty_pane], 'a')
268+
assert len(matches) == 0
269+
270+
# Pane with content but no matches
271+
pane = PaneInfo(
272+
pane_id='%2', active=True, start_y=0, height=10, start_x=0, width=80
273+
)
274+
pane.lines = ['hello world']
275+
276+
matches = find_matches([pane], 'z')
277+
assert len(matches) == 0
278+
279+
280+
# ============================================================================
281+
# Tests for assign_hints_by_distance()
282+
# ============================================================================
283+
284+
def test_assign_hints_by_distance_basic(simple_pane):
285+
"""Test that hints are assigned based on distance from cursor"""
286+
simple_pane.lines = ['hello world']
287+
288+
matches = [
289+
(simple_pane, 0, 0), # 'h' at position (0, 0)
290+
(simple_pane, 0, 6), # 'w' at position (0, 6)
291+
]
292+
293+
# Cursor at (0, 0) - closer to first match
294+
hint_mapping = assign_hints_by_distance(matches, cursor_y=0, cursor_x=0)
295+
296+
# Should have 2 hints
297+
assert len(hint_mapping) == 2
298+
299+
# All matches should be in the mapping
300+
mapped_matches = list(hint_mapping.values())
301+
assert all(match in mapped_matches for match in matches)
302+
303+
304+
def test_assign_hints_by_distance_priority():
305+
"""Test that closer matches get simpler (shorter) hints"""
306+
pane = PaneInfo(
307+
pane_id='%1', active=True, start_y=0, height=10, start_x=0, width=80
308+
)
309+
pane.lines = ['a' * 80]
310+
311+
matches = [
312+
(pane, 0, 50), # Far from cursor
313+
(pane, 0, 2), # Close to cursor
314+
(pane, 0, 25), # Medium distance
315+
]
316+
317+
# Cursor at (0, 0)
318+
import easymotion
319+
original_hints = easymotion.HINTS
320+
321+
try:
322+
easymotion.HINTS = 'abc'
323+
hint_mapping = assign_hints_by_distance(matches, cursor_y=0, cursor_x=0)
324+
325+
# Find hint for closest match
326+
closest_match = (pane, 0, 2)
327+
closest_hint = [k for k, v in hint_mapping.items() if v == closest_match][0]
328+
329+
# Closest match should get shortest hint
330+
all_hint_lengths = [len(h) for h in hint_mapping.keys()]
331+
assert len(closest_hint) == min(all_hint_lengths)
332+
333+
finally:
334+
easymotion.HINTS = original_hints
335+
336+
337+
def test_assign_hints_by_distance_multi_pane(multi_pane):
338+
"""Test hint assignment across multiple panes"""
339+
matches = [
340+
(multi_pane[0], 0, 0), # Left pane at screen x=0
341+
(multi_pane[1], 0, 0), # Right pane at screen x=40
342+
]
343+
344+
# Cursor in left pane at (0, 0)
345+
hint_mapping = assign_hints_by_distance(matches, cursor_y=0, cursor_x=0)
346+
347+
assert len(hint_mapping) == 2
348+
349+
# Verify both matches are assigned hints
350+
mapped_matches = list(hint_mapping.values())
351+
assert matches[0] in mapped_matches
352+
assert matches[1] in mapped_matches
353+
354+
355+
# ============================================================================
356+
# Tests for PaneInfo
357+
# ============================================================================
358+
359+
def test_pane_info_initialization():
360+
"""Test PaneInfo initialization with correct defaults"""
361+
pane = PaneInfo(
362+
pane_id='%1',
363+
active=True,
364+
start_y=5,
365+
height=20,
366+
start_x=10,
367+
width=80
368+
)
369+
370+
# Check provided values
371+
assert pane.pane_id == '%1'
372+
assert pane.active is True
373+
assert pane.start_y == 5
374+
assert pane.height == 20
375+
assert pane.start_x == 10
376+
assert pane.width == 80
377+
378+
# Check defaults
379+
assert pane.lines == []
380+
assert pane.positions == []
381+
assert pane.copy_mode is False
382+
assert pane.scroll_position == 0
383+
assert pane.cursor_y == 0
384+
assert pane.cursor_x == 0
385+
386+
387+
# ============================================================================
388+
# Integration Test
389+
# ============================================================================
390+
391+
def test_search_to_hint_integration(simple_pane):
392+
"""Integration test: search → find matches → assign hints → verify positions"""
393+
simple_pane.lines = ['hello world test']
394+
395+
# Step 1: Find matches for 'e'
396+
matches = find_matches([simple_pane], 'e')
397+
398+
# Should find 'e' in "hello" and "test"
399+
assert len(matches) >= 2
400+
401+
# Step 2: Assign hints based on distance from cursor
402+
cursor_y = simple_pane.start_y + 0 # First line
403+
cursor_x = simple_pane.start_x + 0 # Start of line
404+
405+
hint_mapping = assign_hints_by_distance(matches, cursor_y, cursor_x)
406+
407+
# Step 3: Verify hints are assigned to all matches
408+
assert len(hint_mapping) == len(matches)
409+
410+
# Step 4: Verify positions can be extracted from matches
411+
for hint, (pane, line_num, visual_col) in hint_mapping.items():
412+
assert pane == simple_pane
413+
assert line_num == 0 # All matches on first line
414+
assert visual_col >= 0
415+
assert visual_col < len(simple_pane.lines[line_num])
416+
417+
# Verify hint is valid
418+
assert len(hint) in [1, 2] # Should be 1 or 2 characters

0 commit comments

Comments
 (0)