tracetui 2.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. tracetui-2.0.0/LICENSE +21 -0
  2. tracetui-2.0.0/PKG-INFO +162 -0
  3. tracetui-2.0.0/README.md +148 -0
  4. tracetui-2.0.0/pyproject.toml +26 -0
  5. tracetui-2.0.0/setup.cfg +4 -0
  6. tracetui-2.0.0/tests/test_anomaly_detection.py +127 -0
  7. tracetui-2.0.0/tests/test_detail_panel.py +98 -0
  8. tracetui-2.0.0/tests/test_filter_integration.py +112 -0
  9. tracetui-2.0.0/tests/test_filter_modal.py +17 -0
  10. tracetui-2.0.0/tests/test_live_monitor.py +92 -0
  11. tracetui-2.0.0/tests/test_models.py +90 -0
  12. tracetui-2.0.0/tests/test_parsers.py +251 -0
  13. tracetui-2.0.0/tests/test_widgets.py +404 -0
  14. tracetui-2.0.0/tracetui/__init__.py +3 -0
  15. tracetui-2.0.0/tracetui/__main__.py +6 -0
  16. tracetui-2.0.0/tracetui/app.py +403 -0
  17. tracetui-2.0.0/tracetui/cli.py +56 -0
  18. tracetui-2.0.0/tracetui/models.py +103 -0
  19. tracetui-2.0.0/tracetui/parsers/__init__.py +1 -0
  20. tracetui-2.0.0/tracetui/parsers/log_parser.py +467 -0
  21. tracetui-2.0.0/tracetui/widgets/__init__.py +1 -0
  22. tracetui-2.0.0/tracetui/widgets/detail_panel.py +106 -0
  23. tracetui-2.0.0/tracetui/widgets/filter_input.py +96 -0
  24. tracetui-2.0.0/tracetui/widgets/live_monitor.py +230 -0
  25. tracetui-2.0.0/tracetui/widgets/log_list.py +601 -0
  26. tracetui-2.0.0/tracetui/widgets/source_panel.py +137 -0
  27. tracetui-2.0.0/tracetui.egg-info/PKG-INFO +162 -0
  28. tracetui-2.0.0/tracetui.egg-info/SOURCES.txt +30 -0
  29. tracetui-2.0.0/tracetui.egg-info/dependency_links.txt +1 -0
  30. tracetui-2.0.0/tracetui.egg-info/entry_points.txt +2 -0
  31. tracetui-2.0.0/tracetui.egg-info/requires.txt +5 -0
  32. tracetui-2.0.0/tracetui.egg-info/top_level.txt +1 -0
tracetui-2.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 TraceTUI Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,162 @@
1
+ Metadata-Version: 2.4
2
+ Name: tracetui
3
+ Version: 2.0.0
4
+ Summary: Interactive TUI debugger for logs with real-time parsing and filtering
5
+ License: MIT
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: textual>=1.0.0
10
+ Requires-Dist: rich>=13.0.0
11
+ Provides-Extra: dev
12
+ Requires-Dist: pytest>=7.0; extra == "dev"
13
+ Dynamic: license-file
14
+
15
+ # TraceTUI
16
+
17
+ <p align="left">
18
+ <a href="./README.md">🇺🇸 English</a> •
19
+ <a href="./README.ru.md">🇷🇺 Русский</a>
20
+ </p>
21
+
22
+ **Interactive TUI debugger for logs** — a beautiful console interface built on [Textual](https://textual.io/) that parses logs (`loguru`, `logging`) in real-time, highlights errors, and lets you filter them with arrow keys.
23
+
24
+ ![Tests](https://github.com/slimeopus/TraceTUI/actions/workflows/tests.yml/badge.svg)
25
+ ![Python Version](https://img.shields.io/badge/python-3.10+-blue.svg)
26
+ ![License](https://img.shields.io/github/license/slimeopus/TraceTUI)
27
+ ![Version](https://img.shields.io/badge/version-2.0.0-34C4C4.svg?style=flat)
28
+
29
+ ![Preview](.github/assets/preview.png)
30
+
31
+ ## ✨ Features
32
+
33
+ ### Three-panel interface
34
+ - **Left panel** — list of log sources/files with activity indicators
35
+ - **Center panel** — log stream with color-coded severity levels
36
+ - **Right panel** — selected entry details (traceback, metadata, tags)
37
+
38
+ ### Log formats
39
+ - **loguru** — `2024-01-15 10:30:45 | ERROR | module.py:42 - message`
40
+ - **logging** — `2024-01-15 10:30:45,123 - module.name - ERROR - message`
41
+ - **Simple** — `ERROR - message`
42
+ - **Auto-detect** — automatic format detection
43
+
44
+ ### Navigation & Control
45
+
46
+ #### Log navigation
47
+ - `↑` / `↓` — navigate through logs (by line)
48
+ - Error blocks are highlighted on hover
49
+
50
+ #### Source switching
51
+ - `←` / `→` — switch between sources/log files
52
+ - Active source is marked with `▸` in the left panel
53
+
54
+ #### Keyboard shortcuts
55
+ | Key | Action | Description |
56
+ |-----|--------|-------------|
57
+ | `F` | Filter | Filter mode — type text to search |
58
+ | `E` | Next Error | Jump to next error (skip INFO/DEBUG) |
59
+ | `T` | Theme | Switch theme (light/dark) |
60
+ | `Q` | Quit | Exit the application |
61
+
62
+ #### Additional capabilities
63
+ - Copy traceback to clipboard
64
+ - Add custom tags (labels)
65
+ - Filter by level and text
66
+ - Multiple log file support
67
+ - **Timeline Merge Mode** (`m`) — cross-file event timeline with color-coded source tags
68
+
69
+ ## 📦 Installation
70
+
71
+ ### Via pip
72
+ ```bash
73
+ pip install tracetui
74
+ ```
75
+
76
+ ### From source
77
+ ```bash
78
+ git clone <repository-url>
79
+ cd TraceTUI
80
+ pip install -e .
81
+ ```
82
+
83
+ ### Dependencies
84
+ - Python >= 3.10
85
+ - textual >= 1.0.0
86
+ - rich >= 13.0.0
87
+
88
+ ## 🚀 Usage
89
+
90
+ ### Quick start
91
+ ```bash
92
+ # Run with sample logs
93
+ tracetui sample_logs/app_standard.log
94
+ tracetui sample_logs/app_loguru.log
95
+
96
+ # Multiple files
97
+ tracetui log1.log log2.log log3.log
98
+
99
+ # With format specification
100
+ tracetui -f loguru my_app.log
101
+ tracetui -f logging server.log
102
+
103
+ # Multiple files (timeline mode — press m in UI)
104
+ tracetui app.log worker.log scheduler.log
105
+
106
+ # Filter by level
107
+ tracetui -l ERROR app.log
108
+ ```
109
+
110
+ ### Programmatic usage
111
+ ```python
112
+ from tracetui.app import TraceTUIApp
113
+ from tracetui.parsers.log_parser import load_log_source
114
+ from pathlib import Path
115
+
116
+ app = TraceTUIApp()
117
+
118
+ # Load a file
119
+ source = load_log_source(Path("app.log"))
120
+ app.add_log_source(source)
121
+
122
+ # Run
123
+ app.run()
124
+ ```
125
+
126
+ ## 🧪 Testing
127
+
128
+ ```bash
129
+ # Run all tests
130
+ pytest
131
+
132
+ # With verbose output
133
+ pytest -v
134
+
135
+ # With coverage
136
+ pytest --cov=tracetui --cov-report=html
137
+ ```
138
+
139
+ ## 📝 Usage examples
140
+
141
+ ### Debugging an app with many errors
142
+ ```bash
143
+ # Open a log with auto-detect format
144
+ tracetui /var/log/myapp.log
145
+
146
+ # Navigate with arrows to ERROR
147
+ # Press Enter to view details
148
+ # Check the traceback in the right panel
149
+ # Press 'd' to mark as "investigate"
150
+ ```
151
+
152
+ ### Monitoring multiple files
153
+ ```bash
154
+ # Open several logs simultaneously
155
+ tracetui app.log worker.log scheduler.log
156
+ ```
157
+
158
+ ## 📄 License
159
+
160
+ MIT
161
+
162
+ ---
@@ -0,0 +1,148 @@
1
+ # TraceTUI
2
+
3
+ <p align="left">
4
+ <a href="./README.md">🇺🇸 English</a> •
5
+ <a href="./README.ru.md">🇷🇺 Русский</a>
6
+ </p>
7
+
8
+ **Interactive TUI debugger for logs** — a beautiful console interface built on [Textual](https://textual.io/) that parses logs (`loguru`, `logging`) in real-time, highlights errors, and lets you filter them with arrow keys.
9
+
10
+ ![Tests](https://github.com/slimeopus/TraceTUI/actions/workflows/tests.yml/badge.svg)
11
+ ![Python Version](https://img.shields.io/badge/python-3.10+-blue.svg)
12
+ ![License](https://img.shields.io/github/license/slimeopus/TraceTUI)
13
+ ![Version](https://img.shields.io/badge/version-2.0.0-34C4C4.svg?style=flat)
14
+
15
+ ![Preview](.github/assets/preview.png)
16
+
17
+ ## ✨ Features
18
+
19
+ ### Three-panel interface
20
+ - **Left panel** — list of log sources/files with activity indicators
21
+ - **Center panel** — log stream with color-coded severity levels
22
+ - **Right panel** — selected entry details (traceback, metadata, tags)
23
+
24
+ ### Log formats
25
+ - **loguru** — `2024-01-15 10:30:45 | ERROR | module.py:42 - message`
26
+ - **logging** — `2024-01-15 10:30:45,123 - module.name - ERROR - message`
27
+ - **Simple** — `ERROR - message`
28
+ - **Auto-detect** — automatic format detection
29
+
30
+ ### Navigation & Control
31
+
32
+ #### Log navigation
33
+ - `↑` / `↓` — navigate through logs (by line)
34
+ - Error blocks are highlighted on hover
35
+
36
+ #### Source switching
37
+ - `←` / `→` — switch between sources/log files
38
+ - Active source is marked with `▸` in the left panel
39
+
40
+ #### Keyboard shortcuts
41
+ | Key | Action | Description |
42
+ |-----|--------|-------------|
43
+ | `F` | Filter | Filter mode — type text to search |
44
+ | `E` | Next Error | Jump to next error (skip INFO/DEBUG) |
45
+ | `T` | Theme | Switch theme (light/dark) |
46
+ | `Q` | Quit | Exit the application |
47
+
48
+ #### Additional capabilities
49
+ - Copy traceback to clipboard
50
+ - Add custom tags (labels)
51
+ - Filter by level and text
52
+ - Multiple log file support
53
+ - **Timeline Merge Mode** (`m`) — cross-file event timeline with color-coded source tags
54
+
55
+ ## 📦 Installation
56
+
57
+ ### Via pip
58
+ ```bash
59
+ pip install tracetui
60
+ ```
61
+
62
+ ### From source
63
+ ```bash
64
+ git clone <repository-url>
65
+ cd TraceTUI
66
+ pip install -e .
67
+ ```
68
+
69
+ ### Dependencies
70
+ - Python >= 3.10
71
+ - textual >= 1.0.0
72
+ - rich >= 13.0.0
73
+
74
+ ## 🚀 Usage
75
+
76
+ ### Quick start
77
+ ```bash
78
+ # Run with sample logs
79
+ tracetui sample_logs/app_standard.log
80
+ tracetui sample_logs/app_loguru.log
81
+
82
+ # Multiple files
83
+ tracetui log1.log log2.log log3.log
84
+
85
+ # With format specification
86
+ tracetui -f loguru my_app.log
87
+ tracetui -f logging server.log
88
+
89
+ # Multiple files (timeline mode — press m in UI)
90
+ tracetui app.log worker.log scheduler.log
91
+
92
+ # Filter by level
93
+ tracetui -l ERROR app.log
94
+ ```
95
+
96
+ ### Programmatic usage
97
+ ```python
98
+ from tracetui.app import TraceTUIApp
99
+ from tracetui.parsers.log_parser import load_log_source
100
+ from pathlib import Path
101
+
102
+ app = TraceTUIApp()
103
+
104
+ # Load a file
105
+ source = load_log_source(Path("app.log"))
106
+ app.add_log_source(source)
107
+
108
+ # Run
109
+ app.run()
110
+ ```
111
+
112
+ ## 🧪 Testing
113
+
114
+ ```bash
115
+ # Run all tests
116
+ pytest
117
+
118
+ # With verbose output
119
+ pytest -v
120
+
121
+ # With coverage
122
+ pytest --cov=tracetui --cov-report=html
123
+ ```
124
+
125
+ ## 📝 Usage examples
126
+
127
+ ### Debugging an app with many errors
128
+ ```bash
129
+ # Open a log with auto-detect format
130
+ tracetui /var/log/myapp.log
131
+
132
+ # Navigate with arrows to ERROR
133
+ # Press Enter to view details
134
+ # Check the traceback in the right panel
135
+ # Press 'd' to mark as "investigate"
136
+ ```
137
+
138
+ ### Monitoring multiple files
139
+ ```bash
140
+ # Open several logs simultaneously
141
+ tracetui app.log worker.log scheduler.log
142
+ ```
143
+
144
+ ## 📄 License
145
+
146
+ MIT
147
+
148
+ ---
@@ -0,0 +1,26 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "tracetui"
7
+ version = "2.0.0"
8
+ description = "Interactive TUI debugger for logs with real-time parsing and filtering"
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ requires-python = ">=3.10"
12
+ dependencies = [
13
+ "textual>=1.0.0",
14
+ "rich>=13.0.0",
15
+ ]
16
+
17
+ [project.optional-dependencies]
18
+ dev = [
19
+ "pytest>=7.0",
20
+ ]
21
+
22
+ [project.scripts]
23
+ tracetui = "tracetui.cli:main"
24
+
25
+ [tool.setuptools.packages.find]
26
+ include = ["tracetui*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,127 @@
1
+ """Тесты для автоматического обнаружения аномалий."""
2
+
3
+ from tracetui.models import LogLevel, LogEntry
4
+ from tracetui.widgets.log_list import LogList
5
+
6
+
7
+ class TestAnomalyDetection:
8
+ """Тесты автоматического выделения аномалий."""
9
+
10
+ def test_error_level_is_anomaly(self):
11
+ """Уровень ERROR автоматически аномальный."""
12
+ log_list = LogList()
13
+ entry = LogEntry(level=LogLevel.ERROR, message="some info")
14
+ assert log_list._is_anomaly(entry) is True
15
+
16
+ def test_critical_level_is_anomaly(self):
17
+ """Уровень CRITICAL автоматически аномальный."""
18
+ log_list = LogList()
19
+ entry = LogEntry(level=LogLevel.CRITICAL, message="some info")
20
+ assert log_list._is_anomaly(entry) is True
21
+
22
+ def test_timeout_keyword_detected(self):
23
+ """Ключевое слово Timeout обнаруживается."""
24
+ log_list = LogList()
25
+ entry = LogEntry(level=LogLevel.WARNING, message="Request timeout after 30s")
26
+ assert log_list._is_anomaly(entry) is True
27
+
28
+ def test_connection_error_keyword_detected(self):
29
+ """Ключевое слово ConnectionError обнаруживается."""
30
+ log_list = LogList()
31
+ entry = LogEntry(level=LogLevel.ERROR, message="ConnectionError: host unreachable")
32
+ assert log_list._is_anomaly(entry) is True
33
+
34
+ def test_exception_keyword_detected(self):
35
+ """Ключевое слово Exception обнаруживается."""
36
+ log_list = LogList()
37
+ entry = LogEntry(level=LogLevel.ERROR, message="Unhandled exception in worker")
38
+ assert log_list._is_anomaly(entry) is True
39
+
40
+ def test_failure_keyword_detected(self):
41
+ """Ключевое слово Failure обнаруживается."""
42
+ log_list = LogList()
43
+ entry = LogEntry(level=LogLevel.WARNING, message="Task failure after 3 retries")
44
+ assert log_list._is_anomaly(entry) is True
45
+
46
+ def test_fatal_keyword_detected(self):
47
+ """Ключевое слово Fatal обнаруживается."""
48
+ log_list = LogList()
49
+ entry = LogEntry(level=LogLevel.ERROR, message="Fatal error in main loop")
50
+ assert log_list._is_anomaly(entry) is True
51
+
52
+ def test_status_code_500_detected(self):
53
+ """HTTP 500 обнаруживается."""
54
+ log_list = LogList()
55
+ entry = LogEntry(level=LogLevel.WARNING, message="Server responded with 500")
56
+ assert log_list._is_anomaly(entry) is True
57
+
58
+ def test_status_code_503_detected(self):
59
+ """HTTP 503 обнаруживается."""
60
+ log_list = LogList()
61
+ entry = LogEntry(level=LogLevel.WARNING, message="Service 503 unavailable")
62
+ assert log_list._is_anomaly(entry) is True
63
+
64
+ def test_timed_out_keyword_detected(self):
65
+ """Фраза timed out обнаруживается."""
66
+ log_list = LogList()
67
+ entry = LogEntry(level=LogLevel.WARNING, message="Database connection timed out")
68
+ assert log_list._is_anomaly(entry) is True
69
+
70
+ def test_broken_pipe_detected(self):
71
+ """Фраза broken pipe обнаруживается."""
72
+ log_list = LogList()
73
+ entry = LogEntry(level=LogLevel.ERROR, message="Write failed: broken pipe")
74
+ assert log_list._is_anomaly(entry) is True
75
+
76
+ def test_normal_info_is_not_anomaly(self):
77
+ """Обычный INFO не является аномалией."""
78
+ log_list = LogList()
79
+ entry = LogEntry(level=LogLevel.INFO, message="Application started successfully")
80
+ assert log_list._is_anomaly(entry) is False
81
+
82
+ def test_normal_debug_is_not_anomaly(self):
83
+ """Обычный DEBUG не является аномалией."""
84
+ log_list = LogList()
85
+ entry = LogEntry(level=LogLevel.DEBUG, message="Processing request payload")
86
+ assert log_list._is_anomaly(entry) is False
87
+
88
+ def test_normal_warning_is_not_anomaly(self):
89
+ """Обычный WARNING без ключевых слов не аномалия."""
90
+ log_list = LogList()
91
+ entry = LogEntry(level=LogLevel.WARNING, message="Configuration file is outdated")
92
+ assert log_list._is_anomaly(entry) is False
93
+
94
+ def test_case_insensitive_detection(self):
95
+ """Проверка без учёта регистра."""
96
+ log_list = LogList()
97
+ entry = LogEntry(level=LogLevel.WARNING, message="TIMEOUT detected in module")
98
+ assert log_list._is_anomaly(entry) is True
99
+
100
+ entry2 = LogEntry(level=LogLevel.WARNING, message="Timeout in module")
101
+ assert log_list._is_anomaly(entry2) is True
102
+
103
+ entry3 = LogEntry(level=LogLevel.WARNING, message="tImeOuT occurred")
104
+ assert log_list._is_anomaly(entry3) is True
105
+
106
+ def test_anomaly_count_in_header(self):
107
+ """Подсчёт аномалий в заголовке."""
108
+ log_list = LogList()
109
+ entries = [
110
+ LogEntry(level=LogLevel.INFO, message="normal"),
111
+ LogEntry(level=LogLevel.ERROR, message="error"),
112
+ LogEntry(level=LogLevel.WARNING, message="timeout occurred"),
113
+ LogEntry(level=LogLevel.DEBUG, message="debug info"),
114
+ ]
115
+ log_list._entries = entries
116
+ # DataTable использует _build_line() вместо _build_lines()
117
+ # Проверяем аномалии через get_filtered_entries
118
+ filtered = log_list._get_filtered_entries()
119
+ anomaly_count = sum(1 for e in filtered if log_list._is_anomaly(e))
120
+ assert anomaly_count == 2
121
+
122
+ def test_custom_anomaly_keywords(self):
123
+ """Проверка что список ключевых слов не пустой."""
124
+ log_list = LogList()
125
+ assert len(log_list._anomaly_keywords) > 0
126
+ assert "timeout" in log_list._anomaly_keywords
127
+ assert "connectionerror" in log_list._anomaly_keywords
@@ -0,0 +1,98 @@
1
+ """Тесты для DetailPanel виджета."""
2
+
3
+ from tracetui.models import LogLevel, LogEntry
4
+ from tracetui.widgets.detail_panel import DetailPanel
5
+
6
+
7
+ class TestDetailPanel:
8
+ """Тесты панели деталей."""
9
+
10
+ def test_initial_display(self):
11
+ """Начальное состояние — подсказка."""
12
+ panel = DetailPanel()
13
+ panel.on_mount()
14
+ assert "Select a log entry" in panel.text
15
+
16
+ def test_update_with_entry(self):
17
+ """Обновление деталями записи."""
18
+ panel = DetailPanel()
19
+ entry = LogEntry(
20
+ level=LogLevel.ERROR,
21
+ message="Something broke",
22
+ timestamp="2024-01-01 12:00:00",
23
+ logger_name="app.main",
24
+ )
25
+ panel.update_detail(entry)
26
+ assert "ERROR" in panel.text
27
+ assert "Something broke" in panel.text
28
+ assert "2024-01-01 12:00:00" in panel.text
29
+
30
+ def test_update_with_traceback(self):
31
+ """Отображение traceback."""
32
+ panel = DetailPanel()
33
+ entry = LogEntry(
34
+ level=LogLevel.ERROR,
35
+ message="Crash",
36
+ traceback="Traceback (most recent call last):\n File 'app.py', line 10",
37
+ )
38
+ panel.update_detail(entry)
39
+ assert "Traceback" in panel.text
40
+ assert "app.py" in panel.text
41
+
42
+ def test_toggle_traceback_visible(self):
43
+ """Traceback виден."""
44
+ panel = DetailPanel()
45
+ panel._show_traceback = True
46
+ assert panel._show_traceback is True
47
+
48
+ def test_toggle_traceback_hidden(self):
49
+ """Traceback скрыт."""
50
+ panel = DetailPanel()
51
+ panel._show_traceback = False
52
+ assert panel._show_traceback is False
53
+
54
+ def test_toggle_traceback_action(self):
55
+ """Переключение через action."""
56
+ panel = DetailPanel()
57
+ initial = panel._show_traceback
58
+ panel.action_toggle_traceback()
59
+ assert panel._show_traceback == (not initial)
60
+
61
+ def test_update_none_entry(self):
62
+ """Обновление None — возврат к подсказке."""
63
+ panel = DetailPanel()
64
+ panel.update_detail(None)
65
+ assert "Select a log entry" in panel.text
66
+
67
+ entry = LogEntry(level=LogLevel.ERROR, message="test")
68
+ panel.update_detail(entry)
69
+ assert "test" in panel.text
70
+
71
+ def test_current_entry_property(self):
72
+ """Свойство current_entry."""
73
+ panel = DetailPanel()
74
+ assert panel.current_entry is None
75
+
76
+ entry = LogEntry(level=LogLevel.INFO, message="test")
77
+ panel.update_detail(entry)
78
+ assert panel.current_entry is entry
79
+
80
+ def test_detail_with_tags(self):
81
+ """Отображение тегов."""
82
+ panel = DetailPanel()
83
+ entry = LogEntry(
84
+ level=LogLevel.ERROR,
85
+ message="error",
86
+ tags=["исследовать", "решено"],
87
+ )
88
+ panel.update_detail(entry)
89
+ assert "исследовать" in panel.text
90
+ assert "решено" in panel.text
91
+
92
+ def test_detail_without_optional_fields(self):
93
+ """Минимальная запись без timestamp/logger."""
94
+ panel = DetailPanel()
95
+ entry = LogEntry(level=LogLevel.INFO, message="simple")
96
+ panel.update_detail(entry)
97
+ assert "INFO" in panel.text
98
+ assert "simple" in panel.text