Files
jiu/client/test/finance_summary_search_test.dart
T

195 lines
7.5 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// test/finance_summary_search_test.dart — 财务「应收/应付 汇总」往来单位搜索回归。
//
// financePartnerRowsProviderGET /finance/summary)不分页、一次性全量加载,
// 搜索口径=显示口径:仅需验证本地 contains 过滤在已加载行上生效——桌面
// _summaryTable 工具头 DsSearchBox / 移动 应收·应付 分段头 MSearchRow 均覆盖:
// 1. 输入关键字 → 汇总行按往来单位名过滤,未命中不显示;
// 2. 关键字不匹配任何行 → 展示「没有匹配的往来单位」空态;
// 3. 清空关键字 → 恢复全量展示。
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:jiu_client/core/api/api_client.dart';
import 'package:jiu_client/core/auth/auth_state.dart';
import 'package:jiu_client/core/models/page_result.dart';
import 'package:jiu_client/core/theme/themes.dart';
import 'package:jiu_client/models/finance.dart';
import 'package:jiu_client/models/partner.dart';
import 'package:jiu_client/models/stock_summary.dart';
import 'package:jiu_client/providers/finance_provider.dart';
import 'package:jiu_client/providers/partner_provider.dart';
import 'package:jiu_client/providers/stock_in_provider.dart';
import 'package:jiu_client/providers/stock_out_provider.dart';
import 'package:jiu_client/repositories/finance_repository.dart';
import 'package:jiu_client/screens/finance/finance_screen.dart';
import 'package:jiu_client/widgets/ds/ds_atoms.dart' show DsSearchBox;
import 'package:jiu_client/widgets/ds/m_search_row.dart' show MSearchRow;
/// 不打真实网络:仅用于满足 FinanceRepository 构造签名,方法全部被覆写。
class _DummyApiClient extends ApiClient {
_DummyApiClient() : super(token: 'test-token');
}
/// 固定 3 条往来单位汇总(2 应收 + 1 应付),流水列表恒空(本测试不关心流水)。
class _FakeFinanceRepository extends FinanceRepository {
_FakeFinanceRepository() : super(_DummyApiClient());
@override
Future<PageResult<FinanceRecord>> listRecords({
String? type,
String? month,
String? startDate,
String? endDate,
int? partnerId,
int page = 1,
int pageSize = 50,
}) async =>
const PageResult(data: [], total: 0, page: 1, pageSize: 50);
@override
Future<List<PartnerFinanceSummary>> partnerSummary() async => const [
PartnerFinanceSummary(
partnerId: 1,
partnerName: '华东酒类批发',
type: 'receivable',
recordCount: 2,
totalAmount: 128400),
PartnerFinanceSummary(
partnerId: 2,
partnerName: '金樽商贸',
type: 'receivable',
recordCount: 1,
totalAmount: 86200),
PartnerFinanceSummary(
partnerId: 3,
partnerName: '茅台华南经销',
type: 'payable',
recordCount: 1,
totalAmount: 142800),
];
@override
Future<List<TrendPoint>> trend({int months = 6}) async => const [];
}
class _FakeAllPartners extends PartnerListNotifier {
@override
Future<PageResult<Partner>> build() async => const PageResult(
data: [
Partner(id: 1, name: '华东酒类批发', type: 'customer'),
Partner(id: 2, name: '金樽商贸', type: 'customer'),
Partner(id: 3, name: '茅台华南经销', type: 'supplier'),
],
total: 3,
page: 1,
pageSize: 1000,
);
}
List<Override> _overrides() => [
financeRepositoryProvider.overrideWith((ref) => _FakeFinanceRepository()),
allPartnersProvider.overrideWith(() => _FakeAllPartners()),
stockOutSummaryProvider.overrideWith(
(ref) async => const StockSummary(monthAmount: 0, lastMonthAmount: 0)),
stockInSummaryProvider.overrideWith(
(ref) async => const StockSummary(monthAmount: 0, lastMonthAmount: 0)),
isReadonlyProvider.overrideWithValue(false),
];
Future<void> _pumpFinance(
WidgetTester tester, {
required Size size,
}) async {
tester.view.physicalSize = size;
tester.view.devicePixelRatio = 1.0;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
await tester.pumpWidget(ProviderScope(
overrides: _overrides(),
child: MaterialApp(
debugShowCheckedModeBanner: false,
theme: buildTheme('a'),
home: const Scaffold(body: FinanceScreen()),
),
));
await tester.pumpAndSettle();
}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
SharedPreferences.setMockInitialValues({});
group('桌面汇总卡往来单位搜索', () {
testWidgets('输入关键字过滤汇总行,无匹配报空态,清空恢复全量', (tester) async {
await _pumpFinance(tester, size: const Size(1280, 900));
// 初始:3 条汇总行全部展示。
expect(find.text('华东酒类批发'), findsOneWidget);
expect(find.text('金樽商贸'), findsOneWidget);
expect(find.text('茅台华南经销'), findsOneWidget);
final searchField = find.descendant(
of: find.byType(DsSearchBox),
matching: find.byType(TextField),
);
expect(searchField, findsOneWidget);
// 输入「金樽」:只剩「金樽商贸」。
await tester.enterText(searchField, '金樽');
await tester.pumpAndSettle();
expect(find.text('金樽商贸'), findsOneWidget);
expect(find.text('华东酒类批发'), findsNothing);
expect(find.text('茅台华南经销'), findsNothing);
// 输入不存在的关键字:空态提示。
await tester.enterText(searchField, '不存在的单位名');
await tester.pumpAndSettle();
expect(find.text('没有匹配的往来单位'), findsOneWidget);
expect(find.text('金樽商贸'), findsNothing);
// 清空:恢复全量 3 行。
await tester.enterText(searchField, '');
await tester.pumpAndSettle();
expect(find.text('华东酒类批发'), findsOneWidget);
expect(find.text('金樽商贸'), findsOneWidget);
expect(find.text('茅台华南经销'), findsOneWidget);
});
});
group('移动应收/应付分段往来单位搜索', () {
testWidgets('应收分段输入关键字过滤卡片,清空恢复全量', (tester) async {
await _pumpFinance(tester, size: const Size(390, 1600));
// 默认「应收」分段:仅展示 recv>0 的两条(华东/金樽),不含应付的茅台。
expect(find.text('华东酒类批发'), findsOneWidget);
expect(find.text('金樽商贸'), findsOneWidget);
expect(find.text('茅台华南经销'), findsNothing);
final searchField = find.descendant(
of: find.byType(MSearchRow),
matching: find.byType(TextField),
);
expect(searchField, findsOneWidget);
await tester.enterText(searchField, '金樽');
await tester.pumpAndSettle();
expect(find.text('金樽商贸'), findsOneWidget);
expect(find.text('华东酒类批发'), findsNothing);
// 无匹配 → 分段空态。
await tester.enterText(searchField, '不存在的单位名');
await tester.pumpAndSettle();
expect(find.text('没有匹配的往来单位'), findsOneWidget);
// 清空恢复。
await tester.enterText(searchField, '');
await tester.pumpAndSettle();
expect(find.text('华东酒类批发'), findsOneWidget);
expect(find.text('金樽商贸'), findsOneWidget);
});
});
}