94a5f437a5
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
234 lines
9.6 KiB
Dart
234 lines
9.6 KiB
Dart
// test/finance_summary_search_test.dart — 财务「应收/应付 汇总」往来单位筛选回归。
|
||
//
|
||
// financePartnerRowsProvider(GET /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);
|
||
});
|
||
});
|
||
}
|