Files
jiu/client/test/finance_summary_search_test.dart
T

234 lines
9.6 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)不分页、一次性全量加载,
// 搜索口径=显示口径:
// 桌面(验收反馈改版):表头「往来单位」内嵌 ComboSearchField(与 _flowTable
// 往来单位筛选同款),单选精确匹配 → 汇总只显示该行;× 清除 → 恢复全量。
// 移动 应收·应付 分段头仍是 MSearchRow 本地 contains 过滤,未改动:
// 1. 输入关键字 → 汇总卡片按往来单位名过滤,未命中不显示;
// 2. 关键字不匹配任何行 → 展示「没有匹配的往来单位」空态;
// 3. 清空关键字 → 恢复全量展示。
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:lucide_icons_flutter/lucide_icons.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/combo_search_field.dart';
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 [];
}
/// 第 4 条「未挂账供应商」故意不出现在 partnerSummary() 里,用于验证下拉
/// 候选=全量往来单位(而非只列汇总行里出现的单位):选中它后汇总应展示
/// 既有「没有匹配的往来单位」空态。
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'),
Partner(id: 4, name: '未挂账供应商', type: 'supplier'),
],
total: 4,
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);
// 定位表头内嵌的 ComboSearchField:验收反馈后候选/占位文案与
// _flowTable toolbar 的往来单位 ComboSearchField 完全同款(同为
// hint:'往来单位'),不能再靠 hint/显示文字区分;改用构建顺序——
// 汇总表(_summaryTable)先于流水表(_flowTable)出现在页面里,
// 故 ComboSearchField 的第一个即汇总表头这个。
final combo = find.byType(ComboSearchField).first;
expect(find.byType(ComboSearchField), findsNWidgets(2));
await tester.ensureVisible(combo);
await tester.pumpAndSettle();
// 打开弹层,选中「金樽商贸」。
await tester.tap(combo);
await tester.pumpAndSettle();
await tester.tap(find.widgetWithText(InkWell, '金樽商贸'));
await tester.pumpAndSettle();
// 只剩「金樽商贸」一行。
expect(find.text('华东酒类批发'), findsNothing);
expect(find.text('茅台华南经销'), findsNothing);
expect(find.text('金樽商贸'), findsWidgets);
// 点触发器上的 × 清除选择 → 恢复全量 3 行。
final clearIcon = find.descendant(
of: combo,
matching: find.byIcon(LucideIcons.x),
);
expect(clearIcon, findsOneWidget);
await tester.tap(clearIcon);
await tester.pumpAndSettle();
expect(find.text('华东酒类批发'), findsOneWidget);
expect(find.text('金樽商贸'), findsOneWidget);
expect(find.text('茅台华南经销'), findsOneWidget);
});
testWidgets('候选=全量往来单位;选中汇总里没有行的单位显示空态', (tester) async {
await _pumpFinance(tester, size: const Size(1280, 900));
final combo = find.byType(ComboSearchField).first;
await tester.ensureVisible(combo);
await tester.pumpAndSettle();
await tester.tap(combo);
await tester.pumpAndSettle();
// 候选来自全量往来单位(4 条,含汇总里没有余额行的「未挂账供应商」),
// 而不再是「共 3 条结果」(旧口径=当前汇总行往来单位)。
expect(find.text('共 4 条结果'), findsOneWidget);
expect(find.widgetWithText(InkWell, '未挂账供应商'), findsOneWidget);
await tester.tap(find.widgetWithText(InkWell, '未挂账供应商'));
await tester.pumpAndSettle();
// 该单位在汇总里没有余额行 → 走既有「没有匹配的往来单位」空态。
expect(find.text('没有匹配的往来单位'), findsOneWidget);
expect(find.text('华东酒类批发'), findsNothing);
expect(find.text('金樽商贸'), findsNothing);
expect(find.text('茅台华南经销'), findsNothing);
});
});
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);
});
});
}