f71da0219b
xcodeproj build settings: - Add CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED for localization checks - Add GCC_C_LANGUAGE_STANDARD=gnu17 for C language standard - Add LLVM_LTO=YES for Release link-time optimization - Add ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS - Add ENABLE_INCOMING_NETWORK_CONNECTIONS for MCP agent server - Add ENABLE_PREVIEWS for SwiftUI preview support xcassets (14 new color sets): - EditorBackground, SidebarBackground, ConsoleBackground - KeywordColor, StringColor, CommentColor, TypeColor, NumberColor - LineNumberColor, ToolbarTint, CurrentLineHighlight - ErrorHighlight, WarningHighlight - GitAddedColor, GitModifiedColor, GitDeletedColor - All with proper light/dark mode variants - Remove orphan icon_64x64.png and icon_base.png (fixed warnings) xcscheme: - Add targeted code coverage for CxIDE target only - Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Adv v- Ad- Ad- Ad- Ad- Ad- ALD_- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Ad- Aunch arg (disabled) Info.plist: - Add 12 new document types: C, ObjC, Python, Shell, JS, TS, HTML, CSS, YAML, XML, Plain Text, Property List Entitlements: - Add network.client and network.server for agent HTTP/WebSocket - Add files.user-selec- Add files.user-selec- Add filads.read-write - Add- Add- Add- Add- Aderminal com- Add- Add- Add- Add- Aderminal com- Add- Add- Add- Adrestore workspace state All 53 tests passing
260 lines
8.2 KiB
Swift
260 lines
8.2 KiB
Swift
import XCTest
|
|
@testable import CxIDE
|
|
|
|
// MARK: - EditorViewModel Tests
|
|
|
|
@MainActor
|
|
final class EditorViewModelTests: XCTestCase {
|
|
|
|
func testInitialState() {
|
|
let vm = EditorViewModel()
|
|
XCTAssertFalse(vm.content.isEmpty)
|
|
XCTAssertEqual(vm.fontSize, 13)
|
|
XCTAssertTrue(vm.showSidebar)
|
|
XCTAssertFalse(vm.isRunning)
|
|
XCTAssertEqual(vm.bottomPanel, .console)
|
|
XCTAssertEqual(vm.activePanel, .explorer)
|
|
XCTAssertFalse(vm.showCommandPalette)
|
|
}
|
|
|
|
func testTabManagement() {
|
|
let vm = EditorViewModel()
|
|
let initialCount = vm.tabs.count
|
|
XCTAssertGreaterThanOrEqual(initialCount, 1)
|
|
|
|
// Open a new tab
|
|
let tab = EditorTab(name: "test.swift", content: "let x = 1")
|
|
vm.tabs.append(tab)
|
|
vm.activeTabID = tab.id
|
|
XCTAssertEqual(vm.tabs.count, initialCount + 1)
|
|
|
|
// Close tab
|
|
vm.closeTab(tab)
|
|
XCTAssertEqual(vm.tabs.count, initialCount)
|
|
}
|
|
|
|
func testCloseAllTabs() {
|
|
let vm = EditorViewModel()
|
|
vm.tabs.append(EditorTab(name: "a.swift", content: "a"))
|
|
vm.tabs.append(EditorTab(name: "b.swift", content: "b"))
|
|
vm.closeAllTabs()
|
|
XCTAssertEqual(vm.tabs.count, 1, "Should have one default tab after closing all")
|
|
}
|
|
|
|
func testPerformSearch() {
|
|
let vm = EditorViewModel()
|
|
vm.content = "Hello world! Hello Swift!"
|
|
vm.searchState.query = "Hello"
|
|
vm.performSearch()
|
|
XCTAssertEqual(vm.searchState.matchCount, 2)
|
|
|
|
vm.searchState.query = "hello"
|
|
vm.searchState.isCaseSensitive = true
|
|
vm.performSearch()
|
|
XCTAssertEqual(vm.searchState.matchCount, 0)
|
|
|
|
vm.searchState.isCaseSensitive = false
|
|
vm.performSearch()
|
|
XCTAssertEqual(vm.searchState.matchCount, 2)
|
|
}
|
|
|
|
func testPerformSearchEmpty() {
|
|
let vm = EditorViewModel()
|
|
vm.searchState.query = ""
|
|
vm.performSearch()
|
|
XCTAssertEqual(vm.searchState.matchCount, 0)
|
|
}
|
|
|
|
func testReplaceNext() {
|
|
let vm = EditorViewModel()
|
|
vm.content = "foo bar foo"
|
|
vm.searchState.query = "foo"
|
|
vm.searchState.replacement = "baz"
|
|
vm.performSearch()
|
|
XCTAssertEqual(vm.searchState.matchCount, 2)
|
|
|
|
vm.replaceNext()
|
|
XCTAssertEqual(vm.content, "baz bar foo")
|
|
XCTAssertEqual(vm.searchState.matchCount, 1)
|
|
}
|
|
|
|
func testReplaceAll() {
|
|
let vm = EditorViewModel()
|
|
vm.content = "foo bar foo baz foo"
|
|
vm.searchState.query = "foo"
|
|
vm.searchState.replacement = "qux"
|
|
vm.performSearch()
|
|
vm.replaceAll()
|
|
XCTAssertEqual(vm.content, "qux bar qux baz qux")
|
|
XCTAssertEqual(vm.searchState.matchCount, 0)
|
|
}
|
|
|
|
func testClearConsole() {
|
|
let vm = EditorViewModel()
|
|
XCTAssertFalse(vm.consoleEntries.isEmpty)
|
|
vm.clearConsole()
|
|
// clearConsole adds a "Console cleared." system message
|
|
XCTAssertEqual(vm.consoleEntries.count, 1)
|
|
XCTAssertEqual(vm.consoleEntries.first?.level, .system)
|
|
}
|
|
|
|
func testToggleSearch() {
|
|
let vm = EditorViewModel()
|
|
XCTAssertFalse(vm.searchState.isActive)
|
|
vm.toggleSearch()
|
|
XCTAssertTrue(vm.searchState.isActive)
|
|
vm.toggleSearch()
|
|
XCTAssertFalse(vm.searchState.isActive)
|
|
}
|
|
|
|
func testBottomPanelEnum() {
|
|
let panels = EditorViewModel.BottomPanel.allCases
|
|
XCTAssertEqual(panels.count, 6)
|
|
for panel in panels {
|
|
XCTAssertFalse(panel.rawValue.isEmpty)
|
|
XCTAssertFalse(panel.iconName.isEmpty)
|
|
}
|
|
}
|
|
|
|
func testCppAnalysis() {
|
|
let vm = EditorViewModel()
|
|
vm.content = "import Foundation\nfunc hello() {\n print(\"Hello\")\n}\n"
|
|
vm.runCppAnalysis()
|
|
XCTAssertNotEqual(vm.cppAnalysis, "Ready")
|
|
}
|
|
|
|
func testCommandPalette() {
|
|
let vm = EditorViewModel()
|
|
XCTAssertFalse(vm.commands.isEmpty)
|
|
XCTAssertTrue(vm.commands.count > 20)
|
|
|
|
let filtered = vm.filteredCommands
|
|
XCTAssertEqual(filtered.count, vm.commands.count)
|
|
|
|
vm.commandSearch = "run"
|
|
let runFiltered = vm.filteredCommands
|
|
XCTAssertLessThan(runFiltered.count, vm.commands.count)
|
|
}
|
|
|
|
func testBuildConfig() {
|
|
let vm = EditorViewModel()
|
|
XCTAssertEqual(vm.buildConfig.name, "Debug")
|
|
XCTAssertEqual(vm.buildConfig.optimizationLevel, .debug)
|
|
}
|
|
|
|
func testTheme() {
|
|
let vm = EditorViewModel()
|
|
XCTAssertEqual(vm.currentTheme.name, "Dark+ (VSCode)")
|
|
XCTAssertNotNil(vm.currentTheme.editorBackground)
|
|
XCTAssertNotNil(vm.currentTheme.sidebarBackground)
|
|
}
|
|
|
|
func testNotifications() {
|
|
let vm = EditorViewModel()
|
|
let initialCount = vm.notifications.count
|
|
vm.notify(.info("Test"))
|
|
XCTAssertEqual(vm.notifications.count, initialCount + 1)
|
|
XCTAssertEqual(vm.notifications.last?.message, "Test")
|
|
}
|
|
}
|
|
|
|
// MARK: - Engine Tests
|
|
|
|
final class EngineTests: XCTestCase {
|
|
|
|
func testCppEngineMetrics() {
|
|
let engine = CppEngineController()
|
|
let code = "import Foundation\n\nfunc hello() {\n print(\"Hello\")\n}\n\nfunc goodbye() {\n print(\"Bye\")\n}\n"
|
|
XCTAssertGreaterThan(engine.lineCount(code), 0)
|
|
XCTAssertGreaterThan(engine.codeLineCount(code), 0)
|
|
XCTAssertGreaterThanOrEqual(engine.functionCount(code), 1)
|
|
XCTAssertEqual(engine.classCount(code), 0)
|
|
}
|
|
|
|
func testCppEngineChecksum() {
|
|
let engine = CppEngineController()
|
|
let code = "let x = 1"
|
|
let checksum1 = engine.checksum(code)
|
|
let checksum2 = engine.checksum(code)
|
|
XCTAssertEqual(checksum1, checksum2, "Same input should produce same checksum")
|
|
|
|
let checksum3 = engine.checksum("let x = 2")
|
|
XCTAssertNotEqual(checksum1, checksum3, "Different input should produce different checksum")
|
|
}
|
|
|
|
func testCppEngineStructuredAnalysis() {
|
|
let engine = CppEngineController()
|
|
let code = "struct MyStruct {\n var value: Int\n}\n"
|
|
let result = engine.runStructuredAnalysis(code)
|
|
XCTAssertFalse(result.isEmpty)
|
|
}
|
|
|
|
func testCppEngineConfig() {
|
|
let engine = CppEngineController()
|
|
var config = CppAnalysisConfig()
|
|
config.maxLineLength = 80
|
|
config.detectForceUnwraps = false
|
|
engine.applyConfig(config)
|
|
// Should not crash
|
|
let result = engine.runAnalysis("let x = 1")
|
|
XCTAssertFalse(result.isEmpty)
|
|
}
|
|
|
|
func testCppEngineBlankLines() {
|
|
let engine = CppEngineController()
|
|
let code = "line1\n\nline3\n\n\nline6\n"
|
|
let blanks = engine.blankLineCount(code)
|
|
XCTAssertGreaterThanOrEqual(blanks, 3)
|
|
}
|
|
|
|
func testSwiftRuntimeEngine() async throws {
|
|
let tmpDir = FileManager.default.temporaryDirectory.appendingPathComponent("CxIDETest-\(UUID().uuidString)")
|
|
try FileManager.default.createDirectory(at: tmpDir, withIntermediateDirectories: true)
|
|
defer { try? FileManager.default.removeItem(at: tmpDir) }
|
|
|
|
let engine = SwiftRuntimeEngine(projectDirectory: tmpDir)
|
|
let result = try await engine.execute(script: "print(42)")
|
|
XCTAssertTrue(result.contains("42"))
|
|
}
|
|
|
|
func testEngineFactory() {
|
|
let engine = EngineFactory.make(.sandbox)
|
|
XCTAssertNotNil(engine)
|
|
|
|
let fullEngine = EngineFactory.make(.swift)
|
|
XCTAssertNotNil(fullEngine)
|
|
}
|
|
}
|
|
|
|
// MARK: - Service Tests
|
|
|
|
@MainActor
|
|
final class ServiceTests: XCTestCase {
|
|
|
|
func testGitServiceInit() {
|
|
let service = GitService()
|
|
XCTAssertEqual(service.status.branch, "main")
|
|
XCTAssertTrue(service.status.isClean)
|
|
XCTAssertFalse(service.isGitRepo)
|
|
}
|
|
|
|
func testWorkspaceServiceInit() {
|
|
let service = WorkspaceService()
|
|
XCTAssertNil(service.currentWorkspace)
|
|
XCTAssertTrue(service.fileTree.isEmpty)
|
|
XCTAssertFalse(service.isLoading)
|
|
}
|
|
|
|
func testAgentServiceInit() {
|
|
let service = AgentService()
|
|
XCTAssertTrue(service.messages.isEmpty)
|
|
XCTAssertEqual(service.status, .idle)
|
|
XCTAssertFalse(service.isInitialized)
|
|
}
|
|
|
|
func testAgentServiceToolCount() {
|
|
let service = AgentService()
|
|
XCTAssertGreaterThanOrEqual(service.toolCount, 0)
|
|
}
|
|
}
|