// 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:async'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:platform/platform.dart'; import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/terminal.dart'; import 'package:flutter_tools/src/globals.dart' as globals; import 'package:mockito/mockito.dart'; import '../../src/common.dart'; void main() { group('output preferences', () { testWithoutContext('can wrap output', () async { final BufferLogger bufferLogger = BufferLogger( outputPreferences: OutputPreferences.test(wrapText: true, wrapColumn: 40), terminal: TestTerminal(platform: FakePlatform()..stdoutSupportsAnsi = true), ); bufferLogger.printStatus('0123456789' * 8); expect(bufferLogger.statusText, equals(('0123456789' * 4 + '\n') * 2)); }); testWithoutContext('can turn off wrapping', () async { final BufferLogger bufferLogger = BufferLogger( outputPreferences: OutputPreferences.test(wrapText: false), terminal: TestTerminal(platform: FakePlatform()..stdoutSupportsAnsi = true), ); final String testString = '0123456789' * 20; bufferLogger.printStatus(testString); expect(bufferLogger.statusText, equals('$testString\n')); }); }); group('ANSI coloring and bold', () { AnsiTerminal terminal; setUp(() { terminal = AnsiTerminal( stdio: globals.stdio, // Danger, using real stdio. platform: FakePlatform()..stdoutSupportsAnsi = true, ); }); testWithoutContext('adding colors works', () { for (final TerminalColor color in TerminalColor.values) { expect( terminal.color('output', color), equals('${AnsiTerminal.colorCode(color)}output${AnsiTerminal.resetColor}'), ); } }); testWithoutContext('adding bold works', () { expect( terminal.bolden('output'), equals('${AnsiTerminal.bold}output${AnsiTerminal.resetBold}'), ); }); testWithoutContext('nesting bold within color works', () { expect( terminal.color(terminal.bolden('output'), TerminalColor.blue), equals('${AnsiTerminal.blue}${AnsiTerminal.bold}output${AnsiTerminal.resetBold}${AnsiTerminal.resetColor}'), ); expect( terminal.color('non-bold ${terminal.bolden('output')} also non-bold', TerminalColor.blue), equals('${AnsiTerminal.blue}non-bold ${AnsiTerminal.bold}output${AnsiTerminal.resetBold} also non-bold${AnsiTerminal.resetColor}'), ); }); testWithoutContext('nesting color within bold works', () { expect( terminal.bolden(terminal.color('output', TerminalColor.blue)), equals('${AnsiTerminal.bold}${AnsiTerminal.blue}output${AnsiTerminal.resetColor}${AnsiTerminal.resetBold}'), ); expect( terminal.bolden('non-color ${terminal.color('output', TerminalColor.blue)} also non-color'), equals('${AnsiTerminal.bold}non-color ${AnsiTerminal.blue}output${AnsiTerminal.resetColor} also non-color${AnsiTerminal.resetBold}'), ); }); testWithoutContext('nesting color within color works', () { expect( terminal.color(terminal.color('output', TerminalColor.blue), TerminalColor.magenta), equals('${AnsiTerminal.magenta}${AnsiTerminal.blue}output${AnsiTerminal.resetColor}${AnsiTerminal.magenta}${AnsiTerminal.resetColor}'), ); expect( terminal.color('magenta ${terminal.color('output', TerminalColor.blue)} also magenta', TerminalColor.magenta), equals('${AnsiTerminal.magenta}magenta ${AnsiTerminal.blue}output${AnsiTerminal.resetColor}${AnsiTerminal.magenta} also magenta${AnsiTerminal.resetColor}'), ); }); testWithoutContext('nesting bold within bold works', () { expect( terminal.bolden(terminal.bolden('output')), equals('${AnsiTerminal.bold}output${AnsiTerminal.resetBold}'), ); expect( terminal.bolden('bold ${terminal.bolden('output')} still bold'), equals('${AnsiTerminal.bold}bold output still bold${AnsiTerminal.resetBold}'), ); }); }); group('character input prompt', () { AnsiTerminal terminalUnderTest; setUp(() { terminalUnderTest = TestTerminal(stdio: MockStdio()); }); testWithoutContext('character prompt throws if usesTerminalUi is false', () async { expect(terminalUnderTest.promptForCharInput( ['a', 'b', 'c'], prompt: 'Please choose something', logger: null, ), throwsA(isInstanceOf())); }); testWithoutContext('character prompt', () async { final BufferLogger bufferLogger = BufferLogger( terminal: terminalUnderTest, outputPreferences: OutputPreferences.test(), ); terminalUnderTest.usesTerminalUi = true; mockStdInStream = Stream.fromFutures(>[ Future.value('d'), // Not in accepted list. Future.value('\n'), // Not in accepted list Future.value('b'), ]).asBroadcastStream(); final String choice = await terminalUnderTest.promptForCharInput( ['a', 'b', 'c'], prompt: 'Please choose something', logger: bufferLogger, ); expect(choice, 'b'); expect( bufferLogger.statusText, 'Please choose something [a|b|c]: d\n' 'Please choose something [a|b|c]: \n' '\n' 'Please choose something [a|b|c]: b\n'); }); testWithoutContext('default character choice without displayAcceptedCharacters', () async { final BufferLogger bufferLogger = BufferLogger( terminal: terminalUnderTest, outputPreferences: OutputPreferences.test(), ); terminalUnderTest.usesTerminalUi = true; mockStdInStream = Stream.fromFutures(>[ Future.value('\n'), // Not in accepted list ]).asBroadcastStream(); final String choice = await terminalUnderTest.promptForCharInput( ['a', 'b', 'c'], prompt: 'Please choose something', displayAcceptedCharacters: false, defaultChoiceIndex: 1, // which is b. logger: bufferLogger, ); expect(choice, 'b'); expect( bufferLogger.statusText, 'Please choose something: \n' '\n'); }); testWithoutContext('Does not set single char mode when a terminal is not attached', () { final Stdio stdio = MockStdio(); when(stdio.stdin).thenThrow(StateError('This should not be called')); when(stdio.stdinHasTerminal).thenReturn(false); final AnsiTerminal ansiTerminal = AnsiTerminal( stdio: stdio, platform: const LocalPlatform() ); expect(() => ansiTerminal.singleCharMode = true, returnsNormally); }); }); } Stream mockStdInStream; class TestTerminal extends AnsiTerminal { TestTerminal({ Stdio stdio, Platform platform = const LocalPlatform(), }) : super(stdio: stdio, platform: platform); @override Stream get keystrokes { return mockStdInStream; } bool singleCharMode = false; } class MockStdio extends Mock implements Stdio {}