summaryrefslogtreecommitdiff
path: root/test/column_grip.py
diff options
context:
space:
mode:
Diffstat (limited to 'test/column_grip.py')
-rw-r--r--test/column_grip.py132
1 files changed, 132 insertions, 0 deletions
diff --git a/test/column_grip.py b/test/column_grip.py
new file mode 100644
index 00000000..ac0341c3
--- /dev/null
+++ b/test/column_grip.py
@@ -0,0 +1,132 @@
+#!/usr/bin/env python3
+"""Drag a column grip in the real SDL window with pixel mouse events.
+
+WAYLAND_DISPLAY=/run/user/1000/wayland-1 \\
+ python3 -B test/column_grip.py zig-out/bin/pardes-gui /tmp/pardes-column-grip [--pad 7]
+
+The window is hidden (PARDES_TEST). Mouse events enter as ESC]777;mouse
+sequences on stdin, which the GUI builds into SDL events and dispatches like
+its own, so pixel-to-cell and tag hit testing run as they do under a hand.
+9P observes the grid; each step leaves a PNG of the GPU frame in the output
+directory: grip-rest, grip-resize-drag, grip-reorder-drag, grip-done.
+"""
+import argparse
+import os
+from pathlib import Path
+import shutil
+import subprocess
+import time
+
+from fs import session
+from ui_review import require, wait_for
+
+
+def rows(client):
+ frame = client.screen()
+ cols = frame['cols']
+ return [''.join(cell[0] for cell in frame['cells'][at:at + cols]) for at in range(0, len(frame['cells']), cols)]
+
+
+def rail(client):
+ """The one column the drag preview's rail runs down, if any."""
+ bottom = rows(client)[-1]
+ found = [x for x, ch in enumerate(bottom) if ch == '╎']
+ require(len(found) <= 1, f'more than one rail: {found}')
+ return found[0] if found else None
+
+
+def grab(root, label):
+ time.sleep(.5)
+ def convert():
+ shutil.copyfile(root / 'latest.ppm', root / (label + '.ppm'))
+ return subprocess.run(['magick', str(root / (label + '.ppm')), str(root / (label + '.png'))],
+ stderr=subprocess.DEVNULL).returncode == 0
+ require(wait_for(convert, 8), 'GPU capture did not contain a complete image')
+ return root / (label + '.png')
+
+
+def pixel(path, x, y):
+ out = subprocess.run(['magick', str(path), '-format', f'%[pixel:p{{{x},{y}}}]', 'info:'],
+ capture_output=True, text=True, check=True)
+ return out.stdout.strip()
+
+
+def run(args):
+ root = args.output.resolve()
+ shutil.rmtree(root, ignore_errors=True)
+ root.mkdir(parents=True, mode=0o700)
+ (root / 'config/pardes').mkdir(parents=True)
+ (root / 'config/pardes/init').write_text('Shell /bin/sh\n')
+ (root / 'w.txt').write_text('alpha\nbeta\n')
+ name = 'column-grip-' + str(os.getpid())
+ launch = ['-c', 'stty cols 100 rows 30; exec "$@"', 'column-grip',
+ str(args.binary.resolve()), '--9p=' + name, str(root / 'w.txt')]
+ env = {'PARDES_TEST': '1', 'PARDES_TEST_CAPTURE_DIR': str(root), 'PARDES_TEST_COLS': '100',
+ 'PARDES_TEST_ROWS': '30', 'PARDES_TEST_PAD': str(args.pad)}
+ handles = {}
+ with session('/bin/sh', root, name, socket_name=name, tty=True, launch=launch,
+ inherited=env, terminal=handles) as (client, _):
+ def send(data):
+ os.write(handles['input_fd'], data.encode())
+
+ def mouse(kind, button, col, row):
+ # the middle of a grid cell, in window pixels
+ send(f'\x1b]777;mouse;{kind};{button};{(col + .5) * cw};{(row + .5) * ch}\x07')
+ time.sleep(.15)
+
+ require(wait_for(lambda: 'Newcol' in rows(client)[0]), 'workspace tag never drew')
+ send('\x1bn')
+ time.sleep(.3)
+ send('\x1bc')
+ require(wait_for(lambda: rows(client)[1].count('New Tty') == 2), 'no second column')
+ require(wait_for(lambda: (root / 'latest.ppm').exists(), 8), 'GPU capture missing')
+ time.sleep(.5)
+ with open(root / 'latest.ppm', 'rb') as ppm:
+ ppm.readline()
+ width, height = map(int, ppm.readline().split())
+ frame = client.screen()
+ cw = (width - args.pad) // frame['cols']
+ ch = (height - args.pad) // frame['rows']
+ edge = rows(client)[1].index('New Tty', 10) - 3
+
+ rest = grab(root, 'grip-rest')
+ mouse('down', 1, edge, 1)
+ held = grab(root, 'grip-held')
+ require(pixel(held, int((edge + 1) * cw), int(1.5 * ch)) != pixel(rest, int((edge + 1) * cw), int(1.5 * ch)),
+ 'a grabbed grip looks the same as a resting one')
+ require(rail(client) is None, 'a grip held in place previews a move')
+
+ # Short of the left column's middle: the left edge follows, and the
+ # rail is the seam cell left of where the rule will be.
+ for col in range(edge, edge - 11, -1):
+ mouse('motion', 1, col, 1)
+ require(rail(client) == edge - 11, f'resize rail at {rail(client)}, wanted {edge - 11}')
+ grab(root, 'grip-resize-drag')
+ mouse('up', 1, edge - 10, 1)
+ require(wait_for(lambda: rows(client)[1].index('New Tty', 10) - 3 == edge - 10), 'left edge did not follow the grip')
+ edge -= 10
+
+ # Over the left column's left half: it goes first, and the rail runs
+ # just past the left window edge's scroll rail.
+ mouse('down', 1, edge, 1)
+ for col in range(edge, 4, -5):
+ mouse('motion', 1, col, 10)
+ require(rail(client) == 1, f'reorder rail at {rail(client)}, wanted 1')
+ grab(root, 'grip-reorder-drag')
+ mouse('up', 1, 5, 10)
+ require(wait_for(lambda: 'alpha' in rows(client)[3][50:]), 'the column did not move first')
+ require(rail(client) is None, 'the rail outlived the drag')
+ grab(root, 'grip-done')
+ print('ok', root)
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument('binary', type=Path)
+ parser.add_argument('output', type=Path)
+ parser.add_argument('--pad', type=int, default=0, help='leftover pixels past the grid (PARDES_TEST_PAD)')
+ run(parser.parse_args())
+
+
+if __name__ == '__main__':
+ main()