import Testing import Foundation @testable import TXCore @Suite("tmux control-mode 解析器") struct TmuxControlParserTests { @Test("命令应答块按序号关联") func commandBlock() { let p = TmuxControlParser() let events = p.feed("%begin 1678 7 1\nhello\nworld\n%end 1678 7 1\n") #expect(events.count == 1) guard case .commandResponse(let r) = events[0] else { Issue.record("期望 commandResponse"); return } #expect(r.number == 7) #expect(r.isError == false) #expect(r.lines == ["hello", "world"]) } @Test("%error 块标记为错误") func errorBlock() { let p = TmuxControlParser() let events = p.feed("%begin 1 3 1\nno server\n%error 1 3 1\n") guard case .commandResponse(let r) = events.first else { Issue.record("期望 commandResponse"); return } #expect(r.isError) #expect(r.number == 3) } @Test("块内以 % 开头的行不解释为通知") func percentInsideBlock() { let p = TmuxControlParser() let events = p.feed("%begin 1 1 1\n%output not-a-notification\n%end 1 1 1\n") #expect(events.count == 1) guard case .commandResponse(let r) = events.first else { Issue.record("期望 commandResponse"); return } #expect(r.lines == ["%output not-a-notification"]) } @Test("%output 解析出 pane 与字节") func output() { let p = TmuxControlParser() let events = p.feed("%output %2 ready\\012\n") guard case .output(let pane, let data) = events.first else { Issue.record("期望 output"); return } #expect(pane == TmuxPaneID(2)) #expect(data == Data("ready\n".utf8)) } @Test("跨 feed 的残行被正确缓冲") func splitAcrossFeeds() { let p = TmuxControlParser() #expect(p.feed("%output %0 par").isEmpty) // 无换行 → 无事件 let events = p.feed("tial\n") guard case .output(_, let data) = events.first else { Issue.record("期望 output"); return } #expect(data == Data("partial".utf8)) } @Test("CRLF 行尾被容忍") func crlf() { let p = TmuxControlParser() let events = p.feed("%window-add @5\r\n") #expect(events == [.windowAdd(TmuxWindowID(5))]) } @Test("窗口生命周期通知") func windowNotifications() { let p = TmuxControlParser() #expect(p.feed("%window-add @1\n") == [.windowAdd(TmuxWindowID(1))]) #expect(p.feed("%window-close @1\n") == [.windowClose(TmuxWindowID(1))]) #expect(p.feed("%window-renamed @2 my shell\n") == [.windowRenamed(TmuxWindowID(2), name: "my shell")]) } @Test("会话与布局通知") func sessionAndLayout() { let p = TmuxControlParser() #expect(p.feed("%session-changed $1 main\n") == [.sessionChanged(TmuxSessionID(1), name: "main")]) let layout = p.feed("%layout-change @3 bc62,80x24,0,0,0\n") #expect(layout == [.layoutChange(window: TmuxWindowID(3), layout: "bc62,80x24,0,0,0", visibleLayout: nil)]) } @Test("流控与退出") func flowControlAndExit() { let p = TmuxControlParser() #expect(p.feed("%pause %4\n") == [.pause(TmuxPaneID(4))]) #expect(p.feed("%continue %4\n") == [.unpause(TmuxPaneID(4))]) #expect(p.feed("%exit\n") == [.exit(reason: nil)]) #expect(p.feed("%exit server exited\n") == [.exit(reason: "server exited")]) } @Test("未知通知降级为 .unknown 而非崩溃") func unknownNotification() { let p = TmuxControlParser() let events = p.feed("%some-future-notification foo bar\n") #expect(events == [.unknown(line: "%some-future-notification foo bar")]) } @Test("attach 后的隐式空块") func implicitEmptyBlock() { let p = TmuxControlParser() let events = p.feed("%begin 100 0 0\n%end 100 0 0\n") guard case .commandResponse(let r) = events.first else { Issue.record("期望空 commandResponse"); return } #expect(r.lines.isEmpty) #expect(r.number == 0) } }