import XCTest @testable import CallsKit /// " is connecting and can't hear you yet" (spec §15.24). Paul, /// 2026-10-01, asking for Zoom's "… is connecting to audio and cannot hear /// you yet": the gap after someone joins, said rather than talked into. final class ConnectingWatchTests: XCTestCase { private typealias Peer = ConnectingWatch.Peer private let waiting = Peer(epoch: 1, hearsMe: false, reported: true, connected: false, acked: false) /// Runs the watch at 5 Hz from `from` to `to` seconds; when Sam's words /// showed, and when they cleared with "can hear you now". `sam` nil is /// Sam not in the roster. private func run( _ watch: inout ConnectingWatch, from: Double, to: Double, sam: (Double) -> Peer? ) -> (shown: [Double], cleared: [Double]) { var shown: [Double] = [] var cleared: [Double] = [] var t = from while t < to { let people = sam(t).map { ["sam": $0] } ?? [:] if watch.observe(at: t, people: people).contains("sam") { cleared.append(t) } if watch.shown.contains("sam") { shown.append(t) } t = (t * 10 + 2).rounded() / 10 } return (shown, cleared) } func testTheTimingsAreTheOnesTheSpecNames() { XCTAssertEqual(ConnectingWatch.grace, 1) XCTAssertEqual(ConnectingWatch.cap, 20) } func testAnOrdinaryJoinHeardWithinTheGraceSaysNothing() { var watch = ConnectingWatch() var sam = waiting let seen = run(&watch, from: 0, to: 10) { t in sam.connected = t >= 0.4 sam.hearsMe = t >= 0.8 return sam } XCTAssertEqual(seen.shown, []) XCTAssertEqual(seen.cleared, [], "nothing was said, so \"can hear you now\" is not said either") XCTAssertFalse(watch.joining.contains("sam")) } func testASlowJoinIsSaidAfterTheGraceAndClearsWhenTheyHearYou() { var watch = ConnectingWatch() var sam = waiting let seen = run(&watch, from: 0, to: 12) { t in sam.connected = t >= 5 sam.hearsMe = t >= 6 return sam } XCTAssertEqual(seen.shown.first, 1) XCTAssertEqual(seen.shown.last, 5.8) XCTAssertEqual(seen.cleared, [6]) } func testOnceClearedItStaysClearedWhateverTheirListDoes() { var watch = ConnectingWatch() var sam = waiting sam.connected = true let seen = run(&watch, from: 0, to: 30) { t in sam.hearsMe = t >= 3 && t < 10 return sam } XCTAssertEqual(seen.cleared, [3]) XCTAssertEqual(seen.shown.last, 2.8) } func testItIsGivenUpAtTheCapSilently() { var watch = ConnectingWatch() let seen = run(&watch, from: 0, to: 40) { _ in self.waiting } XCTAssertEqual(seen.shown.first, 1) XCTAssertEqual(seen.shown.last, 19.8) XCTAssertEqual(seen.cleared, [], "a cap is not their hearing you") XCTAssertTrue(watch.joining.isEmpty) } func testAClientThatDoesNotSayIsLetGoOnItsFirstReportAndAConnection() { var watch = ConnectingWatch() let seen = run(&watch, from: 0, to: 10) { t in Peer(epoch: 1, hearsMe: nil, reported: t >= 4, connected: t >= 2, acked: false) } XCTAssertEqual(seen.cleared, [4]) } func testOrOnAConnectionAndReceiverReportsShowingYourAudioReachesThem() { var watch = ConnectingWatch() let seen = run(&watch, from: 0, to: 10) { t in Peer(epoch: 1, hearsMe: nil, reported: false, connected: t >= 2, acked: t >= 3) } XCTAssertEqual(seen.cleared, [3]) var unconnected = ConnectingWatch() let never = run(&unconnected, from: 0, to: 10) { _ in Peer(epoch: 1, hearsMe: nil, reported: true, connected: false, acked: true) } XCTAssertEqual(never.cleared, [], "never without a connection") } func testAClientThatSaysWhoItHearsIsBelievedOverYourOwnEvidence() { var watch = ConnectingWatch() let seen = run(&watch, from: 0, to: 10) { _ in Peer(epoch: 1, hearsMe: false, reported: true, connected: true, acked: true) } XCTAssertEqual(seen.cleared, []) XCTAssertFalse(seen.shown.isEmpty) } func testARejoinStartsItAgainAndSoDoesANewConnection() { var watch = ConnectingWatch() var sam = waiting let seen = run(&watch, from: 0, to: 20) { t in if t >= 5 && t < 6 { return nil } sam.connected = true sam.hearsMe = t < 5 ? t >= 2 : t >= 9 return sam } XCTAssertEqual(seen.cleared, [2, 9]) XCTAssertTrue(seen.shown.contains(7)) XCTAssertFalse(seen.shown.contains(6.8), "the grace again from the moment they are back") var rebuilt = ConnectingWatch() var again = waiting again.connected = true let second = run(&rebuilt, from: 0, to: 20) { t in again.epoch = t < 8 ? 1 : 2 again.hearsMe = t < 1 || t >= 12 return again } XCTAssertEqual(second.cleared, [12]) XCTAssertEqual(second.shown.first, 9) } func testAMoreSpecificReportReplacesConnectingAndReconnectingWaitsUntilAfter() { let plain = PeerMedia(muted: false, cameraOff: false, hearing: []) func tile( _ media: PeerMedia, muted: Bool = false, joining: Bool = true, shown: Bool = true, reconnecting: Bool = false ) -> (notices: [String], connecting: String?) { ConnectingWatch.tile( name: "Sam", media: media, me: "me", muted: muted, joining: joining, shown: shown, reconnecting: reconnecting) } XCTAssertNil(tile(plain, shown: false).connecting, "inside the grace") XCTAssertEqual(tile(plain).connecting, "Sam is connecting and can’t hear you yet") XCTAssertEqual(tile(plain).notices, []) XCTAssertNotNil(tile(plain, muted: true).connecting, "your own mute does not matter") XCTAssertEqual( tile(plain, reconnecting: true).notices, [], "a connection that has never been up is not reconnecting") let blocked = PeerMedia(muted: false, cameraOff: false, audioBlocked: true, hearing: []) XCTAssertEqual(tile(blocked, shown: false).notices, [AudioWords.blocked("Sam")], "even inside the grace") XCTAssertNil(tile(blocked).connecting) let silent = PeerMedia(muted: false, cameraOff: false, noSoundFrom: ["me"], hearing: ["me"]) XCTAssertEqual(tile(silent).notices, [AudioWords.notHearingYou("Sam")]) XCTAssertNil(tile(silent).connecting) XCTAssertNotNil( tile(PeerMedia(muted: false, cameraOff: false, noSoundFrom: ["me"]), muted: true).connecting, "no sound from you while you are muted is expected, so it replaces nothing") XCTAssertEqual( tile(plain, joining: false, shown: false, reconnecting: true).notices, [ReconnectWords.peer("Sam")]) XCTAssertEqual( tile(blocked, joining: false, shown: false, reconnecting: true).notices, [ReconnectWords.peer("Sam"), AudioWords.blocked("Sam")]) XCTAssertEqual(tile(plain, joining: false, shown: false).notices, []) } func testHearsYou() { XCTAssertEqual(HearingRule.hearsYou(PeerMedia(muted: false, cameraOff: false, hearing: ["me"]), me: "me"), true) XCTAssertEqual(HearingRule.hearsYou(PeerMedia(muted: false, cameraOff: false, hearing: []), me: "me"), false) XCTAssertNil(HearingRule.hearsYou(PeerMedia(muted: false, cameraOff: false), me: "me")) XCTAssertNil(HearingRule.hearsYou(nil, me: "me")) } /// This app's own half: when a path counts as heard. A muted sender must /// not look unheard. func testHeardIsArrivingAndDecodedOrAMutedSenderOnALiveConnection() { let base = HearingRule.Facts( connected: true, bad: false, hasReceiver: true, receiverLive: true, packets: 0, decoded: 0, senderMuted: false) func with(_ change: (inout HearingRule.Facts) -> Void) -> HearingRule.Facts { var f = base change(&f) return f } XCTAssertFalse(HearingRule.heard(was: false, base), "nothing has arrived") XCTAssertFalse(HearingRule.heard(was: false, with { $0.packets = 10 }), "packets, nothing decoded") XCTAssertTrue(HearingRule.heard(was: false, with { $0.packets = 10; $0.decoded = 3 })) XCTAssertFalse( HearingRule.heard(was: false, with { $0.packets = 10; $0.decoded = 3; $0.connected = false }), "not before the connection is up") XCTAssertTrue(HearingRule.heard(was: false, with { $0.senderMuted = true }), "a muted sender") XCTAssertTrue( HearingRule.heard(was: false, with { $0.senderMuted = true; $0.hasReceiver = false; $0.receiverLive = false }), "nor one with no microphone at all") XCTAssertFalse( HearingRule.heard(was: false, with { $0.senderMuted = true; $0.receiverLive = false }), "but not a dead receiver") XCTAssertTrue(HearingRule.heard(was: true, base), "once heard, kept") XCTAssertTrue(HearingRule.heard(was: true, with { $0.connected = false }), "a blip is not losing them") XCTAssertFalse(HearingRule.heard(was: true, with { $0.bad = true }), "a failed connection is") } func testTheHearingListDecodesAndAbsentIsNotEmpty() throws { func media(_ json: String) throws -> PeerMedia? { let state = try JSONDecoder().decode( RoomState.self, from: Data( #"{"phase":"admitted","peer":"me","boot":"b","waiting":[],"peers":[{"peer":"sam","name":"Sam","kind":"member","gen":1,"media":\#(json)}]}"# .utf8)) return Call.peerMedia("sam", in: state) } XCTAssertEqual(try media(#"{"muted":false,"cameraOff":false,"hearing":["me"]}"#)?.hearing, ["me"]) XCTAssertEqual(try media(#"{"muted":false,"cameraOff":false,"hearing":[]}"#)?.hearing, []) XCTAssertNil(try media(#"{"muted":false,"cameraOff":false}"#)?.hearing) } func testTheDotsTakeTurnsAndNeverGoOut() { for dot in 0..<3 { for step in 0..<28 { let o = ConnectingText.opacity(at: Double(step) * 0.05, dot: dot) XCTAssertGreaterThanOrEqual(o, 0.35) XCTAssertLessThanOrEqual(o, 1.0001) } } XCTAssertEqual(ConnectingText.opacity(at: 0.56, dot: 0), 1, accuracy: 0.001) XCTAssertEqual(ConnectingText.opacity(at: 0.76, dot: 1), 1, accuracy: 0.001) XCTAssertEqual(ConnectingText.opacity(at: 0.96, dot: 2), 1, accuracy: 0.001) } func testAHearingReportGoesForAGainOrALossOfSomeoneStillHereAndNotForADeparture() { let here: Set = ["a", "b"] XCTAssertTrue(HearingRule.moved(from: [], to: ["a"], present: here)) XCTAssertTrue(HearingRule.moved(from: ["a"], to: ["a", "b"], present: here)) XCTAssertTrue(HearingRule.moved(from: ["a", "b"], to: ["a"], present: here)) XCTAssertFalse(HearingRule.moved(from: ["a", "c"], to: ["a"], present: here), "c has left") XCTAssertFalse(HearingRule.moved(from: ["a"], to: ["a"], present: here)) } }