test(04-01): add failing tests for NotificationSettingsNotifier and NotificationService
- Tests for default state (enabled=false, time=07:00) - Tests for setEnabled persistence to SharedPreferences - Tests for setTime persistence to SharedPreferences - Tests for loading persisted values via _load() - Tests for NotificationService singleton pattern - Tests for nextInstanceOf timezone logic (future/past time)
This commit is contained in:
97
test/core/notifications/notification_service_test.dart
Normal file
97
test/core/notifications/notification_service_test.dart
Normal file
@@ -0,0 +1,97 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:timezone/data/latest_all.dart' as tz;
|
||||
import 'package:timezone/timezone.dart' as tz;
|
||||
|
||||
import 'package:household_keeper/core/notifications/notification_service.dart';
|
||||
|
||||
void main() {
|
||||
setUpAll(() {
|
||||
tz.initializeTimeZones();
|
||||
tz.setLocalLocation(tz.getLocation('Europe/Berlin'));
|
||||
});
|
||||
|
||||
group('NotificationService', () {
|
||||
test('singleton pattern: two instances are identical', () {
|
||||
final a = NotificationService();
|
||||
final b = NotificationService();
|
||||
expect(identical(a, b), isTrue);
|
||||
});
|
||||
|
||||
group('nextInstanceOf', () {
|
||||
test('returns today when time is in the future', () {
|
||||
final service = NotificationService();
|
||||
final now = tz.TZDateTime.now(tz.local);
|
||||
|
||||
// Use a time 2 hours in the future, wrapping midnight if needed
|
||||
final futureHour = (now.hour + 2) % 24;
|
||||
final futureMinute = now.minute;
|
||||
final futureTime = TimeOfDay(hour: futureHour, minute: futureMinute);
|
||||
|
||||
// Only test this case if futureHour > now.hour (no midnight wrap)
|
||||
if (futureHour > now.hour) {
|
||||
final result = service.nextInstanceOf(futureTime);
|
||||
expect(result.day, now.day);
|
||||
expect(result.hour, futureHour);
|
||||
expect(result.minute, futureMinute);
|
||||
}
|
||||
});
|
||||
|
||||
test('returns tomorrow when time has passed', () {
|
||||
final service = NotificationService();
|
||||
final now = tz.TZDateTime.now(tz.local);
|
||||
|
||||
// Use a time in the past (1 hour ago), wrapping to previous day if needed
|
||||
final pastHour = now.hour - 1;
|
||||
|
||||
// Only test if we are not at the beginning of the day
|
||||
if (pastHour >= 0) {
|
||||
final pastTime = TimeOfDay(hour: pastHour, minute: 0);
|
||||
final result = service.nextInstanceOf(pastTime);
|
||||
expect(result.day, now.day + 1);
|
||||
expect(result.hour, pastHour);
|
||||
expect(result.minute, 0);
|
||||
}
|
||||
});
|
||||
|
||||
test('scheduled time is in the future (always)', () {
|
||||
final service = NotificationService();
|
||||
final now = tz.TZDateTime.now(tz.local);
|
||||
|
||||
// Test with midnight (00:00) — always results in a time in future (tomorrow)
|
||||
final result = service.nextInstanceOf(const TimeOfDay(hour: 0, minute: 0));
|
||||
|
||||
expect(result.isAfter(now) || result.isAtSameMomentAs(now), isTrue);
|
||||
});
|
||||
|
||||
test('nextInstanceOf respects hours and minutes exactly', () {
|
||||
final service = NotificationService();
|
||||
final now = tz.TZDateTime.now(tz.local);
|
||||
|
||||
// Use a far future time today: 23:59, which should be today if we are before it
|
||||
const targetTime = TimeOfDay(hour: 23, minute: 59);
|
||||
final todayTarget = tz.TZDateTime(
|
||||
tz.local,
|
||||
now.year,
|
||||
now.month,
|
||||
now.day,
|
||||
23,
|
||||
59,
|
||||
);
|
||||
|
||||
final result = service.nextInstanceOf(targetTime);
|
||||
|
||||
if (now.isBefore(todayTarget)) {
|
||||
expect(result.hour, 23);
|
||||
expect(result.minute, 59);
|
||||
expect(result.day, now.day);
|
||||
} else {
|
||||
// After 23:59, scheduled for tomorrow
|
||||
expect(result.hour, 23);
|
||||
expect(result.minute, 59);
|
||||
expect(result.day, now.day + 1);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
128
test/core/notifications/notification_settings_notifier_test.dart
Normal file
128
test/core/notifications/notification_settings_notifier_test.dart
Normal file
@@ -0,0 +1,128 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'package:household_keeper/core/notifications/notification_settings_notifier.dart';
|
||||
|
||||
void main() {
|
||||
setUp(() {
|
||||
SharedPreferences.setMockInitialValues({});
|
||||
});
|
||||
|
||||
group('NotificationSettingsNotifier', () {
|
||||
test('build() returns default state (enabled=false, time=07:00)', () async {
|
||||
final container = ProviderContainer();
|
||||
addTearDown(container.dispose);
|
||||
|
||||
final state = container.read(notificationSettingsNotifierProvider);
|
||||
|
||||
expect(state.enabled, isFalse);
|
||||
expect(state.time, const TimeOfDay(hour: 7, minute: 0));
|
||||
});
|
||||
|
||||
test('setEnabled(true) updates state.enabled to true', () async {
|
||||
final container = ProviderContainer();
|
||||
addTearDown(container.dispose);
|
||||
|
||||
await container
|
||||
.read(notificationSettingsNotifierProvider.notifier)
|
||||
.setEnabled(true);
|
||||
|
||||
expect(
|
||||
container.read(notificationSettingsNotifierProvider).enabled,
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
|
||||
test('setEnabled(true) persists to SharedPreferences', () async {
|
||||
final container = ProviderContainer();
|
||||
addTearDown(container.dispose);
|
||||
|
||||
await container
|
||||
.read(notificationSettingsNotifierProvider.notifier)
|
||||
.setEnabled(true);
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
expect(prefs.getBool('notifications_enabled'), isTrue);
|
||||
});
|
||||
|
||||
test('setEnabled(false) updates state.enabled to false', () async {
|
||||
final container = ProviderContainer();
|
||||
addTearDown(container.dispose);
|
||||
|
||||
// First enable, then disable
|
||||
await container
|
||||
.read(notificationSettingsNotifierProvider.notifier)
|
||||
.setEnabled(true);
|
||||
await container
|
||||
.read(notificationSettingsNotifierProvider.notifier)
|
||||
.setEnabled(false);
|
||||
|
||||
expect(
|
||||
container.read(notificationSettingsNotifierProvider).enabled,
|
||||
isFalse,
|
||||
);
|
||||
});
|
||||
|
||||
test('setEnabled(false) persists to SharedPreferences', () async {
|
||||
final container = ProviderContainer();
|
||||
addTearDown(container.dispose);
|
||||
|
||||
await container
|
||||
.read(notificationSettingsNotifierProvider.notifier)
|
||||
.setEnabled(true);
|
||||
await container
|
||||
.read(notificationSettingsNotifierProvider.notifier)
|
||||
.setEnabled(false);
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
expect(prefs.getBool('notifications_enabled'), isFalse);
|
||||
});
|
||||
|
||||
test(
|
||||
'setTime(09:30) updates state.time and persists hour+minute',
|
||||
() async {
|
||||
final container = ProviderContainer();
|
||||
addTearDown(container.dispose);
|
||||
|
||||
await container
|
||||
.read(notificationSettingsNotifierProvider.notifier)
|
||||
.setTime(const TimeOfDay(hour: 9, minute: 30));
|
||||
|
||||
expect(
|
||||
container.read(notificationSettingsNotifierProvider).time,
|
||||
const TimeOfDay(hour: 9, minute: 30),
|
||||
);
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
expect(prefs.getInt('notifications_hour'), 9);
|
||||
expect(prefs.getInt('notifications_minute'), 30);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'After _load() with existing prefs, state reflects persisted values',
|
||||
() async {
|
||||
SharedPreferences.setMockInitialValues({
|
||||
'notifications_enabled': true,
|
||||
'notifications_hour': 8,
|
||||
'notifications_minute': 15,
|
||||
});
|
||||
|
||||
final container = ProviderContainer();
|
||||
addTearDown(container.dispose);
|
||||
|
||||
// Trigger build
|
||||
container.read(notificationSettingsNotifierProvider);
|
||||
|
||||
// Wait for async _load() to complete
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
|
||||
final state = container.read(notificationSettingsNotifierProvider);
|
||||
expect(state.enabled, isTrue);
|
||||
expect(state.time, const TimeOfDay(hour: 8, minute: 15));
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user