code-puppy 0.0.153__py3-none-any.whl → 0.0.155__py3-none-any.whl
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.
- code_puppy/agent.py +4 -3
- code_puppy/agents/agent_creator_agent.py +9 -2
- code_puppy/agents/runtime_manager.py +12 -4
- code_puppy/command_line/mcp/install_command.py +50 -1
- code_puppy/command_line/mcp/wizard_utils.py +88 -17
- code_puppy/config.py +8 -2
- code_puppy/main.py +17 -4
- code_puppy/mcp/__init__.py +2 -2
- code_puppy/mcp/config_wizard.py +1 -1
- code_puppy/message_history_processor.py +1 -9
- code_puppy/messaging/spinner/console_spinner.py +1 -1
- code_puppy/model_factory.py +13 -12
- code_puppy/models.json +26 -0
- code_puppy/round_robin_model.py +35 -18
- code_puppy/summarization_agent.py +1 -3
- code_puppy/tools/agent_tools.py +41 -138
- code_puppy/tools/file_operations.py +116 -96
- code_puppy/tui/app.py +1 -1
- {code_puppy-0.0.153.data → code_puppy-0.0.155.data}/data/code_puppy/models.json +26 -0
- {code_puppy-0.0.153.dist-info → code_puppy-0.0.155.dist-info}/METADATA +2 -2
- {code_puppy-0.0.153.dist-info → code_puppy-0.0.155.dist-info}/RECORD +24 -46
- code_puppy/token_utils.py +0 -67
- code_puppy/tools/token_check.py +0 -32
- code_puppy/tui/tests/__init__.py +0 -1
- code_puppy/tui/tests/test_agent_command.py +0 -79
- code_puppy/tui/tests/test_chat_message.py +0 -28
- code_puppy/tui/tests/test_chat_view.py +0 -88
- code_puppy/tui/tests/test_command_history.py +0 -89
- code_puppy/tui/tests/test_copy_button.py +0 -191
- code_puppy/tui/tests/test_custom_widgets.py +0 -27
- code_puppy/tui/tests/test_disclaimer.py +0 -27
- code_puppy/tui/tests/test_enums.py +0 -15
- code_puppy/tui/tests/test_file_browser.py +0 -60
- code_puppy/tui/tests/test_help.py +0 -38
- code_puppy/tui/tests/test_history_file_reader.py +0 -107
- code_puppy/tui/tests/test_input_area.py +0 -33
- code_puppy/tui/tests/test_settings.py +0 -44
- code_puppy/tui/tests/test_sidebar.py +0 -33
- code_puppy/tui/tests/test_sidebar_history.py +0 -153
- code_puppy/tui/tests/test_sidebar_history_navigation.py +0 -132
- code_puppy/tui/tests/test_status_bar.py +0 -54
- code_puppy/tui/tests/test_timestamped_history.py +0 -52
- code_puppy/tui/tests/test_tools.py +0 -82
- {code_puppy-0.0.153.dist-info → code_puppy-0.0.155.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.153.dist-info → code_puppy-0.0.155.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.153.dist-info → code_puppy-0.0.155.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
"""Tests for the FileBrowser component."""
|
|
2
|
-
|
|
3
|
-
from unittest.mock import MagicMock
|
|
4
|
-
|
|
5
|
-
import pytest
|
|
6
|
-
|
|
7
|
-
# Import only Sidebar which exists, and skip FileBrowser
|
|
8
|
-
from code_puppy.tui.components import Sidebar
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
# Use pytest.skip for skipping the FileBrowser tests
|
|
12
|
-
@pytest.mark.skip(reason="FileBrowser component has been removed from the codebase")
|
|
13
|
-
class TestFileBrowser:
|
|
14
|
-
"""Test the FileBrowser component."""
|
|
15
|
-
|
|
16
|
-
def test_file_browser_creation(self):
|
|
17
|
-
"""Test that FileBrowser can be created."""
|
|
18
|
-
# Create a mock instead of the real component
|
|
19
|
-
browser = MagicMock()
|
|
20
|
-
assert browser is not None
|
|
21
|
-
|
|
22
|
-
def test_file_browser_has_directory_tree(self):
|
|
23
|
-
"""Test that FileBrowser contains a DirectoryTree widget."""
|
|
24
|
-
browser = MagicMock()
|
|
25
|
-
browser.compose = MagicMock()
|
|
26
|
-
# This is a basic structure test - in a real app test we'd mount it
|
|
27
|
-
assert hasattr(browser, "compose")
|
|
28
|
-
|
|
29
|
-
def test_file_browser_message_type(self):
|
|
30
|
-
"""Test that FileBrowser.FileSelected message works."""
|
|
31
|
-
|
|
32
|
-
# Create a mock message class
|
|
33
|
-
class MockFileSelected:
|
|
34
|
-
def __init__(self, file_path):
|
|
35
|
-
self.file_path = file_path
|
|
36
|
-
|
|
37
|
-
message = MockFileSelected("/test/path/file.py")
|
|
38
|
-
assert message.file_path == "/test/path/file.py"
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
class TestSidebarTabs:
|
|
42
|
-
"""Test the enhanced Sidebar with tabs."""
|
|
43
|
-
|
|
44
|
-
def test_sidebar_creation(self):
|
|
45
|
-
"""Test that enhanced Sidebar can be created."""
|
|
46
|
-
sidebar = Sidebar()
|
|
47
|
-
assert sidebar is not None
|
|
48
|
-
|
|
49
|
-
def test_sidebar_has_compose_method(self):
|
|
50
|
-
"""Test that Sidebar has the compose method for tab layout."""
|
|
51
|
-
sidebar = Sidebar()
|
|
52
|
-
assert hasattr(sidebar, "compose")
|
|
53
|
-
# Skip checking methods that may have been removed
|
|
54
|
-
# Comment out removed methods:
|
|
55
|
-
# assert hasattr(sidebar, "load_models_list")
|
|
56
|
-
# assert hasattr(sidebar, "on_file_browser_file_selected")
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if __name__ == "__main__":
|
|
60
|
-
pytest.main([__file__])
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import unittest
|
|
2
|
-
|
|
3
|
-
from textual.app import App
|
|
4
|
-
|
|
5
|
-
from code_puppy.tui.screens.help import HelpScreen
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class TestHelpScreen(unittest.TestCase):
|
|
9
|
-
def setUp(self):
|
|
10
|
-
self.screen = HelpScreen()
|
|
11
|
-
|
|
12
|
-
def test_get_help_content(self):
|
|
13
|
-
content = self.screen.get_help_content()
|
|
14
|
-
self.assertIn("Code Puppy TUI", content)
|
|
15
|
-
|
|
16
|
-
def test_compose(self):
|
|
17
|
-
# Create a minimal app context for testing
|
|
18
|
-
class TestApp(App):
|
|
19
|
-
def compose(self):
|
|
20
|
-
yield self.screen
|
|
21
|
-
|
|
22
|
-
app = TestApp()
|
|
23
|
-
self.screen = HelpScreen()
|
|
24
|
-
|
|
25
|
-
# Test that compose returns widgets without error
|
|
26
|
-
try:
|
|
27
|
-
# Use app.run_test() context to provide proper app context
|
|
28
|
-
with app:
|
|
29
|
-
widgets = list(self.screen.compose())
|
|
30
|
-
self.assertGreaterEqual(len(widgets), 1)
|
|
31
|
-
except Exception:
|
|
32
|
-
# If compose still fails, just verify the method exists
|
|
33
|
-
self.assertTrue(hasattr(self.screen, "compose"))
|
|
34
|
-
self.assertTrue(callable(getattr(self.screen, "compose")))
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if __name__ == "__main__":
|
|
38
|
-
unittest.main()
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import tempfile
|
|
3
|
-
import unittest
|
|
4
|
-
|
|
5
|
-
from code_puppy.tui.models.command_history import HistoryFileReader
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class TestHistoryFileReader(unittest.TestCase):
|
|
9
|
-
def setUp(self):
|
|
10
|
-
# Create a temporary file for testing
|
|
11
|
-
self.temp_file = tempfile.NamedTemporaryFile(delete=False)
|
|
12
|
-
self.temp_file_path = self.temp_file.name
|
|
13
|
-
|
|
14
|
-
# Sample content with multiple commands
|
|
15
|
-
sample_content = """
|
|
16
|
-
# 2023-01-01T12:00:00
|
|
17
|
-
First command
|
|
18
|
-
|
|
19
|
-
# 2023-01-01T13:00:00
|
|
20
|
-
Second command
|
|
21
|
-
with multiple lines
|
|
22
|
-
|
|
23
|
-
# 2023-01-01T14:00:00
|
|
24
|
-
Third command
|
|
25
|
-
"""
|
|
26
|
-
# Write sample content to the temporary file
|
|
27
|
-
with open(self.temp_file_path, "w") as f:
|
|
28
|
-
f.write(sample_content)
|
|
29
|
-
|
|
30
|
-
# Initialize reader with the temp file
|
|
31
|
-
self.reader = HistoryFileReader(self.temp_file_path)
|
|
32
|
-
|
|
33
|
-
def tearDown(self):
|
|
34
|
-
# Clean up the temporary file
|
|
35
|
-
if os.path.exists(self.temp_file_path):
|
|
36
|
-
os.unlink(self.temp_file_path)
|
|
37
|
-
|
|
38
|
-
def test_read_history(self):
|
|
39
|
-
# Test reading history entries
|
|
40
|
-
entries = self.reader.read_history()
|
|
41
|
-
|
|
42
|
-
# Check that we have the correct number of entries
|
|
43
|
-
self.assertEqual(len(entries), 3)
|
|
44
|
-
|
|
45
|
-
# Check that entries are in reverse chronological order (newest first)
|
|
46
|
-
self.assertEqual(entries[0]["timestamp"], "2023-01-01T14:00:00")
|
|
47
|
-
self.assertEqual(entries[0]["command"], "Third command")
|
|
48
|
-
|
|
49
|
-
self.assertEqual(entries[1]["timestamp"], "2023-01-01T13:00:00")
|
|
50
|
-
self.assertEqual(entries[1]["command"], "Second command\nwith multiple lines")
|
|
51
|
-
|
|
52
|
-
self.assertEqual(entries[2]["timestamp"], "2023-01-01T12:00:00")
|
|
53
|
-
self.assertEqual(entries[2]["command"], "First command")
|
|
54
|
-
|
|
55
|
-
def test_read_history_with_limit(self):
|
|
56
|
-
# Test reading history with a limit
|
|
57
|
-
entries = self.reader.read_history(max_entries=2)
|
|
58
|
-
|
|
59
|
-
# Check that we only get the specified number of entries
|
|
60
|
-
self.assertEqual(len(entries), 2)
|
|
61
|
-
|
|
62
|
-
# Check that we get the most recent entries
|
|
63
|
-
self.assertEqual(entries[0]["timestamp"], "2023-01-01T14:00:00")
|
|
64
|
-
self.assertEqual(entries[1]["timestamp"], "2023-01-01T13:00:00")
|
|
65
|
-
|
|
66
|
-
def test_read_history_empty_file(self):
|
|
67
|
-
# Create an empty file
|
|
68
|
-
empty_file = tempfile.NamedTemporaryFile(delete=False)
|
|
69
|
-
empty_file_path = empty_file.name
|
|
70
|
-
empty_file.close()
|
|
71
|
-
|
|
72
|
-
try:
|
|
73
|
-
# Create reader with empty file
|
|
74
|
-
empty_reader = HistoryFileReader(empty_file_path)
|
|
75
|
-
|
|
76
|
-
# Should return empty list
|
|
77
|
-
entries = empty_reader.read_history()
|
|
78
|
-
self.assertEqual(len(entries), 0)
|
|
79
|
-
finally:
|
|
80
|
-
# Clean up
|
|
81
|
-
if os.path.exists(empty_file_path):
|
|
82
|
-
os.unlink(empty_file_path)
|
|
83
|
-
|
|
84
|
-
def test_read_history_nonexistent_file(self):
|
|
85
|
-
# Create reader with non-existent file
|
|
86
|
-
nonexistent_reader = HistoryFileReader("/nonexistent/file/path")
|
|
87
|
-
|
|
88
|
-
# Should return empty list, not raise an exception
|
|
89
|
-
entries = nonexistent_reader.read_history()
|
|
90
|
-
self.assertEqual(len(entries), 0)
|
|
91
|
-
|
|
92
|
-
def test_format_timestamp(self):
|
|
93
|
-
# Test default formatting
|
|
94
|
-
formatted = self.reader.format_timestamp("2023-01-01T12:34:56")
|
|
95
|
-
self.assertEqual(formatted, "12:34:56")
|
|
96
|
-
|
|
97
|
-
# Test custom format
|
|
98
|
-
formatted = self.reader.format_timestamp("2023-01-01T12:34:56", "%H:%M")
|
|
99
|
-
self.assertEqual(formatted, "12:34")
|
|
100
|
-
|
|
101
|
-
# Test invalid timestamp
|
|
102
|
-
formatted = self.reader.format_timestamp("invalid")
|
|
103
|
-
self.assertEqual(formatted, "invalid")
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if __name__ == "__main__":
|
|
107
|
-
unittest.main()
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import unittest
|
|
2
|
-
|
|
3
|
-
from textual.app import App
|
|
4
|
-
|
|
5
|
-
from code_puppy.tui.components.input_area import InputArea
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class TestInputArea(unittest.TestCase):
|
|
9
|
-
def setUp(self):
|
|
10
|
-
self.input_area = InputArea()
|
|
11
|
-
|
|
12
|
-
def test_compose(self):
|
|
13
|
-
# Create a minimal app context for testing
|
|
14
|
-
class TestApp(App):
|
|
15
|
-
def compose(self):
|
|
16
|
-
yield self.input_area
|
|
17
|
-
|
|
18
|
-
app = TestApp()
|
|
19
|
-
self.input_area = InputArea()
|
|
20
|
-
|
|
21
|
-
# Test that compose returns widgets without error
|
|
22
|
-
try:
|
|
23
|
-
with app:
|
|
24
|
-
widgets = list(self.input_area.compose())
|
|
25
|
-
self.assertGreaterEqual(len(widgets), 3)
|
|
26
|
-
except Exception:
|
|
27
|
-
# If compose still fails, just verify the method exists
|
|
28
|
-
self.assertTrue(hasattr(self.input_area, "compose"))
|
|
29
|
-
self.assertTrue(callable(getattr(self.input_area, "compose")))
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if __name__ == "__main__":
|
|
33
|
-
unittest.main()
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import unittest
|
|
2
|
-
|
|
3
|
-
from textual.app import App
|
|
4
|
-
|
|
5
|
-
from code_puppy.tui.screens.settings import SettingsScreen
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class TestSettingsScreen(unittest.TestCase):
|
|
9
|
-
def setUp(self):
|
|
10
|
-
self.screen = SettingsScreen()
|
|
11
|
-
|
|
12
|
-
def test_compose(self):
|
|
13
|
-
# Create a minimal app context for testing
|
|
14
|
-
class TestApp(App):
|
|
15
|
-
def compose(self):
|
|
16
|
-
yield self.screen
|
|
17
|
-
|
|
18
|
-
app = TestApp()
|
|
19
|
-
self.screen = SettingsScreen()
|
|
20
|
-
|
|
21
|
-
# Test that compose returns widgets without error
|
|
22
|
-
try:
|
|
23
|
-
with app:
|
|
24
|
-
widgets = list(self.screen.compose())
|
|
25
|
-
self.assertGreaterEqual(len(widgets), 1)
|
|
26
|
-
except Exception:
|
|
27
|
-
# If compose still fails, just verify the method exists
|
|
28
|
-
self.assertTrue(hasattr(self.screen, "compose"))
|
|
29
|
-
self.assertTrue(callable(getattr(self.screen, "compose")))
|
|
30
|
-
|
|
31
|
-
def test_load_model_options_fallback(self):
|
|
32
|
-
class DummySelect:
|
|
33
|
-
def set_options(self, options):
|
|
34
|
-
self.options = options
|
|
35
|
-
|
|
36
|
-
select = DummySelect()
|
|
37
|
-
# Should fallback to default if file not found
|
|
38
|
-
self.screen.load_model_options(select)
|
|
39
|
-
self.assertTrue(hasattr(select, "options"))
|
|
40
|
-
self.assertGreaterEqual(len(select.options), 1)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if __name__ == "__main__":
|
|
44
|
-
unittest.main()
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import unittest
|
|
2
|
-
|
|
3
|
-
from textual.app import App
|
|
4
|
-
|
|
5
|
-
from code_puppy.tui.components.sidebar import Sidebar
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class TestSidebar(unittest.TestCase):
|
|
9
|
-
def setUp(self):
|
|
10
|
-
self.sidebar = Sidebar()
|
|
11
|
-
|
|
12
|
-
def test_compose(self):
|
|
13
|
-
# Create a minimal app context for testing
|
|
14
|
-
class TestApp(App):
|
|
15
|
-
def compose(self):
|
|
16
|
-
yield self.sidebar
|
|
17
|
-
|
|
18
|
-
app = TestApp()
|
|
19
|
-
self.sidebar = Sidebar()
|
|
20
|
-
|
|
21
|
-
# Test that compose returns widgets without error
|
|
22
|
-
try:
|
|
23
|
-
with app:
|
|
24
|
-
widgets = list(self.sidebar.compose())
|
|
25
|
-
self.assertGreaterEqual(len(widgets), 1)
|
|
26
|
-
except Exception:
|
|
27
|
-
# If compose still fails, just verify the method exists
|
|
28
|
-
self.assertTrue(hasattr(self.sidebar, "compose"))
|
|
29
|
-
self.assertTrue(callable(getattr(self.sidebar, "compose")))
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if __name__ == "__main__":
|
|
33
|
-
unittest.main()
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
import unittest
|
|
2
|
-
from unittest.mock import MagicMock, patch
|
|
3
|
-
|
|
4
|
-
from textual.widgets import ListItem, ListView
|
|
5
|
-
|
|
6
|
-
from code_puppy.tui.components.command_history_modal import CommandHistoryModal
|
|
7
|
-
from code_puppy.tui.components.sidebar import Sidebar
|
|
8
|
-
from code_puppy.tui.models.command_history import HistoryFileReader
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class TestSidebarHistory(unittest.TestCase):
|
|
12
|
-
def setUp(self):
|
|
13
|
-
# Create a sidebar
|
|
14
|
-
self.sidebar = Sidebar()
|
|
15
|
-
|
|
16
|
-
# Mock history_list
|
|
17
|
-
self.mock_history_list = MagicMock(spec=ListView)
|
|
18
|
-
self.mock_history_list.children = []
|
|
19
|
-
self.sidebar.query_one = MagicMock(return_value=self.mock_history_list)
|
|
20
|
-
|
|
21
|
-
# Mock the app's push_screen method without trying to set the app property
|
|
22
|
-
self.mock_push_screen = MagicMock()
|
|
23
|
-
|
|
24
|
-
@patch.object(HistoryFileReader, "read_history")
|
|
25
|
-
def test_load_command_history(self, mock_read_history):
|
|
26
|
-
# Mock the history entries
|
|
27
|
-
mock_entries = [
|
|
28
|
-
{"timestamp": "2023-01-01T12:34:56", "command": "First command"},
|
|
29
|
-
{"timestamp": "2023-01-01T13:45:00", "command": "Second command"},
|
|
30
|
-
]
|
|
31
|
-
mock_read_history.return_value = mock_entries
|
|
32
|
-
|
|
33
|
-
# Call the method
|
|
34
|
-
self.sidebar.load_command_history()
|
|
35
|
-
|
|
36
|
-
# Check that ListView.append was called for each entry
|
|
37
|
-
self.assertEqual(self.mock_history_list.append.call_count, 2)
|
|
38
|
-
|
|
39
|
-
# Check that ListView.clear was called
|
|
40
|
-
self.mock_history_list.clear.assert_called_once()
|
|
41
|
-
|
|
42
|
-
@patch.object(HistoryFileReader, "read_history")
|
|
43
|
-
def test_load_command_history_empty(self, mock_read_history):
|
|
44
|
-
# Mock empty history
|
|
45
|
-
mock_read_history.return_value = []
|
|
46
|
-
|
|
47
|
-
# Call the method
|
|
48
|
-
self.sidebar.load_command_history()
|
|
49
|
-
|
|
50
|
-
# Check that an empty message was added
|
|
51
|
-
self.mock_history_list.append.assert_called_once()
|
|
52
|
-
# Just verify append was called, don't try to access complex children structure
|
|
53
|
-
self.assertTrue(self.mock_history_list.append.called)
|
|
54
|
-
|
|
55
|
-
@patch.object(HistoryFileReader, "read_history")
|
|
56
|
-
def test_load_command_history_exception(self, mock_read_history):
|
|
57
|
-
# Force an exception
|
|
58
|
-
mock_read_history.side_effect = Exception("Test error")
|
|
59
|
-
|
|
60
|
-
# Call the method
|
|
61
|
-
self.sidebar.load_command_history()
|
|
62
|
-
|
|
63
|
-
# Check that an error message was added
|
|
64
|
-
self.mock_history_list.append.assert_called_once()
|
|
65
|
-
# Just verify append was called, don't try to access complex children structure
|
|
66
|
-
self.assertTrue(self.mock_history_list.append.called)
|
|
67
|
-
|
|
68
|
-
@patch.object(HistoryFileReader, "read_history")
|
|
69
|
-
def test_load_command_history_filters_cli_commands(self, mock_read_history):
|
|
70
|
-
# Mock history with CLI commands mixed with regular commands
|
|
71
|
-
mock_read_history.return_value = [
|
|
72
|
-
{
|
|
73
|
-
"timestamp": "2024-01-01T10:00:00Z",
|
|
74
|
-
"command": "How do I create a function?",
|
|
75
|
-
},
|
|
76
|
-
{"timestamp": "2024-01-01T10:01:00Z", "command": "/help"},
|
|
77
|
-
{"timestamp": "2024-01-01T10:02:00Z", "command": "Write a Python script"},
|
|
78
|
-
{"timestamp": "2024-01-01T10:04:00Z", "command": "/exit"},
|
|
79
|
-
{"timestamp": "2024-01-01T10:05:00Z", "command": "Debug this error"},
|
|
80
|
-
{"timestamp": "2024-01-01T10:06:00Z", "command": "/m gpt-4"},
|
|
81
|
-
{"timestamp": "2024-01-01T10:07:00Z", "command": "Explain this code"},
|
|
82
|
-
]
|
|
83
|
-
|
|
84
|
-
# Call the method
|
|
85
|
-
self.sidebar.load_command_history()
|
|
86
|
-
|
|
87
|
-
# Verify that CLI commands were filtered out
|
|
88
|
-
# Should have 4 non-CLI commands: "How do I create a function?", "Write a Python script", "Debug this error", "Explain this code"
|
|
89
|
-
self.assertEqual(len(self.sidebar.history_entries), 4)
|
|
90
|
-
|
|
91
|
-
# Verify the filtered commands are the correct ones
|
|
92
|
-
commands = [entry["command"] for entry in self.sidebar.history_entries]
|
|
93
|
-
expected_commands = [
|
|
94
|
-
"How do I create a function?",
|
|
95
|
-
"Write a Python script",
|
|
96
|
-
"Debug this error",
|
|
97
|
-
"Explain this code",
|
|
98
|
-
]
|
|
99
|
-
self.assertEqual(commands, expected_commands)
|
|
100
|
-
|
|
101
|
-
# Verify CLI commands are not in the filtered list
|
|
102
|
-
for entry in self.sidebar.history_entries:
|
|
103
|
-
command = entry["command"]
|
|
104
|
-
self.assertFalse(
|
|
105
|
-
any(
|
|
106
|
-
command.startswith(cli_cmd)
|
|
107
|
-
for cli_cmd in {
|
|
108
|
-
"/help",
|
|
109
|
-
"/exit",
|
|
110
|
-
"/m",
|
|
111
|
-
"/motd",
|
|
112
|
-
"/show",
|
|
113
|
-
"/set",
|
|
114
|
-
"/tools",
|
|
115
|
-
}
|
|
116
|
-
)
|
|
117
|
-
)
|
|
118
|
-
|
|
119
|
-
@patch(
|
|
120
|
-
"code_puppy.tui.components.sidebar.Sidebar.app",
|
|
121
|
-
new_callable=lambda: MagicMock(),
|
|
122
|
-
)
|
|
123
|
-
def test_on_key_enter(self, mock_app_property):
|
|
124
|
-
# Create a mock highlighted child with a command entry
|
|
125
|
-
mock_item = MagicMock(spec=ListItem)
|
|
126
|
-
mock_item.command_entry = {
|
|
127
|
-
"timestamp": "2023-01-01T12:34:56",
|
|
128
|
-
"command": "Test command",
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
self.mock_history_list.highlighted_child = mock_item
|
|
132
|
-
self.mock_history_list.has_focus = True
|
|
133
|
-
self.mock_history_list.index = 0
|
|
134
|
-
|
|
135
|
-
# Create a mock Key event
|
|
136
|
-
mock_event = MagicMock()
|
|
137
|
-
mock_event.key = "enter"
|
|
138
|
-
|
|
139
|
-
# Call the method
|
|
140
|
-
self.sidebar.on_key(mock_event)
|
|
141
|
-
|
|
142
|
-
# Check that push_screen was called with CommandHistoryModal
|
|
143
|
-
mock_app_property.push_screen.assert_called_once()
|
|
144
|
-
args, kwargs = mock_app_property.push_screen.call_args
|
|
145
|
-
self.assertIsInstance(args[0], CommandHistoryModal)
|
|
146
|
-
|
|
147
|
-
# Check that event propagation was stopped
|
|
148
|
-
mock_event.stop.assert_called_once()
|
|
149
|
-
mock_event.prevent_default.assert_called_once()
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if __name__ == "__main__":
|
|
153
|
-
unittest.main()
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Tests for the history navigation in the sidebar component.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import pytest
|
|
6
|
-
from textual.app import App
|
|
7
|
-
|
|
8
|
-
from code_puppy.tui.components.command_history_modal import CommandHistoryModal
|
|
9
|
-
from code_puppy.tui.components.sidebar import Sidebar
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class TestSidebarHistoryNavigation:
|
|
13
|
-
"""Tests for the history navigation functionality in the sidebar."""
|
|
14
|
-
|
|
15
|
-
@pytest.fixture
|
|
16
|
-
def sidebar(self):
|
|
17
|
-
"""Create a sidebar instance for testing."""
|
|
18
|
-
sidebar = Sidebar()
|
|
19
|
-
return sidebar
|
|
20
|
-
|
|
21
|
-
async def test_navigation_index_tracking(self, sidebar):
|
|
22
|
-
"""Test that the index tracking works correctly for navigation."""
|
|
23
|
-
# Setup test data
|
|
24
|
-
sidebar.history_entries = [
|
|
25
|
-
{"command": "command1", "timestamp": "2023-01-01T10:00:00Z"},
|
|
26
|
-
{"command": "command2", "timestamp": "2023-01-01T11:00:00Z"},
|
|
27
|
-
{"command": "command3", "timestamp": "2023-01-01T12:00:00Z"},
|
|
28
|
-
]
|
|
29
|
-
sidebar.current_history_index = 0
|
|
30
|
-
|
|
31
|
-
# Test navigation to next command
|
|
32
|
-
assert sidebar.navigate_to_next_command() is True
|
|
33
|
-
assert sidebar.current_history_index == 1
|
|
34
|
-
|
|
35
|
-
# Test navigation to next command again
|
|
36
|
-
assert sidebar.navigate_to_next_command() is True
|
|
37
|
-
assert sidebar.current_history_index == 2
|
|
38
|
-
|
|
39
|
-
# Test navigation at the end of the list
|
|
40
|
-
assert sidebar.navigate_to_next_command() is False
|
|
41
|
-
assert sidebar.current_history_index == 2 # Index shouldn't change
|
|
42
|
-
|
|
43
|
-
# Test navigation to previous command
|
|
44
|
-
assert sidebar.navigate_to_previous_command() is True
|
|
45
|
-
assert sidebar.current_history_index == 1
|
|
46
|
-
|
|
47
|
-
# Test navigation to previous command again
|
|
48
|
-
assert sidebar.navigate_to_previous_command() is True
|
|
49
|
-
assert sidebar.current_history_index == 0
|
|
50
|
-
|
|
51
|
-
# Test navigation at the beginning of the list
|
|
52
|
-
assert sidebar.navigate_to_previous_command() is False
|
|
53
|
-
assert sidebar.current_history_index == 0 # Index shouldn't change
|
|
54
|
-
|
|
55
|
-
async def test_get_current_command_entry(self, sidebar):
|
|
56
|
-
"""Test that the current command entry is retrieved correctly."""
|
|
57
|
-
# Setup test data
|
|
58
|
-
sidebar.history_entries = [
|
|
59
|
-
{"command": "command1", "timestamp": "2023-01-01T10:00:00Z"},
|
|
60
|
-
{"command": "command2", "timestamp": "2023-01-01T11:00:00Z"},
|
|
61
|
-
]
|
|
62
|
-
|
|
63
|
-
# Test getting entry at index 0
|
|
64
|
-
sidebar.current_history_index = 0
|
|
65
|
-
entry = sidebar.get_current_command_entry()
|
|
66
|
-
assert entry["command"] == "command1"
|
|
67
|
-
assert entry["timestamp"] == "2023-01-01T10:00:00Z"
|
|
68
|
-
|
|
69
|
-
# Test getting entry at index 1
|
|
70
|
-
sidebar.current_history_index = 1
|
|
71
|
-
entry = sidebar.get_current_command_entry()
|
|
72
|
-
assert entry["command"] == "command2"
|
|
73
|
-
assert entry["timestamp"] == "2023-01-01T11:00:00Z"
|
|
74
|
-
|
|
75
|
-
# Test getting entry with invalid index
|
|
76
|
-
sidebar.current_history_index = 99
|
|
77
|
-
entry = sidebar.get_current_command_entry()
|
|
78
|
-
assert entry == {"command": "", "timestamp": ""}
|
|
79
|
-
|
|
80
|
-
# Test getting entry with empty history entries
|
|
81
|
-
sidebar.history_entries = []
|
|
82
|
-
sidebar.current_history_index = 0
|
|
83
|
-
entry = sidebar.get_current_command_entry()
|
|
84
|
-
assert entry == {"command": "", "timestamp": ""}
|
|
85
|
-
|
|
86
|
-
class TestApp(App):
|
|
87
|
-
"""Test app for simulating modal and sidebar interaction."""
|
|
88
|
-
|
|
89
|
-
def compose(self):
|
|
90
|
-
"""Create the app layout."""
|
|
91
|
-
self.sidebar = Sidebar()
|
|
92
|
-
yield self.sidebar
|
|
93
|
-
|
|
94
|
-
async def test_modal_navigation_integration(self, monkeypatch):
|
|
95
|
-
"""Test that the modal uses the sidebar's navigation methods."""
|
|
96
|
-
app = self.TestApp()
|
|
97
|
-
async with app.run_test() as pilot:
|
|
98
|
-
# Setup test data in sidebar
|
|
99
|
-
app.sidebar.history_entries = [
|
|
100
|
-
{"command": "command1", "timestamp": "2023-01-01T10:00:00Z"},
|
|
101
|
-
{"command": "command2", "timestamp": "2023-01-01T11:00:00Z"},
|
|
102
|
-
{"command": "command3", "timestamp": "2023-01-01T12:00:00Z"},
|
|
103
|
-
]
|
|
104
|
-
app.sidebar.current_history_index = 0
|
|
105
|
-
|
|
106
|
-
# Create and mount the modal
|
|
107
|
-
modal = CommandHistoryModal()
|
|
108
|
-
modal.sidebar = app.sidebar
|
|
109
|
-
app.push_screen(modal)
|
|
110
|
-
await pilot.pause()
|
|
111
|
-
|
|
112
|
-
# Test initial state
|
|
113
|
-
assert modal.command == "command1"
|
|
114
|
-
assert modal.timestamp == "2023-01-01T10:00:00Z"
|
|
115
|
-
|
|
116
|
-
# Test navigation down
|
|
117
|
-
await pilot.press("down")
|
|
118
|
-
assert app.sidebar.current_history_index == 1
|
|
119
|
-
assert modal.command == "command2"
|
|
120
|
-
assert modal.timestamp == "2023-01-01T11:00:00Z"
|
|
121
|
-
|
|
122
|
-
# Test navigation down again
|
|
123
|
-
await pilot.press("down")
|
|
124
|
-
assert app.sidebar.current_history_index == 2
|
|
125
|
-
assert modal.command == "command3"
|
|
126
|
-
assert modal.timestamp == "2023-01-01T12:00:00Z"
|
|
127
|
-
|
|
128
|
-
# Test navigation up
|
|
129
|
-
await pilot.press("up")
|
|
130
|
-
assert app.sidebar.current_history_index == 1
|
|
131
|
-
assert modal.command == "command2"
|
|
132
|
-
assert modal.timestamp == "2023-01-01T11:00:00Z"
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import unittest
|
|
2
|
-
from unittest.mock import MagicMock, patch
|
|
3
|
-
|
|
4
|
-
from code_puppy.tui.components.status_bar import StatusBar
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class TestStatusBar(unittest.TestCase):
|
|
8
|
-
def setUp(self):
|
|
9
|
-
self.status_bar = StatusBar()
|
|
10
|
-
|
|
11
|
-
def test_compose(self):
|
|
12
|
-
widgets = list(self.status_bar.compose())
|
|
13
|
-
self.assertGreaterEqual(len(widgets), 1)
|
|
14
|
-
|
|
15
|
-
@patch(
|
|
16
|
-
"code_puppy.tui.components.status_bar.StatusBar.app",
|
|
17
|
-
new_callable=lambda: MagicMock(),
|
|
18
|
-
)
|
|
19
|
-
def test_update_status(self, mock_app_property):
|
|
20
|
-
# Mock the query_one method to avoid DOM dependency
|
|
21
|
-
mock_status_widget = MagicMock()
|
|
22
|
-
self.status_bar.query_one = MagicMock(return_value=mock_status_widget)
|
|
23
|
-
|
|
24
|
-
# Mock the app.size to avoid app dependency
|
|
25
|
-
mock_app_property.size.width = 80
|
|
26
|
-
|
|
27
|
-
# Should not raise
|
|
28
|
-
self.status_bar.update_status()
|
|
29
|
-
|
|
30
|
-
# Verify that update was called on the status widget (may be called multiple times)
|
|
31
|
-
self.assertTrue(mock_status_widget.update.called)
|
|
32
|
-
|
|
33
|
-
@patch(
|
|
34
|
-
"code_puppy.tui.components.status_bar.StatusBar.app",
|
|
35
|
-
new_callable=lambda: MagicMock(),
|
|
36
|
-
)
|
|
37
|
-
def test_watchers(self, mock_app_property):
|
|
38
|
-
# Mock the query_one method to avoid DOM dependency
|
|
39
|
-
mock_status_widget = MagicMock()
|
|
40
|
-
self.status_bar.query_one = MagicMock(return_value=mock_status_widget)
|
|
41
|
-
|
|
42
|
-
# Mock the app.size to avoid app dependency
|
|
43
|
-
mock_app_property.size.width = 80
|
|
44
|
-
|
|
45
|
-
# Should call update_status without error
|
|
46
|
-
self.status_bar.watch_current_model()
|
|
47
|
-
self.status_bar.watch_puppy_name()
|
|
48
|
-
self.status_bar.watch_connection_status()
|
|
49
|
-
self.status_bar.watch_agent_status()
|
|
50
|
-
self.status_bar.watch_progress_visible()
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if __name__ == "__main__":
|
|
54
|
-
unittest.main()
|