import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:forui/forui.dart'; import 'package:pj_supermarket_cloud/common/models/page/reservation_menu_page_models.dart'; import 'package:pj_supermarket_cloud/common/widgets/app/reservation_schedule_card.dart'; import 'package:pj_supermarket_cloud/sub_screen/common/models/reservation_query_display_models.dart'; import 'package:pj_supermarket_cloud/sub_screen/common/utils/reservation_query_coordinator.dart'; import 'package:pj_supermarket_cloud/sub_screen/page/reservation_query_display.dart'; void main() { testWidgets('午餐预约卡只补齐基础三餐,不生成夜间餐占位', (tester) async { ReservationMenuMealData? verifiedMeal; const day = ReservationMenuDayData( date: '2026-07-24', meals: [ ReservationMenuMealData( hourType: 'NOONING', title: '午餐', items: ['香菇滑鸡'], orderIds: [1001], ), ], ); await tester.pumpWidget( MaterialApp( home: FTheme( data: FThemes.zinc.light, child: Center( child: SizedBox( width: 420, height: 420, child: ReservationScheduleCard( day: day, verificationEnabled: true, onVerify: (meal) => verifiedMeal = meal, ), ), ), ), ), ); for (final title in const ['早餐', '午餐', '晚餐']) { expect(find.text(title), findsOneWidget); } expect(find.text('夜间餐'), findsNothing); expect(find.text('未预定'), findsNWidgets(2)); expect(find.text('核销'), findsOneWidget); final verificationVisual = tester.getSize( find.byKey(const ValueKey('reservation-verification-visual-2')), ); final verificationTarget = tester.getSize( find.byKey(const ValueKey('reservation-verification-target-2')), ); expect(verificationVisual.height, 36); expect(verificationTarget.height, 48); expect(verificationTarget.width, greaterThan(verificationVisual.width)); await tester.tap(find.text('核销')); expect(verifiedMeal?.title, '午餐'); }); testWidgets('后端返回夜间餐时正常追加展示', (tester) async { const day = ReservationMenuDayData( date: '2026-07-24', meals: [ ReservationMenuMealData( hourType: 'LATE_NIGHT', title: '夜间餐', items: ['牛肉面'], ), ], ); await tester.pumpWidget( MaterialApp( home: FTheme( data: FThemes.zinc.light, child: const Center( child: SizedBox( width: 420, height: 420, child: ReservationScheduleCard(day: day), ), ), ), ), ); expect(find.text('夜间餐'), findsOneWidget); expect(find.text('牛肉面'), findsOneWidget); final lateNightText = tester.renderObject( find.byKey(const ValueKey('reservation-meal-title-4')), ); expect(lateNightText.maxLines, 1); expect(lateNightText.didExceedMaxLines, isFalse); }); testWidgets('关闭核销配置时预约餐段仍显示查询详情入口', (tester) async { ReservationMenuMealData? selectedMeal; const day = ReservationMenuDayData( date: '2026-07-24', meals: [ ReservationMenuMealData( hourType: '2', title: '午餐', items: ['小青菜'], orderIds: [1001], ), ], ); await tester.pumpWidget( MaterialApp( home: FTheme( data: FThemes.zinc.light, child: Center( child: SizedBox( width: 420, height: 420, child: ReservationScheduleCard( day: day, onVerify: (meal) => selectedMeal = meal, ), ), ), ), ), ); expect(find.text('核销'), findsNothing); expect(find.text('查询详情'), findsOneWidget); await tester.tap(find.text('查询详情')); expect(selectedMeal?.title, '午餐'); }); testWidgets('窄卡片中的查询详情保持紧凑且菜名不再逐字换行', (tester) async { const day = ReservationMenuDayData( date: '2026-07-24', meals: [ ReservationMenuMealData( hourType: '2', title: '午餐', items: ['小青菜'], orderIds: [1001], ), ], ); await tester.pumpWidget( MaterialApp( home: FTheme( data: FThemes.zinc.light, child: const Center( child: SizedBox( width: 220, height: 420, child: ReservationScheduleCard( day: day, ), ), ), ), ), ); expect(tester.takeException(), isNull); final itemText = tester.renderObject( find.text('小青菜'), ); final detailVisual = tester.getSize( find.byKey(const ValueKey('reservation-verification-visual-2')), ); expect(itemText.maxLines, 1); expect(itemText.didExceedMaxLines, isFalse); expect(itemText.size.height, lessThan(24)); expect(detailVisual.width, lessThanOrEqualTo(70)); expect(detailVisual.height, 30); }); for (final size in [const Size(1920, 1080), const Size(1280, 720)]) { testWidgets('${size.width.toInt()}x${size.height.toInt()} 七种预约状态无布局异常', (tester) async { tester.view.physicalSize = size; tester.view.devicePixelRatio = 1; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); for (final scenario in ReservationQueryDisplayScenario.values) { final coordinator = ReservationQueryCoordinator( initialState: ReservationQueryDisplayState.demo( scenario, now: DateTime(2026, 7, 24, 8), ), ); await tester.pumpWidget( MaterialApp( debugShowCheckedModeBanner: false, home: FTheme( data: FThemes.zinc.light, child: ReservationQueryDisplayPage(coordinator: coordinator), ), ), ); await tester.pump(); expect( find.byKey(ValueKey('reservation-scenario-${scenario.name}')), findsOneWidget, ); expect(tester.takeException(), isNull); if (scenario == ReservationQueryDisplayScenario.success) { expect(find.byType(ReservationScheduleCard), findsNWidgets(10)); expect(find.text('张*明 · 三年级二班'), findsOneWidget); expect(find.text('张小明'), findsNothing); expect(find.byKey(const ValueKey('reservation-page-indicator')), findsOneWidget); } await tester.pumpWidget(const SizedBox.shrink()); coordinator.dispose(); } }); } testWidgets('持续人脸副屏同时保留实时预览、预约菜单和十秒倒计时', (tester) async { tester.view.physicalSize = const Size(1920, 1080); tester.view.devicePixelRatio = 1; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); final state = ReservationQueryDisplayState.demo( ReservationQueryDisplayScenario.success, now: DateTime(2026, 8, 7, 10), ).copyWith( remainingSeconds: 10, continuousFaceRecognition: true, ); final coordinator = ReservationQueryCoordinator(initialState: state); await tester.pumpWidget( MaterialApp( debugShowCheckedModeBanner: false, home: FTheme( data: FThemes.zinc.light, child: ReservationQueryDisplayPage(coordinator: coordinator), ), ), ); await tester.pump(); expect( find.byKey(const ValueKey('sub-reservation-live-face-preview')), findsOneWidget, ); expect(find.byType(ReservationScheduleCard), findsNWidgets(10)); expect(find.text('预约结果展示中'), findsOneWidget); expect(find.textContaining('10 秒后识别下一位'), findsOneWidget); expect(tester.takeException(), isNull); await tester.pumpWidget(const SizedBox.shrink()); coordinator.dispose(); }); }