|  | // Copyright 2014 The Flutter Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | import 'package:file/file.dart'; | 
|  | import 'package:file/memory.dart'; | 
|  | import 'package:flutter_tools/src/dart/package_map.dart'; | 
|  | import 'package:flutter_tools/src/flutter_manifest.dart'; | 
|  | import 'package:flutter_tools/src/flutter_plugins.dart'; | 
|  | import 'package:flutter_tools/src/globals.dart' as globals; | 
|  | import 'package:flutter_tools/src/plugins.dart'; | 
|  | import 'package:flutter_tools/src/project.dart'; | 
|  | import 'package:package_config/package_config.dart'; | 
|  | import 'package:pub_semver/pub_semver.dart'; | 
|  | import 'package:test/fake.dart'; | 
|  | import 'package:yaml/yaml.dart'; | 
|  |  | 
|  | import '../src/common.dart'; | 
|  | import '../src/context.dart'; | 
|  |  | 
|  | void main() { | 
|  | group('Dart plugin registrant', () { | 
|  | late FileSystem fs; | 
|  | late FakeFlutterProject flutterProject; | 
|  | late FakeFlutterManifest flutterManifest; | 
|  |  | 
|  | setUp(() async { | 
|  | fs = MemoryFileSystem.test(); | 
|  | final Directory directory = fs.currentDirectory.childDirectory('app'); | 
|  | flutterManifest = FakeFlutterManifest(); | 
|  | flutterProject = FakeFlutterProject() | 
|  | ..manifest = flutterManifest | 
|  | ..directory = directory | 
|  | ..flutterPluginsFile = directory.childFile('.flutter-plugins') | 
|  | ..flutterPluginsDependenciesFile = directory.childFile('.flutter-plugins-dependencies') | 
|  | ..dartPluginRegistrant = directory.childFile('dart_plugin_registrant.dart'); | 
|  | flutterProject.directory.childFile('.packages').createSync(recursive: true); | 
|  | }); | 
|  |  | 
|  | group('resolvePlatformImplementation', () { | 
|  | testWithoutContext('selects uncontested implementation from direct dependency', () async { | 
|  | final Set<String> directDependencies = <String>{ | 
|  | 'url_launcher_linux', | 
|  | 'url_launcher_macos', | 
|  | }; | 
|  | final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(<Plugin>[ | 
|  | Plugin.fromYaml( | 
|  | 'url_launcher_linux', | 
|  | '', | 
|  | YamlMap.wrap(<String, dynamic>{ | 
|  | 'implements': 'url_launcher', | 
|  | 'platforms': <String, dynamic>{ | 
|  | 'linux': <String, dynamic>{ | 
|  | 'dartPluginClass': 'UrlLauncherPluginLinux', | 
|  | }, | 
|  | }, | 
|  | }), | 
|  | null, | 
|  | <String>[], | 
|  | fileSystem: fs, | 
|  | appDependencies: directDependencies, | 
|  | ), | 
|  | Plugin.fromYaml( | 
|  | 'url_launcher_macos', | 
|  | '', | 
|  | YamlMap.wrap(<String, dynamic>{ | 
|  | 'implements': 'url_launcher', | 
|  | 'platforms': <String, dynamic>{ | 
|  | 'macos': <String, dynamic>{ | 
|  | 'dartPluginClass': 'UrlLauncherPluginMacOS', | 
|  | }, | 
|  | }, | 
|  | }), | 
|  | null, | 
|  | <String>[], | 
|  | fileSystem: fs, | 
|  | appDependencies: directDependencies, | 
|  | ), | 
|  | ]); | 
|  |  | 
|  | expect(resolutions.length, equals(2)); | 
|  | expect(resolutions[0].toMap(), equals( | 
|  | <String, String>{ | 
|  | 'pluginName': 'url_launcher_linux', | 
|  | 'dartClass': 'UrlLauncherPluginLinux', | 
|  | 'platform': 'linux', | 
|  | }) | 
|  | ); | 
|  | expect(resolutions[1].toMap(), equals( | 
|  | <String, String>{ | 
|  | 'pluginName': 'url_launcher_macos', | 
|  | 'dartClass': 'UrlLauncherPluginMacOS', | 
|  | 'platform': 'macos', | 
|  | }) | 
|  | ); | 
|  | }); | 
|  |  | 
|  | testWithoutContext('selects uncontested implementation from transitive dependency', () async { | 
|  | final Set<String> directDependencies = <String>{ | 
|  | 'url_launcher_macos', | 
|  | }; | 
|  | final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(<Plugin>[ | 
|  | Plugin.fromYaml( | 
|  | 'url_launcher_macos', | 
|  | '', | 
|  | YamlMap.wrap(<String, dynamic>{ | 
|  | 'implements': 'url_launcher', | 
|  | 'platforms': <String, dynamic>{ | 
|  | 'macos': <String, dynamic>{ | 
|  | 'dartPluginClass': 'UrlLauncherPluginMacOS', | 
|  | }, | 
|  | }, | 
|  | }), | 
|  | null, | 
|  | <String>[], | 
|  | fileSystem: fs, | 
|  | appDependencies: directDependencies, | 
|  | ), | 
|  | Plugin.fromYaml( | 
|  | 'transitive_dependency_plugin', | 
|  | '', | 
|  | YamlMap.wrap(<String, dynamic>{ | 
|  | 'implements': 'url_launcher', | 
|  | 'platforms': <String, dynamic>{ | 
|  | 'windows': <String, dynamic>{ | 
|  | 'dartPluginClass': 'UrlLauncherPluginWindows', | 
|  | }, | 
|  | }, | 
|  | }), | 
|  | null, | 
|  | <String>[], | 
|  | fileSystem: fs, | 
|  | appDependencies: directDependencies, | 
|  | ), | 
|  | ]); | 
|  |  | 
|  | expect(resolutions.length, equals(2)); | 
|  | expect(resolutions[0].toMap(), equals( | 
|  | <String, String>{ | 
|  | 'pluginName': 'url_launcher_macos', | 
|  | 'dartClass': 'UrlLauncherPluginMacOS', | 
|  | 'platform': 'macos', | 
|  | }) | 
|  | ); | 
|  | expect(resolutions[1].toMap(), equals( | 
|  | <String, String>{ | 
|  | 'pluginName': 'transitive_dependency_plugin', | 
|  | 'dartClass': 'UrlLauncherPluginWindows', | 
|  | 'platform': 'windows', | 
|  | }) | 
|  | ); | 
|  | }); | 
|  |  | 
|  | testWithoutContext('selects inline implementation on mobile', () async { | 
|  | final Set<String> directDependencies = <String>{}; | 
|  |  | 
|  | final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(<Plugin>[ | 
|  | Plugin.fromYaml( | 
|  | 'url_launcher', | 
|  | '', | 
|  | YamlMap.wrap(<String, dynamic>{ | 
|  | 'platforms': <String, dynamic>{ | 
|  | 'android': <String, dynamic>{ | 
|  | 'dartPluginClass': 'UrlLauncherAndroid', | 
|  | }, | 
|  | 'ios': <String, dynamic>{ | 
|  | 'dartPluginClass': 'UrlLauncherIos', | 
|  | }, | 
|  | }, | 
|  | }), | 
|  | null, | 
|  | <String>[], | 
|  | fileSystem: fs, | 
|  | appDependencies: directDependencies, | 
|  | ), | 
|  | ]); | 
|  | expect(resolutions.length, equals(2)); | 
|  | expect(resolutions[0].toMap(), equals( | 
|  | <String, String>{ | 
|  | 'pluginName': 'url_launcher', | 
|  | 'dartClass': 'UrlLauncherAndroid', | 
|  | 'platform': 'android', | 
|  | }) | 
|  | ); | 
|  | expect(resolutions[1].toMap(), equals( | 
|  | <String, String>{ | 
|  | 'pluginName': 'url_launcher', | 
|  | 'dartClass': 'UrlLauncherIos', | 
|  | 'platform': 'ios', | 
|  | }) | 
|  | ); | 
|  | }); | 
|  |  | 
|  | // See https://github.com/flutter/flutter/issues/87862 for details. | 
|  | testWithoutContext('does not select inline implementation on desktop for ' | 
|  | 'missing min Flutter SDK constraint', () async { | 
|  | final Set<String> directDependencies = <String>{}; | 
|  |  | 
|  | final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(<Plugin>[ | 
|  | Plugin.fromYaml( | 
|  | 'url_launcher', | 
|  | '', | 
|  | YamlMap.wrap(<String, dynamic>{ | 
|  | 'platforms': <String, dynamic>{ | 
|  | 'linux': <String, dynamic>{ | 
|  | 'dartPluginClass': 'UrlLauncherLinux', | 
|  | }, | 
|  | 'macos': <String, dynamic>{ | 
|  | 'dartPluginClass': 'UrlLauncherMacOS', | 
|  | }, | 
|  | 'windows': <String, dynamic>{ | 
|  | 'dartPluginClass': 'UrlLauncherWindows', | 
|  | }, | 
|  | }, | 
|  | }), | 
|  | null, | 
|  | <String>[], | 
|  | fileSystem: fs, | 
|  | appDependencies: directDependencies, | 
|  | ), | 
|  | ]); | 
|  | expect(resolutions.length, equals(0)); | 
|  | }); | 
|  |  | 
|  | // See https://github.com/flutter/flutter/issues/87862 for details. | 
|  | testWithoutContext('does not select inline implementation on desktop for ' | 
|  | 'min Flutter SDK constraint < 2.11', () async { | 
|  | final Set<String> directDependencies = <String>{}; | 
|  |  | 
|  | final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(<Plugin>[ | 
|  | Plugin.fromYaml( | 
|  | 'url_launcher', | 
|  | '', | 
|  | YamlMap.wrap(<String, dynamic>{ | 
|  | 'platforms': <String, dynamic>{ | 
|  | 'linux': <String, dynamic>{ | 
|  | 'dartPluginClass': 'UrlLauncherLinux', | 
|  | }, | 
|  | 'macos': <String, dynamic>{ | 
|  | 'dartPluginClass': 'UrlLauncherMacOS', | 
|  | }, | 
|  | 'windows': <String, dynamic>{ | 
|  | 'dartPluginClass': 'UrlLauncherWindows', | 
|  | }, | 
|  | }, | 
|  | }), | 
|  | VersionConstraint.parse('>=2.10.0'), | 
|  | <String>[], | 
|  | fileSystem: fs, | 
|  | appDependencies: directDependencies, | 
|  | ), | 
|  | ]); | 
|  | expect(resolutions.length, equals(0)); | 
|  | }); | 
|  |  | 
|  | testWithoutContext('selects inline implementation on desktop for ' | 
|  | 'min Flutter SDK requirement of at least 2.11', () async { | 
|  | final Set<String> directDependencies = <String>{}; | 
|  |  | 
|  | final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(<Plugin>[ | 
|  | Plugin.fromYaml( | 
|  | 'url_launcher', | 
|  | '', | 
|  | YamlMap.wrap(<String, dynamic>{ | 
|  | 'platforms': <String, dynamic>{ | 
|  | 'linux': <String, dynamic>{ | 
|  | 'dartPluginClass': 'UrlLauncherLinux', | 
|  | }, | 
|  | 'macos': <String, dynamic>{ | 
|  | 'dartPluginClass': 'UrlLauncherMacOS', | 
|  | }, | 
|  | 'windows': <String, dynamic>{ | 
|  | 'dartPluginClass': 'UrlLauncherWindows', | 
|  | }, | 
|  | }, | 
|  | }), | 
|  | VersionConstraint.parse('>=2.11.0'), | 
|  | <String>[], | 
|  | fileSystem: fs, | 
|  | appDependencies: directDependencies, | 
|  | ), | 
|  | ]); | 
|  | expect(resolutions.length, equals(3)); | 
|  | expect( | 
|  | resolutions.map((PluginInterfaceResolution resolution) => resolution.toMap()), | 
|  | containsAll(<Map<String, String>>[ | 
|  | <String, String>{ | 
|  | 'pluginName': 'url_launcher', | 
|  | 'dartClass': 'UrlLauncherLinux', | 
|  | 'platform': 'linux', | 
|  | }, | 
|  | <String, String>{ | 
|  | 'pluginName': 'url_launcher', | 
|  | 'dartClass': 'UrlLauncherMacOS', | 
|  | 'platform': 'macos', | 
|  | }, | 
|  | <String, String>{ | 
|  | 'pluginName': 'url_launcher', | 
|  | 'dartClass': 'UrlLauncherWindows', | 
|  | 'platform': 'windows', | 
|  | }, | 
|  | ]) | 
|  | ); | 
|  | }); | 
|  |  | 
|  | testWithoutContext('selects default implementation', () async { | 
|  | final Set<String> directDependencies = <String>{}; | 
|  |  | 
|  | final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(<Plugin>[ | 
|  | Plugin.fromYaml( | 
|  | 'url_launcher', | 
|  | '', | 
|  | YamlMap.wrap(<String, dynamic>{ | 
|  | 'platforms': <String, dynamic>{ | 
|  | 'linux': <String, dynamic>{ | 
|  | 'default_package': 'url_launcher_linux', | 
|  | }, | 
|  | }, | 
|  | }), | 
|  | null, | 
|  | <String>[], | 
|  | fileSystem: fs, | 
|  | appDependencies: directDependencies, | 
|  | ), | 
|  | // Include three possible implementations, one before and one after | 
|  | // to ensure that the selection is working as intended, not just by | 
|  | // coincidence of order. | 
|  | Plugin.fromYaml( | 
|  | 'another_url_launcher_linux', | 
|  | '', | 
|  | YamlMap.wrap(<String, dynamic>{ | 
|  | 'implements': 'url_launcher', | 
|  | 'platforms': <String, dynamic>{ | 
|  | 'linux': <String, dynamic>{ | 
|  | 'dartPluginClass': 'UnofficialUrlLauncherPluginLinux', | 
|  | }, | 
|  | }, | 
|  | }), | 
|  | null, | 
|  | <String>[], | 
|  | fileSystem: fs, | 
|  | appDependencies: directDependencies, | 
|  | ), | 
|  | Plugin.fromYaml( | 
|  | 'url_launcher_linux', | 
|  | '', | 
|  | YamlMap.wrap(<String, dynamic>{ | 
|  | 'implements': 'url_launcher', | 
|  | 'platforms': <String, dynamic>{ | 
|  | 'linux': <String, dynamic>{ | 
|  | 'dartPluginClass': 'UrlLauncherPluginLinux', | 
|  | }, | 
|  | }, | 
|  | }), | 
|  | null, | 
|  | <String>[], | 
|  | fileSystem: fs, | 
|  | appDependencies: directDependencies, | 
|  | ), | 
|  | Plugin.fromYaml( | 
|  | 'yet_another_url_launcher_linux', | 
|  | '', | 
|  | YamlMap.wrap(<String, dynamic>{ | 
|  | 'implements': 'url_launcher', | 
|  | 'platforms': <String, dynamic>{ | 
|  | 'linux': <String, dynamic>{ | 
|  | 'dartPluginClass': 'UnofficialUrlLauncherPluginLinux2', | 
|  | }, | 
|  | }, | 
|  | }), | 
|  | null, | 
|  | <String>[], | 
|  | fileSystem: fs, | 
|  | appDependencies: directDependencies, | 
|  | ), | 
|  | ]); | 
|  | expect(resolutions.length, equals(1)); | 
|  | expect(resolutions[0].toMap(), equals( | 
|  | <String, String>{ | 
|  | 'pluginName': 'url_launcher_linux', | 
|  | 'dartClass': 'UrlLauncherPluginLinux', | 
|  | 'platform': 'linux', | 
|  | }) | 
|  | ); | 
|  | }); | 
|  |  | 
|  | testWithoutContext('selects default implementation if interface is direct dependency', () async { | 
|  | final Set<String> directDependencies = <String>{'url_launcher'}; | 
|  |  | 
|  | final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(<Plugin>[ | 
|  | Plugin.fromYaml( | 
|  | 'url_launcher', | 
|  | '', | 
|  | YamlMap.wrap(<String, dynamic>{ | 
|  | 'platforms': <String, dynamic>{ | 
|  | 'linux': <String, dynamic>{ | 
|  | 'default_package': 'url_launcher_linux', | 
|  | }, | 
|  | }, | 
|  | }), | 
|  | null, | 
|  | <String>[], | 
|  | fileSystem: fs, | 
|  | appDependencies: directDependencies, | 
|  | ), | 
|  | Plugin.fromYaml( | 
|  | 'url_launcher_linux', | 
|  | '', | 
|  | YamlMap.wrap(<String, dynamic>{ | 
|  | 'implements': 'url_launcher', | 
|  | 'platforms': <String, dynamic>{ | 
|  | 'linux': <String, dynamic>{ | 
|  | 'dartPluginClass': 'UrlLauncherPluginLinux', | 
|  | }, | 
|  | }, | 
|  | }), | 
|  | null, | 
|  | <String>[], | 
|  | fileSystem: fs, | 
|  | appDependencies: directDependencies, | 
|  | ), | 
|  | ]); | 
|  | expect(resolutions.length, equals(1)); | 
|  | expect(resolutions[0].toMap(), equals( | 
|  | <String, String>{ | 
|  | 'pluginName': 'url_launcher_linux', | 
|  | 'dartClass': 'UrlLauncherPluginLinux', | 
|  | 'platform': 'linux', | 
|  | }) | 
|  | ); | 
|  | }); | 
|  |  | 
|  | testWithoutContext('selects user selected implementation despites default implementation', () async { | 
|  | final Set<String> directDependencies = <String>{ | 
|  | 'user_selected_url_launcher_implementation', | 
|  | 'url_launcher', | 
|  | }; | 
|  |  | 
|  | final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(<Plugin>[ | 
|  | Plugin.fromYaml( | 
|  | 'url_launcher', | 
|  | '', | 
|  | YamlMap.wrap(<String, dynamic>{ | 
|  | 'platforms': <String, dynamic>{ | 
|  | 'linux': <String, dynamic>{ | 
|  | 'default_package': 'url_launcher_linux', | 
|  | }, | 
|  | }, | 
|  | }), | 
|  | null, | 
|  | <String>[], | 
|  | fileSystem: fs, | 
|  | appDependencies: directDependencies, | 
|  | ), | 
|  | Plugin.fromYaml( | 
|  | 'url_launcher_linux', | 
|  | '', | 
|  | YamlMap.wrap(<String, dynamic>{ | 
|  | 'implements': 'url_launcher', | 
|  | 'platforms': <String, dynamic>{ | 
|  | 'linux': <String, dynamic>{ | 
|  | 'dartPluginClass': 'UrlLauncherPluginLinux', | 
|  | }, | 
|  | }, | 
|  | }), | 
|  | null, | 
|  | <String>[], | 
|  | fileSystem: fs, | 
|  | appDependencies: directDependencies, | 
|  | ), | 
|  | Plugin.fromYaml( | 
|  | 'user_selected_url_launcher_implementation', | 
|  | '', | 
|  | YamlMap.wrap(<String, dynamic>{ | 
|  | 'implements': 'url_launcher', | 
|  | 'platforms': <String, dynamic>{ | 
|  | 'linux': <String, dynamic>{ | 
|  | 'dartPluginClass': 'UrlLauncherPluginLinux', | 
|  | }, | 
|  | }, | 
|  | }), | 
|  | null, | 
|  | <String>[], | 
|  | fileSystem: fs, | 
|  | appDependencies: directDependencies, | 
|  | ), | 
|  | ]); | 
|  | expect(resolutions.length, equals(1)); | 
|  | expect(resolutions[0].toMap(), equals( | 
|  | <String, String>{ | 
|  | 'pluginName': 'user_selected_url_launcher_implementation', | 
|  | 'dartClass': 'UrlLauncherPluginLinux', | 
|  | 'platform': 'linux', | 
|  | }) | 
|  | ); | 
|  | }); | 
|  |  | 
|  | testUsingContext('provides error when user selected multiple implementations', () async { | 
|  | final Set<String> directDependencies = <String>{ | 
|  | 'url_launcher_linux_1', | 
|  | 'url_launcher_linux_2', | 
|  | }; | 
|  | expect(() { | 
|  | resolvePlatformImplementation(<Plugin>[ | 
|  | Plugin.fromYaml( | 
|  | 'url_launcher_linux_1', | 
|  | '', | 
|  | YamlMap.wrap(<String, dynamic>{ | 
|  | 'implements': 'url_launcher', | 
|  | 'platforms': <String, dynamic>{ | 
|  | 'linux': <String, dynamic>{ | 
|  | 'dartPluginClass': 'UrlLauncherPluginLinux', | 
|  | }, | 
|  | }, | 
|  | }), | 
|  | null, | 
|  | <String>[], | 
|  | fileSystem: fs, | 
|  | appDependencies: directDependencies, | 
|  | ), | 
|  | Plugin.fromYaml( | 
|  | 'url_launcher_linux_2', | 
|  | '', | 
|  | YamlMap.wrap(<String, dynamic>{ | 
|  | 'implements': 'url_launcher', | 
|  | 'platforms': <String, dynamic>{ | 
|  | 'linux': <String, dynamic>{ | 
|  | 'dartPluginClass': 'UrlLauncherPluginLinux', | 
|  | }, | 
|  | }, | 
|  | }), | 
|  | null, | 
|  | <String>[], | 
|  | fileSystem: fs, | 
|  | appDependencies: directDependencies, | 
|  | ), | 
|  | ]); | 
|  |  | 
|  | }, | 
|  | throwsToolExit( | 
|  | message: 'Please resolve the errors', | 
|  | )); | 
|  |  | 
|  | expect( | 
|  | testLogger.errorText, | 
|  | 'Plugin url_launcher:linux has conflicting direct dependency implementations:\n' | 
|  | '  url_launcher_linux_1\n' | 
|  | '  url_launcher_linux_2\n' | 
|  | 'To fix this issue, remove all but one of these dependencies from pubspec.yaml.' | 
|  | '\n\n' | 
|  | ); | 
|  | }); | 
|  |  | 
|  | testUsingContext('provides all errors when user selected multiple implementations', () async { | 
|  | final Set<String> directDependencies = <String>{ | 
|  | 'url_launcher_linux_1', | 
|  | 'url_launcher_linux_2', | 
|  | 'url_launcher_windows_1', | 
|  | 'url_launcher_windows_2', | 
|  | }; | 
|  | expect(() { | 
|  | resolvePlatformImplementation(<Plugin>[ | 
|  | Plugin.fromYaml( | 
|  | 'url_launcher_linux_1', | 
|  | '', | 
|  | YamlMap.wrap(<String, dynamic>{ | 
|  | 'implements': 'url_launcher', | 
|  | 'platforms': <String, dynamic>{ | 
|  | 'linux': <String, dynamic>{ | 
|  | 'dartPluginClass': 'UrlLauncherPluginLinux', | 
|  | }, | 
|  | }, | 
|  | }), | 
|  | null, | 
|  | <String>[], | 
|  | fileSystem: fs, | 
|  | appDependencies: directDependencies, | 
|  | ), | 
|  | Plugin.fromYaml( | 
|  | 'url_launcher_linux_2', | 
|  | '', | 
|  | YamlMap.wrap(<String, dynamic>{ | 
|  | 'implements': 'url_launcher', | 
|  | 'platforms': <String, dynamic>{ | 
|  | 'linux': <String, dynamic>{ | 
|  | 'dartPluginClass': 'UrlLauncherPluginLinux', | 
|  | }, | 
|  | }, | 
|  | }), | 
|  | null, | 
|  | <String>[], | 
|  | fileSystem: fs, | 
|  | appDependencies: directDependencies, | 
|  | ), | 
|  | Plugin.fromYaml( | 
|  | 'url_launcher_windows_1', | 
|  | '', | 
|  | YamlMap.wrap(<String, dynamic>{ | 
|  | 'implements': 'url_launcher', | 
|  | 'platforms': <String, dynamic>{ | 
|  | 'windows': <String, dynamic>{ | 
|  | 'dartPluginClass': 'UrlLauncherPluginWindows1', | 
|  | }, | 
|  | }, | 
|  | }), | 
|  | null, | 
|  | <String>[], | 
|  | fileSystem: fs, | 
|  | appDependencies: directDependencies, | 
|  | ), | 
|  | Plugin.fromYaml( | 
|  | 'url_launcher_windows_2', | 
|  | '', | 
|  | YamlMap.wrap(<String, dynamic>{ | 
|  | 'implements': 'url_launcher', | 
|  | 'platforms': <String, dynamic>{ | 
|  | 'windows': <String, dynamic>{ | 
|  | 'dartPluginClass': 'UrlLauncherPluginWindows2', | 
|  | }, | 
|  | }, | 
|  | }), | 
|  | null, | 
|  | <String>[], | 
|  | fileSystem: fs, | 
|  | appDependencies: directDependencies, | 
|  | ), | 
|  | ]); | 
|  | }, | 
|  | throwsToolExit( | 
|  | message: 'Please resolve the errors', | 
|  | )); | 
|  |  | 
|  | expect( | 
|  | testLogger.errorText, | 
|  | 'Plugin url_launcher:linux has conflicting direct dependency implementations:\n' | 
|  | '  url_launcher_linux_1\n' | 
|  | '  url_launcher_linux_2\n' | 
|  | 'To fix this issue, remove all but one of these dependencies from pubspec.yaml.' | 
|  | '\n\n' | 
|  | 'Plugin url_launcher:windows has conflicting direct dependency implementations:\n' | 
|  | '  url_launcher_windows_1\n' | 
|  | '  url_launcher_windows_2\n' | 
|  | 'To fix this issue, remove all but one of these dependencies from pubspec.yaml.' | 
|  | '\n\n' | 
|  | ); | 
|  | }); | 
|  |  | 
|  | testUsingContext('provides error when user needs to select among multiple implementations', () async { | 
|  | final Set<String> directDependencies = <String>{}; | 
|  | expect(() { | 
|  | resolvePlatformImplementation(<Plugin>[ | 
|  | Plugin.fromYaml( | 
|  | 'url_launcher_linux_1', | 
|  | '', | 
|  | YamlMap.wrap(<String, dynamic>{ | 
|  | 'implements': 'url_launcher', | 
|  | 'platforms': <String, dynamic>{ | 
|  | 'linux': <String, dynamic>{ | 
|  | 'dartPluginClass': 'UrlLauncherPluginLinux1', | 
|  | }, | 
|  | }, | 
|  | }), | 
|  | null, | 
|  | <String>[], | 
|  | fileSystem: fs, | 
|  | appDependencies: directDependencies, | 
|  | ), | 
|  | Plugin.fromYaml( | 
|  | 'url_launcher_linux_2', | 
|  | '', | 
|  | YamlMap.wrap(<String, dynamic>{ | 
|  | 'implements': 'url_launcher', | 
|  | 'platforms': <String, dynamic>{ | 
|  | 'linux': <String, dynamic>{ | 
|  | 'dartPluginClass': 'UrlLauncherPluginLinux2', | 
|  | }, | 
|  | }, | 
|  | }), | 
|  | null, | 
|  | <String>[], | 
|  | fileSystem: fs, | 
|  | appDependencies: directDependencies, | 
|  | ), | 
|  | ]); | 
|  | }, | 
|  | throwsToolExit( | 
|  | message: 'Please resolve the errors', | 
|  | )); | 
|  |  | 
|  | expect( | 
|  | testLogger.errorText, | 
|  | 'Plugin url_launcher:linux has multiple possible implementations:\n' | 
|  | '  url_launcher_linux_1\n' | 
|  | '  url_launcher_linux_2\n' | 
|  | 'To fix this issue, add one of these dependencies to pubspec.yaml.' | 
|  | '\n\n' | 
|  | ); | 
|  | }); | 
|  | }); | 
|  |  | 
|  | group('generateMainDartWithPluginRegistrant', () { | 
|  | testUsingContext('Generates new entrypoint', () async { | 
|  | flutterProject.isModule = true; | 
|  |  | 
|  | createFakeDartPlugins( | 
|  | flutterProject, | 
|  | flutterManifest, | 
|  | fs, | 
|  | <String, String>{ | 
|  | 'url_launcher_android': ''' | 
|  | flutter: | 
|  | plugin: | 
|  | implements: url_launcher | 
|  | platforms: | 
|  | android: | 
|  | dartPluginClass: AndroidPlugin | 
|  | ''', | 
|  | 'url_launcher_ios': ''' | 
|  | flutter: | 
|  | plugin: | 
|  | implements: url_launcher | 
|  | platforms: | 
|  | ios: | 
|  | dartPluginClass: IosPlugin | 
|  | ''', | 
|  | 'url_launcher_macos': ''' | 
|  | flutter: | 
|  | plugin: | 
|  | implements: url_launcher | 
|  | platforms: | 
|  | macos: | 
|  | dartPluginClass: MacOSPlugin | 
|  | ''', | 
|  | 'url_launcher_linux': ''' | 
|  | flutter: | 
|  | plugin: | 
|  | implements: url_launcher | 
|  | platforms: | 
|  | linux: | 
|  | dartPluginClass: LinuxPlugin | 
|  | ''', | 
|  | 'url_launcher_windows': ''' | 
|  | flutter: | 
|  | plugin: | 
|  | implements: url_launcher | 
|  | platforms: | 
|  | windows: | 
|  | dartPluginClass: WindowsPlugin | 
|  | ''', | 
|  | 'awesome_macos': ''' | 
|  | flutter: | 
|  | plugin: | 
|  | implements: awesome | 
|  | platforms: | 
|  | macos: | 
|  | dartPluginClass: AwesomeMacOS | 
|  | ''', | 
|  | }); | 
|  |  | 
|  | final Directory libDir = flutterProject.directory.childDirectory('lib'); | 
|  | libDir.createSync(recursive: true); | 
|  |  | 
|  | final File mainFile = libDir.childFile('main.dart'); | 
|  | mainFile.writeAsStringSync(''' | 
|  | // @dart = 2.8 | 
|  | void main() { | 
|  | } | 
|  | '''); | 
|  | final PackageConfig packageConfig = await loadPackageConfigWithLogging( | 
|  | flutterProject.directory.childDirectory('.dart_tool').childFile('package_config.json'), | 
|  | logger: globals.logger, | 
|  | throwOnError: false, | 
|  | ); | 
|  | await generateMainDartWithPluginRegistrant( | 
|  | flutterProject, | 
|  | packageConfig, | 
|  | 'package:app/main.dart', | 
|  | mainFile, | 
|  | throwOnPluginPubspecError: true, | 
|  | ); | 
|  | expect(flutterProject.dartPluginRegistrant.readAsStringSync(), | 
|  | '//\n' | 
|  | '// Generated file. Do not edit.\n' | 
|  | '// This file is generated from template in file `flutter_tools/lib/src/flutter_plugins.dart`.\n' | 
|  | '//\n' | 
|  | '\n' | 
|  | '// @dart = 2.8\n' | 
|  | '\n' | 
|  | "import 'dart:io'; // flutter_ignore: dart_io_import.\n" | 
|  | "import 'package:url_launcher_android/url_launcher_android.dart';\n" | 
|  | "import 'package:url_launcher_ios/url_launcher_ios.dart';\n" | 
|  | "import 'package:url_launcher_linux/url_launcher_linux.dart';\n" | 
|  | "import 'package:awesome_macos/awesome_macos.dart';\n" | 
|  | "import 'package:url_launcher_macos/url_launcher_macos.dart';\n" | 
|  | "import 'package:url_launcher_windows/url_launcher_windows.dart';\n" | 
|  | '\n' | 
|  | "@pragma('vm:entry-point')\n" | 
|  | 'class _PluginRegistrant {\n' | 
|  | '\n' | 
|  | "  @pragma('vm:entry-point')\n" | 
|  | '  static void register() {\n' | 
|  | '    if (Platform.isAndroid) {\n' | 
|  | '      try {\n' | 
|  | '        AndroidPlugin.registerWith();\n' | 
|  | '      } catch (err) {\n' | 
|  | '        print(\n' | 
|  | "          '`url_launcher_android` threw an error: \$err. '\n" | 
|  | "          'The app may not function as expected until you remove this plugin from pubspec.yaml'\n" | 
|  | '        );\n' | 
|  | '      }\n' | 
|  | '\n' | 
|  | '    } else if (Platform.isIOS) {\n' | 
|  | '      try {\n' | 
|  | '        IosPlugin.registerWith();\n' | 
|  | '      } catch (err) {\n' | 
|  | '        print(\n' | 
|  | "          '`url_launcher_ios` threw an error: \$err. '\n" | 
|  | "          'The app may not function as expected until you remove this plugin from pubspec.yaml'\n" | 
|  | '        );\n' | 
|  | '      }\n' | 
|  | '\n' | 
|  | '    } else if (Platform.isLinux) {\n' | 
|  | '      try {\n' | 
|  | '        LinuxPlugin.registerWith();\n' | 
|  | '      } catch (err) {\n' | 
|  | '        print(\n' | 
|  | "          '`url_launcher_linux` threw an error: \$err. '\n" | 
|  | "          'The app may not function as expected until you remove this plugin from pubspec.yaml'\n" | 
|  | '        );\n' | 
|  | '      }\n' | 
|  | '\n' | 
|  | '    } else if (Platform.isMacOS) {\n' | 
|  | '      try {\n' | 
|  | '        AwesomeMacOS.registerWith();\n' | 
|  | '      } catch (err) {\n' | 
|  | '        print(\n' | 
|  | "          '`awesome_macos` threw an error: \$err. '\n" | 
|  | "          'The app may not function as expected until you remove this plugin from pubspec.yaml'\n" | 
|  | '        );\n' | 
|  | '      }\n' | 
|  | '\n' | 
|  | '      try {\n' | 
|  | '        MacOSPlugin.registerWith();\n' | 
|  | '      } catch (err) {\n' | 
|  | '        print(\n' | 
|  | "          '`url_launcher_macos` threw an error: \$err. '\n" | 
|  | "          'The app may not function as expected until you remove this plugin from pubspec.yaml'\n" | 
|  | '        );\n' | 
|  | '      }\n' | 
|  | '\n' | 
|  | '    } else if (Platform.isWindows) {\n' | 
|  | '      try {\n' | 
|  | '        WindowsPlugin.registerWith();\n' | 
|  | '      } catch (err) {\n' | 
|  | '        print(\n' | 
|  | "          '`url_launcher_windows` threw an error: \$err. '\n" | 
|  | "          'The app may not function as expected until you remove this plugin from pubspec.yaml'\n" | 
|  | '        );\n' | 
|  | '      }\n' | 
|  | '\n' | 
|  | '    }\n' | 
|  | '  }\n' | 
|  | '}\n' | 
|  | ); | 
|  | }, overrides: <Type, Generator>{ | 
|  | FileSystem: () => fs, | 
|  | ProcessManager: () => FakeProcessManager.any(), | 
|  | }); | 
|  |  | 
|  | testUsingContext('Plugin without platform support throws tool exit', () async { | 
|  | flutterProject.isModule = false; | 
|  |  | 
|  | createFakeDartPlugins( | 
|  | flutterProject, | 
|  | flutterManifest, | 
|  | fs, | 
|  | <String, String>{ | 
|  | 'url_launcher_macos': ''' | 
|  | flutter: | 
|  | plugin: | 
|  | implements: url_launcher | 
|  | platforms: | 
|  | macos: | 
|  | invalid: | 
|  | ''', | 
|  | }); | 
|  |  | 
|  | final Directory libDir = flutterProject.directory.childDirectory('lib'); | 
|  | libDir.createSync(recursive: true); | 
|  |  | 
|  | final File mainFile = libDir.childFile('main.dart')..writeAsStringSync(''); | 
|  | final PackageConfig packageConfig = await loadPackageConfigWithLogging( | 
|  | flutterProject.directory.childDirectory('.dart_tool').childFile('package_config.json'), | 
|  | logger: globals.logger, | 
|  | throwOnError: false, | 
|  | ); | 
|  | await expectLater( | 
|  | generateMainDartWithPluginRegistrant( | 
|  | flutterProject, | 
|  | packageConfig, | 
|  | 'package:app/main.dart', | 
|  | mainFile, | 
|  | throwOnPluginPubspecError: true, | 
|  | ), throwsToolExit(message: | 
|  | 'Invalid plugin specification url_launcher_macos.\n' | 
|  | 'Invalid "macos" plugin specification.' | 
|  | ), | 
|  | ); | 
|  | }, overrides: <Type, Generator>{ | 
|  | FileSystem: () => fs, | 
|  | ProcessManager: () => FakeProcessManager.any(), | 
|  | }); | 
|  |  | 
|  | testUsingContext('Plugin with platform support without dart plugin class throws tool exit', () async { | 
|  | flutterProject.isModule = false; | 
|  |  | 
|  | createFakeDartPlugins( | 
|  | flutterProject, | 
|  | flutterManifest, | 
|  | fs, | 
|  | <String, String>{ | 
|  | 'url_launcher_macos': ''' | 
|  | flutter: | 
|  | plugin: | 
|  | implements: url_launcher | 
|  | ''', | 
|  | }); | 
|  |  | 
|  | final Directory libDir = flutterProject.directory.childDirectory('lib'); | 
|  | libDir.createSync(recursive: true); | 
|  |  | 
|  | final File mainFile = libDir.childFile('main.dart')..writeAsStringSync(''); | 
|  | final PackageConfig packageConfig = await loadPackageConfigWithLogging( | 
|  | flutterProject.directory.childDirectory('.dart_tool').childFile('package_config.json'), | 
|  | logger: globals.logger, | 
|  | throwOnError: false, | 
|  | ); | 
|  | await expectLater( | 
|  | generateMainDartWithPluginRegistrant( | 
|  | flutterProject, | 
|  | packageConfig, | 
|  | 'package:app/main.dart', | 
|  | mainFile, | 
|  | throwOnPluginPubspecError: true, | 
|  | ), throwsToolExit(message: | 
|  | 'Invalid plugin specification url_launcher_macos.\n' | 
|  | 'Cannot find the `flutter.plugin.platforms` key in the `pubspec.yaml` file. ' | 
|  | 'An instruction to format the `pubspec.yaml` can be found here: ' | 
|  | 'https://flutter.dev/docs/development/packages-and-plugins/developing-packages#plugin-platforms' | 
|  | ), | 
|  | ); | 
|  | }, overrides: <Type, Generator>{ | 
|  | FileSystem: () => fs, | 
|  | ProcessManager: () => FakeProcessManager.any(), | 
|  | }); | 
|  |  | 
|  | testUsingContext('Does not show error messages if throwOnPluginPubspecError is false', () async { | 
|  | final Set<String> directDependencies = <String>{ | 
|  | 'url_launcher_windows', | 
|  | }; | 
|  | resolvePlatformImplementation(<Plugin>[ | 
|  | Plugin.fromYaml( | 
|  | 'url_launcher_windows', | 
|  | '', | 
|  | YamlMap.wrap(<String, dynamic>{ | 
|  | 'platforms': <String, dynamic>{ | 
|  | 'windows': <String, dynamic>{ | 
|  | 'dartPluginClass': 'UrlLauncherPluginWindows', | 
|  | }, | 
|  | }, | 
|  | }), | 
|  | null, | 
|  | <String>[], | 
|  | fileSystem: fs, | 
|  | appDependencies: directDependencies, | 
|  | ), | 
|  | ], | 
|  | throwOnPluginPubspecError: false, | 
|  | ); | 
|  | expect(testLogger.errorText, ''); | 
|  | }, overrides: <Type, Generator>{ | 
|  | FileSystem: () => fs, | 
|  | ProcessManager: () => FakeProcessManager.any(), | 
|  | }); | 
|  |  | 
|  | testUsingContext('Does not create new entrypoint if there are no platform resolutions', () async { | 
|  | flutterProject.isModule = false; | 
|  |  | 
|  | final Directory libDir = flutterProject.directory.childDirectory('lib'); | 
|  | libDir.createSync(recursive: true); | 
|  |  | 
|  | final File mainFile = libDir.childFile('main.dart')..writeAsStringSync(''); | 
|  | final PackageConfig packageConfig = await loadPackageConfigWithLogging( | 
|  | flutterProject.directory.childDirectory('.dart_tool').childFile('package_config.json'), | 
|  | logger: globals.logger, | 
|  | throwOnError: false, | 
|  | ); | 
|  | await generateMainDartWithPluginRegistrant( | 
|  | flutterProject, | 
|  | packageConfig, | 
|  | 'package:app/main.dart', | 
|  | mainFile, | 
|  | throwOnPluginPubspecError: true, | 
|  | ); | 
|  | expect(flutterProject.dartPluginRegistrant.existsSync(), isFalse); | 
|  | }, overrides: <Type, Generator>{ | 
|  | FileSystem: () => fs, | 
|  | ProcessManager: () => FakeProcessManager.any(), | 
|  | }); | 
|  |  | 
|  | testUsingContext('Deletes new entrypoint if there are no platform resolutions', () async { | 
|  | flutterProject.isModule = false; | 
|  |  | 
|  | createFakeDartPlugins( | 
|  | flutterProject, | 
|  | flutterManifest, | 
|  | fs, | 
|  | <String, String>{ | 
|  | 'url_launcher_macos': ''' | 
|  | flutter: | 
|  | plugin: | 
|  | implements: url_launcher | 
|  | platforms: | 
|  | macos: | 
|  | dartPluginClass: MacOSPlugin | 
|  | ''', | 
|  | }); | 
|  |  | 
|  | final Directory libDir = flutterProject.directory.childDirectory('lib'); | 
|  | libDir.createSync(recursive: true); | 
|  |  | 
|  | final File mainFile = libDir.childFile('main.dart')..writeAsStringSync(''); | 
|  | final PackageConfig packageConfig = await loadPackageConfigWithLogging( | 
|  | flutterProject.directory.childDirectory('.dart_tool').childFile('package_config.json'), | 
|  | logger: globals.logger, | 
|  | throwOnError: false, | 
|  | ); | 
|  | await generateMainDartWithPluginRegistrant( | 
|  | flutterProject, | 
|  | packageConfig, | 
|  | 'package:app/main.dart', | 
|  | mainFile, | 
|  | throwOnPluginPubspecError: true, | 
|  | ); | 
|  | expect(flutterProject.dartPluginRegistrant.existsSync(), isTrue); | 
|  |  | 
|  | // No plugins. | 
|  | createFakeDartPlugins( | 
|  | flutterProject, | 
|  | flutterManifest, | 
|  | fs, | 
|  | <String, String>{}); | 
|  |  | 
|  | await generateMainDartWithPluginRegistrant( | 
|  | flutterProject, | 
|  | packageConfig, | 
|  | 'package:app/main.dart', | 
|  | mainFile, | 
|  | throwOnPluginPubspecError: true, | 
|  | ); | 
|  | expect(flutterProject.dartPluginRegistrant.existsSync(), isFalse); | 
|  | }, overrides: <Type, Generator>{ | 
|  | FileSystem: () => fs, | 
|  | ProcessManager: () => FakeProcessManager.any(), | 
|  | }); | 
|  | }); | 
|  | }); | 
|  | } | 
|  |  | 
|  | void createFakeDartPlugins( | 
|  | FakeFlutterProject flutterProject, | 
|  | FakeFlutterManifest flutterManifest, | 
|  | FileSystem fs, | 
|  | Map<String, String> plugins, | 
|  | ) { | 
|  | final Directory fakePubCache = fs.systemTempDirectory.childDirectory('cache'); | 
|  | final File packagesFile = flutterProject.directory | 
|  | .childFile('.packages'); | 
|  | if (packagesFile.existsSync()) { | 
|  | packagesFile.deleteSync(); | 
|  | } | 
|  | packagesFile.createSync(recursive: true); | 
|  |  | 
|  | for (final MapEntry<String, String> entry in plugins.entries) { | 
|  | final String name = fs.path.basename(entry.key); | 
|  | final Directory pluginDirectory = fakePubCache.childDirectory(name); | 
|  | packagesFile.writeAsStringSync( | 
|  | '$name:file://${pluginDirectory.childFile('lib').uri}\n', | 
|  | mode: FileMode.writeOnlyAppend, | 
|  | ); | 
|  | pluginDirectory.childFile('pubspec.yaml') | 
|  | ..createSync(recursive: true) | 
|  | ..writeAsStringSync(entry.value); | 
|  | } | 
|  | flutterManifest.dependencies = plugins.keys.toSet(); | 
|  | } | 
|  |  | 
|  | class FakeFlutterManifest extends Fake implements FlutterManifest { | 
|  | @override | 
|  | Set<String> dependencies = <String>{}; | 
|  | } | 
|  |  | 
|  | class FakeFlutterProject extends Fake implements FlutterProject { | 
|  | @override | 
|  | bool isModule = false; | 
|  |  | 
|  | @override | 
|  | late FlutterManifest manifest; | 
|  |  | 
|  | @override | 
|  | late Directory directory; | 
|  |  | 
|  | @override | 
|  | late File flutterPluginsFile; | 
|  |  | 
|  | @override | 
|  | late File flutterPluginsDependenciesFile; | 
|  |  | 
|  | @override | 
|  | late File dartPluginRegistrant; | 
|  |  | 
|  | @override | 
|  | late IosProject ios; | 
|  |  | 
|  | @override | 
|  | late AndroidProject android; | 
|  |  | 
|  | @override | 
|  | late WebProject web; | 
|  |  | 
|  | @override | 
|  | late MacOSProject macos; | 
|  |  | 
|  | @override | 
|  | late LinuxProject linux; | 
|  |  | 
|  | @override | 
|  | late WindowsProject windows; | 
|  | } |