import 'dart:ui'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:userfront/features/dashboard/domain/dashboard_providers.dart'; import 'package:userfront/features/dashboard/domain/models.dart'; import 'package:userfront/i18n.dart'; AuditLogEntry _log(String id) { return AuditLogEntry.fromJson({ 'event_id': id, 'timestamp': '2026-02-06T00:00:00Z', 'status': 'success', 'session_id': 's-$id', }); } Future _drainMicrotasks() async { for (var i = 0; i < 5; i++) { await Future.delayed(Duration.zero); } } void main() { TestWidgetsFlutterBinding.ensureInitialized(); final dispatcher = TestWidgetsFlutterBinding.instance.platformDispatcher; dispatcher.localeTestValue = const Locale('ko'); dispatcher.localesTestValue = const [Locale('ko')]; tearDownAll(() { dispatcher.clearLocaleTestValue(); dispatcher.clearLocalesTestValue(); }); test('AuthTimelineNotifier는 초기 페이지를 로드한다', () async { final cursors = []; final container = ProviderContainer( overrides: [ authTimelineFetcherProvider.overrideWithValue(({String? cursor}) async { cursors.add(cursor); return AuditPage(items: [_log('1')], nextCursor: 'next'); }), ], ); container.read(authTimelineProvider.notifier); await _drainMicrotasks(); final state = container.read(authTimelineProvider); expect(state.items.length, 1); expect(state.nextCursor, 'next'); expect(cursors, [null]); container.dispose(); }); test('AuthTimelineNotifier는 다음 커서를 사용해 추가 로드한다', () async { final cursors = []; final container = ProviderContainer( overrides: [ authTimelineFetcherProvider.overrideWithValue(({String? cursor}) async { cursors.add(cursor); if (cursor == null) { return AuditPage(items: [_log('1')], nextCursor: 'next'); } return AuditPage(items: [_log('2')], nextCursor: ''); }), ], ); final notifier = container.read(authTimelineProvider.notifier); await _drainMicrotasks(); await notifier.loadMore(); final state = container.read(authTimelineProvider); expect(state.items.map((e) => e.eventId).toList(), ['1', '2']); expect(cursors, [null, 'next']); container.dispose(); }); test('AuthTimelineNotifier는 커서가 없으면 추가 로드를 하지 않는다', () async { var callCount = 0; final container = ProviderContainer( overrides: [ authTimelineFetcherProvider.overrideWithValue(({String? cursor}) async { callCount += 1; return AuditPage(items: [_log('1')], nextCursor: ''); }), ], ); final notifier = container.read(authTimelineProvider.notifier); await _drainMicrotasks(); await notifier.loadMore(); expect(callCount, 1); expect(container.read(authTimelineProvider).items.length, 1); container.dispose(); }); test('AuthTimelineNotifier는 실패 시 오류 메시지를 보관한다', () async { final container = ProviderContainer( overrides: [ authTimelineFetcherProvider.overrideWithValue(({String? cursor}) async { throw Exception('fail'); }), ], ); container.read(authTimelineProvider.notifier); await _drainMicrotasks(); final state = container.read(authTimelineProvider); expect(state.items.isEmpty, true); expect( state.error, tr( 'msg.userfront.dashboard.timeline.load_error', fallback: '접속이력을 불러오지 못했습니다.', ), ); container.dispose(); }); }