import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:jiu_client/core/models/page_result.dart'; import 'package:jiu_client/core/theme/themes.dart'; import 'package:jiu_client/models/partner.dart'; import 'package:jiu_client/providers/license_provider.dart'; import 'package:jiu_client/providers/partner_provider.dart'; import 'package:jiu_client/screens/partners/partners_screen.dart'; import 'package:lucide_icons_flutter/lucide_icons.dart'; /// 往来单位屏(Phase2 重建版:单表 + 类型 chips + 应收应付 + 详情抽屉 + frow 弹窗)。 /// WriteGuard 内层会 watch licenseProvider;覆写成同步返回 normal 授权,避免真实网络请求。 class _FakeLicenseNotifier extends LicenseNotifier { @override Future build() async => const LicenseInfo( id: 1, type: 'annual', isActive: true, maxDevices: 3, phase: 'normal', ); } class _FakePartnerNotifier extends PartnerListNotifier { final AsyncValue> _fixed; _FakePartnerNotifier(this._fixed); @override Future> build() async { state = _fixed; return state.value ?? const PageResult(data: [], total: 0, page: 1, pageSize: 20); } @override void reload() {} @override void setKeyword(String keyword) {} @override void setType(String? t) {} @override void setPage(int page) {} @override Future createPartner(Map data) async {} @override Future updatePartner(int id, Map data) async {} @override Future deletePartner(int id) async {} } /// 永久 loading(build 永不完成)。 class _FakeLoadingPartnerNotifier extends PartnerListNotifier { @override Future> build() => Completer>().future; @override void reload() {} @override void setKeyword(String keyword) {} @override void setType(String? t) {} @override void setPage(int page) {} } Widget _buildApp({ AsyncValue> state = const AsyncValue.data( PageResult(data: [], total: 0, page: 1, pageSize: 1000)), }) { return ProviderScope( overrides: [ licenseProvider.overrideWith(() => _FakeLicenseNotifier()), partnersScreenProvider.overrideWith(() => _FakePartnerNotifier(state)), partnerBalancesProvider.overrideWith((ref) async => { 1: (recv: 126800.0, pay: 0.0), }), // 抽屉「近期单据」:测试环境不发真实请求 partnerRecentDocsProvider.overrideWith((ref, id) async => const []), ], child: MaterialApp( theme: buildTheme('a'), home: const Scaffold(body: PartnersScreen())), ); } Widget _buildLoadingApp() { return ProviderScope( overrides: [ licenseProvider.overrideWith(() => _FakeLicenseNotifier()), partnersScreenProvider.overrideWith(() => _FakeLoadingPartnerNotifier()), partnerBalancesProvider.overrideWith((ref) async => {}), ], child: MaterialApp( theme: buildTheme('a'), home: const Scaffold(body: PartnersScreen())), ); } const _partners = [ Partner( id: 1, code: 'S001', name: '张家酒水供应商', type: 'supplier', contact: '张三', phone: '13800138000', address: '北京市朝阳区', ), ]; void main() { void desktop(WidgetTester tester) { tester.view.physicalSize = const Size(1280, 800); tester.view.devicePixelRatio = 1.0; addTearDown(tester.view.resetPhysicalSize); } group('PartnersScreen', () { testWidgets('loading 态显示 CircularProgressIndicator', (tester) async { desktop(tester); await tester.pumpWidget(_buildLoadingApp()); await tester.pump(); expect(find.byType(CircularProgressIndicator), findsOneWidget); }); testWidgets('错误态显示提示与重试', (tester) async { desktop(tester); await tester.pumpWidget( _buildApp(state: AsyncValue.error('网络错误', StackTrace.empty))); await tester.pump(); expect(find.text('暂无数据,网络不可用'), findsOneWidget); expect(find.text('重试'), findsOneWidget); }); testWidgets('空列表显示原型空态文案', (tester) async { desktop(tester); await tester.pumpWidget(_buildApp()); await tester.pump(); expect(find.text('没有匹配的往来单位 · 试试调整筛选或搜索'), findsOneWidget); }); testWidgets('列表显示 名称/联系人/电话/类型徽章/应收应付', (tester) async { desktop(tester); await tester.pumpWidget(_buildApp( state: const AsyncValue.data( PageResult(data: _partners, total: 1, page: 1, pageSize: 1000)), )); await tester.pumpAndSettle(); expect(find.text('张家酒水供应商'), findsOneWidget); expect(find.text('张三'), findsOneWidget); expect(find.text('13800138000'), findsOneWidget); expect(find.text('供应商'), findsWidgets); // chips + 徽章 expect(find.text('¥126,800'), findsOneWidget); // 应收(balances 假数据) }); testWidgets('类型 chips 与重置在 toolbar 中', (tester) async { desktop(tester); await tester.pumpWidget(_buildApp()); await tester.pump(); expect(find.text('全部'), findsOneWidget); expect(find.text('供应商'), findsOneWidget); expect(find.text('客户'), findsOneWidget); expect(find.text('重置'), findsOneWidget); }); testWidgets('点「新增往来单位」打开 frow 弹窗,空名保存校验', (tester) async { desktop(tester); await tester.pumpWidget(_buildApp()); await tester.pump(); await tester.tap(find.text('新增往来单位').first); await tester.pumpAndSettle(); // DsField:label 在上(「名称」+ 独立红星),非浮动 labelText expect(find.text('名称'), findsOneWidget); expect(find.text('期初余额'), findsOneWidget); await tester.tap(find.text('保存')); await tester.pump(); expect(find.text('不能为空'), findsAtLeastNWidgets(1)); }); testWidgets('铅笔打开预填编辑弹窗,弹窗内可发起删除确认', (tester) async { desktop(tester); await tester.pumpWidget(_buildApp( state: const AsyncValue.data( PageResult(data: _partners, total: 1, page: 1, pageSize: 1000)), )); await tester.pumpAndSettle(); await tester.tap(find.byIcon(LucideIcons.pencil).first); await tester.pumpAndSettle(); expect(find.text('编辑往来单位'), findsOneWidget); expect(find.widgetWithText(TextFormField, '张家酒水供应商'), findsOneWidget); await tester.tap(find.text('删除')); await tester.pumpAndSettle(); expect(find.text('确认删除'), findsOneWidget); expect(find.textContaining('张家酒水供应商'), findsAtLeastNWidgets(1)); }); testWidgets('行点击打开详情抽屉(联系信息 + 应收/应付 + 近期单据)', (tester) async { desktop(tester); await tester.pumpWidget(_buildApp( state: const AsyncValue.data( PageResult(data: _partners, total: 1, page: 1, pageSize: 1000)), )); await tester.pumpAndSettle(); await tester.tap(find.text('张家酒水供应商')); await tester.pump(const Duration(milliseconds: 300)); expect(find.text('联系信息'), findsOneWidget); expect(find.text('应收账款'), findsOneWidget); expect(find.text('应付账款'), findsOneWidget); expect(find.text('近期单据'), findsOneWidget); }); }); }