Skip to content

Commit

Permalink
Add integration tests (#28)
Browse files Browse the repository at this point in the history
Launch UI tests, with screenshots taking.
Fix bug in add/modify entry screen
  • Loading branch information
OroshiX authored Aug 15, 2023
2 parents 23a166c + 008a2bc commit 0200267
Show file tree
Hide file tree
Showing 17 changed files with 376 additions and 64 deletions.
5 changes: 4 additions & 1 deletion .github/workflows/builds.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,14 @@ jobs:
IOS_NATIVE_ID: ${{ secrets.IOS_NATIVE_ID }}
TEST_ADS: ${{ secrets.TEST_ADS }}
run: dart tool/env.dart
- name: Activate flutter packages
run: |
flutter pub global activate intl_utils
- name: Generate files (freezed, json_serializable)
run: flutter pub run build_runner build --delete-conflicting-outputs
- name: Generate translations
run: |
flutter pub global activate intl_utils && flutter pub global run intl_utils:generate
flutter pub global run intl_utils:generate
- run: flutter test
- name: Build app bundle
env:
Expand Down
9 changes: 6 additions & 3 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ jobs:
restore-keys: |
${{ runner.os }}-pub-cache-
- run: flutter config --no-analytics
- name: Install dependencies
run: flutter pub get
- name: Format code
run: dart format . --set-exit-if-changed
- name: Install dependencies
run: flutter pub get
test:
runs-on: ubuntu-latest
steps:
Expand Down Expand Up @@ -78,9 +78,12 @@ jobs:
run: dart tool/env.dart
- name: Generate files (freezed, json_serializable)
run: flutter pub run build_runner build --delete-conflicting-outputs
- name: Activate flutter packages
run: |
flutter pub global activate intl_utils
- name: Generate translations
run: |
flutter pub global activate intl_utils && flutter pub global run intl_utils:generate
flutter pub global run intl_utils:generate
- name: Analyze code
run: flutter analyze --no-fatal-infos --fatal-warnings
- run: flutter test
7 changes: 1 addition & 6 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,6 @@ android {
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
ndk {
debugSymbolLevel 'FULL'
}
}

signingConfigs {
Expand All @@ -75,9 +72,7 @@ flutter {
}

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
testImplementation 'junit:junit:4.12'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test:runner:1.5.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
implementation "androidx.constraintlayout:constraintlayout:2.1.4"
}
2 changes: 1 addition & 1 deletion android/fastlane
76 changes: 76 additions & 0 deletions integration_test/common.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import 'package:drift/native.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:integration_test/integration_test.dart';
import 'package:tagros_comptes/generated/l10n.dart';
import 'package:tagros_comptes/main.dart';
import 'package:tagros_comptes/monetization/domain/premium_plan.dart';
import 'package:tagros_comptes/state/providers.dart';
import 'package:tagros_comptes/tagros/data/source/db/app_database.dart';
import 'package:tagros_comptes/tagros/data/source/db/db_providers.dart';
import 'package:tagros_comptes/theme/domain/theme.dart';
import 'package:tagros_comptes/theme/domain/theme_providers.dart';
import 'package:tagros_comptes/theme/domain/theme_repository.dart';
import 'package:universal_platform/universal_platform.dart';

Future<void> createApp(WidgetTester widgetTester,
{String lang = 'en', ThemeColor? themeColor}) async {
await S.load(Locale(lang));

await widgetTester.pumpWidget(ProviderScope(
overrides: [
themeRepositoryProvider.overrideWithValue(
_FakeThemeRepository(themeColor: themeColor),
),
databaseProvider.overrideWith((ref) {
final appDatabase = AppDatabase(NativeDatabase.memory());
ref.onDispose(() {
appDatabase.close();
});
return appDatabase;
}),
isPremiumProvider.overrideWith((ref) => true),
showAdsProvider.overrideWith((ref) => ShowAds.hide),
bannerAdsProvider.overrideWith((ref, arg) => Future.error('')),
],
child: MyApp(locale: Locale(lang)),
));
}

/// Take a screenshot of the current screen.
/// Cannot take more than one screenshot per test.
Future<void> takeScreenshot(
IntegrationTestWidgetsFlutterBinding binding, WidgetTester tester,
{required String screenshotName, bool settle = true}) async {
if (UniversalPlatform.isAndroid) {
await binding.convertFlutterSurfaceToImage();
if (settle) {
await tester.pumpAndSettle();
}
}
await binding.takeScreenshot(screenshotName);
}

class _FakeThemeRepository extends Fake implements ThemeRepository {
final ThemeColor _themeColor;

_FakeThemeRepository({ThemeColor? themeColor})
: _themeColor = themeColor ?? ThemeColor.defaultTheme();

@override
Stream<ThemeData> get themeData => Stream.value(_themeColor.toDataTheme);

@override
Stream<ThemeColor> selectedTheme() {
return Future.value(ThemeColor.defaultTheme()).asStream();
}

@override
Stream<List<ThemeColor>> allThemes() {
return Future.value(ThemeColor.allThemes).asStream();
}

@override
Future<void> selectTheme({required int id}) async {}
}
127 changes: 127 additions & 0 deletions integration_test/screenshots_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:tagros_comptes/generated/l10n.dart';
import 'package:tagros_comptes/tagros/domain/game/poignee.dart';

import 'common.dart';

void main() {
final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
const namesEn = ['Mary', 'David', 'Lisa', 'Ronald', 'Sarah'];
const namesFr = ['Jade', 'David', 'Lisa', 'Raphaël', 'Sarah'];
group('Taking screenshots', () {
testWidgets('Player dialog with 5 players en', (t) async {
await createApp(t, lang: 'en');
await t.pumpAndSettle();
await _newGame(t, namesEn);
for (var i = 0; i < 20; i++) {
await _addRandomRound(t, namesEn);
}

await takeScreenshot(binding, t, screenshotName: '1_en-US');
});
testWidgets('Player dialog with 5 players fr', (t) async {
await createApp(t, lang: 'fr');
await t.pumpAndSettle();
await _newGame(t, namesFr);
for (var i = 0; i < 20; i++) {
await _addRandomRound(t, namesFr);
}

await takeScreenshot(binding, t, screenshotName: '1_fr-FR');
});

testWidgets('Edit game en', (t) async {
await createApp(t, lang: 'en');
await t.pumpAndSettle();
final names = namesEn.take(4).toList();
await _newGame(t, names);

await _editGame(t, names);
await takeScreenshot(binding, t,
screenshotName: '2_en-US', settle: false);
});

testWidgets('Edit game fr', (t) async {
await createApp(t, lang: 'fr');
await t.pumpAndSettle();
final names = namesFr.take(4).toList();
await _newGame(t, names);
await _editGame(t, names);

await takeScreenshot(binding, t,
screenshotName: '2_fr-FR', settle: false);
});
});
}

Future<void> _editGame(
WidgetTester t,
List<String> names,
) async {
await t.tap(find.byIcon(Icons.add));
await t.pumpAndSettle();

final score = Random().nextInt(91).toString();
await t.enterText(find.byType(EditableText), score);
await t.tap(find.byKey(const ValueKey('dropdown-contract')));
await t.pumpAndSettle();
await t.tap(find.text(S.current.priseTypeGardeContre));
await t.pumpAndSettle();
await t.tap(find.byKey(const ValueKey('dropdown-oudlers')));
await t.pumpAndSettle();
await t.tap(find.text('3'));
await t.pumpAndSettle();
await t.tap(find.byType(Checkbox).first);
await t.pumpAndSettle();
final handful = find.byKey(const ValueKey('dropdown-handful'));
await t.tap(handful);
await t.pumpAndSettle();
await t.tap(find.text(S.current.addModifyPoigneeNbTrumps(
getNbAtouts(PoigneeType.double, names.length),
PoigneeType.double.displayName)));
await t.pump();
}

Future<void> _newGame(WidgetTester tester, List<String> names) async {
await tester.tap(find.text(S.current.newGame));

await tester.pumpAndSettle();

final editText = find.byType(EditableText);
for (final name in names) {
await tester.enterText(editText, name);
await tester.testTextInput.receiveAction(TextInputAction.next);
}

await tester.tap(find.text('OK'));
await tester.pumpAndSettle();
}

Future<void> _addRandomRound(WidgetTester tester, List<String> names) async {
await tester.tap(find.byIcon(Icons.add));
await tester.pumpAndSettle();

var name = names[Random().nextInt(names.length)];
await tester.dragUntilVisible(
find.descendant(
of: find.byKey(const ValueKey('player1')), matching: find.text(name)),
find.byKey(const ValueKey('player1')),
const Offset(500, 0));
await tester.tap(find.text(name).first);
name = names[Random().nextInt(names.length)];
await tester.dragUntilVisible(
find.descendant(
of: find.byKey(const ValueKey('player2')), matching: find.text(name)),
find.byKey(const ValueKey('player2')),
const Offset(500, 0));
await tester.tap(find.text(name).last);
final score = Random().nextInt(91).toString();
await tester.enterText(find.byType(EditableText), score);

await tester.tap(find.byIcon(Icons.check));
await tester.pump();
}
39 changes: 39 additions & 0 deletions integration_test/smoke_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';

import 'common.dart';

void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();

group('smoke testing', () {
testWidgets('go to themes screen smoke test', (t) async {
await createApp(t);

expect(find.text('New game'), findsOneWidget);

expect(find.text('Continue'), findsOneWidget);
expect(find.text('Settings'), findsNothing);

await t.tap(find.byIcon(Icons.settings));
await t.pumpAndSettle();

expect(find.text('Settings'), findsOneWidget);

expect(find.text('New game'), findsNothing);
expect(find.text('Continue'), findsNothing);

await t.tap(find.text('Theme'));
await t.pumpAndSettle();

expect(find.text('Classic'), findsOneWidget);
expect(find.text('Chocolate'), findsOneWidget);

await t.tap(find.text('Chocolate'));

expect(find.text('Chocolate'), findsOneWidget);
expect(find.text('Classic'), findsOneWidget);
});
});
}
2 changes: 1 addition & 1 deletion lib/common/presentation/menu.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class HomeScreen extends HookConsumerWidget {
title: Text(S.of(context).appTitle),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.settings),
icon: const Icon(Icons.settings, semanticLabel: 'settings'),
onPressed: () => const SettingsRoute().push(context))
],
),
Expand Down
6 changes: 5 additions & 1 deletion lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,11 @@ Future<void> main() async {
};
}

@immutable
class MyApp extends HookConsumerWidget {
const MyApp({super.key});
final Locale? locale;

const MyApp({super.key, this.locale});

@override
Widget build(BuildContext context, WidgetRef ref) {
Expand Down Expand Up @@ -62,6 +65,7 @@ class MyApp extends HookConsumerWidget {
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: S.delegate.supportedLocales,
locale: locale,
theme: ref.watch(themeDataProvider).valueOrNull,
);
}
Expand Down
5 changes: 3 additions & 2 deletions lib/monetization/presentation/subscription_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -191,10 +191,11 @@ class AlreadyPremium extends ConsumerWidget {
S.of(context).subscription_manageSubscription_ios_url);
}
if (uri != null) {
final linkError =
S.of(context).subscription_manageSubscription_linkError;
if (!await launchUrl(uri,
mode: LaunchMode.externalApplication)) {
ref.read(_messageProvider.notifier).state =
S.of(context).subscription_manageSubscription_linkError;
ref.read(_messageProvider.notifier).state = linkError;
}
}
},
Expand Down
Loading

0 comments on commit 0200267

Please sign in to comment.