From af48d040c23af384311f66bae7fec468431fd05d Mon Sep 17 00:00:00 2001 From: William Cho Date: Mon, 5 Jan 2026 14:05:51 -0300 Subject: [PATCH 1/7] fix: bump flutter to 3.8.x --- analysis_options.yaml | 4 +- example/ios/.gitignore | 1 + example/ios/Flutter/AppFrameworkInfo.plist | 2 +- example/ios/Runner.xcodeproj/project.pbxproj | 13 +- .../xcshareddata/xcschemes/Runner.xcscheme | 5 +- example/ios/Runner/AppDelegate.swift | 2 +- example/ios/Runner/Info.plist | 4 + example/lib/localizations.dart | 6 +- example/lib/main.dart | 30 +-- example/pubspec.yaml | 7 +- lib/interpolator.dart | 104 ++++----- .../asset_bundle_data_source.dart | 26 +-- lib/src/formatter.dart | 11 +- lib/src/i18next.dart | 18 +- lib/src/i18next_localization_delegate.dart | 4 +- lib/src/options.dart | 114 +++++----- lib/src/plural_resolver.dart | 104 ++++----- lib/src/resource_store.dart | 10 +- lib/src/translator.dart | 14 +- pubspec.yaml | 4 +- .../asset_bundle_data_source_test.dart | 73 +++---- .../asset_bundle_data_source_test.mocks.dart | 36 ++-- test/formatter_test.dart | 198 +++++++++--------- test/i18next_localization_delegate_test.dart | 12 +- ...next_localization_delegate_test.mocks.dart | 67 ++++-- test/i18next_test.dart | 174 +++++++-------- test/i18next_test.mocks.dart | 67 ++++-- test/interpolator_test.dart | 130 +++++------- test/options_test.dart | 40 +--- test/resource_store_test.dart | 55 ++--- test/utils_test.dart | 9 +- 31 files changed, 664 insertions(+), 680 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index 9d3a002..32a5f98 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -2,8 +2,8 @@ include: package:flutter_lints/flutter.yaml analyzer: exclude: - - '**/*.mocks.dart' - - '**/*.g.dart' + - "**/*.mocks.dart" + - "**/*.g.dart" linter: rules: diff --git a/example/ios/.gitignore b/example/ios/.gitignore index e96ef60..61fa3c4 100644 --- a/example/ios/.gitignore +++ b/example/ios/.gitignore @@ -22,6 +22,7 @@ Flutter/app.flx Flutter/app.zip Flutter/flutter_assets/ Flutter/flutter_export_environment.sh +Flutter/ephemeral/ ServiceDefinitions.json Runner/GeneratedPluginRegistrant.* diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist index 9367d48..1dc6cf7 100644 --- a/example/ios/Flutter/AppFrameworkInfo.plist +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 8.0 + 13.0 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index c6759a6..db9bdcf 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -127,7 +127,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -171,10 +171,12 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -185,6 +187,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -272,7 +275,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -346,7 +349,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -395,7 +398,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a28140c..fc5ae03 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ @@ -45,11 +46,13 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" debugServiceExtension = "internal" + enableGPUValidationMode = "1" allowLocationSimulation = "YES"> diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift index 70693e4..b636303 100644 --- a/example/ios/Runner/AppDelegate.swift +++ b/example/ios/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import UIKit import Flutter -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist index a060db6..4f68a2c 100644 --- a/example/ios/Runner/Info.plist +++ b/example/ios/Runner/Info.plist @@ -41,5 +41,9 @@ UIViewControllerBasedStatusBarAppearance + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + diff --git a/example/lib/localizations.dart b/example/lib/localizations.dart index 82c3a59..b055b94 100644 --- a/example/lib/localizations.dart +++ b/example/lib/localizations.dart @@ -15,9 +15,9 @@ class HomePageL10n { i18next.t('homepage:today', variables: {'date': date}); String hello({required String name, required String world}) => i18next.t( - 'homepage:helloMessage', - variables: {'name': name, 'world': world}, - ); + 'homepage:helloMessage', + variables: {'name': name, 'world': world}, + ); String gendered(String gender) => i18next.t('homepage:genderMessage', context: gender); diff --git a/example/lib/main.dart b/example/lib/main.dart index 0b8c84e..23519b2 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -9,7 +9,7 @@ import 'localizations.dart'; void main() => runApp(const MyApp()); class MyApp extends StatefulWidget { - const MyApp({Key? key}) : super(key: key); + const MyApp({super.key}); final List locales = const [ Locale('en', 'US'), @@ -70,25 +70,25 @@ class _MyAppState extends State { } static Map formatters() => { - 'uppercase': (value, format, locale, options) => - value?.toString().toUpperCase(), - 'lowercase': (value, format, locale, options) => - value?.toString().toLowerCase(), - 'datetime': (value, format, locale, options) { - if (value is! DateTime) return value; - var dateFormat = format.options['format']; - dateFormat = dateFormat is String ? dateFormat : 'dd/MM/yyyy'; - return DateFormat(dateFormat, locale.toString()).format(value); - }, - }; + 'uppercase': (value, format, locale, options) => + value?.toString().toUpperCase(), + 'lowercase': (value, format, locale, options) => + value?.toString().toLowerCase(), + 'datetime': (value, format, locale, options) { + if (value is! DateTime) return value; + var dateFormat = format.options['format']; + dateFormat = dateFormat is String ? dateFormat : 'dd/MM/yyyy'; + return DateFormat(dateFormat, locale.toString()).format(value); + }, + }; } class MyHomePage extends StatefulWidget { const MyHomePage({ - Key? key, + super.key, required this.supportedLocales, required this.onUpdateLocale, - }) : super(key: key); + }); final List supportedLocales; final ValueChanged onUpdateLocale; @@ -116,7 +116,7 @@ class _MyHomePageState extends State { children: [ CupertinoSegmentedControl( children: { - for (final e in widget.supportedLocales) e: Text(e.toString()) + for (final e in widget.supportedLocales) e: Text(e.toString()), }, groupValue: Localizations.localeOf(context), onValueChanged: widget.onUpdateLocale, diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 66b5403..92849d1 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -5,7 +5,8 @@ version: 0.1.0 publish_to: none environment: - sdk: ">=2.18.0 <4.0.0" + sdk: ^3.10.0 + flutter: ^3.38.5 dependencies: flutter: @@ -14,10 +15,10 @@ dependencies: sdk: flutter i18next: path: ../ - intl: ^0.18.1 + intl: ^0.20.2 dev_dependencies: - flutter_lints: ^2.0.3 + flutter_lints: ^6.0.0 flutter_test: sdk: flutter diff --git a/lib/interpolator.dart b/lib/interpolator.dart index 31ecd80..e754fc7 100644 --- a/lib/interpolator.dart +++ b/lib/interpolator.dart @@ -5,12 +5,13 @@ import 'src/formatter.dart' as formatter; import 'src/options.dart'; import 'utils.dart'; -typedef Translate = String? Function( - String key, - Locale locale, - Map variables, - I18NextOptions options, -); +typedef Translate = + String? Function( + String key, + Locale locale, + Map variables, + I18NextOptions options, + ); /// Exception thrown when the [interpolate] fails while processing /// for either not containing a variable or with malformed or @@ -71,27 +72,33 @@ String interpolate( return todo.fold( string, - (result, helper) => result.splitMapJoin(helper.pattern, onMatch: (match) { - var variable = match[1]!.trim(); - - Iterable formats = []; - if (variable.contains(formatSeparator)) { - final variableParts = variable.split(formatSeparator); - variable = variableParts.first.trim(); - formats = variableParts.skip(1).map((e) => e.trim()); - } - - if (variable.isEmpty) { - throw InterpolationException('Missing variable', match); - } - - final path = variable.split(keySeparator); - final value = evaluate(path, variables); - final formatted = formatter.format(value, formats, locale, options) ?? - (throw InterpolationException( - 'Could not evaluate or format variable', match)); - return helper.escape(formatted); - }), + (result, helper) => result.splitMapJoin( + helper.pattern, + onMatch: (match) { + var variable = match[1]!.trim(); + + Iterable formats = []; + if (variable.contains(formatSeparator)) { + final variableParts = variable.split(formatSeparator); + variable = variableParts.first.trim(); + formats = variableParts.skip(1).map((e) => e.trim()); + } + + if (variable.isEmpty) { + throw InterpolationException('Missing variable', match); + } + + final path = variable.split(keySeparator); + final value = evaluate(path, variables); + final formatted = + formatter.format(value, formats, locale, options) ?? + (throw InterpolationException( + 'Could not evaluate or format variable', + match, + )); + return helper.escape(formatted); + }, + ), ); } @@ -121,26 +128,29 @@ String nest( I18NextOptions options, ) { final pattern = nestingPattern(options); - return string.splitMapJoin(pattern, onMatch: (match) { - match = match as RegExpMatch; - final key = match.namedGroup('key'); - if (key == null || key.isEmpty) { - throw NestingException('Key not found', match); - } - - var newVariables = variables; - final varsString = match.namedGroup('variables'); - if (varsString != null && varsString.isNotEmpty) { - final Map decoded = jsonDecode(varsString); - newVariables = Map.of(variables)..addAll(decoded); - } - - final value = translate(key, locale, newVariables, options); - if (value == null) { - throw NestingException('Translation not found', match); - } - return value; - }); + return string.splitMapJoin( + pattern, + onMatch: (match) { + match = match as RegExpMatch; + final key = match.namedGroup('key'); + if (key == null || key.isEmpty) { + throw NestingException('Key not found', match); + } + + var newVariables = variables; + final varsString = match.namedGroup('variables'); + if (varsString != null && varsString.isNotEmpty) { + final Map decoded = jsonDecode(varsString); + newVariables = Map.of(variables)..addAll(decoded); + } + + final value = translate(key, locale, newVariables, options); + if (value == null) { + throw NestingException('Translation not found', match); + } + return value; + }, + ); } RegExp interpolationPattern(I18NextOptions options) { diff --git a/lib/src/data_sources/asset_bundle_data_source.dart b/lib/src/data_sources/asset_bundle_data_source.dart index b7478d6..43b24ee 100644 --- a/lib/src/data_sources/asset_bundle_data_source.dart +++ b/lib/src/data_sources/asset_bundle_data_source.dart @@ -13,8 +13,7 @@ class AssetBundleLocalizationDataSource implements LocalizationDataSource { this.bundlePath = 'localizations', AssetBundle? bundle, this.cache = true, - }) : bundle = bundle ?? rootBundle, - super(); + }) : bundle = bundle ?? rootBundle; /// The path prefixed to the asset when retrieving from the [bundle]. /// @@ -50,16 +49,9 @@ class AssetBundleLocalizationDataSource implements LocalizationDataSource { /// The end result is a [Map] that contains all the namespaces which are /// the file names themselves (case sensitive). @override - Future> load( - Locale locale, { - String manifest = 'AssetManifest.json', - }) async { - assert(manifest.isNotEmpty); - - final assetFiles = await bundle - .loadString(manifest, cache: cache) - .then>((string) => json.decode(string)) - .then((map) => map.keys); + Future> load(Locale locale) async { + final assetManifest = await AssetManifest.loadFromAssetBundle(bundle); + final assetFiles = assetManifest.listAssets(); /// On every platform you never should try to get the `path.separator`, /// because Flutter is fetching all assets in `/` style. @@ -75,9 +67,7 @@ class AssetBundleLocalizationDataSource implements LocalizationDataSource { return await loadFromFiles(files); } - Future> loadFromFiles( - Iterable files, - ) async { + Future> loadFromFiles(Iterable files) async { // TODO: make it case insensitive? final namespaces = HashMap(); for (final file in files) { @@ -99,9 +89,5 @@ class AssetBundleLocalizationDataSource implements LocalizationDataSource { bundle == other.bundle; @override - int get hashCode => Object.hash( - bundlePath, - bundle, - cache, - ); + int get hashCode => Object.hash(bundlePath, bundle, cache); } diff --git a/lib/src/formatter.dart b/lib/src/formatter.dart index 5039e25..7648436 100644 --- a/lib/src/formatter.dart +++ b/lib/src/formatter.dart @@ -22,7 +22,8 @@ String? format( final result = formats.fold(value, (currentValue, format) { try { final parsedFormat = parseFormatString(format, options); - final formatter = options.formats?[parsedFormat.name] ?? + final formatter = + options.formats?[parsedFormat.name] ?? options.missingInterpolationHandler; if (formatter != null) { return formatter(currentValue, parsedFormat, locale, options); @@ -40,8 +41,12 @@ String? format( if (result != null && result is! String) { final formatter = options.missingInterpolationHandler; if (formatter != null) { - return formatter(result, InterpolationFormat.fallback, locale, options) - ?.toString(); + return formatter( + result, + InterpolationFormat.fallback, + locale, + options, + )?.toString(); } } diff --git a/lib/src/i18next.dart b/lib/src/i18next.dart index 282a967..cd7a4a1 100644 --- a/lib/src/i18next.dart +++ b/lib/src/i18next.dart @@ -32,8 +32,8 @@ import 'translator.dart'; /// ``` class I18Next { I18Next(this.locale, this.resourceStore, {I18NextOptions? options}) - : pluralResolver = const PluralResolver(), - options = I18NextOptions.base.merge(options); + : pluralResolver = const PluralResolver(), + options = I18NextOptions.base.merge(options); /// The current and default [Locale] for this instance. final Locale locale; @@ -112,11 +112,17 @@ class I18Next { locale ??= this.locale; final newOptions = this.options.merge(options); - var result = Translator(pluralResolver, resourceStore) - .call(key, locale, variables, newOptions); + var result = Translator( + pluralResolver, + resourceStore, + ).call(key, locale, variables, newOptions); if (result == null && newOptions.missingKeyHandler != null) { - result = - newOptions.missingKeyHandler!(locale, key, variables, newOptions); + result = newOptions.missingKeyHandler!( + locale, + key, + variables, + newOptions, + ); } return result; } diff --git a/lib/src/i18next_localization_delegate.dart b/lib/src/i18next_localization_delegate.dart index aab72a8..5be40ac 100644 --- a/lib/src/i18next_localization_delegate.dart +++ b/lib/src/i18next_localization_delegate.dart @@ -16,8 +16,8 @@ class I18NextLocalizationDelegate extends LocalizationsDelegate { required this.dataSource, ResourceStore? resourceStore, this.options, - }) : resourceStore = resourceStore ?? ResourceStore(), - super(); + }) : resourceStore = resourceStore ?? ResourceStore(), + super(); /// The list of supported locales by this delegate. /// diff --git a/lib/src/options.dart b/lib/src/options.dart index 758472f..b1c8b1c 100644 --- a/lib/src/options.dart +++ b/lib/src/options.dart @@ -11,28 +11,31 @@ import 'interpolation_format.dart'; /// /// The [value] can be null if the variable wasn't evaluated properly, giving a /// chance for the formatter to do something -typedef ValueFormatter = Object? Function( - Object? value, - InterpolationFormat format, - Locale locale, - I18NextOptions options, -); +typedef ValueFormatter = + Object? Function( + Object? value, + InterpolationFormat format, + Locale locale, + I18NextOptions options, + ); -typedef MissingKeyHandler = String? Function( - Locale locale, - String key, - Map variables, - I18NextOptions options, -); +typedef MissingKeyHandler = + String? Function( + Locale locale, + String key, + Map variables, + I18NextOptions options, + ); -typedef TranslationFailedHandler = String Function( - Locale locale, - String namespace, - String key, - Map variables, - I18NextOptions options, - Object error, -); +typedef TranslationFailedHandler = + String Function( + Locale locale, + String namespace, + String key, + Map variables, + I18NextOptions options, + Object error, + ); typedef EscapeHandler = String Function(String input); @@ -75,10 +78,7 @@ class I18NextOptions with Diagnosticable { interpolationUnescapePrefix: '-', interpolationUnescapeSuffix: '', formatSeparator: ',', - formatterValues: { - 'true': true, - 'false': false, - }, + formatterValues: {'true': true, 'false': false}, formats: {}, optionsSeparator: ';', optionValueSeparator: ':', @@ -253,8 +253,8 @@ class I18NextOptions with Diagnosticable { formats: formats == null ? other.formats : other.formats == null - ? formats - : {...?formats, ...?other.formats}, + ? formats + : {...?formats, ...?other.formats}, optionsSeparator: other.optionsSeparator ?? optionsSeparator, optionValueSeparator: other.optionValueSeparator ?? optionValueSeparator, nestingPrefix: other.nestingPrefix ?? nestingPrefix, @@ -330,29 +330,29 @@ class I18NextOptions with Diagnosticable { @override int get hashCode => Object.hashAll([ - namespaceSeparator, - contextSeparator, - pluralSeparator, - keySeparator, - interpolationPrefix, - interpolationSuffix, - formatSeparator, - interpolationUnescapePrefix, - interpolationUnescapeSuffix, - formatterValues, - formats, - optionsSeparator, - optionValueSeparator, - nestingPrefix, - nestingSuffix, - nestingSeparator, - pluralSuffix, - missingKeyHandler, - missingInterpolationHandler, - translationFailedHandler, - escape, - escapeValue, - ]); + namespaceSeparator, + contextSeparator, + pluralSeparator, + keySeparator, + interpolationPrefix, + interpolationSuffix, + formatSeparator, + interpolationUnescapePrefix, + interpolationUnescapeSuffix, + formatterValues, + formats, + optionsSeparator, + optionValueSeparator, + nestingPrefix, + nestingSuffix, + nestingSeparator, + pluralSuffix, + missingKeyHandler, + missingInterpolationHandler, + translationFailedHandler, + escape, + escapeValue, + ]); @override bool operator ==(Object other) { @@ -403,10 +403,18 @@ class I18NextOptions with Diagnosticable { ..add(StringProperty('nestingSeparator', nestingSeparator)) ..add(StringProperty('pluralSuffix', pluralSuffix)) ..add(StringProperty('missingKeyHandler', missingKeyHandler?.toString())) - ..add(StringProperty('missingInterpolationHandler', - missingInterpolationHandler?.toString())) - ..add(StringProperty( - 'translationFailedHandler', translationFailedHandler?.toString())) + ..add( + StringProperty( + 'missingInterpolationHandler', + missingInterpolationHandler?.toString(), + ), + ) + ..add( + StringProperty( + 'translationFailedHandler', + translationFailedHandler?.toString(), + ), + ) ..add(StringProperty('escape', escape?.toString())) ..add(StringProperty('escapeValue', escapeValue?.toString())); } diff --git a/lib/src/plural_resolver.dart b/lib/src/plural_resolver.dart index 6858ee2..fa6dda3 100644 --- a/lib/src/plural_resolver.dart +++ b/lib/src/plural_resolver.dart @@ -250,60 +250,60 @@ class PluralResolver { static int _rule4(int n) => n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) - ? 1 - : 2; + ? 1 + : 2; static int _rule5(int n) => n == 0 ? 0 : n == 1 - ? 1 - : n == 2 - ? 2 - : n % 100 >= 3 && n % 100 <= 10 - ? 3 - : n % 100 >= 11 - ? 4 - : 5; + ? 1 + : n == 2 + ? 2 + : n % 100 >= 3 && n % 100 <= 10 + ? 3 + : n % 100 >= 11 + ? 4 + : 5; static int _rule6(int n) => n == 1 ? 0 : n >= 2 && n <= 4 - ? 1 - : 2; + ? 1 + : 2; static int _rule7(int n) => n == 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) - ? 1 - : 2; + ? 1 + : 2; static int _rule8(int n) => (n == 1) ? 0 : (n == 2) - ? 1 - : (n != 8 && n != 11) - ? 2 - : 3; + ? 1 + : (n != 8 && n != 11) + ? 2 + : 3; static int _rule9(int n) => n >= 2 ? 1 : 0; static int _rule10(int n) => n == 1 ? 0 : n == 2 - ? 1 - : n < 7 - ? 2 - : n < 11 - ? 3 - : 4; + ? 1 + : n < 7 + ? 2 + : n < 11 + ? 3 + : 4; static int _rule11(int n) => n == 1 || n == 11 ? 0 : n == 2 || n == 12 - ? 1 - : n > 2 && n < 20 - ? 2 - : 3; + ? 1 + : n > 2 && n < 20 + ? 2 + : 3; static int _rule12(int n) => n % 10 != 1 || n % 100 == 11 ? 1 : 0; @@ -312,58 +312,58 @@ class PluralResolver { static int _rule14(int n) => n == 1 ? 0 : (n == 2) - ? 1 - : (n == 3) - ? 2 - : 3; + ? 1 + : (n == 3) + ? 2 + : 3; static int _rule15(int n) => n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && (n % 100 < 10 || n % 100 >= 20) - ? 1 - : 2; + ? 1 + : 2; static int _rule16(int n) => n % 10 == 1 && n % 100 != 11 ? 0 : n != 0 - ? 1 - : 2; + ? 1 + : 2; static int _rule17(int n) => n == 1 || n % 10 == 1 && n % 100 != 11 ? 0 : 1; static int _rule18(int n) => n == 0 ? 0 : n == 1 - ? 1 - : 2; + ? 1 + : 2; static int _rule19(int n) => n == 1 ? 0 : n == 0 || (n % 100 > 1 && n % 100 < 11) - ? 1 - : n % 100 > 10 && n % 100 < 20 - ? 2 - : 3; + ? 1 + : n % 100 > 10 && n % 100 < 20 + ? 2 + : 3; static int _rule20(int n) => n == 1 ? 0 : n == 0 || n % 100 > 0 && n % 100 < 20 - ? 1 - : 2; + ? 1 + : 2; static int _rule21(int n) => n % 100 == 1 ? 1 : n % 100 == 2 - ? 2 - : n % 100 == 3 || n % 100 == 4 - ? 3 - : 0; + ? 2 + : n % 100 == 3 || n % 100 == 4 + ? 3 + : 0; static int _rule22(int n) => n == 1 ? 0 : n == 2 - ? 1 - : (n < 0 || n > 10) && n % 10 == 0 - ? 2 - : 3; + ? 1 + : (n < 0 || n > 10) && n % 10 == 0 + ? 2 + : 3; } diff --git a/lib/src/resource_store.dart b/lib/src/resource_store.dart index 398627c..499a302 100644 --- a/lib/src/resource_store.dart +++ b/lib/src/resource_store.dart @@ -10,8 +10,8 @@ import 'options.dart'; /// The access is done by [Locale], Namespace, and key in that order. class ResourceStore { ResourceStore({Map>? data}) - : _data = data ?? {}, - super(); + : _data = data ?? {}, + super(); final Map> _data; @@ -67,11 +67,7 @@ class ResourceStore { I18NextOptions options, ) { final keySeparator = options.keySeparator ?? '.'; - final path = [ - locale, - namespace, - ...key.split(keySeparator), - ]; + final path = [locale, namespace, ...key.split(keySeparator)]; final value = evaluate(path, _data); return value is String ? value : null; } diff --git a/lib/src/translator.dart b/lib/src/translator.dart index eb1fb3b..2fa644c 100644 --- a/lib/src/translator.dart +++ b/lib/src/translator.dart @@ -6,11 +6,8 @@ import 'plural_resolver.dart'; import 'resource_store.dart'; class Translator { - Translator( - this.pluralResolver, - this.resourceStore, [ - this.contextNamespace, - ]) : super(); + Translator(this.pluralResolver, this.resourceStore, [this.contextNamespace]) + : super(); final PluralResolver pluralResolver; final ResourceStore resourceStore; @@ -132,8 +129,11 @@ class Translator { return null; } - return Translator(pluralResolver, resourceStore, namespace) - .call(currentKey, locale, newVariables, options); + return Translator( + pluralResolver, + resourceStore, + namespace, + ).call(currentKey, locale, newVariables, options); }, variables, options, diff --git a/pubspec.yaml b/pubspec.yaml index 16f4326..5ccef61 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,7 +5,7 @@ repository: https://github.com/williamhjcho/i18next homepage: https://github.com/williamhjcho/i18next environment: - sdk: ">=2.18.0 <4.0.0" + sdk: ^3.10.0 dependencies: collection: ^1.16.0 @@ -15,7 +15,7 @@ dependencies: dev_dependencies: build_runner: ^2.1.11 - flutter_lints: ^2.0.3 + flutter_lints: ^6.0.0 flutter_test: sdk: flutter mockito: ^5.2.0 diff --git a/test/data_sources/asset_bundle_data_source_test.dart b/test/data_sources/asset_bundle_data_source_test.dart index 7dc37ae..e15280d 100644 --- a/test/data_sources/asset_bundle_data_source_test.dart +++ b/test/data_sources/asset_bundle_data_source_test.dart @@ -25,40 +25,37 @@ void main() { group('#loadFromAssetBundle', () { setUp(() { - when(bundle.loadString(defaultManifest)).thenAnswer((_) async => '''{ + when(bundle.loadString(defaultManifest)).thenAnswer( + (_) async => + '''{ "another/asset/path": [""], "$bundlePath/en-US/file1.json": [""], "$bundlePath/en-US/file2.json": [""], "$bundlePath/pt/file1.json": [""], "$bundlePath/pt/file2.json": [""] - }'''); + }''', + ); }); test('given any locale', () async { - await expectLater( - dataSource.load(const Locale('any')), - completes, - ); + await expectLater(dataSource.load(const Locale('any')), completes); verify(bundle.loadString(defaultManifest)).called(1); }); test('given an unregistered locale', () { - expect( - dataSource.load(const Locale('ar')), - completion(isEmpty), - ); + expect(dataSource.load(const Locale('ar')), completion(isEmpty)); }); test('given a supported full locale', () async { - when(bundle.loadString(argThat(contains('$bundlePath/')))) - .thenAnswer((_) async => '{}'); + when( + bundle.loadString(argThat(contains('$bundlePath/'))), + ).thenAnswer((_) async => '{}'); await expectLater( dataSource.load(const Locale('en', 'US')), - completion(equals(>{ - 'file1': {}, - 'file2': {}, - })), + completion( + equals(>{'file1': {}, 'file2': {}}), + ), ); verify(bundle.loadString('$bundlePath/en-US/file1.json')).called(1); @@ -77,15 +74,15 @@ void main() { }); test('given a supported short locale', () async { - when(bundle.loadString(argThat(contains('$bundlePath/')))) - .thenAnswer((_) async => '{}'); + when( + bundle.loadString(argThat(contains('$bundlePath/'))), + ).thenAnswer((_) async => '{}'); await expectLater( dataSource.load(const Locale('pt')), - completion(equals(>{ - 'file1': {}, - 'file2': {}, - })), + completion( + equals(>{'file1': {}, 'file2': {}}), + ), ); verify(bundle.loadString('$bundlePath/pt/file1.json')).called(1); @@ -108,41 +105,29 @@ void main() { const error = 'Some error'; when(bundle.loadString(any)).thenAnswer((_) async => throw error); - expect( - dataSource.load(const Locale('any')), - throwsA(error), - ); + expect(dataSource.load(const Locale('any')), throwsA(error)); }); test('given manifest empty', () { - expect( - () => dataSource.load( - const Locale('any'), - manifest: '', - ), - throwsAssertionError, - ); + // expect( + // () => dataSource.load( + // const Locale('any'), + // // manifest: '', + // ), + // throwsAssertionError, + // ); }); test('given manifest', () async { const manifest = 'SomeManifestFile.json'; when(bundle.loadString(any)).thenAnswer((_) async => '{}'); - await expectLater( - dataSource.load( - const Locale('any'), - manifest: manifest, - ), - completes, - ); + await expectLater(dataSource.load(const Locale('any')), completes); verify(bundle.loadString(manifest)).called(1); }); test('given incorrect source-path to any bundle asset', () async { - await expectLater( - dataSource.load(const Locale('any')), - completes, - ); + await expectLater(dataSource.load(const Locale('any')), completes); verifyNever(bundle.loadString(argThat(contains('bundle\\path')))); }); diff --git a/test/data_sources/asset_bundle_data_source_test.mocks.dart b/test/data_sources/asset_bundle_data_source_test.mocks.dart index 36135b4..7796b59 100644 --- a/test/data_sources/asset_bundle_data_source_test.mocks.dart +++ b/test/data_sources/asset_bundle_data_source_test.mocks.dart @@ -22,22 +22,34 @@ class MockAssetBundle extends _i1.Mock implements _i3.AssetBundle { } @override - _i4.Future<_i2.ByteData> load(String? key) => (super.noSuchMethod( - Invocation.method(#load, [key]), - returnValue: Future.value(_FakeByteData())) as _i4.Future<_i2.ByteData>); + _i4.Future<_i2.ByteData> load(String? key) => + (super.noSuchMethod( + Invocation.method(#load, [key]), + returnValue: Future.value(_FakeByteData()), + ) + as _i4.Future<_i2.ByteData>); @override - _i4.Future loadString(String? key, {bool? cache = true}) => (super - .noSuchMethod(Invocation.method(#loadString, [key], {#cache: cache}), - returnValue: Future.value('')) as _i4.Future); + _i4.Future loadString(String? key, {bool? cache = true}) => + (super.noSuchMethod( + Invocation.method(#loadString, [key], {#cache: cache}), + returnValue: Future.value(''), + ) + as _i4.Future); @override _i4.Future loadStructuredData( - String? key, _i4.Future Function(String)? parser) => - (super.noSuchMethod(Invocation.method(#loadStructuredData, [key, parser]), - returnValue: Future.value(null)) as _i4.Future); + String? key, + _i4.Future Function(String)? parser, + ) => + (super.noSuchMethod( + Invocation.method(#loadStructuredData, [key, parser]), + returnValue: Future.value(null), + ) + as _i4.Future); @override - void evict(String? key) => - super.noSuchMethod(Invocation.method(#evict, [key]), - returnValueForMissingStub: null); + void evict(String? key) => super.noSuchMethod( + Invocation.method(#evict, [key]), + returnValueForMissingStub: null, + ); @override String toString() => (super.noSuchMethod(Invocation.method(#toString, []), returnValue: '') diff --git a/test/formatter_test.dart b/test/formatter_test.dart index 9c8f28f..54f8a5b 100644 --- a/test/formatter_test.dart +++ b/test/formatter_test.dart @@ -36,15 +36,18 @@ void main() { test('and the fallback returns a value', () { final Object value = ['Value']; options = options.copyWith( - missingInterpolationHandler: expectAsync4( - (val, format, loc, opt) { - expect(format, InterpolationFormat.fallback); - expect(val, value); - expect(loc, locale); - expect(opt, options); - return 'interpolation formatter fallback'; - }, - ), + missingInterpolationHandler: expectAsync4(( + val, + format, + loc, + opt, + ) { + expect(format, InterpolationFormat.fallback); + expect(val, value); + expect(loc, locale); + expect(opt, options); + return 'interpolation formatter fallback'; + }), ); expect( format(value, [], locale, options), @@ -55,15 +58,18 @@ void main() { test('and the fallback returns null', () { final Object value = ['Value']; options = options.copyWith( - missingInterpolationHandler: expectAsync4( - (val, format, loc, opt) { - expect(format, InterpolationFormat.fallback); - expect(val, value); - expect(loc, locale); - expect(opt, options); - return null; - }, - ), + missingInterpolationHandler: expectAsync4(( + val, + format, + loc, + opt, + ) { + expect(format, InterpolationFormat.fallback); + expect(val, value); + expect(loc, locale); + expect(opt, options); + return null; + }), ); expect(format(value, [], locale, options), isNull); }); @@ -80,16 +86,14 @@ void main() { test('with a missingInterpolationHandler', () { options = options.copyWith( - missingInterpolationHandler: expectAsync4( - (value, format, loc, opt) { - expect(value, 'Value'); - expect(format.name, 'format'); - expect(format.options, isEmpty); - expect(loc, locale); - expect(opt, options); - return 'interpolation formatter fallback'; - }, - ), + missingInterpolationHandler: expectAsync4((value, format, loc, opt) { + expect(value, 'Value'); + expect(format.name, 'format'); + expect(format.options, isEmpty); + expect(loc, locale); + expect(opt, options); + return 'interpolation formatter fallback'; + }), ); expect( format('Value', ['format'], locale, options), @@ -137,9 +141,7 @@ void main() { test('and it returns null', () { options = options.copyWith( - formats: { - 'format': expectAsync4((value, format, loc, opt) => null), - }, + formats: {'format': expectAsync4((value, format, loc, opt) => null)}, ); final result = format('My Value', ['format'], locale, options); expect(result, isNull); @@ -148,35 +150,37 @@ void main() { group('when there are multiple formats', () { test('and finds all format names', () { - options = options.copyWith(formats: { - 'fmt1': expectAsync4((value, format, loc, opt) { - expect(value, 'initial value'); - expect(format.name, 'fmt1'); - expect(format.options, isEmpty); - return 'replaced first value'; - }), - 'fmt2': expectAsync4((value, format, loc, opt) { - expect(value, 'replaced first value'); - expect(format.name, 'fmt2'); - expect(format.options, {'option': 'optValue'}); - return 'replaced second value'; - }), - 'fmt3': expectAsync4((value, format, loc, opt) { - expect(value, 'replaced second value'); - expect(format.name, 'fmt3'); - expect(format.options, { - 'option1': 'option value 1', - 'option2': 'option value 2', - }); - return 'replaced third value'; - }), - }); + options = options.copyWith( + formats: { + 'fmt1': expectAsync4((value, format, loc, opt) { + expect(value, 'initial value'); + expect(format.name, 'fmt1'); + expect(format.options, isEmpty); + return 'replaced first value'; + }), + 'fmt2': expectAsync4((value, format, loc, opt) { + expect(value, 'replaced first value'); + expect(format.name, 'fmt2'); + expect(format.options, {'option': 'optValue'}); + return 'replaced second value'; + }), + 'fmt3': expectAsync4((value, format, loc, opt) { + expect(value, 'replaced second value'); + expect(format.name, 'fmt3'); + expect(format.options, { + 'option1': 'option value 1', + 'option2': 'option value 2', + }); + return 'replaced third value'; + }), + }, + ); final result = format( 'initial value', [ 'fmt1', 'fmt2(option:optValue)', - 'fmt3(option1: option value 1; option2: option value 2)' + 'fmt3(option1: option value 1; option2: option value 2)', ], locale, options, @@ -185,36 +189,41 @@ void main() { }); test('and the formats return different types', () { - options = options.copyWith(formats: { - 'fmt1': expectAsync4((value, format, loc, opt) { - expect(value, 'initial value'); - expect(format.name, 'fmt1'); - expect(format.options, isEmpty); - return 123.456; - }), - 'fmt2': expectAsync4((value, format, loc, opt) { - expect(value, 123.456); - expect(format.name, 'fmt2'); - expect(format.options, isEmpty); - return const MapEntry('Some Key', 999); - }), - 'fmt3': expectAsync4((value, format, loc, opt) { - expect(value, const MapEntry('Some Key', 999)); - expect(format.name, 'fmt3'); - expect(format.options, isEmpty); - return ['a', 'b', 'c']; - }), - }); - final result = - format('initial value', ['fmt1', 'fmt2', 'fmt3'], locale, options); + options = options.copyWith( + formats: { + 'fmt1': expectAsync4((value, format, loc, opt) { + expect(value, 'initial value'); + expect(format.name, 'fmt1'); + expect(format.options, isEmpty); + return 123.456; + }), + 'fmt2': expectAsync4((value, format, loc, opt) { + expect(value, 123.456); + expect(format.name, 'fmt2'); + expect(format.options, isEmpty); + return const MapEntry('Some Key', 999); + }), + 'fmt3': expectAsync4((value, format, loc, opt) { + expect(value, const MapEntry('Some Key', 999)); + expect(format.name, 'fmt3'); + expect(format.options, isEmpty); + return ['a', 'b', 'c']; + }), + }, + ); + final result = format( + 'initial value', + ['fmt1', 'fmt2', 'fmt3'], + locale, + options, + ); expect(result, '[a, b, c]'); }); - test( - 'and the first format returns null, ' - 'but last format returns a value', - () { - options = options.copyWith(formats: { + test('and the first format returns null, ' + 'but last format returns a value', () { + options = options.copyWith( + formats: { 'fmt1': expectAsync4((value, format, loc, opt) { expect(value, 'initial value'); expect(format.name, 'fmt1'); @@ -227,16 +236,16 @@ void main() { expect(format.options, isEmpty); return 'replaced second value'; }), - }); - final result = format( - 'initial value', - ['fmt1', 'fmt2'], - locale, - options, - ); - expect(result, 'replaced second value'); - }, - ); + }, + ); + final result = format( + 'initial value', + ['fmt1', 'fmt2'], + locale, + options, + ); + expect(result, 'replaced second value'); + }); }); }); @@ -312,10 +321,7 @@ void main() { }); test('given a format with default formatter values', () { - final format = parseFormatString( - 'someFormat(a:true; b:false)', - options, - ); + final format = parseFormatString('someFormat(a:true; b:false)', options); expect(format.name, 'someFormat'); expect(format.options, {'a': true, 'b': false}); }); diff --git a/test/i18next_localization_delegate_test.dart b/test/i18next_localization_delegate_test.dart index e486cca..4078f0f 100644 --- a/test/i18next_localization_delegate_test.dart +++ b/test/i18next_localization_delegate_test.dart @@ -91,9 +91,9 @@ void main() { test('when dataSource succeeds', () async { const data1 = {'key': 'ns1'}; const data2 = {'key': 'ns1'}; - when(dataSource.load(any)).thenAnswer( - (_) async => {'ns1': data1, 'ns2': data2}, - ); + when( + dataSource.load(any), + ).thenAnswer((_) async => {'ns1': data1, 'ns2': data2}); final i18next = await localizationDelegate.load(en); expect(i18next.locale, en); @@ -103,9 +103,9 @@ void main() { test('when dataSource is synchronous', () { const data1 = {'key': 'ns1'}; - when(dataSource.load(any)).thenAnswer( - (_) => SynchronousFuture({'ns1': data1}), - ); + when( + dataSource.load(any), + ).thenAnswer((_) => SynchronousFuture({'ns1': data1})); // checking if this is being called sync I18Next? result; diff --git a/test/i18next_localization_delegate_test.mocks.dart b/test/i18next_localization_delegate_test.mocks.dart index 7fb73b7..9afae86 100644 --- a/test/i18next_localization_delegate_test.mocks.dart +++ b/test/i18next_localization_delegate_test.mocks.dart @@ -24,8 +24,10 @@ class MockLocalizationDataSource extends _i1.Mock @override _i3.Future> load(_i4.Locale? locale) => - (super.noSuchMethod(Invocation.method(#load, [locale]), - returnValue: Future.value({})) + (super.noSuchMethod( + Invocation.method(#load, [locale]), + returnValue: Future.value({}), + ) as _i3.Future>); } @@ -39,37 +41,58 @@ class MockResourceStore extends _i1.Mock implements _i5.ResourceStore { @override void addNamespace( - _i4.Locale? locale, String? namespace, Map? data) => - super.noSuchMethod( - Invocation.method(#addNamespace, [locale, namespace, data]), - returnValueForMissingStub: null); + _i4.Locale? locale, + String? namespace, + Map? data, + ) => super.noSuchMethod( + Invocation.method(#addNamespace, [locale, namespace, data]), + returnValueForMissingStub: null, + ); @override - void removeNamespace(_i4.Locale? locale, String? namespace) => super - .noSuchMethod(Invocation.method(#removeNamespace, [locale, namespace]), - returnValueForMissingStub: null); + void removeNamespace(_i4.Locale? locale, String? namespace) => + super.noSuchMethod( + Invocation.method(#removeNamespace, [locale, namespace]), + returnValueForMissingStub: null, + ); @override _i3.Future removeLocale(_i4.Locale? locale) => - (super.noSuchMethod(Invocation.method(#removeLocale, [locale]), - returnValue: Future.value(null), - returnValueForMissingStub: Future.value()) as _i3.Future); + (super.noSuchMethod( + Invocation.method(#removeLocale, [locale]), + returnValue: Future.value(null), + returnValueForMissingStub: Future.value(), + ) + as _i3.Future); @override _i3.Future removeAll() => - (super.noSuchMethod(Invocation.method(#removeAll, []), - returnValue: Future.value(null), - returnValueForMissingStub: Future.value()) as _i3.Future); + (super.noSuchMethod( + Invocation.method(#removeAll, []), + returnValue: Future.value(null), + returnValueForMissingStub: Future.value(), + ) + as _i3.Future); @override bool isNamespaceRegistered(_i4.Locale? locale, String? namespace) => (super.noSuchMethod( - Invocation.method(#isNamespaceRegistered, [locale, namespace]), - returnValue: false) as bool); + Invocation.method(#isNamespaceRegistered, [locale, namespace]), + returnValue: false, + ) + as bool); @override bool isLocaleRegistered(_i4.Locale? locale) => - (super.noSuchMethod(Invocation.method(#isLocaleRegistered, [locale]), - returnValue: false) as bool); + (super.noSuchMethod( + Invocation.method(#isLocaleRegistered, [locale]), + returnValue: false, + ) + as bool); @override - String? retrieve(_i4.Locale? locale, String? namespace, String? key, - _i6.I18NextOptions? options) => + String? retrieve( + _i4.Locale? locale, + String? namespace, + String? key, + _i6.I18NextOptions? options, + ) => (super.noSuchMethod( - Invocation.method(#retrieve, [locale, namespace, key, options])) + Invocation.method(#retrieve, [locale, namespace, key, options]), + ) as String?); } diff --git a/test/i18next_test.dart b/test/i18next_test.dart index 4f39ac7..7093159 100644 --- a/test/i18next_test.dart +++ b/test/i18next_test.dart @@ -99,12 +99,9 @@ void main() { mockKey('key', 'my value', locale: anotherLocale); expect(i18next.t('$namespace:key', locale: anotherLocale), 'my value'); - verify(resourceStore.retrieve( - anotherLocale, - namespace, - 'key', - any, - )).called(1); + verify( + resourceStore.retrieve(anotherLocale, namespace, 'key', any), + ).called(1); }); group('given formatter', () { @@ -175,20 +172,14 @@ void main() { resourceStore, options: I18NextOptions( formats: { - 'format1': expectAsync4( - (value, options, loc, opt) { - values.add(value); - return value?.toString().toUpperCase(); - }, - count: 1, - ), - 'format2': expectAsync4( - (value, options, loc, opt) { - values.add(value); - return value?.toString().toUpperCase(); - }, - count: 1, - ), + 'format1': expectAsync4((value, options, loc, opt) { + values.add(value); + return value?.toString().toUpperCase(); + }, count: 1), + 'format2': expectAsync4((value, options, loc, opt) { + values.add(value); + return value?.toString().toUpperCase(); + }, count: 1), }, ), ); @@ -199,10 +190,10 @@ void main() { ); expect( - i18next.t('$namespace:key', variables: { - 'value1': '1eulav', - 'value2': '2eulav', - }), + i18next.t( + '$namespace:key', + variables: {'value1': '1eulav', 'value2': '2eulav'}, + ), 'leading 1EULAV middle 2EULAV trailing', ); expect(values, orderedEquals(['1eulav', '2eulav'])); @@ -215,9 +206,7 @@ void main() { i18next = I18Next( locale, resourceStore, - options: const I18NextOptions( - fallbackNamespaces: [fallbackNamespace1], - ), + options: const I18NextOptions(fallbackNamespaces: [fallbackNamespace1]), ); mockKey('key', 'fallbackValue', ns: fallbackNamespace1); @@ -520,23 +509,25 @@ void main() { test('given all matching arguments', () { expect( - i18next.t('$namespace:key', variables: { - 'first': 'fst', - 'second': 'snd', - 'third': 'trd', - }), + i18next.t( + '$namespace:key', + variables: {'first': 'fst', 'second': 'snd', 'third': 'trd'}, + ), 'fst, snd, and then trd!', ); }); test('given extra matching arguments', () { expect( - i18next.t('$namespace:key', variables: { - 'first': 'fst', - 'second': 'snd', - 'third': 'trd', - 'none': 'none', - }), + i18next.t( + '$namespace:key', + variables: { + 'first': 'fst', + 'second': 'snd', + 'third': 'trd', + 'none': 'none', + }, + ), 'fst, snd, and then trd!', ); }); @@ -636,10 +627,14 @@ void main() { }); test('nested pluralization and interpolation ', () { - mockKey('girlsAndBoys', - r'$t(girls, {"count": {{girls}} }) and {{count}} boy'); - mockKey('girlsAndBoys_plural', - r'$t(girls, {"count": {{girls}} }) and {{count}} boys'); + mockKey( + 'girlsAndBoys', + r'$t(girls, {"count": {{girls}} }) and {{count}} boy', + ); + mockKey( + 'girlsAndBoys_plural', + r'$t(girls, {"count": {{girls}} }) and {{count}} boys', + ); mockKey('girls', '{{count}} girl'); mockKey('girls_plural', '{{count}} girls'); @@ -664,20 +659,17 @@ void main() { }, ); - test( - 'given a failing nesting with custom translationFailedHandler', - () { - mockKey('key', r'This is my $t(anotherKey)'); - const fallbackText = 'Fallback Text'; - final options = I18NextOptions( - translationFailedHandler: expectAsync6( - (locale, namespace, key, variables, options, error) => fallbackText, - count: 1, - ), - ); - expect(i18next.t('$namespace:key', options: options), fallbackText); - }, - ); + test('given a failing nesting with custom translationFailedHandler', () { + mockKey('key', r'This is my $t(anotherKey)'); + const fallbackText = 'Fallback Text'; + final options = I18NextOptions( + translationFailedHandler: expectAsync6( + (locale, namespace, key, variables, options, error) => fallbackText, + count: 1, + ), + ); + expect(i18next.t('$namespace:key', options: options), fallbackText); + }); }); group('escape', () { @@ -722,13 +714,17 @@ void main() { }); test('when formatter returns xml', () { - final opts = I18NextOptions(formats: { - 'fmt': (value, format, locale, options) => '$value', - }); + final opts = I18NextOptions( + formats: { + 'fmt': (value, format, locale, options) => '$value', + }, + ); mockKey('key', 'untagged text {{myVar, fmt}}'); mockKey('keyTagged', 'tagged text {{myVar, fmt}}'); mockKey( - 'keyEscaped', 'tagged text {{- myVar, fmt}}'); + 'keyEscaped', + 'tagged text {{- myVar, fmt}}', + ); expect( i18next.t('$namespace:key', variables: vars, options: opts), @@ -748,10 +744,12 @@ void main() { group('.of', () { BuildContext? capturedContext; - final builder = Builder(builder: (context) { - capturedContext = context; - return Container(); - }); + final builder = Builder( + builder: (context) { + capturedContext = context; + return Container(); + }, + ); setUp(() { capturedContext = null; @@ -766,17 +764,19 @@ void main() { final dataSource = MockLocalizationDataSource(); when(dataSource.load(any)).thenAnswer((_) async => {}); - await tester.pumpWidget(Localizations( - locale: locale, - delegates: [ - DefaultWidgetsLocalizations.delegate, - I18NextLocalizationDelegate( - locales: [locale], - dataSource: dataSource, - ), - ], - child: builder, - )); + await tester.pumpWidget( + Localizations( + locale: locale, + delegates: [ + DefaultWidgetsLocalizations.delegate, + I18NextLocalizationDelegate( + locales: [locale], + dataSource: dataSource, + ), + ], + child: builder, + ), + ); await tester.pump(); expect(I18Next.of(capturedContext!), isNotNull); }); @@ -888,39 +888,25 @@ void main() { group('tOrNull', () { test('when key is not found', () { - expect( - i18next.tOrNull('$namespace:key'), - isNull, - ); + expect(i18next.tOrNull('$namespace:key'), isNull); }); test('when namespace is wrong', () { mockKey('key', 'Translation', ns: namespace); - expect( - i18next.tOrNull('ns2:key'), - isNull, - ); + expect(i18next.tOrNull('ns2:key'), isNull); }); test('when key is found', () { mockKey('key', 'Translation', ns: namespace); - expect( - i18next.tOrNull('$namespace:key'), - 'Translation', - ); + expect(i18next.tOrNull('$namespace:key'), 'Translation'); }); test('when key is found in fallback namespace', () { const fallbackNamespace = 'fallback_namespace'; i18next = I18Next( locale, resourceStore, - options: const I18NextOptions( - fallbackNamespaces: [fallbackNamespace], - ), + options: const I18NextOptions(fallbackNamespaces: [fallbackNamespace]), ); mockKey('key', 'Translation', ns: fallbackNamespace); - expect( - i18next.tOrNull('$namespace:key'), - 'Translation', - ); + expect(i18next.tOrNull('$namespace:key'), 'Translation'); }); }); } diff --git a/test/i18next_test.mocks.dart b/test/i18next_test.mocks.dart index fd3785a..7cf787d 100644 --- a/test/i18next_test.mocks.dart +++ b/test/i18next_test.mocks.dart @@ -23,38 +23,59 @@ class MockResourceStore extends _i1.Mock implements _i2.ResourceStore { @override void addNamespace( - _i3.Locale? locale, String? namespace, Map? data) => - super.noSuchMethod( - Invocation.method(#addNamespace, [locale, namespace, data]), - returnValueForMissingStub: null); + _i3.Locale? locale, + String? namespace, + Map? data, + ) => super.noSuchMethod( + Invocation.method(#addNamespace, [locale, namespace, data]), + returnValueForMissingStub: null, + ); @override - void removeNamespace(_i3.Locale? locale, String? namespace) => super - .noSuchMethod(Invocation.method(#removeNamespace, [locale, namespace]), - returnValueForMissingStub: null); + void removeNamespace(_i3.Locale? locale, String? namespace) => + super.noSuchMethod( + Invocation.method(#removeNamespace, [locale, namespace]), + returnValueForMissingStub: null, + ); @override _i4.Future removeLocale(_i3.Locale? locale) => - (super.noSuchMethod(Invocation.method(#removeLocale, [locale]), - returnValue: Future.value(null), - returnValueForMissingStub: Future.value()) as _i4.Future); + (super.noSuchMethod( + Invocation.method(#removeLocale, [locale]), + returnValue: Future.value(null), + returnValueForMissingStub: Future.value(), + ) + as _i4.Future); @override _i4.Future removeAll() => - (super.noSuchMethod(Invocation.method(#removeAll, []), - returnValue: Future.value(null), - returnValueForMissingStub: Future.value()) as _i4.Future); + (super.noSuchMethod( + Invocation.method(#removeAll, []), + returnValue: Future.value(null), + returnValueForMissingStub: Future.value(), + ) + as _i4.Future); @override bool isNamespaceRegistered(_i3.Locale? locale, String? namespace) => (super.noSuchMethod( - Invocation.method(#isNamespaceRegistered, [locale, namespace]), - returnValue: false) as bool); + Invocation.method(#isNamespaceRegistered, [locale, namespace]), + returnValue: false, + ) + as bool); @override bool isLocaleRegistered(_i3.Locale? locale) => - (super.noSuchMethod(Invocation.method(#isLocaleRegistered, [locale]), - returnValue: false) as bool); + (super.noSuchMethod( + Invocation.method(#isLocaleRegistered, [locale]), + returnValue: false, + ) + as bool); @override - String? retrieve(_i3.Locale? locale, String? namespace, String? key, - _i5.I18NextOptions? options) => + String? retrieve( + _i3.Locale? locale, + String? namespace, + String? key, + _i5.I18NextOptions? options, + ) => (super.noSuchMethod( - Invocation.method(#retrieve, [locale, namespace, key, options])) + Invocation.method(#retrieve, [locale, namespace, key, options]), + ) as String?); } @@ -69,7 +90,9 @@ class MockLocalizationDataSource extends _i1.Mock @override _i4.Future> load(_i3.Locale? locale) => - (super.noSuchMethod(Invocation.method(#load, [locale]), - returnValue: Future.value({})) + (super.noSuchMethod( + Invocation.method(#load, [locale]), + returnValue: Future.value({}), + ) as _i4.Future>); } diff --git a/test/interpolator_test.dart b/test/interpolator_test.dart index 9e6c2b2..4a60d87 100644 --- a/test/interpolator_test.dart +++ b/test/interpolator_test.dart @@ -28,10 +28,7 @@ void main() { final throwsInterpolationException = throwsA(isA()); test('given a non matching string', () { - expect( - interpol('This is a normal string'), - 'This is a normal string', - ); + expect(interpol('This is a normal string'), 'This is a normal string'); }); group('given a matching string', () { @@ -45,10 +42,7 @@ void main() { group('with variable only', () { test('without variables', () { const string = 'This is a {{variable}} string'; - expect( - () => interpol(string), - throwsInterpolationException, - ); + expect(() => interpol(string), throwsInterpolationException); }); test('with replaceable variables', () { @@ -67,8 +61,8 @@ void main() { 'This is a {{grouped.key.variable}} string', variables: { 'grouped': { - 'key': {'variable': 'grouped variable'} - } + 'key': {'variable': 'grouped variable'}, + }, }, ), 'This is a grouped variable string', @@ -80,7 +74,7 @@ void main() { () => interpol( 'This is a {{grouped.key.variable}} string', variables: { - 'grouped': {'key': 'grouped variable'} + 'grouped': {'key': 'grouped variable'}, }, ), throwsInterpolationException, @@ -112,15 +106,13 @@ void main() { 'This is a {{variable, format}} string', variables: {'variable': 'my variable'}, formats: { - 'format': expectAsync4( - (variable, format, locale, options) { - expect(variable, 'my variable'); - expect(format.name, 'format'); - expect(format.options, isEmpty); - expect(locale, defaultLocale); - return 'VALUE'; - }, - ), + 'format': expectAsync4((variable, format, locale, options) { + expect(variable, 'my variable'); + expect(format.name, 'format'); + expect(format.options, isEmpty); + expect(locale, defaultLocale); + return 'VALUE'; + }), }, ), 'This is a VALUE string', @@ -133,15 +125,13 @@ void main() { 'This is a {{variable, format}} string', variables: {'variable': 'my variable'}, formats: { - 'format': expectAsync4( - (variable, format, locale, options) { - expect(variable, 'my variable'); - expect(format.name, 'format'); - expect(format.options, isEmpty); - expect(locale, defaultLocale); - return null; - }, - ), + 'format': expectAsync4((variable, format, locale, options) { + expect(variable, 'my variable'); + expect(format.name, 'format'); + expect(format.options, isEmpty); + expect(locale, defaultLocale); + return null; + }), }, ), throwsInterpolationException, @@ -228,16 +218,14 @@ void main() { final throwsNestingException = throwsA(isA()); - Translate noTranslateCalls() => - expectAsync4((key, a, b, c) => fail('Should not have been called'), - count: 0); + Translate noTranslateCalls() => expectAsync4( + (key, a, b, c) => fail('Should not have been called'), + count: 0, + ); test('given a non matching string', () { expect( - nst( - 'This is my unmatching string', - translate: noTranslateCalls(), - ), + nst('This is my unmatching string', translate: noTranslateCalls()), 'This is my unmatching string', ); }); @@ -245,10 +233,7 @@ void main() { group('given a nesting string', () { test('without key or variables', () { expect( - () => nst( - r'This is my $t() string', - translate: noTranslateCalls(), - ), + () => nst(r'This is my $t() string', translate: noTranslateCalls()), throwsNestingException, ); }); @@ -356,10 +341,7 @@ void main() { group('with multiple nestings', () { test('and both succeed', () { - final returnValues = { - 'key': 'VALUE', - 'anotherKey': 'ANOTHER VALUE', - }; + final returnValues = {'key': 'VALUE', 'anotherKey': 'ANOTHER VALUE'}; expect( nst( @@ -401,22 +383,14 @@ void main() { }); test('when has only one match with format with whitespaces', () { - expect( - allMatches('My text has {{one, Xyz}} match'), - ['one, Xyz'], - ); - expect( - allMatches('My text has {{one, Xyz }} match'), - ['one, Xyz '], - ); - expect( - allMatches('My text has {{one, Xyz }} match'), - ['one, Xyz '], - ); - expect( - allMatches('My text has {{one, \nXyz\n}} match'), - ['one, \nXyz\n'], - ); + expect(allMatches('My text has {{one, Xyz}} match'), ['one, Xyz']); + expect(allMatches('My text has {{one, Xyz }} match'), ['one, Xyz ']); + expect(allMatches('My text has {{one, Xyz }} match'), [ + 'one, Xyz ', + ]); + expect(allMatches('My text has {{one, \nXyz\n}} match'), [ + 'one, \nXyz\n', + ]); }); test('when has multiple matches without formats', () { @@ -448,43 +422,40 @@ void main() { group('nestingPattern', () { final pattern = nestingPattern(baseOptions); - Iterable> allMatches(String text) => - pattern.allMatches(text).map((match) => [ - match.namedGroup('key'), - match.namedGroup('variables'), - ]); + Iterable> allMatches(String text) => pattern + .allMatches(text) + .map( + (match) => [match.namedGroup('key'), match.namedGroup('variables')], + ); test('default pattern', () { - expect( - pattern.pattern, - r'\$t\((?.*?)(,\s*(?.*?)\s*)?\)', - ); + expect(pattern.pattern, r'\$t\((?.*?)(,\s*(?.*?)\s*)?\)'); }); test('when has only one match without variables', () { expect(allMatches(r'My text has $t(one) match'), [ - ['one', null] + ['one', null], ]); }); test('when has only one match with variables', () { expect(allMatches(r'My text has $t(one, {"my": "values"}) match'), [ - ['one', '{"my": "values"}'] + ['one', '{"my": "values"}'], ]); }); test('when has only one match with variables and whitespaces', () { expect(allMatches(r'My text has $t(one, Xyz) match'), [ - ['one', 'Xyz'] + ['one', 'Xyz'], ]); expect(allMatches(r'My text has $t(one, Xyz ) match'), [ - ['one', 'Xyz'] + ['one', 'Xyz'], ]); expect(allMatches(r'My text has $t(one, Xyz ) match'), [ - ['one', 'Xyz'] + ['one', 'Xyz'], ]); expect(allMatches('My text has \$t(one, \nXyz\n) match'), [ - ['one', 'Xyz'] + ['one', 'Xyz'], ]); }); @@ -493,7 +464,7 @@ void main() { ['text', null], ['has', null], ['four', null], - ['matches', null] + ['matches', null], ]); }); @@ -506,7 +477,7 @@ void main() { ['text', 'Aaa'], ['has', 'Bbb'], ['four', 'Ccc'], - ['matches', 'Ddd'] + ['matches', 'Ddd'], ], ); }); @@ -520,7 +491,7 @@ void main() { ['text', null], ['has', 'Bbb'], ['four', 'Ccc'], - ['matches', null] + ['matches', null], ]); }); }); @@ -575,5 +546,4 @@ String? _defaultTranslate( Locale locale, Map variables, I18NextOptions options, -) => - key; +) => key; diff --git a/test/options_test.dart b/test/options_test.dart index 2628c42..80929c1 100644 --- a/test/options_test.dart +++ b/test/options_test.dart @@ -179,14 +179,8 @@ void main() { result.contextSeparator, permutation[2] ?? base.contextSeparator, ); - expect( - result.pluralSeparator, - permutation[3] ?? base.pluralSeparator, - ); - expect( - result.keySeparator, - permutation[4] ?? base.keySeparator, - ); + expect(result.pluralSeparator, permutation[3] ?? base.pluralSeparator); + expect(result.keySeparator, permutation[4] ?? base.keySeparator); expect( result.interpolationPrefix, permutation[5] ?? base.interpolationPrefix, @@ -195,18 +189,9 @@ void main() { result.interpolationSuffix, permutation[6] ?? base.interpolationSuffix, ); - expect( - result.formatSeparator, - permutation[7] ?? base.formatSeparator, - ); - expect( - result.formatterValues, - permutation[8] ?? base.formatterValues, - ); - expect( - result.formats, - permutation[9] ?? base.formats, - ); + expect(result.formatSeparator, permutation[7] ?? base.formatSeparator); + expect(result.formatterValues, permutation[8] ?? base.formatterValues); + expect(result.formats, permutation[9] ?? base.formats); expect( result.optionsSeparator, permutation[10] ?? base.optionsSeparator, @@ -215,22 +200,13 @@ void main() { result.optionValueSeparator, permutation[11] ?? base.optionValueSeparator, ); - expect( - result.nestingPrefix, - permutation[12] ?? base.nestingPrefix, - ); - expect( - result.nestingSuffix, - permutation[13] ?? base.nestingSuffix, - ); + expect(result.nestingPrefix, permutation[12] ?? base.nestingPrefix); + expect(result.nestingSuffix, permutation[13] ?? base.nestingSuffix); expect( result.nestingSeparator, permutation[14] ?? base.nestingSeparator, ); - expect( - result.pluralSuffix, - permutation[15] ?? base.pluralSuffix, - ); + expect(result.pluralSuffix, permutation[15] ?? base.pluralSuffix); }); } }); diff --git a/test/resource_store_test.dart b/test/resource_store_test.dart index 98ce58c..58a365c 100644 --- a/test/resource_store_test.dart +++ b/test/resource_store_test.dart @@ -18,19 +18,19 @@ void main() { const validNamespace = 'ns'; setUp(() { - store = ResourceStore(data: { - locale: { - validNamespace: { - 'key': 'This is a simple key', - 'my': { - 'key': 'This is a nested key', - 'nested': { - 'key': 'This is a more nested key', - } - } - } - } - }); + store = ResourceStore( + data: { + locale: { + validNamespace: { + 'key': 'This is a simple key', + 'my': { + 'key': 'This is a nested key', + 'nested': {'key': 'This is a more nested key'}, + }, + }, + }, + }, + ); }); test('with unmatching locale', () { @@ -89,36 +89,19 @@ void main() { }); test('given a keySeparator', () { - final newOptions = options.copyWith( - keySeparator: '+++', - ); + final newOptions = options.copyWith(keySeparator: '+++'); expect( - store.retrieve( - locale, - validNamespace, - 'my+++nested+++key', - newOptions, - ), + store.retrieve(locale, validNamespace, 'my+++nested+++key', newOptions), 'This is a more nested key', ); expect( - store.retrieve( - locale, - validNamespace, - 'my.nested.key', - newOptions, - ), + store.retrieve(locale, validNamespace, 'my.nested.key', newOptions), isNull, ); expect( - store.retrieve( - locale, - validNamespace, - 'my/nested/key', - newOptions, - ), + store.retrieve(locale, validNamespace, 'my/nested/key', newOptions), isNull, ); }); @@ -208,7 +191,9 @@ void main() { const anotherData = {'a': '00', 'b': '11'}; store.addNamespace(locale, registeredNamespace, anotherData); expect( - store.isNamespaceRegistered(locale, registeredNamespace), isTrue); + store.isNamespaceRegistered(locale, registeredNamespace), + isTrue, + ); expect( store.retrieve(locale, registeredNamespace, 'a', options), equals('00'), diff --git a/test/utils_test.dart b/test/utils_test.dart index 43739be..57b4302 100644 --- a/test/utils_test.dart +++ b/test/utils_test.dart @@ -7,17 +7,12 @@ void main() { group('evaluate', () { const locale = Locale('any'); - final level2 = { - 'key': 'Second level leaf', - }; + final level2 = {'key': 'Second level leaf'}; final level1 = { 'key': 'First level leaf', 'nested': level2, }; - final data = { - 'key': 'Zero level leaf', - locale: level1, - }; + final data = {'key': 'Zero level leaf', locale: level1}; test('given empty path', () { expect(evaluate([], data), data); From 03f87823dbab215417b398f5e7c1ccd480b913c5 Mon Sep 17 00:00:00 2001 From: William Cho Date: Mon, 5 Jan 2026 16:03:07 -0300 Subject: [PATCH 2/7] fix: asset bundle data source tests & mocks --- pubspec.yaml | 3 +- .../asset_bundle_data_source_test.dart | 114 ++++++++++-------- .../asset_bundle_data_source_test.mocks.dart | 57 --------- 3 files changed, 66 insertions(+), 108 deletions(-) delete mode 100644 test/data_sources/asset_bundle_data_source_test.mocks.dart diff --git a/pubspec.yaml b/pubspec.yaml index 5ccef61..6dd7368 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,5 +19,4 @@ dev_dependencies: flutter_test: sdk: flutter mockito: ^5.2.0 - -flutter: + mocktail: ^1.0.4 diff --git a/test/data_sources/asset_bundle_data_source_test.dart b/test/data_sources/asset_bundle_data_source_test.dart index e15280d..be2f82f 100644 --- a/test/data_sources/asset_bundle_data_source_test.dart +++ b/test/data_sources/asset_bundle_data_source_test.dart @@ -1,17 +1,39 @@ +import 'dart:async'; import 'dart:ui'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:i18next/i18next.dart'; -import 'package:mockito/annotations.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; + +class MockAssetBundle extends Mock implements AssetBundle { + @override + Future loadStructuredBinaryData( + String key, + FutureOr Function(ByteData data) parser, + ) async { + return await loadMock(key) as T; + } + + Future loadMock(String key); +} + +class MockAssetManifest implements AssetManifest { + MockAssetManifest(this.assets); + + final List assets; -import 'asset_bundle_data_source_test.mocks.dart'; + @override + List listAssets() => assets; + + @override + List? getAssetVariants(String key) { + throw UnimplementedError(); + } +} -@GenerateMocks([AssetBundle]) void main() { const bundlePath = 'bundle/path'; - const defaultManifest = 'AssetManifest.json'; late MockAssetBundle bundle; late AssetBundleLocalizationDataSource dataSource; @@ -25,21 +47,19 @@ void main() { group('#loadFromAssetBundle', () { setUp(() { - when(bundle.loadString(defaultManifest)).thenAnswer( - (_) async => - '''{ - "another/asset/path": [""], - "$bundlePath/en-US/file1.json": [""], - "$bundlePath/en-US/file2.json": [""], - "$bundlePath/pt/file1.json": [""], - "$bundlePath/pt/file2.json": [""] - }''', + when(() => bundle.loadMock(any())).thenAnswer( + (_) async => MockAssetManifest([ + "another/asset/path", + "$bundlePath/en-US/file1.json", + "$bundlePath/en-US/file2.json", + "$bundlePath/pt/file1.json", + "$bundlePath/pt/file2.json", + ]), ); }); test('given any locale', () async { await expectLater(dataSource.load(const Locale('any')), completes); - verify(bundle.loadString(defaultManifest)).called(1); }); test('given an unregistered locale', () { @@ -48,7 +68,7 @@ void main() { test('given a supported full locale', () async { when( - bundle.loadString(argThat(contains('$bundlePath/'))), + () => bundle.loadString(any(that: contains('$bundlePath/'))), ).thenAnswer((_) async => '{}'); await expectLater( @@ -58,8 +78,8 @@ void main() { ), ); - verify(bundle.loadString('$bundlePath/en-US/file1.json')).called(1); - verify(bundle.loadString('$bundlePath/en-US/file2.json')).called(1); + verify(() => bundle.loadString('$bundlePath/en-US/file1.json')).called(1); + verify(() => bundle.loadString('$bundlePath/en-US/file2.json')).called(1); }); test('given an unsupported long locale', () async { @@ -68,14 +88,20 @@ void main() { completion(isEmpty), ); - verifyNever(bundle.loadString(argThat(contains('$bundlePath/pt/')))); - verifyNever(bundle.loadString(argThat(contains('$bundlePath/pt-BR/')))); - verifyNever(bundle.loadString(argThat(contains('$bundlePath/en-US/')))); + verifyNever( + () => bundle.loadString(any(that: contains('$bundlePath/pt/'))), + ); + verifyNever( + () => bundle.loadString(any(that: contains('$bundlePath/pt-BR/'))), + ); + verifyNever( + () => bundle.loadString(any(that: contains('$bundlePath/en-US/'))), + ); }); test('given a supported short locale', () async { when( - bundle.loadString(argThat(contains('$bundlePath/'))), + () => bundle.loadString(any(that: contains('$bundlePath/'))), ).thenAnswer((_) async => '{}'); await expectLater( @@ -85,9 +111,11 @@ void main() { ), ); - verify(bundle.loadString('$bundlePath/pt/file1.json')).called(1); - verify(bundle.loadString('$bundlePath/pt/file2.json')).called(1); - verifyNever(bundle.loadString(argThat(contains('$bundlePath/en-US/')))); + verify(() => bundle.loadString('$bundlePath/pt/file1.json')).called(1); + verify(() => bundle.loadString('$bundlePath/pt/file2.json')).called(1); + verifyNever( + () => bundle.loadString(any(that: contains('$bundlePath/en-US/'))), + ); }); test('given an unsupported short locale', () async { @@ -96,40 +124,28 @@ void main() { completion(isEmpty), ); - verifyNever(bundle.loadString(argThat(contains('$bundlePath/ar/')))); - verifyNever(bundle.loadString(argThat(contains('$bundlePath/pt/')))); - verifyNever(bundle.loadString(argThat(contains('$bundlePath/en-US/')))); + verifyNever( + () => bundle.loadString(any(that: contains('$bundlePath/ar/'))), + ); + verifyNever( + () => bundle.loadString(any(that: contains('$bundlePath/pt/'))), + ); + verifyNever( + () => bundle.loadString(any(that: contains('$bundlePath/en-US/'))), + ); }); test('when bundle errors', () async { const error = 'Some error'; - when(bundle.loadString(any)).thenAnswer((_) async => throw error); - - expect(dataSource.load(const Locale('any')), throwsA(error)); - }); + when(() => bundle.loadMock(any())).thenAnswer((_) async => throw error); - test('given manifest empty', () { - // expect( - // () => dataSource.load( - // const Locale('any'), - // // manifest: '', - // ), - // throwsAssertionError, - // ); - }); - - test('given manifest', () async { - const manifest = 'SomeManifestFile.json'; - when(bundle.loadString(any)).thenAnswer((_) async => '{}'); - - await expectLater(dataSource.load(const Locale('any')), completes); - verify(bundle.loadString(manifest)).called(1); + await expectLater(dataSource.load(const Locale('any')), throwsA(error)); }); test('given incorrect source-path to any bundle asset', () async { await expectLater(dataSource.load(const Locale('any')), completes); - verifyNever(bundle.loadString(argThat(contains('bundle\\path')))); + verifyNever(() => bundle.loadString(any(that: contains('bundle\\path')))); }); }); } diff --git a/test/data_sources/asset_bundle_data_source_test.mocks.dart b/test/data_sources/asset_bundle_data_source_test.mocks.dart deleted file mode 100644 index 7796b59..0000000 --- a/test/data_sources/asset_bundle_data_source_test.mocks.dart +++ /dev/null @@ -1,57 +0,0 @@ -// Mocks generated by Mockito 5.0.3 from annotations -// in i18next/test/data_sources/asset_bundle_data_source_test.dart. -// Do not manually edit this file. - -import 'dart:async' as _i4; -import 'dart:typed_data' as _i2; - -import 'package:flutter/src/services/asset_bundle.dart' as _i3; -import 'package:mockito/mockito.dart' as _i1; - -// ignore_for_file: comment_references -// ignore_for_file: unnecessary_parenthesis - -class _FakeByteData extends _i1.Fake {} - -/// A class which mocks [AssetBundle]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockAssetBundle extends _i1.Mock implements _i3.AssetBundle { - MockAssetBundle() { - _i1.throwOnMissingStub(this); - } - - @override - _i4.Future<_i2.ByteData> load(String? key) => - (super.noSuchMethod( - Invocation.method(#load, [key]), - returnValue: Future.value(_FakeByteData()), - ) - as _i4.Future<_i2.ByteData>); - @override - _i4.Future loadString(String? key, {bool? cache = true}) => - (super.noSuchMethod( - Invocation.method(#loadString, [key], {#cache: cache}), - returnValue: Future.value(''), - ) - as _i4.Future); - @override - _i4.Future loadStructuredData( - String? key, - _i4.Future Function(String)? parser, - ) => - (super.noSuchMethod( - Invocation.method(#loadStructuredData, [key, parser]), - returnValue: Future.value(null), - ) - as _i4.Future); - @override - void evict(String? key) => super.noSuchMethod( - Invocation.method(#evict, [key]), - returnValueForMissingStub: null, - ); - @override - String toString() => - (super.noSuchMethod(Invocation.method(#toString, []), returnValue: '') - as String); -} From dc42e88f37a9829e143692b324ed8560ce282c5a Mon Sep 17 00:00:00 2001 From: William Cho Date: Mon, 5 Jan 2026 16:09:35 -0300 Subject: [PATCH 3/7] fix: i18next test mocks --- test/i18next_test.dart | 48 ++++++++++++------ test/i18next_test.mocks.dart | 98 ------------------------------------ 2 files changed, 32 insertions(+), 114 deletions(-) delete mode 100644 test/i18next_test.mocks.dart diff --git a/test/i18next_test.dart b/test/i18next_test.dart index 7093159..6691031 100644 --- a/test/i18next_test.dart +++ b/test/i18next_test.dart @@ -1,12 +1,13 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:i18next/i18next.dart'; -import 'package:mockito/annotations.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; -import 'i18next_localization_delegate_test.mocks.dart'; +class MockResourceStore extends Mock implements ResourceStore {} + +class MockLocalizationDataSource extends Mock + implements LocalizationDataSource {} -@GenerateMocks([ResourceStore, LocalizationDataSource]) void main() { const namespace = 'local_namespace'; const locale = Locale('en'); @@ -14,10 +15,17 @@ void main() { late I18Next i18next; late MockResourceStore resourceStore; + setUpAll(() { + registerFallbackValue(Locale('any')); + registerFallbackValue(I18NextOptions()); + }); + setUp(() { resourceStore = MockResourceStore(); i18next = I18Next(locale, resourceStore); - when(resourceStore.retrieve(any, any, any, any)).thenReturn(null); + when( + () => resourceStore.retrieve(any(), any(), any(), any()), + ).thenReturn(null); }); void mockKey( @@ -26,7 +34,9 @@ void main() { String ns = namespace, Locale locale = locale, }) { - when(resourceStore.retrieve(locale, ns, key, any)).thenReturn(answer); + when( + () => resourceStore.retrieve(locale, ns, key, any()), + ).thenReturn(answer); } group('given named namespaces', () { @@ -37,20 +47,20 @@ void main() { test('given key for matching namespaces', () { expect(i18next.t('ns1:key'), 'My first value'); - verify(resourceStore.retrieve(locale, 'ns1', 'key', any)); + verify(() => resourceStore.retrieve(locale, 'ns1', 'key', any())); expect(i18next.t('ns2:key'), 'My second value'); - verify(resourceStore.retrieve(locale, 'ns2', 'key', any)); + verify(() => resourceStore.retrieve(locale, 'ns2', 'key', any())); }); test('given key for unmatching namespaces', () { expect(i18next.t('ns3:key'), 'ns3:key'); - verify(resourceStore.retrieve(locale, 'ns3', 'key', any)); + verify(() => resourceStore.retrieve(locale, 'ns3', 'key', any())); }); test('given key for partially matching namespaces', () { expect(i18next.t('ns:key'), 'ns:key'); - verify(resourceStore.retrieve(locale, 'ns', 'key', any)); + verify(() => resourceStore.retrieve(locale, 'ns', 'key', any())); }); }); @@ -58,17 +68,23 @@ void main() { mockKey('key', 'My value', ns: 'ns'); expect(i18next.t('ns:key'), 'My value'); - verify(resourceStore.retrieve(locale, 'ns', 'key', any)).called(1); + verify(() => resourceStore.retrieve(locale, 'ns', 'key', any())).called(1); }); test('given key without namespace', () { - when(resourceStore.retrieve(any, any, any, any)).thenReturn(null); + when( + () => resourceStore.retrieve(any(), any(), any(), any()), + ).thenReturn(null); expect(i18next.t('someKey'), 'someKey'); - verify(resourceStore.retrieve(locale, '', 'someKey', any)).called(1); + verify( + () => resourceStore.retrieve(locale, '', 'someKey', any()), + ).called(1); expect(i18next.t('some.key'), 'some.key'); - verify(resourceStore.retrieve(locale, '', 'some.key', any)).called(1); + verify( + () => resourceStore.retrieve(locale, '', 'some.key', any()), + ).called(1); }); test('given an existing string key', () { @@ -100,7 +116,7 @@ void main() { expect(i18next.t('$namespace:key', locale: anotherLocale), 'my value'); verify( - resourceStore.retrieve(anotherLocale, namespace, 'key', any), + () => resourceStore.retrieve(anotherLocale, namespace, 'key', any()), ).called(1); }); @@ -762,7 +778,7 @@ void main() { testWidgets('when is registered in the widget tree', (tester) async { final dataSource = MockLocalizationDataSource(); - when(dataSource.load(any)).thenAnswer((_) async => {}); + when(() => dataSource.load(any())).thenAnswer((_) async => {}); await tester.pumpWidget( Localizations( diff --git a/test/i18next_test.mocks.dart b/test/i18next_test.mocks.dart deleted file mode 100644 index 7cf787d..0000000 --- a/test/i18next_test.mocks.dart +++ /dev/null @@ -1,98 +0,0 @@ -// Mocks generated by Mockito 5.0.3 from annotations -// in i18next/test/i18next_test.dart. -// Do not manually edit this file. - -import 'dart:async' as _i4; -import 'dart:ui' as _i3; - -import 'package:i18next/src/data_sources/localization_data_source.dart' as _i6; -import 'package:i18next/src/options.dart' as _i5; -import 'package:i18next/src/resource_store.dart' as _i2; -import 'package:mockito/mockito.dart' as _i1; - -// ignore_for_file: comment_references -// ignore_for_file: unnecessary_parenthesis - -/// A class which mocks [ResourceStore]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockResourceStore extends _i1.Mock implements _i2.ResourceStore { - MockResourceStore() { - _i1.throwOnMissingStub(this); - } - - @override - void addNamespace( - _i3.Locale? locale, - String? namespace, - Map? data, - ) => super.noSuchMethod( - Invocation.method(#addNamespace, [locale, namespace, data]), - returnValueForMissingStub: null, - ); - @override - void removeNamespace(_i3.Locale? locale, String? namespace) => - super.noSuchMethod( - Invocation.method(#removeNamespace, [locale, namespace]), - returnValueForMissingStub: null, - ); - @override - _i4.Future removeLocale(_i3.Locale? locale) => - (super.noSuchMethod( - Invocation.method(#removeLocale, [locale]), - returnValue: Future.value(null), - returnValueForMissingStub: Future.value(), - ) - as _i4.Future); - @override - _i4.Future removeAll() => - (super.noSuchMethod( - Invocation.method(#removeAll, []), - returnValue: Future.value(null), - returnValueForMissingStub: Future.value(), - ) - as _i4.Future); - @override - bool isNamespaceRegistered(_i3.Locale? locale, String? namespace) => - (super.noSuchMethod( - Invocation.method(#isNamespaceRegistered, [locale, namespace]), - returnValue: false, - ) - as bool); - @override - bool isLocaleRegistered(_i3.Locale? locale) => - (super.noSuchMethod( - Invocation.method(#isLocaleRegistered, [locale]), - returnValue: false, - ) - as bool); - @override - String? retrieve( - _i3.Locale? locale, - String? namespace, - String? key, - _i5.I18NextOptions? options, - ) => - (super.noSuchMethod( - Invocation.method(#retrieve, [locale, namespace, key, options]), - ) - as String?); -} - -/// A class which mocks [LocalizationDataSource]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockLocalizationDataSource extends _i1.Mock - implements _i6.LocalizationDataSource { - MockLocalizationDataSource() { - _i1.throwOnMissingStub(this); - } - - @override - _i4.Future> load(_i3.Locale? locale) => - (super.noSuchMethod( - Invocation.method(#load, [locale]), - returnValue: Future.value({}), - ) - as _i4.Future>); -} From 00f26bc8accd1ec1f093049895e001dda966a97f Mon Sep 17 00:00:00 2001 From: William Cho Date: Mon, 5 Jan 2026 16:11:52 -0300 Subject: [PATCH 4/7] fix: i18next localization delegate test mocks --- test/i18next_localization_delegate_test.dart | 37 ++++--- ...next_localization_delegate_test.mocks.dart | 98 ------------------- 2 files changed, 21 insertions(+), 114 deletions(-) delete mode 100644 test/i18next_localization_delegate_test.mocks.dart diff --git a/test/i18next_localization_delegate_test.dart b/test/i18next_localization_delegate_test.dart index 4078f0f..eb6229f 100644 --- a/test/i18next_localization_delegate_test.dart +++ b/test/i18next_localization_delegate_test.dart @@ -2,12 +2,13 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:i18next/i18next.dart'; -import 'package:mockito/annotations.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; -import 'i18next_localization_delegate_test.mocks.dart'; +class MockLocalizationDataSource extends Mock + implements LocalizationDataSource {} + +class MockResourceStore extends Mock implements ResourceStore {} -@GenerateMocks([LocalizationDataSource, ResourceStore]) void main() { const en = Locale('en'), enUS = Locale('en', 'US'); const pt = Locale('pt'), ptBR = Locale('pt', 'BR'); @@ -17,6 +18,10 @@ void main() { late MockResourceStore resourceStore; late I18NextLocalizationDelegate localizationDelegate; + setUpAll(() { + registerFallbackValue(Locale('any')); + }); + setUp(() { dataSource = MockLocalizationDataSource(); resourceStore = MockResourceStore(); @@ -61,29 +66,29 @@ void main() { group('#load', () { test('given an exact matching locale', () async { - when(dataSource.load(any)).thenAnswer((_) async => {}); + when(() => dataSource.load(any())).thenAnswer((_) async => {}); await expectLater(localizationDelegate.load(en), completes); - verify(dataSource.load(en)).called(1); + verify(() => dataSource.load(en)).called(1); }); test('given a language code matching locale', () async { - when(dataSource.load(any)).thenAnswer((_) async => {}); + when(() => dataSource.load(any())).thenAnswer((_) async => {}); await expectLater(localizationDelegate.load(enUS), completes); - verify(dataSource.load(en)).called(1); + verify(() => dataSource.load(en)).called(1); }); test('given a non matching language code locale', () async { - when(dataSource.load(any)).thenAnswer((_) async => {}); + when(() => dataSource.load(any())).thenAnswer((_) async => {}); await expectLater(() => localizationDelegate.load(ar), throwsException); - verifyNever(dataSource.load(any)); + verifyNever(() => dataSource.load(any())); }); test('when dataSource errors', () async { const error = 'Some error'; - when(dataSource.load(any)).thenAnswer((_) async => throw error); + when(() => dataSource.load(any())).thenAnswer((_) async => throw error); await expectLater(localizationDelegate.load(en), throwsA(error)); }); @@ -92,19 +97,19 @@ void main() { const data1 = {'key': 'ns1'}; const data2 = {'key': 'ns1'}; when( - dataSource.load(any), + () => dataSource.load(any()), ).thenAnswer((_) async => {'ns1': data1, 'ns2': data2}); final i18next = await localizationDelegate.load(en); expect(i18next.locale, en); - verify(resourceStore.addNamespace(en, 'ns1', data1)).called(1); - verify(resourceStore.addNamespace(en, 'ns2', data2)).called(1); + verify(() => resourceStore.addNamespace(en, 'ns1', data1)).called(1); + verify(() => resourceStore.addNamespace(en, 'ns2', data2)).called(1); }); test('when dataSource is synchronous', () { const data1 = {'key': 'ns1'}; when( - dataSource.load(any), + () => dataSource.load(any()), ).thenAnswer((_) => SynchronousFuture({'ns1': data1})); // checking if this is being called sync @@ -112,7 +117,7 @@ void main() { localizationDelegate.load(en).then((value) => result = value); expect(result, isNotNull); expect(result!.locale, en); - verify(resourceStore.addNamespace(en, 'ns1', data1)).called(1); + verify(() => resourceStore.addNamespace(en, 'ns1', data1)).called(1); }); }); } diff --git a/test/i18next_localization_delegate_test.mocks.dart b/test/i18next_localization_delegate_test.mocks.dart deleted file mode 100644 index 9afae86..0000000 --- a/test/i18next_localization_delegate_test.mocks.dart +++ /dev/null @@ -1,98 +0,0 @@ -// Mocks generated by Mockito 5.0.3 from annotations -// in i18next/test/i18next_localization_delegate_test.dart. -// Do not manually edit this file. - -import 'dart:async' as _i3; -import 'dart:ui' as _i4; - -import 'package:i18next/src/data_sources/localization_data_source.dart' as _i2; -import 'package:i18next/src/options.dart' as _i6; -import 'package:i18next/src/resource_store.dart' as _i5; -import 'package:mockito/mockito.dart' as _i1; - -// ignore_for_file: comment_references -// ignore_for_file: unnecessary_parenthesis - -/// A class which mocks [LocalizationDataSource]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockLocalizationDataSource extends _i1.Mock - implements _i2.LocalizationDataSource { - MockLocalizationDataSource() { - _i1.throwOnMissingStub(this); - } - - @override - _i3.Future> load(_i4.Locale? locale) => - (super.noSuchMethod( - Invocation.method(#load, [locale]), - returnValue: Future.value({}), - ) - as _i3.Future>); -} - -/// A class which mocks [ResourceStore]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockResourceStore extends _i1.Mock implements _i5.ResourceStore { - MockResourceStore() { - _i1.throwOnMissingStub(this); - } - - @override - void addNamespace( - _i4.Locale? locale, - String? namespace, - Map? data, - ) => super.noSuchMethod( - Invocation.method(#addNamespace, [locale, namespace, data]), - returnValueForMissingStub: null, - ); - @override - void removeNamespace(_i4.Locale? locale, String? namespace) => - super.noSuchMethod( - Invocation.method(#removeNamespace, [locale, namespace]), - returnValueForMissingStub: null, - ); - @override - _i3.Future removeLocale(_i4.Locale? locale) => - (super.noSuchMethod( - Invocation.method(#removeLocale, [locale]), - returnValue: Future.value(null), - returnValueForMissingStub: Future.value(), - ) - as _i3.Future); - @override - _i3.Future removeAll() => - (super.noSuchMethod( - Invocation.method(#removeAll, []), - returnValue: Future.value(null), - returnValueForMissingStub: Future.value(), - ) - as _i3.Future); - @override - bool isNamespaceRegistered(_i4.Locale? locale, String? namespace) => - (super.noSuchMethod( - Invocation.method(#isNamespaceRegistered, [locale, namespace]), - returnValue: false, - ) - as bool); - @override - bool isLocaleRegistered(_i4.Locale? locale) => - (super.noSuchMethod( - Invocation.method(#isLocaleRegistered, [locale]), - returnValue: false, - ) - as bool); - @override - String? retrieve( - _i4.Locale? locale, - String? namespace, - String? key, - _i6.I18NextOptions? options, - ) => - (super.noSuchMethod( - Invocation.method(#retrieve, [locale, namespace, key, options]), - ) - as String?); -} From 6b7bc492f9c2f8c6e71bb8378e663f5a45719a6a Mon Sep 17 00:00:00 2001 From: William Cho Date: Mon, 5 Jan 2026 16:12:21 -0300 Subject: [PATCH 5/7] chore: remove mockito --- pubspec.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 6dd7368..4dd7447 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,5 +18,4 @@ dev_dependencies: flutter_lints: ^6.0.0 flutter_test: sdk: flutter - mockito: ^5.2.0 mocktail: ^1.0.4 From 1dfc2ebb23fa66048dddda717c3d862c30a14846 Mon Sep 17 00:00:00 2001 From: William Cho Date: Mon, 5 Jan 2026 16:23:33 -0300 Subject: [PATCH 6/7] chore: remove unnecessary library name --- lib/i18next.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/i18next.dart b/lib/i18next.dart index 38389ed..813f3ac 100644 --- a/lib/i18next.dart +++ b/lib/i18next.dart @@ -1,5 +1,3 @@ -library i18next; - export 'src/data_sources/asset_bundle_data_source.dart'; export 'src/data_sources/localization_data_source.dart'; export 'src/i18next.dart'; From 7d134a381d4ccf6b016b6c6f3f9c64d094ace355 Mon Sep 17 00:00:00 2001 From: William Cho Date: Mon, 5 Jan 2026 16:24:03 -0300 Subject: [PATCH 7/7] chore: update workflow --- .github/workflows/build.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 4013f71..1c44901 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -12,10 +12,10 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: subosito/flutter-action@v2 - name: Install Dependencies - run: flutter packages get + run: flutter pub get - name: Format run: dart format --set-exit-if-changed lib test - name: Analyze