import sys import unittest if sys.platform != "win32": raise unittest.SkipTest("test only relevant on win32") import itertools from functools import partial from typing import Iterable from unittest import TestCase from unittest.mock import MagicMock, call from .support import handle_all_events, code_to_events try: from _pyrepl.console import Event, Console from _pyrepl.windows_console import ( WindowsConsole, MOVE_LEFT, MOVE_RIGHT, MOVE_UP, MOVE_DOWN, ERASE_IN_LINE, ) except ImportError: pass class WindowsConsoleTests(TestCase): def console(self, events, **kwargs) -> Console: console = WindowsConsole() console.get_event = MagicMock(side_effect=events) console._scroll = MagicMock() console._hide_cursor = MagicMock() console._show_cursor = MagicMock() console._getscrollbacksize = MagicMock(42) console.out = MagicMock() height = kwargs.get("height", 25) width = kwargs.get("width", 80) console.getheightwidth = MagicMock(side_effect=lambda: (height, width)) console.prepare() for key, val in kwargs.items(): setattr(console, key, val) return console def handle_events(self, events: Iterable[Event], **kwargs): return handle_all_events(events, partial(self.console, **kwargs)) def handle_events_narrow(self, events): return self.handle_events(events, width=5) def handle_events_short(self, events): return self.handle_events(events, height=1) def handle_events_height_3(self, events): return self.handle_events(events, height=3) def test_simple_addition(self): code = "12+34" events = code_to_events(code) _, con = self.handle_events(events) con.out.write.assert_any_call(b"1") con.out.write.assert_any_call(b"2") con.out.write.assert_any_call(b"+") con.out.write.assert_any_call(b"3") con.out.write.assert_any_call(b"4") con.restore() def test_wrap(self): code = "12+34" events = code_to_events(code) _, con = self.handle_events_narrow(events) con.out.write.assert_any_call(b"1") con.out.write.assert_any_call(b"2") con.out.write.assert_any_call(b"+") con.out.write.assert_any_call(b"3") con.out.write.assert_any_call(b"\\") con.out.write.assert_any_call(b"\n") con.out.write.assert_any_call(b"4") con.restore() def test_resize_wider(self): code = "1234567890" events = code_to_events(code) reader, console = self.handle_events_narrow(events) console.height = 20 console.width = 80 console.getheightwidth = MagicMock(lambda _: (20, 80)) def same_reader(_): return reader def same_console(events): console.get_event = MagicMock(side_effect=events) return console _, con = handle_all_events( [Event(evt="resize", data=None)], prepare_reader=same_reader, prepare_console=same_console, ) con.out.write.assert_any_call(self.move_right(2)) con.out.write.assert_any_call(self.move_up(2)) con.out.write.assert_any_call(b"567890") con.restore() def test_resize_narrower(self): code = "1234567890" events = code_to_events(code) reader, console = self.handle_events(events) console.height = 20 console.width = 4 console.getheightwidth = MagicMock(lambda _: (20, 4)) def same_reader(_): return reader def same_console(events): console.get_event = MagicMock(side_effect=events) return console _, con = handle_all_events( [Event(evt="resize", data=None)], prepare_reader=same_reader, prepare_console=same_console, ) con.out.write.assert_any_call(b"456\\") con.out.write.assert_any_call(b"789\\") con.restore() def test_cursor_left(self): code = "1" events = itertools.chain( code_to_events(code), [Event(evt="key", data="left", raw=bytearray(b"\x1bOD"))], ) _, con = self.handle_events(events) con.out.write.assert_any_call(self.move_left()) con.restore() def test_cursor_left_right(self): code = "1" events = itertools.chain( code_to_events(code), [ Event(evt="key", data="left", raw=bytearray(b"\x1bOD")), Event(evt="key", data="right", raw=bytearray(b"\x1bOC")), ], ) _, con = self.handle_events(events) con.out.write.assert_any_call(self.move_left()) con.out.write.assert_any_call(self.move_right()) con.restore() def test_cursor_up(self): code = "1\n2+3" events = itertools.chain( code_to_events(code), [Event(evt="key", data="up", raw=bytearray(b"\x1bOA"))], ) _, con = self.handle_events(events) con.out.write.assert_any_call(self.move_up()) con.restore() def test_cursor_up_down(self): code = "1\n2+3" events = itertools.chain( code_to_events(code), [ Event(evt="key", data="up", raw=bytearray(b"\x1bOA")), Event(evt="key", data="down", raw=bytearray(b"\x1bOB")), ], ) _, con = self.handle_events(events) con.out.write.assert_any_call(self.move_up()) con.out.write.assert_any_call(self.move_down()) con.restore() def test_cursor_back_write(self): events = itertools.chain( code_to_events("1"), [Event(evt="key", data="left", raw=bytearray(b"\x1bOD"))], code_to_events("2"), ) _, con = self.handle_events(events) con.out.write.assert_any_call(b"1") con.out.write.assert_any_call(self.move_left()) con.out.write.assert_any_call(b"21") con.restore() def test_multiline_function_move_up_short_terminal(self): # fmt: off code = ( "def f():\n" " foo" ) # fmt: on events = itertools.chain( code_to_events(code), [ Event(evt="key", data="up", raw=bytearray(b"\x1bOA")), Event(evt="scroll", data=None), ], ) _, con = self.handle_events_short(events) con.out.write.assert_any_call(self.move_left(5)) con.out.write.assert_any_call(self.move_up()) con.restore() def test_multiline_function_move_up_down_short_terminal(self): # fmt: off code = ( "def f():\n" " foo" ) # fmt: on events = itertools.chain( code_to_events(code), [ Event(evt="key", data="up", raw=bytearray(b"\x1bOA")), Event(evt="scroll", data=None), Event(evt="key", data="down", raw=bytearray(b"\x1bOB")), Event(evt="scroll", data=None), ], ) _, con = self.handle_events_short(events) con.out.write.assert_any_call(self.move_left(8)) con.out.write.assert_any_call(self.erase_in_line()) con.restore() def test_resize_bigger_on_multiline_function(self): # fmt: off code = ( "def f():\n" " foo" ) # fmt: on events = itertools.chain(code_to_events(code)) reader, console = self.handle_events_short(events) console.height = 2 console.getheightwidth = MagicMock(lambda _: (2, 80)) def same_reader(_): return reader def same_console(events): console.get_event = MagicMock(side_effect=events) return console _, con = handle_all_events( [Event(evt="resize", data=None)], prepare_reader=same_reader, prepare_console=same_console, ) con.out.write.assert_has_calls( [ call(self.move_left(5)), call(self.move_up()), call(b"def f():"), call(self.move_left(3)), call(self.move_down()), ] ) console.restore() con.restore() def test_resize_smaller_on_multiline_function(self): # fmt: off code = ( "def f():\n" " foo" ) # fmt: on events = itertools.chain(code_to_events(code)) reader, console = self.handle_events_height_3(events) console.height = 1 console.getheightwidth = MagicMock(lambda _: (1, 80)) def same_reader(_): return reader def same_console(events): console.get_event = MagicMock(side_effect=events) return console _, con = handle_all_events( [Event(evt="resize", data=None)], prepare_reader=same_reader, prepare_console=same_console, ) con.out.write.assert_has_calls( [ call(self.move_left(5)), call(self.move_up()), call(self.erase_in_line()), call(b" foo"), ] ) console.restore() con.restore() def move_up(self, lines=1): return MOVE_UP.format(lines).encode("utf8") def move_down(self, lines=1): return MOVE_DOWN.format(lines).encode("utf8") def move_left(self, cols=1): return MOVE_LEFT.format(cols).encode("utf8") def move_right(self, cols=1): return MOVE_RIGHT.format(cols).encode("utf8") def erase_in_line(self): return ERASE_IN_LINE.encode("utf8") if __name__ == "__main__": unittest.main()