Metatron_Student_ERP/flutter_app/test/fees_widget_test.dart

236 lines
8.2 KiB
Dart

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<Override> 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);
});
}