test(mobile): lock in invite-page and bare-host community link entry (#9047)

Both mobile community-link fields (Add community and the
welcome/onboarding "Enter a community link" page) already accept a typed
or pasted `https://<host>/invite/<code>` 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 <contact@matttoohey.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Matt Toohey
2026-11-06 09:49:51 +11:00
committed by GitHub
parent e94b547f60
commit 46cdc52d23
2 changed files with 33 additions and 8 deletions

View File

@@ -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',

View File

@@ -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(),