chore: 更新国际化翻译及 UI 组件优化

This commit is contained in:
zl-q
2026-03-30 09:07:30 +08:00
parent 0f3175e303
commit 60318b7aaa
28 changed files with 1360 additions and 66 deletions
@@ -0,0 +1,217 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:go_router/go_router.dart';
import 'package:social_app/l10n/app_localizations.dart';
import 'package:social_app/shared/widgets/ui_schema/ui_schema_renderer.dart';
Map<String, dynamic> _badgeSchema({
required String label,
required String status,
}) {
return {
'root': {
'type': 'stack',
'direction': 'vertical',
'children': [
{'type': 'badge', 'label': label, 'status': status},
],
},
};
}
Widget _buildRendererHost(Map<String, dynamic> schema, Locale locale) {
return MaterialApp(
locale: locale,
supportedLocales: AppLocalizations.supportedLocales,
localizationsDelegates: AppLocalizations.localizationsDelegates,
home: Scaffold(
body: Builder(
builder: (context) {
final colorScheme = Theme.of(context).colorScheme;
return UiSchemaRenderer(context, colorScheme).renderSchema(schema);
},
),
),
);
}
Map<String, dynamic> _buttonSchema(Map<String, dynamic> action) {
return {
'root': {
'type': 'stack',
'direction': 'vertical',
'children': [
{
'type': 'button',
'label': '查看详情',
'style': 'primary',
'action': action,
},
],
},
};
}
Widget _buildRouterHost(Map<String, dynamic> schema, Locale locale) {
final router = GoRouter(
initialLocation: '/',
routes: [
GoRoute(
path: '/',
builder: (context, state) {
final colorScheme = Theme.of(context).colorScheme;
return Scaffold(
body: UiSchemaRenderer(context, colorScheme).renderSchema(schema),
);
},
),
GoRoute(
path: '/todo/123',
builder: (context, state) => const Scaffold(body: Text('todo-detail')),
),
],
);
return MaterialApp.router(
locale: locale,
supportedLocales: AppLocalizations.supportedLocales,
localizationsDelegates: AppLocalizations.localizationsDelegates,
routerConfig: router,
);
}
void main() {
testWidgets('localizes stable status token labels', (tester) async {
final schema = _badgeSchema(label: 'ui.status.success', status: 'success');
await tester.pumpWidget(_buildRendererHost(schema, const Locale('zh')));
expect(find.text('已完成'), findsOneWidget);
});
testWidgets('localizes legacy uppercase status labels', (tester) async {
final schema = _badgeSchema(label: 'SUCCESS', status: 'success');
await tester.pumpWidget(_buildRendererHost(schema, const Locale('zh')));
expect(find.text('已完成'), findsOneWidget);
});
testWidgets('keeps unknown status token label unchanged', (tester) async {
final schema = _badgeSchema(
label: 'ui.status.processing',
status: 'success',
);
await tester.pumpWidget(_buildRendererHost(schema, const Locale('en')));
expect(find.text('ui.status.processing'), findsOneWidget);
});
testWidgets('keeps custom badge label unchanged', (tester) async {
final schema = _badgeSchema(label: '创建完成', status: 'success');
await tester.pumpWidget(_buildRendererHost(schema, const Locale('zh')));
expect(find.text('创建完成'), findsOneWidget);
});
testWidgets('navigates when navigation action path is valid', (tester) async {
final schema = _buttonSchema({
'type': 'navigation',
'path': '/todo/123',
'params': {'from': 'assistant', 'focus': true},
});
await tester.pumpWidget(_buildRouterHost(schema, const Locale('zh')));
await tester.tap(find.text('查看详情'));
await tester.pumpAndSettle();
expect(find.text('todo-detail'), findsOneWidget);
});
testWidgets('shows l10n error when navigation path is invalid', (
tester,
) async {
final schema = _buttonSchema({
'type': 'navigation',
'path': 'https://example.com',
});
await tester.pumpWidget(_buildRouterHost(schema, const Locale('zh')));
await tester.tap(find.text('查看详情'));
await tester.pump();
expect(find.text('导航路径无效'), findsOneWidget);
await tester.pump(const Duration(seconds: 3));
await tester.pumpAndSettle();
});
testWidgets('shows l10n error when url action is invalid', (tester) async {
final schema = _buttonSchema({'type': 'url', 'url': 'javascript:alert(1)'});
await tester.pumpWidget(_buildRouterHost(schema, const Locale('zh')));
await tester.tap(find.text('查看详情'));
await tester.pump();
expect(find.text('链接无效'), findsOneWidget);
await tester.pump(const Duration(seconds: 3));
await tester.pumpAndSettle();
});
testWidgets('rejects encoded navigation path payload', (tester) async {
final schema = _buttonSchema({
'type': 'navigation',
'path': '/calendar/events/%2F%2Fevil.example.com',
});
await tester.pumpWidget(_buildRouterHost(schema, const Locale('zh')));
await tester.tap(find.text('查看详情'));
await tester.pump();
expect(find.text('导航路径无效'), findsOneWidget);
await tester.pump(const Duration(seconds: 3));
await tester.pumpAndSettle();
});
testWidgets('rejects dot-segment traversal navigation path', (tester) async {
final schema = _buttonSchema({
'type': 'navigation',
'path': '/todo/../../settings',
});
await tester.pumpWidget(_buildRouterHost(schema, const Locale('zh')));
await tester.tap(find.text('查看详情'));
await tester.pump();
expect(find.text('导航路径无效'), findsOneWidget);
await tester.pump(const Duration(seconds: 3));
await tester.pumpAndSettle();
});
testWidgets('rejects private-network URL action', (tester) async {
final schema = _buttonSchema({
'type': 'url',
'url': 'http://127.0.0.1:8080/admin',
});
await tester.pumpWidget(_buildRouterHost(schema, const Locale('zh')));
await tester.tap(find.text('查看详情'));
await tester.pump();
expect(find.text('链接无效'), findsOneWidget);
await tester.pump(const Duration(seconds: 3));
await tester.pumpAndSettle();
});
testWidgets('rejects ipv6 loopback URL action', (tester) async {
final schema = _buttonSchema({
'type': 'url',
'url': 'http://[0:0:0:0:0:0:0:1]:8080/admin',
});
await tester.pumpWidget(_buildRouterHost(schema, const Locale('zh')));
await tester.tap(find.text('查看详情'));
await tester.pump();
expect(find.text('链接无效'), findsOneWidget);
await tester.pump(const Duration(seconds: 3));
await tester.pumpAndSettle();
});
}