Skip to content

Commit

Permalink
channel: Add willChangeIfTopicVisible/InStream methods
Browse files Browse the repository at this point in the history
  • Loading branch information
gnprice committed Jul 24, 2024
1 parent 24400bf commit cd90a72
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 2 deletions.
54 changes: 52 additions & 2 deletions lib/model/channel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,21 @@ mixin ChannelStore {
/// For UI contexts that are not specific to a particular stream, see
/// [isTopicVisible].
bool isTopicVisibleInStream(int streamId, String topic) {
switch (topicVisibilityPolicy(streamId, topic)) {
return _isTopicVisibleInStream(topicVisibilityPolicy(streamId, topic));
}

/// Whether the given event will change the result of [isTopicVisibleInStream]
/// for its stream and topic, compared to the current state.
VisibilityEffect willChangeIfTopicVisibleInStream(UserTopicEvent event) {
final streamId = event.streamId;
final topic = event.topicName;
return VisibilityEffect._fromBeforeAfter(
_isTopicVisibleInStream(topicVisibilityPolicy(streamId, topic)),
_isTopicVisibleInStream(event.visibilityPolicy));
}

static bool _isTopicVisibleInStream(UserTopicVisibilityPolicy policy) {
switch (policy) {
case UserTopicVisibilityPolicy.none:
return true;
case UserTopicVisibilityPolicy.muted:
Expand All @@ -48,7 +62,21 @@ mixin ChannelStore {
/// For UI contexts that are specific to a particular stream, see
/// [isTopicVisibleInStream].
bool isTopicVisible(int streamId, String topic) {
switch (topicVisibilityPolicy(streamId, topic)) {
return _isTopicVisible(streamId, topicVisibilityPolicy(streamId, topic));
}

/// Whether the given event will change the result of [isTopicVisible]
/// for its stream and topic, compared to the current state.
VisibilityEffect willChangeIfTopicVisible(UserTopicEvent event) {
final streamId = event.streamId;
final topic = event.topicName;
return VisibilityEffect._fromBeforeAfter(
_isTopicVisible(streamId, topicVisibilityPolicy(streamId, topic)),
_isTopicVisible(streamId, event.visibilityPolicy));
}

bool _isTopicVisible(int streamId, UserTopicVisibilityPolicy policy) {
switch (policy) {
case UserTopicVisibilityPolicy.none:
switch (subscriptions[streamId]?.isMuted) {
case false: return true;
Expand All @@ -67,6 +95,28 @@ mixin ChannelStore {
}
}

/// Whether and how a given [UserTopicEvent] will affect the results
/// that [ChannelStore.isTopicVisible] or [ChannelStore.isTopicVisibleInStream]
/// would give for some messages.
enum VisibilityEffect {
/// The event will have no effect on the visibility results.
none,

/// The event will change some visibility results from true to false.
muted,

/// The event will change some visibility results from false to true.
unmuted;

factory VisibilityEffect._fromBeforeAfter(bool before, bool after) {
return switch ((before, after)) {
(false, true) => VisibilityEffect.unmuted,
(true, false) => VisibilityEffect.muted,
_ => VisibilityEffect.none,
};
}
}

/// The implementation of [ChannelStore] that does the work.
///
/// Generally the only code that should need this class is [PerAccountStore]
Expand Down
91 changes: 91 additions & 0 deletions test/model/channel_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,97 @@ void main() {
});
});

group('willChangeIfTopicVisible/InStream', () {
UserTopicEvent mkEvent(UserTopicVisibilityPolicy policy) =>
eg.userTopicEvent(stream1.streamId, 'topic', policy);

void checkChanges(PerAccountStore store,
UserTopicVisibilityPolicy newPolicy,
VisibilityEffect expectedInStream, VisibilityEffect expectedOverall) {
final event = mkEvent(newPolicy);
check(store.willChangeIfTopicVisibleInStream(event)).equals(expectedInStream);
check(store.willChangeIfTopicVisible (event)).equals(expectedOverall);
}

test('stream not muted, policy none -> followed, no change', () async {
final store = eg.store();
await store.addStream(stream1);
await store.addSubscription(eg.subscription(stream1));
checkChanges(store, UserTopicVisibilityPolicy.followed,
VisibilityEffect.none, VisibilityEffect.none);
});

test('stream not muted, policy none -> muted, means muted', () async {
final store = eg.store();
await store.addStream(stream1);
await store.addSubscription(eg.subscription(stream1));
checkChanges(store, UserTopicVisibilityPolicy.muted,
VisibilityEffect.muted, VisibilityEffect.muted);
});

test('stream muted, policy none -> followed, means none/unmuted', () async {
final store = eg.store();
await store.addStream(stream1);
await store.addSubscription(eg.subscription(stream1, isMuted: true));
checkChanges(store, UserTopicVisibilityPolicy.followed,
VisibilityEffect.none, VisibilityEffect.unmuted);
});

test('stream muted, policy none -> muted, means muted/none', () async {
final store = eg.store();
await store.addStream(stream1);
await store.addSubscription(eg.subscription(stream1, isMuted: true));
checkChanges(store, UserTopicVisibilityPolicy.muted,
VisibilityEffect.muted, VisibilityEffect.none);
});

final policies = [
UserTopicVisibilityPolicy.muted,
UserTopicVisibilityPolicy.none,
UserTopicVisibilityPolicy.unmuted,
];
for (final streamMuted in [null, false, true]) {
for (final oldPolicy in policies) {
for (final newPolicy in policies) {
final streamDesc = switch (streamMuted) {
false => "stream not muted",
true => "stream muted",
null => "stream unsubscribed",
};
test('$streamDesc, topic ${oldPolicy.name} -> ${newPolicy.name}', () async {
final store = eg.store();
await store.addStream(stream1);
if (streamMuted != null) {
await store.addSubscription(
eg.subscription(stream1, isMuted: streamMuted));
}
store.handleEvent(mkEvent(oldPolicy));
final oldVisibleInStream = store.isTopicVisibleInStream(stream1.streamId, 'topic');
final oldVisible = store.isTopicVisible(stream1.streamId, 'topic');

final event = mkEvent(newPolicy);
final willChangeInStream = store.willChangeIfTopicVisibleInStream(event);
final willChange = store.willChangeIfTopicVisible(event);

store.handleEvent(event);
final newVisibleInStream = store.isTopicVisibleInStream(stream1.streamId, 'topic');
final newVisible = store.isTopicVisible(stream1.streamId, 'topic');

VisibilityEffect fromOldNew(bool oldVisible, bool newVisible) {
if (newVisible == oldVisible) return VisibilityEffect.none;
if (newVisible) return VisibilityEffect.unmuted;
return VisibilityEffect.muted;
}
check(willChangeInStream)
.equals(fromOldNew(oldVisibleInStream, newVisibleInStream));
check(willChange)
.equals(fromOldNew(oldVisible, newVisible));
});
}
}
}
});

void compareTopicVisibility(PerAccountStore store, List<UserTopicItem> expected) {
final expectedStore = eg.store(initialSnapshot: eg.initialSnapshot(
userTopics: expected,
Expand Down

0 comments on commit cd90a72

Please sign in to comment.