607278928b
Pure advanceStreak (1-day grace none, milestone flags at 3/7/14/30), persisted in the save blob; StreakNotifier advances on every finished attempt; home screen flame chip and milestone snackbar. Fix flutter create's camelCased iOS bundle id and underscored Android application id to the agreed lowercase form before any store registration. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
75 lines
2.7 KiB
Dart
75 lines
2.7 KiB
Dart
import 'package:block_seasons/data/save_repository.dart';
|
|
import 'package:block_seasons/data/streak.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
void main() {
|
|
TestWidgetsFlutterBinding.ensureInitialized();
|
|
|
|
group('advanceStreak', () {
|
|
test('first ever play starts the streak at 1', () {
|
|
final next = advanceStreak(StreakState.initial, DateTime(2026, 6, 11));
|
|
expect(next.current, 1);
|
|
expect(next.best, 1);
|
|
expect(next.lastYmd, '2026-06-11');
|
|
});
|
|
|
|
test('same-day repeat play changes nothing', () {
|
|
final day1 = advanceStreak(StreakState.initial, DateTime(2026, 6, 11));
|
|
final again = advanceStreak(day1, DateTime(2026, 6, 11, 23, 59));
|
|
expect(again.current, 1);
|
|
expect(again.best, 1);
|
|
});
|
|
|
|
test('consecutive days grow the streak and best follows', () {
|
|
var s = advanceStreak(StreakState.initial, DateTime(2026, 6, 11));
|
|
s = advanceStreak(s, DateTime(2026, 6, 12));
|
|
s = advanceStreak(s, DateTime(2026, 6, 13));
|
|
expect(s.current, 3);
|
|
expect(s.best, 3);
|
|
});
|
|
|
|
test('missing a day resets current but keeps best', () {
|
|
var s = advanceStreak(StreakState.initial, DateTime(2026, 6, 11));
|
|
s = advanceStreak(s, DateTime(2026, 6, 12));
|
|
s = advanceStreak(s, DateTime(2026, 6, 15));
|
|
expect(s.current, 1);
|
|
expect(s.best, 2);
|
|
});
|
|
|
|
test('month boundaries count as consecutive days', () {
|
|
var s = advanceStreak(StreakState.initial, DateTime(2026, 6, 30));
|
|
s = advanceStreak(s, DateTime(2026, 7, 1));
|
|
expect(s.current, 2);
|
|
});
|
|
|
|
test('milestone crossings are flagged once', () {
|
|
var s = StreakState(current: 2, best: 6, lastYmd: '2026-06-10');
|
|
final next = advanceStreak(s, DateTime(2026, 6, 11));
|
|
expect(next.current, 3);
|
|
expect(next.hitMilestone, 3);
|
|
|
|
s = StreakState(current: 3, best: 6, lastYmd: '2026-06-11');
|
|
final after = advanceStreak(s, DateTime(2026, 6, 12));
|
|
expect(after.hitMilestone, isNull, reason: '4 is not a milestone');
|
|
});
|
|
});
|
|
|
|
group('SaveRepository streak persistence', () {
|
|
test('round-trips streak state', () async {
|
|
SharedPreferences.setMockInitialValues({});
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final repo = SaveRepository(prefs);
|
|
expect(repo.streak.current, 0);
|
|
|
|
await repo.saveStreak(
|
|
const StreakState(current: 5, best: 9, lastYmd: '2026-06-11'),
|
|
);
|
|
final reloaded = SaveRepository(prefs);
|
|
expect(reloaded.streak.current, 5);
|
|
expect(reloaded.streak.best, 9);
|
|
expect(reloaded.streak.lastYmd, '2026-06-11');
|
|
});
|
|
});
|
|
}
|