Implement feature flag system for flutter tools (#36138)
diff --git a/packages/flutter_tools/lib/src/base/config.dart b/packages/flutter_tools/lib/src/base/config.dart
index b3a505d..0fef57d 100644
--- a/packages/flutter_tools/lib/src/base/config.dart
+++ b/packages/flutter_tools/lib/src/base/config.dart
@@ -27,7 +27,7 @@
dynamic getValue(String key) => _values[key];
- void setValue(String key, String value) {
+ void setValue(String key, Object value) {
_values[key] = value;
_flushValues();
}
diff --git a/packages/flutter_tools/lib/src/commands/config.dart b/packages/flutter_tools/lib/src/commands/config.dart
index bc28667..61a27e5 100644
--- a/packages/flutter_tools/lib/src/commands/config.dart
+++ b/packages/flutter_tools/lib/src/commands/config.dart
@@ -7,9 +7,11 @@
import '../android/android_sdk.dart';
import '../android/android_studio.dart';
import '../convert.dart';
+import '../features.dart';
import '../globals.dart';
import '../reporting/usage.dart';
import '../runner/flutter_command.dart';
+import '../version.dart';
class ConfigCommand extends FlutterCommand {
ConfigCommand({ bool verboseHelp = false }) {
@@ -26,6 +28,21 @@
negatable: false,
hide: !verboseHelp,
help: 'Print config values as json.');
+ for (Feature feature in allFeatures) {
+ if (feature.configSetting == null) {
+ continue;
+ }
+ argParser.addFlag(
+ feature.configSetting,
+ help: feature.generateHelpMessage(),
+ negatable: true,
+ );
+ }
+ argParser.addFlag(
+ 'clear-features',
+ help: 'Remove all configured features and restore them to the default values.',
+ negatable: false,
+ );
}
@override
@@ -45,14 +62,27 @@
bool get shouldUpdateCache => false;
@override
- Future<Set<DevelopmentArtifact>> get requiredArtifacts async => const <DevelopmentArtifact>{};
-
- @override
String get usageFooter {
- // List all config settings.
- String values = config.keys.map<String>((String key) {
- return ' $key: ${config.getValue(key)}';
- }).join('\n');
+ // List all config settings. for feature flags, include whether they
+ // are available.
+ final Map<String, Feature> featuresByName = <String, Feature>{};
+ final String channel = FlutterVersion.instance.channel;
+ for (Feature feature in allFeatures) {
+ if (feature.configSetting != null) {
+ featuresByName[feature.configSetting] = feature;
+ }
+ }
+ String values = config.keys
+ .map<String>((String key) {
+ String configFooter = '';
+ if (featuresByName.containsKey(key)) {
+ final FeatureChannelSetting setting = featuresByName[key].getSettingForChannel(channel);
+ if (!setting.available) {
+ configFooter = '(Unavailable)';
+ }
+ }
+ return ' $key: ${config.getValue(key)} $configFooter';
+ }).join('\n');
if (values.isEmpty)
values = ' No settings have been configured.';
return
@@ -71,6 +101,15 @@
return null;
}
+ if (argResults['clear-features']) {
+ for (Feature feature in allFeatures) {
+ if (feature.configSetting != null) {
+ config.removeValue(feature.configSetting);
+ }
+ }
+ return null;
+ }
+
if (argResults.wasParsed('analytics')) {
final bool value = argResults['analytics'];
flutterUsage.enabled = value;
@@ -89,6 +128,17 @@
if (argResults.wasParsed('clear-ios-signing-cert'))
_updateConfig('ios-signing-cert', '');
+ for (Feature feature in allFeatures) {
+ if (feature.configSetting == null) {
+ continue;
+ }
+ if (argResults.wasParsed(feature.configSetting)) {
+ final bool keyValue = argResults[feature.configSetting];
+ config.setValue(feature.configSetting, keyValue);
+ printStatus('Setting "${feature.configSetting}" value to "$keyValue".');
+ }
+ }
+
if (argResults.arguments.isEmpty)
printStatus(usage);
diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart
index 69fee62..0cbab98 100644
--- a/packages/flutter_tools/lib/src/context_runner.dart
+++ b/packages/flutter_tools/lib/src/context_runner.dart
@@ -28,6 +28,7 @@
import 'device.dart';
import 'doctor.dart';
import 'emulator.dart';
+import 'features.dart';
import 'fuchsia/fuchsia_device.dart' show FuchsiaDeviceTools;
import 'fuchsia/fuchsia_sdk.dart' show FuchsiaSdk, FuchsiaArtifacts;
import 'fuchsia/fuchsia_workflow.dart' show FuchsiaWorkflow;
@@ -79,6 +80,7 @@
Doctor: () => const Doctor(),
DoctorValidatorsProvider: () => DoctorValidatorsProvider.defaultInstance,
EmulatorManager: () => EmulatorManager(),
+ FeatureFlags: () => const FeatureFlags(),
Flags: () => const EmptyFlags(),
FlutterVersion: () => FlutterVersion(const SystemClock()),
FuchsiaArtifacts: () => FuchsiaArtifacts.find(),
diff --git a/packages/flutter_tools/lib/src/features.dart b/packages/flutter_tools/lib/src/features.dart
new file mode 100644
index 0000000..69157a8
--- /dev/null
+++ b/packages/flutter_tools/lib/src/features.dart
@@ -0,0 +1,220 @@
+// 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:meta/meta.dart';
+
+import 'base/config.dart';
+import 'base/context.dart';
+import 'base/platform.dart';
+import 'version.dart';
+
+/// The current [FeatureFlags] implementation.
+///
+/// If not injected, a default implementation is provided.
+FeatureFlags get featureFlags => context.get<FeatureFlags>();
+
+/// The interface used to determine if a particular [Feature] is enabled.
+///
+/// The rest of the tools code should use this class instead of looking up
+/// features directly. To faciliate rolls to google3 and other clients, all
+/// flags should be provided with a default implementation here. Clients that
+/// use this class should extent instead of implement, so that new flags are
+/// picked up automatically.
+class FeatureFlags {
+ const FeatureFlags();
+
+ /// Whether flutter desktop for linux is enabled.
+ bool get isLinuxEnabled => _isEnabled(flutterLinuxDesktopFeature);
+
+ /// Whether flutter desktop for macOS is enabled.
+ bool get isMacOSEnabled => _isEnabled(flutterMacOSDesktopFeature);
+
+ /// Whether flutter web is enabled.
+ bool get isWebEnabled => _isEnabled(flutterWebFeature);
+
+ /// Whether flutter desktop for Windows is enabled.
+ bool get isWindowsEnabled => _isEnabled(flutterWindowsDesktopFeature);
+
+ // Calculate whether a particular feature is enabled for the current channel.
+ static bool _isEnabled(Feature feature) {
+ final String currentChannel = FlutterVersion.instance.channel;
+ final FeatureChannelSetting featureSetting = feature.getSettingForChannel(currentChannel);
+ if (!featureSetting.available) {
+ return false;
+ }
+ bool isEnabled = featureSetting.enabledByDefault;
+ if (feature.configSetting != null) {
+ final bool configOverride = Config.instance.getValue(feature.configSetting);
+ if (configOverride != null) {
+ isEnabled = configOverride;
+ }
+ }
+ if (feature.environmentOverride != null) {
+ if (platform.environment[feature.environmentOverride]?.toLowerCase() == 'true') {
+ isEnabled = true;
+ }
+ }
+ return isEnabled;
+ }
+}
+
+/// All current Flutter feature flags.
+const List<Feature> allFeatures = <Feature>[
+ flutterWebFeature,
+ flutterLinuxDesktopFeature,
+ flutterMacOSDesktopFeature,
+ flutterWindowsDesktopFeature,
+];
+
+/// The [Feature] for flutter web.
+const Feature flutterWebFeature = Feature(
+ name: 'Flutter Web',
+ configSetting: 'enable-web',
+ environmentOverride: 'FLUTTER_WEB',
+ master: FeatureChannelSetting(
+ available: true,
+ enabledByDefault: false,
+ ),
+ dev: FeatureChannelSetting(
+ available: true,
+ enabledByDefault: false,
+ ),
+);
+
+/// The [Feature] for macOS desktop.
+const Feature flutterMacOSDesktopFeature = Feature(
+ name: 'Flutter Desktop for macOS',
+ configSetting: 'enable-macos-desktop',
+ environmentOverride: 'ENABLE_FLUTTER_DESKTOP',
+ master: FeatureChannelSetting(
+ available: true,
+ enabledByDefault: false,
+ ),
+);
+
+/// The [Feature] for Linux desktop.
+const Feature flutterLinuxDesktopFeature = Feature(
+ name: 'Flutter Desktop for Linux',
+ configSetting: 'enable-linux-desktop',
+ environmentOverride: 'ENABLE_FLUTTER_DESKTOP',
+ master: FeatureChannelSetting(
+ available: true,
+ enabledByDefault: false,
+ ),
+);
+
+/// The [Feature] for Windows desktop.
+const Feature flutterWindowsDesktopFeature = Feature(
+ name: 'Flutter Desktop for Windows',
+ configSetting: 'enable-windows-desktop',
+ environmentOverride: 'ENABLE_FLUTTER_DESKTOP',
+ master: FeatureChannelSetting(
+ available: true,
+ enabledByDefault: false,
+ ),
+);
+
+/// A [Feature] is a process for conditionally enabling tool features.
+///
+/// All settings are optional, and if not provided will generally default to
+/// a "safe" value, such as being off.
+///
+/// The top level feature settings can be provided to apply to all channels.
+/// Otherwise, more specific settings take precidence over higher level
+/// settings.
+class Feature {
+ /// Creates a [Feature].
+ const Feature({
+ @required this.name,
+ this.environmentOverride,
+ this.configSetting,
+ this.master = const FeatureChannelSetting(),
+ this.dev = const FeatureChannelSetting(),
+ this.beta = const FeatureChannelSetting(),
+ this.stable = const FeatureChannelSetting(),
+ });
+
+ /// The user visible name for this feature.
+ final String name;
+
+ /// The settings for the master branch and other unknown channels.
+ final FeatureChannelSetting master;
+
+ /// The settings for the dev branch.
+ final FeatureChannelSetting dev;
+
+ /// The settings for the beta branch.
+ final FeatureChannelSetting beta;
+
+ /// The settings for the stable branch.
+ final FeatureChannelSetting stable;
+
+ /// The name of an environment variable that can override the setting.
+ ///
+ /// The environment variable needs to be set to the value 'true'. This is
+ /// only intended for usage by CI and not as an advertised method to enable
+ /// a feature.
+ ///
+ /// If not provided, defaults to `null` meaning there is no override.
+ final String environmentOverride;
+
+ /// The name of a setting that can be used to enable this feature.
+ ///
+ /// If not provided, defaults to `null` meaning there is no config setting.
+ final String configSetting;
+
+ /// A help message for the `flutter config` command, or null if unsupported.
+ String generateHelpMessage() {
+ if (configSetting == null) {
+ return null;
+ }
+ final StringBuffer buffer = StringBuffer('Enable or disable $name on ');
+ final List<String> channels = <String>[
+ if (master.available) 'master',
+ if (dev.available) 'dev',
+ if (beta.available) 'beta',
+ if (stable.available) 'stable',
+ ];
+ if (channels.length == 1) {
+ buffer.write('the ${channels.single} channel.');
+ } else {
+ buffer.write('${channels.join(', ')} channels.');
+ }
+ return buffer.toString();
+ }
+
+ /// Retrieve the correct setting for the provided `channel`.
+ FeatureChannelSetting getSettingForChannel(String channel) {
+ switch (channel) {
+ case 'stable':
+ return stable;
+ case 'beta':
+ return beta;
+ case 'dev':
+ return dev;
+ case 'master':
+ default:
+ return master;
+ }
+ }
+}
+
+/// A description of the conditions to enable a feature for a particular channel.
+class FeatureChannelSetting {
+ const FeatureChannelSetting({
+ this.available = false,
+ this.enabledByDefault = false,
+ });
+
+ /// Whether the feature is available on this channel.
+ ///
+ /// If not provded, defaults to `false`. This implies that the feature
+ /// cannot be enabled even by the settings below.
+ final bool available;
+
+ /// Whether the feature is enabled by default.
+ ///
+ /// If not provided, defaults to `false`.
+ final bool enabledByDefault;
+}
diff --git a/packages/flutter_tools/lib/src/reporting/usage.dart b/packages/flutter_tools/lib/src/reporting/usage.dart
index 4f4d8ba..fbd6604 100644
--- a/packages/flutter_tools/lib/src/reporting/usage.dart
+++ b/packages/flutter_tools/lib/src/reporting/usage.dart
@@ -7,11 +7,13 @@
import 'package:meta/meta.dart';
import 'package:usage/usage_io.dart';
+import '../base/config.dart';
import '../base/context.dart';
import '../base/file_system.dart';
import '../base/os.dart';
import '../base/platform.dart';
import '../base/utils.dart';
+import '../features.dart';
import '../globals.dart';
import '../version.dart';
@@ -55,7 +57,9 @@
const String reloadExceptionSdkName = 'cd28';
const String reloadExceptionEmulator = 'cd29';
const String reloadExceptionFullRestart = 'cd30';
-// Next ID: cd32
+
+const String enabledFlutterFeatures = 'cd32';
+// Next ID: cd33
Usage get flutterUsage => Usage.instance;
@@ -72,6 +76,17 @@
_analytics.setSessionValue(kSessionHostOsDetails, os.name);
// Send the branch name as the "channel".
_analytics.setSessionValue(kSessionChannelName, flutterVersion.getBranchName(redactUnknownBranches: true));
+ // For each flutter experimental feature, record a session value in a comma
+ // separated list.
+ final String enabledFeatures = allFeatures
+ .where((Feature feature) {
+ return feature.configSetting != null &&
+ Config.instance.getValue(feature.configSetting) == true;
+ })
+ .map((Feature feature) => feature.configSetting)
+ .join(',');
+ _analytics.setSessionValue(enabledFlutterFeatures, enabledFeatures);
+
// Record the host as the application installer ID - the context that flutter_tools is running in.
if (platform.environment.containsKey('FLUTTER_HOST')) {
_analytics.setSessionValue('aiid', platform.environment['FLUTTER_HOST']);
diff --git a/packages/flutter_tools/test/general.shard/commands/config_test.dart b/packages/flutter_tools/test/general.shard/commands/config_test.dart
index b95b4ed..eaaa831 100644
--- a/packages/flutter_tools/test/general.shard/commands/config_test.dart
+++ b/packages/flutter_tools/test/general.shard/commands/config_test.dart
@@ -4,11 +4,15 @@
import 'dart:convert';
+import 'package:args/command_runner.dart';
import 'package:flutter_tools/src/android/android_sdk.dart';
import 'package:flutter_tools/src/android/android_studio.dart';
+import 'package:flutter_tools/src/base/config.dart';
import 'package:flutter_tools/src/base/context.dart';
import 'package:flutter_tools/src/base/logger.dart';
+import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/config.dart';
+import 'package:flutter_tools/src/version.dart';
import 'package:mockito/mockito.dart';
import '../../src/common.dart';
@@ -17,10 +21,16 @@
void main() {
MockAndroidStudio mockAndroidStudio;
MockAndroidSdk mockAndroidSdk;
+ MockFlutterVersion mockFlutterVersion;
+
+ setUpAll(() {
+ Cache.disableLocking();
+ });
setUp(() {
mockAndroidStudio = MockAndroidStudio();
mockAndroidSdk = MockAndroidSdk();
+ mockFlutterVersion = MockFlutterVersion();
});
group('config', () {
@@ -42,6 +52,77 @@
AndroidStudio: () => mockAndroidStudio,
AndroidSdk: () => mockAndroidSdk,
});
+
+ testUsingContext('allows setting and removing feature flags', () async {
+ final ConfigCommand configCommand = ConfigCommand();
+ final CommandRunner<void> commandRunner = createTestCommandRunner(configCommand);
+
+ await commandRunner.run(<String>[
+ 'config',
+ '--enable-web',
+ '--enable-linux-desktop',
+ '--enable-windows-desktop',
+ '--enable-macos-desktop'
+ ]);
+
+ expect(Config.instance.getValue('enable-web'), true);
+ expect(Config.instance.getValue('enable-linux-desktop'), true);
+ expect(Config.instance.getValue('enable-windows-desktop'), true);
+ expect(Config.instance.getValue('enable-macos-desktop'), true);
+
+ await commandRunner.run(<String>[
+ 'config', '--clear-features',
+ ]);
+
+ expect(Config.instance.getValue('enable-web'), null);
+ expect(Config.instance.getValue('enable-linux-desktop'), null);
+ expect(Config.instance.getValue('enable-windows-desktop'), null);
+ expect(Config.instance.getValue('enable-macos-desktop'), null);
+
+ await commandRunner.run(<String>[
+ 'config',
+ '--no-enable-web',
+ '--no-enable-linux-desktop',
+ '--no-enable-windows-desktop',
+ '--no-enable-macos-desktop'
+ ]);
+
+ expect(Config.instance.getValue('enable-web'), false);
+ expect(Config.instance.getValue('enable-linux-desktop'), false);
+ expect(Config.instance.getValue('enable-windows-desktop'), false);
+ expect(Config.instance.getValue('enable-macos-desktop'), false);
+ }, overrides: <Type, Generator>{
+ AndroidStudio: () => mockAndroidStudio,
+ AndroidSdk: () => mockAndroidSdk,
+ });
+
+ testUsingContext('displays which config settings are available on stable', () async {
+ final BufferLogger logger = context.get<Logger>();
+ when(mockFlutterVersion.channel).thenReturn('stable');
+ final ConfigCommand configCommand = ConfigCommand();
+ final CommandRunner<void> commandRunner = createTestCommandRunner(configCommand);
+
+ await commandRunner.run(<String>[
+ 'config',
+ '--enable-web',
+ '--enable-linux-desktop',
+ '--enable-windows-desktop',
+ '--enable-macos-desktop'
+ ]);
+
+ await commandRunner.run(<String>[
+ 'config',
+ ]);
+
+ expect(logger.statusText, contains('enable-web: true (Unavailable)'));
+ expect(logger.statusText, contains('enable-linux-desktop: true (Unavailable)'));
+ expect(logger.statusText, contains('enable-windows-desktop: true (Unavailable)'));
+ expect(logger.statusText, contains('enable-macos-desktop: true (Unavailable)'));
+ }, overrides: <Type, Generator>{
+ AndroidStudio: () => mockAndroidStudio,
+ AndroidSdk: () => mockAndroidSdk,
+ FlutterVersion: () => mockFlutterVersion,
+ });
});
}
@@ -54,3 +135,5 @@
@override
String get directory => 'path/to/android/sdk';
}
+
+class MockFlutterVersion extends Mock implements FlutterVersion {}
\ No newline at end of file
diff --git a/packages/flutter_tools/test/general.shard/config_test.dart b/packages/flutter_tools/test/general.shard/config_test.dart
index 8217375..4e64db3 100644
--- a/packages/flutter_tools/test/general.shard/config_test.dart
+++ b/packages/flutter_tools/test/general.shard/config_test.dart
@@ -29,6 +29,13 @@
expect(config.keys, contains('foo'));
});
+ test('get set bool value', () async {
+ expect(config.getValue('foo'), null);
+ config.setValue('foo', true);
+ expect(config.getValue('foo'), true);
+ expect(config.keys, contains('foo'));
+ });
+
test('containsKey', () async {
expect(config.containsKey('foo'), false);
config.setValue('foo', 'bar');
diff --git a/packages/flutter_tools/test/general.shard/features_test.dart b/packages/flutter_tools/test/general.shard/features_test.dart
new file mode 100644
index 0000000..94b336b
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/features_test.dart
@@ -0,0 +1,428 @@
+// 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:flutter_tools/src/base/config.dart';
+import 'package:flutter_tools/src/features.dart';
+import 'package:flutter_tools/src/base/platform.dart';
+import 'package:flutter_tools/src/version.dart';
+import 'package:mockito/mockito.dart';
+
+import '../src/common.dart';
+import '../src/testbed.dart';
+
+void main() {
+ group('Features', () {
+ MockFlutterVerion mockFlutterVerion;
+ MockFlutterConfig mockFlutterConfig;
+ MockPlatform mockPlatform;
+ Testbed testbed;
+
+ setUp(() {
+ mockFlutterVerion = MockFlutterVerion();
+ mockFlutterConfig = MockFlutterConfig();
+ mockPlatform = MockPlatform();
+ when<bool>(mockFlutterConfig.getValue(any)).thenReturn(false);
+ when(mockPlatform.environment).thenReturn(const <String, String>{});
+ testbed = Testbed(overrides: <Type, Generator>{
+ FlutterVersion: () => mockFlutterVerion,
+ FeatureFlags: () => const FeatureFlags(),
+ Config: () => mockFlutterConfig,
+ Platform: () => mockPlatform,
+ });
+ });
+
+ test('setting has safe defaults', () {
+ const FeatureChannelSetting featureSetting = FeatureChannelSetting();
+
+ expect(featureSetting.available, false);
+ expect(featureSetting.enabledByDefault, false);
+ });
+
+ test('has safe defaults', () {
+ const Feature feature = Feature(name: 'example');
+
+ expect(feature.name, 'example');
+ expect(feature.environmentOverride, null);
+ expect(feature.configSetting, null);
+ });
+
+ test('retrieves the correct setting for each branch', () {
+ final FeatureChannelSetting masterSetting = FeatureChannelSetting(available: nonconst(true));
+ final FeatureChannelSetting devSetting = FeatureChannelSetting(available: nonconst(true));
+ final FeatureChannelSetting betaSetting = FeatureChannelSetting(available: nonconst(true));
+ final FeatureChannelSetting stableSetting = FeatureChannelSetting(available: nonconst(true));
+ final Feature feature = Feature(
+ name: 'example',
+ master: masterSetting,
+ dev: devSetting,
+ beta: betaSetting,
+ stable: stableSetting,
+ );
+
+ expect(feature.getSettingForChannel('master'), masterSetting);
+ expect(feature.getSettingForChannel('dev'), devSetting);
+ expect(feature.getSettingForChannel('beta'), betaSetting);
+ expect(feature.getSettingForChannel('stable'), stableSetting);
+ expect(feature.getSettingForChannel('unknown'), masterSetting);
+ });
+
+ test('env variables are only enabled with "true" string', () => testbed.run(() {
+ when(mockPlatform.environment).thenReturn(<String, String>{'FLUTTER_WEB': 'hello'});
+
+ expect(featureFlags.isWebEnabled, false);
+
+ when(mockPlatform.environment).thenReturn(<String, String>{'FLUTTER_WEB': 'true'});
+
+ expect(featureFlags.isWebEnabled, true);
+ }));
+
+ test('flutter web help string', () {
+ expect(flutterWebFeature.generateHelpMessage(), 'Enable or disable Flutter Web on master, dev channels.');
+ });
+
+ test('flutter macOS desktop help string', () {
+ expect(flutterMacOSDesktopFeature.generateHelpMessage(), 'Enable or disable Flutter Desktop for macOS on the master channel.');
+ });
+
+ test('flutter Linux desktop help string', () {
+ expect(flutterLinuxDesktopFeature.generateHelpMessage(), 'Enable or disable Flutter Desktop for Linux on the master channel.');
+ });
+
+ test('flutter Windows desktop help string', () {
+ expect(flutterWindowsDesktopFeature.generateHelpMessage(), 'Enable or disable Flutter Desktop for Windows on the master channel.');
+ });
+
+ /// Flutter Web
+
+ test('flutter web off by default on master', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('master');
+
+ expect(featureFlags.isWebEnabled, false);
+ }));
+
+ test('flutter web enabled with config on master', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('master');
+ when<bool>(mockFlutterConfig.getValue('enable-web')).thenReturn(true);
+
+ expect(featureFlags.isWebEnabled, true);
+ }));
+
+ test('flutter web enabled with environment variable on master', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('master');
+ when(mockPlatform.environment).thenReturn(<String, String>{'FLUTTER_WEB': 'true'});
+
+ expect(featureFlags.isWebEnabled, true);
+ }));
+
+ test('flutter web off by default on dev', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('dev');
+
+ expect(featureFlags.isWebEnabled, false);
+ }));
+
+ test('flutter web enabled with config on dev', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('dev');
+ when<bool>(mockFlutterConfig.getValue('enable-web')).thenReturn(true);
+
+ expect(featureFlags.isWebEnabled, true);
+ }));
+
+ test('flutter web enabled with environment variable on dev', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('dev');
+ when(mockPlatform.environment).thenReturn(<String, String>{'FLUTTER_WEB': 'true'});
+
+ expect(featureFlags.isWebEnabled, true);
+ }));
+
+ test('flutter web off by default on beta', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('beta');
+
+ expect(featureFlags.isWebEnabled, false);
+ }));
+
+ test('flutter web not enabled with config on beta', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('beta');
+ when<bool>(mockFlutterConfig.getValue('enable-web')).thenReturn(true);
+
+ expect(featureFlags.isWebEnabled, false);
+ }));
+
+ test('flutter web not enabled with environment variable on beta', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('beta');
+ when(mockPlatform.environment).thenReturn(<String, String>{'FLUTTER_WEB': 'true'});
+
+ expect(featureFlags.isWebEnabled, false);
+ }));
+
+ test('flutter web off by default on stable', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('stable');
+
+ expect(featureFlags.isWebEnabled, false);
+ }));
+
+ test('flutter web not enabled with config on stable', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('stable');
+ when<bool>(mockFlutterConfig.getValue('enable-web')).thenReturn(true);
+
+ expect(featureFlags.isWebEnabled, false);
+ }));
+
+ test('flutter web not enabled with environment variable on stable', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('stable');
+ when(mockPlatform.environment).thenReturn(<String, String>{'FLUTTER_WEB': 'enabled'});
+
+ expect(featureFlags.isWebEnabled, false);
+ }));
+
+ /// Flutter macOS desktop.
+
+ test('flutter macos desktop off by default on master', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('master');
+
+ expect(featureFlags.isMacOSEnabled, false);
+ }));
+
+ test('flutter macos desktop enabled with config on master', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('master');
+ when<bool>(mockFlutterConfig.getValue('enable-macos-desktop')).thenReturn(true);
+
+ expect(featureFlags.isMacOSEnabled, true);
+ }));
+
+ test('flutter macos desktop enabled with environment variable on master', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('master');
+ when(mockPlatform.environment).thenReturn(<String, String>{'ENABLE_FLUTTER_DESKTOP': 'true'});
+
+ expect(featureFlags.isMacOSEnabled, true);
+ }));
+
+ test('flutter macos desktop off by default on dev', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('dev');
+
+ expect(featureFlags.isMacOSEnabled, false);
+ }));
+
+ test('flutter macos desktop not enabled with config on dev', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('dev');
+ when<bool>(mockFlutterConfig.getValue('flutter-desktop-macos')).thenReturn(true);
+
+ expect(featureFlags.isMacOSEnabled, false);
+ }));
+
+ test('flutter macos desktop not enabled with environment variable on dev', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('dev');
+ when(mockPlatform.environment).thenReturn(<String, String>{'ENABLE_FLUTTER_DESKTOP': 'true'});
+
+ expect(featureFlags.isMacOSEnabled, false);
+ }));
+
+ test('flutter macos desktop off by default on beta', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('beta');
+
+ expect(featureFlags.isMacOSEnabled, false);
+ }));
+
+ test('fflutter macos desktop not enabled with config on beta', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('beta');
+ when<bool>(mockFlutterConfig.getValue('flutter-desktop-macos')).thenReturn(true);
+
+ expect(featureFlags.isMacOSEnabled, false);
+ }));
+
+ test('flutter macos desktop not enabled with environment variable on beta', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('beta');
+ when(mockPlatform.environment).thenReturn(<String, String>{'ENABLE_FLUTTER_DESKTOP': 'true'});
+
+ expect(featureFlags.isMacOSEnabled, false);
+ }));
+
+ test('flutter macos desktop off by default on stable', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('stable');
+
+ expect(featureFlags.isMacOSEnabled, false);
+ }));
+
+ test('flutter macos desktop not enabled with config on stable', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('stable');
+ when<bool>(mockFlutterConfig.getValue('flutter-desktop-macos')).thenReturn(true);
+
+ expect(featureFlags.isMacOSEnabled, false);
+ }));
+
+ test('flutter macos desktop not enabled with environment variable on stable', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('stable');
+ when(mockPlatform.environment).thenReturn(<String, String>{'ENABLE_FLUTTER_DESKTOP': 'true'});
+
+ expect(featureFlags.isMacOSEnabled, false);
+ }));
+
+ /// Flutter Linux Desktop
+ test('flutter linux desktop off by default on master', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('master');
+
+ expect(featureFlags.isLinuxEnabled, false);
+ }));
+
+ test('flutter linux desktop enabled with config on master', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('master');
+ when<bool>(mockFlutterConfig.getValue('enable-linux-desktop')).thenReturn(true);
+
+ expect(featureFlags.isLinuxEnabled, true);
+ }));
+
+ test('flutter linux desktop enabled with environment variable on master', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('master');
+ when(mockPlatform.environment).thenReturn(<String, String>{'ENABLE_FLUTTER_DESKTOP': 'true'});
+
+ expect(featureFlags.isLinuxEnabled, true);
+ }));
+
+ test('flutter linux desktop off by default on dev', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('dev');
+
+ expect(featureFlags.isLinuxEnabled, false);
+ }));
+
+ test('flutter linux desktop not enabled with config on dev', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('dev');
+ when<bool>(mockFlutterConfig.getValue('enable-linux-desktop')).thenReturn(true);
+
+ expect(featureFlags.isLinuxEnabled, false);
+ }));
+
+ test('flutter linux desktop not enabled with environment variable on dev', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('dev');
+ when(mockPlatform.environment).thenReturn(<String, String>{'ENABLE_FLUTTER_DESKTOP': 'true'});
+
+ expect(featureFlags.isLinuxEnabled, false);
+ }));
+
+ test('flutter linux desktop off by default on beta', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('beta');
+
+ expect(featureFlags.isLinuxEnabled, false);
+ }));
+
+ test('fflutter linux desktop not enabled with config on beta', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('beta');
+ when<bool>(mockFlutterConfig.getValue('enable-linux-desktop')).thenReturn(true);
+
+ expect(featureFlags.isLinuxEnabled, false);
+ }));
+
+ test('flutter linux desktop not enabled with environment variable on beta', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('beta');
+ when(mockPlatform.environment).thenReturn(<String, String>{'ENABLE_FLUTTER_DESKTOP': 'true'});
+
+ expect(featureFlags.isLinuxEnabled, false);
+ }));
+
+ test('flutter linux desktop off by default on stable', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('stable');
+
+ expect(featureFlags.isLinuxEnabled, false);
+ }));
+
+ test('flutter linux desktop not enabled with config on stable', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('stable');
+ when<bool>(mockFlutterConfig.getValue('enable-linux-desktop')).thenReturn(true);
+
+ expect(featureFlags.isLinuxEnabled, false);
+ }));
+
+ test('flutter linux desktop not enabled with environment variable on stable', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('stable');
+ when(mockPlatform.environment).thenReturn(<String, String>{'ENABLE_FLUTTER_DESKTOP': 'true'});
+
+ expect(featureFlags.isLinuxEnabled, false);
+ }));
+
+ /// Flutter Windows desktop.
+ test('flutter windows desktop off by default on master', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('master');
+
+ expect(featureFlags.isWindowsEnabled, false);
+ }));
+
+ test('flutter windows desktop enabled with config on master', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('master');
+ when<bool>(mockFlutterConfig.getValue('enable-windows-desktop')).thenReturn(true);
+
+ expect(featureFlags.isWindowsEnabled, true);
+ }));
+
+ test('flutter windows desktop enabled with environment variable on master', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('master');
+ when(mockPlatform.environment).thenReturn(<String, String>{'ENABLE_FLUTTER_DESKTOP': 'true'});
+
+ expect(featureFlags.isWindowsEnabled, true);
+ }));
+
+ test('flutter windows desktop off by default on dev', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('dev');
+
+ expect(featureFlags.isWindowsEnabled, false);
+ }));
+
+ test('flutter windows desktop not enabled with config on dev', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('dev');
+ when<bool>(mockFlutterConfig.getValue('enable-windows-desktop')).thenReturn(true);
+
+ expect(featureFlags.isWindowsEnabled, false);
+ }));
+
+ test('flutter windows desktop not enabled with environment variable on dev', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('dev');
+ when(mockPlatform.environment).thenReturn(<String, String>{'ENABLE_FLUTTER_DESKTOP': 'true'});
+
+ expect(featureFlags.isWindowsEnabled, false);
+ }));
+
+ test('flutter windows desktop off by default on beta', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('beta');
+
+ expect(featureFlags.isWindowsEnabled, false);
+ }));
+
+ test('fflutter windows desktop not enabled with config on beta', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('beta');
+ when<bool>(mockFlutterConfig.getValue('enable-windows-desktop')).thenReturn(true);
+
+ expect(featureFlags.isWindowsEnabled, false);
+ }));
+
+ test('flutter windows desktop not enabled with environment variable on beta', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('beta');
+ when(mockPlatform.environment).thenReturn(<String, String>{'ENABLE_FLUTTER_DESKTOP': 'true'});
+
+ expect(featureFlags.isWindowsEnabled, false);
+ }));
+
+ test('flutter windows desktop off by default on stable', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('stable');
+
+ expect(featureFlags.isWindowsEnabled, false);
+ }));
+
+ test('flutter windows desktop not enabled with config on stable', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('stable');
+ when<bool>(mockFlutterConfig.getValue('enable-windows-desktop')).thenReturn(true);
+
+ expect(featureFlags.isWindowsEnabled, false);
+ }));
+
+ test('flutter windows desktop not enabled with environment variable on stable', () => testbed.run(() {
+ when(mockFlutterVerion.channel).thenReturn('stable');
+ when(mockPlatform.environment).thenReturn(<String, String>{'ENABLE_FLUTTER_DESKTOP': 'true'});
+
+ expect(featureFlags.isWindowsEnabled, false);
+ }));
+ });
+}
+
+class MockFlutterVerion extends Mock implements FlutterVersion {}
+class MockFlutterConfig extends Mock implements Config {}
+class MockPlatform extends Mock implements Platform {}
+
+T nonconst<T>(T item) => item;