Files
firebase-ios-sdk/FirebaseCore/Tests/Unit/FIRSceneFinderTests.m

470 lines
21 KiB
Objective-C

// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <TargetConditionals.h>
#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_VISION
#import "FirebaseCore/Tests/Unit/FIRTestCase.h"
#import "FirebaseCore/Extension/FIRSceneFinder.h"
@interface MockSceneDelegate : NSObject <UISceneDelegate>
- (void)scene:(UIScene *)scene continueUserActivity:(NSUserActivity *)userActivity;
@end
@implementation MockSceneDelegate
- (void)scene:(UIScene *)scene continueUserActivity:(NSUserActivity *)userActivity {
}
@end
@interface FIRSceneFinderTests : FIRTestCase
@property(nonatomic, strong) id mockApplication;
@end
@implementation FIRSceneFinderTests
- (void)setUp {
[super setUp];
self.mockApplication = OCMClassMock([UIApplication class]);
}
- (void)tearDown {
[self.mockApplication stopMocking];
[super tearDown];
}
- (void)testNilApplicationReturnsNil {
UIScene *result =
[FIRSceneFinder findForegroundSceneForApplication:nil
matchingSelector:@selector(scene:continueUserActivity:)];
XCTAssertNil(result);
}
- (void)testNoMatchingScene {
id mockScene = OCMClassMock([UIScene class]);
OCMStub([mockScene activationState]).andReturn(UISceneActivationStateForegroundActive);
// Delegate does NOT respond to target selector
id mockDelegate = OCMProtocolMock(@protocol(UIApplicationDelegate));
OCMStub([mockScene delegate]).andReturn(mockDelegate);
NSSet *connectedScenes = [NSSet setWithObject:mockScene];
OCMStub([self.mockApplication connectedScenes]).andReturn(connectedScenes);
UIScene *result =
[FIRSceneFinder findForegroundSceneForApplication:self.mockApplication
matchingSelector:@selector(scene:continueUserActivity:)];
XCTAssertNil(result);
}
- (void)testForegroundInactiveFallback {
// Scene A is background
id mockSceneA = OCMClassMock([UIScene class]);
OCMStub([mockSceneA activationState]).andReturn(UISceneActivationStateBackground);
id mockDelegateA = OCMClassMock([MockSceneDelegate class]);
OCMStub([mockSceneA delegate]).andReturn(mockDelegateA);
// Scene B is foreground inactive (implements selector)
id mockSceneB = OCMClassMock([UIScene class]);
OCMStub([mockSceneB activationState]).andReturn(UISceneActivationStateForegroundInactive);
id mockDelegateB = OCMClassMock([MockSceneDelegate class]);
OCMStub([mockSceneB delegate]).andReturn(mockDelegateB);
NSSet *connectedScenes = [NSSet setWithObjects:mockSceneA, mockSceneB, nil];
OCMStub([self.mockApplication connectedScenes]).andReturn(connectedScenes);
UIScene *result =
[FIRSceneFinder findForegroundSceneForApplication:self.mockApplication
matchingSelector:@selector(scene:continueUserActivity:)];
XCTAssertEqual(result, mockSceneB);
}
- (void)testForegroundActivePriority {
// Scene A is foreground inactive
id mockSceneA = OCMClassMock([UIScene class]);
OCMStub([mockSceneA activationState]).andReturn(UISceneActivationStateForegroundInactive);
id mockDelegateA = OCMClassMock([MockSceneDelegate class]);
OCMStub([mockSceneA delegate]).andReturn(mockDelegateA);
// Scene B is foreground active
id mockSceneB = OCMClassMock([UIScene class]);
OCMStub([mockSceneB activationState]).andReturn(UISceneActivationStateForegroundActive);
id mockDelegateB = OCMClassMock([MockSceneDelegate class]);
OCMStub([mockSceneB delegate]).andReturn(mockDelegateB);
NSSet *connectedScenes = [NSSet setWithObjects:mockSceneA, mockSceneB, nil];
OCMStub([self.mockApplication connectedScenes]).andReturn(connectedScenes);
UIScene *result =
[FIRSceneFinder findForegroundSceneForApplication:self.mockApplication
matchingSelector:@selector(scene:continueUserActivity:)];
XCTAssertEqual(result, mockSceneB);
}
- (void)testActiveSceneWithKeyWindowPriority {
// Scene A is foreground active (window is not key)
id mockWindowA = OCMClassMock([UIWindow class]);
OCMStub([mockWindowA isKeyWindow]).andReturn(NO);
id mockSceneA = OCMClassMock([UIWindowScene class]);
OCMStub([mockSceneA activationState]).andReturn(UISceneActivationStateForegroundActive);
OCMStub([mockSceneA windows]).andReturn(@[ mockWindowA ]);
id mockDelegateA = OCMClassMock([MockSceneDelegate class]);
OCMStub([mockSceneA delegate]).andReturn(mockDelegateA);
// Scene B is foreground active (window IS key)
id mockWindowB = OCMClassMock([UIWindow class]);
OCMStub([mockWindowB isKeyWindow]).andReturn(YES);
id mockSceneB = OCMClassMock([UIWindowScene class]);
OCMStub([mockSceneB activationState]).andReturn(UISceneActivationStateForegroundActive);
OCMStub([mockSceneB windows]).andReturn(@[ mockWindowB ]);
id mockDelegateB = OCMClassMock([MockSceneDelegate class]);
OCMStub([mockSceneB delegate]).andReturn(mockDelegateB);
NSSet *connectedScenes = [NSSet setWithObjects:mockSceneA, mockSceneB, nil];
OCMStub([self.mockApplication connectedScenes]).andReturn(connectedScenes);
UIScene *result =
[FIRSceneFinder findForegroundSceneForApplication:self.mockApplication
matchingSelector:@selector(scene:continueUserActivity:)];
XCTAssertEqual(result, mockSceneB);
}
- (void)testInactiveSceneWithKeyWindowPriority {
// Scene A is foreground inactive (window is not key)
id mockWindowA = OCMClassMock([UIWindow class]);
OCMStub([mockWindowA isKeyWindow]).andReturn(NO);
id mockSceneA = OCMClassMock([UIWindowScene class]);
OCMStub([mockSceneA activationState]).andReturn(UISceneActivationStateForegroundInactive);
OCMStub([mockSceneA windows]).andReturn(@[ mockWindowA ]);
id mockDelegateA = OCMClassMock([MockSceneDelegate class]);
OCMStub([mockSceneA delegate]).andReturn(mockDelegateA);
// Scene B is foreground inactive (window IS key)
id mockWindowB = OCMClassMock([UIWindow class]);
OCMStub([mockWindowB isKeyWindow]).andReturn(YES);
id mockSceneB = OCMClassMock([UIWindowScene class]);
OCMStub([mockSceneB activationState]).andReturn(UISceneActivationStateForegroundInactive);
OCMStub([mockSceneB windows]).andReturn(@[ mockWindowB ]);
id mockDelegateB = OCMClassMock([MockSceneDelegate class]);
OCMStub([mockSceneB delegate]).andReturn(mockDelegateB);
NSSet *connectedScenes = [NSSet setWithObjects:mockSceneA, mockSceneB, nil];
OCMStub([self.mockApplication connectedScenes]).andReturn(connectedScenes);
UIScene *result =
[FIRSceneFinder findForegroundSceneForApplication:self.mockApplication
matchingSelector:@selector(scene:continueUserActivity:)];
XCTAssertEqual(result, mockSceneB);
}
- (void)testActiveWithoutKeyWindowBeatsInactiveWithKeyWindow {
// Scene A is foreground active (window is not key)
id mockWindowA = OCMClassMock([UIWindow class]);
OCMStub([mockWindowA isKeyWindow]).andReturn(NO);
id mockSceneA = OCMClassMock([UIWindowScene class]);
OCMStub([mockSceneA activationState]).andReturn(UISceneActivationStateForegroundActive);
OCMStub([mockSceneA windows]).andReturn(@[ mockWindowA ]);
id mockDelegateA = OCMClassMock([MockSceneDelegate class]);
OCMStub([mockSceneA delegate]).andReturn(mockDelegateA);
// Scene B is foreground inactive (window IS key)
id mockWindowB = OCMClassMock([UIWindow class]);
OCMStub([mockWindowB isKeyWindow]).andReturn(YES);
id mockSceneB = OCMClassMock([UIWindowScene class]);
OCMStub([mockSceneB activationState]).andReturn(UISceneActivationStateForegroundInactive);
OCMStub([mockSceneB windows]).andReturn(@[ mockWindowB ]);
id mockDelegateB = OCMClassMock([MockSceneDelegate class]);
OCMStub([mockSceneB delegate]).andReturn(mockDelegateB);
NSSet *connectedScenes = [NSSet setWithObjects:mockSceneA, mockSceneB, nil];
OCMStub([self.mockApplication connectedScenes]).andReturn(connectedScenes);
UIScene *result =
[FIRSceneFinder findForegroundSceneForApplication:self.mockApplication
matchingSelector:@selector(scene:continueUserActivity:)];
XCTAssertEqual(result, mockSceneA);
}
#pragma mark - findForegroundWindowSceneForApplication: Tests
- (void)testFindForegroundWindowScene_nilApplicationReturnsNil {
UIWindowScene *result = [FIRSceneFinder findForegroundWindowSceneForApplication:nil];
XCTAssertNil(result);
}
- (void)testFindForegroundWindowScene_ignoresNonWindowScenes {
// Mock a plain UIScene that is NOT a UIWindowScene
id mockScene = OCMClassMock([UIScene class]);
OCMStub([mockScene activationState]).andReturn(UISceneActivationStateForegroundActive);
NSSet *connectedScenes = [NSSet setWithObject:mockScene];
OCMStub([self.mockApplication connectedScenes]).andReturn(connectedScenes);
UIWindowScene *result =
[FIRSceneFinder findForegroundWindowSceneForApplication:self.mockApplication];
XCTAssertNil(result);
}
- (void)testFindForegroundWindowScene_foregroundInactiveFallback {
// Scene A is background
id mockSceneA = OCMClassMock([UIWindowScene class]);
OCMStub([mockSceneA activationState]).andReturn(UISceneActivationStateBackground);
// Scene B is foreground inactive
id mockSceneB = OCMClassMock([UIWindowScene class]);
OCMStub([mockSceneB activationState]).andReturn(UISceneActivationStateForegroundInactive);
NSSet *connectedScenes = [NSSet setWithObjects:mockSceneA, mockSceneB, nil];
OCMStub([self.mockApplication connectedScenes]).andReturn(connectedScenes);
UIWindowScene *result =
[FIRSceneFinder findForegroundWindowSceneForApplication:self.mockApplication];
XCTAssertEqual(result, mockSceneB);
}
- (void)testFindForegroundWindowScene_foregroundActivePriority {
// Scene A is foreground inactive
id mockSceneA = OCMClassMock([UIWindowScene class]);
OCMStub([mockSceneA activationState]).andReturn(UISceneActivationStateForegroundInactive);
// Scene B is foreground active
id mockSceneB = OCMClassMock([UIWindowScene class]);
OCMStub([mockSceneB activationState]).andReturn(UISceneActivationStateForegroundActive);
NSSet *connectedScenes = [NSSet setWithObjects:mockSceneA, mockSceneB, nil];
OCMStub([self.mockApplication connectedScenes]).andReturn(connectedScenes);
UIWindowScene *result =
[FIRSceneFinder findForegroundWindowSceneForApplication:self.mockApplication];
XCTAssertEqual(result, mockSceneB);
}
- (void)testFindForegroundWindowScene_activeSceneWithKeyWindowPriority {
// Scene A is foreground active (window is not key)
id mockWindowA = OCMClassMock([UIWindow class]);
OCMStub([mockWindowA isKeyWindow]).andReturn(NO);
id mockSceneA = OCMClassMock([UIWindowScene class]);
OCMStub([mockSceneA activationState]).andReturn(UISceneActivationStateForegroundActive);
OCMStub([mockSceneA windows]).andReturn(@[ mockWindowA ]);
// Scene B is foreground active (window IS key)
id mockWindowB = OCMClassMock([UIWindow class]);
OCMStub([mockWindowB isKeyWindow]).andReturn(YES);
id mockSceneB = OCMClassMock([UIWindowScene class]);
OCMStub([mockSceneB activationState]).andReturn(UISceneActivationStateForegroundActive);
OCMStub([mockSceneB windows]).andReturn(@[ mockWindowB ]);
NSSet *connectedScenes = [NSSet setWithObjects:mockSceneA, mockSceneB, nil];
OCMStub([self.mockApplication connectedScenes]).andReturn(connectedScenes);
UIWindowScene *result =
[FIRSceneFinder findForegroundWindowSceneForApplication:self.mockApplication];
XCTAssertEqual(result, mockSceneB);
}
- (void)testFindForegroundWindowScene_inactiveSceneWithKeyWindowPriority {
// Scene A is foreground inactive (window is not key)
id mockWindowA = OCMClassMock([UIWindow class]);
OCMStub([mockWindowA isKeyWindow]).andReturn(NO);
id mockSceneA = OCMClassMock([UIWindowScene class]);
OCMStub([mockSceneA activationState]).andReturn(UISceneActivationStateForegroundInactive);
OCMStub([mockSceneA windows]).andReturn(@[ mockWindowA ]);
// Scene B is foreground inactive (window IS key)
id mockWindowB = OCMClassMock([UIWindow class]);
OCMStub([mockWindowB isKeyWindow]).andReturn(YES);
id mockSceneB = OCMClassMock([UIWindowScene class]);
OCMStub([mockSceneB activationState]).andReturn(UISceneActivationStateForegroundInactive);
OCMStub([mockSceneB windows]).andReturn(@[ mockWindowB ]);
NSSet *connectedScenes = [NSSet setWithObjects:mockSceneA, mockSceneB, nil];
OCMStub([self.mockApplication connectedScenes]).andReturn(connectedScenes);
UIWindowScene *result =
[FIRSceneFinder findForegroundWindowSceneForApplication:self.mockApplication];
XCTAssertEqual(result, mockSceneB);
}
- (void)testFindForegroundWindowScene_activeWithoutKeyWindowBeatsInactiveWithKeyWindow {
// Scene A is foreground active (window is not key)
id mockWindowA = OCMClassMock([UIWindow class]);
OCMStub([mockWindowA isKeyWindow]).andReturn(NO);
id mockSceneA = OCMClassMock([UIWindowScene class]);
OCMStub([mockSceneA activationState]).andReturn(UISceneActivationStateForegroundActive);
OCMStub([mockSceneA windows]).andReturn(@[ mockWindowA ]);
// Scene B is foreground inactive (window IS key)
id mockWindowB = OCMClassMock([UIWindow class]);
OCMStub([mockWindowB isKeyWindow]).andReturn(YES);
id mockSceneB = OCMClassMock([UIWindowScene class]);
OCMStub([mockSceneB activationState]).andReturn(UISceneActivationStateForegroundInactive);
OCMStub([mockSceneB windows]).andReturn(@[ mockWindowB ]);
NSSet *connectedScenes = [NSSet setWithObjects:mockSceneA, mockSceneB, nil];
OCMStub([self.mockApplication connectedScenes]).andReturn(connectedScenes);
UIWindowScene *result =
[FIRSceneFinder findForegroundWindowSceneForApplication:self.mockApplication];
XCTAssertEqual(result, mockSceneA);
}
#pragma mark - findForegroundSceneForApplication:matchingPredicate: Tests
- (void)testFindForegroundScene_matchingPredicate_nilApplicationReturnsNil {
UIScene *result = [FIRSceneFinder findForegroundSceneForApplication:nil
matchingPredicate:^BOOL(UIScene *scene) {
return YES;
}];
XCTAssertNil(result);
}
- (void)testFindForegroundScene_matchingPredicate_nilPredicateReturnsNil {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnonnull"
UIScene *result = [FIRSceneFinder findForegroundSceneForApplication:self.mockApplication
matchingPredicate:nil];
#pragma clang diagnostic pop
XCTAssertNil(result);
}
- (void)testFindForegroundScene_matchingPredicate_filtersNonMatchingScenes {
id mockScene = OCMClassMock([UIScene class]);
OCMStub([mockScene activationState]).andReturn(UISceneActivationStateForegroundActive);
NSSet *connectedScenes = [NSSet setWithObject:mockScene];
OCMStub([self.mockApplication connectedScenes]).andReturn(connectedScenes);
UIScene *result = [FIRSceneFinder findForegroundSceneForApplication:self.mockApplication
matchingPredicate:^BOOL(UIScene *scene) {
return NO;
}];
XCTAssertNil(result);
}
- (void)testFindForegroundScene_matchingPredicate_foregroundInactiveFallback {
// Scene A is background
id mockSceneA = OCMClassMock([UIScene class]);
OCMStub([mockSceneA activationState]).andReturn(UISceneActivationStateBackground);
// Scene B is foreground inactive
id mockSceneB = OCMClassMock([UIScene class]);
OCMStub([mockSceneB activationState]).andReturn(UISceneActivationStateForegroundInactive);
NSSet *connectedScenes = [NSSet setWithObjects:mockSceneA, mockSceneB, nil];
OCMStub([self.mockApplication connectedScenes]).andReturn(connectedScenes);
UIScene *result = [FIRSceneFinder findForegroundSceneForApplication:self.mockApplication
matchingPredicate:^BOOL(UIScene *scene) {
return scene == mockSceneB;
}];
XCTAssertEqual(result, mockSceneB);
}
- (void)testFindForegroundScene_matchingPredicate_foregroundActivePriority {
// Scene A is foreground inactive
id mockSceneA = OCMClassMock([UIScene class]);
OCMStub([mockSceneA activationState]).andReturn(UISceneActivationStateForegroundInactive);
// Scene B is foreground active
id mockSceneB = OCMClassMock([UIScene class]);
OCMStub([mockSceneB activationState]).andReturn(UISceneActivationStateForegroundActive);
NSSet *connectedScenes = [NSSet setWithObjects:mockSceneA, mockSceneB, nil];
OCMStub([self.mockApplication connectedScenes]).andReturn(connectedScenes);
UIScene *result = [FIRSceneFinder findForegroundSceneForApplication:self.mockApplication
matchingPredicate:^BOOL(UIScene *scene) {
return YES;
}];
XCTAssertEqual(result, mockSceneB);
}
- (void)testFindForegroundScene_matchingPredicate_activeSceneWithKeyWindowPriority {
// Scene A is foreground active (window is not key)
id mockWindowA = OCMClassMock([UIWindow class]);
OCMStub([mockWindowA isKeyWindow]).andReturn(NO);
id mockSceneA = OCMClassMock([UIWindowScene class]);
OCMStub([mockSceneA activationState]).andReturn(UISceneActivationStateForegroundActive);
OCMStub([mockSceneA windows]).andReturn(@[ mockWindowA ]);
// Scene B is foreground active (window IS key)
id mockWindowB = OCMClassMock([UIWindow class]);
OCMStub([mockWindowB isKeyWindow]).andReturn(YES);
id mockSceneB = OCMClassMock([UIWindowScene class]);
OCMStub([mockSceneB activationState]).andReturn(UISceneActivationStateForegroundActive);
OCMStub([mockSceneB windows]).andReturn(@[ mockWindowB ]);
NSSet *connectedScenes = [NSSet setWithObjects:mockSceneA, mockSceneB, nil];
OCMStub([self.mockApplication connectedScenes]).andReturn(connectedScenes);
UIScene *result = [FIRSceneFinder findForegroundSceneForApplication:self.mockApplication
matchingPredicate:^BOOL(UIScene *scene) {
return YES;
}];
XCTAssertEqual(result, mockSceneB);
}
- (void)testFindForegroundScene_matchingPredicate_inactiveSceneWithKeyWindowPriority {
// Scene A is foreground inactive (window is not key)
id mockWindowA = OCMClassMock([UIWindow class]);
OCMStub([mockWindowA isKeyWindow]).andReturn(NO);
id mockSceneA = OCMClassMock([UIWindowScene class]);
OCMStub([mockSceneA activationState]).andReturn(UISceneActivationStateForegroundInactive);
OCMStub([mockSceneA windows]).andReturn(@[ mockWindowA ]);
// Scene B is foreground inactive (window IS key)
id mockWindowB = OCMClassMock([UIWindow class]);
OCMStub([mockWindowB isKeyWindow]).andReturn(YES);
id mockSceneB = OCMClassMock([UIWindowScene class]);
OCMStub([mockSceneB activationState]).andReturn(UISceneActivationStateForegroundInactive);
OCMStub([mockSceneB windows]).andReturn(@[ mockWindowB ]);
NSSet *connectedScenes = [NSSet setWithObjects:mockSceneA, mockSceneB, nil];
OCMStub([self.mockApplication connectedScenes]).andReturn(connectedScenes);
UIScene *result = [FIRSceneFinder findForegroundSceneForApplication:self.mockApplication
matchingPredicate:^BOOL(UIScene *scene) {
return YES;
}];
XCTAssertEqual(result, mockSceneB);
}
- (void)testFindForegroundScene_matchingPredicate_activeWithoutKeyWindowBeatsInactiveWithKeyWindow {
// Scene A is foreground active (window is not key)
id mockWindowA = OCMClassMock([UIWindow class]);
OCMStub([mockWindowA isKeyWindow]).andReturn(NO);
id mockSceneA = OCMClassMock([UIWindowScene class]);
OCMStub([mockSceneA activationState]).andReturn(UISceneActivationStateForegroundActive);
OCMStub([mockSceneA windows]).andReturn(@[ mockWindowA ]);
// Scene B is foreground inactive (window IS key)
id mockWindowB = OCMClassMock([UIWindow class]);
OCMStub([mockWindowB isKeyWindow]).andReturn(YES);
id mockSceneB = OCMClassMock([UIWindowScene class]);
OCMStub([mockSceneB activationState]).andReturn(UISceneActivationStateForegroundInactive);
OCMStub([mockSceneB windows]).andReturn(@[ mockWindowB ]);
NSSet *connectedScenes = [NSSet setWithObjects:mockSceneA, mockSceneB, nil];
OCMStub([self.mockApplication connectedScenes]).andReturn(connectedScenes);
UIScene *result = [FIRSceneFinder findForegroundSceneForApplication:self.mockApplication
matchingPredicate:^BOOL(UIScene *scene) {
return YES;
}];
XCTAssertEqual(result, mockSceneA);
}
@end
#endif // TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_VISION