Skip to content

Commit 68e8325

Browse files
committed
internal_links: Add variable realmUrlWithSlash to add '/'
Updates the URL construction to include a trailing slash before the fragment identifier. This ensures that the URL is properly formatted and makes the url linkified. Fixes: #845
1 parent 6979b50 commit 68e8325

File tree

2 files changed

+99
-5
lines changed

2 files changed

+99
-5
lines changed

lib/model/internal_link.dart

+5-1
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,11 @@ Uri narrowLink(PerAccountStore store, Narrow narrow, {int? nearMessageId}) {
9696
fragment.write('/near/$nearMessageId');
9797
}
9898

99-
return store.realmUrl.replace(fragment: fragment.toString());
99+
final realmUrlWithSlash = store.realmUrl.path.endsWith('/')
100+
? store.realmUrl
101+
: store.realmUrl.replace(path: '${store.realmUrl.path}/');
102+
103+
return realmUrlWithSlash.replace(fragment: fragment.toString());
100104
}
101105

102106
/// A [Narrow] from a given URL, on `store`'s realm.

test/widgets/action_sheet_test.dart

+94-4
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,14 @@ late FakeApiConnection connection;
3838
Future<void> setupToMessageActionSheet(WidgetTester tester, {
3939
required Message message,
4040
required Narrow narrow,
41+
Account? account,
4142
}) async {
4243
addTearDown(testBinding.reset);
4344

44-
await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot());
45-
final store = await testBinding.globalStore.perAccount(eg.selfAccount.id);
45+
account ??= eg.selfAccount;
46+
final initialSnapshot = eg.initialSnapshot();
47+
await testBinding.globalStore.add(account, initialSnapshot);
48+
final store = await testBinding.globalStore.perAccount(account.id);
4649
await store.addUser(eg.user(userId: message.senderId));
4750
if (message is StreamMessage) {
4851
final stream = eg.stream(streamId: message.streamId);
@@ -61,7 +64,7 @@ Future<void> setupToMessageActionSheet(WidgetTester tester, {
6164
messages: [message],
6265
).toJson());
6366

64-
await tester.pumpWidget(TestZulipApp(accountId: eg.selfAccount.id,
67+
await tester.pumpWidget(TestZulipApp(accountId: account.id,
6568
child: MessageListPage(initNarrow: narrow)));
6669

6770
// global store, per-account store, and message list get loaded
@@ -551,6 +554,93 @@ void main() {
551554
final expectedLink = narrowLink(store, narrow, nearMessageId: message.id).toString();
552555
check(await Clipboard.getData('text/plain')).isNotNull().text.equals(expectedLink);
553556
});
557+
558+
testWidgets('Full Url Testing', (tester) async {
559+
final message = eg.streamMessage();
560+
final narrow = TopicNarrow.ofMessage(message);
561+
562+
final customAccount = eg.selfAccount.copyWith(
563+
id: 2, realmUrl: Uri.parse('https://chat.example'));
564+
await setupToMessageActionSheet(
565+
tester, message: message, narrow: narrow, account: customAccount);
566+
final customAccountStore = await testBinding.globalStore.perAccount(customAccount.id);
567+
568+
await tapCopyMessageLinkButton(tester);
569+
await tester.pump(Duration.zero);
570+
final expectedLink = narrowLink(
571+
customAccountStore, narrow, nearMessageId: message.id).toString();
572+
check(await Clipboard.getData('text/plain')).isNotNull().text.equals(expectedLink);
573+
});
574+
});
575+
576+
group('Message link verbatim URL tests', () {
577+
578+
Future<void> tapCopyMessageLinkButton(WidgetTester tester) async {
579+
await tester.ensureVisible(find.byIcon(Icons.link, skipOffstage: false));
580+
await tester.tap(find.byIcon(Icons.link));
581+
await tester.pump(); // [MenuItemButton.onPressed] called in a post-frame callback: flutter/flutter@e4a39fa2e
582+
}
583+
584+
testWidgets('Copies message link with verbatim, full URL output', (tester) async {
585+
final stream = eg.stream(streamId: 123, name: 'stream 123');
586+
final message = eg.streamMessage(
587+
id: 13579, stream: stream, topic: 'example topic 341');
588+
final narrow = TopicNarrow.ofMessage(message);
589+
final account = eg.selfAccount.copyWith(
590+
realmUrl: Uri.parse('https://chat.zulip.org'));
591+
await setupToMessageActionSheet(
592+
tester, message: message, narrow: narrow, account: account);
593+
594+
final store = await testBinding.globalStore.perAccount(account.id);
595+
if (!store.streams.containsKey(stream.streamId)) {
596+
await store.addStream(stream);
597+
}
598+
599+
await tapCopyMessageLinkButton(tester);
600+
await tester.pump(Duration.zero);
601+
const expectedLink = 'https://chat.zulip.org/#narrow/stream/123-stream-123/topic/example.20topic.20341/near/13579';
602+
check(await Clipboard.getData('text/plain')).isNotNull().text.equals(expectedLink);
603+
});
604+
605+
testWidgets('Copies link with special characters in stream name and topic', (tester) async {
606+
final stream = eg.stream(streamId: 42, name: 'weird & wonderful');
607+
final message = eg.streamMessage(
608+
id: 98765, stream: stream, topic: 'Whats up? 123!');
609+
final narrow = TopicNarrow.ofMessage(message);
610+
final account = eg.selfAccount.copyWith(
611+
realmUrl: Uri.parse('https://chat.zulip.org'));
612+
await setupToMessageActionSheet(tester, message: message, narrow: narrow, account: account);
613+
614+
final store = await testBinding.globalStore.perAccount(account.id);
615+
if (!store.streams.containsKey(stream.streamId)) {
616+
await store.addStream(stream);
617+
}
618+
619+
await tapCopyMessageLinkButton(tester);
620+
await tester.pump(Duration.zero);
621+
const expectedLink = 'https://chat.zulip.org/#narrow/stream/42-stream-42/topic/Whats.20up.3F.20123!/near/98765';
622+
check(await Clipboard.getData('text/plain')).isNotNull().text.equals(expectedLink);
623+
});
624+
625+
testWidgets('Copies link with numbers only in stream name', (tester) async {
626+
final stream = eg.stream(streamId: 999, name: '12345');
627+
final message = eg.streamMessage(
628+
id: 45678, stream: stream, topic: 'Numbers & Symbols: 100%');
629+
final narrow = TopicNarrow.ofMessage(message);
630+
final account = eg.selfAccount.copyWith(
631+
realmUrl: Uri.parse('https://chat.zulip.org'));
632+
await setupToMessageActionSheet(tester, message: message, narrow: narrow, account: account);
633+
634+
final store = await testBinding.globalStore.perAccount(account.id);
635+
if (!store.streams.containsKey(stream.streamId)) {
636+
await store.addStream(stream);
637+
}
638+
639+
await tapCopyMessageLinkButton(tester);
640+
await tester.pump(Duration.zero);
641+
const expectedLink = 'https://chat.zulip.org/#narrow/stream/999-stream-999/topic/Numbers.20.26.20Symbols.3A.20100.25/near/45678';
642+
check(await Clipboard.getData('text/plain')).isNotNull().text.equals(expectedLink);
643+
});
554644
});
555645

556646
group('ShareButton', () {
@@ -616,4 +706,4 @@ void main() {
616706
check(mockSharePlus.sharedString).isNull();
617707
});
618708
});
619-
}
709+
}

0 commit comments

Comments
 (0)