Skip to content

Commit f65d9b2

Browse files
authored
Write golden image tests (#213)
1 parent 9bcbe97 commit f65d9b2

File tree

224 files changed

+1965
-154
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

224 files changed

+1965
-154
lines changed

.github/workflows/flutter.yml

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ env:
1010
GIT_CRYPT_KEY: ${{ secrets.GIT_CRYPT_KEY }}
1111

1212
jobs:
13-
build_apk:
13+
test:
1414
if: ${{ startsWith(github.ref, 'refs/heads/') }}
1515
runs-on: ubuntu-latest
1616
steps:
@@ -19,6 +19,38 @@ jobs:
1919
run: |
2020
set -e
2121
22+
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
23+
bash ./.github/workflows/prepare.sh
24+
- name: Setup Flutter
25+
uses: subosito/flutter-action@v2
26+
with:
27+
cache: true
28+
flutter-version: ${{ env.FLUTTER_VERSION }}
29+
30+
- name: Prepare packages/api
31+
run: |
32+
set -e
33+
34+
cd ./packages/api
35+
./tool/build.sh
36+
flutter test --coverage
37+
- run: flutter test --coverage
38+
- uses: actions/upload-artifact@v3
39+
if: failure()
40+
with:
41+
name: failures
42+
path: "**/failures/*.png"
43+
- uses: codecov/codecov-action@v3
44+
45+
build_apk:
46+
if: ${{ github.ref == 'refs/heads/master' || contains(github.ref, 'android') }}
47+
runs-on: ubuntu-latest
48+
steps:
49+
- uses: actions/checkout@v3
50+
- name: Prepare repo
51+
run: |
52+
set -e
53+
2254
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
2355
bash ./.github/workflows/prepare.sh
2456

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
/coverage/
2+
**/failures/*.png
3+
14
/android/fastlane/README.md
25
/android/fastlane/report.xml
36
/ios/Flutter/.last_build_id

dart_test.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
tags:
2+
golden:

lib/main.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'package:flutter_localizations/flutter_localizations.dart';
44
import 'package:provider/provider.dart';
55
import 'package:the_app/src/abstracts/error_reporting.dart' as error_reporting;
66
import 'package:the_app/src/abstracts/firebase.dart' as firebase;
7+
import 'package:the_app/src/abstracts/http.dart' as http;
78
import 'package:the_app/src/intl.dart';
89
import 'package:the_app/src/link.dart';
910
import 'package:the_app/src/screens/home.dart';
@@ -74,6 +75,7 @@ class MyApp extends StatelessWidget {
7475
ChangeNotifierProvider<DarkTheme>.value(value: darkTheme),
7576
ChangeNotifierProvider<DevTools>.value(value: devTools),
7677
ChangeNotifierProvider<FontScale>.value(value: fontScale),
78+
Provider(create: (_) => http.configureHttpClient()),
7779
],
7880
child: Builder(builder: (context) => _buildApp(context)),
7981
);
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import 'package:cached_network_image/cached_network_image.dart' as lib;
2+
import 'package:flutter/material.dart';
3+
import 'package:flutter_cache_manager/flutter_cache_manager.dart' as lib;
4+
5+
@visibleForTesting
6+
lib.BaseCacheManager? debugCacheManager;
7+
8+
lib.BaseCacheManager get manager =>
9+
debugCacheManager ?? lib.DefaultCacheManager();
10+
11+
ImageProvider image(String url) =>
12+
lib.CachedNetworkImageProvider(url, cacheManager: manager);
13+
14+
class ImageWidget extends StatelessWidget {
15+
final String imageUrl;
16+
17+
const ImageWidget(this.imageUrl, {super.key});
18+
19+
@override
20+
Widget build(BuildContext context) {
21+
return lib.CachedNetworkImage(
22+
cacheManager: manager,
23+
errorWidget: (_, __, ___) => const DecoratedBox(
24+
decoration: BoxDecoration(
25+
color: Colors.grey,
26+
),
27+
),
28+
fit: BoxFit.cover,
29+
imageUrl: imageUrl,
30+
);
31+
}
32+
}

lib/src/abstracts/http.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import 'package:http/http.dart' as lib;
2+
3+
lib.Client configureHttpClient() => lib.Client();
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import 'package:flutter/material.dart';
2+
3+
@visibleForTesting
4+
var debugDeterministic = false;
5+
6+
class AdaptiveProgressIndicator extends StatelessWidget {
7+
final double? value;
8+
9+
const AdaptiveProgressIndicator({super.key, this.value});
10+
11+
@override
12+
Widget build(BuildContext context) {
13+
return Center(
14+
child: debugDeterministic
15+
? const Text('Loading...')
16+
: CircularProgressIndicator.adaptive(value: value),
17+
);
18+
}
19+
}

lib/src/api.dart

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'dart:io';
44
import 'package:flutter/material.dart';
55
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
66
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
7+
import 'package:http/http.dart' as http;
78
import 'package:provider/provider.dart';
89
import 'package:the_api/api.dart';
910
import 'package:the_api/oauth_token.dart';
@@ -172,22 +173,21 @@ typedef ApiOnJsonMap = void Function(Map jsonMap);
172173
typedef ApiOnError = void Function(dynamic error);
173174

174175
class ApiApp extends StatefulWidget {
175-
final Api api;
176176
final Widget child;
177+
final bool enableBatch;
177178

178-
ApiApp({
179+
const ApiApp({
179180
required this.child,
181+
this.enableBatch = true,
180182
Key? key,
181-
}) : api = Api(config.apiRoot, config.clientId, config.clientSecret)
182-
..httpHeaders['Api-Bb-Code-Chr'] = '1'
183-
..httpHeaders['Api-Post-Tree'] = '1',
184-
super(key: key);
183+
}) : super(key: key);
185184

186185
@override
187186
State<ApiApp> createState() => _ApiAppState();
188187
}
189188

190189
class _ApiAppState extends State<ApiApp> {
190+
late final Api api;
191191
final secureStorage = const FlutterSecureStorage();
192192
final visitor = User.zero();
193193

@@ -196,15 +196,25 @@ class _ApiAppState extends State<ApiApp> {
196196
OauthToken? _token;
197197
var _tokenHasBeenSet = false;
198198

199-
Api get api => widget.api;
200-
201199
String get _secureStorageKeyToken =>
202200
kSecureStorageKeyPrefixToken + api.clientId;
203201

204202
@override
205203
void initState() {
206204
super.initState();
207205

206+
api = Api(
207+
context.read<http.Client>(),
208+
apiRoot: config.apiRoot,
209+
clientId: config.clientId,
210+
clientSecret: config.clientSecret,
211+
enableBatch: widget.enableBatch,
212+
httpHeaders: const {
213+
'Api-Bb-Code-Chr': '1',
214+
'Api-Post-Tree': '1',
215+
},
216+
);
217+
208218
secureStorage.read(key: _secureStorageKeyToken).then<OauthToken?>(
209219
(value) {
210220
try {

lib/src/intl.dart

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,13 @@ import 'package:intl/intl.dart';
33
import 'package:the_app/l10n/messages_all.dart';
44
import 'package:timeago/timeago.dart' as timeago;
55

6+
@visibleForTesting
7+
DateTime? debugClock;
8+
69
final _numberFormatCompact = NumberFormat.compact();
710

11+
DateTime get _clock => debugClock ?? DateTime.now();
12+
813
L10n l(BuildContext context) => Localizations.of<L10n>(context, L10n)!;
914

1015
MaterialLocalizations lm(BuildContext context) =>
@@ -388,8 +393,9 @@ class L10n {
388393
Intl.message('Unignore', locale: localeName, name: 'userUnignore');
389394

390395
static Future<L10n> load(Locale locale) {
396+
final countryCode = locale.countryCode ?? '';
391397
final localeName = Intl.canonicalizedLocale(
392-
locale.countryCode!.isEmpty ? locale.languageCode : locale.toString());
398+
countryCode.isEmpty ? locale.languageCode : locale.toString());
393399
return initializeMessages(localeName).then((_) => L10n(localeName));
394400
}
395401
}
@@ -419,9 +425,10 @@ String formatTimestamp(BuildContext context, int? timestamp) {
419425
if (timestamp == null) return '';
420426

421427
final d = secondsToDateTime(timestamp);
422-
if (DateTime.now().subtract(const Duration(days: 30)).isBefore(d)) {
428+
final clock = _clock;
429+
if (clock.subtract(const Duration(days: 30)).isBefore(d)) {
423430
final locale = Localizations.localeOf(context).languageCode;
424-
return timeago.format(d, locale: locale);
431+
return timeago.format(d, clock: clock, locale: locale);
425432
}
426433

427434
// TODO: use date format from device locale

lib/src/screens/initial_path.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'package:flutter/material.dart';
2+
import 'package:the_app/src/abstracts/progress_indicator.dart';
23
import 'package:the_app/src/api.dart';
34
import 'package:the_app/src/link.dart';
45
import 'package:the_app/src/screens/home.dart';
@@ -68,7 +69,7 @@ class _InitialPathState extends State<InitialPathScreen>
6869
: FutureBuilder<Widget>(
6970
builder: (__, snapshot) =>
7071
snapshot.data ??
71-
const Scaffold(body: Center(child: CircularProgressIndicator())),
72+
const Scaffold(body: AdaptiveProgressIndicator()),
7273
future: _future,
7374
);
7475
}

0 commit comments

Comments
 (0)