chore: 更新国际化翻译及 UI 组件优化
This commit is contained in:
@@ -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();
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user