mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

The code to generate platform files used to use the presence of an 'example' folder to determine whether or not a project was a plugin (in which case that generation is bypassed). Later, robust detection was added based on the pubspec.yaml `plugin` key, but the old check was left in place. This creates false positives where people who add an example folder to their app project start getting mysterious failures. Since the pubspec check is definitive, there's no reason to continue to use the presence of `example` as an indicator at all. Fixes https://github.com/flutter/flutter/issues/87007
1063 lines
44 KiB
Dart
1063 lines
44 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.
|
|
|
|
// @dart = 2.8
|
|
|
|
import 'package:file/file.dart';
|
|
import 'package:file/memory.dart';
|
|
import 'package:flutter_tools/src/base/file_system.dart';
|
|
import 'package:flutter_tools/src/base/logger.dart';
|
|
import 'package:flutter_tools/src/base/os.dart';
|
|
import 'package:flutter_tools/src/build_info.dart';
|
|
import 'package:flutter_tools/src/cache.dart';
|
|
import 'package:flutter_tools/src/convert.dart';
|
|
import 'package:flutter_tools/src/features.dart';
|
|
import 'package:flutter_tools/src/flutter_manifest.dart';
|
|
import 'package:flutter_tools/src/globals.dart' as globals;
|
|
import 'package:flutter_tools/src/ios/plist_parser.dart';
|
|
import 'package:flutter_tools/src/ios/xcodeproj.dart';
|
|
import 'package:flutter_tools/src/project.dart';
|
|
import 'package:meta/meta.dart';
|
|
import 'package:test/fake.dart';
|
|
|
|
import '../src/common.dart';
|
|
import '../src/context.dart';
|
|
import '../src/fakes.dart';
|
|
|
|
void main() {
|
|
// TODO(zanderso): remove once FlutterProject is fully refactored.
|
|
// this is safe since no tests have expectations on the test logger.
|
|
final BufferLogger logger = BufferLogger.test();
|
|
|
|
group('Project', () {
|
|
group('construction', () {
|
|
_testInMemory('fails on null directory', () async {
|
|
expect(
|
|
() => FlutterProject.fromDirectory(null),
|
|
throwsAssertionError,
|
|
);
|
|
});
|
|
|
|
testWithoutContext('invalid utf8 throws a tool exit', () {
|
|
final FileSystem fileSystem = MemoryFileSystem.test();
|
|
final FlutterProjectFactory projectFactory = FlutterProjectFactory(
|
|
fileSystem: fileSystem,
|
|
logger: BufferLogger.test(),
|
|
);
|
|
fileSystem.file('pubspec.yaml').writeAsBytesSync(<int>[0xFFFE]);
|
|
|
|
/// Technically this should throw a FileSystemException but this is
|
|
/// currently a bug in package:file.
|
|
expect(
|
|
() => projectFactory.fromDirectory(fileSystem.currentDirectory),
|
|
throwsToolExit(),
|
|
);
|
|
});
|
|
|
|
_testInMemory('fails on invalid pubspec.yaml', () async {
|
|
final Directory directory = globals.fs.directory('myproject');
|
|
directory.childFile('pubspec.yaml')
|
|
..createSync(recursive: true)
|
|
..writeAsStringSync(invalidPubspec);
|
|
|
|
expect(
|
|
() => FlutterProject.fromDirectory(directory),
|
|
throwsToolExit(),
|
|
);
|
|
});
|
|
|
|
_testInMemory('fails on pubspec.yaml parse failure', () async {
|
|
final Directory directory = globals.fs.directory('myproject');
|
|
directory.childFile('pubspec.yaml')
|
|
..createSync(recursive: true)
|
|
..writeAsStringSync(parseErrorPubspec);
|
|
|
|
expect(
|
|
() => FlutterProject.fromDirectory(directory),
|
|
throwsToolExit(),
|
|
);
|
|
});
|
|
|
|
_testInMemory('fails on invalid example/pubspec.yaml', () async {
|
|
final Directory directory = globals.fs.directory('myproject');
|
|
directory.childDirectory('example').childFile('pubspec.yaml')
|
|
..createSync(recursive: true)
|
|
..writeAsStringSync(invalidPubspec);
|
|
|
|
expect(
|
|
() => FlutterProject.fromDirectory(directory),
|
|
throwsToolExit(),
|
|
);
|
|
});
|
|
|
|
_testInMemory('treats missing pubspec.yaml as empty', () async {
|
|
final Directory directory = globals.fs.directory('myproject')
|
|
..createSync(recursive: true);
|
|
expect(FlutterProject.fromDirectory(directory).manifest.isEmpty,
|
|
true,
|
|
);
|
|
});
|
|
|
|
_testInMemory('reads valid pubspec.yaml', () async {
|
|
final Directory directory = globals.fs.directory('myproject');
|
|
directory.childFile('pubspec.yaml')
|
|
..createSync(recursive: true)
|
|
..writeAsStringSync(validPubspec);
|
|
expect(
|
|
FlutterProject.fromDirectory(directory).manifest.appName,
|
|
'hello',
|
|
);
|
|
});
|
|
|
|
_testInMemory('reads dependencies from pubspec.yaml', () async {
|
|
final Directory directory = globals.fs.directory('myproject');
|
|
directory.childFile('pubspec.yaml')
|
|
..createSync(recursive: true)
|
|
..writeAsStringSync(validPubspecWithDependencies);
|
|
expect(
|
|
FlutterProject.fromDirectory(directory).manifest.dependencies,
|
|
<String>{'plugin_a', 'plugin_b'},
|
|
);
|
|
});
|
|
|
|
_testInMemory('sets up location', () async {
|
|
final Directory directory = globals.fs.directory('myproject');
|
|
expect(
|
|
FlutterProject.fromDirectory(directory).directory.absolute.path,
|
|
directory.absolute.path,
|
|
);
|
|
expect(
|
|
FlutterProject.fromDirectoryTest(directory).directory.absolute.path,
|
|
directory.absolute.path,
|
|
);
|
|
expect(
|
|
FlutterProject.current().directory.absolute.path,
|
|
globals.fs.currentDirectory.absolute.path,
|
|
);
|
|
});
|
|
});
|
|
|
|
group('ensure ready for platform-specific tooling', () {
|
|
_testInMemory('does nothing, if project is not created', () async {
|
|
final FlutterProject project = FlutterProject(
|
|
globals.fs.directory('not_created'),
|
|
FlutterManifest.empty(logger: logger),
|
|
FlutterManifest.empty(logger: logger),
|
|
);
|
|
await project.regeneratePlatformSpecificTooling();
|
|
expectNotExists(project.directory);
|
|
});
|
|
_testInMemory('does nothing in plugin or package root project', () async {
|
|
final FlutterProject project = await aPluginProject();
|
|
await project.regeneratePlatformSpecificTooling();
|
|
expectNotExists(project.ios.hostAppRoot.childDirectory('Runner').childFile('GeneratedPluginRegistrant.h'));
|
|
expectNotExists(androidPluginRegistrant(project.android.hostAppGradleRoot.childDirectory('app')));
|
|
expectNotExists(project.ios.hostAppRoot.childDirectory('Flutter').childFile('Generated.xcconfig'));
|
|
expectNotExists(project.android.hostAppGradleRoot.childFile('local.properties'));
|
|
});
|
|
_testInMemory('works if there is an "example" folder', () async {
|
|
final FlutterProject project = await someProject();
|
|
// The presence of an "example" folder used to be used as an indicator
|
|
// that a project was a plugin, but shouldn't be as this creates false
|
|
// positives.
|
|
project.directory.childDirectory('example').createSync();
|
|
await project.regeneratePlatformSpecificTooling();
|
|
expectExists(project.ios.hostAppRoot.childDirectory('Runner').childFile('GeneratedPluginRegistrant.h'));
|
|
expectExists(androidPluginRegistrant(project.android.hostAppGradleRoot.childDirectory('app')));
|
|
expectExists(project.ios.hostAppRoot.childDirectory('Flutter').childFile('Generated.xcconfig'));
|
|
expectExists(project.android.hostAppGradleRoot.childFile('local.properties'));
|
|
});
|
|
_testInMemory('injects plugins for iOS', () async {
|
|
final FlutterProject project = await someProject();
|
|
await project.regeneratePlatformSpecificTooling();
|
|
expectExists(project.ios.hostAppRoot.childDirectory('Runner').childFile('GeneratedPluginRegistrant.h'));
|
|
});
|
|
_testInMemory('generates Xcode configuration for iOS', () async {
|
|
final FlutterProject project = await someProject();
|
|
await project.regeneratePlatformSpecificTooling();
|
|
expectExists(project.ios.hostAppRoot.childDirectory('Flutter').childFile('Generated.xcconfig'));
|
|
});
|
|
_testInMemory('injects plugins for Android', () async {
|
|
final FlutterProject project = await someProject();
|
|
await project.regeneratePlatformSpecificTooling();
|
|
expectExists(androidPluginRegistrant(project.android.hostAppGradleRoot.childDirectory('app')));
|
|
});
|
|
_testInMemory('updates local properties for Android', () async {
|
|
final FlutterProject project = await someProject();
|
|
await project.regeneratePlatformSpecificTooling();
|
|
expectExists(project.android.hostAppGradleRoot.childFile('local.properties'));
|
|
});
|
|
_testInMemory('Android project not on v2 embedding shows a warning', () async {
|
|
final FlutterProject project = await someProject();
|
|
// The default someProject with an empty <manifest> already indicates
|
|
// v1 embedding, as opposed to having <meta-data
|
|
// android:name="flutterEmbedding" android:value="2" />.
|
|
|
|
project.checkForDeprecation(deprecationBehavior: DeprecationBehavior.ignore);
|
|
expect(testLogger.statusText, contains('https://flutter.dev/go/android-project-migration'));
|
|
});
|
|
_testInMemory('Android project not on v2 embedding exits', () async {
|
|
final FlutterProject project = await someProject();
|
|
// The default someProject with an empty <manifest> already indicates
|
|
// v1 embedding, as opposed to having <meta-data
|
|
// android:name="flutterEmbedding" android:value="2" />.
|
|
|
|
await expectToolExitLater(
|
|
Future<dynamic>.sync(() => project.checkForDeprecation(deprecationBehavior: DeprecationBehavior.exit)),
|
|
contains('Build failed due to use of deprecated Android v1 embedding.')
|
|
);
|
|
expect(testLogger.statusText, contains('https://flutter.dev/go/android-project-migration'));
|
|
expect(testLogger.statusText, contains('No `<meta-data android:name="flutterEmbedding" android:value="2"/>` in '));
|
|
});
|
|
_testInMemory('Project not on v2 embedding does not warn if deprecation status is irrelevant', () async {
|
|
final FlutterProject project = await someProject();
|
|
// The default someProject with an empty <manifest> already indicates
|
|
// v1 embedding, as opposed to having <meta-data
|
|
// android:name="flutterEmbedding" android:value="2" />.
|
|
|
|
project.checkForDeprecation(deprecationBehavior: DeprecationBehavior.none);
|
|
expect(testLogger.statusText, isEmpty);
|
|
});
|
|
_testInMemory('Android project not on v2 embedding ignore continues', () async {
|
|
final FlutterProject project = await someProject();
|
|
// The default someProject with an empty <manifest> already indicates
|
|
// v1 embedding, as opposed to having <meta-data
|
|
// android:name="flutterEmbedding" android:value="2" />.
|
|
|
|
project.checkForDeprecation(deprecationBehavior: DeprecationBehavior.ignore);
|
|
expect(testLogger.statusText, contains('https://flutter.dev/go/android-project-migration'));
|
|
});
|
|
_testInMemory('Android plugin project does not throw v1 embedding deprecation warning', () async {
|
|
final FlutterProject project = await aPluginProject();
|
|
|
|
project.checkForDeprecation(deprecationBehavior: DeprecationBehavior.exit);
|
|
expect(testLogger.statusText, isNot(contains('https://flutter.dev/go/android-project-migration')));
|
|
expect(testLogger.statusText, isNot(contains('No `<meta-data android:name="flutterEmbedding" android:value="2"/>` in ')));
|
|
});
|
|
_testInMemory('Android plugin without example app does not show a warning', () async {
|
|
final FlutterProject project = await aPluginProject();
|
|
project.example.directory.deleteSync();
|
|
|
|
await project.regeneratePlatformSpecificTooling();
|
|
expect(testLogger.statusText, isNot(contains('https://flutter.dev/go/android-project-migration')));
|
|
});
|
|
_testInMemory('updates local properties for Android', () async {
|
|
final FlutterProject project = await someProject();
|
|
await project.regeneratePlatformSpecificTooling();
|
|
expectExists(project.android.hostAppGradleRoot.childFile('local.properties'));
|
|
});
|
|
testUsingContext('injects plugins for macOS', () async {
|
|
final FlutterProject project = await someProject();
|
|
project.macos.managedDirectory.createSync(recursive: true);
|
|
await project.regeneratePlatformSpecificTooling();
|
|
expectExists(project.macos.managedDirectory.childFile('GeneratedPluginRegistrant.swift'));
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => MemoryFileSystem.test(),
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true),
|
|
FlutterProjectFactory: () => FlutterProjectFactory(
|
|
logger: logger,
|
|
fileSystem: globals.fs,
|
|
),
|
|
});
|
|
testUsingContext('generates Xcode configuration for macOS', () async {
|
|
final FlutterProject project = await someProject();
|
|
project.macos.managedDirectory.createSync(recursive: true);
|
|
await project.regeneratePlatformSpecificTooling();
|
|
expectExists(project.macos.generatedXcodePropertiesFile);
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => MemoryFileSystem.test(),
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true),
|
|
FlutterProjectFactory: () => FlutterProjectFactory(
|
|
logger: logger,
|
|
fileSystem: globals.fs,
|
|
),
|
|
});
|
|
testUsingContext('injects plugins for Linux', () async {
|
|
final FlutterProject project = await someProject();
|
|
project.linux.cmakeFile.createSync(recursive: true);
|
|
await project.regeneratePlatformSpecificTooling();
|
|
expectExists(project.linux.managedDirectory.childFile('generated_plugin_registrant.h'));
|
|
expectExists(project.linux.managedDirectory.childFile('generated_plugin_registrant.cc'));
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => MemoryFileSystem.test(),
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
|
|
FlutterProjectFactory: () => FlutterProjectFactory(
|
|
logger: logger,
|
|
fileSystem: globals.fs,
|
|
),
|
|
});
|
|
testUsingContext('injects plugins for Windows', () async {
|
|
final FlutterProject project = await someProject();
|
|
project.windows.cmakeFile.createSync(recursive: true);
|
|
await project.regeneratePlatformSpecificTooling();
|
|
expectExists(project.windows.managedDirectory.childFile('generated_plugin_registrant.h'));
|
|
expectExists(project.windows.managedDirectory.childFile('generated_plugin_registrant.cc'));
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => MemoryFileSystem.test(),
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
|
|
FlutterProjectFactory: () => FlutterProjectFactory(
|
|
logger: logger,
|
|
fileSystem: globals.fs,
|
|
),
|
|
});
|
|
_testInMemory('creates Android library in module', () async {
|
|
final FlutterProject project = await aModuleProject();
|
|
await project.regeneratePlatformSpecificTooling();
|
|
expectExists(project.android.hostAppGradleRoot.childFile('settings.gradle'));
|
|
expectExists(project.android.hostAppGradleRoot.childFile('local.properties'));
|
|
expectExists(androidPluginRegistrant(project.android.hostAppGradleRoot.childDirectory('Flutter')));
|
|
});
|
|
_testInMemory('creates iOS pod in module', () async {
|
|
final FlutterProject project = await aModuleProject();
|
|
await project.regeneratePlatformSpecificTooling();
|
|
final Directory flutter = project.ios.hostAppRoot.childDirectory('Flutter');
|
|
expectExists(flutter.childFile('podhelper.rb'));
|
|
expectExists(flutter.childFile('flutter_export_environment.sh'));
|
|
expectExists(flutter.childFile('${project.manifest.appName}.podspec'));
|
|
expectExists(flutter.childFile('Generated.xcconfig'));
|
|
final Directory pluginRegistrantClasses = flutter
|
|
.childDirectory('FlutterPluginRegistrant')
|
|
.childDirectory('Classes');
|
|
expectExists(pluginRegistrantClasses.childFile('GeneratedPluginRegistrant.h'));
|
|
expectExists(pluginRegistrantClasses.childFile('GeneratedPluginRegistrant.m'));
|
|
});
|
|
|
|
testUsingContext('Version.json info is correct', () {
|
|
final MemoryFileSystem fileSystem = MemoryFileSystem.test();
|
|
final FlutterManifest manifest = FlutterManifest.createFromString('''
|
|
name: test
|
|
version: 1.0.0+3
|
|
''', logger: BufferLogger.test());
|
|
final FlutterProject project = FlutterProject(fileSystem.systemTempDirectory,manifest,manifest);
|
|
final Map<String, dynamic> versionInfo = jsonDecode(project.getVersionInfo()) as Map<String, dynamic>;
|
|
expect(versionInfo['app_name'],'test');
|
|
expect(versionInfo['version'],'1.0.0');
|
|
expect(versionInfo['build_number'],'3');
|
|
expect(versionInfo['package_name'],'test');
|
|
});
|
|
});
|
|
|
|
group('module status', () {
|
|
_testInMemory('is known for module', () async {
|
|
final FlutterProject project = await aModuleProject();
|
|
expect(project.isModule, isTrue);
|
|
expect(project.android.isModule, isTrue);
|
|
expect(project.ios.isModule, isTrue);
|
|
expect(project.android.hostAppGradleRoot.basename, '.android');
|
|
expect(project.ios.hostAppRoot.basename, '.ios');
|
|
});
|
|
_testInMemory('is known for non-module', () async {
|
|
final FlutterProject project = await someProject();
|
|
expect(project.isModule, isFalse);
|
|
expect(project.android.isModule, isFalse);
|
|
expect(project.ios.isModule, isFalse);
|
|
expect(project.android.hostAppGradleRoot.basename, 'android');
|
|
expect(project.ios.hostAppRoot.basename, 'ios');
|
|
});
|
|
});
|
|
|
|
group('example', () {
|
|
_testInMemory('exists for plugin in legacy format', () async {
|
|
final FlutterProject project = await aPluginProject();
|
|
expect(project.isPlugin, isTrue);
|
|
expect(project.hasExampleApp, isTrue);
|
|
});
|
|
_testInMemory('exists for plugin in multi-platform format', () async {
|
|
final FlutterProject project = await aPluginProject(legacy: false);
|
|
expect(project.hasExampleApp, isTrue);
|
|
});
|
|
_testInMemory('does not exist for non-plugin', () async {
|
|
final FlutterProject project = await someProject();
|
|
expect(project.isPlugin, isFalse);
|
|
expect(project.hasExampleApp, isFalse);
|
|
});
|
|
});
|
|
|
|
group('language', () {
|
|
XcodeProjectInterpreter xcodeProjectInterpreter;
|
|
MemoryFileSystem fs;
|
|
FlutterProjectFactory flutterProjectFactory;
|
|
setUp(() {
|
|
fs = MemoryFileSystem.test();
|
|
xcodeProjectInterpreter = XcodeProjectInterpreter.test(processManager: FakeProcessManager.any());
|
|
flutterProjectFactory = FlutterProjectFactory(
|
|
logger: logger,
|
|
fileSystem: fs,
|
|
);
|
|
});
|
|
|
|
_testInMemory('default host app language', () async {
|
|
final FlutterProject project = await someProject();
|
|
expect(project.android.isKotlin, isFalse);
|
|
});
|
|
|
|
testUsingContext('kotlin host app language', () async {
|
|
final FlutterProject project = await someProject();
|
|
|
|
addAndroidGradleFile(project.directory,
|
|
gradleFileContent: () {
|
|
return '''
|
|
apply plugin: 'com.android.application'
|
|
apply plugin: 'kotlin-android'
|
|
''';
|
|
});
|
|
expect(project.android.isKotlin, isTrue);
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
XcodeProjectInterpreter: () => xcodeProjectInterpreter,
|
|
FlutterProjectFactory: () => flutterProjectFactory,
|
|
});
|
|
});
|
|
|
|
group('product bundle identifier', () {
|
|
MemoryFileSystem fs;
|
|
FakePlistParser testPlistUtils;
|
|
MockXcodeProjectInterpreter mockXcodeProjectInterpreter;
|
|
FlutterProjectFactory flutterProjectFactory;
|
|
setUp(() {
|
|
fs = MemoryFileSystem.test();
|
|
testPlistUtils = FakePlistParser();
|
|
mockXcodeProjectInterpreter = MockXcodeProjectInterpreter();
|
|
flutterProjectFactory = FlutterProjectFactory(
|
|
fileSystem: fs,
|
|
logger: logger,
|
|
);
|
|
});
|
|
|
|
void testWithMocks(String description, Future<void> Function() testMethod) {
|
|
testUsingContext(description, testMethod, overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
PlistParser: () => testPlistUtils,
|
|
XcodeProjectInterpreter: () => mockXcodeProjectInterpreter,
|
|
FlutterProjectFactory: () => flutterProjectFactory,
|
|
});
|
|
}
|
|
|
|
testWithMocks('null, if no build settings or plist entries', () async {
|
|
final FlutterProject project = await someProject();
|
|
expect(await project.ios.productBundleIdentifier(null), isNull);
|
|
});
|
|
|
|
testWithMocks('from build settings, if no plist', () async {
|
|
final FlutterProject project = await someProject();
|
|
project.ios.xcodeProject.createSync();
|
|
mockXcodeProjectInterpreter.buildSettings = <String, String>{
|
|
'PRODUCT_BUNDLE_IDENTIFIER': 'io.flutter.someProject',
|
|
};
|
|
mockXcodeProjectInterpreter.xcodeProjectInfo = XcodeProjectInfo(<String>[], <String>[], <String>['Runner'], logger);
|
|
|
|
expect(await project.ios.productBundleIdentifier(null), 'io.flutter.someProject');
|
|
});
|
|
|
|
testWithMocks('from project file, if no plist or build settings', () async {
|
|
final FlutterProject project = await someProject();
|
|
addIosProjectFile(project.directory, projectFileContent: () {
|
|
return projectFileWithBundleId('io.flutter.someProject');
|
|
});
|
|
expect(await project.ios.productBundleIdentifier(null), 'io.flutter.someProject');
|
|
});
|
|
|
|
testWithMocks('from plist, if no variables', () async {
|
|
final FlutterProject project = await someProject();
|
|
project.ios.defaultHostInfoPlist.createSync(recursive: true);
|
|
testPlistUtils.setProperty('CFBundleIdentifier', 'io.flutter.someProject');
|
|
expect(await project.ios.productBundleIdentifier(null), 'io.flutter.someProject');
|
|
});
|
|
|
|
testWithMocks('from build settings and plist, if default variable', () async {
|
|
final FlutterProject project = await someProject();
|
|
project.ios.xcodeProject.createSync();
|
|
mockXcodeProjectInterpreter.buildSettings = <String, String>{
|
|
'PRODUCT_BUNDLE_IDENTIFIER': 'io.flutter.someProject',
|
|
};
|
|
mockXcodeProjectInterpreter.xcodeProjectInfo = XcodeProjectInfo(<String>[], <String>[], <String>['Runner'], logger);
|
|
testPlistUtils.setProperty('CFBundleIdentifier', r'$(PRODUCT_BUNDLE_IDENTIFIER)');
|
|
|
|
expect(await project.ios.productBundleIdentifier(null), 'io.flutter.someProject');
|
|
});
|
|
|
|
testWithMocks('from build settings and plist, by substitution', () async {
|
|
final FlutterProject project = await someProject();
|
|
project.ios.xcodeProject.createSync();
|
|
project.ios.defaultHostInfoPlist.createSync(recursive: true);
|
|
mockXcodeProjectInterpreter.buildSettings = <String, String>{
|
|
'PRODUCT_BUNDLE_IDENTIFIER': 'io.flutter.someProject',
|
|
'SUFFIX': 'suffix',
|
|
};
|
|
mockXcodeProjectInterpreter.xcodeProjectInfo = XcodeProjectInfo(<String>[], <String>[], <String>['Runner'], logger);
|
|
testPlistUtils.setProperty('CFBundleIdentifier', r'$(PRODUCT_BUNDLE_IDENTIFIER).$(SUFFIX)');
|
|
|
|
expect(await project.ios.productBundleIdentifier(null), 'io.flutter.someProject.suffix');
|
|
});
|
|
|
|
testWithMocks('fails with no flavor and defined schemes', () async {
|
|
final FlutterProject project = await someProject();
|
|
project.ios.xcodeProject.createSync();
|
|
mockXcodeProjectInterpreter.xcodeProjectInfo = XcodeProjectInfo(<String>[], <String>[], <String>['free', 'paid'], logger);
|
|
|
|
await expectToolExitLater(
|
|
project.ios.productBundleIdentifier(null),
|
|
contains('You must specify a --flavor option to select one of the available schemes.')
|
|
);
|
|
});
|
|
|
|
testWithMocks('handles case insensitive flavor', () async {
|
|
final FlutterProject project = await someProject();
|
|
project.ios.xcodeProject.createSync();
|
|
mockXcodeProjectInterpreter.buildSettings = <String, String>{
|
|
'PRODUCT_BUNDLE_IDENTIFIER': 'io.flutter.someProject',
|
|
};
|
|
mockXcodeProjectInterpreter.xcodeProjectInfo =XcodeProjectInfo(<String>[], <String>[], <String>['Free'], logger);
|
|
const BuildInfo buildInfo = BuildInfo(BuildMode.debug, 'free', treeShakeIcons: false);
|
|
|
|
expect(await project.ios.productBundleIdentifier(buildInfo), 'io.flutter.someProject');
|
|
});
|
|
|
|
testWithMocks('fails with flavor and default schemes', () async {
|
|
final FlutterProject project = await someProject();
|
|
project.ios.xcodeProject.createSync();
|
|
mockXcodeProjectInterpreter.xcodeProjectInfo = XcodeProjectInfo(<String>[], <String>[], <String>['Runner'], logger);
|
|
const BuildInfo buildInfo = BuildInfo(BuildMode.debug, 'free', treeShakeIcons: false);
|
|
|
|
await expectToolExitLater(
|
|
project.ios.productBundleIdentifier(buildInfo),
|
|
contains('The Xcode project does not define custom schemes. You cannot use the --flavor option.')
|
|
);
|
|
});
|
|
|
|
testWithMocks('empty surrounded by quotes', () async {
|
|
final FlutterProject project = await someProject();
|
|
addIosProjectFile(project.directory, projectFileContent: () {
|
|
return projectFileWithBundleId('', qualifier: '"');
|
|
});
|
|
expect(await project.ios.productBundleIdentifier(null), '');
|
|
});
|
|
|
|
testWithMocks('surrounded by double quotes', () async {
|
|
final FlutterProject project = await someProject();
|
|
addIosProjectFile(project.directory, projectFileContent: () {
|
|
return projectFileWithBundleId('io.flutter.someProject', qualifier: '"');
|
|
});
|
|
expect(await project.ios.productBundleIdentifier(null), 'io.flutter.someProject');
|
|
});
|
|
|
|
testWithMocks('surrounded by single quotes', () async {
|
|
final FlutterProject project = await someProject();
|
|
addIosProjectFile(project.directory, projectFileContent: () {
|
|
return projectFileWithBundleId('io.flutter.someProject', qualifier: "'");
|
|
});
|
|
expect(await project.ios.productBundleIdentifier(null), 'io.flutter.someProject');
|
|
});
|
|
});
|
|
|
|
group('application bundle name', () {
|
|
MemoryFileSystem fs;
|
|
MockXcodeProjectInterpreter mockXcodeProjectInterpreter;
|
|
setUp(() {
|
|
fs = MemoryFileSystem.test();
|
|
mockXcodeProjectInterpreter = MockXcodeProjectInterpreter();
|
|
});
|
|
|
|
testUsingContext('app product name defaults to Runner.app', () async {
|
|
final FlutterProject project = await someProject();
|
|
expect(await project.ios.hostAppBundleName(null), 'Runner.app');
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
XcodeProjectInterpreter: () => mockXcodeProjectInterpreter
|
|
});
|
|
|
|
testUsingContext('app product name xcodebuild settings', () async {
|
|
final FlutterProject project = await someProject();
|
|
project.ios.xcodeProject.createSync();
|
|
mockXcodeProjectInterpreter.buildSettings = <String, String>{
|
|
'FULL_PRODUCT_NAME': 'My App.app'
|
|
};
|
|
mockXcodeProjectInterpreter.xcodeProjectInfo = XcodeProjectInfo(<String>[], <String>[], <String>['Runner'], logger);
|
|
|
|
expect(await project.ios.hostAppBundleName(null), 'My App.app');
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
XcodeProjectInterpreter: () => mockXcodeProjectInterpreter
|
|
});
|
|
});
|
|
|
|
group('organization names set', () {
|
|
_testInMemory('is empty, if project not created', () async {
|
|
final FlutterProject project = await someProject();
|
|
expect(await project.organizationNames, isEmpty);
|
|
});
|
|
_testInMemory('is empty, if no platform folders exist', () async {
|
|
final FlutterProject project = await someProject();
|
|
project.directory.createSync();
|
|
expect(await project.organizationNames, isEmpty);
|
|
});
|
|
_testInMemory('is populated from iOS bundle identifier', () async {
|
|
final FlutterProject project = await someProject();
|
|
addIosProjectFile(project.directory, projectFileContent: () {
|
|
return projectFileWithBundleId('io.flutter.someProject', qualifier: "'");
|
|
});
|
|
expect(await project.organizationNames, <String>['io.flutter']);
|
|
});
|
|
_testInMemory('is populated from Android application ID', () async {
|
|
final FlutterProject project = await someProject();
|
|
addAndroidGradleFile(project.directory,
|
|
gradleFileContent: () {
|
|
return gradleFileWithApplicationId('io.flutter.someproject');
|
|
});
|
|
expect(await project.organizationNames, <String>['io.flutter']);
|
|
});
|
|
_testInMemory('is populated from iOS bundle identifier in plugin example', () async {
|
|
final FlutterProject project = await someProject();
|
|
addIosProjectFile(project.example.directory, projectFileContent: () {
|
|
return projectFileWithBundleId('io.flutter.someProject', qualifier: "'");
|
|
});
|
|
expect(await project.organizationNames, <String>['io.flutter']);
|
|
});
|
|
_testInMemory('is populated from Android application ID in plugin example', () async {
|
|
final FlutterProject project = await someProject();
|
|
addAndroidGradleFile(project.example.directory,
|
|
gradleFileContent: () {
|
|
return gradleFileWithApplicationId('io.flutter.someproject');
|
|
});
|
|
expect(await project.organizationNames, <String>['io.flutter']);
|
|
});
|
|
_testInMemory('is populated from Android group in plugin', () async {
|
|
final FlutterProject project = await someProject();
|
|
addAndroidWithGroup(project.directory, 'io.flutter.someproject');
|
|
expect(await project.organizationNames, <String>['io.flutter']);
|
|
});
|
|
_testInMemory('is singleton, if sources agree', () async {
|
|
final FlutterProject project = await someProject();
|
|
addIosProjectFile(project.directory, projectFileContent: () {
|
|
return projectFileWithBundleId('io.flutter.someProject');
|
|
});
|
|
addAndroidGradleFile(project.directory,
|
|
gradleFileContent: () {
|
|
return gradleFileWithApplicationId('io.flutter.someproject');
|
|
});
|
|
expect(await project.organizationNames, <String>['io.flutter']);
|
|
});
|
|
_testInMemory('is non-singleton, if sources disagree', () async {
|
|
final FlutterProject project = await someProject();
|
|
addIosProjectFile(project.directory, projectFileContent: () {
|
|
return projectFileWithBundleId('io.flutter.someProject');
|
|
});
|
|
addAndroidGradleFile(project.directory,
|
|
gradleFileContent: () {
|
|
return gradleFileWithApplicationId('io.clutter.someproject');
|
|
});
|
|
expect(
|
|
await project.organizationNames,
|
|
<String>['io.flutter', 'io.clutter'],
|
|
);
|
|
});
|
|
});
|
|
});
|
|
group('watch companion', () {
|
|
MemoryFileSystem fs;
|
|
FakePlistParser testPlistParser;
|
|
MockXcodeProjectInterpreter mockXcodeProjectInterpreter;
|
|
FlutterProjectFactory flutterProjectFactory;
|
|
setUp(() {
|
|
fs = MemoryFileSystem.test();
|
|
testPlistParser = FakePlistParser();
|
|
mockXcodeProjectInterpreter = MockXcodeProjectInterpreter();
|
|
flutterProjectFactory = FlutterProjectFactory(
|
|
fileSystem: fs,
|
|
logger: logger,
|
|
);
|
|
});
|
|
|
|
testUsingContext('cannot find bundle identifier', () async {
|
|
final FlutterProject project = await someProject();
|
|
expect(await project.ios.containsWatchCompanion(<String>['WatchTarget'], null, '123'), isFalse);
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
PlistParser: () => testPlistParser,
|
|
XcodeProjectInterpreter: () => mockXcodeProjectInterpreter,
|
|
FlutterProjectFactory: () => flutterProjectFactory,
|
|
});
|
|
|
|
group('with bundle identifier', () {
|
|
setUp(() {
|
|
mockXcodeProjectInterpreter.buildSettings = <String, String>{
|
|
'PRODUCT_BUNDLE_IDENTIFIER': 'io.flutter.someProject',
|
|
};
|
|
mockXcodeProjectInterpreter.xcodeProjectInfo = XcodeProjectInfo(<String>[], <String>[], <String>['Runner'], logger);
|
|
});
|
|
|
|
testUsingContext('no Info.plist in target', () async {
|
|
final FlutterProject project = await someProject();
|
|
expect(await project.ios.containsWatchCompanion(<String>['WatchTarget'], null, '123'), isFalse);
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
PlistParser: () => testPlistParser,
|
|
XcodeProjectInterpreter: () => mockXcodeProjectInterpreter,
|
|
FlutterProjectFactory: () => flutterProjectFactory,
|
|
});
|
|
|
|
testUsingContext('Info.plist in target does not contain WKCompanionAppBundleIdentifier', () async {
|
|
final FlutterProject project = await someProject();
|
|
project.ios.hostAppRoot.childDirectory('WatchTarget').childFile('Info.plist').createSync(recursive: true);
|
|
|
|
expect(await project.ios.containsWatchCompanion(<String>['WatchTarget'], null, '123'), isFalse);
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
PlistParser: () => testPlistParser,
|
|
XcodeProjectInterpreter: () => mockXcodeProjectInterpreter,
|
|
FlutterProjectFactory: () => flutterProjectFactory,
|
|
});
|
|
|
|
testUsingContext('target WKCompanionAppBundleIdentifier is not project bundle identifier', () async {
|
|
final FlutterProject project = await someProject();
|
|
project.ios.hostAppRoot.childDirectory('WatchTarget').childFile('Info.plist').createSync(recursive: true);
|
|
|
|
testPlistParser.setProperty('WKCompanionAppBundleIdentifier', 'io.flutter.someOTHERproject');
|
|
expect(await project.ios.containsWatchCompanion(<String>['WatchTarget'], null, '123'), isFalse);
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
PlistParser: () => testPlistParser,
|
|
XcodeProjectInterpreter: () => mockXcodeProjectInterpreter,
|
|
FlutterProjectFactory: () => flutterProjectFactory,
|
|
});
|
|
|
|
testUsingContext('has watch companion', () async {
|
|
final FlutterProject project = await someProject();
|
|
project.ios.xcodeProject.createSync();
|
|
project.ios.hostAppRoot.childDirectory('WatchTarget').childFile('Info.plist').createSync(recursive: true);
|
|
testPlistParser.setProperty('WKCompanionAppBundleIdentifier', 'io.flutter.someProject');
|
|
|
|
expect(await project.ios.containsWatchCompanion(<String>['WatchTarget'], null, '123'), isTrue);
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
PlistParser: () => testPlistParser,
|
|
XcodeProjectInterpreter: () => mockXcodeProjectInterpreter,
|
|
FlutterProjectFactory: () => flutterProjectFactory,
|
|
});
|
|
|
|
testUsingContext('has watch companion with build settings', () async {
|
|
final FlutterProject project = await someProject();
|
|
project.ios.xcodeProject.createSync();
|
|
mockXcodeProjectInterpreter.buildSettings = <String, String>{
|
|
'PRODUCT_BUNDLE_IDENTIFIER': 'io.flutter.someProject',
|
|
};
|
|
project.ios.hostAppRoot.childDirectory('WatchTarget').childFile('Info.plist').createSync(recursive: true);
|
|
testPlistParser.setProperty('WKCompanionAppBundleIdentifier', r'$(PRODUCT_BUNDLE_IDENTIFIER)');
|
|
|
|
expect(await project.ios.containsWatchCompanion(<String>['WatchTarget'], null, '123'), isTrue);
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
PlistParser: () => testPlistParser,
|
|
XcodeProjectInterpreter: () => mockXcodeProjectInterpreter,
|
|
FlutterProjectFactory: () => flutterProjectFactory,
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
Future<FlutterProject> someProject() async {
|
|
final Directory directory = globals.fs.directory('some_project');
|
|
directory.childDirectory('.dart_tool')
|
|
.childFile('package_config.json')
|
|
..createSync(recursive: true)
|
|
..writeAsStringSync('{"configVersion":2,"packages":[]}');
|
|
directory.childDirectory('ios').createSync(recursive: true);
|
|
final Directory androidDirectory = directory
|
|
.childDirectory('android')
|
|
..createSync(recursive: true);
|
|
androidDirectory
|
|
.childFile('AndroidManifest.xml')
|
|
.writeAsStringSync('<manifest></manifest>');
|
|
return FlutterProject.fromDirectory(directory);
|
|
}
|
|
|
|
Future<FlutterProject> aPluginProject({bool legacy = true}) async {
|
|
final Directory directory = globals.fs.directory('plugin_project');
|
|
directory.childDirectory('ios').createSync(recursive: true);
|
|
directory.childDirectory('android').createSync(recursive: true);
|
|
directory.childDirectory('example').createSync(recursive: true);
|
|
String pluginPubSpec;
|
|
if (legacy) {
|
|
pluginPubSpec = '''
|
|
name: my_plugin
|
|
flutter:
|
|
plugin:
|
|
androidPackage: com.example
|
|
pluginClass: MyPlugin
|
|
iosPrefix: FLT
|
|
''';
|
|
} else {
|
|
pluginPubSpec = '''
|
|
name: my_plugin
|
|
flutter:
|
|
plugin:
|
|
platforms:
|
|
android:
|
|
package: com.example
|
|
pluginClass: MyPlugin
|
|
ios:
|
|
pluginClass: MyPlugin
|
|
linux:
|
|
pluginClass: MyPlugin
|
|
macos:
|
|
pluginClass: MyPlugin
|
|
windows:
|
|
pluginClass: MyPlugin
|
|
''';
|
|
}
|
|
directory.childFile('pubspec.yaml').writeAsStringSync(pluginPubSpec);
|
|
return FlutterProject.fromDirectory(directory);
|
|
}
|
|
|
|
Future<FlutterProject> aModuleProject() async {
|
|
final Directory directory = globals.fs.directory('module_project');
|
|
directory
|
|
.childDirectory('.dart_tool')
|
|
.childFile('package_config.json')
|
|
..createSync(recursive: true)
|
|
..writeAsStringSync('{"configVersion":2,"packages":[]}');
|
|
directory.childFile('pubspec.yaml').writeAsStringSync('''
|
|
name: my_module
|
|
flutter:
|
|
module:
|
|
androidPackage: com.example
|
|
''');
|
|
return FlutterProject.fromDirectory(directory);
|
|
}
|
|
|
|
/// Executes the [testMethod] in a context where the file system
|
|
/// is in memory.
|
|
@isTest
|
|
void _testInMemory(String description, Future<void> Function() testMethod) {
|
|
Cache.flutterRoot = getFlutterRoot();
|
|
final FileSystem testFileSystem = MemoryFileSystem(
|
|
style: globals.platform.isWindows ? FileSystemStyle.windows : FileSystemStyle.posix,
|
|
);
|
|
testFileSystem
|
|
.directory('.dart_tool')
|
|
.childFile('package_config.json')
|
|
..createSync(recursive: true)
|
|
..writeAsStringSync('{"configVersion":2,"packages":[]}');
|
|
// Transfer needed parts of the Flutter installation folder
|
|
// to the in-memory file system used during testing.
|
|
final Logger logger = BufferLogger.test();
|
|
transfer(Cache(
|
|
fileSystem: globals.fs,
|
|
logger: logger,
|
|
artifacts: <ArtifactSet>[],
|
|
osUtils: OperatingSystemUtils(
|
|
fileSystem: globals.fs,
|
|
logger: logger,
|
|
platform: globals.platform,
|
|
processManager: globals.processManager,
|
|
),
|
|
platform: globals.platform,
|
|
).getArtifactDirectory('gradle_wrapper'), testFileSystem);
|
|
transfer(globals.fs.directory(Cache.flutterRoot)
|
|
.childDirectory('packages')
|
|
.childDirectory('flutter_tools')
|
|
.childDirectory('templates'), testFileSystem);
|
|
// Set up enough of the packages to satisfy the templating code.
|
|
final File packagesFile = testFileSystem.directory(Cache.flutterRoot)
|
|
.childDirectory('packages')
|
|
.childDirectory('flutter_tools')
|
|
.childDirectory('.dart_tool')
|
|
.childFile('package_config.json');
|
|
final Directory dummyTemplateImagesDirectory = testFileSystem.directory(Cache.flutterRoot).parent;
|
|
dummyTemplateImagesDirectory.createSync(recursive: true);
|
|
packagesFile.createSync(recursive: true);
|
|
packagesFile.writeAsStringSync(json.encode(<String, Object>{
|
|
'configVersion': 2,
|
|
'packages': <Object>[
|
|
<String, Object>{
|
|
'name': 'flutter_template_images',
|
|
'rootUri': dummyTemplateImagesDirectory.uri.toString(),
|
|
'packageUri': 'lib/',
|
|
'languageVersion': '2.6'
|
|
},
|
|
],
|
|
}));
|
|
|
|
testUsingContext(
|
|
description,
|
|
testMethod,
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => testFileSystem,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Cache: () => Cache(
|
|
logger: globals.logger,
|
|
fileSystem: testFileSystem,
|
|
osUtils: globals.os,
|
|
platform: globals.platform,
|
|
artifacts: <ArtifactSet>[],
|
|
),
|
|
FlutterProjectFactory: () => FlutterProjectFactory(
|
|
fileSystem: testFileSystem,
|
|
logger: globals.logger ?? BufferLogger.test(),
|
|
),
|
|
},
|
|
);
|
|
}
|
|
|
|
/// Transfers files and folders from the local file system's Flutter
|
|
/// installation to an (in-memory) file system used for testing.
|
|
void transfer(FileSystemEntity entity, FileSystem target) {
|
|
if (entity is Directory) {
|
|
target.directory(entity.absolute.path).createSync(recursive: true);
|
|
for (final FileSystemEntity child in entity.listSync()) {
|
|
transfer(child, target);
|
|
}
|
|
} else if (entity is File) {
|
|
target.file(entity.absolute.path).writeAsBytesSync(entity.readAsBytesSync(), flush: true);
|
|
} else {
|
|
throw 'Unsupported FileSystemEntity ${entity.runtimeType}';
|
|
}
|
|
}
|
|
|
|
void expectExists(FileSystemEntity entity) {
|
|
expect(entity.existsSync(), isTrue);
|
|
}
|
|
|
|
void expectNotExists(FileSystemEntity entity) {
|
|
expect(entity.existsSync(), isFalse);
|
|
}
|
|
|
|
void addIosProjectFile(Directory directory, {String Function() projectFileContent}) {
|
|
directory
|
|
.childDirectory('ios')
|
|
.childDirectory('Runner.xcodeproj')
|
|
.childFile('project.pbxproj')
|
|
..createSync(recursive: true)
|
|
..writeAsStringSync(projectFileContent());
|
|
}
|
|
|
|
void addAndroidGradleFile(Directory directory, { String Function() gradleFileContent }) {
|
|
directory
|
|
.childDirectory('android')
|
|
.childDirectory('app')
|
|
.childFile('build.gradle')
|
|
..createSync(recursive: true)
|
|
..writeAsStringSync(gradleFileContent());
|
|
}
|
|
|
|
void addAndroidWithGroup(Directory directory, String id) {
|
|
directory.childDirectory('android').childFile('build.gradle')
|
|
..createSync(recursive: true)
|
|
..writeAsStringSync(gradleFileWithGroupId(id));
|
|
}
|
|
|
|
String get validPubspec => '''
|
|
name: hello
|
|
flutter:
|
|
''';
|
|
|
|
String get validPubspecWithDependencies => '''
|
|
name: hello
|
|
flutter:
|
|
|
|
dependencies:
|
|
plugin_a:
|
|
plugin_b:
|
|
''';
|
|
|
|
|
|
String get invalidPubspec => '''
|
|
name: hello
|
|
flutter:
|
|
invalid:
|
|
''';
|
|
|
|
String get parseErrorPubspec => '''
|
|
name: hello
|
|
# Whitespace is important.
|
|
flutter:
|
|
something:
|
|
something_else:
|
|
''';
|
|
|
|
String projectFileWithBundleId(String id, {String qualifier}) {
|
|
return '''
|
|
97C147061CF9000F007C117D /* Debug */ = {
|
|
isa = XCBuildConfiguration;
|
|
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
|
buildSettings = {
|
|
PRODUCT_BUNDLE_IDENTIFIER = ${qualifier ?? ''}$id${qualifier ?? ''};
|
|
PRODUCT_NAME = "\$(TARGET_NAME)";
|
|
};
|
|
name = Debug;
|
|
};
|
|
''';
|
|
}
|
|
|
|
String gradleFileWithApplicationId(String id) {
|
|
return '''
|
|
apply plugin: 'com.android.application'
|
|
android {
|
|
compileSdkVersion 31
|
|
|
|
defaultConfig {
|
|
applicationId '$id'
|
|
}
|
|
}
|
|
''';
|
|
}
|
|
|
|
String gradleFileWithGroupId(String id) {
|
|
return '''
|
|
group '$id'
|
|
version '1.0-SNAPSHOT'
|
|
|
|
apply plugin: 'com.android.library'
|
|
|
|
android {
|
|
compileSdkVersion 31
|
|
}
|
|
''';
|
|
}
|
|
|
|
File androidPluginRegistrant(Directory parent) {
|
|
return parent.childDirectory('src')
|
|
.childDirectory('main')
|
|
.childDirectory('java')
|
|
.childDirectory('io')
|
|
.childDirectory('flutter')
|
|
.childDirectory('plugins')
|
|
.childFile('GeneratedPluginRegistrant.java');
|
|
}
|
|
|
|
class MockXcodeProjectInterpreter extends Fake implements XcodeProjectInterpreter {
|
|
Map<String, String> buildSettings = <String, String>{};
|
|
XcodeProjectInfo xcodeProjectInfo;
|
|
|
|
@override
|
|
Future<Map<String, String>> getBuildSettings(String projectPath, {
|
|
XcodeProjectBuildContext buildContext,
|
|
Duration timeout = const Duration(minutes: 1),
|
|
}) async {
|
|
return buildSettings;
|
|
}
|
|
|
|
@override
|
|
Future<XcodeProjectInfo> getInfo(String projectPath, {String projectFilename}) async {
|
|
return xcodeProjectInfo;
|
|
}
|
|
|
|
@override
|
|
bool get isInstalled => true;
|
|
}
|