Skip to content

Commit e5a5abf

Browse files
authored
Fix unit test host crash on iOS 27 by adopting the UIScene life cycle (#25930)
* Fix unit test host crash on iOS 27 by adopting the UIScene life cycle iOS 27 removed the legacy window life cycle, so the unit test host's plain UIWindow() crashed at launch with "UIScene life cycle is required for apps built with this SDK" before the test runner could connect. This blocked every app-hosted (WordPressTest) suite on iOS 27 while the same binary ran on iOS 26.5. TestingAppDelegate now opts into the scene life cycle the same way WordPressAppDelegate does, vending a minimal TestingSceneDelegate that builds the host window in scene(_:willConnectTo:). The window shows a SwiftUI "Running unit tests" placeholder. Verified the previously crashing suites pass on both iOS 27 and iOS 26.5. * Update mainWindow comment: test host now connects a window scene The unit test host adopted the UIScene life cycle, so the delegate-window fallback no longer covers it. Drop the stale claim that the test host never connects a window scene.
1 parent 4a48e36 commit e5a5abf

3 files changed

Lines changed: 42 additions & 10 deletions

File tree

Modules/Sources/WordPressSharedUI/UIApplication+MainWindow.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@ import UIKit
22

33
public extension UIApplication {
44
@objc var mainWindow: UIWindow? {
5-
// The delegate-window fallback covers the moments when no scene key window
6-
// exists: early in scene connection (before makeKeyAndVisible) and in the unit
7-
// test host, which never connects a window scene.
5+
// The delegate-window fallback covers the brief moment early in scene
6+
// connection, before the scene's key window is made visible.
87
connectedScenes
98
.compactMap { ($0 as? UIWindowScene)?.keyWindow }
109
.first

WordPress/Classes/System/WordPressAppDelegate.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,8 @@ public class WordPressAppDelegate: UIResponder, UIApplicationDelegate {
136136

137137
/// Opts the app into the UIScene life cycle and names the scene delegate in code,
138138
/// instead of a `UISceneConfigurations` entry duplicated in each target's Info.plist.
139-
/// The unit test host runs TestingAppDelegate, which doesn't implement this method,
140-
/// so tests keep the legacy life cycle.
139+
/// The unit test host (TestingAppDelegate) opts into the scene life cycle the same
140+
/// way, because iOS 27 removed the legacy window life cycle.
141141
public func application(
142142
_ application: UIApplication,
143143
configurationForConnecting connectingSceneSession: UISceneSession,

WordPress/Classes/System/main.swift

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import BuildSettingsKit
22
import Foundation
3+
import SwiftUI
34
import UIKit
45

56
let isRunningTests = NSClassFromString("XCTestCase") != nil
6-
let appDelegateClass = isRunningTests ? NSStringFromClass(TestingAppDelegate.self) : NSStringFromClass(WordPressAppDelegate.self)
7+
let appDelegateClass =
8+
isRunningTests ? NSStringFromClass(TestingAppDelegate.self) : NSStringFromClass(WordPressAppDelegate.self)
79

810
// The secrets MUST be configured before the app launches.
911
//
@@ -20,11 +22,42 @@ UIApplicationMain(
2022
)
2123

2224
final class TestingAppDelegate: NSObject, UIApplicationDelegate {
25+
/// Opt the unit test host into the UIScene life cycle. iOS 27 removed the legacy
26+
/// window life cycle, so a plain `UIWindow()` created here crashes the host at launch.
27+
/// Mirrors `WordPressAppDelegate`'s programmatic scene opt-in.
28+
func application(
29+
_ application: UIApplication,
30+
configurationForConnecting connectingSceneSession: UISceneSession,
31+
options: UIScene.ConnectionOptions
32+
) -> UISceneConfiguration {
33+
let configuration = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role)
34+
if connectingSceneSession.role == .windowApplication {
35+
configuration.delegateClass = TestingSceneDelegate.self
36+
}
37+
return configuration
38+
}
39+
}
40+
41+
final class TestingSceneDelegate: UIResponder, UIWindowSceneDelegate {
2342
var window: UIWindow?
2443

25-
func applicationDidFinishLaunching(_ application: UIApplication) {
26-
window = UIWindow()
27-
window?.rootViewController = UIViewController()
28-
window?.makeKeyAndVisible()
44+
func scene(
45+
_ scene: UIScene,
46+
willConnectTo session: UISceneSession,
47+
options connectionOptions: UIScene.ConnectionOptions
48+
) {
49+
guard let windowScene = scene as? UIWindowScene else {
50+
return
51+
}
52+
let window = UIWindow(windowScene: windowScene)
53+
window.rootViewController = UIHostingController(rootView: TestingRootView())
54+
window.makeKeyAndVisible()
55+
self.window = window
56+
}
57+
}
58+
59+
private struct TestingRootView: View {
60+
var body: some View {
61+
Text("Running unit tests")
2962
}
3063
}

0 commit comments

Comments
 (0)