Skip to content

Commit 9425eb9

Browse files
committed
test: regression for issue #229
1 parent 2004062 commit 9425eb9

2 files changed

Lines changed: 88 additions & 1 deletion

File tree

‎sqlit/domains/results/ui/mixins/results.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
MIN_TIMER_DELAY_S = 0.001
1414

1515

16-
def _strip_table_markup(table: SqlitDataTable | None, value: Any) -> Any:
16+
def _strip_table_markup(table: Any, value: Any) -> Any:
1717
"""Strip Rich markup from a cell value when the table renders markup.
1818
1919
The results filter stores cells with Rich markup (e.g. `[bold #FFFF00]Ja[/]ne`)
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
"""Regression tests for issue #229 - Rich markup leaking into copied cells.
2+
3+
When the results filter is active, cells are stored in the table with Rich
4+
markup (e.g. `[bold #FFFF00]Ja[/]ne`). Copy actions used to feed that markup
5+
straight to the clipboard. These tests drive `action_copy_cell` / _row through
6+
their public entry points against a fake table that mimics the filter state,
7+
so they fail without the fix and pass with it.
8+
"""
9+
10+
from __future__ import annotations
11+
12+
from typing import Any
13+
14+
import pytest
15+
16+
from sqlit.domains.results.ui.mixins.results import ResultsMixin
17+
18+
19+
class _FakeTable:
20+
def __init__(self, cells: list[tuple[str, ...]], render_markup: bool) -> None:
21+
self._cells = cells
22+
self.row_count = len(cells)
23+
self.render_markup = render_markup
24+
self.cursor_row = 0
25+
self.cursor_coordinate = (0, 0)
26+
27+
def get_cell_at(self, coord: Any) -> Any:
28+
if isinstance(coord, tuple):
29+
row, col = coord
30+
else:
31+
row, col = coord.row, coord.column
32+
return self._cells[row][col]
33+
34+
def get_row_at(self, row: int) -> list[Any]:
35+
return list(self._cells[row])
36+
37+
38+
class _FakeApp(ResultsMixin):
39+
"""Just enough harness to exercise the copy actions without Textual."""
40+
41+
def __init__(self, cells: list[tuple[str, ...]], *, render_markup: bool = True) -> None:
42+
self._table = _FakeTable(cells, render_markup=render_markup)
43+
self.clipboard_text: str | None = None
44+
45+
def _get_active_results_context(self) -> tuple[Any, list, list, bool]:
46+
return self._table, [], [], False
47+
48+
def _copy_text(self, text: str) -> bool: # type: ignore[override]
49+
self.clipboard_text = text
50+
return True
51+
52+
def _flash_table_yank(self, *_args: Any, **_kwargs: Any) -> None:
53+
pass
54+
55+
def notify(self, *_args: Any, **_kwargs: Any) -> None:
56+
pass
57+
58+
def _clear_leader_pending(self) -> None:
59+
pass
60+
61+
62+
@pytest.mark.parametrize(
63+
"action_name",
64+
["action_copy_cell", "action_ry_cell"],
65+
)
66+
def test_copy_cell_strips_filter_markup(action_name: str) -> None:
67+
app = _FakeApp([("[bold #FFFF00]Ja[/]ne",)])
68+
getattr(app, action_name)()
69+
assert app.clipboard_text == "Jane"
70+
71+
72+
@pytest.mark.parametrize(
73+
"action_name",
74+
["action_copy_row", "action_ry_row"],
75+
)
76+
def test_copy_row_strips_filter_markup(action_name: str) -> None:
77+
app = _FakeApp([("[bold #FFFF00]Ja[/]ne", "Doe")])
78+
getattr(app, action_name)()
79+
assert app.clipboard_text == "Jane\tDoe"
80+
81+
82+
def test_copy_cell_preserves_literal_brackets_when_not_rendering_markup() -> None:
83+
# When the table is in plain mode, cells are stored verbatim. We must NOT
84+
# treat brackets as markup, or legitimate data like "[bold]hi" gets eaten.
85+
app = _FakeApp([("[bold]hello",)], render_markup=False)
86+
app.action_copy_cell()
87+
assert app.clipboard_text == "[bold]hello"

0 commit comments

Comments
 (0)