// 功能描述:收藏版本契约、跨入口同步及账号切换迟到响应保护;版本:1.0.0。 import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart' as http; import 'package:http/testing.dart'; import 'package:user_app/data/repositories/client_repository.dart'; import 'package:user_app/data/services/api_client.dart'; import 'package:user_app/domain/models/product_favorite.dart'; import 'package:user_app/ui/features/shop/favorite_button.dart'; http.Response _response(Object details) => http.Response( jsonEncode({'code': 0, 'details': details}), 200, headers: {'content-type': 'application/json; charset=utf-8'}, ); void main() { testWidgets('两个入口同步服务端收藏状态,失败读取后不反转图标', (tester) async { var active = false, version = 0, fail = false; final repo = ClientRepository( ApiClient( () => 'test', client: MockClient((request) async { if (request.method == 'PUT') { final body = jsonDecode(request.body) as Map; expect(body['revision'], version == 0 ? '' : 'f:$version'); if (fail) return http.Response('{"code":500}', 200); active = body['favorite'] as bool; version++; } return _response({ 'product_identity': 'p1', 'favorite': active, 'revision': version == 0 ? '' : 'f:$version', }); }), ), ); await tester.pumpWidget( MaterialApp( home: Scaffold( body: Row( children: [ for (var i = 0; i < 2; i++) FavoriteButton(repository: repo, identity: 'p1', redirect: '/shop', name: '商品'), ], ), ), ), ); await tester.pumpAndSettle(); expect(find.byTooltip('收藏 商品'), findsNWidgets(2)); await tester.tap(find.byTooltip('收藏 商品').first); await tester.pumpAndSettle(); expect(find.byTooltip('取消收藏 商品'), findsNWidgets(2)); fail = true; await tester.tap(find.byTooltip('取消收藏 商品').first); await tester.pumpAndSettle(); expect(find.byTooltip('取消收藏 商品'), findsNWidgets(2)); }); test('账号切换后不广播前一账号收藏结果', () async { var token = 'account-a'; final pending = Completer(); final events = []; final repo = ClientRepository( ApiClient(() => token, client: MockClient((_) => pending.future)), ); final subscription = repo.favoriteChanges.listen(events.add); addTearDown(subscription.cancel); final result = repo.setFavorite( const ProductFavoriteState(productIdentity: 'p1', active: false, revision: ''), true, ); final assertion = expectLater(result, throwsA(isA())); token = 'account-b'; pending.complete(_response({'product_identity': 'p1', 'favorite': true, 'revision': 'f:1'})); await assertion; expect(events, isEmpty); }); test('收藏分页、图片URL及金额数据校验', () async { var reads = 0; final repo = ClientRepository( ApiClient( () => 'test', client: MockClient((request) async { reads++; final page = int.parse(request.url.queryParameters['page']!); return _response({ 'items': [ { 'product_identity': 'p$page', 'favorite': true, 'revision': 'r$page', 'name': '商品$page', 'price_amount': 16900, 'stock_quantity': 0, 'available': false, 'category_name': '配件', 'image_url': 'javascript:bad', 'specifications': ['家用'], }, ], 'has_more': page == 1, 'page': page, 'page_size': 30, }); }), ), ); final items = await repo.favorites(); expect(reads, 2); expect(items.length, 2); expect(items.first.product.imageUrl, ''); expect(items.first.available, isFalse); expect( () => ProductFavoriteState.fromJson({ 'product_identity': 'p1', 'favorite': 'true', 'revision': '', }), throwsFormatException, ); }); }