flutter/packages/flutter_localizations/test/widgets_test.dart
Gary Qian ec5719a1e0
Default locales test for flutter tester. Roll engine 162b2e98c03..7be0217d67969 (#23596)
162b2e98c036a8f8c355fd475dbe0db4f4bc886c..7be0217d6796954694753647b7a757b8e3907f64
7be0217d6 Roll back _countryCode assert - breaking change (#6693)
91a019cfd Fix popSystemNavigator (#6691)
2b2fbf0f6 Add Locale.fromSubtags and support for scriptCode. (#6518)
58c8e30e5 Roll src/third_party/skia ab18c8e6cc20..d48b7a881b24 (5 commits) (#6690)
3b17cfb68 Flutter tester default locales (#6689)
96bbd2b36 Roll buildroot to 11a934e99e (#6687)
0e9defbd6 Roll src/third_party/skia 68825776f4b4..ab18c8e6cc20 (11 commits) (#6688)
cc686d7ae Don't populate the external view embedder in PaintContext. (#6686)
ab782faa7 Roll src/third_party/skia 1de48d8040aa..68825776f4b4 (1 commits) (#6685)
c4aa8d343 Roll src/third_party/skia 797197a772b8..1de48d8040aa (2 commits) (#6684)
7352251eb Roll src/third_party/skia 79c96811863f..797197a772b8 (1 commits) (#6683)
3dac47e4d Roll src/third_party/skia 38e4fd0c5654..79c96811863f (1 commits) (#6682)
2f06a53a8 Roll src/third_party/skia b53f1f46982d..38e4fd0c5654 (1 commits) (#6681)
a1d7cad70 Fix inconsistent include syntax (#6680)
236661cd8 Roll src/third_party/skia 3b79aa3a5ad0..b53f1f46982d (13 commits) (#6679)
c4f50611d Roll buildroot to pick up Mac toolchain updates. (#6678)
ba8f6aa71 Handle Windows headers defining ERROR to 0 in log levels. (#6677)
505d2a923 Roll buildroot to pick up updates to custom toolchains. (#6674)
6c2a0b3a6 Undefine ERROR in platform_view_layer.cc (#6675)
55e12993a Update FlutterPlugin.h docs, suppress warning for older API (#6672)
f7970048d Attach and position embedded UIVIews (#6614)
df85722fa Plumb the iOS PlatformViewsController into flow. (#6603)
2bfb893cf iOS Embedding Refactor (#6447)
2018-10-29 21:40:45 -07:00

699 lines
23 KiB
Dart

// Copyright 2017 The Chromium Authors. All rights reserved.rint
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui' as ui;
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
class TestLocalizations {
TestLocalizations(this.locale, this.prefix);
final Locale locale;
final String prefix;
static Future<TestLocalizations> loadSync(Locale locale, String prefix) {
return SynchronousFuture<TestLocalizations>(TestLocalizations(locale, prefix));
}
static Future<TestLocalizations> loadAsync(Locale locale, String prefix) {
return Future<TestLocalizations>.delayed(const Duration(milliseconds: 100))
.then((_) => TestLocalizations(locale, prefix));
}
static TestLocalizations of(BuildContext context) {
return Localizations.of<TestLocalizations>(context, TestLocalizations);
}
String get message => '${prefix ?? ""}$locale';
}
class SyncTestLocalizationsDelegate extends LocalizationsDelegate<TestLocalizations> {
SyncTestLocalizationsDelegate([this.prefix]);
final String prefix; // Changing this value triggers a rebuild
final List<bool> shouldReloadValues = <bool>[];
@override
bool isSupported(Locale locale) => true;
@override
Future<TestLocalizations> load(Locale locale) => TestLocalizations.loadSync(locale, prefix);
@override
bool shouldReload(SyncTestLocalizationsDelegate old) {
shouldReloadValues.add(prefix != old.prefix);
return prefix != old.prefix;
}
@override
String toString() => '$runtimeType($prefix)';
}
class AsyncTestLocalizationsDelegate extends LocalizationsDelegate<TestLocalizations> {
AsyncTestLocalizationsDelegate([this.prefix]);
final String prefix; // Changing this value triggers a rebuild
final List<bool> shouldReloadValues = <bool>[];
@override
bool isSupported(Locale locale) => true;
@override
Future<TestLocalizations> load(Locale locale) => TestLocalizations.loadAsync(locale, prefix);
@override
bool shouldReload(AsyncTestLocalizationsDelegate old) {
shouldReloadValues.add(prefix != old.prefix);
return prefix != old.prefix;
}
@override
String toString() => '$runtimeType($prefix)';
}
class MoreLocalizations {
MoreLocalizations(this.locale);
final Locale locale;
static Future<MoreLocalizations> loadSync(Locale locale) {
return SynchronousFuture<MoreLocalizations>(MoreLocalizations(locale));
}
static Future<MoreLocalizations> loadAsync(Locale locale) {
return Future<MoreLocalizations>.delayed(const Duration(milliseconds: 100))
.then((_) => MoreLocalizations(locale));
}
static MoreLocalizations of(BuildContext context) {
return Localizations.of<MoreLocalizations>(context, MoreLocalizations);
}
String get message => '$locale';
}
class SyncMoreLocalizationsDelegate extends LocalizationsDelegate<MoreLocalizations> {
@override
Future<MoreLocalizations> load(Locale locale) => MoreLocalizations.loadSync(locale);
@override
bool isSupported(Locale locale) => true;
@override
bool shouldReload(SyncMoreLocalizationsDelegate old) => false;
}
class AsyncMoreLocalizationsDelegate extends LocalizationsDelegate<MoreLocalizations> {
@override
Future<MoreLocalizations> load(Locale locale) => MoreLocalizations.loadAsync(locale);
@override
bool isSupported(Locale locale) => true;
@override
bool shouldReload(AsyncMoreLocalizationsDelegate old) => false;
}
class OnlyRTLDefaultWidgetsLocalizations extends DefaultWidgetsLocalizations {
@override
TextDirection get textDirection => TextDirection.rtl;
}
class OnlyRTLDefaultWidgetsLocalizationsDelegate extends LocalizationsDelegate<WidgetsLocalizations> {
const OnlyRTLDefaultWidgetsLocalizationsDelegate();
@override
bool isSupported(Locale locale) => true;
@override
Future<WidgetsLocalizations> load(Locale locale) {
return SynchronousFuture<WidgetsLocalizations>(OnlyRTLDefaultWidgetsLocalizations());
}
@override
bool shouldReload(OnlyRTLDefaultWidgetsLocalizationsDelegate old) => false;
}
Widget buildFrame({
Locale locale,
Iterable<LocalizationsDelegate<dynamic>> delegates,
WidgetBuilder buildContent,
LocaleResolutionCallback localeResolutionCallback,
List<Locale> supportedLocales = const <Locale>[
Locale('en', 'US'),
Locale('en', 'GB'),
],
}) {
return WidgetsApp(
color: const Color(0xFFFFFFFF),
locale: locale,
localizationsDelegates: delegates,
localeResolutionCallback: localeResolutionCallback,
supportedLocales: supportedLocales,
onGenerateRoute: (RouteSettings settings) {
return PageRouteBuilder<void>(
pageBuilder: (BuildContext context, Animation<double> _, Animation<double> __) {
return buildContent(context);
}
);
},
);
}
class SyncLoadTest extends StatefulWidget {
const SyncLoadTest();
@override
SyncLoadTestState createState() => SyncLoadTestState();
}
class SyncLoadTestState extends State<SyncLoadTest> {
@override
Widget build(BuildContext context) {
return Text(
TestLocalizations.of(context).message,
textDirection: TextDirection.rtl,
);
}
}
void main() {
testWidgets('Localizations.localeFor in a WidgetsApp with system locale', (WidgetTester tester) async {
BuildContext pageContext;
await tester.pumpWidget(
buildFrame(
buildContent: (BuildContext context) {
pageContext = context;
return const Text('Hello World', textDirection: TextDirection.ltr);
}
)
);
await tester.binding.setLocale('en', 'GB');
await tester.pump();
expect(Localizations.localeOf(pageContext), const Locale('en', 'GB'));
await tester.binding.setLocale('en', 'US');
await tester.pump();
expect(Localizations.localeOf(pageContext), const Locale('en', 'US'));
});
testWidgets('Localizations.localeFor in a WidgetsApp with an explicit locale', (WidgetTester tester) async {
const Locale locale = Locale('en', 'US');
BuildContext pageContext;
await tester.pumpWidget(
buildFrame(
locale: locale,
buildContent: (BuildContext context) {
pageContext = context;
return const Text('Hello World');
},
)
);
expect(Localizations.localeOf(pageContext), locale);
await tester.binding.setLocale('en', 'GB');
await tester.pump();
// The WidgetApp's explicit locale overrides the system's locale.
expect(Localizations.localeOf(pageContext), locale);
});
testWidgets('Synchronously loaded localizations in a WidgetsApp', (WidgetTester tester) async {
final List<LocalizationsDelegate<dynamic>> delegates = <LocalizationsDelegate<dynamic>>[
SyncTestLocalizationsDelegate(),
DefaultWidgetsLocalizations.delegate,
];
Future<void> pumpTest(Locale locale) async {
await tester.pumpWidget(Localizations(
locale: locale,
delegates: delegates,
child: const SyncLoadTest(),
));
}
await pumpTest(const Locale('en', 'US'));
expect(find.text('en_US'), findsOneWidget);
await pumpTest(const Locale('en', 'GB'));
await tester.pump();
expect(find.text('en_GB'), findsOneWidget);
await pumpTest(const Locale('en', 'US'));
await tester.pump();
expect(find.text('en_US'), findsOneWidget);
});
testWidgets('Asynchronously loaded localizations in a WidgetsApp', (WidgetTester tester) async {
await tester.pumpWidget(
buildFrame(
delegates: <LocalizationsDelegate<dynamic>>[
AsyncTestLocalizationsDelegate(),
],
buildContent: (BuildContext context) {
return Text(TestLocalizations.of(context).message);
}
)
);
await tester.pump(const Duration(milliseconds: 50)); // TestLocalizations.loadAsync() takes 100ms
expect(find.text('en_US'), findsNothing); // TestLocalizations hasn't been loaded yet
await tester.pump(const Duration(milliseconds: 50)); // TestLocalizations.loadAsync() completes
await tester.pumpAndSettle();
expect(find.text('en_US'), findsOneWidget); // default test locale is US english
await tester.binding.setLocale('en', 'GB');
await tester.pump(const Duration(milliseconds: 100));
await tester.pumpAndSettle();
expect(find.text('en_GB'), findsOneWidget);
await tester.binding.setLocale('en', 'US');
await tester.pump(const Duration(milliseconds: 50));
// TestLocalizations.loadAsync() hasn't completed yet so the old text
// localization is still displayed
expect(find.text('en_GB'), findsOneWidget);
await tester.pump(const Duration(milliseconds: 50)); // finish the async load
await tester.pumpAndSettle();
expect(find.text('en_US'), findsOneWidget);
});
testWidgets('Localizations with multiple sync delegates', (WidgetTester tester) async {
await tester.pumpWidget(
buildFrame(
delegates: <LocalizationsDelegate<dynamic>>[
SyncTestLocalizationsDelegate(),
SyncMoreLocalizationsDelegate(),
],
locale: const Locale('en', 'US'),
buildContent: (BuildContext context) {
return Column(
children: <Widget>[
Text('A: ${TestLocalizations.of(context).message}'),
Text('B: ${MoreLocalizations.of(context).message}'),
],
);
}
)
);
// All localizations were loaded synchronously
expect(find.text('A: en_US'), findsOneWidget);
expect(find.text('B: en_US'), findsOneWidget);
});
testWidgets('Localizations with multiple delegates', (WidgetTester tester) async {
await tester.pumpWidget(
buildFrame(
delegates: <LocalizationsDelegate<dynamic>>[
SyncTestLocalizationsDelegate(),
AsyncMoreLocalizationsDelegate(), // No resources until this completes
],
locale: const Locale('en', 'US'),
buildContent: (BuildContext context) {
return Column(
children: <Widget>[
Text('A: ${TestLocalizations.of(context).message}'),
Text('B: ${MoreLocalizations.of(context).message}'),
],
);
}
)
);
await tester.pump(const Duration(milliseconds: 50));
expect(find.text('A: en_US'), findsNothing); // MoreLocalizations.load() hasn't completed yet
expect(find.text('B: en_US'), findsNothing);
await tester.pump(const Duration(milliseconds: 50));
await tester.pumpAndSettle();
expect(find.text('A: en_US'), findsOneWidget);
expect(find.text('B: en_US'), findsOneWidget);
});
testWidgets('Multiple Localizations', (WidgetTester tester) async {
await tester.pumpWidget(
buildFrame(
delegates: <LocalizationsDelegate<dynamic>>[
SyncTestLocalizationsDelegate(),
],
locale: const Locale('en', 'US'),
buildContent: (BuildContext context) {
return Column(
children: <Widget>[
Text('A: ${TestLocalizations.of(context).message}'),
Localizations(
locale: const Locale('en', 'GB'),
delegates: <LocalizationsDelegate<dynamic>>[
SyncTestLocalizationsDelegate(),
DefaultWidgetsLocalizations.delegate,
],
// Create a new context within the en_GB Localization
child: Builder(
builder: (BuildContext context) {
return Text('B: ${TestLocalizations.of(context).message}');
},
),
),
],
);
}
)
);
expect(find.text('A: en_US'), findsOneWidget);
expect(find.text('B: en_GB'), findsOneWidget);
});
// If both the locale and the length and type of a Localizations delegate list
// stays the same BUT one of its delegate.shouldReload() methods returns true,
// then the dependent widgets should rebuild.
testWidgets('Localizations sync delegate shouldReload returns true', (WidgetTester tester) async {
final SyncTestLocalizationsDelegate originalDelegate = SyncTestLocalizationsDelegate();
await tester.pumpWidget(
buildFrame(
delegates: <LocalizationsDelegate<dynamic>>[
originalDelegate,
SyncMoreLocalizationsDelegate(),
],
locale: const Locale('en', 'US'),
buildContent: (BuildContext context) {
return Column(
children: <Widget>[
Text('A: ${TestLocalizations.of(context).message}'),
Text('B: ${MoreLocalizations.of(context).message}'),
],
);
}
)
);
await tester.pumpAndSettle();
expect(find.text('A: en_US'), findsOneWidget);
expect(find.text('B: en_US'), findsOneWidget);
expect(originalDelegate.shouldReloadValues, <bool>[]);
final SyncTestLocalizationsDelegate modifiedDelegate = SyncTestLocalizationsDelegate('---');
await tester.pumpWidget(
buildFrame(
delegates: <LocalizationsDelegate<dynamic>>[
modifiedDelegate,
SyncMoreLocalizationsDelegate(),
],
locale: const Locale('en', 'US'),
buildContent: (BuildContext context) {
return Column(
children: <Widget>[
Text('A: ${TestLocalizations.of(context).message}'),
Text('B: ${MoreLocalizations.of(context).message}'),
],
);
}
)
);
await tester.pumpAndSettle();
expect(find.text('A: ---en_US'), findsOneWidget);
expect(find.text('B: en_US'), findsOneWidget);
expect(modifiedDelegate.shouldReloadValues, <bool>[true]);
expect(originalDelegate.shouldReloadValues, <bool>[]);
});
testWidgets('Localizations async delegate shouldReload returns true', (WidgetTester tester) async {
await tester.pumpWidget(
buildFrame(
delegates: <LocalizationsDelegate<dynamic>>[
AsyncTestLocalizationsDelegate(),
AsyncMoreLocalizationsDelegate(),
],
locale: const Locale('en', 'US'),
buildContent: (BuildContext context) {
return Column(
children: <Widget>[
Text('A: ${TestLocalizations.of(context).message}'),
Text('B: ${MoreLocalizations.of(context).message}'),
],
);
}
)
);
await tester.pumpAndSettle();
expect(find.text('A: en_US'), findsOneWidget);
expect(find.text('B: en_US'), findsOneWidget);
final AsyncTestLocalizationsDelegate modifiedDelegate = AsyncTestLocalizationsDelegate('---');
await tester.pumpWidget(
buildFrame(
delegates: <LocalizationsDelegate<dynamic>>[
modifiedDelegate,
AsyncMoreLocalizationsDelegate(),
],
locale: const Locale('en', 'US'),
buildContent: (BuildContext context) {
return Column(
children: <Widget>[
Text('A: ${TestLocalizations.of(context).message}'),
Text('B: ${MoreLocalizations.of(context).message}'),
],
);
}
)
);
await tester.pumpAndSettle();
expect(find.text('A: ---en_US'), findsOneWidget);
expect(find.text('B: en_US'), findsOneWidget);
expect(modifiedDelegate.shouldReloadValues, <bool>[true]);
});
testWidgets('Directionality tracks system locale', (WidgetTester tester) async {
BuildContext pageContext;
await tester.pumpWidget(
buildFrame(
delegates: const <LocalizationsDelegate<dynamic>>[
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: const <Locale>[
Locale('en', 'GB'),
Locale('ar', 'EG'),
],
buildContent: (BuildContext context) {
pageContext = context;
return const Text('Hello World');
}
)
);
await tester.binding.setLocale('en', 'GB');
await tester.pump();
expect(Directionality.of(pageContext), TextDirection.ltr);
await tester.binding.setLocale('ar', 'EG');
await tester.pump();
expect(Directionality.of(pageContext), TextDirection.rtl);
});
testWidgets('localeResolutionCallback override', (WidgetTester tester) async {
await tester.pumpWidget(
buildFrame(
localeResolutionCallback: (Locale newLocale, Iterable<Locale> supportedLocales) {
return const Locale('foo', 'BAR');
},
buildContent: (BuildContext context) {
return Text(Localizations.localeOf(context).toString());
}
)
);
await tester.pumpAndSettle();
expect(find.text('foo_BAR'), findsOneWidget);
await tester.binding.setLocale('en', 'GB');
await tester.pumpAndSettle();
expect(find.text('foo_BAR'), findsOneWidget);
});
testWidgets('supportedLocales and defaultLocaleChangeHandler', (WidgetTester tester) async {
await tester.pumpWidget(
buildFrame(
supportedLocales: const <Locale>[
Locale('zh', 'CN'),
Locale('en', 'GB'),
Locale('en', 'CA'),
],
buildContent: (BuildContext context) {
return Text(Localizations.localeOf(context).toString());
}
)
);
await tester.pumpAndSettle();
expect(find.text('en_GB'), findsOneWidget);
// defaultLocaleChangedHandler prefers exact supported locale match
await tester.binding.setLocale('en', 'CA');
await tester.pumpAndSettle();
expect(find.text('en_CA'), findsOneWidget);
// defaultLocaleChangedHandler chooses 1st matching supported locale.languageCode
await tester.binding.setLocale('en', 'US');
await tester.pumpAndSettle();
expect(find.text('en_GB'), findsOneWidget);
// defaultLocaleChangedHandler: no matching supported locale, so use the 1st one
await tester.binding.setLocale('da', 'DA');
await tester.pumpAndSettle();
expect(find.text('zh_CN'), findsOneWidget);
});
testWidgets('Localizations.override widget tracks parent\'s locale and delegates', (WidgetTester tester) async {
await tester.pumpWidget(
buildFrame(
// Accept whatever locale we're given
localeResolutionCallback: (Locale locale, Iterable<Locale> supportedLocales) => locale,
delegates: const <LocalizationsDelegate<dynamic>>[
GlobalWidgetsLocalizations.delegate,
],
buildContent: (BuildContext context) {
return Localizations.override(
context: context,
child: Builder(
builder: (BuildContext context) {
final Locale locale = Localizations.localeOf(context);
final TextDirection direction = WidgetsLocalizations.of(context).textDirection;
return Text('$locale $direction');
},
),
);
}
)
);
// Initial WidgetTester locale is `en_US`.
await tester.pumpAndSettle();
expect(find.text('en_US TextDirection.ltr'), findsOneWidget);
await tester.binding.setLocale('en', 'CA');
await tester.pumpAndSettle();
expect(find.text('en_CA TextDirection.ltr'), findsOneWidget);
await tester.binding.setLocale('ar', 'EG');
await tester.pumpAndSettle();
expect(find.text('ar_EG TextDirection.rtl'), findsOneWidget);
await tester.binding.setLocale('da', 'DA');
await tester.pumpAndSettle();
expect(find.text('da_DA TextDirection.ltr'), findsOneWidget);
});
testWidgets('Localizations.override widget overrides parent\'s DefaultWidgetLocalizations', (WidgetTester tester) async {
await tester.pumpWidget(
buildFrame(
// Accept whatever locale we're given
localeResolutionCallback: (Locale locale, Iterable<Locale> supportedLocales) => locale,
buildContent: (BuildContext context) {
return Localizations.override(
context: context,
delegates: const <OnlyRTLDefaultWidgetsLocalizationsDelegate>[
// Override: no matter what the locale, textDirection is always RTL.
OnlyRTLDefaultWidgetsLocalizationsDelegate(),
],
child: Builder(
builder: (BuildContext context) {
final Locale locale = Localizations.localeOf(context);
final TextDirection direction = WidgetsLocalizations.of(context).textDirection;
return Text('$locale $direction');
},
),
);
}
)
);
// Initial WidgetTester locale is `en_US`.
await tester.pumpAndSettle();
expect(find.text('en_US TextDirection.rtl'), findsOneWidget);
await tester.binding.setLocale('en', 'CA');
await tester.pumpAndSettle();
expect(find.text('en_CA TextDirection.rtl'), findsOneWidget);
await tester.binding.setLocale('ar', 'EG');
await tester.pumpAndSettle();
expect(find.text('ar_EG TextDirection.rtl'), findsOneWidget);
await tester.binding.setLocale('da', 'DA');
await tester.pumpAndSettle();
expect(find.text('da_DA TextDirection.rtl'), findsOneWidget);
});
testWidgets('WidgetsApp overrides DefaultWidgetLocalizations', (WidgetTester tester) async {
await tester.pumpWidget(
buildFrame(
// Accept whatever locale we're given
localeResolutionCallback: (Locale locale, Iterable<Locale> supportedLocales) => locale,
delegates: <OnlyRTLDefaultWidgetsLocalizationsDelegate>[
const OnlyRTLDefaultWidgetsLocalizationsDelegate(),
],
buildContent: (BuildContext context) {
final Locale locale = Localizations.localeOf(context);
final TextDirection direction = WidgetsLocalizations.of(context).textDirection;
return Text('$locale $direction');
}
)
);
// Initial WidgetTester locale is `en_US`.
await tester.pumpAndSettle();
expect(find.text('en_US TextDirection.rtl'), findsOneWidget);
await tester.binding.setLocale('en', 'CA');
await tester.pumpAndSettle();
expect(find.text('en_CA TextDirection.rtl'), findsOneWidget);
await tester.binding.setLocale('ar', 'EG');
await tester.pumpAndSettle();
expect(find.text('ar_EG TextDirection.rtl'), findsOneWidget);
await tester.binding.setLocale('da', 'DA');
await tester.pumpAndSettle();
expect(find.text('da_DA TextDirection.rtl'), findsOneWidget);
});
// We provide <Locale>[Locale('en', 'US'), Locale('zh', 'CN')] as ui.window.locales
// for flutter tester so that the behavior of tests match that of production
// environments. Here, we test the default locales.
testWidgets('WidgetsApp DefaultWidgetLocalizations', (WidgetTester tester) async {
await tester.pumpAndSettle();
await tester.pumpWidget(
buildFrame(
// Accept whatever locale we're given
localeResolutionCallback: (Locale locale, Iterable<Locale> supportedLocales) => locale,
delegates: <OnlyRTLDefaultWidgetsLocalizationsDelegate>[
const OnlyRTLDefaultWidgetsLocalizationsDelegate(),
],
buildContent: (BuildContext context) {
final Locale locale1 = ui.window.locales.first;
final Locale locale2 = ui.window.locales[1];
return Text('$locale1 $locale2');
}
)
);
// Initial WidgetTester default locales is `en_US` and `zh_CN`.
await tester.pumpAndSettle();
expect(find.text('en_US zh_CN'), findsOneWidget);
});
}