Skip to content

feat: basic testing framework #215

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

Merged
merged 3 commits into from
Jul 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 24 additions & 6 deletions design_system/lib/theme/app_text_styles.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
//ignore_for_file: unused-files, unused-code

import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:google_fonts/google_fonts.dart';

const FontWeight _semiboldWeight = FontWeight.w600;

class AppTextStyles extends TextTheme {
final _isTesting = Platform.environment.containsKey('FLUTTER_TEST');

class AppTextStyles extends TextTheme {
const AppTextStyles({
super.headlineLarge,
super.headlineMedium,
Expand Down Expand Up @@ -45,12 +48,17 @@ class AppTextStyles extends TextTheme {
double fontSize,
FontWeight fontWeight,
) =>
GoogleFonts.roboto(
fontSize: fontSize,
fontWeight: fontWeight,
);
_isTesting
? TextStyle(fontSize: fontSize, fontWeight: fontWeight)
: GoogleFonts.roboto(
fontSize: fontSize,
fontWeight: fontWeight,
);

static AppTextStyles getDefaultAppStyles() => AppTextStyles.fromTextTheme(
static AppTextStyles getDefaultAppStyles() =>
_isTesting ? _testingTextTheme() : _appTextTheme();

static AppTextStyles _appTextTheme() => AppTextStyles.fromTextTheme(
textTheme: GoogleFonts.robotoTextTheme().copyWith(
labelLarge: _robotoTextStyle(20.sp, FontWeight.normal),
labelMedium: _robotoTextStyle(16.sp, FontWeight.normal),
Expand All @@ -60,6 +68,16 @@ class AppTextStyles extends TextTheme {
),
);

static AppTextStyles _testingTextTheme() => AppTextStyles.fromTextTheme(
textTheme: TextTheme(
labelLarge: _robotoTextStyle(20.sp, FontWeight.normal),
labelMedium: _robotoTextStyle(16.sp, FontWeight.normal),
labelSmall: _robotoTextStyle(14.sp, FontWeight.normal),
headlineMedium: _robotoTextStyle(20.sp, FontWeight.bold),
headlineLarge: _robotoTextStyle(24.sp, FontWeight.bold),
),
);

TextTheme getThemeData() => getDefaultAppStyles();
}

Expand Down
20 changes: 14 additions & 6 deletions design_system/lib/theme/custom_text_styles.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
// ignore_for_file: overridden_fields

import 'dart:io';

import 'package:design_system/extensions/color_extensions.dart';
import 'package:design_system/theme/custom_colors.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:design_system/theme/custom_colors.dart';

const FontWeight _semiboldWeight = FontWeight.w600;

Expand Down Expand Up @@ -57,11 +59,17 @@ class CustomTextStyles extends ThemeExtension<CustomTextStyles> {
FontWeight fontWeight,
CustomColors customColors,
) =>
GoogleFonts.roboto(
fontSize: fontSize,
fontWeight: fontWeight,
color: customColors.textColor!.getShade(500),
);
Platform.environment.containsKey('FLUTTER_TEST')
? TextStyle(
fontSize: fontSize,
fontWeight: fontWeight,
color: customColors.textColor!.getShade(500),
)
: GoogleFonts.roboto(
fontSize: fontSize,
fontWeight: fontWeight,
color: customColors.textColor!.getShade(500),
);

@override
CustomTextStyles copyWith({MaterialColor? primary}) =>
Expand Down
2 changes: 1 addition & 1 deletion ios/Flutter/AppFrameworkInfo.plist
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>11.0</string>
<string>12.0</string>
</dict>
</plist>
32 changes: 12 additions & 20 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ PODS:
- Flutter
- flutter_web_browser (0.17.1):
- Flutter
- FMDB (2.7.5):
- FMDB/standard (= 2.7.5)
- FMDB/standard (2.7.5)
- integration_test (0.0.1):
- Flutter
- path_provider_foundation (0.0.1):
Expand All @@ -19,7 +16,7 @@ PODS:
- FlutterMacOS
- sqflite (0.0.3):
- Flutter
- FMDB (>= 2.7.5)
- FlutterMacOS

DEPENDENCIES:
- Flutter (from `Flutter`)
Expand All @@ -29,11 +26,7 @@ DEPENDENCIES:
- integration_test (from `.symlinks/plugins/integration_test/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite (from `.symlinks/plugins/sqflite/ios`)

SPEC REPOS:
trunk:
- FMDB
- sqflite (from `.symlinks/plugins/sqflite/darwin`)

EXTERNAL SOURCES:
Flutter:
Expand All @@ -51,19 +44,18 @@ EXTERNAL SOURCES:
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
sqflite:
:path: ".symlinks/plugins/sqflite/ios"
:path: ".symlinks/plugins/sqflite/darwin"

SPEC CHECKSUMS:
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
flutter_web_browser: 7bccaafbb0c5b8862afe7bcd158f15557109f61f
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
integration_test: 13825b8a9334a850581300559b8839134b124670
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_native_splash: 35ddbc7228eafcb3969dcc5f1fbbe27c1145a4f0
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
flutter_web_browser: 8fe4d18e7b1328ab3fbec6e67029d6996c2335d9
integration_test: d5929033778cc4991a187e4e1a85396fa4f59b3a
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
sqflite: c35dad70033b8862124f8337cc994a809fcd9fa3

PODFILE CHECKSUM: 4e8f8b2be68aeea4c0d5beb6ff1e79fface1d048

COCOAPODS: 1.15.2
COCOAPODS: 1.16.2
2 changes: 1 addition & 1 deletion ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1430;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
Expand Down
3 changes: 2 additions & 1 deletion lib/core/common/config.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// ignore_for_file: constant_identifier_names

import 'dart:async';
import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:flutter_template/core/common/environments.dart';
Expand All @@ -13,7 +14,7 @@ interface class Config {
static const String environmentFolder = 'environments';

static const debugMode = kDebugMode;

static bool testingMode = Platform.environment.containsKey('FLUTTER_TEST');
static late String apiBaseUrl;
static late String supabaseApiKey;
static late String appDirectoryPath;
Expand Down
2 changes: 1 addition & 1 deletion lib/core/di/di_utils_module.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ class UtilsDiModule {

extension _GetItDiModuleExtensions on GetIt {
void _setupModule() {
registerLazySingleton(() => AppRouter(get()));
registerLazySingleton(() => AppRouter(sessionRepository: get()));
}
}
7 changes: 4 additions & 3 deletions lib/ui/router/app_router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ part 'app_router.gr.dart';
class AppRouter extends _$AppRouter {
@override
final List<AutoRoute> routes;
final String? initialRoute;

ReevaluateListenable authReevaluateListenable;

AppRouter(SessionRepository sessionRepository)
AppRouter({required SessionRepository sessionRepository, this.initialRoute})
: authReevaluateListenable = ReevaluateListenable.stream(
sessionRepository.status.distinct().skip(1),
),
Expand All @@ -26,7 +27,7 @@ class AppRouter extends _$AppRouter {
path: '/',
guards: [UnauthenticatedGuard(sessionRepository)],
children: [
RedirectRoute(path: '', redirectTo: 'login'),
RedirectRoute(path: '', redirectTo: initialRoute ?? 'login'),
AutoRoute(path: 'login', page: SignInRoute.page),
],
),
Expand All @@ -35,7 +36,7 @@ class AppRouter extends _$AppRouter {
guards: [AuthenticatedGuard(sessionRepository)],
path: '/',
children: [
RedirectRoute(path: '', redirectTo: 'welcome'),
RedirectRoute(path: '', redirectTo: initialRoute ?? 'welcome'),
AutoRoute(path: 'welcome', page: WelcomeRoute.page),
],
),
Expand Down
4 changes: 2 additions & 2 deletions pubspec.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ dev_dependencies:
json_serializable: 6.8.0
lints: 4.0.0
mocktail: 1.0.3
path_provider_platform_interface: 2.1.2
plugin_platform_interface: 2.1.8

## TODO remove this when dart_code_linter updates the dependencies
dependency_overrides:
Expand Down
92 changes: 92 additions & 0 deletions test/mocks/mock_app.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import 'dart:async';

import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:flutter_template/core/common/config.dart';
import 'package:flutter_template/core/common/logger.dart';
import 'package:flutter_template/core/di/di_provider.dart';
import 'package:flutter_template/core/source/common/http_service.dart';
import 'package:flutter_template/main.dart';
import 'package:flutter_template/ui/router/app_router.dart';
import 'package:hive/hive.dart';
import 'package:path_provider_platform_interface/path_provider_platform_interface.dart';
import 'package:shared_preferences/shared_preferences.dart';

import 'services/http_service.dart';
import 'services/path_provider.dart';
import 'sources/secure_storage.dart';

class SimpleTesteableApp extends MyApp {
final Map<ApiOverrideKey, ResponseData>? apiOverrides;

final String initialRoute;
const SimpleTesteableApp._({
super.key,
this.apiOverrides,
this.initialRoute = '/',
}) : super();

static Future<SimpleTesteableApp> getUnauthenticatedApp({
Map<ApiOverrideKey, ResponseData>? apiOverrides,
String initialRoute = '/',
Map<String, String>? secureStorageData,
Map<String, Object>? sharedPreferencesData,
}) async {
await _initMockSdks(
apiOverrides: apiOverrides,
initialRoute: initialRoute,
sharedPreferenceData: secureStorageData,
secureStorageData: secureStorageData,
);
final app = SimpleTesteableApp._(
apiOverrides: apiOverrides,
initialRoute: initialRoute,
);
return app;
}
}

Future<void> _initMockSdks({
Map<ApiOverrideKey, ResponseData>? apiOverrides,
Map<String, String>? secureStorageData,
Map<String, Object>? sharedPreferenceData,
String? initialRoute,
}) async {
_initializeLocalSources(
secureStorageData: secureStorageData,
sharedPreferenceData: sharedPreferenceData,
);
await _initializeProviders(apiOverrides, initialRoute);
await Logger.init();
await Config.initialize();
Hive.init(Config.appDirectoryPath);
}

void _initializeLocalSources({
Map<String, String>? secureStorageData,
Map<String, Object>? sharedPreferenceData,
}) {
FakeFlutterSecureStorage.setInitialData(secureStorageData ?? {});
PathProviderPlatform.instance = FakePathProviderPlatform();
SharedPreferences.setMockInitialValues(sharedPreferenceData ?? {});
}

Future<void> _initializeProviders(
Map<ApiOverrideKey, ResponseData>? apiOverrides,
String? initialRoute,
) async {
await DiProvider.init();
DiProvider.instance
..allowReassignment = true
..registerLazySingleton<FlutterSecureStorage>(
() => FakeFlutterSecureStorage(),
)
..registerLazySingleton<HttpService>(
() => FakeHttpService()..mockApi(apiOverrides ?? {}),
)
..registerLazySingleton<AppRouter>(
() => AppRouter(
initialRoute: initialRoute,
sessionRepository: DiProvider.get(),
),
);
}
Loading
Loading