import 'package:ducafe_ui_core/ducafe_ui_core.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:forui/forui.dart'; import 'package:pj_supermarket_cloud/common/index.dart'; import 'package:pj_supermarket_cloud/pages/terminal/menu_query/controller.dart'; import 'package:pj_supermarket_cloud/pages/terminal/menu_query/widgets/query_method_panel.dart'; void main() { testWidgets('手动模式保留开始按钮,自动模式改为饭卡等待状态且仍可切换方式', (tester) async { await tester.binding.setSurfaceSize(const Size(1920, 1080)); addTearDown(() => tester.binding.setSurfaceSize(null)); var startCalls = 0; ReservationRecognitionMethod? selectedMethod; final mealSlotController = FRadioSelectGroupController(value: '1'); addTearDown(mealSlotController.dispose); await tester.pumpWidget( _testApp( QueryMethodPanel( methods: ReservationRecognitionMethod.values, selected: ReservationRecognitionMethod.card, status: ReservationMenuQueryStatus.idle, mealSlotController: mealSlotController, autoRecognitionEnabled: false, continuousRecognitionEnabled: false, quickVerificationEnabled: false, allowManualMealPeriodChange: true, onSelect: (method) => selectedMethod = method, onStart: () => startCalls++, ), ), ); expect(find.text('开始识别'), findsOneWidget); await tester.tap(find.text('开始识别')); await tester.pump(const Duration(milliseconds: 250)); expect(startCalls, 1); await tester.pumpWidget( _testApp( QueryMethodPanel( methods: ReservationRecognitionMethod.values, selected: ReservationRecognitionMethod.card, status: ReservationMenuQueryStatus.recognizing, mealSlotController: mealSlotController, autoRecognitionEnabled: true, continuousRecognitionEnabled: true, quickVerificationEnabled: false, allowManualMealPeriodChange: true, onSelect: (method) => selectedMethod = method, onStart: () => startCalls++, ), ), ); expect(find.text('开始识别'), findsNothing); expect(find.text('持续等待饭卡'), findsOneWidget); await tester.tap(find.text('人脸查询')); await tester.pump(const Duration(milliseconds: 250)); expect(selectedMethod, ReservationRecognitionMethod.face); expect(startCalls, 1); expect(tester.takeException(), isNull); }); testWidgets('餐段只由快速核销开关控制,不受持续自动识别影响', (tester) async { await tester.binding.setSurfaceSize(const Size(1920, 1080)); addTearDown(() => tester.binding.setSurfaceSize(null)); final mealSlotController = FRadioSelectGroupController(value: '2'); addTearDown(mealSlotController.dispose); await tester.pumpWidget( _testApp( QueryMethodPanel( methods: ReservationRecognitionMethod.values, selected: ReservationRecognitionMethod.card, status: ReservationMenuQueryStatus.recognizing, mealSlotController: mealSlotController, autoRecognitionEnabled: true, continuousRecognitionEnabled: true, quickVerificationEnabled: false, allowManualMealPeriodChange: true, onSelect: (_) {}, onStart: () {}, ), ), ); expect( find.byKey(const ValueKey('reservation-meal-slot-trigger')), findsNothing, ); expect(find.text('餐段'), findsNothing); await tester.pumpWidget( _testApp( QueryMethodPanel( methods: ReservationRecognitionMethod.values, selected: ReservationRecognitionMethod.card, status: ReservationMenuQueryStatus.recognizing, mealSlotController: mealSlotController, autoRecognitionEnabled: true, continuousRecognitionEnabled: false, quickVerificationEnabled: true, allowManualMealPeriodChange: true, onSelect: (_) {}, onStart: () {}, ), ), ); expect( find.byKey(const ValueKey('reservation-meal-slot-trigger')), findsOneWidget, ); expect(find.text('餐段'), findsOneWidget); expect(find.text('中餐'), findsOneWidget); expect(tester.takeException(), isNull); }); testWidgets('餐段默认仅显示当前值,点开后可选择且查询中禁用', (tester) async { await tester.binding.setSurfaceSize(const Size(1920, 1080)); addTearDown(() => tester.binding.setSurfaceSize(null)); var selectedMealSlot = ReservationMealSlot.morning; var status = ReservationMenuQueryStatus.idle; final mealSlotController = FRadioSelectGroupController(value: '1'); mealSlotController.addValueListener((selectedCodes) { final selectedCode = selectedCodes.firstOrNull; selectedMealSlot = ReservationMealSlot.querySelectableValues.firstWhere( (mealSlot) => mealSlot.code == selectedCode, ); }); addTearDown(mealSlotController.dispose); await tester.pumpWidget( _testApp( QueryMethodPanel( methods: ReservationRecognitionMethod.values, selected: ReservationRecognitionMethod.card, status: status, mealSlotController: mealSlotController, autoRecognitionEnabled: false, continuousRecognitionEnabled: true, quickVerificationEnabled: true, allowManualMealPeriodChange: true, onSelect: (_) {}, onStart: () {}, ), ), ); expect(find.byType(FSelectMenuTile), findsOneWidget); expect(find.text('餐段'), findsOneWidget); expect(find.text('早餐'), findsOneWidget); expect(find.text('中餐'), findsNothing); expect(find.text('晚餐'), findsNothing); final trigger = find.byKey( const ValueKey('reservation-meal-slot-trigger'), ); expect(tester.getSize(trigger).height, greaterThanOrEqualTo(48)); expect(tester.widget>(trigger).enabled, isTrue); await tester.tap(trigger); await tester.pumpAndSettle(); final noonOption = find.byKey( const ValueKey('meal-slot-option-2'), ); expect(tester.getSize(noonOption).height, greaterThanOrEqualTo(48)); await tester.tap(noonOption); await tester.pumpAndSettle(); expect(selectedMealSlot, ReservationMealSlot.noon); expect(find.text('早餐'), findsNothing); expect(find.text('中餐'), findsOneWidget); status = ReservationMenuQueryStatus.recognizing; await tester.pumpWidget( _testApp( QueryMethodPanel( methods: ReservationRecognitionMethod.values, selected: ReservationRecognitionMethod.card, status: status, mealSlotController: mealSlotController, autoRecognitionEnabled: false, continuousRecognitionEnabled: true, quickVerificationEnabled: true, allowManualMealPeriodChange: true, onSelect: (_) {}, onStart: () {}, ), ), ); await tester.tap(trigger); await tester.pumpAndSettle(); final nightOption = find.byKey( const ValueKey('meal-slot-option-3'), ); await tester.tap(nightOption); await tester.pumpAndSettle(); expect(selectedMealSlot, ReservationMealSlot.night); status = ReservationMenuQueryStatus.querying; await tester.pumpWidget( _testApp( QueryMethodPanel( methods: ReservationRecognitionMethod.values, selected: ReservationRecognitionMethod.card, status: status, mealSlotController: mealSlotController, autoRecognitionEnabled: false, continuousRecognitionEnabled: true, quickVerificationEnabled: true, allowManualMealPeriodChange: true, onSelect: (_) {}, onStart: () {}, ), ), ); final disabledTrigger = find.byKey( const ValueKey('reservation-meal-slot-trigger'), ); expect( tester.widget>(disabledTrigger).enabled, isFalse, ); await tester.tap(disabledTrigger); await tester.pump(); expect(selectedMealSlot, ReservationMealSlot.night); expect( find.byKey(const ValueKey('meal-slot-option-1')), findsNothing, ); expect(tester.takeException(), isNull); }); testWidgets('禁止主动修改餐段时选择器禁用且无法打开餐段菜单', (tester) async { await tester.binding.setSurfaceSize(const Size(1920, 1080)); addTearDown(() => tester.binding.setSurfaceSize(null)); final mealSlotController = FRadioSelectGroupController(value: '1'); addTearDown(mealSlotController.dispose); await tester.pumpWidget( _testApp( QueryMethodPanel( methods: ReservationRecognitionMethod.values, selected: ReservationRecognitionMethod.card, status: ReservationMenuQueryStatus.idle, mealSlotController: mealSlotController, autoRecognitionEnabled: false, continuousRecognitionEnabled: true, quickVerificationEnabled: true, allowManualMealPeriodChange: false, onSelect: (_) {}, onStart: () {}, ), ), ); final trigger = find.byKey( const ValueKey('reservation-meal-slot-trigger'), ); expect(find.text('早餐'), findsOneWidget); expect(tester.widget>(trigger).enabled, isFalse); await tester.tap(trigger); await tester.pumpAndSettle(); expect(find.byKey(const ValueKey('meal-slot-option-2')), findsNothing); expect(mealSlotController.value, {'1'}); expect(tester.takeException(), isNull); }); } /// 构建与终端设计基准一致的主题和尺寸环境,只验证本组件的可见状态与点击链路。 Widget _testApp(Widget child) { return ScreenUtilInit( designSize: const Size(1920, 1080), builder: (_, __) => MaterialApp( home: FTheme( data: FThemes.zinc.light, child: Scaffold(body: child), ), ), ); }