236 lines
8.2 KiB
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);
|
|
});
|
|
}
|