Files
Termind/TFMTests/SerialFeatureTests.swift
T
2026-09-21 18:30:39 +08:00

131 lines
4.8 KiB
Swift

import Foundation
import TermindApplication
import TermindDomain
import TermindInfrastructure
import TermindPersistence
import TermindPresentation
import TermindRemoteInfrastructure
import Testing
@testable import Termind
nonisolated private struct InertSerialConnection: SerialPortConnection {
func write(_: Data) async throws {}
func close() async {}
}
nonisolated private struct InertSerialService: SerialPortServing {
func availablePorts() async -> [SerialPortDescriptor] { [] }
func portUpdates() -> AsyncStream<[SerialPortDescriptor]> {
AsyncStream { $0.finish() }
}
func open(
_: SerialConnectionConfiguration,
receive _: @escaping @Sendable (Data) -> Void,
disconnected _: @escaping @Sendable (String) -> Void,
progress _: @escaping @Sendable (SerialPortOpenStage) -> Void
) async throws -> any SerialPortConnection {
InertSerialConnection()
}
}
@Suite
struct SerialFeatureTests {
@Test
func defaultConfigurationIs9600EightNOneWithRawCarriageReturn() {
var configuration = SerialConnectionConfiguration()
configuration.devicePath = "/dev/ttyUSB0"
#expect(configuration.isValid)
#expect(configuration.summary == "9600 8-N-1")
#expect(configuration.flowControl == .none)
#expect(configuration.enterKey == .carriageReturn)
#expect(!configuration.localEcho)
#expect(configuration.charset == .utf8)
#expect(configuration.transformInteractiveInput(Data("a\r".utf8)) == Data("a\r".utf8))
}
@Test
func enterMappingChangesOnlyCarriageReturnBytes() {
var configuration = SerialConnectionConfiguration()
configuration.enterKey = .lineFeed
#expect(
configuration.transformInteractiveInput(Data([0x41, 0x0D, 0x42, 0x0A])) == Data([0x41, 0x0A, 0x42, 0x0A]))
configuration.enterKey = .carriageReturnLineFeed
#expect(configuration.transformInteractiveInput(Data([0x0D])) == Data([0x0D, 0x0A]))
}
@Test
func validationAcceptsCustomAbsolutePathsAndRejectsBadBaudRates() {
var configuration = SerialConnectionConfiguration(devicePath: " /dev/ttyACM0 ")
#expect(configuration.isValid)
#expect(configuration.normalizedDevicePath == "/dev/ttyACM0")
configuration.baudRate = 0
#expect(!configuration.isValid)
configuration.baudRate = 4_000_001
#expect(!configuration.isValid)
}
@MainActor
@Test
func editableComboKeepsTypedValuesAndPresetMetadataDistinct() {
let option = SerialComboOption(value: "/dev/cu.usbserial-01", label: "USB UART — /dev/cu.usbserial-01")
#expect(option.id == option.value)
#expect(option.label.contains(option.value))
let rates = SerialConnectionConfiguration.commonBaudRates.map(String.init)
#expect(rates.contains("115200"))
#expect(rates.contains("4000000"))
}
@Test
func discoveryCollapsesCalloutAndDialInAliasesWithoutHidingCustomTTYPaths() {
let ports = SerialPortDiscovery.deduplicated([
SerialPortDescriptor(path: "/dev/tty.usbserial-01", name: "USB UART"),
SerialPortDescriptor(path: "/dev/cu.usbserial-01", name: "USB UART"),
SerialPortDescriptor(path: "/dev/cu.usbserial-01", name: "USB UART"),
SerialPortDescriptor(path: "/dev/ttyUSB0", name: "ttyUSB0"),
])
#expect(ports.map(\.path) == ["/dev/cu.usbserial-01", "/dev/ttyUSB0"])
}
@MainActor
@Test
func serialConnectionUsesTheSharedConnectionJourney() {
let journey = ConnectionJourney()
journey.beginSerial(name: "USB UART", address: "/dev/cu.usbserial-01")
#expect(journey.purpose == .serialSession)
#expect(journey.phase == .connecting)
#expect(journey.endpoints.count == 2)
#expect(journey.destination?.name == "USB UART")
#expect(journey.destination?.address == "/dev/cu.usbserial-01")
journey.fail("Device busy")
#expect(journey.phase == .failed("Device busy"))
}
@MainActor
@Test
func serialPaneParticipatesInSessionsAndFindButCannotBeDuplicated() {
let configuration = SerialConnectionConfiguration(
devicePath: "/dev/ttyUSB0", deviceName: "USB UART"
)
let pane = Pane.serialSession(configuration: configuration, service: InertSerialService())
let workspace = Workspace(pane: pane)
let window = WindowModel(workspaces: [workspace])
window.select(workspace.id)
#expect(pane.kind == .serialSession)
#expect(pane.serialSession?.title == "USB UART")
#expect(window.sessionPanes.map(\.pane.id) == [pane.id])
#expect(window.serialSessionPanes.map(\.pane.id) == [pane.id])
#expect(window.canFindInTerminal)
#expect(window.canSplitSelected)
#expect(!window.canDuplicateSelected)
}
}