What's new

- Reordered Settings Page.
- Added "Click to Unblur" for posts.
- Added Persistent Notification
- Improved Grayscale Scheduling.
and more.
This commit is contained in:
Ujwal
2026-03-04 10:48:14 +05:45
commit 7bb472d212
92 changed files with 14740 additions and 0 deletions
+293
View File
@@ -0,0 +1,293 @@
// test/services/session_manager_test.dart
//
// Tests for SessionManager — reel session logic, daily quotas, cooldowns,
// app session tracking, and scheduled blocking.
//
// SessionManager uses SharedPreferences and Timer internally.
// We use SharedPreferences.setMockInitialValues({}) for isolation.
// Timer-based tests use FakeAsync where needed.
//
// Run with: flutter test test/services/session_manager_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:focusgram/services/session_manager.dart';
// Helper: initialise a SessionManager with a clean SharedPreferences slate.
Future<SessionManager> _freshManager() async {
SharedPreferences.setMockInitialValues({});
final sm = SessionManager();
await sm.init();
return sm;
}
void main() {
// ── Initial state ────────────────────────────────────────────────────────
group('SessionManager — initial state', () {
test('no reel session active on first init', () async {
final sm = await _freshManager();
expect(sm.isSessionActive, isFalse);
});
test('remainingSessionSeconds is 0 when no session', () async {
final sm = await _freshManager();
expect(sm.remainingSessionSeconds, 0);
});
test('dailyUsedSeconds starts at 0', () async {
final sm = await _freshManager();
expect(sm.dailyUsedSeconds, 0);
});
test('dailyRemainingSeconds matches default limit', () async {
final sm = await _freshManager();
expect(sm.dailyRemainingSeconds, sm.dailyLimitSeconds);
});
test('isDailyLimitExhausted is false initially', () async {
final sm = await _freshManager();
expect(sm.isDailyLimitExhausted, isFalse);
});
test('isCooldownActive is false initially', () async {
final sm = await _freshManager();
expect(sm.isCooldownActive, isFalse);
});
test('cooldownRemainingSeconds is 0 when no cooldown', () async {
final sm = await _freshManager();
expect(sm.cooldownRemainingSeconds, 0);
});
test('daily open count is incremented on init', () async {
final sm = await _freshManager();
// init() calls _incrementOpenCount once
expect(sm.dailyOpenCount, 1);
});
});
// ── Reel session — startSession ──────────────────────────────────────────
group('SessionManager.startSession', () {
test('returns true and activates session within daily quota', () async {
final sm = await _freshManager();
final ok = sm.startSession(5);
expect(ok, isTrue);
expect(sm.isSessionActive, isTrue);
});
test('session expires after requested minutes (approx)', () async {
final sm = await _freshManager();
sm.startSession(5);
// Remaining should be <= 5 min = 300 s
expect(sm.remainingSessionSeconds, lessThanOrEqualTo(300));
expect(sm.remainingSessionSeconds, greaterThan(290));
});
test('returns false when daily limit is exhausted', () async {
SharedPreferences.setMockInitialValues({
'sessn_daily_limit_sec': 300, // 5 min daily limit
'sessn_daily_used_sec': 300, // already used all of it
'sessn_daily_date': _today(),
});
final sm = SessionManager();
await sm.init();
final ok = sm.startSession(5);
expect(ok, isFalse);
expect(sm.isSessionActive, isFalse);
});
test('returns false during cooldown', () async {
// Last session ended 5 minutes ago; cooldown is 15 min
final lastEnd = DateTime.now().subtract(const Duration(minutes: 5));
SharedPreferences.setMockInitialValues({
'sessn_last_end_ts': lastEnd.millisecondsSinceEpoch,
'sessn_cooldown_sec': 900, // 15 min cooldown
'sessn_daily_date': _today(),
});
final sm = SessionManager();
await sm.init();
expect(sm.isCooldownActive, isTrue);
final ok = sm.startSession(5);
expect(ok, isFalse);
});
test('notifies listeners when session starts', () async {
final sm = await _freshManager();
bool notified = false;
sm.addListener(() => notified = true);
sm.startSession(5);
expect(notified, isTrue);
});
});
// ── Reel session — endSession ────────────────────────────────────────────
group('SessionManager.endSession', () {
test('deactivates an active session', () async {
final sm = await _freshManager();
sm.startSession(5);
expect(sm.isSessionActive, isTrue);
sm.endSession();
expect(sm.isSessionActive, isFalse);
});
test('remainingSessionSeconds becomes 0 after end', () async {
final sm = await _freshManager();
sm.startSession(5);
sm.endSession();
expect(sm.remainingSessionSeconds, 0);
});
test('cooldown becomes active after ending a session', () async {
final sm = await _freshManager();
sm.startSession(5);
sm.endSession();
// Only active if cooldownSeconds > 0
if (sm.cooldownSeconds > 0) {
expect(sm.isCooldownActive, isTrue);
}
});
test('notifies listeners on end', () async {
final sm = await _freshManager();
sm.startSession(5);
bool notified = false;
sm.addListener(() => notified = true);
sm.endSession();
expect(notified, isTrue);
});
});
// ── Daily quota ──────────────────────────────────────────────────────────
group('SessionManager — daily quota', () {
test('dailyRemainingSeconds is capped at 0 when exhausted', () async {
SharedPreferences.setMockInitialValues({
'sessn_daily_limit_sec': 300,
'sessn_daily_used_sec': 400, // over limit
'sessn_daily_date': _today(),
});
final sm = SessionManager();
await sm.init();
expect(sm.dailyRemainingSeconds, 0);
expect(sm.isDailyLimitExhausted, isTrue);
});
test('dailyRemainingSeconds decrements correctly', () async {
SharedPreferences.setMockInitialValues({
'sessn_daily_limit_sec': 600,
'sessn_daily_used_sec': 100,
'sessn_daily_date': _today(),
});
final sm = SessionManager();
await sm.init();
expect(sm.dailyRemainingSeconds, 500);
});
});
// ── App session ──────────────────────────────────────────────────────────
group('SessionManager — app session', () {
test('app session is active when end is in the future', () async {
final future = DateTime.now().add(const Duration(minutes: 30));
SharedPreferences.setMockInitialValues({
'app_sess_end_ts': future.millisecondsSinceEpoch,
'sessn_daily_date': _today(),
});
final sm = SessionManager();
await sm.init();
expect(sm.isAppSessionActive, isTrue);
});
test('app session is NOT active when end is in the past', () async {
final past = DateTime.now().subtract(const Duration(minutes: 10));
SharedPreferences.setMockInitialValues({
'app_sess_end_ts': past.millisecondsSinceEpoch,
'sessn_daily_date': _today(),
});
final sm = SessionManager();
await sm.init();
expect(sm.isAppSessionActive, isFalse);
});
test('appSessionRemainingSeconds is > 0 for a future session', () async {
final future = DateTime.now().add(const Duration(minutes: 30));
SharedPreferences.setMockInitialValues({
'app_sess_end_ts': future.millisecondsSinceEpoch,
'sessn_daily_date': _today(),
});
final sm = SessionManager();
await sm.init();
expect(sm.appSessionRemainingSeconds, greaterThan(0));
});
test('canExtendAppSession is true by default', () async {
final sm = await _freshManager();
expect(sm.canExtendAppSession, isTrue);
});
test('canExtendAppSession is false when extension used', () async {
SharedPreferences.setMockInitialValues({
'app_sess_ext_used': true,
'sessn_daily_date': _today(),
});
final sm = SessionManager();
await sm.init();
expect(sm.canExtendAppSession, isFalse);
});
});
// ── Scheduled blocking ───────────────────────────────────────────────────
group('SessionManager — scheduled blocking', () {
test('isScheduledBlockActive is false when schedule disabled', () async {
final sm = await _freshManager();
expect(sm.scheduleEnabled, isFalse);
expect(sm.isScheduledBlockActive, isFalse);
});
test('simple daytime range (9:0017:00) blocks at noon', () async {
final sm = await _freshManager();
// We can't control DateTime.now() but we CAN test the logic
// by verifying the method doesn't throw and returns a bool.
expect(sm.isScheduledBlockActive, isA<bool>());
});
});
// ── setAppForeground ─────────────────────────────────────────────────────
group('SessionManager.setAppForeground', () {
test('does nothing when value is unchanged', () async {
final sm = await _freshManager();
bool notified = false;
sm.addListener(() => notified = true);
sm.setAppForeground(true); // already true by default (in foreground)
expect(notified, isFalse);
});
test('notifies when transitioning to background', () async {
final sm = await _freshManager();
bool notified = false;
sm.addListener(() => notified = true);
sm.setAppForeground(false);
expect(notified, isTrue);
});
test('notifies when returning to foreground', () async {
final sm = await _freshManager();
sm.setAppForeground(false); // go to background first
bool notified = false;
sm.addListener(() => notified = true);
sm.setAppForeground(true);
expect(notified, isTrue);
});
});
}
/// Returns today's date formatted as 'yyyy-MM-dd' (same format as SessionManager).
String _today() {
final now = DateTime.now();
return '${now.year}-${now.month.toString().padLeft(2, '0')}-${now.day.toString().padLeft(2, '0')}';
}