blob: 1528828d3895962e64e57108ea52669eebbcc88e [file] [log] [blame]
// Copyright 2019 The Chromium 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/features.dart';
import 'package:flutter_tools/src/ios/xcodeproj.dart';
import 'package:flutter_tools/src/plugins.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:mockito/mockito.dart';
import '../src/common.dart';
import '../src/context.dart';
void main() {
FileSystem fs;
MockFlutterProject flutterProject;
MockIosProject iosProject;
MockMacOSProject macosProject;
MockAndroidProject androidProject;
File packagesFile;
Directory dummyPackageDirectory;
setUp(() async {
fs = MemoryFileSystem();
// Add basic properties to the Flutter project and subprojects
flutterProject = MockFlutterProject();
when(flutterProject.directory).thenReturn(fs.directory('/'));
when(flutterProject.flutterPluginsFile).thenReturn(flutterProject.directory.childFile('.plugins'));
iosProject = MockIosProject();
when(flutterProject.ios).thenReturn(iosProject);
when(iosProject.pluginRegistrantHost).thenReturn(flutterProject.directory.childDirectory('Runner'));
when(iosProject.podfile).thenReturn(flutterProject.directory.childDirectory('ios').childFile('Podfile'));
when(iosProject.podManifestLock).thenReturn(flutterProject.directory.childDirectory('ios').childFile('Podfile.lock'));
macosProject = MockMacOSProject();
when(flutterProject.macos).thenReturn(macosProject);
when(macosProject.podfile).thenReturn(flutterProject.directory.childDirectory('macos').childFile('Podfile'));
when(macosProject.podManifestLock).thenReturn(flutterProject.directory.childDirectory('macos').childFile('Podfile.lock'));
androidProject = MockAndroidProject();
when(flutterProject.android).thenReturn(androidProject);
when(androidProject.pluginRegistrantHost).thenReturn(flutterProject.directory.childDirectory('android').childDirectory('app'));
when(androidProject.hostAppGradleRoot).thenReturn(flutterProject.directory.childDirectory('android'));
// Set up a simple .packages file for all the tests to use, pointing to one package.
dummyPackageDirectory = fs.directory('/pubcache/apackage/lib/');
packagesFile = fs.file(fs.path.join(flutterProject.directory.path, PackageMap.globalPackagesPath));
packagesFile..createSync(recursive: true)
..writeAsStringSync('apackage:file://${dummyPackageDirectory.path}');
});
// Makes the dummy package pointed to by packagesFile look like a plugin.
void configureDummyPackageAsPlugin() {
dummyPackageDirectory.parent.childFile('pubspec.yaml')..createSync(recursive: true)..writeAsStringSync('''
flutter:
plugin:
platforms:
ios:
pluginClass: FLESomePlugin
''');
}
// Creates the files that would indicate that pod install has run for the
// given project.
void simulatePodInstallRun(XcodeBasedProject project) {
project.podManifestLock.createSync(recursive: true);
}
group('refreshPlugins', () {
testUsingContext('Refreshing the plugin list is a no-op when the plugins list stays empty', () {
refreshPluginsList(flutterProject);
expect(flutterProject.flutterPluginsFile.existsSync(), false);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
});
testUsingContext('Refreshing the plugin list deletes the plugin file when there were plugins but no longer are', () {
flutterProject.flutterPluginsFile.createSync();
when(iosProject.existsSync()).thenReturn(false);
when(macosProject.existsSync()).thenReturn(false);
refreshPluginsList(flutterProject);
expect(flutterProject.flutterPluginsFile.existsSync(), false);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
});
testUsingContext('Refreshing the plugin list creates a plugin directory when there are plugins', () {
configureDummyPackageAsPlugin();
when(iosProject.existsSync()).thenReturn(false);
when(macosProject.existsSync()).thenReturn(false);
refreshPluginsList(flutterProject);
expect(flutterProject.flutterPluginsFile.existsSync(), true);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
});
testUsingContext('Changes to the plugin list invalidates the Cocoapod lockfiles', () {
simulatePodInstallRun(iosProject);
simulatePodInstallRun(macosProject);
configureDummyPackageAsPlugin();
when(iosProject.existsSync()).thenReturn(true);
when(macosProject.existsSync()).thenReturn(true);
refreshPluginsList(flutterProject);
expect(iosProject.podManifestLock.existsSync(), false);
expect(macosProject.podManifestLock.existsSync(), false);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
});
});
group('injectPlugins', () {
MockFeatureFlags featureFlags;
MockXcodeProjectInterpreter xcodeProjectInterpreter;
const String kAndroidManifestUsingOldEmbedding = '''
<manifest>
<application>
</application>
</manifest>
''';
const String kAndroidManifestUsingNewEmbedding = '''
<manifest>
<application>
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>
''';
setUp(() {
featureFlags = MockFeatureFlags();
when(featureFlags.isLinuxEnabled).thenReturn(false);
when(featureFlags.isMacOSEnabled).thenReturn(false);
when(featureFlags.isWindowsEnabled).thenReturn(false);
when(featureFlags.isWebEnabled).thenReturn(false);
xcodeProjectInterpreter = MockXcodeProjectInterpreter();
when(xcodeProjectInterpreter.isInstalled).thenReturn(false);
});
testUsingContext('Registrant uses old embedding in app project', () async {
when(flutterProject.isModule).thenReturn(false);
when(featureFlags.isNewAndroidEmbeddingEnabled).thenReturn(false);
await injectPlugins(flutterProject);
final File registrant = flutterProject.directory
.childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'))
.childFile('GeneratedPluginRegistrant.java');
expect(registrant.existsSync(), isTrue);
expect(registrant.readAsStringSync(), contains('package io.flutter.plugins'));
expect(registrant.readAsStringSync(), contains('class GeneratedPluginRegistrant'));
}, overrides: <Type, Generator>{
FileSystem: () => fs,
FeatureFlags: () => featureFlags,
});
testUsingContext('Registrant uses new embedding if app uses new embedding', () async {
when(flutterProject.isModule).thenReturn(false);
when(featureFlags.isNewAndroidEmbeddingEnabled).thenReturn(true);
final File androidManifest = flutterProject.directory
.childDirectory('android')
.childFile('AndroidManifest.xml')
..createSync(recursive: true)
..writeAsStringSync(kAndroidManifestUsingNewEmbedding);
when(androidProject.appManifestFile).thenReturn(androidManifest);
await injectPlugins(flutterProject);
final File registrant = flutterProject.directory
.childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'dev', 'flutter', 'plugins'))
.childFile('GeneratedPluginRegistrant.java');
expect(registrant.existsSync(), isTrue);
expect(registrant.readAsStringSync(), contains('package dev.flutter.plugins'));
expect(registrant.readAsStringSync(), contains('class GeneratedPluginRegistrant'));
}, overrides: <Type, Generator>{
FileSystem: () => fs,
FeatureFlags: () => featureFlags,
});
testUsingContext('Registrant uses shim for plugins using old embedding if app uses new embedding', () async {
when(flutterProject.isModule).thenReturn(false);
when(featureFlags.isNewAndroidEmbeddingEnabled).thenReturn(true);
final File androidManifest = flutterProject.directory
.childDirectory('android')
.childFile('AndroidManifest.xml')
..createSync(recursive: true)
..writeAsStringSync(kAndroidManifestUsingNewEmbedding);
when(androidProject.appManifestFile).thenReturn(androidManifest);
final Directory pluginUsingJavaAndNewEmbeddingDir =
fs.systemTempDirectory.createTempSync('pluginUsingJavaAndNewEmbeddingDir.');
pluginUsingJavaAndNewEmbeddingDir
.childFile('pubspec.yaml')
.writeAsStringSync('''
flutter:
plugin:
androidPackage: plugin1
pluginClass: UseNewEmbedding
''');
pluginUsingJavaAndNewEmbeddingDir
.childDirectory('android')
.childDirectory('src')
.childDirectory('main')
.childDirectory('java')
.childDirectory('plugin1')
.childFile('UseNewEmbedding.java')
..createSync(recursive: true)
..writeAsStringSync('import io.flutter.embedding.engine.plugins.FlutterPlugin;');
final Directory pluginUsingKotlinAndNewEmbeddingDir =
fs.systemTempDirectory.createTempSync('pluginUsingKotlinAndNewEmbeddingDir.');
pluginUsingKotlinAndNewEmbeddingDir
.childFile('pubspec.yaml')
.writeAsStringSync('''
flutter:
plugin:
androidPackage: plugin2
pluginClass: UseNewEmbedding
''');
pluginUsingKotlinAndNewEmbeddingDir
.childDirectory('android')
.childDirectory('src')
.childDirectory('main')
.childDirectory('kotlin')
.childDirectory('plugin2')
.childFile('UseNewEmbedding.kt')
..createSync(recursive: true)
..writeAsStringSync('import io.flutter.embedding.engine.plugins.FlutterPlugin');
final Directory pluginUsingOldEmbeddingDir =
fs.systemTempDirectory.createTempSync('pluginUsingOldEmbeddingDir.');
pluginUsingOldEmbeddingDir
.childFile('pubspec.yaml')
.writeAsStringSync('''
flutter:
plugin:
androidPackage: plugin3
pluginClass: UseOldEmbedding
''');
pluginUsingOldEmbeddingDir
.childDirectory('android')
.childDirectory('src')
.childDirectory('main')
.childDirectory('java')
.childDirectory('plugin3')
.childFile('UseOldEmbedding.java')
..createSync(recursive: true);
flutterProject.directory
.childFile('.packages')
.writeAsStringSync('''
plugin1:${pluginUsingJavaAndNewEmbeddingDir.childDirectory('lib').uri.toString()}
plugin2:${pluginUsingKotlinAndNewEmbeddingDir.childDirectory('lib').uri.toString()}
plugin3:${pluginUsingOldEmbeddingDir.childDirectory('lib').uri.toString()}
''');
await injectPlugins(flutterProject);
final File registrant = flutterProject.directory
.childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'dev', 'flutter', 'plugins'))
.childFile('GeneratedPluginRegistrant.java');
expect(registrant.readAsStringSync(),
contains('flutterEngine.getPlugins().add(new plugin1.UseNewEmbedding());'));
expect(registrant.readAsStringSync(),
contains('flutterEngine.getPlugins().add(new plugin2.UseNewEmbedding());'));
expect(registrant.readAsStringSync(),
contains('plugin3.UseOldEmbedding.registerWith(shimPluginRegistry.registrarFor("plugin3.UseOldEmbedding"));'));
}, overrides: <Type, Generator>{
FileSystem: () => fs,
FeatureFlags: () => featureFlags,
XcodeProjectInterpreter: () => xcodeProjectInterpreter,
});
testUsingContext('Registrant doesn\'t use new embedding if app doesn\'t use new embedding', () async {
when(flutterProject.isModule).thenReturn(false);
when(featureFlags.isNewAndroidEmbeddingEnabled).thenReturn(true);
final File androidManifest = flutterProject.directory
.childDirectory('android')
.childFile('AndroidManifest.xml')
..createSync(recursive: true)
..writeAsStringSync(kAndroidManifestUsingOldEmbedding);
when(androidProject.appManifestFile).thenReturn(androidManifest);
await injectPlugins(flutterProject);
final File registrant = flutterProject.directory
.childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'))
.childFile('GeneratedPluginRegistrant.java');
expect(registrant.existsSync(), isTrue);
expect(registrant.readAsStringSync(), contains('package io.flutter.plugins'));
expect(registrant.readAsStringSync(), contains('class GeneratedPluginRegistrant'));
}, overrides: <Type, Generator>{
FileSystem: () => fs,
FeatureFlags: () => featureFlags,
});
testUsingContext('Registrant uses old embedding in module project', () async {
when(flutterProject.isModule).thenReturn(true);
when(featureFlags.isNewAndroidEmbeddingEnabled).thenReturn(false);
await injectPlugins(flutterProject);
final File registrant = flutterProject.directory
.childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'))
.childFile('GeneratedPluginRegistrant.java');
expect(registrant.existsSync(), isTrue);
expect(registrant.readAsStringSync(), contains('package io.flutter.plugins'));
expect(registrant.readAsStringSync(), contains('class GeneratedPluginRegistrant'));
}, overrides: <Type, Generator>{
FileSystem: () => fs,
FeatureFlags: () => featureFlags,
});
testUsingContext('Registrant uses new embedding if module uses new embedding', () async {
when(flutterProject.isModule).thenReturn(true);
when(featureFlags.isNewAndroidEmbeddingEnabled).thenReturn(true);
final File androidManifest = flutterProject.directory
.childDirectory('android')
.childFile('AndroidManifest.xml')
..createSync(recursive: true)
..writeAsStringSync(kAndroidManifestUsingNewEmbedding);
when(androidProject.appManifestFile).thenReturn(androidManifest);
await injectPlugins(flutterProject);
final File registrant = flutterProject.directory
.childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'dev', 'flutter', 'plugins'))
.childFile('GeneratedPluginRegistrant.java');
expect(registrant.existsSync(), isTrue);
expect(registrant.readAsStringSync(), contains('package dev.flutter.plugins'));
expect(registrant.readAsStringSync(), contains('class GeneratedPluginRegistrant'));
}, overrides: <Type, Generator>{
FileSystem: () => fs,
FeatureFlags: () => featureFlags,
});
testUsingContext('Registrant doesn\'t use new embedding if module doesn\'t use new embedding', () async {
when(flutterProject.isModule).thenReturn(true);
when(featureFlags.isNewAndroidEmbeddingEnabled).thenReturn(true);
final File androidManifest = flutterProject.directory
.childDirectory('android')
.childFile('AndroidManifest.xml')
..createSync(recursive: true)
..writeAsStringSync(kAndroidManifestUsingOldEmbedding);
when(androidProject.appManifestFile).thenReturn(androidManifest);
await injectPlugins(flutterProject);
final File registrant = flutterProject.directory
.childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'))
.childFile('GeneratedPluginRegistrant.java');
expect(registrant.existsSync(), isTrue);
expect(registrant.readAsStringSync(), contains('package io.flutter.plugins'));
expect(registrant.readAsStringSync(), contains('class GeneratedPluginRegistrant'));
}, overrides: <Type, Generator>{
FileSystem: () => fs,
FeatureFlags: () => featureFlags,
});
});
}
class MockAndroidProject extends Mock implements AndroidProject {}
class MockFeatureFlags extends Mock implements FeatureFlags {}
class MockFlutterProject extends Mock implements FlutterProject {}
class MockIosProject extends Mock implements IosProject {}
class MockMacOSProject extends Mock implements MacOSProject {}
class MockXcodeProjectInterpreter extends Mock implements XcodeProjectInterpreter {}