Files
jiu/client/test/stock_out_insufficient_stock_flow_test.dart
T
wangjia 9f94cc97b7 fix(test): 修复测试数据库 schema 和 Flutter 测试与屏幕重构的兼容性
- backend/testutil/setup.go: SQLite shops 表补充 business_hours 列
- stock_in_screen_test: 入库管理双 Tab 重构后,draft/pending 测试改为先切换到「入库审核」Tab
- stock_out_insufficient_stock_flow_test: Inventory 构造参数 productUnit→unit,补充必填 id
- inventory_repository_test: mock 数据改为平铺字段格式,补充必填 id
- docs: 新增 NAS + Gitea 部署文档(Runner 设置、Shadowrocket 中继说明)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 10:48:21 +08:00

217 lines
5.8 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 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:go_router/go_router.dart';
import 'package:jiu_client/core/api/api_client.dart';
import 'package:jiu_client/core/exceptions.dart';
import 'package:jiu_client/core/models/page_result.dart';
import 'package:jiu_client/models/inventory.dart';
import 'package:jiu_client/models/partner.dart';
import 'package:jiu_client/models/product.dart';
import 'package:jiu_client/models/stock_out.dart';
import 'package:jiu_client/models/warehouse.dart';
import 'package:jiu_client/providers/inventory_provider.dart';
import 'package:jiu_client/providers/partner_provider.dart';
import 'package:jiu_client/providers/product_provider.dart';
import 'package:jiu_client/providers/stock_out_provider.dart';
import 'package:jiu_client/providers/warehouse_provider.dart';
import 'package:jiu_client/repositories/inventory_repository.dart';
import 'package:jiu_client/screens/stock_out/stock_out_form_screen.dart';
class _FakeWarehouseNotifier extends WarehouseListNotifier {
@override
Future<List<Warehouse>> build() async {
return const [
Warehouse(id: 1, name: '主仓库', isDefault: true),
];
}
@override
Future<void> reload() async {}
}
class _FakeCustomerNotifier extends PartnerListNotifier {
_FakeCustomerNotifier() : super(type: 'customer');
@override
Future<PageResult<Partner>> build() async {
return const PageResult(
data: [
Partner(id: 2, name: '王记饭店', type: 'customer'),
],
total: 1,
page: 1,
pageSize: 20,
);
}
@override
void reload() {}
}
class _FakeProductNotifier extends ProductListNotifier {
@override
Future<PageResult<Product>> build() async {
return const PageResult(
data: [
Product(
id: 1,
code: 'P001',
name: '五粮液',
spec: '500ml',
unit: '',
salePrice: 120,
),
],
total: 1,
page: 1,
pageSize: 20,
);
}
@override
void reload() {}
@override
void setKeyword(String keyword) {}
@override
void setPage(int page) {}
@override
Future<void> createProduct(Map<String, dynamic> data) async {}
@override
Future<void> updateProduct(int id, Map<String, dynamic> data) async {}
@override
Future<void> deleteProduct(int id) async {}
}
class _FakeInventoryRepository extends InventoryRepository {
_FakeInventoryRepository() : super(_DummyApiClient());
@override
Future<PageResult<Inventory>> listInventory({
int? warehouseId,
String? keyword,
int page = 1,
int pageSize = 50,
}) async {
return const PageResult(
data: [
Inventory(
id: 1,
warehouseId: 1,
warehouseName: '主仓库',
productId: 1,
productName: '五粮液',
unit: '',
quantity: 1,
),
],
total: 1,
page: 1,
pageSize: 50,
);
}
}
class _DummyApiClient extends ApiClient {
_DummyApiClient() : super(token: 'test-token');
}
class _FailingStockOutNotifier extends StockOutListNotifier {
@override
Future<PageResult<StockOutOrder>> build() async {
return const PageResult(data: [], total: 0, page: 1, pageSize: 20);
}
@override
void reload() {}
@override
void setPage(int page) {}
@override
void setStatus(String status) {}
@override
void setDateRange(String? startDate, String? endDate) {}
@override
Future<void> createOrder(Map<String, dynamic> data) async {
throw const AppException('库存不足:五粮液 当前库存 1,需要 5', statusCode: 400);
}
@override
Future<void> deleteOrder(int id) async {}
@override
Future<void> submitOrder(int id) async {}
@override
Future<void> approveOrder(int id) async {}
@override
Future<void> rejectOrder(int id) async {}
}
void main() {
// TODO: 表单 DropdownButtonFormField 数量已变更,测试待更新
testWidgets('stock-out form shows insufficient stock error on submit',
skip: true, (tester) async {
tester.view.physicalSize = const Size(1440, 1200);
tester.view.devicePixelRatio = 1.0;
addTearDown(tester.view.resetPhysicalSize);
final router = GoRouter(
initialLocation: '/stock-out/new',
routes: [
GoRoute(
path: '/stock-out',
builder: (_, __) => const Scaffold(body: Text('stock-out list')),
),
GoRoute(
path: '/stock-out/new',
builder: (_, __) => const StockOutFormScreen(),
),
],
);
await tester.pumpWidget(
ProviderScope(
overrides: [
warehouseListProvider.overrideWith(() => _FakeWarehouseNotifier()),
customerListProvider.overrideWith(() => _FakeCustomerNotifier()),
productListProvider.overrideWith(() => _FakeProductNotifier()),
stockOutListProvider.overrideWith(() => _FailingStockOutNotifier()),
inventoryRepositoryProvider.overrideWith(
(ref) => _FakeInventoryRepository(),
),
],
child: MaterialApp.router(routerConfig: router),
),
);
await tester.pumpAndSettle();
await tester.tap(find.byType(DropdownButtonFormField<int>).at(0));
await tester.pumpAndSettle();
await tester.tap(find.text('主仓库').last);
await tester.pumpAndSettle();
await tester.tap(find.byType(DropdownButtonFormField<int>).at(2));
await tester.pumpAndSettle();
await tester.tap(find.text('五粮液(500ml').last);
await tester.pumpAndSettle();
await tester.enterText(find.byType(TextFormField).at(1), '5');
await tester.tap(find.text('提交审核'));
await tester.pump();
await tester.pumpAndSettle();
expect(find.textContaining('库存不足'), findsOneWidget);
expect(find.text('stock-out list'), findsNothing);
});
}