blob: 63d811805b0d7e3e1c823c777c0d1a6364565df6 [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.
@TestOn('vm')
import 'package:ansicolor/ansicolor.dart';
import 'package:devtools_app/src/config_specific/ide_theme/ide_theme.dart';
import 'package:devtools_app/src/screens/logging/_log_details.dart';
import 'package:devtools_app/src/screens/logging/_logs_table.dart';
import 'package:devtools_app/src/screens/logging/_message_column.dart';
import 'package:devtools_app/src/screens/logging/logging_controller.dart';
import 'package:devtools_app/src/screens/logging/logging_screen.dart';
import 'package:devtools_app/src/service/service_extensions.dart';
import 'package:devtools_app/src/service/service_manager.dart';
import 'package:devtools_app/src/shared/globals.dart';
import 'package:devtools_app/src/ui/service_extension_widgets.dart';
import 'package:devtools_test/devtools_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
void main() async {
late LoggingScreen screen;
late MockLoggingController mockLoggingController;
const windowSize = Size(1000.0, 1000.0);
await ensureInspectorDependencies();
mockLoggingController = MockLoggingController();
final FakeServiceManager fakeServiceManager = FakeServiceManager();
when(fakeServiceManager.connectedApp!.isFlutterWebAppNow).thenReturn(false);
when(fakeServiceManager.connectedApp!.isProfileBuildNow).thenReturn(false);
when(fakeServiceManager.errorBadgeManager.errorCountNotifier('logging'))
.thenReturn(ValueNotifier<int>(0));
group('Logging Screen', () {
Future<void> pumpLoggingScreen(WidgetTester tester) async {
await tester.pumpWidget(
wrapWithControllers(
const LoggingScreenBody(),
logging: mockLoggingController,
),
);
}
setUp(() async {
setGlobal(ServiceConnectionManager, fakeServiceManager);
setGlobal(IdeTheme, IdeTheme());
screen = const LoggingScreen();
});
testWidgets('builds its tab', (WidgetTester tester) async {
await tester.pumpWidget(wrap(Builder(builder: screen.buildTab)));
expect(find.text('Logging'), findsOneWidget);
});
testWidgetsWithWindowSize('builds with no data', windowSize,
(WidgetTester tester) async {
await pumpLoggingScreen(tester);
expect(find.byType(LoggingScreenBody), findsOneWidget);
expect(find.byType(LogsTable), findsOneWidget);
expect(find.byType(LogDetails), findsOneWidget);
expect(find.text('Clear'), findsOneWidget);
expect(find.byType(TextField), findsOneWidget);
expect(find.byType(StructuredErrorsToggle), findsOneWidget);
});
testWidgetsWithWindowSize('can clear logs', windowSize,
(WidgetTester tester) async {
await pumpLoggingScreen(tester);
verifyNever(mockLoggingController.clear());
await tester.tap(find.text('Clear'));
verify(mockLoggingController.clear()).called(1);
});
testWidgetsWithWindowSize(
'search field is disabled with no data', windowSize,
(WidgetTester tester) async {
await pumpLoggingScreen(tester);
verifyNever(mockLoggingController.clear());
final textFieldFinder = find.byKey(loggingSearchFieldKey);
expect(textFieldFinder, findsOneWidget);
final TextField textField = tester.widget(textFieldFinder) as TextField;
expect(textField.enabled, isFalse);
});
testWidgetsWithWindowSize('can toggle structured errors', windowSize,
(WidgetTester tester) async {
final serviceManager = FakeServiceManager();
when(serviceManager.connectedApp!.isFlutterWebAppNow).thenReturn(false);
when(serviceManager.connectedApp!.isProfileBuildNow).thenReturn(false);
setGlobal(
ServiceConnectionManager,
serviceManager,
);
await pumpLoggingScreen(tester);
Switch toggle = tester.widget(find.byType(Switch));
expect(toggle.value, false);
serviceManager.serviceExtensionManager
.fakeServiceExtensionStateChanged(structuredErrors.extension, 'true');
await tester.pumpAndSettle();
toggle = tester.widget(find.byType(Switch));
expect(toggle.value, true);
// TODO(djshuckerow): Hook up fake extension state querying.
});
group('MessageColumn', () {
late MessageColumn column;
setUp(() {
column = MessageColumn();
});
test('compare sorts logs correctly', () {
final a = LogData('test', 'Hello world', 1);
final b = LogData('test', 'Test test test', 1);
expect(column.compare(a, b), equals(-1));
});
test('compare special cases sorting for frame logs', () {
final a = LogData('flutter.frame', '#9 3.6ms ', 1);
final b = LogData('flutter.frame', '#10 3.6ms ', 1);
expect(column.compare(a, b), equals(-1));
// The number of spaces between the frame number and duration as well
// as after the duration can be inconsistent. Verify that the regexp
// still works.
final c = LogData('flutter.frame', '#10 3.6ms', 1);
final d = LogData('flutter.frame', '#9 3.6ms ', 1);
expect(column.compare(c, d), equals(1));
final e = LogData('flutter.frame', '#10 3.6ms ', 1);
final f = LogData('flutter.frame', '#9foo 3.6ms ', 1);
expect(column.compare(e, f), equals(-1));
final l1 = LogData('flutter.frame', '#2 3.6ms ', 1);
final l2 = LogData('flutter.frame', '#2NOTAMATCH 3.6ms ', 1);
final l3 = LogData('flutter.frame', '#10 3.6ms ', 1);
final l4 = LogData('flutter.frame', '#10NOTAMATCH 3.6ms ', 1);
final l5 = LogData('flutter.frame', '#11 3.6ms ', 1);
final l6 = LogData('flutter.frame', '#11NOTAMATCH 3.6ms ', 1);
final list = [l1, l2, l3, l4, l5, l6];
list.sort(column.compare);
expect(list[0], equals(l1));
expect(list[1], equals(l3));
expect(list[2], equals(l5));
expect(list[3], equals(l4));
expect(list[4], equals(l6));
expect(list[5], equals(l2));
});
});
});
}
const totalLogs = 10;
final fakeLogData = List<LogData>.generate(totalLogs, _generate);
LogData _generate(int i) {
String? details = 'log event $i';
String kind = 'kind $i';
String? computedDetails;
switch (i) {
case 9:
computedDetails = jsonOutput;
break;
case 8:
computedDetails = nonJsonOutput;
break;
case 7:
details = null;
break;
case 5:
kind = 'stdout';
details = _ansiCodesOutput();
break;
default:
break;
}
final detailsComputer = computedDetails == null
? null
: () =>
Future.delayed(const Duration(seconds: 1), () => computedDetails!);
return LogData(kind, details, i, detailsComputer: detailsComputer);
}
const nonJsonOutput = 'Non-json details for log number 8';
const jsonOutput = '{\n"Details": "of log event 9",\n"logEvent": "9"\n}\n';
String _ansiCodesOutput() {
final sb = StringBuffer();
sb.write('Ansi color codes processed for ');
final pen = AnsiPen()..rgb(r: 0.8, g: 0.3, b: 0.4, bg: true);
sb.write(pen('log 5'));
return sb.toString();
}