#if os(macOS) import AppKit import SwiftUI import LiveKitWebRTC import XCTest @testable import CallsKit /// The mini window's rules, held without a real call or a person /// switching apps: the defaults, where each corner lands, and when the /// window comes and goes. final class MiniWindowPreferencesTests: XCTestCase { private func defaults() -> UserDefaults { let name = "MiniWindowTests.\(UUID().uuidString)" let defaults = UserDefaults(suiteName: name)! addTeardownBlock { defaults.removePersistentDomain(forName: name) } return defaults } func testOnAndBottomRightUntilSomeoneSaysOtherwise() { let preferences = MiniWindowPreferences(defaults: defaults()) XCTAssertTrue(preferences.enabled) XCTAssertEqual(preferences.corner, .bottomRight) } func testChoicesPersist() { let store = defaults() MiniWindowPreferences(defaults: store).enabled = false MiniWindowPreferences(defaults: store).corner = .topLeft XCTAssertFalse(MiniWindowPreferences(defaults: store).enabled) XCTAssertEqual(MiniWindowPreferences(defaults: store).corner, .topLeft) } func testAnUnknownCornerFallsBackToTheDefault() { let store = defaults() store.set("middle", forKey: MiniWindowPreferences.cornerKey) XCTAssertEqual(MiniWindowPreferences(defaults: store).corner, .bottomRight) } func testTheCornersAreOfferedInTheOrderTheSettingShowsThem() { XCTAssertEqual( MiniWindowCorner.allCases.map(\.title), ["Bottom right", "Bottom left", "Top right", "Top left"]) } } final class MiniWindowCornerTests: XCTestCase { // A 1440×900 screen with a 25pt menu bar and a 70pt Dock along the // bottom: the visible frame AppKit reports for it. private let visible = CGRect(x: 0, y: 70, width: 1440, height: 805) private let size = MiniWindowRules.size func testEachCornerSitsInsetFromTheVisibleFrame() { XCTAssertEqual( MiniWindowCorner.bottomRight.frame(size: size, in: visible), CGRect(x: 1100, y: 90, width: 320, height: 180)) XCTAssertEqual( MiniWindowCorner.bottomLeft.frame(size: size, in: visible), CGRect(x: 20, y: 90, width: 320, height: 180)) XCTAssertEqual( MiniWindowCorner.topRight.frame(size: size, in: visible), CGRect(x: 1100, y: 675, width: 320, height: 180)) XCTAssertEqual( MiniWindowCorner.topLeft.frame(size: size, in: visible), CGRect(x: 20, y: 675, width: 320, height: 180)) } func testASecondScreenLeftOfTheMainOneHasANegativeOrigin() { let left = CGRect(x: -1920, y: 0, width: 1920, height: 1055) XCTAssertEqual( MiniWindowCorner.bottomRight.frame(size: size, in: left), CGRect(x: -340, y: 20, width: 320, height: 180)) XCTAssertEqual( MiniWindowCorner.topLeft.frame(size: size, in: left), CGRect(x: -1900, y: 855, width: 320, height: 180)) } func testTheWindowIs16By9() { XCTAssertEqual(size.width / size.height, 16 / 9, accuracy: 0.001) } func testADraggedWindowSnapsToTheCornerItsMiddleIsNearest() { func dropped(_ x: CGFloat, _ y: CGFloat) -> MiniWindowCorner { .nearest(to: CGRect(origin: CGPoint(x: x, y: y), size: size), in: visible) } XCTAssertEqual(dropped(900, 120), .bottomRight) XCTAssertEqual(dropped(300, 120), .bottomLeft) XCTAssertEqual(dropped(900, 600), .topRight) XCTAssertEqual(dropped(100, 600), .topLeft) // Half off the screen still belongs to the corner it is nearest. XCTAssertEqual(dropped(1300, -50), .bottomRight) // Every corner's own frame snaps back to that corner. for corner in MiniWindowCorner.allCases { XCTAssertEqual(.nearest(to: corner.frame(size: size, in: visible), in: visible), corner) } } } final class MiniWindowPolicyTests: XCTestCase { private let inCall = MiniWindowPolicy.Conditions( enabled: true, inCall: true, mainWindowVisible: true) func testSwitchingAwayDuringACallShowsItAndComingBackRestores() { var policy = MiniWindowPolicy() XCTAssertEqual(policy.resignedActive(inCall), .show(hideMain: true)) XCTAssertTrue(policy.showing) XCTAssertEqual(policy.becameActive(), .restore(activate: true)) XCTAssertFalse(policy.showing) // Coming back again with nothing up is nothing to do. XCTAssertEqual(policy.becameActive(), .none) } func testASecondResignWhileShowingDoesNotOpenAnother() { var policy = MiniWindowPolicy() _ = policy.resignedActive(inCall) XCTAssertEqual(policy.resignedActive(inCall), .none) } func testNothingAppearsOutsideACallOrWithTheSettingOff() { var policy = MiniWindowPolicy() var conditions = inCall conditions.inCall = false XCTAssertEqual(policy.resignedActive(conditions), .none) conditions = inCall conditions.enabled = false XCTAssertEqual(policy.resignedActive(conditions), .none) XCTAssertFalse(policy.showing) } func testACallWindowPutAwayByThePersonStaysAway() { var policy = MiniWindowPolicy() var conditions = inCall conditions.mainWindowMiniaturized = true XCTAssertEqual(policy.resignedActive(conditions), .none) conditions = inCall conditions.mainWindowVisible = false XCTAssertEqual(policy.resignedActive(conditions), .none) } func testAFullScreenCallWindowIsLeftInItsSpace() { var policy = MiniWindowPolicy() var conditions = inCall conditions.mainWindowFullScreen = true XCTAssertEqual(policy.resignedActive(conditions), .show(hideMain: false)) XCTAssertFalse(policy.mainHidden) XCTAssertEqual(policy.becameActive(), .restore(activate: true)) } func testHidingTheAppBeforeItResignsShowsNothing() { var policy = MiniWindowPolicy() XCTAssertEqual(policy.willHide(), .none) XCTAssertEqual(policy.resignedActive(inCall), .none) policy.didUnhide() XCTAssertEqual(policy.becameActive(), .none) // And once it is back, switching away works as before. XCTAssertEqual(policy.resignedActive(inCall), .show(hideMain: true)) } func testHidingTheAppAfterItResignedPutsTheCallWindowBackFirst() { var policy = MiniWindowPolicy() _ = policy.resignedActive(inCall) XCTAssertEqual(policy.willHide(), .restore(activate: false)) XCTAssertEqual(policy.resignedActive(inCall), .none) } func testTheCallEndingPutsItAwayWithoutPullingTheAppForward() { var policy = MiniWindowPolicy() _ = policy.resignedActive(inCall) XCTAssertEqual(policy.callEnded(), .restore(activate: false)) XCTAssertEqual(policy.becameActive(), .none) XCTAssertEqual(policy.callEnded(), .none) } } /// Size: 16:9 always, never under 200 pt wide, never over 40% of the /// screen, and remembered. final class MiniWindowSizeTests: XCTestCase { private let visible = CGRect(x: 0, y: 70, width: 1440, height: 805) func testEverySizeIs16By9() { for width in [0, 150, 200, 247, 320, 575, 900, 5000] as [CGFloat] { let size = MiniWindowRules.size(width: width, in: visible) XCTAssertEqual(size.width / size.height, 16 / 9, accuracy: 0.0001, "\(width)") } } func testTheWidthIsHeldBetweenTheMinimumAnd40PercentOfTheScreen() { XCTAssertEqual(MiniWindowRules.size(width: 120, in: visible).width, 200) XCTAssertEqual(MiniWindowRules.size(width: 320, in: visible).width, 320) XCTAssertEqual(MiniWindowRules.size(width: 900, in: visible).width, 576) // 1440 × 0.4 // A width chosen on a big display, shown on a small one. let small = CGRect(x: 0, y: 0, width: 1024, height: 700) XCTAssertEqual(MiniWindowRules.size(width: 576, in: small).width, 409) // A screen too small for 40% of it to reach the minimum still gets it. let tiny = CGRect(x: 0, y: 0, width: 400, height: 300) XCTAssertEqual(MiniWindowRules.size(width: 900, in: tiny).width, 200) } func testResizingFromACornerKeepsTheOppositeCornerAndTheAspect() { let start = CGRect(x: 1100, y: 90, width: 320, height: 180) // The top-left corner, dragged up and left: the bottom right stays. let grown = MiniWindowResize.frame( from: start, dragging: [.top, .left], dx: -80, dy: 20, in: visible) XCTAssertEqual(grown.maxX, start.maxX) XCTAssertEqual(grown.minY, start.minY) XCTAssertEqual(grown.width, 400) XCTAssertEqual(grown.width / grown.height, 16 / 9, accuracy: 0.0001) // Whichever direction asks for more wins. let tall = MiniWindowResize.frame( from: start, dragging: [.top, .left], dx: -10, dy: 90, in: visible) XCTAssertEqual(tall.height, 270, accuracy: 0.001) XCTAssertEqual(tall.width, 480) } func testResizingIsClampedAtBothEnds() { let start = CGRect(x: 100, y: 100, width: 320, height: 180) let shrunk = MiniWindowResize.frame( from: start, dragging: .right, dx: -500, dy: 0, in: visible) XCTAssertEqual(shrunk.size, MiniWindowRules.minimumSize()) let huge = MiniWindowResize.frame( from: start, dragging: .bottom, dx: 0, dy: -2000, in: visible) XCTAssertEqual(huge.size, MiniWindowRules.maximumSize(in: visible)) XCTAssertEqual(huge.maxY, start.maxY, "a bottom edge grows downward") // An edge alone grows evenly either side of the middle. let wide = MiniWindowResize.frame(from: start, dragging: .right, dx: 80, dy: 0, in: visible) XCTAssertEqual(wide.minX, start.minX) XCTAssertEqual(wide.midY, start.midY, accuracy: 0.001) } func testThePressTakesHoldOfTheEdgesItIsNear() { let size = CGSize(width: 320, height: 180) XCTAssertEqual(MiniWindowResize.handle(at: CGPoint(x: 160, y: 90), in: size), []) XCTAssertEqual(MiniWindowResize.handle(at: CGPoint(x: 2, y: 90), in: size), .left) XCTAssertEqual(MiniWindowResize.handle(at: CGPoint(x: 318, y: 178), in: size), [.right, .top]) XCTAssertEqual(MiniWindowResize.handle(at: CGPoint(x: 3, y: 3), in: size), [.left, .bottom]) } func testTheSizeIsRemembered() { let name = "MiniWindowSizeTests.\(UUID().uuidString)" let store = UserDefaults(suiteName: name)! defer { store.removePersistentDomain(forName: name) } XCTAssertEqual(MiniWindowPreferences(defaults: store).width, 320) MiniWindowPreferences(defaults: store).width = 480 XCTAssertEqual(MiniWindowPreferences(defaults: store).width, 480) } func testSnappingKeepsTheChosenSize() { let name = "MiniWindowSizeTests.\(UUID().uuidString)" let store = UserDefaults(suiteName: name)! defer { store.removePersistentDomain(forName: name) } var memory = MiniWindowCallMemory() let dropped = CGRect(x: 800, y: 500, width: 480, height: 270) let settled = memory.dropped( dropped, in: visible, display: "1", option: false, preferences: MiniWindowPreferences(defaults: store)) XCTAssertEqual(settled, MiniWindowCorner.topRight.frame(size: dropped.size, in: visible)) } } /// The tab: against the nearest edge, off it by dragging, and saying who /// the call is with. final class MiniWindowTabTests: XCTestCase { private let visible = CGRect(x: 0, y: 70, width: 1440, height: 805) private let size = MiniWindowTab.size func testEachCornerWindowDocksToItsSide() { let window = CGSize(width: 320, height: 180) let (right, tab) = MiniWindowTab.dock( MiniWindowCorner.bottomRight.frame(size: window, in: visible), in: visible) XCTAssertEqual(right, .right) XCTAssertEqual(tab.maxX, visible.maxX, "flush against the edge") XCTAssertEqual(tab.midY, 90 + 90, accuracy: 0.001, "level with the window's middle") XCTAssertEqual(tab.size, size) XCTAssertEqual( MiniWindowTab.dock(MiniWindowCorner.topLeft.frame(size: window, in: visible), in: visible).0, .left) } func testAWindowNearerTheTopOrBottomDocksThere() { let nearBottom = CGRect(x: 600, y: 75, width: 320, height: 180) let (edge, tab) = MiniWindowTab.dock(nearBottom, in: visible) XCTAssertEqual(edge, .bottom) XCTAssertEqual(tab.minY, visible.minY) XCTAssertEqual(tab.midX, nearBottom.midX, accuracy: 0.001) let nearTop = CGRect(x: 600, y: 690, width: 320, height: 180) XCTAssertEqual(MiniWindowTab.dock(nearTop, in: visible).0, .top) XCTAssertEqual(MiniWindowTab.dock(nearTop, in: visible).1.maxY, visible.maxY) } func testTheTabStaysOnScreen() { // A window half off the bottom right: the tab is still all on screen. let off = CGRect(x: 1400, y: 20, width: 320, height: 180) let tab = MiniWindowTab.dock(off, in: visible).1 XCTAssertTrue(visible.contains(tab), "\(tab)") } func testDraggingAlongAnEdgeRedocksAndPullingItOutExpands() { let docked = MiniWindowTab.dock(CGRect(x: 1100, y: 90, width: 320, height: 180), in: visible).1 // Slid up the right edge, a little off it. let slid = docked.offsetBy(dx: -20, dy: 300) guard case .dock(.right, let frame) = MiniWindowTab.release(slid, in: visible) else { return XCTFail("slid along the edge should dock again") } XCTAssertEqual(frame.maxX, visible.maxX) XCTAssertEqual(frame.minY, slid.minY) // Pulled into the middle of the screen. XCTAssertEqual(MiniWindowTab.release(docked.offsetBy(dx: -500, dy: 200), in: visible), .expand) } func testAPulledOutWindowOpensWhereTheTabWasAndOnScreen() { let tab = CGRect(x: 1284, y: 400, width: size.width, height: size.height) let frame = MiniWindowTab.expanded( from: tab, size: CGSize(width: 320, height: 180), in: visible) XCTAssertEqual(frame.midY, tab.midY, accuracy: 0.001) XCTAssertEqual(frame.maxX, visible.maxX, "clamped rather than hanging off the edge") } func testTheCaption() { XCTAssertEqual(MiniWindowTab.caption(others: []), "Just you") XCTAssertEqual(MiniWindowTab.caption(others: ["Bea"]), "Bea") XCTAssertEqual(MiniWindowTab.caption(others: ["Bea", "Cai"]), "3 people") } } /// Placement: corners, anywhere, and ⌥ for one call. final class MiniWindowPlacementTests: XCTestCase { private let visible = CGRect(x: 0, y: 70, width: 1440, height: 805) private let size = CGSize(width: 320, height: 180) private func preferences() -> MiniWindowPreferences { let name = "MiniWindowPlacementTests.\(UUID().uuidString)" let defaults = UserDefaults(suiteName: name)! addTeardownBlock { defaults.removePersistentDomain(forName: name) } return MiniWindowPreferences(defaults: defaults) } func testCornersByDefault() { let preferences = preferences() XCTAssertEqual(preferences.placement, .corners) XCTAssertEqual( MiniWindowPlacement.allCases.map(\.title), ["Snap to corners", "Anywhere I put it"]) XCTAssertEqual( MiniWindowCallMemory().frame(size: size, in: visible, display: "1", preferences: preferences), MiniWindowCorner.bottomRight.frame(size: size, in: visible)) } func testAnywhereKeepsTheDropExactlyAndRemembersItPerDisplay() { let preferences = preferences() preferences.placement = .anywhere var memory = MiniWindowCallMemory() let drop = CGRect(x: 612, y: 333, width: 320, height: 180) XCTAssertEqual( memory.dropped(drop, in: visible, display: "1", option: false, preferences: preferences), drop) XCTAssertEqual(preferences.corner, .bottomRight, "a free drop is not a corner") // Next call, same display: there. Another display: its own default. let next = MiniWindowCallMemory() XCTAssertEqual(next.frame(size: size, in: visible, display: "1", preferences: preferences), drop) let other = CGRect(x: 1440, y: 0, width: 1920, height: 1055) XCTAssertEqual( next.frame(size: size, in: other, display: "2", preferences: preferences), MiniWindowCorner.bottomRight.frame(size: size, in: other)) } func testAFreePositionFollowsItsDisplayWhenTheArrangementChanges() { let preferences = preferences() preferences.placement = .anywhere var memory = MiniWindowCallMemory() let right = CGRect(x: 1440, y: 0, width: 1920, height: 1055) _ = memory.dropped( CGRect(x: 2000, y: 400, width: 320, height: 180), in: right, display: "2", option: false, preferences: preferences) // The display dragged to the left of the main one in Arrange. let left = CGRect(x: -1920, y: 0, width: 1920, height: 1055) XCTAssertEqual( memory.frame(size: size, in: left, display: "2", preferences: preferences), CGRect(x: -1920 + 560, y: 400, width: 320, height: 180)) } func testAFreePositionIsClampedBackOnAScreenThatShrank() { let preferences = preferences() preferences.placement = .anywhere var memory = MiniWindowCallMemory() let big = CGRect(x: 0, y: 0, width: 2560, height: 1415) _ = memory.dropped( CGRect(x: 2200, y: 1200, width: 320, height: 180), in: big, display: "1", option: false, preferences: preferences) // Same display at a lower resolution. let smaller = CGRect(x: 0, y: 0, width: 1440, height: 875) let frame = memory.frame(size: size, in: smaller, display: "1", preferences: preferences) XCTAssertTrue(smaller.contains(frame), "\(frame)") XCTAssertEqual(frame.maxX, smaller.maxX) XCTAssertEqual(frame.maxY, smaller.maxY) // And a drop half off the screen lands on it. let half = memory.dropped( CGRect(x: -100, y: -50, width: 320, height: 180), in: smaller, display: "1", option: false, preferences: preferences) XCTAssertEqual(half.origin, CGPoint(x: 0, y: 0)) } func testOptionDragPlacesFreelyForThisCallOnly() { let preferences = preferences() var memory = MiniWindowCallMemory() let drop = CGRect(x: 612, y: 333, width: 320, height: 180) XCTAssertEqual( memory.dropped(drop, in: visible, display: "1", option: true, preferences: preferences), drop) XCTAssertEqual(preferences.corner, .bottomRight, "the setting is untouched") XCTAssertNil(preferences.position(on: "1"), "and nothing is saved") // Shown again in the same call: where it was dropped. XCTAssertEqual(memory.frame(size: size, in: visible, display: "1", preferences: preferences), drop) // Resizing it keeps it placed rather than snapping it. let bigger = CGRect(x: 612, y: 333, width: 400, height: 225) XCTAssertEqual( memory.resized(bigger, in: visible, display: "1", preferences: preferences), bigger) // The next call starts from the setting again. XCTAssertEqual( MiniWindowCallMemory().frame(size: size, in: visible, display: "1", preferences: preferences), MiniWindowCorner.bottomRight.frame(size: size, in: visible)) } func testAnOrdinaryDragAfterAnOptionDragSnapsAndForgetsTheOneOff() { let preferences = preferences() var memory = MiniWindowCallMemory() _ = memory.dropped( CGRect(x: 612, y: 333, width: 320, height: 180), in: visible, display: "1", option: true, preferences: preferences) let snapped = memory.dropped( CGRect(x: 100, y: 600, width: 320, height: 180), in: visible, display: "1", option: false, preferences: preferences) XCTAssertEqual(snapped, MiniWindowCorner.topLeft.frame(size: size, in: visible)) XCTAssertEqual(preferences.corner, .topLeft) XCTAssertEqual(memory.frame(size: size, in: visible, display: "1", preferences: preferences), snapped) XCTAssertTrue(memory.oneOff.isEmpty) } /// Collapsed, and any ⌥ one-off, last for the call and no longer. @MainActor func testEachCallStartsExpandedWithNoOneOffs() { let name = "MiniWindowPlacementTests.\(UUID().uuidString)" let defaults = UserDefaults(suiteName: name)! defer { defaults.removePersistentDomain(forName: name) } let model = CallsModel(defaults: defaults) let controller = MiniWindowController(preferences: MiniWindowPreferences(defaults: defaults)) controller.attach(model: model, mainWindow: nil) model.joining = true controller.memory.collapsed = true _ = controller.memory.dropped( CGRect(x: 612, y: 333, width: 320, height: 180), in: visible, display: "1", option: true, preferences: MiniWindowPreferences(defaults: defaults)) XCTAssertNotEqual(controller.memory, MiniWindowCallMemory()) model.joining = false XCTAssertEqual(controller.memory, MiniWindowCallMemory()) XCTAssertFalse(controller.memory.collapsed) } func testForgettingPositionsPutsItBackInTheCorner() { let preferences = preferences() preferences.placement = .anywhere preferences.setPosition(CGPoint(x: 10, y: 10), on: "1") preferences.forgetPositions() XCTAssertNil(preferences.position(on: "1")) } } /// Space-to-mute's rules, and how AppKit's first responder is read. final class SpaceToMuteTests: XCTestCase { func testBareSpaceWithNothingFocusedToggles() { XCTAssertEqual( SpaceToMute.action(isSpace: true, isRepeat: false, modified: false, focus: .none), .toggle) } func testARepeatIsSwallowedNotToggled() { XCTAssertEqual( SpaceToMute.action(isSpace: true, isRepeat: true, modified: false, focus: .none), .swallow) } func testTextFieldsAndControlsKeepTheirSpace() { for focus in [KeyboardFocus.text, .control] { for repeating in [false, true] { XCTAssertEqual( SpaceToMute.action(isSpace: true, isRepeat: repeating, modified: false, focus: focus), .pass) } } } func testOtherKeysAndModifiedSpaceAreNotOurs() { XCTAssertEqual( SpaceToMute.action(isSpace: false, isRepeat: false, modified: false, focus: .none), .pass) XCTAssertEqual( SpaceToMute.action(isSpace: true, isRepeat: false, modified: true, focus: .none), .pass) } @MainActor func testTheFirstResponderIsReadConservatively() { let root = NSView() XCTAssertEqual(SpaceToMute.focus(of: nil, contentView: root), .none) XCTAssertEqual(SpaceToMute.focus(of: NSWindow(), contentView: root), .none) XCTAssertEqual(SpaceToMute.focus(of: root, contentView: root), .none) // The field editor a SwiftUI TextField (the chat composer) edits // through, and an editable field itself. XCTAssertEqual(SpaceToMute.focus(of: NSTextView(), contentView: root), .text) XCTAssertEqual(SpaceToMute.focus(of: NSTextField(string: ""), contentView: root), .text) XCTAssertEqual(SpaceToMute.focus(of: NSButton(), contentView: root), .control) XCTAssertEqual(SpaceToMute.focus(of: NSPopUpButton(), contentView: root), .control) // Anything unrecognised — SwiftUI's focus proxy for a Button among // them — keeps its Space. XCTAssertEqual(SpaceToMute.focus(of: NSView(), contentView: root), .control) } @MainActor func testAKeyEventInAWindowIsDecidedByItsFirstResponder() throws { NSApplication.shared.setActivationPolicy(.prohibited) let window = NSWindow( contentRect: NSRect(x: 0, y: 0, width: 200, height: 100), styleMask: [.titled], backing: .buffered, defer: false) window.isReleasedWhenClosed = false let field = NSTextField(frame: NSRect(x: 10, y: 10, width: 100, height: 24)) window.contentView?.addSubview(field) func key(_ code: UInt16, _ characters: String, repeating: Bool = false, flags: NSEvent.ModifierFlags = []) throws -> NSEvent { try XCTUnwrap( NSEvent.keyEvent( with: .keyDown, location: .zero, modifierFlags: flags, timestamp: 0, windowNumber: window.windowNumber, context: nil, characters: characters, charactersIgnoringModifiers: characters, isARepeat: repeating, keyCode: code)) } window.makeFirstResponder(nil) XCTAssertEqual(SpaceToMute.action(for: try key(49, " "), in: window), .toggle) XCTAssertEqual(SpaceToMute.action(for: try key(49, " ", repeating: true), in: window), .swallow) XCTAssertEqual(SpaceToMute.action(for: try key(49, " ", flags: .command), in: window), .pass) XCTAssertEqual(SpaceToMute.action(for: try key(49, " ", flags: .capsLock), in: window), .toggle) XCTAssertEqual(SpaceToMute.action(for: try key(46, "m"), in: window), .pass) window.makeFirstResponder(field) XCTAssertEqual(SpaceToMute.action(for: try key(49, " "), in: window), .pass, "typing a space") window.close() } } final class MiniWindowPanelTests: XCTestCase { @MainActor func testThePanelFloatsEverywhereAndStaysOutOfScreenCaptures() throws { NSApplication.shared.setActivationPolicy(.prohibited) let screen = try XCTUnwrap(NSScreen.main) let panel = MiniWindowPanel(call: Call(playAudio: false), actions: MiniWindowActions()) XCTAssertEqual(panel.sharingType, .none) XCTAssertEqual(panel.level, .floating) XCTAssertFalse(panel.hidesOnDeactivate) XCTAssertTrue(panel.styleMask.contains(.nonactivatingPanel)) XCTAssertTrue(panel.collectionBehavior.contains(.canJoinAllSpaces)) XCTAssertTrue(panel.collectionBehavior.contains(.fullScreenAuxiliary)) XCTAssertTrue(panel.canBecomeKey) XCTAssertTrue(panel.becomesKeyOnlyIfNeeded, "clicking Mute must not take the keyboard") let visible = screen.visibleFrame let big = MiniWindowRules.size(width: 480, in: visible) let frame = MiniWindowCorner.topLeft.frame(size: big, in: visible) panel.expand(to: frame, animated: false) XCTAssertEqual(panel.frame, frame) XCTAssertFalse(panel.collapsed) // The tab is the same window: it keeps every one of those settings. let tab = MiniWindowTab.dock(frame, in: visible).1 panel.collapse(to: tab, expanded: frame) XCTAssertTrue(panel.collapsed) XCTAssertEqual(panel.frame, tab) XCTAssertEqual(panel.expandedFrame, frame, "the size to come back to") XCTAssertEqual(panel.sharingType, .none) XCTAssertEqual(panel.level, .floating) XCTAssertTrue(panel.collectionBehavior.contains(.canJoinAllSpaces)) panel.expand(to: panel.expandedFrame, animated: false) XCTAssertEqual(panel.frame, frame) panel.dismiss() XCTAssertNil(panel.contentView) XCTAssertFalse(panel.isVisible) } /// The mechanism dismiss() relies on to hand renderers back: once the /// content stops naming a track, SwiftUI takes the video view out of /// the hierarchy on the next layout, which is when MacVideoTile /// removes its renderer from the track. @MainActor func testTakingTheVideoOutRemovesItsRendererAtOnce() { NSApplication.shared.setActivationPolicy(.prohibited) let factory = RTCPeerConnectionFactory() let track = factory.videoTrack(with: factory.videoSource(), trackId: "mini-test") let holder = Holder(track: track) let window = NSWindow( contentRect: NSRect(origin: .zero, size: MiniWindowRules.size), styleMask: [.borderless], backing: .buffered, defer: false) window.isReleasedWhenClosed = false window.contentView = NSHostingView(rootView: HolderView(holder: holder)) window.orderFrontRegardless() window.contentView?.layoutSubtreeIfNeeded() XCTAssertEqual(renderers(in: window.contentView), 1) holder.track = nil window.contentView?.layoutSubtreeIfNeeded() window.contentView?.displayIfNeeded() XCTAssertEqual(renderers(in: window.contentView), 0) window.close() } private func renderers(in view: NSView?) -> Int { guard let view else { return 0 } return (view is RTCMTLNSVideoView ? 1 : 0) + view.subviews.reduce(0) { $0 + renderers(in: $1) } } @MainActor private final class Holder: ObservableObject { @Published var track: RTCVideoTrack? init(track: RTCVideoTrack) { self.track = track } } private struct HolderView: View { @ObservedObject var holder: Holder var body: some View { if let track = holder.track { MacVideoTile(track: track, label: "Bea") } else { Color.black } } } } #endif