cct-cli 0.7.9.0__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.
Files changed (325) hide show
  1. calc_terminal/__init__.py +14 -0
  2. calc_terminal/__main__.py +14 -0
  3. calc_terminal/activity.py +1334 -0
  4. calc_terminal/agent.py +3387 -0
  5. calc_terminal/agent_runtime.py +519 -0
  6. calc_terminal/ai_context.py +447 -0
  7. calc_terminal/ai_modes.py +752 -0
  8. calc_terminal/ai_personalization.py +286 -0
  9. calc_terminal/ai_preview_feedback.py +213 -0
  10. calc_terminal/aicore.py +2572 -0
  11. calc_terminal/anim.py +367 -0
  12. calc_terminal/app.py +3685 -0
  13. calc_terminal/art.py +639 -0
  14. calc_terminal/atomsim.py +368 -0
  15. calc_terminal/attachments.py +743 -0
  16. calc_terminal/benchmark_system.py +414 -0
  17. calc_terminal/browser/__init__.py +36 -0
  18. calc_terminal/browser/browser_state.py +346 -0
  19. calc_terminal/browser/devserver.py +176 -0
  20. calc_terminal/browser/engine.py +494 -0
  21. calc_terminal/browser/navigation.py +84 -0
  22. calc_terminal/browser/preview.py +429 -0
  23. calc_terminal/browser/preview_entry.py +95 -0
  24. calc_terminal/browser/project_detector.py +144 -0
  25. calc_terminal/browser/server.py +449 -0
  26. calc_terminal/browser/state.py +75 -0
  27. calc_terminal/browser/watcher.py +99 -0
  28. calc_terminal/browser_gui/__init__.py +1 -0
  29. calc_terminal/browser_gui/__main__.py +3 -0
  30. calc_terminal/browser_gui/launcher.py +173 -0
  31. calc_terminal/browser_gui/playwright_browser.py +117 -0
  32. calc_terminal/browser_gui/qt_browser.py +1501 -0
  33. calc_terminal/browser_gui/webview_browser.py +57 -0
  34. calc_terminal/capabilities/__init__.py +35 -0
  35. calc_terminal/capabilities/adapters/__init__.py +33 -0
  36. calc_terminal/capabilities/adapters/bioinformatics.py +204 -0
  37. calc_terminal/capabilities/adapters/browser_adapter.py +205 -0
  38. calc_terminal/capabilities/adapters/filesystem.py +206 -0
  39. calc_terminal/capabilities/adapters/git_adapter.py +202 -0
  40. calc_terminal/capabilities/adapters/jupyter_adapter.py +138 -0
  41. calc_terminal/capabilities/adapters/ml_frameworks.py +158 -0
  42. calc_terminal/capabilities/adapters/platforms.py +200 -0
  43. calc_terminal/capabilities/adapters/python_exec.py +93 -0
  44. calc_terminal/capabilities/adapters/quantum_adapter.py +150 -0
  45. calc_terminal/capabilities/adapters/scientific_comp.py +123 -0
  46. calc_terminal/capabilities/adapters/structural_bio.py +161 -0
  47. calc_terminal/capabilities/adapters/terminal.py +99 -0
  48. calc_terminal/capabilities/bus.py +178 -0
  49. calc_terminal/capabilities/discovery.py +207 -0
  50. calc_terminal/capabilities/schema.py +221 -0
  51. calc_terminal/cat.ico +0 -0
  52. calc_terminal/cat_browser.py +2018 -0
  53. calc_terminal/chat_store.py +703 -0
  54. calc_terminal/cli.py +1178 -0
  55. calc_terminal/code_editor.py +640 -0
  56. calc_terminal/collaboration.py +723 -0
  57. calc_terminal/commands_data.py +139 -0
  58. calc_terminal/compatibility_engine.py +352 -0
  59. calc_terminal/compute/__init__.py +31 -0
  60. calc_terminal/compute/fabric.py +350 -0
  61. calc_terminal/config.py +227 -0
  62. calc_terminal/core/__init__.py +41 -0
  63. calc_terminal/core/checkpoint.py +156 -0
  64. calc_terminal/core/input/__init__.py +45 -0
  65. calc_terminal/core/mode_registry.py +300 -0
  66. calc_terminal/core/project_graph.py +172 -0
  67. calc_terminal/core/recovery.py +129 -0
  68. calc_terminal/core/security_layer.py +112 -0
  69. calc_terminal/core/task_graph.py +202 -0
  70. calc_terminal/core/unified_runtime.py +184 -0
  71. calc_terminal/core/verification.py +257 -0
  72. calc_terminal/customization.py +1566 -0
  73. calc_terminal/derivations.py +153 -0
  74. calc_terminal/device_control.py +263 -0
  75. calc_terminal/diagnostics/__init__.py +27 -0
  76. calc_terminal/diagnostics/doctor_engine.py +382 -0
  77. calc_terminal/diagnostics/self_test.py +247 -0
  78. calc_terminal/doctor.py +519 -0
  79. calc_terminal/easter_eggs.py +274 -0
  80. calc_terminal/editor/__init__.py +1 -0
  81. calc_terminal/editor/actions.py +263 -0
  82. calc_terminal/editor/commands.py +160 -0
  83. calc_terminal/editor/shortcuts.py +226 -0
  84. calc_terminal/engine.py +259 -0
  85. calc_terminal/errors.py +120 -0
  86. calc_terminal/event_stream.py +146 -0
  87. calc_terminal/eventbus.py +133 -0
  88. calc_terminal/extensions.py +733 -0
  89. calc_terminal/fallback_cli.py +1321 -0
  90. calc_terminal/first_run.py +265 -0
  91. calc_terminal/fomoji_auth.py +1043 -0
  92. calc_terminal/formulas.py +82 -0
  93. calc_terminal/fs_cache.py +121 -0
  94. calc_terminal/fs_watcher.py +277 -0
  95. calc_terminal/game.py +193 -0
  96. calc_terminal/gen1.py +5 -0
  97. calc_terminal/generators.py +245 -0
  98. calc_terminal/gestures/__init__.py +42 -0
  99. calc_terminal/gestures/bindings.py +175 -0
  100. calc_terminal/gestures/manager.py +477 -0
  101. calc_terminal/goodbye.py +363 -0
  102. calc_terminal/gpu3d.py +290 -0
  103. calc_terminal/graphs.py +358 -0
  104. calc_terminal/hardware_analyzer.py +440 -0
  105. calc_terminal/host/__init__.py +30 -0
  106. calc_terminal/host/browser_manager.py +187 -0
  107. calc_terminal/host/desktop.py +1386 -0
  108. calc_terminal/host/launcher.py +395 -0
  109. calc_terminal/host/terminal.py +279 -0
  110. calc_terminal/identity.py +216 -0
  111. calc_terminal/input/__init__.py +54 -0
  112. calc_terminal/input/capabilities.py +258 -0
  113. calc_terminal/input/focus.py +87 -0
  114. calc_terminal/input/gestures.py +64 -0
  115. calc_terminal/input/pointer.py +114 -0
  116. calc_terminal/input/touch.py +345 -0
  117. calc_terminal/keys.py +84 -0
  118. calc_terminal/live_automation.py +165 -0
  119. calc_terminal/mathtext.py +433 -0
  120. calc_terminal/mcp.py +386 -0
  121. calc_terminal/memory.py +337 -0
  122. calc_terminal/memory_v2.py +479 -0
  123. calc_terminal/metrics.py +333 -0
  124. calc_terminal/mode_detection.py +146 -0
  125. calc_terminal/model.py +2431 -0
  126. calc_terminal/model_router.py +665 -0
  127. calc_terminal/models/__init__.py +0 -0
  128. calc_terminal/models/active_state.py +187 -0
  129. calc_terminal/models/dynamic_registry.py +584 -0
  130. calc_terminal/models/manager.py +781 -0
  131. calc_terminal/models/model_metadata.json +3526 -0
  132. calc_terminal/models/profiles.py +194 -0
  133. calc_terminal/models/registry.py +265 -0
  134. calc_terminal/models/schema.py +197 -0
  135. calc_terminal/models/validator.py +287 -0
  136. calc_terminal/models/verification_engine.py +368 -0
  137. calc_terminal/native_picker.py +215 -0
  138. calc_terminal/ollama_catalog.py +279 -0
  139. calc_terminal/ollama_download.py +233 -0
  140. calc_terminal/orchestrator.py +304 -0
  141. calc_terminal/package_research.py +322 -0
  142. calc_terminal/packages.py +1024 -0
  143. calc_terminal/pc_specs.py +116 -0
  144. calc_terminal/permissions.py +334 -0
  145. calc_terminal/pet.py +106 -0
  146. calc_terminal/pipeline.py +505 -0
  147. calc_terminal/platform/__init__.py +491 -0
  148. calc_terminal/platform/desktop.py +491 -0
  149. calc_terminal/platform/web.py +781 -0
  150. calc_terminal/preview/__init__.py +1 -0
  151. calc_terminal/preview/dev_server.py +303 -0
  152. calc_terminal/preview/diagnostics.py +131 -0
  153. calc_terminal/preview/live_reload.py +66 -0
  154. calc_terminal/preview/manager.py +129 -0
  155. calc_terminal/project_stats.py +209 -0
  156. calc_terminal/projects.py +328 -0
  157. calc_terminal/providers/__init__.py +0 -0
  158. calc_terminal/providers/adapters/__init__.py +80 -0
  159. calc_terminal/providers/adapters/anthropic_adapter.py +127 -0
  160. calc_terminal/providers/adapters/base.py +106 -0
  161. calc_terminal/providers/adapters/chinese_adapters.py +420 -0
  162. calc_terminal/providers/adapters/gemini_adapter.py +101 -0
  163. calc_terminal/providers/adapters/ollama_adapter.py +83 -0
  164. calc_terminal/providers/adapters/openai_adapter.py +159 -0
  165. calc_terminal/providers/adapters/other_adapters.py +246 -0
  166. calc_terminal/providers/anthropic_provider.py +172 -0
  167. calc_terminal/providers/auto_update.py +416 -0
  168. calc_terminal/providers/base_provider.py +105 -0
  169. calc_terminal/providers/discovery_manager.py +207 -0
  170. calc_terminal/providers/gemini_provider.py +178 -0
  171. calc_terminal/providers/lifecycle.py +767 -0
  172. calc_terminal/providers/ollama_adapter.py +707 -0
  173. calc_terminal/providers/openai_provider.py +254 -0
  174. calc_terminal/providers/provider_manager.py +1827 -0
  175. calc_terminal/providers/providers.json +4075 -0
  176. calc_terminal/reactionsim.py +279 -0
  177. calc_terminal/registry.py +337 -0
  178. calc_terminal/report.py +162 -0
  179. calc_terminal/research/__init__.py +45 -0
  180. calc_terminal/research/artifact_intel.py +126 -0
  181. calc_terminal/research/data_lineage.py +123 -0
  182. calc_terminal/research/experiment_ledger.py +303 -0
  183. calc_terminal/research/reproducibility.py +131 -0
  184. calc_terminal/resilience/__init__.py +47 -0
  185. calc_terminal/resilience/agent_state.py +121 -0
  186. calc_terminal/resilience/capability_matcher.py +174 -0
  187. calc_terminal/resilience/circuit_breaker.py +158 -0
  188. calc_terminal/resilience/failover_engine.py +230 -0
  189. calc_terminal/resilience/health_monitor.py +192 -0
  190. calc_terminal/resilience/ollama_adapter.py +125 -0
  191. calc_terminal/resilience/orchestrator.py +312 -0
  192. calc_terminal/resilience/types.py +134 -0
  193. calc_terminal/sandbox.py +98 -0
  194. calc_terminal/scires.py +558 -0
  195. calc_terminal/security_scanner.py +126 -0
  196. calc_terminal/session.py +294 -0
  197. calc_terminal/sim3d.py +206 -0
  198. calc_terminal/solver.py +276 -0
  199. calc_terminal/sound.py +127 -0
  200. calc_terminal/task_reports.py +287 -0
  201. calc_terminal/terminal_host.py +201 -0
  202. calc_terminal/terminal_identity.py +411 -0
  203. calc_terminal/test_ai_mode_reliability.py +344 -0
  204. calc_terminal/test_browser.py +368 -0
  205. calc_terminal/test_code_editor_upgrade.py +485 -0
  206. calc_terminal/test_customization.py +1148 -0
  207. calc_terminal/test_customization_ui.py +612 -0
  208. calc_terminal/test_dynamic_registry.py +304 -0
  209. calc_terminal/test_extensions.py +436 -0
  210. calc_terminal/test_overhaul.py +557 -0
  211. calc_terminal/test_project_detect.py +255 -0
  212. calc_terminal/test_root_cause_fix.py +527 -0
  213. calc_terminal/test_stability.py +532 -0
  214. calc_terminal/test_terminal_identity.py +132 -0
  215. calc_terminal/test_v079_speed.py +460 -0
  216. calc_terminal/theme.py +1107 -0
  217. calc_terminal/timeline.py +139 -0
  218. calc_terminal/todos.py +246 -0
  219. calc_terminal/tool_call_normalizer.py +419 -0
  220. calc_terminal/tui.py +104 -0
  221. calc_terminal/ui/__init__.py +8 -0
  222. calc_terminal/ui/activity_panel.py +231 -0
  223. calc_terminal/ui/activity_stream_panel.py +238 -0
  224. calc_terminal/ui/animations.py +122 -0
  225. calc_terminal/ui/app.py +7271 -0
  226. calc_terminal/ui/attach_panel.py +597 -0
  227. calc_terminal/ui/attachments.py +424 -0
  228. calc_terminal/ui/backup_panel.py +810 -0
  229. calc_terminal/ui/browser_shell.py +887 -0
  230. calc_terminal/ui/cat_agent.py +357 -0
  231. calc_terminal/ui/chats_panel.py +899 -0
  232. calc_terminal/ui/command_palette.py +125 -0
  233. calc_terminal/ui/command_palette_modal.py +166 -0
  234. calc_terminal/ui/composer.py +1141 -0
  235. calc_terminal/ui/context_menu.py +197 -0
  236. calc_terminal/ui/conversation.py +1435 -0
  237. calc_terminal/ui/customization_panel.py +1229 -0
  238. calc_terminal/ui/dashboard.py +404 -0
  239. calc_terminal/ui/design_system.py +557 -0
  240. calc_terminal/ui/diff_panel.py +213 -0
  241. calc_terminal/ui/editor.py +2102 -0
  242. calc_terminal/ui/empty_state.py +302 -0
  243. calc_terminal/ui/events.py +487 -0
  244. calc_terminal/ui/extensions_panel.py +815 -0
  245. calc_terminal/ui/footer.py +166 -0
  246. calc_terminal/ui/gestures_panel.py +383 -0
  247. calc_terminal/ui/goodbye_screen.py +100 -0
  248. calc_terminal/ui/header.py +1034 -0
  249. calc_terminal/ui/help_panel.py +254 -0
  250. calc_terminal/ui/live_activities.py +914 -0
  251. calc_terminal/ui/mcp_panel.py +570 -0
  252. calc_terminal/ui/memory_center.py +524 -0
  253. calc_terminal/ui/mode_colors_panel.py +525 -0
  254. calc_terminal/ui/nav_screens.py +747 -0
  255. calc_terminal/ui/ollama_panel.py +536 -0
  256. calc_terminal/ui/palette.py +221 -0
  257. calc_terminal/ui/permission_panel.py +269 -0
  258. calc_terminal/ui/personalization_panel.py +517 -0
  259. calc_terminal/ui/personalize_center.py +1568 -0
  260. calc_terminal/ui/preview_panel.py +441 -0
  261. calc_terminal/ui/resizers.py +402 -0
  262. calc_terminal/ui/sidebar.py +1285 -0
  263. calc_terminal/ui/statusbar.py +168 -0
  264. calc_terminal/ui/theme_css.py +1396 -0
  265. calc_terminal/ui/thinking.py +226 -0
  266. calc_terminal/ui/timeline_panel.py +102 -0
  267. calc_terminal/ui/todo_panel.py +193 -0
  268. calc_terminal/ui/viewport.py +136 -0
  269. calc_terminal/ui/vision_panel.py +489 -0
  270. calc_terminal/ui/welcome_modal.py +343 -0
  271. calc_terminal/ui/widgets.py +160 -0
  272. calc_terminal/ui/workspace.py +831 -0
  273. calc_terminal/viewers/__init__.py +1 -0
  274. calc_terminal/viewers/document_viewer.py +252 -0
  275. calc_terminal/viewers/image_viewer.py +241 -0
  276. calc_terminal/viewers/pdf_viewer.py +203 -0
  277. calc_terminal/viewers/presentation_viewer.py +164 -0
  278. calc_terminal/viewers/registry.py +120 -0
  279. calc_terminal/viewers/spreadsheet_viewer.py +204 -0
  280. calc_terminal/vision/__init__.py +89 -0
  281. calc_terminal/vision/analysis.py +194 -0
  282. calc_terminal/vision/annotations.py +297 -0
  283. calc_terminal/vision/capture.py +171 -0
  284. calc_terminal/vision/context.py +231 -0
  285. calc_terminal/vision/correlation.py +169 -0
  286. calc_terminal/vision/cursor.py +258 -0
  287. calc_terminal/vision/events.py +66 -0
  288. calc_terminal/vision/frame_pipeline.py +259 -0
  289. calc_terminal/vision/priority.py +218 -0
  290. calc_terminal/vision/provider.py +180 -0
  291. calc_terminal/vision/safety.py +149 -0
  292. calc_terminal/vision/session.py +281 -0
  293. calc_terminal/vision/verify.py +162 -0
  294. calc_terminal/vision.py +514 -0
  295. calc_terminal/vscode_integration.py +113 -0
  296. calc_terminal/web/__init__.py +8 -0
  297. calc_terminal/web/cat_runtime.py +710 -0
  298. calc_terminal/web/server.py +2891 -0
  299. calc_terminal/web/static/css/app.css +3152 -0
  300. calc_terminal/web/static/icons/badge-72.png +0 -0
  301. calc_terminal/web/static/icons/cat.ico +0 -0
  302. calc_terminal/web/static/icons/icon-128.png +0 -0
  303. calc_terminal/web/static/icons/icon-144.png +0 -0
  304. calc_terminal/web/static/icons/icon-152.png +0 -0
  305. calc_terminal/web/static/icons/icon-192.png +0 -0
  306. calc_terminal/web/static/icons/icon-384.png +0 -0
  307. calc_terminal/web/static/icons/icon-512.png +0 -0
  308. calc_terminal/web/static/icons/icon-72.png +0 -0
  309. calc_terminal/web/static/icons/icon-96.png +0 -0
  310. calc_terminal/web/static/icons/icon.svg +34 -0
  311. calc_terminal/web/static/icons/new-project.png +0 -0
  312. calc_terminal/web/static/icons/open-project.png +0 -0
  313. calc_terminal/web/static/index.html +734 -0
  314. calc_terminal/web/static/js/app.js +2403 -0
  315. calc_terminal/web/static/manifest.json +88 -0
  316. calc_terminal/web/static/sw.js +230 -0
  317. calc_terminal/workflow_engine.py +769 -0
  318. calc_terminal/workspace.py +593 -0
  319. calc_terminal/workspace_index.py +385 -0
  320. cct_cli-0.7.9.0.dist-info/METADATA +210 -0
  321. cct_cli-0.7.9.0.dist-info/RECORD +325 -0
  322. cct_cli-0.7.9.0.dist-info/WHEEL +5 -0
  323. cct_cli-0.7.9.0.dist-info/entry_points.txt +4 -0
  324. cct_cli-0.7.9.0.dist-info/licenses/LICENSE +21 -0
  325. cct_cli-0.7.9.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,485 @@
1
+ """Comprehensive test suite for CAT Code Editor Upgrade:
2
+ - VS Code Shortcut Compatibility & Command Registry
3
+ - Editor Line & Text Manipulation Actions
4
+ - DevServerManager & Live Preview Management
5
+ - Diagnostics & Error Location Navigation
6
+ - Universal Visual File Viewers (Image, PDF, DOCX, PPTX, XLSX, CSV)
7
+ """
8
+
9
+ import io
10
+ import os
11
+ import shutil
12
+ import tempfile
13
+ import time
14
+ import zipfile
15
+ import pytest
16
+
17
+ from calc_terminal.editor.commands import CATCommand, CommandRegistry, get_command_registry
18
+ from calc_terminal.editor.shortcuts import Keybinding, ShortcutManager, get_shortcut_manager, normalize_key
19
+ from calc_terminal.editor import actions
20
+ from calc_terminal.preview.dev_server import DevServerManager, detect_project_type, find_available_port, is_port_available
21
+ from calc_terminal.preview.manager import PreviewManager
22
+ from calc_terminal.preview.diagnostics import DiagnosticsManager, parse_error_location, get_diagnostics_manager
23
+ from calc_terminal.viewers.registry import ViewerRegistry, get_viewer_registry
24
+ from calc_terminal.viewers.image_viewer import ImageViewer
25
+ from calc_terminal.viewers.pdf_viewer import PdfViewer
26
+ from calc_terminal.viewers.document_viewer import DocumentViewer
27
+ from calc_terminal.viewers.presentation_viewer import PresentationViewer
28
+ from calc_terminal.viewers.spreadsheet_viewer import SpreadsheetViewer
29
+
30
+
31
+ # ============================================================================
32
+ # 1. Command Registry Tests
33
+ # ============================================================================
34
+
35
+ def test_command_registry_basic():
36
+ reg = CommandRegistry()
37
+ called = []
38
+
39
+ cmd = CATCommand(
40
+ id="test.hello",
41
+ title="Say Hello",
42
+ category="Test",
43
+ handler=lambda ctx: called.append(ctx.get("name", "world")),
44
+ )
45
+ reg.register(cmd)
46
+
47
+ assert reg.get("test.hello") is not None
48
+ assert len(reg.list_commands(category="Test")) == 1
49
+
50
+ reg.execute("test.hello", {"name": "Alice"})
51
+ assert called == ["Alice"]
52
+
53
+ reg.unregister("test.hello")
54
+ assert reg.get("test.hello") is None
55
+
56
+
57
+ def test_default_command_registry():
58
+ reg = get_command_registry()
59
+ cmds = reg.list_commands()
60
+ assert len(cmds) >= 10
61
+
62
+ # Verify key commands are registered
63
+ assert reg.get("editor.toggleComment") is not None
64
+ assert reg.get("editor.deleteLine") is not None
65
+ assert reg.get("editor.moveLineUp") is not None
66
+ assert reg.get("editor.moveLineDown") is not None
67
+ assert reg.get("editor.save") is not None
68
+ assert reg.get("preview.open") is not None
69
+ assert reg.get("editor.commandPalette") is not None
70
+
71
+
72
+ # ============================================================================
73
+ # 2. Shortcuts & Conflict Detection Tests
74
+ # ============================================================================
75
+
76
+ def test_key_normalization():
77
+ assert normalize_key("Ctrl+Shift+P") == "ctrl+shift+p"
78
+ assert normalize_key("SHIFT+ENTER") == "shift+enter"
79
+ assert normalize_key("Ctrl+Alt+S") == "ctrl+alt+s"
80
+ assert normalize_key("f1") == "f1"
81
+
82
+
83
+ def test_shortcut_manager_defaults():
84
+ sm = ShortcutManager()
85
+
86
+ # Test lookup
87
+ cmd = sm.match("ctrl+s")
88
+ assert cmd == "editor.save"
89
+
90
+ cmd_preview = sm.match("shift+enter")
91
+ assert cmd_preview == "preview.open"
92
+
93
+ cmd_palette = sm.match("ctrl+shift+p")
94
+ assert cmd_palette == "editor.commandPalette"
95
+
96
+
97
+ def test_shortcut_conflict_detection():
98
+ sm = ShortcutManager()
99
+ conflicts = sm.detect_conflicts(key="ctrl+s")
100
+ assert isinstance(conflicts, list)
101
+
102
+ # Register custom shortcut with higher priority
103
+ sm.register(Keybinding(command_id="custom.save", key="ctrl+s", when="editor", priority=50))
104
+ matched = sm.match("ctrl+s", context="editor")
105
+ assert matched == "custom.save"
106
+
107
+
108
+ # ============================================================================
109
+ # 3. Editor Actions Tests
110
+ # ============================================================================
111
+
112
+ class MockTextArea:
113
+ """Mock Textual TextArea for action testing."""
114
+ def __init__(self, text="", cursor=(0, 0), language="python"):
115
+ self.text = text
116
+ self.cursor_location = cursor
117
+ self.language = language
118
+
119
+ def move_cursor(self, loc):
120
+ self.cursor_location = loc
121
+
122
+
123
+ def test_toggle_line_comment_python():
124
+ area = MockTextArea(text="x = 1\ny = 2", cursor=(0, 0), language="python")
125
+ # Comment
126
+ actions.toggle_line_comment(area)
127
+ assert area.text == "# x = 1\ny = 2"
128
+ # Uncomment
129
+ actions.toggle_line_comment(area)
130
+ assert area.text == "x = 1\ny = 2"
131
+
132
+
133
+ def test_toggle_line_comment_js_and_html():
134
+ # JavaScript
135
+ js_area = MockTextArea(text="console.log(42);", cursor=(0, 0), language="javascript")
136
+ actions.toggle_line_comment(js_area)
137
+ assert js_area.text == "// console.log(42);"
138
+ actions.toggle_line_comment(js_area)
139
+ assert js_area.text == "console.log(42);"
140
+
141
+ # HTML
142
+ html_area = MockTextArea(text="<div>Hello</div>", cursor=(0, 0), language="html")
143
+ actions.toggle_line_comment(html_area)
144
+ assert html_area.text == "<!-- <div>Hello</div> -->"
145
+ actions.toggle_line_comment(html_area)
146
+ assert html_area.text == "<div>Hello</div>"
147
+
148
+
149
+ def test_delete_line():
150
+ area = MockTextArea(text="line 1\nline 2\nline 3", cursor=(1, 0))
151
+ actions.delete_line(area)
152
+ assert area.text == "line 1\nline 3"
153
+ assert area.cursor_location == (1, 0)
154
+
155
+
156
+ def test_move_line_up_and_down():
157
+ area = MockTextArea(text="line 1\nline 2\nline 3", cursor=(1, 0))
158
+ actions.move_line_up(area)
159
+ assert area.text == "line 2\nline 1\nline 3"
160
+ assert area.cursor_location == (0, 0)
161
+
162
+ actions.move_line_down(area)
163
+ assert area.text == "line 1\nline 2\nline 3"
164
+ assert area.cursor_location == (1, 0)
165
+
166
+
167
+ def test_copy_line_up_and_down():
168
+ area = MockTextArea(text="alpha\nbeta", cursor=(0, 2))
169
+ actions.copy_line_down(area)
170
+ assert area.text == "alpha\nalpha\nbeta"
171
+
172
+ area2 = MockTextArea(text="alpha\nbeta", cursor=(1, 2))
173
+ actions.copy_line_up(area2)
174
+ assert area2.text == "alpha\nbeta\nbeta"
175
+
176
+
177
+ def test_insert_line_below_and_above():
178
+ area = MockTextArea(text="def foo():\n pass", cursor=(0, 5))
179
+ actions.insert_line_below(area)
180
+ assert "def foo():" in area.text
181
+ assert "pass" in area.text
182
+
183
+ area2 = MockTextArea(text=" pass", cursor=(0, 5))
184
+ actions.insert_line_above(area2)
185
+ assert "pass" in area2.text
186
+
187
+
188
+ # ============================================================================
189
+ # 4. DevServerManager & Project Detection Tests
190
+ # ============================================================================
191
+
192
+ def test_port_availability_and_finding():
193
+ # Should find an open port in high range
194
+ port = find_available_port(preferred=18500)
195
+ assert port is not None
196
+ assert is_port_available(port)
197
+
198
+
199
+ def test_detect_project_type():
200
+ with tempfile.TemporaryDirectory() as tmpdir:
201
+ # Empty dir -> static web fallback
202
+ info = detect_project_type(tmpdir)
203
+ assert info.kind == "static"
204
+
205
+ # Add index.html -> static
206
+ with open(os.path.join(tmpdir, "index.html"), "w") as f:
207
+ f.write("<h1>Test</h1>")
208
+ assert detect_project_type(tmpdir).kind == "static"
209
+
210
+ # Add package.json and vite config -> vite
211
+ with open(os.path.join(tmpdir, "package.json"), "w") as f:
212
+ f.write('{"scripts": {"dev": "vite"}}')
213
+ with open(os.path.join(tmpdir, "vite.config.js"), "w") as f:
214
+ f.write("// vite")
215
+ assert detect_project_type(tmpdir).kind == "vite"
216
+
217
+
218
+ def test_dev_server_lifecycle():
219
+ with tempfile.TemporaryDirectory() as tmpdir:
220
+ # Create a static HTML site
221
+ html_file = os.path.join(tmpdir, "index.html")
222
+ with open(html_file, "w", encoding="utf-8") as f:
223
+ f.write("<!DOCTYPE html><html><body><h1>CAT Test</h1></body></html>")
224
+
225
+ port = find_available_port(preferred=18600)
226
+ mgr = DevServerManager(tmpdir)
227
+
228
+ # Start static server
229
+ ok, url = mgr.start(preferred_port=port)
230
+ assert ok is True
231
+ assert mgr.is_running()
232
+ assert mgr.port > 0
233
+ assert "127.0.0.1" in url
234
+
235
+ # Stop server
236
+ mgr.stop()
237
+ assert not mgr.is_running()
238
+
239
+
240
+ # ============================================================================
241
+ # 5. Live Preview Manager Tests
242
+ # ============================================================================
243
+
244
+ def test_preview_manager():
245
+ with tempfile.TemporaryDirectory() as tmpdir:
246
+ html_file = os.path.join(tmpdir, "test.html")
247
+ with open(html_file, "w", encoding="utf-8") as f:
248
+ f.write("<html><body>Hello Preview</body></html>")
249
+
250
+ pm = PreviewManager()
251
+ session = pm.start(tmpdir)
252
+ assert session is not None
253
+ assert session.status == "running"
254
+ assert session.url.startswith("http://127.0.0.1:")
255
+
256
+ # Reload
257
+ ok = pm.reload(session.id)
258
+ assert ok is True
259
+
260
+ # Stop
261
+ pm.stop(session.id)
262
+ assert session.status == "stopped"
263
+
264
+
265
+ # ============================================================================
266
+ # 6. Diagnostics & Error Location Tests
267
+ # ============================================================================
268
+
269
+ def test_parse_error_location():
270
+ loc1 = parse_error_location("Error in app.js:42:15 - SyntaxError")
271
+ assert loc1 is not None
272
+ assert loc1.file == "app.js"
273
+ assert loc1.line == 42
274
+ assert loc1.column == 15
275
+
276
+ loc2 = parse_error_location("File \"main.py\", line 88, in run")
277
+ assert loc2 is not None
278
+ assert loc2.file == "main.py"
279
+ assert loc2.line == 88
280
+
281
+ loc3 = parse_error_location("index.html:10 Uncaught ReferenceError")
282
+ assert loc3 is not None
283
+ assert loc3.file == "index.html"
284
+ assert loc3.line == 10
285
+
286
+ assert parse_error_location("Just a plain error without location") is None
287
+
288
+
289
+ def test_diagnostics_manager():
290
+ dm = DiagnosticsManager()
291
+ d1 = dm.add("error", "Failed to compile at src/App.tsx:24:5", source="build")
292
+ assert d1.severity == "error"
293
+ assert d1.file == "src/App.tsx"
294
+ assert d1.line == 24
295
+ assert d1.column == 5
296
+
297
+ d2 = dm.add("warning", "Deprecated API", source="editor")
298
+ assert len(dm.list_diagnostics()) == 2
299
+ assert len(dm.list_diagnostics(severity="error")) == 1
300
+ assert len(dm.list_diagnostics(source="build")) == 1
301
+
302
+ dm.clear(source="build")
303
+ assert len(dm.list_diagnostics()) == 1
304
+
305
+
306
+ # ============================================================================
307
+ # 7. Universal File Viewers & ViewerRegistry Tests
308
+ # ============================================================================
309
+
310
+ def test_viewer_registry_routing():
311
+ reg = get_viewer_registry()
312
+ assert reg.can_open_visually("test.png")
313
+ assert reg.can_open_visually("doc.pdf")
314
+ assert reg.can_open_visually("notes.docx")
315
+ assert reg.can_open_visually("slides.pptx")
316
+ assert reg.can_open_visually("data.xlsx")
317
+ assert reg.can_open_visually("table.csv")
318
+ assert not reg.can_open_visually("script.py")
319
+ assert not reg.can_open_visually("style.css")
320
+
321
+
322
+ def test_image_viewer():
323
+ from PIL import Image
324
+ with tempfile.TemporaryDirectory() as tmpdir:
325
+ img_path = os.path.join(tmpdir, "test.png")
326
+ # Create a simple test image (red square)
327
+ img = Image.new("RGBA", (20, 20), color=(255, 0, 0, 255))
328
+ img.save(img_path)
329
+
330
+ viewer = ImageViewer(img_path)
331
+ assert viewer.path == img_path
332
+ w, h, size_str, has_alpha, fmt = viewer._get_info()
333
+ assert w == 20
334
+ assert h == 20
335
+ assert has_alpha is True
336
+ assert fmt == "PNG"
337
+
338
+ # Test zoom state
339
+ viewer._zoom = 1.5
340
+ assert viewer._zoom == 1.5
341
+
342
+ # Test rotate
343
+ viewer._rotation = 90
344
+ assert viewer._rotation == 90
345
+
346
+
347
+ def test_pdf_viewer():
348
+ import fitz # PyMuPDF
349
+ with tempfile.TemporaryDirectory() as tmpdir:
350
+ pdf_path = os.path.join(tmpdir, "test.pdf")
351
+ # Create a test PDF with 2 pages
352
+ doc = fitz.open()
353
+ p1 = doc.new_page()
354
+ p1.insert_text((50, 50), "Hello CAT Page 1")
355
+ p2 = doc.new_page()
356
+ p2.insert_text((50, 50), "Hello CAT Page 2")
357
+ doc.save(pdf_path)
358
+ doc.close()
359
+
360
+ viewer = PdfViewer(pdf_path)
361
+ assert viewer._page_count == 2
362
+ assert viewer._current_page == 0
363
+
364
+ # Navigation
365
+ viewer._current_page = 1
366
+ assert viewer._current_page == 1
367
+
368
+ # Search
369
+ results = []
370
+ if viewer._doc:
371
+ for i, page in enumerate(viewer._doc):
372
+ hits = page.search_for("CAT")
373
+ if hits:
374
+ results.append((i, hits))
375
+ assert len(results) >= 1
376
+ viewer.close()
377
+
378
+
379
+ def test_document_viewer_docx():
380
+ with tempfile.TemporaryDirectory() as tmpdir:
381
+ docx_path = os.path.join(tmpdir, "sample.docx")
382
+ # Create valid DOCX OpenXML zip file
383
+ doc_xml = (
384
+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
385
+ '<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">'
386
+ '<w:body>'
387
+ '<w:p><w:pPr><w:pStyle w:val="Heading1"/></w:pPr><w:r><w:t>Project Report</w:t></w:r></w:p>'
388
+ '<w:p><w:r><w:t>This is a paragraph of document text.</w:t></w:r></w:p>'
389
+ '<w:tbl>'
390
+ '<w:tr><w:tc><w:p><w:r><w:t>Item</w:t></w:r></w:p></w:tc><w:tc><w:p><w:r><w:t>Price</w:t></w:r></w:p></w:tc></w:tr>'
391
+ '<w:tr><w:tc><w:p><w:r><w:t>Widget</w:t></w:r></w:p></w:tc><w:tc><w:p><w:r><w:t>$10</w:t></w:r></w:p></w:tc></w:tr>'
392
+ '</w:tbl>'
393
+ '</w:body></w:document>'
394
+ )
395
+ with zipfile.ZipFile(docx_path, "w") as z:
396
+ z.writestr("word/document.xml", doc_xml)
397
+
398
+ viewer = DocumentViewer(docx_path)
399
+ assert viewer.path == docx_path
400
+ assert len(viewer._elements) == 3
401
+ assert viewer._elements[0]["type"] == "heading"
402
+ assert viewer._elements[0]["text"] == "Project Report"
403
+ assert viewer._elements[1]["type"] == "paragraph"
404
+ assert viewer._elements[2]["type"] == "table"
405
+
406
+
407
+ def test_presentation_viewer_pptx():
408
+ with tempfile.TemporaryDirectory() as tmpdir:
409
+ pptx_path = os.path.join(tmpdir, "slides.pptx")
410
+ slide1_xml = (
411
+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
412
+ '<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" '
413
+ 'xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">'
414
+ '<p:cSld><p:spTree><p:sp><p:txBody><a:p><a:r><a:t>Slide 1 Title</a:t></a:r></a:p></p:txBody></p:sp></p:spTree></p:cSld>'
415
+ '</p:sld>'
416
+ )
417
+ slide2_xml = (
418
+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
419
+ '<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" '
420
+ 'xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">'
421
+ '<p:cSld><p:spTree><p:sp><p:txBody><a:p><a:r><a:t>Slide 2 Details</a:t></a:r></a:p></p:txBody></p:sp></p:spTree></p:cSld>'
422
+ '</p:sld>'
423
+ )
424
+ with zipfile.ZipFile(pptx_path, "w") as z:
425
+ z.writestr("ppt/slides/slide1.xml", slide1_xml)
426
+ z.writestr("ppt/slides/slide2.xml", slide2_xml)
427
+
428
+ viewer = PresentationViewer(pptx_path)
429
+ assert viewer.path == pptx_path
430
+ assert viewer.slide_count == 2
431
+ assert viewer._current_slide == 0
432
+
433
+
434
+ def test_spreadsheet_viewer_csv():
435
+ with tempfile.TemporaryDirectory() as tmpdir:
436
+ csv_path = os.path.join(tmpdir, "data.csv")
437
+ with open(csv_path, "w", encoding="utf-8") as f:
438
+ f.write("Name,Age,Role\nAlice,30,Engineer\nBob,25,Designer\nCharlie,35,Manager\n")
439
+
440
+ viewer = SpreadsheetViewer(csv_path)
441
+ assert viewer.path == csv_path
442
+ assert len(viewer._sheets) >= 1
443
+ rows = viewer._sheets.get("Sheet1", [])
444
+ assert len(rows) == 4
445
+ assert rows[0] == ["Name", "Age", "Role"]
446
+ assert rows[1] == ["Alice", "30", "Engineer"]
447
+
448
+
449
+ def test_spreadsheet_viewer_xlsx():
450
+ with tempfile.TemporaryDirectory() as tmpdir:
451
+ xlsx_path = os.path.join(tmpdir, "data.xlsx")
452
+ workbook_xml = (
453
+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
454
+ '<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">'
455
+ '<sheets><sheet name="Sales" sheetId="1"/></sheets>'
456
+ '</workbook>'
457
+ )
458
+ sheet1_xml = (
459
+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
460
+ '<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">'
461
+ '<sheetData>'
462
+ '<row r="1"><c r="A1" t="s"><v>0</v></c><c r="B1"><v>100</v></c></row>'
463
+ '<row r="2"><c r="A2" t="s"><v>1</v></c><c r="B2"><v>200</v></c></row>'
464
+ '</sheetData>'
465
+ '</worksheet>'
466
+ )
467
+ strings_xml = (
468
+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
469
+ '<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">'
470
+ '<si><t>Product</t></si>'
471
+ '<si><t>Widgets</t></si>'
472
+ '</sst>'
473
+ )
474
+ with zipfile.ZipFile(xlsx_path, "w") as z:
475
+ z.writestr("xl/workbook.xml", workbook_xml)
476
+ z.writestr("xl/worksheets/sheet1.xml", sheet1_xml)
477
+ z.writestr("xl/sharedStrings.xml", strings_xml)
478
+
479
+ viewer = SpreadsheetViewer(xlsx_path)
480
+ assert viewer.path == xlsx_path
481
+ assert len(viewer._sheets) >= 1
482
+ sheet_data = viewer._sheets.get("Sales", [])
483
+ assert len(sheet_data) >= 2
484
+ assert sheet_data[0][0] == "Product"
485
+ assert sheet_data[1][0] == "Widgets"