Skip to content

Commit

Permalink
Write golden image tests (#213)
Browse files Browse the repository at this point in the history
  • Loading branch information
daohoangson authored Aug 16, 2023
1 parent 9bcbe97 commit f65d9b2
Show file tree
Hide file tree
Showing 224 changed files with 1,965 additions and 154 deletions.
34 changes: 33 additions & 1 deletion .github/workflows/flutter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ env:
GIT_CRYPT_KEY: ${{ secrets.GIT_CRYPT_KEY }}

jobs:
build_apk:
test:
if: ${{ startsWith(github.ref, 'refs/heads/') }}
runs-on: ubuntu-latest
steps:
Expand All @@ -19,6 +19,38 @@ jobs:
run: |
set -e
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
bash ./.github/workflows/prepare.sh
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
cache: true
flutter-version: ${{ env.FLUTTER_VERSION }}

- name: Prepare packages/api
run: |
set -e
cd ./packages/api
./tool/build.sh
flutter test --coverage
- run: flutter test --coverage
- uses: actions/upload-artifact@v3
if: failure()
with:
name: failures
path: "**/failures/*.png"
- uses: codecov/codecov-action@v3

build_apk:
if: ${{ github.ref == 'refs/heads/master' || contains(github.ref, 'android') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Prepare repo
run: |
set -e
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
bash ./.github/workflows/prepare.sh
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/coverage/
**/failures/*.png

/android/fastlane/README.md
/android/fastlane/report.xml
/ios/Flutter/.last_build_id
Expand Down
2 changes: 2 additions & 0 deletions dart_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
tags:
golden:
2 changes: 2 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:provider/provider.dart';
import 'package:the_app/src/abstracts/error_reporting.dart' as error_reporting;
import 'package:the_app/src/abstracts/firebase.dart' as firebase;
import 'package:the_app/src/abstracts/http.dart' as http;
import 'package:the_app/src/intl.dart';
import 'package:the_app/src/link.dart';
import 'package:the_app/src/screens/home.dart';
Expand Down Expand Up @@ -74,6 +75,7 @@ class MyApp extends StatelessWidget {
ChangeNotifierProvider<DarkTheme>.value(value: darkTheme),
ChangeNotifierProvider<DevTools>.value(value: devTools),
ChangeNotifierProvider<FontScale>.value(value: fontScale),
Provider(create: (_) => http.configureHttpClient()),
],
child: Builder(builder: (context) => _buildApp(context)),
);
Expand Down
32 changes: 32 additions & 0 deletions lib/src/abstracts/cached_network_image.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import 'package:cached_network_image/cached_network_image.dart' as lib;
import 'package:flutter/material.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart' as lib;

@visibleForTesting
lib.BaseCacheManager? debugCacheManager;

lib.BaseCacheManager get manager =>
debugCacheManager ?? lib.DefaultCacheManager();

ImageProvider image(String url) =>
lib.CachedNetworkImageProvider(url, cacheManager: manager);

class ImageWidget extends StatelessWidget {
final String imageUrl;

const ImageWidget(this.imageUrl, {super.key});

@override
Widget build(BuildContext context) {
return lib.CachedNetworkImage(
cacheManager: manager,
errorWidget: (_, __, ___) => const DecoratedBox(
decoration: BoxDecoration(
color: Colors.grey,
),
),
fit: BoxFit.cover,
imageUrl: imageUrl,
);
}
}
3 changes: 3 additions & 0 deletions lib/src/abstracts/http.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import 'package:http/http.dart' as lib;

lib.Client configureHttpClient() => lib.Client();
19 changes: 19 additions & 0 deletions lib/src/abstracts/progress_indicator.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import 'package:flutter/material.dart';

@visibleForTesting
var debugDeterministic = false;

class AdaptiveProgressIndicator extends StatelessWidget {
final double? value;

const AdaptiveProgressIndicator({super.key, this.value});

@override
Widget build(BuildContext context) {
return Center(
child: debugDeterministic
? const Text('Loading...')
: CircularProgressIndicator.adaptive(value: value),
);
}
}
26 changes: 18 additions & 8 deletions lib/src/api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
import 'package:http/http.dart' as http;
import 'package:provider/provider.dart';
import 'package:the_api/api.dart';
import 'package:the_api/oauth_token.dart';
Expand Down Expand Up @@ -172,22 +173,21 @@ typedef ApiOnJsonMap = void Function(Map jsonMap);
typedef ApiOnError = void Function(dynamic error);

class ApiApp extends StatefulWidget {
final Api api;
final Widget child;
final bool enableBatch;

ApiApp({
const ApiApp({
required this.child,
this.enableBatch = true,
Key? key,
}) : api = Api(config.apiRoot, config.clientId, config.clientSecret)
..httpHeaders['Api-Bb-Code-Chr'] = '1'
..httpHeaders['Api-Post-Tree'] = '1',
super(key: key);
}) : super(key: key);

@override
State<ApiApp> createState() => _ApiAppState();
}

class _ApiAppState extends State<ApiApp> {
late final Api api;
final secureStorage = const FlutterSecureStorage();
final visitor = User.zero();

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

Api get api => widget.api;

String get _secureStorageKeyToken =>
kSecureStorageKeyPrefixToken + api.clientId;

@override
void initState() {
super.initState();

api = Api(
context.read<http.Client>(),
apiRoot: config.apiRoot,
clientId: config.clientId,
clientSecret: config.clientSecret,
enableBatch: widget.enableBatch,
httpHeaders: const {
'Api-Bb-Code-Chr': '1',
'Api-Post-Tree': '1',
},
);

secureStorage.read(key: _secureStorageKeyToken).then<OauthToken?>(
(value) {
try {
Expand Down
13 changes: 10 additions & 3 deletions lib/src/intl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@ import 'package:intl/intl.dart';
import 'package:the_app/l10n/messages_all.dart';
import 'package:timeago/timeago.dart' as timeago;

@visibleForTesting
DateTime? debugClock;

final _numberFormatCompact = NumberFormat.compact();

DateTime get _clock => debugClock ?? DateTime.now();

L10n l(BuildContext context) => Localizations.of<L10n>(context, L10n)!;

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

static Future<L10n> load(Locale locale) {
final countryCode = locale.countryCode ?? '';
final localeName = Intl.canonicalizedLocale(
locale.countryCode!.isEmpty ? locale.languageCode : locale.toString());
countryCode.isEmpty ? locale.languageCode : locale.toString());
return initializeMessages(localeName).then((_) => L10n(localeName));
}
}
Expand Down Expand Up @@ -419,9 +425,10 @@ String formatTimestamp(BuildContext context, int? timestamp) {
if (timestamp == null) return '';

final d = secondsToDateTime(timestamp);
if (DateTime.now().subtract(const Duration(days: 30)).isBefore(d)) {
final clock = _clock;
if (clock.subtract(const Duration(days: 30)).isBefore(d)) {
final locale = Localizations.localeOf(context).languageCode;
return timeago.format(d, locale: locale);
return timeago.format(d, clock: clock, locale: locale);
}

// TODO: use date format from device locale
Expand Down
3 changes: 2 additions & 1 deletion lib/src/screens/initial_path.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:the_app/src/abstracts/progress_indicator.dart';
import 'package:the_app/src/api.dart';
import 'package:the_app/src/link.dart';
import 'package:the_app/src/screens/home.dart';
Expand Down Expand Up @@ -68,7 +69,7 @@ class _InitialPathState extends State<InitialPathScreen>
: FutureBuilder<Widget>(
builder: (__, snapshot) =>
snapshot.data ??
const Scaffold(body: Center(child: CircularProgressIndicator())),
const Scaffold(body: AdaptiveProgressIndicator()),
future: _future,
);
}
Expand Down
5 changes: 2 additions & 3 deletions lib/src/screens/search/thread.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:the_api/node.dart';
import 'package:the_api/user.dart';
import 'package:the_app/src/abstracts/progress_indicator.dart';
import 'package:the_app/src/intl.dart';
import 'package:the_app/src/widgets/threads.dart';
import 'package:the_app/src/api.dart';
Expand Down Expand Up @@ -80,9 +81,7 @@ class ThreadSearchDelegate extends SearchDelegate {
initialJson: snapshot.data,
threadsKey: 'data',
)
: const Center(
child: CircularProgressIndicator(),
),
: const AdaptiveProgressIndicator(),
);
}

Expand Down
5 changes: 2 additions & 3 deletions lib/src/screens/thread_view.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:provider/provider.dart';
import 'package:share_plus/share_plus.dart';
import 'package:the_api/thread.dart';
import 'package:the_app/src/abstracts/cached_network_image.dart' as cached;
import 'package:the_app/src/constants.dart';
import 'package:the_app/src/widgets/font_control.dart';
import 'package:the_app/src/widgets/post_editor.dart';
Expand Down Expand Up @@ -104,8 +104,7 @@ class _ThreadViewState extends State<ThreadViewScreen> {
Widget built = Row(
children: <Widget>[
CircleAvatar(
backgroundImage:
avatar != null ? CachedNetworkImageProvider(avatar) : null,
backgroundImage: avatar != null ? cached.image(avatar) : null,
),
Expanded(
child: Padding(
Expand Down
5 changes: 2 additions & 3 deletions lib/src/widgets/app_bar.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:the_api/user.dart';
import 'package:the_app/src/abstracts/cached_network_image.dart' as cached;
import 'package:the_app/src/intl.dart';
import 'package:the_app/src/screens/login.dart';
import 'package:the_app/src/api.dart';
Expand Down Expand Up @@ -29,8 +29,7 @@ class AppBarDrawerHeader extends StatelessWidget {
return AspectRatio(
aspectRatio: 1.0,
child: CircleAvatar(
backgroundImage:
avatar != null ? CachedNetworkImageProvider(avatar) : null,
backgroundImage: avatar != null ? cached.image(avatar) : null,
),
);
}
Expand Down
4 changes: 2 additions & 2 deletions lib/src/widgets/attachment_editor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import 'package:image_picker/image_picker.dart';
import 'package:provider/provider.dart';
import 'package:the_api/attachment.dart';
import 'package:the_api/user.dart';
import 'package:the_app/src/abstracts/cached_network_image.dart' as cached;
import 'package:the_app/src/intl.dart';
import 'package:the_app/src/widgets/image.dart';
import 'package:the_app/src/api.dart';

class AttachmentEditorWidget extends StatefulWidget {
Expand Down Expand Up @@ -82,7 +82,7 @@ class AttachmentEditorState extends State<AttachmentEditorWidget> {
child: Opacity(
opacity: apiData != null ? 1.0 : 0.5,
child: thumbnail != null
? buildCachedNetworkImage(thumbnail)
? cached.ImageWidget(thumbnail)
: Image.file(
attachment.file,
fit: BoxFit.cover,
Expand Down
27 changes: 16 additions & 11 deletions lib/src/widgets/font_control.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,36 @@ import 'package:the_app/src/constants.dart';
import 'package:the_app/src/intl.dart';
import 'package:the_app/src/widgets/menu/dark_theme.dart';

class FontScale extends ChangeNotifier {
abstract class FontScale extends ChangeNotifier {
static var min = .5;
static var max = 3.0;

double get value;
set value(double v);

static Future<FontScale> create() async {
final fontScale = _FontScale();
final prefs = await SharedPreferences.getInstance();
fontScale._value = prefs.getDouble(kPrefKeyFontScale);
return fontScale;
}
}

class _FontScale extends ChangeNotifier implements FontScale {
double? _value;

@override
double get value => _value ?? 1.0;

FontScale._();

@override
set value(double v) {
if (v < min || v > max) return;
if (v < FontScale.min || v > FontScale.max) return;
_value = v;
notifyListeners();

SharedPreferences.getInstance()
.then((prefs) => prefs.setDouble(kPrefKeyFontScale, v));
}

static Future<FontScale> create() async {
final fontScale = FontScale._();
final prefs = await SharedPreferences.getInstance();
fontScale._value = prefs.getDouble(kPrefKeyFontScale);
return fontScale;
}
}

class FontControlWidget extends StatelessWidget {
Expand Down
Loading

1 comment on commit f65d9b2

@github-actions
Copy link

@github-actions github-actions bot commented on f65d9b2 Aug 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Linux build

# Generated code do not commit.
file(TO_CMAKE_PATH "/opt/hostedtoolcache/flutter/stable-3.10.6-x64" FLUTTER_ROOT)
file(TO_CMAKE_PATH "/home/runner/work/flutter-tinhte_demo/flutter-tinhte_demo" PROJECT_DIR)

set(FLUTTER_VERSION "2.0.1+6121" PARENT_SCOPE)
set(FLUTTER_VERSION_MAJOR 2 PARENT_SCOPE)
set(FLUTTER_VERSION_MINOR 0 PARENT_SCOPE)
set(FLUTTER_VERSION_PATCH 1 PARENT_SCOPE)
set(FLUTTER_VERSION_BUILD 6121 PARENT_SCOPE)

# Environment variables to pass to tool_backend.sh
list(APPEND FLUTTER_TOOL_ENVIRONMENT
  "FLUTTER_ROOT=/opt/hostedtoolcache/flutter/stable-3.10.6-x64"
  "PROJECT_DIR=/home/runner/work/flutter-tinhte_demo/flutter-tinhte_demo"
  "DART_OBFUSCATION=false"
  "TRACK_WIDGET_CREATION=true"
  "TREE_SHAKE_ICONS=true"
  "PACKAGE_CONFIG=/home/runner/work/flutter-tinhte_demo/flutter-tinhte_demo/.dart_tool/package_config.json"
  "FLUTTER_TARGET=lib/main.dart"
)

bundle.zip

Windows build

# Generated code do not commit.
file(TO_CMAKE_PATH "C:\\hostedtoolcache\\windows\\flutter\\stable-3.10.6-x64" FLUTTER_ROOT)
file(TO_CMAKE_PATH "D:\\a\\flutter-tinhte_demo\\flutter-tinhte_demo" PROJECT_DIR)

set(FLUTTER_VERSION "2.0.1+6121" PARENT_SCOPE)
set(FLUTTER_VERSION_MAJOR 2 PARENT_SCOPE)
set(FLUTTER_VERSION_MINOR 0 PARENT_SCOPE)
set(FLUTTER_VERSION_PATCH 1 PARENT_SCOPE)
set(FLUTTER_VERSION_BUILD 6121 PARENT_SCOPE)

# Environment variables to pass to tool_backend.sh
list(APPEND FLUTTER_TOOL_ENVIRONMENT
  "FLUTTER_ROOT=C:\\hostedtoolcache\\windows\\flutter\\stable-3.10.6-x64"
  "PROJECT_DIR=D:\\a\\flutter-tinhte_demo\\flutter-tinhte_demo"
  "FLUTTER_ROOT=C:\\hostedtoolcache\\windows\\flutter\\stable-3.10.6-x64"
  "FLUTTER_EPHEMERAL_DIR=D:\\a\\flutter-tinhte_demo\\flutter-tinhte_demo\\windows\\flutter\\ephemeral"
  "PROJECT_DIR=D:\\a\\flutter-tinhte_demo\\flutter-tinhte_demo"
  "FLUTTER_TARGET=lib\\main.dart"
  "DART_OBFUSCATION=false"
  "TRACK_WIDGET_CREATION=true"
  "TREE_SHAKE_ICONS=true"
  "PACKAGE_CONFIG=D:\\a\\flutter-tinhte_demo\\flutter-tinhte_demo\\.dart_tool\\package_config.json"
)

  1. Installing a test certificate directly from an MSIX package
  2. Then run the installer

Android builds

sdk.dir=/usr/local/lib/android/sdk
flutter.sdk=/opt/hostedtoolcache/flutter/stable-3.10.6-x64
flutter.buildMode=release
flutter.versionName=2.0.1
flutter.versionCode=6121

macOS build

// This is a generated file; do not edit or check into version control.
FLUTTER_ROOT=/Users/runner/hostedtoolcache/flutter/stable-3.10.6-x64
FLUTTER_APPLICATION_PATH=/Users/runner/work/flutter-tinhte_demo/flutter-tinhte_demo
COCOAPODS_PARALLEL_CODE_SIGN=true
FLUTTER_TARGET=lib/main.dart
FLUTTER_BUILD_DIR=build
FLUTTER_BUILD_NAME=2.0.1
FLUTTER_BUILD_NUMBER=6121
DART_OBFUSCATION=false
TRACK_WIDGET_CREATION=true
TREE_SHAKE_ICONS=true
PACKAGE_CONFIG=/Users/runner/work/flutter-tinhte_demo/flutter-tinhte_demo/.dart_tool/package_config.json

iOS build

// This is a generated file; do not edit or check into version control.
FLUTTER_ROOT=/Users/runner/hostedtoolcache/flutter/stable-3.10.6-x64
FLUTTER_APPLICATION_PATH=/Users/runner/work/flutter-tinhte_demo/flutter-tinhte_demo
COCOAPODS_PARALLEL_CODE_SIGN=true
FLUTTER_TARGET=lib/main.dart
FLUTTER_BUILD_DIR=build
FLUTTER_BUILD_NAME=2.0.1
FLUTTER_BUILD_NUMBER=6121
EXCLUDED_ARCHS[sdk=iphonesimulator*]=i386
EXCLUDED_ARCHS[sdk=iphoneos*]=armv7
DART_OBFUSCATION=false
TRACK_WIDGET_CREATION=false
TREE_SHAKE_ICONS=true
PACKAGE_CONFIG=/Users/runner/work/flutter-tinhte_demo/flutter-tinhte_demo/.dart_tool/package_config.json

manifest.plist

Please sign in to comment.