Files
photo-live/apps/mobile/test/app/locale_test.dart
T
2026-09-19 05:00:11 +08:00

173 lines
6.8 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/// App 内语言切换测试(P3 计划 Task 10)。
///
/// 覆盖:
/// - 启动:持久档位注入 Notifier → 首帧即对应语言(无闪变);
/// - 切换:setChoice → MaterialApp 即刻重建(登录页文案随切)+ 写持久层;
/// - 解析矩阵(system 档):系统 zh → zh、系统 en → en、系统其他 →
/// 回退模板语言 zh(l10n.yaml template=app_zh.arb);
/// - 显式选择优先于系统 locale;
/// - 持久值解析容错(null/非法值/存储异常 → system)。
library;
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:photo_live/app/app.dart';
import 'package:photo_live/app/locale_controller.dart';
import 'package:photo_live/core/api_client.dart';
import '../helpers/fakes.dart';
/// 写入即抛的存储替身(持久化失败容错断言用)。
class ThrowingWriteLocaleStore extends FakeLocaleStore {
@override
Future<void> write(String value) async => throw StateError('disk full');
}
void main() {
group('持久值解析(纯逻辑)', () {
test('fromPersisted:合法值/空值/非法值', () {
expect(AppLocaleChoice.fromPersisted('system'), AppLocaleChoice.system);
expect(AppLocaleChoice.fromPersisted('zh'), AppLocaleChoice.zh);
expect(AppLocaleChoice.fromPersisted('en'), AppLocaleChoice.en);
expect(AppLocaleChoice.fromPersisted(null), AppLocaleChoice.system);
expect(AppLocaleChoice.fromPersisted('fr'), AppLocaleChoice.system,
reason: '未来档位/手改损坏的旧值一律回退 system');
});
test('readPersisted:读取异常 → system(启动不因存储故障卡死)', () async {
final store = ThrowingWriteLocaleStore();
final choice = await LocaleSettings.readPersisted(store);
expect(choice, AppLocaleChoice.system);
});
test('setChoice 写失败不回滚本次会话状态', () async {
final container = ProviderContainer(
overrides: [
localeSettingsProvider.overrideWith(
() => LocaleSettings(store: ThrowingWriteLocaleStore()),
),
],
);
addTearDown(container.dispose);
await container
.read(localeSettingsProvider.notifier)
.setChoice(AppLocaleChoice.en);
expect(container.read(localeSettingsProvider), AppLocaleChoice.en);
});
});
group('PhotoLiveApp 语言接线(widget)', () {
late FakeSessionStore session;
late ApiClient client;
setUp(() {
// 无会话:App 停在登录页(文案断言载体);客户端拒绝一切请求
//(语言测试不触网,仅为满足 Provider 依赖图)。
session = FakeSessionStore();
client = ApiClient(
sessionStore: session,
dio: Dio(BaseOptions(baseUrl: 'http://test.local'))
..httpClientAdapter =
FakeAdapter((o) => throw StateError('不应发起请求: ${o.path}')),
);
});
Future<ProviderContainer> pumpApp(
WidgetTester tester, {
AppLocaleChoice initial = AppLocaleChoice.system,
FakeLocaleStore? localeStore,
}) async {
final store = localeStore ?? FakeLocaleStore();
await tester.pumpWidget(
ProviderScope(
overrides: [
...pageTestOverrides(session: session, client: client),
localeSettingsProvider.overrideWith(
() => LocaleSettings(initial: initial, store: store),
),
],
child: const PhotoLiveApp(),
),
);
await tester.pumpAndSettle();
return ProviderScope.containerOf(
tester.element(find.byType(PhotoLiveApp)),
);
}
testWidgets('持久档位 zh → 首帧中文文案', (tester) async {
final store = FakeLocaleStore()..value = 'zh';
await pumpApp(tester,
initial: AppLocaleChoice.zh, localeStore: store);
expect(find.text('登录'), findsOneWidget);
expect(find.text('Sign in'), findsNothing);
});
testWidgets('切换 en → 登录页即刻英文 + 持久化 en', (tester) async {
final store = FakeLocaleStore();
final container = await pumpApp(tester,
initial: AppLocaleChoice.zh, localeStore: store);
expect(find.text('登录'), findsOneWidget);
await container
.read(localeSettingsProvider.notifier)
.setChoice(AppLocaleChoice.en);
await tester.pumpAndSettle();
expect(find.text('Sign in'), findsOneWidget,
reason: 'MaterialApp 已随 provider 重建');
expect(find.text('登录'), findsNothing);
expect(store.writes, ['en'], reason: '写入持久层');
});
testWidgets('切换回 system → 交还系统 locale', (tester) async {
tester.platformDispatcher.localesTestValue = const [Locale('en')];
addTearDown(tester.platformDispatcher.clearLocalesTestValue);
final container = await pumpApp(tester, initial: AppLocaleChoice.zh);
expect(find.text('登录'), findsOneWidget);
await container
.read(localeSettingsProvider.notifier)
.setChoice(AppLocaleChoice.system);
await tester.pumpAndSettle();
expect(find.text('Sign in'), findsOneWidget, reason: '系统 en 接管');
});
testWidgets('解析矩阵:system + 系统 zh → zh', (tester) async {
tester.platformDispatcher.localesTestValue = const [Locale('zh')];
addTearDown(tester.platformDispatcher.clearLocalesTestValue);
await pumpApp(tester);
expect(find.text('登录'), findsOneWidget);
});
testWidgets('解析矩阵:system + 系统 en → en', (tester) async {
tester.platformDispatcher.localesTestValue = const [Locale('en')];
addTearDown(tester.platformDispatcher.clearLocalesTestValue);
await pumpApp(tester);
expect(find.text('Sign in'), findsOneWidget);
});
testWidgets('解析矩阵:system + 系统其他(fr)→ 回退模板语言 zh',
(tester) async {
// Localizations 解析用 platformDispatcher.locales(首选语言列表),
// 单值 localeTestValue 不影响它——须整体注入列表。
tester.platformDispatcher.localesTestValue = const [Locale('fr')];
addTearDown(tester.platformDispatcher.clearLocalesTestValue);
await pumpApp(tester);
expect(find.text('登录'), findsOneWidget,
reason: '不依赖 supportedLocales 排序(默认解析会回退 en)');
});
testWidgets('显式选择优先于系统 locale(选 en + 系统 zh → en)',
(tester) async {
tester.platformDispatcher.localesTestValue = const [Locale('zh')];
addTearDown(tester.platformDispatcher.clearLocalesTestValue);
await pumpApp(tester, initial: AppLocaleChoice.en);
expect(find.text('Sign in'), findsOneWidget);
});
});
}