create_test.dart 130 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'dart:async';
6
import 'dart:convert';
7
import 'dart:io' as io;
8

9
import 'package:args/command_runner.dart';
10
import 'package:file_testing/file_testing.dart';
11
import 'package:flutter_tools/src/artifacts.dart';
12
import 'package:flutter_tools/src/base/file_system.dart';
13
import 'package:flutter_tools/src/base/io.dart';
14
import 'package:flutter_tools/src/base/logger.dart';
15
import 'package:flutter_tools/src/base/net.dart';
16
import 'package:flutter_tools/src/base/platform.dart';
17
import 'package:flutter_tools/src/build_info.dart';
18
import 'package:flutter_tools/src/cache.dart';
19
import 'package:flutter_tools/src/commands/create.dart';
20
import 'package:flutter_tools/src/commands/create_base.dart';
21
import 'package:flutter_tools/src/dart/pub.dart';
22
import 'package:flutter_tools/src/features.dart';
23
import 'package:flutter_tools/src/globals.dart' as globals;
24
import 'package:flutter_tools/src/project.dart';
25
import 'package:flutter_tools/src/version.dart';
26
import 'package:process/process.dart';
27
import 'package:pub_semver/pub_semver.dart';
28
import 'package:pubspec_parse/pubspec_parse.dart';
29
import 'package:uuid/uuid.dart';
30
import 'package:yaml/yaml.dart';
31

32 33
import '../../src/common.dart';
import '../../src/context.dart';
34
import '../../src/fake_http_client.dart';
35
import '../../src/fakes.dart';
36
import '../../src/pubspec_schema.dart';
37
import '../../src/test_flutter_command_runner.dart';
38

39
const String _kNoPlatformsMessage = "You've created a plugin project that doesn't yet support any platforms.\n";
40 41
const String frameworkRevision = '12345678';
const String frameworkChannel = 'omega';
42
const String _kDisabledPlatformRequestedMessage = 'currently not supported on your local environment.';
43 44

// This needs to be created from the local platform due to re-entrant flutter calls made in this test.
45
FakePlatform _kNoColorTerminalPlatform() => FakePlatform.fromPlatform(const LocalPlatform())..stdoutSupportsAnsi = false;
46 47 48
FakePlatform _kNoColorTerminalMacOSPlatform() => FakePlatform.fromPlatform(const LocalPlatform())
  ..stdoutSupportsAnsi = false
  ..operatingSystem = 'macos';
49

50
final Map<Type, Generator> noColorTerminalOverride = <Type, Generator>{
51 52
  Platform: _kNoColorTerminalPlatform,
};
53

54 55
const String samplesIndexJson = '''
[
56 57 58
  { "id": "sample1" },
  { "id": "sample2" }
]''';
59

60
void main() {
61 62 63 64 65 66
  late Directory tempDir;
  late Directory projectDir;
  late FakeFlutterVersion fakeFlutterVersion;
  late LoggingProcessManager loggingProcessManager;
  late FakeProcessManager fakeProcessManager;
  late BufferLogger logger;
67
  late FakeStdio mockStdio;
68

69
  setUpAll(() async {
70
    Cache.disableLocking();
71
    await _ensureFlutterToolsSnapshot();
72 73 74 75
  });

  setUp(() {
    loggingProcessManager = LoggingProcessManager();
76
    logger = BufferLogger.test();
77
    tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_tools_create_test.');
78
    projectDir = tempDir.childDirectory('flutter_project');
79 80 81 82
    fakeFlutterVersion = FakeFlutterVersion(
      frameworkRevision: frameworkRevision,
      channel: frameworkChannel,
    );
83
    fakeProcessManager = FakeProcessManager.empty();
84
    mockStdio = FakeStdio();
85
  });
86

87 88 89 90
  tearDown(() {
    tryToDelete(tempDir);
  });

91 92 93 94
  tearDownAll(() async {
    await _restoreFlutterToolsSnapshot();
  });

95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
  test('createAndroidIdentifier emits a valid identifier', () {
    final String identifier = CreateBase.createAndroidIdentifier('42org', '8project');
    expect(identifier.contains('.'), isTrue);

    final RegExp startsWithLetter = RegExp(r'^[a-zA-Z][\w]*$');
    final List<String> segments = identifier.split('.');
    for (final String segment in segments) {
      expect(startsWithLetter.hasMatch(segment), isTrue);
    }
  });

  test('createUTIIdentifier emits a valid identifier', () {
    final String identifier = CreateBase.createUTIIdentifier('org@', 'project');
    expect(identifier.contains('.'), isTrue);
    expect(identifier.contains('@'), isFalse);
  });

  test('createWindowsIdentifier emits a GUID', () {
    final String identifier = CreateBase.createWindowsIdentifier('org', 'project');
    expect(Uuid.isValidUUID(fromString: identifier), isTrue);
  });

117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
  testUsingContext('tool exits on Windows if given a drive letter without a path', () async {
    // Must use LocalFileSystem as it is dependent on dart:io handling of
    // Windows paths, which the MemoryFileSystem does not implement
    final Directory workingDir = globals.fs.directory(r'X:\path\to\working\dir');
    // Must use [io.IOOverrides] as directory.absolute depends on Directory.current
    // from dart:io.
    await io.IOOverrides.runZoned<Future<void>>(
      () async {
        // Verify IOOverrides is working
        expect(io.Directory.current, workingDir);
        final CreateCommand command = CreateCommand();
        final CommandRunner<void> runner = createTestCommandRunner(command);
        const String driveName = 'X:';
        await expectToolExitLater(
          runner.run(<String>[
            'create',
            '--project-name',
            'test_app',
            '--offline',
            driveName,
          ]),
          contains('You attempted to create a flutter project at the path "$driveName"'),
        );
      },
      getCurrentDirectory: () => workingDir,
    );
  }, overrides: <Type, Generator>{
    Logger: () => BufferLogger.test(),
  }, skip: !io.Platform.isWindows // [intended] relies on Windows file system
  );

148
  // Verify that we create a default project ('app') that is
149 150
  // well-formed.
  testUsingContext('can create a default project', () async {
151 152
    await _createAndAnalyzeProject(
      projectDir,
153
      <String>['-i', 'objc', '-a', 'java'],
154
      <String>[
155
        'analysis_options.yaml',
156
        'android/app/src/main/java/com/example/flutter_project/MainActivity.java',
157 158 159 160 161
        'android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java',
        'flutter_project.iml',
        'ios/Flutter/AppFrameworkInfo.plist',
        'ios/Runner/AppDelegate.m',
        'ios/Runner/GeneratedPluginRegistrant.h',
162 163
        'ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png',
        'ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png',
164
        'lib/main.dart',
165 166
      ],
    );
167
    return _runFlutterTest(projectDir);
168
  }, overrides: <Type, Generator>{
169 170 171 172 173 174 175
    Pub: () => Pub(
      fileSystem: globals.fs,
      logger: globals.logger,
      processManager: globals.processManager,
      usage: globals.flutterUsage,
      botDetector: globals.botDetector,
      platform: globals.platform,
176
      stdio: mockStdio,
177
    ),
178
  });
179

180 181 182 183 184
  testUsingContext('can create a skeleton (list/detail) app', () async {
    await _createAndAnalyzeProject(
      projectDir,
      <String>['-t', 'skeleton', '-i', 'objc', '-a', 'java', '--implementation-tests'],
      <String>[
185 186
        '.dart_tool/flutter_gen/pubspec.yaml',
        '.dart_tool/flutter_gen/gen_l10n/app_localizations.dart',
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
        'analysis_options.yaml',
        'android/app/src/main/java/com/example/flutter_project/MainActivity.java',
        'android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java',
        'flutter_project.iml',
        'ios/Flutter/AppFrameworkInfo.plist',
        'ios/Runner/AppDelegate.m',
        'ios/Runner/GeneratedPluginRegistrant.h',
        'lib/main.dart',
        'l10n.yaml',
        'assets/images/2.0x/flutter_logo.png',
        'assets/images/flutter_logo.png',
        'assets/images/3.0x/flutter_logo.png',
        'test/unit_test.dart',
        'test/widget_test.dart',
        'test/implementation_test.dart',
        'lib/src/localization/app_en.arb',
        'lib/src/app.dart',
        'lib/src/sample_feature/sample_item_details_view.dart',
        'lib/src/sample_feature/sample_item_list_view.dart',
        'lib/src/sample_feature/sample_item.dart',
        'lib/src/settings/settings_controller.dart',
        'lib/src/settings/settings_view.dart',
        'lib/src/settings/settings_service.dart',
        'lib/main.dart',
        'pubspec.yaml',
        'README.md',
      ],
    );
    return _runFlutterTest(projectDir);
  }, overrides: <Type, Generator>{
    Pub: () => Pub(
      fileSystem: globals.fs,
      logger: globals.logger,
      processManager: globals.processManager,
      usage: globals.flutterUsage,
      botDetector: globals.botDetector,
      platform: globals.platform,
224
      stdio: mockStdio,
225 226 227
    ),
  });

228 229 230 231
  testUsingContext('can create a default project if empty directory exists', () async {
    await projectDir.create(recursive: true);
    await _createAndAnalyzeProject(
      projectDir,
232
      <String>['-i', 'objc', '-a', 'java'],
233
      <String>[
234
        'analysis_options.yaml',
235
        'android/app/src/main/java/com/example/flutter_project/MainActivity.java',
236 237 238 239 240 241 242
        'android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java',
        'flutter_project.iml',
        'ios/Flutter/AppFrameworkInfo.plist',
        'ios/Runner/AppDelegate.m',
        'ios/Runner/GeneratedPluginRegistrant.h',
      ],
    );
243
  }, overrides: <Type, Generator>{
244 245 246 247 248 249 250
    Pub: () => Pub(
      fileSystem: globals.fs,
      logger: globals.logger,
      processManager: globals.processManager,
      usage: globals.flutterUsage,
      botDetector: globals.botDetector,
      platform: globals.platform,
251
      stdio: mockStdio,
252
    ),
253
  });
254

255 256
  testUsingContext('creates a module project correctly', () async {
    await _createAndAnalyzeProject(projectDir, <String>[
257
      '--template=module',
258
    ], <String>[
259 260 261 262
      '.android/app/',
      '.gitignore',
      '.ios/Flutter',
      '.metadata',
263
      'analysis_options.yaml',
264 265 266 267 268 269 270 271
      'lib/main.dart',
      'pubspec.yaml',
      'README.md',
      'test/widget_test.dart',
    ], unexpectedPaths: <String>[
      'android/',
      'ios/',
    ]);
272
    return _runFlutterTest(projectDir);
273
  }, overrides: <Type, Generator>{
274 275 276 277 278 279 280
    Pub: () => Pub(
      fileSystem: globals.fs,
      logger: globals.logger,
      processManager: globals.processManager,
      usage: globals.flutterUsage,
      botDetector: globals.botDetector,
      platform: globals.platform,
281
      stdio: mockStdio,
282
    ),
283
  });
284 285 286 287

  testUsingContext('cannot create a project if non-empty non-project directory exists with .metadata', () async {
    await projectDir.absolute.childDirectory('blag').create(recursive: true);
    await projectDir.absolute.childFile('.metadata').writeAsString('project_type: blag\n');
288
    expect(() async => _createAndAnalyzeProject(
289 290 291 292 293 294 295 296 297 298
        projectDir,
        <String>[],
        <String>[],
        unexpectedPaths: <String>[
          'android/',
          'ios/',
          '.android/',
          '.ios/',
        ]),
      throwsToolExit(message: 'Sorry, unable to detect the type of project to recreate'));
299
  }, overrides: <Type, Generator>{
300 301 302 303 304 305 306
    Pub: () => Pub(
      fileSystem: globals.fs,
      logger: globals.logger,
      processManager: globals.processManager,
      usage: globals.flutterUsage,
      botDetector: globals.botDetector,
      platform: globals.platform,
307
      stdio: mockStdio,
308
    ),
309 310
    ...noColorTerminalOverride,
  });
311

312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332
  testUsingContext('cannot create a project in flutter root', () async {
    Cache.flutterRoot = '../..';
    final String flutterBin = globals.fs.path.join(getFlutterRoot(), 'bin', globals.platform.isWindows ? 'flutter.bat' : 'flutter');
    final ProcessResult exec = await Process.run(
      flutterBin,
      <String>[
        'create',
        'flutter_project',
      ],
      workingDirectory: Cache.flutterRoot,
    );
    expect(exec.exitCode, 2);
    expect(exec.stderr, contains('Cannot create a project within the Flutter SDK'));
  }, overrides: <Type, Generator>{
    Pub: () => Pub(
      fileSystem: globals.fs,
      logger: globals.logger,
      processManager: globals.processManager,
      usage: globals.flutterUsage,
      botDetector: globals.botDetector,
      platform: globals.platform,
333
      stdio: mockStdio,
334 335 336 337
    ),
    ...noColorTerminalOverride,
  });

338 339 340
  testUsingContext('Will create an app project if non-empty non-project directory exists without .metadata', () async {
    await projectDir.absolute.childDirectory('blag').create(recursive: true);
    await projectDir.absolute.childDirectory('.idea').create(recursive: true);
341 342
    await _createAndAnalyzeProject(
      projectDir,
343
      <String>[
344 345 346 347 348 349 350 351 352 353 354 355 356 357 358
        '-i', 'objc', '-a', 'java',
      ],
      <String>[
        'android/app/src/main/java/com/example/flutter_project/MainActivity.java',
        'android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java',
        'flutter_project.iml',
        'ios/Flutter/AppFrameworkInfo.plist',
        'ios/Runner/AppDelegate.m',
        'ios/Runner/GeneratedPluginRegistrant.h',
      ],
      unexpectedPaths: <String>[
        '.android/',
        '.ios/',
      ],
    );
359
  }, overrides: <Type, Generator>{
360 361 362 363 364 365 366
    Pub: () => Pub(
      fileSystem: globals.fs,
      logger: globals.logger,
      processManager: globals.processManager,
      usage: globals.flutterUsage,
      botDetector: globals.botDetector,
      platform: globals.platform,
367
      stdio: mockStdio,
368
    ),
369
  });
370 371

  testUsingContext('detects and recreates an app project correctly', () async {
372
    await projectDir.absolute.childDirectory('lib').create(recursive: true);
373
    await projectDir.absolute.childDirectory('ios').create(recursive: true);
374 375
    await _createAndAnalyzeProject(
      projectDir,
376
      <String>[
377 378 379 380 381 382 383 384 385 386 387 388 389 390 391
        '-i', 'objc', '-a', 'java',
      ],
      <String>[
        'android/app/src/main/java/com/example/flutter_project/MainActivity.java',
        'android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java',
        'flutter_project.iml',
        'ios/Flutter/AppFrameworkInfo.plist',
        'ios/Runner/AppDelegate.m',
        'ios/Runner/GeneratedPluginRegistrant.h',
      ],
      unexpectedPaths: <String>[
        '.android/',
        '.ios/',
      ],
    );
392
  }, overrides: <Type, Generator>{
393 394 395 396 397 398 399
    Pub: () => Pub(
      fileSystem: globals.fs,
      logger: globals.logger,
      processManager: globals.processManager,
      usage: globals.flutterUsage,
      botDetector: globals.botDetector,
      platform: globals.platform,
400
      stdio: mockStdio,
401
    ),
402
  });
403 404 405 406

  testUsingContext('detects and recreates a plugin project correctly', () async {
    await projectDir.create(recursive: true);
    await projectDir.absolute.childFile('.metadata').writeAsString('project_type: plugin\n');
407
    await _createAndAnalyzeProject(
408
      projectDir,
409
      <String>[],
410 411 412 413 414
      <String>[
        'example/lib/main.dart',
        'flutter_project.iml',
        'lib/flutter_project.dart',
      ],
415 416 417 418
      unexpectedPaths: <String>[
        'android/app/src/main/java/com/example/flutter_project/MainActivity.java',
        'android/src/main/java/com/example/flutter_project/FlutterProjectPlugin.java',
        'example/android/app/src/main/java/com/example/flutter_project_example/MainActivity.java',]
419
    );
420
  }, overrides: <Type, Generator>{
421 422 423 424 425 426 427
    Pub: () => Pub(
      fileSystem: globals.fs,
      logger: globals.logger,
      processManager: globals.processManager,
      usage: globals.flutterUsage,
      botDetector: globals.botDetector,
      platform: globals.platform,
428
      stdio: mockStdio,
429
    ),
430
  });
431 432 433 434 435 436 437 438 439 440 441 442

  testUsingContext('detects and recreates a package project correctly', () async {
    await projectDir.create(recursive: true);
    await projectDir.absolute.childFile('.metadata').writeAsString('project_type: package\n');
    return _createAndAnalyzeProject(
      projectDir,
      <String>[],
      <String>[
        'lib/flutter_project.dart',
        'test/flutter_project_test.dart',
      ],
      unexpectedPaths: <String>[
443 444 445
        'android/app/src/main/java/com/example/flutter_project/MainActivity.java',
        'android/src/main/java/com/example/flutter_project/FlutterProjectPlugin.java',
        'example/android/app/src/main/java/com/example/flutter_project_example/MainActivity.java',
446 447 448 449 450 451 452 453 454 455 456 457 458
        'example/ios/Runner/AppDelegate.h',
        'example/ios/Runner/AppDelegate.m',
        'example/ios/Runner/main.m',
        'example/lib/main.dart',
        'ios/Classes/FlutterProjectPlugin.h',
        'ios/Classes/FlutterProjectPlugin.m',
        'ios/Runner/AppDelegate.h',
        'ios/Runner/AppDelegate.m',
        'ios/Runner/main.m',
        'lib/main.dart',
        'test/widget_test.dart',
      ],
    );
459
  }, overrides: <Type, Generator>{
460 461 462 463 464 465 466
    Pub: () => Pub(
      fileSystem: globals.fs,
      logger: globals.logger,
      processManager: globals.processManager,
      usage: globals.flutterUsage,
      botDetector: globals.botDetector,
      platform: globals.platform,
467
      stdio: mockStdio,
468
    ),
469
  });
470

471 472 473 474 475
  testUsingContext('kotlin/swift legacy app project', () async {
    return _createProject(
      projectDir,
      <String>['--no-pub', '--template=app', '--android-language=kotlin', '--ios-language=swift'],
      <String>[
476
        'android/app/src/main/kotlin/com/example/flutter_project/MainActivity.kt',
477 478 479
        'ios/Runner/AppDelegate.swift',
        'ios/Runner/Runner-Bridging-Header.h',
        'lib/main.dart',
480
        '.idea/libraries/KotlinJavaRuntime.xml',
481 482
      ],
      unexpectedPaths: <String>[
483
        'android/app/src/main/java/com/example/flutter_project/MainActivity.java',
484 485 486 487 488
        'ios/Runner/AppDelegate.h',
        'ios/Runner/AppDelegate.m',
        'ios/Runner/main.m',
      ],
    );
489
  }, overrides: <Type, Generator>{
490 491 492 493 494 495 496
    Pub: () => Pub(
      fileSystem: globals.fs,
      logger: globals.logger,
      processManager: globals.processManager,
      usage: globals.flutterUsage,
      botDetector: globals.botDetector,
      platform: globals.platform,
497
      stdio: mockStdio,
498
    ),
499
  });
500

501
  testUsingContext('can create a package project', () async {
502 503 504 505
    await _createAndAnalyzeProject(
      projectDir,
      <String>['--template=package'],
      <String>[
506
        'analysis_options.yaml',
507 508 509 510
        'lib/flutter_project.dart',
        'test/flutter_project_test.dart',
      ],
      unexpectedPaths: <String>[
511 512 513
        'android/app/src/main/java/com/example/flutter_project/MainActivity.java',
        'android/src/main/java/com/example/flutter_project/FlutterProjectPlugin.java',
        'example/android/app/src/main/java/com/example/flutter_project_example/MainActivity.java',
514 515 516 517 518 519 520 521 522 523 524 525 526 527
        'example/ios/Runner/AppDelegate.h',
        'example/ios/Runner/AppDelegate.m',
        'example/ios/Runner/main.m',
        'example/lib/main.dart',
        'ios/Classes/FlutterProjectPlugin.h',
        'ios/Classes/FlutterProjectPlugin.m',
        'ios/Runner/AppDelegate.h',
        'ios/Runner/AppDelegate.m',
        'ios/Runner/main.m',
        'lib/main.dart',
        'test/widget_test.dart',
      ],
    );
    return _runFlutterTest(projectDir);
528
  }, overrides: <Type, Generator>{
529 530 531 532 533 534 535
    Pub: () => Pub(
      fileSystem: globals.fs,
      logger: globals.logger,
      processManager: globals.processManager,
      usage: globals.flutterUsage,
      botDetector: globals.botDetector,
      platform: globals.platform,
536
      stdio: mockStdio,
537
    ),
538
  });
539

540
  testUsingContext('can create a plugin project', () async {
541 542
    await _createAndAnalyzeProject(
      projectDir,
543
      <String>['--template=plugin', '-i', 'objc', '-a', 'java'],
544
      <String>[
545
        'analysis_options.yaml',
546 547
        'LICENSE',
        'README.md',
548 549 550 551
        'example/lib/main.dart',
        'flutter_project.iml',
        'lib/flutter_project.dart',
      ],
552
      unexpectedPaths: <String>[
553 554
        'android/src/main/java/com/example/flutter_project/FlutterProjectPlugin.java',
        'example/android/app/src/main/java/com/example/flutter_project_example/MainActivity.java',
555 556
        'lib/flutter_project_web.dart',
      ],
557 558
    );
    return _runFlutterTest(projectDir.childDirectory('example'));
559
  }, overrides: <Type, Generator>{
560 561 562 563 564 565 566
    Pub: () => Pub(
      fileSystem: globals.fs,
      logger: globals.logger,
      processManager: globals.processManager,
      usage: globals.flutterUsage,
      botDetector: globals.botDetector,
      platform: globals.platform,
567
      stdio: mockStdio,
568
    ),
569
  });
570

571 572 573
  testUsingContext('plugin project supports web', () async {
    await _createAndAnalyzeProject(
      projectDir,
574
      <String>['--template=plugin', '--platform=web'],
575 576 577 578 579 580 581 582 583 584
      <String>[
        'lib/flutter_project.dart',
        'lib/flutter_project_web.dart',
      ],
    );
    final String rawPubspec = await projectDir.childFile('pubspec.yaml').readAsString();
    final Pubspec pubspec = Pubspec.parse(rawPubspec);
    // Expect the dependency on flutter_web_plugins exists
    expect(pubspec.dependencies, contains('flutter_web_plugins'));
    // The platform is correctly registered
585
    final YamlMap web = ((pubspec.flutter!['plugin'] as YamlMap)['platforms'] as YamlMap)['web'] as YamlMap;
586 587
    expect(web['pluginClass'], 'FlutterProjectWeb');
    expect(web['fileName'], 'flutter_project_web.dart');
588
    expect(logger.errorText, isNot(contains(_kNoPlatformsMessage)));
589 590 591 592 593 594 595 596 597
  }, overrides: <Type, Generator>{
    FeatureFlags: () => TestFeatureFlags(isWebEnabled: true),
    Pub: () => Pub(
      fileSystem: globals.fs,
      logger: globals.logger,
      processManager: globals.processManager,
      usage: globals.flutterUsage,
      botDetector: globals.botDetector,
      platform: globals.platform,
598
      stdio: mockStdio,
599
    ),
600
    Logger: ()=>logger,
601 602
  });

603 604 605 606 607 608 609 610 611 612 613 614 615
  testUsingContext('plugin example app depends on plugin', () async {
    await _createProject(
      projectDir,
      <String>['--template=plugin', '-i', 'objc', '-a', 'java'],
      <String>[
        'example/pubspec.yaml',
      ],
    );
    final String rawPubspec = await projectDir.childDirectory('example').childFile('pubspec.yaml').readAsString();
    final Pubspec pubspec = Pubspec.parse(rawPubspec);
    final String pluginName = projectDir.basename;
    expect(pubspec.dependencies, contains(pluginName));
    expect(pubspec.dependencies[pluginName] is PathDependency, isTrue);
616
    final PathDependency pathDependency = pubspec.dependencies[pluginName]! as PathDependency;
617 618
    expect(pathDependency.path, '../');
  }, overrides: <Type, Generator>{
619 620 621 622 623 624 625
    Pub: () => Pub(
      fileSystem: globals.fs,
      logger: globals.logger,
      processManager: globals.processManager,
      usage: globals.flutterUsage,
      botDetector: globals.botDetector,
      platform: globals.platform,
626
      stdio: mockStdio,
627
    ),
628 629
  });

630 631 632
  testUsingContext('kotlin/swift plugin project', () async {
    return _createProject(
      projectDir,
633
      <String>['--no-pub', '--template=plugin', '-a', 'kotlin', '--ios-language', 'swift', '--platforms', 'ios,android'],
634
      <String>[
635
        'analysis_options.yaml',
636 637
        'android/src/main/kotlin/com/example/flutter_project/FlutterProjectPlugin.kt',
        'example/android/app/src/main/kotlin/com/example/flutter_project_example/MainActivity.kt',
638 639 640 641 642 643 644 645 646
        'example/ios/Runner/AppDelegate.swift',
        'example/ios/Runner/Runner-Bridging-Header.h',
        'example/lib/main.dart',
        'ios/Classes/FlutterProjectPlugin.h',
        'ios/Classes/FlutterProjectPlugin.m',
        'ios/Classes/SwiftFlutterProjectPlugin.swift',
        'lib/flutter_project.dart',
      ],
      unexpectedPaths: <String>[
647 648
        'android/src/main/java/com/example/flutter_project/FlutterProjectPlugin.java',
        'example/android/app/src/main/java/com/example/flutter_project_example/MainActivity.java',
649 650 651 652 653
        'example/ios/Runner/AppDelegate.h',
        'example/ios/Runner/AppDelegate.m',
        'example/ios/Runner/main.m',
      ],
    );
654
  });
655 656 657 658 659

  testUsingContext('plugin project with custom org', () async {
    return _createProject(
      projectDir,
      <String>[
660 661 662 663 664
        '--no-pub',
        '--template=plugin',
        '--org', 'com.bar.foo',
        '-i', 'objc',
        '-a', 'java',
665
        '--platform', 'android',
666
      ], <String>[
667 668
        'android/src/main/java/com/bar/foo/flutter_project/FlutterProjectPlugin.java',
        'example/android/app/src/main/java/com/bar/foo/flutter_project_example/MainActivity.java',
669 670
      ],
      unexpectedPaths: <String>[
671 672
        'android/src/main/java/com/example/flutter_project/FlutterProjectPlugin.java',
        'example/android/app/src/main/java/com/example/flutter_project_example/MainActivity.java',
673 674
      ],
    );
675
  });
676

677 678 679 680
  testUsingContext('plugin project with valid custom project name', () async {
    return _createProject(
      projectDir,
      <String>[
681 682 683 684 685
        '--no-pub',
        '--template=plugin',
        '--project-name', 'xyz',
        '-i', 'objc',
        '-a', 'java',
686
        '--platforms', 'android,ios',
687
      ], <String>[
688
        'android/src/main/java/com/example/xyz/XyzPlugin.java',
689
        'example/android/app/src/main/java/com/example/xyz_example/MainActivity.java',
690 691
      ],
      unexpectedPaths: <String>[
692 693
        'android/src/main/java/com/example/flutter_project/FlutterProjectPlugin.java',
        'example/android/app/src/main/java/com/example/flutter_project_example/MainActivity.java',
694 695
      ],
    );
696
  });
697 698 699 700

  testUsingContext('plugin project with invalid custom project name', () async {
    expect(
      () => _createProject(projectDir,
701
        <String>['--no-pub', '--template=plugin', '--project-name', 'xyz.xyz', '--platforms', 'android,ios',],
702 703 704 705
        <String>[],
      ),
      throwsToolExit(message: '"xyz.xyz" is not a valid Dart package name.'),
    );
706
  });
707

708
  testUsingContext('module project with pub', () async {
709
    return _createProject(projectDir, <String>[
710
      '--template=module',
711 712 713
    ], <String>[
      '.android/build.gradle',
      '.android/Flutter/build.gradle',
714
      '.android/Flutter/flutter.iml',
715 716 717 718 719 720 721 722 723 724 725 726
      '.android/Flutter/src/main/AndroidManifest.xml',
      '.android/Flutter/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java',
      '.android/gradle.properties',
      '.android/gradle/wrapper/gradle-wrapper.jar',
      '.android/gradle/wrapper/gradle-wrapper.properties',
      '.android/gradlew',
      '.android/gradlew.bat',
      '.android/include_flutter.groovy',
      '.android/local.properties',
      '.android/settings.gradle',
      '.gitignore',
      '.metadata',
727
      '.dart_tool/package_config.json',
728
      'analysis_options.yaml',
729 730 731 732 733 734 735 736
      'lib/main.dart',
      'pubspec.lock',
      'pubspec.yaml',
      'README.md',
      'test/widget_test.dart',
    ], unexpectedPaths: <String>[
      'android/',
      'ios/',
737 738
      '.android/Flutter/src/main/java/io/flutter/facade/FlutterFragment.java',
      '.android/Flutter/src/main/java/io/flutter/facade/Flutter.java',
739
    ]);
740
  }, overrides: <Type, Generator>{
741 742 743 744 745 746 747
    Pub: () => Pub(
      fileSystem: globals.fs,
      logger: globals.logger,
      processManager: globals.processManager,
      usage: globals.flutterUsage,
      botDetector: globals.botDetector,
      platform: globals.platform,
748
      stdio: mockStdio,
749
    ),
750
  });
751

752

753
  testUsingContext('androidx is used by default in an app project', () async {
754 755 756 757 758
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

759
    await runner.run(<String>['create', '--no-pub', projectDir.path]);
760 761

    void expectExists(String relPath) {
762
      expect(globals.fs.isFileSync('${projectDir.path}/$relPath'), true);
763 764 765 766
    }

    expectExists('android/gradle.properties');

767
    final String actualContents = await globals.fs.file('${projectDir.path}/android/gradle.properties').readAsString();
768 769

    expect(actualContents.contains('useAndroidX'), true);
770
  });
771

772
  testUsingContext('androidx is used by default in a module project', () async {
773 774 775 776 777
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

778
    await runner.run(<String>['create', '--template=module', '--no-pub', projectDir.path]);
779 780 781 782 783 784

    final FlutterProject project = FlutterProject.fromDirectory(projectDir);
    expect(
      project.usesAndroidX,
      true,
    );
785
  });
786

787 788 789 790 791 792
  testUsingContext('creating a new project should create v2 embedding and never show an Android v1 deprecation warning', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

793
    await runner.run(<String>['create', '--no-pub', '--platform', 'android', projectDir.path]);
794 795

    final String androidManifest = await globals.fs.file(
796
      '${projectDir.path}/android/app/src/main/AndroidManifest.xml'
797 798 799 800 801
    ).readAsString();
    expect(androidManifest.contains('android:name="flutterEmbedding"'), true);
    expect(androidManifest.contains('android:value="2"'), true);

    final String mainActivity = await globals.fs.file(
802
      '${projectDir.path}/android/app/src/main/kotlin/com/example/flutter_project/MainActivity.kt'
803 804 805 806
    ).readAsString();
    // Import for the new embedding class.
    expect(mainActivity.contains('import io.flutter.embedding.android.FlutterActivity'), true);

807
    expect(logger.statusText, isNot(contains('https://github.com/flutter/flutter/wiki/Upgrading-pre-1.12-Android-projects')));
808 809
  }, overrides: <Type, Generator>{
    Logger: () => logger,
810 811
  });

812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849
  testUsingContext('app supports android and ios by default', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

    await runner.run(<String>['create', '--no-pub', projectDir.path]);

    expect(projectDir.childDirectory('android'), exists);
    expect(projectDir.childDirectory('ios'), exists);
  }, overrides: <Type, Generator>{});

  testUsingContext('app does not include android if disabled in config', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

    await runner.run(<String>['create', '--no-pub', projectDir.path]);

    expect(projectDir.childDirectory('android'), isNot(exists));
  }, overrides: <Type, Generator>{
    FeatureFlags: () => TestFeatureFlags(isAndroidEnabled: false),
  });

  testUsingContext('app does not include ios if disabled in config', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

    await runner.run(<String>['create', '--no-pub', projectDir.path]);

    expect(projectDir.childDirectory('ios'), isNot(exists));
  }, overrides: <Type, Generator>{
    FeatureFlags: () => TestFeatureFlags(isIOSEnabled: false),
  });

850
  testUsingContext('app does not include desktop or web by default', () async {
851 852 853 854 855
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

856
    await runner.run(<String>['create', '--no-pub', projectDir.path]);
857

858 859 860 861
    expect(projectDir.childDirectory('linux'), isNot(exists));
    expect(projectDir.childDirectory('macos'), isNot(exists));
    expect(projectDir.childDirectory('windows'), isNot(exists));
    expect(projectDir.childDirectory('web'), isNot(exists));
862
  }, overrides: <Type, Generator>{
863
    FeatureFlags: () => TestFeatureFlags(),
864 865
  });

866 867
  testUsingContext('plugin does not include desktop or web by default',
      () async {
868 869 870 871 872
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

873 874 875 876 877 878 879
    await runner.run(
        <String>['create', '--no-pub', '--template=plugin', projectDir.path]);

    expect(projectDir.childDirectory('linux'), isNot(exists));
    expect(projectDir.childDirectory('macos'), isNot(exists));
    expect(projectDir.childDirectory('windows'), isNot(exists));
    expect(projectDir.childDirectory('web'), isNot(exists));
880 881 882 883
    expect(projectDir.childDirectory('example').childDirectory('linux'), isNot(exists));
    expect(projectDir.childDirectory('example').childDirectory('macos'), isNot(exists));
    expect(projectDir.childDirectory('example').childDirectory('windows'), isNot(exists));
    expect(projectDir.childDirectory('example').childDirectory('web'), isNot(exists));
884
  }, overrides: <Type, Generator>{
885
    FeatureFlags: () => TestFeatureFlags(),
886 887
  });

888
  testUsingContext('app supports Linux if requested', () async {
889 890 891 892 893
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

894 895 896
    await runner.run(<String>[
      'create',
      '--no-pub',
897
      '--platform=linux',
898 899
      projectDir.path,
    ]);
900

901
    expect(projectDir.childDirectory('linux').childFile('CMakeLists.txt'), exists);
902 903 904 905 906
    expect(projectDir.childDirectory('android'), isNot(exists));
    expect(projectDir.childDirectory('ios'), isNot(exists));
    expect(projectDir.childDirectory('windows'), isNot(exists));
    expect(projectDir.childDirectory('macos'), isNot(exists));
    expect(projectDir.childDirectory('web'), isNot(exists));
907
    expect(logger.errorText, isNot(contains(_kNoPlatformsMessage)));
908 909
  }, overrides: <Type, Generator>{
    FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
910
    Logger: () => logger,
911 912
  });

913
  testUsingContext('plugin supports Linux if requested', () async {
914 915 916 917 918
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

919
    await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platform=linux', projectDir.path]);
920

921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941
    expect(
        projectDir.childDirectory('linux').childFile('CMakeLists.txt'), exists);
    expect(
        projectDir.childDirectory('example').childDirectory('linux'), exists);
    expect(projectDir.childDirectory('example').childDirectory('android'),
        isNot(exists));
    expect(projectDir.childDirectory('example').childDirectory('ios'),
        isNot(exists));
    expect(projectDir.childDirectory('example').childDirectory('windows'),
        isNot(exists));
    expect(projectDir.childDirectory('example').childDirectory('macos'),
        isNot(exists));
    expect(projectDir.childDirectory('example').childDirectory('web'),
        isNot(exists));
    validatePubspecForPlugin(
        projectDir: projectDir.absolute.path,
        expectedPlatforms: const <String>[
          'linux',
        ],
        pluginClass: 'FlutterProjectPlugin',
    unexpectedPlatforms: <String>['some_platform']);
942
    expect(logger.errorText, isNot(contains(_kNoPlatformsMessage)));
943
  }, overrides: <Type, Generator>{
944
    FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
945
    Logger: () => logger,
946
  });
947

948
  testUsingContext('app supports macOS if requested', () async {
949 950 951 952 953
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

954 955 956
    await runner.run(<String>[
      'create',
      '--no-pub',
957
      '--platform=macos',
958 959
      projectDir.path,
    ]);
960

961 962 963 964 965 966 967 968
    expect(
        projectDir.childDirectory('macos').childDirectory('Runner.xcworkspace'),
        exists);
    expect(projectDir.childDirectory('android'), isNot(exists));
    expect(projectDir.childDirectory('ios'), isNot(exists));
    expect(projectDir.childDirectory('linux'), isNot(exists));
    expect(projectDir.childDirectory('windows'), isNot(exists));
    expect(projectDir.childDirectory('web'), isNot(exists));
969
    expect(logger.errorText, isNot(contains(_kNoPlatformsMessage)));
970
  }, overrides: <Type, Generator>{
971
    FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true),
972
    Logger: () => logger,
973
  });
974 975 976 977 978 979 980

  testUsingContext('plugin supports macOS if requested', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

981
    await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platform=macos', projectDir.path]);
982

983 984 985 986 987 988 989 990 991 992 993 994 995 996
    expect(projectDir.childDirectory('macos').childFile('flutter_project.podspec'),
        exists);
    expect(
        projectDir.childDirectory('example').childDirectory('macos'), exists);
    expect(projectDir.childDirectory('example').childDirectory('linux'),
        isNot(exists));
    expect(projectDir.childDirectory('example').childDirectory('android'),
        isNot(exists));
    expect(projectDir.childDirectory('example').childDirectory('ios'),
        isNot(exists));
    expect(projectDir.childDirectory('example').childDirectory('windows'),
        isNot(exists));
    expect(projectDir.childDirectory('example').childDirectory('web'),
        isNot(exists));
997 998 999 1000
    validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: const <String>[
      'macos',
    ], pluginClass: 'FlutterProjectPlugin',
    unexpectedPlatforms: <String>['some_platform']);
1001
    expect(logger.errorText, isNot(contains(_kNoPlatformsMessage)));
1002 1003
  }, overrides: <Type, Generator>{
    FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true),
1004
    Logger: () => logger,
1005
  });
1006

1007 1008 1009 1010 1011 1012
  testUsingContext('app supports Windows if requested', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

1013 1014 1015
    await runner.run(<String>[
      'create',
      '--no-pub',
1016
      '--platform=windows',
1017 1018
      projectDir.path,
    ]);
1019

1020 1021 1022 1023 1024 1025 1026
    expect(projectDir.childDirectory('windows').childFile('CMakeLists.txt'),
        exists);
    expect(projectDir.childDirectory('android'), isNot(exists));
    expect(projectDir.childDirectory('ios'), isNot(exists));
    expect(projectDir.childDirectory('linux'), isNot(exists));
    expect(projectDir.childDirectory('macos'), isNot(exists));
    expect(projectDir.childDirectory('web'), isNot(exists));
1027
    expect(logger.errorText, isNot(contains(_kNoPlatformsMessage)));
1028 1029
  }, overrides: <Type, Generator>{
    FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
1030
    Logger: () => logger,
1031 1032
  });

1033 1034 1035 1036 1037 1038 1039 1040 1041
  testUsingContext('Windows has correct VERSIONINFO', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

    await runner.run(<String>['create', '--no-pub', '--org', 'com.foo.bar', projectDir.path]);

    final File resourceFile = projectDir.childDirectory('windows').childDirectory('runner').childFile('Runner.rc');
1042
    expect(resourceFile, exists);
1043 1044
    final String contents = resourceFile.readAsStringSync();
    expect(contents, contains('"CompanyName", "com.foo.bar"'));
1045
    expect(contents, contains('"FileDescription", "flutter_project"'));
1046 1047 1048 1049 1050
    expect(contents, contains('"ProductName", "flutter_project"'));
  }, overrides: <Type, Generator>{
    FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
  });

1051 1052 1053 1054 1055 1056
  testUsingContext('plugin supports Windows if requested', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

1057
    await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platform=windows', projectDir.path]);
1058

1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083
    expect(projectDir.childDirectory('windows').childFile('CMakeLists.txt'),
        exists);
    expect(
        projectDir.childDirectory('example').childDirectory('windows'), exists);
    expect(
        projectDir
            .childDirectory('example')
            .childDirectory('android'),
        isNot(exists));
    expect(
        projectDir.childDirectory('example').childDirectory('ios'),
        isNot(exists));
    expect(
        projectDir
            .childDirectory('example')
            .childDirectory('linux'),
        isNot(exists));
    expect(
        projectDir
            .childDirectory('example')
            .childDirectory('macos'),
        isNot(exists));
    expect(
        projectDir.childDirectory('example').childDirectory('web'),
        isNot(exists));
1084
    validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: const <String>[
1085
      'windows',
1086
    ], pluginClass: 'FlutterProjectPluginCApi',
1087
    unexpectedPlatforms: <String>['some_platform']);
1088
    expect(logger.errorText, isNot(contains(_kNoPlatformsMessage)));
1089 1090
  }, overrides: <Type, Generator>{
    FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
1091
    Logger: () => logger,
1092 1093
  });

1094
  testUsingContext('app supports web if requested', () async {
1095 1096 1097 1098 1099
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

1100 1101 1102
    await runner.run(<String>[
      'create',
      '--no-pub',
1103
      '--platform=web',
1104 1105
      projectDir.path,
    ]);
1106

1107 1108 1109 1110 1111 1112 1113 1114
    expect(
        projectDir.childDirectory('web').childFile('index.html'),
        exists);
    expect(projectDir.childDirectory('android'), isNot(exists));
    expect(projectDir.childDirectory('ios'), isNot(exists));
    expect(projectDir.childDirectory('linux'), isNot(exists));
    expect(projectDir.childDirectory('macos'), isNot(exists));
    expect(projectDir.childDirectory('windows'), isNot(exists));
1115
    expect(logger.errorText, isNot(contains(_kNoPlatformsMessage)));
1116
  }, overrides: <Type, Generator>{
1117
    FeatureFlags: () => TestFeatureFlags(isWebEnabled: true),
1118
    Logger: () => logger,
1119 1120
  });

1121 1122 1123 1124 1125 1126 1127 1128 1129
  testUsingContext('app creates maskable icons for web', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

    await runner.run(<String>[
      'create',
      '--no-pub',
1130
      '--platform=web',
1131 1132 1133 1134 1135 1136 1137 1138 1139
      projectDir.path,
    ]);

    final Directory iconsDir = projectDir.childDirectory('web').childDirectory('icons');

    expect(iconsDir.childFile('Icon-maskable-192.png'), exists);
    expect(iconsDir.childFile('Icon-maskable-512.png'), exists);
  });

1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152
  testUsingContext('plugin uses new platform schema', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

    await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);

    final String pubspecContents = await globals.fs.directory(projectDir.path).childFile('pubspec.yaml').readAsString();

    expect(pubspecContents.contains('platforms:'), true);
  });

1153
  testUsingContext('has correct content and formatting with module template', () async {
1154 1155 1156
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
1157
    final CommandRunner<void> runner = createTestCommandRunner(command);
1158

1159
    await runner.run(<String>['create', '--template=module', '--no-pub', '--org', 'com.foo.bar', projectDir.path]);
1160

1161
    void expectExists(String relPath, [bool expectation = true]) {
1162
      expect(globals.fs.isFileSync('${projectDir.path}/$relPath'), expectation);
1163 1164 1165 1166 1167
    }

    expectExists('lib/main.dart');
    expectExists('test/widget_test.dart');

1168
    final String actualContents = await globals.fs.file('${projectDir.path}/test/widget_test.dart').readAsString();
jslavitz's avatar
jslavitz committed
1169 1170 1171

    expect(actualContents.contains('flutter_test.dart'), true);

1172
    for (final FileSystemEntity file in projectDir.listSync(recursive: true)) {
1173 1174 1175 1176
      if (file is File && file.path.endsWith('.dart')) {
        final String original = file.readAsStringSync();

        final Process process = await Process.start(
1177
          globals.artifacts!.getHostArtifact(HostArtifact.engineDartBinary).path,
1178
          <String>['format', '--output=show', file.path],
1179 1180 1181 1182
          workingDirectory: projectDir.path,
        );
        final String formatted = await process.stdout.transform(utf8.decoder).join();

1183
        expect(formatted, contains(original), reason: file.path);
1184
      }
1185 1186
    }

1187
    await _runFlutterTest(projectDir, target: globals.fs.path.join(projectDir.path, 'test', 'widget_test.dart'));
1188 1189

    // Generated Xcode settings
1190
    final String xcodeConfigPath = globals.fs.path.join('.ios', 'Flutter', 'Generated.xcconfig');
1191
    expectExists(xcodeConfigPath);
1192
    final File xcodeConfigFile = globals.fs.file(globals.fs.path.join(projectDir.path, xcodeConfigPath));
1193 1194 1195 1196
    final String xcodeConfig = xcodeConfigFile.readAsStringSync();
    expect(xcodeConfig, contains('FLUTTER_ROOT='));
    expect(xcodeConfig, contains('FLUTTER_APPLICATION_PATH='));
    expect(xcodeConfig, contains('FLUTTER_TARGET='));
1197
    expect(xcodeConfig, contains('COCOAPODS_PARALLEL_CODE_SIGN=true'));
1198
    expect(xcodeConfig, contains('EXCLUDED_ARCHS[sdk=iphoneos*]=armv7'));
1199 1200
    // Avoid legacy build locations to support Swift Package Manager.
    expect(xcodeConfig, isNot(contains('SYMROOT')));
1201 1202

    // Generated export environment variables script
1203
    final String buildPhaseScriptPath = globals.fs.path.join('.ios', 'Flutter', 'flutter_export_environment.sh');
1204
    expectExists(buildPhaseScriptPath);
1205
    final File buildPhaseScriptFile = globals.fs.file(globals.fs.path.join(projectDir.path, buildPhaseScriptPath));
1206 1207 1208 1209
    final String buildPhaseScript = buildPhaseScriptFile.readAsStringSync();
    expect(buildPhaseScript, contains('FLUTTER_ROOT='));
    expect(buildPhaseScript, contains('FLUTTER_APPLICATION_PATH='));
    expect(buildPhaseScript, contains('FLUTTER_TARGET='));
1210
    expect(buildPhaseScript, contains('COCOAPODS_PARALLEL_CODE_SIGN=true'));
1211 1212
    // Do not override host app build settings.
    expect(buildPhaseScript, isNot(contains('SYMROOT')));
1213

1214
    // App identification
1215
    final String xcodeProjectPath = globals.fs.path.join('.ios', 'Runner.xcodeproj', 'project.pbxproj');
1216
    expectExists(xcodeProjectPath);
1217
    final File xcodeProjectFile = globals.fs.file(globals.fs.path.join(projectDir.path, xcodeProjectPath));
1218 1219
    final String xcodeProject = xcodeProjectFile.readAsStringSync();
    expect(xcodeProject, contains('PRODUCT_BUNDLE_IDENTIFIER = com.foo.bar.flutterProject'));
1220
    expect(xcodeProject, contains('LastUpgradeCheck = 1300;'));
1221 1222 1223 1224 1225 1226 1227 1228 1229
    // Xcode workspace shared data
    final Directory workspaceSharedData = globals.fs.directory(globals.fs.path.join('.ios', 'Runner.xcworkspace', 'xcshareddata'));
    expectExists(workspaceSharedData.childFile('WorkspaceSettings.xcsettings').path);
    expectExists(workspaceSharedData.childFile('IDEWorkspaceChecks.plist').path);
    // Xcode project shared data
    final Directory projectSharedData = globals.fs.directory(globals.fs.path.join('.ios', 'Runner.xcodeproj', 'project.xcworkspace', 'xcshareddata'));
    expectExists(projectSharedData.childFile('WorkspaceSettings.xcsettings').path);
    expectExists(projectSharedData.childFile('IDEWorkspaceChecks.plist').path);

1230

1231
    final String versionPath = globals.fs.path.join('.metadata');
1232
    expectExists(versionPath);
1233
    final String version = globals.fs.file(globals.fs.path.join(projectDir.path, versionPath)).readAsStringSync();
1234 1235 1236 1237 1238
    expect(version, contains('version:'));
    expect(version, contains('revision: 12345678'));
    expect(version, contains('channel: omega'));

    // IntelliJ metadata
1239
    final String intelliJSdkMetadataPath = globals.fs.path.join('.idea', 'libraries', 'Dart_SDK.xml');
1240
    expectExists(intelliJSdkMetadataPath);
1241 1242
    final String sdkMetaContents = globals.fs
        .file(globals.fs.path.join(
1243 1244 1245 1246 1247 1248 1249
          projectDir.path,
          intelliJSdkMetadataPath,
        ))
        .readAsStringSync();
    expect(sdkMetaContents, contains('<root url="file:/'));
    expect(sdkMetaContents, contains('/bin/cache/dart-sdk/lib/core"'));
  }, overrides: <Type, Generator>{
1250
    FlutterVersion: () => fakeFlutterVersion,
1251
    Platform: _kNoColorTerminalPlatform,
1252
  });
1253

1254
  testUsingContext('has correct default content and formatting with app template', () async {
1255 1256 1257
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
1258
    final CommandRunner<void> runner = createTestCommandRunner(command);
1259 1260 1261 1262

    await runner.run(<String>['create', '--template=app', '--no-pub', '--org', 'com.foo.bar', projectDir.path]);

    void expectExists(String relPath) {
1263
      expect(globals.fs.isFileSync('${projectDir.path}/$relPath'), true);
1264 1265 1266 1267 1268
    }

    expectExists('lib/main.dart');
    expectExists('test/widget_test.dart');

1269
    for (final FileSystemEntity file in projectDir.listSync(recursive: true)) {
1270 1271 1272 1273
      if (file is File && file.path.endsWith('.dart')) {
        final String original = file.readAsStringSync();

        final Process process = await Process.start(
1274
          globals.artifacts!.getHostArtifact(HostArtifact.engineDartBinary).path,
1275
          <String>['format', '--output=show', file.path],
1276 1277 1278 1279
          workingDirectory: projectDir.path,
        );
        final String formatted = await process.stdout.transform(utf8.decoder).join();

1280
        expect(formatted, contains(original), reason: file.path);
1281 1282
      }
    }
1283

1284
    await _runFlutterTest(projectDir, target: globals.fs.path.join(projectDir.path, 'test', 'widget_test.dart'));
1285

1286
    // Generated Xcode settings
1287
    final String xcodeConfigPath = globals.fs.path.join('ios', 'Flutter', 'Generated.xcconfig');
1288
    expectExists(xcodeConfigPath);
1289
    final File xcodeConfigFile = globals.fs.file(globals.fs.path.join(projectDir.path, xcodeConfigPath));
1290 1291 1292
    final String xcodeConfig = xcodeConfigFile.readAsStringSync();
    expect(xcodeConfig, contains('FLUTTER_ROOT='));
    expect(xcodeConfig, contains('FLUTTER_APPLICATION_PATH='));
1293
    expect(xcodeConfig, contains('COCOAPODS_PARALLEL_CODE_SIGN=true'));
1294
    expect(xcodeConfig, contains('EXCLUDED_ARCHS[sdk=iphoneos*]=armv7'));
1295
    // Xcode project
1296
    final String xcodeProjectPath = globals.fs.path.join('ios', 'Runner.xcodeproj', 'project.pbxproj');
1297
    expectExists(xcodeProjectPath);
1298
    final File xcodeProjectFile = globals.fs.file(globals.fs.path.join(projectDir.path, xcodeProjectPath));
1299 1300
    final String xcodeProject = xcodeProjectFile.readAsStringSync();
    expect(xcodeProject, contains('PRODUCT_BUNDLE_IDENTIFIER = com.foo.bar.flutterProject'));
1301
    expect(xcodeProject, contains('LastUpgradeCheck = 1300;'));
1302 1303 1304 1305 1306 1307 1308 1309
    // Xcode workspace shared data
    final Directory workspaceSharedData = globals.fs.directory(globals.fs.path.join('ios', 'Runner.xcworkspace', 'xcshareddata'));
    expectExists(workspaceSharedData.childFile('WorkspaceSettings.xcsettings').path);
    expectExists(workspaceSharedData.childFile('IDEWorkspaceChecks.plist').path);
    // Xcode project shared data
    final Directory projectSharedData = globals.fs.directory(globals.fs.path.join('ios', 'Runner.xcodeproj', 'project.xcworkspace', 'xcshareddata'));
    expectExists(projectSharedData.childFile('WorkspaceSettings.xcsettings').path);
    expectExists(projectSharedData.childFile('IDEWorkspaceChecks.plist').path);
1310

1311
    final String versionPath = globals.fs.path.join('.metadata');
1312
    expectExists(versionPath);
1313
    final String version = globals.fs.file(globals.fs.path.join(projectDir.path, versionPath)).readAsStringSync();
1314 1315 1316
    expect(version, contains('version:'));
    expect(version, contains('revision: 12345678'));
    expect(version, contains('channel: omega'));
1317

1318
    // IntelliJ metadata
1319
    final String intelliJSdkMetadataPath = globals.fs.path.join('.idea', 'libraries', 'Dart_SDK.xml');
1320
    expectExists(intelliJSdkMetadataPath);
1321 1322
    final String sdkMetaContents = globals.fs
        .file(globals.fs.path.join(
1323 1324 1325 1326 1327 1328 1329
          projectDir.path,
          intelliJSdkMetadataPath,
        ))
        .readAsStringSync();
    expect(sdkMetaContents, contains('<root url="file:/'));
    expect(sdkMetaContents, contains('/bin/cache/dart-sdk/lib/core"'));
  }, overrides: <Type, Generator>{
1330
    FlutterVersion: () => fakeFlutterVersion,
1331
    Platform: _kNoColorTerminalPlatform,
1332
  });
1333

1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360
  testUsingContext('has iOS development team with app template', () async {
    Cache.flutterRoot = '../..';

    final Completer<void> completer = Completer<void>();
    final StreamController<List<int>> controller = StreamController<List<int>>();
    const String certificates = '''
1) 86f7e437faa5a7fce15d1ddcb9eaeaea377667b8 "iPhone Developer: Profile 1 (1111AAAA11)"
    1 valid identities found''';
    fakeProcessManager.addCommands(<FakeCommand>[
      const FakeCommand(
        command: <String>['which', 'security'],
      ),
      const FakeCommand(
        command: <String>['which', 'openssl'],
      ),
      const FakeCommand(
        command: <String>['security', 'find-identity', '-p', 'codesigning', '-v'],
        stdout: certificates,
      ),
      const FakeCommand(
        command: <String>['security', 'find-certificate', '-c', '1111AAAA11', '-p'],
        stdout: 'This is a fake certificate',
      ),
      FakeCommand(
        command: const <String>['openssl', 'x509', '-subject'],
        stdin: IOSink(controller.sink),
        stdout: 'subject= /CN=iPhone Developer: Profile 1 (1111AAAA11)/OU=3333CCCC33/O=My Team/C=US',
1361
      ),
1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383
    ]);

    controller.stream.listen((List<int> chunk) {
      completer.complete();
    });

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

    await runner.run(<String>['create', '--template=app', '--no-pub', '--org', 'com.foo.bar', projectDir.path]);

    final String xcodeProjectPath = globals.fs.path.join('ios', 'Runner.xcodeproj', 'project.pbxproj');
    final File xcodeProjectFile = globals.fs.file(globals.fs.path.join(projectDir.path, xcodeProjectPath));
    expect(xcodeProjectFile, exists);
    final String xcodeProject = xcodeProjectFile.readAsStringSync();
    expect(xcodeProject, contains('DEVELOPMENT_TEAM = 3333CCCC33;'));
  }, overrides: <Type, Generator>{
    FlutterVersion: () => fakeFlutterVersion,
    Platform: _kNoColorTerminalMacOSPlatform,
    ProcessManager: () => fakeProcessManager,
  });

1384
  testUsingContext('Correct info.plist key-value pairs for objc iOS project.', () async {
1385 1386 1387 1388 1389 1390 1391 1392 1393 1394
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

    await runner.run(<String>['create', '--template=app', '--no-pub', '--org', 'com.foo.bar','--ios-language=objc', '--project-name=my_project', projectDir.path]);

    final String plistPath = globals.fs.path.join('ios', 'Runner', 'Info.plist');
    final File plistFile = globals.fs.file(globals.fs.path.join(projectDir.path, plistPath));
    expect(plistFile, exists);
1395 1396
    final bool disabled = _getBooleanValueFromPlist(plistFile: plistFile, key: 'CADisableMinimumFrameDurationOnPhone');
    expect(disabled, isTrue);
1397 1398
    final bool indirectInput = _getBooleanValueFromPlist(plistFile: plistFile, key: 'UIApplicationSupportsIndirectInputEvents');
    expect(indirectInput, isTrue);
1399 1400 1401 1402
    final String displayName = _getStringValueFromPlist(plistFile: plistFile, key: 'CFBundleDisplayName');
    expect(displayName, 'My Project');
  });

1403
  testUsingContext('Correct info.plist key-value pairs for objc swift project.', () async {
1404 1405 1406 1407 1408 1409 1410 1411 1412 1413
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

    await runner.run(<String>['create', '--template=app', '--no-pub', '--org', 'com.foo.bar','--ios-language=swift', '--project-name=my_project', projectDir.path]);

    final String plistPath = globals.fs.path.join('ios', 'Runner', 'Info.plist');
    final File plistFile = globals.fs.file(globals.fs.path.join(projectDir.path, plistPath));
    expect(plistFile, exists);
1414 1415
    final bool disabled = _getBooleanValueFromPlist(plistFile: plistFile, key: 'CADisableMinimumFrameDurationOnPhone');
    expect(disabled, isTrue);
1416 1417
    final bool indirectInput = _getBooleanValueFromPlist(plistFile: plistFile, key: 'UIApplicationSupportsIndirectInputEvents');
    expect(indirectInput, isTrue);
1418 1419 1420 1421
    final String displayName = _getStringValueFromPlist(plistFile: plistFile, key: 'CFBundleDisplayName');
    expect(displayName, 'My Project');
  });

1422
  testUsingContext('Correct info.plist key-value pairs for objc iOS module.', () async {
1423 1424 1425 1426 1427
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

1428
    await runner.run(<String>['create', '--template=module', '--org', 'com.foo.bar','--ios-language=objc', '--project-name=my_project', projectDir.path]);
1429 1430 1431 1432

    final String plistPath = globals.fs.path.join('.ios', 'Runner', 'Info.plist');
    final File plistFile = globals.fs.file(globals.fs.path.join(projectDir.path, plistPath));
    expect(plistFile, exists);
1433 1434
    final bool disabled = _getBooleanValueFromPlist(plistFile: plistFile, key: 'CADisableMinimumFrameDurationOnPhone');
    expect(disabled, isTrue);
1435 1436
    final bool indirectInput = _getBooleanValueFromPlist(plistFile: plistFile, key: 'UIApplicationSupportsIndirectInputEvents');
    expect(indirectInput, isTrue);
1437 1438 1439 1440 1441 1442 1443 1444 1445 1446
    final String displayName = _getStringValueFromPlist(plistFile: plistFile, key: 'CFBundleDisplayName');
    expect(displayName, 'My Project');
  }, overrides: <Type, Generator>{
    Pub: () => Pub(
      fileSystem: globals.fs,
      logger: globals.logger,
      processManager: globals.processManager,
      usage: globals.flutterUsage,
      botDetector: globals.botDetector,
      platform: globals.platform,
1447
      stdio: mockStdio,
1448 1449 1450
    ),
  });

1451
  testUsingContext('Correct info.plist key-value pairs for swift iOS module.', () async {
1452 1453 1454 1455 1456
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

1457
    await runner.run(<String>['create', '--template=module', '--org', 'com.foo.bar','--ios-language=swift', '--project-name=my_project', projectDir.path]);
1458 1459 1460 1461

    final String plistPath = globals.fs.path.join('.ios', 'Runner', 'Info.plist');
    final File plistFile = globals.fs.file(globals.fs.path.join(projectDir.path, plistPath));
    expect(plistFile, exists);
1462 1463
    final bool disabled = _getBooleanValueFromPlist(plistFile: plistFile, key: 'CADisableMinimumFrameDurationOnPhone');
    expect(disabled, isTrue);
1464 1465
    final bool indirectInput = _getBooleanValueFromPlist(plistFile: plistFile, key: 'UIApplicationSupportsIndirectInputEvents');
    expect(indirectInput, isTrue);
1466 1467 1468 1469 1470 1471 1472 1473 1474 1475
    final String displayName = _getStringValueFromPlist(plistFile: plistFile, key: 'CFBundleDisplayName');
    expect(displayName, 'My Project');
  }, overrides: <Type, Generator>{
    Pub: () => Pub(
      fileSystem: globals.fs,
      logger: globals.logger,
      processManager: globals.processManager,
      usage: globals.flutterUsage,
      botDetector: globals.botDetector,
      platform: globals.platform,
1476
      stdio: mockStdio,
1477
    ),
1478
  });
1479

1480
  testUsingContext('Correct info.plist key-value pairs for swift iOS plugin.', () async {
1481 1482 1483 1484 1485 1486 1487 1488 1489 1490
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

    await runner.run(<String>['create', '--template=plugin', '--no-pub', '--org', 'com.foo.bar', '--platforms=ios', '--ios-language=swift', '--project-name=my_project', projectDir.path]);

    final String plistPath = globals.fs.path.join('example', 'ios', 'Runner', 'Info.plist');
    final File plistFile = globals.fs.file(globals.fs.path.join(projectDir.path, plistPath));
    expect(plistFile, exists);
1491 1492
    final bool disabled = _getBooleanValueFromPlist(plistFile: plistFile, key: 'CADisableMinimumFrameDurationOnPhone');
    expect(disabled, isTrue);
1493 1494
    final bool indirectInput = _getBooleanValueFromPlist(plistFile: plistFile, key: 'UIApplicationSupportsIndirectInputEvents');
    expect(indirectInput, isTrue);
1495 1496 1497 1498
    final String displayName = _getStringValueFromPlist(plistFile: plistFile, key: 'CFBundleDisplayName');
    expect(displayName, 'My Project');
  });

1499
  testUsingContext('Correct info.plist key-value pairs for objc iOS plugin.', () async {
1500 1501 1502 1503 1504 1505 1506 1507 1508 1509
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

    await runner.run(<String>['create', '--template=plugin', '--no-pub', '--org', 'com.foo.bar', '--platforms=ios', '--ios-language=objc', '--project-name=my_project', projectDir.path]);

    final String plistPath = globals.fs.path.join('example', 'ios', 'Runner', 'Info.plist');
    final File plistFile = globals.fs.file(globals.fs.path.join(projectDir.path, plistPath));
    expect(plistFile, exists);
1510 1511
    final bool disabled = _getBooleanValueFromPlist(plistFile: plistFile, key: 'CADisableMinimumFrameDurationOnPhone');
    expect(disabled, isTrue);
1512 1513
    final bool indirectInput = _getBooleanValueFromPlist(plistFile: plistFile, key: 'UIApplicationSupportsIndirectInputEvents');
    expect(indirectInput, isTrue);
1514 1515 1516 1517
    final String displayName = _getStringValueFromPlist(plistFile: plistFile, key: 'CFBundleDisplayName');
    expect(displayName, 'My Project');
  });

1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557
  testUsingContext('has correct content and formatting with macOS app template', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

    await runner.run(<String>['create', '--template=app', '--platforms=macos', '--no-pub', '--org', 'com.foo.bar', projectDir.path]);

    void expectExists(String relPath) {
      expect(globals.fs.isFileSync('${projectDir.path}/$relPath'), true);
    }

    // Generated Xcode settings
    final String macosXcodeConfigPath = globals.fs.path.join('macos', 'Runner', 'Configs', 'AppInfo.xcconfig');
    expectExists(macosXcodeConfigPath);
    final File macosXcodeConfigFile = globals.fs.file(globals.fs.path.join(projectDir.path, macosXcodeConfigPath));
    final String macosXcodeConfig = macosXcodeConfigFile.readAsStringSync();
    expect(macosXcodeConfig, contains('PRODUCT_NAME = flutter_project'));
    expect(macosXcodeConfig, contains('PRODUCT_BUNDLE_IDENTIFIER = com.foo.bar.flutterProject'));
    expect(macosXcodeConfig, contains('PRODUCT_COPYRIGHT ='));

    // Xcode project
    final String xcodeProjectPath = globals.fs.path.join('macos', 'Runner.xcodeproj', 'project.pbxproj');
    expectExists(xcodeProjectPath);
    final File xcodeProjectFile = globals.fs.file(globals.fs.path.join(projectDir.path, xcodeProjectPath));
    final String xcodeProject = xcodeProjectFile.readAsStringSync();
    expect(xcodeProject, contains('path = "flutter_project.app";'));
    expect(xcodeProject, contains('LastUpgradeCheck = 1300;'));

    // Xcode workspace shared data
    final Directory workspaceSharedData = globals.fs.directory(globals.fs.path.join('macos', 'Runner.xcworkspace', 'xcshareddata'));
    expectExists(workspaceSharedData.childFile('IDEWorkspaceChecks.plist').path);
    // Xcode project shared data
    final Directory projectSharedData = globals.fs.directory(globals.fs.path.join('macos', 'Runner.xcodeproj', 'project.xcworkspace', 'xcshareddata'));
    expectExists(projectSharedData.childFile('IDEWorkspaceChecks.plist').path);
  }, overrides: <Type, Generator>{
    Platform: _kNoColorTerminalPlatform,
    FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true),
  });

1558
  testUsingContext('has correct application id for android, bundle id for ios and application id for Linux', () async {
1559 1560 1561 1562 1563
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

1564
    String tmpProjectDir = globals.fs.path.join(tempDir.path, 'hello_flutter');
1565
    await runner.run(<String>['create', '--template=app', '--no-pub', '--org', 'com.example', tmpProjectDir]);
1566
    FlutterProject project = FlutterProject.fromDirectory(globals.fs.directory(tmpProjectDir));
1567
    expect(
1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581
      await project.ios.productBundleIdentifier(BuildInfo.debug),
      'com.example.helloFlutter',
    );
    expect(
      await project.ios.productBundleIdentifier(BuildInfo.profile),
      'com.example.helloFlutter',
    );
    expect(
      await project.ios.productBundleIdentifier(BuildInfo.release),
      'com.example.helloFlutter',
    );
    expect(
      await project.ios.productBundleIdentifier(null),
      'com.example.helloFlutter',
1582 1583 1584
    );
    expect(
        project.android.applicationId,
1585
        'com.example.hello_flutter',
1586
    );
1587 1588 1589 1590
    expect(
        project.linux.applicationId,
        'com.example.hello_flutter',
    );
1591

1592
    tmpProjectDir = globals.fs.path.join(tempDir.path, 'test_abc');
1593
    await runner.run(<String>['create', '--template=app', '--no-pub', '--org', 'abc^*.1#@', tmpProjectDir]);
1594
    project = FlutterProject.fromDirectory(globals.fs.directory(tmpProjectDir));
1595
    expect(
1596
        await project.ios.productBundleIdentifier(BuildInfo.debug),
1597
        'abc.1.testAbc',
1598 1599 1600
    );
    expect(
        project.android.applicationId,
1601
        'abc.u1.test_abc',
1602 1603
    );

1604
    tmpProjectDir = globals.fs.path.join(tempDir.path, 'flutter_project');
1605
    await runner.run(<String>['create', '--template=app', '--no-pub', '--org', '#+^%', tmpProjectDir]);
1606
    project = FlutterProject.fromDirectory(globals.fs.directory(tmpProjectDir));
1607
    expect(
1608
        await project.ios.productBundleIdentifier(BuildInfo.debug),
1609
        'flutterProject.untitled',
1610 1611 1612
    );
    expect(
        project.android.applicationId,
1613
        'flutter_project.untitled',
1614
    );
1615 1616 1617 1618
    expect(
        project.linux.applicationId,
        'flutter_project.untitled',
    );
1619
  }, overrides: <Type, Generator>{
1620
    FlutterVersion: () => fakeFlutterVersion,
1621
    Platform: _kNoColorTerminalPlatform,
1622
    FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
1623
  });
1624

1625 1626
  testUsingContext('can re-gen default template over existing project', () async {
    Cache.flutterRoot = '../..';
1627

1628
    final CreateCommand command = CreateCommand();
1629
    final CommandRunner<void> runner = createTestCommandRunner(command);
1630

1631
    await runner.run(<String>['create', '--no-pub', projectDir.path]);
1632

1633
    await runner.run(<String>['create', '--no-pub', projectDir.path]);
1634

1635
    final String metadata = globals.fs.file(globals.fs.path.join(projectDir.path, '.metadata')).readAsStringSync();
1636
    expect(LineSplitter.split(metadata), contains('project_type: app'));
1637
  });
1638

1639
  testUsingContext('can re-gen default template over existing app project with no metadta and detect the type', () async {
1640
    Cache.flutterRoot = '../..';
1641

1642
    final CreateCommand command = CreateCommand();
1643
    final CommandRunner<void> runner = createTestCommandRunner(command);
1644 1645 1646 1647

    await runner.run(<String>['create', '--no-pub', '--template=app', projectDir.path]);

    // Remove the .metadata to simulate an older instantiation that didn't generate those.
1648
    globals.fs.file(globals.fs.path.join(projectDir.path, '.metadata')).deleteSync();
1649 1650 1651

    await runner.run(<String>['create', '--no-pub', projectDir.path]);

1652
    final String metadata = globals.fs.file(globals.fs.path.join(projectDir.path, '.metadata')).readAsStringSync();
1653
    expect(LineSplitter.split(metadata), contains('project_type: app'));
1654
  });
1655

1656
  testUsingContext('can re-gen app template over existing app project and detect the type', () async {
1657 1658 1659
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
1660
    final CommandRunner<void> runner = createTestCommandRunner(command);
1661 1662 1663 1664 1665

    await runner.run(<String>['create', '--no-pub', '--template=app', projectDir.path]);

    await runner.run(<String>['create', '--no-pub', projectDir.path]);

1666
    final String metadata = globals.fs.file(globals.fs.path.join(projectDir.path, '.metadata')).readAsStringSync();
1667
    expect(LineSplitter.split(metadata), contains('project_type: app'));
1668
  });
1669

1670 1671 1672 1673 1674 1675 1676 1677 1678 1679
  testUsingContext('can re-gen template over existing module project and detect the type', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

    await runner.run(<String>['create', '--no-pub', '--template=module', projectDir.path]);

    await runner.run(<String>['create', '--no-pub', projectDir.path]);

1680
    final String metadata = globals.fs.file(globals.fs.path.join(projectDir.path, '.metadata')).readAsStringSync();
1681
    expect(LineSplitter.split(metadata), contains('project_type: module'));
1682
  });
1683

1684 1685 1686 1687
  testUsingContext('can re-gen default template over existing plugin project and detect the type', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
1688
    final CommandRunner<void> runner = createTestCommandRunner(command);
1689 1690 1691 1692 1693

    await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);

    await runner.run(<String>['create', '--no-pub', projectDir.path]);

1694
    final String metadata = globals.fs.file(globals.fs.path.join(projectDir.path, '.metadata')).readAsStringSync();
1695
    expect(LineSplitter.split(metadata), contains('project_type: plugin'));
1696
  });
1697 1698 1699 1700 1701

  testUsingContext('can re-gen default template over existing package project and detect the type', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
1702
    final CommandRunner<void> runner = createTestCommandRunner(command);
1703

1704 1705 1706 1707
    await runner.run(<String>['create', '--no-pub', '--template=package', projectDir.path]);

    await runner.run(<String>['create', '--no-pub', projectDir.path]);

1708
    final String metadata = globals.fs.file(globals.fs.path.join(projectDir.path, '.metadata')).readAsStringSync();
1709
    expect(LineSplitter.split(metadata), contains('project_type: package'));
1710
  });
1711

1712
  testUsingContext('can re-gen module .android/ folder, reusing custom org', () async {
1713 1714
    await _createProject(
      projectDir,
1715
      <String>['--template=module', '--org', 'com.bar.foo'],
1716 1717 1718 1719 1720 1721 1722
      <String>[],
    );
    projectDir.childDirectory('.android').deleteSync(recursive: true);
    return _createProject(
      projectDir,
      <String>[],
      <String>[
1723
        '.android/app/src/main/java/com/bar/foo/flutter_project/host/MainActivity.java',
1724 1725
      ],
    );
1726
  }, overrides: <Type, Generator>{
1727 1728 1729 1730 1731 1732 1733
    Pub: () => Pub(
      fileSystem: globals.fs,
      logger: globals.logger,
      processManager: globals.processManager,
      usage: globals.flutterUsage,
      botDetector: globals.botDetector,
      platform: globals.platform,
1734
      stdio: mockStdio,
1735
    ),
1736
  });
1737

1738
  testUsingContext('can re-gen module .ios/ folder, reusing custom org', () async {
1739 1740
    await _createProject(
      projectDir,
1741
      <String>['--template=module', '--org', 'com.bar.foo'],
1742 1743 1744 1745
      <String>[],
    );
    projectDir.childDirectory('.ios').deleteSync(recursive: true);
    await _createProject(projectDir, <String>[], <String>[]);
1746
    final FlutterProject project = FlutterProject.fromDirectory(projectDir);
1747
    expect(
1748
      await project.ios.productBundleIdentifier(BuildInfo.debug),
1749 1750
      'com.bar.foo.flutterProject',
    );
1751
  }, overrides: <Type, Generator>{
1752 1753 1754 1755 1756 1757 1758
    Pub: () => Pub(
      fileSystem: globals.fs,
      logger: globals.logger,
      processManager: globals.processManager,
      usage: globals.flutterUsage,
      botDetector: globals.botDetector,
      platform: globals.platform,
1759
      stdio: mockStdio,
1760
    ),
1761
  });
1762

1763
  testUsingContext('can re-gen app android/ folder, reusing custom org', () async {
1764 1765
    await _createProject(
      projectDir,
1766 1767 1768 1769 1770
      <String>[
        '--no-pub',
        '--template=app',
        '--org', 'com.bar.foo',
        '-i', 'objc',
1771
        '-a', 'java',
1772
      ],
1773 1774 1775 1776 1777
      <String>[],
    );
    projectDir.childDirectory('android').deleteSync(recursive: true);
    return _createProject(
      projectDir,
1778
      <String>['--no-pub', '-i', 'objc', '-a', 'java'],
1779
      <String>[
1780
        'android/app/src/main/java/com/bar/foo/flutter_project/MainActivity.java',
1781 1782
      ],
      unexpectedPaths: <String>[
1783
        'android/app/src/main/java/com/example/flutter_project/MainActivity.java',
1784 1785
      ],
    );
1786
  });
1787

1788
  testUsingContext('can re-gen app ios/ folder, reusing custom org', () async {
1789 1790 1791 1792 1793 1794 1795
    await _createProject(
      projectDir,
      <String>['--no-pub', '--template=app', '--org', 'com.bar.foo'],
      <String>[],
    );
    projectDir.childDirectory('ios').deleteSync(recursive: true);
    await _createProject(projectDir, <String>['--no-pub'], <String>[]);
1796
    final FlutterProject project = FlutterProject.fromDirectory(projectDir);
1797
    expect(
1798
      await project.ios.productBundleIdentifier(BuildInfo.debug),
1799 1800
      'com.bar.foo.flutterProject',
    );
1801
  });
1802 1803 1804 1805

  testUsingContext('can re-gen plugin ios/ and example/ folders, reusing custom org', () async {
    await _createProject(
      projectDir,
1806 1807 1808 1809 1810 1811
      <String>[
        '--no-pub',
        '--template=plugin',
        '--org', 'com.bar.foo',
        '-i', 'objc',
        '-a', 'java',
1812
        '--platforms', 'ios,android',
1813
      ],
1814 1815 1816 1817 1818 1819
      <String>[],
    );
    projectDir.childDirectory('example').deleteSync(recursive: true);
    projectDir.childDirectory('ios').deleteSync(recursive: true);
    await _createProject(
      projectDir,
1820
      <String>['--no-pub', '--template=plugin', '-i', 'objc', '-a', 'java', '--platforms', 'ios,android'],
1821
      <String>[
1822
        'example/android/app/src/main/java/com/bar/foo/flutter_project_example/MainActivity.java',
1823 1824 1825
        'ios/Classes/FlutterProjectPlugin.h',
      ],
      unexpectedPaths: <String>[
1826 1827
        'example/android/app/src/main/java/com/example/flutter_project_example/MainActivity.java',
        'android/src/main/java/com/example/flutter_project/FlutterProjectPlugin.java',
1828 1829
      ],
    );
1830
    final FlutterProject project = FlutterProject.fromDirectory(projectDir);
1831
    expect(
1832
      await project.example.ios.productBundleIdentifier(BuildInfo.debug),
1833 1834
      'com.bar.foo.flutterProjectExample',
    );
1835
  });
1836 1837 1838 1839 1840 1841 1842

  testUsingContext('fails to re-gen without specified org when org is ambiguous', () async {
    await _createProject(
      projectDir,
      <String>['--no-pub', '--template=app', '--org', 'com.bar.foo'],
      <String>[],
    );
1843
    globals.fs.directory(globals.fs.path.join(projectDir.path, 'ios')).deleteSync(recursive: true);
1844 1845 1846 1847 1848 1849 1850 1851 1852
    await _createProject(
      projectDir,
      <String>['--no-pub', '--template=app', '--org', 'com.bar.baz'],
      <String>[],
    );
    expect(
      () => _createProject(projectDir, <String>[], <String>[]),
      throwsToolExit(message: 'Ambiguous organization'),
    );
1853
  });
1854

1855
  testUsingContext('fails when file exists where output directory should be', () async {
1856 1857
    Cache.flutterRoot = '../..';
    final CreateCommand command = CreateCommand();
1858
    final CommandRunner<void> runner = createTestCommandRunner(command);
1859
    final File existingFile = globals.fs.file(globals.fs.path.join(projectDir.path, 'bad'));
1860 1861 1862 1863 1864
    if (!existingFile.existsSync()) {
      existingFile.createSync(recursive: true);
    }
    expect(
      runner.run(<String>['create', existingFile.path]),
1865 1866 1867 1868 1869 1870 1871 1872
      throwsToolExit(message: 'existing file'),
    );
  });

  testUsingContext('fails overwrite when file exists where output directory should be', () async {
    Cache.flutterRoot = '../..';
    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);
1873
    final File existingFile = globals.fs.file(globals.fs.path.join(projectDir.path, 'bad'));
1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884
    if (!existingFile.existsSync()) {
      existingFile.createSync(recursive: true);
    }
    expect(
      runner.run(<String>['create', '--overwrite', existingFile.path]),
      throwsToolExit(message: 'existing file'),
    );
  });

  testUsingContext('overwrites existing directory when requested', () async {
    Cache.flutterRoot = '../..';
1885
    final Directory existingDirectory = globals.fs.directory(globals.fs.path.join(projectDir.path, 'bad'));
1886 1887 1888
    if (!existingDirectory.existsSync()) {
      existingDirectory.createSync(recursive: true);
    }
1889
    final File existingFile = globals.fs.file(globals.fs.path.join(existingDirectory.path, 'lib', 'main.dart'));
1890 1891
    existingFile.createSync(recursive: true);
    await _createProject(
1892
      globals.fs.directory(existingDirectory.path),
1893
      <String>['--overwrite', '-i', 'objc', '-a', 'java'],
1894 1895 1896 1897 1898 1899 1900
      <String>[
        'android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java',
        'lib/main.dart',
        'ios/Flutter/AppFrameworkInfo.plist',
        'ios/Runner/AppDelegate.m',
        'ios/Runner/GeneratedPluginRegistrant.h',
      ],
1901
    );
1902
  }, overrides: <Type, Generator>{
1903 1904 1905 1906 1907 1908 1909
    Pub: () => Pub(
      fileSystem: globals.fs,
      logger: globals.logger,
      processManager: globals.processManager,
      usage: globals.flutterUsage,
      botDetector: globals.botDetector,
      platform: globals.platform,
1910
      stdio: mockStdio,
1911
    ),
1912 1913 1914
  });

  testUsingContext(
1915
    'invokes pub in online and offline modes',
1916
    () async {
1917 1918
      Cache.flutterRoot = '../..';

1919
      final CreateCommand command = CreateCommand();
1920
      final CommandRunner<void> runner = createTestCommandRunner(command);
1921

1922 1923 1924 1925 1926 1927 1928
      // Run pub online first in order to populate the pub cache.
      await runner.run(<String>['create', '--pub', projectDir.path]);
      expect(loggingProcessManager.commands.first, contains(matches(r'dart-sdk[\\/]bin[\\/]dart')));
      expect(loggingProcessManager.commands.first, isNot(contains('--offline')));

      // Run pub offline.
      loggingProcessManager.clear();
1929
      await runner.run(<String>['create', '--pub', '--offline', projectDir.path]);
1930
      expect(loggingProcessManager.commands.first, contains(matches(r'dart-sdk[\\/]bin[\\/]dart')));
1931
      expect(loggingProcessManager.commands.first, contains('--offline'));
1932
    },
1933 1934
    overrides: <Type, Generator>{
      ProcessManager: () => loggingProcessManager,
1935
        Pub: () => Pub(
1936 1937 1938 1939 1940 1941
        fileSystem: globals.fs,
        logger: globals.logger,
        processManager: globals.processManager,
        usage: globals.flutterUsage,
        botDetector: globals.botDetector,
        platform: globals.platform,
1942
      stdio: mockStdio,
1943
      ),
1944 1945
    },
  );
1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960

  testUsingContext('can create a sample-based project', () async {
    await _createAndAnalyzeProject(
      projectDir,
      <String>['--no-pub', '--sample=foo.bar.Baz'],
      <String>[
        'lib/main.dart',
        'flutter_project.iml',
        'android/app/src/main/AndroidManifest.xml',
        'ios/Flutter/AppFrameworkInfo.plist',
      ],
      unexpectedPaths: <String>['test'],
    );
    expect(projectDir.childDirectory('lib').childFile('main.dart').readAsStringSync(),
      contains('void main() {}'));
1961
  }, overrides: <Type, Generator>{
1962 1963 1964 1965 1966 1967
    HttpClientFactory: () {
      return () {
        return FakeHttpClient.list(<FakeRequest>[
          FakeRequest(
            Uri.parse('https://master-api.flutter.dev/snippets/foo.bar.Baz.dart'),
            response: FakeResponse(body: utf8.encode('void main() {}')),
1968
          ),
1969 1970 1971
        ]);
      };
    },
1972
  });
1973

1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984
  testUsingContext('null-safe sample-based project have no analyzer errors', () async {
    await _createAndAnalyzeProject(
      projectDir,
      <String>['--no-pub', '--sample=foo.bar.Baz'],
      <String>['lib/main.dart'],
    );
    expect(
      projectDir.childDirectory('lib').childFile('main.dart').readAsStringSync(),
      contains('String?'), // uses null-safe syntax
    );
  }, overrides: <Type, Generator>{
1985 1986 1987 1988 1989
    HttpClientFactory: () {
      return () {
        return FakeHttpClient.list(<FakeRequest>[
          FakeRequest(
            Uri.parse('https://master-api.flutter.dev/snippets/foo.bar.Baz.dart'),
1990
            response: FakeResponse(body: utf8.encode('void main() { String? foo; print(foo); } // ignore: avoid_print')),
1991
          ),
1992 1993 1994
        ]);
      };
    },
1995 1996
  });

1997
  testUsingContext('can write samples index to disk', () async {
1998
    final String outputFile = globals.fs.path.join(tempDir.path, 'flutter_samples.json');
1999 2000 2001 2002 2003
    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);
    final List<String> args = <String>[
      'create',
      '--list-samples',
2004
      outputFile,
2005 2006 2007
    ];

    await runner.run(args);
2008
    final File expectedFile = globals.fs.file(outputFile);
2009
    expect(expectedFile, exists);
2010 2011
    expect(expectedFile.readAsStringSync(), equals(samplesIndexJson));
  }, overrides: <Type, Generator>{
2012 2013 2014 2015 2016 2017
    HttpClientFactory: () {
      return () {
        return FakeHttpClient.list(<FakeRequest>[
          FakeRequest(
            Uri.parse('https://master-api.flutter.dev/snippets/index.json'),
            response: FakeResponse(body: utf8.encode(samplesIndexJson)),
2018
          ),
2019 2020 2021
        ]);
      };
    },
2022
  });
2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040

  testUsingContext('Throws tool exit on empty samples index', () async {
    final String outputFile = globals.fs.path.join(tempDir.path, 'flutter_samples.json');
    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);
    final List<String> args = <String>[
      'create',
      '--list-samples',
      outputFile,
    ];

    await expectLater(
      runner.run(args),
      throwsToolExit(
        exitCode: 2,
        message: 'Unable to download samples',
    ));
  }, overrides: <Type, Generator>{
2041 2042 2043 2044 2045
    HttpClientFactory: () {
      return () {
        return FakeHttpClient.list(<FakeRequest>[
          FakeRequest(
            Uri.parse('https://master-api.flutter.dev/snippets/index.json'),
2046
          ),
2047 2048 2049
        ]);
      };
    },
2050 2051
  });

2052
  testUsingContext('provides an error to the user if samples json download fails', () async {
2053
    final String outputFile = globals.fs.path.join(tempDir.path, 'flutter_samples.json');
2054 2055 2056 2057 2058
    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);
    final List<String> args = <String>[
      'create',
      '--list-samples',
2059
      outputFile,
2060 2061 2062
    ];

    await expectLater(runner.run(args), throwsToolExit(exitCode: 2, message: 'Failed to write samples'));
2063
    expect(globals.fs.file(outputFile), isNot(exists));
2064
  }, overrides: <Type, Generator>{
2065 2066 2067 2068 2069 2070
    HttpClientFactory: () {
      return () {
        return FakeHttpClient.list(<FakeRequest>[
          FakeRequest(
            Uri.parse('https://master-api.flutter.dev/snippets/index.json'),
            response: const FakeResponse(statusCode: HttpStatus.notFound),
2071
          ),
2072 2073 2074
        ]);
      };
    },
2075
  });
2076 2077 2078 2079 2080 2081 2082 2083 2084

  testUsingContext('plugin does not support any platform by default', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

    await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);

2085 2086 2087 2088 2089 2090
    expect(projectDir.childDirectory('ios'), isNot(exists));
    expect(projectDir.childDirectory('android'), isNot(exists));
    expect(projectDir.childDirectory('web'), isNot(exists));
    expect(projectDir.childDirectory('linux'), isNot(exists));
    expect(projectDir.childDirectory('windows'), isNot(exists));
    expect(projectDir.childDirectory('macos'), isNot(exists));
2091

2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103
    expect(projectDir.childDirectory('example').childDirectory('ios'),
        isNot(exists));
    expect(projectDir.childDirectory('example').childDirectory('android'),
        isNot(exists));
    expect(projectDir.childDirectory('example').childDirectory('web'),
        isNot(exists));
    expect(projectDir.childDirectory('example').childDirectory('linux'),
        isNot(exists));
    expect(projectDir.childDirectory('example').childDirectory('windows'),
        isNot(exists));
    expect(projectDir.childDirectory('example').childDirectory('macos'),
        isNot(exists));
2104
    validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: <String>[
2105
      'some_platform',
2106 2107 2108
    ], pluginClass: 'somePluginClass',
    unexpectedPlatforms: <String>[ 'ios', 'android', 'web', 'linux', 'windows', 'macos']);
  }, overrides: <Type, Generator>{
2109
    FeatureFlags: () => TestFeatureFlags(),
2110 2111
  });

2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158
  testUsingContext('plugin creates platform interface by default', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

    await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);

    expect(projectDir.childDirectory('lib').childFile('flutter_project_method_channel.dart'),
      exists);
    expect(projectDir.childDirectory('lib').childFile('flutter_project_platform_interface.dart'),
      exists);

  }, overrides: <Type, Generator>{
    FeatureFlags: () => TestFeatureFlags(),
  });

  testUsingContext('plugin passes analysis and unit tests', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

    await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);

    await _getPackages(projectDir);
    await _analyzeProject(projectDir.path);
    await _runFlutterTest(projectDir);
  }, overrides: <Type, Generator>{
    FeatureFlags: () => TestFeatureFlags(),
  });

  testUsingContext('plugin example passes analysis and unit tests', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

    await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);

    final Directory exampleDir = projectDir.childDirectory('example');

    await _getPackages(exampleDir);
    await _analyzeProject(exampleDir.path);
    await _runFlutterTest(exampleDir);
  });

2159 2160 2161 2162 2163 2164
  testUsingContext('plugin supports ios if requested', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

2165
    await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platform=ios', projectDir.path]);
2166

2167 2168
    expect(projectDir.childDirectory('ios'), exists);
    expect(projectDir.childDirectory('example').childDirectory('ios'), exists);
2169 2170 2171 2172
    validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: <String>[
      'ios',
    ], pluginClass: 'FlutterProjectPlugin',
    unexpectedPlatforms: <String>['some_platform']);
2173
    expect(logger.errorText, isNot(contains(_kNoPlatformsMessage)));
2174
  }, overrides: <Type, Generator>{
2175
    FeatureFlags: () => TestFeatureFlags(),
2176
    Logger: () => logger,
2177 2178 2179 2180 2181 2182 2183 2184
  });

  testUsingContext('plugin supports android if requested', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

2185
    await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platform=android', projectDir.path]);
2186

2187 2188 2189
    expect(projectDir.childDirectory('android'), exists);
    expect(
        projectDir.childDirectory('example').childDirectory('android'), exists);
2190
    validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: const <String>[
2191
      'android',
2192 2193 2194
    ], pluginClass: 'FlutterProjectPlugin',
    unexpectedPlatforms: <String>['some_platform'],
    androidIdentifier: 'com.example.flutter_project');
2195
    expect(logger.errorText, isNot(contains(_kNoPlatformsMessage)));
2196
  }, overrides: <Type, Generator>{
2197
    FeatureFlags: () => TestFeatureFlags(),
2198
    Logger: () => logger,
2199 2200 2201 2202 2203 2204 2205 2206
  });

  testUsingContext('plugin supports web if requested', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

2207
    await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platform=web', projectDir.path]);
2208 2209 2210
    expect(
        projectDir.childDirectory('lib').childFile('flutter_project_web.dart'),
        exists);
2211
    validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: const <String>[
2212
      'web',
2213 2214 2215 2216
    ], pluginClass: 'FlutterProjectWeb',
    unexpectedPlatforms: <String>['some_platform'],
    androidIdentifier: 'com.example.flutter_project',
    webFileName: 'flutter_project_web.dart');
2217
    expect(logger.errorText, isNot(contains(_kNoPlatformsMessage)));
2218 2219 2220 2221

    await _getPackages(projectDir);
    await _analyzeProject(projectDir.path);
    await _runFlutterTest(projectDir);
2222 2223
  }, overrides: <Type, Generator>{
    FeatureFlags: () => TestFeatureFlags(isWebEnabled: true),
2224
    Logger: () => logger,
2225 2226
  });

2227
  testUsingContext('plugin does not support web if feature is not enabled', () async {
2228 2229 2230 2231 2232
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

2233
    await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platform=web', projectDir.path]);
2234 2235 2236
    expect(
        projectDir.childDirectory('lib').childFile('flutter_project_web.dart'),
        isNot(exists));
2237
    validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: const <String>[
2238
      'some_platform',
2239 2240
    ], pluginClass: 'somePluginClass',
    unexpectedPlatforms: <String>['web']);
2241
    expect(logger.errorText, contains(_kNoPlatformsMessage));
2242
  }, overrides: <Type, Generator>{
2243
    FeatureFlags: () => TestFeatureFlags(),
2244
    Logger: () => logger,
2245 2246 2247 2248 2249 2250 2251 2252
  });

  testUsingContext('create an empty plugin, then add ios', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);
    await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);
2253
    await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platform=ios', projectDir.path]);
2254

2255 2256
    expect(projectDir.childDirectory('ios'), exists);
    expect(projectDir.childDirectory('example').childDirectory('ios'), exists);
2257
  }, overrides: <Type, Generator>{
2258
    FeatureFlags: () => TestFeatureFlags(),
2259 2260 2261 2262 2263 2264 2265 2266
  });

  testUsingContext('create an empty plugin, then add android', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);
    await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);
2267
    await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platform=android', projectDir.path]);
2268

2269 2270 2271
    expect(projectDir.childDirectory('android'), exists);
    expect(
        projectDir.childDirectory('example').childDirectory('android'), exists);
2272
  }, overrides: <Type, Generator>{
2273
    FeatureFlags: () => TestFeatureFlags(),
2274 2275 2276 2277 2278 2279 2280 2281
  });

  testUsingContext('create an empty plugin, then add linux', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);
    await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);
2282
    await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platform=linux', projectDir.path]);
2283

2284 2285 2286
    expect(projectDir.childDirectory('linux'), exists);
    expect(
        projectDir.childDirectory('example').childDirectory('linux'), exists);
2287 2288 2289 2290 2291 2292 2293 2294 2295 2296
  }, overrides: <Type, Generator>{
    FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
  });

  testUsingContext('create an empty plugin, then add macos', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);
    await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);
2297
    await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platform=macos', projectDir.path]);
2298

2299 2300 2301
    expect(projectDir.childDirectory('macos'), exists);
    expect(
        projectDir.childDirectory('example').childDirectory('macos'), exists);
2302 2303 2304 2305 2306 2307 2308 2309 2310 2311
  }, overrides: <Type, Generator>{
    FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true),
  });

  testUsingContext('create an empty plugin, then add windows', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);
    await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);
2312
    await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platform=windows', projectDir.path]);
2313

2314 2315 2316
    expect(projectDir.childDirectory('windows'), exists);
    expect(
        projectDir.childDirectory('example').childDirectory('windows'), exists);
2317 2318 2319 2320 2321 2322 2323 2324 2325 2326
  }, overrides: <Type, Generator>{
    FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
  });

  testUsingContext('create an empty plugin, then add web', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);
    await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);
2327
    await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platform=web', projectDir.path]);
2328

2329 2330 2331
    expect(
        projectDir.childDirectory('lib').childFile('flutter_project_web.dart'),
        exists);
2332 2333 2334 2335 2336 2337 2338 2339 2340
  }, overrides: <Type, Generator>{
    FeatureFlags: () => TestFeatureFlags(isWebEnabled: true),
  });

  testUsingContext('create a plugin with ios, then add macos', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);
2341
    await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platform=ios', projectDir.path]);
2342 2343
    expect(projectDir.childDirectory('ios'), exists);
    expect(projectDir.childDirectory('example').childDirectory('ios'), exists);
2344 2345 2346 2347 2348
    validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: const <String>[
      'ios',
    ], pluginClass: 'FlutterProjectPlugin',
    unexpectedPlatforms: <String>['some_platform']);

2349
    await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platform=macos', projectDir.path]);
2350 2351 2352 2353 2354
    expect(projectDir.childDirectory('macos'), exists);
    expect(
        projectDir.childDirectory('example').childDirectory('macos'), exists);
    expect(projectDir.childDirectory('ios'), exists);
    expect(projectDir.childDirectory('example').childDirectory('ios'), exists);
2355 2356 2357 2358 2359 2360 2361 2362 2363 2364
  }, overrides: <Type, Generator>{
    FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true),
  });

  testUsingContext('create a plugin with ios and android', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);
    await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=ios,android', projectDir.path]);
2365 2366
    expect(projectDir.childDirectory('ios'), exists);
    expect(projectDir.childDirectory('example').childDirectory('ios'), exists);
2367

2368 2369 2370 2371 2372
    expect(projectDir.childDirectory('android'), exists);
    expect(
        projectDir.childDirectory('example').childDirectory('android'), exists);
    expect(projectDir.childDirectory('ios'), exists);
    expect(projectDir.childDirectory('example').childDirectory('ios'), exists);
2373
    validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: const <String>[
2374
      'ios', 'android',
2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385
    ], pluginClass: 'FlutterProjectPlugin',
    unexpectedPlatforms: <String>['some_platform'],
    androidIdentifier: 'com.example.flutter_project');
  });

  testUsingContext('create a module with --platforms throws error.', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);
    await expectLater(
2386
      runner.run(<String>['create', '--no-pub', '--template=module', '--platform=ios', projectDir.path])
2387 2388 2389 2390 2391 2392 2393 2394 2395
      , throwsToolExit(message: 'The "--platforms" argument is not supported', exitCode:2));
  });

  testUsingContext('create a package with --platforms throws error.', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);
    await expectLater(
2396
      runner.run(<String>['create', '--no-pub', '--template=package', '--platform=ios', projectDir.path])
2397 2398 2399
      , throwsToolExit(message: 'The "--platforms" argument is not supported', exitCode: 2));
  });

2400 2401 2402 2403 2404
  testUsingContext('create a plugin with android, delete then re-create folders', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);
2405
    await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platform=android', projectDir.path]);
2406 2407 2408
    expect(projectDir.childDirectory('android'), exists);
    expect(
        projectDir.childDirectory('example').childDirectory('android'), exists);
2409 2410 2411

    globals.fs.file(globals.fs.path.join(projectDir.path, 'android')).deleteSync(recursive: true);
    globals.fs.file(globals.fs.path.join(projectDir.path, 'example/android')).deleteSync(recursive: true);
2412 2413 2414
    expect(projectDir.childDirectory('android'), isNot(exists));
    expect(projectDir.childDirectory('example').childDirectory('android'),
        isNot(exists));
2415 2416 2417

    await runner.run(<String>['create', '--no-pub', projectDir.path]);

2418 2419 2420
    expect(projectDir.childDirectory('android'), exists);
    expect(
        projectDir.childDirectory('example').childDirectory('android'), exists);
2421 2422 2423 2424 2425 2426 2427
  });

  testUsingContext('create a plugin with android, delete then re-create folders while also adding windows', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);
2428
    await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platform=android', projectDir.path]);
2429 2430 2431
    expect(projectDir.childDirectory('android'), exists);
    expect(
        projectDir.childDirectory('example').childDirectory('android'), exists);
2432 2433 2434

    globals.fs.file(globals.fs.path.join(projectDir.path, 'android')).deleteSync(recursive: true);
    globals.fs.file(globals.fs.path.join(projectDir.path, 'example/android')).deleteSync(recursive: true);
2435 2436 2437
    expect(projectDir.childDirectory('android'), isNot(exists));
    expect(projectDir.childDirectory('example').childDirectory('android'),
        isNot(exists));
2438

2439
    await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platform=windows', projectDir.path]);
2440

2441 2442 2443 2444 2445 2446
    expect(projectDir.childDirectory('android'), exists);
    expect(
        projectDir.childDirectory('example').childDirectory('android'), exists);
    expect(projectDir.childDirectory('windows'), exists);
    expect(
        projectDir.childDirectory('example').childDirectory('windows'), exists);
2447 2448 2449 2450 2451 2452 2453 2454 2455
  }, overrides: <Type, Generator>{
    FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
  });

  testUsingContext('flutter create . on and existing plugin does not add android folders if android is not supported in pubspec', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);
2456
    await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platform=ios', projectDir.path]);
2457 2458

    await runner.run(<String>['create', '--no-pub', projectDir.path]);
2459 2460
    expect(projectDir.childDirectory('android'), isNot(exists));
    expect(projectDir.childDirectory('example').childDirectory('android'), isNot(exists));
2461 2462 2463 2464 2465 2466 2467
  });

  testUsingContext('flutter create . on and existing plugin does not add windows folder even feature is enabled', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);
2468
    await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platform=android', projectDir.path]);
2469 2470

    await runner.run(<String>['create', '--no-pub', projectDir.path]);
2471 2472
    expect(projectDir.childDirectory('windows'), isNot(exists));
    expect(projectDir.childDirectory('example').childDirectory('windows'), isNot(exists));
2473 2474 2475 2476 2477 2478 2479 2480 2481
  }, overrides: <Type, Generator>{
    FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
  });

  testUsingContext('flutter create . on and existing plugin does not add linux folder even feature is enabled', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);
2482
    await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platform=android', projectDir.path]);
2483 2484

    await runner.run(<String>['create', '--no-pub', projectDir.path]);
2485 2486
    expect(projectDir.childDirectory('linux'), isNot(exists));
    expect(projectDir.childDirectory('example').childDirectory('linux'), isNot(exists));
2487 2488 2489 2490 2491 2492 2493 2494 2495
  }, overrides: <Type, Generator>{
    FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
  });

  testUsingContext('flutter create . on and existing plugin does not add web files even feature is enabled', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);
2496
    await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platform=android', projectDir.path]);
2497 2498

    await runner.run(<String>['create', '--no-pub', projectDir.path]);
2499
    expect(projectDir.childDirectory('lib').childFile('flutter_project_web.dart'), isNot(exists));
2500 2501 2502 2503 2504 2505 2506 2507 2508
  }, overrides: <Type, Generator>{
    FeatureFlags: () => TestFeatureFlags(isWebEnabled: true),
  });

  testUsingContext('flutter create . on and existing plugin does not add macos folder even feature is enabled', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);
2509
    await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platform=android', projectDir.path]);
2510 2511

    await runner.run(<String>['create', '--no-pub', projectDir.path]);
2512 2513
    expect(projectDir.childDirectory('macos'), isNot(exists));
    expect(projectDir.childDirectory('example').childDirectory('macos'), isNot(exists));
2514 2515 2516 2517
  }, overrides: <Type, Generator>{
    FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true),
  });

2518 2519 2520 2521 2522 2523 2524 2525 2526
  testUsingContext('flutter create . on and existing plugin should show "Your example app code in"', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);
    final String projectDirPath = globals.fs.path.normalize(projectDir.absolute.path);
    final String relativePluginPath = globals.fs.path.normalize(globals.fs.path.relative(projectDirPath));
    final String relativeExamplePath = globals.fs.path.normalize(globals.fs.path.join(relativePluginPath, 'example/lib/main.dart'));

2527
    await runner.run(<String>['create', '--no-pub', '--org=com.example', '--template=plugin', '--platform=android', projectDir.path]);
2528
    expect(logger.statusText, contains('Your example app code is in $relativeExamplePath.\n'));
2529
    await runner.run(<String>['create', '--no-pub', '--org=com.example', '--template=plugin', '--platform=ios', projectDir.path]);
2530 2531 2532 2533 2534 2535 2536
    expect(logger.statusText, contains('Your example app code is in $relativeExamplePath.\n'));
    await runner.run(<String>['create', '--no-pub', projectDir.path]);
    expect(logger.statusText, contains('Your example app code is in $relativeExamplePath.\n'));
  }, overrides: <Type, Generator> {
    Logger: () => logger,
  });

2537 2538 2539 2540 2541
  testUsingContext('flutter create -t plugin in an empty folder should not show pubspec.yaml updating suggestion', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);
2542
    await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platform=android', projectDir.path]);
2543 2544 2545
    final String projectDirPath = globals.fs.path.normalize(projectDir.absolute.path);
    final String relativePluginPath = globals.fs.path.normalize(globals.fs.path.relative(projectDirPath));
    expect(logger.statusText, isNot(contains('You need to update $relativePluginPath/pubspec.yaml to support android.\n')));
2546 2547 2548 2549 2550 2551 2552 2553 2554
  }, overrides: <Type, Generator> {
    Logger: () => logger,
  });

  testUsingContext('flutter create -t plugin in an existing plugin should show pubspec.yaml updating suggestion', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);
2555 2556
    final String projectDirPath = globals.fs.path.normalize(projectDir.absolute.path);
    final String relativePluginPath = globals.fs.path.normalize(globals.fs.path.relative(projectDirPath));
2557
    await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platform=ios', projectDir.path]);
2558
    expect(logger.statusText, isNot(contains('You need to update $relativePluginPath/pubspec.yaml to support ios.\n')));
2559
    await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platform=android', projectDir.path]);
2560
    expect(logger.statusText, contains('You need to update $relativePluginPath/pubspec.yaml to support android.\n'));
2561 2562 2563 2564
  }, overrides: <Type, Generator> {
    Logger: () => logger,
  });

2565
  testUsingContext('newly created plugin has min flutter sdk version as 2.5.0', () async {
2566 2567 2568 2569 2570 2571 2572
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);
    await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);
    final String rawPubspec = await projectDir.childFile('pubspec.yaml').readAsString();
    final Pubspec pubspec = Pubspec.parse(rawPubspec);
2573 2574 2575
    final Map<String, VersionConstraint?> env = pubspec.environment!;
    expect(env['flutter']!.allows(Version(2, 5, 0)), true);
    expect(env['flutter']!.allows(Version(2, 4, 9)), false);
2576 2577
  });

2578
  testUsingContext('default app uses flutter default versions', () async {
2579 2580 2581 2582 2583 2584 2585 2586 2587
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

    await runner.run(<String>['create', '--no-pub', projectDir.path]);

    expect(globals.fs.isFileSync('${projectDir.path}/android/app/build.gradle'), true);

2588
    final String buildContent = await globals.fs.file('${projectDir.path}/android/app/build.gradle').readAsString();
2589

2590
    expect(buildContent.contains('compileSdkVersion flutter.compileSdkVersion'), true);
Daco Harkes's avatar
Daco Harkes committed
2591
    expect(buildContent.contains('ndkVersion flutter.ndkVersion'), true);
2592
    expect(buildContent.contains('targetSdkVersion flutter.targetSdkVersion'), true);
2593 2594
  });

2595 2596 2597 2598 2599 2600 2601 2602
  testUsingContext('Linux plugins handle partially camel-case project names correctly', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

    const String projectName = 'foo_BarBaz';
    final Directory projectDir = tempDir.childDirectory(projectName);
2603
    await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platform=linux', '--skip-name-checks', projectDir.path]);
2604 2605 2606 2607 2608 2609 2610 2611 2612 2613
    final Directory platformDir = projectDir.childDirectory('linux');

    const String classFilenameBase = 'foo_bar_baz_plugin';
    const String headerName = '$classFilenameBase.h';
    final File headerFile = platformDir
        .childDirectory('include')
        .childDirectory(projectName)
        .childFile(headerName);
    final File implFile = platformDir.childFile('$classFilenameBase.cc');
    // Ensure that the files have the right names.
2614 2615
    expect(headerFile, exists);
    expect(implFile, exists);
2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633
    // Ensure that the include is correct.
    expect(implFile.readAsStringSync(), contains(headerName));
    // Ensure that the CMake file has the right target and source values.
    final String cmakeContents = platformDir.childFile('CMakeLists.txt').readAsStringSync();
    expect(cmakeContents, contains('"$classFilenameBase.cc"'));
    expect(cmakeContents, contains('set(PLUGIN_NAME "foo_BarBaz_plugin")'));
  }, overrides: <Type, Generator>{
    FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
  });

  testUsingContext('Windows plugins handle partially camel-case project names correctly', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

    const String projectName = 'foo_BarBaz';
    final Directory projectDir = tempDir.childDirectory(projectName);
2634
    await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platform=windows', '--skip-name-checks', projectDir.path]);
2635 2636 2637
    final Directory platformDir = projectDir.childDirectory('windows');

    const String classFilenameBase = 'foo_bar_baz_plugin';
2638 2639 2640
    const String cApiHeaderName = '${classFilenameBase}_c_api.h';
    const String pluginClassHeaderName = '$classFilenameBase.h';
    final File cApiHeaderFile = platformDir
2641 2642
        .childDirectory('include')
        .childDirectory(projectName)
2643 2644 2645 2646
        .childFile(cApiHeaderName);
    final File cApiImplFile = platformDir.childFile('${classFilenameBase}_c_api.cpp');
    final File pluginClassHeaderFile = platformDir.childFile(pluginClassHeaderName);
    final File pluginClassImplFile = platformDir.childFile('$classFilenameBase.cpp');
2647
    // Ensure that the files have the right names.
2648 2649 2650 2651 2652 2653 2654 2655 2656 2657
    expect(cApiHeaderFile, exists);
    expect(cApiImplFile, exists);
    expect(pluginClassHeaderFile, exists);
    expect(pluginClassImplFile, exists);
    // Ensure that the includes are correct.
    expect(cApiImplFile.readAsLinesSync(), containsAllInOrder(<Matcher>[
      contains('#include "include/$projectName/$cApiHeaderName"'),
      contains('#include "$pluginClassHeaderName"'),
    ]));
    expect(pluginClassImplFile.readAsLinesSync(), contains('#include "$pluginClassHeaderName"'));
2658 2659 2660 2661
    // Ensure that the plugin target name matches the post-processed version.
    // Ensure that the CMake file has the right target and source values.
    final String cmakeContents = platformDir.childFile('CMakeLists.txt').readAsStringSync();
    expect(cmakeContents, contains('"$classFilenameBase.cpp"'));
2662 2663 2664
    expect(cmakeContents, contains('"$classFilenameBase.h"'));
    expect(cmakeContents, contains('"${classFilenameBase}_c_api.cpp"'));
    expect(cmakeContents, contains('"include/$projectName/${classFilenameBase}_c_api.h"'));
2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677
    expect(cmakeContents, contains('set(PLUGIN_NAME "foo_BarBaz_plugin")'));
  }, overrides: <Type, Generator>{
    FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
  });

  testUsingContext('Linux plugins handle project names ending in _plugin correctly', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

    const String projectName = 'foo_bar_plugin';
    final Directory projectDir = tempDir.childDirectory(projectName);
2678
    await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platform=linux', projectDir.path]);
2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689
    final Directory platformDir = projectDir.childDirectory('linux');

    // If the project already ends in _plugin, it shouldn't be added again.
    const String classFilenameBase = projectName;
    const String headerName = '$classFilenameBase.h';
    final File headerFile = platformDir
        .childDirectory('include')
        .childDirectory(projectName)
        .childFile(headerName);
    final File implFile = platformDir.childFile('$classFilenameBase.cc');
    // Ensure that the files have the right names.
2690 2691
    expect(headerFile, exists);
    expect(implFile, exists);
2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 2712 2713
    // Ensure that the include is correct.
    expect(implFile.readAsStringSync(), contains(headerName));
    // Ensure that the CMake file has the right target and source values.
    final String cmakeContents = platformDir.childFile('CMakeLists.txt').readAsStringSync();
    expect(cmakeContents, contains('"$classFilenameBase.cc"'));
    // The "_plugin_plugin" suffix is intentional; because the target names must
    // be unique across the ecosystem, no canonicalization can be done,
    // otherwise plugins called "foo_bar" and "foo_bar_plugin" would collide in
    // builds.
    expect(cmakeContents, contains('set(PLUGIN_NAME "foo_bar_plugin_plugin")'));
  }, overrides: <Type, Generator>{
    FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
  });

  testUsingContext('Windows plugins handle project names ending in _plugin correctly', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

    const String projectName = 'foo_bar_plugin';
    final Directory projectDir = tempDir.childDirectory(projectName);
2714
    await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platform=windows', projectDir.path]);
2715 2716 2717 2718
    final Directory platformDir = projectDir.childDirectory('windows');

    // If the project already ends in _plugin, it shouldn't be added again.
    const String classFilenameBase = projectName;
2719 2720 2721
    const String cApiHeaderName = '${classFilenameBase}_c_api.h';
    const String pluginClassHeaderName = '$classFilenameBase.h';
    final File cApiHeaderFile = platformDir
2722 2723
        .childDirectory('include')
        .childDirectory(projectName)
2724 2725 2726 2727
        .childFile(cApiHeaderName);
    final File cApiImplFile = platformDir.childFile('${classFilenameBase}_c_api.cpp');
    final File pluginClassHeaderFile = platformDir.childFile(pluginClassHeaderName);
    final File pluginClassImplFile = platformDir.childFile('$classFilenameBase.cpp');
2728
    // Ensure that the files have the right names.
2729 2730 2731 2732 2733 2734 2735 2736 2737 2738
    expect(cApiHeaderFile, exists);
    expect(cApiImplFile, exists);
    expect(pluginClassHeaderFile, exists);
    expect(pluginClassImplFile, exists);
    // Ensure that the includes are correct.
    expect(cApiImplFile.readAsLinesSync(), containsAllInOrder(<Matcher>[
      contains('#include "include/$projectName/$cApiHeaderName"'),
      contains('#include "$pluginClassHeaderName"'),
    ]));
    expect(pluginClassImplFile.readAsLinesSync(), contains('#include "$pluginClassHeaderName"'));
2739 2740 2741 2742 2743 2744 2745 2746 2747 2748 2749
    // Ensure that the CMake file has the right target and source values.
    final String cmakeContents = platformDir.childFile('CMakeLists.txt').readAsStringSync();
    expect(cmakeContents, contains('"$classFilenameBase.cpp"'));
    // The "_plugin_plugin" suffix is intentional; because the target names must
    // be unique across the ecosystem, no canonicalization can be done,
    // otherwise plugins called "foo_bar" and "foo_bar_plugin" would collide in
    // builds.
    expect(cmakeContents, contains('set(PLUGIN_NAME "foo_bar_plugin_plugin")'));
  }, overrides: <Type, Generator>{
    FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
  });
2750 2751 2752 2753 2754 2755 2756 2757 2758

  testUsingContext('created plugin supports no platforms should print `no platforms` message', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

    await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);
    expect(logger.errorText, contains(_kNoPlatformsMessage));
2759 2760
    expect(logger.statusText, contains('To add platforms, run `flutter create -t plugin --platforms <platforms> .` under ${globals.fs.path.normalize(globals.fs.path.relative(projectDir.path))}.'));
    expect(logger.statusText, contains('For more information, see https://flutter.dev/go/plugin-platforms.'));
2761 2762

  }, overrides: <Type, Generator>{
2763
    FeatureFlags: () => TestFeatureFlags(),
2764 2765 2766
    Logger: ()=> logger,
  });

2767 2768 2769 2770 2771 2772 2773 2774 2775 2776 2777 2778 2779 2780 2781 2782
  testUsingContext('created FFI plugin supports no platforms should print `no platforms` message', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

    await runner.run(<String>['create', '--no-pub', '--template=plugin_ffi', projectDir.path]);
    expect(logger.errorText, contains(_kNoPlatformsMessage));
    expect(logger.statusText, contains('To add platforms, run `flutter create -t plugin_ffi --platforms <platforms> .` under ${globals.fs.path.normalize(globals.fs.path.relative(projectDir.path))}.'));
    expect(logger.statusText, contains('For more information, see https://flutter.dev/go/plugin-platforms.'));

  }, overrides: <Type, Generator>{
    FeatureFlags: () => TestFeatureFlags(),
    Logger: ()=> logger,
  });

2783 2784 2785 2786 2787 2788
  testUsingContext('created plugin with no --platforms flag should not print `no platforms` message if the existing plugin supports a platform.', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

2789
    await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platform=ios', projectDir.path]);
2790 2791 2792 2793
    await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);
    expect(logger.errorText, isNot(contains(_kNoPlatformsMessage)));

  }, overrides: <Type, Generator>{
2794
    FeatureFlags: () => TestFeatureFlags(),
2795 2796
    Logger: () => logger,
  });
2797

2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 2840 2841 2842 2843 2844 2845 2846 2847 2848
  testUsingContext('should show warning when disabled platforms are selected while creating a plugin', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

    await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=android,ios,web,windows,macos,linux', projectDir.path]);
    await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);
    expect(logger.statusText, contains(_kDisabledPlatformRequestedMessage));

  }, overrides: <Type, Generator>{
    FeatureFlags: () => TestFeatureFlags(),
    Logger: () => logger,
  });

  testUsingContext("shouldn't show warning when only enabled platforms are selected while creating a plugin", () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

    await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=android,ios,windows', projectDir.path]);
    await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);
    expect(logger.statusText, isNot(contains(_kDisabledPlatformRequestedMessage)));

  }, overrides: <Type, Generator>{
    FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
    Logger: () => logger,
  });

  testUsingContext('should show warning when disabled platforms are selected while creating a app', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

    await runner.run(<String>['create', '--no-pub', '--platforms=android,ios,web,windows,macos,linux', projectDir.path]);
    await runner.run(<String>['create', '--no-pub', projectDir.path]);
    expect(logger.statusText, contains(_kDisabledPlatformRequestedMessage));

  }, overrides: <Type, Generator>{
    FeatureFlags: () => TestFeatureFlags(),
    Logger: () => logger,
  });

  testUsingContext("shouldn't show warning when only enabled platforms are selected while creating a app", () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

2849
    await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platform=windows', projectDir.path]);
2850 2851 2852 2853 2854 2855 2856
    await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);
    expect(logger.statusText, isNot(contains(_kDisabledPlatformRequestedMessage)));

  }, overrides: <Type, Generator>{
    FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true, isAndroidEnabled: false, isIOSEnabled: false),
    Logger: () => logger,
  });
2857 2858 2859 2860 2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 2880 2881 2882 2883 2884 2885 2886 2887 2888 2889 2890 2891 2892 2893 2894 2895

  testUsingContext('default project has analysis_options.yaml set up correctly', () async {
    await _createProject(
        projectDir,
        <String>[],
        <String>[
          'analysis_options.yaml',
        ],
    );
    final String dataPath = globals.fs.path.join(
      getFlutterRoot(),
      'packages',
      'flutter_tools',
      'test',
      'commands.shard',
      'permeable',
      'data',
    );
    final File toAnalyze = await globals.fs.file(globals.fs.path.join(dataPath, 'to_analyze.dart.test'))
        .copy(globals.fs.path.join(projectDir.path, 'lib', 'to_analyze.dart'));
    final String relativePath = globals.fs.path.join('lib', 'to_analyze.dart');
    final List<String> expectedFailures = <String>[
      '$relativePath:11:7: use_key_in_widget_constructors',
      '$relativePath:20:3: prefer_const_constructors_in_immutables',
      '$relativePath:31:26: use_full_hex_values_for_flutter_colors',
    ];
    expect(expectedFailures.length, '// LINT:'.allMatches(toAnalyze.readAsStringSync()).length);
    await _analyzeProject(
      projectDir.path,
      expectedFailures: expectedFailures,
    );
  }, overrides: <Type, Generator>{
    Pub: () => Pub(
      fileSystem: globals.fs,
      logger: globals.logger,
      processManager: globals.processManager,
      usage: globals.flutterUsage,
      botDetector: globals.botDetector,
      platform: globals.platform,
2896
      stdio: mockStdio,
2897 2898
    ),
  });
Daco Harkes's avatar
Daco Harkes committed
2899 2900 2901 2902 2903 2904 2905 2906 2907 2908 2909 2910 2911 2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 2947 2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 2959 2960 2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 2977 2978 2979 2980 2981 2982 2983 2984 2985 2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996

  testUsingContext('create an FFI plugin with ios, then add macos', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);
    await runner.run(<String>['create', '--no-pub', '--template=plugin_ffi', '--platform=ios', projectDir.path]);
    expect(projectDir.childDirectory('src'), exists);
    expect(projectDir.childDirectory('ios'), exists);
    expect(projectDir.childDirectory('example').childDirectory('ios'), exists);
    validatePubspecForPlugin(
      projectDir: projectDir.absolute.path,
      expectedPlatforms: const <String>[
        'ios',
      ],
      ffiPlugin: true,
      unexpectedPlatforms: <String>['some_platform'],
    );

    await runner.run(<String>['create', '--no-pub', '--template=plugin_ffi', '--platform=macos', projectDir.path]);
    expect(projectDir.childDirectory('macos'), exists);
    expect(
        projectDir.childDirectory('example').childDirectory('macos'), exists);
    expect(projectDir.childDirectory('ios'), exists);
    expect(projectDir.childDirectory('example').childDirectory('ios'), exists);
  }, overrides: <Type, Generator>{
    FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true),
  });

  testUsingContext('FFI plugins error android language', () async {
    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);
    final List<String> args = <String>[
      'create',
      '--no-pub',
      '--template=plugin_ffi',
      '-a',
      'kotlin',
      '--platforms=android',
      projectDir.path,
    ];

    await expectLater(
      runner.run(args),
      throwsToolExit(message: 'The "android-language" option is not supported with the plugin_ffi template: the language will always be C or C++.'),
    );
  });

  testUsingContext('FFI plugins error ios language', () async {
    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);
    final List<String> args = <String>[
      'create',
      '--no-pub',
      '--template=plugin_ffi',
      '--ios-language',
      'swift',
      '--platforms=ios',
      projectDir.path,
    ];

    await expectLater(
      runner.run(args),
      throwsToolExit(message: 'The "ios-language" option is not supported with the plugin_ffi template: the language will always be C or C++.'),
    );
  });

  testUsingContext('FFI plugins error web platform', () async {
    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);
    final List<String> args = <String>[
      'create',
      '--no-pub',
      '--template=plugin_ffi',
      '--platforms=web',
      projectDir.path,
    ];

    await expectLater(
      runner.run(args),
      throwsToolExit(message: 'The web platform is not supported in plugin_ffi template.'),
    );
  });

  testUsingContext('should show warning when disabled platforms are selected while creating an FFI plugin', () async {
    Cache.flutterRoot = '../..';

    final CreateCommand command = CreateCommand();
    final CommandRunner<void> runner = createTestCommandRunner(command);

    await runner.run(<String>['create', '--no-pub', '--template=plugin_ffi', '--platforms=android,ios,windows,macos,linux', projectDir.path]);
    await runner.run(<String>['create', '--no-pub', '--template=plugin_ffi', projectDir.path]);
    expect(logger.statusText, contains(_kDisabledPlatformRequestedMessage));

  }, overrides: <Type, Generator>{
    FeatureFlags: () => TestFeatureFlags(),
    Logger: () => logger,
  });
2997
}
2998

2999
Future<void> _createProject(
3000 3001 3002 3003 3004
  Directory dir,
  List<String> createArgs,
  List<String> expectedPaths, {
  List<String> unexpectedPaths = const <String>[],
}) async {
3005
  Cache.flutterRoot = '../..';
3006
  final CreateCommand command = CreateCommand();
3007
  final CommandRunner<void> runner = createTestCommandRunner(command);
3008 3009 3010 3011 3012
  await runner.run(<String>[
    'create',
    ...createArgs,
    dir.path,
  ]);
3013

3014
  bool pathExists(String path) {
3015 3016
    final String fullPath = globals.fs.path.join(dir.path, path);
    return globals.fs.typeSync(fullPath) != FileSystemEntityType.notFound;
3017 3018
  }

3019
  final List<String> failures = <String>[
3020
    for (final String path in expectedPaths)
3021 3022
      if (!pathExists(path))
        'Path "$path" does not exist.',
3023
    for (final String path in unexpectedPaths)
3024 3025 3026
      if (pathExists(path))
        'Path "$path" exists when it shouldn\'t.',
  ];
3027
  expect(failures, isEmpty, reason: failures.join('\n'));
3028
}
3029

3030
Future<void> _createAndAnalyzeProject(
3031 3032 3033 3034 3035 3036 3037
  Directory dir,
  List<String> createArgs,
  List<String> expectedPaths, {
  List<String> unexpectedPaths = const <String>[],
}) async {
  await _createProject(dir, createArgs, expectedPaths, unexpectedPaths: unexpectedPaths);
  await _analyzeProject(dir.path);
3038 3039
}

3040
Future<void> _ensureFlutterToolsSnapshot() async {
3041
  final String flutterToolsPath = globals.fs.path.absolute(globals.fs.path.join(
3042 3043 3044
    'bin',
    'flutter_tools.dart',
  ));
3045
  final String flutterToolsSnapshotPath = globals.fs.path.absolute(globals.fs.path.join(
3046 3047 3048 3049 3050 3051
    '..',
    '..',
    'bin',
    'cache',
    'flutter_tools.snapshot',
  ));
3052 3053 3054
  final String packageConfig = globals.fs.path.absolute(globals.fs.path.join(
    '.dart_tool',
    'package_config.json'
3055
  ));
3056

3057
  final File snapshotFile = globals.fs.file(flutterToolsSnapshotPath);
3058
  if (snapshotFile.existsSync()) {
3059
    snapshotFile.renameSync('$flutterToolsSnapshotPath.bak');
3060 3061 3062 3063
  }

  final List<String> snapshotArgs = <String>[
    '--snapshot=$flutterToolsSnapshotPath',
3064
    '--packages=$packageConfig',
3065
    flutterToolsPath,
3066 3067 3068 3069 3070
  ];
  final ProcessResult snapshotResult = await Process.run(
    '../../bin/cache/dart-sdk/bin/dart',
    snapshotArgs,
  );
3071 3072 3073
  printOnFailure('Results of generating snapshot:');
  printOnFailure(snapshotResult.stdout.toString());
  printOnFailure(snapshotResult.stderr.toString());
3074 3075 3076 3077
  expect(snapshotResult.exitCode, 0);
}

Future<void> _restoreFlutterToolsSnapshot() async {
3078
  final String flutterToolsSnapshotPath = globals.fs.path.absolute(globals.fs.path.join(
3079 3080 3081 3082 3083 3084 3085
    '..',
    '..',
    'bin',
    'cache',
    'flutter_tools.snapshot',
  ));

3086
  final File snapshotBackup = globals.fs.file('$flutterToolsSnapshotPath.bak');
3087 3088 3089 3090 3091 3092 3093 3094
  if (!snapshotBackup.existsSync()) {
    // No backup to restore.
    return;
  }

  snapshotBackup.renameSync(flutterToolsSnapshotPath);
}

3095
Future<void> _analyzeProject(String workingDir, { List<String> expectedFailures = const <String>[] }) async {
3096
  final String flutterToolsSnapshotPath = globals.fs.path.absolute(globals.fs.path.join(
3097 3098 3099 3100 3101 3102 3103 3104 3105
    '..',
    '..',
    'bin',
    'cache',
    'flutter_tools.snapshot',
  ));

  final List<String> args = <String>[
    flutterToolsSnapshotPath,
3106 3107
    'analyze',
  ];
3108

3109
  final ProcessResult exec = await Process.run(
3110
    globals.artifacts!.getHostArtifact(HostArtifact.engineDartBinary).path,
3111 3112
    args,
    workingDirectory: workingDir,
3113
  );
3114
  if (expectedFailures.isEmpty) {
3115 3116 3117
    printOnFailure('Results of running analyzer:');
    printOnFailure(exec.stdout.toString());
    printOnFailure(exec.stderr.toString());
3118 3119 3120 3121 3122
    expect(exec.exitCode, 0);
    return;
  }
  expect(exec.exitCode, isNot(0));
  String lineParser(String line) {
3123 3124 3125 3126 3127 3128 3129
    try {
      final String analyzerSeparator = globals.platform.isWindows ? ' - ' : ' • ';
      final List<String> lineComponents = line.trim().split(analyzerSeparator);
      final String lintName = lineComponents.removeLast();
      final String location = lineComponents.removeLast();
      return '$location: $lintName';
    } on RangeError catch (err) {
3130
      throw RangeError('Received "$err" while trying to parse: "$line".');
3131
    }
3132
  }
3133
  final String stdout = exec.stdout.toString();
3134
  final List<String> errors = <String>[];
3135
  try {
3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 3148
    bool analyzeLineFound = false;
    const LineSplitter().convert(stdout).forEach((String line) {
      // Conditional to filter out any stdout from `pub get`
      if (!analyzeLineFound && line.startsWith('Analyzing')) {
        analyzeLineFound = true;
        return;
      }

      if (analyzeLineFound && line.trim().isNotEmpty) {
        errors.add(lineParser(line.trim()));
      }
    });
  } on Exception catch (err) {
3149 3150
    fail('$err\n\nComplete STDOUT was:\n\n$stdout');
  }
3151 3152
  expect(errors, unorderedEquals(expectedFailures),
      reason: 'Failed with stdout:\n\n$stdout');
3153
}
3154

3155
Future<void> _getPackages(Directory workingDir) async {
3156
  final String flutterToolsSnapshotPath = globals.fs.path.absolute(globals.fs.path.join(
3157 3158
    '..',
    '..',
3159
    'bin',
3160 3161
    'cache',
    'flutter_tools.snapshot',
3162 3163
  ));

3164 3165 3166
  // While flutter test does get packages, it doesn't write version
  // files anymore.
  await Process.run(
3167
    globals.artifacts!.getHostArtifact(HostArtifact.engineDartBinary).path,
3168
    <String>[
3169
      flutterToolsSnapshotPath,
3170 3171 3172
      'packages',
      'get',
    ],
3173 3174
    workingDirectory: workingDir.path,
  );
3175 3176
}

3177
Future<void> _runFlutterTest(Directory workingDir, { String? target }) async {
3178 3179 3180 3181 3182 3183 3184 3185 3186
  final String flutterToolsSnapshotPath = globals.fs.path.absolute(globals.fs.path.join(
    '..',
    '..',
    'bin',
    'cache',
    'flutter_tools.snapshot',
  ));

  await _getPackages(workingDir);
3187

3188
  final List<String> args = <String>[
3189
    flutterToolsSnapshotPath,
3190 3191 3192 3193
    'test',
    '--no-color',
    if (target != null) target,
  ];
3194 3195

  final ProcessResult exec = await Process.run(
3196
    globals.artifacts!.getHostArtifact(HostArtifact.engineDartBinary).path,
3197 3198 3199
    args,
    workingDirectory: workingDir.path,
  );
3200 3201 3202
  printOnFailure('Output of running flutter test:');
  printOnFailure(exec.stdout.toString());
  printOnFailure(exec.stderr.toString());
3203 3204 3205
  expect(exec.exitCode, 0);
}

3206
/// A ProcessManager that invokes a real process manager, but keeps
3207
/// track of all commands sent to it.
3208
class LoggingProcessManager extends LocalProcessManager {
3209
  List<List<String>> commands = <List<String>>[];
3210 3211 3212

  @override
  Future<Process> start(
3213
    List<Object> command, {
3214 3215
    String? workingDirectory,
    Map<String, String>? environment,
3216 3217 3218 3219
    bool includeParentEnvironment = true,
    bool runInShell = false,
    ProcessStartMode mode = ProcessStartMode.normal,
  }) {
3220
    commands.add(command.map((Object arg) => arg.toString()).toList());
3221 3222 3223 3224 3225 3226 3227 3228 3229
    return super.start(
      command,
      workingDirectory: workingDirectory,
      environment: environment,
      includeParentEnvironment: includeParentEnvironment,
      runInShell: runInShell,
      mode: mode,
    );
  }
3230 3231 3232 3233

  void clear() {
    commands.clear();
  }
3234
}
3235

3236
String _getStringValueFromPlist({required File plistFile, String? key}) {
3237 3238 3239 3240 3241
  final List<String> plist = plistFile.readAsLinesSync().map((String line) => line.trim()).toList();
  final int keyIndex = plist.indexOf('<key>$key</key>');
  assert(keyIndex > 0);
  return plist[keyIndex+1].replaceAll('<string>', '').replaceAll('</string>', '');
}
3242

3243
bool _getBooleanValueFromPlist({required File plistFile, String? key}) {
3244 3245 3246 3247 3248
  final List<String> plist = plistFile.readAsLinesSync().map((String line) => line.trim()).toList();
  final int keyIndex = plist.indexOf('<key>$key</key>');
  assert(keyIndex > 0);
  return plist[keyIndex+1].replaceAll('<', '').replaceAll('/>', '') == 'true';
}