Skip to content

Commit 876e60f

Browse files
committed
content: Handle internal links
Integrates internal_links into link nodes so that urls that resolve to internal Narrows navigate to that instead of launching in an external browser. Fixes: #73
1 parent e3a2dcc commit 876e60f

File tree

2 files changed

+60
-0
lines changed

2 files changed

+60
-0
lines changed

lib/widgets/content.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import '../model/store.dart';
1212
import 'code_block.dart';
1313
import 'dialog.dart';
1414
import 'lightbox.dart';
15+
import 'message_list.dart';
1516
import 'store.dart';
1617
import 'text.dart';
1718

@@ -668,6 +669,14 @@ void _launchUrl(BuildContext context, String urlString) async {
668669
return;
669670
}
670671

672+
final internalNarrow = parseInternalLink(url, store);
673+
if (internalNarrow != null) {
674+
Navigator.push(context,
675+
MessageListPage.buildRoute(context: context,
676+
narrow: internalNarrow));
677+
return;
678+
}
679+
671680
bool launched = false;
672681
String? errorMessage;
673682
try {

test/widgets/content_test.dart

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,19 @@ import 'package:flutter_test/flutter_test.dart';
88
import 'package:url_launcher/url_launcher.dart';
99
import 'package:zulip/api/core.dart';
1010
import 'package:zulip/model/content.dart';
11+
import 'package:zulip/model/narrow.dart';
1112
import 'package:zulip/widgets/content.dart';
13+
import 'package:zulip/widgets/message_list.dart';
14+
import 'package:zulip/widgets/page.dart';
1215
import 'package:zulip/widgets/store.dart';
1316

1417
import '../example_data.dart' as eg;
1518
import '../model/binding.dart';
1619
import '../test_images.dart';
20+
import '../test_navigation.dart';
1721
import 'dialog_checks.dart';
22+
import 'message_list_checks.dart';
23+
import 'page_checks.dart';
1824

1925
void main() {
2026
TestZulipBinding.ensureInitialized();
@@ -158,6 +164,51 @@ void main() {
158164
});
159165
});
160166

167+
group('LinkNode on internal links', () {
168+
Future<List<Route<dynamic>>> prepareContent(WidgetTester tester, {
169+
required String html,
170+
}) async {
171+
await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot(
172+
streams: [eg.stream(streamId: 1, name: 'check')],
173+
));
174+
addTearDown(testBinding.reset);
175+
final pushedRoutes = <Route<dynamic>>[];
176+
final testNavObserver = TestNavigatorObserver()
177+
..onPushed = (route, prevRoute) => pushedRoutes.add(route);
178+
await tester.pumpWidget(GlobalStoreWidget(child: MaterialApp(
179+
navigatorObservers: [testNavObserver],
180+
home: PerAccountStoreWidget(accountId: eg.selfAccount.id,
181+
child: BlockContentList(nodes: parseContent(html).nodes)))));
182+
await tester.pump();
183+
await tester.pump();
184+
assert(pushedRoutes.length == 1);
185+
pushedRoutes.removeLast();
186+
return pushedRoutes;
187+
}
188+
189+
testWidgets('valid internal links are resolved', (tester) async {
190+
final pushedRoutes = await prepareContent(tester,
191+
html: '<p><a href="/#narrow/stream/1-check">stream</a></p>');
192+
193+
await tester.tap(find.text('stream'));
194+
check(testBinding.takeLaunchUrlCalls()).isEmpty();
195+
check(pushedRoutes).single.isA<WidgetRoute>()
196+
.page.isA<MessageListPage>()
197+
.narrow.equals(const StreamNarrow(1));
198+
});
199+
200+
testWidgets('invalid internal links are not followed', (tester) async {
201+
final pushedRoutes = await prepareContent(tester,
202+
html: '<p><a href="/#narrow/stream/1-check/topic">invalid</a></p>');
203+
204+
await tester.tap(find.text('invalid'));
205+
final expectedUrl = Uri.parse('${eg.realmUrl}#narrow/stream/1-check/topic');
206+
check(testBinding.takeLaunchUrlCalls())
207+
.single.equals((url: expectedUrl, mode: LaunchMode.externalApplication));
208+
check(pushedRoutes).isEmpty();
209+
});
210+
});
211+
161212
group('UnicodeEmoji', () {
162213
Future<void> prepareContent(WidgetTester tester, String html) async {
163214
await tester.pumpWidget(MaterialApp(home: BlockContentList(nodes: parseContent(html).nodes)));

0 commit comments

Comments
 (0)