Add faster test harness which fakes curses
This commit is contained in:
9
babi.py
9
babi.py
@@ -23,6 +23,7 @@ from typing import Match
|
|||||||
from typing import NamedTuple
|
from typing import NamedTuple
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from typing import Pattern
|
from typing import Pattern
|
||||||
|
from typing import Sequence
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from typing import TypeVar
|
from typing import TypeVar
|
||||||
@@ -1582,8 +1583,8 @@ def _color_test(stdscr: 'curses._CursesWindow') -> None:
|
|||||||
header += '<< color test >>'.center(curses.COLS)[len(header):]
|
header += '<< color test >>'.center(curses.COLS)[len(header):]
|
||||||
stdscr.insstr(0, 0, header, curses.A_REVERSE)
|
stdscr.insstr(0, 0, header, curses.A_REVERSE)
|
||||||
|
|
||||||
maxy, maxx = stdscr.getmaxyx()
|
# will be deleted eventually
|
||||||
if maxy < 19 or maxx < 68: # pragma: no cover (will be deleted)
|
if curses.LINES < 19 or curses.COLS < 68: # pragma: no cover
|
||||||
raise SystemExit('--color-test needs a window of at least 68 x 19')
|
raise SystemExit('--color-test needs a window of at least 68 x 19')
|
||||||
|
|
||||||
y = 1
|
y = 1
|
||||||
@@ -1735,11 +1736,11 @@ def make_stdscr() -> Generator['curses._CursesWindow', None, None]:
|
|||||||
curses.endwin()
|
curses.endwin()
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('--color-test', action='store_true')
|
parser.add_argument('--color-test', action='store_true')
|
||||||
parser.add_argument('filenames', metavar='filename', nargs='*')
|
parser.add_argument('filenames', metavar='filename', nargs='*')
|
||||||
args = parser.parse_args()
|
args = parser.parse_args(argv)
|
||||||
with make_stdscr() as stdscr:
|
with make_stdscr() as stdscr:
|
||||||
c_main(stdscr, args)
|
c_main(stdscr, args)
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@@ -1,63 +1,54 @@
|
|||||||
import contextlib
|
import contextlib
|
||||||
import shlex
|
|
||||||
import sys
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from hecate import Runner
|
from hecate import Runner
|
||||||
|
|
||||||
import babi
|
|
||||||
|
|
||||||
|
|
||||||
class PrintsErrorRunner(Runner):
|
class PrintsErrorRunner(Runner):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self._screenshots: List[str] = []
|
self._prev_screenshot = None
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
def screenshot(self, *args, **kwargs):
|
def screenshot(self, *args, **kwargs):
|
||||||
ret = super().screenshot(*args, **kwargs)
|
ret = super().screenshot(*args, **kwargs)
|
||||||
if not self._screenshots or self._screenshots[-1] != ret:
|
if ret != self._prev_screenshot:
|
||||||
self._screenshots.append(ret)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def _onerror(self):
|
|
||||||
try:
|
|
||||||
yield
|
|
||||||
except Exception: # pragma: no cover
|
|
||||||
# take a screenshot of the final state
|
|
||||||
self.screenshot()
|
|
||||||
print('=' * 79, flush=True)
|
print('=' * 79, flush=True)
|
||||||
for screenshot in self._screenshots:
|
print(ret, end='', flush=True)
|
||||||
print(screenshot, end='', flush=True)
|
print('=' * 79, flush=True)
|
||||||
print('=' * 79, flush=True)
|
self._prev_screenshot = ret
|
||||||
raise
|
return ret
|
||||||
|
|
||||||
def await_exit(self, *args, **kwargs):
|
|
||||||
with self._onerror():
|
|
||||||
return super().await_exit(*args, **kwargs)
|
|
||||||
|
|
||||||
def await_text(self, text, timeout=None):
|
def await_text(self, text, timeout=None):
|
||||||
"""copied from the base implementation but doesn't munge newlines"""
|
"""copied from the base implementation but doesn't munge newlines"""
|
||||||
with self._onerror():
|
for _ in self.poll_until_timeout(timeout):
|
||||||
for _ in self.poll_until_timeout(timeout):
|
screen = self.screenshot()
|
||||||
screen = self.screenshot()
|
if text in screen: # pragma: no branch
|
||||||
if text in screen: # pragma: no branch
|
return
|
||||||
return
|
raise AssertionError(
|
||||||
raise AssertionError(
|
f'Timeout while waiting for text {text!r} to appear',
|
||||||
f'Timeout while waiting for text {text!r} to appear',
|
)
|
||||||
)
|
|
||||||
|
|
||||||
def await_text_missing(self, s):
|
def await_text_missing(self, s):
|
||||||
"""largely based on await_text"""
|
"""largely based on await_text"""
|
||||||
with self._onerror():
|
for _ in self.poll_until_timeout():
|
||||||
for _ in self.poll_until_timeout():
|
screen = self.screenshot()
|
||||||
screen = self.screenshot()
|
munged = screen.replace('\n', '')
|
||||||
munged = screen.replace('\n', '')
|
if s not in munged: # pragma: no branch
|
||||||
if s not in munged: # pragma: no branch
|
return
|
||||||
return
|
raise AssertionError(
|
||||||
raise AssertionError(
|
f'Timeout while waiting for text {s!r} to disappear',
|
||||||
f'Timeout while waiting for text {s!r} to disappear',
|
)
|
||||||
)
|
|
||||||
|
def assert_cursor_line_equals(self, s):
|
||||||
|
cursor_line = self._get_cursor_line()
|
||||||
|
assert cursor_line == s, (cursor_line, s)
|
||||||
|
|
||||||
|
def assert_screen_line_equals(self, n, s):
|
||||||
|
screen_line = self._get_screen_line(n)
|
||||||
|
assert screen_line == s, (screen_line, s)
|
||||||
|
|
||||||
|
def assert_full_contents(self, s):
|
||||||
|
contents = self.screenshot()
|
||||||
|
assert contents == s
|
||||||
|
|
||||||
def get_pane_size(self):
|
def get_pane_size(self):
|
||||||
cmd = ('display', '-t0', '-p', '#{pane_width}\t#{pane_height}')
|
cmd = ('display', '-t0', '-p', '#{pane_width}\t#{pane_height}')
|
||||||
@@ -70,23 +61,22 @@ class PrintsErrorRunner(Runner):
|
|||||||
return int(x), int(y)
|
return int(x), int(y)
|
||||||
|
|
||||||
def await_cursor_position(self, *, x, y):
|
def await_cursor_position(self, *, x, y):
|
||||||
with self._onerror():
|
for _ in self.poll_until_timeout():
|
||||||
for _ in self.poll_until_timeout():
|
pos = self._get_cursor_position()
|
||||||
pos = self._get_cursor_position()
|
if pos == (x, y): # pragma: no branch
|
||||||
if pos == (x, y): # pragma: no branch
|
return
|
||||||
return
|
|
||||||
|
|
||||||
raise AssertionError(
|
raise AssertionError(
|
||||||
f'Timeout while waiting for cursor to reach {(x, y)}\n'
|
f'Timeout while waiting for cursor to reach {(x, y)}\n'
|
||||||
f'Last cursor position: {pos}',
|
f'Last cursor position: {pos}',
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_screen_line(self, n):
|
def _get_screen_line(self, n):
|
||||||
return self.screenshot().splitlines()[n]
|
return self.screenshot().splitlines()[n]
|
||||||
|
|
||||||
def get_cursor_line(self):
|
def _get_cursor_line(self):
|
||||||
_, y = self._get_cursor_position()
|
_, y = self._get_cursor_position()
|
||||||
return self.get_screen_line(y)
|
return self._get_screen_line(y)
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def resize(self, width, height):
|
def resize(self, width, height):
|
||||||
@@ -122,16 +112,13 @@ class PrintsErrorRunner(Runner):
|
|||||||
self.press(s)
|
self.press(s)
|
||||||
self.press('Enter')
|
self.press('Enter')
|
||||||
|
|
||||||
|
def answer_no_if_modified(self):
|
||||||
|
if '*' in self._get_screen_line(0):
|
||||||
|
self.press('n')
|
||||||
|
|
||||||
@contextlib.contextmanager
|
def run(self, callback):
|
||||||
def run(*args, color=True, **kwargs):
|
# this is a bit of a hack, the in-process fake defers all execution
|
||||||
cmd = (sys.executable, '-mcoverage', 'run', '-m', 'babi', *args)
|
callback()
|
||||||
quoted = ' '.join(shlex.quote(p) for p in cmd)
|
|
||||||
term = 'screen-256color' if color else 'screen'
|
|
||||||
cmd = ('bash', '-c', f'export TERM={term}; exec {quoted}')
|
|
||||||
with PrintsErrorRunner(*cmd, **kwargs) as h:
|
|
||||||
h.await_text(babi.VERSION_STR)
|
|
||||||
yield h
|
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
@@ -139,9 +126,7 @@ def and_exit(h):
|
|||||||
yield
|
yield
|
||||||
# only try and exit in non-exceptional cases
|
# only try and exit in non-exceptional cases
|
||||||
h.press('^X')
|
h.press('^X')
|
||||||
# dismiss the save prompt
|
h.answer_no_if_modified()
|
||||||
if ' *' in h.get_screen_line(0):
|
|
||||||
h.press('n')
|
|
||||||
h.await_exit()
|
h.await_exit()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from testing.runner import and_exit
|
from testing.runner import and_exit
|
||||||
from testing.runner import run
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('color', (True, False))
|
@pytest.mark.parametrize('colors', (8, 256))
|
||||||
def test_color_test(color):
|
def test_color_test(run, colors):
|
||||||
with run('--color-test', color=color) as h, and_exit(h):
|
with run('--color-test', colors=colors) as h, and_exit(h):
|
||||||
h.await_text('* 1* 2')
|
h.await_text('* 1* 2')
|
||||||
|
|
||||||
|
|
||||||
def test_can_start_without_color():
|
def test_can_start_without_color(run):
|
||||||
with run(color=False) as h, and_exit(h):
|
with run(colors=8) as h, and_exit(h):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from testing.runner import and_exit
|
from testing.runner import and_exit
|
||||||
from testing.runner import run
|
|
||||||
from testing.runner import trigger_command_mode
|
from testing.runner import trigger_command_mode
|
||||||
|
|
||||||
|
|
||||||
def test_quit_via_colon_q():
|
def test_quit_via_colon_q(run):
|
||||||
with run() as h:
|
with run() as h:
|
||||||
trigger_command_mode(h)
|
trigger_command_mode(h)
|
||||||
h.press_and_enter(':q')
|
h.press_and_enter(':q')
|
||||||
h.await_exit()
|
h.await_exit()
|
||||||
|
|
||||||
|
|
||||||
def test_key_navigation_in_command_mode():
|
def test_key_navigation_in_command_mode(run):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
trigger_command_mode(h)
|
trigger_command_mode(h)
|
||||||
h.press('hello world')
|
h.press('hello world')
|
||||||
@@ -48,7 +47,7 @@ def test_key_navigation_in_command_mode():
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('key', ('BSpace', '^H'))
|
@pytest.mark.parametrize('key', ('BSpace', '^H'))
|
||||||
def test_command_mode_backspace(key):
|
def test_command_mode_backspace(run, key):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
trigger_command_mode(h)
|
trigger_command_mode(h)
|
||||||
h.press('hello world')
|
h.press('hello world')
|
||||||
@@ -65,7 +64,7 @@ def test_command_mode_backspace(key):
|
|||||||
h.press('^C')
|
h.press('^C')
|
||||||
|
|
||||||
|
|
||||||
def test_command_mode_ctrl_k():
|
def test_command_mode_ctrl_k(run):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
trigger_command_mode(h)
|
trigger_command_mode(h)
|
||||||
h.press('hello world')
|
h.press('hello world')
|
||||||
@@ -77,7 +76,7 @@ def test_command_mode_ctrl_k():
|
|||||||
h.press('Enter')
|
h.press('Enter')
|
||||||
|
|
||||||
|
|
||||||
def test_command_mode_control_left():
|
def test_command_mode_control_left(run):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
trigger_command_mode(h)
|
trigger_command_mode(h)
|
||||||
h.press('hello world')
|
h.press('hello world')
|
||||||
@@ -95,7 +94,7 @@ def test_command_mode_control_left():
|
|||||||
h.press('^C')
|
h.press('^C')
|
||||||
|
|
||||||
|
|
||||||
def test_command_mode_control_right():
|
def test_command_mode_control_right(run):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
trigger_command_mode(h)
|
trigger_command_mode(h)
|
||||||
h.press('hello world')
|
h.press('hello world')
|
||||||
@@ -115,7 +114,7 @@ def test_command_mode_control_right():
|
|||||||
h.press('^C')
|
h.press('^C')
|
||||||
|
|
||||||
|
|
||||||
def test_save_via_command_mode(tmpdir):
|
def test_save_via_command_mode(run, tmpdir):
|
||||||
f = tmpdir.join('f')
|
f = tmpdir.join('f')
|
||||||
|
|
||||||
with run(str(f)) as h, and_exit(h):
|
with run(str(f)) as h, and_exit(h):
|
||||||
@@ -126,7 +125,7 @@ def test_save_via_command_mode(tmpdir):
|
|||||||
assert f.read() == 'hello world\n'
|
assert f.read() == 'hello world\n'
|
||||||
|
|
||||||
|
|
||||||
def test_repeated_command_mode_does_not_show_previous_command(tmpdir):
|
def test_repeated_command_mode_does_not_show_previous_command(run, tmpdir):
|
||||||
f = tmpdir.join('f')
|
f = tmpdir.join('f')
|
||||||
|
|
||||||
with run(str(f)) as h, and_exit(h):
|
with run(str(f)) as h, and_exit(h):
|
||||||
@@ -138,7 +137,7 @@ def test_repeated_command_mode_does_not_show_previous_command(tmpdir):
|
|||||||
h.press('Enter')
|
h.press('Enter')
|
||||||
|
|
||||||
|
|
||||||
def test_write_and_quit(tmpdir):
|
def test_write_and_quit(run, tmpdir):
|
||||||
f = tmpdir.join('f')
|
f = tmpdir.join('f')
|
||||||
|
|
||||||
with run(str(f)) as h, and_exit(h):
|
with run(str(f)) as h, and_exit(h):
|
||||||
@@ -150,7 +149,7 @@ def test_write_and_quit(tmpdir):
|
|||||||
assert f.read() == 'hello world\n'
|
assert f.read() == 'hello world\n'
|
||||||
|
|
||||||
|
|
||||||
def test_resizing_and_scrolling_in_command_mode():
|
def test_resizing_and_scrolling_in_command_mode(run):
|
||||||
with run(width=20) as h, and_exit(h):
|
with run(width=20) as h, and_exit(h):
|
||||||
h.press('a' * 15)
|
h.press('a' * 15)
|
||||||
h.await_text(f'\n{"a" * 15}\n')
|
h.await_text(f'\n{"a" * 15}\n')
|
||||||
@@ -169,14 +168,14 @@ def test_resizing_and_scrolling_in_command_mode():
|
|||||||
h.press('Enter')
|
h.press('Enter')
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_command():
|
def test_invalid_command(run):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
trigger_command_mode(h)
|
trigger_command_mode(h)
|
||||||
h.press_and_enter(':fake')
|
h.press_and_enter(':fake')
|
||||||
h.await_text('invalid command: :fake')
|
h.await_text('invalid command: :fake')
|
||||||
|
|
||||||
|
|
||||||
def test_empty_command_is_noop():
|
def test_empty_command_is_noop(run):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('hello ')
|
h.press('hello ')
|
||||||
trigger_command_mode(h)
|
trigger_command_mode(h)
|
||||||
@@ -186,7 +185,7 @@ def test_empty_command_is_noop():
|
|||||||
h.await_text_missing('invalid command')
|
h.await_text_missing('invalid command')
|
||||||
|
|
||||||
|
|
||||||
def test_cancel_command_mode():
|
def test_cancel_command_mode(run):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('hello ')
|
h.press('hello ')
|
||||||
trigger_command_mode(h)
|
trigger_command_mode(h)
|
||||||
|
|||||||
@@ -1,8 +1,18 @@
|
|||||||
|
import contextlib
|
||||||
|
import curses
|
||||||
import os
|
import os
|
||||||
|
import shlex
|
||||||
|
import sys
|
||||||
|
from typing import Callable
|
||||||
|
from typing import NamedTuple
|
||||||
|
from typing import Union
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
import babi
|
||||||
|
from testing.runner import PrintsErrorRunner
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def xdg_data_home(tmpdir):
|
def xdg_data_home(tmpdir):
|
||||||
@@ -15,4 +25,358 @@ def xdg_data_home(tmpdir):
|
|||||||
def ten_lines(tmpdir):
|
def ten_lines(tmpdir):
|
||||||
f = tmpdir.join('f')
|
f = tmpdir.join('f')
|
||||||
f.write('\n'.join(f'line_{i}' for i in range(10)))
|
f.write('\n'.join(f'line_{i}' for i in range(10)))
|
||||||
yield f
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
class Screen:
|
||||||
|
def __init__(self, width, height):
|
||||||
|
self.disabled = True
|
||||||
|
self.width = width
|
||||||
|
self.height = height
|
||||||
|
self.lines = [' ' * self.width for _ in range(self.height)]
|
||||||
|
self.x = self.y = 0
|
||||||
|
self._prev_screenshot = None
|
||||||
|
|
||||||
|
def screenshot(self):
|
||||||
|
ret = ''.join(f'{line.rstrip()}\n' for line in self.lines)
|
||||||
|
if ret != self._prev_screenshot:
|
||||||
|
print('=' * 79)
|
||||||
|
print(ret, end='')
|
||||||
|
print('=' * 79)
|
||||||
|
self._prev_screenshot = ret
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def addstr(self, y, x, s):
|
||||||
|
line = self.lines[y]
|
||||||
|
self.lines[y] = line[:x] + s + line[x + len(s):]
|
||||||
|
|
||||||
|
def insstr(self, y, x, s):
|
||||||
|
line = self.lines[y]
|
||||||
|
self.lines[y] = (line[:x] + s + line[x:])[:self.width]
|
||||||
|
|
||||||
|
def move(self, y, x):
|
||||||
|
print(f'MOVE: y: {y}, x: {x}')
|
||||||
|
self.y, self.x = y, x
|
||||||
|
|
||||||
|
def resize(self, *, width, height):
|
||||||
|
if height > self.height:
|
||||||
|
self.lines.extend([''] * (height - self.height))
|
||||||
|
else:
|
||||||
|
self.lines = self.lines[:height]
|
||||||
|
if width > self.width:
|
||||||
|
self.lines[:] = [line.ljust(width) for line in self.lines]
|
||||||
|
else:
|
||||||
|
self.lines[:] = [line[:width] for line in self.lines]
|
||||||
|
self.width, self.height = width, height
|
||||||
|
|
||||||
|
|
||||||
|
class AwaitText(NamedTuple):
|
||||||
|
text: str
|
||||||
|
|
||||||
|
def __call__(self, screen):
|
||||||
|
if self.text not in screen.screenshot():
|
||||||
|
raise AssertionError(f'expected: {self.text!r}')
|
||||||
|
|
||||||
|
|
||||||
|
class AwaitTextMissing(NamedTuple):
|
||||||
|
text: str
|
||||||
|
|
||||||
|
def __call__(self, screen):
|
||||||
|
if self.text in screen.screenshot():
|
||||||
|
raise AssertionError(f'expected missing: {self.text!r}')
|
||||||
|
|
||||||
|
|
||||||
|
class AwaitCursorPosition(NamedTuple):
|
||||||
|
x: int
|
||||||
|
y: int
|
||||||
|
|
||||||
|
def __call__(self, screen):
|
||||||
|
assert (self.x, self.y) == (screen.x, screen.y)
|
||||||
|
|
||||||
|
|
||||||
|
class AssertCursorLineEquals(NamedTuple):
|
||||||
|
line: str
|
||||||
|
|
||||||
|
def __call__(self, screen):
|
||||||
|
assert screen.lines[screen.y].rstrip() == self.line
|
||||||
|
|
||||||
|
|
||||||
|
class AssertScreenLineEquals(NamedTuple):
|
||||||
|
n: int
|
||||||
|
line: str
|
||||||
|
|
||||||
|
def __call__(self, screen):
|
||||||
|
assert screen.lines[self.n].rstrip() == self.line
|
||||||
|
|
||||||
|
|
||||||
|
class AssertFullContents(NamedTuple):
|
||||||
|
contents: str
|
||||||
|
|
||||||
|
def __call__(self, screen):
|
||||||
|
assert screen.screenshot() == self.contents
|
||||||
|
|
||||||
|
|
||||||
|
class Resize(NamedTuple):
|
||||||
|
width: int
|
||||||
|
height: int
|
||||||
|
|
||||||
|
def __call__(self, screen):
|
||||||
|
screen.resize(width=self.width, height=self.height)
|
||||||
|
|
||||||
|
|
||||||
|
class KeyPress(NamedTuple):
|
||||||
|
wch: Union[int, str]
|
||||||
|
|
||||||
|
def __call__(self, screen):
|
||||||
|
raise AssertionError('unreachable')
|
||||||
|
|
||||||
|
|
||||||
|
class CursesError(NamedTuple):
|
||||||
|
def __call__(self, screen):
|
||||||
|
raise curses.error()
|
||||||
|
|
||||||
|
|
||||||
|
class CursesScreen:
|
||||||
|
def __init__(self, runner):
|
||||||
|
self._runner = runner
|
||||||
|
|
||||||
|
def keypad(self, val):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def addstr(self, y, x, s, attr=0):
|
||||||
|
self._runner.screen.addstr(y, x, s)
|
||||||
|
|
||||||
|
def insstr(self, y, x, s, attr=0):
|
||||||
|
self._runner.screen.insstr(y, x, s)
|
||||||
|
|
||||||
|
def move(self, y, x):
|
||||||
|
self._runner.screen.move(y, x)
|
||||||
|
|
||||||
|
def get_wch(self):
|
||||||
|
return self._runner._get_wch()
|
||||||
|
|
||||||
|
def chgat(self, y, x, n, color):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def nodelay(self, val):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Key(NamedTuple):
|
||||||
|
tmux: str
|
||||||
|
curses: bytes
|
||||||
|
wch: Union[int, str]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self) -> int:
|
||||||
|
return self.wch if isinstance(self.wch, int) else ord(self.wch)
|
||||||
|
|
||||||
|
|
||||||
|
KEYS = [
|
||||||
|
Key('Enter', b'^M', '\r'),
|
||||||
|
Key('Tab', b'^I', '\t'),
|
||||||
|
Key('BTab', b'KEY_BTAB', curses.KEY_BTAB),
|
||||||
|
Key('DC', b'KEY_DC', curses.KEY_DC),
|
||||||
|
Key('BSpace', b'KEY_BACKSPACE', curses.KEY_BACKSPACE),
|
||||||
|
Key('Up', b'KEY_UP', curses.KEY_UP),
|
||||||
|
Key('Down', b'KEY_DOWN', curses.KEY_DOWN),
|
||||||
|
Key('Right', b'KEY_RIGHT', curses.KEY_RIGHT),
|
||||||
|
Key('Left', b'KEY_LEFT', curses.KEY_LEFT),
|
||||||
|
Key('Home', b'KEY_HOME', curses.KEY_HOME),
|
||||||
|
Key('End', b'KEY_END', curses.KEY_END),
|
||||||
|
Key('PageUp', b'KEY_PPAGE', curses.KEY_PPAGE),
|
||||||
|
Key('PageDown', b'KEY_NPAGE', curses.KEY_NPAGE),
|
||||||
|
Key('^Up', b'kUP5', 566),
|
||||||
|
Key('^Down', b'kDN5', 525),
|
||||||
|
Key('^Right', b'kRIT5', 560),
|
||||||
|
Key('^Left', b'kLFT5', 545),
|
||||||
|
Key('^Home', b'kHOM5', 535),
|
||||||
|
Key('^End', b'kEND5', 530),
|
||||||
|
Key('M-Right', b'kRIT3', 558),
|
||||||
|
Key('M-Left', b'kLFT3', 543),
|
||||||
|
Key('S-Up', b'KEY_SR', curses.KEY_SR),
|
||||||
|
Key('S-Down', b'KEY_SF', curses.KEY_SF),
|
||||||
|
Key('S-Right', b'KEY_SRIGHT', curses.KEY_SRIGHT),
|
||||||
|
Key('S-Left', b'KEY_SLEFT', curses.KEY_SLEFT),
|
||||||
|
Key('S-Home', b'KEY_SHOME', curses.KEY_SHOME),
|
||||||
|
Key('S-End', b'KEY_SEND', curses.KEY_SEND),
|
||||||
|
Key('^A', b'^A', '\x01'),
|
||||||
|
Key('^C', b'^C', '\x03'),
|
||||||
|
Key('^H', b'^H', '\x08'),
|
||||||
|
Key('^K', b'^K', '\x0b'),
|
||||||
|
Key('^E', b'^E', '\x05'),
|
||||||
|
Key('^J', b'^J', '\n'),
|
||||||
|
Key('^O', b'^O', '\x0f'),
|
||||||
|
Key('^R', b'^R', '\x12'),
|
||||||
|
Key('^S', b'^S', '\x13'),
|
||||||
|
Key('^U', b'^U', '\x15'),
|
||||||
|
Key('^V', b'^V', '\x16'),
|
||||||
|
Key('^W', b'^W', '\x17'),
|
||||||
|
Key('^X', b'^X', '\x18'),
|
||||||
|
Key('^Y', b'^Y', '\x19'),
|
||||||
|
Key('^[', b'^[', '\x1b'),
|
||||||
|
Key('^_', b'^_', '\x1f'),
|
||||||
|
Key('^\\', b'^\\', '\x1c'),
|
||||||
|
Key('!resize', b'KEY_RESIZE', curses.KEY_RESIZE),
|
||||||
|
]
|
||||||
|
KEYS_TMUX = {k.tmux: k.value for k in KEYS}
|
||||||
|
KEYS_CURSES = {k.value: k.curses for k in KEYS}
|
||||||
|
|
||||||
|
|
||||||
|
class DeferredRunner:
|
||||||
|
def __init__(self, command, width=80, height=24, colors=256):
|
||||||
|
self.command = command
|
||||||
|
self._i = 0
|
||||||
|
self._ops: Callable[[Screen], None] = []
|
||||||
|
self.screen = Screen(width, height)
|
||||||
|
self._colors = colors
|
||||||
|
|
||||||
|
def _get_wch(self):
|
||||||
|
while not isinstance(self._ops[self._i], KeyPress):
|
||||||
|
self._i += 1
|
||||||
|
self._ops[self._i - 1](self.screen)
|
||||||
|
self._i += 1
|
||||||
|
print(f'KEY: {self._ops[self._i - 1].wch!r}')
|
||||||
|
return self._ops[self._i - 1].wch
|
||||||
|
|
||||||
|
def await_text(self, text):
|
||||||
|
self._ops.append(AwaitText(text))
|
||||||
|
|
||||||
|
def await_text_missing(self, text):
|
||||||
|
self._ops.append(AwaitTextMissing(text))
|
||||||
|
|
||||||
|
def await_cursor_position(self, *, x, y):
|
||||||
|
self._ops.append(AwaitCursorPosition(x, y))
|
||||||
|
|
||||||
|
def assert_cursor_line_equals(self, line):
|
||||||
|
self._ops.append(AssertCursorLineEquals(line))
|
||||||
|
|
||||||
|
def assert_screen_line_equals(self, n, line):
|
||||||
|
self._ops.append(AssertScreenLineEquals(n, line))
|
||||||
|
|
||||||
|
def assert_full_contents(self, contents):
|
||||||
|
self._ops.append(AssertFullContents(contents))
|
||||||
|
|
||||||
|
def run(self, callback):
|
||||||
|
self._ops.append(lambda screen: callback())
|
||||||
|
|
||||||
|
def _expand_key(self, s):
|
||||||
|
if s == 'Escape':
|
||||||
|
return [KeyPress('\x1b'), CursesError()]
|
||||||
|
elif s in KEYS_TMUX:
|
||||||
|
return [KeyPress(KEYS_TMUX[s])]
|
||||||
|
elif s.startswith('^') and len(s) > 1 and s[1].isupper():
|
||||||
|
raise AssertionError(f'unknown key {s}')
|
||||||
|
elif s.startswith('M-'):
|
||||||
|
return [KeyPress('\x1b'), KeyPress(s[2:]), CursesError()]
|
||||||
|
else:
|
||||||
|
return [KeyPress(k) for k in s]
|
||||||
|
|
||||||
|
def press(self, s):
|
||||||
|
self._ops.extend(self._expand_key(s))
|
||||||
|
|
||||||
|
def press_and_enter(self, s):
|
||||||
|
self.press(s)
|
||||||
|
self.press('Enter')
|
||||||
|
|
||||||
|
def answer_no_if_modified(self):
|
||||||
|
self._ops.append(KeyPress('n'))
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def resize(self, *, width, height):
|
||||||
|
orig_width, orig_height = self.screen.width, self.screen.height
|
||||||
|
self._ops.append(Resize(width, height))
|
||||||
|
self._ops.append(KeyPress(curses.KEY_RESIZE))
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
self._ops.append(Resize(orig_width, orig_height))
|
||||||
|
self._ops.append(KeyPress(curses.KEY_RESIZE))
|
||||||
|
|
||||||
|
def _curses__noop(self, *_, **__):
|
||||||
|
pass
|
||||||
|
|
||||||
|
_curses_cbreak = _curses_init_pair = _curses_noecho = _curses__noop
|
||||||
|
_curses_nonl = _curses_raw = _curses_start_color = _curses__noop
|
||||||
|
_curses_use_default_colors = _curses__noop
|
||||||
|
|
||||||
|
_curses_error = curses.error # so we don't mock the exception
|
||||||
|
|
||||||
|
def _curses_color_pair(self, n):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def _curses_keyname(self, k):
|
||||||
|
return KEYS_CURSES.get(k, b'')
|
||||||
|
|
||||||
|
def _curses_update_lines_cols(self):
|
||||||
|
curses.LINES = self.screen.height
|
||||||
|
curses.COLS = self.screen.width
|
||||||
|
|
||||||
|
def _curses_initscr(self):
|
||||||
|
curses.COLORS = self._colors
|
||||||
|
self._curses_update_lines_cols()
|
||||||
|
self.screen.enabled = True
|
||||||
|
return CursesScreen(self)
|
||||||
|
|
||||||
|
def _curses_endwin(self):
|
||||||
|
self.screen.disabled = True
|
||||||
|
|
||||||
|
def _curses_not_implemented(self, fn):
|
||||||
|
def fn_inner(*args, **kwargs):
|
||||||
|
raise NotImplementedError(fn)
|
||||||
|
return fn_inner
|
||||||
|
|
||||||
|
def _patch_curses(self):
|
||||||
|
patches = {
|
||||||
|
k: getattr(self, f'_curses_{k}', self._curses_not_implemented(k))
|
||||||
|
for k in dir(curses)
|
||||||
|
if not k.startswith('_') and callable(getattr(curses, k))
|
||||||
|
}
|
||||||
|
return mock.patch.multiple(curses, **patches)
|
||||||
|
|
||||||
|
def await_exit(self):
|
||||||
|
with self._patch_curses():
|
||||||
|
babi.main(self.command)
|
||||||
|
# we have already exited -- check remaining things
|
||||||
|
# KeyPress with failing condition or error
|
||||||
|
for i in range(self._i, len(self._ops)):
|
||||||
|
if self._ops[i] != KeyPress('n'):
|
||||||
|
raise AssertionError(self.ops[i:])
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def screenshot_on_assert(h):
|
||||||
|
try:
|
||||||
|
yield h
|
||||||
|
except AssertionError: # pragma: no cover
|
||||||
|
h.screenshot()
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def run_fake(*cmd, **kwargs):
|
||||||
|
h = DeferredRunner(cmd, **kwargs)
|
||||||
|
with screenshot_on_assert(h):
|
||||||
|
h.await_text(babi.VERSION_STR)
|
||||||
|
yield h
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def run_tmux(*args, colors=256, **kwargs):
|
||||||
|
cmd = (sys.executable, '-mcoverage', 'run', '-m', 'babi', *args)
|
||||||
|
quoted = ' '.join(shlex.quote(p) for p in cmd)
|
||||||
|
term = 'screen-256color' if colors == 256 else 'screen'
|
||||||
|
cmd = ('bash', '-c', f'export TERM={term}; exec {quoted}')
|
||||||
|
with PrintsErrorRunner(*cmd, **kwargs) as h, screenshot_on_assert(h):
|
||||||
|
# startup with coverage can be slow
|
||||||
|
h.await_text(babi.VERSION_STR, timeout=2)
|
||||||
|
yield h
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(
|
||||||
|
scope='session',
|
||||||
|
params=[run_fake, run_tmux],
|
||||||
|
ids=['fake', 'tmux'],
|
||||||
|
)
|
||||||
|
def run(request):
|
||||||
|
return request.param
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
from testing.runner import and_exit
|
from testing.runner import and_exit
|
||||||
from testing.runner import run
|
|
||||||
|
|
||||||
|
|
||||||
def test_current_position(ten_lines):
|
def test_current_position(run, ten_lines):
|
||||||
with run(str(ten_lines)) as h, and_exit(h):
|
with run(str(ten_lines)) as h, and_exit(h):
|
||||||
h.press('^C')
|
h.press('^C')
|
||||||
h.await_text('line 1, col 1 (of 10 lines)')
|
h.await_text('line 1, col 1 (of 10 lines)')
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
from testing.runner import and_exit
|
from testing.runner import and_exit
|
||||||
from testing.runner import run
|
|
||||||
|
|
||||||
|
|
||||||
def test_cut_and_uncut(ten_lines):
|
def test_cut_and_uncut(run, ten_lines):
|
||||||
with run(str(ten_lines)) as h, and_exit(h):
|
with run(str(ten_lines)) as h, and_exit(h):
|
||||||
h.press('^K')
|
h.press('^K')
|
||||||
h.await_text_missing('line_0')
|
h.await_text_missing('line_0')
|
||||||
@@ -18,7 +17,7 @@ def test_cut_and_uncut(ten_lines):
|
|||||||
h.await_text('line_0')
|
h.await_text('line_0')
|
||||||
|
|
||||||
|
|
||||||
def test_cut_at_beginning_of_file():
|
def test_cut_at_beginning_of_file(run):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('^K')
|
h.press('^K')
|
||||||
h.press('^K')
|
h.press('^K')
|
||||||
@@ -26,7 +25,7 @@ def test_cut_at_beginning_of_file():
|
|||||||
h.await_text_missing('*')
|
h.await_text_missing('*')
|
||||||
|
|
||||||
|
|
||||||
def test_cut_end_of_file():
|
def test_cut_end_of_file(run):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('hi')
|
h.press('hi')
|
||||||
h.press('Down')
|
h.press('Down')
|
||||||
@@ -34,7 +33,7 @@ def test_cut_end_of_file():
|
|||||||
h.press('hi')
|
h.press('hi')
|
||||||
|
|
||||||
|
|
||||||
def test_cut_uncut_multiple_file_buffers(tmpdir):
|
def test_cut_uncut_multiple_file_buffers(run, tmpdir):
|
||||||
f1 = tmpdir.join('f1')
|
f1 = tmpdir.join('f1')
|
||||||
f1.write('hello\nworld\n')
|
f1.write('hello\nworld\n')
|
||||||
f2 = tmpdir.join('f2')
|
f2 = tmpdir.join('f2')
|
||||||
@@ -50,7 +49,7 @@ def test_cut_uncut_multiple_file_buffers(tmpdir):
|
|||||||
h.await_text('hello\ngood\nbye\n')
|
h.await_text('hello\ngood\nbye\n')
|
||||||
|
|
||||||
|
|
||||||
def test_selection_cut_uncut(ten_lines):
|
def test_selection_cut_uncut(run, ten_lines):
|
||||||
with run(str(ten_lines)) as h, and_exit(h):
|
with run(str(ten_lines)) as h, and_exit(h):
|
||||||
h.press('Right')
|
h.press('Right')
|
||||||
h.press('S-Right')
|
h.press('S-Right')
|
||||||
@@ -65,7 +64,7 @@ def test_selection_cut_uncut(ten_lines):
|
|||||||
h.await_text('line_0\nline_1')
|
h.await_text('line_0\nline_1')
|
||||||
|
|
||||||
|
|
||||||
def test_selection_cut_uncut_backwards_select(ten_lines):
|
def test_selection_cut_uncut_backwards_select(run, ten_lines):
|
||||||
with run(str(ten_lines)) as h, and_exit(h):
|
with run(str(ten_lines)) as h, and_exit(h):
|
||||||
for _ in range(3):
|
for _ in range(3):
|
||||||
h.press('Down')
|
h.press('Down')
|
||||||
@@ -85,7 +84,7 @@ def test_selection_cut_uncut_backwards_select(ten_lines):
|
|||||||
h.await_cursor_position(x=1, y=4)
|
h.await_cursor_position(x=1, y=4)
|
||||||
|
|
||||||
|
|
||||||
def test_selection_cut_uncut_within_line(ten_lines):
|
def test_selection_cut_uncut_within_line(run, ten_lines):
|
||||||
with run(str(ten_lines)) as h, and_exit(h):
|
with run(str(ten_lines)) as h, and_exit(h):
|
||||||
h.press('Right')
|
h.press('Right')
|
||||||
h.press('S-Right')
|
h.press('S-Right')
|
||||||
@@ -101,7 +100,7 @@ def test_selection_cut_uncut_within_line(ten_lines):
|
|||||||
h.await_cursor_position(x=3, y=1)
|
h.await_cursor_position(x=3, y=1)
|
||||||
|
|
||||||
|
|
||||||
def test_selection_cut_uncut_selection_offscreen_y(ten_lines):
|
def test_selection_cut_uncut_selection_offscreen_y(run, ten_lines):
|
||||||
with run(str(ten_lines), height=4) as h, and_exit(h):
|
with run(str(ten_lines), height=4) as h, and_exit(h):
|
||||||
for _ in range(3):
|
for _ in range(3):
|
||||||
h.press('S-Down')
|
h.press('S-Down')
|
||||||
@@ -112,7 +111,7 @@ def test_selection_cut_uncut_selection_offscreen_y(ten_lines):
|
|||||||
h.await_cursor_position(x=0, y=1)
|
h.await_cursor_position(x=0, y=1)
|
||||||
|
|
||||||
|
|
||||||
def test_selection_cut_uncut_selection_offscreen_x():
|
def test_selection_cut_uncut_selection_offscreen_x(run):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press(f'hello{"o" * 100}')
|
h.press(f'hello{"o" * 100}')
|
||||||
h.await_text_missing('hello')
|
h.await_text_missing('hello')
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from testing.runner import and_exit
|
from testing.runner import and_exit
|
||||||
from testing.runner import run
|
|
||||||
|
|
||||||
|
|
||||||
def test_prompt_window_width():
|
def test_prompt_window_width(run):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('^_')
|
h.press('^_')
|
||||||
h.await_text('enter line number:')
|
h.await_text('enter line number:')
|
||||||
@@ -20,7 +19,7 @@ def test_prompt_window_width():
|
|||||||
h.press('Enter')
|
h.press('Enter')
|
||||||
|
|
||||||
|
|
||||||
def test_go_to_line_line(ten_lines):
|
def test_go_to_line_line(run, ten_lines):
|
||||||
def _jump_to_line(n):
|
def _jump_to_line(n):
|
||||||
h.press('^_')
|
h.press('^_')
|
||||||
h.await_text('enter line number:')
|
h.await_text('enter line number:')
|
||||||
@@ -37,19 +36,19 @@ def test_go_to_line_line(ten_lines):
|
|||||||
# should go to end of the file
|
# should go to end of the file
|
||||||
_jump_to_line(999)
|
_jump_to_line(999)
|
||||||
h.await_cursor_position(x=0, y=4)
|
h.await_cursor_position(x=0, y=4)
|
||||||
assert h.get_screen_line(3) == 'line_9'
|
h.assert_screen_line_equals(3, 'line_9')
|
||||||
# should also go to the end of the file
|
# should also go to the end of the file
|
||||||
_jump_to_line(-1)
|
_jump_to_line(-1)
|
||||||
h.await_cursor_position(x=0, y=4)
|
h.await_cursor_position(x=0, y=4)
|
||||||
assert h.get_screen_line(3) == 'line_9'
|
h.assert_screen_line_equals(3, 'line_9')
|
||||||
# should go to beginning of file
|
# should go to beginning of file
|
||||||
_jump_to_line(-999)
|
_jump_to_line(-999)
|
||||||
h.await_cursor_position(x=0, y=1)
|
h.await_cursor_position(x=0, y=1)
|
||||||
assert h.get_cursor_line() == 'line_0'
|
h.assert_cursor_line_equals('line_0')
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('key', ('Enter', '^C'))
|
@pytest.mark.parametrize('key', ('Enter', '^C'))
|
||||||
def test_go_to_line_cancel(ten_lines, key):
|
def test_go_to_line_cancel(run, ten_lines, key):
|
||||||
with run(str(ten_lines)) as h, and_exit(h):
|
with run(str(ten_lines)) as h, and_exit(h):
|
||||||
h.press('Down')
|
h.press('Down')
|
||||||
h.await_cursor_position(x=0, y=2)
|
h.await_cursor_position(x=0, y=2)
|
||||||
@@ -61,7 +60,7 @@ def test_go_to_line_cancel(ten_lines, key):
|
|||||||
h.await_text('cancelled')
|
h.await_text('cancelled')
|
||||||
|
|
||||||
|
|
||||||
def test_go_to_line_not_an_integer():
|
def test_go_to_line_not_an_integer(run):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('^_')
|
h.press('^_')
|
||||||
h.await_text('enter line number:')
|
h.await_text('enter line number:')
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
from testing.runner import and_exit
|
from testing.runner import and_exit
|
||||||
from testing.runner import run
|
|
||||||
|
|
||||||
|
|
||||||
def test_indent_at_beginning_of_line():
|
def test_indent_at_beginning_of_line(run):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('hello')
|
h.press('hello')
|
||||||
h.press('Home')
|
h.press('Home')
|
||||||
@@ -11,7 +10,7 @@ def test_indent_at_beginning_of_line():
|
|||||||
h.await_cursor_position(x=4, y=1)
|
h.await_cursor_position(x=4, y=1)
|
||||||
|
|
||||||
|
|
||||||
def test_indent_not_full_tab():
|
def test_indent_not_full_tab(run):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('h')
|
h.press('h')
|
||||||
h.press('Tab')
|
h.press('Tab')
|
||||||
@@ -20,14 +19,14 @@ def test_indent_not_full_tab():
|
|||||||
h.await_cursor_position(x=8, y=1)
|
h.await_cursor_position(x=8, y=1)
|
||||||
|
|
||||||
|
|
||||||
def test_indent_fixes_eof():
|
def test_indent_fixes_eof(run):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('Tab')
|
h.press('Tab')
|
||||||
h.press('Down')
|
h.press('Down')
|
||||||
h.await_cursor_position(x=0, y=2)
|
h.await_cursor_position(x=0, y=2)
|
||||||
|
|
||||||
|
|
||||||
def test_indent_selection(ten_lines):
|
def test_indent_selection(run, ten_lines):
|
||||||
with run(str(ten_lines)) as h, and_exit(h):
|
with run(str(ten_lines)) as h, and_exit(h):
|
||||||
h.press('S-Right')
|
h.press('S-Right')
|
||||||
h.press('Tab')
|
h.press('Tab')
|
||||||
@@ -37,7 +36,7 @@ def test_indent_selection(ten_lines):
|
|||||||
h.await_text('\nine_0\n')
|
h.await_text('\nine_0\n')
|
||||||
|
|
||||||
|
|
||||||
def test_indent_selection_does_not_extend_mid_line_selection(ten_lines):
|
def test_indent_selection_does_not_extend_mid_line_selection(run, ten_lines):
|
||||||
with run(str(ten_lines)) as h, and_exit(h):
|
with run(str(ten_lines)) as h, and_exit(h):
|
||||||
h.press('Right')
|
h.press('Right')
|
||||||
h.press('S-Right')
|
h.press('S-Right')
|
||||||
@@ -48,7 +47,7 @@ def test_indent_selection_does_not_extend_mid_line_selection(ten_lines):
|
|||||||
h.await_text('\n lne_0\n')
|
h.await_text('\n lne_0\n')
|
||||||
|
|
||||||
|
|
||||||
def test_indent_selection_leaves_blank_lines(tmpdir):
|
def test_indent_selection_leaves_blank_lines(run, tmpdir):
|
||||||
f = tmpdir.join('f')
|
f = tmpdir.join('f')
|
||||||
f.write('1\n\n2\n\n3\n')
|
f.write('1\n\n2\n\n3\n')
|
||||||
with run(str(f)) as h, and_exit(h):
|
with run(str(f)) as h, and_exit(h):
|
||||||
@@ -59,7 +58,7 @@ def test_indent_selection_leaves_blank_lines(tmpdir):
|
|||||||
assert f.read() == ' 1\n\n 2\n\n3\n'
|
assert f.read() == ' 1\n\n 2\n\n3\n'
|
||||||
|
|
||||||
|
|
||||||
def test_dedent_no_indentation():
|
def test_dedent_no_indentation(run):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('a')
|
h.press('a')
|
||||||
h.press('BTab')
|
h.press('BTab')
|
||||||
@@ -67,7 +66,7 @@ def test_dedent_no_indentation():
|
|||||||
h.await_cursor_position(x=1, y=1)
|
h.await_cursor_position(x=1, y=1)
|
||||||
|
|
||||||
|
|
||||||
def test_dedent_exactly_one_indent():
|
def test_dedent_exactly_one_indent(run):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('Tab')
|
h.press('Tab')
|
||||||
h.press('a')
|
h.press('a')
|
||||||
@@ -77,7 +76,7 @@ def test_dedent_exactly_one_indent():
|
|||||||
h.await_cursor_position(x=1, y=1)
|
h.await_cursor_position(x=1, y=1)
|
||||||
|
|
||||||
|
|
||||||
def test_dedent_selection(tmpdir):
|
def test_dedent_selection(run, tmpdir):
|
||||||
f = tmpdir.join('f')
|
f = tmpdir.join('f')
|
||||||
f.write('1\n 2\n 3\n')
|
f.write('1\n 2\n 3\n')
|
||||||
with run(str(f)) as h, and_exit(h):
|
with run(str(f)) as h, and_exit(h):
|
||||||
@@ -87,7 +86,7 @@ def test_dedent_selection(tmpdir):
|
|||||||
h.await_text('\n1\n2\n 3\n')
|
h.await_text('\n1\n2\n 3\n')
|
||||||
|
|
||||||
|
|
||||||
def test_dedent_selection_does_not_make_selection_negative():
|
def test_dedent_selection_does_not_make_selection_negative(run):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('Tab')
|
h.press('Tab')
|
||||||
h.press('hello')
|
h.press('hello')
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from testing.runner import and_exit
|
from testing.runner import and_exit
|
||||||
from testing.runner import run
|
|
||||||
|
|
||||||
|
|
||||||
def test_arrow_key_movement(tmpdir):
|
def test_arrow_key_movement(run, tmpdir):
|
||||||
f = tmpdir.join('f')
|
f = tmpdir.join('f')
|
||||||
f.write(
|
f.write(
|
||||||
'short\n'
|
'short\n'
|
||||||
@@ -63,7 +62,7 @@ def test_arrow_key_movement(tmpdir):
|
|||||||
('page_up', 'page_down'),
|
('page_up', 'page_down'),
|
||||||
(('PageUp', 'PageDown'), ('^Y', '^V')),
|
(('PageUp', 'PageDown'), ('^Y', '^V')),
|
||||||
)
|
)
|
||||||
def test_page_up_and_page_down(ten_lines, page_up, page_down):
|
def test_page_up_and_page_down(run, ten_lines, page_up, page_down):
|
||||||
with run(str(ten_lines), height=10) as h, and_exit(h):
|
with run(str(ten_lines), height=10) as h, and_exit(h):
|
||||||
h.press('Down')
|
h.press('Down')
|
||||||
h.press('Down')
|
h.press('Down')
|
||||||
@@ -73,37 +72,37 @@ def test_page_up_and_page_down(ten_lines, page_up, page_down):
|
|||||||
h.press(page_down)
|
h.press(page_down)
|
||||||
h.await_text('line_8')
|
h.await_text('line_8')
|
||||||
h.await_cursor_position(x=0, y=1)
|
h.await_cursor_position(x=0, y=1)
|
||||||
assert h.get_cursor_line() == 'line_6'
|
h.assert_cursor_line_equals('line_6')
|
||||||
|
|
||||||
h.press(page_up)
|
h.press(page_up)
|
||||||
h.await_text_missing('line_8')
|
h.await_text_missing('line_8')
|
||||||
h.await_cursor_position(x=0, y=1)
|
h.await_cursor_position(x=0, y=1)
|
||||||
assert h.get_cursor_line() == 'line_0'
|
h.assert_cursor_line_equals('line_0')
|
||||||
|
|
||||||
h.press(page_down)
|
h.press(page_down)
|
||||||
h.press(page_down)
|
h.press(page_down)
|
||||||
h.await_cursor_position(x=0, y=5)
|
h.await_cursor_position(x=0, y=5)
|
||||||
assert h.get_cursor_line() == ''
|
h.assert_cursor_line_equals('')
|
||||||
h.press('Up')
|
h.press('Up')
|
||||||
h.await_cursor_position(x=0, y=4)
|
h.await_cursor_position(x=0, y=4)
|
||||||
assert h.get_cursor_line() == 'line_9'
|
h.assert_cursor_line_equals('line_9')
|
||||||
|
|
||||||
|
|
||||||
def test_page_up_page_down_size_small_window(ten_lines):
|
def test_page_up_page_down_size_small_window(run, ten_lines):
|
||||||
with run(str(ten_lines), height=4) as h, and_exit(h):
|
with run(str(ten_lines), height=4) as h, and_exit(h):
|
||||||
h.press('PageDown')
|
h.press('PageDown')
|
||||||
h.await_text('line_2')
|
h.await_text('line_2')
|
||||||
h.await_cursor_position(x=0, y=1)
|
h.await_cursor_position(x=0, y=1)
|
||||||
assert h.get_cursor_line() == 'line_1'
|
h.assert_cursor_line_equals('line_1')
|
||||||
|
|
||||||
h.press('Down')
|
h.press('Down')
|
||||||
h.press('PageUp')
|
h.press('PageUp')
|
||||||
h.await_text_missing('line_2')
|
h.await_text_missing('line_2')
|
||||||
h.await_cursor_position(x=0, y=1)
|
h.await_cursor_position(x=0, y=1)
|
||||||
assert h.get_cursor_line() == 'line_0'
|
h.assert_cursor_line_equals('line_0')
|
||||||
|
|
||||||
|
|
||||||
def test_ctrl_home(ten_lines):
|
def test_ctrl_home(run, ten_lines):
|
||||||
with run(str(ten_lines), height=4) as h, and_exit(h):
|
with run(str(ten_lines), height=4) as h, and_exit(h):
|
||||||
for _ in range(3):
|
for _ in range(3):
|
||||||
h.press('PageDown')
|
h.press('PageDown')
|
||||||
@@ -114,14 +113,14 @@ def test_ctrl_home(ten_lines):
|
|||||||
h.await_cursor_position(x=0, y=1)
|
h.await_cursor_position(x=0, y=1)
|
||||||
|
|
||||||
|
|
||||||
def test_ctrl_end(ten_lines):
|
def test_ctrl_end(run, ten_lines):
|
||||||
with run(str(ten_lines), height=6) as h, and_exit(h):
|
with run(str(ten_lines), height=6) as h, and_exit(h):
|
||||||
h.press('^End')
|
h.press('^End')
|
||||||
h.await_cursor_position(x=0, y=3)
|
h.await_cursor_position(x=0, y=3)
|
||||||
assert h.get_screen_line(2) == 'line_9'
|
h.assert_screen_line_equals(2, 'line_9')
|
||||||
|
|
||||||
|
|
||||||
def test_ctrl_end_already_on_last_page(ten_lines):
|
def test_ctrl_end_already_on_last_page(run, ten_lines):
|
||||||
with run(str(ten_lines), height=9) as h, and_exit(h):
|
with run(str(ten_lines), height=9) as h, and_exit(h):
|
||||||
h.press('PageDown')
|
h.press('PageDown')
|
||||||
h.await_cursor_position(x=0, y=1)
|
h.await_cursor_position(x=0, y=1)
|
||||||
@@ -129,10 +128,10 @@ def test_ctrl_end_already_on_last_page(ten_lines):
|
|||||||
|
|
||||||
h.press('^End')
|
h.press('^End')
|
||||||
h.await_cursor_position(x=0, y=6)
|
h.await_cursor_position(x=0, y=6)
|
||||||
assert h.get_screen_line(5) == 'line_9'
|
h.assert_screen_line_equals(5, 'line_9')
|
||||||
|
|
||||||
|
|
||||||
def test_scrolling_arrow_key_movement(ten_lines):
|
def test_scrolling_arrow_key_movement(run, ten_lines):
|
||||||
with run(str(ten_lines), height=10) as h, and_exit(h):
|
with run(str(ten_lines), height=10) as h, and_exit(h):
|
||||||
h.await_text('line_7')
|
h.await_text('line_7')
|
||||||
# we should not have scrolled after 7 presses
|
# we should not have scrolled after 7 presses
|
||||||
@@ -144,7 +143,7 @@ def test_scrolling_arrow_key_movement(ten_lines):
|
|||||||
h.press('Down')
|
h.press('Down')
|
||||||
h.await_text('line_8')
|
h.await_text('line_8')
|
||||||
h.await_cursor_position(x=0, y=4)
|
h.await_cursor_position(x=0, y=4)
|
||||||
assert h.get_cursor_line() == 'line_8'
|
h.assert_cursor_line_equals('line_8')
|
||||||
# we should not have scrolled after 3 up presses
|
# we should not have scrolled after 3 up presses
|
||||||
for _ in range(3):
|
for _ in range(3):
|
||||||
h.press('Up')
|
h.press('Up')
|
||||||
@@ -154,15 +153,15 @@ def test_scrolling_arrow_key_movement(ten_lines):
|
|||||||
h.await_text('line_0')
|
h.await_text('line_0')
|
||||||
|
|
||||||
|
|
||||||
def test_ctrl_down_beginning_of_file(ten_lines):
|
def test_ctrl_down_beginning_of_file(run, ten_lines):
|
||||||
with run(str(ten_lines), height=5) as h, and_exit(h):
|
with run(str(ten_lines), height=5) as h, and_exit(h):
|
||||||
h.press('^Down')
|
h.press('^Down')
|
||||||
h.await_text('line_3')
|
h.await_text('line_3')
|
||||||
h.await_cursor_position(x=0, y=1)
|
h.await_cursor_position(x=0, y=1)
|
||||||
assert h.get_cursor_line() == 'line_1'
|
h.assert_cursor_line_equals('line_1')
|
||||||
|
|
||||||
|
|
||||||
def test_ctrl_up_moves_screen_up_one_line(ten_lines):
|
def test_ctrl_up_moves_screen_up_one_line(run, ten_lines):
|
||||||
with run(str(ten_lines), height=5) as h, and_exit(h):
|
with run(str(ten_lines), height=5) as h, and_exit(h):
|
||||||
h.press('^Down')
|
h.press('^Down')
|
||||||
h.press('^Up')
|
h.press('^Up')
|
||||||
@@ -171,7 +170,7 @@ def test_ctrl_up_moves_screen_up_one_line(ten_lines):
|
|||||||
h.await_cursor_position(x=0, y=2)
|
h.await_cursor_position(x=0, y=2)
|
||||||
|
|
||||||
|
|
||||||
def test_ctrl_up_at_beginning_of_file_does_nothing(ten_lines):
|
def test_ctrl_up_at_beginning_of_file_does_nothing(run, ten_lines):
|
||||||
with run(str(ten_lines), height=5) as h, and_exit(h):
|
with run(str(ten_lines), height=5) as h, and_exit(h):
|
||||||
h.press('^Up')
|
h.press('^Up')
|
||||||
h.await_text('line_0')
|
h.await_text('line_0')
|
||||||
@@ -179,7 +178,7 @@ def test_ctrl_up_at_beginning_of_file_does_nothing(ten_lines):
|
|||||||
h.await_cursor_position(x=0, y=1)
|
h.await_cursor_position(x=0, y=1)
|
||||||
|
|
||||||
|
|
||||||
def test_ctrl_up_at_bottom_of_screen(ten_lines):
|
def test_ctrl_up_at_bottom_of_screen(run, ten_lines):
|
||||||
with run(str(ten_lines), height=5) as h, and_exit(h):
|
with run(str(ten_lines), height=5) as h, and_exit(h):
|
||||||
h.press('^Down')
|
h.press('^Down')
|
||||||
h.press('Down')
|
h.press('Down')
|
||||||
@@ -192,17 +191,17 @@ def test_ctrl_up_at_bottom_of_screen(ten_lines):
|
|||||||
h.await_cursor_position(x=0, y=3)
|
h.await_cursor_position(x=0, y=3)
|
||||||
|
|
||||||
|
|
||||||
def test_ctrl_down_at_end_of_file(ten_lines):
|
def test_ctrl_down_at_end_of_file(run, ten_lines):
|
||||||
with run(str(ten_lines), height=5) as h, and_exit(h):
|
with run(str(ten_lines), height=5) as h, and_exit(h):
|
||||||
h.press('^End')
|
h.press('^End')
|
||||||
for i in range(4):
|
for i in range(4):
|
||||||
h.press('^Down')
|
h.press('^Down')
|
||||||
h.press('Up')
|
h.press('Up')
|
||||||
h.await_text('line_9')
|
h.await_text('line_9')
|
||||||
assert h.get_cursor_line() == 'line_9'
|
h.assert_cursor_line_equals('line_9')
|
||||||
|
|
||||||
|
|
||||||
def test_ctrl_down_causing_cursor_movement_should_fix_x(tmpdir):
|
def test_ctrl_down_causing_cursor_movement_should_fix_x(run, tmpdir):
|
||||||
f = tmpdir.join('f')
|
f = tmpdir.join('f')
|
||||||
f.write('line_1\n\nline_2\n\nline_3\n\nline_4\n')
|
f.write('line_1\n\nline_2\n\nline_3\n\nline_4\n')
|
||||||
|
|
||||||
@@ -213,7 +212,7 @@ def test_ctrl_down_causing_cursor_movement_should_fix_x(tmpdir):
|
|||||||
h.await_cursor_position(x=0, y=1)
|
h.await_cursor_position(x=0, y=1)
|
||||||
|
|
||||||
|
|
||||||
def test_ctrl_up_causing_cursor_movement_should_fix_x(tmpdir):
|
def test_ctrl_up_causing_cursor_movement_should_fix_x(run, tmpdir):
|
||||||
f = tmpdir.join('f')
|
f = tmpdir.join('f')
|
||||||
f.write('line_1\n\nline_2\n\nline_3\n\nline_4\n')
|
f.write('line_1\n\nline_2\n\nline_3\n\nline_4\n')
|
||||||
|
|
||||||
@@ -230,7 +229,7 @@ def test_ctrl_up_causing_cursor_movement_should_fix_x(tmpdir):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('k', ('End', '^E'))
|
@pytest.mark.parametrize('k', ('End', '^E'))
|
||||||
def test_end_key(tmpdir, k):
|
def test_end_key(run, tmpdir, k):
|
||||||
f = tmpdir.join('f')
|
f = tmpdir.join('f')
|
||||||
f.write('hello world\nhello world\n')
|
f.write('hello world\nhello world\n')
|
||||||
|
|
||||||
@@ -244,7 +243,7 @@ def test_end_key(tmpdir, k):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('k', ('Home', '^A'))
|
@pytest.mark.parametrize('k', ('Home', '^A'))
|
||||||
def test_home_key(tmpdir, k):
|
def test_home_key(run, tmpdir, k):
|
||||||
f = tmpdir.join('f')
|
f = tmpdir.join('f')
|
||||||
f.write('hello world\nhello world\n')
|
f.write('hello world\nhello world\n')
|
||||||
|
|
||||||
@@ -259,14 +258,14 @@ def test_home_key(tmpdir, k):
|
|||||||
h.await_cursor_position(x=0, y=2)
|
h.await_cursor_position(x=0, y=2)
|
||||||
|
|
||||||
|
|
||||||
def test_page_up_does_not_go_negative(ten_lines):
|
def test_page_up_does_not_go_negative(run, ten_lines):
|
||||||
with run(str(ten_lines), height=10) as h, and_exit(h):
|
with run(str(ten_lines), height=10) as h, and_exit(h):
|
||||||
for _ in range(8):
|
for _ in range(8):
|
||||||
h.press('Down')
|
h.press('Down')
|
||||||
h.await_cursor_position(x=0, y=4)
|
h.await_cursor_position(x=0, y=4)
|
||||||
h.press('^Y')
|
h.press('^Y')
|
||||||
h.await_cursor_position(x=0, y=1)
|
h.await_cursor_position(x=0, y=1)
|
||||||
assert h.get_cursor_line() == 'line_0'
|
h.assert_cursor_line_equals('line_0')
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@@ -283,7 +282,7 @@ hi
|
|||||||
yield f
|
yield f
|
||||||
|
|
||||||
|
|
||||||
def test_ctrl_right_jump_by_word(jump_word_file):
|
def test_ctrl_right_jump_by_word(run, jump_word_file):
|
||||||
with run(str(jump_word_file)) as h, and_exit(h):
|
with run(str(jump_word_file)) as h, and_exit(h):
|
||||||
h.press('^Right')
|
h.press('^Right')
|
||||||
h.await_cursor_position(x=5, y=1)
|
h.await_cursor_position(x=5, y=1)
|
||||||
@@ -308,7 +307,7 @@ def test_ctrl_right_jump_by_word(jump_word_file):
|
|||||||
h.await_cursor_position(x=0, y=6)
|
h.await_cursor_position(x=0, y=6)
|
||||||
|
|
||||||
|
|
||||||
def test_ctrl_left_jump_by_word(jump_word_file):
|
def test_ctrl_left_jump_by_word(run, jump_word_file):
|
||||||
with run(str(jump_word_file)) as h, and_exit(h):
|
with run(str(jump_word_file)) as h, and_exit(h):
|
||||||
h.press('^Left')
|
h.press('^Left')
|
||||||
h.await_cursor_position(x=0, y=1)
|
h.await_cursor_position(x=0, y=1)
|
||||||
@@ -329,16 +328,16 @@ def test_ctrl_left_jump_by_word(jump_word_file):
|
|||||||
h.await_cursor_position(x=2, y=3)
|
h.await_cursor_position(x=2, y=3)
|
||||||
|
|
||||||
|
|
||||||
def test_ctrl_right_triggering_scroll(jump_word_file):
|
def test_ctrl_right_triggering_scroll(run, jump_word_file):
|
||||||
with run(str(jump_word_file), height=4) as h, and_exit(h):
|
with run(str(jump_word_file), height=4) as h, and_exit(h):
|
||||||
h.press('Down')
|
h.press('Down')
|
||||||
h.await_cursor_position(x=0, y=2)
|
h.await_cursor_position(x=0, y=2)
|
||||||
h.press('^Right')
|
h.press('^Right')
|
||||||
h.await_cursor_position(x=0, y=1)
|
h.await_cursor_position(x=0, y=1)
|
||||||
assert h.get_cursor_line() == 'hi'
|
h.assert_cursor_line_equals('hi')
|
||||||
|
|
||||||
|
|
||||||
def test_ctrl_left_triggering_scroll(jump_word_file):
|
def test_ctrl_left_triggering_scroll(run, jump_word_file):
|
||||||
with run(str(jump_word_file)) as h, and_exit(h):
|
with run(str(jump_word_file)) as h, and_exit(h):
|
||||||
h.press('Down')
|
h.press('Down')
|
||||||
h.await_cursor_position(x=0, y=2)
|
h.await_cursor_position(x=0, y=2)
|
||||||
@@ -346,4 +345,4 @@ def test_ctrl_left_triggering_scroll(jump_word_file):
|
|||||||
h.await_cursor_position(x=0, y=1)
|
h.await_cursor_position(x=0, y=1)
|
||||||
h.press('^Left')
|
h.press('^Left')
|
||||||
h.await_cursor_position(x=11, y=1)
|
h.await_cursor_position(x=11, y=1)
|
||||||
assert h.get_cursor_line() == 'hello world'
|
h.assert_cursor_line_equals('hello world')
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
from testing.runner import run
|
def test_multiple_files(run, tmpdir):
|
||||||
|
|
||||||
|
|
||||||
def test_multiple_files(tmpdir):
|
|
||||||
a = tmpdir.join('file_a')
|
a = tmpdir.join('file_a')
|
||||||
a.write('a text')
|
a.write('a text')
|
||||||
b = tmpdir.join('file_b')
|
b = tmpdir.join('file_b')
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from testing.runner import and_exit
|
from testing.runner import and_exit
|
||||||
from testing.runner import run
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('key', ('^C', 'Enter'))
|
@pytest.mark.parametrize('key', ('^C', 'Enter'))
|
||||||
def test_replace_cancel(key):
|
def test_replace_cancel(run, key):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('^\\')
|
h.press('^\\')
|
||||||
h.await_text('search (to replace):')
|
h.await_text('search (to replace):')
|
||||||
@@ -13,7 +12,7 @@ def test_replace_cancel(key):
|
|||||||
h.await_text('cancelled')
|
h.await_text('cancelled')
|
||||||
|
|
||||||
|
|
||||||
def test_replace_invalid_regex():
|
def test_replace_invalid_regex(run):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('^\\')
|
h.press('^\\')
|
||||||
h.await_text('search (to replace):')
|
h.await_text('search (to replace):')
|
||||||
@@ -21,7 +20,7 @@ def test_replace_invalid_regex():
|
|||||||
h.await_text("invalid regex: '('")
|
h.await_text("invalid regex: '('")
|
||||||
|
|
||||||
|
|
||||||
def test_replace_cancel_at_replace_string():
|
def test_replace_cancel_at_replace_string(run):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('^\\')
|
h.press('^\\')
|
||||||
h.await_text('search (to replace):')
|
h.await_text('search (to replace):')
|
||||||
@@ -31,7 +30,7 @@ def test_replace_cancel_at_replace_string():
|
|||||||
h.await_text('cancelled')
|
h.await_text('cancelled')
|
||||||
|
|
||||||
|
|
||||||
def test_replace_actual_contents(ten_lines):
|
def test_replace_actual_contents(run, ten_lines):
|
||||||
with run(str(ten_lines)) as h, and_exit(h):
|
with run(str(ten_lines)) as h, and_exit(h):
|
||||||
h.press('^\\')
|
h.press('^\\')
|
||||||
h.await_text('search (to replace):')
|
h.await_text('search (to replace):')
|
||||||
@@ -46,7 +45,7 @@ def test_replace_actual_contents(ten_lines):
|
|||||||
h.await_text('replaced 1 occurrence')
|
h.await_text('replaced 1 occurrence')
|
||||||
|
|
||||||
|
|
||||||
def test_replace_sets_x_hint_properly(tmpdir):
|
def test_replace_sets_x_hint_properly(run, tmpdir):
|
||||||
f = tmpdir.join('f')
|
f = tmpdir.join('f')
|
||||||
contents = '''\
|
contents = '''\
|
||||||
beginning_line
|
beginning_line
|
||||||
@@ -68,7 +67,7 @@ match me!
|
|||||||
h.await_cursor_position(x=6, y=1)
|
h.await_cursor_position(x=6, y=1)
|
||||||
|
|
||||||
|
|
||||||
def test_replace_cancel_at_individual_replace(ten_lines):
|
def test_replace_cancel_at_individual_replace(run, ten_lines):
|
||||||
with run(str(ten_lines)) as h, and_exit(h):
|
with run(str(ten_lines)) as h, and_exit(h):
|
||||||
h.press('^\\')
|
h.press('^\\')
|
||||||
h.await_text('search (to replace):')
|
h.await_text('search (to replace):')
|
||||||
@@ -80,7 +79,7 @@ def test_replace_cancel_at_individual_replace(ten_lines):
|
|||||||
h.await_text('cancelled')
|
h.await_text('cancelled')
|
||||||
|
|
||||||
|
|
||||||
def test_replace_unknown_characters_at_individual_replace(ten_lines):
|
def test_replace_unknown_characters_at_individual_replace(run, ten_lines):
|
||||||
with run(str(ten_lines)) as h, and_exit(h):
|
with run(str(ten_lines)) as h, and_exit(h):
|
||||||
h.press('^\\')
|
h.press('^\\')
|
||||||
h.await_text('search (to replace):')
|
h.await_text('search (to replace):')
|
||||||
@@ -93,7 +92,7 @@ def test_replace_unknown_characters_at_individual_replace(ten_lines):
|
|||||||
h.await_text('cancelled')
|
h.await_text('cancelled')
|
||||||
|
|
||||||
|
|
||||||
def test_replace_say_no_to_individual_replace(ten_lines):
|
def test_replace_say_no_to_individual_replace(run, ten_lines):
|
||||||
with run(str(ten_lines)) as h, and_exit(h):
|
with run(str(ten_lines)) as h, and_exit(h):
|
||||||
h.press('^\\')
|
h.press('^\\')
|
||||||
h.await_text('search (to replace):')
|
h.await_text('search (to replace):')
|
||||||
@@ -110,7 +109,7 @@ def test_replace_say_no_to_individual_replace(ten_lines):
|
|||||||
h.await_text('replaced 2 occurrences')
|
h.await_text('replaced 2 occurrences')
|
||||||
|
|
||||||
|
|
||||||
def test_replace_all(ten_lines):
|
def test_replace_all(run, ten_lines):
|
||||||
with run(str(ten_lines)) as h, and_exit(h):
|
with run(str(ten_lines)) as h, and_exit(h):
|
||||||
h.press('^\\')
|
h.press('^\\')
|
||||||
h.await_text('search (to replace):')
|
h.await_text('search (to replace):')
|
||||||
@@ -124,7 +123,7 @@ def test_replace_all(ten_lines):
|
|||||||
h.await_text('replaced 10 occurrences')
|
h.await_text('replaced 10 occurrences')
|
||||||
|
|
||||||
|
|
||||||
def test_replace_with_empty_string(ten_lines):
|
def test_replace_with_empty_string(run, ten_lines):
|
||||||
with run(str(ten_lines)) as h, and_exit(h):
|
with run(str(ten_lines)) as h, and_exit(h):
|
||||||
h.press('^\\')
|
h.press('^\\')
|
||||||
h.await_text('search (to replace):')
|
h.await_text('search (to replace):')
|
||||||
@@ -136,7 +135,7 @@ def test_replace_with_empty_string(ten_lines):
|
|||||||
h.await_text_missing('line_1')
|
h.await_text_missing('line_1')
|
||||||
|
|
||||||
|
|
||||||
def test_replace_search_not_found(ten_lines):
|
def test_replace_search_not_found(run, ten_lines):
|
||||||
with run(str(ten_lines)) as h, and_exit(h):
|
with run(str(ten_lines)) as h, and_exit(h):
|
||||||
h.press('^\\')
|
h.press('^\\')
|
||||||
h.await_text('search (to replace):')
|
h.await_text('search (to replace):')
|
||||||
@@ -147,7 +146,7 @@ def test_replace_search_not_found(ten_lines):
|
|||||||
h.await_text('no matches')
|
h.await_text('no matches')
|
||||||
|
|
||||||
|
|
||||||
def test_replace_small_window_size(ten_lines):
|
def test_replace_small_window_size(run, ten_lines):
|
||||||
with run(str(ten_lines)) as h, and_exit(h):
|
with run(str(ten_lines)) as h, and_exit(h):
|
||||||
h.press('^\\')
|
h.press('^\\')
|
||||||
h.await_text('search (to replace):')
|
h.await_text('search (to replace):')
|
||||||
@@ -162,9 +161,10 @@ def test_replace_small_window_size(ten_lines):
|
|||||||
h.press('^C')
|
h.press('^C')
|
||||||
|
|
||||||
|
|
||||||
def test_replace_height_1_highlight():
|
def test_replace_height_1_highlight(run, tmpdir):
|
||||||
with run() as h, and_exit(h):
|
f = tmpdir.join('f')
|
||||||
h.press('x' * 90)
|
f.write('x' * 90)
|
||||||
|
with run(str(f)) as h, and_exit(h):
|
||||||
h.press('^\\')
|
h.press('^\\')
|
||||||
h.await_text('search (to replace):')
|
h.await_text('search (to replace):')
|
||||||
h.press_and_enter('^x+$')
|
h.press_and_enter('^x+$')
|
||||||
@@ -179,7 +179,7 @@ def test_replace_height_1_highlight():
|
|||||||
h.press('^C')
|
h.press('^C')
|
||||||
|
|
||||||
|
|
||||||
def test_replace_line_goes_off_screen():
|
def test_replace_line_goes_off_screen(run):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press(f'{"a" * 20}{"b" * 90}')
|
h.press(f'{"a" * 20}{"b" * 90}')
|
||||||
h.press('^A')
|
h.press('^A')
|
||||||
@@ -196,7 +196,7 @@ def test_replace_line_goes_off_screen():
|
|||||||
h.await_text('replaced 1 occurrence')
|
h.await_text('replaced 1 occurrence')
|
||||||
|
|
||||||
|
|
||||||
def test_replace_undo_undoes_only_one(ten_lines):
|
def test_replace_undo_undoes_only_one(run, ten_lines):
|
||||||
with run(str(ten_lines)) as h, and_exit(h):
|
with run(str(ten_lines)) as h, and_exit(h):
|
||||||
h.press('^\\')
|
h.press('^\\')
|
||||||
h.await_text('search (to replace):')
|
h.await_text('search (to replace):')
|
||||||
@@ -213,7 +213,7 @@ def test_replace_undo_undoes_only_one(ten_lines):
|
|||||||
h.await_text_missing('line_0')
|
h.await_text_missing('line_0')
|
||||||
|
|
||||||
|
|
||||||
def test_replace_multiple_occurrences_in_line():
|
def test_replace_multiple_occurrences_in_line(run):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('baaaaabaaaaa')
|
h.press('baaaaabaaaaa')
|
||||||
h.press('^\\')
|
h.press('^\\')
|
||||||
@@ -226,7 +226,7 @@ def test_replace_multiple_occurrences_in_line():
|
|||||||
h.await_text('bqbq')
|
h.await_text('bqbq')
|
||||||
|
|
||||||
|
|
||||||
def test_replace_after_wrapping(ten_lines):
|
def test_replace_after_wrapping(run, ten_lines):
|
||||||
with run(str(ten_lines)) as h, and_exit(h):
|
with run(str(ten_lines)) as h, and_exit(h):
|
||||||
h.press('Down')
|
h.press('Down')
|
||||||
h.press('^\\')
|
h.press('^\\')
|
||||||
@@ -242,7 +242,7 @@ def test_replace_after_wrapping(ten_lines):
|
|||||||
h.await_text('replaced 2 occurrences')
|
h.await_text('replaced 2 occurrences')
|
||||||
|
|
||||||
|
|
||||||
def test_replace_after_cursor_after_wrapping():
|
def test_replace_after_cursor_after_wrapping(run):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('baaab')
|
h.press('baaab')
|
||||||
h.press('Left')
|
h.press('Left')
|
||||||
@@ -258,7 +258,7 @@ def test_replace_after_cursor_after_wrapping():
|
|||||||
h.await_text('qaaab')
|
h.await_text('qaaab')
|
||||||
|
|
||||||
|
|
||||||
def test_replace_separate_line_after_wrapping(ten_lines):
|
def test_replace_separate_line_after_wrapping(run, ten_lines):
|
||||||
with run(str(ten_lines)) as h, and_exit(h):
|
with run(str(ten_lines)) as h, and_exit(h):
|
||||||
h.press('Down')
|
h.press('Down')
|
||||||
h.press('Down')
|
h.press('Down')
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import babi
|
import babi
|
||||||
from testing.runner import and_exit
|
from testing.runner import and_exit
|
||||||
from testing.runner import run
|
|
||||||
|
|
||||||
|
|
||||||
def test_window_height_2(tmpdir):
|
def test_window_height_2(run, tmpdir):
|
||||||
# 2 tall:
|
# 2 tall:
|
||||||
# - header is hidden, otherwise behaviour is normal
|
# - header is hidden, otherwise behaviour is normal
|
||||||
f = tmpdir.join('f.txt')
|
f = tmpdir.join('f.txt')
|
||||||
@@ -12,16 +11,16 @@ def test_window_height_2(tmpdir):
|
|||||||
with run(str(f)) as h, and_exit(h):
|
with run(str(f)) as h, and_exit(h):
|
||||||
h.await_text('hello world')
|
h.await_text('hello world')
|
||||||
|
|
||||||
with h.resize(80, 2):
|
with h.resize(width=80, height=2):
|
||||||
h.await_text_missing(babi.VERSION_STR)
|
h.await_text_missing(babi.VERSION_STR)
|
||||||
assert h.screenshot() == 'hello world\n\n'
|
h.assert_full_contents('hello world\n\n')
|
||||||
h.press('^J')
|
h.press('^J')
|
||||||
h.await_text('unknown key')
|
h.await_text('unknown key')
|
||||||
|
|
||||||
h.await_text(babi.VERSION_STR)
|
h.await_text(babi.VERSION_STR)
|
||||||
|
|
||||||
|
|
||||||
def test_window_height_1(tmpdir):
|
def test_window_height_1(run, tmpdir):
|
||||||
# 1 tall:
|
# 1 tall:
|
||||||
# - only file contents as body
|
# - only file contents as body
|
||||||
# - status takes precedence over body, but cleared after single action
|
# - status takes precedence over body, but cleared after single action
|
||||||
@@ -31,9 +30,9 @@ def test_window_height_1(tmpdir):
|
|||||||
with run(str(f)) as h, and_exit(h):
|
with run(str(f)) as h, and_exit(h):
|
||||||
h.await_text('hello world')
|
h.await_text('hello world')
|
||||||
|
|
||||||
with h.resize(80, 1):
|
with h.resize(width=80, height=1):
|
||||||
h.await_text_missing(babi.VERSION_STR)
|
h.await_text_missing(babi.VERSION_STR)
|
||||||
assert h.screenshot() == 'hello world\n'
|
h.assert_full_contents('hello world\n')
|
||||||
h.press('^J')
|
h.press('^J')
|
||||||
h.await_text('unknown key')
|
h.await_text('unknown key')
|
||||||
h.press('Right')
|
h.press('Right')
|
||||||
@@ -41,15 +40,15 @@ def test_window_height_1(tmpdir):
|
|||||||
h.press('Down')
|
h.press('Down')
|
||||||
|
|
||||||
|
|
||||||
def test_reacts_to_resize():
|
def test_reacts_to_resize(run):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
first_line = h.get_screen_line(0)
|
h.await_text('<<new file>>')
|
||||||
with h.resize(40, 20):
|
with h.resize(width=10, height=20):
|
||||||
# the first line should be different after resize
|
h.await_text_missing('<<new file>>')
|
||||||
h.await_text_missing(first_line)
|
h.await_text('<<new file>>')
|
||||||
|
|
||||||
|
|
||||||
def test_resize_scrolls_up(ten_lines):
|
def test_resize_scrolls_up(run, ten_lines):
|
||||||
with run(str(ten_lines)) as h, and_exit(h):
|
with run(str(ten_lines)) as h, and_exit(h):
|
||||||
h.await_text('line_9')
|
h.await_text('line_9')
|
||||||
|
|
||||||
@@ -58,37 +57,37 @@ def test_resize_scrolls_up(ten_lines):
|
|||||||
h.await_cursor_position(x=0, y=8)
|
h.await_cursor_position(x=0, y=8)
|
||||||
|
|
||||||
# a resize to a height of 10 should not scroll
|
# a resize to a height of 10 should not scroll
|
||||||
with h.resize(80, 10):
|
with h.resize(width=80, height=10):
|
||||||
h.await_text_missing('line_8')
|
h.await_text_missing('line_8')
|
||||||
h.await_cursor_position(x=0, y=8)
|
h.await_cursor_position(x=0, y=8)
|
||||||
|
|
||||||
h.await_text('line_8')
|
h.await_text('line_8')
|
||||||
|
|
||||||
# but a resize to smaller should
|
# but a resize to smaller should
|
||||||
with h.resize(80, 9):
|
with h.resize(width=80, height=9):
|
||||||
h.await_text_missing('line_0')
|
h.await_text_missing('line_0')
|
||||||
h.await_cursor_position(x=0, y=4)
|
h.await_cursor_position(x=0, y=4)
|
||||||
# make sure we're still on the same line
|
# make sure we're still on the same line
|
||||||
assert h.get_cursor_line() == 'line_7'
|
h.assert_cursor_line_equals('line_7')
|
||||||
|
|
||||||
|
|
||||||
def test_resize_scroll_does_not_go_negative(ten_lines):
|
def test_resize_scroll_does_not_go_negative(run, ten_lines):
|
||||||
with run(str(ten_lines)) as h, and_exit(h):
|
with run(str(ten_lines)) as h, and_exit(h):
|
||||||
for _ in range(5):
|
for _ in range(5):
|
||||||
h.press('Down')
|
h.press('Down')
|
||||||
h.await_cursor_position(x=0, y=6)
|
h.await_cursor_position(x=0, y=6)
|
||||||
|
|
||||||
with h.resize(80, 7):
|
with h.resize(width=80, height=7):
|
||||||
h.await_text_missing('line_9')
|
h.await_text_missing('line_9')
|
||||||
h.await_text('line_9')
|
h.await_text('line_9')
|
||||||
|
|
||||||
for _ in range(3):
|
for _ in range(3):
|
||||||
h.press('Up')
|
h.press('Up')
|
||||||
|
|
||||||
assert h.get_screen_line(1) == 'line_0'
|
h.assert_screen_line_equals(1, 'line_0')
|
||||||
|
|
||||||
|
|
||||||
def test_horizontal_scrolling(tmpdir):
|
def test_horizontal_scrolling(run, tmpdir):
|
||||||
f = tmpdir.join('f')
|
f = tmpdir.join('f')
|
||||||
lots_of_text = ''.join(
|
lots_of_text = ''.join(
|
||||||
''.join(str(i) * 10 for i in range(10))
|
''.join(str(i) * 10 for i in range(10))
|
||||||
@@ -115,7 +114,7 @@ def test_horizontal_scrolling(tmpdir):
|
|||||||
h.await_text('1222»')
|
h.await_text('1222»')
|
||||||
|
|
||||||
|
|
||||||
def test_horizontal_scrolling_exact_width(tmpdir):
|
def test_horizontal_scrolling_exact_width(run, tmpdir):
|
||||||
f = tmpdir.join('f')
|
f = tmpdir.join('f')
|
||||||
f.write('0' * 80)
|
f.write('0' * 80)
|
||||||
|
|
||||||
@@ -130,12 +129,12 @@ def test_horizontal_scrolling_exact_width(tmpdir):
|
|||||||
h.await_cursor_position(x=7, y=1)
|
h.await_cursor_position(x=7, y=1)
|
||||||
|
|
||||||
|
|
||||||
def test_horizontal_scrolling_narrow_window(tmpdir):
|
def test_horizontal_scrolling_narrow_window(run, tmpdir):
|
||||||
f = tmpdir.join('f')
|
f = tmpdir.join('f')
|
||||||
f.write(''.join(str(i) * 10 for i in range(10)))
|
f.write(''.join(str(i) * 10 for i in range(10)))
|
||||||
|
|
||||||
with run(str(f)) as h, and_exit(h):
|
with run(str(f)) as h, and_exit(h):
|
||||||
with h.resize(5, 24):
|
with h.resize(width=5, height=24):
|
||||||
h.await_text('0000»')
|
h.await_text('0000»')
|
||||||
for _ in range(3):
|
for _ in range(3):
|
||||||
h.press('Right')
|
h.press('Right')
|
||||||
@@ -148,12 +147,12 @@ def test_horizontal_scrolling_narrow_window(tmpdir):
|
|||||||
h.await_text('«001»')
|
h.await_text('«001»')
|
||||||
|
|
||||||
|
|
||||||
def test_window_width_1(tmpdir):
|
def test_window_width_1(run, tmpdir):
|
||||||
f = tmpdir.join('f')
|
f = tmpdir.join('f')
|
||||||
f.write('hello')
|
f.write('hello')
|
||||||
|
|
||||||
with run(str(f)) as h, and_exit(h):
|
with run(str(f)) as h, and_exit(h):
|
||||||
with h.resize(1, 24):
|
with h.resize(width=1, height=24):
|
||||||
h.await_text('»')
|
h.await_text('»')
|
||||||
for _ in range(3):
|
for _ in range(3):
|
||||||
h.press('Right')
|
h.press('Right')
|
||||||
@@ -161,7 +160,7 @@ def test_window_width_1(tmpdir):
|
|||||||
h.await_cursor_position(x=3, y=1)
|
h.await_cursor_position(x=3, y=1)
|
||||||
|
|
||||||
|
|
||||||
def test_resize_while_cursor_at_bottom(tmpdir):
|
def test_resize_while_cursor_at_bottom(run, tmpdir):
|
||||||
f = tmpdir.join('f')
|
f = tmpdir.join('f')
|
||||||
f.write('x\n' * 35)
|
f.write('x\n' * 35)
|
||||||
with run(str(f), height=40) as h, and_exit(h):
|
with run(str(f), height=40) as h, and_exit(h):
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from testing.runner import and_exit
|
from testing.runner import and_exit
|
||||||
from testing.runner import run
|
|
||||||
|
|
||||||
|
|
||||||
def test_mixed_newlines(tmpdir):
|
def test_mixed_newlines(run, tmpdir):
|
||||||
f = tmpdir.join('f')
|
f = tmpdir.join('f')
|
||||||
f.write_binary(b'foo\nbar\r\n')
|
f.write_binary(b'foo\nbar\r\n')
|
||||||
with run(str(f)) as h, and_exit(h):
|
with run(str(f)) as h, and_exit(h):
|
||||||
@@ -13,20 +12,20 @@ def test_mixed_newlines(tmpdir):
|
|||||||
h.await_text(r"mixed newlines will be converted to '\n'")
|
h.await_text(r"mixed newlines will be converted to '\n'")
|
||||||
|
|
||||||
|
|
||||||
def test_new_file():
|
def test_new_file(run):
|
||||||
with run('this_is_a_new_file') as h, and_exit(h):
|
with run('this_is_a_new_file') as h, and_exit(h):
|
||||||
h.await_text('this_is_a_new_file')
|
h.await_text('this_is_a_new_file')
|
||||||
h.await_text('(new file)')
|
h.await_text('(new file)')
|
||||||
|
|
||||||
|
|
||||||
def test_not_a_file(tmpdir):
|
def test_not_a_file(run, tmpdir):
|
||||||
d = tmpdir.join('d').ensure_dir()
|
d = tmpdir.join('d').ensure_dir()
|
||||||
with run(str(d)) as h, and_exit(h):
|
with run(str(d)) as h, and_exit(h):
|
||||||
h.await_text('<<new file>>')
|
h.await_text('<<new file>>')
|
||||||
h.await_text("d' is not a file")
|
h.await_text("d' is not a file")
|
||||||
|
|
||||||
|
|
||||||
def test_save_no_filename_specified(tmpdir):
|
def test_save_no_filename_specified(run, tmpdir):
|
||||||
f = tmpdir.join('f')
|
f = tmpdir.join('f')
|
||||||
|
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
@@ -36,11 +35,11 @@ def test_save_no_filename_specified(tmpdir):
|
|||||||
h.press_and_enter(str(f))
|
h.press_and_enter(str(f))
|
||||||
h.await_text('saved! (1 line written)')
|
h.await_text('saved! (1 line written)')
|
||||||
h.await_text_missing('*')
|
h.await_text_missing('*')
|
||||||
assert f.read() == 'hello world\n'
|
assert f.read() == 'hello world\n'
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('k', ('Enter', '^C'))
|
@pytest.mark.parametrize('k', ('Enter', '^C'))
|
||||||
def test_save_no_filename_specified_cancel(k):
|
def test_save_no_filename_specified_cancel(run, k):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('hello world')
|
h.press('hello world')
|
||||||
h.press('^S')
|
h.press('^S')
|
||||||
@@ -49,22 +48,22 @@ def test_save_no_filename_specified_cancel(k):
|
|||||||
h.await_text('cancelled')
|
h.await_text('cancelled')
|
||||||
|
|
||||||
|
|
||||||
def test_saving_file_on_disk_changes(tmpdir):
|
def test_saving_file_on_disk_changes(run, tmpdir):
|
||||||
# TODO: this should show some sort of diffing thing or just allow overwrite
|
# TODO: this should show some sort of diffing thing or just allow overwrite
|
||||||
f = tmpdir.join('f')
|
f = tmpdir.join('f')
|
||||||
|
|
||||||
with run(str(f)) as h, and_exit(h):
|
with run(str(f)) as h, and_exit(h):
|
||||||
f.write('hello world')
|
h.run(lambda: f.write('hello world'))
|
||||||
|
|
||||||
h.press('^S')
|
h.press('^S')
|
||||||
h.await_text('file changed on disk, not implemented')
|
h.await_text('file changed on disk, not implemented')
|
||||||
|
|
||||||
|
|
||||||
def test_allows_saving_same_contents_as_modified_contents(tmpdir):
|
def test_allows_saving_same_contents_as_modified_contents(run, tmpdir):
|
||||||
f = tmpdir.join('f')
|
f = tmpdir.join('f')
|
||||||
|
|
||||||
with run(str(f)) as h, and_exit(h):
|
with run(str(f)) as h, and_exit(h):
|
||||||
f.write('hello world\n')
|
h.run(lambda: f.write('hello world\n'))
|
||||||
h.press('hello world')
|
h.press('hello world')
|
||||||
h.await_text('hello world')
|
h.await_text('hello world')
|
||||||
|
|
||||||
@@ -75,7 +74,7 @@ def test_allows_saving_same_contents_as_modified_contents(tmpdir):
|
|||||||
assert f.read() == 'hello world\n'
|
assert f.read() == 'hello world\n'
|
||||||
|
|
||||||
|
|
||||||
def test_allows_saving_if_file_on_disk_does_not_change(tmpdir):
|
def test_allows_saving_if_file_on_disk_does_not_change(run, tmpdir):
|
||||||
f = tmpdir.join('f')
|
f = tmpdir.join('f')
|
||||||
f.write('hello world\n')
|
f.write('hello world\n')
|
||||||
|
|
||||||
@@ -91,7 +90,7 @@ def test_allows_saving_if_file_on_disk_does_not_change(tmpdir):
|
|||||||
assert f.read() == 'ohai\nhello world\n'
|
assert f.read() == 'ohai\nhello world\n'
|
||||||
|
|
||||||
|
|
||||||
def test_save_file_when_it_did_not_exist(tmpdir):
|
def test_save_file_when_it_did_not_exist(run, tmpdir):
|
||||||
f = tmpdir.join('f')
|
f = tmpdir.join('f')
|
||||||
|
|
||||||
with run(str(f)) as h, and_exit(h):
|
with run(str(f)) as h, and_exit(h):
|
||||||
@@ -103,7 +102,7 @@ def test_save_file_when_it_did_not_exist(tmpdir):
|
|||||||
assert f.read() == 'hello world\n'
|
assert f.read() == 'hello world\n'
|
||||||
|
|
||||||
|
|
||||||
def test_save_via_ctrl_o(tmpdir):
|
def test_save_via_ctrl_o(run, tmpdir):
|
||||||
f = tmpdir.join('f')
|
f = tmpdir.join('f')
|
||||||
with run(str(f)) as h, and_exit(h):
|
with run(str(f)) as h, and_exit(h):
|
||||||
h.press('hello world')
|
h.press('hello world')
|
||||||
@@ -111,10 +110,10 @@ def test_save_via_ctrl_o(tmpdir):
|
|||||||
h.await_text(f'enter filename: {f}')
|
h.await_text(f'enter filename: {f}')
|
||||||
h.press('Enter')
|
h.press('Enter')
|
||||||
h.await_text('saved! (1 line written)')
|
h.await_text('saved! (1 line written)')
|
||||||
assert f.read() == 'hello world\n'
|
assert f.read() == 'hello world\n'
|
||||||
|
|
||||||
|
|
||||||
def test_save_via_ctrl_o_set_filename(tmpdir):
|
def test_save_via_ctrl_o_set_filename(run, tmpdir):
|
||||||
f = tmpdir.join('f')
|
f = tmpdir.join('f')
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('hello world')
|
h.press('hello world')
|
||||||
@@ -122,11 +121,11 @@ def test_save_via_ctrl_o_set_filename(tmpdir):
|
|||||||
h.await_text('enter filename:')
|
h.await_text('enter filename:')
|
||||||
h.press_and_enter(str(f))
|
h.press_and_enter(str(f))
|
||||||
h.await_text('saved! (1 line written)')
|
h.await_text('saved! (1 line written)')
|
||||||
assert f.read() == 'hello world\n'
|
assert f.read() == 'hello world\n'
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('key', ('^C', 'Enter'))
|
@pytest.mark.parametrize('key', ('^C', 'Enter'))
|
||||||
def test_save_via_ctrl_o_cancelled(tmpdir, key):
|
def test_save_via_ctrl_o_cancelled(run, tmpdir, key):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('hello world')
|
h.press('hello world')
|
||||||
h.press('^O')
|
h.press('^O')
|
||||||
@@ -135,7 +134,7 @@ def test_save_via_ctrl_o_cancelled(tmpdir, key):
|
|||||||
h.await_text('cancelled')
|
h.await_text('cancelled')
|
||||||
|
|
||||||
|
|
||||||
def test_save_on_exit_cancel_yn():
|
def test_save_on_exit_cancel_yn(run):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('hello')
|
h.press('hello')
|
||||||
h.await_text('hello')
|
h.await_text('hello')
|
||||||
@@ -145,7 +144,7 @@ def test_save_on_exit_cancel_yn():
|
|||||||
h.await_text('cancelled')
|
h.await_text('cancelled')
|
||||||
|
|
||||||
|
|
||||||
def test_save_on_exit_cancel_filename():
|
def test_save_on_exit_cancel_filename(run):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('hello')
|
h.press('hello')
|
||||||
h.await_text('hello')
|
h.await_text('hello')
|
||||||
@@ -157,7 +156,7 @@ def test_save_on_exit_cancel_filename():
|
|||||||
h.await_text('cancelled')
|
h.await_text('cancelled')
|
||||||
|
|
||||||
|
|
||||||
def test_save_on_exit_save(tmpdir):
|
def test_save_on_exit(run, tmpdir):
|
||||||
f = tmpdir.join('f')
|
f = tmpdir.join('f')
|
||||||
with run(str(f)) as h:
|
with run(str(f)) as h:
|
||||||
h.press('hello')
|
h.press('hello')
|
||||||
@@ -170,7 +169,7 @@ def test_save_on_exit_save(tmpdir):
|
|||||||
h.await_exit()
|
h.await_exit()
|
||||||
|
|
||||||
|
|
||||||
def test_save_on_exit_resize(tmpdir):
|
def test_save_on_exit_resize(run, tmpdir):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('hello')
|
h.press('hello')
|
||||||
h.await_text('hello')
|
h.await_text('hello')
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from testing.runner import and_exit
|
from testing.runner import and_exit
|
||||||
from testing.runner import run
|
|
||||||
|
|
||||||
|
|
||||||
def test_search_wraps(ten_lines):
|
def test_search_wraps(run, ten_lines):
|
||||||
with run(str(ten_lines)) as h, and_exit(h):
|
with run(str(ten_lines)) as h, and_exit(h):
|
||||||
h.press('Down')
|
h.press('Down')
|
||||||
h.press('Down')
|
h.press('Down')
|
||||||
@@ -16,7 +15,7 @@ def test_search_wraps(ten_lines):
|
|||||||
h.await_cursor_position(x=0, y=1)
|
h.await_cursor_position(x=0, y=1)
|
||||||
|
|
||||||
|
|
||||||
def test_search_find_next_line(ten_lines):
|
def test_search_find_next_line(run, ten_lines):
|
||||||
with run(str(ten_lines)) as h, and_exit(h):
|
with run(str(ten_lines)) as h, and_exit(h):
|
||||||
h.await_cursor_position(x=0, y=1)
|
h.await_cursor_position(x=0, y=1)
|
||||||
h.press('^W')
|
h.press('^W')
|
||||||
@@ -25,7 +24,7 @@ def test_search_find_next_line(ten_lines):
|
|||||||
h.await_cursor_position(x=0, y=2)
|
h.await_cursor_position(x=0, y=2)
|
||||||
|
|
||||||
|
|
||||||
def test_search_find_later_in_line():
|
def test_search_find_later_in_line(run):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press_and_enter('lol')
|
h.press_and_enter('lol')
|
||||||
h.press('Up')
|
h.press('Up')
|
||||||
@@ -38,7 +37,7 @@ def test_search_find_later_in_line():
|
|||||||
h.await_cursor_position(x=2, y=1)
|
h.await_cursor_position(x=2, y=1)
|
||||||
|
|
||||||
|
|
||||||
def test_search_only_one_match_already_at_that_match(ten_lines):
|
def test_search_only_one_match_already_at_that_match(run, ten_lines):
|
||||||
with run(str(ten_lines)) as h, and_exit(h):
|
with run(str(ten_lines)) as h, and_exit(h):
|
||||||
h.press('Down')
|
h.press('Down')
|
||||||
h.await_cursor_position(x=0, y=2)
|
h.await_cursor_position(x=0, y=2)
|
||||||
@@ -49,7 +48,7 @@ def test_search_only_one_match_already_at_that_match(ten_lines):
|
|||||||
h.await_cursor_position(x=0, y=2)
|
h.await_cursor_position(x=0, y=2)
|
||||||
|
|
||||||
|
|
||||||
def test_search_sets_x_hint_properly(tmpdir):
|
def test_search_sets_x_hint_properly(run, tmpdir):
|
||||||
f = tmpdir.join('f')
|
f = tmpdir.join('f')
|
||||||
contents = '''\
|
contents = '''\
|
||||||
beginning_line
|
beginning_line
|
||||||
@@ -67,7 +66,7 @@ match me!
|
|||||||
h.await_cursor_position(x=6, y=1)
|
h.await_cursor_position(x=6, y=1)
|
||||||
|
|
||||||
|
|
||||||
def test_search_not_found(ten_lines):
|
def test_search_not_found(run, ten_lines):
|
||||||
with run(str(ten_lines)) as h, and_exit(h):
|
with run(str(ten_lines)) as h, and_exit(h):
|
||||||
h.press('^W')
|
h.press('^W')
|
||||||
h.await_text('search:')
|
h.await_text('search:')
|
||||||
@@ -76,7 +75,7 @@ def test_search_not_found(ten_lines):
|
|||||||
h.await_cursor_position(x=0, y=1)
|
h.await_cursor_position(x=0, y=1)
|
||||||
|
|
||||||
|
|
||||||
def test_search_invalid_regex(ten_lines):
|
def test_search_invalid_regex(run, ten_lines):
|
||||||
with run(str(ten_lines)) as h, and_exit(h):
|
with run(str(ten_lines)) as h, and_exit(h):
|
||||||
h.press('^W')
|
h.press('^W')
|
||||||
h.await_text('search:')
|
h.await_text('search:')
|
||||||
@@ -85,7 +84,7 @@ def test_search_invalid_regex(ten_lines):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('key', ('Enter', '^C'))
|
@pytest.mark.parametrize('key', ('Enter', '^C'))
|
||||||
def test_search_cancel(ten_lines, key):
|
def test_search_cancel(run, ten_lines, key):
|
||||||
with run(str(ten_lines)) as h, and_exit(h):
|
with run(str(ten_lines)) as h, and_exit(h):
|
||||||
h.press('^W')
|
h.press('^W')
|
||||||
h.await_text('search:')
|
h.await_text('search:')
|
||||||
@@ -93,7 +92,7 @@ def test_search_cancel(ten_lines, key):
|
|||||||
h.await_text('cancelled')
|
h.await_text('cancelled')
|
||||||
|
|
||||||
|
|
||||||
def test_search_repeated_search(ten_lines):
|
def test_search_repeated_search(run, ten_lines):
|
||||||
with run(str(ten_lines)) as h, and_exit(h):
|
with run(str(ten_lines)) as h, and_exit(h):
|
||||||
h.press('^W')
|
h.press('^W')
|
||||||
h.press('line')
|
h.press('line')
|
||||||
@@ -107,7 +106,7 @@ def test_search_repeated_search(ten_lines):
|
|||||||
h.await_cursor_position(x=0, y=3)
|
h.await_cursor_position(x=0, y=3)
|
||||||
|
|
||||||
|
|
||||||
def test_search_history_recorded():
|
def test_search_history_recorded(run):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('^W')
|
h.press('^W')
|
||||||
h.await_text('search:')
|
h.await_text('search:')
|
||||||
@@ -127,7 +126,7 @@ def test_search_history_recorded():
|
|||||||
h.await_text('asdtest')
|
h.await_text('asdtest')
|
||||||
h.press('Up') # can't go past the beginning
|
h.press('Up') # can't go past the beginning
|
||||||
h.await_text('asdtest')
|
h.await_text('asdtest')
|
||||||
h.press('enter')
|
h.press('Enter')
|
||||||
h.await_text('no matches')
|
h.await_text('no matches')
|
||||||
|
|
||||||
h.press('^W')
|
h.press('^W')
|
||||||
@@ -138,7 +137,7 @@ def test_search_history_recorded():
|
|||||||
h.press('^C')
|
h.press('^C')
|
||||||
|
|
||||||
|
|
||||||
def test_search_history_duplicates_dont_repeat():
|
def test_search_history_duplicates_dont_repeat(run):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('^W')
|
h.press('^W')
|
||||||
h.await_text('search:')
|
h.await_text('search:')
|
||||||
@@ -163,7 +162,7 @@ def test_search_history_duplicates_dont_repeat():
|
|||||||
h.press('Enter')
|
h.press('Enter')
|
||||||
|
|
||||||
|
|
||||||
def test_search_history_is_saved_between_sessions(xdg_data_home):
|
def test_search_history_is_saved_between_sessions(run, xdg_data_home):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('^W')
|
h.press('^W')
|
||||||
h.press_and_enter('search1')
|
h.press_and_enter('search1')
|
||||||
@@ -182,7 +181,7 @@ def test_search_history_is_saved_between_sessions(xdg_data_home):
|
|||||||
h.press('Enter')
|
h.press('Enter')
|
||||||
|
|
||||||
|
|
||||||
def test_search_multiple_sessions_append_to_history(xdg_data_home):
|
def test_search_multiple_sessions_append_to_history(run, xdg_data_home):
|
||||||
xdg_data_home.join('babi/history/search').ensure().write(
|
xdg_data_home.join('babi/history/search').ensure().write(
|
||||||
'orig\n'
|
'orig\n'
|
||||||
'history\n',
|
'history\n',
|
||||||
@@ -205,7 +204,7 @@ def test_search_multiple_sessions_append_to_history(xdg_data_home):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('key', ('BSpace', '^H'))
|
@pytest.mark.parametrize('key', ('BSpace', '^H'))
|
||||||
def test_search_reverse_search_history_backspace(xdg_data_home, key):
|
def test_search_reverse_search_history_backspace(run, xdg_data_home, key):
|
||||||
xdg_data_home.join('babi/history/search').ensure().write(
|
xdg_data_home.join('babi/history/search').ensure().write(
|
||||||
'line_5\n'
|
'line_5\n'
|
||||||
'line_3\n'
|
'line_3\n'
|
||||||
@@ -222,7 +221,7 @@ def test_search_reverse_search_history_backspace(xdg_data_home, key):
|
|||||||
h.press('^C')
|
h.press('^C')
|
||||||
|
|
||||||
|
|
||||||
def test_search_reverse_search_history(xdg_data_home, ten_lines):
|
def test_search_reverse_search_history(run, xdg_data_home, ten_lines):
|
||||||
xdg_data_home.join('babi/history/search').ensure().write(
|
xdg_data_home.join('babi/history/search').ensure().write(
|
||||||
'line_5\n'
|
'line_5\n'
|
||||||
'line_3\n'
|
'line_3\n'
|
||||||
@@ -240,7 +239,7 @@ def test_search_reverse_search_history(xdg_data_home, ten_lines):
|
|||||||
h.await_cursor_position(x=0, y=4)
|
h.await_cursor_position(x=0, y=4)
|
||||||
|
|
||||||
|
|
||||||
def test_search_reverse_search_history_pos_after(xdg_data_home, ten_lines):
|
def test_search_reverse_search_pos_after(run, xdg_data_home, ten_lines):
|
||||||
xdg_data_home.join('babi/history/search').ensure().write(
|
xdg_data_home.join('babi/history/search').ensure().write(
|
||||||
'line_3\n',
|
'line_3\n',
|
||||||
)
|
)
|
||||||
@@ -255,7 +254,7 @@ def test_search_reverse_search_history_pos_after(xdg_data_home, ten_lines):
|
|||||||
h.press('^C')
|
h.press('^C')
|
||||||
|
|
||||||
|
|
||||||
def test_search_reverse_search_enter_saves_entry(xdg_data_home, ten_lines):
|
def test_search_reverse_search_enter_appends(run, xdg_data_home, ten_lines):
|
||||||
xdg_data_home.join('babi/history/search').ensure().write(
|
xdg_data_home.join('babi/history/search').ensure().write(
|
||||||
'line_1\n'
|
'line_1\n'
|
||||||
'line_3\n',
|
'line_3\n',
|
||||||
@@ -272,7 +271,7 @@ def test_search_reverse_search_enter_saves_entry(xdg_data_home, ten_lines):
|
|||||||
h.press('^C')
|
h.press('^C')
|
||||||
|
|
||||||
|
|
||||||
def test_search_reverse_search_history_cancel():
|
def test_search_reverse_search_history_cancel(run):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('^W')
|
h.press('^W')
|
||||||
h.press('^R')
|
h.press('^R')
|
||||||
@@ -281,7 +280,7 @@ def test_search_reverse_search_history_cancel():
|
|||||||
h.await_text('cancelled')
|
h.await_text('cancelled')
|
||||||
|
|
||||||
|
|
||||||
def test_search_reverse_search_resizing():
|
def test_search_reverse_search_resizing(run):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('^W')
|
h.press('^W')
|
||||||
h.press('^R')
|
h.press('^R')
|
||||||
@@ -290,7 +289,7 @@ def test_search_reverse_search_resizing():
|
|||||||
h.press('^C')
|
h.press('^C')
|
||||||
|
|
||||||
|
|
||||||
def test_search_reverse_search_does_not_wrap_around(xdg_data_home):
|
def test_search_reverse_search_does_not_wrap_around(run, xdg_data_home):
|
||||||
xdg_data_home.join('babi/history/search').ensure().write(
|
xdg_data_home.join('babi/history/search').ensure().write(
|
||||||
'line_1\n'
|
'line_1\n'
|
||||||
'line_3\n',
|
'line_3\n',
|
||||||
@@ -305,7 +304,7 @@ def test_search_reverse_search_does_not_wrap_around(xdg_data_home):
|
|||||||
h.press('^C')
|
h.press('^C')
|
||||||
|
|
||||||
|
|
||||||
def test_search_reverse_search_ctrl_r_on_failed_match(xdg_data_home):
|
def test_search_reverse_search_ctrl_r_on_failed_match(run, xdg_data_home):
|
||||||
xdg_data_home.join('babi/history/search').ensure().write(
|
xdg_data_home.join('babi/history/search').ensure().write(
|
||||||
'nomatch\n'
|
'nomatch\n'
|
||||||
'line_1\n',
|
'line_1\n',
|
||||||
@@ -320,7 +319,7 @@ def test_search_reverse_search_ctrl_r_on_failed_match(xdg_data_home):
|
|||||||
h.press('^C')
|
h.press('^C')
|
||||||
|
|
||||||
|
|
||||||
def test_search_reverse_search_keeps_current_text_displayed():
|
def test_search_reverse_search_keeps_current_text_displayed(run):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('^W')
|
h.press('^W')
|
||||||
h.press('ohai')
|
h.press('ohai')
|
||||||
@@ -330,7 +329,7 @@ def test_search_reverse_search_keeps_current_text_displayed():
|
|||||||
h.press('^C')
|
h.press('^C')
|
||||||
|
|
||||||
|
|
||||||
def test_search_history_extra_blank_lines(xdg_data_home):
|
def test_search_history_extra_blank_lines(run, xdg_data_home):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('^W')
|
h.press('^W')
|
||||||
h.press_and_enter('hello')
|
h.press_and_enter('hello')
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from testing.runner import and_exit
|
from testing.runner import and_exit
|
||||||
from testing.runner import run
|
|
||||||
from testing.runner import trigger_command_mode
|
from testing.runner import trigger_command_mode
|
||||||
|
|
||||||
|
|
||||||
@@ -12,7 +11,7 @@ def unsorted(tmpdir):
|
|||||||
return f
|
return f
|
||||||
|
|
||||||
|
|
||||||
def test_sort_entire_file(unsorted):
|
def test_sort_entire_file(run, unsorted):
|
||||||
with run(str(unsorted)) as h, and_exit(h):
|
with run(str(unsorted)) as h, and_exit(h):
|
||||||
trigger_command_mode(h)
|
trigger_command_mode(h)
|
||||||
h.press_and_enter(':sort')
|
h.press_and_enter(':sort')
|
||||||
@@ -22,7 +21,7 @@ def test_sort_entire_file(unsorted):
|
|||||||
assert unsorted.read() == 'a\nb\nc\nd\n'
|
assert unsorted.read() == 'a\nb\nc\nd\n'
|
||||||
|
|
||||||
|
|
||||||
def test_sort_selection(unsorted):
|
def test_sort_selection(run, unsorted):
|
||||||
with run(str(unsorted)) as h, and_exit(h):
|
with run(str(unsorted)) as h, and_exit(h):
|
||||||
h.press('S-Down')
|
h.press('S-Down')
|
||||||
trigger_command_mode(h)
|
trigger_command_mode(h)
|
||||||
@@ -33,7 +32,7 @@ def test_sort_selection(unsorted):
|
|||||||
assert unsorted.read() == 'b\nd\nc\na\n'
|
assert unsorted.read() == 'b\nd\nc\na\n'
|
||||||
|
|
||||||
|
|
||||||
def test_sort_selection_does_not_include_eof(unsorted):
|
def test_sort_selection_does_not_include_eof(run, unsorted):
|
||||||
with run(str(unsorted)) as h, and_exit(h):
|
with run(str(unsorted)) as h, and_exit(h):
|
||||||
for _ in range(5):
|
for _ in range(5):
|
||||||
h.press('S-Down')
|
h.press('S-Down')
|
||||||
|
|||||||
@@ -1,20 +1,19 @@
|
|||||||
from testing.runner import and_exit
|
from testing.runner import and_exit
|
||||||
from testing.runner import run
|
|
||||||
|
|
||||||
|
|
||||||
def test_status_clearing_behaviour():
|
def test_status_clearing_behaviour(run):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('^J')
|
h.press('^J')
|
||||||
h.await_text('unknown key')
|
h.await_text('unknown key')
|
||||||
for i in range(24):
|
for i in range(24):
|
||||||
h.press('LEFT')
|
h.press('Left')
|
||||||
h.await_text('unknown key')
|
h.await_text('unknown key')
|
||||||
h.press('LEFT')
|
h.press('Left')
|
||||||
h.await_text_missing('unknown key')
|
h.await_text_missing('unknown key')
|
||||||
|
|
||||||
|
|
||||||
def test_very_narrow_window_status():
|
def test_very_narrow_window_status(run):
|
||||||
with run(height=50) as h, and_exit(h):
|
with run(height=50) as h, and_exit(h):
|
||||||
with h.resize(5, 50):
|
with h.resize(width=5, height=50):
|
||||||
h.press('^J')
|
h.press('^J')
|
||||||
h.await_text('unkno')
|
h.await_text('unkno')
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ def test_suspend(tmpdir):
|
|||||||
with PrintsErrorRunner('bash', '--norc') as h:
|
with PrintsErrorRunner('bash', '--norc') as h:
|
||||||
cmd = (sys.executable, '-mcoverage', 'run', '-m', 'babi', str(f))
|
cmd = (sys.executable, '-mcoverage', 'run', '-m', 'babi', str(f))
|
||||||
h.press_and_enter(' '.join(shlex.quote(part) for part in cmd))
|
h.press_and_enter(' '.join(shlex.quote(part) for part in cmd))
|
||||||
h.await_text(babi.VERSION_STR)
|
h.await_text(babi.VERSION_STR, timeout=2)
|
||||||
h.await_text('hello')
|
h.await_text('hello')
|
||||||
|
|
||||||
h.press('^Z')
|
h.press('^Z')
|
||||||
@@ -33,7 +33,7 @@ def test_suspend_with_resize(tmpdir):
|
|||||||
with PrintsErrorRunner('bash', '--norc') as h:
|
with PrintsErrorRunner('bash', '--norc') as h:
|
||||||
cmd = (sys.executable, '-mcoverage', 'run', '-m', 'babi', str(f))
|
cmd = (sys.executable, '-mcoverage', 'run', '-m', 'babi', str(f))
|
||||||
h.press_and_enter(' '.join(shlex.quote(part) for part in cmd))
|
h.press_and_enter(' '.join(shlex.quote(part) for part in cmd))
|
||||||
h.await_text(babi.VERSION_STR)
|
h.await_text(babi.VERSION_STR, timeout=2)
|
||||||
h.await_text('hello')
|
h.await_text('hello')
|
||||||
|
|
||||||
h.press('^Z')
|
h.press('^Z')
|
||||||
|
|||||||
@@ -1,28 +1,27 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from testing.runner import and_exit
|
from testing.runner import and_exit
|
||||||
from testing.runner import run
|
|
||||||
|
|
||||||
|
|
||||||
def test_basic_text_editing(tmpdir):
|
def test_basic_text_editing(run, tmpdir):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('hello world')
|
h.press('hello world')
|
||||||
h.await_text('hello world')
|
h.await_text('hello world')
|
||||||
h.press('Down')
|
h.press('Down')
|
||||||
h.press('bye!')
|
h.press('bye!')
|
||||||
h.await_text('bye!')
|
h.await_text('bye!')
|
||||||
assert h.screenshot().strip().endswith('world\nbye!')
|
h.await_text('hello world\nbye!\n')
|
||||||
|
|
||||||
|
|
||||||
def test_backspace_at_beginning_of_file():
|
def test_backspace_at_beginning_of_file(run):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('BSpace')
|
h.press('BSpace')
|
||||||
h.await_text_missing('unknown key')
|
h.await_text_missing('unknown key')
|
||||||
assert h.screenshot().strip().splitlines()[1:] == []
|
h.assert_cursor_line_equals('')
|
||||||
assert '*' not in h.screenshot()
|
h.await_text_missing('*')
|
||||||
|
|
||||||
|
|
||||||
def test_backspace_joins_lines(tmpdir):
|
def test_backspace_joins_lines(run, tmpdir):
|
||||||
f = tmpdir.join('f')
|
f = tmpdir.join('f')
|
||||||
f.write('foo\nbar\nbaz\n')
|
f.write('foo\nbar\nbaz\n')
|
||||||
|
|
||||||
@@ -38,7 +37,7 @@ def test_backspace_joins_lines(tmpdir):
|
|||||||
h.await_cursor_position(x=3, y=2)
|
h.await_cursor_position(x=3, y=2)
|
||||||
|
|
||||||
|
|
||||||
def test_backspace_at_end_of_file_still_allows_scrolling_down(tmpdir):
|
def test_backspace_at_end_of_file_still_allows_scrolling_down(run, tmpdir):
|
||||||
f = tmpdir.join('f')
|
f = tmpdir.join('f')
|
||||||
f.write('hello world')
|
f.write('hello world')
|
||||||
|
|
||||||
@@ -48,11 +47,11 @@ def test_backspace_at_end_of_file_still_allows_scrolling_down(tmpdir):
|
|||||||
h.press('BSpace')
|
h.press('BSpace')
|
||||||
h.press('Down')
|
h.press('Down')
|
||||||
h.await_cursor_position(x=0, y=2)
|
h.await_cursor_position(x=0, y=2)
|
||||||
assert '*' not in h.screenshot()
|
h.await_text_missing('*')
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('key', ('BSpace', '^H'))
|
@pytest.mark.parametrize('key', ('BSpace', '^H'))
|
||||||
def test_backspace_deletes_text(tmpdir, key):
|
def test_backspace_deletes_text(run, tmpdir, key):
|
||||||
f = tmpdir.join('f')
|
f = tmpdir.join('f')
|
||||||
f.write('ohai there')
|
f.write('ohai there')
|
||||||
|
|
||||||
@@ -66,14 +65,14 @@ def test_backspace_deletes_text(tmpdir, key):
|
|||||||
h.await_cursor_position(x=2, y=1)
|
h.await_cursor_position(x=2, y=1)
|
||||||
|
|
||||||
|
|
||||||
def test_delete_at_end_of_file(tmpdir):
|
def test_delete_at_end_of_file(run, tmpdir):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('DC')
|
h.press('DC')
|
||||||
h.await_text_missing('unknown key')
|
h.await_text_missing('unknown key')
|
||||||
h.await_text_missing('*')
|
h.await_text_missing('*')
|
||||||
|
|
||||||
|
|
||||||
def test_delete_removes_character_afterwards(tmpdir):
|
def test_delete_removes_character_afterwards(run, tmpdir):
|
||||||
f = tmpdir.join('f')
|
f = tmpdir.join('f')
|
||||||
f.write('hello world')
|
f.write('hello world')
|
||||||
|
|
||||||
@@ -85,7 +84,7 @@ def test_delete_removes_character_afterwards(tmpdir):
|
|||||||
h.await_text('f *')
|
h.await_text('f *')
|
||||||
|
|
||||||
|
|
||||||
def test_delete_at_end_of_line(tmpdir):
|
def test_delete_at_end_of_line(run, tmpdir):
|
||||||
f = tmpdir.join('f')
|
f = tmpdir.join('f')
|
||||||
f.write('hello\nworld\n')
|
f.write('hello\nworld\n')
|
||||||
|
|
||||||
@@ -98,7 +97,7 @@ def test_delete_at_end_of_line(tmpdir):
|
|||||||
h.await_text('f *')
|
h.await_text('f *')
|
||||||
|
|
||||||
|
|
||||||
def test_press_enter_beginning_of_file(tmpdir):
|
def test_press_enter_beginning_of_file(run, tmpdir):
|
||||||
f = tmpdir.join('f')
|
f = tmpdir.join('f')
|
||||||
f.write('hello world')
|
f.write('hello world')
|
||||||
|
|
||||||
@@ -110,7 +109,7 @@ def test_press_enter_beginning_of_file(tmpdir):
|
|||||||
h.await_text('f *')
|
h.await_text('f *')
|
||||||
|
|
||||||
|
|
||||||
def test_press_enter_mid_line(tmpdir):
|
def test_press_enter_mid_line(run, tmpdir):
|
||||||
f = tmpdir.join('f')
|
f = tmpdir.join('f')
|
||||||
f.write('hello world')
|
f.write('hello world')
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
from testing.runner import and_exit
|
from testing.runner import and_exit
|
||||||
from testing.runner import run
|
|
||||||
|
|
||||||
|
|
||||||
def test_nothing_to_undo_redo():
|
def test_nothing_to_undo_redo(run):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('M-u')
|
h.press('M-u')
|
||||||
h.await_text('nothing to undo!')
|
h.await_text('nothing to undo!')
|
||||||
@@ -10,7 +9,7 @@ def test_nothing_to_undo_redo():
|
|||||||
h.await_text('nothing to redo!')
|
h.await_text('nothing to redo!')
|
||||||
|
|
||||||
|
|
||||||
def test_undo_redo():
|
def test_undo_redo(run):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('hello')
|
h.press('hello')
|
||||||
h.await_text('hello')
|
h.await_text('hello')
|
||||||
@@ -24,7 +23,7 @@ def test_undo_redo():
|
|||||||
h.await_text(' *')
|
h.await_text(' *')
|
||||||
|
|
||||||
|
|
||||||
def test_undo_redo_movement_interrupts_actions():
|
def test_undo_redo_movement_interrupts_actions(run):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('hello')
|
h.press('hello')
|
||||||
h.press('Left')
|
h.press('Left')
|
||||||
@@ -35,7 +34,7 @@ def test_undo_redo_movement_interrupts_actions():
|
|||||||
h.await_text('hello')
|
h.await_text('hello')
|
||||||
|
|
||||||
|
|
||||||
def test_undo_redo_action_interrupts_actions():
|
def test_undo_redo_action_interrupts_actions(run):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('hello')
|
h.press('hello')
|
||||||
h.await_text('hello')
|
h.await_text('hello')
|
||||||
@@ -50,7 +49,7 @@ def test_undo_redo_action_interrupts_actions():
|
|||||||
h.await_text('hello')
|
h.await_text('hello')
|
||||||
|
|
||||||
|
|
||||||
def test_undo_redo_mixed_newlines(tmpdir):
|
def test_undo_redo_mixed_newlines(run, tmpdir):
|
||||||
f = tmpdir.join('f')
|
f = tmpdir.join('f')
|
||||||
f.write_binary(b'foo\nbar\r\n')
|
f.write_binary(b'foo\nbar\r\n')
|
||||||
|
|
||||||
@@ -61,7 +60,7 @@ def test_undo_redo_mixed_newlines(tmpdir):
|
|||||||
h.await_text(' *')
|
h.await_text(' *')
|
||||||
|
|
||||||
|
|
||||||
def test_undo_redo_with_save(tmpdir):
|
def test_undo_redo_with_save(run, tmpdir):
|
||||||
f = tmpdir.join('f').ensure()
|
f = tmpdir.join('f').ensure()
|
||||||
|
|
||||||
with run(str(f)) as h, and_exit(h):
|
with run(str(f)) as h, and_exit(h):
|
||||||
@@ -80,22 +79,25 @@ def test_undo_redo_with_save(tmpdir):
|
|||||||
h.await_text(' *')
|
h.await_text(' *')
|
||||||
|
|
||||||
|
|
||||||
def test_undo_redo_implicit_linebreak(tmpdir):
|
def test_undo_redo_implicit_linebreak(run, tmpdir):
|
||||||
f = tmpdir.join('f')
|
f = tmpdir.join('f')
|
||||||
|
|
||||||
|
def _assert_contents(s):
|
||||||
|
assert f.read() == s
|
||||||
|
|
||||||
with run(str(f)) as h, and_exit(h):
|
with run(str(f)) as h, and_exit(h):
|
||||||
h.press('hello')
|
h.press('hello')
|
||||||
h.press('M-u')
|
h.press('M-u')
|
||||||
h.press('^S')
|
h.press('^S')
|
||||||
h.await_text('saved!')
|
h.await_text('saved!')
|
||||||
assert f.read() == ''
|
h.run(lambda: _assert_contents(''))
|
||||||
h.press('M-U')
|
h.press('M-U')
|
||||||
h.press('^S')
|
h.press('^S')
|
||||||
h.await_text('saved!')
|
h.await_text('saved!')
|
||||||
assert f.read() == 'hello\n'
|
h.run(lambda: _assert_contents('hello\n'))
|
||||||
|
|
||||||
|
|
||||||
def test_redo_cleared_after_action(tmpdir):
|
def test_redo_cleared_after_action(run, tmpdir):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('hello')
|
h.press('hello')
|
||||||
h.press('M-u')
|
h.press('M-u')
|
||||||
@@ -104,7 +106,7 @@ def test_redo_cleared_after_action(tmpdir):
|
|||||||
h.await_text('nothing to redo!')
|
h.await_text('nothing to redo!')
|
||||||
|
|
||||||
|
|
||||||
def test_undo_no_action_when_noop():
|
def test_undo_no_action_when_noop(run):
|
||||||
with run() as h, and_exit(h):
|
with run() as h, and_exit(h):
|
||||||
h.press('hello')
|
h.press('hello')
|
||||||
h.press('Enter')
|
h.press('Enter')
|
||||||
@@ -116,7 +118,7 @@ def test_undo_no_action_when_noop():
|
|||||||
h.await_cursor_position(x=0, y=2)
|
h.await_cursor_position(x=0, y=2)
|
||||||
|
|
||||||
|
|
||||||
def test_undo_redo_causes_scroll():
|
def test_undo_redo_causes_scroll(run):
|
||||||
with run(height=8) as h, and_exit(h):
|
with run(height=8) as h, and_exit(h):
|
||||||
for i in range(10):
|
for i in range(10):
|
||||||
h.press('Enter')
|
h.press('Enter')
|
||||||
|
|||||||
Reference in New Issue
Block a user