Skip to content

Commit 1bb4c50

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 1bb4c50

File tree

2 files changed

+62
-0
lines changed

2 files changed

+62
-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: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,21 @@ 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

17+
import '../api/fake_api.dart';
1418
import '../example_data.dart' as eg;
1519
import '../model/binding.dart';
20+
import '../model/message_list_test.dart';
1621
import '../test_images.dart';
22+
import '../test_navigation.dart';
1723
import 'dialog_checks.dart';
24+
import 'message_list_checks.dart';
25+
import 'page_checks.dart';
1826

1927
void main() {
2028
TestZulipBinding.ensureInitialized();
@@ -158,6 +166,51 @@ void main() {
158166
});
159167
});
160168

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

0 commit comments

Comments
 (0)