chore: add integration tests
Signed-off-by: TheOneWithTheBraid <the-one@with-the-braid.cf>krille/nicer-modal-bottom-sheet
parent
803205b7b7
commit
1992d18a64
@ -1,49 +1,117 @@
|
||||
import 'dart:developer';
|
||||
import 'package:fluffychat/config/setting_keys.dart';
|
||||
import 'package:fluffychat/pages/chat/chat_view.dart';
|
||||
import 'package:fluffychat/pages/chat_list/chat_list_body.dart';
|
||||
import 'package:fluffychat/pages/chat_list/search_title.dart';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'package:fluffychat/main.dart' as app;
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'extensions/default_flows.dart';
|
||||
import 'extensions/wait_for.dart';
|
||||
import 'users.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('Integration Test', () {
|
||||
testWidgets('Test if the app starts', (WidgetTester tester) async {
|
||||
app.main();
|
||||
await tester.pumpAndSettle();
|
||||
group(
|
||||
'Integration Test',
|
||||
() {
|
||||
setUpAll(
|
||||
() async {
|
||||
// this random dialog popping up is super hard to cover in tests
|
||||
SharedPreferences.setMockInitialValues({
|
||||
SettingKeys.showNoGoogle: false,
|
||||
});
|
||||
try {
|
||||
Hive.deleteFromDisk();
|
||||
Hive.initFlutter();
|
||||
} catch (_) {}
|
||||
},
|
||||
);
|
||||
|
||||
await Future.delayed(const Duration(seconds: 10));
|
||||
testWidgets(
|
||||
'Start app, login and logout',
|
||||
(WidgetTester tester) async {
|
||||
app.main();
|
||||
await tester.ensureAppStartedHomescreen();
|
||||
await tester.ensureLoggedOut();
|
||||
},
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
testWidgets(
|
||||
'Login again',
|
||||
(WidgetTester tester) async {
|
||||
app.main();
|
||||
await tester.ensureAppStartedHomescreen();
|
||||
},
|
||||
);
|
||||
|
||||
expect(find.text('Connect'), findsOneWidget);
|
||||
testWidgets(
|
||||
'Start chat and send message',
|
||||
(WidgetTester tester) async {
|
||||
app.main();
|
||||
await tester.ensureAppStartedHomescreen();
|
||||
await tester.waitFor(find.byType(TextField));
|
||||
await tester.enterText(find.byType(TextField), Users.user2.name);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final input = find.byType(TextField);
|
||||
await tester.scrollUntilVisible(
|
||||
find.text('Chats'),
|
||||
500,
|
||||
scrollable: find.descendant(
|
||||
of: find.byType(ChatListViewBody),
|
||||
matching: find.byType(Scrollable),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Chats'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.waitFor(find.byType(SearchTitle));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(input, findsOneWidget);
|
||||
await tester.scrollUntilVisible(
|
||||
find.text(Users.user2.name).first,
|
||||
500,
|
||||
scrollable: find.descendant(
|
||||
of: find.byType(ChatListViewBody),
|
||||
matching: find.byType(Scrollable),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text(Users.user2.name).first);
|
||||
|
||||
await tester.enterText(input, homeserver);
|
||||
await tester.testTextInput.receiveAction(TextInputAction.done);
|
||||
await tester.pumpAndSettle();
|
||||
try {
|
||||
await tester.waitFor(
|
||||
find.byType(ChatView),
|
||||
timeout: const Duration(seconds: 5),
|
||||
);
|
||||
} catch (_) {
|
||||
// in case the homeserver sends the username as search result
|
||||
if (find.byIcon(Icons.send_outlined).evaluate().isNotEmpty) {
|
||||
await tester.tap(find.byIcon(Icons.send_outlined));
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
}
|
||||
|
||||
// in case registration is allowed
|
||||
try {
|
||||
await tester.tap(find.text('Login'));
|
||||
await tester.pumpAndSettle();
|
||||
} catch (e) {
|
||||
log('Registration is not allowed. Proceeding with login...');
|
||||
}
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final inputs = find.byType(TextField);
|
||||
|
||||
await tester.enterText(inputs.first, Users.user1.name);
|
||||
await tester.enterText(inputs.last, Users.user1.password);
|
||||
await tester.testTextInput.receiveAction(TextInputAction.done);
|
||||
});
|
||||
});
|
||||
await tester.waitFor(find.byType(ChatView));
|
||||
await tester.enterText(find.byType(TextField).last, 'Test');
|
||||
await tester.pumpAndSettle();
|
||||
try {
|
||||
await tester.waitFor(find.byIcon(Icons.send_outlined));
|
||||
await tester.tap(find.byIcon(Icons.send_outlined));
|
||||
} catch (_) {
|
||||
await tester.testTextInput.receiveAction(TextInputAction.done);
|
||||
}
|
||||
await tester.pumpAndSettle();
|
||||
await tester.waitFor(find.text('Test'));
|
||||
await tester.pumpAndSettle();
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,182 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:fluffychat/pages/chat_list/chat_list_body.dart';
|
||||
import 'package:fluffychat/pages/homeserver_picker/homeserver_picker.dart';
|
||||
import 'package:fluffychat/pages/settings_account/settings_account_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../users.dart';
|
||||
import 'wait_for.dart';
|
||||
|
||||
extension DefaultFlowExtensions on WidgetTester {
|
||||
Future<void> login() async {
|
||||
final tester = this;
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.waitFor(find.text('Let\'s start'));
|
||||
|
||||
expect(find.text('Let\'s start'), findsOneWidget);
|
||||
|
||||
final input = find.byType(TextField);
|
||||
|
||||
expect(input, findsOneWidget);
|
||||
|
||||
// getting the placeholder in place
|
||||
await tester.tap(find.byIcon(Icons.search));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.enterText(input, homeserver);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.testTextInput.receiveAction(TextInputAction.done);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// in case registration is allowed
|
||||
// try {
|
||||
await Future.delayed(const Duration(milliseconds: 50));
|
||||
|
||||
await tester.scrollUntilVisible(
|
||||
find.text('Login'),
|
||||
500,
|
||||
scrollable: find.descendant(
|
||||
of: find.byKey(const Key('ConnectPageListView')),
|
||||
matching: find.byType(Scrollable).first,
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('Login'));
|
||||
await tester.pumpAndSettle();
|
||||
/*} catch (e) {
|
||||
log('Registration is not allowed. Proceeding with login...');
|
||||
}*/
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await Future.delayed(const Duration(milliseconds: 50));
|
||||
|
||||
final inputs = find.byType(TextField);
|
||||
|
||||
await tester.enterText(inputs.first, Users.user1.name);
|
||||
await tester.enterText(inputs.last, Users.user1.password);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.testTextInput.receiveAction(TextInputAction.done);
|
||||
|
||||
try {
|
||||
// pumpAndSettle does not work in here as setState is called
|
||||
// asynchronously
|
||||
await tester.waitFor(
|
||||
find.byType(LinearProgressIndicator),
|
||||
timeout: const Duration(milliseconds: 1500),
|
||||
skipPumpAndSettle: true,
|
||||
);
|
||||
} catch (_) {
|
||||
// in case the input action does not work on the desired platform
|
||||
if (find.text('Login').evaluate().isNotEmpty) {
|
||||
await tester.tap(find.text('Login'));
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await tester.pumpAndSettle();
|
||||
} catch (_) {
|
||||
// may fail because of ongoing animation below dialog
|
||||
}
|
||||
|
||||
await tester.waitFor(
|
||||
find.byType(ChatListViewBody),
|
||||
skipPumpAndSettle: true,
|
||||
);
|
||||
}
|
||||
|
||||
/// ensure PushProvider check passes
|
||||
Future<void> acceptPushWarning() async {
|
||||
final tester = this;
|
||||
|
||||
final matcher = find.maybeUppercaseText('Do not show again');
|
||||
|
||||
try {
|
||||
await tester.waitFor(matcher, timeout: const Duration(seconds: 5));
|
||||
|
||||
// the FCM push error dialog to be handled...
|
||||
await tester.tap(matcher);
|
||||
await tester.pumpAndSettle();
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
Future<void> ensureLoggedOut() async {
|
||||
final tester = this;
|
||||
await tester.pumpAndSettle();
|
||||
if (find.byType(ChatListViewBody).evaluate().isNotEmpty) {
|
||||
await tester.tap(find.byTooltip('Show menu'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Settings'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.scrollUntilVisible(
|
||||
find.text('Account'),
|
||||
500,
|
||||
scrollable: find.descendant(
|
||||
of: find.byKey(const Key('SettingsListViewContent')),
|
||||
matching: find.byType(Scrollable),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Account'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.scrollUntilVisible(
|
||||
find.text('Logout'),
|
||||
500,
|
||||
scrollable: find.descendant(
|
||||
of: find.byType(SettingsAccountView),
|
||||
matching: find.byType(Scrollable),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Logout'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.maybeUppercaseText('Yes'));
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> ensureAppStartedHomescreen({
|
||||
Duration timeout = const Duration(seconds: 20),
|
||||
}) async {
|
||||
final tester = this;
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final homeserverPickerFinder = find.byType(HomeserverPicker);
|
||||
final chatListFinder = find.byType(ChatListViewBody);
|
||||
|
||||
final end = DateTime.now().add(timeout);
|
||||
|
||||
log(
|
||||
'Waiting for HomeserverPicker or ChatListViewBody...',
|
||||
name: 'Test Runner',
|
||||
);
|
||||
do {
|
||||
if (DateTime.now().isAfter(end)) {
|
||||
throw Exception(
|
||||
'Timed out waiting for HomeserverPicker or ChatListViewBody');
|
||||
}
|
||||
|
||||
await pumpAndSettle();
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
} while (homeserverPickerFinder.evaluate().isEmpty &&
|
||||
chatListFinder.evaluate().isEmpty);
|
||||
|
||||
if (homeserverPickerFinder.evaluate().isNotEmpty) {
|
||||
log(
|
||||
'Found HomeserverPicker, performing login.',
|
||||
name: 'Test Runner',
|
||||
);
|
||||
await tester.login();
|
||||
} else {
|
||||
log(
|
||||
'Found ChatListViewBody, skipping login.',
|
||||
name: 'Test Runner',
|
||||
);
|
||||
}
|
||||
|
||||
await tester.acceptPushWarning();
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
/// Workaround for https://github.com/flutter/flutter/issues/88765
|
||||
extension WaitForExtension on WidgetTester {
|
||||
Future<void> waitFor(
|
||||
Finder finder, {
|
||||
Duration timeout = const Duration(seconds: 20),
|
||||
bool skipPumpAndSettle = false,
|
||||
}) async {
|
||||
final end = DateTime.now().add(timeout);
|
||||
|
||||
do {
|
||||
if (DateTime.now().isAfter(end)) {
|
||||
throw Exception('Timed out waiting for $finder');
|
||||
}
|
||||
|
||||
if (!skipPumpAndSettle) {
|
||||
await pumpAndSettle();
|
||||
}
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
} while (finder.evaluate().isEmpty);
|
||||
}
|
||||
}
|
||||
|
||||
extension MaybeUppercaseFinder on CommonFinders {
|
||||
/// On Android some button labels are in uppercase while on iOS they
|
||||
/// are not. This method tries both.
|
||||
Finder maybeUppercaseText(
|
||||
String text, {
|
||||
bool findRichText = false,
|
||||
bool skipOffstage = true,
|
||||
}) {
|
||||
try {
|
||||
final finder = find.text(
|
||||
text.toUpperCase(),
|
||||
findRichText: findRichText,
|
||||
skipOffstage: skipOffstage,
|
||||
);
|
||||
expect(finder, findsOneWidget);
|
||||
return finder;
|
||||
} catch (_) {
|
||||
return find.text(
|
||||
text,
|
||||
findRichText: findRichText,
|
||||
skipOffstage: skipOffstage,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
apk update && apk add docker drill grep
|
@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
if ! command -v apk &>/dev/null; then
|
||||
apt update && apt install -y -qq docker.io ldnsutils grep scrcpy ffmpeg
|
||||
else
|
||||
apk update && apk add docker drill grep scrcpy ffmpeg
|
||||
fi
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
chmod 777 -R /dev/kvm
|
||||
adb start-server
|
||||
emulator -avd test -no-audio -no-boot-anim -no-window -accel on -gpu swiftshader_indirect
|
||||
emulator -avd test -wipe-data -no-audio -no-boot-anim -no-window -accel on -gpu swiftshader_indirect
|
||||
|
Loading…
Reference in New Issue