| // 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. |
| |
| /// @docImport 'package:yaml_edit/yaml_edit.dart'; |
| library; |
| |
| import 'package:file/file.dart'; |
| import 'package:file/memory.dart'; |
| import 'package:flutter_tools/src/android/android_sdk.dart'; |
| import 'package:flutter_tools/src/android/android_studio.dart'; |
| import 'package:flutter_tools/src/android/gradle_utils.dart' as gradle_utils; |
| import 'package:flutter_tools/src/android/java.dart'; |
| import 'package:flutter_tools/src/base/file_system.dart'; |
| import 'package:flutter_tools/src/base/logger.dart'; |
| import 'package:flutter_tools/src/base/version.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/dart/pub.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'; |
| import '../src/package_config.dart'; |
| import '../src/throwing_pub.dart'; |
| |
| void main() { |
| // TODO(zanderso): remove once FlutterProject is fully refactored. |
| // this is safe since no tests have expectations on the test logger. |
| final logger = BufferLogger.test(); |
| |
| group('Project', () { |
| group('construction', () { |
| testWithoutContext('invalid utf8 throws a tool exit', () { |
| final FileSystem fileSystem = MemoryFileSystem.test(); |
| final 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 project = FlutterProject( |
| globals.fs.directory('not_created'), |
| FlutterManifest.empty(logger: logger), |
| FlutterManifest.empty(logger: logger), |
| ); |
| await project.regeneratePlatformSpecificTooling(releaseMode: false); |
| expectNotExists(project.directory); |
| }); |
| _testInMemory('does nothing in plugin or package root project', () async { |
| final FlutterProject project = await aPluginProject(); |
| await project.regeneratePlatformSpecificTooling(releaseMode: false); |
| 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(releaseMode: false); |
| 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(releaseMode: false); |
| expectExists( |
| project.ios.hostAppRoot.childDirectory('Runner').childFile('GeneratedPluginRegistrant.h'), |
| ); |
| }); |
| _testInMemory('generates Xcode configuration for iOS', () async { |
| final FlutterProject project = await someProject(); |
| await project.regeneratePlatformSpecificTooling(releaseMode: false); |
| expectExists( |
| project.ios.hostAppRoot.childDirectory('Flutter').childFile('Generated.xcconfig'), |
| ); |
| }); |
| _testInMemory('injects plugins for Android', () async { |
| final FlutterProject project = await someProject(); |
| await project.regeneratePlatformSpecificTooling(releaseMode: false); |
| expectExists( |
| androidPluginRegistrant(project.android.hostAppGradleRoot.childDirectory('app')), |
| ); |
| }); |
| _testInMemory('updates local properties for Android', () async { |
| final FlutterProject project = await someProject(); |
| await project.regeneratePlatformSpecificTooling(releaseMode: false); |
| expectExists(project.android.hostAppGradleRoot.childFile('local.properties')); |
| }); |
| _testInMemory('checkForDeprecation fails on invalid android app manifest file', () async { |
| // This is not a valid Xml document |
| const invalidManifest = '<manifest></application>'; |
| final FlutterProject project = await someProject(androidManifestOverride: invalidManifest); |
| |
| expect( |
| () => project.checkForDeprecation(deprecationBehavior: DeprecationBehavior.ignore), |
| throwsToolExit( |
| message: |
| 'Please ensure that the android manifest is a valid XML document and try again.', |
| ), |
| ); |
| }); |
| _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" />. |
| |
| // Default is "DeprecationBehavior.none" |
| project.checkForDeprecation(); |
| expect(testLogger.statusText, isEmpty); |
| }, |
| ); |
| _testInMemory('Android project no pubspec continues', () async { |
| final FlutterProject project = await someProject(includePubspec: false); |
| // 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, |
| isNot( |
| contains( |
| 'https://github.com/flutter/flutter/blob/main/docs/platforms/android/Upgrading-pre-1.12-Android-projects.md', |
| ), |
| ), |
| ); |
| }); |
| _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://github.com/flutter/flutter/blob/main/docs/platforms/android/Upgrading-pre-1.12-Android-projects.md', |
| ), |
| ), |
| ); |
| 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(releaseMode: false); |
| expect( |
| testLogger.statusText, |
| isNot( |
| contains( |
| 'https://github.com/flutter/flutter/blob/main/docs/platforms/android/Upgrading-pre-1.12-Android-projects.md', |
| ), |
| ), |
| ); |
| }); |
| _testInMemory('updates local properties for Android', () async { |
| final FlutterProject project = await someProject(); |
| await project.regeneratePlatformSpecificTooling(releaseMode: false); |
| expectExists(project.android.hostAppGradleRoot.childFile('local.properties')); |
| }); |
| |
| testUsingContext( |
| 'determines dev dependencies', |
| () async { |
| // Create a plugin. |
| await aPluginProject(legacy: false); |
| // Create a project that depends on that plugin. |
| final FlutterProject project = await projectWithPluginDependency(); |
| // Don't bother with Android, we just want the manifest. |
| project.directory.childDirectory('android').deleteSync(recursive: true); |
| |
| await project.regeneratePlatformSpecificTooling(releaseMode: false); |
| expect( |
| project.flutterPluginsDependenciesFile.readAsStringSync(), |
| contains('"dev_dependency":true'), |
| ); |
| }, |
| overrides: <Type, Generator>{ |
| FileSystem: () => MemoryFileSystem.test(), |
| ProcessManager: () => FakeProcessManager.any(), |
| Pub: ThrowingPub.new, |
| FlutterProjectFactory: () => |
| FlutterProjectFactory(logger: logger, fileSystem: globals.fs), |
| }, |
| ); |
| |
| testUsingContext( |
| 'releaseMode: false retains dev plugins', |
| () async { |
| // Create a plugin. |
| await aPluginProject(includeAndroidMain: true, legacy: false); |
| // Create a project that depends on that plugin. |
| final FlutterProject project = await projectWithPluginDependency(); |
| |
| await project.regeneratePlatformSpecificTooling(releaseMode: false); |
| expect( |
| project.android.generatedPluginRegistrantFile.readAsStringSync(), |
| contains('MyPlugin'), |
| ); |
| }, |
| overrides: <Type, Generator>{ |
| FileSystem: () => MemoryFileSystem.test(), |
| ProcessManager: () => FakeProcessManager.any(), |
| Pub: ThrowingPub.new, |
| FlutterProjectFactory: () => |
| FlutterProjectFactory(logger: logger, fileSystem: globals.fs), |
| }, |
| ); |
| |
| testUsingContext( |
| 'releaseMode: true omits dev plugins', |
| () async { |
| // Create a plugin. |
| await aPluginProject(includeAndroidMain: true, legacy: false); |
| // Create a project that depends on that plugin. |
| final FlutterProject project = await projectWithPluginDependency(); |
| |
| await project.regeneratePlatformSpecificTooling(releaseMode: true); |
| expect( |
| project.android.generatedPluginRegistrantFile.readAsStringSync(), |
| isNot(contains('MyPlugin')), |
| ); |
| }, |
| overrides: <Type, Generator>{ |
| FileSystem: () => MemoryFileSystem.test(), |
| ProcessManager: () => FakeProcessManager.any(), |
| Pub: ThrowingPub.new, |
| FlutterProjectFactory: () => |
| FlutterProjectFactory(logger: logger, fileSystem: globals.fs), |
| }, |
| ); |
| |
| testUsingContext( |
| 'injects plugins for macOS', |
| () async { |
| final FlutterProject project = await someProject(); |
| project.macos.managedDirectory.createSync(recursive: true); |
| await project.regeneratePlatformSpecificTooling(releaseMode: false); |
| expectExists(project.macos.pluginRegistrantImplementation); |
| }, |
| 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(releaseMode: false); |
| 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(releaseMode: false); |
| 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(releaseMode: false); |
| 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(releaseMode: false); |
| 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(releaseMode: false); |
| final Directory flutter = project.ios.hostAppRoot.childDirectory('Flutter'); |
| expectExists(flutter.childFile('podhelper.rb')); |
| expectExists(flutter.childFile('flutter_export_environment.sh')); |
| 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 fileSystem = MemoryFileSystem.test(); |
| final FlutterManifest manifest = FlutterManifest.createFromString(''' |
| name: test |
| version: 1.0.0+3 |
| ''', logger: BufferLogger.test())!; |
| final project = FlutterProject(fileSystem.systemTempDirectory, manifest, manifest); |
| final 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'); |
| }); |
| _testInMemory('gets xcworkspace directory', () async { |
| final FlutterProject project = await someProject(); |
| project.ios.xcodeProject.createSync(); |
| project.ios.hostAppRoot.childFile('._Runner.xcworkspace').createSync(recursive: true); |
| project.ios.hostAppRoot.childFile('Runner.xcworkspace').createSync(recursive: true); |
| |
| expect(project.ios.xcodeWorkspace?.basename, 'Runner.xcworkspace'); |
| }); |
| _testInMemory('no xcworkspace directory found', () async { |
| final FlutterProject project = await someProject(); |
| project.ios.xcodeProject.createSync(); |
| expect(project.ios.xcodeWorkspace?.basename, null); |
| }); |
| }); |
| |
| 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('java gradle agp compatibility', () { |
| Future<FlutterProject?> configureGradleAgpForTest({ |
| required String gradleV, |
| required String agpV, |
| }) async { |
| final FlutterProject project = await someProject(); |
| addRootGradleFile( |
| project.directory, |
| gradleFileContent: () { |
| return ''' |
| dependencies { |
| classpath 'com.android.tools.build:gradle:$agpV' |
| } |
| '''; |
| }, |
| ); |
| addGradleWrapperFile(project.directory, gradleV); |
| return project; |
| } |
| |
| // Tests in this group that use overrides and _testInMemory should |
| // be placed in their own group to avoid test pollution. This is |
| // especially important for filesystem. |
| group('_', () { |
| final FakeProcessManager processManager; |
| final Java java; |
| final AndroidStudio androidStudio; |
| final FakeAndroidSdkWithDir androidSdk; |
| final FileSystem fileSystem = getFileSystemForPlatform(); |
| java = FakeJava(version: Version(17, 0, 2)); |
| processManager = FakeProcessManager.list(<FakeCommand>[createKgpVersionCommand('1.9.20')]); |
| androidStudio = FakeAndroidStudio(); |
| androidSdk = FakeAndroidSdkWithDir(fileSystem.currentDirectory); |
| fileSystem.currentDirectory.childDirectory(androidStudio.javaPath!).createSync(); |
| _testInMemory( |
| 'flamingo values are compatible', |
| () async { |
| final FlutterProject? project = await configureGradleAgpForTest( |
| gradleV: '8.0', |
| agpV: '7.4.2', |
| ); |
| final CompatibilityResult value = await project!.android |
| .hasValidJavaGradleAgpVersions(); |
| expect(value.success, isTrue); |
| }, |
| java: java, |
| androidStudio: androidStudio, |
| processManager: processManager, |
| androidSdk: androidSdk, |
| ); |
| }); |
| group('_', () { |
| final FakeProcessManager processManager; |
| final Java java; |
| final AndroidStudio androidStudio; |
| final FakeAndroidSdkWithDir androidSdk; |
| final FileSystem fileSystem = getFileSystemForPlatform(); |
| java = FakeJava(version: const Version.withText(1, 8, 0, '1.8.0_242')); |
| processManager = FakeProcessManager.list(<FakeCommand>[createKgpVersionCommand('1.7.20')]); |
| androidStudio = FakeAndroidStudio(); |
| androidSdk = FakeAndroidSdkWithDir(fileSystem.currentDirectory); |
| fileSystem.currentDirectory.childDirectory(androidStudio.javaPath!).createSync(); |
| _testInMemory( |
| 'java 8 era values are compatible', |
| () async { |
| final FlutterProject? project = await configureGradleAgpForTest( |
| gradleV: '6.7.1', |
| agpV: '4.2.0', |
| ); |
| final CompatibilityResult value = await project!.android |
| .hasValidJavaGradleAgpVersions(); |
| expect(value.success, isTrue); |
| }, |
| java: java, |
| androidStudio: androidStudio, |
| processManager: processManager, |
| androidSdk: androidSdk, |
| ); |
| }); |
| |
| group('_', () { |
| final FakeProcessManager processManager; |
| final Java java; |
| final AndroidStudio androidStudio; |
| final FakeAndroidSdkWithDir androidSdk; |
| final FileSystem fileSystem = getFileSystemForPlatform(); |
| processManager = FakeProcessManager.list(<FakeCommand>[createKgpVersionCommand('1.9.1')]); |
| java = FakeJava(version: Version(11, 0, 14)); |
| androidStudio = FakeAndroidStudio(); |
| androidSdk = FakeAndroidSdkWithDir(fileSystem.currentDirectory); |
| fileSystem.currentDirectory.childDirectory(androidStudio.javaPath!).createSync(); |
| _testInMemory( |
| 'electric eel era values are compatible', |
| () async { |
| final FlutterProject? project = await configureGradleAgpForTest( |
| gradleV: '7.3.3', |
| agpV: '7.2.0', |
| ); |
| final CompatibilityResult value = await project!.android |
| .hasValidJavaGradleAgpVersions(); |
| expect(value.success, isTrue); |
| }, |
| java: java, |
| androidStudio: androidStudio, |
| processManager: processManager, |
| androidSdk: androidSdk, |
| ); |
| }); |
| group('_', () { |
| const javaV = '17.0.2'; |
| const gradleV = '6.7.3'; |
| const agpV = '7.2.0'; |
| const kgpV = '2.1.0'; |
| |
| final FakeProcessManager processManager; |
| final Java java; |
| final AndroidStudio androidStudio; |
| final FakeAndroidSdkWithDir androidSdk; |
| final FileSystem fileSystem = getFileSystemForPlatform(); |
| processManager = FakeProcessManager.list(<FakeCommand>[createKgpVersionCommand(kgpV)]); |
| java = FakeJava(version: Version.parse(javaV)); |
| androidStudio = FakeAndroidStudio(); |
| androidSdk = FakeAndroidSdkWithDir(fileSystem.currentDirectory); |
| fileSystem.currentDirectory.childDirectory(androidStudio.javaPath!).createSync(); |
| _testInMemory( |
| 'incompatible everything', |
| () async { |
| final FlutterProject? project = await configureGradleAgpForTest( |
| gradleV: gradleV, |
| agpV: agpV, |
| ); |
| final CompatibilityResult value = await project!.android |
| .hasValidJavaGradleAgpVersions(); |
| expect(value.success, isFalse); |
| // Should not have the valid string |
| expect( |
| value.description, |
| isNot(contains(RegExp(AndroidProject.validJavaGradleAgpKgpString))), |
| ); |
| // On gradle/agp error print help url and gradle and agp versions. |
| expect(value.description, contains(RegExp(AndroidProject.gradleAgpCompatUrl))); |
| expect(value.description, contains(RegExp(gradleV))); |
| expect(value.description, contains(RegExp(agpV))); |
| // On gradle/agp error print help url and java and gradle versions. |
| expect(value.description, contains(RegExp(AndroidProject.javaGradleCompatUrl))); |
| expect(value.description, contains(RegExp(javaV))); |
| expect(value.description, contains(RegExp(gradleV))); |
| // On kgp/gradle eror print help url and kgp versions |
| expect(value.description, contains(RegExp(kgpV))); |
| expect(value.description, contains(RegExp('KGP/Gradle'))); |
| expect(value.description, contains(RegExp(AndroidProject.kgpCompatUrl))); |
| // On agp/kgp error print help url and agp and kgp versions |
| expect(value.description, contains(RegExp(agpV))); |
| expect(value.description, contains(RegExp(kgpV))); |
| expect(value.description, contains(RegExp('AGP/KGP'))); |
| expect(value.description, contains(RegExp(AndroidProject.kgpCompatUrl))); |
| }, |
| java: java, |
| androidStudio: androidStudio, |
| processManager: processManager, |
| androidSdk: androidSdk, |
| ); |
| }); |
| group('_', () { |
| const javaV = '17.0.2'; |
| const gradleV = '6.7.3'; |
| const agpV = '4.2.0'; |
| const kgpV = '1.7.22'; |
| |
| final FakeProcessManager processManager; |
| final Java java; |
| final AndroidStudio androidStudio; |
| final FakeAndroidSdkWithDir androidSdk; |
| final FileSystem fileSystem = getFileSystemForPlatform(); |
| processManager = FakeProcessManager.list(<FakeCommand>[createKgpVersionCommand(kgpV)]); |
| java = FakeJava(version: Version(17, 0, 2)); |
| androidStudio = FakeAndroidStudio(); |
| androidSdk = FakeAndroidSdkWithDir(fileSystem.currentDirectory); |
| fileSystem.currentDirectory.childDirectory(androidStudio.javaPath!).createSync(); |
| _testInMemory( |
| 'incompatible java/gradle only', |
| () async { |
| final FlutterProject? project = await configureGradleAgpForTest( |
| gradleV: gradleV, |
| agpV: agpV, |
| ); |
| final CompatibilityResult value = await project!.android |
| .hasValidJavaGradleAgpVersions(); |
| expect(value.success, isFalse); |
| // Should not have the valid string. |
| expect( |
| value.description, |
| isNot(contains(RegExp(AndroidProject.validJavaGradleAgpKgpString))), |
| ); |
| // On gradle/agp error print help url and java and gradle versions. |
| expect(value.description, contains(RegExp(AndroidProject.javaGradleCompatUrl))); |
| expect(value.description, contains(RegExp(javaV))); |
| expect(value.description, contains(RegExp(gradleV))); |
| }, |
| java: java, |
| androidStudio: androidStudio, |
| processManager: processManager, |
| androidSdk: androidSdk, |
| ); |
| }); |
| group('_', () { |
| final FakeProcessManager processManager; |
| final Java java; |
| final AndroidStudio androidStudio; |
| final FakeAndroidSdkWithDir androidSdk; |
| final FileSystem fileSystem = getFileSystemForPlatform(); |
| java = FakeJava(version: Version(11, 0, 2)); |
| processManager = FakeProcessManager.any(); |
| androidStudio = FakeAndroidStudio(); |
| androidSdk = FakeAndroidSdkWithDir(fileSystem.currentDirectory); |
| fileSystem.currentDirectory.childDirectory(androidStudio.javaPath!).createSync(); |
| _testInMemory( |
| 'incompatible gradle/agp only', |
| () async { |
| const gradleV = '7.0.3'; |
| const agpV = '7.1.0'; |
| final FlutterProject? project = await configureGradleAgpForTest( |
| gradleV: gradleV, |
| agpV: agpV, |
| ); |
| final CompatibilityResult value = await project!.android |
| .hasValidJavaGradleAgpVersions(); |
| expect(value.success, isFalse); |
| // Should not have the valid string. |
| expect( |
| value.description, |
| isNot(contains(RegExp(AndroidProject.validJavaGradleAgpKgpString))), |
| ); |
| // On gradle/agp error print help url and gradle and agp versions. |
| expect(value.description, contains(RegExp(AndroidProject.gradleAgpCompatUrl))); |
| expect(value.description, contains(RegExp(gradleV))); |
| expect(value.description, contains(RegExp(agpV))); |
| }, |
| java: java, |
| androidStudio: androidStudio, |
| processManager: processManager, |
| androidSdk: androidSdk, |
| ); |
| }); |
| group('_', () { |
| const gradleV = '8.11'; |
| const agpV = '8.7.2'; |
| const kgpV = '2.1.10'; |
| |
| final FakeProcessManager processManager; |
| final Java java; |
| final AndroidStudio androidStudio; |
| final FakeAndroidSdkWithDir androidSdk; |
| final FileSystem fileSystem = getFileSystemForPlatform(); |
| processManager = FakeProcessManager.list(<FakeCommand>[createKgpVersionCommand(kgpV)]); |
| java = FakeJava(version: Version(17, 0, 2)); |
| androidStudio = FakeAndroidStudio(); |
| androidSdk = FakeAndroidSdkWithDir(fileSystem.currentDirectory); |
| fileSystem.currentDirectory.childDirectory(androidStudio.javaPath!).createSync(); |
| _testInMemory( |
| 'incompatible kgp/gradle only', |
| () async { |
| final FlutterProject? project = await configureGradleAgpForTest( |
| gradleV: gradleV, |
| agpV: agpV, |
| ); |
| final CompatibilityResult value = await project!.android |
| .hasValidJavaGradleAgpVersions(); |
| expect(value.success, isFalse); |
| // Should not have the valid string. |
| expect( |
| value.description, |
| isNot(contains(RegExp(AndroidProject.validJavaGradleAgpKgpString))), |
| ); |
| // On gradle/agp error print help url and java and gradle versions. |
| expect(value.description, contains(RegExp(AndroidProject.kgpCompatUrl))); |
| expect(value.description, contains(RegExp(kgpV))); |
| expect(value.description, contains(RegExp(gradleV))); |
| }, |
| java: java, |
| androidStudio: androidStudio, |
| processManager: processManager, |
| androidSdk: androidSdk, |
| ); |
| }); |
| group('_', () { |
| const gradleV = '8.9'; |
| const agpV = '8.7.2'; |
| const kgpV = '2.0.20'; |
| |
| final FakeProcessManager processManager; |
| final Java java; |
| final AndroidStudio androidStudio; |
| final FakeAndroidSdkWithDir androidSdk; |
| final FileSystem fileSystem = getFileSystemForPlatform(); |
| processManager = FakeProcessManager.list(<FakeCommand>[createKgpVersionCommand(kgpV)]); |
| java = FakeJava(version: Version(17, 0, 2)); |
| androidStudio = FakeAndroidStudio(); |
| androidSdk = FakeAndroidSdkWithDir(fileSystem.currentDirectory); |
| fileSystem.currentDirectory.childDirectory(androidStudio.javaPath!).createSync(); |
| _testInMemory( |
| 'incompatible agp/kgp only', |
| () async { |
| final FlutterProject? project = await configureGradleAgpForTest( |
| gradleV: gradleV, |
| agpV: agpV, |
| ); |
| final CompatibilityResult value = await project!.android |
| .hasValidJavaGradleAgpVersions(); |
| expect(value.success, isFalse); |
| // Should not have the valid string. |
| expect( |
| value.description, |
| isNot(contains(RegExp(AndroidProject.validJavaGradleAgpKgpString))), |
| ); |
| // On gradle/agp error print help url and java and gradle versions. |
| expect(value.description, contains(RegExp(kgpV))); |
| expect(value.description, contains(RegExp(agpV))); |
| expect(value.description, contains(RegExp('AGP/KGP'))); |
| expect(value.description, contains(RegExp(AndroidProject.kgpCompatUrl))); |
| }, |
| java: java, |
| androidStudio: androidStudio, |
| processManager: processManager, |
| androidSdk: androidSdk, |
| ); |
| }); |
| group('_', () { |
| final FakeProcessManager processManager; |
| final Java java; |
| final AndroidStudio androidStudio; |
| final FakeAndroidSdkWithDir androidSdk; |
| final FileSystem fileSystem = getFileSystemForPlatform(); |
| java = FakeJava(version: Version(11, 0, 2)); |
| processManager = FakeProcessManager.any(); |
| androidStudio = FakeAndroidStudio(); |
| androidSdk = FakeAndroidSdkWithDir(fileSystem.currentDirectory); |
| fileSystem.currentDirectory.childDirectory(androidStudio.javaPath!).createSync(); |
| _testInMemory( |
| 'null agp only', |
| () async { |
| const gradleV = '7.0.3'; |
| final FlutterProject? project = await configureGradleAgpForTest( |
| gradleV: gradleV, |
| agpV: '', |
| ); |
| final CompatibilityResult value = await project!.android |
| .hasValidJavaGradleAgpVersions(); |
| expect(value.success, isFalse); |
| // Should not have the valid string. |
| expect( |
| value.description, |
| isNot(contains(RegExp(AndroidProject.validJavaGradleAgpKgpString))), |
| ); |
| // On gradle/agp error print help url null value for agp. |
| expect(value.description, contains(RegExp(AndroidProject.gradleAgpCompatUrl))); |
| expect(value.description, contains(RegExp(gradleV))); |
| expect(value.description, contains(RegExp('null'))); |
| }, |
| java: java, |
| androidStudio: androidStudio, |
| processManager: processManager, |
| androidSdk: androidSdk, |
| ); |
| }); |
| }); |
| |
| group('language', () { |
| late XcodeProjectInterpreter xcodeProjectInterpreter; |
| late MemoryFileSystem fs; |
| late 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, |
| }, |
| ); |
| |
| testUsingContext( |
| 'kotlin host app language with Gradle Kotlin DSL', |
| () async { |
| final FlutterProject project = await someProject(); |
| |
| addAndroidGradleFile( |
| project.directory, |
| kotlinDsl: true, |
| gradleFileContent: () { |
| return ''' |
| plugins { |
| id "com.android.application" |
| id "kotlin-android" |
| id "dev.flutter.flutter-gradle-plugin" |
| } |
| '''; |
| }, |
| ); |
| expect(project.android.isKotlin, isTrue); |
| }, |
| overrides: <Type, Generator>{ |
| FileSystem: () => fs, |
| ProcessManager: () => FakeProcessManager.any(), |
| XcodeProjectInterpreter: () => xcodeProjectInterpreter, |
| FlutterProjectFactory: () => flutterProjectFactory, |
| }, |
| ); |
| |
| testUsingContext( |
| 'kotlin host app language with Gradle Kotlin DSL and typesafe plugin id', |
| () async { |
| final FlutterProject project = await someProject(); |
| |
| addAndroidGradleFile( |
| project.directory, |
| kotlinDsl: true, |
| gradleFileContent: () { |
| return ''' |
| plugins { |
| id "com.android.application" |
| id "kotlin-android" |
| dev.flutter.`flutter-gradle-plugin` |
| } |
| '''; |
| }, |
| ); |
| expect(project.android.isKotlin, isTrue); |
| }, |
| overrides: <Type, Generator>{ |
| FileSystem: () => fs, |
| ProcessManager: () => FakeProcessManager.any(), |
| XcodeProjectInterpreter: () => xcodeProjectInterpreter, |
| FlutterProjectFactory: () => flutterProjectFactory, |
| }, |
| ); |
| |
| testUsingContext( |
| 'Gradle Groovy files are preferred to Gradle Kotlin files', |
| () async { |
| final FlutterProject project = await someProject(); |
| |
| addAndroidGradleFile( |
| project.directory, |
| gradleFileContent: () { |
| return ''' |
| plugins { |
| id "com.android.application" |
| id "dev.flutter.flutter-gradle-plugin" |
| } |
| '''; |
| }, |
| ); |
| addAndroidGradleFile( |
| project.directory, |
| kotlinDsl: true, |
| gradleFileContent: () { |
| return ''' |
| plugins { |
| id("com.android.application") |
| id("kotlin-android") |
| id("dev.flutter.flutter-gradle-plugin") |
| } |
| '''; |
| }, |
| ); |
| |
| expect(project.android.isKotlin, isFalse); |
| }, |
| overrides: <Type, Generator>{ |
| FileSystem: () => fs, |
| ProcessManager: () => FakeProcessManager.any(), |
| XcodeProjectInterpreter: () => xcodeProjectInterpreter, |
| FlutterProjectFactory: () => flutterProjectFactory, |
| }, |
| ); |
| }); |
| |
| group('With mocked context', () { |
| late MemoryFileSystem fs; |
| late FakePlistParser testPlistUtils; |
| late FakeXcodeProjectInterpreter xcodeProjectInterpreter; |
| late FlutterProjectFactory flutterProjectFactory; |
| setUp(() { |
| fs = MemoryFileSystem.test(); |
| testPlistUtils = FakePlistParser(); |
| xcodeProjectInterpreter = FakeXcodeProjectInterpreter(); |
| 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: () => xcodeProjectInterpreter, |
| FlutterProjectFactory: () => flutterProjectFactory, |
| }, |
| ); |
| } |
| |
| group('universal link', () { |
| testWithMocks('build with flavor', () async { |
| final FlutterProject project = await someProject(); |
| project.ios.xcodeProject.createSync(); |
| project.ios.defaultHostInfoPlist.createSync(recursive: true); |
| const entitlementFilePath = 'myEntitlement.Entitlement'; |
| project.ios.hostAppRoot.childFile(entitlementFilePath).createSync(recursive: true); |
| |
| const buildContext = XcodeProjectBuildContext(target: 'Runner', configuration: 'config'); |
| xcodeProjectInterpreter.buildSettingsByBuildContext[buildContext] = <String, String>{ |
| IosProject.kProductBundleIdKey: 'io.flutter.someProject', |
| IosProject.kTeamIdKey: 'ABC', |
| IosProject.kEntitlementFilePathKey: entitlementFilePath, |
| 'SUFFIX': 'suffix', |
| }; |
| xcodeProjectInterpreter.xcodeProjectInfo = XcodeProjectInfo( |
| <String>[], |
| <String>[], |
| <String>['Runner'], |
| logger, |
| ); |
| testPlistUtils.setProperty( |
| PlistParser.kCFBundleIdentifierKey, |
| r'$(PRODUCT_BUNDLE_IDENTIFIER).$(SUFFIX)', |
| ); |
| testPlistUtils.setProperty(PlistParser.kAssociatedDomainsKey, <String>[ |
| 'applinks:example.com', |
| 'applinks:example2.com', |
| 'applinks:example3.com?mode=developer', |
| ]); |
| final String outputFilePath = await project.ios.outputsUniversalLinkSettings( |
| target: 'Runner', |
| configuration: 'config', |
| ); |
| final File outputFile = fs.file(outputFilePath); |
| final json = jsonDecode(outputFile.readAsStringSync()) as Map<String, Object?>; |
| |
| expect( |
| json['associatedDomains'], |
| unorderedEquals(<String>['example.com', 'example2.com', 'example3.com']), |
| ); |
| expect(json['teamIdentifier'], 'ABC'); |
| expect(json['bundleIdentifier'], 'io.flutter.someProject.suffix'); |
| }); |
| |
| testWithMocks('can handle entitlement file in nested directory structure.', () async { |
| final FlutterProject project = await someProject(); |
| project.ios.xcodeProject.createSync(); |
| project.ios.defaultHostInfoPlist.createSync(recursive: true); |
| const entitlementFilePath = 'nested/somewhere/myEntitlement.Entitlement'; |
| project.ios.hostAppRoot.childFile(entitlementFilePath).createSync(recursive: true); |
| |
| const buildContext = XcodeProjectBuildContext(target: 'Runner', configuration: 'config'); |
| xcodeProjectInterpreter.buildSettingsByBuildContext[buildContext] = <String, String>{ |
| IosProject.kProductBundleIdKey: 'io.flutter.someProject', |
| IosProject.kTeamIdKey: 'ABC', |
| IosProject.kEntitlementFilePathKey: entitlementFilePath, |
| 'SUFFIX': 'suffix', |
| }; |
| xcodeProjectInterpreter.xcodeProjectInfo = XcodeProjectInfo( |
| <String>[], |
| <String>[], |
| <String>['Runner'], |
| logger, |
| ); |
| testPlistUtils.setProperty( |
| PlistParser.kCFBundleIdentifierKey, |
| r'$(PRODUCT_BUNDLE_IDENTIFIER).$(SUFFIX)', |
| ); |
| testPlistUtils.setProperty(PlistParser.kAssociatedDomainsKey, <String>[ |
| 'applinks:example.com', |
| 'applinks:example2.com', |
| ]); |
| |
| final String outputFilePath = await project.ios.outputsUniversalLinkSettings( |
| target: 'Runner', |
| configuration: 'config', |
| ); |
| final File outputFile = fs.file(outputFilePath); |
| final json = jsonDecode(outputFile.readAsStringSync()) as Map<String, Object?>; |
| expect( |
| json['associatedDomains'], |
| unorderedEquals(<String>['example.com', 'example2.com']), |
| ); |
| expect(json['teamIdentifier'], 'ABC'); |
| expect(json['bundleIdentifier'], 'io.flutter.someProject.suffix'); |
| }); |
| |
| testWithMocks('return empty when no entitlement', () async { |
| final FlutterProject project = await someProject(); |
| project.ios.xcodeProject.createSync(); |
| project.ios.defaultHostInfoPlist.createSync(recursive: true); |
| |
| const buildContext = XcodeProjectBuildContext(target: 'Runner', configuration: 'config'); |
| xcodeProjectInterpreter.buildSettingsByBuildContext[buildContext] = <String, String>{ |
| IosProject.kProductBundleIdKey: 'io.flutter.someProject', |
| IosProject.kTeamIdKey: 'ABC', |
| }; |
| xcodeProjectInterpreter.xcodeProjectInfo = XcodeProjectInfo( |
| <String>[], |
| <String>[], |
| <String>['Runner'], |
| logger, |
| ); |
| testPlistUtils.setProperty( |
| PlistParser.kCFBundleIdentifierKey, |
| r'$(PRODUCT_BUNDLE_IDENTIFIER)', |
| ); |
| final String outputFilePath = await project.ios.outputsUniversalLinkSettings( |
| target: 'Runner', |
| configuration: 'config', |
| ); |
| final File outputFile = fs.file(outputFilePath); |
| final json = jsonDecode(outputFile.readAsStringSync()) as Map<String, Object?>; |
| expect(json['teamIdentifier'], 'ABC'); |
| expect(json['bundleIdentifier'], 'io.flutter.someProject'); |
| expect(json['associatedDomains'], unorderedEquals(<String>[])); |
| }); |
| }); |
| |
| group('product bundle identifier', () { |
| 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(); |
| const buildContext = XcodeProjectBuildContext(scheme: 'Runner'); |
| xcodeProjectInterpreter.buildSettingsByBuildContext[buildContext] = <String, String>{ |
| IosProject.kProductBundleIdKey: 'io.flutter.someProject', |
| }; |
| xcodeProjectInterpreter.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(); |
| xcodeProjectInterpreter.xcodeProjectInfo = XcodeProjectInfo( |
| <String>[], |
| <String>[], |
| <String>['Runner'], |
| logger, |
| ); |
| |
| 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(); |
| const buildContext = XcodeProjectBuildContext(scheme: 'Runner'); |
| xcodeProjectInterpreter.buildSettingsByBuildContext[buildContext] = <String, String>{ |
| IosProject.kProductBundleIdKey: 'io.flutter.someProject', |
| }; |
| xcodeProjectInterpreter.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); |
| const buildContext = XcodeProjectBuildContext(scheme: 'Runner'); |
| xcodeProjectInterpreter.buildSettingsByBuildContext[buildContext] = <String, String>{ |
| IosProject.kProductBundleIdKey: 'io.flutter.someProject', |
| 'SUFFIX': 'suffix', |
| }; |
| xcodeProjectInterpreter.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('Always pass parsing org on ios project with flavors', () async { |
| final FlutterProject project = await someProject(); |
| addIosProjectFile( |
| project.directory, |
| projectFileContent: () { |
| return projectFileWithBundleId('io.flutter.someProject', qualifier: "'"); |
| }, |
| ); |
| project.ios.xcodeProject.createSync(); |
| xcodeProjectInterpreter.xcodeProjectInfo = XcodeProjectInfo( |
| <String>[], |
| <String>[], |
| <String>['free', 'paid'], |
| logger, |
| ); |
| |
| expect(await project.organizationNames, <String>[]); |
| }); |
| |
| testWithMocks('fails with no flavor and defined schemes', () async { |
| final FlutterProject project = await someProject(); |
| project.ios.xcodeProject.createSync(); |
| xcodeProjectInterpreter.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(); |
| const buildContext = XcodeProjectBuildContext(scheme: 'Free'); |
| xcodeProjectInterpreter.buildSettingsByBuildContext[buildContext] = <String, String>{ |
| IosProject.kProductBundleIdKey: 'io.flutter.someProject', |
| }; |
| xcodeProjectInterpreter.xcodeProjectInfo = XcodeProjectInfo( |
| <String>[], |
| <String>[], |
| <String>['Free'], |
| logger, |
| ); |
| const buildInfo = BuildInfo( |
| BuildMode.debug, |
| 'free', |
| treeShakeIcons: false, |
| packageConfigPath: '.dart_tool/package_config.json', |
| ); |
| |
| 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(); |
| xcodeProjectInterpreter.xcodeProjectInfo = XcodeProjectInfo( |
| <String>[], |
| <String>[], |
| <String>['Runner'], |
| logger, |
| ); |
| const buildInfo = BuildInfo( |
| BuildMode.debug, |
| 'free', |
| treeShakeIcons: false, |
| packageConfigPath: '.dart_tool/package_config.json', |
| ); |
| |
| 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(); |
| xcodeProjectInterpreter.xcodeProjectInfo = XcodeProjectInfo( |
| <String>[], |
| <String>[], |
| <String>['Runner'], |
| logger, |
| ); |
| addIosProjectFile( |
| project.directory, |
| projectFileContent: () { |
| return projectFileWithBundleId('', qualifier: '"'); |
| }, |
| ); |
| expect(await project.ios.productBundleIdentifier(null), ''); |
| }); |
| |
| testWithMocks('surrounded by double quotes', () async { |
| final FlutterProject project = await someProject(); |
| xcodeProjectInterpreter.xcodeProjectInfo = XcodeProjectInfo( |
| <String>[], |
| <String>[], |
| <String>['Runner'], |
| logger, |
| ); |
| 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(); |
| xcodeProjectInterpreter.xcodeProjectInfo = XcodeProjectInfo( |
| <String>[], |
| <String>[], |
| <String>['Runner'], |
| logger, |
| ); |
| addIosProjectFile( |
| project.directory, |
| projectFileContent: () { |
| return projectFileWithBundleId('io.flutter.someProject', qualifier: "'"); |
| }, |
| ); |
| expect(await project.ios.productBundleIdentifier(null), 'io.flutter.someProject'); |
| }); |
| }); |
| |
| group('flutterSwiftPackageInProjectSettings', () { |
| testWithMocks('is false if pbxproj missing', () async { |
| final FlutterProject project = await someProject(); |
| expect(project.ios.xcodeProjectInfoFile.existsSync(), isFalse); |
| expect(project.ios.flutterPluginSwiftPackageInProjectSettings, isFalse); |
| }); |
| |
| testWithMocks( |
| 'is false if pbxproj does not contain FlutterGeneratedPluginSwiftPackage in build process', |
| () async { |
| final FlutterProject project = await someProject(); |
| project.ios.xcodeProjectInfoFile.createSync(recursive: true); |
| expect(project.ios.xcodeProjectInfoFile.existsSync(), isTrue); |
| expect(project.ios.flutterPluginSwiftPackageInProjectSettings, isFalse); |
| }, |
| ); |
| |
| testWithMocks( |
| 'is true if pbxproj does contain FlutterGeneratedPluginSwiftPackage in build process', |
| () async { |
| final FlutterProject project = await someProject(); |
| project.ios.xcodeProjectInfoFile.createSync(recursive: true); |
| project.ios.xcodeProjectInfoFile.writeAsStringSync(''' |
| ' 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };'; |
| '''); |
| expect(project.ios.xcodeProjectInfoFile.existsSync(), isTrue); |
| expect(project.ios.flutterPluginSwiftPackageInProjectSettings, isTrue); |
| }, |
| ); |
| }); |
| }); |
| |
| group('application bundle name', () { |
| late MemoryFileSystem fs; |
| late FakeXcodeProjectInterpreter mockXcodeProjectInterpreter; |
| setUp(() { |
| fs = MemoryFileSystem.test(); |
| mockXcodeProjectInterpreter = FakeXcodeProjectInterpreter(); |
| }); |
| |
| testUsingContext( |
| 'app product name defaults to Runner', |
| () async { |
| final FlutterProject project = await someProject(); |
| expect(await project.ios.productName(null), 'Runner'); |
| }, |
| 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(); |
| const buildContext = XcodeProjectBuildContext(scheme: 'Runner'); |
| mockXcodeProjectInterpreter.buildSettingsByBuildContext[buildContext] = <String, String>{ |
| 'PRODUCT_NAME': 'My App', |
| }; |
| mockXcodeProjectInterpreter.xcodeProjectInfo = XcodeProjectInfo( |
| <String>[], |
| <String>[], |
| <String>['Runner'], |
| logger, |
| ); |
| |
| expect(await project.ios.productName(null), 'My 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('manifest', () { |
| _testInMemory('can be replaced', () async { |
| final FlutterProject project = await someProject(); |
| final String originalPubspecContents = project.pubspecFile.readAsStringSync(); |
| final FlutterManifest updated = FlutterManifest.createFromString( |
| validPubspecWithDependencies, |
| logger: logger, |
| )!; |
| // Verifies the pubspec.yaml from [project] is overwritten with the pubspec |
| // defined by [updated], both in the [FlutterProject] and on disk. |
| expect(project.manifest, isNot(equals(updated))); |
| project.replacePubspec(updated); |
| expect(project.manifest, equals(updated)); |
| final String updatedPubspecContents = project.pubspecFile.readAsStringSync(); |
| expect(updatedPubspecContents, isNot(equals(originalPubspecContents))); |
| expect(updatedPubspecContents, validPubspecWithDependenciesAndNullValues); |
| }); |
| }); |
| |
| group('Android project file getters', () { |
| _testInMemory( |
| 'Project.android.gradleWrapperPropertiesFile resolves to gradle/wrapper/gradle-wrapper.properties', |
| () async { |
| final Directory tempDir = globals.fs.systemTempDirectory.createTempSync( |
| 'flutter_project_test', |
| ); |
| final Directory androidDir = tempDir.childDirectory('android') |
| ..createSync(recursive: true); |
| |
| // Create gradle/wrapper/gradle-wrapper.properties inside the fake android dir |
| final File expected = |
| androidDir |
| .childDirectory('gradle') |
| .childDirectory('wrapper') |
| .childFile('gradle-wrapper.properties') |
| ..createSync(recursive: true); |
| |
| final FlutterProject project = FlutterProject.fromDirectory(tempDir); |
| |
| expect(project.android.gradleWrapperPropertiesFile.path, expected.path); |
| }, |
| ); |
| _testInMemory('Project.android.appGradleFile resolves to app/build.gradle', () async { |
| final Directory tempDir = globals.fs.systemTempDirectory.createTempSync( |
| 'flutter_project_build_files', |
| ); |
| final Directory androidDir = tempDir.childDirectory('android').childDirectory('app') |
| ..createSync(recursive: true); |
| |
| final File expected = androidDir.childFile('build.gradle')..createSync(recursive: true); |
| |
| final FlutterProject project = FlutterProject.fromDirectory(tempDir); |
| |
| expect(project.android.appGradleFile.path, expected.path); |
| }); |
| _testInMemory('Project.android.appGradleFile resolves to app/build.gradle.kts', () async { |
| final Directory tempDir = globals.fs.systemTempDirectory.createTempSync( |
| 'flutter_project_build_files', |
| ); |
| final Directory androidDir = tempDir.childDirectory('android').childDirectory('app') |
| ..createSync(recursive: true); |
| |
| final File expected = androidDir.childFile('build.gradle.kts')..createSync(recursive: true); |
| |
| final FlutterProject project = FlutterProject.fromDirectory(tempDir); |
| |
| expect(project.android.appGradleFile.path, expected.path); |
| }); |
| _testInMemory( |
| 'Project.android.appGradleFile prefers app/build.gradle over app/build.gradle.kts', |
| () async { |
| final Directory tempDir = globals.fs.systemTempDirectory.createTempSync( |
| 'flutter_project_build_files', |
| ); |
| final Directory androidDir = tempDir.childDirectory('android').childDirectory('app') |
| ..createSync(recursive: true); |
| androidDir.childFile('build.gradle.kts').createSync(recursive: true); |
| final File expected = androidDir.childFile('build.gradle')..createSync(recursive: true); |
| |
| final FlutterProject project = FlutterProject.fromDirectory(tempDir); |
| |
| expect(project.android.appGradleFile.path, expected.path); |
| }, |
| ); |
| |
| _testInMemory( |
| 'Project.android.hostAppGradleFile resolves to android/build.gradle ', |
| () async { |
| final Directory tempDir = globals.fs.systemTempDirectory.createTempSync( |
| 'flutter_project_build_files', |
| ); |
| final Directory androidDir = tempDir.childDirectory('android') |
| ..createSync(recursive: true); |
| |
| final File expected = androidDir.childFile('build.gradle')..createSync(recursive: true); |
| |
| final FlutterProject project = FlutterProject.fromDirectory(tempDir); |
| |
| expect(project.android.hostAppGradleFile.path, expected.path); |
| }, |
| ); |
| _testInMemory( |
| 'Project.android.hostAppGradleFile resolves to android/build.gradle.kts', |
| () async { |
| final Directory tempDir = globals.fs.systemTempDirectory.createTempSync( |
| 'flutter_project_build_files', |
| ); |
| final Directory androidDir = tempDir.childDirectory('android') |
| ..createSync(recursive: true); |
| |
| final File expected = androidDir.childFile('build.gradle.kts') |
| ..createSync(recursive: true); |
| |
| final FlutterProject project = FlutterProject.fromDirectory(tempDir); |
| |
| expect(project.android.hostAppGradleFile.path, expected.path); |
| }, |
| ); |
| _testInMemory( |
| 'Project.android.hostAppGradleFile prefers android/build.gradle over android/build.gradle.kts', |
| () async { |
| final Directory tempDir = globals.fs.systemTempDirectory.createTempSync( |
| 'flutter_project_build_files', |
| ); |
| final Directory androidDir = tempDir.childDirectory('android') |
| ..createSync(recursive: true); |
| androidDir.childFile('build.gradle.kts').createSync(recursive: true); |
| final File expected = androidDir.childFile('build.gradle')..createSync(recursive: true); |
| |
| final FlutterProject project = FlutterProject.fromDirectory(tempDir); |
| |
| expect(project.android.hostAppGradleFile.path, expected.path); |
| }, |
| ); |
| _testInMemory( |
| 'Project.android.settingsGradleFile resolves to android/settings.gradle', |
| () async { |
| final Directory tempDir = globals.fs.systemTempDirectory.createTempSync( |
| 'flutter_project_build_files', |
| ); |
| final Directory androidDir = tempDir.childDirectory('android') |
| ..createSync(recursive: true); |
| |
| final File expected = androidDir.childFile('settings.gradle') |
| ..createSync(recursive: true); |
| |
| final FlutterProject project = FlutterProject.fromDirectory(tempDir); |
| |
| expect(project.android.settingsGradleFile.path, expected.path); |
| }, |
| ); |
| _testInMemory( |
| 'Project.android.settingsGradleFile resolves to android/settings.gradle.kts', |
| () async { |
| final Directory tempDir = globals.fs.systemTempDirectory.createTempSync( |
| 'flutter_project_build_files', |
| ); |
| final Directory androidDir = tempDir.childDirectory('android') |
| ..createSync(recursive: true); |
| |
| final File expected = androidDir.childFile('settings.gradle.kts') |
| ..createSync(recursive: true); |
| |
| final FlutterProject project = FlutterProject.fromDirectory(tempDir); |
| |
| expect(project.android.settingsGradleFile.path, expected.path); |
| }, |
| ); |
| _testInMemory( |
| 'Project.android.settingsGradleFile prefers android/settings.gradle over android/settings.gradle.kts', |
| () async { |
| final Directory tempDir = globals.fs.systemTempDirectory.createTempSync( |
| 'flutter_project_build_files', |
| ); |
| final Directory androidDir = tempDir.childDirectory('android') |
| ..createSync(recursive: true); |
| androidDir.childFile('settings.gradle.kts').createSync(recursive: true); |
| final File expected = androidDir.childFile('settings.gradle') |
| ..createSync(recursive: true); |
| |
| final FlutterProject project = FlutterProject.fromDirectory(tempDir); |
| |
| expect(project.android.settingsGradleFile.path, expected.path); |
| }, |
| ); |
| _testInMemory( |
| 'Project.android.appManifestFile resolves to android/app/src/main/AndroidManifest.xml when build.gradle exists', |
| () async { |
| final Directory tempDir = globals.fs.systemTempDirectory.createTempSync( |
| 'flutter_project_test', |
| ); |
| final Directory androidDir = tempDir.childDirectory('android') |
| ..createSync(recursive: true); |
| |
| androidDir.childFile('build.gradle').createSync(); |
| |
| final File expected = |
| androidDir |
| .childDirectory('app') |
| .childDirectory('src') |
| .childDirectory('main') |
| .childFile('AndroidManifest.xml') |
| ..createSync(recursive: true); |
| |
| final FlutterProject project = FlutterProject.fromDirectory(tempDir); |
| |
| expect(project.android.appManifestFile.path, expected.path); |
| }, |
| ); |
| _testInMemory( |
| 'Project.android.appManifestFile resolves to android/app/src/main/AndroidManifest.xml when build.gradle.kts exists', |
| () async { |
| final Directory tempDir = globals.fs.systemTempDirectory.createTempSync( |
| 'flutter_project_test', |
| ); |
| final Directory androidDir = tempDir.childDirectory('android') |
| ..createSync(recursive: true); |
| |
| androidDir.childFile('build.gradle.kts').createSync(); |
| |
| final File expected = |
| androidDir |
| .childDirectory('app') |
| .childDirectory('src') |
| .childDirectory('main') |
| .childFile('AndroidManifest.xml') |
| ..createSync(recursive: true); |
| |
| final FlutterProject project = FlutterProject.fromDirectory(tempDir); |
| |
| expect(project.android.appManifestFile.path, expected.path); |
| }, |
| ); |
| _testInMemory( |
| 'Project.android.appManifestFile resolves to android/app/src/main/AndroidManifest.xml when both build.gradle and build.gradle.kts exists', |
| () async { |
| final Directory tempDir = globals.fs.systemTempDirectory.createTempSync( |
| 'flutter_project_test', |
| ); |
| final Directory androidDir = tempDir.childDirectory('android') |
| ..createSync(recursive: true); |
| |
| androidDir.childFile('build.gradle').createSync(); |
| androidDir.childFile('build.gradle.kts').createSync(); |
| |
| final File expected = |
| androidDir |
| .childDirectory('app') |
| .childDirectory('src') |
| .childDirectory('main') |
| .childFile('AndroidManifest.xml') |
| ..createSync(recursive: true); |
| |
| final FlutterProject project = FlutterProject.fromDirectory(tempDir); |
| |
| expect(project.android.appManifestFile.path, expected.path); |
| }, |
| ); |
| _testInMemory( |
| 'Project.android.appManifestFile resolves to android/AndroidManifest.xml when not using Gradle', |
| () async { |
| final Directory tempDir = globals.fs.systemTempDirectory.createTempSync( |
| 'flutter_project_test', |
| ); |
| final Directory androidDir = tempDir.childDirectory('android') |
| ..createSync(recursive: true); |
| |
| final File expected = androidDir.childFile('AndroidManifest.xml') |
| ..createSync(recursive: true); |
| |
| final FlutterProject project = FlutterProject.fromDirectory(tempDir); |
| |
| expect(project.android.appManifestFile.path, expected.path); |
| }, |
| ); |
| _testInMemory( |
| 'Project.android.localPropertiesFile resolves to android/local.properties', |
| () async { |
| final Directory tempDir = globals.fs.systemTempDirectory.createTempSync( |
| 'flutter_project_test', |
| ); |
| final Directory androidDir = tempDir.childDirectory('android') |
| ..createSync(recursive: true); |
| |
| final File expected = androidDir.childFile('local.properties') |
| ..createSync(recursive: true); |
| |
| final FlutterProject project = FlutterProject.fromDirectory(tempDir); |
| |
| expect(project.android.localPropertiesFile.path, expected.path); |
| }, |
| ); |
| }); |
| |
| group('workspaces', () { |
| _testInMemory('fails on invalid pubspec.yaml', () async { |
| final Directory directory = globals.fs.directory('myproject'); |
| directory.childFile('pubspec.yaml') |
| ..createSync(recursive: true) |
| ..writeAsStringSync(''' |
| name: parent |
| flutter: |
| workspace: |
| - child1 |
| - child2 |
| - child2/example |
| '''); |
| directory.childDirectory('child1').childFile('pubspec.yaml') |
| ..createSync(recursive: true) |
| ..writeAsStringSync(''' |
| name: child1 |
| flutter: |
| resolution: workspace |
| '''); |
| directory.childDirectory('child2').childFile('pubspec.yaml') |
| ..createSync(recursive: true) |
| ..writeAsStringSync(''' |
| name: child2 |
| flutter: |
| resolution: workspace |
| '''); |
| directory.childDirectory('child2').childDirectory('example').childFile('pubspec.yaml') |
| ..createSync(recursive: true) |
| ..writeAsStringSync(''' |
| name: child2_example |
| flutter: |
| resolution: workspace |
| '''); |
| |
| expect( |
| FlutterProject.fromDirectory(directory).workspaceProjects |
| .map((FlutterProject subproject) => subproject.manifest.appName) |
| .toList(), |
| <String>['child1', 'child2', 'child2_example'], |
| ); |
| }); |
| }); |
| }); |
| |
| group('watch companion', () { |
| late MemoryFileSystem fs; |
| late FakePlistParser testPlistParser; |
| late FakeXcodeProjectInterpreter mockXcodeProjectInterpreter; |
| late FlutterProjectFactory flutterProjectFactory; |
| setUp(() { |
| fs = MemoryFileSystem.test(); |
| testPlistParser = FakePlistParser(); |
| mockXcodeProjectInterpreter = FakeXcodeProjectInterpreter(); |
| flutterProjectFactory = FlutterProjectFactory(fileSystem: fs, logger: logger); |
| }); |
| |
| testUsingContext( |
| 'cannot find bundle identifier', |
| () async { |
| final FlutterProject project = await someProject(); |
| final projectInfo = XcodeProjectInfo( |
| <String>['WatchTarget'], |
| <String>[], |
| <String>[], |
| logger, |
| ); |
| expect( |
| await project.ios.containsWatchCompanion( |
| projectInfo: projectInfo, |
| buildInfo: BuildInfo.debug, |
| deviceId: '123', |
| ), |
| isFalse, |
| ); |
| }, |
| overrides: <Type, Generator>{ |
| FileSystem: () => fs, |
| ProcessManager: () => FakeProcessManager.any(), |
| PlistParser: () => testPlistParser, |
| XcodeProjectInterpreter: () => mockXcodeProjectInterpreter, |
| FlutterProjectFactory: () => flutterProjectFactory, |
| }, |
| ); |
| |
| group('with bundle identifier', () { |
| setUp(() { |
| const buildContext = XcodeProjectBuildContext(scheme: 'Runner'); |
| mockXcodeProjectInterpreter.buildSettingsByBuildContext[buildContext] = <String, String>{ |
| IosProject.kProductBundleIdKey: 'io.flutter.someProject', |
| }; |
| mockXcodeProjectInterpreter.xcodeProjectInfo = XcodeProjectInfo( |
| <String>['Runner', 'WatchTarget'], |
| <String>[], |
| <String>['Runner', 'WatchScheme'], |
| logger, |
| ); |
| }); |
| |
| testUsingContext( |
| 'no Info.plist in target', |
| () async { |
| final FlutterProject project = await someProject(); |
| expect( |
| await project.ios.containsWatchCompanion( |
| projectInfo: mockXcodeProjectInterpreter.xcodeProjectInfo, |
| buildInfo: BuildInfo.debug, |
| deviceId: '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( |
| projectInfo: mockXcodeProjectInterpreter.xcodeProjectInfo, |
| buildInfo: BuildInfo.debug, |
| deviceId: '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( |
| projectInfo: mockXcodeProjectInterpreter.xcodeProjectInfo, |
| buildInfo: BuildInfo.debug, |
| deviceId: '123', |
| ), |
| isFalse, |
| ); |
| }, |
| overrides: <Type, Generator>{ |
| FileSystem: () => fs, |
| ProcessManager: () => FakeProcessManager.any(), |
| PlistParser: () => testPlistParser, |
| XcodeProjectInterpreter: () => mockXcodeProjectInterpreter, |
| FlutterProjectFactory: () => flutterProjectFactory, |
| }, |
| ); |
| |
| testUsingContext( |
| 'has watch companion in plist', |
| () 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( |
| projectInfo: mockXcodeProjectInterpreter.xcodeProjectInfo, |
| buildInfo: BuildInfo.debug, |
| deviceId: '123', |
| ), |
| isTrue, |
| ); |
| }, |
| overrides: <Type, Generator>{ |
| FileSystem: () => fs, |
| ProcessManager: () => FakeProcessManager.any(), |
| PlistParser: () => testPlistParser, |
| XcodeProjectInterpreter: () => mockXcodeProjectInterpreter, |
| FlutterProjectFactory: () => flutterProjectFactory, |
| }, |
| ); |
| |
| testUsingContext( |
| 'has watch companion in plist with xcode variable', |
| () async { |
| final FlutterProject project = await someProject(); |
| project.ios.xcodeProject.createSync(); |
| const buildContext = XcodeProjectBuildContext(scheme: 'Runner', deviceId: '123'); |
| mockXcodeProjectInterpreter.buildSettingsByBuildContext[buildContext] = <String, String>{ |
| IosProject.kProductBundleIdKey: '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( |
| projectInfo: mockXcodeProjectInterpreter.xcodeProjectInfo, |
| buildInfo: BuildInfo.debug, |
| deviceId: '123', |
| ), |
| isTrue, |
| ); |
| }, |
| overrides: <Type, Generator>{ |
| FileSystem: () => fs, |
| ProcessManager: () => FakeProcessManager.any(), |
| PlistParser: () => testPlistParser, |
| XcodeProjectInterpreter: () => mockXcodeProjectInterpreter, |
| FlutterProjectFactory: () => flutterProjectFactory, |
| }, |
| ); |
| |
| testUsingContext( |
| 'has watch companion in other scheme build settings', |
| () async { |
| final FlutterProject project = await someProject(); |
| project.ios.xcodeProject.createSync(); |
| project.ios.xcodeProjectInfoFile.writeAsStringSync(''' |
| Build settings for action build and target "WatchTarget": |
| INFOPLIST_KEY_WKCompanionAppBundleIdentifier = io.flutter.someProject |
| '''); |
| |
| const buildContext = XcodeProjectBuildContext(scheme: 'Runner', deviceId: '123'); |
| mockXcodeProjectInterpreter.buildSettingsByBuildContext[buildContext] = <String, String>{ |
| IosProject.kProductBundleIdKey: 'io.flutter.someProject', |
| }; |
| |
| const watchBuildContext = XcodeProjectBuildContext( |
| scheme: 'WatchScheme', |
| deviceId: '123', |
| sdk: XcodeSdk.WatchOS, |
| ); |
| mockXcodeProjectInterpreter.buildSettingsByBuildContext[watchBuildContext] = |
| <String, String>{ |
| 'INFOPLIST_KEY_WKCompanionAppBundleIdentifier': 'io.flutter.someProject', |
| }; |
| |
| expect( |
| await project.ios.containsWatchCompanion( |
| projectInfo: mockXcodeProjectInterpreter.xcodeProjectInfo, |
| buildInfo: BuildInfo.debug, |
| deviceId: '123', |
| ), |
| isTrue, |
| ); |
| }, |
| overrides: <Type, Generator>{ |
| FileSystem: () => fs, |
| ProcessManager: () => FakeProcessManager.any(), |
| PlistParser: () => testPlistParser, |
| XcodeProjectInterpreter: () => mockXcodeProjectInterpreter, |
| FlutterProjectFactory: () => flutterProjectFactory, |
| }, |
| ); |
| |
| testUsingContext( |
| 'has watch companion in other scheme build settings with xcode variable', |
| () async { |
| final FlutterProject project = await someProject(); |
| project.ios.xcodeProject.createSync(); |
| project.ios.xcodeProjectInfoFile.writeAsStringSync(r''' |
| Build settings for action build and target "WatchTarget": |
| INFOPLIST_KEY_WKCompanionAppBundleIdentifier = $(PRODUCT_BUNDLE_IDENTIFIER) |
| '''); |
| const buildContext = XcodeProjectBuildContext(scheme: 'Runner', deviceId: '123'); |
| mockXcodeProjectInterpreter.buildSettingsByBuildContext[buildContext] = <String, String>{ |
| IosProject.kProductBundleIdKey: 'io.flutter.someProject', |
| }; |
| |
| const watchBuildContext = XcodeProjectBuildContext( |
| scheme: 'WatchScheme', |
| deviceId: '123', |
| sdk: XcodeSdk.WatchOS, |
| ); |
| mockXcodeProjectInterpreter.buildSettingsByBuildContext[watchBuildContext] = |
| <String, String>{ |
| IosProject.kProductBundleIdKey: 'io.flutter.someProject', |
| 'INFOPLIST_KEY_WKCompanionAppBundleIdentifier': r'$(PRODUCT_BUNDLE_IDENTIFIER)', |
| }; |
| |
| expect( |
| await project.ios.containsWatchCompanion( |
| projectInfo: mockXcodeProjectInterpreter.xcodeProjectInfo, |
| buildInfo: BuildInfo.debug, |
| deviceId: '123', |
| ), |
| isTrue, |
| ); |
| }, |
| overrides: <Type, Generator>{ |
| FileSystem: () => fs, |
| ProcessManager: () => FakeProcessManager.any(), |
| PlistParser: () => testPlistParser, |
| XcodeProjectInterpreter: () => mockXcodeProjectInterpreter, |
| FlutterProjectFactory: () => flutterProjectFactory, |
| }, |
| ); |
| }); |
| }); |
| } |
| |
| Future<FlutterProject> someProject({ |
| String? androidManifestOverride, |
| bool includePubspec = true, |
| }) async { |
| final Directory directory = globals.fs.directory('some_project'); |
| writePackageConfigFiles(directory: globals.fs.currentDirectory, mainLibName: 'hello'); |
| if (includePubspec) { |
| directory.childFile('pubspec.yaml') |
| ..createSync(recursive: true) |
| ..writeAsStringSync(validPubspec); |
| } |
| directory.childDirectory('ios').createSync(recursive: true); |
| final Directory androidDirectory = directory.childDirectory('android') |
| ..createSync(recursive: true); |
| androidDirectory |
| .childFile('AndroidManifest.xml') |
| .writeAsStringSync(androidManifestOverride ?? '<manifest></manifest>'); |
| return FlutterProject.fromDirectory(directory); |
| } |
| |
| Future<FlutterProject> projectWithPluginDependency() async { |
| final Directory directory = globals.fs.directory('some_project'); |
| writePackageConfigFiles( |
| directory: directory, |
| mainLibName: 'app_name', |
| packages: <String, String>{'my_plugin': '/plugin_project'}, |
| devDependencies: <String>['my_plugin'], |
| ); |
| directory.childFile('pubspec.yaml') |
| ..createSync(recursive: true) |
| ..writeAsStringSync(''' |
| name: app_name |
| flutter: |
| |
| dev_dependencies: |
| my_plugin: |
| sdk: flutter |
| '''); |
| 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, bool includeAndroidMain = false}) 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); |
| if (includeAndroidMain) { |
| directory |
| .childDirectory('android') |
| .childFile(globals.fs.path.join('src', 'main', 'java', 'com', 'example', 'MyPlugin.java')) |
| ..createSync(recursive: true) |
| ..writeAsStringSync(''' |
| import io.flutter.embedding.engine.plugins.FlutterPlugin; |
| class MyPlugin extends FluttPlugin { /* ... */ } |
| '''); |
| } |
| return FlutterProject.fromDirectory(directory); |
| } |
| |
| Future<FlutterProject> aModuleProject() async { |
| final Directory directory = globals.fs.directory('module_project'); |
| writePackageConfigFiles(mainLibName: 'my_module', directory: directory); |
| directory.childFile('pubspec.yaml').writeAsStringSync(''' |
| name: my_module |
| flutter: |
| module: |
| androidPackage: com.example |
| '''); |
| return FlutterProject.fromDirectory(directory); |
| } |
| |
| FakeCommand createKgpVersionCommand(String kgpV) { |
| return FakeCommand( |
| command: const <String>['./gradlew', 'kgpVersion', '-q'], |
| stdout: |
| ''' |
| KGP Version: $kgpV |
| ''', |
| ); |
| } |
| |
| /// Returns a fake of the `cache/artifacts/gradle_wrapper` directory. |
| /// |
| /// Otherwise this hermeric (general.shard) test needs to download files from |
| /// the internet to run. |
| /// |
| /// See https://github.com/flutter/flutter/issues/83275 for details. |
| void _insertFakeGradleArtifactDir({required Directory flutterRoot}) { |
| final Directory artifactDir = flutterRoot |
| .childDirectory('bin') |
| .childDirectory('cache') |
| .childDirectory('artifacts') |
| .childDirectory('gradle_wrapper'); |
| |
| artifactDir |
| ..childFile('gradlew').createSync(recursive: true) |
| ..childFile('gradlew.bat').createSync(recursive: true) |
| ..childDirectory('wrapper').childFile('gradle-wrapper.jar').createSync(recursive: true); |
| } |
| |
| /// Executes the [testMethod] in a context where the file system |
| /// is in memory. |
| @isTest |
| void _testInMemory( |
| String description, |
| Future<void> Function() testMethod, { |
| FileSystem? fileSystem, |
| Java? java, |
| AndroidStudio? androidStudio, |
| ProcessManager? processManager, |
| AndroidSdk? androidSdk, |
| }) { |
| Cache.flutterRoot = getFlutterRoot(); |
| final FileSystem testFileSystem = fileSystem ?? getFileSystemForPlatform(); |
| |
| final Directory fakeFlutterRoot = testFileSystem.directory(Cache.flutterRoot); |
| _insertFakeGradleArtifactDir(flutterRoot: fakeFlutterRoot); |
| 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 Directory dummyTemplateImagesDirectory = fakeFlutterRoot.parent; |
| dummyTemplateImagesDirectory.createSync(recursive: true); |
| writePackageConfigFiles( |
| directory: testFileSystem |
| .directory(Cache.flutterRoot) |
| .childDirectory('packages') |
| .childDirectory('flutter_tools'), |
| mainLibName: 'app_name', |
| packages: <String, String>{ |
| 'flutter_template_images': dummyTemplateImagesDirectory.uri.toString(), |
| }, |
| ); |
| |
| testUsingContext( |
| description, |
| testMethod, |
| overrides: <Type, Generator>{ |
| FileSystem: () => testFileSystem, |
| ProcessManager: () => processManager ?? FakeProcessManager.any(), |
| Java: () => java, |
| AndroidStudio: () => androidStudio ?? FakeAndroidStudio(), |
| // Intentionally null if not set. Some ios tests fail if this is a fake. |
| AndroidSdk: () => androidSdk, |
| Cache: () => Cache( |
| logger: globals.logger, |
| fileSystem: testFileSystem, |
| osUtils: globals.os, |
| platform: globals.platform, |
| artifacts: <ArtifactSet>[], |
| ), |
| FlutterProjectFactory: () => |
| FlutterProjectFactory(fileSystem: testFileSystem, logger: globals.logger), |
| Pub: ThrowingPub.new, |
| }, |
| ); |
| } |
| |
| /// 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 Exception('Unsupported FileSystemEntity ${entity.runtimeType}'); |
| } |
| } |
| |
| void expectExists(FileSystemEntity entity) { |
| expect(entity.existsSync(), isTrue); |
| } |
| |
| void expectNotExists(FileSystemEntity entity) { |
| expect(entity.existsSync(), isFalse); |
| } |
| |
| void addIosProjectFile(Directory directory, {required String Function() projectFileContent}) { |
| directory.childDirectory('ios').childDirectory('Runner.xcodeproj').childFile('project.pbxproj') |
| ..createSync(recursive: true) |
| ..writeAsStringSync(projectFileContent()); |
| } |
| |
| /// Adds app-level Gradle Groovy build file (build.gradle) to [directory]. |
| /// |
| /// If [kotlinDsl] is true, then build.gradle.kts is created instead of |
| /// build.gradle. It's the caller's responsibility to make sure that |
| /// [gradleFileContent] is consistent with the value of the [kotlinDsl] flag. |
| void addAndroidGradleFile( |
| Directory directory, { |
| required String Function() gradleFileContent, |
| bool kotlinDsl = false, |
| }) { |
| directory |
| .childDirectory('android') |
| .childDirectory('app') |
| .childFile(kotlinDsl ? 'build.gradle.kts' : 'build.gradle') |
| ..createSync(recursive: true) |
| ..writeAsStringSync(gradleFileContent()); |
| } |
| |
| void addRootGradleFile(Directory directory, {required String Function() gradleFileContent}) { |
| directory.childDirectory('android').childFile('build.gradle') |
| ..createSync(recursive: true) |
| ..writeAsStringSync(gradleFileContent()); |
| } |
| |
| void addGradleWrapperFile(Directory directory, String gradleVersion) { |
| directory |
| .childDirectory('android') |
| .childDirectory(gradle_utils.gradleDirectoryName) |
| .childDirectory(gradle_utils.gradleWrapperDirectoryName) |
| .childFile(gradle_utils.gradleWrapperPropertiesFilename) |
| ..createSync(recursive: true) |
| ..writeAsStringSync(''' |
| distributionBase=GRADLE_USER_HOME |
| distributionPath=wrapper/dists |
| zipStoreBase=GRADLE_USER_HOME |
| zipStorePath=wrapper/dists |
| distributionUrl=https://services.gradle.org/distributions/gradle-$gradleVersion-all.zip |
| '''); |
| } |
| |
| FileSystem getFileSystemForPlatform() { |
| return MemoryFileSystem( |
| style: globals.platform.isWindows ? FileSystemStyle.windows : FileSystemStyle.posix, |
| ); |
| } |
| |
| void addAndroidWithGroup(Directory directory, String id, {bool kotlinDsl = false}) { |
| directory.childDirectory('android').childFile(kotlinDsl ? 'build.gradle.kts' : '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: |
| '''; |
| |
| /// This is the equivalent to [validPubspecWithDependencies] after it's been |
| /// passed through [YamlEditor], which explicitly populates the null values |
| /// even if they were specified implicitly. |
| String get validPubspecWithDependenciesAndNullValues => ''' |
| name: hello |
| flutter: null |
| dependencies: |
| plugin_a: null |
| plugin_b: null'''; |
| |
| 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 { |
| compileSdk 34 |
| |
| defaultConfig { |
| applicationId '$id' |
| } |
| } |
| '''; |
| } |
| |
| String gradleFileWithGroupId(String id) { |
| return ''' |
| group '$id' |
| version '1.0-SNAPSHOT' |
| |
| apply plugin: 'com.android.library' |
| |
| android { |
| compileSdk 34 |
| } |
| '''; |
| } |
| |
| File androidPluginRegistrant(Directory parent) { |
| return parent |
| .childDirectory('src') |
| .childDirectory('main') |
| .childDirectory('java') |
| .childDirectory('io') |
| .childDirectory('flutter') |
| .childDirectory('plugins') |
| .childFile('GeneratedPluginRegistrant.java'); |
| } |
| |
| class FakeXcodeProjectInterpreter extends Fake implements XcodeProjectInterpreter { |
| FakeXcodeProjectInterpreter({this.version}); |
| |
| final buildSettingsByBuildContext = <XcodeProjectBuildContext, Map<String, String>>{}; |
| late XcodeProjectInfo xcodeProjectInfo; |
| |
| @override |
| Future<Map<String, String>> getBuildSettings( |
| String projectPath, { |
| XcodeProjectBuildContext? buildContext, |
| Duration timeout = const Duration(minutes: 1), |
| }) async { |
| if (buildSettingsByBuildContext[buildContext] == null) { |
| return <String, String>{}; |
| } |
| return buildSettingsByBuildContext[buildContext]!; |
| } |
| |
| @override |
| Future<XcodeProjectInfo> getInfo(String projectPath, {String? projectFilename}) async { |
| return xcodeProjectInfo; |
| } |
| |
| @override |
| bool get isInstalled => true; |
| |
| @override |
| Version? version; |
| } |
| |
| class FakeAndroidSdkWithDir extends Fake implements AndroidSdk { |
| FakeAndroidSdkWithDir(this._directory); |
| |
| final Directory _directory; |
| |
| @override |
| Directory get directory => _directory; |
| } |