Skip to content

Internal_link: Add ApiIsNarrow. #869

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 38 additions & 9 deletions lib/api/model/narrow.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import 'package:json_annotation/json_annotation.dart';

part 'narrow.g.dart';

typedef ApiNarrow = List<ApiNarrowElement>;

/// Resolve any [ApiNarrowDm] elements appropriately.
Expand Down Expand Up @@ -114,19 +118,44 @@ class ApiNarrowPmWith extends ApiNarrowDm {
ApiNarrowPmWith._(super.operand, {super.negated});
}

// TODO: generalize into ApiNarrowIs
class ApiNarrowIsMentioned extends ApiNarrowElement {
class ApiNarrowIs extends ApiNarrowElement {
@override String get operator => 'is';
@override String get operand => 'mentioned';

ApiNarrowIsMentioned({super.negated});
}
@override final IsOperand operand;

class ApiNarrowIsUnread extends ApiNarrowElement {
@override String get operator => 'is';
@override String get operand => 'unread';
ApiNarrowIs(this.operand, {super.negated});

ApiNarrowIsUnread({super.negated});
factory ApiNarrowIs.fromJson(Map<String, dynamic> json) => ApiNarrowIs(
IsOperand.fromRawString(json['operand'] as String),
negated: json['negated'] as bool? ?? false,
);
}

/// An operand value of "is" operator.
///
/// See also:
/// - https://zulip.com/api/construct-narrow
/// - https://zulip.com/help/search-for-messages#search-your-important-messages
/// - https://zulip.com/help/search-for-messages#search-by-message-status
@JsonEnum(alwaysCreate: true)
enum IsOperand {
dm, // TODO(server-7) new in FL 177
private, // TODO(server-7) deprecated in FL 177, equivalent to [dm].
alerted,
mentioned,
starred,
followed, // TODO(server-9) new in FL 265
resolved,
unread,
unknown;

static IsOperand fromRawString(String raw) => $enumDecode(
_$IsOperandEnumMap, raw, unknownValue: unknown);

@override
String toString() => _$IsOperandEnumMap[this]!;

String toJson() => toString();
}

class ApiNarrowMessageId extends ApiNarrowElement {
Expand Down
21 changes: 21 additions & 0 deletions lib/api/model/narrow.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 19 additions & 8 deletions lib/model/internal_link.dart
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,7 @@ Uri narrowLink(PerAccountStore store, Narrow narrow, {int? nearMessageId}) {
fragment.write('${element.operand.join(',')}-$suffix');
case ApiNarrowDm():
assert(false, 'ApiNarrowDm should have been resolved');
case ApiNarrowIsMentioned():
fragment.write(element.operand.toString());
case ApiNarrowIsUnread():
case ApiNarrowIs():
fragment.write(element.operand.toString());
case ApiNarrowMessageId():
fragment.write(element.operand.toString());
Expand Down Expand Up @@ -154,7 +152,7 @@ Narrow? _interpretNarrowSegments(List<String> segments, PerAccountStore store) {
ApiNarrowStream? streamElement;
ApiNarrowTopic? topicElement;
ApiNarrowDm? dmElement;
ApiNarrowIsMentioned? isMentionedElement;
Set<IsOperand> isElementOperands = {};

for (var i = 0; i < segments.length; i += 2) {
final (operator, negated) = _parseOperator(segments[i]);
Expand Down Expand Up @@ -183,8 +181,8 @@ Narrow? _interpretNarrowSegments(List<String> segments, PerAccountStore store) {
dmElement = ApiNarrowDm(dmIds, negated: negated);

case _NarrowOperator.is_:
if (isMentionedElement != null) return null;
if (operand == 'mentioned') isMentionedElement = ApiNarrowIsMentioned();
// It is fine to have duplicates of the same [IsOperand].
isElementOperands.add(IsOperand.fromRawString(operand));

case _NarrowOperator.near: // TODO(#82): support for near
case _NarrowOperator.with_: // TODO(#683): support for with
Expand All @@ -195,9 +193,22 @@ Narrow? _interpretNarrowSegments(List<String> segments, PerAccountStore store) {
}
}

if (isMentionedElement != null) {
if (isElementOperands.isNotEmpty) {
if (streamElement != null || topicElement != null || dmElement != null) return null;
return const MentionsNarrow();
if (isElementOperands.length > 1) return null;
switch (isElementOperands.single) {
case IsOperand.mentioned:
return const MentionsNarrow();
case IsOperand.dm:
case IsOperand.private:
case IsOperand.alerted:
case IsOperand.starred:
case IsOperand.followed:
case IsOperand.resolved:
case IsOperand.unread:
case IsOperand.unknown:
return null;
}
} else if (dmElement != null) {
if (streamElement != null || topicElement != null) return null;
return DmNarrow.withUsers(dmElement.operand, selfUserId: store.selfUserId);
Expand Down
2 changes: 1 addition & 1 deletion lib/model/narrow.dart
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ class MentionsNarrow extends Narrow {
}

@override
ApiNarrow apiEncode() => [ApiNarrowIsMentioned()];
ApiNarrow apiEncode() => [ApiNarrowIs(IsOperand.mentioned)];

@override
bool operator ==(Object other) {
Expand Down
2 changes: 1 addition & 1 deletion lib/widgets/actions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Future<void> markNarrowAsRead(BuildContext context, Narrow narrow) async {
// The server applies the same optimization within the (deprecated)
// specialized endpoints for marking messages as read; see
// `do_mark_stream_messages_as_read` in `zulip:zerver/actions/message_flags.py`.
apiNarrow: narrow.apiEncode()..add(ApiNarrowIsUnread()),
apiNarrow: narrow.apiEncode()..add(ApiNarrowIs(IsOperand.unread)),
// Use [AnchorCode.oldest], because [AnchorCode.firstUnread]
// will be the oldest non-muted unread message, which would
// result in muted unreads older than the first unread not
Expand Down
46 changes: 34 additions & 12 deletions test/model/internal_link_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import 'package:checks/checks.dart';
import 'package:test/scaffolding.dart';
import 'package:zulip/api/model/model.dart';
import 'package:zulip/api/model/narrow.dart';
import 'package:zulip/model/internal_link.dart';
import 'package:zulip/model/narrow.dart';
import 'package:zulip/model/store.dart';
Expand Down Expand Up @@ -221,18 +222,39 @@ void main() {
testExpectedNarrows(testCases, streams: streams);
});

group('"/#narrow/is/mentioned returns expected MentionsNarrow', () {
final testCases = [
('/#narrow/is/mentioned', const MentionsNarrow()),
('/#narrow/is/mentioned/near/1', const MentionsNarrow()),
('/#narrow/is/mentioned/with/2', const MentionsNarrow()),
('/#narrow/channel/7-test-here/is/mentioned', null),
('/#narrow/channel/check/topic/test/is/mentioned', null),
('/#narrow/topic/test/is/mentioned', null),
('/#narrow/dm/17327-Chris-Bobbe-(Test-Account)/is/mentioned', null),
('/#narrow/-is/mentioned', null),
];
testExpectedNarrows(testCases, streams: streams);
group('/#narrow/is/<...> returns corresponding narrow', () {
// For these tests, we are more interested in the internal links
// containing a single effective `is` operator.
// Internal links with multiple operators should be tested separately.
for (final operand in IsOperand.values) {
List<(String, Narrow?)> sharedCases(Narrow? narrow) => [
('/#narrow/is/$operand', narrow),
('/#narrow/is/$operand/is/$operand', narrow),
('/#narrow/is/$operand/near/1', narrow),
('/#narrow/is/$operand/with/2', narrow),
('/#narrow/channel/7-test-here/is/$operand', null),
('/#narrow/channel/check/topic/test/is/$operand', null),
('/#narrow/topic/test/is/$operand', null),
('/#narrow/dm/17327-Chris-Bobbe-(Test-Account)/is/$operand', null),
('/#narrow/-is/$operand', null),
];
final List<(String, Narrow?)> testCases;
switch (operand) {
case IsOperand.mentioned:
testCases = sharedCases(const MentionsNarrow());
case IsOperand.dm:
case IsOperand.private:
case IsOperand.alerted:
case IsOperand.starred:
case IsOperand.followed:
case IsOperand.resolved:
case IsOperand.unread:
case IsOperand.unknown:
// Unsupported operands should not return any narrow.
testCases = sharedCases(null);
}
testExpectedNarrows(testCases, streams: streams);
}
});

group('unexpected link shapes are rejected', () {
Expand Down
4 changes: 2 additions & 2 deletions test/widgets/actions_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ void main() {
foundOldest: true, foundNewest: true).toJson());
markNarrowAsRead(context, narrow);
await tester.pump(Duration.zero);
final apiNarrow = narrow.apiEncode()..add(ApiNarrowIsUnread());
final apiNarrow = narrow.apiEncode()..add(ApiNarrowIs(IsOperand.unread));
check(connection.lastRequest).isA<http.Request>()
..method.equals('POST')
..url.path.equals('/api/v1/messages/flags/narrow')
Expand Down Expand Up @@ -208,7 +208,7 @@ void main() {
const progressMessage = 'progressMessage';
const onFailedTitle = 'onFailedTitle';
final narrow = TopicNarrow.ofMessage(eg.streamMessage());
final apiNarrow = narrow.apiEncode()..add(ApiNarrowIsUnread());
final apiNarrow = narrow.apiEncode()..add(ApiNarrowIs(IsOperand.unread));

Future<bool> invokeUpdateMessageFlagsStartingFromAnchor() =>
updateMessageFlagsStartingFromAnchor(
Expand Down
2 changes: 1 addition & 1 deletion test/widgets/message_list_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,7 @@ void main() {
firstProcessedId: null, lastProcessedId: null,
foundOldest: true, foundNewest: true).toJson());
await tester.tap(find.byType(MarkAsReadWidget));
final apiNarrow = narrow.apiEncode()..add(ApiNarrowIsUnread());
final apiNarrow = narrow.apiEncode()..add(ApiNarrowIs(IsOperand.unread));
check(connection.lastRequest).isA<http.Request>()
..method.equals('POST')
..url.path.equals('/api/v1/messages/flags/narrow')
Expand Down