Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions packages/cli_core/lib/src/logger_extension.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ import 'package:universal_io/io.dart';
@visibleForTesting
const fallbackStdoutTerminalColumns = 80;

@visibleForTesting
int Function() stdoutTerminalColumnsResolver = defaultTerminalColumnsForTest;

@visibleForTesting
int defaultTerminalColumnsForTest() {
if (stdout.hasTerminal) {
return stdout.terminalColumns; // coverage:ignore-line
}
return fallbackStdoutTerminalColumns;
}

extension LoggerX on Logger {
void created(String message) {
info(lightCyan.wrap(styleBold.wrap(message)));
Expand All @@ -15,14 +26,7 @@ extension LoggerX on Logger {
required void Function(String?) print,
int? length,
}) {
late final int maxLength;
if (length != null) {
maxLength = length;
} else if (stdout.hasTerminal) {
maxLength = stdout.terminalColumns;
} else {
maxLength = fallbackStdoutTerminalColumns;
}
final int maxLength = length ?? stdoutTerminalColumnsResolver();

for (final sentence in text?.split('/n') ?? <String>[]) {
final words = sentence.split(' ');
Expand Down
69 changes: 69 additions & 0 deletions packages/cli_core/test/cli_command_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import 'package:cli_core/cli_core.dart';
import 'package:mason/mason.dart';
import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
import 'package:test/test.dart';

class _FakeHookContext extends Mock implements HookContext {}

class _FakeLogger extends Mock implements Logger {}

class _FakeProgress extends Mock implements Progress {}

final class _TestCommand extends CliCommand {}

void main() {
late _FakeHookContext context;
late _FakeLogger logger;
late _FakeProgress progress;

setUp(() {
context = _FakeHookContext();
logger = _FakeLogger();
progress = _FakeProgress();
when(() => context.logger).thenReturn(logger);
when(() => logger.progress(any())).thenReturn(progress);
when(() => progress.complete(any())).thenReturn(null);
when(() => progress.fail(any())).thenReturn(null);
});

group('CliCommand', () {
test('default run completes without error', () async {
final cmd = _TestCommand();
await cmd.run(context);
});

test('trackOperation completes on success', () async {
final cmd = _TestCommand();
var called = false;
await cmd.trackOperation(
context,
startMessage: 'starting',
endMessage: 'done',
operation: () async {
called = true;
},
);
expect(called, isTrue);
verify(() => logger.progress('starting')).called(1);
verify(() => progress.complete('done')).called(1);
verifyNever(() => progress.fail(any()));
});

test('trackOperation fails and rethrows when operation throws', () async {
final cmd = _TestCommand();
await expectLater(
cmd.trackOperation(
context,
startMessage: 'starting',
endMessage: 'done',
operation: () async => throw StateError('boom'),
),
throwsA(isA<StateError>()),
);
verify(() => logger.progress('starting')).called(1);
verify(() => progress.fail(any(that: contains('boom')))).called(1);
verifyNever(() => progress.complete(any()));
});
});
}
13 changes: 13 additions & 0 deletions packages/cli_core/test/file_utils_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,19 @@ void main() {
test('returns false when no melos.yaml exists', () async {
expect(await FileUtils.isMonoRepo(tempDir.path), isFalse);
});

test('defaults to current directory when no path is provided', () async {
// No argument — exercises the Directory.current fallback branch.
final result = await FileUtils.isMonoRepo();
expect(result, isA<bool>());
});
});

test('readYamlFile throws when file is missing', () {
expect(
() => FileUtils.readYamlFile(p.join(tempDir.path, 'missing.yaml')),
throwsA(isA<FileSystemException>()),
);
});
});
}
107 changes: 107 additions & 0 deletions packages/cli_core/test/flutter_command_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import 'dart:io';

import 'package:cli_core/cli_core.dart';
import 'package:mason/mason.dart';
import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
import 'package:test/test.dart';

class _FakeHookContext extends Mock implements HookContext {}

class _FakeLogger extends Mock implements Logger {}

class _FakeProgress extends Mock implements Progress {}

final class _TestFlutterCommand extends BaseFlutterCommand {}

void main() {
late _FakeHookContext context;
late _FakeLogger logger;
late _FakeProgress progress;

setUp(() {
context = _FakeHookContext();
logger = _FakeLogger();
progress = _FakeProgress();
when(() => context.logger).thenReturn(logger);
when(() => logger.progress(any())).thenReturn(progress);
when(() => progress.complete(any())).thenReturn(null);
when(() => progress.fail(any())).thenReturn(null);
});

group('BaseFlutterCommand.createFlutterProject', () {
test('reports failure when working directory does not exist', () async {
final cmd = _TestFlutterCommand();
await expectLater(
cmd.createFlutterProject(
context: context,
name: 'tmp_app',
description: 'desc',
outputPath: '/definitely/not/a/real/path/xyz123',
orgName: 'com.example',
),
throwsA(isA<ProcessException>()),
);
verify(() => progress.fail(any())).called(1);
});

test('package template omits platforms arg and reports failure', () async {
final cmd = _TestFlutterCommand();
await expectLater(
cmd.createFlutterProject(
context: context,
name: 'tmp_pkg',
description: 'desc',
outputPath: '/definitely/not/a/real/path/xyz456',
template: 'package',
),
throwsA(isA<ProcessException>()),
);
});
});

group('BaseFlutterCommand.removeAnalysisOptions', () {
test('succeeds when file is present', () async {
final tmp = await Directory.systemTemp.createTemp('flutter_cmd_');
addTearDown(() async {
if (await tmp.exists()) await tmp.delete(recursive: true);
});
final file = File('${tmp.path}/analysis_options.yaml');
await file.writeAsString('');
final cmd = _TestFlutterCommand();
await cmd.removeAnalysisOptions(
context: context,
projectPath: tmp.path,
);
expect(await file.exists(), isFalse);
verify(() => progress.complete(any())).called(1);
});

test('rethrows when file is missing', () async {
final tmp = await Directory.systemTemp.createTemp('flutter_cmd_');
addTearDown(() async {
if (await tmp.exists()) await tmp.delete(recursive: true);
});
final cmd = _TestFlutterCommand();
await expectLater(
cmd.removeAnalysisOptions(context: context, projectPath: tmp.path),
throwsA(isA<FileSystemException>()),
);
verify(() => progress.fail(any())).called(1);
});
});

group('BaseFlutterCommand.pubGet', () {
test('reports failure when working directory does not exist', () async {
final cmd = _TestFlutterCommand();
await expectLater(
cmd.pubGet(
context: context,
projectPath: '/definitely/not/a/real/path/xyz789',
),
throwsA(isA<ProcessException>()),
);
verify(() => progress.fail(any())).called(1);
});
});
}
73 changes: 73 additions & 0 deletions packages/cli_core/test/logger_extension_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import 'package:cli_core/cli_core.dart';
import 'package:cli_core/src/logger_extension.dart' as ext;
import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
import 'package:test/test.dart';

class _FakeLogger extends Mock implements Logger {}

void main() {
group('LoggerX.created', () {
test('forwards message to info', () {
final logger = _FakeLogger();
when(() => logger.info(any())).thenReturn(null);
logger.created('hello');
verify(() => logger.info(any(that: contains('hello')))).called(1);
});
});

group('LoggerX.wrap', () {
final logger = _FakeLogger();

tearDown(() {
ext.stdoutTerminalColumnsResolver = ext.defaultTerminalColumnsForTest;
});

test('does nothing when text is null', () {
final collected = <String?>[];
logger.wrap(null, print: collected.add, length: 20);
expect(collected, isEmpty);
});

test('wraps long text across lines based on length', () {
final collected = <String?>[];
logger.wrap(
'aaa bbb ccc ddd eee',
print: collected.add,
length: 8,
);
expect(collected, isNotEmpty);
expect(collected.last, isNot(contains('eee eee')));
expect(collected.join('|'), contains('aaa'));
expect(collected.join('|'), contains('eee'));
});

test('uses resolver when length is not provided', () {
var resolverCalls = 0;
ext.stdoutTerminalColumnsResolver = () {
resolverCalls++;
return 120;
};
final collected = <String?>[];
logger.wrap('short text', print: collected.add);
expect(resolverCalls, 1);
expect(collected.single, 'short text ');
});

test('strips ANSI codes when measuring char length', () {
final collected = <String?>[];
// ANSI escape + short word. Raw length is 12 for the first word, but
// the ANSI-stripped char length is 3, so the wrap check uses 3.
logger.wrap('\x1B[31mred\x1B[0m word', print: collected.add, length: 20);
expect(collected, hasLength(1));
expect(collected.single, contains('red'));
expect(collected.single, contains('word'));
});

test('default resolver returns fallback when stdout has no terminal', () {
// In the test environment stdout never has a real terminal, so this
// exercises the fallback path of the default resolver.
expect(ext.defaultTerminalColumnsForTest(), ext.fallbackStdoutTerminalColumns);
});
});
}
59 changes: 59 additions & 0 deletions packages/cli_core/test/melos_command_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import 'dart:io';

import 'package:cli_core/cli_core.dart';
import 'package:mason/mason.dart';
import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
import 'package:test/test.dart';

class _FakeHookContext extends Mock implements HookContext {}

class _FakeLogger extends Mock implements Logger {}

class _FakeProgress extends Mock implements Progress {}

final class _TestMelosCommand extends BaseMelosCommand {}

void main() {
late _FakeHookContext context;
late _FakeLogger logger;
late _FakeProgress progress;

setUp(() {
context = _FakeHookContext();
logger = _FakeLogger();
progress = _FakeProgress();
when(() => context.logger).thenReturn(logger);
when(() => logger.progress(any())).thenReturn(progress);
when(() => progress.complete(any())).thenReturn(null);
when(() => progress.fail(any())).thenReturn(null);
});

group('BaseMelosCommand.bootstrap', () {
test('reports failure when working directory does not exist', () async {
final cmd = _TestMelosCommand();
await expectLater(
cmd.bootstrap(
context: context,
workspacePath: '/definitely/not/a/real/path/melos1',
),
throwsA(isA<ProcessException>()),
);
verify(() => progress.fail(any())).called(1);
});
});

group('BaseMelosCommand.clean', () {
test('reports failure when working directory does not exist', () async {
final cmd = _TestMelosCommand();
await expectLater(
cmd.clean(
context: context,
workspacePath: '/definitely/not/a/real/path/melos2',
),
throwsA(isA<ProcessException>()),
);
verify(() => progress.fail(any())).called(1);
});
});
}
6 changes: 3 additions & 3 deletions packages/connectivity_wrapper/lib/connectivity_wrapper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ enum ConnectivityStatus { CONNECTED, DISCONNECTED }
///
class ConnectivityWrapper {
static List<AddressCheckOptions> get _defaultAddresses => (kIsWeb)
? []
? [] // coverage:ignore-line
: List<AddressCheckOptions>.unmodifiable(
<AddressCheckOptions>[
AddressCheckOptions(
Expand Down Expand Up @@ -120,7 +120,7 @@ class ConnectivityWrapper {
isSuccess: true,
);
} catch (e) {
sock?.destroy();
sock?.destroy(); // coverage:ignore-line
return AddressCheckResult(
options,
isSuccess: false,
Expand Down Expand Up @@ -208,7 +208,7 @@ class ConnectivityWrapper {

_maybeEmitStatusUpdate([Timer? timer]) async {
_timerHandle?.cancel();
timer?.cancel();
timer?.cancel(); // coverage:ignore-line

var currentStatus = await connectionStatus;

Expand Down
Loading
Loading