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.
- calc_terminal/__init__.py +14 -0
- calc_terminal/__main__.py +14 -0
- calc_terminal/activity.py +1334 -0
- calc_terminal/agent.py +3387 -0
- calc_terminal/agent_runtime.py +519 -0
- calc_terminal/ai_context.py +447 -0
- calc_terminal/ai_modes.py +752 -0
- calc_terminal/ai_personalization.py +286 -0
- calc_terminal/ai_preview_feedback.py +213 -0
- calc_terminal/aicore.py +2572 -0
- calc_terminal/anim.py +367 -0
- calc_terminal/app.py +3685 -0
- calc_terminal/art.py +639 -0
- calc_terminal/atomsim.py +368 -0
- calc_terminal/attachments.py +743 -0
- calc_terminal/benchmark_system.py +414 -0
- calc_terminal/browser/__init__.py +36 -0
- calc_terminal/browser/browser_state.py +346 -0
- calc_terminal/browser/devserver.py +176 -0
- calc_terminal/browser/engine.py +494 -0
- calc_terminal/browser/navigation.py +84 -0
- calc_terminal/browser/preview.py +429 -0
- calc_terminal/browser/preview_entry.py +95 -0
- calc_terminal/browser/project_detector.py +144 -0
- calc_terminal/browser/server.py +449 -0
- calc_terminal/browser/state.py +75 -0
- calc_terminal/browser/watcher.py +99 -0
- calc_terminal/browser_gui/__init__.py +1 -0
- calc_terminal/browser_gui/__main__.py +3 -0
- calc_terminal/browser_gui/launcher.py +173 -0
- calc_terminal/browser_gui/playwright_browser.py +117 -0
- calc_terminal/browser_gui/qt_browser.py +1501 -0
- calc_terminal/browser_gui/webview_browser.py +57 -0
- calc_terminal/capabilities/__init__.py +35 -0
- calc_terminal/capabilities/adapters/__init__.py +33 -0
- calc_terminal/capabilities/adapters/bioinformatics.py +204 -0
- calc_terminal/capabilities/adapters/browser_adapter.py +205 -0
- calc_terminal/capabilities/adapters/filesystem.py +206 -0
- calc_terminal/capabilities/adapters/git_adapter.py +202 -0
- calc_terminal/capabilities/adapters/jupyter_adapter.py +138 -0
- calc_terminal/capabilities/adapters/ml_frameworks.py +158 -0
- calc_terminal/capabilities/adapters/platforms.py +200 -0
- calc_terminal/capabilities/adapters/python_exec.py +93 -0
- calc_terminal/capabilities/adapters/quantum_adapter.py +150 -0
- calc_terminal/capabilities/adapters/scientific_comp.py +123 -0
- calc_terminal/capabilities/adapters/structural_bio.py +161 -0
- calc_terminal/capabilities/adapters/terminal.py +99 -0
- calc_terminal/capabilities/bus.py +178 -0
- calc_terminal/capabilities/discovery.py +207 -0
- calc_terminal/capabilities/schema.py +221 -0
- calc_terminal/cat.ico +0 -0
- calc_terminal/cat_browser.py +2018 -0
- calc_terminal/chat_store.py +703 -0
- calc_terminal/cli.py +1178 -0
- calc_terminal/code_editor.py +640 -0
- calc_terminal/collaboration.py +723 -0
- calc_terminal/commands_data.py +139 -0
- calc_terminal/compatibility_engine.py +352 -0
- calc_terminal/compute/__init__.py +31 -0
- calc_terminal/compute/fabric.py +350 -0
- calc_terminal/config.py +227 -0
- calc_terminal/core/__init__.py +41 -0
- calc_terminal/core/checkpoint.py +156 -0
- calc_terminal/core/input/__init__.py +45 -0
- calc_terminal/core/mode_registry.py +300 -0
- calc_terminal/core/project_graph.py +172 -0
- calc_terminal/core/recovery.py +129 -0
- calc_terminal/core/security_layer.py +112 -0
- calc_terminal/core/task_graph.py +202 -0
- calc_terminal/core/unified_runtime.py +184 -0
- calc_terminal/core/verification.py +257 -0
- calc_terminal/customization.py +1566 -0
- calc_terminal/derivations.py +153 -0
- calc_terminal/device_control.py +263 -0
- calc_terminal/diagnostics/__init__.py +27 -0
- calc_terminal/diagnostics/doctor_engine.py +382 -0
- calc_terminal/diagnostics/self_test.py +247 -0
- calc_terminal/doctor.py +519 -0
- calc_terminal/easter_eggs.py +274 -0
- calc_terminal/editor/__init__.py +1 -0
- calc_terminal/editor/actions.py +263 -0
- calc_terminal/editor/commands.py +160 -0
- calc_terminal/editor/shortcuts.py +226 -0
- calc_terminal/engine.py +259 -0
- calc_terminal/errors.py +120 -0
- calc_terminal/event_stream.py +146 -0
- calc_terminal/eventbus.py +133 -0
- calc_terminal/extensions.py +733 -0
- calc_terminal/fallback_cli.py +1321 -0
- calc_terminal/first_run.py +265 -0
- calc_terminal/fomoji_auth.py +1043 -0
- calc_terminal/formulas.py +82 -0
- calc_terminal/fs_cache.py +121 -0
- calc_terminal/fs_watcher.py +277 -0
- calc_terminal/game.py +193 -0
- calc_terminal/gen1.py +5 -0
- calc_terminal/generators.py +245 -0
- calc_terminal/gestures/__init__.py +42 -0
- calc_terminal/gestures/bindings.py +175 -0
- calc_terminal/gestures/manager.py +477 -0
- calc_terminal/goodbye.py +363 -0
- calc_terminal/gpu3d.py +290 -0
- calc_terminal/graphs.py +358 -0
- calc_terminal/hardware_analyzer.py +440 -0
- calc_terminal/host/__init__.py +30 -0
- calc_terminal/host/browser_manager.py +187 -0
- calc_terminal/host/desktop.py +1386 -0
- calc_terminal/host/launcher.py +395 -0
- calc_terminal/host/terminal.py +279 -0
- calc_terminal/identity.py +216 -0
- calc_terminal/input/__init__.py +54 -0
- calc_terminal/input/capabilities.py +258 -0
- calc_terminal/input/focus.py +87 -0
- calc_terminal/input/gestures.py +64 -0
- calc_terminal/input/pointer.py +114 -0
- calc_terminal/input/touch.py +345 -0
- calc_terminal/keys.py +84 -0
- calc_terminal/live_automation.py +165 -0
- calc_terminal/mathtext.py +433 -0
- calc_terminal/mcp.py +386 -0
- calc_terminal/memory.py +337 -0
- calc_terminal/memory_v2.py +479 -0
- calc_terminal/metrics.py +333 -0
- calc_terminal/mode_detection.py +146 -0
- calc_terminal/model.py +2431 -0
- calc_terminal/model_router.py +665 -0
- calc_terminal/models/__init__.py +0 -0
- calc_terminal/models/active_state.py +187 -0
- calc_terminal/models/dynamic_registry.py +584 -0
- calc_terminal/models/manager.py +781 -0
- calc_terminal/models/model_metadata.json +3526 -0
- calc_terminal/models/profiles.py +194 -0
- calc_terminal/models/registry.py +265 -0
- calc_terminal/models/schema.py +197 -0
- calc_terminal/models/validator.py +287 -0
- calc_terminal/models/verification_engine.py +368 -0
- calc_terminal/native_picker.py +215 -0
- calc_terminal/ollama_catalog.py +279 -0
- calc_terminal/ollama_download.py +233 -0
- calc_terminal/orchestrator.py +304 -0
- calc_terminal/package_research.py +322 -0
- calc_terminal/packages.py +1024 -0
- calc_terminal/pc_specs.py +116 -0
- calc_terminal/permissions.py +334 -0
- calc_terminal/pet.py +106 -0
- calc_terminal/pipeline.py +505 -0
- calc_terminal/platform/__init__.py +491 -0
- calc_terminal/platform/desktop.py +491 -0
- calc_terminal/platform/web.py +781 -0
- calc_terminal/preview/__init__.py +1 -0
- calc_terminal/preview/dev_server.py +303 -0
- calc_terminal/preview/diagnostics.py +131 -0
- calc_terminal/preview/live_reload.py +66 -0
- calc_terminal/preview/manager.py +129 -0
- calc_terminal/project_stats.py +209 -0
- calc_terminal/projects.py +328 -0
- calc_terminal/providers/__init__.py +0 -0
- calc_terminal/providers/adapters/__init__.py +80 -0
- calc_terminal/providers/adapters/anthropic_adapter.py +127 -0
- calc_terminal/providers/adapters/base.py +106 -0
- calc_terminal/providers/adapters/chinese_adapters.py +420 -0
- calc_terminal/providers/adapters/gemini_adapter.py +101 -0
- calc_terminal/providers/adapters/ollama_adapter.py +83 -0
- calc_terminal/providers/adapters/openai_adapter.py +159 -0
- calc_terminal/providers/adapters/other_adapters.py +246 -0
- calc_terminal/providers/anthropic_provider.py +172 -0
- calc_terminal/providers/auto_update.py +416 -0
- calc_terminal/providers/base_provider.py +105 -0
- calc_terminal/providers/discovery_manager.py +207 -0
- calc_terminal/providers/gemini_provider.py +178 -0
- calc_terminal/providers/lifecycle.py +767 -0
- calc_terminal/providers/ollama_adapter.py +707 -0
- calc_terminal/providers/openai_provider.py +254 -0
- calc_terminal/providers/provider_manager.py +1827 -0
- calc_terminal/providers/providers.json +4075 -0
- calc_terminal/reactionsim.py +279 -0
- calc_terminal/registry.py +337 -0
- calc_terminal/report.py +162 -0
- calc_terminal/research/__init__.py +45 -0
- calc_terminal/research/artifact_intel.py +126 -0
- calc_terminal/research/data_lineage.py +123 -0
- calc_terminal/research/experiment_ledger.py +303 -0
- calc_terminal/research/reproducibility.py +131 -0
- calc_terminal/resilience/__init__.py +47 -0
- calc_terminal/resilience/agent_state.py +121 -0
- calc_terminal/resilience/capability_matcher.py +174 -0
- calc_terminal/resilience/circuit_breaker.py +158 -0
- calc_terminal/resilience/failover_engine.py +230 -0
- calc_terminal/resilience/health_monitor.py +192 -0
- calc_terminal/resilience/ollama_adapter.py +125 -0
- calc_terminal/resilience/orchestrator.py +312 -0
- calc_terminal/resilience/types.py +134 -0
- calc_terminal/sandbox.py +98 -0
- calc_terminal/scires.py +558 -0
- calc_terminal/security_scanner.py +126 -0
- calc_terminal/session.py +294 -0
- calc_terminal/sim3d.py +206 -0
- calc_terminal/solver.py +276 -0
- calc_terminal/sound.py +127 -0
- calc_terminal/task_reports.py +287 -0
- calc_terminal/terminal_host.py +201 -0
- calc_terminal/terminal_identity.py +411 -0
- calc_terminal/test_ai_mode_reliability.py +344 -0
- calc_terminal/test_browser.py +368 -0
- calc_terminal/test_code_editor_upgrade.py +485 -0
- calc_terminal/test_customization.py +1148 -0
- calc_terminal/test_customization_ui.py +612 -0
- calc_terminal/test_dynamic_registry.py +304 -0
- calc_terminal/test_extensions.py +436 -0
- calc_terminal/test_overhaul.py +557 -0
- calc_terminal/test_project_detect.py +255 -0
- calc_terminal/test_root_cause_fix.py +527 -0
- calc_terminal/test_stability.py +532 -0
- calc_terminal/test_terminal_identity.py +132 -0
- calc_terminal/test_v079_speed.py +460 -0
- calc_terminal/theme.py +1107 -0
- calc_terminal/timeline.py +139 -0
- calc_terminal/todos.py +246 -0
- calc_terminal/tool_call_normalizer.py +419 -0
- calc_terminal/tui.py +104 -0
- calc_terminal/ui/__init__.py +8 -0
- calc_terminal/ui/activity_panel.py +231 -0
- calc_terminal/ui/activity_stream_panel.py +238 -0
- calc_terminal/ui/animations.py +122 -0
- calc_terminal/ui/app.py +7271 -0
- calc_terminal/ui/attach_panel.py +597 -0
- calc_terminal/ui/attachments.py +424 -0
- calc_terminal/ui/backup_panel.py +810 -0
- calc_terminal/ui/browser_shell.py +887 -0
- calc_terminal/ui/cat_agent.py +357 -0
- calc_terminal/ui/chats_panel.py +899 -0
- calc_terminal/ui/command_palette.py +125 -0
- calc_terminal/ui/command_palette_modal.py +166 -0
- calc_terminal/ui/composer.py +1141 -0
- calc_terminal/ui/context_menu.py +197 -0
- calc_terminal/ui/conversation.py +1435 -0
- calc_terminal/ui/customization_panel.py +1229 -0
- calc_terminal/ui/dashboard.py +404 -0
- calc_terminal/ui/design_system.py +557 -0
- calc_terminal/ui/diff_panel.py +213 -0
- calc_terminal/ui/editor.py +2102 -0
- calc_terminal/ui/empty_state.py +302 -0
- calc_terminal/ui/events.py +487 -0
- calc_terminal/ui/extensions_panel.py +815 -0
- calc_terminal/ui/footer.py +166 -0
- calc_terminal/ui/gestures_panel.py +383 -0
- calc_terminal/ui/goodbye_screen.py +100 -0
- calc_terminal/ui/header.py +1034 -0
- calc_terminal/ui/help_panel.py +254 -0
- calc_terminal/ui/live_activities.py +914 -0
- calc_terminal/ui/mcp_panel.py +570 -0
- calc_terminal/ui/memory_center.py +524 -0
- calc_terminal/ui/mode_colors_panel.py +525 -0
- calc_terminal/ui/nav_screens.py +747 -0
- calc_terminal/ui/ollama_panel.py +536 -0
- calc_terminal/ui/palette.py +221 -0
- calc_terminal/ui/permission_panel.py +269 -0
- calc_terminal/ui/personalization_panel.py +517 -0
- calc_terminal/ui/personalize_center.py +1568 -0
- calc_terminal/ui/preview_panel.py +441 -0
- calc_terminal/ui/resizers.py +402 -0
- calc_terminal/ui/sidebar.py +1285 -0
- calc_terminal/ui/statusbar.py +168 -0
- calc_terminal/ui/theme_css.py +1396 -0
- calc_terminal/ui/thinking.py +226 -0
- calc_terminal/ui/timeline_panel.py +102 -0
- calc_terminal/ui/todo_panel.py +193 -0
- calc_terminal/ui/viewport.py +136 -0
- calc_terminal/ui/vision_panel.py +489 -0
- calc_terminal/ui/welcome_modal.py +343 -0
- calc_terminal/ui/widgets.py +160 -0
- calc_terminal/ui/workspace.py +831 -0
- calc_terminal/viewers/__init__.py +1 -0
- calc_terminal/viewers/document_viewer.py +252 -0
- calc_terminal/viewers/image_viewer.py +241 -0
- calc_terminal/viewers/pdf_viewer.py +203 -0
- calc_terminal/viewers/presentation_viewer.py +164 -0
- calc_terminal/viewers/registry.py +120 -0
- calc_terminal/viewers/spreadsheet_viewer.py +204 -0
- calc_terminal/vision/__init__.py +89 -0
- calc_terminal/vision/analysis.py +194 -0
- calc_terminal/vision/annotations.py +297 -0
- calc_terminal/vision/capture.py +171 -0
- calc_terminal/vision/context.py +231 -0
- calc_terminal/vision/correlation.py +169 -0
- calc_terminal/vision/cursor.py +258 -0
- calc_terminal/vision/events.py +66 -0
- calc_terminal/vision/frame_pipeline.py +259 -0
- calc_terminal/vision/priority.py +218 -0
- calc_terminal/vision/provider.py +180 -0
- calc_terminal/vision/safety.py +149 -0
- calc_terminal/vision/session.py +281 -0
- calc_terminal/vision/verify.py +162 -0
- calc_terminal/vision.py +514 -0
- calc_terminal/vscode_integration.py +113 -0
- calc_terminal/web/__init__.py +8 -0
- calc_terminal/web/cat_runtime.py +710 -0
- calc_terminal/web/server.py +2891 -0
- calc_terminal/web/static/css/app.css +3152 -0
- calc_terminal/web/static/icons/badge-72.png +0 -0
- calc_terminal/web/static/icons/cat.ico +0 -0
- calc_terminal/web/static/icons/icon-128.png +0 -0
- calc_terminal/web/static/icons/icon-144.png +0 -0
- calc_terminal/web/static/icons/icon-152.png +0 -0
- calc_terminal/web/static/icons/icon-192.png +0 -0
- calc_terminal/web/static/icons/icon-384.png +0 -0
- calc_terminal/web/static/icons/icon-512.png +0 -0
- calc_terminal/web/static/icons/icon-72.png +0 -0
- calc_terminal/web/static/icons/icon-96.png +0 -0
- calc_terminal/web/static/icons/icon.svg +34 -0
- calc_terminal/web/static/icons/new-project.png +0 -0
- calc_terminal/web/static/icons/open-project.png +0 -0
- calc_terminal/web/static/index.html +734 -0
- calc_terminal/web/static/js/app.js +2403 -0
- calc_terminal/web/static/manifest.json +88 -0
- calc_terminal/web/static/sw.js +230 -0
- calc_terminal/workflow_engine.py +769 -0
- calc_terminal/workspace.py +593 -0
- calc_terminal/workspace_index.py +385 -0
- cct_cli-0.7.9.0.dist-info/METADATA +210 -0
- cct_cli-0.7.9.0.dist-info/RECORD +325 -0
- cct_cli-0.7.9.0.dist-info/WHEEL +5 -0
- cct_cli-0.7.9.0.dist-info/entry_points.txt +4 -0
- cct_cli-0.7.9.0.dist-info/licenses/LICENSE +21 -0
- 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"
|