diff --git a/docs/code_snippets/06_03_comments.dart b/docs/code_snippets/06_03_comments.dart index ccca8432..0e58dd50 100644 --- a/docs/code_snippets/06_03_comments.dart +++ b/docs/code_snippets/06_03_comments.dart @@ -119,3 +119,58 @@ Future commentThreading() async { ); final replies = await replyList.get(); } + +Future restrictReplies() async { + // Post an activity that restricts who can reply/comment. + // Options: everyone (default), nobody, peopleIFollow. + // Note: the activity author can always comment regardless of this setting. + await feed.addActivity( + request: const FeedAddActivityRequest( + type: 'post', + text: 'Only my followers can reply to this.', + restrictReplies: AddActivityRequestRestrictReplies.peopleIFollow, + ), + ); + + // To check whether the current user may comment on an activity whose + // restrictReplies is peopleIFollow, the server must enrich the response with + // the author's own_followings. Enable this via enrichmentOptions on the feed + // query — it is not included in the default getOrCreate() response. + final enrichedFeed = client.feedFromQuery( + const FeedQuery( + fid: FeedId(group: 'user', id: 'john'), + enrichmentOptions: EnrichmentOptions(enrichOwnFollowings: true), + ), + ); + await enrichedFeed.getOrCreate(); + final activity = enrichedFeed.state.activities.firstOrNull; + if (activity == null) return; + + switch (activity.restrictReplies) { + case ActivityRestrictReplies.everyone: + print('Anyone can comment'); + case ActivityRestrictReplies.nobody: + print('Comments are disabled'); + case ActivityRestrictReplies.peopleIFollow: + // own_followings lists follow relationships where the source feed is the + // activity author's and the target is the current user's feed. A non-empty + // list means the activity author follows the current user, so they may + // comment. + final ownFollowings = activity.currentFeed?.ownFollowings ?? []; + final canComment = ownFollowings.isNotEmpty; + print('Can current user comment? $canComment'); + default: // future/unknown values from the server + print('Unknown restriction: ${activity.restrictReplies}'); + } +} + +Future updateRestrictReplies() async { + // Update an existing activity's reply restriction after it was created. + // restrictReplies is passed directly on the generated UpdateActivityRequest. + await feed.updateActivity( + id: 'activity-id', + request: const UpdateActivityRequest( + restrictReplies: UpdateActivityRequestRestrictReplies.nobody, + ), + ); +} diff --git a/packages/stream_feeds/CHANGELOG.md b/packages/stream_feeds/CHANGELOG.md index 1b00fdc3..29e1c245 100644 --- a/packages/stream_feeds/CHANGELOG.md +++ b/packages/stream_feeds/CHANGELOG.md @@ -1,6 +1,9 @@ ## Upcoming ### New fields +- Added `restrictReplies` (`ActivityRestrictReplies`) to `ActivityData` to expose the comment-reply restriction on an activity (everyone / nobody / people_i_follow). +- Added `restrictReplies` (`AddActivityRequestRestrictReplies?`) to `FeedAddActivityRequest` so comment restrictions can be set when creating an activity. +- Added `enrichmentOptions` (`EnrichmentOptions?`) to `FeedQuery` so optional server enrichment can be enabled per feed. Use `EnrichmentOptions(enrichOwnFollowings: true)` to receive `ownFollowings` on each activity's feed — required to determine whether the current user may comment when an activity's `restrictReplies` is `people_i_follow`. - Added `isRead` and `isSeen` fields to `ActivityData` and `AggregatedActivityData` for notification-feed read/seen state. - Added `friendReactionCount` and `friendReactions` fields to `ActivityData` to expose reactions from friends. - Added `metrics` field to `ActivityData` for server-side activity metrics (impressions, clicks, etc.). diff --git a/packages/stream_feeds/lib/src/models/activity_data.dart b/packages/stream_feeds/lib/src/models/activity_data.dart index 41c6705e..f354b1e8 100644 --- a/packages/stream_feeds/lib/src/models/activity_data.dart +++ b/packages/stream_feeds/lib/src/models/activity_data.dart @@ -60,6 +60,7 @@ class ActivityData with _$ActivityData { this.preview = false, this.reactionCount = 0, this.reactionGroups = const {}, + this.restrictReplies = ActivityRestrictReplies.everyone, this.score = 0.0, this.searchData = const {}, this.shareCount = 0, @@ -226,6 +227,17 @@ class ActivityData with _$ActivityData { @override final Map reactionGroups; + /// Who is allowed to add comments/replies to this activity. + /// + /// Controls the comment restriction setting for this activity: + /// - [ActivityRestrictReplies.everyone] (default) — anyone can comment. + /// - [ActivityRestrictReplies.nobody] — comments are disabled. + /// - [ActivityRestrictReplies.peopleIFollow] — only users followed by the + /// activity author may comment. Use [FeedData.ownFollowings] from the + /// current feed to decide whether to show the comment input. + @override + final ActivityRestrictReplies restrictReplies; + /// A relevance or quality score assigned to the activity. @override final double score; @@ -348,6 +360,7 @@ extension ActivityResponseMapper on ActivityResponse { reactionGroups: { for (final entry in reactionGroups.entries) entry.key: entry.value.toModel(), }, + restrictReplies: restrictReplies.toModel(), score: score, searchData: searchData, shareCount: shareCount, @@ -613,6 +626,37 @@ extension ActivityDataMutations on ActivityData { } } +/// Controls who can add comments/replies to an activity. +/// +/// Implemented as an extension type so any future server-side values are +/// handled without requiring a client update. +extension type const ActivityRestrictReplies(String value) implements String { + /// Anyone can comment on the activity (default). + static const everyone = ActivityRestrictReplies('everyone'); + + /// No one can comment on the activity. + static const nobody = ActivityRestrictReplies('nobody'); + + /// Only users followed by the activity author can comment. + static const peopleIFollow = ActivityRestrictReplies('people_i_follow'); + + /// Unknown value received from the API. + static const unknown = ActivityRestrictReplies('unknown'); +} + +/// Extension function to convert an [ActivityResponseRestrictReplies] to an [ActivityRestrictReplies]. +extension ActivityResponseRestrictRepliesMapper on ActivityResponseRestrictReplies { + /// Converts this API restrict-replies enum to the domain [ActivityRestrictReplies]. + ActivityRestrictReplies toModel() { + return switch (this) { + ActivityResponseRestrictReplies.everyone => ActivityRestrictReplies.everyone, + ActivityResponseRestrictReplies.nobody => ActivityRestrictReplies.nobody, + ActivityResponseRestrictReplies.peopleIFollow => ActivityRestrictReplies.peopleIFollow, + ActivityResponseRestrictReplies.unknown => ActivityRestrictReplies.unknown, + }; + } +} + /// Extension function to convert an [ActivityResponseVisibility] to an [ActivityDataVisibility]. extension ActivityResponseVisibilityMapper on ActivityResponseVisibility { /// Converts this API visibility enum to a domain [ActivityDataVisibility] extension type. diff --git a/packages/stream_feeds/lib/src/models/activity_data.freezed.dart b/packages/stream_feeds/lib/src/models/activity_data.freezed.dart index d9786c54..2d73a82c 100644 --- a/packages/stream_feeds/lib/src/models/activity_data.freezed.dart +++ b/packages/stream_feeds/lib/src/models/activity_data.freezed.dart @@ -47,6 +47,7 @@ mixin _$ActivityData { bool get preview; int get reactionCount; Map get reactionGroups; + ActivityRestrictReplies get restrictReplies; double get score; Map get searchData; int get shareCount; @@ -138,6 +139,7 @@ mixin _$ActivityData { other.reactionGroups, reactionGroups, ) && + (identical(other.restrictReplies, restrictReplies) || other.restrictReplies == restrictReplies) && (identical(other.score, score) || other.score == score) && const DeepCollectionEquality().equals( other.searchData, @@ -190,6 +192,7 @@ mixin _$ActivityData { preview, reactionCount, const DeepCollectionEquality().hash(reactionGroups), + restrictReplies, score, const DeepCollectionEquality().hash(searchData), shareCount, @@ -204,7 +207,7 @@ mixin _$ActivityData { @override String toString() { - return 'ActivityData(attachments: $attachments, bookmarkCount: $bookmarkCount, collections: $collections, commentCount: $commentCount, comments: $comments, createdAt: $createdAt, currentFeed: $currentFeed, deletedAt: $deletedAt, editedAt: $editedAt, expiresAt: $expiresAt, feeds: $feeds, filterTags: $filterTags, friendReactionCount: $friendReactionCount, friendReactions: $friendReactions, id: $id, interestTags: $interestTags, isRead: $isRead, isSeen: $isSeen, isWatched: $isWatched, latestReactions: $latestReactions, location: $location, mentionedUsers: $mentionedUsers, metrics: $metrics, moderation: $moderation, notificationContext: $notificationContext, ownBookmarks: $ownBookmarks, ownReactions: $ownReactions, parent: $parent, poll: $poll, popularity: $popularity, hidden: $hidden, preview: $preview, reactionCount: $reactionCount, reactionGroups: $reactionGroups, score: $score, searchData: $searchData, shareCount: $shareCount, text: $text, type: $type, updatedAt: $updatedAt, user: $user, visibility: $visibility, visibilityTag: $visibilityTag, custom: $custom)'; + return 'ActivityData(attachments: $attachments, bookmarkCount: $bookmarkCount, collections: $collections, commentCount: $commentCount, comments: $comments, createdAt: $createdAt, currentFeed: $currentFeed, deletedAt: $deletedAt, editedAt: $editedAt, expiresAt: $expiresAt, feeds: $feeds, filterTags: $filterTags, friendReactionCount: $friendReactionCount, friendReactions: $friendReactions, id: $id, interestTags: $interestTags, isRead: $isRead, isSeen: $isSeen, isWatched: $isWatched, latestReactions: $latestReactions, location: $location, mentionedUsers: $mentionedUsers, metrics: $metrics, moderation: $moderation, notificationContext: $notificationContext, ownBookmarks: $ownBookmarks, ownReactions: $ownReactions, parent: $parent, poll: $poll, popularity: $popularity, hidden: $hidden, preview: $preview, reactionCount: $reactionCount, reactionGroups: $reactionGroups, restrictReplies: $restrictReplies, score: $score, searchData: $searchData, shareCount: $shareCount, text: $text, type: $type, updatedAt: $updatedAt, user: $user, visibility: $visibility, visibilityTag: $visibilityTag, custom: $custom)'; } } @@ -250,6 +253,7 @@ abstract mixin class $ActivityDataCopyWith<$Res> { bool preview, int reactionCount, Map reactionGroups, + ActivityRestrictReplies restrictReplies, double score, Map searchData, int shareCount, @@ -309,6 +313,7 @@ class _$ActivityDataCopyWithImpl<$Res> implements $ActivityDataCopyWith<$Res> { Object? preview = null, Object? reactionCount = null, Object? reactionGroups = null, + Object? restrictReplies = null, Object? score = null, Object? searchData = null, Object? shareCount = null, @@ -458,6 +463,10 @@ class _$ActivityDataCopyWithImpl<$Res> implements $ActivityDataCopyWith<$Res> { ? _self.reactionGroups : reactionGroups // ignore: cast_nullable_to_non_nullable as Map, + restrictReplies: null == restrictReplies + ? _self.restrictReplies + : restrictReplies // ignore: cast_nullable_to_non_nullable + as ActivityRestrictReplies, score: null == score ? _self.score : score // ignore: cast_nullable_to_non_nullable diff --git a/packages/stream_feeds/lib/src/models/request/feed_add_activity_request.dart b/packages/stream_feeds/lib/src/models/request/feed_add_activity_request.dart index d6cbb400..6a38600f 100644 --- a/packages/stream_feeds/lib/src/models/request/feed_add_activity_request.dart +++ b/packages/stream_feeds/lib/src/models/request/feed_add_activity_request.dart @@ -31,6 +31,7 @@ class FeedAddActivityRequest with _$FeedAddActivityRequest implements HasAttachm this.mentionedUserIds, this.parentId, this.pollId, + this.restrictReplies, this.searchData, this.skipPush, this.text, @@ -107,6 +108,15 @@ class FeedAddActivityRequest with _$FeedAddActivityRequest implements HasAttachm @override final String? pollId; + /// Controls who can add comments/replies to this activity. + /// + /// Defaults to [AddActivityRequestRestrictReplies.everyone] (no restriction). + /// Use [AddActivityRequestRestrictReplies.nobody] to disable comments, or + /// [AddActivityRequestRestrictReplies.peopleIFollow] to restrict to users + /// followed by the activity author. + @override + final AddActivityRequestRestrictReplies? restrictReplies; + /// Optional search metadata for enhanced discoverability. @override final Map? searchData; @@ -169,6 +179,7 @@ extension FeedAddActivityRequestMapper on FeedAddActivityRequest { mentionedUserIds: mentionedUserIds, parentId: parentId, pollId: pollId, + restrictReplies: restrictReplies, searchData: searchData, skipPush: skipPush, text: text, diff --git a/packages/stream_feeds/lib/src/models/request/feed_add_activity_request.freezed.dart b/packages/stream_feeds/lib/src/models/request/feed_add_activity_request.freezed.dart index e41cd78b..a676bc77 100644 --- a/packages/stream_feeds/lib/src/models/request/feed_add_activity_request.freezed.dart +++ b/packages/stream_feeds/lib/src/models/request/feed_add_activity_request.freezed.dart @@ -28,6 +28,7 @@ mixin _$FeedAddActivityRequest { List? get mentionedUserIds; String? get parentId; String? get pollId; + AddActivityRequestRestrictReplies? get restrictReplies; Map? get searchData; bool? get skipPush; String? get text; @@ -87,6 +88,7 @@ mixin _$FeedAddActivityRequest { ) && (identical(other.parentId, parentId) || other.parentId == parentId) && (identical(other.pollId, pollId) || other.pollId == pollId) && + (identical(other.restrictReplies, restrictReplies) || other.restrictReplies == restrictReplies) && const DeepCollectionEquality().equals( other.searchData, searchData, @@ -116,6 +118,7 @@ mixin _$FeedAddActivityRequest { const DeepCollectionEquality().hash(mentionedUserIds), parentId, pollId, + restrictReplies, const DeepCollectionEquality().hash(searchData), skipPush, text, @@ -126,7 +129,7 @@ mixin _$FeedAddActivityRequest { @override String toString() { - return 'FeedAddActivityRequest(attachments: $attachments, attachmentUploads: $attachmentUploads, collectionRefs: $collectionRefs, createNotificationActivity: $createNotificationActivity, custom: $custom, enrichOwnFields: $enrichOwnFields, expiresAt: $expiresAt, feeds: $feeds, filterTags: $filterTags, id: $id, interestTags: $interestTags, location: $location, mentionedUserIds: $mentionedUserIds, parentId: $parentId, pollId: $pollId, searchData: $searchData, skipPush: $skipPush, text: $text, type: $type, visibility: $visibility, visibilityTag: $visibilityTag)'; + return 'FeedAddActivityRequest(attachments: $attachments, attachmentUploads: $attachmentUploads, collectionRefs: $collectionRefs, createNotificationActivity: $createNotificationActivity, custom: $custom, enrichOwnFields: $enrichOwnFields, expiresAt: $expiresAt, feeds: $feeds, filterTags: $filterTags, id: $id, interestTags: $interestTags, location: $location, mentionedUserIds: $mentionedUserIds, parentId: $parentId, pollId: $pollId, restrictReplies: $restrictReplies, searchData: $searchData, skipPush: $skipPush, text: $text, type: $type, visibility: $visibility, visibilityTag: $visibilityTag)'; } } @@ -154,6 +157,7 @@ abstract mixin class $FeedAddActivityRequestCopyWith<$Res> { List? mentionedUserIds, String? parentId, String? pollId, + AddActivityRequestRestrictReplies? restrictReplies, Map? searchData, bool? skipPush, String? text, @@ -190,6 +194,7 @@ class _$FeedAddActivityRequestCopyWithImpl<$Res> implements $FeedAddActivityRequ Object? mentionedUserIds = freezed, Object? parentId = freezed, Object? pollId = freezed, + Object? restrictReplies = freezed, Object? searchData = freezed, Object? skipPush = freezed, Object? text = freezed, @@ -262,6 +267,10 @@ class _$FeedAddActivityRequestCopyWithImpl<$Res> implements $FeedAddActivityRequ ? _self.pollId : pollId // ignore: cast_nullable_to_non_nullable as String?, + restrictReplies: freezed == restrictReplies + ? _self.restrictReplies + : restrictReplies // ignore: cast_nullable_to_non_nullable + as AddActivityRequestRestrictReplies?, searchData: freezed == searchData ? _self.searchData : searchData // ignore: cast_nullable_to_non_nullable diff --git a/packages/stream_feeds/lib/src/state/query/feed_query.dart b/packages/stream_feeds/lib/src/state/query/feed_query.dart index de7c226f..9da3deb6 100644 --- a/packages/stream_feeds/lib/src/state/query/feed_query.dart +++ b/packages/stream_feeds/lib/src/state/query/feed_query.dart @@ -3,6 +3,7 @@ import 'package:stream_core/stream_core.dart'; import '../../generated/api/models.dart'; import '../../models/activity_data.dart'; +import '../../models/feed_data.dart'; import '../../models/feed_id.dart'; import '../../models/feed_input_data.dart'; import '../../utils/filter.dart'; @@ -33,6 +34,7 @@ class FeedQuery with _$FeedQuery { this.activityNext, this.activityPrevious, this.data, + this.enrichmentOptions, this.externalRanking, this.followerLimit, this.followingLimit, @@ -68,6 +70,25 @@ class FeedQuery with _$FeedQuery { @override final FeedInputData? data; + /// Controls which optional fields the server enriches the response with. + /// + /// Pass [EnrichmentOptions] to enable enrichment that is not included by + /// default. For example, set [EnrichmentOptions.enrichOwnFollowings] to + /// `true` to receive [FeedData.ownFollowings] on each activity's current + /// feed, which is required to determine whether the current user may comment + /// when an activity's `restrictReplies` is set to `people_i_follow`. + /// + /// ```dart + /// client.feedFromQuery( + /// FeedQuery( + /// fid: feedId, + /// enrichmentOptions: const EnrichmentOptions(enrichOwnFollowings: true), + /// ), + /// ); + /// ``` + @override + final EnrichmentOptions? enrichmentOptions; + /// Additional data used for ranking activities in the feed. @override final Map? externalRanking; @@ -107,6 +128,7 @@ extension FeedQueryRequest on FeedQuery { view: view, watch: watch, data: data?.toRequest(), + enrichmentOptions: enrichmentOptions, externalRanking: externalRanking, filter: activityFilter?.toRequest(), followersPagination: followerLimit?.let((it) => PagerRequest(limit: it)), diff --git a/packages/stream_feeds/lib/src/state/query/feed_query.freezed.dart b/packages/stream_feeds/lib/src/state/query/feed_query.freezed.dart index 7c01318a..215ed4bf 100644 --- a/packages/stream_feeds/lib/src/state/query/feed_query.freezed.dart +++ b/packages/stream_feeds/lib/src/state/query/feed_query.freezed.dart @@ -19,6 +19,7 @@ mixin _$FeedQuery { String? get activityNext; String? get activityPrevious; FeedInputData? get data; + EnrichmentOptions? get enrichmentOptions; Map? get externalRanking; int? get followerLimit; int? get followingLimit; @@ -44,6 +45,7 @@ mixin _$FeedQuery { (identical(other.activityNext, activityNext) || other.activityNext == activityNext) && (identical(other.activityPrevious, activityPrevious) || other.activityPrevious == activityPrevious) && (identical(other.data, data) || other.data == data) && + (identical(other.enrichmentOptions, enrichmentOptions) || other.enrichmentOptions == enrichmentOptions) && const DeepCollectionEquality().equals( other.externalRanking, externalRanking, @@ -68,6 +70,7 @@ mixin _$FeedQuery { activityNext, activityPrevious, data, + enrichmentOptions, const DeepCollectionEquality().hash(externalRanking), followerLimit, followingLimit, @@ -79,7 +82,7 @@ mixin _$FeedQuery { @override String toString() { - return 'FeedQuery(fid: $fid, activityFilter: $activityFilter, activityLimit: $activityLimit, activityNext: $activityNext, activityPrevious: $activityPrevious, data: $data, externalRanking: $externalRanking, followerLimit: $followerLimit, followingLimit: $followingLimit, interestWeights: $interestWeights, memberLimit: $memberLimit, view: $view, watch: $watch)'; + return 'FeedQuery(fid: $fid, activityFilter: $activityFilter, activityLimit: $activityLimit, activityNext: $activityNext, activityPrevious: $activityPrevious, data: $data, enrichmentOptions: $enrichmentOptions, externalRanking: $externalRanking, followerLimit: $followerLimit, followingLimit: $followingLimit, interestWeights: $interestWeights, memberLimit: $memberLimit, view: $view, watch: $watch)'; } } @@ -94,6 +97,7 @@ abstract mixin class $FeedQueryCopyWith<$Res> { String? activityNext, String? activityPrevious, FeedInputData? data, + EnrichmentOptions? enrichmentOptions, Map? externalRanking, int? followerLimit, int? followingLimit, @@ -122,6 +126,7 @@ class _$FeedQueryCopyWithImpl<$Res> implements $FeedQueryCopyWith<$Res> { Object? activityNext = freezed, Object? activityPrevious = freezed, Object? data = freezed, + Object? enrichmentOptions = freezed, Object? externalRanking = freezed, Object? followerLimit = freezed, Object? followingLimit = freezed, @@ -156,6 +161,10 @@ class _$FeedQueryCopyWithImpl<$Res> implements $FeedQueryCopyWith<$Res> { ? _self.data : data // ignore: cast_nullable_to_non_nullable as FeedInputData?, + enrichmentOptions: freezed == enrichmentOptions + ? _self.enrichmentOptions + : enrichmentOptions // ignore: cast_nullable_to_non_nullable + as EnrichmentOptions?, externalRanking: freezed == externalRanking ? _self.externalRanking : externalRanking // ignore: cast_nullable_to_non_nullable diff --git a/packages/stream_feeds/test/models/feed_add_activity_request_test.dart b/packages/stream_feeds/test/models/feed_add_activity_request_test.dart new file mode 100644 index 00000000..150d321f --- /dev/null +++ b/packages/stream_feeds/test/models/feed_add_activity_request_test.dart @@ -0,0 +1,22 @@ +import 'package:stream_feeds/stream_feeds.dart'; +import 'package:stream_feeds_test/stream_feeds_test.dart'; + +void main() { + group('FeedAddActivityRequest - restrictReplies', () { + test('stores restrictReplies field', () { + const request = FeedAddActivityRequest( + type: 'post', + restrictReplies: AddActivityRequestRestrictReplies.nobody, + ); + expect( + request.restrictReplies, + equals(AddActivityRequestRestrictReplies.nobody), + ); + }); + + test('restrictReplies defaults to null', () { + const request = FeedAddActivityRequest(type: 'post'); + expect(request.restrictReplies, isNull); + }); + }); +} diff --git a/packages/stream_feeds/test/state/feed_test.dart b/packages/stream_feeds/test/state/feed_test.dart index 60b9e661..d4ff7302 100644 --- a/packages/stream_feeds/test/state/feed_test.dart +++ b/packages/stream_feeds/test/state/feed_test.dart @@ -26,6 +26,30 @@ void main() { }, ); + feedTest( + 'getOrCreate() sends enrichmentOptions from FeedQuery to the API', + build: (client) => client.feedFromQuery( + const FeedQuery( + fid: feedId, + enrichmentOptions: EnrichmentOptions(enrichOwnFollowings: true), + ), + ), + body: (tester) async { + final result = await tester.getOrCreate(); + expect(result.isSuccess, isTrue); + }, + verify: (tester) => tester.verifyApi( + (api) => api.getOrCreateFeed( + feedId: feedId.id, + feedGroupId: feedId.group, + getOrCreateFeedRequest: const GetOrCreateFeedRequest( + watch: true, + enrichmentOptions: EnrichmentOptions(enrichOwnFollowings: true), + ), + ), + ), + ); + feedTest( 'stopWatching() - should stop watching feed', build: (client) => client.feed(group: 'user', id: 'john'), @@ -4442,5 +4466,119 @@ void main() { ), ), ); + + feedTest( + 'addActivity() passes restrictReplies to API', + build: (client) => client.feedFromId(feedId), + setUp: (tester) => tester.getOrCreate(), + body: (tester) async { + tester.mockApi( + (api) => api.addActivity( + addActivityRequest: const AddActivityRequest( + type: 'post', + feeds: [], + restrictReplies: AddActivityRequestRestrictReplies.peopleIFollow, + ), + ), + result: AddActivityResponse( + duration: '0ms', + activity: createDefaultActivityResponse(id: 'activity-1'), + ), + ); + + final result = await tester.feed.addActivity( + request: const FeedAddActivityRequest( + type: 'post', + restrictReplies: AddActivityRequestRestrictReplies.peopleIFollow, + ), + ); + + expect(result.isSuccess, isTrue); + }, + verify: (tester) => tester.verifyApi( + (api) => api.addActivity( + addActivityRequest: const AddActivityRequest( + type: 'post', + feeds: [], + restrictReplies: AddActivityRequestRestrictReplies.peopleIFollow, + ), + ), + ), + ); + + feedTest( + 'getOrCreate() exposes restrictReplies on activity state', + build: (client) => client.feedFromId(feedId), + body: (tester) async { + await tester.getOrCreate( + modifyResponse: (it) => it.copyWith( + activities: [ + createDefaultActivityResponse( + id: 'activity-1', + feeds: [feedId.rawValue], + restrictReplies: ActivityResponseRestrictReplies.nobody, + ), + ], + ), + ); + + final activity = tester.feedState.activities.firstWhere( + (a) => a.id == 'activity-1', + ); + expect(activity.restrictReplies, equals(ActivityRestrictReplies.nobody)); + }, + ); + + feedTest( + 'updateActivity() passes restrictReplies to API', + build: (client) => client.feedFromId(feedId), + setUp: (tester) => tester.getOrCreate( + modifyResponse: (it) => it.copyWith( + activities: [ + createDefaultActivityResponse( + id: 'activity-1', + feeds: [feedId.rawValue], + ), + ], + ), + ), + body: (tester) async { + tester.mockApi( + (api) => api.updateActivity( + id: 'activity-1', + updateActivityRequest: const UpdateActivityRequest( + restrictReplies: UpdateActivityRequestRestrictReplies.nobody, + ), + ), + result: UpdateActivityResponse( + duration: '0ms', + activity: createDefaultActivityResponse( + id: 'activity-1', + feeds: [feedId.rawValue], + restrictReplies: ActivityResponseRestrictReplies.nobody, + ), + ), + ); + + final result = await tester.feed.updateActivity( + id: 'activity-1', + request: const UpdateActivityRequest( + restrictReplies: UpdateActivityRequestRestrictReplies.nobody, + ), + ); + + expect(result.isSuccess, isTrue); + final activity = result.getOrThrow(); + expect(activity.restrictReplies, equals(ActivityRestrictReplies.nobody)); + }, + verify: (tester) => tester.verifyApi( + (api) => api.updateActivity( + id: 'activity-1', + updateActivityRequest: const UpdateActivityRequest( + restrictReplies: UpdateActivityRequestRestrictReplies.nobody, + ), + ), + ), + ); }); } diff --git a/packages/stream_feeds_test/lib/src/helpers/test_data.dart b/packages/stream_feeds_test/lib/src/helpers/test_data.dart index b095ceb5..9d9e1158 100644 --- a/packages/stream_feeds_test/lib/src/helpers/test_data.dart +++ b/packages/stream_feeds_test/lib/src/helpers/test_data.dart @@ -102,6 +102,7 @@ ActivityResponse createDefaultActivityResponse({ List? friendReactions, int? friendReactionCount, Map? metrics, + ActivityResponseRestrictReplies? restrictReplies, }) { latestReactions = latestReactions.isEmpty ? ownReactions : latestReactions; reactionGroups = switch (reactionGroups.isNotEmpty) { @@ -155,7 +156,7 @@ ActivityResponse createDefaultActivityResponse({ preview: false, reactionCount: reactionGroups.values.sumOf((group) => group.count), reactionGroups: reactionGroups, - restrictReplies: ActivityResponseRestrictReplies.everyone, + restrictReplies: restrictReplies ?? ActivityResponseRestrictReplies.everyone, score: 0, searchData: const {}, shareCount: 0,