Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add UI tests #355

Merged
merged 14 commits into from
Dec 6, 2022
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
.pub-cache/
.pub/
/build/
/test/widgets/**/failures/

# Web related
lib/generated_plugin_registrant.dart
Expand Down
18 changes: 18 additions & 0 deletions lib/widgets/pages/settings/setting_value_text.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import 'package:coffeecard/base/style/text_styles.dart';
import 'package:flutter/material.dart';

class SettingValueText extends StatelessWidget {
final String value;

const SettingValueText({required this.value});

@override
Widget build(BuildContext context) {
return Text(
value,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: AppTextStyle.settingValue,
);
}
}
13 changes: 6 additions & 7 deletions lib/widgets/pages/settings/settings_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import 'package:coffeecard/widgets/pages/settings/change_passcode_flow.dart';
import 'package:coffeecard/widgets/pages/settings/credits_page.dart';
import 'package:coffeecard/widgets/pages/settings/faq_page.dart';
import 'package:coffeecard/widgets/pages/settings/opening_hours_page.dart';
import 'package:coffeecard/widgets/pages/settings/setting_value_text.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:gap/gap.dart';
Expand Down Expand Up @@ -79,11 +80,10 @@ class SettingsPage extends StatelessWidget {
builder: (context, colorIfShimmer) {
return ColoredBox(
color: colorIfShimmer,
child: Text(
(userState is UserLoaded)
child: SettingValueText(
value: (userState is UserLoaded)
? userState.user.email
: Strings.emailShimmerText,
style: AppTextStyle.settingValue,
),
);
},
Expand All @@ -99,9 +99,8 @@ class SettingsPage extends StatelessWidget {
),
SettingListEntry(
name: Strings.passcode,
valueWidget: Text(
Strings.change,
style: AppTextStyle.settingValue,
valueWidget: const SettingValueText(
value: Strings.change,
),
onTap: _ifUserStateLoaded(
userState,
Expand Down Expand Up @@ -158,7 +157,7 @@ class SettingsPage extends StatelessWidget {

return ColoredBox(
color: colorIfShimmer,
child: Text(text, style: AppTextStyle.settingValue),
child: SettingValueText(value: text),
);
},
),
Expand Down
15 changes: 14 additions & 1 deletion test/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,17 @@ void main() {
// Create mock object.
var cat = MockSecureStorage();
}
```
```

## Golden tests

Golden files for UI tests are auto-generated. This is doing by first implementing the test (Pump widget, compare to golden file), then generating the file using
```bash
flutter test --update-goldens [path/to/test.dart]
```

If no path is given, all goldens will be updated.

Due to OS specific rendering, golden tests will fail in the pipeline if not generated using a linux distro.

(**TODO**: Make dockerfile for golden generation)
37 changes: 37 additions & 0 deletions test/widgets/components/error_section_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import 'package:coffeecard/widgets/components/error_section.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
testWidgets('Error section has error message', (tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: ErrorSection(
error: 'Test error',
retry: () {},
),
),
),
);

expect(find.textContaining('Test error'), findsOneWidget);
});

testWidgets('Error section matches golden file', (tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: ErrorSection(
error: 'Test error',
retry: () {},
),
),
),
);
await expectLater(
find.byType(ErrorSection),
matchesGoldenFile('goldens/error_section.png'),
);
});
}
Binary file added test/widgets/components/goldens/error_section.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
50 changes: 50 additions & 0 deletions test/widgets/components/settings_list_entry_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import 'package:coffeecard/widgets/components/settings_list_entry.dart';
import 'package:coffeecard/widgets/pages/settings/setting_value_text.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
testWidgets('SettingsListEntry should match golden file with "normal" value',
(tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Scaffold(
body: SettingListEntry(
name: 'email',
valueWidget: SettingValueText(
value: 'Normal email',
),
),
),
),
);

expect(find.byType(SettingListEntry), findsOneWidget);
expect(find.text('Normal email'), findsOneWidget);

await expectLater(
find.byType(SettingListEntry),
matchesGoldenFile('goldens/settings_list_entry_normal.png'),
);
});
testWidgets('SettingListEntry when truncated should match golden file',
(tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Scaffold(
body: SettingListEntry(
name: 'email',
valueWidget: SettingValueText(
value:
'This is a very long text that should be truncated, This is a very long text that should be truncated, This is a very long text that should be truncated',
),
),
),
),
);
await expectLater(
find.byType(SettingListEntry),
matchesGoldenFile('goldens/setting_list_entry_truncated.png'),
);
});
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
86 changes: 86 additions & 0 deletions test/widgets/components/stats/leaderboard_list_entry_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import 'package:coffeecard/widgets/components/stats/leaderboard_list_entry.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
testWidgets('LeaderboardListEntry given rank 0 should display rank as -',
(tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Scaffold(
body: LeaderboardListEntry(
id: 0,
name: 'name',
score: 0,
rank: 0,
highlight: false,
),
),
),
);

expect(find.byType(LeaderboardListEntry), findsOneWidget);
expect(find.text('-'), findsOneWidget);
});
testWidgets('LeaderboardListEntry given rank 1 should display rank as 1',
(tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Scaffold(
body: LeaderboardListEntry(
id: 0,
name: 'name',
score: 0,
rank: 1,
highlight: false,
),
),
),
);

expect(find.byType(LeaderboardListEntry), findsOneWidget);
expect(find.text('1'), findsOneWidget);
});
testWidgets('LeaderboardListEntry should match golden file', (tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Scaffold(
body: LeaderboardListEntry(
id: 1,
highlight: false,
name: 'Test',
rank: 1,
score: 100,
),
),
),
);
await expectLater(
find.byType(LeaderboardListEntry),
matchesGoldenFile('goldens/leaderboard_list_entry.png'),
);
});

testWidgets(
'LeaderboardListEntry when highlighted and truncated should match golden file',
(tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Scaffold(
body: LeaderboardListEntry(
id: 1,
highlight: true,
name:
'TestHighlighted with a very long name so it gets truncated and the score remains on the right',
rank: 30,
score: 1,
),
),
),
);
await expectLater(
find.byType(LeaderboardListEntry),
matchesGoldenFile('goldens/leaderboard_list_entry_highlighted.png'),
);
});
}
57 changes: 57 additions & 0 deletions test/widgets/components/stats/stat_card_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import 'package:coffeecard/widgets/components/stats/stat_card.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
testWidgets('Statistics card has a title and rank', (tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Scaffold(
body: StatisticsCard(
title: 'StatsTest',
rank: 2,
loading: false,
),
),
),
);

expect(find.text('StatsTest'), findsOneWidget);
expect(find.text('2nd'), findsOneWidget);
});

testWidgets('Statistics card shows N/A if rank is 0', (tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Scaffold(
body: StatisticsCard(
title: 'StatsTest',
rank: 0,
loading: false,
),
),
),
);

expect(find.text('StatsTest'), findsOneWidget);
expect(find.textContaining('N/A'), findsOneWidget);
});

testWidgets('Statistics card matches golden file', (tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Scaffold(
body: StatisticsCard(
title: 'This semester',
rank: 2,
loading: false,
),
),
),
);
await expectLater(
find.byType(StatisticsCard),
matchesGoldenFile('goldens/stat_card.png'),
);
});
}
54 changes: 54 additions & 0 deletions test/widgets/components/tickets/buy_tickets_card_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import 'package:coffeecard/models/ticket/product.dart';
import 'package:coffeecard/widgets/components/tickets/buy_tickets_card.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
testWidgets('BuyTicketsCard should have product information', (tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: BuyTicketsCard(
product: const Product(
id: 0,
name: 'CoffeeTitle',
description: 'CoffeeDescription',
amount: 2,
price: 10,
),
onTap: (context, product, state) {},
),
),
),
);

expect(find.text('CoffeeTitle'), findsOneWidget);
expect(find.text('CoffeeDescription'), findsOneWidget);
expect(find.text('2 tickets'), findsOneWidget);
expect(find.text('10,-'), findsOneWidget);
});

testWidgets('BuyTicketsCard should match golden file', (tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: BuyTicketsCard(
product: const Product(
id: 0,
name: 'Coffee',
description: 'Coffee',
amount: 1,
price: 1,
),
onTap: (context, product, state) {},
),
),
),
);

await expectLater(
find.byType(BuyTicketsCard),
matchesGoldenFile('goldens/buy_tickets_card.png'),
);
});
}
Loading