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
3d57af9
py(deps[dev]): Add syrupy for snapshot testing
tony Dec 6, 2025
7466a93
tests(textframe): Add TextFrame ASCII frame prototype
tony Dec 6, 2025
b518667
tests(textframe): Add snapshot baselines
tony Dec 6, 2025
1423a86
py(deps[dev]) Bump dev packages
tony Dec 7, 2025
2d6008e
TextFrame(feat[__post_init__]): Add dimension and fill_char validation
tony Dec 7, 2025
5fff019
TextFrame(feat[overflow_behavior]): Add truncate mode for content ove…
tony Dec 7, 2025
7a5ac4b
tests(textframe): Add truncate behavior test cases
tony Dec 7, 2025
e1229ac
tests(textframe): Add snapshot baselines for truncate tests
tony Dec 7, 2025
c121d00
tests(textframe): Add pytest_assertrepr_compare hook
tony Dec 7, 2025
928b2a8
tests(textframe): Switch to SingleFileSnapshotExtension
tony Dec 7, 2025
ebe87d1
tests(textframe): Remove old .ambr snapshot file
tony Dec 7, 2025
2c8777d
tests(textframe): Add .frame snapshot baselines
tony Dec 7, 2025
102c6f2
docs(textframe): Document assertion customization patterns
tony Dec 7, 2025
101af08
libtmux(textframe): Move core module to src/libtmux/textframe/
tony Dec 7, 2025
6a06381
libtmux(textframe): Add pytest plugin with hooks and fixtures
tony Dec 7, 2025
4305e38
py(deps): Add textframe extras with syrupy dependency
tony Dec 7, 2025
babc74d
docs(textframe): Update for distributable plugin
tony Dec 7, 2025
aa08802
py(deps): Update lockfile for textframe extras
tony Dec 7, 2025
d2b259a
docs(CHANGES): Document TextFrame features for 0.52.x (#613)
tony Dec 7, 2025
29cf073
Pane(feat[capture_frame]): Add capture_frame() method
tony Dec 7, 2025
4b1266e
tests(pane): Add capture_frame() integration tests
tony Dec 7, 2025
f303c5d
tests(pane): Add capture_frame snapshot baseline
tony Dec 7, 2025
ea47f1e
docs(textframe): Document capture_frame() integration
tony Dec 7, 2025
68763ed
docs(CHANGES): Document Pane.capture_frame() for 0.52.x (#613)
tony Dec 7, 2025
1d00e99
tests(pane): Add exhaustive capture_frame() snapshot tests
tony Dec 7, 2025
bdc930b
tests(pane): Add capture_frame snapshot baselines
tony Dec 7, 2025
cbae7bf
docs(CHANGES): Add visual examples for capture_frame()
tony Dec 7, 2025
033930e
Pane(docs[capture_frame]): Fix doctest to work without SKIP
tony Dec 7, 2025
f3443ef
Pane(feat[capture_frame]): Forward capture_pane() flags
tony Dec 7, 2025
662426c
tests(pane): Add capture_frame() flag forwarding tests
tony Dec 7, 2025
c521282
docs(CHANGES): Document capture_frame() flag forwarding
tony Dec 7, 2025
fa6028c
TextFrame(feat[display]): Add interactive curses viewer
tony Dec 8, 2025
112ba05
docs(textframe): Document display() method
tony Dec 8, 2025
3f4803a
docs(CHANGES): Document TextFrame.display()
tony Dec 8, 2025
d68c15d
TextFrame(fix[display]): Use shutil for terminal size detection
tony Dec 8, 2025
0b0d7ad
tests(textframe): Add shutil terminal size detection test
tony Dec 8, 2025
7cf98d1
textframe(fix): Make TextFrameExtension import conditional
tony Dec 8, 2025
987ea1d
textframe(fix): Inherit ContentOverflowError from LibTmuxException
tony Dec 8, 2025
75f2587
textframe(style): Use namespace import for difflib
tony Dec 8, 2025
e9b35a9
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.53.0 (2025-12-14)

### Breaking changes
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