mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
WIP Commits separated as follows: - Update lints in analysis_options files - Run `dart fix --apply` - Clean up leftover analysis issues - Run `dart format .` in the right places. Local analysis and testing passes. Checking CI now. Part of https://github.com/flutter/flutter/issues/178827 - Adoption of flutter_lints in examples/api coming in a separate change (cc @loic-sharma) ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
667 lines
30 KiB
Dart
667 lines
30 KiB
Dart
// 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 kDestination = 'destination';
|
|
const kTestName = 'name';
|
|
const kXcodeProjecType = 'type';
|
|
final 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;
|
|
var 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 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,
|
|
);
|
|
|
|
var testCount = 0;
|
|
var testFailedCount = 0;
|
|
await testWithNewIOSSimulator(
|
|
'TestAdd2AppSim',
|
|
deviceTypeId: 'com.apple.CoreSimulator.SimDeviceType.iPad-Pro-11-inch-3rd-generation',
|
|
(String deviceId) async {
|
|
for (final xcodeProjectType in projectTypesToTest) {
|
|
final (String xcodeProjectName, Directory xcodeProjectDir) = await _createNativeApp(
|
|
destinationDir: destinationDir,
|
|
templatesDir: templatesDir,
|
|
xcodeProjectType: xcodeProjectType,
|
|
);
|
|
|
|
simulatorDeviceId = deviceId;
|
|
final 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 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 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 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 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 xcodeProjectDir = Directory(path.join(destinationDir.path, xcodeProjectName));
|
|
xcodeProjectDir.createSync(recursive: true);
|
|
final 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 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 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 templateFile = File(templatePath);
|
|
final destinationFile = File(destinationPath);
|
|
if (!destinationFile.existsSync()) {
|
|
File(destinationPath).createSync(recursive: true);
|
|
} else {
|
|
originalContent = destinationFile.readAsStringSync();
|
|
}
|
|
templateFile.copySync(destinationPath);
|
|
}
|
|
|
|
void reset() {
|
|
final 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 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',
|
|
};
|
|
}
|