9f620e9fc7
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
209 lines
8.3 KiB
Dart
209 lines
8.3 KiB
Dart
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=1)isDefault=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);
|
||
});
|
||
}
|