8f3966d7b5
Co-Authored-By: Claude Code <noreply@anthropic.com>
173 lines
6.8 KiB
Dart
173 lines
6.8 KiB
Dart
/// 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);
|
||
});
|
||
});
|
||
}
|