Skip to content

internal_link: Recognize channel operator in narrow links #704

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 1 commit into from
Jun 12, 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
2 changes: 2 additions & 0 deletions lib/model/internal_link.dart
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ Narrow? _interpretNarrowSegments(List<String> segments, PerAccountStore store) {
final operand = segments[i + 1];
switch (operator) {
case _NarrowOperator.stream:
case _NarrowOperator.channel:
if (streamElement != null) return null;
final streamId = _parseStreamOperand(operand, store);
if (streamId == null) return null;
Expand Down Expand Up @@ -207,6 +208,7 @@ enum _NarrowOperator {
near,
pmWith,
stream,
channel,
subject,
topic,
unknown;
Expand Down
1 change: 1 addition & 0 deletions lib/model/internal_link.g.dart

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

47 changes: 35 additions & 12 deletions test/model/internal_link_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ import 'package:zulip/model/store.dart';
import '../example_data.dart' as eg;
import 'test_store.dart';

// Using Set instead of List in to avoid any duplicated test urls.
Set<String> getUrlSyntaxVariants(String urlString) {
final urlWithChannelSyntax = urlString.replaceFirst('#narrow/stream', '#narrow/channel');
final urlWithStreamSyntax = urlString.replaceFirst('#narrow/channel', '#narrow/stream');
Comment on lines +14 to +15
Copy link
Collaborator

@chrisbobbe chrisbobbe Jun 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's relax this so it handles the stream/channel operator even if it's at some position other than the first. It's usually the first, but it doesn't have to be. For example, this is a valid narrow link that the web app knows how to handle: https://chat.zulip.org/#narrow/is/unread/stream/14-GSoC

I think this could be done like this:

  • in urlString, skip past the #narrow/ at the beginning
  • split the rest by '/'
  • in that list of segments, look at the even-indexed ones (i.e., index % 2 == 0)
  • for each of those that's equal to "stream", replace it with "channel" and vice versa

(We wouldn't want to just replace all occurrences of "stream" or "channel" in the URL; we want to avoid doing things like changing a topic operand that happens to have one of those strings in it.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's true those are valid links, but it looks like we don't currently have any examples of them in this test file.

(Likely we should! Our implementation looks like it accepts them. But we don't right now. These tests were basically ported from zulip-mobile, because the tests there for this area were quite thorough, but that implementation doesn't accept those links.)

So for this PR, I'm happy to keep it simple in this respect.

Adding that logic would be a good followup, along with adding some test cases where /channel/ comes after /topic/, and where things come in other surprising orders.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, thanks for that suggestion, Greg. Since this was my only comment, I'll mark this for integration review. 🙂

return {urlWithStreamSyntax, urlWithChannelSyntax};
}

Future<PerAccountStore> setupStore({
required Uri realmUrl,
List<ZulipStream>? streams,
Expand Down Expand Up @@ -36,12 +43,14 @@ void main() {
assert(streams != null || users != null);
for (final testCase in testCases) {
final String urlString = testCase.$1;
final Narrow? expected = testCase.$2;
test(urlString, () async {
final store = await setupStore(realmUrl: realmUrl, streams: streams, users: users);
final url = store.tryResolveUrl(urlString)!;
check(parseInternalLink(url, store)).equals(expected);
});
for (final urlString in getUrlSyntaxVariants(urlString)) {
final Narrow? expected = testCase.$2;
test(urlString, () async {
final store = await setupStore(realmUrl: realmUrl, streams: streams, users: users);
final url = store.tryResolveUrl(urlString)!;
check(parseInternalLink(url, store)).equals(expected);
});
}
}
}

Expand Down Expand Up @@ -131,12 +140,14 @@ void main() {
final String description = testCase.$2;
final String urlString = testCase.$3;
final Uri realmUrl = testCase.$4;
test('${expected ? 'accepts': 'rejects'} $description: $urlString', () async {
final store = await setupStore(realmUrl: realmUrl, streams: streams);
final url = store.tryResolveUrl(urlString)!;
final result = parseInternalLink(url, store);
check(result != null).equals(expected);
});
for (final urlString in getUrlSyntaxVariants(urlString)) {
test('${expected ? 'accepts': 'rejects'} $description: $urlString', () async {
final store = await setupStore(realmUrl: realmUrl, streams: streams);
final url = store.tryResolveUrl(urlString)!;
final result = parseInternalLink(url, store);
check(result != null).equals(expected);
});
}
}
});

Expand Down Expand Up @@ -169,6 +180,18 @@ void main() {
testExpectedNarrows(testCases, streams: streams);
});

group('Both `stream` and `channel` can be used interchangeably', () {
const testCases = [
('/#narrow/stream/check', StreamNarrow(1)),
('/#narrow/channel/check', StreamNarrow(1)),
('/#narrow/stream/check/topic/test', TopicNarrow(1, 'test')),
('/#narrow/channel/check/topic/test', TopicNarrow(1, 'test')),
('/#narrow/stream/check/topic/test/near/378333', TopicNarrow(1, 'test')),
('/#narrow/channel/check/topic/test/near/378333', TopicNarrow(1, 'test')),
];
testExpectedNarrows(testCases, streams: streams);
});

group('"/#narrow/dm/<...>" returns expected DmNarrow', () {
final expectedNarrow = DmNarrow.withUsers([1, 2],
selfUserId: eg.selfUser.userId);
Expand Down
Loading