2026-02-25 15:09:29 +08:00
|
|
|
import 'package:bloc_test/bloc_test.dart';
|
2026-03-19 18:42:05 +08:00
|
|
|
import 'package:fake_async/fake_async.dart';
|
2026-02-25 15:09:29 +08:00
|
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
|
import 'package:formz/formz.dart';
|
|
|
|
|
import 'package:mocktail/mocktail.dart';
|
|
|
|
|
import 'package:social_app/features/auth/data/auth_repository.dart';
|
|
|
|
|
import 'package:social_app/features/auth/presentation/cubits/login_cubit.dart';
|
|
|
|
|
|
|
|
|
|
class MockAuthRepository extends Mock implements AuthRepository {}
|
|
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
|
late LoginCubit cubit;
|
|
|
|
|
late MockAuthRepository mockRepository;
|
|
|
|
|
|
|
|
|
|
setUp(() {
|
|
|
|
|
mockRepository = MockAuthRepository();
|
|
|
|
|
cubit = LoginCubit(mockRepository);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
tearDown(() {
|
|
|
|
|
cubit.close();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
group('LoginCubit', () {
|
|
|
|
|
test('initial state has pure status', () {
|
|
|
|
|
expect(cubit.state.status, FormzSubmissionStatus.initial);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
blocTest<LoginCubit, LoginState>(
|
2026-03-19 18:42:05 +08:00
|
|
|
'phoneChanged updates phone',
|
2026-02-25 15:09:29 +08:00
|
|
|
build: () => cubit,
|
2026-03-19 18:42:05 +08:00
|
|
|
act: (c) => c.phoneChanged('+8613812345678'),
|
2026-02-25 15:09:29 +08:00
|
|
|
expect: () => [isA<LoginState>()],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
blocTest<LoginCubit, LoginState>(
|
2026-03-19 18:42:05 +08:00
|
|
|
'codeChanged updates code',
|
2026-02-25 15:09:29 +08:00
|
|
|
build: () => cubit,
|
2026-03-19 18:42:05 +08:00
|
|
|
act: (c) => c.codeChanged('123456'),
|
2026-02-25 15:09:29 +08:00
|
|
|
expect: () => [isA<LoginState>()],
|
|
|
|
|
);
|
2026-03-19 18:42:05 +08:00
|
|
|
|
|
|
|
|
test('sendCode success starts 60s cooldown', () {
|
|
|
|
|
when(() => mockRepository.sendOtp(any())).thenAnswer((_) async {});
|
|
|
|
|
|
|
|
|
|
fakeAsync((async) {
|
|
|
|
|
cubit.phoneChanged('13812345678');
|
|
|
|
|
|
|
|
|
|
cubit.sendCode();
|
|
|
|
|
async.flushMicrotasks();
|
|
|
|
|
|
|
|
|
|
expect(cubit.state.resendCooldownSeconds, 60);
|
|
|
|
|
|
|
|
|
|
async.elapse(const Duration(seconds: 1));
|
|
|
|
|
expect(cubit.state.resendCooldownSeconds, 59);
|
|
|
|
|
|
|
|
|
|
async.elapse(const Duration(seconds: 59));
|
|
|
|
|
expect(cubit.state.resendCooldownSeconds, 0);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('sendCode is blocked during cooldown', () async {
|
|
|
|
|
when(() => mockRepository.sendOtp(any())).thenAnswer((_) async {});
|
|
|
|
|
cubit.phoneChanged('13812345678');
|
|
|
|
|
|
|
|
|
|
final first = await cubit.sendCode();
|
|
|
|
|
final second = await cubit.sendCode();
|
|
|
|
|
|
|
|
|
|
expect(first, isTrue);
|
|
|
|
|
expect(second, isFalse);
|
|
|
|
|
verify(() => mockRepository.sendOtp(any())).called(1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('phone change resets cooldown and code state', () {
|
|
|
|
|
when(() => mockRepository.sendOtp(any())).thenAnswer((_) async {});
|
|
|
|
|
|
|
|
|
|
fakeAsync((async) {
|
|
|
|
|
cubit.phoneChanged('13812345678');
|
|
|
|
|
cubit.codeChanged('123456');
|
|
|
|
|
cubit.sendCode();
|
|
|
|
|
async.flushMicrotasks();
|
|
|
|
|
|
|
|
|
|
expect(cubit.state.resendCooldownSeconds, 60);
|
|
|
|
|
expect(cubit.state.codeSent, isTrue);
|
|
|
|
|
|
|
|
|
|
cubit.phoneChanged('14155552671');
|
|
|
|
|
|
|
|
|
|
expect(cubit.state.resendCooldownSeconds, 0);
|
|
|
|
|
expect(cubit.state.codeSent, isFalse);
|
|
|
|
|
expect(cubit.state.code.value, '');
|
|
|
|
|
});
|
|
|
|
|
});
|
2026-02-25 15:09:29 +08:00
|
|
|
});
|
|
|
|
|
}
|