From a9a34fbbc50357bf6cb78e39dd37f1bee5f8baa8 Mon Sep 17 00:00:00 2001 From: JulianAlcala Date: Sun, 26 Apr 2026 08:25:18 +0200 Subject: [PATCH] test(location): add LocationMapController unit tests (22 cases) --- .../location_map_controller_test.dart | 311 ++++++++++++++++++ 1 file changed, 311 insertions(+) create mode 100644 modules/legacy/modules/location/test/features/location/location_map_controller_test.dart diff --git a/modules/legacy/modules/location/test/features/location/location_map_controller_test.dart b/modules/legacy/modules/location/test/features/location/location_map_controller_test.dart new file mode 100644 index 00000000..763ddd0e --- /dev/null +++ b/modules/legacy/modules/location/test/features/location/location_map_controller_test.dart @@ -0,0 +1,311 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:latlong2/latlong.dart'; +import 'package:location/src/core/domain/entities/geofence_entity.dart'; +import 'package:location/src/core/domain/entities/frequent_place_entity.dart'; +import 'package:location/src/features/location/presentation/providers/location_map_controller.dart'; +import 'package:location/src/features/location/presentation/providers/location_map_state.dart'; +import 'package:sf_shared/testing.dart'; +import 'package:sf_tracking/sf_tracking.dart'; + +const _geofence = GeofenceEntity( + id: 'g1', + name: 'Home', + latitude: 40.0, + longitude: -3.0, + radius: 200, + isActive: true, + createdAt: 0, +); + +const _frequentPlace = FrequentPlaceEntity( + id: 'f1', + name: 'School', + lat: 41.0, + lng: -3.5, + createdAt: 0, +); + +ProviderContainer buildContainer() { + return makeContainer( + overrides: [ + sfTrackingProvider.overrideWithValue( + SfTrackingRepository(clients: const []), + ), + ], + ); +} + +LocationMapState readState(ProviderContainer container) => + container.read(locationMapControllerProvider); + +LocationMapController readNotifier(ProviderContainer container) => + container.read(locationMapControllerProvider.notifier); + +void main() { + group('history navigation', () { + test('startHistoryNavigation sets index to 0', () { + final container = buildContainer(); + addTearDown(container.dispose); + + readNotifier(container).startHistoryNavigation(); + + expect(readState(container).historyNavigationIndex, 0); + expect(readState(container).historyPlaying, false); + }); + + test('stopHistoryNavigation resets index to -1', () { + final container = buildContainer(); + addTearDown(container.dispose); + + readNotifier(container).startHistoryNavigation(); + readNotifier(container).stopHistoryNavigation(); + + expect(readState(container).historyNavigationIndex, -1); + expect(readState(container).historyPlaying, false); + }); + + test('nextHistoryPosition increments index', () { + final container = buildContainer(); + addTearDown(container.dispose); + + readNotifier(container).startHistoryNavigation(); + readNotifier(container).nextHistoryPosition(10); + + expect(readState(container).historyNavigationIndex, 1); + }); + + test('nextHistoryPosition at end sets historyPlaying false', () { + final container = buildContainer(); + addTearDown(container.dispose); + + readNotifier(container).startHistoryNavigation(); + readNotifier(container).nextHistoryPosition(2); + readNotifier(container).nextHistoryPosition(2); + + expect(readState(container).historyNavigationIndex, 1); + expect(readState(container).historyPlaying, false); + }); + + test('previousHistoryPosition decrements index', () { + final container = buildContainer(); + addTearDown(container.dispose); + + readNotifier(container).startHistoryNavigation(); + readNotifier(container).nextHistoryPosition(10); + readNotifier(container).nextHistoryPosition(10); + readNotifier(container).previousHistoryPosition(); + + expect(readState(container).historyNavigationIndex, 1); + }); + + test('previousHistoryPosition at 0 stays at 0', () { + final container = buildContainer(); + addTearDown(container.dispose); + + readNotifier(container).startHistoryNavigation(); + readNotifier(container).previousHistoryPosition(); + + expect(readState(container).historyNavigationIndex, 0); + }); + }); + + group('history playback', () { + test('toggleHistoryPlayback activates playback', () { + final container = buildContainer(); + addTearDown(container.dispose); + + readNotifier(container).startHistoryNavigation(); + readNotifier(container).toggleHistoryPlayback(10); + + expect(readState(container).historyPlaying, true); + }); + + test('toggleHistoryPlayback deactivates playback', () { + final container = buildContainer(); + addTearDown(container.dispose); + + readNotifier(container).startHistoryNavigation(); + readNotifier(container).toggleHistoryPlayback(10); + readNotifier(container).toggleHistoryPlayback(10); + + expect(readState(container).historyPlaying, false); + }); + + test('toggleHistoryPlayback at end restarts from 0', () { + final container = buildContainer(); + addTearDown(container.dispose); + + readNotifier(container).startHistoryNavigation(); + readNotifier(container).nextHistoryPosition(3); + readNotifier(container).nextHistoryPosition(3); + readNotifier(container).toggleHistoryPlayback(3); + + expect(readState(container).historyNavigationIndex, 0); + expect(readState(container).historyPlaying, true); + }); + + test('stopPlaybackTimer cancels timer', () { + final container = buildContainer(); + addTearDown(container.dispose); + + readNotifier(container).startHistoryNavigation(); + readNotifier(container).toggleHistoryPlayback(10); + readNotifier(container).stopPlaybackTimer(); + + expect(readState(container).historyPlaying, true); + }); + + test('startPlaybackTimer sets up timer and stopPlaybackTimer clears it', () { + final container = buildContainer(); + addTearDown(container.dispose); + + readNotifier(container).startHistoryNavigation(); + readNotifier(container).toggleHistoryPlayback(10); + + expect(readState(container).historyPlaying, true); + + readNotifier(container).stopPlaybackTimer(); + readNotifier(container).stopPlaybackTimer(); + }); + }); + + group('reveal animation', () { + test('startReveal sets animating true and count 1', () { + final container = buildContainer(); + addTearDown(container.dispose); + + readNotifier(container).startReveal(); + + expect(readState(container).isRevealAnimating, true); + expect(readState(container).historyRevealCount, 1); + }); + + test('updateRevealCount updates count', () { + final container = buildContainer(); + addTearDown(container.dispose); + + readNotifier(container).startReveal(); + readNotifier(container).updateRevealCount(5); + + expect(readState(container).historyRevealCount, 5); + }); + + test('finishReveal clears animation state', () { + final container = buildContainer(); + addTearDown(container.dispose); + + readNotifier(container).startReveal(); + readNotifier(container).finishReveal(); + + expect(readState(container).isRevealAnimating, false); + expect(readState(container).historyRevealCount, isNull); + }); + }); + + group('geofence editing', () { + test('startEditingGeofence enters placement mode', () { + final container = buildContainer(); + addTearDown(container.dispose); + + readNotifier(container).startEditingGeofence(_geofence); + + final state = readState(container); + expect(state.placingMode, PlacingMode.geofence); + expect(state.editingGeofence, _geofence); + expect(state.previewPoint, LatLng(40.0, -3.0)); + expect(state.previewRadius, 200); + expect(state.selectedGeofence, isNull); + }); + + test('confirmGeofencePlacement preserves editing radius', () { + final container = buildContainer(); + addTearDown(container.dispose); + + readNotifier(container).startEditingGeofence(_geofence); + readNotifier(container).confirmGeofencePlacement(LatLng(41.0, -2.0)); + + final state = readState(container); + expect(state.placingMode, PlacingMode.none); + expect(state.adjustingRadius, true); + expect(state.previewPoint, LatLng(41.0, -2.0)); + expect(state.previewRadius, 200); + }); + + test('confirmGeofencePlacement defaults radius 200 for new geofence', () { + final container = buildContainer(); + addTearDown(container.dispose); + + readNotifier(container).startPlacing(PlacingMode.geofence); + readNotifier(container).confirmGeofencePlacement(LatLng(41.0, -2.0)); + + expect(readState(container).previewRadius, 200); + }); + + test('cancelPlacing clears editing state', () { + final container = buildContainer(); + addTearDown(container.dispose); + + readNotifier(container).startEditingGeofence(_geofence); + readNotifier(container).cancelPlacing(); + + final state = readState(container); + expect(state.placingMode, PlacingMode.none); + expect(state.editingGeofence, isNull); + expect(state.editingFrequentPlace, isNull); + expect(state.previewPoint, isNull); + }); + }); + + group('frequent place editing', () { + test('startEditingFrequentPlace enters placement mode', () { + final container = buildContainer(); + addTearDown(container.dispose); + + readNotifier(container).startEditingFrequentPlace(_frequentPlace); + + final state = readState(container); + expect(state.placingMode, PlacingMode.frequentPlace); + expect(state.editingFrequentPlace, _frequentPlace); + expect(state.previewPoint, LatLng(41.0, -3.5)); + expect(state.selectedFrequentPlace, isNull); + }); + + test('confirmFrequentPlacePlacement clears editing state', () { + final container = buildContainer(); + addTearDown(container.dispose); + + readNotifier(container).startEditingFrequentPlace(_frequentPlace); + readNotifier(container).confirmFrequentPlacePlacement(); + + final state = readState(container); + expect(state.placingMode, PlacingMode.none); + expect(state.editingFrequentPlace, isNull); + expect(state.previewPoint, isNull); + }); + }); + + group('recenter', () { + test('scheduleRecenter does nothing when not following', () { + final container = buildContainer(); + addTearDown(container.dispose); + + expect(readState(container).isFollowing, false); + + var called = false; + readNotifier(container).scheduleRecenter(() => called = true); + + expect(called, false); + }); + + test('cancelRecenter does not throw', () { + final container = buildContainer(); + addTearDown(container.dispose); + + readNotifier(container).toggleFollowing(); + readNotifier(container).scheduleRecenter(() {}); + readNotifier(container).cancelRecenter(); + readNotifier(container).cancelRecenter(); + }); + }); +}