// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import '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/android/java.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/commands/config.dart'; import 'package:flutter_tools/src/features.dart'; import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/reporting/reporting.dart'; import 'package:flutter_tools/src/version.dart'; import 'package:test/fake.dart'; import '../../src/common.dart'; import '../../src/context.dart'; import '../../src/fakes.dart'; import '../../src/test_flutter_command_runner.dart'; void main() { late Java fakeJava; late FakeAndroidStudio fakeAndroidStudio; late FakeAndroidSdk fakeAndroidSdk; late FakeFlutterVersion fakeFlutterVersion; late TestUsage testUsage; setUpAll(() { Cache.disableLocking(); }); setUp(() { fakeJava = FakeJava(); fakeAndroidStudio = FakeAndroidStudio(); fakeAndroidSdk = FakeAndroidSdk(); fakeFlutterVersion = FakeFlutterVersion(); testUsage = TestUsage(); }); void verifyNoAnalytics() { expect(testUsage.commands, isEmpty); expect(testUsage.events, isEmpty); expect(testUsage.timings, isEmpty); } group('config', () { testUsingContext('prints all settings with --list', () async { final ConfigCommand configCommand = ConfigCommand(); final CommandRunner<void> commandRunner = createTestCommandRunner(configCommand); await commandRunner.run(<String>['config', '--list']); expect( testLogger.statusText, 'All Settings:\n' '${allFeatures .where((Feature e) => e.configSetting != null) .map((Feature e) => ' ${e.configSetting}: (Not set)') .join('\n')}' '\n\n', ); }, overrides: <Type, Generator>{ Usage: () => testUsage, }); testUsingContext('throws error on excess arguments', () { final ConfigCommand configCommand = ConfigCommand(); final CommandRunner<void> commandRunner = createTestCommandRunner(configCommand); expect(() => commandRunner.run(<String>[ 'config', '--android-studio-dir=/opt/My', 'Android', 'Studio', ]), throwsToolExit()); verifyNoAnalytics(); }, overrides: <Type, Generator>{ Usage: () => testUsage, }); testUsingContext('machine flag', () async { final ConfigCommand command = ConfigCommand(); await command.handleMachine(); expect(testLogger.statusText, isNotEmpty); final dynamic jsonObject = json.decode(testLogger.statusText); expect(jsonObject, const TypeMatcher<Map<String, dynamic>>()); if (jsonObject is Map<String, dynamic>) { expect(jsonObject['android-studio-dir'], fakeAndroidStudio.directory); expect(jsonObject['android-sdk'], fakeAndroidSdk.directory.path); expect(jsonObject['jdk-dir'], fakeJava.javaHome); } verifyNoAnalytics(); }, overrides: <Type, Generator>{ AndroidStudio: () => fakeAndroidStudio, AndroidSdk: () => fakeAndroidSdk, Java: () => fakeJava, Usage: () => testUsage, }); testUsingContext('Can set build-dir', () async { final ConfigCommand configCommand = ConfigCommand(); final CommandRunner<void> commandRunner = createTestCommandRunner(configCommand); await commandRunner.run(<String>[ 'config', '--build-dir=foo', ]); expect(getBuildDirectory(), 'foo'); verifyNoAnalytics(); }, overrides: <Type, Generator>{ Usage: () => testUsage, }); testUsingContext('throws error on absolute path to build-dir', () async { final ConfigCommand configCommand = ConfigCommand(); final CommandRunner<void> commandRunner = createTestCommandRunner(configCommand); expect(() => commandRunner.run(<String>[ 'config', '--build-dir=/foo', ]), throwsToolExit()); verifyNoAnalytics(); }, overrides: <Type, Generator>{ Usage: () => testUsage, }); testUsingContext('allows setting and removing feature flags', () async { final ConfigCommand configCommand = ConfigCommand(); final CommandRunner<void> commandRunner = createTestCommandRunner(configCommand); await commandRunner.run(<String>[ 'config', '--enable-android', '--enable-ios', '--enable-web', '--enable-linux-desktop', '--enable-windows-desktop', '--enable-macos-desktop', ]); expect(globals.config.getValue('enable-android'), true); expect(globals.config.getValue('enable-ios'), true); expect(globals.config.getValue('enable-web'), true); expect(globals.config.getValue('enable-linux-desktop'), true); expect(globals.config.getValue('enable-windows-desktop'), true); expect(globals.config.getValue('enable-macos-desktop'), true); await commandRunner.run(<String>[ 'config', '--clear-features', ]); expect(globals.config.getValue('enable-android'), null); expect(globals.config.getValue('enable-ios'), null); expect(globals.config.getValue('enable-web'), null); expect(globals.config.getValue('enable-linux-desktop'), null); expect(globals.config.getValue('enable-windows-desktop'), null); expect(globals.config.getValue('enable-macos-desktop'), null); await commandRunner.run(<String>[ 'config', '--no-enable-android', '--no-enable-ios', '--no-enable-web', '--no-enable-linux-desktop', '--no-enable-windows-desktop', '--no-enable-macos-desktop', ]); expect(globals.config.getValue('enable-android'), false); expect(globals.config.getValue('enable-ios'), false); expect(globals.config.getValue('enable-web'), false); expect(globals.config.getValue('enable-linux-desktop'), false); expect(globals.config.getValue('enable-windows-desktop'), false); expect(globals.config.getValue('enable-macos-desktop'), false); verifyNoAnalytics(); }, overrides: <Type, Generator>{ AndroidStudio: () => fakeAndroidStudio, AndroidSdk: () => fakeAndroidSdk, Usage: () => testUsage, }); testUsingContext('warns the user to reload IDE', () async { final ConfigCommand configCommand = ConfigCommand(); final CommandRunner<void> commandRunner = createTestCommandRunner(configCommand); await commandRunner.run(<String>[ 'config', '--enable-web', ]); expect( testLogger.statusText, containsIgnoringWhitespace('You may need to restart any open editors'), ); }, overrides: <Type, Generator>{ Usage: () => testUsage, }); testUsingContext('displays which config settings are available on stable', () async { fakeFlutterVersion.channel = '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', '--list' ]); expect( testLogger.statusText, containsIgnoringWhitespace('enable-web: true'), ); expect( testLogger.statusText, containsIgnoringWhitespace('enable-linux-desktop: true'), ); expect( testLogger.statusText, containsIgnoringWhitespace('enable-windows-desktop: true'), ); expect( testLogger.statusText, containsIgnoringWhitespace('enable-macos-desktop: true'), ); verifyNoAnalytics(); }, overrides: <Type, Generator>{ AndroidStudio: () => fakeAndroidStudio, AndroidSdk: () => fakeAndroidSdk, FlutterVersion: () => fakeFlutterVersion, Usage: () => testUsage, }); testUsingContext('no-analytics flag flips usage flag and sends event', () async { final ConfigCommand configCommand = ConfigCommand(); final CommandRunner<void> commandRunner = createTestCommandRunner(configCommand); expect(testUsage.enabled, true); await commandRunner.run(<String>[ 'config', '--no-analytics', ]); expect(testUsage.enabled, false); // Verify that we flushed the analytics queue. expect(testUsage.ensureAnalyticsSentCalls, 1); // Verify that we only send the analytics disable event, and no other // info. expect(testUsage.events, equals(<TestUsageEvent>[ const TestUsageEvent('analytics', 'enabled', label: 'false'), ])); expect(testUsage.commands, isEmpty); expect(testUsage.timings, isEmpty); }, overrides: <Type, Generator>{ Usage: () => testUsage, }); testUsingContext('analytics flag flips usage flag and sends event', () async { final ConfigCommand configCommand = ConfigCommand(); final CommandRunner<void> commandRunner = createTestCommandRunner(configCommand); await commandRunner.run(<String>[ 'config', '--analytics', ]); expect(testUsage.enabled, true); // Verify that we only send the analytics enable event, and no other // info. expect(testUsage.events, equals(<TestUsageEvent>[ const TestUsageEvent('analytics', 'enabled', label: 'true'), ])); expect(testUsage.commands, isEmpty); expect(testUsage.timings, isEmpty); }, overrides: <Type, Generator>{ Usage: () => testUsage, }); testUsingContext('analytics reported with help usages', () async { final ConfigCommand configCommand = ConfigCommand(); createTestCommandRunner(configCommand); testUsage.suppressAnalytics = true; expect( configCommand.usage, containsIgnoringWhitespace('Analytics reporting is currently disabled'), ); testUsage.suppressAnalytics = false; expect( configCommand.usage, containsIgnoringWhitespace('Analytics reporting is currently enabled'), ); }, overrides: <Type, Generator>{ Usage: () => testUsage, }); }); } class FakeAndroidStudio extends Fake implements AndroidStudio, Comparable<AndroidStudio> { @override String get directory => 'path/to/android/studio'; @override String? get javaPath => 'path/to/android/studio/jbr'; } class FakeAndroidSdk extends Fake implements AndroidSdk { @override Directory get directory => globals.fs.directory('path/to/android/sdk'); } class FakeFlutterVersion extends Fake implements FlutterVersion { @override late String channel; @override void ensureVersionFile() {} @override Future<void> checkFlutterVersionFreshness() async {} }