import XCTest @testable import CallsKit /// "Reconnecting…" only for a problem that lasts (spec §15.23). Paul, /// 2026-09-30: "I saw 'Reconnecting' appear occasionally over the video, /// but only for a second … I didn't notice any interruption." final class ReconnectWatchTests: XCTestCase { private typealias Peer = ReconnectWatch.Peer private let fine = Peer() /// Runs the watch at 5 Hz from `from` to `to` seconds, and returns the /// times at which the whole call's words were showing. private func run( _ watch: inout ReconnectWatch, from: Double, to: Double, stream: (Double) -> Bool, people: (Double) -> [String: Peer] ) -> (call: [Double], peers: [Double: Set]) { var call: [Double] = [] var peers: [Double: Set] = [:] var t = from while t < to { watch.observe(at: t, streamDown: stream(t), people: people(t)) if watch.call { call.append(t) } if !watch.peers.isEmpty { peers[t] = watch.peers } t = (t * 10 + 2).rounded() / 10 } return (call, peers) } func testARoutineStreamRenewalIsNeverShown() { // The stream ends on purpose and is back half a second later, over and // over, and a slower resume of three and a half seconds as well. var watch = ReconnectWatch() let seen = run( &watch, from: 0, to: 60, stream: { t in (10..<10.6).contains(t) || (30..<33.5).contains(t) || (50..<50.4).contains(t) }, people: { _ in ["sam": self.fine] }) XCTAssertEqual(seen.call, []) XCTAssertTrue(seen.peers.isEmpty) } func testAStreamDownFourSecondsIsShownAndHeldASecondPastRecovery() { var watch = ReconnectWatch() let seen = run(&watch, from: 0, to: 20, stream: { (5..<12).contains($0) }, people: { _ in ["sam": self.fine] }) XCTAssertEqual(seen.call.first, 9, "down at 5, said at 9") XCTAssertEqual(seen.call.last, 12.8, "back at 12, said until a second after") } func testShortBlipsOnOnePersonAreNeverShown() { var watch = ReconnectWatch() let seen = run( &watch, from: 0, to: 30, stream: { _ in false }, people: { t in ["sam": Peer(connectionBad: (5..<7.5).contains(t), audioMissing: (15..<16.5).contains(t))] }) XCTAssertEqual(seen.call, []) XCTAssertTrue(seen.peers.isEmpty, "\(seen.peers)") } func testOnePersonsLastingTroubleIsSaidOnTheirTileAlone() { var watch = ReconnectWatch() let seen = run( &watch, from: 0, to: 30, stream: { _ in false }, people: { t in ["sam": Peer(connectionBad: (5..<15).contains(t)), "ana": self.fine] }) XCTAssertEqual(seen.call, [], "not the whole call") let times = seen.peers.keys.sorted() XCTAssertEqual(times.first, 8, "disconnected at 5, said at 8") XCTAssertEqual(times.last, 15.8, "held a second past recovery") XCTAssertTrue(seen.peers.values.allSatisfy { $0 == ["sam"] }) } func testAudioThatStopsFromSomeoneUnmutedIsSaidAfterTwoSeconds() { var watch = ReconnectWatch() let seen = run( &watch, from: 0, to: 20, stream: { _ in false }, people: { t in ["sam": Peer(audioMissing: (5..<10).contains(t)), "ana": self.fine] }) XCTAssertEqual(seen.peers.keys.sorted().first, 7) } func testEveryonesTroubleIsTheWholeCall() { var watch = ReconnectWatch() let seen = run( &watch, from: 0, to: 20, stream: { _ in false }, people: { t in let bad = (5..<12).contains(t) return ["sam": Peer(connectionBad: bad), "ana": Peer(connectionBad: bad)] }) XCTAssertEqual(seen.call.first, 8) XCTAssertTrue(seen.peers.isEmpty, "not two tiles as well") } func testTheWords() { XCTAssertEqual(ReconnectWords.call, "Reconnecting…") XCTAssertEqual(ReconnectWords.peer("Samuel"), "Reconnecting to Samuel…") } func testTheNoticeFollowsTheWatchNotThePhase() { let notices = CallNotices.notices( phase: "admitted", status: "In the meeting", sharingScreen: false, screenNote: "", floor: false) XCTAssertFalse(notices.contains { $0.kind == .reconnecting }) } }