diff --git a/lib/api/model/events.dart b/lib/api/model/events.dart index 0af145330..b1789e3ce 100644 --- a/lib/api/model/events.dart +++ b/lib/api/model/events.dart @@ -244,11 +244,11 @@ class UserSettingsUpdateEvent extends Event { @JsonKey(includeToJson: true) String get op => 'update'; - /// The name of the setting, or null if we don't recognize it. - @JsonKey(unknownEnumValue: JsonKey.nullForUndefinedEnumValue) - final UserSettingName? property; + /// The name of the setting, or [UserSettingName.unknown] if we don't recognize it. + @JsonKey(unknownEnumValue: UserSettingName.unknown) + final UserSettingName property; - /// The new value, or null if we don't recognize the setting. + /// The new value, or null if we don't recognize the setting ([UserSettingName.unknown]). /// /// This will have the type appropriate for [property]; for example, /// if the setting is boolean, then `value is bool` will always be true. @@ -261,17 +261,17 @@ class UserSettingsUpdateEvent extends Event { static Object? _readValue(Map json, String key) { final value = json['value']; switch (UserSettingName.fromRawString(json['property'] as String)) { - case UserSettingName.twentyFourHourTime: + case .twentyFourHourTime: return TwentyFourHourTimeMode.fromApiValue(value as bool?); - case UserSettingName.starredMessageCounts: - case UserSettingName.displayEmojiReactionUsers: + case .starredMessageCounts: + case .displayEmojiReactionUsers: return value as bool; - case UserSettingName.emojiset: + case .emojiset: return Emojiset.fromRawString(value as String); - case UserSettingName.webInboxShowChannelFolders: - case UserSettingName.presenceEnabled: + case .webInboxShowChannelFolders: + case .presenceEnabled: return value as bool; - case null: + case .unknown: return null; } } @@ -818,11 +818,12 @@ class ChannelUpdateEvent extends ChannelEvent { final int streamId; final String name; - /// The name of the channel property, or null if we don't recognize it. - @JsonKey(unknownEnumValue: JsonKey.nullForUndefinedEnumValue) - final ChannelPropertyName? property; + /// The name of the channel property, + /// or [ChannelProperty.unknown] if we don't recognize it. + @JsonKey(unknownEnumValue: ChannelProperty.unknown) + final ChannelProperty property; - /// The new value, or null if we don't recognize the property. + /// The new value, or null if we don't recognize the property ([ChannelProperty.unknown]). /// /// This will have the type appropriate for [property]; for example, /// if the property is boolean, then `value is bool` will always be true. @@ -849,36 +850,36 @@ class ChannelUpdateEvent extends ChannelEvent { /// (e.g., `value as bool`). static Object? _readValue(Map json, String key) { final value = json['value']; - switch (ChannelPropertyName.fromRawString(json['property'] as String)) { - case ChannelPropertyName.name: + switch (ChannelProperty.fromRawString(json['property'] as String)) { + case .name: return value as String; - case ChannelPropertyName.isArchived: + case .isArchived: return value as bool; - case ChannelPropertyName.description: + case .description: return value as String; - case ChannelPropertyName.firstMessageId: + case .firstMessageId: return value as int?; - case ChannelPropertyName.inviteOnly: + case .inviteOnly: return value as bool; - case ChannelPropertyName.messageRetentionDays: + case .messageRetentionDays: return value as int?; - case ChannelPropertyName.topicsPolicy: + case .topicsPolicy: return ChannelTopicsPolicy.fromApiValue(value as String); - case ChannelPropertyName.channelPostPolicy: + case .channelPostPolicy: return ChannelPostPolicy.fromApiValue(value as int); - case ChannelPropertyName.folderId: + case .folderId: return value as int?; - case ChannelPropertyName.canAddSubscribersGroup: - case ChannelPropertyName.canDeleteAnyMessageGroup: - case ChannelPropertyName.canDeleteOwnMessageGroup: - case ChannelPropertyName.canSendMessageGroup: - case ChannelPropertyName.canSubscribeGroup: + case .canAddSubscribersGroup: + case .canDeleteAnyMessageGroup: + case .canDeleteOwnMessageGroup: + case .canSendMessageGroup: + case .canSubscribeGroup: return GroupSettingValue.fromJson(value); - case ChannelPropertyName.isRecentlyActive: + case .isRecentlyActive: return value as bool; - case ChannelPropertyName.streamWeeklyTraffic: + case .streamWeeklyTraffic: return value as int?; - case null: + case .unknown: return null; } } @@ -1405,8 +1406,9 @@ class DeleteMessageEvent extends Event { final List messageIds; // final int messageId; // Not present; we support the bulk_message_deletion capability - // The server never actually sends "direct" here yet (it's "private" instead), - // but we accept both forms for forward-compatibility. + // The server never actually sends "channel" or "direct" here yet + // (it's "stream" or "private" instead), but we accept both the old + // and new forms for forward-compatibility. @MessageTypeConverter() final MessageType messageType; final int? streamId; @@ -1423,7 +1425,7 @@ class DeleteMessageEvent extends Event { factory DeleteMessageEvent.fromJson(Map json) { final result = _$DeleteMessageEventFromJson(json); // Crunchy-shell validation - if (result.messageType == MessageType.stream) { + if (result.messageType == .channel) { result.streamId as int; result.topic as String; } @@ -1434,30 +1436,6 @@ class DeleteMessageEvent extends Event { Map toJson() => _$DeleteMessageEventToJson(this); } -/// As in [DeleteMessageEvent.messageType], -/// [UpdateMessageFlagsMessageDetail.type], -/// or [TypingEvent.messageType]. -@JsonEnum(alwaysCreate: true) -enum MessageType { - stream, - direct; -} - -class MessageTypeConverter extends JsonConverter { - const MessageTypeConverter(); - - @override - MessageType fromJson(String json) { - if (json == 'private') json = 'direct'; // TODO(server-future) - return $enumDecode(_$MessageTypeEnumMap, json); - } - - @override - String toJson(MessageType object) { - return _$MessageTypeEnumMap[object]!; - } -} - /// A Zulip event of type `update_message_flags`. /// /// For the corresponding API docs, see subclasses. @@ -1536,8 +1514,9 @@ class UpdateMessageFlagsRemoveEvent extends UpdateMessageFlagsEvent { /// As in [UpdateMessageFlagsRemoveEvent.messageDetails]. @JsonSerializable(fieldRename: FieldRename.snake) class UpdateMessageFlagsMessageDetail { - // The server never actually sends "direct" here yet (it's "private" instead), - // but we accept both forms for forward-compatibility. + // The server never actually sends "channel" or "direct" here yet + // (it's "stream" or "private" instead), but we accept both the old + // and new forms for forward-compatibility. @MessageTypeConverter() final MessageType type; final bool? mentioned; @@ -1557,10 +1536,10 @@ class UpdateMessageFlagsMessageDetail { final result = _$UpdateMessageFlagsMessageDetailFromJson(json); // Crunchy-shell validation switch (result.type) { - case MessageType.stream: + case .channel: result.streamId as int; result.topic as String; - case MessageType.direct: + case .direct: result.userIds as List; } return result; @@ -1613,7 +1592,10 @@ class TypingEvent extends Event { @JsonKey(includeToJson: true) String get type => 'typing'; + @JsonKey(unknownEnumValue: TypingOp.unknown) final TypingOp op; + // The server never actually sends "channel" here yet (it's "stream" instead), + // but we accept both forms for forward-compatibility. @MessageTypeConverter() final MessageType messageType; @JsonKey(readValue: _readSenderId) @@ -1647,10 +1629,10 @@ class TypingEvent extends Event { final result = _$TypingEventFromJson(json); // Crunchy-shell validation switch (result.messageType) { - case MessageType.stream: + case .channel: result.streamId as int; result.topic as String; - case MessageType.direct: + case .direct: result.recipientIds as List; } return result; @@ -1664,7 +1646,10 @@ class TypingEvent extends Event { @JsonEnum(fieldRename: FieldRename.snake) enum TypingOp { start, - stop; + stop, + + /// A new, unrecognized operation. + unknown; String toJson() => _$TypingOpEnumMap[this]!; } @@ -1743,6 +1728,7 @@ class ReactionEvent extends Event { @JsonKey(includeToJson: true) String get type => 'reaction'; + @JsonKey(unknownEnumValue: ReactionOp.unknown) final ReactionOp op; final String emojiName; @@ -1774,6 +1760,9 @@ class ReactionEvent extends Event { enum ReactionOp { add, remove, + + /// A new, unrecognized operation. + unknown; } /// A Zulip event of type `heartbeat`: https://zulip.com/api/get-events#heartbeat diff --git a/lib/api/model/events.g.dart b/lib/api/model/events.g.dart index b6d46e49c..08b5b36ab 100644 --- a/lib/api/model/events.g.dart +++ b/lib/api/model/events.g.dart @@ -85,10 +85,10 @@ UserSettingsUpdateEvent _$UserSettingsUpdateEventFromJson( Map json, ) => UserSettingsUpdateEvent( id: (json['id'] as num).toInt(), - property: $enumDecodeNullable( + property: $enumDecode( _$UserSettingNameEnumMap, json['property'], - unknownValue: JsonKey.nullForUndefinedEnumValue, + unknownValue: UserSettingName.unknown, ), value: UserSettingsUpdateEvent._readValue(json, 'value'), ); @@ -110,6 +110,7 @@ const _$UserSettingNameEnumMap = { UserSettingName.emojiset: 'emojiset', UserSettingName.webInboxShowChannelFolders: 'web_inbox_show_channel_folders', UserSettingName.presenceEnabled: 'presence_enabled', + UserSettingName.unknown: 'unknown', }; DeviceAddEvent _$DeviceAddEventFromJson(Map json) => @@ -564,10 +565,10 @@ ChannelUpdateEvent _$ChannelUpdateEventFromJson(Map json) => id: (json['id'] as num).toInt(), streamId: (json['stream_id'] as num).toInt(), name: json['name'] as String, - property: $enumDecodeNullable( - _$ChannelPropertyNameEnumMap, + property: $enumDecode( + _$ChannelPropertyEnumMap, json['property'], - unknownValue: JsonKey.nullForUndefinedEnumValue, + unknownValue: ChannelProperty.unknown, ), value: ChannelUpdateEvent._readValue(json, 'value'), renderedDescription: json['rendered_description'] as String?, @@ -583,30 +584,31 @@ Map _$ChannelUpdateEventToJson(ChannelUpdateEvent instance) => 'op': instance.op, 'stream_id': instance.streamId, 'name': instance.name, - 'property': _$ChannelPropertyNameEnumMap[instance.property], + 'property': _$ChannelPropertyEnumMap[instance.property]!, 'value': instance.value, 'rendered_description': instance.renderedDescription, 'history_public_to_subscribers': instance.historyPublicToSubscribers, 'is_web_public': instance.isWebPublic, }; -const _$ChannelPropertyNameEnumMap = { - ChannelPropertyName.name: 'name', - ChannelPropertyName.isArchived: 'is_archived', - ChannelPropertyName.description: 'description', - ChannelPropertyName.firstMessageId: 'first_message_id', - ChannelPropertyName.inviteOnly: 'invite_only', - ChannelPropertyName.messageRetentionDays: 'message_retention_days', - ChannelPropertyName.topicsPolicy: 'topics_policy', - ChannelPropertyName.channelPostPolicy: 'stream_post_policy', - ChannelPropertyName.folderId: 'folder_id', - ChannelPropertyName.canAddSubscribersGroup: 'can_add_subscribers_group', - ChannelPropertyName.canDeleteAnyMessageGroup: 'can_delete_any_message_group', - ChannelPropertyName.canDeleteOwnMessageGroup: 'can_delete_own_message_group', - ChannelPropertyName.canSendMessageGroup: 'can_send_message_group', - ChannelPropertyName.canSubscribeGroup: 'can_subscribe_group', - ChannelPropertyName.isRecentlyActive: 'is_recently_active', - ChannelPropertyName.streamWeeklyTraffic: 'stream_weekly_traffic', +const _$ChannelPropertyEnumMap = { + ChannelProperty.name: 'name', + ChannelProperty.isArchived: 'is_archived', + ChannelProperty.description: 'description', + ChannelProperty.firstMessageId: 'first_message_id', + ChannelProperty.inviteOnly: 'invite_only', + ChannelProperty.messageRetentionDays: 'message_retention_days', + ChannelProperty.topicsPolicy: 'topics_policy', + ChannelProperty.channelPostPolicy: 'stream_post_policy', + ChannelProperty.folderId: 'folder_id', + ChannelProperty.canAddSubscribersGroup: 'can_add_subscribers_group', + ChannelProperty.canDeleteAnyMessageGroup: 'can_delete_any_message_group', + ChannelProperty.canDeleteOwnMessageGroup: 'can_delete_own_message_group', + ChannelProperty.canSendMessageGroup: 'can_send_message_group', + ChannelProperty.canSubscribeGroup: 'can_subscribe_group', + ChannelProperty.isRecentlyActive: 'is_recently_active', + ChannelProperty.streamWeeklyTraffic: 'stream_weekly_traffic', + ChannelProperty.unknown: 'unknown', }; SubscriptionAddEvent _$SubscriptionAddEventFromJson( @@ -1061,7 +1063,11 @@ const _$SubmessageTypeEnumMap = { TypingEvent _$TypingEventFromJson(Map json) => TypingEvent( id: (json['id'] as num).toInt(), - op: $enumDecode(_$TypingOpEnumMap, json['op']), + op: $enumDecode( + _$TypingOpEnumMap, + json['op'], + unknownValue: TypingOp.unknown, + ), messageType: const MessageTypeConverter().fromJson( json['message_type'] as String, ), @@ -1085,7 +1091,11 @@ Map _$TypingEventToJson(TypingEvent instance) => 'topic': instance.topic, }; -const _$TypingOpEnumMap = {TypingOp.start: 'start', TypingOp.stop: 'stop'}; +const _$TypingOpEnumMap = { + TypingOp.start: 'start', + TypingOp.stop: 'stop', + TypingOp.unknown: 'unknown', +}; PresenceEvent _$PresenceEventFromJson(Map json) => PresenceEvent( @@ -1131,7 +1141,11 @@ const _$PresenceStatusEnumMap = { ReactionEvent _$ReactionEventFromJson(Map json) => ReactionEvent( id: (json['id'] as num).toInt(), - op: $enumDecode(_$ReactionOpEnumMap, json['op']), + op: $enumDecode( + _$ReactionOpEnumMap, + json['op'], + unknownValue: ReactionOp.unknown, + ), emojiName: json['emoji_name'] as String, emojiCode: json['emoji_code'] as String, reactionType: $enumDecode(_$ReactionTypeEnumMap, json['reaction_type']), @@ -1154,6 +1168,7 @@ Map _$ReactionEventToJson(ReactionEvent instance) => const _$ReactionOpEnumMap = { ReactionOp.add: 'add', ReactionOp.remove: 'remove', + ReactionOp.unknown: 'unknown', }; const _$ReactionTypeEnumMap = { @@ -1167,8 +1182,3 @@ HeartbeatEvent _$HeartbeatEventFromJson(Map json) => Map _$HeartbeatEventToJson(HeartbeatEvent instance) => {'id': instance.id, 'type': instance.type}; - -const _$MessageTypeEnumMap = { - MessageType.stream: 'stream', - MessageType.direct: 'direct', -}; diff --git a/lib/api/model/initial_snapshot.dart b/lib/api/model/initial_snapshot.dart index 1709d007b..e85a775cc 100644 --- a/lib/api/model/initial_snapshot.dart +++ b/lib/api/model/initial_snapshot.dart @@ -94,7 +94,7 @@ class InitialSnapshot { /// The policy for who can use wildcard mentions in large channels. /// /// Search for "realm_wildcard_mention_policy" in https://zulip.com/api/register-queue. - final RealmWildcardMentionPolicy realmWildcardMentionPolicy; + final RealmWildcardMentionPolicy? realmWildcardMentionPolicy; // TODO(server-10) remove @JsonKey(unknownEnumValue: RealmTopicsPolicy.unknown) final RealmTopicsPolicy? realmTopicsPolicy; // TODO(server-11) @@ -237,7 +237,12 @@ enum RealmWildcardMentionPolicy { fullMembers(apiValue: 3), admins(apiValue: 5), nobody(apiValue: 6), - moderators(apiValue: 7); + moderators(apiValue: 7), + + // We don't accept an unknown value. `realm_wildcard_mention_policy` is + // deprecated in server-10, making future additions to this enum unlikely. + // unknown(apiValue: null), + ; const RealmWildcardMentionPolicy({required this.apiValue}); @@ -252,7 +257,12 @@ enum RealmDeleteOwnMessagePolicy { admins(apiValue: 2), fullMembers(apiValue: 3), moderators(apiValue: 4), - everyone(apiValue: 5); + everyone(apiValue: 5), + + // We don't accept an unknown value. `realm_delete_own_message_policy` is + // removed in server-10, making future additions to this enum impossible. + // unknown(apiValue: -100), + ; const RealmDeleteOwnMessagePolicy({required this.apiValue}); diff --git a/lib/api/model/initial_snapshot.g.dart b/lib/api/model/initial_snapshot.g.dart index 8ecc74a76..55ccb43d8 100644 --- a/lib/api/model/initial_snapshot.g.dart +++ b/lib/api/model/initial_snapshot.g.dart @@ -107,7 +107,7 @@ InitialSnapshot _$InitialSnapshotFromJson( _$RealmDeleteOwnMessagePolicyEnumMap, json['realm_delete_own_message_policy'], ), - realmWildcardMentionPolicy: $enumDecode( + realmWildcardMentionPolicy: $enumDecodeNullable( _$RealmWildcardMentionPolicyEnumMap, json['realm_wildcard_mention_policy'], ), diff --git a/lib/api/model/model.dart b/lib/api/model/model.dart index 271985a58..365ad42dd 100644 --- a/lib/api/model/model.dart +++ b/lib/api/model/model.dart @@ -341,13 +341,20 @@ enum UserSettingName { emojiset, webInboxShowChannelFolders, presenceEnabled, + // TODO: add more as needed + + /// A user setting (name) that: + /// - The server sends, but we currently don't support. + /// - The server may start sending in the future. + unknown, ; - /// Get a [UserSettingName] from a raw, snake-case string we recognize, else null. + /// Get a [UserSettingName] from a raw, snake-case string we recognize, + /// else [UserSettingName.unknown]. /// /// Example: /// 'display_emoji_reaction_users' -> UserSettingName.displayEmojiReactionUsers - static UserSettingName? fromRawString(String raw) => _byRawString[raw]; + static UserSettingName fromRawString(String raw) => _byRawString[raw] ?? unknown; // _$…EnumMap is thanks to `alwaysCreate: true` and `fieldRename: FieldRename.snake` static final _byRawString = _$UserSettingNameEnumMap @@ -368,6 +375,12 @@ enum TwentyFourHourTimeMode { // TODO(server-future) Write down what server N starts sending null; // adjust the comment; leave a TODO(server-N) to delete the comment localeDefault(apiValue: null), + + // We can't have an unknown value because all values permitted by the + // server API (true, false, and null) are represented above. + // If the server ever sends a different kind of value, that would indicate + // a change to the API and we should update this model accordingly. + // unknown(apiValue: null), ; const TwentyFourHourTimeMode({required this.apiValue}); @@ -394,7 +407,7 @@ enum Emojiset { text, unknown; - /// Get an [Emojiset] from a raw string. Throws if the string is unrecognized. + /// Get an [Emojiset] from a raw string we recognize, else [Emojiset.unknown]. /// /// Example: /// 'google-blob' -> Emojiset.googleBlob @@ -613,7 +626,13 @@ class PerUserPresence { @JsonEnum(fieldRename: FieldRename.snake, alwaysCreate: true) enum PresenceStatus { active, - idle; + idle, + + // We don't accept an unknown value. The [PerClientPresence] format is + // considered legacy and is expected to be removed in a future server release, + // making additions to this enum unlikely. + // unknown, + ; String toJson() => _$PresenceStatusEnumMap[this]!; } @@ -651,7 +670,7 @@ class SavedSnippet { @JsonSerializable(fieldRename: FieldRename.snake) class ZulipStream { // When adding a field to this class: - // * Add it to [ChannelPropertyName] too, or add a comment there explaining + // * Add it to [ChannelProperty] too, or add a comment there explaining // why there isn't a corresponding value in that enum. // * If the field can never change for a given Zulip stream, mark it final. // Otherwise, make sure it gets updated on [ChannelUpdateEvent]. @@ -765,7 +784,7 @@ class ZulipStream { /// we switch exhaustively on a value of this type /// to ensure that every property in [ZulipStream] responds to the event. @JsonEnum(fieldRename: FieldRename.snake, alwaysCreate: true) -enum ChannelPropertyName { +enum ChannelProperty { // streamId is immutable name, isArchived, @@ -787,16 +806,24 @@ enum ChannelPropertyName { canSendMessageGroup, canSubscribeGroup, isRecentlyActive, - streamWeeklyTraffic; + streamWeeklyTraffic, + // TODO: add more as needed + + /// A channel property name that: + /// - The server sends, but we currently don't support. + /// - The server may start sending in the future. + unknown, + ; - /// Get a [ChannelPropertyName] from a raw, snake-case string we recognize, else null. + /// Get a [ChannelProperty] from a raw, snake-case string we recognize, + /// else [ChannelProperty.unknown]. /// /// Example: - /// 'invite_only' -> ChannelPropertyName.inviteOnly - static ChannelPropertyName? fromRawString(String raw) => _byRawString[raw]; + /// 'invite_only' -> ChannelProperty.inviteOnly + static ChannelProperty fromRawString(String raw) => _byRawString[raw] ?? unknown; // _$…EnumMap is thanks to `alwaysCreate: true` and `fieldRename: FieldRename.snake` - static final _byRawString = _$ChannelPropertyNameEnumMap + static final _byRawString = _$ChannelPropertyEnumMap .map((key, value) => MapEntry(value, key)); } @@ -1221,7 +1248,10 @@ sealed class Message extends MessageBase { @JsonKey(name: 'submessages', readValue: _readPoll, fromJson: Poll.fromJson, toJson: Poll.toJson) Poll? poll; - String get type; + // The server never actually sends "channel" or "direct" here yet + // (it's "stream" or "private" instead), but we accept both the old + // and new forms for forward-compatibility. + MessageType get type; // final List topicLinks; // TODO handle // final string type; // handled by runtime type of object @@ -1280,15 +1310,50 @@ sealed class Message extends MessageBase { // TODO(dart): This has to be a static method, because factories/constructors // do not support type parameters: https://github.com/dart-lang/language/issues/647 static Message fromJson(Map json) { - final type = json['type'] as String; - if (type == 'stream') return StreamMessage.fromJson(json); - if (type == 'private') return DmMessage.fromJson(json); - throw Exception("Message.fromJson: unexpected message type $type"); + return switch (MessageType.fromJson(json['type'] as String)) { + .channel => StreamMessage.fromJson(json), + .direct => DmMessage.fromJson(json), + }; } Map toJson(); } +/// As in [Message.type], [DeleteMessageEvent.messageType], +/// [UpdateMessageFlagsMessageDetail.type], or [TypingEvent.messageType]. +@JsonEnum(alwaysCreate: true) +enum MessageType { + channel, + direct, + + // We don't accept an unknown message type because we generally cannot decide + // how to interpret such data. In particular, when a message with an unknown + // type is received from the server, we cannot determine which message model + // to instantiate: either [StreamMessage] or [DmMessage]. See [Message.fromJson]. + // unknown, + ; + + factory fromJson(String json) { + if (json == 'stream') json = 'channel'; // TODO(server-future) + if (json == 'private') json = 'direct'; // TODO(server-future) + return $enumDecode(_$MessageTypeEnumMap, json); + } +} + +class MessageTypeConverter extends JsonConverter { + const MessageTypeConverter(); + + @override + MessageType fromJson(String json) { + return MessageType.fromJson(json); + } + + @override + String toJson(MessageType object) { + return _$MessageTypeEnumMap[object]!; + } +} + /// https://zulip.com/api/update-message-flags#available-flags @JsonEnum(fieldRename: FieldRename.snake, alwaysCreate: true) enum MessageFlag { @@ -1339,7 +1404,7 @@ enum MessageFlag { class StreamMessage extends Message { @override @JsonKey(includeToJson: true) - String get type => 'stream'; + MessageType get type => .channel; @JsonKey(includeToJson: true) int get streamId => conversation.streamId; @@ -1393,7 +1458,7 @@ class StreamMessage extends Message { class DmMessage extends Message { @override @JsonKey(includeToJson: true) - String get type => 'private'; + MessageType get type => .direct; /// The user IDs of all users in the thread, sorted numerically, as in /// `display_recipient` from the server. diff --git a/lib/api/model/model.g.dart b/lib/api/model/model.g.dart index 4e71852af..bf015ca40 100644 --- a/lib/api/model/model.g.dart +++ b/lib/api/model/model.g.dart @@ -504,7 +504,7 @@ Map _$StreamMessageToJson(StreamMessage instance) => 'flags': instance.flags, 'match_content': instance.matchContent, 'match_subject': instance.matchTopic, - 'type': instance.type, + 'type': _$MessageTypeEnumMap[instance.type]!, 'stream_id': instance.streamId, 'subject': instance.topic, 'display_recipient': instance.displayRecipient, @@ -516,6 +516,11 @@ const _$MessageEditStateEnumMap = { MessageEditState.moved: 'moved', }; +const _$MessageTypeEnumMap = { + MessageType.channel: 'channel', + MessageType.direct: 'direct', +}; + DmMessage _$DmMessageFromJson(Map json) => DmMessage( client: json['client'] as String, content: json['content'] as String, @@ -558,7 +563,7 @@ Map _$DmMessageToJson(DmMessage instance) => { 'flags': instance.flags, 'match_content': instance.matchContent, 'match_subject': instance.matchTopic, - 'type': instance.type, + 'type': _$MessageTypeEnumMap[instance.type]!, 'display_recipient': DmMessage._allRecipientIdsToJson( instance.allRecipientIds, ), @@ -571,6 +576,7 @@ const _$UserSettingNameEnumMap = { UserSettingName.emojiset: 'emojiset', UserSettingName.webInboxShowChannelFolders: 'web_inbox_show_channel_folders', UserSettingName.presenceEnabled: 'presence_enabled', + UserSettingName.unknown: 'unknown', }; const _$EmojisetEnumMap = { @@ -586,23 +592,24 @@ const _$PresenceStatusEnumMap = { PresenceStatus.idle: 'idle', }; -const _$ChannelPropertyNameEnumMap = { - ChannelPropertyName.name: 'name', - ChannelPropertyName.isArchived: 'is_archived', - ChannelPropertyName.description: 'description', - ChannelPropertyName.firstMessageId: 'first_message_id', - ChannelPropertyName.inviteOnly: 'invite_only', - ChannelPropertyName.messageRetentionDays: 'message_retention_days', - ChannelPropertyName.topicsPolicy: 'topics_policy', - ChannelPropertyName.channelPostPolicy: 'stream_post_policy', - ChannelPropertyName.folderId: 'folder_id', - ChannelPropertyName.canAddSubscribersGroup: 'can_add_subscribers_group', - ChannelPropertyName.canDeleteAnyMessageGroup: 'can_delete_any_message_group', - ChannelPropertyName.canDeleteOwnMessageGroup: 'can_delete_own_message_group', - ChannelPropertyName.canSendMessageGroup: 'can_send_message_group', - ChannelPropertyName.canSubscribeGroup: 'can_subscribe_group', - ChannelPropertyName.isRecentlyActive: 'is_recently_active', - ChannelPropertyName.streamWeeklyTraffic: 'stream_weekly_traffic', +const _$ChannelPropertyEnumMap = { + ChannelProperty.name: 'name', + ChannelProperty.isArchived: 'is_archived', + ChannelProperty.description: 'description', + ChannelProperty.firstMessageId: 'first_message_id', + ChannelProperty.inviteOnly: 'invite_only', + ChannelProperty.messageRetentionDays: 'message_retention_days', + ChannelProperty.topicsPolicy: 'topics_policy', + ChannelProperty.channelPostPolicy: 'stream_post_policy', + ChannelProperty.folderId: 'folder_id', + ChannelProperty.canAddSubscribersGroup: 'can_add_subscribers_group', + ChannelProperty.canDeleteAnyMessageGroup: 'can_delete_any_message_group', + ChannelProperty.canDeleteOwnMessageGroup: 'can_delete_own_message_group', + ChannelProperty.canSendMessageGroup: 'can_send_message_group', + ChannelProperty.canSubscribeGroup: 'can_subscribe_group', + ChannelProperty.isRecentlyActive: 'is_recently_active', + ChannelProperty.streamWeeklyTraffic: 'stream_weekly_traffic', + ChannelProperty.unknown: 'unknown', }; const _$SubscriptionPropertyEnumMap = { diff --git a/lib/api/route/messages.dart b/lib/api/route/messages.dart index 6724b6236..8a83caf90 100644 --- a/lib/api/route/messages.dart +++ b/lib/api/route/messages.dart @@ -134,12 +134,13 @@ Future sendMessage( String? localId, bool? readBySender, }) { + final supportsTypeChannel = connection.zulipFeatureLevel! >= 248; // TODO(server-9) final supportsTypeDirect = connection.zulipFeatureLevel! >= 174; // TODO(server-7) final supportsReadBySender = connection.zulipFeatureLevel! >= 236; // TODO(server-8) return connection.post('sendMessage', SendMessageResult.fromJson, 'messages', { ...(switch (destination) { StreamDestination() => { - 'type': RawParameter('stream'), + 'type': supportsTypeChannel ? RawParameter('channel') : RawParameter('stream'), 'to': destination.streamId, 'topic': RawParameter(destination.topic.apiName), }, diff --git a/lib/api/route/settings.dart b/lib/api/route/settings.dart index 141994cb2..858fa9af4 100644 --- a/lib/api/route/settings.dart +++ b/lib/api/route/settings.dart @@ -11,19 +11,21 @@ Future updateSettings(ApiConnection connection, { final valueRaw = entry.value; final Object? value; switch (name) { - case UserSettingName.twentyFourHourTime: + case .twentyFourHourTime: final mode = (valueRaw as TwentyFourHourTimeMode); // TODO(server-future) allow localeDefault for servers that support it - assert(mode != TwentyFourHourTimeMode.localeDefault); + assert(mode != .localeDefault); value = mode.toJson(); - case UserSettingName.starredMessageCounts: - case UserSettingName.displayEmojiReactionUsers: + case .starredMessageCounts: + case .displayEmojiReactionUsers: value = valueRaw as bool; - case UserSettingName.emojiset: + case .emojiset: value = RawParameter((valueRaw as Emojiset).toJson()); - case UserSettingName.webInboxShowChannelFolders: - case UserSettingName.presenceEnabled: + case .webInboxShowChannelFolders: + case .presenceEnabled: value = valueRaw as bool; + case .unknown: + continue; } params[name.toJson()] = value; } diff --git a/lib/api/route/typing.dart b/lib/api/route/typing.dart index 8e2e5dae6..81899d867 100644 --- a/lib/api/route/typing.dart +++ b/lib/api/route/typing.dart @@ -7,7 +7,11 @@ import 'messages.dart'; Future setTypingStatus(ApiConnection connection, { required TypingOp op, required MessageDestination destination, -}) { +}) async { + if (op == .unknown) { + assert(false, 'TypingOp.unknown is not supported when setting typing status.'); + return; + } switch (destination) { case StreamDestination(): final supportsTypeChannel = connection.zulipFeatureLevel! >= 248; // TODO(server-9) diff --git a/lib/model/channel.dart b/lib/model/channel.dart index 4f994cede..bd40ba643 100644 --- a/lib/model/channel.dart +++ b/lib/model/channel.dart @@ -236,7 +236,7 @@ mixin ChannelStore on UserStore { if (channel is Subscription) return true; // Here web calls has_metadata_access... but that always returns true, // as its comment says. - if (selfUser.role == UserRole.guest) return false; + if (selfUser.role == .guest) return false; if (!channel.inviteOnly) return true; return _selfHasContentAccessViaGroupPermissions(channel); } @@ -288,14 +288,14 @@ mixin ChannelStore on UserStore { switch (inChannel.channelPostPolicy!) { case ChannelPostPolicy.any: return true; case ChannelPostPolicy.fullMembers: { - if (!role.isAtLeast(UserRole.member)) return false; - if (role == UserRole.member) { + if (!role.isAtLeast(.member)) return false; + if (role == .member) { return selfHasPassedWaitingPeriod(byDate: atDate); } return true; } - case ChannelPostPolicy.moderators: return role.isAtLeast(UserRole.moderator); - case ChannelPostPolicy.administrators: return role.isAtLeast(UserRole.administrator); + case ChannelPostPolicy.moderators: return role.isAtLeast(.moderator); + case ChannelPostPolicy.administrators: return role.isAtLeast(.administrator); case ChannelPostPolicy.unknown: return true; } } @@ -542,48 +542,51 @@ class ChannelStoreImpl extends HasUserStore with ChannelStore { stream.isWebPublic = event.isWebPublic!; } - if (event.property == null) { + if (event.property == .unknown) { // unrecognized property; do nothing return; } - switch (event.property!) { - case ChannelPropertyName.name: + switch (event.property) { + case .name: final streamName = stream.name; assert(streamName == event.name); assert(identical(streams[stream.streamId], streamsByName[streamName])); stream.name = event.value as String; streamsByName.remove(streamName); streamsByName[stream.name] = stream; - case ChannelPropertyName.isArchived: + case .isArchived: stream.isArchived = event.value as bool; - case ChannelPropertyName.description: + case .description: stream.description = event.value as String; - case ChannelPropertyName.firstMessageId: + case .firstMessageId: stream.firstMessageId = event.value as int?; - case ChannelPropertyName.inviteOnly: + case .inviteOnly: stream.inviteOnly = event.value as bool; - case ChannelPropertyName.messageRetentionDays: + case .messageRetentionDays: stream.messageRetentionDays = event.value as int?; - case ChannelPropertyName.topicsPolicy: + case .topicsPolicy: stream.topicsPolicy = event.value as ChannelTopicsPolicy; - case ChannelPropertyName.channelPostPolicy: + case .channelPostPolicy: stream.channelPostPolicy = event.value as ChannelPostPolicy; - case ChannelPropertyName.folderId: + case .folderId: stream.folderId = event.value as int?; - case ChannelPropertyName.canAddSubscribersGroup: + case .canAddSubscribersGroup: stream.canAddSubscribersGroup = event.value as GroupSettingValue; - case ChannelPropertyName.canDeleteAnyMessageGroup: + case .canDeleteAnyMessageGroup: stream.canDeleteAnyMessageGroup = event.value as GroupSettingValue; - case ChannelPropertyName.canDeleteOwnMessageGroup: + case .canDeleteOwnMessageGroup: stream.canDeleteOwnMessageGroup = event.value as GroupSettingValue; - case ChannelPropertyName.canSendMessageGroup: + case .canSendMessageGroup: stream.canSendMessageGroup = event.value as GroupSettingValue; - case ChannelPropertyName.canSubscribeGroup: + case .canSubscribeGroup: stream.canSubscribeGroup = event.value as GroupSettingValue; - case ChannelPropertyName.isRecentlyActive: + case .isRecentlyActive: stream.isRecentlyActive = event.value as bool; - case ChannelPropertyName.streamWeeklyTraffic: + case .streamWeeklyTraffic: stream.streamWeeklyTraffic = event.value as int?; + case .unknown: + // Shouldn't reach here because of the early return. + assert(false); } } } diff --git a/lib/model/emoji.dart b/lib/model/emoji.dart index 7338c50c8..959ab356d 100644 --- a/lib/model/emoji.dart +++ b/lib/model/emoji.dart @@ -20,7 +20,7 @@ sealed class EmojiDisplay { EmojiDisplay resolve(UserSettings userSettings) { if (this is TextEmojiDisplay) return this; - if (userSettings.emojiset == Emojiset.text) { + if (userSettings.emojiset == .text) { return TextEmojiDisplay(emojiName: emojiName); } return this; diff --git a/lib/model/message.dart b/lib/model/message.dart index 17d4775fb..a375b2a44 100644 --- a/lib/model/message.dart +++ b/lib/model/message.dart @@ -7,7 +7,6 @@ import 'package:flutter/foundation.dart'; import '../api/exception.dart'; import '../api/model/events.dart'; -import '../api/model/initial_snapshot.dart'; import '../api/model/model.dart'; import '../api/route/messages.dart'; import '../log.dart'; @@ -185,21 +184,21 @@ mixin MessageStore on ChannelStore { // but pre-291 servers shouldn't be giving us an unknown role.) switch (realmDeleteOwnMessagePolicy!) { - case RealmDeleteOwnMessagePolicy.everyone: + case .everyone: return true; - case RealmDeleteOwnMessagePolicy.members: - return role.isAtLeast(UserRole.member); - case RealmDeleteOwnMessagePolicy.fullMembers: { - if (!role.isAtLeast(UserRole.member)) return false; - if (role == UserRole.member) { + case .members: + return role.isAtLeast(.member); + case .fullMembers: { + if (!role.isAtLeast(.member)) return false; + if (role == .member) { return selfHasPassedWaitingPeriod(byDate: atDate); } return true; } - case RealmDeleteOwnMessagePolicy.moderators: - return role.isAtLeast(UserRole.moderator); - case RealmDeleteOwnMessagePolicy.admins: - return role.isAtLeast(UserRole.administrator); + case .moderators: + return role.isAtLeast(.moderator); + case .admins: + return role.isAtLeast(.administrator); } } } @@ -832,18 +831,20 @@ class MessageStoreImpl extends HasChannelStore with MessageStore, _OutboxMessage } void handleReactionEvent(ReactionEvent event) { + if (event.op == .unknown) return; + final message = messages[event.messageId]; if (message == null) return; switch (event.op) { - case ReactionOp.add: + case .add: (message.reactions ??= Reactions([])).add(Reaction( emojiName: event.emojiName, emojiCode: event.emojiCode, reactionType: event.reactionType, userId: event.userId, )); - case ReactionOp.remove: + case .remove: if (message.reactions == null) { // TODO(log) return; } @@ -852,6 +853,8 @@ class MessageStoreImpl extends HasChannelStore with MessageStore, _OutboxMessage emojiCode: event.emojiCode, userId: event.userId, ); + case .unknown: + // Shouldn't reach here because of the early return. } _notifyMessageListViewsForOneMessage(event.messageId); } diff --git a/lib/model/narrow.dart b/lib/model/narrow.dart index 8042dbee5..aa47b5328 100644 --- a/lib/model/narrow.dart +++ b/lib/model/narrow.dart @@ -241,7 +241,7 @@ class DmNarrow extends Narrow implements SendableNarrow { UpdateMessageFlagsMessageDetail detail, { required int selfUserId, }) { - assert(detail.type == MessageType.direct); + assert(detail.type == .direct); return DmNarrow.withOtherUsers(detail.userIds!, selfUserId: selfUserId); } diff --git a/lib/model/presence.dart b/lib/model/presence.dart index 882f12031..8772c33c2 100644 --- a/lib/model/presence.dart +++ b/lib/model/presence.dart @@ -76,14 +76,14 @@ class Presence extends HasRealmStore with ChangeNotifier { // when the user closes the app completely. result = await updatePresence(connection, pingOnly: pingOnly, - status: PresenceStatus.idle, + status: .idle, newUserInput: false); case AppLifecycleState.resumed: // > […] the default running mode for a running application that has // > input focus and is visible. result = await updatePresence(connection, pingOnly: pingOnly, - status: PresenceStatus.active, + status: .active, newUserInput: true); case AppLifecycleState.inactive: // > At least one view of the application is visible, but none have @@ -92,7 +92,7 @@ class Presence extends HasRealmStore with ChangeNotifier { // to upload. result = await updatePresence(connection, pingOnly: pingOnly, - status: PresenceStatus.active, + status: .active, newUserInput: false); } if (!pingOnly) { @@ -141,12 +141,12 @@ class Presence extends HasRealmStore with ChangeNotifier { final PerUserPresence(:activeTimestamp, :idleTimestamp) = perUserPresence; if (now - activeTimestamp <= serverPresenceOfflineThresholdSeconds) { - return PresenceStatus.active; + return .active; } else if (now - idleTimestamp <= serverPresenceOfflineThresholdSeconds) { // The API doc is kind of confusing, but this seems correct: // https://chat.zulip.org/#narrow/channel/378-api-design/topic/presence.3A.20.22potentially.20present.22.3F/near/2202431 // TODO clarify that API doc - return PresenceStatus.idle; + return .idle; } else { return null; } diff --git a/lib/model/realm.dart b/lib/model/realm.dart index cffb3095f..2691b79ef 100644 --- a/lib/model/realm.dart +++ b/lib/model/realm.dart @@ -73,7 +73,7 @@ mixin RealmStore on PerAccountStoreBase, UserGroupStore { // Realm settings previously found in realm/update_dict events, // but now deprecated. - RealmWildcardMentionPolicy get realmWildcardMentionPolicy; // TODO(server-10) remove + RealmWildcardMentionPolicy? get realmWildcardMentionPolicy; // TODO(server-10) remove RealmDeleteOwnMessagePolicy? get realmDeleteOwnMessagePolicy; // TODO(server-10) remove //|////////////////////////////// @@ -212,7 +212,7 @@ mixin ProxyRealmStore on RealmStore { @override int get realmWaitingPeriodThreshold => realmStore.realmWaitingPeriodThreshold; @override - RealmWildcardMentionPolicy get realmWildcardMentionPolicy => realmStore.realmWildcardMentionPolicy; + RealmWildcardMentionPolicy? get realmWildcardMentionPolicy => realmStore.realmWildcardMentionPolicy; @override RealmDeleteOwnMessagePolicy? get realmDeleteOwnMessagePolicy => realmStore.realmDeleteOwnMessagePolicy; @override @@ -317,7 +317,7 @@ class RealmStoreImpl extends HasUserGroupStore with RealmStore { final config = _groupSettingConfig(type, name); - if (_selfUserRole == UserRole.guest && !config.allowEveryoneGroup) { + if (_selfUserRole == .guest && !config.allowEveryoneGroup) { return false; } @@ -348,7 +348,7 @@ class RealmStoreImpl extends HasUserGroupStore with RealmStore { case SystemGroupName.everyone: return true; case SystemGroupName.members: - return _selfUserRole.isAtLeast(UserRole.member); + return _selfUserRole.isAtLeast(.member); case SystemGroupName.fullMembers: // There aren't any permissions where this is the default, and we // probably won't add any. So for now we skip the complication of @@ -356,13 +356,13 @@ class RealmStoreImpl extends HasUserGroupStore with RealmStore { assert(() { throw UnimplementedError(); }()); - return _selfUserRole.isAtLeast(UserRole.member); + return _selfUserRole.isAtLeast(.member); case SystemGroupName.moderators: - return _selfUserRole.isAtLeast(UserRole.moderator); + return _selfUserRole.isAtLeast(.moderator); case SystemGroupName.administrators: - return _selfUserRole.isAtLeast(UserRole.administrator); + return _selfUserRole.isAtLeast(.administrator); case SystemGroupName.owners: - return _selfUserRole.isAtLeast(UserRole.owner); + return _selfUserRole.isAtLeast(.owner); case SystemGroupName.nobody: return false; } @@ -449,7 +449,7 @@ class RealmStoreImpl extends HasUserGroupStore with RealmStore { final int realmWaitingPeriodThreshold; @override - final RealmWildcardMentionPolicy realmWildcardMentionPolicy; + final RealmWildcardMentionPolicy? realmWildcardMentionPolicy; @override final RealmDeleteOwnMessagePolicy? realmDeleteOwnMessagePolicy; diff --git a/lib/model/recent_senders.dart b/lib/model/recent_senders.dart index 274d70fd0..2e2d4b617 100644 --- a/lib/model/recent_senders.dart +++ b/lib/model/recent_senders.dart @@ -87,7 +87,7 @@ class RecentSenders { } void handleDeleteMessageEvent(DeleteMessageEvent event, Map cachedMessages) { - if (event.messageType != MessageType.stream) return; + if (event.messageType != .channel) return; final messagesByUser = >{}; for (final id in event.messageIds) { diff --git a/lib/model/store.dart b/lib/model/store.dart index 5b554bf64..8508e1248 100644 --- a/lib/model/store.dart +++ b/lib/model/store.dart @@ -903,24 +903,27 @@ class PerAccountStore extends PerAccountStoreBase with // We don't yet store this data, so there's nothing to update. case UserSettingsUpdateEvent(): - assert(debugLog("server event: user_settings/update ${event.property?.name ?? '[unrecognized]'}")); - if (event.property == null) { + assert(debugLog("server event: user_settings/update ${event.property.name}")); + if (event.property == .unknown) { // unrecognized setting; do nothing return; } - switch (event.property!) { - case UserSettingName.twentyFourHourTime: + switch (event.property) { + case .twentyFourHourTime: userSettings.twentyFourHourTime = event.value as TwentyFourHourTimeMode; - case UserSettingName.starredMessageCounts: + case .starredMessageCounts: userSettings.starredMessageCounts = event.value as bool; - case UserSettingName.displayEmojiReactionUsers: + case .displayEmojiReactionUsers: userSettings.displayEmojiReactionUsers = event.value as bool; - case UserSettingName.emojiset: + case .emojiset: userSettings.emojiset = event.value as Emojiset; - case UserSettingName.webInboxShowChannelFolders: + case .webInboxShowChannelFolders: userSettings.webInboxShowChannelFolders = event.value as bool; - case UserSettingName.presenceEnabled: + case .presenceEnabled: userSettings.presenceEnabled = event.value as bool; + case .unknown: + // Shouldn't reach here because of the early return. + assert(false); } notifyListeners(); diff --git a/lib/model/typing_status.dart b/lib/model/typing_status.dart index fdb63339b..f8b8f22a3 100644 --- a/lib/model/typing_status.dart +++ b/lib/model/typing_status.dart @@ -62,18 +62,22 @@ class TypingStatus extends HasRealmStore with ChangeNotifier { } void handleTypingEvent(TypingEvent event) { + if (event.op == .unknown) return; + SendableNarrow narrow = switch (event.messageType) { - MessageType.direct => DmNarrow( + .direct => DmNarrow( allRecipientIds: event.recipientIds!, selfUserId: selfUserId), - MessageType.stream => TopicNarrow(event.streamId!, event.topic!), + .channel => TopicNarrow(event.streamId!, event.topic!), }; bool hasUpdate = false; switch (event.op) { - case TypingOp.start: + case .start: hasUpdate = _addTypist(narrow, event.senderId); - case TypingOp.stop: + case .stop: hasUpdate = _removeTypist(narrow, event.senderId); + case .unknown: + // Shouldn't reach here because of the early return. } if (hasUpdate) { @@ -176,7 +180,7 @@ class TypingNotifier extends HasRealmStore { unawaited(setTypingStatus( connection, - op: TypingOp.start, + op: .start, destination: _currentDestination!.destination)); } @@ -190,7 +194,7 @@ class TypingNotifier extends HasRealmStore { unawaited(setTypingStatus( connection, - op: TypingOp.stop, + op: .stop, destination: destination.destination)); } diff --git a/lib/model/unreads.dart b/lib/model/unreads.dart index a45f53f50..5e8096155 100644 --- a/lib/model/unreads.dart +++ b/lib/model/unreads.dart @@ -412,13 +412,13 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier { void handleDeleteMessageEvent(DeleteMessageEvent event) { mentions.removeAll(event.messageIds); switch (event.messageType) { - case MessageType.stream: + case .channel: // All the messages are in [event.streamId] and [event.topic], // so we can be more efficient than _removeAllInStreamsAndDms. final streamId = event.streamId!; final topic = event.topic!; _removeAllInStreamTopic(Set.of(event.messageIds), streamId, topic); - case MessageType.direct: + case .direct: _removeAllInStreamsAndDms(event.messageIds, expectOnlyDms: true); } for (final messageId in event.messageIds) { @@ -490,13 +490,13 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier { mentions.add(messageId); } switch (detail.type) { - case MessageType.stream: + case .channel: final UpdateMessageFlagsMessageDetail(:streamId, :topic) = detail; locatorMap[messageId] = TopicNarrow(streamId!, topic!); final topics = (newlyUnreadInStreams[streamId] ??= makeTopicKeyedMap()); final messageIds = (topics[topic] ??= QueueList()); messageIds.add(messageId); - case MessageType.direct: + case .direct: final narrow = DmNarrow.ofUpdateMessageFlagsMessageDetail(selfUserId: selfUserId, detail); locatorMap[messageId] = narrow; diff --git a/lib/widgets/content.dart b/lib/widgets/content.dart index a6dcc5fe9..be3082c85 100644 --- a/lib/widgets/content.dart +++ b/lib/widgets/content.dart @@ -1439,9 +1439,9 @@ class GlobalTime extends StatelessWidget { // see zulip:web/styles/rendered_markdown.css . // TODO(i18n): localize; see plan with ffi in #45 final format = switch (twentyFourHourTimeMode) { - TwentyFourHourTimeMode.twelveHour => _format12, - TwentyFourHourTimeMode.twentyFourHour => _format24, - TwentyFourHourTimeMode.localeDefault => _formatLocaleDefault, + .twelveHour => _format12, + .twentyFourHour => _format24, + .localeDefault => _formatLocaleDefault, }; final text = format.format(node.datetime.toLocal()); final contentTheme = ContentTheme.of(context); diff --git a/lib/widgets/home.dart b/lib/widgets/home.dart index 79ce45f4e..52c4c1a15 100644 --- a/lib/widgets/home.dart +++ b/lib/widgets/home.dart @@ -4,7 +4,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import '../api/core.dart'; -import '../api/model/model.dart'; import '../generated/l10n/zulip_localizations.dart'; import '../model/narrow.dart'; import 'about_zulip.dart'; @@ -871,7 +870,7 @@ class _ServerCompatBanner extends StatelessWidget { final showCompatBanner = store.zulipFeatureLevel < kMinSupportedZulipFeatureLevel && !globalStore.getServerCompatBannerDismissed(store.accountId); - final isAtLeastAdmin = store.selfUser.role.isAtLeast(UserRole.administrator); + final isAtLeastAdmin = store.selfUser.role.isAtLeast(.administrator); final label = isAtLeastAdmin ? zulipLocalizations.serverCompatBannerAdminMessage( store.realmUrl.toString(), store.zulipVersion) diff --git a/lib/widgets/message_list.dart b/lib/widgets/message_list.dart index 602e7c992..dfd3dff20 100644 --- a/lib/widgets/message_list.dart +++ b/lib/widgets/message_list.dart @@ -2257,15 +2257,15 @@ enum MessageTimestampStyle { static final _timeFormatLocaleDefaultWithSeconds = DateFormat('jms'); static DateFormat _resolveTimeFormat(TwentyFourHourTimeMode mode) => switch (mode) { - TwentyFourHourTimeMode.twelveHour => _timeFormat12, - TwentyFourHourTimeMode.twentyFourHour => _timeFormat24, - TwentyFourHourTimeMode.localeDefault => _timeFormatLocaleDefault, + .twelveHour => _timeFormat12, + .twentyFourHour => _timeFormat24, + .localeDefault => _timeFormatLocaleDefault, }; static DateFormat _resolveTimeFormatWithSeconds(TwentyFourHourTimeMode mode) => switch (mode) { - TwentyFourHourTimeMode.twelveHour => _timeFormat12WithSeconds, - TwentyFourHourTimeMode.twentyFourHour => _timeFormat24WithSeconds, - TwentyFourHourTimeMode.localeDefault => _timeFormatLocaleDefaultWithSeconds, + .twelveHour => _timeFormat12WithSeconds, + .twentyFourHour => _timeFormat24WithSeconds, + .localeDefault => _timeFormatLocaleDefaultWithSeconds, }; /// Format a [Message.timestamp] for this mode. diff --git a/lib/widgets/profile.dart b/lib/widgets/profile.dart index f10f6211d..7bd49162d 100644 --- a/lib/widgets/profile.dart +++ b/lib/widgets/profile.dart @@ -191,8 +191,8 @@ class _LastActiveTimeState extends State<_LastActiveTime> with PerAccountStoreAw final status = model!.presenceStatusForUser(widget.userId, utcNow: nowDate); switch (status) { - case PresenceStatus.active: return zulipLocalizations.userActiveNow; - case PresenceStatus.idle: return zulipLocalizations.userIdle; + case .active: return zulipLocalizations.userActiveNow; + case .idle: return zulipLocalizations.userIdle; case null: break; // handle below } @@ -297,7 +297,7 @@ class _InvisibleModeToggle extends StatelessWidget { return RemoteSettingBuilder( findValueInStore: (store) => !store.userSettings.presenceEnabled, sendValueToServer: (value) => updateSettings(store.connection, - newSettings: {UserSettingName.presenceEnabled: !value}), + newSettings: {.presenceEnabled: !value}), // TODO(#741) interpret API errors for user onError: (e, requestedValue) => reportErrorToUserBriefly( requestedValue @@ -334,12 +334,12 @@ class _ProfileErrorPage extends StatelessWidget { String roleToLabel(UserRole role, ZulipLocalizations zulipLocalizations) { return switch (role) { - UserRole.owner => zulipLocalizations.userRoleOwner, - UserRole.administrator => zulipLocalizations.userRoleAdministrator, - UserRole.moderator => zulipLocalizations.userRoleModerator, - UserRole.member => zulipLocalizations.userRoleMember, - UserRole.guest => zulipLocalizations.userRoleGuest, - UserRole.unknown => zulipLocalizations.userRoleUnknown, + .owner => zulipLocalizations.userRoleOwner, + .administrator => zulipLocalizations.userRoleAdministrator, + .moderator => zulipLocalizations.userRoleModerator, + .member => zulipLocalizations.userRoleMember, + .guest => zulipLocalizations.userRoleGuest, + .unknown => zulipLocalizations.userRoleUnknown, }; } @@ -362,16 +362,16 @@ class _ProfileDataTable extends StatelessWidget { final store = PerAccountStoreWidget.of(context); switch (realmField.type) { - case CustomProfileFieldType.link: + case .link: return _LinkWidget(url: value, text: value); - case CustomProfileFieldType.choice: + case .choice: final choiceFieldData = _tryDecode(CustomProfileFieldChoiceDataItem.parseFieldDataChoices, realmField.fieldData); if (choiceFieldData == null) return null; final choiceItem = choiceFieldData[value]; return (choiceItem == null) ? null : _TextWidget(text: choiceItem.text); - case CustomProfileFieldType.externalAccount: + case .externalAccount: final externalAccountFieldData = _tryDecode(CustomProfileFieldExternalAccountData.fromJson, realmField.fieldData); if (externalAccountFieldData == null) return null; final urlPattern = externalAccountFieldData.urlPattern ?? @@ -380,7 +380,7 @@ class _ProfileDataTable extends StatelessWidget { final url = urlPattern.replaceFirst('%(username)s', value); return _LinkWidget(url: url, text: value); - case CustomProfileFieldType.user: + case .user: // TODO(server): This is completely undocumented. The key to // reverse-engineering it was: // https://github.com/zulip/zulip/blob/18230fcd9/static/js/settings_account.js#L247 @@ -391,21 +391,21 @@ class _ProfileDataTable extends StatelessWidget { return Column( children: userIds.map((userId) => _UserWidget(userId: userId)).toList()); - case CustomProfileFieldType.date: + case .date: // TODO(server): The value's format is undocumented, but empirically // it's a date in ISO format, like 2000-01-01. // That's readable as is, but: // TODO(i18n) format this date using user's locale. return _TextWidget(text: value); - case CustomProfileFieldType.shortText: - case CustomProfileFieldType.longText: - case CustomProfileFieldType.pronouns: + case .shortText: + case .longText: + case .pronouns: // The web client appears to treat `longText` identically to `shortText`; // `pronouns` is explicitly meant to display the same as `shortText`. return _TextWidget(text: value); - case CustomProfileFieldType.unknown: + case .unknown: return null; } } diff --git a/lib/widgets/subscription_list.dart b/lib/widgets/subscription_list.dart index 5143ab6c6..1ecc88195 100644 --- a/lib/widgets/subscription_list.dart +++ b/lib/widgets/subscription_list.dart @@ -114,7 +114,7 @@ class _SubscriptionListPageBodyState extends State wit // > Guests can never subscribe themselves to a channel. // (Web also hides the corresponding link for guests; // see web/templates/left_sidebar.hbs.) - && store.selfUser.role.isAtLeast(UserRole.member); + && store.selfUser.role.isAtLeast(.member); final List pinned = []; final List unpinned = []; diff --git a/lib/widgets/user.dart b/lib/widgets/user.dart index 013dfcea0..83d9d7279 100644 --- a/lib/widgets/user.dart +++ b/lib/widgets/user.dart @@ -263,9 +263,9 @@ class _PresenceCircleState extends State with PerAccountStoreAwa } else { return SizedBox.square(dimension: widget.size); } - case PresenceStatus.active: + case .active: color = designVariables.statusOnline; - case PresenceStatus.idle: + case .idle: gradient = LinearGradient( begin: AlignmentDirectional.centerStart, end: AlignmentDirectional.centerEnd, diff --git a/test/api/model/events_checks.dart b/test/api/model/events_checks.dart index 030172169..64cbb614e 100644 --- a/test/api/model/events_checks.dart +++ b/test/api/model/events_checks.dart @@ -15,6 +15,11 @@ extension AlertWordsEventChecks on Subject { Subject> get alertWords => has((e) => e.alertWords, 'alertWords'); } +extension UserSettingsUpdateEventChecks on Subject { + Subject get property => has((e) => e.property, 'property'); + Subject get value => has((e) => e.value, 'value'); +} + extension DeviceUpdateEventChecks on Subject { Subject?> get pushKeyId => has((e) => e.pushKeyId, 'pushKeyId'); Subject?> get pushTokenId => has((e) => e.pushTokenId, 'pushTokenId'); @@ -36,6 +41,11 @@ extension RealmUserUpdateEventChecks on Subject { Subject?> get deliveryEmail => has((e) => e.deliveryEmail, 'deliveryEmail'); } +extension ChannelUpdateEventChecks on Subject { + Subject get property => has((e) => e.property, 'property'); + Subject get value => has((e) => e.value, 'value'); +} + extension SubscriptionRemoveEventChecks on Subject { Subject> get channelIds => has((e) => e.channelIds, 'channelIds'); } @@ -85,6 +95,7 @@ extension UpdateMessageFlagsMessageDetailCheck on Subject { + Subject get op => has((e) => e.op, 'op'); Subject get messageType => has((e) => e.messageType, 'messageType'); Subject get senderId => has((e) => e.senderId, 'senderId'); Subject?> get recipientIds => has((e) => e.recipientIds, 'recipientIds'); @@ -92,6 +103,10 @@ extension TypingEventChecks on Subject { Subject get topic => has((e) => e.topic, 'topic'); } +extension ReactionEventChecks on Subject { + Subject get op => has((e) => e.op, 'op'); +} + extension HeartbeatEventChecks on Subject { // No properties not covered by Event. } diff --git a/test/api/model/events_test.dart b/test/api/model/events_test.dart index 234915c39..518ea4592 100644 --- a/test/api/model/events_test.dart +++ b/test/api/model/events_test.dart @@ -30,6 +30,26 @@ void main() { ).isEmpty(); }); + test('user_settings/update: unknown property', () { + final json = Map.unmodifiable({ + 'id': 1, + 'type': 'user_settings', + 'op': 'update', + 'property': 'twenty_four_hour_time', + 'value': true, + }); + + check(UserSettingsUpdateEvent.fromJson(json)) + ..property.equals(.twentyFourHourTime) + ..value.equals(TwentyFourHourTimeMode.twentyFourHour); + + for (final unknown in ['unknown_user_setting_name', '']) { + check(UserSettingsUpdateEvent.fromJson({...json, 'property': unknown})) + ..property.equals(.unknown) + ..value.equals(null); + } + }); + group('device/update', () { final baseJson = {'id': 1, 'type': 'device', 'op': 'update', 'device_id': 3 }; @@ -86,6 +106,28 @@ void main() { }); }); + test('stream/update: unknown property', () { + final json = Map.unmodifiable({ + 'id': 1, + 'type': 'stream', + 'op': 'update', + 'stream_id': 1, + 'name': 'channel name', + 'property': 'is_recently_active', + 'value': true, + }); + + check(ChannelUpdateEvent.fromJson(json)) + ..property.equals(.isRecentlyActive) + ..value.equals(true); + + for (final unknown in ['unknown_channel_property', '']) { + check(ChannelUpdateEvent.fromJson({...json, 'property': unknown})) + ..property.equals(.unknown) + ..value.equals(null); + } + }); + test('subscription/remove: deserialize stream_ids correctly', () { check(Event.fromJson({ 'id': 1, @@ -245,6 +287,7 @@ void main() { 'message_type': 'private', })).returnsNormally(); + // TODO(server-future): remove final baseJsonStream = { 'id': 1, 'type': 'delete_message', @@ -252,21 +295,31 @@ void main() { 'message_type': 'stream', }; - check(() => DeleteMessageEvent.fromJson({ - ...baseJsonStream - })).throws(); + // Future server. + final baseJsonChannel = { + 'id': 1, + 'type': 'delete_message', + 'message_ids': [1, 2, 3], + 'message_type': 'channel', + }; - check(() => DeleteMessageEvent.fromJson({ - ...baseJsonStream, 'stream_id': 1, 'topic': 'some topic', - })).returnsNormally(); + for (final baseJson in [baseJsonStream, baseJsonChannel]) { + check(() => DeleteMessageEvent.fromJson({ + ...baseJson + })).throws(); - check(() => DeleteMessageEvent.fromJson({ - ...baseJsonStream, 'stream_id': 1, - })).throws(); + check(() => DeleteMessageEvent.fromJson({ + ...baseJson, 'stream_id': 1, 'topic': 'some topic', + })).returnsNormally(); - check(() => DeleteMessageEvent.fromJson({ - ...baseJsonStream, 'topic': 'some topic', - })).throws(); + check(() => DeleteMessageEvent.fromJson({ + ...baseJson, 'stream_id': 1, + })).throws(); + + check(() => DeleteMessageEvent.fromJson({ + ...baseJson, 'topic': 'some topic', + })).throws(); + } }); test('delete_message: private -> direct', () { @@ -275,7 +328,18 @@ void main() { 'type': 'delete_message', 'message_ids': [1, 2, 3], 'message_type': 'private', - })).messageType.equals(MessageType.direct); + })).messageType.equals(.direct); + }); + + test('delete_message: stream -> channel', () { + check(DeleteMessageEvent.fromJson({ + 'id': 1, + 'type': 'delete_message', + 'message_ids': [1, 2, 3], + 'message_type': 'stream', + 'stream_id': 1, + 'topic': 'some topic', + })).messageType.equals(.channel); }); group('update_message_flags/remove', () { @@ -310,7 +374,17 @@ void main() { ...messageDetail, 'type': 'private', }}})).messageDetails.isNotNull() - .values.single.type.equals(MessageType.direct); + .values.single.type.equals(.direct); + }); + + test('stream -> channel', () { + check(UpdateMessageFlagsRemoveEvent.fromJson({ + ...baseJson, + 'flag': 'read', + 'message_details': { + '123': {'type': 'stream', 'mentioned': false, 'stream_id': 1, 'topic': 'some topic'} + }})).messageDetails.isNotNull() + .values.single.type.equals(.channel); }); }); @@ -328,6 +402,16 @@ void main() { 'recipients': [1, 2, 3].map((e) => {'user_id': e, 'email': '$e@example.com'}).toList(), }; + test('handle unknown op', () { + check(TypingEvent.fromJson(directMessageJson)) + .op.equals(.start); + + for (final unknown in ['unknown_op', '']) { + check(TypingEvent.fromJson({...directMessageJson, 'op': unknown})) + .op.equals(.unknown); + } + }); + test('direct message typing events', () { check(TypingEvent.fromJson(directMessageJson)) ..recipientIds.isNotNull().deepEquals([1, 2, 3]) @@ -343,19 +427,35 @@ void main() { check(TypingEvent.fromJson({ ...directMessageJson, 'message_type': 'private', - })).messageType.equals(MessageType.direct); + })).messageType.equals(.direct); }); - test('stream type missing streamId/topic', () { - check(() => TypingEvent.fromJson({ - ...baseJson, 'message_type': 'stream', 'stream_id': 123, 'topic': 'foo'})) - .returnsNormally(); - check(() => TypingEvent.fromJson({ - ...baseJson, 'message_type': 'stream'})).throws(); - check(() => TypingEvent.fromJson({ - ...baseJson, 'message_type': 'stream', 'topic': 'foo'})).throws(); - check(() => TypingEvent.fromJson({ - ...baseJson, 'message_type': 'stream', 'stream_id': 123})).throws(); + test('stream/channel type missing streamId/topic', () { + // TODO(server-future): remove + final baseJsonStream = {...baseJson, 'message_type': 'stream'}; + // Future server. + final baseJsonChannel = {...baseJson, 'message_type': 'channel'}; + + for (final baseJson in [baseJsonStream, baseJsonChannel]) { + check(() => TypingEvent.fromJson({ + ...baseJson, 'stream_id': 123, 'topic': 'foo'})) + .returnsNormally(); + check(() => TypingEvent.fromJson({ + ...baseJson})).throws(); + check(() => TypingEvent.fromJson({ + ...baseJson, 'topic': 'foo'})).throws(); + check(() => TypingEvent.fromJson({ + ...baseJson, 'stream_id': 123})).throws(); + } + }); + + test('stream -> channel', () { + check(TypingEvent.fromJson({ + ...baseJson, + 'stream_id': 123, + 'topic': 'foo', + 'message_type': 'stream', + })).messageType.equals(.channel); }); test('direct type sort recipient ids', () { @@ -365,4 +465,25 @@ void main() { })).recipientIds.isNotNull().deepEquals([1, 2, 4, 8, 10]); }); }); + + group('reaction event', () { + final json = Map.unmodifiable({ + 'id': 1, + 'type': 'reaction', + 'op': 'add', + 'emoji_name': '+1', + 'emoji_code': '1f44d', + 'reaction_type': 'unicode_emoji', + 'user_id': 100, + 'message_id': 1000, + }); + + test('handle unknown op', () { + check(ReactionEvent.fromJson(json)).op.equals(.add); + + for (final unknown in ['unknown_op', '']) { + check(ReactionEvent.fromJson({...json, 'op': unknown})).op.equals(.unknown); + } + }); + }); } diff --git a/test/api/model/initial_snapshot_test.dart b/test/api/model/initial_snapshot_test.dart index 2dff367d2..918099032 100644 --- a/test/api/model/initial_snapshot_test.dart +++ b/test/api/model/initial_snapshot_test.dart @@ -69,7 +69,7 @@ void main() { for (final unknownValue in unknownValues) { final json = eg.userSettings().toJson()..['emojiset'] = unknownValue; final settings = UserSettings.fromJson(json); - check(settings.emojiset).equals(Emojiset.unknown); + check(settings.emojiset).equals(.unknown); } }); } diff --git a/test/api/model/model_checks.dart b/test/api/model/model_checks.dart index dddd4d8a9..0aebdb256 100644 --- a/test/api/model/model_checks.dart +++ b/test/api/model/model_checks.dart @@ -109,7 +109,7 @@ extension MessageChecks on Subject { Subject get senderFullName => has((e) => e.senderFullName, 'senderFullName'); Subject get senderRealmStr => has((e) => e.senderRealmStr, 'senderRealmStr'); Subject get poll => has((e) => e.poll, 'poll'); - Subject get type => has((e) => e.type, 'type'); + Subject get type => has((e) => e.type, 'type'); Subject> get flags => has((e) => e.flags, 'flags'); Subject get matchContent => has((e) => e.matchContent, 'matchContent'); Subject get matchTopic => has((e) => e.matchTopic, 'matchTopic'); diff --git a/test/api/model/model_test.dart b/test/api/model/model_test.dart index f04bbd6d4..2eed4d251 100644 --- a/test/api/model/model_test.dart +++ b/test/api/model/model_test.dart @@ -91,6 +91,16 @@ void main() { expected: (OptionNone(), OptionNone())); }); + test('UserSettingName.fromRawString handles unknown values', () { + check(UserSettingName.fromRawString('twenty_four_hour_time')) + .equals(.twentyFourHourTime); + + for (final unknown in ['unknown_user_setting_name', '']) { + check(UserSettingName.fromRawString(unknown)) + .equals(.unknown); + } + }); + group('User', () { final Map baseJson = Map.unmodifiable({ 'user_id': 123, @@ -131,6 +141,14 @@ void main() { }); }); + test('ChannelProperty.fromRawString handles unknown values', () { + check(ChannelProperty.fromRawString('is_recently_active')).equals(.isRecentlyActive); + + for (final unknown in ['unknown_channel_property', '']) { + check(ChannelProperty.fromRawString(unknown)).equals(.unknown); + } + }); + group('Subscription', () { test('converts color to int', () { Subscription subWithColor(String color) { @@ -149,6 +167,33 @@ void main() { Map baseStreamJson() => deepToJson(eg.streamMessage()) as Map; + Map baseDmJson() => + deepToJson(eg.dmMessage(from: eg.selfUser, to: [])) as Map; + + test('type: stream -> channel', () { + check(baseStreamJson()['type']).equals('channel'); + check(Message.fromJson(baseStreamJson())) + .isA() + .type.equals(.channel); + + check(Message.fromJson(baseStreamJson() + ..['type'] = 'stream' + )).isA() + .type.equals(.channel); + }); + + test('type: private -> direct', () { + check(baseDmJson()['type']).equals('direct'); + check(Message.fromJson(baseDmJson())) + .isA() + .type.equals(.direct); + + check(Message.fromJson(baseDmJson() + ..['type'] = 'private' + )).isA() + .type.equals(.direct); + }); + test('subject -> topic', () { check(baseStreamJson()).not((it) => it.containsKey('topic')); check(Message.fromJson(baseStreamJson() diff --git a/test/api/route/messages_test.dart b/test/api/route/messages_test.dart index 1b4f86c62..0395645d0 100644 --- a/test/api/route/messages_test.dart +++ b/test/api/route/messages_test.dart @@ -286,7 +286,7 @@ void main() { localId: '456', readBySender: true, expectedBodyFields: { - 'type': 'stream', + 'type': 'channel', 'to': streamId.toString(), 'topic': topic, 'content': content, @@ -299,6 +299,21 @@ void main() { test('to stream', () { return FakeApiConnection.with_((connection) async { + await checkSendMessage(connection, + destination: StreamDestination(streamId, eg.t(topic)), content: content, + readBySender: true, + expectedBodyFields: { + 'type': 'channel', + 'to': streamId.toString(), + 'topic': topic, + 'content': content, + 'read_by_sender': 'true', + }); + }); + }); + + test('to stream, with legacy type "stream"', () { + return FakeApiConnection.with_(zulipFeatureLevel: 247, (connection) async { await checkSendMessage(connection, destination: StreamDestination(streamId, eg.t(topic)), content: content, readBySender: true, @@ -347,7 +362,7 @@ void main() { destination: StreamDestination(streamId, eg.t(topic)), content: content, readBySender: null, expectedBodyFields: { - 'type': 'stream', + 'type': 'channel', 'to': streamId.toString(), 'topic': topic, 'content': content, diff --git a/test/api/route/settings_test.dart b/test/api/route/settings_test.dart index a89faaa99..5f2f70e45 100644 --- a/test/api/route/settings_test.dart +++ b/test/api/route/settings_test.dart @@ -16,24 +16,26 @@ void main() { final expectedBodyFields = {}; for (final name in UserSettingName.values) { switch (name) { - case UserSettingName.twentyFourHourTime: + case .twentyFourHourTime: newSettings[name] = TwentyFourHourTimeMode.twelveHour; expectedBodyFields['twenty_four_hour_time'] = 'false'; - case UserSettingName.starredMessageCounts: + case .starredMessageCounts: newSettings[name] = false; expectedBodyFields['starred_message_counts'] = 'false'; - case UserSettingName.displayEmojiReactionUsers: + case .displayEmojiReactionUsers: newSettings[name] = false; expectedBodyFields['display_emoji_reaction_users'] = 'false'; - case UserSettingName.emojiset: + case .emojiset: newSettings[name] = Emojiset.googleBlob; expectedBodyFields['emojiset'] = 'google-blob'; - case UserSettingName.webInboxShowChannelFolders: + case .webInboxShowChannelFolders: newSettings[name] = false; expectedBodyFields['web_inbox_show_channel_folders'] = 'false'; - case UserSettingName.presenceEnabled: + case .presenceEnabled: newSettings[name] = true; expectedBodyFields['presence_enabled'] = 'true'; + case .unknown: + continue; } } @@ -52,7 +54,7 @@ void main() { // TODO(server-future) instead, check for twenty_four_hour_time: null // (could be an error-prone part of the JSONification) check(() => updateSettings(connection, - newSettings: {UserSettingName.twentyFourHourTime: TwentyFourHourTimeMode.localeDefault}) + newSettings: {.twentyFourHourTime: TwentyFourHourTimeMode.localeDefault}) ).throws(); }); }); diff --git a/test/api/route/typing_test.dart b/test/api/route/typing_test.dart index 551f1a5a4..c6210f0a6 100644 --- a/test/api/route/typing_test.dart +++ b/test/api/route/typing_test.dart @@ -43,16 +43,16 @@ void main() { } test('send typing status start for topic', () { - return checkSetTypingStatusForTopic(TypingOp.start, 'start'); + return checkSetTypingStatusForTopic(.start, 'start'); }); test('send typing status stop for topic', () { - return checkSetTypingStatusForTopic(TypingOp.stop, 'stop'); + return checkSetTypingStatusForTopic(.stop, 'stop'); }); test('send typing status start for dm', () { return FakeApiConnection.with_((connection) { - return checkSetTypingStatus(connection, TypingOp.start, + return checkSetTypingStatus(connection, .start, destination: const DmDestination(userIds: userIds), expectedBodyFields: { 'op': 'start', @@ -64,7 +64,7 @@ void main() { test('legacy: use "stream" instead of "channel"', () { return FakeApiConnection.with_(zulipFeatureLevel: 247, (connection) { - return checkSetTypingStatus(connection, TypingOp.start, + return checkSetTypingStatus(connection, .start, destination: const StreamDestination(streamId, TopicName(topic)), expectedBodyFields: { 'op': 'start', @@ -77,7 +77,7 @@ void main() { test('legacy: use to=[streamId] instead of stream_id=streamId', () { return FakeApiConnection.with_(zulipFeatureLevel: 214, (connection) { - return checkSetTypingStatus(connection, TypingOp.start, + return checkSetTypingStatus(connection, .start, destination: const StreamDestination(streamId, TopicName(topic)), expectedBodyFields: { 'op': 'start', @@ -90,7 +90,7 @@ void main() { test('legacy: use "private" instead of "direct"', () { return FakeApiConnection.with_(zulipFeatureLevel: 173, (connection) { - return checkSetTypingStatus(connection, TypingOp.start, + return checkSetTypingStatus(connection, .start, destination: const DmDestination(userIds: userIds), expectedBodyFields: { 'op': 'start', diff --git a/test/api/route/users_test.dart b/test/api/route/users_test.dart index 16975bbc2..deb33532c 100644 --- a/test/api/route/users_test.dart +++ b/test/api/route/users_test.dart @@ -41,7 +41,7 @@ void main() { historyLimitDays: 21, newUserInput: false, pingOnly: false, - status: PresenceStatus.active, + status: .active, ); check(connection.takeRequests()).single.isA() ..method.equals('POST') diff --git a/test/example_data.dart b/test/example_data.dart index 820f00497..ba9a72140 100644 --- a/test/example_data.dart +++ b/test/example_data.dart @@ -309,7 +309,7 @@ User user({ isBot: isBot ?? false, botType: null, botOwnerId: botOwnerId, - role: role ?? UserRole.member, + role: role ?? .member, timezone: 'UTC', avatarUrl: avatarUrl, avatarVersion: 0, @@ -801,7 +801,7 @@ StreamMessage streamMessage({ 'subject': topic ?? _defaultTopic, 'submessages': submessages ?? [], 'timestamp': timestamp ?? 1678139636, - 'type': 'stream', + 'type': 'channel', 'match_content': matchContent, 'match_subject': matchTopic, }) as Map); @@ -844,7 +844,7 @@ DmMessage dmMessage({ 'subject': '', 'submessages': submessages ?? [], 'timestamp': timestamp ?? 1678139636, - 'type': 'private', + 'type': 'direct', }) as Map); } @@ -1107,7 +1107,7 @@ DeleteMessageEvent deleteMessageEvent(List messages) { return DeleteMessageEvent( id: 0, messageIds: messages.map((message) => message.id).toList(), - messageType: MessageType.stream, + messageType: .channel, streamId: messages[0].streamId, topic: messages[0].topic, ); @@ -1255,14 +1255,14 @@ UpdateMessageFlagsRemoveEvent updateMessageFlagsRemoveEvent( message.id, switch (message) { StreamMessage() => UpdateMessageFlagsMessageDetail( - type: MessageType.stream, + type: .channel, mentioned: mentioned, streamId: message.streamId, topic: message.topic, userIds: null, ), DmMessage() => UpdateMessageFlagsMessageDetail( - type: MessageType.direct, + type: .direct, mentioned: mentioned, streamId: null, topic: null, @@ -1293,13 +1293,13 @@ TypingEvent typingEvent(SendableNarrow narrow, TypingOp op, int senderId) { switch (narrow) { case TopicNarrow(): return TypingEvent(id: 0, op: op, senderId: senderId, - messageType: MessageType.stream, + messageType: .channel, streamId: narrow.channelId, topic: narrow.topic, recipientIds: null); case DmNarrow(): return TypingEvent(id: 0, op: op, senderId: senderId, - messageType: MessageType.direct, + messageType: .direct, recipientIds: narrow.allRecipientIds, streamId: null, topic: null); @@ -1320,38 +1320,40 @@ ReactionEvent reactionEvent(Reaction reaction, ReactionOp op, int messageId) { ChannelUpdateEvent channelUpdateEvent( ZulipStream stream, { - required ChannelPropertyName property, + required ChannelProperty property, required Object? value, }) { switch (property) { - case ChannelPropertyName.name: + case .name: assert(value is String); - case ChannelPropertyName.isArchived: + case .isArchived: assert(value is bool); - case ChannelPropertyName.description: + case .description: assert(value is String); - case ChannelPropertyName.firstMessageId: + case .firstMessageId: assert(value is int?); - case ChannelPropertyName.inviteOnly: + case .inviteOnly: assert(value is bool); - case ChannelPropertyName.messageRetentionDays: + case .messageRetentionDays: assert(value is int?); - case ChannelPropertyName.topicsPolicy: + case .topicsPolicy: assert(value is ChannelTopicsPolicy); - case ChannelPropertyName.channelPostPolicy: + case .channelPostPolicy: assert(value is ChannelPostPolicy); - case ChannelPropertyName.folderId: + case .folderId: assert(value is int?); - case ChannelPropertyName.canAddSubscribersGroup: - case ChannelPropertyName.canDeleteAnyMessageGroup: - case ChannelPropertyName.canDeleteOwnMessageGroup: - case ChannelPropertyName.canSendMessageGroup: - case ChannelPropertyName.canSubscribeGroup: + case .canAddSubscribersGroup: + case .canDeleteAnyMessageGroup: + case .canDeleteOwnMessageGroup: + case .canSendMessageGroup: + case .canSubscribeGroup: assert(value is GroupSettingValue); - case ChannelPropertyName.isRecentlyActive: + case .isRecentlyActive: assert(value is bool); - case ChannelPropertyName.streamWeeklyTraffic: + case .streamWeeklyTraffic: assert(value is int?); + case .unknown: + assert(value == null); } return ChannelUpdateEvent( id: 1, @@ -1393,10 +1395,10 @@ UserSettings userSettings({ bool? presenceEnabled, }) { return UserSettings( - twentyFourHourTime: twentyFourHourTime ?? TwentyFourHourTimeMode.twelveHour, + twentyFourHourTime: twentyFourHourTime ?? .twelveHour, starredMessageCounts: true, displayEmojiReactionUsers: displayEmojiReactionUsers ?? true, - emojiset: emojiset ?? Emojiset.google, + emojiset: emojiset ?? .google, webInboxShowChannelFolders: webInboxShowChannelFolders ?? true, presenceEnabled: presenceEnabled ?? true, ); @@ -1502,7 +1504,7 @@ InitialSnapshot initialSnapshot({ realmCanDeleteAnyMessageGroup: realmCanDeleteAnyMessageGroup, realmCanDeleteOwnMessageGroup: realmCanDeleteOwnMessageGroup, realmDeleteOwnMessagePolicy: realmDeleteOwnMessagePolicy, - realmWildcardMentionPolicy: realmWildcardMentionPolicy ?? RealmWildcardMentionPolicy.everyone, + realmWildcardMentionPolicy: realmWildcardMentionPolicy ?? .everyone, // no default; allow `null` to simulate servers without this realmTopicsPolicy: realmTopicsPolicy, realmMandatoryTopics: realmMandatoryTopics ?? true, diff --git a/test/model/channel_test.dart b/test/model/channel_test.dart index 9c0369524..e99a9017a 100644 --- a/test/model/channel_test.dart +++ b/test/model/channel_test.dart @@ -63,12 +63,12 @@ void main() { checkUnified(store); await store.handleEvent(eg.channelUpdateEvent(store.streams[stream1.streamId]!, - property: ChannelPropertyName.name, value: 'new stream', + property: .name, value: 'new stream', )); checkUnified(store); await store.handleEvent(eg.channelUpdateEvent(store.streams[stream1.streamId]!, - property: ChannelPropertyName.channelPostPolicy, + property: .channelPostPolicy, value: ChannelPostPolicy.administrators, )); checkUnified(store); @@ -587,33 +587,33 @@ void main() { }); group('selfCanSendMessage, legacy', () { - final testCases = [ - (ChannelPostPolicy.unknown, UserRole.guest, true), - (ChannelPostPolicy.unknown, UserRole.member, true), - (ChannelPostPolicy.unknown, UserRole.moderator, true), - (ChannelPostPolicy.unknown, UserRole.administrator, true), - (ChannelPostPolicy.unknown, UserRole.owner, true), - (ChannelPostPolicy.any, UserRole.guest, true), - (ChannelPostPolicy.any, UserRole.member, true), - (ChannelPostPolicy.any, UserRole.moderator, true), - (ChannelPostPolicy.any, UserRole.administrator, true), - (ChannelPostPolicy.any, UserRole.owner, true), - (ChannelPostPolicy.fullMembers, UserRole.guest, false), + final testCases = <(ChannelPostPolicy, UserRole, bool)>[ + (ChannelPostPolicy.unknown, .guest, true), + (ChannelPostPolicy.unknown, .member, true), + (ChannelPostPolicy.unknown, .moderator, true), + (ChannelPostPolicy.unknown, .administrator, true), + (ChannelPostPolicy.unknown, .owner, true), + (ChannelPostPolicy.any, .guest, true), + (ChannelPostPolicy.any, .member, true), + (ChannelPostPolicy.any, .moderator, true), + (ChannelPostPolicy.any, .administrator, true), + (ChannelPostPolicy.any, .owner, true), + (ChannelPostPolicy.fullMembers, .guest, false), // The fullMembers/member case gets its own tests further below. - // (ChannelPostPolicy.fullMembers, UserRole.member, /* complicated */), - (ChannelPostPolicy.fullMembers, UserRole.moderator, true), - (ChannelPostPolicy.fullMembers, UserRole.administrator, true), - (ChannelPostPolicy.fullMembers, UserRole.owner, true), - (ChannelPostPolicy.moderators, UserRole.guest, false), - (ChannelPostPolicy.moderators, UserRole.member, false), - (ChannelPostPolicy.moderators, UserRole.moderator, true), - (ChannelPostPolicy.moderators, UserRole.administrator, true), - (ChannelPostPolicy.moderators, UserRole.owner, true), - (ChannelPostPolicy.administrators, UserRole.guest, false), - (ChannelPostPolicy.administrators, UserRole.member, false), - (ChannelPostPolicy.administrators, UserRole.moderator, false), - (ChannelPostPolicy.administrators, UserRole.administrator, true), - (ChannelPostPolicy.administrators, UserRole.owner, true), + // (ChannelPostPolicy.fullMembers, .member, /* complicated */), + (ChannelPostPolicy.fullMembers, .moderator, true), + (ChannelPostPolicy.fullMembers, .administrator, true), + (ChannelPostPolicy.fullMembers, .owner, true), + (ChannelPostPolicy.moderators, .guest, false), + (ChannelPostPolicy.moderators, .member, false), + (ChannelPostPolicy.moderators, .moderator, true), + (ChannelPostPolicy.moderators, .administrator, true), + (ChannelPostPolicy.moderators, .owner, true), + (ChannelPostPolicy.administrators, .guest, false), + (ChannelPostPolicy.administrators, .member, false), + (ChannelPostPolicy.administrators, .moderator, false), + (ChannelPostPolicy.administrators, .administrator, true), + (ChannelPostPolicy.administrators, .owner, true), ]; for (final (ChannelPostPolicy policy, UserRole role, bool canPost) in testCases) { @@ -643,7 +643,7 @@ void main() { realmUsers: [selfUser])); User memberUser({required String dateJoined}) => eg.user( - role: UserRole.member, dateJoined: dateJoined); + role: .member, dateJoined: dateJoined); test('a "full" member -> can post in the channel', () { final store = localStore( diff --git a/test/model/message_list_test.dart b/test/model/message_list_test.dart index ac484bf64..45c8aaa69 100644 --- a/test/model/message_list_test.dart +++ b/test/model/message_list_test.dart @@ -2193,7 +2193,7 @@ void main() { await store.addMessage(message); await store.handleEvent( - eg.reactionEvent(eg.unicodeEmojiReaction, ReactionOp.add, message.id)); + eg.reactionEvent(eg.unicodeEmojiReaction, .add, message.id)); check(notifiedCount1).equals(3); // fetch, new-message event, reaction event check(notifiedCount2).equals(3); // fetch, new-message event, reaction event @@ -2236,7 +2236,7 @@ void main() { test('ReactionEvent is applied even when message not in any msglists', () async { await checkApplied( mkEvent: (message) => - eg.reactionEvent(eg.unicodeEmojiReaction, ReactionOp.add, message.id), + eg.reactionEvent(eg.unicodeEmojiReaction, .add, message.id), doCheckMessageAfterFetch: (messageSubject) => messageSubject.reactions.isNotNull().total.equals(1), ); diff --git a/test/model/message_test.dart b/test/model/message_test.dart index 9b8517c6d..af1b37edd 100644 --- a/test/model/message_test.dart +++ b/test/model/message_test.dart @@ -159,7 +159,7 @@ void main() { ..method.equals('POST') ..url.path.equals('/api/v1/messages') ..bodyFields.deepEquals({ - 'type': 'stream', + 'type': 'channel', 'to': stream.streamId.toString(), 'topic': 'world', 'content': 'hello', @@ -1365,8 +1365,8 @@ void main() { timeLimitConfig: CanDeleteMessageTimeLimitConfig.notLimited, inRealmCanDeleteAnyMessageGroup: false, isChannelArchived: false, - realmDeleteOwnMessagePolicy: RealmDeleteOwnMessagePolicy.everyone, - selfUserRole: UserRole.member, + realmDeleteOwnMessagePolicy: .everyone, + selfUserRole: .member, ))) ..equals(await evaluate( CanDeleteMessageParams.pre407( @@ -1385,8 +1385,8 @@ void main() { timeLimitConfig: CanDeleteMessageTimeLimitConfig.notLimited, inRealmCanDeleteAnyMessageGroup: false, isChannelArchived: false, - realmDeleteOwnMessagePolicy: RealmDeleteOwnMessagePolicy.admins, - selfUserRole: UserRole.administrator, + realmDeleteOwnMessagePolicy: .admins, + selfUserRole: .administrator, ))) ..equals(await evaluate( CanDeleteMessageParams.pre407( @@ -1405,8 +1405,8 @@ void main() { timeLimitConfig: CanDeleteMessageTimeLimitConfig.notLimited, inRealmCanDeleteAnyMessageGroup: false, isChannelArchived: false, - realmDeleteOwnMessagePolicy: RealmDeleteOwnMessagePolicy.admins, - selfUserRole: UserRole.moderator, + realmDeleteOwnMessagePolicy: .admins, + selfUserRole: .moderator, )))..equals(await evaluate( CanDeleteMessageParams.pre407( senderConfig: CanDeleteMessageSenderConfig.self, @@ -1429,16 +1429,16 @@ void main() { senderConfig: CanDeleteMessageSenderConfig.otherHuman, timeLimitConfig: CanDeleteMessageTimeLimitConfig.notLimited, isChannelArchived: false, - realmDeleteOwnMessagePolicy: RealmDeleteOwnMessagePolicy.everyone, - selfUserRole: UserRole.member, + realmDeleteOwnMessagePolicy: .everyone, + selfUserRole: .member, )))..equals(await evaluate( CanDeleteMessageParams.pre291( senderConfig: CanDeleteMessageSenderConfig.otherHuman, timeLimitConfig: CanDeleteMessageTimeLimitConfig.notLimited, inRealmCanDeleteAnyMessageGroup: false, isChannelArchived: false, - realmDeleteOwnMessagePolicy: RealmDeleteOwnMessagePolicy.everyone, - selfUserRole: UserRole.member))) + realmDeleteOwnMessagePolicy: .everyone, + selfUserRole: .member))) ..isFalse(); }); @@ -1448,16 +1448,16 @@ void main() { senderConfig: CanDeleteMessageSenderConfig.otherHuman, timeLimitConfig: CanDeleteMessageTimeLimitConfig.notLimited, isChannelArchived: false, - realmDeleteOwnMessagePolicy: RealmDeleteOwnMessagePolicy.everyone, - selfUserRole: UserRole.administrator, + realmDeleteOwnMessagePolicy: .everyone, + selfUserRole: .administrator, )))..equals(await evaluate( CanDeleteMessageParams.pre291( senderConfig: CanDeleteMessageSenderConfig.otherHuman, timeLimitConfig: CanDeleteMessageTimeLimitConfig.notLimited, inRealmCanDeleteAnyMessageGroup: true, isChannelArchived: false, - realmDeleteOwnMessagePolicy: RealmDeleteOwnMessagePolicy.everyone, - selfUserRole: UserRole.administrator))) + realmDeleteOwnMessagePolicy: .everyone, + selfUserRole: .administrator))) ..isTrue(); }); }); @@ -1915,7 +1915,7 @@ void main() { final message = store.messages.values.single; await store.handleEvent( - eg.reactionEvent(eg.unicodeEmojiReaction, ReactionOp.add, originalMessage.id)); + eg.reactionEvent(eg.unicodeEmojiReaction, .add, originalMessage.id)); checkNotifiedOnce(); check(store.messages).values.single ..identicalTo(message) @@ -1927,7 +1927,7 @@ void main() { await prepare(); await prepareMessages([someMessage]); await store.handleEvent( - eg.reactionEvent(eg.unicodeEmojiReaction, ReactionOp.add, 1000)); + eg.reactionEvent(eg.unicodeEmojiReaction, .add, 1000)); checkNotNotified(); check(store.messages).values.single .reactions.isNull(); @@ -1959,7 +1959,7 @@ void main() { final message = store.messages.values.single; await store.handleEvent( - eg.reactionEvent(eventReaction, ReactionOp.remove, originalMessage.id)); + eg.reactionEvent(eventReaction, .remove, originalMessage.id)); checkNotifiedOnce(); check(store.messages).values.single ..identicalTo(message) @@ -1971,7 +1971,7 @@ void main() { await prepare(); await prepareMessages([someMessage]); await store.handleEvent( - eg.reactionEvent(eg.unicodeEmojiReaction, ReactionOp.remove, 1000)); + eg.reactionEvent(eg.unicodeEmojiReaction, .remove, 1000)); checkNotNotified(); check(store.messages).values.single .reactions.isNotNull().jsonEquals([eg.unicodeEmojiReaction]); diff --git a/test/model/realm_test.dart b/test/model/realm_test.dart index 404d67446..c9c395031 100644 --- a/test/model/realm_test.dart +++ b/test/model/realm_test.dart @@ -94,21 +94,21 @@ void main() { }); test('guest -> no permission, despite group', () { - final selfUser = eg.user(role: UserRole.guest); + final selfUser = eg.user(role: .guest); final group = eg.userGroup(members: [selfUser.userId]); check(hasPermission(selfUser, group, 'can_subscribe_group')) .isFalse(); }); test('guest -> still has permission, if allowEveryoneGroup', () { - final selfUser = eg.user(role: UserRole.guest); + final selfUser = eg.user(role: .guest); final group = eg.userGroup(members: [selfUser.userId]); check(hasPermission(selfUser, group, 'can_send_message_group')) .isTrue(); }); test('guest not in group -> no permission, even if allowEveryoneGroup', () { - final selfUser = eg.user(role: UserRole.guest); + final selfUser = eg.user(role: .guest); final group = eg.userGroup(members: []); check(hasPermission(selfUser, group, 'can_send_message_group')) .isFalse(); @@ -141,16 +141,16 @@ void main() { break; case SystemGroupName.everyone: test('everyone', () { - prepare(selfUserRole: UserRole.guest); + prepare(selfUserRole: .guest); doCheck(GroupSettingType.realm, 'can_access_all_users_group', true); }); case SystemGroupName.members: test('members, is guest', () { - prepare(selfUserRole: UserRole.guest); + prepare(selfUserRole: .guest); doCheck(GroupSettingType.realm, 'can_add_custom_emoji_group', false); }); test('members, is member', () { - prepare(selfUserRole: UserRole.member); + prepare(selfUserRole: .member); doCheck(GroupSettingType.realm, 'can_add_custom_emoji_group', true); }); case SystemGroupName.fullMembers: @@ -158,34 +158,34 @@ void main() { break; case SystemGroupName.moderators: test('moderators, is member', () { - prepare(selfUserRole: UserRole.member); + prepare(selfUserRole: .member); doCheck(GroupSettingType.realm, 'can_set_delete_message_policy_group', false); }); test('moderators, is moderator', () { - prepare(selfUserRole: UserRole.moderator); + prepare(selfUserRole: .moderator); doCheck(GroupSettingType.realm, 'can_set_delete_message_policy_group', true); }); case SystemGroupName.administrators: test('administrators, is moderator', () { - prepare(selfUserRole: UserRole.moderator); + prepare(selfUserRole: .moderator); doCheck(GroupSettingType.stream, 'can_remove_subscribers_group', false); }); test('administrators, is administrator', () { - prepare(selfUserRole: UserRole.administrator); + prepare(selfUserRole: .administrator); doCheck(GroupSettingType.stream, 'can_remove_subscribers_group', true); }); case SystemGroupName.owners: test('owners, is administrator', () { - prepare(selfUserRole: UserRole.administrator); + prepare(selfUserRole: .administrator); doCheck(GroupSettingType.realm, 'can_create_web_public_channel_group', false); }); test('owners, is owner', () { - prepare(selfUserRole: UserRole.owner); + prepare(selfUserRole: .owner); doCheck(GroupSettingType.realm, 'can_create_web_public_channel_group', true); }); case SystemGroupName.nobody: test('nobody', () { - prepare(selfUserRole: UserRole.owner); + prepare(selfUserRole: .owner); doCheck(GroupSettingType.stream, 'can_delete_own_message_group', false); }); } @@ -194,7 +194,7 @@ void main() { test('throw on unknown name', () { // We should know about all the permissions we're trying to implement, // even the ones old servers don't know about. - prepare(selfUserRole: UserRole.member); + prepare(selfUserRole: .member); check(() => store.selfHasPermissionForGroupSetting(null, GroupSettingType.realm, 'example_future_permission_name'), ).throws(); @@ -206,14 +206,14 @@ void main() { test('update clobbers old list', () async { final store = eg.store(initialSnapshot: eg.initialSnapshot( customProfileFields: [ - eg.customProfileField(0, CustomProfileFieldType.shortText), - eg.customProfileField(1, CustomProfileFieldType.shortText), + eg.customProfileField(0, .shortText), + eg.customProfileField(1, .shortText), ])); check(store.customProfileFields.map((f) => f.id)).deepEquals([0, 1]); await store.handleEvent(CustomProfileFieldsEvent(id: 0, fields: [ - eg.customProfileField(0, CustomProfileFieldType.shortText), - eg.customProfileField(2, CustomProfileFieldType.shortText), + eg.customProfileField(0, .shortText), + eg.customProfileField(2, .shortText), ])); check(store.customProfileFields.map((f) => f.id)).deepEquals([0, 2]); }); @@ -222,22 +222,22 @@ void main() { // Sorts both the data from the initial snapshot… final store = eg.store(initialSnapshot: eg.initialSnapshot( customProfileFields: [ - eg.customProfileField(0, CustomProfileFieldType.shortText, + eg.customProfileField(0, .shortText, displayInProfileSummary: false), - eg.customProfileField(1, CustomProfileFieldType.shortText, + eg.customProfileField(1, .shortText, displayInProfileSummary: true), - eg.customProfileField(2, CustomProfileFieldType.shortText, + eg.customProfileField(2, .shortText, displayInProfileSummary: false), ])); check(store.customProfileFields.map((f) => f.id)).deepEquals([1, 0, 2]); // … and from an event. await store.handleEvent(CustomProfileFieldsEvent(id: 0, fields: [ - eg.customProfileField(0, CustomProfileFieldType.shortText, + eg.customProfileField(0, .shortText, displayInProfileSummary: false), - eg.customProfileField(1, CustomProfileFieldType.shortText, + eg.customProfileField(1, .shortText, displayInProfileSummary: false), - eg.customProfileField(2, CustomProfileFieldType.shortText, + eg.customProfileField(2, .shortText, displayInProfileSummary: true), ])); check(store.customProfileFields.map((f) => f.id)).deepEquals([2, 0, 1]); diff --git a/test/model/recent_senders_test.dart b/test/model/recent_senders_test.dart index e8435fb17..62e2f4a62 100644 --- a/test/model/recent_senders_test.dart +++ b/test/model/recent_senders_test.dart @@ -179,7 +179,7 @@ void main() { model.handleDeleteMessageEvent(DeleteMessageEvent( id: 0, messageIds: [messages[1].id], - messageType: MessageType.stream, + messageType: .channel, streamId: stream.streamId, topic: eg.t('oThEr'), ), {messages[1].id: messages[1]}); diff --git a/test/model/store_test.dart b/test/model/store_test.dart index d4852153c..e5476ec05 100644 --- a/test/model/store_test.dart +++ b/test/model/store_test.dart @@ -12,7 +12,6 @@ import 'package:zulip/api/core.dart'; import 'package:zulip/api/exception.dart'; import 'package:zulip/api/model/events.dart'; import 'package:zulip/api/model/initial_snapshot.dart'; -import 'package:zulip/api/model/model.dart'; import 'package:zulip/api/route/events.dart'; import 'package:zulip/api/route/realm.dart'; import 'package:zulip/log.dart'; @@ -815,15 +814,14 @@ void main() { // Pick some arbitrary event and check it gets processed on the store. check(store.userSettings.twentyFourHourTime) - .equals(TwentyFourHourTimeMode.twelveHour); + .equals(.twelveHour); connection.prepare(json: GetEventsResult(events: [ - UserSettingsUpdateEvent(id: 2, - property: UserSettingName.twentyFourHourTime, value: true), + UserSettingsUpdateEvent(id: 2, property: .twentyFourHourTime, value: true), ], queueId: null).toJson()); updateMachine.debugAdvanceLoop(); async.elapse(Duration.zero); check(store.userSettings.twentyFourHourTime) - .equals(TwentyFourHourTimeMode.twentyFourHour); + .equals(.twentyFourHour); })); void checkReload(FutureOr Function() prepareError, { @@ -854,15 +852,14 @@ void main() { updateMachine.debugPauseLoop(); updateMachine.poll(); check(store.userSettings.twentyFourHourTime) - .equals(TwentyFourHourTimeMode.twelveHour); + .equals(.twelveHour); connection.prepare(json: GetEventsResult(events: [ - UserSettingsUpdateEvent(id: 2, - property: UserSettingName.twentyFourHourTime, value: true), + UserSettingsUpdateEvent(id: 2, property: .twentyFourHourTime, value: true), ], queueId: null).toJson()); updateMachine.debugAdvanceLoop(); async.elapse(Duration.zero); check(store.userSettings.twentyFourHourTime) - .equals(TwentyFourHourTimeMode.twentyFourHour); + .equals(.twentyFourHour); }); } diff --git a/test/model/test_store.dart b/test/model/test_store.dart index ef75659c0..47dc93b44 100644 --- a/test/model/test_store.dart +++ b/test/model/test_store.dart @@ -366,7 +366,7 @@ extension PerAccountStoreTestExtension on PerAccountStore { Future updateChannel( int channelId, - ChannelPropertyName property, + ChannelProperty property, Object? value, ) async { await handleEvent(ChannelUpdateEvent( diff --git a/test/model/typing_status_test.dart b/test/model/typing_status_test.dart index eb0cc83d0..d31691a4c 100644 --- a/test/model/typing_status_test.dart +++ b/test/model/typing_status_test.dart @@ -88,7 +88,7 @@ void main() { typistsByNarrow.forEach((narrow, typists) { for (final typist in typists) { - handleTypingEvent(narrow, TypingOp.start, typist); + handleTypingEvent(narrow, .start, typist); checkNotifiedOnce(); } }); @@ -114,15 +114,15 @@ void main() { test('add typists in separate narrows', () { prepareModel(); - handleTypingEvent(dmNarrow, TypingOp.start, eg.otherUser); + handleTypingEvent(dmNarrow, .start, eg.otherUser); checkTypists({dmNarrow: [eg.otherUser]}); checkNotifiedOnce(); - handleTypingEvent(groupNarrow, TypingOp.start, eg.thirdUser); + handleTypingEvent(groupNarrow, .start, eg.thirdUser); checkTypists({dmNarrow: [eg.otherUser], groupNarrow: [eg.thirdUser]}); checkNotifiedOnce(); - handleTypingEvent(topicNarrow, TypingOp.start, eg.fourthUser); + handleTypingEvent(topicNarrow, .start, eg.fourthUser); checkTypists({ dmNarrow: [eg.otherUser], groupNarrow: [eg.thirdUser], @@ -133,7 +133,7 @@ void main() { test('add a typist in the same narrow', () { prepareModel(typistsByNarrow: {groupNarrow: [eg.otherUser]}); - handleTypingEvent(groupNarrow, TypingOp.start, eg.thirdUser); + handleTypingEvent(groupNarrow, .start, eg.thirdUser); checkTypists({groupNarrow: [eg.otherUser, eg.thirdUser]}); checkNotifiedOnce(); }); @@ -142,7 +142,7 @@ void main() { prepareModel(); model.handleTypingEvent( - eg.typingEvent(groupNarrow, TypingOp.start, eg.selfUser.userId)); + eg.typingEvent(groupNarrow, .start, eg.selfUser.userId)); checkTypists({}); checkNotNotified(); }); @@ -152,7 +152,7 @@ void main() { test('remove a typist from an unknown narrow', () { prepareModel(typistsByNarrow: {groupNarrow: [eg.otherUser]}); - handleTypingEvent(dmNarrow, TypingOp.stop, eg.otherUser); + handleTypingEvent(dmNarrow, .stop, eg.otherUser); checkTypists({groupNarrow: [eg.otherUser]}); checkNotNotified(); }); @@ -160,7 +160,7 @@ void main() { test('remove an unknown typist from a known narrow', () { prepareModel(typistsByNarrow: {groupNarrow: [eg.otherUser]}); - handleTypingEvent(groupNarrow, TypingOp.stop, eg.thirdUser); + handleTypingEvent(groupNarrow, .stop, eg.thirdUser); checkTypists({groupNarrow: [eg.otherUser]}); checkNotNotified(); }); @@ -170,7 +170,7 @@ void main() { groupNarrow: [eg.otherUser, eg.thirdUser], }); - handleTypingEvent(groupNarrow, TypingOp.stop, eg.otherUser); + handleTypingEvent(groupNarrow, .stop, eg.otherUser); checkTypists({groupNarrow: [eg.thirdUser]}); checkNotifiedOnce(); }); @@ -178,7 +178,7 @@ void main() { test('remove all typists in a narrow', () { prepareModel(typistsByNarrow: {dmNarrow: [eg.otherUser]}); - handleTypingEvent(dmNarrow, TypingOp.stop, eg.otherUser); + handleTypingEvent(dmNarrow, .stop, eg.otherUser); checkTypists({}); checkNotifiedOnce(); }); @@ -190,15 +190,15 @@ void main() { topicNarrow: [eg.fourthUser], }); - handleTypingEvent(groupNarrow, TypingOp.stop, eg.thirdUser); + handleTypingEvent(groupNarrow, .stop, eg.thirdUser); checkTypists({dmNarrow: [eg.otherUser], topicNarrow: [eg.fourthUser]}); checkNotifiedOnce(); - handleTypingEvent(dmNarrow, TypingOp.stop, eg.otherUser); + handleTypingEvent(dmNarrow, .stop, eg.otherUser); checkTypists({topicNarrow: [eg.fourthUser]}); checkNotifiedOnce(); - handleTypingEvent(topicNarrow, TypingOp.stop, eg.fourthUser); + handleTypingEvent(topicNarrow, .stop, eg.fourthUser); checkTypists({}); checkNotifiedOnce(); }); @@ -222,7 +222,7 @@ void main() { prepareModel(typistsByNarrow: {dmNarrow: [eg.otherUser]}); check(async.pendingTimers).length.equals(1); - handleTypingEvent(dmNarrow, TypingOp.stop, eg.otherUser); + handleTypingEvent(dmNarrow, .stop, eg.otherUser); checkTypists({}); check(async.pendingTimers).isEmpty(); checkNotifiedOnce(); @@ -235,7 +235,7 @@ void main() { async.elapse(const Duration(seconds: 10)); checkTypists({dmNarrow: [eg.otherUser]}); // The timer should restart from the event. - handleTypingEvent(dmNarrow, TypingOp.start, eg.otherUser); + handleTypingEvent(dmNarrow, .start, eg.otherUser); check(async.pendingTimers).length.equals(1); checkNotNotified(); @@ -282,7 +282,7 @@ void main() { await prepare(); connection.prepare(json: {}); model.keystroke(narrow); - checkTypingRequest(TypingOp.start, narrow); + checkTypingRequest(.start, narrow); // Finish the pending API request first, // so that the idle timer is the only timer left. @@ -316,7 +316,7 @@ void main() { async.elapse(const Duration(milliseconds: 1)); // t = typingStoppedWaitPeriod + 100ms: // The new timer expires and the "typing stopped" notice is sent. - checkTypingRequest(TypingOp.stop, narrow); + checkTypingRequest(.stop, narrow); })); test('start typing repeatedly does not resend "typing started" notices', () => awaitFakeAsync((async) async { @@ -342,12 +342,12 @@ void main() { // t > typingStartedWaitPeriod: Resume sending "typing started" notices. connection.prepare(json: {}); model.keystroke(narrow); - checkTypingRequest(TypingOp.start, narrow); + checkTypingRequest(.start, narrow); // Ensures that a "typing stopped" notice is sent when the test ends. connection.prepare(json: {}); async.flushTimers(); - checkTypingRequest(TypingOp.stop, narrow); + checkTypingRequest(.stop, narrow); })); test('after stopped wait period, send a "typing stopped" notice', () => awaitFakeAsync((async) async { @@ -355,7 +355,7 @@ void main() { connection.prepare(json: {}); async.elapse(store.serverTypingStoppedWaitPeriod); - checkTypingRequest(TypingOp.stop, narrow); + checkTypingRequest(.stop, narrow); check(async.pendingTimers).isEmpty(); })); @@ -364,7 +364,7 @@ void main() { connection.prepare(json: {}); model.stoppedComposing(); - checkTypingRequest(TypingOp.stop, narrow); + checkTypingRequest(.stop, narrow); async.elapse(Duration.zero); check(async.pendingTimers).isEmpty(); @@ -375,19 +375,19 @@ void main() { connection.prepare(json: {}); model.stoppedComposing(); - checkTypingRequest(TypingOp.stop, narrow); + checkTypingRequest(.stop, narrow); // The "typing started" notice would have been throttled if we did not // reset the TypingNotifier internal states before sending the "typing // stopped" notice. connection.prepare(json: {}); model.keystroke(narrow); - checkTypingRequest(TypingOp.start, narrow); + checkTypingRequest(.start, narrow); // Ensures that a "typing stopped" notice is sent when the test ends. connection.prepare(json: {}); async.flushTimers(); - checkTypingRequest(TypingOp.stop, narrow); + checkTypingRequest(.stop, narrow); })); test('disposing store cancels the idle timer', () => awaitFakeAsync((async) async { @@ -411,7 +411,7 @@ void main() { // t = 0ms: Start typing. The idle timer is set to typingStoppedWaitPeriod. connection.prepare(json: {}); model.keystroke(topicNarrow); - checkTypingRequest(TypingOp.start, topicNarrow); + checkTypingRequest(.start, topicNarrow); async.elapse(Duration.zero); check(async.pendingTimers).single; @@ -424,7 +424,7 @@ void main() { connection.prepare(json: {}); model.keystroke(dmNarrow); checkSetTypingStatusRequests(connection.takeRequests(), - [(TypingOp.stop, topicNarrow), (TypingOp.start, dmNarrow)]); + [(.stop, topicNarrow), (.start, dmNarrow)]); async.elapse(Duration.zero); check(async.pendingTimers).single; @@ -440,7 +440,7 @@ void main() { async.elapse(waitTime); // t = typingStoppedPeriod + 100ms: // The new timer has expired, and a "typing stopped" notice is expected. - checkTypingRequest(TypingOp.stop, dmNarrow); + checkTypingRequest(.stop, dmNarrow); })); test('start typing in a different destination resets typing started wait timeout', () => awaitFakeAsync((async) async { @@ -457,7 +457,7 @@ void main() { // t = 0ms: Start typing. The typing started time is set to 0ms. connection.prepare(json: {}); model.keystroke(topicNarrow); - checkTypingRequest(TypingOp.start, topicNarrow); + checkTypingRequest(.start, topicNarrow); async.elapse(waitInterval); // t = waitInterval * 1: @@ -469,7 +469,7 @@ void main() { connection.prepare(json: {}); model.keystroke(dmNarrow); checkSetTypingStatusRequests(connection.takeRequests(), - [(TypingOp.stop, topicNarrow), (TypingOp.start, dmNarrow)]); + [(.stop, topicNarrow), (.start, dmNarrow)]); while (async.elapsed <= store.serverTypingStartedWaitPeriod) { // t <= typingStartedWaitPeriod: "still typing" requests are throttled. @@ -494,12 +494,12 @@ void main() { // enough time since the last time we sent a "typing started" notice. connection.prepare(json: {}); model.keystroke(dmNarrow); - checkTypingRequest(TypingOp.start, dmNarrow); + checkTypingRequest(.start, dmNarrow); // Ensures that a "typing stopped" notice is sent when the test ends. connection.prepare(json: {}); async.flushTimers(); - checkTypingRequest(TypingOp.stop, dmNarrow); + checkTypingRequest(.stop, dmNarrow); })); }); } diff --git a/test/model/unreads_test.dart b/test/model/unreads_test.dart index 1d5261311..81ff3c5ec 100644 --- a/test/model/unreads_test.dart +++ b/test/model/unreads_test.dart @@ -855,7 +855,7 @@ void main() { final event = switch (message) { StreamMessage() => DeleteMessageEvent( id: 0, - messageType: MessageType.stream, + messageType: .channel, messageIds: [message.id], streamId: message.streamId, topic: () { @@ -867,7 +867,7 @@ void main() { ), DmMessage() => DeleteMessageEvent( id: 0, - messageType: MessageType.direct, + messageType: .direct, messageIds: [message.id], streamId: null, topic: null, @@ -888,7 +888,7 @@ void main() { model.handleDeleteMessageEvent(DeleteMessageEvent( id: 0, messageIds: [11, 12], - messageType: MessageType.stream, + messageType: .channel, streamId: stream1.streamId, topic: eg.t('a'), )); @@ -897,7 +897,7 @@ void main() { model.handleDeleteMessageEvent(DeleteMessageEvent( id: 0, messageIds: [13, 14], - messageType: MessageType.stream, + messageType: .channel, streamId: stream2.streamId, topic: eg.t('b'), )); @@ -906,7 +906,7 @@ void main() { model.handleDeleteMessageEvent(DeleteMessageEvent( id: 0, messageIds: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - messageType: MessageType.direct, + messageType: .direct, streamId: null, topic: null, )); @@ -922,7 +922,7 @@ void main() { model.handleDeleteMessageEvent(DeleteMessageEvent( id: 0, messageIds: [message.id], - messageType: MessageType.stream, + messageType: .channel, streamId: message.streamId, topic: message.topic, )); @@ -941,7 +941,7 @@ void main() { model.handleDeleteMessageEvent(DeleteMessageEvent( id: 0, messageIds: [message.id], - messageType: MessageType.direct, + messageType: .direct, streamId: null, topic: null, )); @@ -1373,7 +1373,7 @@ void main() { messages: [message1.id, message2.id, message3.id, message4.id], messageDetails: { message1.id: UpdateMessageFlagsMessageDetail( - type: MessageType.stream, + type: .channel, mentioned: false, streamId: stream.streamId, topic: eg.t(topic), @@ -1381,7 +1381,7 @@ void main() { ), // message 2 and 3 have their details missing message4.id: UpdateMessageFlagsMessageDetail( - type: MessageType.direct, + type: .direct, mentioned: false, streamId: null, topic: null, diff --git a/test/widgets/action_sheet_test.dart b/test/widgets/action_sheet_test.dart index 519715ddd..fe3bb1c51 100644 --- a/test/widgets/action_sheet_test.dart +++ b/test/widgets/action_sheet_test.dart @@ -81,7 +81,7 @@ Future setupToMessageActionSheet(WidgetTester tester, { assert(narrow.containsMessage(message)!); selfUser ??= eg.selfUser; - assert(!(hasDeletePermission && selfUser.role == UserRole.guest)); + assert(!(hasDeletePermission && selfUser.role == .guest)); final selfAccount = eg.account(user: selfUser, zulipFeatureLevel: zulipFeatureLevel); await testBinding.globalStore.add( @@ -334,7 +334,7 @@ void main() { await store.handleEvent(ChannelUpdateEvent(id: 1, streamId: someChannel.streamId, name: someChannel.name, - property: null, value: null, + property: .unknown, value: null, // (Ideally we'd use `property` and `value` but I'm not sure if // modern servers actually do that or if they still use this // separate field.) @@ -349,7 +349,7 @@ void main() { testWidgets('private channel', (tester) async { await prepare(); await store.handleEvent(eg.channelUpdateEvent(someChannel, - property: ChannelPropertyName.inviteOnly, value: true)); + property: .inviteOnly, value: true)); check(store.streams[someChannel.streamId]).isNotNull() ..inviteOnly.isTrue()..isWebPublic.isFalse(); await showFromInbox(tester); @@ -433,9 +433,9 @@ void main() { await prepare(); await store.addStream(privateChannel); await store.updateChannel(privateChannel.streamId, - ChannelPropertyName.canSubscribeGroup, eg.groupSetting(members: [])); + .canSubscribeGroup, eg.groupSetting(members: [])); await store.updateChannel(privateChannel.streamId, - ChannelPropertyName.canAddSubscribersGroup, eg.groupSetting(members: [])); + .canAddSubscribersGroup, eg.groupSetting(members: [])); final narrow = ChannelNarrow(privateChannel.streamId); check(store.selfHasContentAccess(privateChannel)).isFalse(); await showFromMsglistAppBar(tester, @@ -2159,16 +2159,16 @@ void main() { }); testWidgets('no error if user lost posting permission after action sheet opened', (tester) async { - final selfUser = eg.user(role: UserRole.member); + final selfUser = eg.user(role: .member); final stream = eg.stream(); final message = eg.streamMessage(stream: stream); await setupToMessageActionSheet(tester, selfUser: selfUser, message: message, narrow: TopicNarrow.ofMessage(message)); await store.handleEvent(RealmUserUpdateEvent(id: 1, userId: selfUser.userId, - role: UserRole.guest)); + role: .guest)); await store.handleEvent(eg.channelUpdateEvent(stream, - property: ChannelPropertyName.channelPostPolicy, + property: .channelPostPolicy, value: ChannelPostPolicy.administrators)); await tester.pump(); diff --git a/test/widgets/autocomplete_test.dart b/test/widgets/autocomplete_test.dart index 5dd47fc50..38ff30023 100644 --- a/test/widgets/autocomplete_test.dart +++ b/test/widgets/autocomplete_test.dart @@ -486,7 +486,7 @@ void main() { final composeInputFinder = await setupToComposeInput(tester); final store = await testBinding.globalStore.perAccount(eg.selfAccount.id); await store.handleEvent(UserSettingsUpdateEvent(id: 1, - property: UserSettingName.emojiset, value: Emojiset.text)); + property: .emojiset, value: Emojiset.text)); // TODO(#226): Remove this extra edit when this bug is fixed. await tester.enterText(composeInputFinder, 'hi :'); diff --git a/test/widgets/compose_box_test.dart b/test/widgets/compose_box_test.dart index 22dfeecc3..d47eb555e 100644 --- a/test/widgets/compose_box_test.dart +++ b/test/widgets/compose_box_test.dart @@ -759,7 +759,7 @@ void main() { Future changePolicy(ChannelTopicsPolicy value) async { await store.handleEvent(eg.channelUpdateEvent(store.streams[channel.streamId]!, - property: ChannelPropertyName.topicsPolicy, value: value)); + property: .topicsPolicy, value: value)); await tester.pump(); } @@ -789,7 +789,7 @@ void main() { .topic.text.equals('some topic'); await store.handleEvent(eg.channelUpdateEvent(store.streams[channel.streamId]!, - property: ChannelPropertyName.topicsPolicy, + property: .topicsPolicy, value: ChannelTopicsPolicy.emptyTopicOnly)); await tester.pump(Duration.zero); check(state).controller.isA() @@ -845,7 +845,7 @@ void main() { Future checkStartTyping(WidgetTester tester, SendableNarrow narrow) async { connection.prepare(json: {}); await enterContent(tester, 'hello world'); - checkTypingRequest(TypingOp.start, narrow); + checkTypingRequest(.start, narrow); } testWidgets('smoke TopicNarrow', (tester) async { @@ -856,7 +856,7 @@ void main() { connection.prepare(json: {}); await tester.pump(store.serverTypingStoppedWaitPeriod); - checkTypingRequest(TypingOp.stop, narrow); + checkTypingRequest(.stop, narrow); }); testWidgets('smoke DmNarrow', (tester) async { @@ -868,7 +868,7 @@ void main() { connection.prepare(json: {}); await tester.pump(store.serverTypingStoppedWaitPeriod); - checkTypingRequest(TypingOp.stop, narrow); + checkTypingRequest(.stop, narrow); }); testWidgets('smoke ChannelNarrow', (tester) async { @@ -882,7 +882,7 @@ void main() { connection.prepare(json: {}); await tester.pump(store.serverTypingStoppedWaitPeriod); - checkTypingRequest(TypingOp.stop, destinationNarrow); + checkTypingRequest(.stop, destinationNarrow); }); testWidgets('clearing text sends a "typing stopped" notice', (tester) async { @@ -893,7 +893,7 @@ void main() { connection.prepare(json: {}); await enterContent(tester, ''); - checkTypingRequest(TypingOp.stop, narrow); + checkTypingRequest(.stop, narrow); }); testWidgets('hitting send button sends a "typing stopped" notice', (tester) async { @@ -909,7 +909,7 @@ void main() { await tester.tap(sendButtonFinder); await tester.pump(Duration.zero); final requests = connection.takeRequests(); - checkSetTypingStatusRequests([requests.first], [(TypingOp.stop, narrow)]); + checkSetTypingStatusRequests([requests.first], [(.stop, narrow)]); check(requests).length.equals(2); }); @@ -940,7 +940,7 @@ void main() { connection.prepare(json: {}); (await ZulipApp.navigator).pop(); await tester.pump(Duration.zero); - checkTypingRequest(TypingOp.stop, narrow); + checkTypingRequest(.stop, narrow); }); testWidgets('for content input, unfocusing sends a "typing stopped" notice', (tester) async { @@ -955,7 +955,7 @@ void main() { connection.prepare(json: {}); FocusManager.instance.primaryFocus!.unfocus(); await tester.pump(Duration.zero); - checkTypingRequest(TypingOp.stop, destinationNarrow); + checkTypingRequest(.stop, destinationNarrow); }); testWidgets('selection change sends a "typing started" notice', (tester) async { @@ -966,17 +966,17 @@ void main() { connection.prepare(json: {}); await tester.pump(store.serverTypingStoppedWaitPeriod); - checkTypingRequest(TypingOp.stop, narrow); + checkTypingRequest(.stop, narrow); connection.prepare(json: {}); controller!.content.selection = const TextSelection(baseOffset: 0, extentOffset: 2); - checkTypingRequest(TypingOp.start, narrow); + checkTypingRequest(.start, narrow); // Ensures that a "typing stopped" notice is sent when the test ends. connection.prepare(json: {}); await tester.pump(store.serverTypingStoppedWaitPeriod); - checkTypingRequest(TypingOp.stop, narrow); + checkTypingRequest(.stop, narrow); }); testWidgets('unfocusing app sends a "typing stopped" notice', (tester) async { @@ -994,7 +994,7 @@ void main() { WidgetsBinding.instance.handleAppLifecycleStateChanged( AppLifecycleState.hidden); await tester.pump(Duration.zero); - checkTypingRequest(TypingOp.stop, narrow); + checkTypingRequest(.stop, narrow); WidgetsBinding.instance.handleAppLifecycleStateChanged( AppLifecycleState.paused); @@ -1027,7 +1027,7 @@ void main() { ..method.equals('POST') ..url.path.equals('/api/v1/messages') ..bodyFields.deepEquals({ - 'type': 'stream', + 'type': 'channel', 'to': '123', 'topic': 'some topic', 'content': 'hello world', @@ -1734,7 +1734,7 @@ void main() { await prepareComposeBox(tester, narrow: narrow, selfUser: eg.user( - role: canSend ? UserRole.administrator : UserRole.member), + role: canSend ? .administrator : .member), streams: [channel], subscriptions: isChannelSubscribed ? [eg.subscription(channel)] : []); checkComposeBoxIsShown(expected, @@ -1840,7 +1840,7 @@ void main() { testRefreshSubscribeButtons(narrow: topicNarrow, canSendMessages: true); testWidgets('user loses privilege -> compose box is replaced with the banner', (tester) async { - final selfUser = eg.user(role: UserRole.administrator); + final selfUser = eg.user(role: .administrator); await prepareComposeBox(tester, narrow: const ChannelNarrow(1), selfUser: selfUser, @@ -1849,13 +1849,13 @@ void main() { checkComposeBox(isShown: true); await store.handleEvent(RealmUserUpdateEvent(id: 1, - userId: selfUser.userId, role: UserRole.moderator)); + userId: selfUser.userId, role: .moderator)); await tester.pump(); checkComposeBox(isShown: false); }); testWidgets('user gains privilege -> banner is replaced with the compose box', (tester) async { - final selfUser = eg.user(role: UserRole.guest); + final selfUser = eg.user(role: .guest); await prepareComposeBox(tester, narrow: const ChannelNarrow(1), selfUser: selfUser, @@ -1864,13 +1864,13 @@ void main() { checkComposeBox(isShown: false); await store.handleEvent(RealmUserUpdateEvent(id: 1, - userId: selfUser.userId, role: UserRole.administrator)); + userId: selfUser.userId, role: .administrator)); await tester.pump(); checkComposeBox(isShown: true); }); testWidgets('channel policy becomes stricter -> compose box is replaced with the banner', (tester) async { - final selfUser = eg.user(role: UserRole.guest); + final selfUser = eg.user(role: .guest); final channel = eg.stream(streamId: 1, channelPostPolicy: ChannelPostPolicy.any); @@ -1881,14 +1881,14 @@ void main() { checkComposeBox(isShown: true); await store.handleEvent(eg.channelUpdateEvent(channel, - property: ChannelPropertyName.channelPostPolicy, + property: .channelPostPolicy, value: ChannelPostPolicy.fullMembers)); await tester.pump(); checkComposeBox(isShown: false); }); testWidgets('channel policy becomes less strict -> banner is replaced with the compose box', (tester) async { - final selfUser = eg.user(role: UserRole.moderator); + final selfUser = eg.user(role: .moderator); final channel = eg.stream(streamId: 1, channelPostPolicy: ChannelPostPolicy.administrators); @@ -1899,7 +1899,7 @@ void main() { checkComposeBox(isShown: false); await store.handleEvent(eg.channelUpdateEvent(channel, - property: ChannelPropertyName.channelPostPolicy, + property: .channelPostPolicy, value: ChannelPostPolicy.moderators)); await tester.pump(); checkComposeBox(isShown: true); @@ -2062,7 +2062,7 @@ void main() { .topic.text.equals('some topic'); await newStore.handleEvent(eg.channelUpdateEvent(newStore.streams[channel.streamId]!, - property: ChannelPropertyName.topicsPolicy, + property: .topicsPolicy, value: ChannelTopicsPolicy.emptyTopicOnly)); await tester.pump(Duration.zero); check(state).controller.isA() @@ -2238,7 +2238,7 @@ void main() { ..content.text.isNotNull().isEmpty(); await store.handleEvent(eg.channelUpdateEvent(store.streams[channel.streamId]!, - property: ChannelPropertyName.topicsPolicy, + property: .topicsPolicy, value: ChannelTopicsPolicy.emptyTopicOnly)); await tester.pump(Duration.zero); diff --git a/test/widgets/content_test.dart b/test/widgets/content_test.dart index 77eaadecb..7f9e4002f 100644 --- a/test/widgets/content_test.dart +++ b/test/widgets/content_test.dart @@ -1594,7 +1594,7 @@ void main() { Future prepare( WidgetTester tester, - [TwentyFourHourTimeMode twentyFourHourTimeMode = TwentyFourHourTimeMode.localeDefault] + [TwentyFourHourTimeMode twentyFourHourTimeMode = .localeDefault] ) async { final initialSnapshot = eg.initialSnapshot() ..userSettings.twentyFourHourTime = twentyFourHourTimeMode; @@ -1611,17 +1611,17 @@ void main() { }); testWidgets('TwentyFourHourTimeMode.twelveHour', (tester) async { - await prepare(tester, TwentyFourHourTimeMode.twelveHour); + await prepare(tester, .twelveHour); check(find.textContaining(renderedTextRegexpTwelveHour)).findsOne(); }); testWidgets('TwentyFourHourTimeMode.twentyFourHour', (tester) async { - await prepare(tester, TwentyFourHourTimeMode.twentyFourHour); + await prepare(tester, .twentyFourHour); check(find.textContaining(renderedTextRegexpTwentyFourHour)).findsOne(); }); testWidgets('TwentyFourHourTimeMode.localeDefault', (tester) async { - await prepare(tester, TwentyFourHourTimeMode.localeDefault); + await prepare(tester, .localeDefault); // This expectation holds as long as we're always formatting in en_US, // the default locale, which uses the twelve-hour format. // TODO(#1727) follow the actual locale; test with different locales diff --git a/test/widgets/emoji_reaction_test.dart b/test/widgets/emoji_reaction_test.dart index 72ccac41a..233b964f5 100644 --- a/test/widgets/emoji_reaction_test.dart +++ b/test/widgets/emoji_reaction_test.dart @@ -143,7 +143,7 @@ void main() { group('ReactionChipsList', () { // Smoke tests under various conditions. for (final displayEmojiReactionUsers in [true, false]) { - for (final emojiset in [Emojiset.text, Emojiset.google]) { + for (final emojiset in [.text, .google]) { for (final textDirection in TextDirection.values) { for (final textScaleFactor in kTextScaleFactors) { void runSmokeTest( @@ -190,10 +190,10 @@ void main() { await store.handleEvent(RealmEmojiUpdateEvent(id: 1, realmEmoji: realmEmoji)); await store.handleEvent(UserSettingsUpdateEvent(id: 1, - property: UserSettingName.displayEmojiReactionUsers, + property: .displayEmojiReactionUsers, value: displayEmojiReactionUsers)); await store.handleEvent(UserSettingsUpdateEvent(id: 1, - property: UserSettingName.emojiset, + property: .emojiset, value: emojiset)); // This does mean that all image emoji will look the same… diff --git a/test/widgets/home_test.dart b/test/widgets/home_test.dart index 31f2f4a78..ae49fa47e 100644 --- a/test/widgets/home_test.dart +++ b/test/widgets/home_test.dart @@ -760,7 +760,7 @@ void main () { await prepareBanner(tester, zulipFeatureLevel: unsupportedFeatureLevel, zulipVersion: unsupportedVersion, - selfUser: eg.user(role: UserRole.administrator)); + selfUser: eg.user(role: .administrator)); check(find.text(zulipLocalizations.serverCompatBannerAdminMessage( sampleRealmUrl, unsupportedVersion))).findsOne(); }); @@ -769,7 +769,7 @@ void main () { await prepareBanner(tester, zulipFeatureLevel: unsupportedFeatureLevel, zulipVersion: unsupportedVersion, - selfUser: eg.user(role: UserRole.owner)); + selfUser: eg.user(role: .owner)); check(find.text(zulipLocalizations.serverCompatBannerAdminMessage( sampleRealmUrl, unsupportedVersion))).findsOne(); }); diff --git a/test/widgets/message_list_test.dart b/test/widgets/message_list_test.dart index f703aae43..afa6b451a 100644 --- a/test/widgets/message_list_test.dart +++ b/test/widgets/message_list_test.dart @@ -1176,19 +1176,19 @@ void main() { await tester.pump(); check(finder.evaluate()).isEmpty(); await checkTyping(tester, - eg.typingEvent(narrow, TypingOp.start, eg.otherUser.userId), + eg.typingEvent(narrow, .start, eg.otherUser.userId), expected: 'Other User is typing…'); await checkTyping(tester, - eg.typingEvent(narrow, TypingOp.start, eg.selfUser.userId), + eg.typingEvent(narrow, .start, eg.selfUser.userId), expected: 'Other User is typing…'); await checkTyping(tester, - eg.typingEvent(narrow, TypingOp.start, eg.thirdUser.userId), + eg.typingEvent(narrow, .start, eg.thirdUser.userId), expected: 'Other User and Third User are typing…'); await checkTyping(tester, - eg.typingEvent(narrow, TypingOp.start, eg.fourthUser.userId), + eg.typingEvent(narrow, .start, eg.fourthUser.userId), expected: 'Several people are typing…'); await checkTyping(tester, - eg.typingEvent(narrow, TypingOp.stop, eg.otherUser.userId), + eg.typingEvent(narrow, .stop, eg.otherUser.userId), expected: 'Third User and Fourth User are typing…'); // Verify that typing indicators expire after a set duration. await tester.pump(const Duration(seconds: 15)); @@ -1202,7 +1202,7 @@ void main() { await setupMessageListPage(tester, narrow: narrow, users: [], messages: [streamMessage]); await checkTyping(tester, - eg.typingEvent(narrow, TypingOp.start, 1000), + eg.typingEvent(narrow, .start, 1000), expected: '(unknown user) is typing…', ); // Wait for the pending timers to end. @@ -1214,18 +1214,18 @@ void main() { narrow: topicNarrow, users: users, messages: [streamMessage]); await checkTyping(tester, - eg.typingEvent(topicNarrow, TypingOp.start, eg.otherUser.userId), + eg.typingEvent(topicNarrow, .start, eg.otherUser.userId), expected: 'Other User is typing…'); await checkTyping(tester, - eg.typingEvent(topicNarrow, TypingOp.start, eg.thirdUser.userId), + eg.typingEvent(topicNarrow, .start, eg.thirdUser.userId), expected: 'Other User and Third User are typing…'); await store.setMutedUsers([eg.otherUser.userId]); await tester.pump(); await checkTyping(tester, - eg.typingEvent(topicNarrow, TypingOp.start, eg.thirdUser.userId), + eg.typingEvent(topicNarrow, .start, eg.thirdUser.userId), expected: 'Third User is typing…', // no "Other User" ); @@ -1681,7 +1681,7 @@ void main() { ..method.equals('POST') ..url.path.equals('/api/v1/messages') ..bodyFields.deepEquals({ - 'type': 'stream', + 'type': 'channel', 'to': '${otherChannel.streamId}', 'topic': 'new topic', 'content': 'Some text', @@ -2167,12 +2167,12 @@ void main() { for (final (timestampStr, expectedTwelveHour, expectedTwentyFourHour) in cases) { for (final mode in TwentyFourHourTimeMode.values) { final expected = switch (mode) { - TwentyFourHourTimeMode.twelveHour => expectedTwelveHour, - TwentyFourHourTimeMode.twentyFourHour => expectedTwentyFourHour, + .twelveHour => expectedTwelveHour, + .twentyFourHour => expectedTwentyFourHour, // This expectation will hold as long as we're always using the // default locale, en_US, which uses the twelve-hour format. // TODO(#1727) test with other locales - TwentyFourHourTimeMode.localeDefault => expectedTwelveHour, + .localeDefault => expectedTwelveHour, }; test('${style.name} in ${mode.name}: $timestampStr returns $expected', () { diff --git a/test/widgets/profile_test.dart b/test/widgets/profile_test.dart index c21361909..801188c34 100644 --- a/test/widgets/profile_test.dart +++ b/test/widgets/profile_test.dart @@ -219,16 +219,16 @@ void main() { ], pageUserId: 1, customProfileFields: [ - eg.customProfileField(0, CustomProfileFieldType.shortText), - eg.customProfileField(1, CustomProfileFieldType.longText), - eg.customProfileField(2, CustomProfileFieldType.choice, + eg.customProfileField(0, .shortText), + eg.customProfileField(1, .longText), + eg.customProfileField(2, .choice, fieldData: '{"x": {"text": "choiceValue", "order": "1"}}'), - eg.customProfileField(3, CustomProfileFieldType.date), - eg.customProfileField(4, CustomProfileFieldType.link), - eg.customProfileField(5, CustomProfileFieldType.user), - eg.customProfileField(6, CustomProfileFieldType.externalAccount, + eg.customProfileField(3, .date), + eg.customProfileField(4, .link), + eg.customProfileField(5, .user), + eg.customProfileField(6, .externalAccount, fieldData: '{"subtype": "external1"}'), - eg.customProfileField(7, CustomProfileFieldType.pronouns), + eg.customProfileField(7, .pronouns), ], realmDefaultExternalAccounts: { 'external1': RealmDefaultExternalAccount( name: 'external1', @@ -236,15 +236,15 @@ void main() { hint: '', urlPattern: 'https://example/%(username)s')}); - final testCases = [ - (find.text('field0'), find.text('shortTextValue'), CustomProfileFieldType.shortText), - (find.text('field1'), find.text('longTextValue'), CustomProfileFieldType.longText), - (find.text('field2'), find.text('choiceValue'), CustomProfileFieldType.choice), - (find.text('field3'), find.text('dateValue'), CustomProfileFieldType.date), - (find.text('field4'), find.text('http://example/linkValue'), CustomProfileFieldType.link), - (find.text('field5'), find.text('userValue'), CustomProfileFieldType.user), - (find.text('field6'), find.text('externalValue'), CustomProfileFieldType.externalAccount), - (find.text('field7'), find.text('pronounsValue'), CustomProfileFieldType.pronouns), + final testCases = <(Finder, Finder, CustomProfileFieldType)>[ + (find.text('field0'), find.text('shortTextValue'), .shortText), + (find.text('field1'), find.text('longTextValue'), .longText), + (find.text('field2'), find.text('choiceValue'), .choice), + (find.text('field3'), find.text('dateValue'), .date), + (find.text('field4'), find.text('http://example/linkValue'), .link), + (find.text('field5'), find.text('userValue'), .user), + (find.text('field6'), find.text('externalValue'), .externalAccount), + (find.text('field7'), find.text('pronounsValue'), .pronouns), ]; for (final testCase in testCases) { Finder labelFinder = testCase.$1; @@ -273,7 +273,7 @@ void main() { await setupPage(tester, users: [user], pageUserId: user.userId, - customProfileFields: [eg.customProfileField(0, CustomProfileFieldType.link)], + customProfileFields: [eg.customProfileField(0, .link)], ); await tester.tap(find.text(testUrl)); @@ -292,7 +292,7 @@ void main() { users: [user], pageUserId: user.userId, customProfileFields: [ - eg.customProfileField(0, CustomProfileFieldType.externalAccount, + eg.customProfileField(0, .externalAccount, fieldData: '{"subtype": "external1"}') ], realmDefaultExternalAccounts: { @@ -324,7 +324,7 @@ void main() { await setupPage(tester, users: users, pageUserId: 1, - customProfileFields: [eg.customProfileField(0, CustomProfileFieldType.user)], + customProfileFields: [eg.customProfileField(0, .user)], navigatorObserver: testNavObserver, ); @@ -345,7 +345,7 @@ void main() { await setupPage(tester, users: users, pageUserId: 1, - customProfileFields: [eg.customProfileField(0, CustomProfileFieldType.user)], + customProfileFields: [eg.customProfileField(0, .user)], ); final textFinder = find.text('(unknown user)'); @@ -376,7 +376,7 @@ void main() { users: users, mutedUserIds: [2], pageUserId: 1, - customProfileFields: [eg.customProfileField(0, CustomProfileFieldType.user)]); + customProfileFields: [eg.customProfileField(0, .user)]); check(find.text('Muted user')).findsOne(); check(mutedAvatarFinder(2)).findsOne(); @@ -401,7 +401,7 @@ void main() { await setupPage(tester, users: users, pageUserId: 1, - customProfileFields: [eg.customProfileField(0, CustomProfileFieldType.user)], + customProfileFields: [eg.customProfileField(0, .user)], ); final avatars = tester.widgetList(find.byType(Avatar)); @@ -424,16 +424,16 @@ void main() { await setupPage(tester, users: [user, user2], pageUserId: user.userId, customProfileFields: [ - eg.customProfileField(0, CustomProfileFieldType.shortText), - eg.customProfileField(1, CustomProfileFieldType.longText), - eg.customProfileField(2, CustomProfileFieldType.choice, + eg.customProfileField(0, .shortText), + eg.customProfileField(1, .longText), + eg.customProfileField(2, .choice, fieldData: '{"x": {"text": "$longString", "order": "1"}}'), // no [CustomProfileFieldType.date] because those can't be made long - eg.customProfileField(3, CustomProfileFieldType.link), - eg.customProfileField(4, CustomProfileFieldType.user), - eg.customProfileField(5, CustomProfileFieldType.externalAccount, + eg.customProfileField(3, .link), + eg.customProfileField(4, .user), + eg.customProfileField(5, .externalAccount, fieldData: '{"subtype": "external1"}'), - eg.customProfileField(6, CustomProfileFieldType.pronouns), + eg.customProfileField(6, .pronouns), ], realmDefaultExternalAccounts: { 'external1': RealmDefaultExternalAccount( name: 'external1', @@ -565,7 +565,7 @@ void main() { void scheduleEventAfter(Duration duration, bool newInvisibleModeValue) async { await Future.delayed(duration); await store.handleEvent(UserSettingsUpdateEvent(id: 1, - property: UserSettingName.presenceEnabled, value: !newInvisibleModeValue)); + property: .presenceEnabled, value: !newInvisibleModeValue)); } void checkRequest(bool requestedInvisibleModeValue) { @@ -619,7 +619,7 @@ void main() { checkAppearsActive(tester, false); await store.handleEvent(UserSettingsUpdateEvent(id: 1, - property: UserSettingName.presenceEnabled, value: false)); + property: .presenceEnabled, value: false)); await tester.pump(); checkAppearsActive(tester, true); @@ -654,7 +654,7 @@ void main() { await setupPage(tester, pageUserId: eg.selfUser.userId); await store.handleEvent(UserSettingsUpdateEvent(id: 1, - property: UserSettingName.presenceEnabled, value: false)); + property: .presenceEnabled, value: false)); await tester.pump(); checkAppearsActive(tester, true);