From 46cdc52d234c92191a6b79de9cc20db4f14d74b3 Mon Sep 17 00:00:00 2001 From: Matt Toohey Date: Fri, 6 Nov 2026 09:49:51 +1100 Subject: [PATCH] test(mobile): lock in invite-page and bare-host community link entry (#9047) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both mobile community-link fields (Add community and the welcome/onboarding "Enter a community link" page) already accept a typed or pasted `https:///invite/` link and keep the code. They also read a bare host as `wss://`. This PR adds regression coverage for both. **It doesn't change any production code.** Companion: block/buzz-app#2412 fixes the same two inputs in buzz-app's Join a community. ## What's covered - **`community_link_input_test.dart`** - The address loop is now an input → relay table, with two new rows: `secondary.example` and `relay.example.com:8443` (the port is kept). - The invite case now uses `https://primary.example/invite/fixture`, and expects relay `wss://primary.example` with code `fixture`. - Two new rejected inputs: `ws://secondary.example`, and the invite path with a trailing slash, which the relay doesn't serve. - **`onboarding_entry_routes_test.dart`**: before the bare-host step, the journey pastes the invite link into the Add route's field. It expects the app-level `InviteJoinSheet` to show host `primary.example` and code `fixture`. ## Shown to catch a regression I removed the invite/add-community short-circuit in `parseCommunityLinkInput`. Both the invite unit case and the new journey step then failed. ## Not in this PR Two parser differences are left for a separate decision, along with the tests that depend on them: - `buzz://connect?relay=…`: mobile rejects it in the fields and silently drops it as an OS link, but buzz-app, desktop and the web repo browser's Connect button all use it. - An invite-page link with a query string: mobile accepts it and throws the query away, but buzz-app rejects it. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Signed-off-by: Matt Toohey Co-authored-by: Claude Opus 5.5 --- .../onboarding/community_link_input_test.dart | 20 +++++++++++------- .../onboarding_entry_routes_test.dart | 21 +++++++++++++++++++ 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/mobile/test/features/onboarding/community_link_input_test.dart b/mobile/test/features/onboarding/community_link_input_test.dart index 12d8c1f2df..ad783d9c65 100644 --- a/mobile/test/features/onboarding/community_link_input_test.dart +++ b/mobile/test/features/onboarding/community_link_input_test.dart @@ -4,24 +4,26 @@ import 'package:flutter_test/flutter_test.dart'; void main() { test('normalizes addresses through the existing community parser', () { - for (final value in [ - ' team.communities.buzz.xyz ', - 'https://team.communities.buzz.xyz/', - 'wss://team.communities.buzz.xyz', + for (final (value, relay) in [ + (' team.communities.buzz.xyz ', 'wss://team.communities.buzz.xyz'), + ('https://team.communities.buzz.xyz/', 'wss://team.communities.buzz.xyz'), + ('wss://team.communities.buzz.xyz', 'wss://team.communities.buzz.xyz'), + ('secondary.example', 'wss://secondary.example'), + ('relay.example.com:8443', 'wss://relay.example.com:8443'), ]) { final uri = parseCommunityLinkInput(value)!; final link = parseBuzzDeepLink(uri) as AddCommunityDeepLink; - expect(link.relayUrl, 'wss://team.communities.buzz.xyz'); + expect(link.relayUrl, relay, reason: value); } }); test('preserves the invite and its destination', () { final uri = parseCommunityLinkInput( - 'https://community.example.com/invite/abc123', + 'https://primary.example/invite/fixture', )!; final link = parseBuzzDeepLink(uri) as InviteDeepLink; - expect(link.code, 'abc123'); - expect(link.relayUrl, 'wss://community.example.com'); + expect(link.code, 'fixture'); + expect(link.relayUrl, 'wss://primary.example'); }); test('rejects unrelated actions and unsafe or incomplete addresses', () { @@ -29,9 +31,11 @@ void main() { '', 'not a link', 'http://community.example.com', + 'ws://secondary.example', 'https://user:secret@community.example.com', 'https://community.example.com/other', 'https://community.example.com/invite/', + 'https://primary.example/invite/fixture/', 'https://community.example.com#secret', 'https://community.example.com?token=secret', 'https://127.0.0.1', diff --git a/mobile/test/features/onboarding/onboarding_entry_routes_test.dart b/mobile/test/features/onboarding/onboarding_entry_routes_test.dart index 93bb638ce2..38053c5724 100644 --- a/mobile/test/features/onboarding/onboarding_entry_routes_test.dart +++ b/mobile/test/features/onboarding/onboarding_entry_routes_test.dart @@ -5,6 +5,8 @@ import 'dart:convert'; import 'package:buzz/features/channels/channel.dart'; import 'package:buzz/features/channels/channels_provider.dart'; +import 'package:buzz/features/invites/invite_join_provider.dart'; +import 'package:buzz/features/invites/invite_join_sheet.dart'; import 'package:buzz/features/onboarding/hosted_connect_page.dart'; import 'package:buzz/features/onboarding/self_hosted_entry_sheet.dart'; import 'package:buzz/hosted_app_gate.dart'; @@ -243,6 +245,25 @@ void main() { find.text('Enter a valid community address or invite link.'), findsOneWidget, ); + // A pasted invite-page link keeps its code through to the join sheet. + await tester.enterText( + find.byKey(const Key('community-link-input')).hitTestable(), + 'https://primary.example/invite/fixture', + ); + await tester.tap(find.text('Continue').hitTestable()); + await tester.pumpAndSettle(); + expect(find.byType(InviteJoinSheet), findsOneWidget); + expect( + find.descendant( + of: find.byType(InviteJoinSheet), + matching: find.text('primary.example'), + ), + findsOneWidget, + ); + expect(container.read(inviteJoinProvider).invite?.code, 'fixture'); + await tester.tap(find.text('Cancel').hitTestable()); + await tester.pumpAndSettle(); + expect(find.byType(InviteJoinSheet), findsNothing); // Drive the real deep-link dispatcher, sheet, service and auth persistence. await tester.enterText( find.byKey(const Key('community-link-input')).hitTestable(),