#!/usr/bin/env python3 """Check local Collapse height transfers through an owned SDL or TTY 9P session. python3 -B test/collapse_layout.py zig-out/column-review/bin/pardes-gui python3 -B test/collapse_layout.py zig-out/column-review/bin/pardes --tty Measures tagline boundaries in the rendered core grid, not host pixel geometry. No user session is contacted, and all fixture files live in a temporary directory. """ import argparse from pathlib import Path import tempfile from fs import new_pane, session from ui_review import panes, require, wait_for def geometry(client, names): frame = client.screen() rows = [''.join(cell[0] for cell in frame['cells'][row * frame['cols']:(row + 1) * frame['cols']]) for row in range(frame['rows'])] starts = [next((row for row, text in enumerate(rows) if '/' + name + ' ' in text), None) for name in names] if any(row is None for row in starts) or starts != sorted(set(starts)): return None ends = starts[1:] + [frame['rows']] return tuple((start, end - start) for start, end in zip(starts, ends)) def toggle(client, serial): """Execute the default tag's Collapse word without changing any body or tag.""" pane = f'/self/pane/{serial}' tag = client.read(pane + '/tag') at = tag.rfind(b' Collapse') require(at >= 0, 'default pane tag lacks Collapse') at += 1 client.write(pane + '/event', f'Mx{at} {at + len("Collapse")}\n'.encode()) def run(args): binary = str(args.binary.resolve()) with tempfile.TemporaryDirectory(prefix='pardes-collapse-') as directory: root = Path(directory) name = 'collapse-layout' inherited = {} if args.tty else { 'PARDES_TEST': '1', 'PARDES_TEST_COLS': '120', 'PARDES_TEST_ROWS': '48', 'PARDES_TEST_CAPTURE_DIR': str(root), 'SDL_VIDEODRIVER': 'x11', } launch = ['-c', 'stty cols 120 rows 48; exec "$@"', name, binary, '--9p=' + name, str(root / (name + '.txt'))] with session('/bin/sh', root, name, socket_name=name, tty=True, launch=launch, inherited=inherited) as (client, _): serials = [next(iter(panes(client)))] names = [f'collapse-{letter}' for letter in 'ABCD'] contents = b'owned collapse fixture\n' * 60 for index, label in enumerate(names): if index: serials.append(new_pane(client, b'')) pane = f'/self/pane/{serials[index]}' client.write(pane + '/ctl', ('name ' + label + '\n').encode()) client.write(pane + '/body', contents, truncate=True) original = require(wait_for(lambda: geometry(client, names)), 'four pane tags not visible') require(all(height > 1 for _, height in original), f'fixture has no body room: {original}') for target in [1, 3, 0, 2, 1, 3, 0]: recipient = target - 1 if target else 1 heights = [height for _, height in original] heights[recipient] += heights[target] - 1 heights[target] = 1 expected = [] row = original[0][0] for height in heights: expected.append((row, height)) row += height toggle(client, serials[target]) require(wait_for(lambda: geometry(client, names) == tuple(expected)), f'Collapse {names[target]} changed unrelated geometry: {geometry(client, names)} != {expected}') for index in range(4): if index not in [target, recipient]: require(expected[index] == original[index], 'unrelated pane moved') toggle(client, serials[target]) require(wait_for(lambda: geometry(client, names) == original), f'expansion did not restore original geometry: {geometry(client, names)}') for serial in serials: require(client.read(f'/self/pane/{serial}/body') == contents, 'Collapse changed pane contents') require((root / (name + '.txt')).read_bytes() == b'initial\n', 'fixture source file changed') print(f'{"TTY" if args.tty else "SDL"}: 7 local collapse/expand pairs, exact unrelated pane geometry, and content preservation passed') if __name__ == '__main__': parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('binary', type=Path) parser.add_argument('--tty', action='store_true') run(parser.parse_args())