app_flow_test.dart 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. import 'package:flutter/material.dart';
  2. import 'package:flutter_test/flutter_test.dart';
  3. import 'package:flutter_riverpod/flutter_riverpod.dart';
  4. import 'package:go_router/go_router.dart';
  5. import 'package:integration_test/integration_test.dart';
  6. import 'package:asr_client/models/websocket_message.dart';
  7. import 'package:asr_client/pages/history/history_page.dart';
  8. import 'package:asr_client/pages/profile/profile_page.dart';
  9. import 'package:asr_client/pages/recording/recording_page.dart';
  10. import 'package:asr_client/pages/recording/widgets/recording_header.dart';
  11. import 'package:asr_client/pages/shell/app_shell.dart';
  12. import 'package:asr_client/pages/tasks/tasks_page.dart';
  13. import 'package:asr_client/providers/audio_providers.dart';
  14. import 'package:asr_client/providers/settings_providers.dart';
  15. import 'package:asr_client/providers/websocket_providers.dart';
  16. import 'package:asr_client/theme/app_theme.dart';
  17. import '../test/fakes/fake_services.dart';
  18. class _IntegrationTestApp extends StatelessWidget {
  19. const _IntegrationTestApp({required this.overrides, required this.router});
  20. final List<Override> overrides;
  21. final GoRouter router;
  22. @override
  23. Widget build(BuildContext context) {
  24. return ProviderScope(
  25. overrides: overrides,
  26. child: MaterialApp.router(
  27. title: 'AI试验员',
  28. theme: AppTheme.light,
  29. routerConfig: router,
  30. debugShowCheckedModeBanner: false,
  31. ),
  32. );
  33. }
  34. }
  35. GoRouter _buildRouter() {
  36. final rootNavKey = GlobalKey<NavigatorState>();
  37. return GoRouter(
  38. navigatorKey: rootNavKey,
  39. initialLocation: '/tasks',
  40. routes: [
  41. StatefulShellRoute.indexedStack(
  42. builder: (context, state, navigationShell) {
  43. return AppShell(navigationShell: navigationShell);
  44. },
  45. branches: [
  46. StatefulShellBranch(
  47. routes: [
  48. GoRoute(
  49. path: '/tasks',
  50. builder: (context, state) => const TasksPage(),
  51. ),
  52. ],
  53. ),
  54. StatefulShellBranch(
  55. routes: [
  56. GoRoute(
  57. path: '/records',
  58. builder: (context, state) => const HistoryPage(),
  59. ),
  60. ],
  61. ),
  62. StatefulShellBranch(
  63. routes: [
  64. GoRoute(
  65. path: '/reports',
  66. builder: (context, state) => const HistoryPage(),
  67. ),
  68. ],
  69. ),
  70. StatefulShellBranch(
  71. routes: [
  72. GoRoute(
  73. path: '/profile',
  74. builder: (context, state) => const ProfilePage(),
  75. ),
  76. ],
  77. ),
  78. ],
  79. ),
  80. GoRoute(
  81. path: '/recording',
  82. parentNavigatorKey: rootNavKey,
  83. builder: (context, state) => const RecordingPage(),
  84. ),
  85. ],
  86. );
  87. }
  88. Future<void> _pumpUntil(
  89. WidgetTester tester,
  90. Finder finder, {
  91. int maxIterations = 50,
  92. }) async {
  93. for (var i = 0; i < maxIterations; i++) {
  94. await tester.pump(const Duration(milliseconds: 100));
  95. if (finder.evaluate().isNotEmpty) return;
  96. }
  97. }
  98. void main() {
  99. IntegrationTestWidgetsFlutterBinding.ensureInitialized();
  100. group('App flow', () {
  101. late FakeWebSocketService fakeWebSocket;
  102. late FakeAudioCaptureService fakeAudio;
  103. setUp(() {
  104. fakeWebSocket = FakeWebSocketService();
  105. fakeAudio = FakeAudioCaptureService();
  106. });
  107. tearDown(() {
  108. fakeWebSocket.dispose();
  109. fakeAudio.dispose();
  110. });
  111. testWidgets('navigates tabs and records a transcript', (tester) async {
  112. await tester.pumpWidget(
  113. _IntegrationTestApp(
  114. overrides: [
  115. settingsServiceProvider.overrideWithValue(FakeSettingsService()),
  116. webSocketServiceProvider.overrideWithValue(fakeWebSocket),
  117. audioCaptureServiceProvider.overrideWithValue(fakeAudio),
  118. ],
  119. router: _buildRouter(),
  120. ),
  121. );
  122. await _pumpUntil(tester, find.text('今日任务'));
  123. expect(find.text('今日任务'), findsOneWidget);
  124. await tester.tap(find.text('记录'));
  125. await _pumpUntil(tester, find.text('检测记录'));
  126. expect(find.text('检测记录'), findsOneWidget);
  127. await tester.tap(find.text('报告'));
  128. await _pumpUntil(tester, find.text('检测记录'));
  129. expect(find.text('检测记录'), findsOneWidget);
  130. await tester.tap(find.text('我的'));
  131. await _pumpUntil(tester, find.text('采集设置'));
  132. expect(find.text('采集设置'), findsOneWidget);
  133. await tester.tap(find.text('任务'));
  134. await _pumpUntil(tester, find.text('今日任务'));
  135. await tester.tap(find.byIcon(Icons.add));
  136. await _pumpUntil(tester, find.text('实时语音识别'));
  137. await _pumpUntil(tester, find.text('⏸ 暂停'));
  138. final headerFinder = find.byType(RecordingHeader);
  139. expect(headerFinder, findsOneWidget);
  140. final header = tester.widget<RecordingHeader>(headerFinder);
  141. expect(header.taskName, '混凝土坍落度试验');
  142. expect(
  143. find.descendant(of: headerFinder, matching: find.text('混凝土坍落度试验')),
  144. findsOneWidget,
  145. );
  146. expect(find.text('⏸ 暂停'), findsOneWidget);
  147. fakeWebSocket.emit(const TranscriptMessage(text: '坍落度 180'));
  148. await _pumpUntil(tester, find.textContaining('坍落度 180'));
  149. expect(find.textContaining('坍落度 180'), findsOneWidget);
  150. expect(find.text('AI'), findsWidgets);
  151. await tester.tap(find.text('⏸ 暂停'));
  152. await _pumpUntil(tester, find.text('▶ 继续'));
  153. expect(find.text('▶ 继续'), findsOneWidget);
  154. await tester.tap(find.text('▶ 继续'));
  155. await _pumpUntil(tester, find.text('⏸ 暂停'));
  156. expect(find.text('⏸ 暂停'), findsOneWidget);
  157. });
  158. });
  159. }