diff --git a/lib/api/model/events.dart b/lib/api/model/events.dart index 9faa3d367e..ae997adc57 100644 --- a/lib/api/model/events.dart +++ b/lib/api/model/events.dart @@ -708,13 +708,13 @@ class UpdateMessageEvent extends Event { @JsonKey(includeToJson: true) String get type => 'update_message'; - final int? userId; // TODO(server-5) - final bool? renderingOnly; // TODO(server-5) + final int userId; + final bool renderingOnly; final int messageId; final List messageIds; final List flags; - final int? editTimestamp; // TODO(server-5) + final int editTimestamp; // final String? streamName; // ignore diff --git a/lib/api/model/events.g.dart b/lib/api/model/events.g.dart index 5d47444ecd..dc56b1d157 100644 --- a/lib/api/model/events.g.dart +++ b/lib/api/model/events.g.dart @@ -416,8 +416,8 @@ const _$UserTopicVisibilityPolicyEnumMap = { UpdateMessageEvent _$UpdateMessageEventFromJson(Map json) => UpdateMessageEvent( id: (json['id'] as num).toInt(), - userId: (json['user_id'] as num?)?.toInt(), - renderingOnly: json['rendering_only'] as bool?, + userId: (json['user_id'] as num).toInt(), + renderingOnly: json['rendering_only'] as bool, messageId: (json['message_id'] as num).toInt(), messageIds: (json['message_ids'] as List) .map((e) => (e as num).toInt()) @@ -425,7 +425,7 @@ UpdateMessageEvent _$UpdateMessageEventFromJson(Map json) => flags: (json['flags'] as List) .map((e) => $enumDecode(_$MessageFlagEnumMap, e)) .toList(), - editTimestamp: (json['edit_timestamp'] as num?)?.toInt(), + editTimestamp: (json['edit_timestamp'] as num).toInt(), origStreamId: (json['stream_id'] as num?)?.toInt(), newStreamId: (json['new_stream_id'] as num?)?.toInt(), propagateMode: diff --git a/lib/api/model/initial_snapshot.dart b/lib/api/model/initial_snapshot.dart index 1adc44196f..fcccf0dcf3 100644 --- a/lib/api/model/initial_snapshot.dart +++ b/lib/api/model/initial_snapshot.dart @@ -18,7 +18,7 @@ class InitialSnapshot { final int lastEventId; final int zulipFeatureLevel; final String zulipVersion; - final String? zulipMergeBase; // TODO(server-5) + final String zulipMergeBase; final List alertWords; @@ -54,13 +54,7 @@ class InitialSnapshot { final List streams; - // Servers pre-5.0 don't have `user_settings`, and instead provide whatever - // user settings they support at toplevel in the initial snapshot. Since we're - // likely to desupport pre-5.0 servers before wide release, we prefer to - // ignore the toplevel fields and use `user_settings` where present instead, - // even at the expense of functionality with pre-5.0 servers. - // TODO(server-5) remove pre-5.0 comment - final UserSettings? userSettings; // TODO(server-5) + final UserSettings userSettings; final List? userTopics; // TODO(server-6) @@ -312,15 +306,9 @@ class UnreadMessagesSnapshot { /// An item in [UnreadMessagesSnapshot.dms]. @JsonSerializable(fieldRename: FieldRename.snake) class UnreadDmSnapshot { - @JsonKey(readValue: _readOtherUserId) final int otherUserId; final List unreadMessageIds; - // TODO(server-5): Simplify away. - static dynamic _readOtherUserId(Map json, String key) { - return json[key] ?? json['sender_id']; - } - UnreadDmSnapshot({ required this.otherUserId, required this.unreadMessageIds, diff --git a/lib/api/model/initial_snapshot.g.dart b/lib/api/model/initial_snapshot.g.dart index a69b6ebafe..59ffc0b6aa 100644 --- a/lib/api/model/initial_snapshot.g.dart +++ b/lib/api/model/initial_snapshot.g.dart @@ -14,7 +14,7 @@ InitialSnapshot _$InitialSnapshotFromJson(Map json) => lastEventId: (json['last_event_id'] as num).toInt(), zulipFeatureLevel: (json['zulip_feature_level'] as num).toInt(), zulipVersion: json['zulip_version'] as String, - zulipMergeBase: json['zulip_merge_base'] as String?, + zulipMergeBase: json['zulip_merge_base'] as String, alertWords: (json['alert_words'] as List) .map((e) => e as String) .toList(), @@ -51,10 +51,8 @@ InitialSnapshot _$InitialSnapshotFromJson(Map json) => streams: (json['streams'] as List) .map((e) => ZulipStream.fromJson(e as Map)) .toList(), - userSettings: json['user_settings'] == null - ? null - : UserSettings.fromJson( - json['user_settings'] as Map), + userSettings: + UserSettings.fromJson(json['user_settings'] as Map), userTopics: (json['user_topics'] as List?) ?.map((e) => UserTopicItem.fromJson(e as Map)) .toList(), @@ -258,9 +256,7 @@ Map _$UnreadMessagesSnapshotToJson( UnreadDmSnapshot _$UnreadDmSnapshotFromJson(Map json) => UnreadDmSnapshot( - otherUserId: - (UnreadDmSnapshot._readOtherUserId(json, 'other_user_id') as num) - .toInt(), + otherUserId: (json['other_user_id'] as num).toInt(), unreadMessageIds: (json['unread_message_ids'] as List) .map((e) => (e as num).toInt()) .toList(), diff --git a/lib/api/model/model.dart b/lib/api/model/model.dart index fad8ddc5bc..c8011598a4 100644 --- a/lib/api/model/model.dart +++ b/lib/api/model/model.dart @@ -205,7 +205,7 @@ class User { // bool isOwner; // obsoleted by [role]; ignore // bool isAdmin; // obsoleted by [role]; ignore // bool isGuest; // obsoleted by [role]; ignore - bool? isBillingAdmin; // TODO(server-5) + bool isBillingAdmin; final bool isBot; final int? botType; // TODO enum int? botOwnerId; @@ -221,7 +221,9 @@ class User { @JsonKey(readValue: _readProfileData) Map? profileData; - @JsonKey(readValue: _readIsSystemBot) + // This field is absent in `realm_users` and `realm_non_active_users`, + // which contain no system bots; it's present in `cross_realm_bots`. + @JsonKey(defaultValue: false) final bool isSystemBot; static Map? _readProfileData(Map json, String key) { @@ -233,14 +235,6 @@ class User { return (value != null && value.isNotEmpty) ? value : null; } - static bool _readIsSystemBot(Map json, String key) { - // This field is absent in `realm_users` and `realm_non_active_users`, - // which contain no system bots; it's present in `cross_realm_bots`. - return (json[key] as bool?) - ?? (json['is_cross_realm_bot'] as bool?) // TODO(server-5): renamed to `is_system_bot` - ?? false; - } - User({ required this.userId, required this.deliveryEmail, @@ -924,18 +918,15 @@ enum MessageEditState { continue; } - // TODO(server-5) prev_subject was the old name of prev_topic on pre-5.0 servers - final prevTopicStr = (entry['prev_topic'] ?? entry['prev_subject']) as String?; + final prevTopicStr = entry['prev_topic'] as String?; final prevTopic = prevTopicStr == null ? null : TopicName.fromJson(prevTopicStr); final topicStr = entry['topic'] as String?; final topic = topicStr == null ? null : TopicName.fromJson(topicStr); - if (prevTopic != null) { - // TODO(server-5) pre-5.0 servers do not have the 'topic' field - if (topic == null) { - hasMoved = true; - } else { - hasMoved |= !topicMoveWasResolveOrUnresolve(topic, prevTopic); - } + if (topic != null || prevTopic != null) { + // Crunchy-shell validation: Both are present if the topic was edited + topic as TopicName; + prevTopic as TopicName; + hasMoved |= !topicMoveWasResolveOrUnresolve(topic, prevTopic); } } diff --git a/lib/api/model/model.g.dart b/lib/api/model/model.g.dart index cfa38aec5f..ee76a4ae72 100644 --- a/lib/api/model/model.g.dart +++ b/lib/api/model/model.g.dart @@ -97,7 +97,7 @@ User _$UserFromJson(Map json) => User( fullName: json['full_name'] as String, dateJoined: json['date_joined'] as String, isActive: json['is_active'] as bool, - isBillingAdmin: json['is_billing_admin'] as bool?, + isBillingAdmin: json['is_billing_admin'] as bool, isBot: json['is_bot'] as bool, botType: (json['bot_type'] as num?)?.toInt(), botOwnerId: (json['bot_owner_id'] as num?)?.toInt(), @@ -112,7 +112,7 @@ User _$UserFromJson(Map json) => User( (k, e) => MapEntry(int.parse(k), ProfileFieldUserData.fromJson(e as Map)), ), - isSystemBot: User._readIsSystemBot(json, 'is_system_bot') as bool, + isSystemBot: json['is_system_bot'] as bool? ?? false, ); Map _$UserToJson(User instance) => { diff --git a/lib/api/model/web_auth.dart b/lib/api/model/web_auth.dart index 490c4b79db..5029a8e39f 100644 --- a/lib/api/model/web_auth.dart +++ b/lib/api/model/web_auth.dart @@ -7,7 +7,7 @@ import 'package:flutter/foundation.dart'; class WebAuthPayload { final Uri realm; final String email; - final int? userId; // TODO(server-5) new in FL 108 + final int userId; final String otpEncryptedApiKey; WebAuthPayload._({ @@ -25,7 +25,7 @@ class WebAuthPayload { queryParameters: { 'realm': String realmStr, 'email': String email, - // 'user_id' handled below + 'user_id': String userIdStr, 'otp_encrypted_api_key': String otpEncryptedApiKey, }, ) @@ -33,13 +33,8 @@ class WebAuthPayload { final Uri? realm = Uri.tryParse(realmStr); if (realm == null) throw const FormatException(); - // TODO(server-5) require in queryParameters (new in FL 108) - final userIdStr = url.queryParameters['user_id']; - int? userId; - if (userIdStr != null) { - userId = int.tryParse(userIdStr, radix: 10); - if (userId == null) throw const FormatException(); - } + final userId = int.tryParse(userIdStr, radix: 10); + if (userId == null) throw const FormatException(); if (!RegExp(r'^[0-9a-fA-F]{64}$').hasMatch(otpEncryptedApiKey)) { throw const FormatException(); diff --git a/lib/api/notifications.dart b/lib/api/notifications.dart index 6d028aa267..9c5df3c749 100644 --- a/lib/api/notifications.dart +++ b/lib/api/notifications.dart @@ -177,13 +177,9 @@ sealed class FcmMessageRecipient { @JsonSerializable(fieldRename: FieldRename.snake, createToJson: false) @_IntConverter() class FcmMessageChannelRecipient extends FcmMessageRecipient { - // Sending the stream ID in notifications is new in Zulip Server 5. - // But handling the lack of it would add complication, and we don't strictly - // need to -- we intend (#268) to cut pre-server-5 support before beta release. - // TODO(server-5): cut comment final int streamId; - // Current servers (as of 2023) always send the stream name. But + // Current servers (as of 2025) always send the stream name. But // future servers might not, once clients get the name from local data. // So might as well be ready. @JsonKey(name: 'stream') diff --git a/lib/api/route/messages.dart b/lib/api/route/messages.dart index 5af312ce45..a735523abd 100644 --- a/lib/api/route/messages.dart +++ b/lib/api/route/messages.dart @@ -1,64 +1,16 @@ import 'package:json_annotation/json_annotation.dart'; import '../core.dart'; -import '../exception.dart'; import '../model/model.dart'; import '../model/narrow.dart'; part 'messages.g.dart'; -/// Convenience function to get a single message from any server. -/// -/// This encapsulates a server-feature check. -/// -/// Gives null if the server reports that the message doesn't exist. -// TODO(server-5) Simplify this away; just use getMessage. -Future getMessageCompat(ApiConnection connection, { - required int messageId, - bool? applyMarkdown, -}) async { - final useLegacyApi = connection.zulipFeatureLevel! < 120; - if (useLegacyApi) { - final response = await getMessages(connection, - narrow: [ApiNarrowMessageId(messageId)], - anchor: NumericAnchor(messageId), - numBefore: 0, - numAfter: 0, - applyMarkdown: applyMarkdown, - - // Hard-code this param to `true`, as the new single-message API - // effectively does: - // https://chat.zulip.org/#narrow/stream/378-api-design/topic/.60client_gravatar.60.20in.20.60messages.2F.7Bmessage_id.7D.60/near/1418337 - clientGravatar: true, - ); - return response.messages.firstOrNull; - } else { - try { - final response = await getMessage(connection, - messageId: messageId, - applyMarkdown: applyMarkdown, - ); - return response.message; - } on ZulipApiException catch (e) { - if (e.code == 'BAD_REQUEST') { - // Servers use this code when the message doesn't exist, according to - // the example in the doc. - return null; - } - rethrow; - } - } -} - /// https://zulip.com/api/get-message -/// -/// This binding only supports feature levels 120+. -// TODO(server-5) remove FL 120+ mention in doc, and the related `assert` Future getMessage(ApiConnection connection, { required int messageId, bool? applyMarkdown, }) { - assert(connection.zulipFeatureLevel! >= 120); return connection.get('getMessage', GetMessageResult.fromJson, 'messages/$messageId', { if (applyMarkdown != null) 'apply_markdown': applyMarkdown, }); diff --git a/lib/api/route/realm.dart b/lib/api/route/realm.dart index a43c2e9921..d80fc69286 100644 --- a/lib/api/route/realm.dart +++ b/lib/api/route/realm.dart @@ -36,7 +36,8 @@ class GetServerSettingsResult { final int zulipFeatureLevel; final String zulipVersion; - final String? zulipMergeBase; // TODO(server-5) + // TODO(server-5): Modernize this once we get to #267. + final String? zulipMergeBase; final bool pushNotificationsEnabled; final bool isIncompatible; @@ -50,7 +51,7 @@ class GetServerSettingsResult { final String realmName; final String realmIcon; final String realmDescription; - final bool? realmWebPublicAccessEnabled; // TODO(server-5) + final bool realmWebPublicAccessEnabled; GetServerSettingsResult({ required this.authenticationMethods, diff --git a/lib/api/route/realm.g.dart b/lib/api/route/realm.g.dart index e34fbc9d0c..77362eb60d 100644 --- a/lib/api/route/realm.g.dart +++ b/lib/api/route/realm.g.dart @@ -31,7 +31,7 @@ GetServerSettingsResult _$GetServerSettingsResultFromJson( realmIcon: json['realm_icon'] as String, realmDescription: json['realm_description'] as String, realmWebPublicAccessEnabled: - json['realm_web_public_access_enabled'] as bool?, + json['realm_web_public_access_enabled'] as bool, ); Map _$GetServerSettingsResultToJson( diff --git a/lib/model/emoji.dart b/lib/model/emoji.dart index b0ec5f7324..6ac675986a 100644 --- a/lib/model/emoji.dart +++ b/lib/model/emoji.dart @@ -18,9 +18,9 @@ sealed class EmojiDisplay { EmojiDisplay({required this.emojiName}); - EmojiDisplay resolve(UserSettings? userSettings) { // TODO(server-5) + EmojiDisplay resolve(UserSettings userSettings) { if (this is TextEmojiDisplay) return this; - if (userSettings?.emojiset == Emojiset.text) { + if (userSettings.emojiset == Emojiset.text) { return TextEmojiDisplay(emojiName: emojiName); } return this; diff --git a/lib/model/message.dart b/lib/model/message.dart index 84f3bbc3e5..30645b7a7f 100644 --- a/lib/model/message.dart +++ b/lib/model/message.dart @@ -128,9 +128,7 @@ class MessageStoreImpl with MessageStore { } void _handleUpdateMessageEventTimestamp(UpdateMessageEvent event) { - // TODO(server-5): Cut this fallback; rely on renderingOnly from FL 114 - final isRenderingOnly = event.renderingOnly ?? (event.userId == null); - if (event.editTimestamp == null || isRenderingOnly) { + if (event.renderingOnly) { // A rendering-only update gets omitted from the message edit history, // and [Message.lastEditTimestamp] is the last timestamp of that history. // So on a rendering-only update, the timestamp doesn't get updated. diff --git a/lib/model/store.dart b/lib/model/store.dart index 94f6c56084..9483c699d2 100644 --- a/lib/model/store.dart +++ b/lib/model/store.dart @@ -456,7 +456,7 @@ class PerAccountStore extends ChangeNotifier with EmojiStore, UserStore, Channel /// Will throw if called after [dispose] has been called. Account get account => _globalStore.getAccount(accountId)!; - final UserSettings? userSettings; // TODO(server-5) + final UserSettings userSettings; final TypingNotifier typingNotifier; @@ -626,11 +626,11 @@ class PerAccountStore extends ChangeNotifier with EmojiStore, UserStore, Channel } switch (event.property!) { case UserSettingName.twentyFourHourTime: - userSettings?.twentyFourHourTime = event.value as bool; + userSettings.twentyFourHourTime = event.value as bool; case UserSettingName.displayEmojiReactionUsers: - userSettings?.displayEmojiReactionUsers = event.value as bool; + userSettings.displayEmojiReactionUsers = event.value as bool; case UserSettingName.emojiset: - userSettings?.emojiset = event.value as Emojiset; + userSettings.emojiset = event.value as Emojiset; } notifyListeners(); diff --git a/lib/widgets/action_sheet.dart b/lib/widgets/action_sheet.dart index aa81461421..5ebe6c7032 100644 --- a/lib/widgets/action_sheet.dart +++ b/lib/widgets/action_sheet.dart @@ -693,17 +693,20 @@ Future fetchRawContentWithFeedback({ // On final failure or success, auto-dismiss the snackbar. final zulipLocalizations = ZulipLocalizations.of(context); try { - fetchedMessage = await getMessageCompat(PerAccountStoreWidget.of(context).connection, + fetchedMessage = (await getMessage(PerAccountStoreWidget.of(context).connection, messageId: messageId, applyMarkdown: false, - ); - if (fetchedMessage == null) { - errorMessage = zulipLocalizations.errorMessageDoesNotSeemToExist; - } + )).message; } catch (e) { switch (e) { case ZulipApiException(): - errorMessage = e.message; + if (e.code == 'BAD_REQUEST') { + // Servers use this code when the message doesn't exist, according + // to the example in the doc. + errorMessage = zulipLocalizations.errorMessageDoesNotSeemToExist; + } else { + errorMessage = e.message; + } // TODO specific messages for common errors, like network errors // (support with reusable code) default: diff --git a/lib/widgets/emoji_reaction.dart b/lib/widgets/emoji_reaction.dart index c418337e62..c3a5035aba 100644 --- a/lib/widgets/emoji_reaction.dart +++ b/lib/widgets/emoji_reaction.dart @@ -121,7 +121,7 @@ class ReactionChipsList extends StatelessWidget { @override Widget build(BuildContext context) { final store = PerAccountStoreWidget.of(context); - final displayEmojiReactionUsers = store.userSettings?.displayEmojiReactionUsers ?? false; + final displayEmojiReactionUsers = store.userSettings.displayEmojiReactionUsers ?? false; final showNames = displayEmojiReactionUsers && reactions.total <= 3; return Wrap(spacing: 4, runSpacing: 4, crossAxisAlignment: WrapCrossAlignment.center, diff --git a/lib/widgets/login.dart b/lib/widgets/login.dart index 504289adc1..31848d953f 100644 --- a/lib/widgets/login.dart +++ b/lib/widgets/login.dart @@ -309,8 +309,7 @@ class _LoginPageState extends State { if (payload.realm.origin != widget.serverSettings.realmUrl.origin) throw Error(); final apiKey = payload.decodeApiKey(_otp!); await _tryInsertAccountAndNavigate( - // TODO(server-5): Rely on userId from payload. - userId: payload.userId ?? await _getUserId(payload.email, apiKey), + userId: payload.userId, email: payload.email, apiKey: apiKey, ); diff --git a/test/api/model/events_checks.dart b/test/api/model/events_checks.dart index c1fa0a117f..01f5243568 100644 --- a/test/api/model/events_checks.dart +++ b/test/api/model/events_checks.dart @@ -42,12 +42,12 @@ extension MessageEventChecks on Subject { } extension UpdateMessageEventChecks on Subject { - Subject get userId => has((e) => e.userId, 'userId'); - Subject get renderingOnly => has((e) => e.renderingOnly, 'renderingOnly'); + Subject get userId => has((e) => e.userId, 'userId'); + Subject get renderingOnly => has((e) => e.renderingOnly, 'renderingOnly'); Subject get messageId => has((e) => e.messageId, 'messageId'); Subject> get messageIds => has((e) => e.messageIds, 'messageIds'); Subject> get flags => has((e) => e.flags, 'flags'); - Subject get editTimestamp => has((e) => e.editTimestamp, 'editTimestamp'); + Subject get editTimestamp => has((e) => e.editTimestamp, 'editTimestamp'); Subject get origStreamId => has((e) => e.origStreamId, 'origStreamId'); Subject get newStreamId => has((e) => e.newStreamId, 'newStreamId'); Subject get propagateMode => has((e) => e.propagateMode, 'propagateMode'); diff --git a/test/api/model/model_checks.dart b/test/api/model/model_checks.dart index 8b39b1ad57..469078ccca 100644 --- a/test/api/model/model_checks.dart +++ b/test/api/model/model_checks.dart @@ -9,7 +9,7 @@ extension UserChecks on Subject { Subject get fullName => has((x) => x.fullName, 'fullName'); Subject get dateJoined => has((x) => x.dateJoined, 'dateJoined'); Subject get isActive => has((x) => x.isActive, 'isActive'); - Subject get isBillingAdmin => has((x) => x.isBillingAdmin, 'isBillingAdmin'); + Subject get isBillingAdmin => has((x) => x.isBillingAdmin, 'isBillingAdmin'); Subject get isBot => has((x) => x.isBot, 'isBot'); Subject get botType => has((x) => x.botType, 'botType'); Subject get botOwnerId => has((x) => x.botOwnerId, 'botOwnerId'); diff --git a/test/api/model/model_test.dart b/test/api/model/model_test.dart index 95737c173f..d512c60c80 100644 --- a/test/api/model/model_test.dart +++ b/test/api/model/model_test.dart @@ -62,7 +62,6 @@ void main() { test('is_system_bot', () { check(mkUser({}).isSystemBot).isFalse(); - check(mkUser({'is_cross_realm_bot': true}).isSystemBot).isTrue(); check(mkUser({'is_system_bot': true}).isSystemBot).isTrue(); }); }); @@ -286,16 +285,6 @@ void main() { checkEditState(MessageEditState.edited, [{'prev_content': 'old_content'}]); }); - - test("'prev_topic' present without the 'topic' field -> moved", () { - checkEditState(MessageEditState.moved, - [{'prev_topic': 'old_topic'}]); - }); - - test("'prev_subject' present from a pre-5.0 server -> moved", () { - checkEditState(MessageEditState.moved, - [{'prev_subject': 'old_topic'}]); - }); }); group('topic resolved in edit history', () { diff --git a/test/api/model/web_auth_test.dart b/test/api/model/web_auth_test.dart index 01a670103f..7f8c326ed6 100644 --- a/test/api/model/web_auth_test.dart +++ b/test/api/model/web_auth_test.dart @@ -23,19 +23,6 @@ void main() { check(payload.decodeApiKey(otp)).equals(eg.selfAccount.apiKey); }); - // TODO(server-5) remove - test('legacy: no userId', () { - final queryParams = {...wellFormed.queryParameters}..remove('user_id'); - final payload = WebAuthPayload.parse( - wellFormed.replace(queryParameters: queryParams)); - check(payload) - ..otpEncryptedApiKey.equals(encryptedApiKey) - ..email.equals('self@example') - ..userId.isNull() - ..realm.equals(Uri.parse('https://chat.example/')); - check(payload.decodeApiKey(otp)).equals(eg.selfAccount.apiKey); - }); - test('parse fails when an expected field is missing', () { final queryParams = {...wellFormed.queryParameters}..remove('email'); final input = wellFormed.replace(queryParameters: queryParams); @@ -93,6 +80,6 @@ void main() { extension WebAuthPayloadChecks on Subject { Subject get otpEncryptedApiKey => has((x) => x.otpEncryptedApiKey, 'otpEncryptedApiKey'); Subject get email => has((x) => x.email, 'email'); - Subject get userId => has((x) => x.userId, 'userId'); + Subject get userId => has((x) => x.userId, 'userId'); Subject get realm => has((x) => x.realm, 'realm'); } diff --git a/test/api/route/messages_test.dart b/test/api/route/messages_test.dart index a29a87e24b..3c4ce7b028 100644 --- a/test/api/route/messages_test.dart +++ b/test/api/route/messages_test.dart @@ -15,110 +15,6 @@ import '../fake_api.dart'; import 'route_checks.dart'; void main() { - group('getMessageCompat', () { - Future checkGetMessageCompat(FakeApiConnection connection, { - required bool expectLegacy, - required int messageId, - bool? applyMarkdown, - }) async { - final result = await getMessageCompat(connection, - messageId: messageId, - applyMarkdown: applyMarkdown, - ); - if (expectLegacy) { - check(connection.lastRequest).isA() - ..method.equals('GET') - ..url.path.equals('/api/v1/messages') - ..url.queryParameters.deepEquals({ - 'narrow': jsonEncode([ApiNarrowMessageId(messageId)]), - 'anchor': messageId.toString(), - 'num_before': '0', - 'num_after': '0', - if (applyMarkdown != null) 'apply_markdown': applyMarkdown.toString(), - 'client_gravatar': 'true', - }); - } else { - check(connection.lastRequest).isA() - ..method.equals('GET') - ..url.path.equals('/api/v1/messages/$messageId') - ..url.queryParameters.deepEquals({ - if (applyMarkdown != null) 'apply_markdown': applyMarkdown.toString(), - }); - } - return result; - } - - test('modern; message found', () { - return FakeApiConnection.with_((connection) async { - final message = eg.streamMessage(); - final fakeResult = GetMessageResult(message: message); - connection.prepare(json: fakeResult.toJson()); - final result = await checkGetMessageCompat(connection, - expectLegacy: false, - messageId: message.id, - applyMarkdown: true, - ); - check(result).isNotNull().jsonEquals(message); - }); - }); - - test('modern; message not found', () { - return FakeApiConnection.with_((connection) async { - final message = eg.streamMessage(); - connection.prepare( - apiException: eg.apiBadRequest(message: 'Invalid message(s)')); - final result = await checkGetMessageCompat(connection, - expectLegacy: false, - messageId: message.id, - applyMarkdown: true, - ); - check(result).isNull(); - }); - }); - - test('legacy; message found', () { - return FakeApiConnection.with_(zulipFeatureLevel: 119, (connection) async { - final message = eg.streamMessage(); - final fakeResult = GetMessagesResult( - anchor: message.id, - foundNewest: false, - foundOldest: false, - foundAnchor: true, - historyLimited: false, - messages: [message], - ); - connection.prepare(json: fakeResult.toJson()); - final result = await checkGetMessageCompat(connection, - expectLegacy: true, - messageId: message.id, - applyMarkdown: true, - ); - check(result).isNotNull().jsonEquals(message); - }); - }); - - test('legacy; message not found', () { - return FakeApiConnection.with_(zulipFeatureLevel: 119, (connection) async { - final message = eg.streamMessage(); - final fakeResult = GetMessagesResult( - anchor: message.id, - foundNewest: false, - foundOldest: false, - foundAnchor: false, - historyLimited: false, - messages: [], - ); - connection.prepare(json: fakeResult.toJson()); - final result = await checkGetMessageCompat(connection, - expectLegacy: true, - messageId: message.id, - applyMarkdown: true, - ); - check(result).isNull(); - }); - }); - }); - group('getMessage', () { Future checkGetMessage( FakeApiConnection connection, { @@ -158,15 +54,6 @@ void main() { expected: {'apply_markdown': 'false'}); }); }); - - test('Throws assertion error when FL <120', () { - return FakeApiConnection.with_(zulipFeatureLevel: 119, (connection) async { - connection.prepare(json: fakeResult.toJson()); - check(() => getMessage(connection, - messageId: 1, - )).throws(); - }); - }); }); test('ApiNarrow.toJson', () { diff --git a/test/example_data.dart b/test/example_data.dart index 04d6723b7a..a275e47705 100644 --- a/test/example_data.dart +++ b/test/example_data.dart @@ -635,8 +635,8 @@ DeleteMessageEvent deleteMessageEvent(List messages) { UpdateMessageEvent updateMessageEditEvent( Message origMessage, { - int? userId = -1, // null means null; default is [selfUser.userId] - bool? renderingOnly = false, + int? userId, + bool renderingOnly = false, int? messageId, List? flags, int? editTimestamp, @@ -647,7 +647,7 @@ UpdateMessageEvent updateMessageEditEvent( messageId ??= origMessage.id; return UpdateMessageEvent( id: 0, - userId: userId == -1 ? selfUser.userId : userId, + userId: userId ?? selfUser.userId, renderingOnly: renderingOnly, messageId: messageId, messageIds: [messageId], diff --git a/test/model/message_test.dart b/test/model/message_test.dart index 9c9a940d42..3b2f678412 100644 --- a/test/model/message_test.dart +++ b/test/model/message_test.dart @@ -246,16 +246,14 @@ void main() { ..content.not((it) => it.equals(updateEvent.renderedContent!)); }); - // TODO(server-5): Cut legacy case for rendering-only message update - Future checkRenderingOnly({required bool legacy}) async { + test('rendering-only update does not change timestamp', () async { final originalMessage = eg.streamMessage( lastEditTimestamp: 78492, content: "

Hello, world

"); final updateEvent = eg.updateMessageEditEvent(originalMessage, renderedContent: "

Hello, world

Some link preview
", editTimestamp: 99999, - renderingOnly: legacy ? null : true, - userId: null, + renderingOnly: true, ); await prepare(); await prepareMessages([originalMessage]); @@ -270,14 +268,6 @@ void main() { // ... edit timestamp is not. ..lastEditTimestamp.equals(originalMessage.lastEditTimestamp) ..lastEditTimestamp.not((it) => it.equals(updateEvent.editTimestamp)); - } - - test('rendering-only update does not change timestamp', () async { - await checkRenderingOnly(legacy: false); - }); - - test('rendering-only update does not change timestamp (for old server versions)', () async { - await checkRenderingOnly(legacy: true); }); group('Handle message edit state update', () { diff --git a/test/model/store_checks.dart b/test/model/store_checks.dart index 00ada1eea5..1d9d16dca5 100644 --- a/test/model/store_checks.dart +++ b/test/model/store_checks.dart @@ -38,7 +38,7 @@ extension PerAccountStoreChecks on Subject { Subject get accountId => has((x) => x.accountId, 'accountId'); Subject get account => has((x) => x.account, 'account'); Subject get selfUserId => has((x) => x.selfUserId, 'selfUserId'); - Subject get userSettings => has((x) => x.userSettings, 'userSettings'); + Subject get userSettings => has((x) => x.userSettings, 'userSettings'); Subject> get streams => has((x) => x.streams, 'streams'); Subject> get streamsByName => has((x) => x.streamsByName, 'streamsByName'); Subject> get subscriptions => has((x) => x.subscriptions, 'subscriptions'); diff --git a/test/model/store_test.dart b/test/model/store_test.dart index eb6444309d..44519ab1a2 100644 --- a/test/model/store_test.dart +++ b/test/model/store_test.dart @@ -623,14 +623,14 @@ void main() { await preparePoll(); // Pick some arbitrary event and check it gets processed on the store. - check(store.userSettings!.twentyFourHourTime).isFalse(); + check(store.userSettings.twentyFourHourTime).isFalse(); connection.prepare(json: GetEventsResult(events: [ UserSettingsUpdateEvent(id: 2, property: UserSettingName.twentyFourHourTime, value: true), ], queueId: null).toJson()); updateMachine.debugAdvanceLoop(); async.elapse(Duration.zero); - check(store.userSettings!.twentyFourHourTime).isTrue(); + check(store.userSettings.twentyFourHourTime).isTrue(); })); void checkReload(FutureOr Function() prepareError, { @@ -660,14 +660,14 @@ void main() { // The new UpdateMachine updates the new store. updateMachine.debugPauseLoop(); updateMachine.poll(); - check(store.userSettings!.twentyFourHourTime).isFalse(); + check(store.userSettings.twentyFourHourTime).isFalse(); connection.prepare(json: GetEventsResult(events: [ UserSettingsUpdateEvent(id: 2, property: UserSettingName.twentyFourHourTime, value: true), ], queueId: null).toJson()); updateMachine.debugAdvanceLoop(); async.elapse(Duration.zero); - check(store.userSettings!.twentyFourHourTime).isTrue(); + check(store.userSettings.twentyFourHourTime).isTrue(); }); }