Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
e2c5c8e
py(deps[dev]): Add syrupy for snapshot testing
tony Dec 6, 2025
260fe90
tests(textframe): Add TextFrame ASCII frame prototype
tony Dec 6, 2025
821ad87
tests(textframe): Add snapshot baselines
tony Dec 6, 2025
c4e869e
py(deps[dev]) Bump dev packages
tony Dec 7, 2025
285db33
TextFrame(feat[__post_init__]): Add dimension and fill_char validation
tony Dec 7, 2025
b11274d
TextFrame(feat[overflow_behavior]): Add truncate mode for content ove…
tony Dec 7, 2025
cc60f54
tests(textframe): Add truncate behavior test cases
tony Dec 7, 2025
f5f25ef
tests(textframe): Add snapshot baselines for truncate tests
tony Dec 7, 2025
e5f088f
tests(textframe): Add pytest_assertrepr_compare hook
tony Dec 7, 2025
868b31c
tests(textframe): Switch to SingleFileSnapshotExtension
tony Dec 7, 2025
58b9769
tests(textframe): Remove old .ambr snapshot file
tony Dec 7, 2025
85e879c
tests(textframe): Add .frame snapshot baselines
tony Dec 7, 2025
1584418
docs(textframe): Document assertion customization patterns
tony Dec 7, 2025
26c9b9a
libtmux(textframe): Move core module to src/libtmux/textframe/
tony Dec 7, 2025
4130a61
libtmux(textframe): Add pytest plugin with hooks and fixtures
tony Dec 7, 2025
3176317
py(deps): Add textframe extras with syrupy dependency
tony Dec 7, 2025
14610d8
docs(textframe): Update for distributable plugin
tony Dec 7, 2025
0c8d73f
py(deps): Update lockfile for textframe extras
tony Dec 7, 2025
82723ce
docs(CHANGES): Document TextFrame features for 0.52.x (#613)
tony Dec 7, 2025
27e0a8d
Pane(feat[capture_frame]): Add capture_frame() method
tony Dec 7, 2025
855a5ff
tests(pane): Add capture_frame() integration tests
tony Dec 7, 2025
3ebf78e
tests(pane): Add capture_frame snapshot baseline
tony Dec 7, 2025
e629cbf
docs(textframe): Document capture_frame() integration
tony Dec 7, 2025
807df38
docs(CHANGES): Document Pane.capture_frame() for 0.52.x (#613)
tony Dec 7, 2025
f162f92
tests(pane): Add exhaustive capture_frame() snapshot tests
tony Dec 7, 2025
a9e0160
tests(pane): Add capture_frame snapshot baselines
tony Dec 7, 2025
748173b
docs(CHANGES): Add visual examples for capture_frame()
tony Dec 7, 2025
4530e8f
Pane(docs[capture_frame]): Fix doctest to work without SKIP
tony Dec 7, 2025
557681c
Pane(feat[capture_frame]): Forward capture_pane() flags
tony Dec 7, 2025
49aa016
tests(pane): Add capture_frame() flag forwarding tests
tony Dec 7, 2025
3d16cee
docs(CHANGES): Document capture_frame() flag forwarding
tony Dec 7, 2025
90010ab
TextFrame(feat[display]): Add interactive curses viewer
tony Dec 8, 2025
3dc3375
docs(textframe): Document display() method
tony Dec 8, 2025
398f9fd
docs(CHANGES): Document TextFrame.display()
tony Dec 8, 2025
79e4236
TextFrame(fix[display]): Use shutil for terminal size detection
tony Dec 8, 2025
d335fc9
tests(textframe): Add shutil terminal size detection test
tony Dec 8, 2025
cdb274f
textframe(fix): Make TextFrameExtension import conditional
tony Dec 8, 2025
e8c7ca8
textframe(fix): Inherit ContentOverflowError from LibTmuxException
tony Dec 8, 2025
50a6f97
textframe(style): Use namespace import for difflib
tony Dec 8, 2025
912288b
tests(textframe): Replace patch() with monkeypatch.setattr()
tony Dec 8, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 134 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,140 @@ $ uvx --from 'libtmux' --prerelease allow python

<!-- To maintainers and contributors: Please add notes for the forthcoming version below -->

### New features

#### TextFrame primitive (#613)

New {class}`~libtmux.textframe.TextFrame` dataclass for testing terminal UI output.
Provides a fixed-size ASCII frame simulator with overflow detection - useful for
validating `capture_pane()` output and terminal rendering in tests.

```python
from libtmux.textframe import TextFrame

frame = TextFrame(content_width=10, content_height=2)
frame.set_content(["hello", "world"])
print(frame.render())
# +----------+
# |hello |
# |world |
# +----------+
```

**Features:**

- Configurable dimensions with `content_width` and `content_height`
- Overflow handling: `overflow_behavior="error"` raises {class}`~libtmux.textframe.ContentOverflowError`
with visual diagnostic, `overflow_behavior="truncate"` clips content silently
- Dimension validation via `__post_init__`
- Interactive curses viewer via `display()` for exploring large frames

#### pytest assertion hook for TextFrame (#613)

Rich assertion output when comparing {class}`~libtmux.textframe.TextFrame` objects.
Shows dimension mismatches and line-by-line content diffs using `difflib.ndiff`.

```
TextFrame comparison failed:
width: 20 != 10
Content diff:
- +----------+
+ +--------------------+
```

#### syrupy snapshot extension for TextFrame (#613)

{class}`~libtmux.textframe.TextFrameExtension` stores snapshots as `.frame` files -
one file per test for cleaner git diffs.

**Installation:**

```console
$ pip install libtmux[textframe]
```

**Usage:**

```python
from libtmux.textframe import TextFrame

def test_pane_output(textframe_snapshot):
frame = TextFrame(content_width=20, content_height=5)
frame.set_content(["Hello", "World"])
assert frame == textframe_snapshot
```

The `textframe_snapshot` fixture and assertion hooks are auto-discovered via
pytest's `pytest11` entry point when the `textframe` extra is installed.

#### Pane.capture_frame() (#613)

New {meth}`~libtmux.pane.Pane.capture_frame` method that wraps
{meth}`~libtmux.pane.Pane.capture_pane` and returns a
{class}`~libtmux.textframe.TextFrame` for visualization and snapshot testing.

**Basic usage:**

```python
pane.send_keys('echo "Hello, TextFrame!"', enter=True)
frame = pane.capture_frame(content_width=30, content_height=5)
print(frame.render())
# +------------------------------+
# |$ echo "Hello, TextFrame!" |
# |Hello, TextFrame! |
# |$ |
# | |
# | |
# +------------------------------+
```

**Multiline output:**

```python
pane.send_keys('printf "a\\nb\\nc\\n"', enter=True)
frame = pane.capture_frame(content_width=20, content_height=6)
print(frame.render())
# +--------------------+
# |$ printf "a\nb\nc\n"|
# |a |
# |b |
# |c |
# |$ |
# | |
# +--------------------+
```

**Truncation (long lines clipped to frame width):**

```python
pane.send_keys('echo "' + "x" * 50 + '"', enter=True)
frame = pane.capture_frame(content_width=15, content_height=4)
print(frame.render())
# +---------------+
# |$ echo "xxxxxxx|
# |xxxxxxxxxxxxxxx|
# |$ |
# | |
# +---------------+
```

**Snapshot testing:**

```python
def test_cli_output(pane, textframe_snapshot):
pane.send_keys("echo 'Hello'", enter=True)
frame = pane.capture_frame(content_width=40, content_height=10)
assert frame == textframe_snapshot
```

**Features:**

- Defaults to pane dimensions when `content_width` / `content_height` not specified
- Uses `overflow_behavior="truncate"` by default for CI robustness
- Accepts same `start` / `end` parameters as `capture_pane()`
- Forwards all `capture_pane()` flags: `escape_sequences`, `escape_non_printable`,
`join_wrapped`, `preserve_trailing`, `trim_trailing`

## libtmux 0.52.1 (2025-12-07)

### CI
Expand Down
1 change: 1 addition & 0 deletions docs/internals/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ dataclasses
query_list
constants
sparse_array
textframe
```

## Environmental variables
Expand Down
Loading
Loading