Skip to content

Commit d1421e2

Browse files
committed
Add more tests
1 parent 984cf53 commit d1421e2

File tree

1 file changed

+297
-0
lines changed

1 file changed

+297
-0
lines changed

Lib/test/test_pyrepl/test_reader.py

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,303 @@ def test_translator_stack_preserves_mode(self):
679679
reader, _ = self._run_vi(events_normal_path)
680680
self.assertTrue(reader.editor_mode.is_normal())
681681

682+
def test_insert_bol_and_append_eol(self):
683+
events = itertools.chain(
684+
code_to_events("hello"),
685+
[
686+
Event(evt="key", data="\x1b", raw=bytearray(b"\x1b")), # ESC to normal
687+
Event(evt="key", data="I", raw=bytearray(b"I")), # Insert at BOL
688+
Event(evt="key", data="[", raw=bytearray(b"[")),
689+
Event(evt="key", data="\x1b", raw=bytearray(b"\x1b")), # Back to normal
690+
Event(evt="key", data="A", raw=bytearray(b"A")), # Append at EOL
691+
Event(evt="key", data="]", raw=bytearray(b"]")),
692+
Event(evt="key", data="\x1b", raw=bytearray(b"\x1b")),
693+
],
694+
)
695+
reader, _ = self._run_vi(events)
696+
self.assertEqual(reader.get_unicode(), "[hello]")
697+
self.assertTrue(reader.editor_mode.is_normal())
698+
699+
def test_insert_mode_from_normal(self):
700+
events = itertools.chain(
701+
code_to_events("hello"),
702+
[
703+
Event(evt="key", data="\x1b", raw=bytearray(b"\x1b")), # ESC to normal
704+
Event(evt="key", data="0", raw=bytearray(b"0")), # Go to beginning
705+
Event(evt="key", data="l", raw=bytearray(b"l")), # Move right
706+
Event(evt="key", data="l", raw=bytearray(b"l")), # Move right again
707+
Event(evt="key", data="i", raw=bytearray(b"i")), # Insert mode
708+
Event(evt="key", data="X", raw=bytearray(b"X")),
709+
],
710+
)
711+
reader, _ = self._run_vi(events)
712+
self.assertEqual(reader.get_unicode(), "heXllo")
713+
self.assertTrue(reader.editor_mode.is_insert())
714+
715+
def test_hjkl_motions(self):
716+
events = itertools.chain(
717+
code_to_events("hello"),
718+
[
719+
Event(evt="key", data="\x1b", raw=bytearray(b"\x1b")), # ESC to normal
720+
Event(evt="key", data="0", raw=bytearray(b"0")), # Go to start of line
721+
Event(evt="key", data="l", raw=bytearray(b"l")), # Right (h->e)
722+
Event(evt="key", data="l", raw=bytearray(b"l")), # Right (e->l)
723+
Event(evt="key", data="h", raw=bytearray(b"h")), # Left (l->e)
724+
Event(evt="key", data="x", raw=bytearray(b"x")), # Delete 'e'
725+
],
726+
)
727+
reader, _ = self._run_vi(events)
728+
self.assertEqual(reader.get_unicode(), "hllo")
729+
self.assertTrue(reader.editor_mode.is_normal())
730+
731+
def test_dollar_end_of_line(self):
732+
events = itertools.chain(
733+
code_to_events("hello"),
734+
[
735+
Event(evt="key", data="\x1b", raw=bytearray(b"\x1b")), # ESC
736+
Event(evt="key", data="0", raw=bytearray(b"0")), # Beginning
737+
Event(evt="key", data="$", raw=bytearray(b"$")), # End (on last char)
738+
Event(evt="key", data="x", raw=bytearray(b"x")), # Delete 'o'
739+
],
740+
)
741+
reader, _ = self._run_vi(events)
742+
self.assertEqual(reader.get_unicode(), "hell")
743+
744+
def test_word_motions(self):
745+
events = itertools.chain(
746+
code_to_events("one two"),
747+
[
748+
Event(evt="key", data="\x1b", raw=bytearray(b"\x1b")), # ESC
749+
Event(evt="key", data="0", raw=bytearray(b"0")), # Beginning
750+
Event(evt="key", data="w", raw=bytearray(b"w")), # Forward word
751+
Event(evt="key", data="x", raw=bytearray(b"x")), # Delete first char of 'two'
752+
],
753+
)
754+
reader, _ = self._run_vi(events)
755+
self.assertIn("one", reader.get_unicode())
756+
self.assertNotEqual(reader.get_unicode(), "one two") # Something was deleted
757+
758+
def test_repeat_counts(self):
759+
events = itertools.chain(
760+
code_to_events("abcdefghij"),
761+
[
762+
Event(evt="key", data="\x1b", raw=bytearray(b"\x1b")), # ESC
763+
Event(evt="key", data="0", raw=bytearray(b"0")), # Beginning
764+
Event(evt="key", data="3", raw=bytearray(b"3")), # Count 3
765+
Event(evt="key", data="l", raw=bytearray(b"l")), # Move right 3 times
766+
Event(evt="key", data="2", raw=bytearray(b"2")), # Count 2
767+
Event(evt="key", data="x", raw=bytearray(b"x")), # Delete 2 chars (d, e)
768+
],
769+
)
770+
reader, _ = self._run_vi(events)
771+
self.assertEqual(reader.get_unicode(), "abcfghij")
772+
self.assertTrue(reader.editor_mode.is_normal())
773+
774+
def test_multiline_navigation(self):
775+
# Test j/k navigation across multiple lines
776+
code = "first\nsecond\nthird"
777+
events = itertools.chain(
778+
code_to_events(code),
779+
[
780+
Event(evt="key", data="\x1b", raw=bytearray(b"\x1b")), # ESC
781+
Event(evt="key", data="k", raw=bytearray(b"k")), # Up to "second"
782+
Event(evt="key", data="0", raw=bytearray(b"0")), # Beginning of line
783+
Event(evt="key", data="x", raw=bytearray(b"x")), # Delete 's'
784+
Event(evt="key", data="j", raw=bytearray(b"j")), # Down to "third"
785+
Event(evt="key", data="0", raw=bytearray(b"0")), # Beginning
786+
Event(evt="key", data="x", raw=bytearray(b"x")), # Delete 't'
787+
],
788+
)
789+
reader, _ = self._run_vi(events)
790+
self.assertEqual(reader.get_unicode(), "first\necond\nhird")
791+
792+
def test_arrow_keys_in_normal_mode(self):
793+
events = itertools.chain(
794+
code_to_events("test"),
795+
[
796+
Event(evt="key", data="\x1b", raw=bytearray(b"\x1b")), # ESC
797+
Event(evt="key", data="left", raw=bytearray(b"\x1b[D")), # Left arrow
798+
Event(evt="key", data="left", raw=bytearray(b"\x1b[D")), # Left arrow
799+
Event(evt="key", data="x", raw=bytearray(b"x")), # Delete 'e'
800+
],
801+
)
802+
reader, _ = self._run_vi(events)
803+
self.assertEqual(reader.get_unicode(), "tst")
804+
805+
def test_escape_in_normal_mode_is_noop(self):
806+
events = itertools.chain(
807+
code_to_events("hello"),
808+
[
809+
Event(evt="key", data="\x1b", raw=bytearray(b"\x1b")), # ESC to normal
810+
Event(evt="key", data="\x1b", raw=bytearray(b"\x1b")), # ESC again (no-op)
811+
Event(evt="key", data="\x1b", raw=bytearray(b"\x1b")), # ESC again (no-op)
812+
],
813+
)
814+
reader, _ = self._run_vi(events)
815+
self.assertTrue(reader.editor_mode.is_normal())
816+
self.assertEqual(reader.get_unicode(), "hello")
817+
818+
def test_backspace_in_normal_mode(self):
819+
events = itertools.chain(
820+
code_to_events("abcd"),
821+
[
822+
Event(evt="key", data="\x1b", raw=bytearray(b"\x1b")), # ESC
823+
Event(evt="key", data="\x7f", raw=bytearray(b"\x7f")), # Backspace
824+
Event(evt="key", data="\x7f", raw=bytearray(b"\x7f")), # Backspace again
825+
],
826+
)
827+
reader, _ = self._run_vi(events)
828+
self.assertTrue(reader.editor_mode.is_normal())
829+
self.assertIsNotNone(reader.get_unicode())
830+
831+
def test_end_of_word_motion(self):
832+
events = itertools.chain(
833+
code_to_events("hello world test"),
834+
[
835+
Event(evt="key", data="\x1b", raw=bytearray(b"\x1b")), # ESC
836+
Event(evt="key", data="0", raw=bytearray(b"0")), # Beginning
837+
Event(evt="key", data="e", raw=bytearray(b"e")), # End of "hello"
838+
],
839+
)
840+
reader, _ = self._run_vi(events)
841+
# Should be on 'o' of "hello" (last char of word)
842+
self.assertEqual(reader.pos, 4)
843+
self.assertEqual(reader.buffer[reader.pos], 'o')
844+
845+
# Test multiple 'e' commands
846+
events2 = itertools.chain(
847+
code_to_events("one two three"),
848+
[
849+
Event(evt="key", data="\x1b", raw=bytearray(b"\x1b")),
850+
Event(evt="key", data="0", raw=bytearray(b"0")),
851+
Event(evt="key", data="e", raw=bytearray(b"e")), # End of "one"
852+
Event(evt="key", data="e", raw=bytearray(b"e")), # End of "two"
853+
],
854+
)
855+
reader2, _ = self._run_vi(events2)
856+
# Should be on 'o' of "two"
857+
self.assertEqual(reader2.buffer[reader2.pos], 'o')
858+
859+
def test_backward_word_motion(self):
860+
# Test from end of buffer
861+
events = itertools.chain(
862+
code_to_events("one two"),
863+
[
864+
Event(evt="key", data="\x1b", raw=bytearray(b"\x1b")), # ESC at end
865+
Event(evt="key", data="b", raw=bytearray(b"b")), # Back to start of "two"
866+
],
867+
)
868+
reader, _ = self._run_vi(events)
869+
self.assertEqual(reader.pos, 4) # At 't' of "two"
870+
self.assertEqual(reader.buffer[reader.pos], 't')
871+
872+
# Test multiple backwards
873+
events2 = itertools.chain(
874+
code_to_events("one two three"),
875+
[
876+
Event(evt="key", data="\x1b", raw=bytearray(b"\x1b")), # ESC
877+
Event(evt="key", data="b", raw=bytearray(b"b")), # Back to "three"
878+
Event(evt="key", data="b", raw=bytearray(b"b")), # Back to "two"
879+
Event(evt="key", data="b", raw=bytearray(b"b")), # Back to "one"
880+
],
881+
)
882+
reader2, _ = self._run_vi(events2)
883+
# Should be at beginning of "one"
884+
self.assertEqual(reader2.pos, 0)
885+
self.assertEqual(reader2.buffer[reader2.pos], 'o')
886+
887+
def test_first_non_whitespace_character(self):
888+
events = itertools.chain(
889+
code_to_events(" hello world"),
890+
[
891+
Event(evt="key", data="\x1b", raw=bytearray(b"\x1b")), # ESC
892+
Event(evt="key", data="^", raw=bytearray(b"^")), # First non-ws
893+
],
894+
)
895+
reader, _ = self._run_vi(events)
896+
# Should be at 'h' of "hello", skipping the 3 spaces
897+
self.assertEqual(reader.pos, 3)
898+
self.assertEqual(reader.buffer[reader.pos], 'h')
899+
900+
# Test with tabs and spaces
901+
events2 = itertools.chain(
902+
code_to_events("\t text"),
903+
[
904+
Event(evt="key", data="\x1b", raw=bytearray(b"\x1b")),
905+
Event(evt="key", data="0", raw=bytearray(b"0")), # Go to BOL first
906+
Event(evt="key", data="^", raw=bytearray(b"^")), # Then to first non-ws
907+
],
908+
)
909+
reader2, _ = self._run_vi(events2)
910+
self.assertEqual(reader2.buffer[reader2.pos], 't')
911+
912+
def test_word_motion_edge_cases(self):
913+
# Test with punctuation - underscore should be a word boundary
914+
events = itertools.chain(
915+
code_to_events("hello_world"),
916+
[
917+
Event(evt="key", data="\x1b", raw=bytearray(b"\x1b")),
918+
Event(evt="key", data="0", raw=bytearray(b"0")),
919+
Event(evt="key", data="w", raw=bytearray(b"w")), # Forward word
920+
],
921+
)
922+
reader, _ = self._run_vi(events)
923+
# 'w' moves to next word, underscore is not alphanumeric so treated as boundary
924+
self.assertIn(reader.pos, [5, 6]) # Could be on '_' or 'w' depending on implementation
925+
926+
# Test 'e' at end of buffer stays in bounds
927+
events2 = itertools.chain(
928+
code_to_events("end"),
929+
[
930+
Event(evt="key", data="\x1b", raw=bytearray(b"\x1b")),
931+
Event(evt="key", data="e", raw=bytearray(b"e")), # Already at end of word
932+
Event(evt="key", data="e", raw=bytearray(b"e")), # Should stay in bounds
933+
],
934+
)
935+
reader2, _ = self._run_vi(events2)
936+
# Should not go past end of buffer
937+
self.assertLessEqual(reader2.pos, len(reader2.buffer) - 1)
938+
939+
# Test 'b' at beginning doesn't crash
940+
events3 = itertools.chain(
941+
code_to_events("start"),
942+
[
943+
Event(evt="key", data="\x1b", raw=bytearray(b"\x1b")),
944+
Event(evt="key", data="0", raw=bytearray(b"0")),
945+
Event(evt="key", data="b", raw=bytearray(b"b")), # Should stay at 0
946+
],
947+
)
948+
reader3, _ = self._run_vi(events3)
949+
self.assertEqual(reader3.pos, 0)
950+
951+
def test_repeat_count_with_word_motions(self):
952+
events = itertools.chain(
953+
code_to_events("one two three four"),
954+
[
955+
Event(evt="key", data="\x1b", raw=bytearray(b"\x1b")),
956+
Event(evt="key", data="0", raw=bytearray(b"0")),
957+
Event(evt="key", data="2", raw=bytearray(b"2")), # Count 2
958+
Event(evt="key", data="w", raw=bytearray(b"w")), # Forward 2 words
959+
],
960+
)
961+
reader, _ = self._run_vi(events)
962+
# Should be at start of "three" (2 words forward from "one")
963+
self.assertEqual(reader.buffer[reader.pos], 't') # 't' of "three"
964+
965+
# Test with 'e'
966+
events2 = itertools.chain(
967+
code_to_events("alpha beta gamma"),
968+
[
969+
Event(evt="key", data="\x1b", raw=bytearray(b"\x1b")),
970+
Event(evt="key", data="0", raw=bytearray(b"0")),
971+
Event(evt="key", data="2", raw=bytearray(b"2")),
972+
Event(evt="key", data="e", raw=bytearray(b"e")), # End of 2nd word
973+
],
974+
)
975+
reader2, _ = self._run_vi(events2)
976+
# Should be at end of "beta"
977+
self.assertEqual(reader2.buffer[reader2.pos], 'a') # Last 'a' of "beta"
978+
682979

683980
@force_not_colorized_test_class
684981
class TestHistoricalReaderBindings(TestCase):

0 commit comments

Comments
 (0)