feat(client): 出入库列表按原型重建(KPI/详情抽屉/详细搜索/工具栏/选中复制/窗口尺寸)
- 列表重建:KPI 卡、状态+时间列、操作改眼睛开右侧详情抽屉、重置右置 - 详情抽屉(order_detail_drawer) 100% 还原原型 + SelectionArea 可选中复制 - 详细搜索弹窗:ComboSearchField/滚轮日期、字段重构、白底盒子按钮 - 工具栏:搜索框×清除+占位精简、供应商/客户下拉取全部、状态菜单收窄 - 加载不白屏(半透明遮罩)、输入框与下拉等高、windows/macOS 默认窗口尺寸调大 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 132 KiB |
|
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 129 KiB |
|
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 132 KiB |
|
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 122 KiB |
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 122 KiB |
|
Before Width: | Height: | Size: 185 KiB After Width: | Height: | Size: 205 KiB |
|
Before Width: | Height: | Size: 183 KiB After Width: | Height: | Size: 203 KiB |
|
Before Width: | Height: | Size: 186 KiB After Width: | Height: | Size: 207 KiB |
|
Before Width: | Height: | Size: 182 KiB After Width: | Height: | Size: 188 KiB |
|
Before Width: | Height: | Size: 183 KiB After Width: | Height: | Size: 188 KiB |
|
Before Width: | Height: | Size: 182 KiB After Width: | Height: | Size: 190 KiB |
|
Before Width: | Height: | Size: 125 KiB After Width: | Height: | Size: 125 KiB |
|
Before Width: | Height: | Size: 121 KiB After Width: | Height: | Size: 121 KiB |
|
Before Width: | Height: | Size: 125 KiB After Width: | Height: | Size: 125 KiB |
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 182 KiB After Width: | Height: | Size: 196 KiB |
|
Before Width: | Height: | Size: 180 KiB After Width: | Height: | Size: 194 KiB |
|
Before Width: | Height: | Size: 183 KiB After Width: | Height: | Size: 197 KiB |
|
Before Width: | Height: | Size: 174 KiB After Width: | Height: | Size: 182 KiB |
|
Before Width: | Height: | Size: 176 KiB After Width: | Height: | Size: 184 KiB |
|
Before Width: | Height: | Size: 175 KiB After Width: | Height: | Size: 184 KiB |
@@ -12,7 +12,8 @@ import 'package:jiu_client/providers/license_provider.dart';
|
||||
import 'package:jiu_client/providers/stock_in_provider.dart';
|
||||
import 'package:jiu_client/screens/stock_in/stock_in_list_screen.dart';
|
||||
|
||||
/// WriteGuard 内层会 watch licenseProvider;覆写成同步 normal 授权,避免真实网络请求。
|
||||
/// 入库列表屏(照原型重建:单视图无 tab)的行为测试。
|
||||
/// WriteGuard 内层 watch licenseProvider;覆写成同步 normal 授权,避免真实网络请求。
|
||||
class _FakeLicenseNotifier extends LicenseNotifier {
|
||||
@override
|
||||
Future<LicenseInfo?> build() async => const LicenseInfo(
|
||||
@@ -24,13 +25,8 @@ class _FakeLicenseNotifier extends LicenseNotifier {
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Fake notifier
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class _FakeStockInNotifier extends StockInListNotifier {
|
||||
final AsyncValue<PageResult<StockInOrder>> _fixed;
|
||||
|
||||
_FakeStockInNotifier(this._fixed);
|
||||
|
||||
@override
|
||||
@@ -40,67 +36,65 @@ class _FakeStockInNotifier extends StockInListNotifier {
|
||||
const PageResult(data: [], total: 0, page: 1, pageSize: 20);
|
||||
}
|
||||
|
||||
// 交互 setter 全部 no-op,避免命中真实 repo。
|
||||
@override
|
||||
void reload() {}
|
||||
|
||||
@override
|
||||
void setPage(int page) {}
|
||||
|
||||
@override
|
||||
void setPageSize(int pageSize) {}
|
||||
@override
|
||||
void setStatus(String status) {}
|
||||
|
||||
@override
|
||||
void setKeyword(String keyword) {}
|
||||
@override
|
||||
void setDateRange(String? startDate, String? endDate) {}
|
||||
|
||||
@override
|
||||
void setDetail(Map<String, String> detail) {}
|
||||
@override
|
||||
Future<void> createOrder(Map<String, dynamic> data) async {}
|
||||
|
||||
@override
|
||||
Future<void> submitOrder(int id) async {}
|
||||
|
||||
@override
|
||||
Future<void> approveOrder(int id) async {}
|
||||
|
||||
@override
|
||||
Future<void> rejectOrder(int id) async {}
|
||||
}
|
||||
|
||||
/// A notifier that stays permanently in loading state (build never completes).
|
||||
/// 永远停在 loading(build 不完成)。
|
||||
class _FakeLoadingStockInNotifier extends StockInListNotifier {
|
||||
@override
|
||||
Future<PageResult<StockInOrder>> build() {
|
||||
// Never completes — keeps the widget in CircularProgressIndicator state.
|
||||
return Completer<PageResult<StockInOrder>>().future;
|
||||
Future<PageResult<StockInOrder>> build() =>
|
||||
Completer<PageResult<StockInOrder>>().future;
|
||||
@override
|
||||
void reload() {}
|
||||
}
|
||||
|
||||
/// 持久化 provider 里已有残留关键词 'sfs'(模拟用户上次搜索后未清)。
|
||||
/// build 仍返回数据——用来验证「进页面把残留筛选回填到 UI」而非隐形丢数据。
|
||||
class _StaleKeywordStockInNotifier extends StockInListNotifier {
|
||||
@override
|
||||
Future<PageResult<StockInOrder>> build() async {
|
||||
state = AsyncValue.data(
|
||||
PageResult(data: [_makeOrder()], total: 1, page: 1, pageSize: 20));
|
||||
return state.value!;
|
||||
}
|
||||
|
||||
@override
|
||||
void reload() {}
|
||||
|
||||
String get currentKeyword => 'sfs';
|
||||
@override
|
||||
void setPage(int page) {}
|
||||
|
||||
void reload() {}
|
||||
@override
|
||||
void setStatus(String status) {}
|
||||
|
||||
@override
|
||||
void setKeyword(String keyword) {}
|
||||
@override
|
||||
void setDateRange(String? startDate, String? endDate) {}
|
||||
|
||||
@override
|
||||
Future<void> createOrder(Map<String, dynamic> data) async {}
|
||||
|
||||
@override
|
||||
Future<void> submitOrder(int id) async {}
|
||||
|
||||
@override
|
||||
Future<void> approveOrder(int id) async {}
|
||||
|
||||
@override
|
||||
Future<void> rejectOrder(int id) async {}
|
||||
void setDetail(Map<String, String> detail) {}
|
||||
}
|
||||
|
||||
/// Build a testable app. GoRouter is needed because the screen uses
|
||||
/// `context.go('/stock-in/new')`.
|
||||
Widget _buildApp(AsyncValue<PageResult<StockInOrder>> state) {
|
||||
Widget _appWith(Override overrideNotifier) {
|
||||
final router = GoRouter(
|
||||
initialLocation: '/stock-in',
|
||||
routes: [
|
||||
@@ -114,39 +108,20 @@ Widget _buildApp(AsyncValue<PageResult<StockInOrder>> state) {
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
return ProviderScope(
|
||||
overrides: [
|
||||
licenseProvider.overrideWith(() => _FakeLicenseNotifier()),
|
||||
stockInListProvider.overrideWith(() => _FakeStockInNotifier(state)),
|
||||
overrideNotifier,
|
||||
],
|
||||
child: MaterialApp.router(theme: buildTheme('a'), routerConfig: router),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLoadingApp() {
|
||||
final router = GoRouter(
|
||||
initialLocation: '/stock-in',
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/stock-in',
|
||||
builder: (_, __) => const Scaffold(body: StockInListScreen()),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/stock-in/new',
|
||||
builder: (_, __) => const Scaffold(body: Text('new order form')),
|
||||
),
|
||||
],
|
||||
);
|
||||
Widget _buildApp(AsyncValue<PageResult<StockInOrder>> state) => _appWith(
|
||||
stockInListProvider.overrideWith(() => _FakeStockInNotifier(state)));
|
||||
|
||||
return ProviderScope(
|
||||
overrides: [
|
||||
licenseProvider.overrideWith(() => _FakeLicenseNotifier()),
|
||||
stockInListProvider.overrideWith(() => _FakeLoadingStockInNotifier()),
|
||||
],
|
||||
child: MaterialApp.router(theme: buildTheme('a'), routerConfig: router),
|
||||
);
|
||||
}
|
||||
Widget _buildLoadingApp() => _appWith(
|
||||
stockInListProvider.overrideWith(() => _FakeLoadingStockInNotifier()));
|
||||
|
||||
StockInOrder _makeOrder({
|
||||
int id = 1,
|
||||
@@ -166,253 +141,99 @@ StockInOrder _makeOrder({
|
||||
orderDate: '2024-01-10',
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tests
|
||||
// ---------------------------------------------------------------------------
|
||||
void main() {
|
||||
group('StockInListScreen', () {
|
||||
testWidgets('shows CircularProgressIndicator while loading', (tester) async {
|
||||
tester.view.physicalSize = const Size(1280, 800);
|
||||
tester.view.devicePixelRatio = 1.0;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
void _bigView(WidgetTester tester) {
|
||||
tester.view.physicalSize = const Size(1400, 1000);
|
||||
tester.view.devicePixelRatio = 1.0;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('StockInListScreen(单视图重建版)', () {
|
||||
testWidgets('loading 显示进度圈', (tester) async {
|
||||
_bigView(tester);
|
||||
await tester.pumpWidget(_buildLoadingApp());
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byType(CircularProgressIndicator), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('shows error message and retry button on failure', (tester) async {
|
||||
tester.view.physicalSize = const Size(1280, 800);
|
||||
tester.view.devicePixelRatio = 1.0;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
|
||||
testWidgets('error 显示「加载失败」+ 重试', (tester) async {
|
||||
_bigView(tester);
|
||||
await tester.pumpWidget(
|
||||
_buildApp(AsyncValue.error('连接超时', StackTrace.empty)),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('暂无数据,网络不可用'), findsOneWidget);
|
||||
_buildApp(AsyncValue.error('连接超时', StackTrace.empty)));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.textContaining('加载失败'), findsOneWidget);
|
||||
expect(find.text('重试'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('shows 暂无入库单 when list is empty', (tester) async {
|
||||
tester.view.physicalSize = const Size(1280, 800);
|
||||
tester.view.devicePixelRatio = 1.0;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
|
||||
testWidgets('空列表显示空态提示', (tester) async {
|
||||
_bigView(tester);
|
||||
const empty =
|
||||
PageResult<StockInOrder>(data: [], total: 0, page: 1, pageSize: 20);
|
||||
|
||||
await tester.pumpWidget(_buildApp(const AsyncValue.data(empty)));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('暂无入库单'), findsOneWidget);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.textContaining('没有匹配的入库单'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('shows order list with order number and supplier', (tester) async {
|
||||
tester.view.physicalSize = const Size(1280, 800);
|
||||
tester.view.devicePixelRatio = 1.0;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
|
||||
testWidgets('列表直接展示单号与供应商(无 tab)', (tester) async {
|
||||
_bigView(tester);
|
||||
final orders = [
|
||||
_makeOrder(id: 1, orderNo: 'SI2024010001', partnerName: '张家酒水'),
|
||||
_makeOrder(id: 2, orderNo: 'SI2024010002', partnerName: '李记酒行'),
|
||||
];
|
||||
|
||||
final result =
|
||||
PageResult<StockInOrder>(data: orders, total: 2, page: 1, pageSize: 20);
|
||||
|
||||
await tester.pumpWidget(_buildApp(AsyncValue.data(result)));
|
||||
await tester.pump();
|
||||
|
||||
// draft orders only appear in the "入库审核" tab (index 1)
|
||||
await tester.tap(find.text('入库审核'));
|
||||
await tester.pumpWidget(_buildApp(
|
||||
AsyncValue.data(PageResult(data: orders, total: 2, page: 1, pageSize: 20))));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('SI2024010001'), findsOneWidget);
|
||||
expect(find.text('SI2024010002'), findsOneWidget);
|
||||
expect(find.text('张家酒水'), findsOneWidget);
|
||||
expect(find.text('李记酒行'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('shows 新建入库单 button in toolbar', (tester) async {
|
||||
tester.view.physicalSize = const Size(1280, 800);
|
||||
tester.view.devicePixelRatio = 1.0;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
|
||||
testWidgets('工具栏有「新增入库」并可导航', (tester) async {
|
||||
_bigView(tester);
|
||||
const empty =
|
||||
PageResult<StockInOrder>(data: [], total: 0, page: 1, pageSize: 20);
|
||||
|
||||
await tester.pumpWidget(_buildApp(const AsyncValue.data(empty)));
|
||||
await tester.pump();
|
||||
|
||||
// "新建入库审核单" button only appears in the "入库审核" tab (index 1)
|
||||
await tester.tap(find.text('入库审核'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('新建入库审核单'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('tapping 新建入库单 navigates to new order route', (tester) async {
|
||||
tester.view.physicalSize = const Size(1280, 800);
|
||||
tester.view.devicePixelRatio = 1.0;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
|
||||
const empty =
|
||||
PageResult<StockInOrder>(data: [], total: 0, page: 1, pageSize: 20);
|
||||
|
||||
await tester.pumpWidget(_buildApp(const AsyncValue.data(empty)));
|
||||
await tester.pump();
|
||||
|
||||
// button only exists in the "入库审核" tab (index 1)
|
||||
await tester.tap(find.text('入库审核'));
|
||||
final newBtn = find.text('新增入库');
|
||||
expect(newBtn, findsOneWidget);
|
||||
await tester.tap(newBtn);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('新建入库审核单'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('new order form'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('draft order shows 提交 button', (tester) async {
|
||||
tester.view.physicalSize = const Size(1280, 800);
|
||||
tester.view.devicePixelRatio = 1.0;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
|
||||
final orders = [_makeOrder(id: 1, status: 'draft')];
|
||||
final result =
|
||||
PageResult<StockInOrder>(data: orders, total: 1, page: 1, pageSize: 20);
|
||||
|
||||
await tester.pumpWidget(_buildApp(AsyncValue.data(result)));
|
||||
await tester.pump();
|
||||
|
||||
// draft orders only appear in the "入库审核" tab (index 1)
|
||||
await tester.tap(find.text('入库审核'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('提交'), findsOneWidget);
|
||||
// 操作已改为「行内只放眼睛,审核/退单/打印移入右侧详情抽屉」。
|
||||
// 抽屉内容与动作布局由 order_detail_drawer 组件保真验证,这里只验行内行为。
|
||||
testWidgets('各状态行内只显示眼睛(详情入口),无内联审核按钮', (tester) async {
|
||||
_bigView(tester);
|
||||
for (final status in ['draft', 'pending', 'approved']) {
|
||||
final orders = [_makeOrder(id: 1, status: status)];
|
||||
await tester.pumpWidget(_buildApp(AsyncValue.data(
|
||||
PageResult(data: orders, total: 1, page: 1, pageSize: 20))));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byTooltip('详情'), findsOneWidget); // 眼睛图标
|
||||
expect(find.byKey(const Key('btn_approve_1')), findsNothing);
|
||||
expect(find.byKey(const Key('btn_reject_1')), findsNothing);
|
||||
expect(find.text('提交'), findsNothing);
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('pending order shows 通过 and 拒绝 buttons', (tester) async {
|
||||
tester.view.physicalSize = const Size(1280, 800);
|
||||
tester.view.devicePixelRatio = 1.0;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
|
||||
final orders = [_makeOrder(id: 1, status: 'pending')];
|
||||
final result =
|
||||
PageResult<StockInOrder>(data: orders, total: 1, page: 1, pageSize: 20);
|
||||
|
||||
await tester.pumpWidget(_buildApp(AsyncValue.data(result)));
|
||||
await tester.pump();
|
||||
|
||||
// pending orders only appear in the "入库审核" tab (index 1)
|
||||
await tester.tap(find.text('入库审核'));
|
||||
testWidgets('进页面把 provider 残留关键词回填到搜索框(不隐形过滤)',
|
||||
(tester) async {
|
||||
_bigView(tester);
|
||||
await tester.pumpWidget(_appWith(
|
||||
stockInListProvider.overrideWith(() => _StaleKeywordStockInNotifier())));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byKey(const Key('btn_approve_1')), findsOneWidget);
|
||||
expect(find.byKey(const Key('btn_reject_1')), findsOneWidget);
|
||||
// 残留 'sfs' 必须显示在搜索框里,用户看得见可清除;
|
||||
// 修复前入库屏无 initState 回填 → 框空但仍按 'sfs' 过滤 → 列表恒空。
|
||||
expect(find.text('sfs'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('approved order shows no action buttons', (tester) async {
|
||||
tester.view.physicalSize = const Size(1280, 800);
|
||||
tester.view.devicePixelRatio = 1.0;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
|
||||
final orders = [_makeOrder(id: 1, status: 'approved')];
|
||||
final result =
|
||||
PageResult<StockInOrder>(data: orders, total: 1, page: 1, pageSize: 20);
|
||||
|
||||
await tester.pumpWidget(_buildApp(AsyncValue.data(result)));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byKey(const Key('btn_approve_1')), findsNothing);
|
||||
expect(find.byKey(const Key('btn_reject_1')), findsNothing);
|
||||
expect(find.text('提交'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('tapping approve shows confirmation dialog', (tester) async {
|
||||
tester.view.physicalSize = const Size(1280, 800);
|
||||
tester.view.devicePixelRatio = 1.0;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
|
||||
final orders = [
|
||||
_makeOrder(id: 1, status: 'pending', orderNo: 'SI2024010001')
|
||||
];
|
||||
final result =
|
||||
PageResult<StockInOrder>(data: orders, total: 1, page: 1, pageSize: 20);
|
||||
|
||||
await tester.pumpWidget(_buildApp(AsyncValue.data(result)));
|
||||
await tester.pump();
|
||||
|
||||
// pending orders only appear in the "入库审核" tab (index 1)
|
||||
await tester.tap(find.text('入库审核'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.byKey(const Key('btn_approve_1')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('审核确认'), findsOneWidget);
|
||||
expect(find.textContaining('SI2024010001'), findsAtLeastNWidgets(1));
|
||||
expect(find.text('通过'), findsAtLeastNWidgets(1));
|
||||
});
|
||||
|
||||
testWidgets('tapping reject shows confirmation dialog', (tester) async {
|
||||
tester.view.physicalSize = const Size(1280, 800);
|
||||
tester.view.devicePixelRatio = 1.0;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
|
||||
final orders = [
|
||||
_makeOrder(id: 1, status: 'pending', orderNo: 'SI2024010001')
|
||||
];
|
||||
final result =
|
||||
PageResult<StockInOrder>(data: orders, total: 1, page: 1, pageSize: 20);
|
||||
|
||||
await tester.pumpWidget(_buildApp(AsyncValue.data(result)));
|
||||
await tester.pump();
|
||||
|
||||
// pending orders only appear in the "入库审核" tab (index 1)
|
||||
await tester.tap(find.text('入库审核'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.byKey(const Key('btn_reject_1')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('拒绝确认'), findsOneWidget);
|
||||
expect(find.textContaining('SI2024010001'), findsAtLeastNWidgets(1));
|
||||
expect(find.text('拒绝'), findsAtLeastNWidgets(1));
|
||||
});
|
||||
|
||||
testWidgets('shows status filter dropdown in first tab toolbar', (tester) async {
|
||||
tester.view.physicalSize = const Size(1280, 800);
|
||||
tester.view.devicePixelRatio = 1.0;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
|
||||
const empty =
|
||||
PageResult<StockInOrder>(data: [], total: 0, page: 1, pageSize: 20);
|
||||
|
||||
await tester.pumpWidget(_buildApp(const AsyncValue.data(empty)));
|
||||
await tester.pump();
|
||||
|
||||
await tester.tap(find.text('入库单'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('全部状态'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('shows total record count in pagination bar', (tester) async {
|
||||
tester.view.physicalSize = const Size(1280, 800);
|
||||
tester.view.devicePixelRatio = 1.0;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
|
||||
testWidgets('分页栏显示总数', (tester) async {
|
||||
_bigView(tester);
|
||||
final orders = [_makeOrder(id: 1)];
|
||||
final result =
|
||||
PageResult<StockInOrder>(data: orders, total: 35, page: 1, pageSize: 20);
|
||||
|
||||
await tester.pumpWidget(_buildApp(AsyncValue.data(result)));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.textContaining('共 35 条记录'), findsOneWidget);
|
||||
await tester.pumpWidget(_buildApp(
|
||||
AsyncValue.data(PageResult(data: orders, total: 35, page: 1, pageSize: 20))));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.textContaining('共 35'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ class _CountingStockInRepo extends StockInRepository {
|
||||
String? startDate,
|
||||
String? endDate,
|
||||
String? keyword,
|
||||
Map<String, String>? detail,
|
||||
int page = 1,
|
||||
int pageSize = 20,
|
||||
}) async {
|
||||
|
||||