diff --git a/lib/features/reservation/presentation/widgets/reservation_section_widget.dart b/lib/features/reservation/presentation/widgets/reservation_section_widget.dart index d8ec4e1..d247407 100644 --- a/lib/features/reservation/presentation/widgets/reservation_section_widget.dart +++ b/lib/features/reservation/presentation/widgets/reservation_section_widget.dart @@ -56,10 +56,7 @@ class _ReservationSectionWidgetState } List _floorsFrom(List machines, int? userFloor) { - final floors = machines - .map((m) => m.floorNumber) - .whereType() - .toSet(); + final floors = machines.map((m) => m.floorNumber).whereType().toSet(); if (userFloor != null) { floors.add(userFloor); } @@ -220,8 +217,9 @@ class _ReservationSectionWidgetState data: (data) { final typedMachines = _machinesForType(data.machines); final floors = _floorsFrom(typedMachines, userFloor); + // 해당 타입 기기가 없고 방 번호로 층도 못 읽으면 floors 가 비어 있다. final currentFloor = - _selectedFloor ?? (floors.isNotEmpty ? floors.first : 0); + _selectedFloor ?? userFloor ?? floors.firstOrNull ?? 0; final items = _buildItems( typedMachines, activeReservations, diff --git a/lib/main.dart b/lib/main.dart index 1a060b8..c8d71c4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,6 +4,7 @@ import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_crashlytics/firebase_crashlytics.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:washer/core/env/app_environment.dart'; @@ -18,6 +19,9 @@ void main() async { // 서로 독립적인 초기화는 병렬로 처리한다. await Future.wait([ AppEnvironment.initialize(), + SystemChrome.setPreferredOrientations([ + DeviceOrientation.portraitUp, // 세로 방향으로 고정 + ]), Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform), ]); if (kDebugMode) { diff --git a/test/features/reservation/reservation_section_floor_test.dart b/test/features/reservation/reservation_section_floor_test.dart new file mode 100644 index 0000000..1f9373a --- /dev/null +++ b/test/features/reservation/reservation_section_floor_test.dart @@ -0,0 +1,133 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:washer/core/enums/laundry_machine_type.dart'; +import 'package:washer/features/reservation/data/models/local/active_reservation_model.dart'; +import 'package:washer/features/reservation/data/models/local/laundry_machine_model.dart'; +import 'package:washer/features/reservation/presentation/providers/reservation_status_provider.dart'; +import 'package:washer/features/reservation/presentation/widgets/reservation_section_widget.dart'; +import 'package:washer/features/user/data/models/my_user_model.dart'; +import 'package:washer/features/user/presentation/providers/my_user_provider.dart'; + +class _FakeMachineStatusNotifier extends MachineStatusNotifier { + _FakeMachineStatusNotifier(this.machines); + + final List machines; + + @override + Future build() async => + MachineStatusResponse(machines: machines, totalCount: machines.length); +} + +class _FakeActiveReservationNotifier extends ActiveReservationNotifier { + @override + Future> build() async => const []; + + @override + Future ensureLoaded() async {} +} + +class _FakeMyUserNotifier extends MyUserNotifier { + _FakeMyUserNotifier(this.roomNumber); + + final String? roomNumber; + + @override + Future build() async => MyUserModel( + id: 1, + name: '테스트', + roomNumber: roomNumber, + ); +} + +MachineModel _washer(String name) => MachineModel( + machineId: name.hashCode, + name: name, + type: 'WASHER', + status: 'AVAILABLE', + availability: 'AVAILABLE', +); + +Future _pumpSection( + WidgetTester tester, { + required List machines, + required String? roomNumber, +}) async { + await tester.pumpWidget( + ProviderScope( + overrides: [ + machineStatusProvider.overrideWith( + () => _FakeMachineStatusNotifier(machines), + ), + activeReservationProvider.overrideWith( + _FakeActiveReservationNotifier.new, + ), + myUserProvider.overrideWith(() => _FakeMyUserNotifier(roomNumber)), + ], + child: ScreenUtilInit( + designSize: const Size(412, 917), + builder: (_, __) => const MaterialApp( + home: Scaffold( + body: ReservationSectionWidget( + laundryMachineType: LaundryMachineType.washer, + ), + ), + ), + ), + ), + ); + await tester.pumpAndSettle(); +} + +void main() { + group('ReservationSectionWidget currentFloor', () { + testWidgets('기기 목록이 비어 있고 사용자 층도 없으면 빈 안내를 보여준다', (tester) async { + await _pumpSection(tester, machines: const [], roomNumber: null); + + expect(tester.takeException(), isNull); + expect(find.text('표시할 기기가 없습니다.'), findsOneWidget); + }); + + testWidgets('해당 타입 기기가 하나도 없어도(건조기만 존재) 크래시하지 않는다', (tester) async { + await _pumpSection( + tester, + machines: [ + const MachineModel( + machineId: 1, + name: 'Dryer-3F-L1', + type: 'DRYER', + status: 'AVAILABLE', + availability: 'AVAILABLE', + ), + ], + roomNumber: null, + ); + + expect(tester.takeException(), isNull); + expect(find.text('표시할 기기가 없습니다.'), findsOneWidget); + }); + + testWidgets('사용자 층을 못 읽어도 기기가 있으면 가장 낮은 층을 보여준다', (tester) async { + await _pumpSection( + tester, + machines: [_washer('Washer-4F-L1'), _washer('Washer-3F-L1')], + roomNumber: null, + ); + + expect(tester.takeException(), isNull); + expect(find.text('Washer-3F-L1'), findsOneWidget); + }); + + testWidgets('사용자 층이 있으면 그 층이 기본 선택된다', (tester) async { + await _pumpSection( + tester, + machines: [_washer('Washer-3F-L1'), _washer('Washer-4F-L1')], + roomNumber: '412', + ); + + expect(find.text('Washer-4F-L1'), findsOneWidget); + expect(find.text('Washer-3F-L1'), findsNothing); + }); + }); +}