Files
FocusGram-Android/test/services/session_manager_test.dart
Ujwal 7bb472d212 What's new
- Reordered Settings Page.
- Added "Click to Unblur" for posts.
- Added Persistent Notification
- Improved Grayscale Scheduling.
and more.
2026-03-04 10:48:14 +05:45

294 lines
10 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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')}';
}