Files
jiu/client/test/stock_in_form_pick_test.dart
T
2026-07-14 11:16:29 +08:00

209 lines
8.3 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: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/theme/themes.dart';
import 'package:jiu_client/models/product_option.dart';
import 'package:jiu_client/models/stock_in.dart';
import 'package:jiu_client/models/warehouse.dart';
import 'package:jiu_client/providers/stock_in_provider.dart';
import 'package:jiu_client/providers/warehouse_provider.dart';
import 'package:jiu_client/providers/product_option_provider.dart';
import 'package:jiu_client/repositories/stock_in_repository.dart';
import 'package:jiu_client/screens/stock_in/stock_in_form_screen.dart';
import 'package:jiu_client/widgets/ds/grid_combo_cell.dart';
class _FakeWarehouses extends WarehouseListNotifier {
@override
Future<List<Warehouse>> build() async =>
const [Warehouse(id: 1, name: '主仓', isDefault: true)];
}
/// 可配置仓库列表的假 notifier(T5 默认仓填充回归用)。
class _ConfigurableWarehouses extends WarehouseListNotifier {
final List<Warehouse> _list;
_ConfigurableWarehouses(this._list);
@override
Future<List<Warehouse>> build() async => _list;
}
/// 编辑模式假仓库:get() 返回固定单据(供 _loadEditOrder 消费)。
class _FakeStockInRepoForEdit extends StockInRepository {
final StockInOrder order;
_FakeStockInRepoForEdit(this.order) : super(ApiClient(token: 't'));
@override
Future<StockInOrder> get(int id) async => order;
}
class _FakeNames extends ProductNameListNotifier {
@override
Future<List<ProductNameOption>> build() async => const [
ProductNameOption(id: 10, name: '茅台飞天', code: 'P001'),
ProductNameOption(id: 11, name: '五粮液', code: 'P002'),
];
}
class _FakeSeries extends ProductSeriesListNotifier {
@override
Future<List<ProductSeriesOption>> build() async => const [
ProductSeriesOption(id: 20, name: '53度'),
];
}
class _FakeSpecs extends ProductSpecListNotifier {
@override
Future<List<ProductSpecOption>> build() async => const [
ProductSpecOption(id: 30, name: '500ml'),
];
}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
SharedPreferences.setMockInitialValues({});
testWidgets('stock-in form: picking 商品名称 fills the cell', (tester) async {
tester.view.physicalSize = const Size(1400, 1000);
tester.view.devicePixelRatio = 1.0;
addTearDown(tester.view.resetPhysicalSize);
await tester.pumpWidget(ProviderScope(
overrides: [
warehouseListProvider.overrideWith(() => _FakeWarehouses()),
productNameListProvider.overrideWith(() => _FakeNames()),
productSeriesListProvider.overrideWith(() => _FakeSeries()),
productSpecListProvider.overrideWith(() => _FakeSpecs()),
isReadonlyProvider.overrideWithValue(false),
],
child: MaterialApp(
theme: buildTheme('a'),
home: const Scaffold(body: StockInFormScreen()),
),
));
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
// Tap the 商品名称 cell to focus/open its popup.
final nameCell = find.text('选择商品名称');
expect(nameCell, findsWidgets);
await tester.tap(nameCell.first);
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
// The option list should appear.
expect(find.text('五粮液'), findsWidgets,
reason: 'popup list should show options');
// Tap the option.
await tester.tap(find.text('五粮液').last);
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
// After picking, the cell should display 五粮液 and the hint should be gone.
expect(find.text('五粮液'), findsOneWidget,
reason: 'cell should show the picked name');
expect(find.text('选择商品名称'), findsNothing,
reason: 'hint should be replaced by the picked name');
});
// ── T5:新建入库单默认填充默认仓库(2026-07-14)──────────────────────
//
// _initWarehouseDefault 语义:仓库列表加载完成后,
// isDefault 的仓 优先;无默认时仅剩 1 个仓才取它;否则保持未选(null)。
// 编辑模式不受影响(_loadEditOrder 已从单据自身仓库回填)。
int? _warehouseSelectedId(WidgetTester tester) => tester
.widgetList<GridComboCell>(find.byType(GridComboCell))
.firstWhere((c) => c.hint == '选择仓库…')
.selectedId;
Future<void> _pumpForm(WidgetTester tester, List<Override> overrides,
{int? editOrderId}) async {
tester.view.physicalSize = const Size(1400, 1000);
tester.view.devicePixelRatio = 1.0;
addTearDown(tester.view.resetPhysicalSize);
await tester.pumpWidget(ProviderScope(
overrides: overrides,
child: MaterialApp(
theme: buildTheme('a'),
home: Scaffold(body: StockInFormScreen(editOrderId: editOrderId)),
),
));
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
// _initWarehouseDefault / _loadEditOrder 都是异步(await provider.future),
// 多 pump 几拍确保 setState 落地。
await tester.pump(const Duration(milliseconds: 100));
}
testWidgets('新建入库单:有默认仓(isDefault=true)时自动选中默认仓',
(tester) async {
await _pumpForm(tester, [
warehouseListProvider.overrideWith(() => _FakeWarehouses()), // 主仓 isDefault
productNameListProvider.overrideWith(() => _FakeNames()),
productSeriesListProvider.overrideWith(() => _FakeSeries()),
productSpecListProvider.overrideWith(() => _FakeSpecs()),
isReadonlyProvider.overrideWithValue(false),
]);
expect(_warehouseSelectedId(tester), 1);
});
testWidgets('新建入库单:无默认仓且多仓时保持未选', (tester) async {
await _pumpForm(tester, [
warehouseListProvider.overrideWith(() => _ConfigurableWarehouses(const [
Warehouse(id: 1, name: '主仓', isDefault: false),
Warehouse(id: 2, name: '分仓', isDefault: false),
])),
productNameListProvider.overrideWith(() => _FakeNames()),
productSeriesListProvider.overrideWith(() => _FakeSeries()),
productSpecListProvider.overrideWith(() => _FakeSpecs()),
isReadonlyProvider.overrideWithValue(false),
]);
expect(_warehouseSelectedId(tester), isNull);
});
testWidgets('新建入库单:无默认仓但仅有 1 个仓时自动选中该仓', (tester) async {
await _pumpForm(tester, [
warehouseListProvider.overrideWith(() => _ConfigurableWarehouses(const [
Warehouse(id: 7, name: '唯一仓', isDefault: false),
])),
productNameListProvider.overrideWith(() => _FakeNames()),
productSeriesListProvider.overrideWith(() => _FakeSeries()),
productSpecListProvider.overrideWith(() => _FakeSpecs()),
isReadonlyProvider.overrideWithValue(false),
]);
expect(_warehouseSelectedId(tester), 7);
});
testWidgets('编辑模式:不被默认仓覆盖,仍显示单据原仓库', (tester) async {
const order = StockInOrder(
id: 99,
orderNo: 'SI2024010099',
warehouseId: 2, // 单据原仓库 = 分仓(非默认仓)
status: 'draft',
items: [],
);
await _pumpForm(
tester,
[
// 主仓(id=1isDefault=true,若默认仓填充逻辑误在编辑模式生效会把它选中,
// 但单据自身仓库是 id=2,回归断言二者不一致时以单据为准。
warehouseListProvider.overrideWith(() => _ConfigurableWarehouses(const [
Warehouse(id: 1, name: '主仓', isDefault: true),
Warehouse(id: 2, name: '分仓', isDefault: false),
])),
stockInRepositoryProvider
.overrideWithValue(_FakeStockInRepoForEdit(order)),
productNameListProvider.overrideWith(() => _FakeNames()),
productSeriesListProvider.overrideWith(() => _FakeSeries()),
productSpecListProvider.overrideWith(() => _FakeSpecs()),
isReadonlyProvider.overrideWithValue(false),
],
editOrderId: 99,
);
expect(_warehouseSelectedId(tester), 2);
});
}