Add faster test harness which fakes curses

This commit is contained in:
Anthony Sottile
2020-01-06 20:12:11 -08:00
parent 1c66b81dc3
commit 11c195e9bf
20 changed files with 633 additions and 297 deletions

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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)')

View File

@@ -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')

View File

@@ -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:')

View File

@@ -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')

View File

@@ -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')

View File

@@ -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')

View File

@@ -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')

View File

@@ -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):

View File

@@ -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')

View File

@@ -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')

View File

@@ -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')

View File

@@ -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')

View File

@@ -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')

View File

@@ -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')

View File

@@ -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')