import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:metatron_student_erp/features/fees/fees_screen.dart'; import 'package:metatron_student_erp/models/fee.dart'; import 'package:metatron_student_erp/models/student.dart'; import 'package:metatron_student_erp/providers/fee_provider.dart'; import 'package:metatron_student_erp/repositories/fee_repository.dart'; void main() { const sampleSummary = FeeSummary( totalFees: 30000.0, totalPaid: 10000.0, outstandingBalance: 20000.0, overdueAmount: 0.0, paymentStatus: 'partial', nextDueDate: '2026-10-23', lastPaymentDate: '2026-10-08', lastReceiptNumber: 'REC/000001', currency: CurrencyInfo(id: 20, name: 'INR', symbol: '₹'), ); final sampleInvoices = [ const FeeInvoice( id: 59, invoiceNumber: 'INV/2026/00035', title: 'B.Tech CS - Sem 1 Fee Structure (Demo)', academicYear: NamedRecord(id: 10, name: 'Test Year'), semester: NamedRecord(id: 4, name: 'Semester 1'), assignmentDate: '2026-09-23', dueDate: '2026-10-23', amount: 30000.0, amountPaid: 10000.0, amountDue: 20000.0, paymentStatus: 'partial', state: 'confirmed', currency: CurrencyInfo(id: 20, name: 'INR', symbol: '₹'), lines: [ FeeInvoiceLine(id: 75, name: 'Tuition Fee', category: 'Academic Fees', amount: 25000.0, mandatory: true), FeeInvoiceLine(id: 76, name: 'Laboratory & Library Fee', category: 'Academic Fees', amount: 5000.0, mandatory: true), ], payments: [ FeeInvoicePayment( id: 29, receiptNumber: 'REC/000001', paymentDate: '2026-10-08', paymentMethod: 'online', paymentMethodLabel: 'Online Payment', amount: 10000.0, referenceNumber: 'DEMO-PAY-001', ), ], ), ]; final samplePayments = [ const FeePayment( id: 29, receiptNumber: 'REC/000001', paymentDate: '2026-10-08', paymentMethod: 'online', paymentMethodLabel: 'Online Payment', amount: 10000.0, referenceNumber: 'DEMO-PAY-001', remarks: 'Partial Online Fee Payment', status: 'confirmed', invoiceId: 59, invoiceNumber: 'INV/2026/00035', currency: CurrencyInfo(id: 20, name: 'INR', symbol: '₹'), ), ]; Widget buildTestableScreen(List overrides) { return ProviderScope( overrides: overrides, child: const MaterialApp( home: FeesScreen(), ), ); } testWidgets('FeesScreen renders loading state initially', (tester) async { await tester.pumpWidget( buildTestableScreen([ feeSummaryProvider.overrideWith( (ref) => Future.delayed(const Duration(seconds: 1), () => sampleSummary), ), feeInvoicesProvider.overrideWith( (ref) => Future.delayed( const Duration(seconds: 1), () => FeeInvoicesPageResult(invoices: sampleInvoices, totalCount: 1, page: 1, limit: 20), ), ), ]), ); expect(find.byType(CircularProgressIndicator), findsWidgets); await tester.pumpAndSettle(); }); testWidgets('FeesScreen renders fee summary and invoice cards with status badges', (tester) async { await tester.pumpWidget( buildTestableScreen([ feeSummaryProvider.overrideWith((ref) async => sampleSummary), feeInvoicesProvider.overrideWith( (ref) async => FeeInvoicesPageResult(invoices: sampleInvoices, totalCount: 1, page: 1, limit: 20), ), ]), ); await tester.pumpAndSettle(); // Verify Title & Tabs expect(find.text('Fee Management'), findsOneWidget); expect(find.text('Invoices & Summary'), findsOneWidget); expect(find.text('Payment History'), findsOneWidget); // Verify Summary Card expect(find.text('Financial Overview'), findsOneWidget); expect(find.text('Partially Paid'), findsOneWidget); // Status badge expect(find.text('₹20000.00'), findsWidgets); // Outstanding dues expect(find.text('₹30000.00'), findsWidgets); // Assessed expect(find.text('₹10000.00'), findsWidgets); // Paid expect(find.text('Next Due Date: 2026-10-23'), findsOneWidget); // Verify Invoice List expect(find.text('Fee Invoices'), findsOneWidget); expect(find.text('INV/2026/00035'), findsOneWidget); expect(find.text('B.Tech CS - Sem 1 Fee Structure (Demo)'), findsOneWidget); expect(find.text('Due: 2026-10-23'), findsOneWidget); expect(find.text('View Details'), findsOneWidget); }); testWidgets('FeesScreen renders empty state when no invoices exist', (tester) async { await tester.pumpWidget( buildTestableScreen([ feeSummaryProvider.overrideWith( (ref) async => const FeeSummary( totalFees: 0.0, totalPaid: 0.0, outstandingBalance: 0.0, overdueAmount: 0.0, paymentStatus: 'no_dues', ), ), feeInvoicesProvider.overrideWith( (ref) async => const FeeInvoicesPageResult(invoices: [], totalCount: 0, page: 1, limit: 20), ), ]), ); await tester.pumpAndSettle(); expect(find.text('No fee invoices found for your account.'), findsOneWidget); }); testWidgets('FeesScreen opens invoice detail bottom sheet on tap', (tester) async { await tester.pumpWidget( buildTestableScreen([ feeSummaryProvider.overrideWith((ref) async => sampleSummary), feeInvoicesProvider.overrideWith( (ref) async => FeeInvoicesPageResult(invoices: sampleInvoices, totalCount: 1, page: 1, limit: 20), ), feeInvoiceDetailProvider(59).overrideWith((ref) async => sampleInvoices[0]), ]), ); await tester.pumpAndSettle(); // Tap on invoice card await tester.tap(find.text('INV/2026/00035')); await tester.pumpAndSettle(); // Verify Detail Sheet Content expect(find.text('Fee Breakdown'), findsOneWidget); expect(find.text('Tuition Fee'), findsOneWidget); expect(find.text('Laboratory & Library Fee'), findsOneWidget); expect(find.text('₹25000.00'), findsOneWidget); expect(find.text('₹5000.00'), findsOneWidget); // Scroll down to reveal totals and payments await tester.drag(find.text('Fee Breakdown'), const Offset(0, -300)); await tester.pumpAndSettle(); expect(find.text('Balance Due'), findsOneWidget); expect(find.text('Payments Received'), findsOneWidget); expect(find.text('REC/000001'), findsOneWidget); }); testWidgets('FeesScreen renders payment history in tab 2', (tester) async { await tester.pumpWidget( buildTestableScreen([ feeSummaryProvider.overrideWith((ref) async => sampleSummary), feeInvoicesProvider.overrideWith( (ref) async => FeeInvoicesPageResult(invoices: sampleInvoices, totalCount: 1, page: 1, limit: 20), ), feePaymentsProvider.overrideWith( (ref) async => FeePaymentsPageResult(payments: samplePayments, totalCount: 1, page: 1, limit: 20), ), ]), ); await tester.pumpAndSettle(); // Switch to Payment History Tab await tester.tap(find.text('Payment History')); await tester.pumpAndSettle(); // Verify Payment Record expect(find.text('REC/000001'), findsOneWidget); expect(find.text('Method: Online Payment'), findsOneWidget); expect(find.text('Invoice: INV/2026/00035'), findsOneWidget); expect(find.text('Ref: DEMO-PAY-001'), findsOneWidget); expect(find.text('₹10000.00'), findsOneWidget); }); testWidgets('FeesScreen renders error state and retry on failure', (tester) async { await tester.pumpWidget( buildTestableScreen([ feeSummaryProvider.overrideWith( (ref) async => throw Exception('Network Connection Refused'), ), feeInvoicesProvider.overrideWith( (ref) async => throw Exception('Network Connection Refused'), ), ]), ); await tester.pumpAndSettle(); expect(find.textContaining('Failed to load fee summary'), findsOneWidget); expect(find.textContaining('Failed to load invoices'), findsOneWidget); expect(find.text('Retry'), findsWidgets); }); }