import XCTest @testable import CallsBroadcastKit @testable import CallsKit /// §5.4's receiving half. These are room-share.test.mjs's classifyTrack /// cases, case for case: the browser and the apps look at the same /// senders, and the day they disagree about which m-line is a screen, one /// of them captions a face as a screen. A case added there belongs here. final class ScreenShareTests: XCTestCase { private let nothing = ScreenClues() private let known = ScreenClues(cameraMid: "0", cameraStream: "cam", peerStream: "cam") private func video(_ stream: String = "", mid: String = "", direction: String = "") -> ArrivingTrack { ArrivingTrack(kind: "video", streamId: stream, mid: mid, direction: direction) } func testAudioIsNeverAScreen() { let audio = ArrivingTrack(kind: "audio", streamId: "other", mid: "9") XCTAssertEqual(classifyTrack(nothing, audio, hasVideo: true), .audio) XCTAssertEqual(classifyTrack(known, audio, hasVideo: true), .audio) } func testTheFirstVideoTrackIsTheCamera() { XCTAssertEqual(classifyTrack(nothing, video("cam", mid: "0"), hasVideo: false), .camera) XCTAssertEqual(classifyTrack(nothing, video(), hasVideo: false), .camera) } func testASecondVideoTrackOnAnotherTransceiverIsTheScreen() { XCTAssertEqual(classifyTrack(known, video("screen", mid: "2"), hasVideo: false), .screen) } func testTheCameraAgainIsStillTheCamera() { XCTAssertEqual(classifyTrack(known, video("cam", mid: "0"), hasVideo: false), .camera) // The mid decides when there is one, whatever the stream is now called. XCTAssertEqual(classifyTrack(known, video("renamed", mid: "0"), hasVideo: false), .camera) } func testWithNoMidTheStreamDecides() { let byStream = ScreenClues(cameraStream: "cam") XCTAssertEqual(classifyTrack(byStream, video("cam"), hasVideo: false), .camera) XCTAssertEqual(classifyTrack(byStream, video("screen"), hasVideo: false), .screen) XCTAssertEqual(classifyTrack(known, video("cam"), hasVideo: false), .camera) XCTAssertEqual(classifyTrack(known, video("screen"), hasVideo: false), .screen) } func testASecondVideoTrackWithNothingToCompareIsTheScreen() { XCTAssertEqual(classifyTrack(known, video(), hasVideo: false), .screen) } func testRecvonlyIsTheScreenWhenThisSideSendsACamera() { // The rule that decides in practice here: the apps always add a // camera, so a video m-line arriving recvonly is one the far side // added, and a screen is the only one it adds — even as the first // video track that peer ever sends. XCTAssertEqual( classifyTrack(nothing, video("screen", mid: "2", direction: "recvonly"), hasVideo: true), .screen) } func testRecvonlySaysNothingWithoutACameraOfOurOwn() { XCTAssertEqual( classifyTrack(nothing, video("cam", mid: "1", direction: "recvonly"), hasVideo: false), .camera) } func testASendrecvCameraIsACamera() { XCTAssertEqual( classifyTrack(nothing, video("cam", mid: "1", direction: "sendrecv"), hasVideo: true), .camera) } func testAVideoTrackOutsideTheMicrophonesStreamIsTheScreen() { let audioOnly = ScreenClues(peerStream: "theirs") XCTAssertEqual(classifyTrack(audioOnly, video("screen", mid: "2"), hasVideo: false), .screen) XCTAssertEqual(classifyTrack(audioOnly, video("theirs", mid: "1"), hasVideo: false), .camera) } func testTheShareMessageBreaksTheTieAndOnlyTheTie() { XCTAssertEqual(classifyTrack(ScreenClues(sharing: true), video(), hasVideo: false), .screen) XCTAssertEqual(classifyTrack(ScreenClues(sharing: false), video(), hasVideo: false), .camera) let sharing = ScreenClues(peerStream: "theirs", sharing: true) XCTAssertEqual(classifyTrack(sharing, video("theirs", mid: "1"), hasVideo: false), .camera) XCTAssertEqual( classifyTrack(ScreenClues(cameraMid: "1", sharing: true), video(mid: "1"), hasVideo: false), .camera) } func testTheCaptionIsTheBrowsers() { // Typographic apostrophe, as room.js's shareCaption writes it. XCTAssertEqual(screenCaption("Bea"), "Bea\u{2019}s screen") } func testAScreenIsFittedEvenAndNeverEnlarged() { // A 5K display at 2x comes down to 1920 on its long side, keeping its // shape; both sides even, for the encoders' 4:2:0 chroma. let big = fitted(width: 5120, height: 2880, longest: 1920) XCTAssertEqual(big.width, 1920) XCTAssertEqual(big.height, 1080) // A small window is sent at its own size, rounded down to even. let small = fitted(width: 801, height: 601, longest: 1920) XCTAssertEqual(small.width, 800) XCTAssertEqual(small.height, 600) // A portrait phone screen is fitted on its height. let phone = fitted(width: 1179, height: 2556, longest: 1280) XCTAssertEqual(phone.height, 1280) XCTAssertEqual(phone.width % 2, 0) XCTAssertEqual(fitted(width: 0, height: 100, longest: 1920).width, 2) } // --------------------------------------------------------------- // The stage // --------------------------------------------------------------- func testTheStageIsTheFirstCameraWithNoScreens() { XCTAssertEqual(Stage.speaker(pinned: nil, active: nil, others: ["a", "b"], screens: []), .camera("a")) XCTAssertNil(Stage.speaker(pinned: nil, active: nil, others: [], screens: [])) } func testTheNewestScreenTakesTheStage() { XCTAssertEqual(Stage.speaker(pinned: nil, active: "a", others: ["a", "b"], screens: ["b"]), .screen("b")) XCTAssertEqual( Stage.speaker(pinned: nil, active: nil, others: ["a", "b"], screens: ["b", "a"]), .screen("a")) } func testAPinStandsWhileItIsThere() { XCTAssertEqual( Stage.speaker(pinned: .camera("a"), active: "b", others: ["a", "b"], screens: ["b"]), .camera("a")) XCTAssertEqual( Stage.speaker(pinned: .screen("a"), active: nil, others: ["a", "b"], screens: ["a", "b"]), .screen("a")) } func testAPinThatWentAwayFallsBack() { // A stopped screen, and a person who left: neither leaves the stage // empty while there is something else to show. XCTAssertEqual( Stage.speaker(pinned: .screen("a"), active: nil, others: ["a", "b"], screens: []), .camera("a")) XCTAssertEqual( Stage.speaker(pinned: .camera("c"), active: nil, others: ["a"], screens: ["a"]), .screen("a")) } }