Files
buzz/mobile/test/features/invites/invite_join_sheet_test.dart
Tom Brow 6eff84d127 fix(mobile): join starter channels after accepting invite (#5915)
### What changed?

After a new mobile invite claim succeeds, Buzz now best-effort ensures
membership in the same public starter channels as desktop:

- `#general`
- `#welcome-everyone`

Missing starters use desktop's deterministic per-relay IDs and exact
public channel configuration, so concurrent mobile and desktop setup
converges safely. Setup failures do not invalidate or retry an already
successful invite claim, and failure for one starter does not block the
other.

The success sheet offers **Continue to #welcome-everyone** when that
channel is available. Mobile does not create the private `Welcome`
channel because it cannot provision the desktop Welcome agents that make
that channel useful.

This PR is stacked on #6145 because it deliberately reuses that PR's
open-channel directory and join behavior. Once #6145 merges, this PR can
be retargeted to `main` without changing its BUZZ-12 diff.

Fixes
[BUZZ-12](https://linear.app/squareup/issue/BUZZ-12/bug-community-appears-empty-after-using-invite-link-on-mobile).

### How is it tested?

- Desktop/mobile deterministic starter-ID parity coverage.
- Existing-channel join and missing-channel creation coverage.
- Duplicate-create convergence and per-channel failure isolation
coverage.
- Invite success remains successful when starter setup fails.
- Widget coverage for continuing directly into `#welcome-everyone`.
- Focused invite/deep-link tests: 23 passed.
- `just mobile-check`: passed.
- `just mobile-test`: 1,483 passed.
- Pre-push repository checks: passed.

---------

Signed-off-by: Tom Brow <tomb@block.xyz>
Co-authored-by: Codex <noreply@openai.com>
2026-08-24 12:59:21 -07:00

120 lines
3.3 KiB
Dart

import 'package:buzz/features/invites/invite_join_provider.dart';
import 'package:buzz/features/invites/invite_join_sheet.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import '../../helpers/widget_helpers.dart';
void main() {
testWidgets('recovery error is scrollable and exposes retry setup', (
tester,
) async {
tester.view.physicalSize = const Size(375, 400);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.reset);
await tester.pumpWidget(
WidgetHelpers.testable(
child: const InviteJoinSheet(),
overrides: [
inviteJoinProvider.overrideWith(_RecoveryErrorInviteJoinNotifier.new),
],
),
);
await tester.pump();
expect(find.text('Finish setting up'), findsOneWidget);
expect(find.widgetWithText(FilledButton, 'Retry setup'), findsOneWidget);
expect(find.byType(SingleChildScrollView), findsOneWidget);
expect(tester.takeException(), isNull);
});
for (final fixture in [
(
name: 'membership claim',
state: const InviteJoinState(
status: InviteJoinStatus.claiming,
host: 'relay.example.com',
),
label: 'Joining…',
),
(
name: 'starter recovery',
state: const InviteJoinState(
status: InviteJoinStatus.claiming,
host: 'relay.example.com',
isStarterSetupRecovery: true,
),
label: 'Finishing setup…',
),
]) {
testWidgets('${fixture.name} cannot dismiss the in-flight invite sheet', (
tester,
) async {
await tester.pumpWidget(
WidgetHelpers.testable(
child: const _InviteJoinSheetLauncher(),
overrides: [
inviteJoinProvider.overrideWith(
() => _StaticInviteJoinNotifier(fixture.state),
),
],
),
);
await tester.tap(find.text('Open invite'));
await tester.pump();
await tester.pump(const Duration(milliseconds: 300));
expect(find.text(fixture.label), findsOneWidget);
expect(
find.byKey(const ValueKey('buzz-sheet-drag-handle')),
findsNothing,
);
expect(find.byTooltip('Close sheet'), findsNothing);
await tester.tapAt(const Offset(8, 8));
await tester.pump();
expect(find.text(fixture.label), findsOneWidget);
await tester.binding.handlePopRoute();
await tester.pump();
expect(find.text(fixture.label), findsOneWidget);
});
}
}
class _InviteJoinSheetLauncher extends StatelessWidget {
const _InviteJoinSheetLauncher();
@override
Widget build(BuildContext context) => Scaffold(
body: Center(
child: FilledButton(
onPressed: () => showInviteJoinSheet(context),
child: const Text('Open invite'),
),
),
);
}
class _StaticInviteJoinNotifier extends InviteJoinNotifier {
_StaticInviteJoinNotifier(this._state);
final InviteJoinState _state;
@override
InviteJoinState build() => _state;
}
class _RecoveryErrorInviteJoinNotifier extends InviteJoinNotifier {
@override
InviteJoinState build() => const InviteJoinState(
status: InviteJoinStatus.error,
host: 'relay.example.com',
communityName: 'Example',
errorMessage:
'Starter setup could not reach the relay. Retry when the connection is available.',
isStarterSetupRecovery: true,
);
}