Test Cases
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
import 'package:tasq/providers/realtime_controller.dart';
|
||||
import 'package:tasq/providers/stream_recovery.dart';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// RealtimeController only accesses `_client.auth.onAuthStateChange` during
|
||||
// `_init()`, which wraps the subscription in a try-catch. We use a minimal
|
||||
// SupabaseClient pointed at localhost — the auth stream subscription will
|
||||
// either return an empty stream or throw (caught internally), so no network
|
||||
// activity occurs during tests.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void main() {
|
||||
group('RealtimeController', () {
|
||||
late RealtimeController controller;
|
||||
|
||||
setUp(() {
|
||||
// SupabaseClient constructor does not connect eagerly; _init() catches
|
||||
// any exception thrown when accessing auth.onAuthStateChange.
|
||||
controller = RealtimeController(
|
||||
SupabaseClient('http://localhost', 'test-anon-key',
|
||||
authOptions: const AuthClientOptions(autoRefreshToken: false)),
|
||||
);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
controller.dispose();
|
||||
});
|
||||
|
||||
// ── Initial state ───────────────────────────────────────────────────
|
||||
|
||||
test('starts with no recovering channels', () {
|
||||
expect(controller.recoveringChannels, isEmpty);
|
||||
expect(controller.isAnyStreamRecovering, isFalse);
|
||||
});
|
||||
|
||||
test('isChannelRecovering returns false for unknown channel', () {
|
||||
expect(controller.isChannelRecovering('tasks'), isFalse);
|
||||
});
|
||||
|
||||
// ── markChannelRecovering ───────────────────────────────────────────
|
||||
|
||||
test('markChannelRecovering adds channel to the set', () {
|
||||
controller.markChannelRecovering('tasks');
|
||||
|
||||
expect(controller.isChannelRecovering('tasks'), isTrue);
|
||||
expect(controller.isAnyStreamRecovering, isTrue);
|
||||
});
|
||||
|
||||
test('markChannelRecovering with multiple channels tracks all of them', () {
|
||||
controller.markChannelRecovering('tasks');
|
||||
controller.markChannelRecovering('tickets');
|
||||
|
||||
expect(controller.isChannelRecovering('tasks'), isTrue);
|
||||
expect(controller.isChannelRecovering('tickets'), isTrue);
|
||||
expect(controller.recoveringChannels.length, 2);
|
||||
});
|
||||
|
||||
test('markChannelRecovering notifies listeners exactly once per new channel',
|
||||
() {
|
||||
int notifyCount = 0;
|
||||
controller.addListener(() => notifyCount++);
|
||||
|
||||
controller.markChannelRecovering('tasks');
|
||||
expect(notifyCount, 1);
|
||||
|
||||
// Adding the same channel again must NOT trigger a second notification.
|
||||
controller.markChannelRecovering('tasks');
|
||||
expect(notifyCount, 1);
|
||||
});
|
||||
|
||||
// ── markChannelRecovered ────────────────────────────────────────────
|
||||
|
||||
test('markChannelRecovered removes channel from the set', () {
|
||||
controller.markChannelRecovering('tasks');
|
||||
controller.markChannelRecovered('tasks');
|
||||
|
||||
expect(controller.isChannelRecovering('tasks'), isFalse);
|
||||
expect(controller.isAnyStreamRecovering, isFalse);
|
||||
});
|
||||
|
||||
test('markChannelRecovered on unknown channel does not notify', () {
|
||||
int notifyCount = 0;
|
||||
controller.addListener(() => notifyCount++);
|
||||
|
||||
// Recovering a channel that was never marked recovering is a no-op.
|
||||
controller.markChannelRecovered('nonexistent');
|
||||
expect(notifyCount, 0);
|
||||
});
|
||||
|
||||
test('markChannelRecovered notifies listeners when channel is removed', () {
|
||||
controller.markChannelRecovering('tasks');
|
||||
|
||||
int notifyCount = 0;
|
||||
controller.addListener(() => notifyCount++);
|
||||
|
||||
controller.markChannelRecovered('tasks');
|
||||
expect(notifyCount, 1);
|
||||
});
|
||||
|
||||
test('recovering two channels and recovering one leaves the other active',
|
||||
() {
|
||||
controller.markChannelRecovering('tasks');
|
||||
controller.markChannelRecovering('tickets');
|
||||
controller.markChannelRecovered('tasks');
|
||||
|
||||
expect(controller.isChannelRecovering('tasks'), isFalse);
|
||||
expect(controller.isChannelRecovering('tickets'), isTrue);
|
||||
expect(controller.isAnyStreamRecovering, isTrue);
|
||||
});
|
||||
|
||||
// ── recoveringChannels unmodifiable view ────────────────────────────
|
||||
|
||||
test('recoveringChannels returns an unmodifiable set', () {
|
||||
controller.markChannelRecovering('tasks');
|
||||
final view = controller.recoveringChannels;
|
||||
|
||||
expect(() => (view as dynamic).add('other'), throwsUnsupportedError);
|
||||
});
|
||||
|
||||
// ── handleChannelStatus ─────────────────────────────────────────────
|
||||
|
||||
test('handleChannelStatus with connected marks channel as recovered', () {
|
||||
controller.markChannelRecovering('tasks');
|
||||
controller.handleChannelStatus('tasks', StreamConnectionStatus.connected);
|
||||
|
||||
expect(controller.isChannelRecovering('tasks'), isFalse);
|
||||
});
|
||||
|
||||
test('handleChannelStatus with polling marks channel as recovered', () {
|
||||
controller.markChannelRecovering('tasks');
|
||||
controller.handleChannelStatus('tasks', StreamConnectionStatus.polling);
|
||||
|
||||
expect(controller.isChannelRecovering('tasks'), isFalse);
|
||||
});
|
||||
|
||||
test('handleChannelStatus with recovering marks channel as recovering', () {
|
||||
controller.handleChannelStatus(
|
||||
'tasks', StreamConnectionStatus.recovering);
|
||||
|
||||
expect(controller.isChannelRecovering('tasks'), isTrue);
|
||||
});
|
||||
|
||||
test('handleChannelStatus with stale marks channel as recovering', () {
|
||||
controller.handleChannelStatus('tasks', StreamConnectionStatus.stale);
|
||||
|
||||
expect(controller.isChannelRecovering('tasks'), isTrue);
|
||||
});
|
||||
|
||||
test('handleChannelStatus with failed marks channel as recovering', () {
|
||||
controller.handleChannelStatus('tasks', StreamConnectionStatus.failed);
|
||||
|
||||
expect(controller.isChannelRecovering('tasks'), isTrue);
|
||||
});
|
||||
|
||||
test('handleChannelStatus connected after recovering clears it', () {
|
||||
controller.handleChannelStatus(
|
||||
'tasks', StreamConnectionStatus.recovering);
|
||||
expect(controller.isAnyStreamRecovering, isTrue);
|
||||
|
||||
controller.handleChannelStatus('tasks', StreamConnectionStatus.connected);
|
||||
expect(controller.isAnyStreamRecovering, isFalse);
|
||||
});
|
||||
|
||||
// ── Legacy compat ───────────────────────────────────────────────────
|
||||
|
||||
test('markStreamRecovering maps to _global channel', () {
|
||||
controller.markStreamRecovering();
|
||||
|
||||
expect(controller.isChannelRecovering('_global'), isTrue);
|
||||
expect(controller.isAnyStreamRecovering, isTrue);
|
||||
});
|
||||
|
||||
test('markStreamRecovered clears _global channel', () {
|
||||
controller.markStreamRecovering();
|
||||
controller.markStreamRecovered();
|
||||
|
||||
expect(controller.isChannelRecovering('_global'), isFalse);
|
||||
expect(controller.isAnyStreamRecovering, isFalse);
|
||||
});
|
||||
|
||||
// ── isAnyStreamRecovering aggregate ────────────────────────────────
|
||||
|
||||
test('isAnyStreamRecovering is false only when all channels are recovered',
|
||||
() {
|
||||
controller.markChannelRecovering('a');
|
||||
controller.markChannelRecovering('b');
|
||||
controller.markChannelRecovered('a');
|
||||
|
||||
expect(controller.isAnyStreamRecovering, isTrue);
|
||||
|
||||
controller.markChannelRecovered('b');
|
||||
expect(controller.isAnyStreamRecovering, isFalse);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user