已完成用户APP首期功能开发
交付用户端首期页面、配套接口、后台资源及测试文档。用户APP构建、静态分析和三个管理后台构建通过;完整测试仍有2项失败,后端模型注释检查未通过,详见交付记录。
This commit is contained in:
188
apps/user_app/test/ui/cart_page_test.dart
Normal file
188
apps/user_app/test/ui/cart_page_test.dart
Normal file
@@ -0,0 +1,188 @@
|
||||
// 功能描述:购物车服务端确认、失效处理、管理删除与多商品重试回归;版本:1.0.0。
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:user_app/domain/models/cart_item.dart';
|
||||
import 'package:user_app/domain/models/primary_models.dart';
|
||||
import 'package:user_app/domain/models/shipping_address.dart';
|
||||
import 'package:user_app/data/services/api_client.dart';
|
||||
import 'package:user_app/ui/features/shop/cart_page.dart';
|
||||
import 'package:user_app/ui/core/app_theme.dart';
|
||||
import 'package:user_app/ui/features/shop/checkout_page.dart';
|
||||
import 'package:user_app/ui/features/shop/product_detail_page.dart';
|
||||
import '../support/a1_fixture.dart';
|
||||
|
||||
class _Repo extends A1FixtureRepository {
|
||||
List<CartItem> items = [
|
||||
const CartItem(
|
||||
product: ProductSummary(identity: 'p1', name: '测试商品一', price: 1999, stock: 4),
|
||||
quantity: 1,
|
||||
selected: true,
|
||||
available: true,
|
||||
revision: 'one',
|
||||
),
|
||||
const CartItem(
|
||||
product: ProductSummary(identity: 'p2', name: '测试商品二', price: 3000, stock: 5),
|
||||
quantity: 2,
|
||||
selected: true,
|
||||
available: true,
|
||||
revision: 'two',
|
||||
),
|
||||
const CartItem(
|
||||
product: ProductSummary(identity: 'p3', name: '失效商品', price: 1000, stock: 0),
|
||||
quantity: 1,
|
||||
selected: true,
|
||||
available: false,
|
||||
revision: 'three',
|
||||
),
|
||||
];
|
||||
final submitted = <({String request, int amount, List<CartItem> items})>[];
|
||||
int writes = 0;
|
||||
bool failWrite = false;
|
||||
final targets = <int>[];
|
||||
@override
|
||||
Future<List<CartItem>> cart() async => List.of(items);
|
||||
@override
|
||||
Future<CartItem> cartItem(String identity) async =>
|
||||
items.firstWhere((i) => i.product.identity == identity);
|
||||
@override
|
||||
Future<CartItem> setCartItem(
|
||||
CartItem item, {
|
||||
required int quantity,
|
||||
required bool selected,
|
||||
}) async {
|
||||
writes++;
|
||||
targets.add(quantity);
|
||||
final updated = CartItem(
|
||||
product: item.product,
|
||||
quantity: quantity,
|
||||
selected: selected,
|
||||
available: item.available,
|
||||
revision: 'v$writes',
|
||||
);
|
||||
items = [
|
||||
for (final old in items)
|
||||
if (old.product.identity != item.product.identity) old else if (quantity > 0) updated,
|
||||
];
|
||||
if (failWrite) {
|
||||
failWrite = false;
|
||||
throw const ApiException(500, '结果待确认');
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, Object?>> submitCartOrder({
|
||||
required String requestNo,
|
||||
required List<CartItem> items,
|
||||
required ShippingAddress address,
|
||||
required int expectedAmount,
|
||||
String remark = '',
|
||||
}) async {
|
||||
submitted.add((request: requestNo, amount: expectedAmount, items: List.of(items)));
|
||||
if (submitted.length == 1) throw const ApiException(500, '提交结果未知');
|
||||
return {'identity': 'order'};
|
||||
}
|
||||
}
|
||||
|
||||
Future<GoRouter> _show(WidgetTester tester, _Repo repo, {String path = '/cart'}) async {
|
||||
final router = GoRouter(
|
||||
initialLocation: path,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/cart',
|
||||
builder: (_, s) => CartPage(repository: repo),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/cart/checkout',
|
||||
builder: (_, s) => CheckoutPage(repository: repo, productIdentity: '', fromCart: true),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/products/p1',
|
||||
builder: (_, s) => ProductDetailPage(repository: repo, identity: 'p1'),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/orders',
|
||||
builder: (_, s) => const Scaffold(body: Text('订单列表')),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/payment/shop/:identity',
|
||||
builder: (_, s) => Scaffold(body: Text('支付订单 ${s.pathParameters['identity']}')),
|
||||
),
|
||||
],
|
||||
);
|
||||
addTearDown(router.dispose);
|
||||
await tester.pumpWidget(MaterialApp.router(routerConfig: router, theme: AppTheme.light()));
|
||||
await tester.pumpAndSettle();
|
||||
return router;
|
||||
}
|
||||
|
||||
void main() {
|
||||
testWidgets('服务端数量与失败重读,全选、失效取消、管理删除', (tester) async {
|
||||
final repo = _Repo();
|
||||
await _show(tester, repo);
|
||||
expect(find.text('合计:¥79.99'), findsOneWidget);
|
||||
expect(find.text('商品已失效'), findsOneWidget);
|
||||
repo.failWrite = true;
|
||||
await tester.tap(find.byTooltip('增加 测试商品一'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(repo.items.first.quantity, 2);
|
||||
expect(find.text('合计:¥99.98'), findsOneWidget);
|
||||
expect(find.text('结果待确认'), findsOneWidget);
|
||||
await tester.tap(find.text('重新加载'));
|
||||
await tester.pumpAndSettle();
|
||||
final invalid = find.descendant(
|
||||
of: find.bySemanticsLabel('选择 失效商品'),
|
||||
matching: find.byType(Checkbox),
|
||||
);
|
||||
await tester.scrollUntilVisible(invalid, 180);
|
||||
await tester.drag(find.byType(Scrollable).first, const Offset(0, -120));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(invalid);
|
||||
await tester.pumpAndSettle();
|
||||
expect(repo.items.last.selected, isFalse);
|
||||
await tester.tap(find.text('管理'));
|
||||
await tester.pumpAndSettle();
|
||||
final all = find.descendant(of: find.bySemanticsLabel('全选商品'), matching: find.byType(Checkbox));
|
||||
await tester.tap(all);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('删除(3)'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.widgetWithText(TextButton, '删除'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(repo.items, isEmpty);
|
||||
expect(find.text('购物车还是空的'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('多商品确认与未知结果重试保留同一请求和整数金额', (tester) async {
|
||||
final repo = _Repo()..items.removeLast();
|
||||
await _show(tester, repo);
|
||||
await tester.tap(find.text('去结算(2)'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('测试商品一'), findsOneWidget);
|
||||
expect(find.text('测试商品二'), findsOneWidget);
|
||||
expect(repo.submitted, isEmpty);
|
||||
await tester.tap(find.widgetWithText(FilledButton, '提交订单'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.widgetWithText(FilledButton, '重试提交'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(repo.submitted.length, 2);
|
||||
expect(repo.submitted[0].request, repo.submitted[1].request);
|
||||
expect(repo.submitted[0].amount, 7999);
|
||||
expect(repo.submitted[1].items.last.quantity, 2);
|
||||
expect(find.text('支付订单 order'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('加入购物车未知结果重试不在新数量上重复累加', (tester) async {
|
||||
final repo = _Repo()..failWrite = true;
|
||||
await _show(tester, repo, path: '/products/p1');
|
||||
await tester.ensureVisible(find.text('加入购物车'));
|
||||
await tester.tap(find.text('加入购物车'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(repo.targets, [2]);
|
||||
await tester.tap(find.text('重试加入'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(repo.targets, [2, 2]);
|
||||
expect(repo.items.first.quantity, 2);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user