mirror of
https://github.com/Ujwal223/FocusGram.git
synced 2026-04-01 17:10:23 +02:00
- Reordered Settings Page. - Added "Click to Unblur" for posts. - Added Persistent Notification - Improved Grayscale Scheduling. and more.
294 lines
10 KiB
Dart
294 lines
10 KiB
Dart
// 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:00–17: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')}';
|
||
}
|