Skip to content

Commit 151059f

Browse files
committed
test: add coverage for CompactAutoResetController and auto-compact extension
1 parent 00d0491 commit 151059f

3 files changed

Lines changed: 242 additions & 0 deletions

File tree

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
import 'package:fake_async/fake_async.dart';
2+
import 'package:flutter_test/flutter_test.dart';
3+
4+
import 'package:webtrit_phone/features/call/call.dart';
5+
6+
import '../../../mocks/mocks.dart';
7+
8+
void main() {
9+
group('CompactAutoResetController', () {
10+
late CompactAutoResetController controller;
11+
const int autoResetSeconds = 5;
12+
13+
setUp(() {
14+
controller = CompactAutoResetController(autoResetSeconds: autoResetSeconds);
15+
});
16+
17+
tearDown(() {
18+
controller.dispose();
19+
});
20+
21+
test('Initial state is inactive and expanded (not compact)', () {
22+
expect(controller.active, isFalse);
23+
expect(controller.compact, isFalse);
24+
expect(controller.isRunning, isFalse);
25+
});
26+
27+
test('Constructor throws assertion error if autoResetSeconds <= 0', () {
28+
expect(() => CompactAutoResetController(autoResetSeconds: 0), throwsA(isA<AssertionError>()));
29+
});
30+
31+
test('activate() starts the timer and switches to compact after timeout', () {
32+
fakeAsync((async) {
33+
controller.activate();
34+
35+
// Immediately active but still expanded
36+
expect(controller.active, isTrue);
37+
expect(controller.compact, isFalse);
38+
expect(controller.isRunning, isTrue);
39+
40+
// Advance time partially (2s)
41+
async.elapse(const Duration(seconds: 2));
42+
expect(controller.compact, isFalse);
43+
44+
// Advance time past limit (Total 5s)
45+
async.elapse(const Duration(seconds: 3));
46+
expect(controller.compact, isTrue);
47+
48+
// Timer should stop after compacting
49+
expect(controller.isRunning, isFalse);
50+
});
51+
});
52+
53+
test('deactivate() stops the timer and resets state', () {
54+
fakeAsync((async) {
55+
controller.activate();
56+
async.elapse(const Duration(seconds: 2));
57+
58+
controller.deactivate();
59+
60+
expect(controller.active, isFalse);
61+
expect(controller.compact, isFalse);
62+
expect(controller.isRunning, isFalse);
63+
64+
// Advance time past original timeout - nothing should happen
65+
async.elapse(const Duration(seconds: 10));
66+
expect(controller.compact, isFalse);
67+
});
68+
});
69+
70+
test('poke() resets the countdown', () {
71+
fakeAsync((async) {
72+
controller.activate();
73+
74+
// 4 seconds passed (1s left)
75+
async.elapse(const Duration(seconds: 4));
76+
expect(controller.compact, isFalse);
77+
78+
// User interaction!
79+
controller.poke();
80+
81+
// 2 more seconds passed (Total 6s, but only 2s since poke)
82+
async.elapse(const Duration(seconds: 2));
83+
expect(controller.compact, isFalse);
84+
85+
// 3 more seconds passed (Total 5s since poke)
86+
async.elapse(const Duration(seconds: 3));
87+
expect(controller.compact, isTrue);
88+
});
89+
});
90+
91+
test('poke() is ignored if controller is inactive', () {
92+
fakeAsync((async) {
93+
controller.poke(); // Should do nothing
94+
expect(controller.isRunning, isFalse);
95+
});
96+
});
97+
98+
test('poke() is ignored if already compact', () {
99+
fakeAsync((async) {
100+
controller.activate();
101+
controller.setCompact(true); // Force compact
102+
103+
controller.poke(); // Should NOT restart timer
104+
105+
async.elapse(const Duration(seconds: 1));
106+
expect(controller.compact, isTrue); // Should remain compact
107+
});
108+
});
109+
110+
test('toggle() manually switches state and manages timer', () {
111+
fakeAsync((async) {
112+
controller.activate();
113+
114+
// Manually compact
115+
controller.toggle();
116+
expect(controller.compact, isTrue);
117+
expect(controller.isRunning, isFalse); // Timer shouldn't run when compact
118+
119+
// Manually expand
120+
controller.toggle();
121+
expect(controller.compact, isFalse);
122+
expect(controller.isRunning, isTrue); // Timer should restart
123+
124+
// Should compact again after timeout
125+
async.elapse(const Duration(seconds: 5));
126+
expect(controller.compact, isTrue);
127+
});
128+
});
129+
});
130+
131+
group('ActiveCallListAutoCompact Extension', () {
132+
test('Returns false for empty list', () {
133+
final List<ActiveCall> calls = [];
134+
expect(calls.shouldAutoCompact, isFalse);
135+
});
136+
137+
test('Returns TRUE only for CallProcessingStatus.connected', () {
138+
// Loop through ALL statuses defined in the enum
139+
for (final status in CallProcessingStatus.values) {
140+
final calls = [MockActiveCall(processingStatus: status, cameraEnabled: true, remoteVideo: true)];
141+
142+
if (status == CallProcessingStatus.connected) {
143+
expect(calls.shouldAutoCompact, isTrue, reason: 'Status $status should allow compact mode');
144+
} else {
145+
expect(calls.shouldAutoCompact, isFalse, reason: 'Status $status should NOT allow compact mode');
146+
}
147+
}
148+
});
149+
150+
test('Returns false if any call was hung up', () {
151+
final calls = [
152+
MockActiveCall(
153+
processingStatus: CallProcessingStatus.connected,
154+
wasHungUp: true,
155+
cameraEnabled: true,
156+
remoteVideo: true,
157+
),
158+
];
159+
expect(calls.shouldAutoCompact, isFalse);
160+
});
161+
162+
test('Returns true ONLY if cameraEnabled AND remoteVideo (Both active)', () {
163+
final calls = [
164+
MockActiveCall(processingStatus: CallProcessingStatus.connected, cameraEnabled: true, remoteVideo: true),
165+
];
166+
expect(calls.shouldAutoCompact, isTrue);
167+
});
168+
169+
test('Returns false if local camera is OFF (The "Black Void" prevention)', () {
170+
// UX Logic: If I turn off my camera, I must see the UI,
171+
// even if the remote side sends video (or black frames).
172+
final calls = [
173+
MockActiveCall(
174+
processingStatus: CallProcessingStatus.connected,
175+
cameraEnabled: false, // Local OFF
176+
remoteVideo: true, // Remote ON
177+
),
178+
];
179+
expect(
180+
calls.shouldAutoCompact,
181+
isFalse,
182+
reason: 'UI must remain visible when local camera is off to prevent black screen',
183+
);
184+
});
185+
186+
test('Returns false if remote video is OFF', () {
187+
final calls = [
188+
MockActiveCall(processingStatus: CallProcessingStatus.connected, cameraEnabled: true, remoteVideo: false),
189+
];
190+
expect(calls.shouldAutoCompact, isFalse);
191+
});
192+
193+
test('Multi-call: Returns true if all connected and at least one has full video', () {
194+
final calls = [
195+
// Call 1: Audio only (Connected)
196+
MockActiveCall(processingStatus: CallProcessingStatus.connected, cameraEnabled: false, remoteVideo: false),
197+
// Call 2: Video (Connected & Full Video)
198+
MockActiveCall(processingStatus: CallProcessingStatus.connected, cameraEnabled: true, remoteVideo: true),
199+
];
200+
expect(calls.shouldAutoCompact, isTrue);
201+
});
202+
203+
test('Multi-call: Returns false if ANY call is not connected', () {
204+
final calls = [
205+
// Call 1: Still ringing
206+
MockActiveCall(
207+
processingStatus: CallProcessingStatus.outgoingRinging,
208+
cameraEnabled: false,
209+
remoteVideo: false,
210+
),
211+
// Call 2: Connected Video
212+
MockActiveCall(processingStatus: CallProcessingStatus.connected, cameraEnabled: true, remoteVideo: true),
213+
];
214+
expect(calls.shouldAutoCompact, isFalse);
215+
});
216+
});
217+
}

test/mocks/mock_active_call.dart

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import 'package:mocktail/mocktail.dart';
2+
3+
import 'package:webtrit_phone/features/call/call.dart';
4+
5+
class MockActiveCall extends Fake implements ActiveCall {
6+
MockActiveCall({
7+
this.processingStatus = CallProcessingStatus.connected,
8+
this.wasHungUp = false,
9+
this.cameraEnabled = false,
10+
this.remoteVideo = false,
11+
});
12+
13+
@override
14+
final CallProcessingStatus processingStatus;
15+
16+
@override
17+
final bool wasHungUp;
18+
19+
@override
20+
final bool cameraEnabled;
21+
22+
@override
23+
final bool remoteVideo;
24+
}

test/mocks/mocks.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export 'fake_connectivity_service.dart';
2+
export 'mock_active_call.dart';
23
export 'mock_app_preferences.dart';
34
export 'mock_secure_storage.dart';
45
export 'mock_system_info.dart';

0 commit comments

Comments
 (0)