1// Copyright 2018 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5import 'dart:async'; 6 7import 'package:file/file.dart'; 8import 'package:file/memory.dart'; 9import 'package:flutter_tools/src/base/common.dart'; 10import 'package:flutter_tools/src/base/context.dart'; 11import 'package:flutter_tools/src/base/file_system.dart'; 12import 'package:flutter_tools/src/base/platform.dart'; 13import 'package:flutter_tools/src/cache.dart'; 14import 'package:flutter_tools/src/flutter_manifest.dart'; 15import 'package:flutter_tools/src/ios/plist_parser.dart'; 16import 'package:flutter_tools/src/ios/xcodeproj.dart'; 17import 'package:flutter_tools/src/project.dart'; 18import 'package:meta/meta.dart'; 19import 'package:mockito/mockito.dart'; 20 21import '../src/common.dart'; 22import '../src/context.dart'; 23import '../src/testbed.dart'; 24 25void main() { 26 group('Project', () { 27 group('construction', () { 28 testInMemory('fails on null directory', () async { 29 expect( 30 () => FlutterProject.fromDirectory(null), 31 throwsA(isInstanceOf<AssertionError>()), 32 ); 33 }); 34 35 testInMemory('fails on invalid pubspec.yaml', () async { 36 final Directory directory = fs.directory('myproject'); 37 directory.childFile('pubspec.yaml') 38 ..createSync(recursive: true) 39 ..writeAsStringSync(invalidPubspec); 40 41 expect( 42 () => FlutterProject.fromDirectory(directory), 43 throwsA(isInstanceOf<ToolExit>()), 44 ); 45 }); 46 47 testInMemory('fails on pubspec.yaml parse failure', () async { 48 final Directory directory = fs.directory('myproject'); 49 directory.childFile('pubspec.yaml') 50 ..createSync(recursive: true) 51 ..writeAsStringSync(parseErrorPubspec); 52 53 expect( 54 () => FlutterProject.fromDirectory(directory), 55 throwsA(isInstanceOf<ToolExit>()), 56 ); 57 }); 58 59 testInMemory('fails on invalid example/pubspec.yaml', () async { 60 final Directory directory = fs.directory('myproject'); 61 directory.childDirectory('example').childFile('pubspec.yaml') 62 ..createSync(recursive: true) 63 ..writeAsStringSync(invalidPubspec); 64 65 expect( 66 () => FlutterProject.fromDirectory(directory), 67 throwsA(isInstanceOf<ToolExit>()), 68 ); 69 }); 70 71 testInMemory('treats missing pubspec.yaml as empty', () async { 72 final Directory directory = fs.directory('myproject') 73 ..createSync(recursive: true); 74 expect((FlutterProject.fromDirectory(directory)).manifest.isEmpty, 75 true, 76 ); 77 }); 78 79 testInMemory('reads valid pubspec.yaml', () async { 80 final Directory directory = fs.directory('myproject'); 81 directory.childFile('pubspec.yaml') 82 ..createSync(recursive: true) 83 ..writeAsStringSync(validPubspec); 84 expect( 85 FlutterProject.fromDirectory(directory).manifest.appName, 86 'hello', 87 ); 88 }); 89 90 testInMemory('sets up location', () async { 91 final Directory directory = fs.directory('myproject'); 92 expect( 93 FlutterProject.fromDirectory(directory).directory.absolute.path, 94 directory.absolute.path, 95 ); 96 expect( 97 FlutterProject.fromPath(directory.path).directory.absolute.path, 98 directory.absolute.path, 99 ); 100 expect( 101 FlutterProject.current().directory.absolute.path, 102 fs.currentDirectory.absolute.path, 103 ); 104 }); 105 }); 106 107 group('editable Android host app', () { 108 testInMemory('fails on non-module', () async { 109 final FlutterProject project = await someProject(); 110 await expectLater( 111 project.android.makeHostAppEditable(), 112 throwsA(isInstanceOf<AssertionError>()), 113 ); 114 }); 115 testInMemory('exits on already editable module', () async { 116 final FlutterProject project = await aModuleProject(); 117 await project.android.makeHostAppEditable(); 118 return expectToolExitLater(project.android.makeHostAppEditable(), contains('already editable')); 119 }); 120 testInMemory('creates android/app folder in place of .android/app', () async { 121 final FlutterProject project = await aModuleProject(); 122 await project.android.makeHostAppEditable(); 123 expectNotExists(project.directory.childDirectory('.android').childDirectory('app')); 124 expect( 125 project.directory.childDirectory('.android').childFile('settings.gradle').readAsStringSync(), 126 isNot(contains("include ':app'")), 127 ); 128 expectExists(project.directory.childDirectory('android').childDirectory('app')); 129 expectExists(project.directory.childDirectory('android').childFile('local.properties')); 130 expect( 131 project.directory.childDirectory('android').childFile('settings.gradle').readAsStringSync(), 132 contains("include ':app'"), 133 ); 134 }); 135 testInMemory('retains .android/Flutter folder and references it', () async { 136 final FlutterProject project = await aModuleProject(); 137 await project.android.makeHostAppEditable(); 138 expectExists(project.directory.childDirectory('.android').childDirectory('Flutter')); 139 expect( 140 project.directory.childDirectory('android').childFile('settings.gradle').readAsStringSync(), 141 contains('new File(settingsDir.parentFile, \'.android/include_flutter.groovy\')'), 142 ); 143 }); 144 testInMemory('can be redone after deletion', () async { 145 final FlutterProject project = await aModuleProject(); 146 await project.android.makeHostAppEditable(); 147 project.directory.childDirectory('android').deleteSync(recursive: true); 148 await project.android.makeHostAppEditable(); 149 expectExists(project.directory.childDirectory('android').childDirectory('app')); 150 }); 151 }); 152 153 group('ensure ready for platform-specific tooling', () { 154 testInMemory('does nothing, if project is not created', () async { 155 final FlutterProject project = FlutterProject( 156 fs.directory('not_created'), 157 FlutterManifest.empty(), 158 FlutterManifest.empty(), 159 ); 160 await project.ensureReadyForPlatformSpecificTooling(); 161 expectNotExists(project.directory); 162 }); 163 testInMemory('does nothing in plugin or package root project', () async { 164 final FlutterProject project = await aPluginProject(); 165 await project.ensureReadyForPlatformSpecificTooling(); 166 expectNotExists(project.ios.hostAppRoot.childDirectory('Runner').childFile('GeneratedPluginRegistrant.h')); 167 expectNotExists(androidPluginRegistrant(project.android.hostAppGradleRoot.childDirectory('app'))); 168 expectNotExists(project.ios.hostAppRoot.childDirectory('Flutter').childFile('Generated.xcconfig')); 169 expectNotExists(project.android.hostAppGradleRoot.childFile('local.properties')); 170 }); 171 testInMemory('injects plugins for iOS', () async { 172 final FlutterProject project = await someProject(); 173 await project.ensureReadyForPlatformSpecificTooling(); 174 expectExists(project.ios.hostAppRoot.childDirectory('Runner').childFile('GeneratedPluginRegistrant.h')); 175 }); 176 testInMemory('generates Xcode configuration for iOS', () async { 177 final FlutterProject project = await someProject(); 178 await project.ensureReadyForPlatformSpecificTooling(); 179 expectExists(project.ios.hostAppRoot.childDirectory('Flutter').childFile('Generated.xcconfig')); 180 }); 181 testInMemory('injects plugins for Android', () async { 182 final FlutterProject project = await someProject(); 183 await project.ensureReadyForPlatformSpecificTooling(); 184 expectExists(androidPluginRegistrant(project.android.hostAppGradleRoot.childDirectory('app'))); 185 }); 186 testInMemory('updates local properties for Android', () async { 187 final FlutterProject project = await someProject(); 188 await project.ensureReadyForPlatformSpecificTooling(); 189 expectExists(project.android.hostAppGradleRoot.childFile('local.properties')); 190 }); 191 testInMemory('creates Android library in module', () async { 192 final FlutterProject project = await aModuleProject(); 193 await project.ensureReadyForPlatformSpecificTooling(); 194 expectExists(project.android.hostAppGradleRoot.childFile('settings.gradle')); 195 expectExists(project.android.hostAppGradleRoot.childFile('local.properties')); 196 expectExists(androidPluginRegistrant(project.android.hostAppGradleRoot.childDirectory('Flutter'))); 197 }); 198 testInMemory('creates iOS pod in module', () async { 199 final FlutterProject project = await aModuleProject(); 200 await project.ensureReadyForPlatformSpecificTooling(); 201 final Directory flutter = project.ios.hostAppRoot.childDirectory('Flutter'); 202 expectExists(flutter.childFile('podhelper.rb')); 203 expectExists(flutter.childFile('flutter_export_environment.sh')); 204 expectExists(flutter.childFile('${project.manifest.appName}.podspec')); 205 expectExists(flutter.childFile('Generated.xcconfig')); 206 final Directory pluginRegistrantClasses = flutter 207 .childDirectory('FlutterPluginRegistrant') 208 .childDirectory('Classes'); 209 expectExists(pluginRegistrantClasses.childFile('GeneratedPluginRegistrant.h')); 210 expectExists(pluginRegistrantClasses.childFile('GeneratedPluginRegistrant.m')); 211 }); 212 }); 213 214 group('module status', () { 215 testInMemory('is known for module', () async { 216 final FlutterProject project = await aModuleProject(); 217 expect(project.isModule, isTrue); 218 expect(project.android.isModule, isTrue); 219 expect(project.ios.isModule, isTrue); 220 expect(project.android.hostAppGradleRoot.basename, '.android'); 221 expect(project.ios.hostAppRoot.basename, '.ios'); 222 }); 223 testInMemory('is known for non-module', () async { 224 final FlutterProject project = await someProject(); 225 expect(project.isModule, isFalse); 226 expect(project.android.isModule, isFalse); 227 expect(project.ios.isModule, isFalse); 228 expect(project.android.hostAppGradleRoot.basename, 'android'); 229 expect(project.ios.hostAppRoot.basename, 'ios'); 230 }); 231 }); 232 233 group('example', () { 234 testInMemory('exists for plugin', () async { 235 final FlutterProject project = await aPluginProject(); 236 expect(project.hasExampleApp, isTrue); 237 }); 238 testInMemory('does not exist for non-plugin', () async { 239 final FlutterProject project = await someProject(); 240 expect(project.hasExampleApp, isFalse); 241 }); 242 }); 243 244 group('language', () { 245 MockXcodeProjectInterpreter mockXcodeProjectInterpreter; 246 MemoryFileSystem fs; 247 setUp(() { 248 fs = MemoryFileSystem(); 249 mockXcodeProjectInterpreter = MockXcodeProjectInterpreter(); 250 }); 251 252 testInMemory('default host app language', () async { 253 final FlutterProject project = await someProject(); 254 expect(project.ios.isSwift, isFalse); 255 expect(project.android.isKotlin, isFalse); 256 }); 257 258 testUsingContext('swift and kotlin host app language', () async { 259 final FlutterProject project = await someProject(); 260 261 when(mockXcodeProjectInterpreter.getBuildSettings(any, any)).thenReturn(<String, String>{ 262 'SWIFT_VERSION': '4.0', 263 }); 264 addAndroidGradleFile(project.directory, 265 gradleFileContent: () { 266 return ''' 267apply plugin: 'com.android.application' 268apply plugin: 'kotlin-android' 269'''; 270 }); 271 expect(project.ios.isSwift, isTrue); 272 expect(project.android.isKotlin, isTrue); 273 }, overrides: <Type, Generator>{ 274 FileSystem: () => fs, 275 XcodeProjectInterpreter: () => mockXcodeProjectInterpreter, 276 }); 277 }); 278 279 group('product bundle identifier', () { 280 MemoryFileSystem fs; 281 MockPlistUtils mockPlistUtils; 282 MockXcodeProjectInterpreter mockXcodeProjectInterpreter; 283 setUp(() { 284 fs = MemoryFileSystem(); 285 mockPlistUtils = MockPlistUtils(); 286 mockXcodeProjectInterpreter = MockXcodeProjectInterpreter(); 287 }); 288 289 void testWithMocks(String description, Future<void> testMethod()) { 290 testUsingContext(description, testMethod, overrides: <Type, Generator>{ 291 FileSystem: () => fs, 292 PlistParser: () => mockPlistUtils, 293 XcodeProjectInterpreter: () => mockXcodeProjectInterpreter, 294 }); 295 } 296 297 testWithMocks('null, if no pbxproj or plist entries', () async { 298 final FlutterProject project = await someProject(); 299 expect(project.ios.productBundleIdentifier, isNull); 300 }); 301 testWithMocks('from pbxproj file, if no plist', () async { 302 final FlutterProject project = await someProject(); 303 addIosProjectFile(project.directory, projectFileContent: () { 304 return projectFileWithBundleId('io.flutter.someProject'); 305 }); 306 expect(project.ios.productBundleIdentifier, 'io.flutter.someProject'); 307 }); 308 testWithMocks('from plist, if no variables', () async { 309 final FlutterProject project = await someProject(); 310 when(mockPlistUtils.getValueFromFile(any, any)).thenReturn('io.flutter.someProject'); 311 expect(project.ios.productBundleIdentifier, 'io.flutter.someProject'); 312 }); 313 testWithMocks('from pbxproj and plist, if default variable', () async { 314 final FlutterProject project = await someProject(); 315 addIosProjectFile(project.directory, projectFileContent: () { 316 return projectFileWithBundleId('io.flutter.someProject'); 317 }); 318 when(mockPlistUtils.getValueFromFile(any, any)).thenReturn('\$(PRODUCT_BUNDLE_IDENTIFIER)'); 319 expect(project.ios.productBundleIdentifier, 'io.flutter.someProject'); 320 }); 321 testWithMocks('from pbxproj and plist, by substitution', () async { 322 final FlutterProject project = await someProject(); 323 when(mockXcodeProjectInterpreter.getBuildSettings(any, any)).thenReturn(<String, String>{ 324 'PRODUCT_BUNDLE_IDENTIFIER': 'io.flutter.someProject', 325 'SUFFIX': 'suffix', 326 }); 327 when(mockPlistUtils.getValueFromFile(any, any)).thenReturn('\$(PRODUCT_BUNDLE_IDENTIFIER).\$(SUFFIX)'); 328 expect(project.ios.productBundleIdentifier, 'io.flutter.someProject.suffix'); 329 }); 330 testWithMocks('empty surrounded by quotes', () async { 331 final FlutterProject project = await someProject(); 332 addIosProjectFile(project.directory, projectFileContent: () { 333 return projectFileWithBundleId('', qualifier: '"'); 334 }); 335 expect(project.ios.productBundleIdentifier, ''); 336 }); 337 testWithMocks('surrounded by double quotes', () async { 338 final FlutterProject project = await someProject(); 339 addIosProjectFile(project.directory, projectFileContent: () { 340 return projectFileWithBundleId('io.flutter.someProject', qualifier: '"'); 341 }); 342 expect(project.ios.productBundleIdentifier, 'io.flutter.someProject'); 343 }); 344 testWithMocks('surrounded by single quotes', () async { 345 final FlutterProject project = await someProject(); 346 addIosProjectFile(project.directory, projectFileContent: () { 347 return projectFileWithBundleId('io.flutter.someProject', qualifier: '\''); 348 }); 349 expect(project.ios.productBundleIdentifier, 'io.flutter.someProject'); 350 }); 351 }); 352 353 group('organization names set', () { 354 testInMemory('is empty, if project not created', () async { 355 final FlutterProject project = await someProject(); 356 expect(project.organizationNames, isEmpty); 357 }); 358 testInMemory('is empty, if no platform folders exist', () async { 359 final FlutterProject project = await someProject(); 360 project.directory.createSync(); 361 expect(project.organizationNames, isEmpty); 362 }); 363 testInMemory('is populated from iOS bundle identifier', () async { 364 final FlutterProject project = await someProject(); 365 addIosProjectFile(project.directory, projectFileContent: () { 366 return projectFileWithBundleId('io.flutter.someProject', qualifier: '\''); 367 }); 368 expect(project.organizationNames, <String>['io.flutter']); 369 }); 370 testInMemory('is populated from Android application ID', () async { 371 final FlutterProject project = await someProject(); 372 addAndroidGradleFile(project.directory, 373 gradleFileContent: () { 374 return gradleFileWithApplicationId('io.flutter.someproject'); 375 }); 376 expect(project.organizationNames, <String>['io.flutter']); 377 }); 378 testInMemory('is populated from iOS bundle identifier in plugin example', () async { 379 final FlutterProject project = await someProject(); 380 addIosProjectFile(project.example.directory, projectFileContent: () { 381 return projectFileWithBundleId('io.flutter.someProject', qualifier: '\''); 382 }); 383 expect(project.organizationNames, <String>['io.flutter']); 384 }); 385 testInMemory('is populated from Android application ID in plugin example', () async { 386 final FlutterProject project = await someProject(); 387 addAndroidGradleFile(project.example.directory, 388 gradleFileContent: () { 389 return gradleFileWithApplicationId('io.flutter.someproject'); 390 }); 391 expect(project.organizationNames, <String>['io.flutter']); 392 }); 393 testInMemory('is populated from Android group in plugin', () async { 394 final FlutterProject project = await someProject(); 395 addAndroidWithGroup(project.directory, 'io.flutter.someproject'); 396 expect(project.organizationNames, <String>['io.flutter']); 397 }); 398 testInMemory('is singleton, if sources agree', () async { 399 final FlutterProject project = await someProject(); 400 addIosProjectFile(project.directory, projectFileContent: () { 401 return projectFileWithBundleId('io.flutter.someProject'); 402 }); 403 addAndroidGradleFile(project.directory, 404 gradleFileContent: () { 405 return gradleFileWithApplicationId('io.flutter.someproject'); 406 }); 407 expect(project.organizationNames, <String>['io.flutter']); 408 }); 409 testInMemory('is non-singleton, if sources disagree', () async { 410 final FlutterProject project = await someProject(); 411 addIosProjectFile(project.directory, projectFileContent: () { 412 return projectFileWithBundleId('io.flutter.someProject'); 413 }); 414 addAndroidGradleFile(project.directory, 415 gradleFileContent: () { 416 return gradleFileWithApplicationId('io.clutter.someproject'); 417 }); 418 expect( 419 project.organizationNames, 420 <String>['io.flutter', 'io.clutter'], 421 ); 422 }); 423 }); 424 }); 425 426 group('Regression test for invalid pubspec', () { 427 Testbed testbed; 428 429 setUp(() { 430 testbed = Testbed(); 431 }); 432 433 test('Handles asking for builders from an invalid pubspec', () => testbed.run(() { 434 fs.file('pubspec.yaml') 435 ..createSync() 436 ..writeAsStringSync(r''' 437# Hello, World 438'''); 439 final FlutterProject flutterProject = FlutterProject.current(); 440 441 expect(flutterProject.builders, null); 442 })); 443 444 test('Handles asking for builders from a trivial pubspec', () => testbed.run(() { 445 fs.file('pubspec.yaml') 446 ..createSync() 447 ..writeAsStringSync(r''' 448# Hello, World 449name: foo_bar 450'''); 451 final FlutterProject flutterProject = FlutterProject.current(); 452 453 expect(flutterProject.builders, null); 454 })); 455 }); 456} 457 458Future<FlutterProject> someProject() async { 459 final Directory directory = fs.directory('some_project'); 460 directory.childFile('.packages').createSync(recursive: true); 461 directory.childDirectory('ios').createSync(recursive: true); 462 directory.childDirectory('android').createSync(recursive: true); 463 return FlutterProject.fromDirectory(directory); 464} 465 466Future<FlutterProject> aPluginProject() async { 467 final Directory directory = fs.directory('plugin_project'); 468 directory.childDirectory('ios').createSync(recursive: true); 469 directory.childDirectory('android').createSync(recursive: true); 470 directory.childDirectory('example').createSync(recursive: true); 471 directory.childFile('pubspec.yaml').writeAsStringSync(''' 472name: my_plugin 473flutter: 474 plugin: 475 androidPackage: com.example 476 pluginClass: MyPlugin 477 iosPrefix: FLT 478'''); 479 return FlutterProject.fromDirectory(directory); 480} 481 482Future<FlutterProject> aModuleProject() async { 483 final Directory directory = fs.directory('module_project'); 484 directory.childFile('.packages').createSync(recursive: true); 485 directory.childFile('pubspec.yaml').writeAsStringSync(''' 486name: my_module 487flutter: 488 module: 489 androidPackage: com.example 490'''); 491 return FlutterProject.fromDirectory(directory); 492} 493 494/// Executes the [testMethod] in a context where the file system 495/// is in memory. 496@isTest 497void testInMemory(String description, Future<void> testMethod()) { 498 Cache.flutterRoot = getFlutterRoot(); 499 final FileSystem testFileSystem = MemoryFileSystem( 500 style: platform.isWindows ? FileSystemStyle.windows : FileSystemStyle.posix, 501 ); 502 // Transfer needed parts of the Flutter installation folder 503 // to the in-memory file system used during testing. 504 transfer(Cache().getArtifactDirectory('gradle_wrapper'), testFileSystem); 505 transfer(fs.directory(Cache.flutterRoot) 506 .childDirectory('packages') 507 .childDirectory('flutter_tools') 508 .childDirectory('templates'), testFileSystem); 509 transfer(fs.directory(Cache.flutterRoot) 510 .childDirectory('packages') 511 .childDirectory('flutter_tools') 512 .childDirectory('schema'), testFileSystem); 513 testUsingContext( 514 description, 515 testMethod, 516 overrides: <Type, Generator>{ 517 FileSystem: () => testFileSystem, 518 Cache: () => Cache(), 519 }, 520 ); 521} 522 523/// Transfers files and folders from the local file system's Flutter 524/// installation to an (in-memory) file system used for testing. 525void transfer(FileSystemEntity entity, FileSystem target) { 526 if (entity is Directory) { 527 target.directory(entity.absolute.path).createSync(recursive: true); 528 for (FileSystemEntity child in entity.listSync()) { 529 transfer(child, target); 530 } 531 } else if (entity is File) { 532 target.file(entity.absolute.path).writeAsBytesSync(entity.readAsBytesSync(), flush: true); 533 } else { 534 throw 'Unsupported FileSystemEntity ${entity.runtimeType}'; 535 } 536} 537 538void expectExists(FileSystemEntity entity) { 539 expect(entity.existsSync(), isTrue); 540} 541 542void expectNotExists(FileSystemEntity entity) { 543 expect(entity.existsSync(), isFalse); 544} 545 546void addIosProjectFile(Directory directory, {String projectFileContent()}) { 547 directory 548 .childDirectory('ios') 549 .childDirectory('Runner.xcodeproj') 550 .childFile('project.pbxproj') 551 ..createSync(recursive: true) 552 ..writeAsStringSync(projectFileContent()); 553} 554 555void addAndroidGradleFile(Directory directory, { String gradleFileContent() }) { 556 directory 557 .childDirectory('android') 558 .childDirectory('app') 559 .childFile('build.gradle') 560 ..createSync(recursive: true) 561 ..writeAsStringSync(gradleFileContent()); 562} 563 564void addAndroidWithGroup(Directory directory, String id) { 565 directory.childDirectory('android').childFile('build.gradle') 566 ..createSync(recursive: true) 567 ..writeAsStringSync(gradleFileWithGroupId(id)); 568} 569 570String get validPubspec => ''' 571name: hello 572flutter: 573'''; 574 575String get invalidPubspec => ''' 576name: hello 577flutter: 578 invalid: 579'''; 580 581String get parseErrorPubspec => ''' 582name: hello 583# Whitespace is important. 584flutter: 585 something: 586 something_else: 587'''; 588 589String projectFileWithBundleId(String id, {String qualifier}) { 590 return ''' 59197C147061CF9000F007C117D /* Debug */ = { 592 isa = XCBuildConfiguration; 593 baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 594 buildSettings = { 595 PRODUCT_BUNDLE_IDENTIFIER = ${qualifier ?? ''}$id${qualifier ?? ''}; 596 PRODUCT_NAME = "\$(TARGET_NAME)"; 597 }; 598 name = Debug; 599}; 600'''; 601} 602 603String gradleFileWithApplicationId(String id) { 604 return ''' 605apply plugin: 'com.android.application' 606android { 607 compileSdkVersion 28 608 609 defaultConfig { 610 applicationId '$id' 611 } 612} 613'''; 614} 615 616String gradleFileWithGroupId(String id) { 617 return ''' 618group '$id' 619version '1.0-SNAPSHOT' 620 621apply plugin: 'com.android.library' 622 623android { 624 compileSdkVersion 28 625} 626'''; 627} 628 629File androidPluginRegistrant(Directory parent) { 630 return parent.childDirectory('src') 631 .childDirectory('main') 632 .childDirectory('java') 633 .childDirectory('io') 634 .childDirectory('flutter') 635 .childDirectory('plugins') 636 .childFile('GeneratedPluginRegistrant.java'); 637} 638 639class MockPlistUtils extends Mock implements PlistParser {} 640 641class MockXcodeProjectInterpreter extends Mock implements XcodeProjectInterpreter { 642 @override 643 bool get isInstalled => true; 644} 645