Files
platforms/apps/user_app/test/ui/cart_page_test.dart

189 lines
6.8 KiB
Dart
Raw Normal View History

// 功能描述购物车服务端确认、失效处理、管理删除与多商品重试回归版本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);
});
}