Files
jiu/client/test/partners_screen_test.dart
T
wangjia 6238b86dcb feat(client): 登录/注册页照原型重建,ds 真相源组件族统一全部屏
- 登录/注册(login.html/register.html 1:1):两栏卡片+品牌渐变面板+主题小衣服
  pill(onSurface 变体)+记住我(记录并预填最近账号)+原型式 toast 校验;
  登录 fidelity 1.4–2.1% 三主题全绿;注册暂不入闸(少 门店编号/兑换券 字段,
  已记 CONTRACT,screens.mjs 留存根)
- ds 原子补齐:DsToast(.toast 单例)/DsCheck(.check/.agree)/DsButton lg 档/
  DsSelect 替换全部旧 DropdownButton/DsField label 在上/DsInput 后缀与密码形态
- 全屏统一:对话框按钮全 DsButton、盒式输入主题钉死(visualDensity.standard、
  h38、InputDecorationTheme 渗漏修复)、图标全 lucide、JetBrains Mono 三端同源、
  BrandMark 真相源 logo、只读模式写操作全量守卫(WriteGuard+DsToast)
- 出入库列表:版式对齐原型(卡片对齐+搜索框居中)、KPI 近30天滚动、结清后
  失效财务应收应付表;商品编辑抽屉介绍库改搜索下拉、图片双击全屏预览
- 删除旧 UI 死代码:DataTableCard/FormDialog/PageScaffold/SearchChip/
  SelectProductDialog/tabStateProvider
- golden/fidelity:stock-in/out 补注册(9%)、login 入册(8%)、goldens 全量重打;
  修复 pubspec flutter_web_plugins 非法声明(CI pub get 阻断)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JJ1g8XV1YhhmHRzhwWEW7o
2026-07-03 09:58:14 +08:00

221 lines
7.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.
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<LicenseInfo?> build() async => const LicenseInfo(
id: 1,
type: 'annual',
isActive: true,
maxDevices: 3,
phase: 'normal',
);
}
class _FakePartnerNotifier extends PartnerListNotifier {
final AsyncValue<PageResult<Partner>> _fixed;
_FakePartnerNotifier(this._fixed);
@override
Future<PageResult<Partner>> 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<void> createPartner(Map<String, dynamic> data) async {}
@override
Future<void> updatePartner(int id, Map<String, dynamic> data) async {}
@override
Future<void> deletePartner(int id) async {}
}
/// 永久 loadingbuild 永不完成)。
class _FakeLoadingPartnerNotifier extends PartnerListNotifier {
@override
Future<PageResult<Partner>> build() =>
Completer<PageResult<Partner>>().future;
@override
void reload() {}
@override
void setKeyword(String keyword) {}
@override
void setType(String? t) {}
@override
void setPage(int page) {}
}
Widget _buildApp({
AsyncValue<PageResult<Partner>> 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();
// DsFieldlabel 在上(「名称」+ 独立红星),非浮动 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);
});
});
}