import Foundation import Testing @testable import VibeTunnel @Suite("TerminalData Tests", .tags(.models)) struct TerminalDataTests { // MARK: - TerminalEvent Tests @Test("Parse header event") func parseHeaderEvent() throws { let headerJSON = """ { "version": 2, "width": 80, "height": 24, "timestamp": 1234567890.5, "duration": 120.5, "command": "/bin/bash", "title": "Test Session", "env": {"TERM": "xterm-256color", "SHELL": "/bin/bash"} } """ let event = TerminalEvent(from: headerJSON) #expect(event != nil) if case let .header(header) = event { #expect(header.version == 2) #expect(header.width == 80) #expect(header.height == 24) #expect(header.timestamp == 1234567890.5) #expect(header.duration == 120.5) #expect(header.command == "/bin/bash") #expect(header.title == "Test Session") #expect(header.env?["TERM"] == "xterm-256color") #expect(header.env?["SHELL"] == "/bin/bash") } else { Issue.record("Expected header event but got \(String(describing: event))") } } @Test("Parse minimal header event") func parseMinimalHeaderEvent() throws { let headerJSON = """ { "version": 2, "width": 80, "height": 24 } """ let event = TerminalEvent(from: headerJSON) #expect(event != nil) if case let .header(header) = event { #expect(header.version == 2) #expect(header.width == 80) #expect(header.height == 24) #expect(header.timestamp == nil) #expect(header.duration == nil) #expect(header.command == nil) #expect(header.title == nil) #expect(header.env == nil) } else { Issue.record("Expected header event but got \(String(describing: event))") } } @Test("Parse output event") func parseOutputEvent() throws { let outputJSON = "[1.5, \"o\", \"Hello, world!\\r\\n\"]" let event = TerminalEvent(from: outputJSON) #expect(event != nil) if case let .output(timestamp, data) = event { #expect(timestamp == 1.5) #expect(data == "Hello, world!\\r\\n") } else { Issue.record("Expected output event but got \(String(describing: event))") } } @Test("Parse resize event") func parseResizeEvent() throws { let resizeJSON = "[2.5, \"r\", \"80x25\"]" let event = TerminalEvent(from: resizeJSON) #expect(event != nil) if case let .resize(timestamp, dimensions) = event { #expect(timestamp == 2.5) #expect(dimensions == "80x25") } else { Issue.record("Expected resize event but got \(String(describing: event))") } } @Test("Parse exit event") func parseExitEvent() throws { let exitJSON = "[\"exit\", 0, \"test-session-123\"]" let event = TerminalEvent(from: exitJSON) #expect(event != nil) if case let .exit(code, sessionId) = event { #expect(code == 0) #expect(sessionId == "test-session-123") } else { Issue.record("Expected exit event but got \(String(describing: event))") } } @Test("Parse exit event with non-zero code") func parseExitEventNonZero() throws { let exitJSON = "[\"exit\", 127, \"error-session\"]" let event = TerminalEvent(from: exitJSON) #expect(event != nil) if case let .exit(code, sessionId) = event { #expect(code == 127) #expect(sessionId == "error-session") } else { Issue.record("Expected exit event but got \(String(describing: event))") } } @Test("Invalid JSON returns nil") func invalidJSON() throws { let invalidJSON = "not valid json" let event = TerminalEvent(from: invalidJSON) #expect(event == nil) } @Test("Invalid event type returns nil") func invalidEventType() throws { let invalidEvent = "[1.0, \"x\", \"unknown type\"]" let event = TerminalEvent(from: invalidEvent) #expect(event == nil) } @Test("Missing array elements returns nil") func missingArrayElements() throws { let incompleteEvent = "[1.0, \"o\"]" let event = TerminalEvent(from: incompleteEvent) #expect(event == nil) } @Test("Wrong data types in array returns nil") func wrongDataTypes() throws { let wrongTypes = "[\"not-a-number\", \"o\", \"data\"]" let event = TerminalEvent(from: wrongTypes) #expect(event == nil) } @Test("Empty string returns nil") func emptyString() throws { let event = TerminalEvent(from: "") #expect(event == nil) } @Test("Array with wrong exit format returns nil") func wrongExitFormat() throws { // Wrong first element let wrongExit1 = "[\"not-exit\", 0, \"session\"]" #expect(TerminalEvent(from: wrongExit1) == nil) // Wrong second element type let wrongExit2 = "[\"exit\", \"not-a-number\", \"session\"]" #expect(TerminalEvent(from: wrongExit2) == nil) // Wrong third element type let wrongExit3 = "[\"exit\", 0, 123]" #expect(TerminalEvent(from: wrongExit3) == nil) // Too few elements let wrongExit4 = "[\"exit\", 0]" #expect(TerminalEvent(from: wrongExit4) == nil) // Too many elements let wrongExit5 = "[\"exit\", 0, \"session\", \"extra\"]" #expect(TerminalEvent(from: wrongExit5) == nil) } // MARK: - AsciinemaHeader Tests @Test("Encode and decode header") func encodeDecodeHeader() throws { let header = AsciinemaHeader( version: 2, width: 120, height: 40, timestamp: 1234567890.123, duration: 300.5, command: "/bin/zsh", title: "My Terminal Session", env: ["TERM": "xterm-256color", "USER": "testuser"] ) let encoder = JSONEncoder() let data = try encoder.encode(header) let decoder = JSONDecoder() let decodedHeader = try decoder.decode(AsciinemaHeader.self, from: data) #expect(decodedHeader.version == header.version) #expect(decodedHeader.width == header.width) #expect(decodedHeader.height == header.height) #expect(decodedHeader.timestamp == header.timestamp) #expect(decodedHeader.duration == header.duration) #expect(decodedHeader.command == header.command) #expect(decodedHeader.title == header.title) #expect(decodedHeader.env?["TERM"] == header.env?["TERM"]) #expect(decodedHeader.env?["USER"] == header.env?["USER"]) } // MARK: - TerminalInput Tests @Test("Create input from text") func createInputFromText() { let input = TerminalInput(text: "Hello, world!") #expect(input.text == "Hello, world!") } @Test("Create input from special keys") func createInputFromSpecialKeys() { // Arrow keys let arrowUp = TerminalInput(specialKey: .arrowUp) #expect(arrowUp.text == "\u{001B}[A") let arrowDown = TerminalInput(specialKey: .arrowDown) #expect(arrowDown.text == "\u{001B}[B") let arrowRight = TerminalInput(specialKey: .arrowRight) #expect(arrowRight.text == "\u{001B}[C") let arrowLeft = TerminalInput(specialKey: .arrowLeft) #expect(arrowLeft.text == "\u{001B}[D") // Special keys let escape = TerminalInput(specialKey: .escape) #expect(escape.text == "\u{001B}") let enter = TerminalInput(specialKey: .enter) #expect(enter.text == "\r") let tab = TerminalInput(specialKey: .tab) #expect(tab.text == "\t") // Control keys let ctrlC = TerminalInput(specialKey: .ctrlC) #expect(ctrlC.text == "\u{0003}") let ctrlD = TerminalInput(specialKey: .ctrlD) #expect(ctrlD.text == "\u{0004}") let ctrlZ = TerminalInput(specialKey: .ctrlZ) #expect(ctrlZ.text == "\u{001A}") let ctrlL = TerminalInput(specialKey: .ctrlL) #expect(ctrlL.text == "\u{000C}") let ctrlA = TerminalInput(specialKey: .ctrlA) #expect(ctrlA.text == "\u{0001}") let ctrlE = TerminalInput(specialKey: .ctrlE) #expect(ctrlE.text == "\u{0005}") // Web compatibility keys let ctrlEnter = TerminalInput(specialKey: .ctrlEnter) #expect(ctrlEnter.text == "ctrl_enter") let shiftEnter = TerminalInput(specialKey: .shiftEnter) #expect(shiftEnter.text == "shift_enter") } @Test("Encode and decode terminal input") func encodeDecodeTerminalInput() throws { let input = TerminalInput(text: "test input") let encoder = JSONEncoder() let data = try encoder.encode(input) let decoder = JSONDecoder() let decodedInput = try decoder.decode(TerminalInput.self, from: data) #expect(decodedInput.text == input.text) } // MARK: - TerminalResize Tests @Test("Create and encode terminal resize") func createEncodeTerminalResize() throws { let resize = TerminalResize(cols: 100, rows: 50) #expect(resize.cols == 100) #expect(resize.rows == 50) let encoder = JSONEncoder() let data = try encoder.encode(resize) let decoder = JSONDecoder() let decodedResize = try decoder.decode(TerminalResize.self, from: data) #expect(decodedResize.cols == resize.cols) #expect(decodedResize.rows == resize.rows) } @Test("Edge cases for terminal dimensions") func terminalDimensionEdgeCases() throws { // Minimum dimensions let minResize = TerminalResize(cols: 1, rows: 1) #expect(minResize.cols == 1) #expect(minResize.rows == 1) // Large dimensions let largeResize = TerminalResize(cols: 999, rows: 999) #expect(largeResize.cols == 999) #expect(largeResize.rows == 999) // Zero dimensions (though typically invalid in practice) let zeroResize = TerminalResize(cols: 0, rows: 0) #expect(zeroResize.cols == 0) #expect(zeroResize.rows == 0) } }