blob: 50bb3db00c59a6777f30abb946d9137a2b13b7fe [file] [log] [blame]
Ian Hickson449f4a62019-11-27 15:04:02 -08001// Copyright 2014 The Flutter Authors. All rights reserved.
stuartmorganc32e5432019-09-18 21:42:57 -07002// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Francisco Magdalenofcf341e2020-01-17 14:43:34 -08005import 'dart:convert';
6
stuartmorganc32e5432019-09-18 21:42:57 -07007import 'package:file/file.dart';
8import 'package:file/memory.dart';
Francisco Magdalenofcf341e2020-01-17 14:43:34 -08009import 'package:flutter_tools/src/base/time.dart';
stuartmorganc32e5432019-09-18 21:42:57 -070010import 'package:flutter_tools/src/dart/package_map.dart';
Emmanuel Garcia5961bcc2019-10-04 06:23:03 -070011import 'package:flutter_tools/src/features.dart';
12import 'package:flutter_tools/src/ios/xcodeproj.dart';
stuartmorganc32e5432019-09-18 21:42:57 -070013import 'package:flutter_tools/src/plugins.dart';
14import 'package:flutter_tools/src/project.dart';
Francisco Magdalenofcf341e2020-01-17 14:43:34 -080015import 'package:flutter_tools/src/version.dart';
Emmanuel Garciab6e92002019-11-22 15:02:20 -080016import 'package:meta/meta.dart';
stuartmorganc32e5432019-09-18 21:42:57 -070017import 'package:mockito/mockito.dart';
18
19import '../src/common.dart';
20import '../src/context.dart';
21
stuartmorganc32e5432019-09-18 21:42:57 -070022void main() {
Emmanuel Garcia7e60a652019-10-29 16:05:13 -070023 group('plugins', () {
24 FileSystem fs;
25 MockFlutterProject flutterProject;
26 MockIosProject iosProject;
27 MockMacOSProject macosProject;
28 MockAndroidProject androidProject;
29 MockWebProject webProject;
Francisco Magdalenofcf341e2020-01-17 14:43:34 -080030 MockWindowsProject windowsProject;
31 MockLinuxProject linuxProject;
Emmanuel Garcia7e60a652019-10-29 16:05:13 -070032 File packagesFile;
33 Directory dummyPackageDirectory;
Francisco Magdalenofcf341e2020-01-17 14:43:34 -080034 SystemClock mockClock;
35 FlutterVersion mockVersion;
stuartmorganc32e5432019-09-18 21:42:57 -070036
Emmanuel Garcia7e60a652019-10-29 16:05:13 -070037 setUp(() async {
38 fs = MemoryFileSystem();
Francisco Magdalenofcf341e2020-01-17 14:43:34 -080039 mockClock = MockClock();
40 mockVersion = MockFlutterVersion();
stuartmorganc32e5432019-09-18 21:42:57 -070041
Emmanuel Garcia7e60a652019-10-29 16:05:13 -070042 // Add basic properties to the Flutter project and subprojects
43 flutterProject = MockFlutterProject();
44 when(flutterProject.directory).thenReturn(fs.directory('/'));
Francisco Magdalenofcf341e2020-01-17 14:43:34 -080045 // TODO(franciscojma): Remove logic for .flutter-plugins it's deprecated.
Emmanuel Garciab6e92002019-11-22 15:02:20 -080046 when(flutterProject.flutterPluginsFile).thenReturn(flutterProject.directory.childFile('.flutter-plugins'));
47 when(flutterProject.flutterPluginsDependenciesFile).thenReturn(flutterProject.directory.childFile('.flutter-plugins-dependencies'));
Emmanuel Garcia7e60a652019-10-29 16:05:13 -070048 iosProject = MockIosProject();
49 when(flutterProject.ios).thenReturn(iosProject);
50 when(iosProject.pluginRegistrantHost).thenReturn(flutterProject.directory.childDirectory('Runner'));
51 when(iosProject.podfile).thenReturn(flutterProject.directory.childDirectory('ios').childFile('Podfile'));
52 when(iosProject.podManifestLock).thenReturn(flutterProject.directory.childDirectory('ios').childFile('Podfile.lock'));
Francisco Magdalenofcf341e2020-01-17 14:43:34 -080053 when(iosProject.pluginConfigKey).thenReturn('ios');
54 when(iosProject.existsSync()).thenReturn(false);
Emmanuel Garcia7e60a652019-10-29 16:05:13 -070055 macosProject = MockMacOSProject();
56 when(flutterProject.macos).thenReturn(macosProject);
57 when(macosProject.podfile).thenReturn(flutterProject.directory.childDirectory('macos').childFile('Podfile'));
58 when(macosProject.podManifestLock).thenReturn(flutterProject.directory.childDirectory('macos').childFile('Podfile.lock'));
Francisco Magdalenofcf341e2020-01-17 14:43:34 -080059 when(macosProject.pluginConfigKey).thenReturn('macos');
60 when(macosProject.existsSync()).thenReturn(false);
Emmanuel Garcia7e60a652019-10-29 16:05:13 -070061 androidProject = MockAndroidProject();
62 when(flutterProject.android).thenReturn(androidProject);
63 when(androidProject.pluginRegistrantHost).thenReturn(flutterProject.directory.childDirectory('android').childDirectory('app'));
64 when(androidProject.hostAppGradleRoot).thenReturn(flutterProject.directory.childDirectory('android'));
Francisco Magdalenofcf341e2020-01-17 14:43:34 -080065 when(androidProject.pluginConfigKey).thenReturn('android');
66 when(androidProject.existsSync()).thenReturn(false);
Emmanuel Garcia7e60a652019-10-29 16:05:13 -070067 webProject = MockWebProject();
68 when(flutterProject.web).thenReturn(webProject);
69 when(webProject.libDirectory).thenReturn(flutterProject.directory.childDirectory('lib'));
70 when(webProject.existsSync()).thenReturn(true);
Francisco Magdalenofcf341e2020-01-17 14:43:34 -080071 when(webProject.pluginConfigKey).thenReturn('web');
72 when(webProject.existsSync()).thenReturn(false);
73 windowsProject = MockWindowsProject();
74 when(flutterProject.windows).thenReturn(windowsProject);
75 when(windowsProject.pluginConfigKey).thenReturn('windows');
stuartmorganf4177a62020-02-13 12:53:28 -080076 final Directory windowsManagedDirectory = flutterProject.directory.childDirectory('windows').childDirectory('flutter');
77 when(windowsProject.managedDirectory).thenReturn(windowsManagedDirectory);
78 when(windowsProject.vcprojFile).thenReturn(windowsManagedDirectory.parent.childFile('Runner.vcxproj'));
stuartmorgan22c80772020-02-25 14:16:27 -080079 when(windowsProject.solutionFile).thenReturn(windowsManagedDirectory.parent.childFile('Runner.sln'));
stuartmorganf4177a62020-02-13 12:53:28 -080080 when(windowsProject.pluginSymlinkDirectory).thenReturn(windowsManagedDirectory.childDirectory('ephemeral').childDirectory('.plugin_symlinks'));
81 when(windowsProject.generatedPluginPropertySheetFile).thenReturn(windowsManagedDirectory.childFile('GeneratedPlugins.props'));
Francisco Magdalenofcf341e2020-01-17 14:43:34 -080082 when(windowsProject.existsSync()).thenReturn(false);
83 linuxProject = MockLinuxProject();
84 when(flutterProject.linux).thenReturn(linuxProject);
85 when(linuxProject.pluginConfigKey).thenReturn('linux');
stuartmorgandd2756c2020-02-27 09:45:22 -080086 final Directory linuxManagedDirectory = flutterProject.directory.childDirectory('linux').childDirectory('flutter');
87 final Directory linuxEphemeralDirectory = linuxManagedDirectory.childDirectory('ephemeral');
88 when(linuxProject.managedDirectory).thenReturn(linuxManagedDirectory);
89 when(linuxProject.ephemeralDirectory).thenReturn(linuxEphemeralDirectory);
90 when(linuxProject.pluginSymlinkDirectory).thenReturn(linuxEphemeralDirectory.childDirectory('.plugin_symlinks'));
91 when(linuxProject.makeFile).thenReturn(linuxManagedDirectory.parent.childFile('Makefile'));
92 when(linuxProject.generatedPluginMakeFile).thenReturn(linuxManagedDirectory.childFile('generated_plugins.mk'));
Francisco Magdalenofcf341e2020-01-17 14:43:34 -080093 when(linuxProject.existsSync()).thenReturn(false);
94
95 when(mockClock.now()).thenAnswer(
96 (Invocation _) => DateTime(1970, 1, 1)
97 );
98 when(mockVersion.frameworkVersion).thenAnswer(
99 (Invocation _) => '1.0.0'
100 );
stuartmorganc32e5432019-09-18 21:42:57 -0700101
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700102 // Set up a simple .packages file for all the tests to use, pointing to one package.
103 dummyPackageDirectory = fs.directory('/pubcache/apackage/lib/');
104 packagesFile = fs.file(fs.path.join(flutterProject.directory.path, PackageMap.globalPackagesPath));
105 packagesFile..createSync(recursive: true)
xster9e0df252019-11-11 15:56:43 -0800106 ..writeAsStringSync('apackage:file://${dummyPackageDirectory.path}\n');
stuartmorganc32e5432019-09-18 21:42:57 -0700107 });
108
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700109 // Makes the dummy package pointed to by packagesFile look like a plugin.
110 void configureDummyPackageAsPlugin() {
111 dummyPackageDirectory.parent.childFile('pubspec.yaml')..createSync(recursive: true)..writeAsStringSync('''
112 flutter:
113 plugin:
114 platforms:
115 ios:
116 pluginClass: FLESomePlugin
Francisco Magdalenofcf341e2020-01-17 14:43:34 -0800117 macos:
118 pluginClass: FLESomePlugin
119 windows:
stuartmorganf4177a62020-02-13 12:53:28 -0800120 pluginClass: SomePlugin
Francisco Magdalenofcf341e2020-01-17 14:43:34 -0800121 linux:
stuartmorganf4177a62020-02-13 12:53:28 -0800122 pluginClass: SomePlugin
Francisco Magdalenofcf341e2020-01-17 14:43:34 -0800123 web:
124 pluginClass: SomePlugin
125 fileName: lib/SomeFile.dart
126 android:
127 pluginClass: SomePlugin
128 package: AndroidPackage
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700129 ''');
130 }
131
xster9e0df252019-11-11 15:56:43 -0800132
133 void createNewJavaPlugin1() {
134 final Directory pluginUsingJavaAndNewEmbeddingDir =
135 fs.systemTempDirectory.createTempSync('flutter_plugin_using_java_and_new_embedding_dir.');
136 pluginUsingJavaAndNewEmbeddingDir
137 .childFile('pubspec.yaml')
138 .writeAsStringSync('''
139flutter:
140 plugin:
141 androidPackage: plugin1
142 pluginClass: UseNewEmbedding
143 ''');
144 pluginUsingJavaAndNewEmbeddingDir
145 .childDirectory('android')
146 .childDirectory('src')
147 .childDirectory('main')
148 .childDirectory('java')
149 .childDirectory('plugin1')
150 .childFile('UseNewEmbedding.java')
151 ..createSync(recursive: true)
152 ..writeAsStringSync('import io.flutter.embedding.engine.plugins.FlutterPlugin;');
153
154 flutterProject.directory
155 .childFile('.packages')
156 .writeAsStringSync(
157 'plugin1:${pluginUsingJavaAndNewEmbeddingDir.childDirectory('lib').uri.toString()}\n',
158 mode: FileMode.append,
159 );
160 }
161
Emmanuel Garcia466681c2020-01-28 17:48:26 -0800162 void createPluginWithInvalidAndroidPackage() {
163 final Directory pluginUsingJavaAndNewEmbeddingDir =
164 fs.systemTempDirectory.createTempSync('flutter_plugin_invalid_package.');
165 pluginUsingJavaAndNewEmbeddingDir
166 .childFile('pubspec.yaml')
167 .writeAsStringSync('''
168flutter:
169 plugin:
170 androidPackage: plugin1.invalid
171 pluginClass: UseNewEmbedding
172 ''');
173 pluginUsingJavaAndNewEmbeddingDir
174 .childDirectory('android')
175 .childDirectory('src')
176 .childDirectory('main')
177 .childDirectory('java')
178 .childDirectory('plugin1')
179 .childDirectory('correct')
180 .childFile('UseNewEmbedding.java')
181 ..createSync(recursive: true)
182 ..writeAsStringSync('import io.flutter.embedding.engine.plugins.FlutterPlugin;');
183
184 flutterProject.directory
185 .childFile('.packages')
186 .writeAsStringSync(
187 'plugin1:${pluginUsingJavaAndNewEmbeddingDir.childDirectory('lib').uri.toString()}\n',
188 mode: FileMode.append,
189 );
190 }
191
xster9e0df252019-11-11 15:56:43 -0800192 void createNewKotlinPlugin2() {
193 final Directory pluginUsingKotlinAndNewEmbeddingDir =
194 fs.systemTempDirectory.createTempSync('flutter_plugin_using_kotlin_and_new_embedding_dir.');
195 pluginUsingKotlinAndNewEmbeddingDir
196 .childFile('pubspec.yaml')
197 .writeAsStringSync('''
198flutter:
199 plugin:
200 androidPackage: plugin2
201 pluginClass: UseNewEmbedding
202 ''');
203 pluginUsingKotlinAndNewEmbeddingDir
204 .childDirectory('android')
205 .childDirectory('src')
206 .childDirectory('main')
207 .childDirectory('kotlin')
208 .childDirectory('plugin2')
209 .childFile('UseNewEmbedding.kt')
210 ..createSync(recursive: true)
211 ..writeAsStringSync('import io.flutter.embedding.engine.plugins.FlutterPlugin');
212
213 flutterProject.directory
214 .childFile('.packages')
215 .writeAsStringSync(
216 'plugin2:${pluginUsingKotlinAndNewEmbeddingDir.childDirectory('lib').uri.toString()}\n',
217 mode: FileMode.append,
218 );
219 }
220
xster9eb9ea02020-01-13 21:30:42 -0800221 void createOldJavaPlugin(String pluginName) {
xster9e0df252019-11-11 15:56:43 -0800222 final Directory pluginUsingOldEmbeddingDir =
223 fs.systemTempDirectory.createTempSync('flutter_plugin_using_old_embedding_dir.');
224 pluginUsingOldEmbeddingDir
225 .childFile('pubspec.yaml')
226 .writeAsStringSync('''
227flutter:
228 plugin:
xster9eb9ea02020-01-13 21:30:42 -0800229 androidPackage: $pluginName
xster9e0df252019-11-11 15:56:43 -0800230 pluginClass: UseOldEmbedding
231 ''');
232 pluginUsingOldEmbeddingDir
233 .childDirectory('android')
234 .childDirectory('src')
235 .childDirectory('main')
236 .childDirectory('java')
xster9eb9ea02020-01-13 21:30:42 -0800237 .childDirectory(pluginName)
xster9e0df252019-11-11 15:56:43 -0800238 .childFile('UseOldEmbedding.java')
239 ..createSync(recursive: true);
240
241 flutterProject.directory
242 .childFile('.packages')
243 .writeAsStringSync(
xster9eb9ea02020-01-13 21:30:42 -0800244 '$pluginName:${pluginUsingOldEmbeddingDir.childDirectory('lib').uri.toString()}\n',
xster9e0df252019-11-11 15:56:43 -0800245 mode: FileMode.append,
246 );
247 }
248
249 void createDualSupportJavaPlugin4() {
250 final Directory pluginUsingJavaAndNewEmbeddingDir =
251 fs.systemTempDirectory.createTempSync('flutter_plugin_using_java_and_new_embedding_dir.');
252 pluginUsingJavaAndNewEmbeddingDir
253 .childFile('pubspec.yaml')
254 .writeAsStringSync('''
255flutter:
256 plugin:
257 androidPackage: plugin4
258 pluginClass: UseBothEmbedding
259''');
260 pluginUsingJavaAndNewEmbeddingDir
261 .childDirectory('android')
262 .childDirectory('src')
263 .childDirectory('main')
264 .childDirectory('java')
265 .childDirectory('plugin4')
266 .childFile('UseBothEmbedding.java')
267 ..createSync(recursive: true)
268 ..writeAsStringSync(
269 'import io.flutter.embedding.engine.plugins.FlutterPlugin;\n'
270 'PluginRegistry\n'
271 'registerWith(Irrelevant registrar)\n'
272 );
273
274 flutterProject.directory
275 .childFile('.packages')
276 .writeAsStringSync(
277 'plugin4:${pluginUsingJavaAndNewEmbeddingDir.childDirectory('lib').uri.toString()}',
278 mode: FileMode.append,
279 );
280 }
281
Emmanuel Garciab6e92002019-11-22 15:02:20 -0800282 void createPluginWithDependencies({
283 @required String name,
284 @required List<String> dependencies,
285 }) {
286 assert(name != null);
287 assert(dependencies != null);
288
289 final Directory pluginDirectory = fs.systemTempDirectory.createTempSync('plugin.');
290 pluginDirectory
291 .childFile('pubspec.yaml')
292 .writeAsStringSync('''
293name: $name
294flutter:
295 plugin:
296 androidPackage: plugin2
297 pluginClass: UseNewEmbedding
298dependencies:
299''');
Alexandre Ardhuin4f9b6cf2020-01-07 16:32:04 +0100300 for (final String dependency in dependencies) {
Emmanuel Garciab6e92002019-11-22 15:02:20 -0800301 pluginDirectory
302 .childFile('pubspec.yaml')
303 .writeAsStringSync(' $dependency:\n', mode: FileMode.append);
304 }
305 flutterProject.directory
306 .childFile('.packages')
307 .writeAsStringSync(
308 '$name:${pluginDirectory.childDirectory('lib').uri.toString()}\n',
309 mode: FileMode.append,
310 );
311 }
312
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700313 // Creates the files that would indicate that pod install has run for the
314 // given project.
315 void simulatePodInstallRun(XcodeBasedProject project) {
316 project.podManifestLock.createSync(recursive: true);
317 }
318
stuartmorgan22c80772020-02-25 14:16:27 -0800319 // Creates a Windows solution file sufficient to allow plugin injection
320 // to run without failing.
321 void createDummyWindowsSolutionFile() {
322 windowsProject.solutionFile.createSync(recursive: true);
323 // This isn't a valid solution file, but it's just enough to work with the
324 // plugin injection.
325 windowsProject.solutionFile.writeAsStringSync('''
326Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Runner", "Runner.vcxproj", "{3842E94C-E348-463A-ADBE-625A2B69B628}"
327 ProjectSection(ProjectDependencies) = postProject
328 {6419BF13-6ECD-4CD2-9E85-E566A1F03F8F} = {6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}
329 EndProjectSection
330EndProject
331Global
332 GlobalSection(ProjectConfigurationPlatforms) = postSolution
333 EndGlobalSection
334EndGlobal''');
335 }
336
337 // Creates a Windows project file for dummyPackageDirectory sufficient to
338 // allow plugin injection to run without failing.
339 void createDummyPluginWindowsProjectFile() {
340 final File projectFile = dummyPackageDirectory
341 .parent
342 .childDirectory('windows')
343 .childFile('plugin.vcxproj');
344 projectFile.createSync(recursive: true);
345 // This isn't a valid project file, but it's just enough to work with the
346 // plugin injection.
347 projectFile.writeAsStringSync('''
348<?xml version="1.0" encoding="utf-8"?>
349<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
350 <PropertyGroup Label="Globals">
351 <ProjectGuid>{5919689F-A5D5-462C-AF50-D405CCEF89B8}</ProjectGuid>'}
352 <ProjectName>apackage</ProjectName>
353 </PropertyGroup>
354</Project>''');
355 }
356
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700357 group('refreshPlugins', () {
358 testUsingContext('Refreshing the plugin list is a no-op when the plugins list stays empty', () {
359 refreshPluginsList(flutterProject);
360 expect(flutterProject.flutterPluginsFile.existsSync(), false);
Emmanuel Garciab6e92002019-11-22 15:02:20 -0800361 expect(flutterProject.flutterPluginsDependenciesFile.existsSync(), false);
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700362 }, overrides: <Type, Generator>{
363 FileSystem: () => fs,
364 ProcessManager: () => FakeProcessManager.any(),
365 });
366
367 testUsingContext('Refreshing the plugin list deletes the plugin file when there were plugins but no longer are', () {
368 flutterProject.flutterPluginsFile.createSync();
Francisco Magdalenofcf341e2020-01-17 14:43:34 -0800369 flutterProject.flutterPluginsDependenciesFile.createSync();
370
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700371 refreshPluginsList(flutterProject);
372 expect(flutterProject.flutterPluginsFile.existsSync(), false);
Emmanuel Garciab6e92002019-11-22 15:02:20 -0800373 expect(flutterProject.flutterPluginsDependenciesFile.existsSync(), false);
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700374 }, overrides: <Type, Generator>{
375 FileSystem: () => fs,
376 ProcessManager: () => FakeProcessManager.any(),
377 });
378
379 testUsingContext('Refreshing the plugin list creates a plugin directory when there are plugins', () {
380 configureDummyPackageAsPlugin();
Francisco Magdalenofcf341e2020-01-17 14:43:34 -0800381 when(iosProject.existsSync()).thenReturn(true);
382
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700383 refreshPluginsList(flutterProject);
384 expect(flutterProject.flutterPluginsFile.existsSync(), true);
Emmanuel Garciab6e92002019-11-22 15:02:20 -0800385 expect(flutterProject.flutterPluginsDependenciesFile.existsSync(), true);
386 }, overrides: <Type, Generator>{
387 FileSystem: () => fs,
388 ProcessManager: () => FakeProcessManager.any(),
389 });
390
391 testUsingContext('Refreshing the plugin list modifies .flutter-plugins and .flutter-plugins-dependencies when there are plugins', () {
392 createPluginWithDependencies(name: 'plugin-a', dependencies: const <String>['plugin-b', 'plugin-c', 'random-package']);
393 createPluginWithDependencies(name: 'plugin-b', dependencies: const <String>['plugin-c']);
394 createPluginWithDependencies(name: 'plugin-c', dependencies: const <String>[]);
Francisco Magdalenofcf341e2020-01-17 14:43:34 -0800395 when(iosProject.existsSync()).thenReturn(true);
396
397 final DateTime dateCreated = DateTime(1970, 1, 1);
398 when(mockClock.now()).thenAnswer(
399 (Invocation _) => dateCreated
400 );
401 const String version = '1.0.0';
402 when(mockVersion.frameworkVersion).thenAnswer(
403 (Invocation _) => version
404 );
Emmanuel Garciab6e92002019-11-22 15:02:20 -0800405
406 refreshPluginsList(flutterProject);
407
Francisco Magdalenofcf341e2020-01-17 14:43:34 -0800408 // Verify .flutter-plugins-dependencies is configured correctly.
Emmanuel Garciab6e92002019-11-22 15:02:20 -0800409 expect(flutterProject.flutterPluginsFile.existsSync(), true);
410 expect(flutterProject.flutterPluginsDependenciesFile.existsSync(), true);
411 expect(flutterProject.flutterPluginsFile.readAsStringSync(),
Michael Klimushyn901eb0f2019-11-25 11:47:20 -0800412 '# This is a generated file; do not edit or check into version control.\n'
Emmanuel Garciab6e92002019-11-22 15:02:20 -0800413 'plugin-a=/.tmp_rand0/plugin.rand0/\n'
414 'plugin-b=/.tmp_rand0/plugin.rand1/\n'
415 'plugin-c=/.tmp_rand0/plugin.rand2/\n'
416 ''
417 );
Francisco Magdalenofcf341e2020-01-17 14:43:34 -0800418
419 final String pluginsString = flutterProject.flutterPluginsDependenciesFile.readAsStringSync();
420 final Map<String, dynamic> jsonContent = json.decode(pluginsString) as Map<String, dynamic>;
421 expect(jsonContent['info'], 'This is a generated file; do not edit or check into version control.');
422
423 final Map<String, dynamic> plugins = jsonContent['plugins'] as Map<String, dynamic>;
424 final List<dynamic> expectedPlugins = <dynamic>[
425 <String, dynamic> {
426 'name': 'plugin-a',
427 'path': '/.tmp_rand0/plugin.rand0/',
428 'dependencies': <String>[
429 'plugin-b',
430 'plugin-c'
431 ]
432 },
433 <String, dynamic> {
434 'name': 'plugin-b',
435 'path': '/.tmp_rand0/plugin.rand1/',
436 'dependencies': <String>[
437 'plugin-c'
438 ]
439 },
440 <String, dynamic> {
441 'name': 'plugin-c',
442 'path': '/.tmp_rand0/plugin.rand2/',
443 'dependencies': <String>[]
444 },
445 ];
446 expect(plugins['ios'], expectedPlugins);
447 expect(plugins['android'], expectedPlugins);
448 expect(plugins['macos'], <dynamic>[]);
449 expect(plugins['windows'], <dynamic>[]);
450 expect(plugins['linux'], <dynamic>[]);
451 expect(plugins['web'], <dynamic>[]);
452
453 final List<dynamic> expectedDependencyGraph = <dynamic>[
454 <String, dynamic> {
455 'name': 'plugin-a',
456 'dependencies': <String>[
457 'plugin-b',
458 'plugin-c'
459 ]
460 },
461 <String, dynamic> {
462 'name': 'plugin-b',
463 'dependencies': <String>[
464 'plugin-c'
465 ]
466 },
467 <String, dynamic> {
468 'name': 'plugin-c',
469 'dependencies': <String>[]
470 },
471 ];
472
473 expect(jsonContent['dependencyGraph'], expectedDependencyGraph);
474 expect(jsonContent['date_created'], dateCreated.toString());
475 expect(jsonContent['version'], version);
476
477 // Make sure tests are updated if a new object is added/removed.
478 final List<String> expectedKeys = <String>[
479 'info',
480 'plugins',
481 'dependencyGraph',
482 'date_created',
483 'version',
484 ];
485 expect(jsonContent.keys, expectedKeys);
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700486 }, overrides: <Type, Generator>{
487 FileSystem: () => fs,
488 ProcessManager: () => FakeProcessManager.any(),
Francisco Magdalenofcf341e2020-01-17 14:43:34 -0800489 SystemClock: () => mockClock,
490 FlutterVersion: () => mockVersion
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700491 });
492
493 testUsingContext('Changes to the plugin list invalidates the Cocoapod lockfiles', () {
494 simulatePodInstallRun(iosProject);
495 simulatePodInstallRun(macosProject);
496 configureDummyPackageAsPlugin();
497 when(iosProject.existsSync()).thenReturn(true);
498 when(macosProject.existsSync()).thenReturn(true);
499 refreshPluginsList(flutterProject);
500 expect(iosProject.podManifestLock.existsSync(), false);
501 expect(macosProject.podManifestLock.existsSync(), false);
502 }, overrides: <Type, Generator>{
503 FileSystem: () => fs,
504 ProcessManager: () => FakeProcessManager.any(),
Francisco Magdalenofcf341e2020-01-17 14:43:34 -0800505 SystemClock: () => mockClock,
506 FlutterVersion: () => mockVersion
507 });
508
509 testUsingContext('No changes to the plugin list does not invalidate the Cocoapod lockfiles', () {
510 configureDummyPackageAsPlugin();
511 when(iosProject.existsSync()).thenReturn(true);
512 when(macosProject.existsSync()).thenReturn(true);
513
514 // First call will create the .flutter-plugins-dependencies and the legacy .flutter-plugins file.
515 // Since there was no plugins list, the lock files will be invalidated.
516 // The second call is where the plugins list is compared to the existing one, and if there is no change,
517 // the podfiles shouldn't be invalidated.
518 refreshPluginsList(flutterProject);
519 simulatePodInstallRun(iosProject);
520 simulatePodInstallRun(macosProject);
521
522 refreshPluginsList(flutterProject);
523 expect(iosProject.podManifestLock.existsSync(), true);
524 expect(macosProject.podManifestLock.existsSync(), true);
525 }, overrides: <Type, Generator>{
526 FileSystem: () => fs,
527 ProcessManager: () => FakeProcessManager.any(),
528 SystemClock: () => mockClock,
529 FlutterVersion: () => mockVersion
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700530 });
stuartmorganc32e5432019-09-18 21:42:57 -0700531 });
532
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700533 group('injectPlugins', () {
534 MockFeatureFlags featureFlags;
535 MockXcodeProjectInterpreter xcodeProjectInterpreter;
stuartmorganc32e5432019-09-18 21:42:57 -0700536
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700537 setUp(() {
538 featureFlags = MockFeatureFlags();
539 when(featureFlags.isLinuxEnabled).thenReturn(false);
540 when(featureFlags.isMacOSEnabled).thenReturn(false);
541 when(featureFlags.isWindowsEnabled).thenReturn(false);
542 when(featureFlags.isWebEnabled).thenReturn(false);
Emmanuel Garcia5961bcc2019-10-04 06:23:03 -0700543
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700544 xcodeProjectInterpreter = MockXcodeProjectInterpreter();
545 when(xcodeProjectInterpreter.isInstalled).thenReturn(false);
546 });
Emmanuel Garcia5961bcc2019-10-04 06:23:03 -0700547
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700548 testUsingContext('Registrant uses old embedding in app project', () async {
549 when(flutterProject.isModule).thenReturn(false);
xsterba26f922019-11-05 12:38:42 -0800550 when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v1);
Emmanuel Garcia5961bcc2019-10-04 06:23:03 -0700551
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700552 await injectPlugins(flutterProject);
Emmanuel Garcia0a93f4e2019-10-16 12:47:04 -0700553
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700554 final File registrant = flutterProject.directory
555 .childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'))
556 .childFile('GeneratedPluginRegistrant.java');
Emmanuel Garcia5961bcc2019-10-04 06:23:03 -0700557
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700558 expect(registrant.existsSync(), isTrue);
559 expect(registrant.readAsStringSync(), contains('package io.flutter.plugins'));
560 expect(registrant.readAsStringSync(), contains('class GeneratedPluginRegistrant'));
xster9e0df252019-11-11 15:56:43 -0800561 expect(registrant.readAsStringSync(), contains('public static void registerWith(PluginRegistry registry)'));
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700562 }, overrides: <Type, Generator>{
563 FileSystem: () => fs,
564 ProcessManager: () => FakeProcessManager.any(),
565 FeatureFlags: () => featureFlags,
566 });
Emmanuel Garcia5961bcc2019-10-04 06:23:03 -0700567
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700568 testUsingContext('Registrant uses new embedding if app uses new embedding', () async {
569 when(flutterProject.isModule).thenReturn(false);
xsterba26f922019-11-05 12:38:42 -0800570 when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v2);
Emmanuel Garcia5961bcc2019-10-04 06:23:03 -0700571
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700572 await injectPlugins(flutterProject);
Emmanuel Garcia5961bcc2019-10-04 06:23:03 -0700573
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700574 final File registrant = flutterProject.directory
Emmanuel Garciaade8dfa2019-11-01 16:58:26 -0700575 .childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'))
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700576 .childFile('GeneratedPluginRegistrant.java');
Emmanuel Garcia5961bcc2019-10-04 06:23:03 -0700577
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700578 expect(registrant.existsSync(), isTrue);
Emmanuel Garciaade8dfa2019-11-01 16:58:26 -0700579 expect(registrant.readAsStringSync(), contains('package io.flutter.plugins'));
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700580 expect(registrant.readAsStringSync(), contains('class GeneratedPluginRegistrant'));
xster9e0df252019-11-11 15:56:43 -0800581 expect(registrant.readAsStringSync(), contains('public static void registerWith(@NonNull FlutterEngine flutterEngine)'));
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700582 }, overrides: <Type, Generator>{
583 FileSystem: () => fs,
584 ProcessManager: () => FakeProcessManager.any(),
585 FeatureFlags: () => featureFlags,
586 });
Emmanuel Garcia5961bcc2019-10-04 06:23:03 -0700587
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700588 testUsingContext('Registrant uses shim for plugins using old embedding if app uses new embedding', () async {
589 when(flutterProject.isModule).thenReturn(false);
xsterba26f922019-11-05 12:38:42 -0800590 when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v2);
Emmanuel Garcia5961bcc2019-10-04 06:23:03 -0700591
xster9e0df252019-11-11 15:56:43 -0800592 createNewJavaPlugin1();
593 createNewKotlinPlugin2();
xster9eb9ea02020-01-13 21:30:42 -0800594 createOldJavaPlugin('plugin3');
Emmanuel Garcia5961bcc2019-10-04 06:23:03 -0700595
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700596 await injectPlugins(flutterProject);
Emmanuel Garcia5961bcc2019-10-04 06:23:03 -0700597
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700598 final File registrant = flutterProject.directory
Emmanuel Garciaade8dfa2019-11-01 16:58:26 -0700599 .childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'))
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700600 .childFile('GeneratedPluginRegistrant.java');
Emmanuel Garcia5961bcc2019-10-04 06:23:03 -0700601
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700602 expect(registrant.readAsStringSync(),
603 contains('flutterEngine.getPlugins().add(new plugin1.UseNewEmbedding());'));
604 expect(registrant.readAsStringSync(),
605 contains('flutterEngine.getPlugins().add(new plugin2.UseNewEmbedding());'));
606 expect(registrant.readAsStringSync(),
607 contains('plugin3.UseOldEmbedding.registerWith(shimPluginRegistry.registrarFor("plugin3.UseOldEmbedding"));'));
Emmanuel Garcia5961bcc2019-10-04 06:23:03 -0700608
xster9e0df252019-11-11 15:56:43 -0800609 // There should be no warning message
610 expect(testLogger.statusText, isNot(contains('go/android-plugin-migration')));
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700611 }, overrides: <Type, Generator>{
612 FileSystem: () => fs,
613 ProcessManager: () => FakeProcessManager.any(),
614 FeatureFlags: () => featureFlags,
615 XcodeProjectInterpreter: () => xcodeProjectInterpreter,
616 });
Emmanuel Garcia5961bcc2019-10-04 06:23:03 -0700617
Emmanuel Garciacb614d12019-11-07 08:24:19 -0800618 testUsingContext('exits the tool if an app uses the v1 embedding and a plugin only supports the v2 embedding', () async {
619 when(flutterProject.isModule).thenReturn(false);
620 when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v1);
621
xster9e0df252019-11-11 15:56:43 -0800622 createNewJavaPlugin1();
Emmanuel Garciacb614d12019-11-07 08:24:19 -0800623
Emmanuel Garciacb614d12019-11-07 08:24:19 -0800624 await expectLater(
625 () async {
626 await injectPlugins(flutterProject);
627 },
628 throwsToolExit(
629 message: 'The plugin `plugin1` requires your app to be migrated to the Android embedding v2. '
630 'Follow the steps on https://flutter.dev/go/android-project-migration and re-run this command.'
631 ),
632 );
633 }, overrides: <Type, Generator>{
634 FileSystem: () => fs,
635 ProcessManager: () => FakeProcessManager.any(),
636 FeatureFlags: () => featureFlags,
637 XcodeProjectInterpreter: () => xcodeProjectInterpreter,
638 });
639
Emmanuel Garcia466681c2020-01-28 17:48:26 -0800640 // Issue: https://github.com/flutter/flutter/issues/47803
641 testUsingContext('exits the tool if a plugin sets an invalid android package in pubspec.yaml', () async {
642 when(flutterProject.isModule).thenReturn(false);
643 when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v1);
644
645 createPluginWithInvalidAndroidPackage();
646
647 await expectLater(
648 () async {
649 await injectPlugins(flutterProject);
650 },
651 throwsToolExit(
Alexandre Ardhuinf15c8872020-02-11 20:58:27 +0100652 message: "The plugin `plugin1` doesn't have a main class defined in "
Emmanuel Garcia466681c2020-01-28 17:48:26 -0800653 '/.tmp_rand2/flutter_plugin_invalid_package.rand2/android/src/main/java/plugin1/invalid/UseNewEmbedding.java or '
654 '/.tmp_rand2/flutter_plugin_invalid_package.rand2/android/src/main/kotlin/plugin1/invalid/UseNewEmbedding.kt. '
Alexandre Ardhuinf15c8872020-02-11 20:58:27 +0100655 "This is likely to due to an incorrect `androidPackage: plugin1.invalid` or `mainClass` entry in the plugin's pubspec.yaml.\n"
Emmanuel Garcia466681c2020-01-28 17:48:26 -0800656 'If you are the author of this plugin, fix the `androidPackage` entry or move the main class to any of locations used above. '
657 'Otherwise, please contact the author of this plugin and consider using a different plugin in the meanwhile.',
658 ),
659 );
660 }, overrides: <Type, Generator>{
661 FileSystem: () => fs,
662 ProcessManager: () => FakeProcessManager.any(),
663 FeatureFlags: () => featureFlags,
664 XcodeProjectInterpreter: () => xcodeProjectInterpreter,
665 });
666
xster9e0df252019-11-11 15:56:43 -0800667 testUsingContext('old embedding app uses a plugin that supports v1 and v2 embedding', () async {
Emmanuel Garciacb614d12019-11-07 08:24:19 -0800668 when(flutterProject.isModule).thenReturn(false);
669 when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v1);
670
xster9e0df252019-11-11 15:56:43 -0800671 createDualSupportJavaPlugin4();
Emmanuel Garciacb614d12019-11-07 08:24:19 -0800672
Emmanuel Garciacb614d12019-11-07 08:24:19 -0800673 await injectPlugins(flutterProject);
674
675 final File registrant = flutterProject.directory
676 .childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'))
677 .childFile('GeneratedPluginRegistrant.java');
678
679 expect(registrant.existsSync(), isTrue);
680 expect(registrant.readAsStringSync(), contains('package io.flutter.plugins'));
681 expect(registrant.readAsStringSync(), contains('class GeneratedPluginRegistrant'));
xster9e0df252019-11-11 15:56:43 -0800682 expect(registrant.readAsStringSync(),
683 contains('UseBothEmbedding.registerWith(registry.registrarFor("plugin4.UseBothEmbedding"));'));
Emmanuel Garciacb614d12019-11-07 08:24:19 -0800684 }, overrides: <Type, Generator>{
685 FileSystem: () => fs,
686 ProcessManager: () => FakeProcessManager.any(),
687 FeatureFlags: () => featureFlags,
688 XcodeProjectInterpreter: () => xcodeProjectInterpreter,
689 });
690
xster9e0df252019-11-11 15:56:43 -0800691 testUsingContext('new embedding app uses a plugin that supports v1 and v2 embedding', () async {
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700692 when(flutterProject.isModule).thenReturn(false);
xster9e0df252019-11-11 15:56:43 -0800693 when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v2);
694
695 createDualSupportJavaPlugin4();
Emmanuel Garcia5961bcc2019-10-04 06:23:03 -0700696
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700697 await injectPlugins(flutterProject);
Emmanuel Garcia5961bcc2019-10-04 06:23:03 -0700698
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700699 final File registrant = flutterProject.directory
700 .childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'))
701 .childFile('GeneratedPluginRegistrant.java');
Emmanuel Garcia5961bcc2019-10-04 06:23:03 -0700702
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700703 expect(registrant.existsSync(), isTrue);
704 expect(registrant.readAsStringSync(), contains('package io.flutter.plugins'));
705 expect(registrant.readAsStringSync(), contains('class GeneratedPluginRegistrant'));
xster9e0df252019-11-11 15:56:43 -0800706 expect(registrant.readAsStringSync(),
707 contains('flutterEngine.getPlugins().add(new plugin4.UseBothEmbedding());'));
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700708 }, overrides: <Type, Generator>{
709 FileSystem: () => fs,
710 ProcessManager: () => FakeProcessManager.any(),
711 FeatureFlags: () => featureFlags,
xster9e0df252019-11-11 15:56:43 -0800712 XcodeProjectInterpreter: () => xcodeProjectInterpreter,
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700713 });
Emmanuel Garcia5961bcc2019-10-04 06:23:03 -0700714
xster9e0df252019-11-11 15:56:43 -0800715 testUsingContext('Modules use new embedding', () async {
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700716 when(flutterProject.isModule).thenReturn(true);
xsterba26f922019-11-05 12:38:42 -0800717 when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v2);
Emmanuel Garcia5961bcc2019-10-04 06:23:03 -0700718
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700719 await injectPlugins(flutterProject);
Emmanuel Garcia5961bcc2019-10-04 06:23:03 -0700720
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700721 final File registrant = flutterProject.directory
Emmanuel Garciaade8dfa2019-11-01 16:58:26 -0700722 .childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'))
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700723 .childFile('GeneratedPluginRegistrant.java');
Emmanuel Garcia5961bcc2019-10-04 06:23:03 -0700724
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700725 expect(registrant.existsSync(), isTrue);
Emmanuel Garciaade8dfa2019-11-01 16:58:26 -0700726 expect(registrant.readAsStringSync(), contains('package io.flutter.plugins'));
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700727 expect(registrant.readAsStringSync(), contains('class GeneratedPluginRegistrant'));
xster9e0df252019-11-11 15:56:43 -0800728 expect(registrant.readAsStringSync(), contains('public static void registerWith(@NonNull FlutterEngine flutterEngine)'));
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700729 }, overrides: <Type, Generator>{
730 FileSystem: () => fs,
731 ProcessManager: () => FakeProcessManager.any(),
732 FeatureFlags: () => featureFlags,
733 });
Emmanuel Garcia5961bcc2019-10-04 06:23:03 -0700734
xster9e0df252019-11-11 15:56:43 -0800735 testUsingContext('Module using old plugin shows warning', () async {
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700736 when(flutterProject.isModule).thenReturn(true);
xster9e0df252019-11-11 15:56:43 -0800737 when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v2);
738
xster9eb9ea02020-01-13 21:30:42 -0800739 createOldJavaPlugin('plugin3');
Emmanuel Garcia5961bcc2019-10-04 06:23:03 -0700740
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700741 await injectPlugins(flutterProject);
Emmanuel Garcia5961bcc2019-10-04 06:23:03 -0700742
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700743 final File registrant = flutterProject.directory
744 .childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'))
745 .childFile('GeneratedPluginRegistrant.java');
xster9e0df252019-11-11 15:56:43 -0800746 expect(registrant.readAsStringSync(),
747 contains('plugin3.UseOldEmbedding.registerWith(shimPluginRegistry.registrarFor("plugin3.UseOldEmbedding"));'));
748 expect(testLogger.statusText, contains('The plugin `plugin3` is built using an older version of the Android plugin API'));
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700749 }, overrides: <Type, Generator>{
750 FileSystem: () => fs,
751 ProcessManager: () => FakeProcessManager.any(),
752 FeatureFlags: () => featureFlags,
xster9e0df252019-11-11 15:56:43 -0800753 XcodeProjectInterpreter: () => xcodeProjectInterpreter,
754 });
755
756 testUsingContext('Module using new plugin shows no warnings', () async {
757 when(flutterProject.isModule).thenReturn(true);
758 when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v2);
759
760 createNewJavaPlugin1();
761
762 await injectPlugins(flutterProject);
763
764 final File registrant = flutterProject.directory
765 .childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'))
766 .childFile('GeneratedPluginRegistrant.java');
767 expect(registrant.readAsStringSync(),
768 contains('flutterEngine.getPlugins().add(new plugin1.UseNewEmbedding());'));
769
770 expect(testLogger.statusText, isNot(contains('go/android-plugin-migration')));
771 }, overrides: <Type, Generator>{
772 FileSystem: () => fs,
773 ProcessManager: () => FakeProcessManager.any(),
774 FeatureFlags: () => featureFlags,
775 XcodeProjectInterpreter: () => xcodeProjectInterpreter,
776 });
777
778 testUsingContext('Module using plugin with v1 and v2 support shows no warning', () async {
779 when(flutterProject.isModule).thenReturn(true);
780 when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v2);
781
782 createDualSupportJavaPlugin4();
783
784 await injectPlugins(flutterProject);
785
786 final File registrant = flutterProject.directory
787 .childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'))
788 .childFile('GeneratedPluginRegistrant.java');
789 expect(registrant.readAsStringSync(),
790 contains('flutterEngine.getPlugins().add(new plugin4.UseBothEmbedding());'));
791
792 expect(testLogger.statusText, isNot(contains('go/android-plugin-migration')));
793 }, overrides: <Type, Generator>{
794 FileSystem: () => fs,
795 ProcessManager: () => FakeProcessManager.any(),
796 FeatureFlags: () => featureFlags,
797 XcodeProjectInterpreter: () => xcodeProjectInterpreter,
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700798 });
Harry Terkelseneba69ca2019-10-25 15:00:05 -0700799
xster9eb9ea02020-01-13 21:30:42 -0800800 testUsingContext('Module using multiple old plugins all show warnings', () async {
801 when(flutterProject.isModule).thenReturn(true);
802 when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v2);
803
804 createOldJavaPlugin('plugin3');
805 createOldJavaPlugin('plugin4');
806
807 await injectPlugins(flutterProject);
808
809 final File registrant = flutterProject.directory
810 .childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'))
811 .childFile('GeneratedPluginRegistrant.java');
812 expect(registrant.readAsStringSync(),
813 contains('plugin3.UseOldEmbedding.registerWith(shimPluginRegistry.registrarFor("plugin3.UseOldEmbedding"));'));
814 expect(registrant.readAsStringSync(),
815 contains('plugin4.UseOldEmbedding.registerWith(shimPluginRegistry.registrarFor("plugin4.UseOldEmbedding"));'));
816 expect(testLogger.statusText, contains('The plugin `plugin3` is built using an older version of the Android plugin API'));
817 expect(testLogger.statusText, contains('The plugin `plugin4` is built using an older version of the Android plugin API'));
818 }, overrides: <Type, Generator>{
819 FileSystem: () => fs,
820 ProcessManager: () => FakeProcessManager.any(),
821 FeatureFlags: () => featureFlags,
822 XcodeProjectInterpreter: () => xcodeProjectInterpreter,
823 });
824
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700825 testUsingContext('Does not throw when AndroidManifest.xml is not found', () async {
826 when(flutterProject.isModule).thenReturn(false);
Harry Terkelseneba69ca2019-10-25 15:00:05 -0700827
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700828 final File manifest = MockFile();
829 when(manifest.existsSync()).thenReturn(false);
830 when(androidProject.appManifestFile).thenReturn(manifest);
Harry Terkelseneba69ca2019-10-25 15:00:05 -0700831
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700832 await injectPlugins(flutterProject);
Harry Terkelseneba69ca2019-10-25 15:00:05 -0700833
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700834 }, overrides: <Type, Generator>{
835 FileSystem: () => fs,
836 ProcessManager: () => FakeProcessManager.any(),
837 });
838
Alexandre Ardhuinf15c8872020-02-11 20:58:27 +0100839 testUsingContext("Registrant for web doesn't escape slashes in imports", () async {
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700840 when(flutterProject.isModule).thenReturn(true);
841 when(featureFlags.isWebEnabled).thenReturn(true);
Francisco Magdalenofcf341e2020-01-17 14:43:34 -0800842 when(webProject.existsSync()).thenReturn(true);
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700843
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700844 final Directory webPluginWithNestedFile =
845 fs.systemTempDirectory.createTempSync('web_plugin_with_nested');
846 webPluginWithNestedFile.childFile('pubspec.yaml').writeAsStringSync('''
847 flutter:
848 plugin:
849 platforms:
850 web:
851 pluginClass: WebPlugin
852 fileName: src/web_plugin.dart
853 ''');
854 webPluginWithNestedFile
855 .childDirectory('lib')
856 .childDirectory('src')
857 .childFile('web_plugin.dart')
858 ..createSync(recursive: true);
859
860 flutterProject.directory
861 .childFile('.packages')
862 .writeAsStringSync('''
Harry Terkelseneba69ca2019-10-25 15:00:05 -0700863web_plugin_with_nested:${webPluginWithNestedFile.childDirectory('lib').uri.toString()}
864''');
865
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700866 await injectPlugins(flutterProject);
Harry Terkelseneba69ca2019-10-25 15:00:05 -0700867
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700868 final File registrant = flutterProject.directory
869 .childDirectory('lib')
870 .childFile('generated_plugin_registrant.dart');
Harry Terkelseneba69ca2019-10-25 15:00:05 -0700871
Emmanuel Garcia7e60a652019-10-29 16:05:13 -0700872 expect(registrant.existsSync(), isTrue);
873 expect(registrant.readAsStringSync(), contains("import 'package:web_plugin_with_nested/src/web_plugin.dart';"));
874 }, overrides: <Type, Generator>{
875 FileSystem: () => fs,
876 ProcessManager: () => FakeProcessManager.any(),
877 FeatureFlags: () => featureFlags,
878 });
stuartmorganf4177a62020-02-13 12:53:28 -0800879
stuartmorgandd2756c2020-02-27 09:45:22 -0800880 testUsingContext('Injecting creates generated Linux registrant', () async {
881 when(linuxProject.existsSync()).thenReturn(true);
882 when(featureFlags.isLinuxEnabled).thenReturn(true);
883 when(flutterProject.isModule).thenReturn(false);
884 configureDummyPackageAsPlugin();
885
886 await injectPlugins(flutterProject, checkProjects: true);
887
888 final File registrantHeader = linuxProject.managedDirectory.childFile('generated_plugin_registrant.h');
889 final File registrantImpl = linuxProject.managedDirectory.childFile('generated_plugin_registrant.cc');
890
891 expect(registrantHeader.existsSync(), isTrue);
892 expect(registrantImpl.existsSync(), isTrue);
893 expect(registrantImpl.readAsStringSync(), contains('SomePluginRegisterWithRegistrar'));
894 }, overrides: <Type, Generator>{
895 FileSystem: () => fs,
896 ProcessManager: () => FakeProcessManager.any(),
897 FeatureFlags: () => featureFlags,
898 });
899
900 testUsingContext('Injecting creates generated Linux plugin makefile', () async {
901 when(linuxProject.existsSync()).thenReturn(true);
902 when(featureFlags.isLinuxEnabled).thenReturn(true);
903 when(flutterProject.isModule).thenReturn(false);
904 configureDummyPackageAsPlugin();
905
906 await injectPlugins(flutterProject, checkProjects: true);
907
908 final File pluginMakefile = linuxProject.generatedPluginMakeFile;
909
910 expect(pluginMakefile.existsSync(), isTrue);
911 final String contents = pluginMakefile.readAsStringSync();
912 expect(contents, contains('libapackage_plugin.so'));
913 // Verify all the variables the app-level Makefile rely on.
914 expect(contents, contains('PLUGIN_TARGETS='));
915 expect(contents, contains('PLUGIN_LIBRARIES='));
916 expect(contents, contains('PLUGIN_LDFLAGS='));
917 expect(contents, contains('PLUGIN_CPPFLAGS='));
918 }, overrides: <Type, Generator>{
919 FileSystem: () => fs,
920 ProcessManager: () => FakeProcessManager.any(),
921 FeatureFlags: () => featureFlags,
922 });
923
924
stuartmorganf4177a62020-02-13 12:53:28 -0800925 testUsingContext('Injecting creates generated Windows registrant', () async {
926 when(windowsProject.existsSync()).thenReturn(true);
927 when(featureFlags.isWindowsEnabled).thenReturn(true);
928 when(flutterProject.isModule).thenReturn(false);
929 configureDummyPackageAsPlugin();
stuartmorgan22c80772020-02-25 14:16:27 -0800930 createDummyWindowsSolutionFile();
931 createDummyPluginWindowsProjectFile();
stuartmorganf4177a62020-02-13 12:53:28 -0800932
933 await injectPlugins(flutterProject, checkProjects: true);
934
935 final File registrantHeader = windowsProject.managedDirectory.childFile('generated_plugin_registrant.h');
936 final File registrantImpl = windowsProject.managedDirectory.childFile('generated_plugin_registrant.cc');
937
938 expect(registrantHeader.existsSync(), isTrue);
939 expect(registrantImpl.existsSync(), isTrue);
940 expect(registrantImpl.readAsStringSync(), contains('SomePluginRegisterWithRegistrar'));
941 }, overrides: <Type, Generator>{
942 FileSystem: () => fs,
943 ProcessManager: () => FakeProcessManager.any(),
944 FeatureFlags: () => featureFlags,
945 });
946
947 testUsingContext('Injecting creates generated Windows plugin properties', () async {
948 when(windowsProject.existsSync()).thenReturn(true);
949 when(featureFlags.isWindowsEnabled).thenReturn(true);
950 when(flutterProject.isModule).thenReturn(false);
951 configureDummyPackageAsPlugin();
stuartmorgan22c80772020-02-25 14:16:27 -0800952 createDummyWindowsSolutionFile();
953 createDummyPluginWindowsProjectFile();
stuartmorganf4177a62020-02-13 12:53:28 -0800954
955 await injectPlugins(flutterProject, checkProjects: true);
956
957 final File properties = windowsProject.generatedPluginPropertySheetFile;
958 final String includePath = fs.path.join('flutter', 'ephemeral', '.plugin_symlinks', 'apackage', 'windows');
959
960 expect(properties.existsSync(), isTrue);
961 expect(properties.readAsStringSync(), contains('apackage_plugin.lib'));
962 expect(properties.readAsStringSync(), contains('>$includePath;'));
963 }, overrides: <Type, Generator>{
964 FileSystem: () => fs,
965 ProcessManager: () => FakeProcessManager.any(),
966 FeatureFlags: () => featureFlags,
967 });
stuartmorgan22c80772020-02-25 14:16:27 -0800968
969 testUsingContext('Injecting updates Windows solution file', () async {
970 when(windowsProject.existsSync()).thenReturn(true);
971 when(featureFlags.isWindowsEnabled).thenReturn(true);
972 when(flutterProject.isModule).thenReturn(false);
973 configureDummyPackageAsPlugin();
974 createDummyWindowsSolutionFile();
975 createDummyPluginWindowsProjectFile();
976
977 await injectPlugins(flutterProject, checkProjects: true);
978
979 expect(windowsProject.solutionFile.readAsStringSync(), contains(r'apackage\windows\plugin.vcxproj'));
980 }, overrides: <Type, Generator>{
981 FileSystem: () => fs,
982 ProcessManager: () => FakeProcessManager.any(),
983 FeatureFlags: () => featureFlags,
984 });
Harry Terkelseneba69ca2019-10-25 15:00:05 -0700985 });
stuartmorgan7bdd4752020-02-12 16:23:27 -0800986
987 group('createPluginSymlinks', () {
988 MockFeatureFlags featureFlags;
989
990 setUp(() {
991 featureFlags = MockFeatureFlags();
992 when(featureFlags.isLinuxEnabled).thenReturn(true);
993 when(featureFlags.isWindowsEnabled).thenReturn(true);
994 });
995
996 testUsingContext('Symlinks are created for Linux plugins', () {
997 when(linuxProject.existsSync()).thenReturn(true);
998 configureDummyPackageAsPlugin();
999 // refreshPluginsList should call createPluginSymlinks.
1000 refreshPluginsList(flutterProject);
1001
1002 expect(linuxProject.pluginSymlinkDirectory.childLink('apackage').existsSync(), true);
1003 }, overrides: <Type, Generator>{
1004 FileSystem: () => fs,
1005 ProcessManager: () => FakeProcessManager.any(),
1006 FeatureFlags: () => featureFlags,
1007 });
1008
1009 testUsingContext('Symlinks are created for Windows plugins', () {
1010 when(windowsProject.existsSync()).thenReturn(true);
1011 configureDummyPackageAsPlugin();
1012 // refreshPluginsList should call createPluginSymlinks.
1013 refreshPluginsList(flutterProject);
1014
1015 expect(windowsProject.pluginSymlinkDirectory.childLink('apackage').existsSync(), true);
1016 }, overrides: <Type, Generator>{
1017 FileSystem: () => fs,
1018 ProcessManager: () => FakeProcessManager.any(),
1019 FeatureFlags: () => featureFlags,
1020 });
1021
1022 testUsingContext('Existing symlinks are removed when no longer in use with force', () {
1023 when(linuxProject.existsSync()).thenReturn(true);
1024 when(windowsProject.existsSync()).thenReturn(true);
1025
1026 final List<File> dummyFiles = <File>[
1027 flutterProject.linux.pluginSymlinkDirectory.childFile('dummy'),
1028 flutterProject.windows.pluginSymlinkDirectory.childFile('dummy'),
1029 ];
1030 for (final File file in dummyFiles) {
1031 file.createSync(recursive: true);
1032 }
1033
1034 createPluginSymlinks(flutterProject, force: true);
1035
1036 for (final File file in dummyFiles) {
1037 expect(file.existsSync(), false);
1038 }
1039 }, overrides: <Type, Generator>{
1040 FileSystem: () => fs,
1041 ProcessManager: () => FakeProcessManager.any(),
1042 FeatureFlags: () => featureFlags,
1043 });
1044
1045 testUsingContext('Existing symlinks are removed automatically on refresh when no longer in use', () {
1046 when(linuxProject.existsSync()).thenReturn(true);
1047 when(windowsProject.existsSync()).thenReturn(true);
1048
1049 final List<File> dummyFiles = <File>[
1050 flutterProject.linux.pluginSymlinkDirectory.childFile('dummy'),
1051 flutterProject.windows.pluginSymlinkDirectory.childFile('dummy'),
1052 ];
1053 for (final File file in dummyFiles) {
1054 file.createSync(recursive: true);
1055 }
1056
1057 // refreshPluginsList should remove existing links and recreate on changes.
1058 configureDummyPackageAsPlugin();
1059 refreshPluginsList(flutterProject);
1060
1061 for (final File file in dummyFiles) {
1062 expect(file.existsSync(), false);
1063 }
1064 }, overrides: <Type, Generator>{
1065 FileSystem: () => fs,
1066 ProcessManager: () => FakeProcessManager.any(),
1067 FeatureFlags: () => featureFlags,
1068 });
1069
1070 testUsingContext('createPluginSymlinks is a no-op without force when up to date', () {
1071 when(linuxProject.existsSync()).thenReturn(true);
1072 when(windowsProject.existsSync()).thenReturn(true);
1073
1074 final List<File> dummyFiles = <File>[
1075 flutterProject.linux.pluginSymlinkDirectory.childFile('dummy'),
1076 flutterProject.windows.pluginSymlinkDirectory.childFile('dummy'),
1077 ];
1078 for (final File file in dummyFiles) {
1079 file.createSync(recursive: true);
1080 }
1081
1082 // Without force, this should do nothing to existing files.
1083 createPluginSymlinks(flutterProject);
1084
1085 for (final File file in dummyFiles) {
1086 expect(file.existsSync(), true);
1087 }
1088 }, overrides: <Type, Generator>{
1089 FileSystem: () => fs,
1090 ProcessManager: () => FakeProcessManager.any(),
1091 FeatureFlags: () => featureFlags,
1092 });
1093
1094 testUsingContext('createPluginSymlinks repairs missing links', () {
1095 when(linuxProject.existsSync()).thenReturn(true);
1096 when(windowsProject.existsSync()).thenReturn(true);
1097 configureDummyPackageAsPlugin();
1098 refreshPluginsList(flutterProject);
1099
1100 final List<Link> links = <Link>[
1101 linuxProject.pluginSymlinkDirectory.childLink('apackage'),
1102 windowsProject.pluginSymlinkDirectory.childLink('apackage'),
1103 ];
1104 for (final Link link in links) {
1105 link.deleteSync();
1106 }
1107 createPluginSymlinks(flutterProject);
1108
1109 for (final Link link in links) {
1110 expect(link.existsSync(), true);
1111 }
1112 }, overrides: <Type, Generator>{
1113 FileSystem: () => fs,
1114 ProcessManager: () => FakeProcessManager.any(),
1115 FeatureFlags: () => featureFlags,
1116 });
1117 });
Emmanuel Garcia5961bcc2019-10-04 06:23:03 -07001118 });
stuartmorganc32e5432019-09-18 21:42:57 -07001119}
Emmanuel Garcia5961bcc2019-10-04 06:23:03 -07001120
1121class MockAndroidProject extends Mock implements AndroidProject {}
1122class MockFeatureFlags extends Mock implements FeatureFlags {}
1123class MockFlutterProject extends Mock implements FlutterProject {}
Emmanuel Garcia7e60a652019-10-29 16:05:13 -07001124class MockFile extends Mock implements File {}
Emmanuel Garcia466681c2020-01-28 17:48:26 -08001125class MockFileSystem extends Mock implements FileSystem {}
Emmanuel Garcia5961bcc2019-10-04 06:23:03 -07001126class MockIosProject extends Mock implements IosProject {}
1127class MockMacOSProject extends Mock implements MacOSProject {}
1128class MockXcodeProjectInterpreter extends Mock implements XcodeProjectInterpreter {}
Harry Terkelseneba69ca2019-10-25 15:00:05 -07001129class MockWebProject extends Mock implements WebProject {}
Francisco Magdalenofcf341e2020-01-17 14:43:34 -08001130class MockWindowsProject extends Mock implements WindowsProject {}
1131class MockLinuxProject extends Mock implements LinuxProject {}