Files
biz_xfj_cloud/test/common/nested_navigator_ownership_test.dart
T
2026-08-06 20:55:09 +08:00

160 lines
5.4 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pj_supermarket_cloud/common/index.dart';
import 'package:pj_supermarket_cloud/pages/terminal/setting/routes/navigation.dart';
void main() {
testWidgets('终端新旧页面重叠时使用不同 Key,并将菜单跳转交给新实例', (tester) async {
final oldOwner = Object();
final newOwner = Object();
final oldRoutes = <String>[];
final newRoutes = <String>[];
final oldKey = TerminalNavigationTool.attachNavigator(oldOwner);
final newKey = TerminalNavigationTool.attachNavigator(newOwner);
addTearDown(() => TerminalNavigationTool.detachNavigator(newOwner));
expect(identical(oldKey, newKey), isFalse);
await tester.pumpWidget(
_overlappingNavigators(
oldKey: oldKey,
newKey: newKey,
oldRoutes: oldRoutes,
newRoutes: newRoutes,
onOldRouteChanged: (route) =>
TerminalNavigationTool.handleRouteChanged(oldOwner, route),
onNewRouteChanged: (route) =>
TerminalNavigationTool.handleRouteChanged(newOwner, route),
),
);
await tester.pumpAndSettle();
expect(tester.takeException(), isNull);
TerminalNavigationTool.detachNavigator(oldOwner);
TerminalNavigationTool.replaceWorkspace(RouteNames.cashier);
await tester.pumpAndSettle();
expect(find.text('new:${RouteNames.cashier}'), findsOneWidget);
expect(find.text('old:/'), findsOneWidget);
final generatedRouteCount = newRoutes.length;
TerminalNavigationTool.replaceWorkspace(RouteNames.cashier);
await tester.pumpAndSettle();
expect(newRoutes.length, generatedRouteCount);
});
testWidgets('设置新旧内容区重叠时旧实例解绑不会清除新实例导航', (tester) async {
final oldOwner = Object();
final newOwner = Object();
final oldRoutes = <String>[];
final newRoutes = <String>[];
final oldKey = SettingNavigation.attachNavigator(oldOwner);
final newKey = SettingNavigation.attachNavigator(newOwner);
addTearDown(() => SettingNavigation.detachNavigator(newOwner));
expect(identical(oldKey, newKey), isFalse);
await tester.pumpWidget(
_overlappingNavigators(
oldKey: oldKey,
newKey: newKey,
oldRoutes: oldRoutes,
newRoutes: newRoutes,
onOldRouteChanged: (route) =>
SettingNavigation.handleRouteChanged(oldOwner, route),
onNewRouteChanged: (route) =>
SettingNavigation.handleRouteChanged(newOwner, route),
),
);
await tester.pumpAndSettle();
expect(tester.takeException(), isNull);
SettingNavigation.detachNavigator(oldOwner);
SettingNavigation.openMenu(SettingMenuType.syncSetting);
await tester.pumpAndSettle();
expect(find.text('new:${RouteNames.settingSync}'), findsOneWidget);
expect(find.text('old:/'), findsOneWidget);
final generatedRouteCount = newRoutes.length;
SettingNavigation.openMenu(SettingMenuType.syncSetting);
await tester.pumpAndSettle();
expect(newRoutes.length, generatedRouteCount);
});
}
/// 构建同时挂载的新旧嵌套 Navigator,复现根路由替换期间的短暂重叠状态。
Widget _overlappingNavigators({
required GlobalKey<NavigatorState> oldKey,
required GlobalKey<NavigatorState> newKey,
required List<String> oldRoutes,
required List<String> newRoutes,
required ValueChanged<String?> onOldRouteChanged,
required ValueChanged<String?> onNewRouteChanged,
}) {
return MaterialApp(
home: Row(
children: [
Expanded(
child: Navigator(
key: oldKey,
observers: [_TestRouteObserver(onOldRouteChanged)],
onGenerateRoute: (settings) =>
_testRoute('old', settings, oldRoutes),
),
),
Expanded(
child: Navigator(
key: newKey,
observers: [_TestRouteObserver(onNewRouteChanged)],
onGenerateRoute: (settings) =>
_testRoute('new', settings, newRoutes),
),
),
],
),
);
}
/// 创建无动画测试路由,并记录实际生成的路由名称供重复跳转断言使用。
Route<void> _testRoute(
String navigatorLabel,
RouteSettings settings,
List<String> generatedRoutes,
) {
final routeName = settings.name ?? '/';
generatedRoutes.add(routeName);
return PageRouteBuilder<void>(
settings: settings,
transitionDuration: Duration.zero,
reverseTransitionDuration: Duration.zero,
pageBuilder: (_, __, ___) => Center(
child: Text('$navigatorLabel:$routeName'),
),
);
}
/// 将测试 Navigator 的真实路由变化回传给对应导航工具。
class _TestRouteObserver extends NavigatorObserver {
/// 创建绑定到指定新旧页面实例的测试路由观察器。
_TestRouteObserver(this.onChanged);
/// 路由变化时接收当前页面名称的回调。
final ValueChanged<String?> onChanged;
/// 首个页面和后续页面推入时同步实际路由。
@override
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
onChanged(route.settings.name);
}
/// 页面替换时同步新路由。
@override
void didReplace({Route<dynamic>? newRoute, Route<dynamic>? oldRoute}) {
onChanged(newRoute?.settings.name);
}
/// 页面返回时同步恢复后的路由。
@override
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
onChanged(previousRoute?.settings.name);
}
}