blob: 07c1a77d0eb6a65b06a2f68d838d1cf76bd8dca0 [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:io';
import 'package:args/args.dart';
import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/framework/host_agent.dart';
import 'package:flutter_devicelab/framework/ios.dart';
import 'package:flutter_devicelab/framework/task_result.dart';
import 'package:flutter_devicelab/framework/utils.dart';
import 'package:path/path.dart' as path;
/// This test creates a Flutter module, Flutter plugin, and native iOS app.
/// It adds the plugin as a dependency and embeds the Flutter module into the native iOS app.
/// Additional scenarios can be added in [Scenarios.scenarios].
///
/// To run this test locally, follow instructions in dev/devicelab/README.md.
/// The `--local-engine` and `--local-engine-host` flags can be used to run with a local engine.
///
/// `--task-args destination=[/path/to/copy/destination]` can be used to override the destination
/// of the generated apps/plugins.
///
/// e.g. `../../bin/cache/dart-sdk/bin/dart bin/test_runner.dart test -t module_uiscene_test_ios --local-engine ios_debug_sim_unopt_arm64 --local-engine-host host_debug --task-args destination=/path/to/copy/destination`
Future<void> main(List<String> args) async {
const String kDestination = 'destination';
const String kTestName = 'name';
const String kXcodeProjecType = 'type';
final ArgParser argParser = ArgParser()
..addOption(kDestination)
..addOption(kTestName)
..addOption(kXcodeProjecType);
await task(() async {
final ArgResults argResults = argParser.parse(args);
final String? destination = argResults.option(kDestination);
final Directory destinationDir;
bool destinationOverride = false;
if (destination != null) {
destinationOverride = true;
destinationDir = Directory(destination);
if (destinationDir.existsSync()) {
destinationDir.deleteSync(recursive: true);
}
destinationDir.createSync(recursive: true);
} else {
destinationDir = Directory.systemTemp.createTempSync('flutter_module_test.');
}
final String? testName = argResults.option(kTestName);
XcodeProjectType? projectType;
final String? projectTypeName = argResults.option(kXcodeProjecType);
if (projectTypeName?.toLowerCase() == 'swiftui') {
projectType = XcodeProjectType.SwiftUI;
} else if (projectTypeName?.toLowerCase() == 'uikit-swift') {
projectType = XcodeProjectType.UIKitSwift;
}
List<XcodeProjectType> projectTypesToTest = XcodeProjectType.values;
if (projectType != null) {
projectTypesToTest = <XcodeProjectType>[projectType];
}
String? simulatorDeviceId;
final Directory templatesDir = Directory(
path.join(flutterDirectory.path, 'dev', 'integration_tests', 'ios_add2app_uiscene'),
);
try {
final Directory appDir = await _createFlutterModuleApp(
destinationDir: destinationDir,
templatesDir: templatesDir,
);
final Directory pluginDir = await _createFlutterPlugin(
destinationDir: destinationDir,
templatesDir: templatesDir,
);
int testCount = 0;
int testFailedCount = 0;
await testWithNewIOSSimulator(
'TestAdd2AppSim',
deviceTypeId: 'com.apple.CoreSimulator.SimDeviceType.iPad-Pro-11-inch-3rd-generation',
(String deviceId) async {
for (final XcodeProjectType xcodeProjectType in projectTypesToTest) {
final (String xcodeProjectName, Directory xcodeProjectDir) = await _createNativeApp(
destinationDir: destinationDir,
templatesDir: templatesDir,
xcodeProjectType: xcodeProjectType,
);
simulatorDeviceId = deviceId;
final Scenarios scenarios = Scenarios();
final Map<String, Map<String, String>> scenariosMap = scenarios.scenarios(
xcodeProjectType,
);
for (final String scenarioName in scenariosMap.keys) {
if (testName != null && scenarioName != testName) {
continue;
}
final List<FileReplacements> replacements = FileReplacements.fromScenario(
scenariosMap[scenarioName]!,
templatesDir: templatesDir,
xcodeProjectDir: xcodeProjectDir,
pluginDir: pluginDir,
appDir: appDir,
);
for (final FileReplacements replacement in replacements) {
replacement.replace();
}
section('Test Scenario $scenarioName');
await _installPlugins(appDir: appDir, xcodeProjectDir: xcodeProjectDir);
final int result = await _testNativeApp(
deviceId: simulatorDeviceId!,
scenarioName: scenarioName,
templatesDir: templatesDir,
xcodeProjectDir: xcodeProjectDir,
xcodeProjectName: xcodeProjectName,
);
testCount++;
if (result != 0) {
testFailedCount++;
}
// Reset files to original between scenarios unless we're targetting a specific test.
if (testName == null) {
for (final FileReplacements replacement in replacements) {
replacement.reset();
}
}
}
}
},
);
if (testFailedCount > 0) {
return TaskResult.failure(
'$testFailedCount out of $testCount native tests failed. Search the logs for "** TEST FAILED **"',
);
}
return TaskResult.success(null);
} catch (e, stackTrace) {
print(e);
print('Task exception stack trace:\n$stackTrace');
return TaskResult.failure(e.toString());
} finally {
unawaited(removeIOSSimulator(simulatorDeviceId));
if (!destinationOverride) {
rmTree(destinationDir);
}
}
});
}
Future<Directory> _createFlutterModuleApp({
required Directory templatesDir,
required Directory destinationDir,
}) async {
section('Create Flutter Module');
const String moduleName = 'my_module';
await flutter(
'create',
options: <String>['--org', 'dev.flutter.devicelab', '--template=module', moduleName],
workingDirectory: destinationDir.path,
);
return Directory(path.join(destinationDir.path, moduleName));
}
Future<Directory> _createFlutterPlugin({
required Directory templatesDir,
required Directory destinationDir,
}) async {
section('Create Flutter Plugin');
const String pluginName = 'my_plugin';
await flutter(
'create',
options: <String>[
'--org',
'dev.flutter.devicelab',
'--template=plugin',
'--platform=ios',
pluginName,
],
workingDirectory: destinationDir.path,
);
return Directory(path.join(destinationDir.path, pluginName));
}
Future<(String, Directory)> _createNativeApp({
required Directory templatesDir,
required Directory destinationDir,
required XcodeProjectType xcodeProjectType,
}) async {
section('Create Xcode Project');
final String xcodeProjectName;
switch (xcodeProjectType) {
case XcodeProjectType.UIKitSwift:
xcodeProjectName = 'NativeUIKitSwiftExperiment';
case XcodeProjectType.SwiftUI:
xcodeProjectName = 'NativeSwiftUIExperiment';
}
// Copy Xcode project
final Directory xcodeProjectDir = Directory(path.join(destinationDir.path, xcodeProjectName));
xcodeProjectDir.createSync(recursive: true);
final Directory xcodeProjectTemplate = Directory(path.join(templatesDir.path, xcodeProjectName));
recursiveCopy(xcodeProjectTemplate, xcodeProjectDir);
return (xcodeProjectName, xcodeProjectDir);
}
Future<void> _installPlugins({
required Directory appDir,
required Directory xcodeProjectDir,
}) async {
// Poke the pubspec to reset the fingerprinter to ensure the module is re-generated.
// See [_regenerateModuleFromTemplateIfNeeded] in packages/flutter_tools/lib/src/xcode_project.dart.
final File pubspec = File(path.join(appDir.path, 'pubspec.yaml'));
pubspec.writeAsStringSync(pubspec.readAsStringSync());
await flutter(
'build',
options: <String>['ios', '--config-only', '-v'],
workingDirectory: appDir.path,
);
await flutter('pub', options: <String>['get'], workingDirectory: appDir.path);
await eval(
'pod',
<String>['install', '--verbose'],
environment: <String, String>{
// See https://github.com/flutter/flutter/issues/10873.
// CocoaPods analytics adds a lot of latency.
'COCOAPODS_DISABLE_STATS': 'true',
'LANG': 'en_US.UTF-8',
},
workingDirectory: xcodeProjectDir.path,
);
}
class FileReplacements {
FileReplacements(this.templatePath, this.destinationPath);
final String templatePath;
final String destinationPath;
String? originalContent;
static List<FileReplacements> fromScenario(
Map<String, String> replacementMap, {
required Directory templatesDir,
required Directory xcodeProjectDir,
required Directory pluginDir,
required Directory appDir,
}) {
final List<FileReplacements> replacements = <FileReplacements>[];
for (final String source in replacementMap.keys) {
final String destination = replacementMap[source]!;
final String sourcePath = source.replaceFirst(r'$TEMPLATE_DIR', templatesDir.path);
final String destinationPath = destination
.replaceFirst(r'$XCODE_PROJ_DIR', xcodeProjectDir.path)
.replaceFirst(r'$PLUGIN_DIR', pluginDir.path)
.replaceFirst(r'$APP_DIR', appDir.path);
replacements.add(FileReplacements(sourcePath, destinationPath));
}
return replacements;
}
void replace() {
final File templateFile = File(templatePath);
final File destinationFile = File(destinationPath);
if (!destinationFile.existsSync()) {
File(destinationPath).createSync(recursive: true);
} else {
originalContent = destinationFile.readAsStringSync();
}
templateFile.copySync(destinationPath);
}
void reset() {
final File destinationFile = File(destinationPath);
if (originalContent != null) {
destinationFile.writeAsStringSync(originalContent!);
} else {
if (destinationFile.existsSync()) {
destinationFile.deleteSync(recursive: true);
}
}
}
}
Future<int> _testNativeApp({
required Directory templatesDir,
required String scenarioName,
required Directory xcodeProjectDir,
required String deviceId,
required String xcodeProjectName,
}) async {
final String resultBundleTemp = Directory.systemTemp
.createTempSync('flutter_module_test_ios_xcresult.')
.path;
final String resultBundlePath = path.join(resultBundleTemp, 'result');
final int testResultExit = await exec(
'xcodebuild',
<String>[
'-workspace',
'$xcodeProjectName.xcworkspace',
'-scheme',
xcodeProjectName,
'-configuration',
'Debug',
'-destination',
'id=$deviceId',
'-resultBundlePath',
resultBundlePath,
'test',
'-parallel-testing-enabled',
'NO',
'COMPILER_INDEX_STORE_ENABLE=NO',
],
workingDirectory: xcodeProjectDir.path,
canFail: true,
);
if (testResultExit != 0) {
await _uploadTestResults(scenarioName: scenarioName, resultBundlePath: resultBundleTemp);
}
return testResultExit;
}
Future<void> _uploadTestResults({
required String scenarioName,
required String resultBundlePath,
}) async {
final Directory? dumpDirectory = hostAgent.dumpDirectory;
if (dumpDirectory != null) {
// Zip the test results to the artifacts directory for upload.
final String zipName =
'module_uiscene_test_ios-$scenarioName-${DateTime.now().toLocal().toIso8601String()}.zip';
await inDirectory(resultBundlePath, () {
final String zipPath = path.join(dumpDirectory.path, zipName);
return exec(
'zip',
<String>['-r', '-9', '-q', zipPath, 'result.xcresult'],
canFail: true, // Best effort to get the logs.
);
});
}
}
enum XcodeProjectType { UIKitSwift, SwiftUI }
class Scenarios {
Scenarios();
Map<String, Map<String, String>> scenarios(XcodeProjectType projectType) {
switch (projectType) {
case XcodeProjectType.UIKitSwift:
return uiKitSwiftScenarios;
case XcodeProjectType.SwiftUI:
return swiftUIScenarios;
}
}
/// A map of scenario names to a map of file replacements.
///
/// Each scenario is a different configuration for testing the Flutter module
/// in a native iOS app. The file replacements are used to set up the
/// specific configuration for each scenario.
late Map<String, Map<String, String>> uiKitSwiftScenarios = <String, Map<String, String>>{
...basicLifecycleScenarios,
...stateRestorationScenarios,
...implicitEngineDelegateScenarios,
...multiSceneScenarios,
};
late Map<String, Map<String, String>> swiftUIScenarios = <String, Map<String, String>>{
'SwiftUI-FlutterSceneDelegate': <String, String>{
...sharedAppLifecycleFiles,
...sharedPluginLifecycleFiles,
r'$TEMPLATE_DIR/native/Info-migrated-no-config.plist':
r'$XCODE_PROJ_DIR/NativeSwiftUIExperiment/NativeSwiftUIExperiment-Info.plist',
r'$TEMPLATE_DIR/native/SwiftUIApp-FlutterSceneDelegate.swift':
r'$XCODE_PROJ_DIR/NativeSwiftUIExperiment/NativeSwiftUIExperimentApp.swift',
r'$TEMPLATE_DIR/native/SwiftUIApp-ContentView.swift':
r'$XCODE_PROJ_DIR/NativeSwiftUIExperiment/ContentView.swift',
r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift':
r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift',
r'$TEMPLATE_DIR/native/UITests-SceneEvents-NoApplicationEvents.swift':
r'$XCODE_PROJ_DIR/NativeSwiftUIExperimentUITests/NativeSwiftUIExperimentUITests.swift',
},
'SwiftUI-FlutterSceneLifeCycleProvider': <String, String>{
...sharedAppLifecycleFiles,
...sharedPluginLifecycleFiles,
r'$TEMPLATE_DIR/native/Info-migrated-no-config.plist':
r'$XCODE_PROJ_DIR/NativeSwiftUIExperiment/NativeSwiftUIExperiment-Info.plist',
r'$TEMPLATE_DIR/native/SwiftUIApp-FlutterSceneLifeCycleProvider.swift':
r'$XCODE_PROJ_DIR/NativeSwiftUIExperiment/NativeSwiftUIExperimentApp.swift',
r'$TEMPLATE_DIR/native/SwiftUIApp-ContentView.swift':
r'$XCODE_PROJ_DIR/NativeSwiftUIExperiment/ContentView.swift',
r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift':
r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift',
r'$TEMPLATE_DIR/native/UITests-SceneEvents-NoApplicationEvents.swift':
r'$XCODE_PROJ_DIR/NativeSwiftUIExperimentUITests/NativeSwiftUIExperimentUITests.swift',
},
};
late Map<String, Map<String, String>> basicLifecycleScenarios = <String, Map<String, String>>{
// When both the app and the plugin have migrated to scenes, we expect scene events.
'AppMigrated-FlutterSceneDelegate-PluginMigrated': <String, String>{
...sharedLifecycleFiles,
r'$TEMPLATE_DIR/native/SceneDelegate-FlutterSceneDelegate.swift':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/SceneDelegate.swift',
r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift':
r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift',
r'$TEMPLATE_DIR/native/UITests-SceneEvents.swift':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperimentUITests/NativeUIKitSwiftExperimentUITests.swift',
},
// When the app has migrated but the plugin hasn't, we expect application events to be used as
// a fallback.
'AppMigrated-FlutterSceneDelegate-PluginNotMigrated': <String, String>{
...sharedLifecycleFiles,
r'$TEMPLATE_DIR/native/SceneDelegate-FlutterSceneDelegate.swift':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/SceneDelegate.swift',
r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-unmigrated.swift':
r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift',
r'$TEMPLATE_DIR/native/UITests-ApplicationEvents-AppMigrated.swift':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperimentUITests/NativeUIKitSwiftExperimentUITests.swift',
},
// When both the app and the plugin have migrated to scenes, we expect scene events.
'AppMigrated-FlutterSceneLifeCycleProvider-PluginMigrated': <String, String>{
...sharedLifecycleFiles,
r'$TEMPLATE_DIR/native/SceneDelegate-FlutterSceneLifeCycleProvider.swift':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/SceneDelegate.swift',
r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift':
r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift',
r'$TEMPLATE_DIR/native/UITests-SceneEvents.swift':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperimentUITests/NativeUIKitSwiftExperimentUITests.swift',
},
// When the app has migrated but the plugin hasn't, we expect application events to be used as
// a fallback.
'AppMigrated-FlutterSceneLifeCycleProvider-PluginNotMigrated': <String, String>{
...sharedLifecycleFiles,
r'$TEMPLATE_DIR/native/SceneDelegate-FlutterSceneLifeCycleProvider.swift':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/SceneDelegate.swift',
r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-unmigrated.swift':
r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift',
r'$TEMPLATE_DIR/native/UITests-ApplicationEvents-AppMigrated.swift':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperimentUITests/NativeUIKitSwiftExperimentUITests.swift',
},
// When the app has not migrated, but the plugin supports both, we expect application events.
'AppNotMigrated-FlutterSceneDelegate-PluginMigrated': <String, String>{
...sharedLifecycleFiles,
r'$TEMPLATE_DIR/native/Info-unmigrated.plist':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/Info.plist',
r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift':
r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift',
r'$TEMPLATE_DIR/native/UITests-ApplicationEvents-AppNotMigrated.swift':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperimentUITests/NativeUIKitSwiftExperimentUITests.swift',
},
// When the app and plugin have not migrated, we expect application events.
'AppNotMigrated-FlutterSceneDelegate-PluginNotMigrated': <String, String>{
...sharedLifecycleFiles,
r'$TEMPLATE_DIR/native/Info-unmigrated.plist':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/Info.plist',
r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-unmigrated.swift':
r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift',
r'$TEMPLATE_DIR/native/UITests-ApplicationEvents-AppNotMigrated.swift':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperimentUITests/NativeUIKitSwiftExperimentUITests.swift',
},
};
late Map<String, Map<String, String>> multiSceneScenarios = <String, Map<String, String>>{
// When multi scene is enabled and the rootViewController is a FlutterViewController, we
// expect all scene events without manual registration.
'MultiSceneEnabled-FlutterSceneDelegate-RootViewController': <String, String>{
...sharedAppLifecycleFiles,
...sharedPluginLifecycleFiles,
r'$TEMPLATE_DIR/native/Info-MultiSceneEnabled-Storyboard.plist':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/Info.plist',
r'$TEMPLATE_DIR/native/AppDelegate-FlutterAppDelegate.swift':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/AppDelegate.swift',
r'$TEMPLATE_DIR/native/Main-FlutterViewController.storyboard':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/Base.lproj/Main.storyboard',
r'$TEMPLATE_DIR/native/SceneDelegate-FlutterSceneDelegate.swift':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/SceneDelegate.swift',
r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift':
r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift',
r'$TEMPLATE_DIR/native/UITests-SceneEvents.swift':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperimentUITests/NativeUIKitSwiftExperimentUITests.swift',
},
// When multi scene is enabled and the ViewController is created programatically with a
// manually registered FlutterEngine, we expect all scene events.
'MultiSceneEnabled-FlutterSceneDelegate-ManualRegistration-NoStoryboard': <String, String>{
...sharedAppLifecycleFiles,
...sharedPluginLifecycleFiles,
r'$TEMPLATE_DIR/native/Info-MultiSceneEnabled-NoStoryboard.plist':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/Info.plist',
r'$TEMPLATE_DIR/native/AppDelegate-FlutterAppDelegate.swift':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/AppDelegate.swift',
r'$TEMPLATE_DIR/native/SceneDelegate-FlutterSceneDelegate-MultiScene-NoStoryboard.swift':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/SceneDelegate.swift',
r'$TEMPLATE_DIR/native/ViewController-FlutterEngineFromSceneDelegate-NoStoryboard.swift':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/ViewController.swift',
r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift':
r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift',
r'$TEMPLATE_DIR/native/UITests-SceneEvents-NoApplicationEvents.swift':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperimentUITests/NativeUIKitSwiftExperimentUITests.swift',
},
// When multi scene is enabled and the ViewController is created via Storyboard with a
// manually registered FlutterEngine, we expect all scene events.
'MultiSceneEnabled-FlutterSceneDelegate-ManualRegistration-Storyboard': <String, String>{
...sharedAppLifecycleFiles,
...sharedPluginLifecycleFiles,
r'$TEMPLATE_DIR/native/Info-MultiSceneEnabled-Storyboard.plist':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/Info.plist',
r'$TEMPLATE_DIR/native/AppDelegate-FlutterAppDelegate.swift':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/AppDelegate.swift',
r'$TEMPLATE_DIR/native/SceneDelegate-FlutterSceneDelegate-MultiScene-Storyboard.swift':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/SceneDelegate.swift',
r'$TEMPLATE_DIR/native/ViewController-FlutterEngineFromSceneDelegate-Storyboard.swift':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/ViewController.swift',
r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift':
r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift',
r'$TEMPLATE_DIR/native/UITests-SceneEvents-NoApplicationEvents.swift':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperimentUITests/NativeUIKitSwiftExperimentUITests.swift',
},
};
late Map<String, Map<String, String>> stateRestorationScenarios = <String, Map<String, String>>{
// State restoration work both when migrated and when not.
'AppMigrated-StateRestoration': <String, String>{...sharedStateRestorationFiles},
'AppNotMigrated-StateRestoration': <String, String>{
...sharedStateRestorationFiles,
r'$TEMPLATE_DIR/native/Info-unmigrated.plist':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/Info.plist',
},
};
late Map<String, Map<String, String>>
implicitEngineDelegateScenarios = <String, Map<String, String>>{
// When using an implicit FlutterEngine created by the storyboard, we expect plugins to
// receive application launch events and scene events.
'FlutterImplicitEngineDelegate-AppMigrated-StoryboardFlutterViewController': <String, String>{
...sharedAppLifecycleFiles,
...sharedPluginLifecycleFiles,
r'$TEMPLATE_DIR/native/SceneDelegate-FlutterSceneDelegate.swift':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/SceneDelegate.swift',
r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift':
r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift',
r'$TEMPLATE_DIR/native/Main-FlutterViewController.storyboard':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/Base.lproj/Main.storyboard',
r'$TEMPLATE_DIR/native/AppDelegate-FlutterImplicitEngineDelegate.swift':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/AppDelegate.swift',
r'$TEMPLATE_DIR/native/UITests-SceneEvents-ApplicationLaunchEvents.swift':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperimentUITests/NativeUIKitSwiftExperimentUITests.swift',
},
// When registering plugins with the AppDelegate's self (and therefore the FlutterLaunchEngine)
// alongside the FlutterImplicitEngineDelegate, we expect application events starting where
// registration occurs, such as `application:didFinishingLaunchingWithOptions`.
'FlutterImplicitEngineDelegateWithLaunchEngine-AppMigrated-StoryboardFlutterViewController':
<String, String>{
...sharedAppLifecycleFiles,
...sharedPluginLifecycleFiles,
r'$TEMPLATE_DIR/native/SceneDelegate-FlutterSceneDelegate.swift':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/SceneDelegate.swift',
r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift':
r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift',
r'$TEMPLATE_DIR/native/Main-FlutterViewController.storyboard':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/Base.lproj/Main.storyboard',
r'$TEMPLATE_DIR/native/AppDelegate-FlutterImplicitEngineDelegateWithLaunchEngine.swift':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/AppDelegate.swift',
r'$TEMPLATE_DIR/native/UITests-SceneEvents.swift':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperimentUITests/NativeUIKitSwiftExperimentUITests.swift',
},
// When the app has not migrated to scenes, storyboard is instantiated earlier in the lifecycle.
// So when using an implicit FlutterEngine created by the storyboard, we expect plugins to
// receive all application events.
'FlutterImplicitEngineDelegate-AppNotMigrated-StoryboardFlutterViewController': <String, String>{
...sharedAppLifecycleFiles,
...sharedPluginLifecycleFiles,
r'$TEMPLATE_DIR/native/Info-unmigrated.plist':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/Info.plist',
r'$TEMPLATE_DIR/native/SceneDelegate-FlutterSceneDelegate.swift':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/SceneDelegate.swift',
r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift':
r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift',
r'$TEMPLATE_DIR/native/AppDelegate-FlutterImplicitEngineDelegate.swift':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/AppDelegate.swift',
r'$TEMPLATE_DIR/native/Main-FlutterViewController.storyboard':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/Base.lproj/Main.storyboard',
r'$TEMPLATE_DIR/native/UITests-ApplicationEvents-FlutterImplicitEngineDelegate.swift':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperimentUITests/NativeUIKitSwiftExperimentUITests.swift',
},
// When using an implicit FlutterEngine, created by the FlutterViewController in another
// ViewController, we expect plugins to be registered after the FlutterViewController is
// created, which results in the `application:didFinishLaunchingWithOptions:` and
// `scene:willConnectToSession:options:` events being missed. This is not a expected use case
// but it could be utilized.
'FlutterImplicitEngineDelegate-AppMigrated-ImplicitFlutterEngine': <String, String>{
...sharedAppLifecycleFiles,
...sharedPluginLifecycleFiles,
r'$TEMPLATE_DIR/native/SceneDelegate-FlutterSceneDelegate.swift':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/SceneDelegate.swift',
r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift':
r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift',
r'$TEMPLATE_DIR/native/AppDelegate-FlutterImplicitEngineDelegate.swift':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/AppDelegate.swift',
r'$TEMPLATE_DIR/native/ViewController-ImplicitFlutterEngine.swift':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/ViewController.swift',
r'$TEMPLATE_DIR/native/UITests-SceneEventsNoConnect-NoApplicationEvents.swift':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperimentUITests/NativeUIKitSwiftExperimentUITests.swift',
},
};
late Map<String, String> sharedLifecycleFiles = <String, String>{
...sharedAppLifecycleFiles,
...sharedPluginLifecycleFiles,
r'$TEMPLATE_DIR/native/AppDelegate-FlutterAppDelegate-FlutterEngine.swift':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/AppDelegate.swift',
r'$TEMPLATE_DIR/native/ViewController-FlutterEngineFromAppDelegate.swift':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/ViewController.swift',
};
late Map<String, String> sharedAppLifecycleFiles = <String, String>{
r'$TEMPLATE_DIR/flutterapp/lib/main-LifeCycleTest': r'$APP_DIR/lib/main.dart',
r'$TEMPLATE_DIR/flutterapp/pubspec-LifeCycleTest.yaml': r'$APP_DIR/pubspec.yaml',
};
late Map<String, String> sharedPluginLifecycleFiles = <String, String>{
r'$TEMPLATE_DIR/flutterplugin/lib/lifecycle_plugin': r'$PLUGIN_DIR/lib/my_plugin.dart',
r'$TEMPLATE_DIR/flutterplugin/lib/lifecycle_plugin_method_channel':
r'$PLUGIN_DIR/lib/my_plugin_method_channel.dart',
r'$TEMPLATE_DIR/flutterplugin/lib/lifecycle_plugin_platform_interface':
r'$PLUGIN_DIR/lib/my_plugin_platform_interface.dart',
};
late Map<String, String> sharedStateRestorationFiles = <String, String>{
r'$TEMPLATE_DIR/flutterapp/lib/main-StateRestorationTest': r'$APP_DIR/lib/main.dart',
r'$TEMPLATE_DIR/native/AppDelegate-FlutterAppDelegate-FlutterEngine.swift':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/AppDelegate.swift',
r'$TEMPLATE_DIR/native/SceneDelegate-FlutterSceneDelegate.swift':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/SceneDelegate.swift',
r'$TEMPLATE_DIR/native/Main-FlutterViewController-RestorationId.storyboard':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/Base.lproj/Main.storyboard',
r'$TEMPLATE_DIR/native/UITests-StateRestoration.swift':
r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperimentUITests/NativeUIKitSwiftExperimentUITests.swift',
};
}