import SwiftUI import XCTest @testable @_spi(Fixtures) import CallsKit /// The full-window call's rules: when its controls show and hide, where /// the self view settles, which notices outlive the controls, and how a /// grid fits. final class ControlsVisibilityTests: XCTestCase { typealias V = ControlsVisibility func testStartsVisibleAndHidesAfterTheDelay() { var pointer = V(input: .pointer, now: 0) XCTAssertTrue(pointer.visible) XCTAssertEqual(pointer.hideAt, 3) pointer.tick(now: 2.9) XCTAssertTrue(pointer.visible, "not before the delay") pointer.tick(now: 3) XCTAssertFalse(pointer.visible) XCTAssertNil(pointer.hideAt) var touch = V(input: .touch, now: 10) XCTAssertEqual(touch.hideAt, 13) touch.tick(now: 12.9) XCTAssertTrue(touch.visible) touch.tick(now: 13) XCTAssertFalse(touch.visible) } /// Paul, on 0.3.4: "it should hang around for 2–3 seconds before /// fading away". func testTheTimings() { XCTAssertEqual(V.pointerDelay, 3) XCTAssertEqual(V.touchDelay, 3) XCTAssertEqual(V.pointerExitDelay, 1.5) XCTAssertEqual(V.fadeOutDuration, 0.35) XCTAssertEqual(V.fadeDuration(showing: false, reduceMotion: false), 0.35) XCTAssertEqual(V.fadeDuration(showing: true, reduceMotion: false), V.fadeInDuration) } func testPointerMovementShowsAndRestartsTheWait() { var state = V(input: .pointer, now: 0) state.tick(now: 3) XCTAssertFalse(state.visible) state.pointerMoved(now: 4) XCTAssertTrue(state.visible) XCTAssertEqual(state.hideAt, 7) state.pointerMoved(now: 5) state.tick(now: 7) XCTAssertTrue(state.visible, "movement at 5 pushed the hide to 8") state.tick(now: 8) XCTAssertFalse(state.visible) } func testLeavingTheWindowLingersThenHides() { var state = V(input: .pointer, now: 0) state.pointerMoved(now: 0.2) state.pointerExited(now: 0.3) XCTAssertTrue(state.visible, "not at once") XCTAssertEqual(state.hideAt, 1.8, "1.5 s after leaving, sooner than the 3 s wait") XCTAssertFalse(state.pointerInside) state.tick(now: 1.7) XCTAssertTrue(state.visible) state.tick(now: 1.8) XCTAssertFalse(state.visible) XCTAssertFalse(state.hidesPointer, "no pointer to hide outside the window") } func testLeavingTheWindowNeverLengthensTheWait() { var state = V(input: .pointer, now: 0) state.pointerMoved(now: 0) state.pointerExited(now: 2.5) XCTAssertEqual(state.hideAt, 3, "already due at 3") state.tick(now: 3) XCTAssertFalse(state.visible) state.pointerExited(now: 4) XCTAssertFalse(state.visible, "leaving does not bring hidden controls back") XCTAssertNil(state.hideAt) } func testComingBackBeforeTheLingerEndsKeepsThem() { var state = V(input: .pointer, now: 0) state.pointerExited(now: 1) state.pointerMoved(now: 2) state.tick(now: 2.5) XCTAssertTrue(state.visible) XCTAssertEqual(state.hideAt, 5) } func testLeavingTheWindowDoesNotHideHeldControls() { var state = V(input: .pointer, now: 0) state.set(.menuOpen, true, now: 0.1) state.pointerExited(now: 0.2) XCTAssertTrue(state.visible, "a menu is open") state.set(.menuOpen, false, now: 3) XCTAssertTrue(state.visible, "letting go starts the wait rather than hiding at once") XCTAssertEqual(state.hideAt, 6) state.tick(now: 6) XCTAssertFalse(state.visible) } func testLeavingTheWindowIsLeavingTheBar() { var state = V(input: .pointer, now: 0) state.set(.pointerOverControls, true, now: 0.1) state.pointerExited(now: 0.2) XCTAssertFalse(state.holds.contains(.pointerOverControls)) XCTAssertEqual(state.hideAt, 1.7, "the bar no longer holds them; they linger, then go") state.tick(now: 1.7) XCTAssertFalse(state.visible) } /// Every rule that keeps the controls up does so for as long as it /// lasts, however long that is, and each lets go on its own. func testEveryHoldKeepsTheControlsUp() { let holds: [V.Holds] = [.pointerOverControls, .menuOpen, .keyboardFocus, .voiceOver, .alert] for input in [V.Input.pointer, .touch] { for hold in holds { var state = V(input: input, now: 0) state.set(hold, true, now: 0.5) XCTAssertNil(state.hideAt, "\(hold) \(input)") state.tick(now: 1000) XCTAssertTrue(state.visible, "\(hold) \(input) holds") state.set(hold, false, now: 1000) state.tick(now: 1000 + state.delay) XCTAssertFalse(state.visible, "\(hold) \(input) lets go") } } } func testAHoldShowsHiddenControls() { var state = V(input: .pointer, now: 0) state.tick(now: 5) XCTAssertFalse(state.visible) state.set(.voiceOver, true, now: 6) XCTAssertTrue(state.visible, "VoiceOver starting brings the controls back") state = V(input: .pointer, now: 0) state.tick(now: 5) state.set(.keyboardFocus, true, now: 6) XCTAssertTrue(state.visible, "focus moving into the bar shows it") } func testOneHoldEndingLeavesTheOthers() { var state = V(input: .pointer, now: 0) state.set(.menuOpen, true, now: 0) state.set(.alert, true, now: 0) state.set(.menuOpen, false, now: 1) state.tick(now: 100) XCTAssertTrue(state.visible, "the alert still holds") state.set(.alert, false, now: 100) state.tick(now: 103) XCTAssertFalse(state.visible) } func testReleasingAHoldThatWasNotHeldChangesNothing() { var state = V(input: .pointer, now: 0) state.tick(now: 3) state.set(.menuOpen, false, now: 4) XCTAssertFalse(state.visible) XCTAssertNil(state.hideAt) } func testVoiceOverNeverLetsATapHideTheControls() { var state = V(input: .touch, now: 0) state.set(.voiceOver, true, now: 0) state.tapped(now: 1) XCTAssertTrue(state.visible) state.tick(now: 1000) XCTAssertTrue(state.visible) var pointer = V(input: .pointer, now: 0) pointer.set(.voiceOver, true, now: 0) pointer.pointerExited(now: 1) pointer.tick(now: 1000) XCTAssertTrue(pointer.visible, "nor the pointer leaving") } func testATapToggles() { var state = V(input: .touch, now: 0) state.tapped(now: 0.5) XCTAssertFalse(state.visible, "a tap on showing controls hides them") state.tapped(now: 1) XCTAssertTrue(state.visible) XCTAssertEqual(state.hideAt, 4) state.tick(now: 3.9) XCTAssertTrue(state.visible) state.tick(now: 4) XCTAssertFalse(state.visible) } func testATapOnHeldControlsKeepsThem() { var state = V(input: .touch, now: 0) state.set(.menuOpen, true, now: 0) state.tapped(now: 1) XCTAssertTrue(state.visible) XCTAssertNil(state.hideAt) } func testActivityShowsAndRestarts() { var state = V(input: .pointer, now: 0) state.tick(now: 3) state.activity(now: 4) // Tab, say XCTAssertTrue(state.visible) XCTAssertEqual(state.hideAt, 7) } func testThePointerHidesWithTheControlsOnlyOverTheCall() { var state = V(input: .pointer, now: 0) state.pointerMoved(now: 0) XCTAssertFalse(state.hidesPointer, "controls showing") state.tick(now: 3) XCTAssertTrue(state.hidesPointer) state.pointerMoved(now: 4) XCTAssertFalse(state.hidesPointer, "movement brings it back") var touch = V(input: .touch, now: 0) touch.tick(now: 5) XCTAssertFalse(touch.hidesPointer) } func testReduceMotionIsAShortCrossFade() { XCTAssertLessThanOrEqual(V.fadeDuration(showing: true, reduceMotion: true), 0.1) XCTAssertLessThanOrEqual(V.fadeDuration(showing: false, reduceMotion: true), 0.1) XCTAssertGreaterThan(V.fadeDuration(showing: false, reduceMotion: false), 0.1) XCTAssertLessThan( V.fadeDuration(showing: true, reduceMotion: false), V.fadeDuration(showing: false, reduceMotion: false), "they arrive faster than they go") } @MainActor func testTheModelHidesOnItsOwnClock() async throws { let model = ControlsVisibilityModel(input: .pointer) XCTAssertTrue(model.visible) try await Task.sleep(for: .seconds(2)) XCTAssertTrue(model.visible, "still up two seconds in") try await Task.sleep(for: .seconds(1.3)) XCTAssertFalse(model.visible, "hidden by its own timer") model.pointerMoved() XCTAssertTrue(model.visible) model.set(.menuOpen, true) try await Task.sleep(for: .seconds(3.3)) XCTAssertTrue(model.visible, "held") } @MainActor func testAPinnedModelIgnoresInput() { let hidden = ControlsVisibilityModel(input: .touch, pinned: false) hidden.tapped() XCTAssertFalse(hidden.visible) let shown = ControlsVisibilityModel(input: .pointer, pinned: true) shown.pointerExited() XCTAssertTrue(shown.visible) } /// The traffic lights go and come back with the controls, and are left /// alone in full screen. func testTheWindowButtonsFollowTheControls() { var state = V(input: .pointer, now: 0) XCTAssertTrue(state.showsWindowButtons(fullScreen: false)) state.tick(now: 3) XCTAssertFalse(state.showsWindowButtons(fullScreen: false), "gone with the controls") XCTAssertTrue(state.showsWindowButtons(fullScreen: true), "full screen's are the system's") state.pointerMoved(now: 4) XCTAssertTrue(state.showsWindowButtons(fullScreen: false), "back as soon as the pointer moves") state.set(.menuOpen, true, now: 4) state.tick(now: 100) XCTAssertTrue(state.showsWindowButtons(fullScreen: false), "held up with the controls") } #if os(macOS) /// The drag strip's double click follows System Settings. func testTitleBarDoubleClickFollowsTheSetting() { XCTAssertEqual(TitleBarDoubleClick.from(action: nil, miniaturize: nil), .zoom, "the default") XCTAssertEqual(TitleBarDoubleClick.from(action: "Maximize", miniaturize: nil), .zoom) XCTAssertEqual(TitleBarDoubleClick.from(action: "Fill", miniaturize: nil), .zoom) XCTAssertEqual(TitleBarDoubleClick.from(action: "Minimize", miniaturize: nil), .minimize) XCTAssertEqual(TitleBarDoubleClick.from(action: "None", miniaturize: true), .none) XCTAssertEqual(TitleBarDoubleClick.from(action: nil, miniaturize: true), .minimize) } #endif @MainActor func testAPinnedModelPinsTheWindowButtons() { XCTAssertFalse( ControlsVisibilityModel(input: .pointer, pinned: false).showsWindowButtons(fullScreen: false)) XCTAssertTrue( ControlsVisibilityModel(input: .pointer, pinned: true).showsWindowButtons(fullScreen: false)) } } /// WCAG 2.2 §1.4.11: the overlay's symbols and text against what they sit /// on, at 3:1 or better, over the worst video there is for each — pure /// white for anything light on the dark surfaces — as well as over black /// and mid-grey. final class CallOverlayContrastTests: XCTestCase { typealias RGB = (r: Double, g: Double, b: Double) /// Gamma-encoded sRGB components, which is what the compositor blends /// and what WCAG's formula starts from. private func rgb(_ color: Color) -> (RGB, Double) { let c = color.resolve(in: EnvironmentValues()) return ((Double(c.red), Double(c.green), Double(c.blue)), Double(c.opacity)) } private func over(_ top: Color, _ bottom: RGB) -> RGB { let (t, a) = rgb(top) return (t.r * a + bottom.r * (1 - a), t.g * a + bottom.g * (1 - a), t.b * a + bottom.b * (1 - a)) } private func luminance(_ c: RGB) -> Double { func linear(_ v: Double) -> Double { v <= 0.04045 ? v / 12.92 : pow((v + 0.055) / 1.055, 2.4) } return 0.2126 * linear(c.r) + 0.7152 * linear(c.g) + 0.0722 * linear(c.b) } private func ratio(_ a: RGB, _ b: RGB) -> Double { let (x, y) = (luminance(a), luminance(b)) return (max(x, y) + 0.05) / (min(x, y) + 0.05) } func testEverythingOnTheOverlayIsAtLeastThreeToOne() { // The formula itself, against WebAIM's figures. XCTAssertEqual(ratio((1, 1, 1), (0, 0, 0)), 21, accuracy: 0.01) XCTAssertEqual(ratio((1, 1, 1), (0x77 / 255.0, 0x77 / 255.0, 0x77 / 255.0)), 4.48, accuracy: 0.01) let white: RGB = (1, 1, 1) for (name, video) in [("white", white), ("black", (0, 0, 0)), ("grey", (0.2, 0.2, 0.2))] as [(String, RGB)] { // The black alone, as if neither glass nor blur were drawn: the // rendered glass over white measures darker than this (#5B5B5B). let surface = over(Color.black.opacity(CallOverlay.scrim), video) let hovered = over(CallOverlay.hoverFill, surface) let checks: [(String, Double, Double)] = [ ("white on the surface (a resting glyph, a name, the title)", ratio(white, surface), 6), ("a white glyph under the pointer", ratio(white, hovered), 4.5), ("secondary text", ratio(over(CallOverlay.secondaryText, surface), surface), 4.5), ("alert text", ratio(rgb(CallOverlay.alertText).0, surface), 4.5), ("an on capsule against the pane", ratio(rgb(CallOverlay.onFill).0, surface), 3), ("an on capsule's symbol", ratio(rgb(CallOverlay.onSymbol).0, rgb(CallOverlay.onFill).0), 4.5), ("white on amber (off)", ratio(white, rgb(CallOverlay.offFill).0), 4.5), ("white on Leave's red", ratio(white, rgb(CallOverlay.leaveFill).0), 4.5), ("the unread badge", ratio(white, rgb(CallOverlay.badgeFill).0), 4.5), ] for (what, measured, least) in checks { XCTAssertGreaterThanOrEqual(measured, least, "\(what) over \(name) video: \(measured)") } } } /// The Devices panel over the picture: opaque, so the video never /// enters into it, with the dark appearance's own label colours. func testTheDevicesPanelIsReadable() { let panel = rgb(CallOverlay.panelFill).0 XCTAssertEqual(rgb(CallOverlay.panelFill).1, 1, "opaque") XCTAssertGreaterThanOrEqual(ratio((1, 1, 1), panel), 15, "primary text") // secondaryLabelColor in the dark appearance: white at 55 %. let secondary = over(Color.white.opacity(0.55), panel) XCTAssertGreaterThanOrEqual(ratio(secondary, panel), 4.5, "secondary text") } } final class PiPLayoutTests: XCTestCase { let stage = CGSize(width: 1000, height: 600) func testEachCornerSitsTheMarginIn() { let size = CGSize(width: 200, height: 112) let m = PiPLayout.margin XCTAssertEqual( PiPLayout.frame(corner: .topLeading, size: size, in: stage).origin, CGPoint(x: m, y: m)) XCTAssertEqual( PiPLayout.frame(corner: .topTrailing, size: size, in: stage).origin, CGPoint(x: 1000 - m - 200, y: m)) XCTAssertEqual( PiPLayout.frame(corner: .bottomLeading, size: size, in: stage).origin, CGPoint(x: m, y: 600 - m - 112)) XCTAssertEqual( PiPLayout.frame(corner: .bottomTrailing, size: size, in: stage).origin, CGPoint(x: 1000 - m - 200, y: 600 - m - 112)) } func testInsetsKeepItClearOfTheBarAndSidebar() { let size = CGSize(width: 200, height: 112) let insets = EdgeInsets(top: 32, leading: 0, bottom: 80, trailing: 332) let frame = PiPLayout.frame(corner: .bottomTrailing, size: size, in: stage, insets: insets) XCTAssertEqual(frame.maxY, 600 - 80 - PiPLayout.margin, "above the bar") XCTAssertEqual(frame.maxX, 1000 - 332 - PiPLayout.margin, "beside the chat") let top = PiPLayout.frame(corner: .topLeading, size: size, in: stage, insets: insets) XCTAssertEqual(top.minY, 32 + PiPLayout.margin, "under the title bar") } func testATinyStageStillHoldsIt() { let frame = PiPLayout.frame( corner: .bottomTrailing, size: CGSize(width: 200, height: 112), in: CGSize(width: 150, height: 100), insets: EdgeInsets(top: 0, leading: 0, bottom: 300, trailing: 300)) XCTAssertGreaterThanOrEqual(frame.minX, 0) XCTAssertGreaterThanOrEqual(frame.minY, 0) } func testADropSettlesInTheQuarterItWasHeadedFor() { XCTAssertEqual(PiPLayout.corner(droppedAt: CGPoint(x: 100, y: 100), in: stage), .topLeading) XCTAssertEqual(PiPLayout.corner(droppedAt: CGPoint(x: 900, y: 100), in: stage), .topTrailing) XCTAssertEqual(PiPLayout.corner(droppedAt: CGPoint(x: 100, y: 500), in: stage), .bottomLeading) XCTAssertEqual(PiPLayout.corner(droppedAt: CGPoint(x: 900, y: 500), in: stage), .bottomTrailing) } func testAFlickCarriesItToTheCornerItWasThrownAt() { // Let go just left of centre, moving fast to the right and up. let corner = PiPLayout.corner( droppedAt: CGPoint(x: 480, y: 320), predicted: CGPoint(x: 1300, y: -80), in: stage) XCTAssertEqual(corner, .topTrailing) } func testSizes() { let mac = PiPLayout.size(in: stage, touch: false) XCTAssertEqual(mac.width, 200) XCTAssertEqual(mac.height, 113) XCTAssertEqual(PiPLayout.size(in: CGSize(width: 3000, height: 2000), touch: false).width, 260) XCTAssertEqual(PiPLayout.size(in: CGSize(width: 600, height: 400), touch: false).width, 150) let portrait = PiPLayout.size(in: CGSize(width: 390, height: 844), touch: true) XCTAssertGreaterThan(portrait.height, portrait.width, "upright on an upright phone") let landscape = PiPLayout.size(in: CGSize(width: 844, height: 390), touch: true) XCTAssertGreaterThan(landscape.width, landscape.height) } } final class CallNoticesTests: XCTestCase { func testAnOrdinaryCallHasNothingToSay() { XCTAssertEqual( CallNotices.notices( phase: "admitted", status: "In the meeting", sharingScreen: false, screenNote: "", floor: false), []) } func testWhatStaysAndWhatFades() { let notices = CallNotices.notices( phase: "reconnecting", status: "Reconnecting…", sharingScreen: true, screenNote: "", floor: true) XCTAssertEqual(notices.map(\.kind), [.reconnecting, .sharing, .floor]) XCTAssertEqual(notices.map(\.important), [true, true, false], "the relay line fades") } /// The title pill (until 2026-10-02) said how the call was going beside /// the title. It is gone, so every state it carried has to be a notice, /// and one that stays whatever the controls do. func testEveryStateThePillCarriedIsANotice() { let states: [(phase: String?, status: String, kind: CallNotice.Kind)] = [ (nil, "Connecting…", .connecting), ("connecting", "Connecting…", .connecting), ("waiting", "Waiting to be let in", .waitingToJoin), ("full", "The meeting is full. Try joining again shortly.", .full), ("reconnecting", "Reconnecting…", .reconnecting), ("ended", "This meeting has ended", .ended), ] for state in states { let notices = CallNotices.notices( phase: state.phase, status: state.status, sharingScreen: false, screenNote: "", floor: false) XCTAssertEqual(notices.map(\.kind), [state.kind], "\(state)") XCTAssertEqual(notices.first?.text, state.status) XCTAssertEqual(notices.first?.important, true, "\(state) stays with the controls hidden") } } /// Anything the call says that is not the quiet "In the meeting" is a /// notice now — not only what began "Could not", which was all the pill /// left to the notices before. func testEveryOtherStatusIsAProblemNotice() { for status in [ "Could not send to the meeting. Try again.", "A participant’s media could not connect.", "Keep the message within 2 KB.", ] { let notices = CallNotices.notices( phase: "admitted", status: status, sharingScreen: false, screenNote: "", floor: false) XCTAssertEqual(notices.map(\.kind), [.problem], status) XCTAssertEqual(notices.first?.dismissible, true) } for quiet in ["In the meeting", "Ready to join", "You left the meeting", ""] { XCTAssertEqual( CallNotices.notices( phase: nil, status: quiet, sharingScreen: false, screenNote: "", floor: false), [], quiet) } } func testEndedFullAndWaitingAreImportant() { for (phase, kind) in [ ("ended", CallNotice.Kind.ended), ("full", .full), ("waiting", .waitingToJoin), ] { let notice = CallNotices.notices( phase: phase, status: "x", sharingScreen: false, screenNote: "", floor: false ).first XCTAssertEqual(notice?.kind, kind) XCTAssertEqual(notice?.important, true) } } func testProblemsAreImportantAndDismissible() { let notices = CallNotices.notices( phase: "admitted", status: "Could not send to the meeting. Try again.", sharingScreen: false, screenNote: "Screen sharing was not allowed.", floor: false) XCTAssertEqual(notices.map(\.kind), [.problem, .problem]) XCTAssertTrue(notices.allSatisfy { $0.important && $0.dismissible }) XCTAssertFalse( CallNotice(kind: .reconnecting, text: "").dismissible, "reconnecting ends on its own") } } final class GridFitTests: XCTestCase { func testOneTileFillsTheStage() { XCTAssertEqual(GridFit.columns(count: 1, in: CGSize(width: 1600, height: 900), aspect: 16 / 9), 1) } func testTwoSideBySideOnAWideStageAndStackedOnATallOne() { XCTAssertEqual(GridFit.columns(count: 2, in: CGSize(width: 1600, height: 600), aspect: 16 / 9), 2) XCTAssertEqual(GridFit.columns(count: 2, in: CGSize(width: 390, height: 844), aspect: 3 / 4), 1) } func testFourMakeTwoByTwo() { XCTAssertEqual(GridFit.columns(count: 4, in: CGSize(width: 1600, height: 900), aspect: 16 / 9), 2) let tile = GridFit.tile( count: 4, columns: 2, in: CGSize(width: 1604, height: 904), aspect: 16 / 9, spacing: 4) XCTAssertEqual(tile.width, 800, accuracy: 0.01) } } final class CameraFlipTests: XCTestCase { let front = CameraDevice(id: "front", name: "Front Camera", builtIn: true) let back = CameraDevice(id: "back", name: "Back Camera") let wide = CameraDevice(id: "wide", name: "Back Ultra Wide Camera") func testFrontAndBackSwap() { XCTAssertEqual(CameraFlip.next(from: "front", in: [front, back, wide]), back) XCTAssertEqual(CameraFlip.next(from: "wide", in: [front, back, wide]), front) } func testNothingToFlipTo() { XCTAssertNil(CameraFlip.next(from: "front", in: [front])) XCTAssertNil(CameraFlip.next(from: nil, in: [])) } } final class InviteLinkCopyTests: XCTestCase { func testOnlyInACall() { XCTAssertEqual( InviteLinkCopy.rule(inCall: false, phase: nil, standing: "member"), .unavailable) XCTAssertEqual( InviteLinkCopy.rule(inCall: true, phase: "ended", standing: "member"), .unavailable, "an ended meeting has no link to share") } func testAMemberCopies() { for phase in ["admitted", "waiting", "reconnecting", nil] { XCTAssertEqual(InviteLinkCopy.rule(inCall: true, phase: phase, standing: "member"), .copy) } } func testAGuestIsToldWhyNot() { guard case .explain(let why) = InviteLinkCopy.rule(inCall: true, phase: "admitted", standing: "guest") else { return XCTFail("a guest is told, not beeped at") } XCTAssertTrue(why.contains("Only members")) guard case .explain = InviteLinkCopy.rule(inCall: true, phase: nil, standing: "") else { return XCTFail("before the meeting says who you are") } } func testTheCallMenuHasCopyInviteLinkBesideMute() { XCTAssertEqual(CallMenuItem.allCases, [.mute, .copyInviteLink]) XCTAssertEqual(CallMenuItem.copyInviteLink.title(muted: false), "Copy Invite Link") XCTAssertEqual(CallMenuItem.copyInviteLink.shortcut.key, "i") XCTAssertEqual(CallMenuItem.copyInviteLink.shortcut.modifiers, [.command, .shift]) XCTAssertEqual(CallMenuItem.mute.shortcut.key, "m") XCTAssertEqual(CallMenuItem.mute.shortcut.modifiers, [.command, .shift]) } @MainActor func testAGuestPressGivesAToastAndNoClipboard() async { let call = Call(playAudio: false) call.showFixture( title: "x", standing: "guest", state: #"{"phase":"admitted","peer":"me","boot":"b","peers":[],"waiting":[]}"#) await call.copyInviteLink() XCTAssertEqual(call.toast, "Only members of this meeting can share its invite link.") } } final class AudioWarningTests: XCTestCase { func report( _ suppression: SuppressionState = .running(engine: "DeepFilterNet", cpu: 0.1), echo: String = "WebRTC", refused: String? = nil, started: Bool = true ) -> ProcessingReport { ProcessingReport( suppression: suppression, echoCancellation: echo, noiseSuppression: "off", gainControl: "Calls, after noise reduction", refused: refused, started: started) } let clean = AudioHealth(concealed: 10, total: 10_000, inserted: 5, removed: 5, jitterBufferMs: 60) func testAHealthyCallSaysNothing() { XCTAssertNil(AudioWarning.line(health: clean, processing: report(), reduceNoise: true)) XCTAssertNil(AudioWarning.line(health: nil, processing: nil, reduceNoise: true)) } func testGrowlAndDropouts() { let growl = AudioHealth(concealed: 0, total: 10_000, inserted: 400, removed: 0, jitterBufferMs: 60) XCTAssertTrue( AudioWarning.line(health: growl, processing: report(), reduceNoise: true)!.contains("growling")) let lossy = AudioHealth(concealed: 900, total: 10_000, inserted: 0, removed: 0, jitterBufferMs: 60) XCTAssertEqual( AudioWarning.line(health: lossy, processing: report(), reduceNoise: true), "9% of incoming audio is going missing.") } func testProcessingProblems() { XCTAssertNotNil(AudioWarning.line(health: clean, processing: report(echo: "off"), reduceNoise: true)) XCTAssertNotNil( AudioWarning.line(health: clean, processing: report(refused: "aec"), reduceNoise: true)) XCTAssertEqual( AudioWarning.line(health: clean, processing: report(.unsupportedRate(16_000)), reduceNoise: true), "Noise reduction is bypassed: the microphone runs at 16 kHz.") XCTAssertNil( AudioWarning.line(health: clean, processing: report(.standingAside), reduceNoise: true), "Voice Isolation standing in is a choice, not a fault") XCTAssertNil( AudioWarning.line(health: clean, processing: report(echo: "off", started: false), reduceNoise: true), "before the microphone starts, the defaults mean nothing") } } /// The call's title over the picture (2026-10-02): shown only when someone /// chose it, and the window's title either way. final class CallTitleTests: XCTestCase { func testANamedMeetingShowsItsTitle() { XCTAssertEqual(CallTitle.shown(title: "Board meeting", named: true), "Board meeting") XCTAssertEqual(CallTitle.shown(title: " Board meeting \n", named: true), "Board meeting") } func testAnAutomaticTitleIsNotShown() { XCTAssertNil(CallTitle.shown(title: "Call, 2 Oct 15:40", named: false)) // A server from before 0.4.7 says nothing, which reads as not named. XCTAssertNil(CallTitle.shown(title: "Call, 2 Oct 15:40 Europe/Dublin", named: false)) XCTAssertNil(CallTitle.shown(title: " ", named: true), "nothing to show is nothing shown") } func testTheWindowIsAlwaysTitledForTheMeeting() { XCTAssertEqual(CallTitle.window(title: "Call, 2 Oct 15:40"), "Call, 2 Oct 15:40") XCTAssertEqual(CallTitle.window(title: "Board meeting"), "Board meeting") XCTAssertEqual(CallTitle.window(title: ""), "Calls") } }