import 'package:patbond_flutter/features/community/community_models.dart'; import 'package:patbond_flutter/features/community/community_repository.dart'; /// community 域测试样本 JSON(字段与契约 v1.3.0 逐字一致)。 Map sampleAuthorJson({ String userId = 'u-1', String? nickname = '毛毛的铲屎官', String? avatarUrl = 'https://minio.local/avatar.jpg?X-Amz-Signature=sig', }) => {'userId': userId, 'nickname': nickname, 'avatarUrl': avatarUrl}; Map samplePostMediaItemJson({ String assetId = 'a-1', int position = 0, bool isCover = true, }) => { 'assetId': assetId, 'position': position, 'isCover': isCover, 'url': 'https://minio.local/p.jpg?X-Amz-Signature=sig', 'widthPx': 1080, 'heightPx': 810, 'caption': '晒太阳', }; Map samplePostJson({ String id = 'p-1', String status = 'published', bool likedByMe = false, int likeCount = 6, bool bookmarkedByMe = false, int bookmarkCount = 2, int version = 1, }) => { 'id': id, 'author': sampleAuthorJson(), 'petId': 'pet-1', 'category': 'general', 'title': '今天的豆豆', 'content': '晒了一下午太阳。', 'status': status, 'visibility': 'public', 'media': [samplePostMediaItemJson()], 'likeCount': likeCount, 'commentCount': 3, 'bookmarkCount': bookmarkCount, 'likedByMe': likedByMe, 'bookmarkedByMe': bookmarkedByMe, 'createdAt': '2026-09-08T10:00:00.000Z', 'updatedAt': '2026-09-08T10:05:00.000Z', 'publishedAt': status == 'published' ? '2026-09-08T10:05:00.000Z' : null, 'version': version, }; Map sampleFeedCardJson({ String id = 'p-1', bool likedByMe = false, int likeCount = 6, bool bookmarkedByMe = false, int bookmarkCount = 2, }) => { 'id': id, 'author': sampleAuthorJson(), 'category': 'general', 'title': '今天的豆豆', 'contentPreview': '晒了一下午太阳。', 'coverImage': samplePostMediaItemJson(), 'mediaCount': 1, 'likeCount': likeCount, 'commentCount': 3, 'bookmarkCount': bookmarkCount, 'likedByMe': likedByMe, 'bookmarkedByMe': bookmarkedByMe, 'publishedAt': '2026-09-08T10:05:00.000Z', }; Map sampleCommentJson({ String id = 'c-1', Map? replyToUser, }) => { 'id': id, 'postId': 'p-1', 'author': sampleAuthorJson(), 'replyToUser': replyToUser, 'content': '好可爱!', 'createdAt': '2026-09-08T11:00:00.000Z', }; Map sampleUploadCredentialsJson() => { 'assetId': 'a-1', 'uploadUrl': 'https://minio.local/patbond-media/post_image/a-1?X-Amz-Signature=sig', 'method': 'PUT', 'requiredHeaders': {'Content-Type': 'image/jpeg'}, 'expiresAt': '2026-09-08T10:10:00.000Z', }; Map sampleMediaAssetJson({String status = 'ready'}) => { 'id': 'a-1', 'kind': 'image', 'purpose': 'post_image', 'mimeType': 'image/jpeg', 'byteSize': 204800, 'widthPx': 1080, 'heightPx': 810, 'status': status, 'url': status == 'ready' ? 'https://minio.local/p.jpg?X-Amz-Signature=sig' : null, 'readyAt': status == 'ready' ? '2026-09-08T10:06:00.000Z' : null, 'createdAt': '2026-09-08T10:00:00.000Z', }; Map cursorPageJson( List> items, { String? nextCursor, bool hasMore = false, }) => {'items': items, 'nextCursor': nextCursor, 'hasMore': hasMore}; FeedCard sampleFeedCard({ String id = 'p-1', bool likedByMe = false, int likeCount = 6, }) => FeedCard.fromJson( sampleFeedCardJson(id: id, likedByMe: likedByMe, likeCount: likeCount), ); CursorPage feedPage( List items, { String? nextCursor, bool hasMore = false, }) => CursorPage(items: items, nextCursor: nextCursor, hasMore: hasMore); /// 假仓库:controller 测试注入行为并记录调用(Completer 控时序)。 /// 未注入 handler 的方法一律 UnimplementedError(误触发即测试失败)。 class FakeCommunityRepository implements CommunityRepository { /// 调用日志,如 `feed:cursor=null`、`like:p-1`、`unlike:p-1`。 final List calls = []; Future> Function(int? limit, String? cursor)? onFeed; Future Function(String postId, bool target)? onLikeToggle; Future Function(String postId, bool target)? onBookmarkToggle; Future Function(String postId)? onGetPost; Future Function(CreateMediaUploadRequest request)? onCreateMediaUpload; Future Function(String assetId)? onCompleteMediaUpload; @override Future> getFeed({int? limit, String? cursor}) { calls.add('feed:cursor=$cursor'); return onFeed!(limit, cursor); } @override Future likePost(String postId) { calls.add('like:$postId'); return onLikeToggle!(postId, true); } @override Future unlikePost(String postId) { calls.add('unlike:$postId'); return onLikeToggle!(postId, false); } @override Future bookmarkPost(String postId) { calls.add('bookmark:$postId'); return onBookmarkToggle!(postId, true); } @override Future unbookmarkPost(String postId) { calls.add('unbookmark:$postId'); return onBookmarkToggle!(postId, false); } @override Future getPost(String postId) { calls.add('getPost:$postId'); return onGetPost!(postId); } @override Future createMediaUpload( CreateMediaUploadRequest request, ) { calls.add('createUpload:${request.mimeType}:${request.byteSize}'); return onCreateMediaUpload!(request); } @override Future completeMediaUpload(String assetId) { calls.add('complete:$assetId'); return onCompleteMediaUpload!(assetId); } @override Future createPost(CreatePostRequest request) => throw UnimplementedError(); @override Future updatePost(String postId, UpdatePostRequest request) => throw UnimplementedError(); @override Future deletePost(String postId) => throw UnimplementedError(); @override Future> listMyPosts({ int? limit, String? cursor, PostStatus? status, }) => throw UnimplementedError(); @override Future> listComments( String postId, { int? limit, String? cursor, }) => throw UnimplementedError(); @override Future createComment( String postId, CreateCommentRequest request, ) => throw UnimplementedError(); @override Future deleteComment(String commentId) => throw UnimplementedError(); @override Future> listMyBookmarks({int? limit, String? cursor}) => throw UnimplementedError(); @override Future followUser(String userId) => throw UnimplementedError(); @override Future unfollowUser(String userId) => throw UnimplementedError(); @override Future getFollowStats(String userId) => throw UnimplementedError(); }