mirror of
https://github.com/block/buzz.git
synced 2026-11-02 23:41:08 +00:00
## Summary Use consistent fullscreen screens for identity setup, community selection, joining, and Add Community. Restore all memberships and open the selected community; share the community grid with the switcher. Add QR-first choices, animated link entry with 12px keyboard clearance, a two-finger double-tap pairing toggle, and the centered reversing bee loader. Preserve cancellation, Back, and reduced-motion behavior. Add intentionally omits community creation and Builderlab sign-in: this route follows sign-in and the existing-community picker. ### Related issue Related: #8628 (native profile setup). No duplicate fullscreen onboarding PR found. ### Testing - Full standard and hosted-authentication mobile suites, static analysis, and required push checks passed. - iPhone preview iterated with Kenny; final simulator walkthrough below uses synthetic data and does not authenticate or save account changes. - Independent review completed; regressions cover cancellation during loading, Add recovery after failed/conflicting memberships, retrying skipped communities, and explicit picker stages. Walkthrough recorded before the copy and recovery followups: https://github.com/user-attachments/assets/bfea3ad3-1dde-40c4-8748-44c84226c292 --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Co-authored-by: Terp <5856f6900e8e0a4e0ea1f4f8d2ee33e4753d2119bc122f94ecafc660b46c0511@buzz.block.builderlab.xyz>
129 lines
4.3 KiB
Dart
129 lines
4.3 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:buzz/shared/hosted/builderlab_account.dart';
|
|
import 'package:buzz/shared/hosted/browser_auth.dart';
|
|
import 'package:buzz/shared/hosted/account_identity_store.dart';
|
|
import 'package:buzz/shared/hosted/hosted_flow.dart';
|
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
import '../../helpers/hosted_communities_fixture.dart';
|
|
|
|
void main() {
|
|
TestWidgetsFlutterBinding.ensureInitialized();
|
|
setUp(() => FlutterSecureStorage.setMockInitialValues({}));
|
|
|
|
Future<HostedCommunitiesFixture> fixture({bool backup = true}) async {
|
|
final result = HostedCommunitiesFixture();
|
|
await result.initialize(backup: backup);
|
|
addTearDown(result.dispose);
|
|
return result;
|
|
}
|
|
|
|
for (final failure in ['binding', 'backup']) {
|
|
test(
|
|
'$failure proof failure blocks discovery and preserves saved identity',
|
|
() async {
|
|
final f = await fixture(backup: failure != 'backup');
|
|
if (failure == 'binding') f.bound = '2' * 64;
|
|
final before = await const FlutterSecureStorage().readAll();
|
|
await f.flow.signInAccount();
|
|
expect(f.flow.stage, HostedFlowStage.blocked);
|
|
if (failure == 'binding') {
|
|
expect(f.flow.problem, HostedFlowProblem.identityMismatch);
|
|
}
|
|
expect(
|
|
f.requests.any((path) => path.endsWith('/known-communities/list')),
|
|
isFalse,
|
|
);
|
|
expect(await const FlutterSecureStorage().readAll(), before);
|
|
expect(f.admitted, isEmpty);
|
|
},
|
|
);
|
|
}
|
|
|
|
test(
|
|
'selecting a skipped relay retries it as the landing community',
|
|
() async {
|
|
final f = await fixture();
|
|
f.rows = [f.row('one'), f.row('two'), f.row('three')];
|
|
f.beforeAdmission = (name) async {
|
|
if (name != 'one.communities.buzz.xyz') {
|
|
throw AccountFailure.unavailable;
|
|
}
|
|
};
|
|
await f.flow.signInAccount();
|
|
await f.flow.addSelectedCommunities();
|
|
await f.flow.skipFailedCommunity();
|
|
expect(f.flow.stage, HostedFlowStage.blocked);
|
|
f.flow.backToCommunityChoices();
|
|
f.flow.chooseLandingCommunity('wss://two.communities.buzz.xyz');
|
|
f.beforeAdmission = null;
|
|
await f.flow.addSelectedCommunities();
|
|
expect(f.admitted, [
|
|
'one.communities.buzz.xyz',
|
|
'two.communities.buzz.xyz',
|
|
'three.communities.buzz.xyz',
|
|
]);
|
|
expect(f.openedCommunity, 'wss://two.communities.buzz.xyz');
|
|
},
|
|
);
|
|
|
|
test('malformed list targets cannot authorize admission', () async {
|
|
final f = await fixture();
|
|
f.rows = [
|
|
{...f.row('one'), 'community_url': 42},
|
|
];
|
|
await f.flow.signInAccount();
|
|
expect(f.flow.problem, HostedFlowProblem.invalidResponse);
|
|
expect(f.admitted, isEmpty);
|
|
});
|
|
test('expired A batch is discarded after browser login as B', () async {
|
|
final f = await fixture();
|
|
await f.flow.initialize();
|
|
f.rows = [f.row('a-one'), f.row('a-two')];
|
|
f.beforeAdmission = (name) async {
|
|
if (name == 'a-two.communities.buzz.xyz') {
|
|
throw AccountFailure.unavailable;
|
|
}
|
|
};
|
|
await f.flow.signInAccount();
|
|
expect(f.flow.stage, HostedFlowStage.chooseCommunities);
|
|
expect(f.admitted, isEmpty);
|
|
await f.flow.addSelectedCommunities();
|
|
expect(f.admitted, ['a-one.communities.buzz.xyz']);
|
|
f.expired = true;
|
|
await f.flow.retry();
|
|
expect(f.flow.stage, HostedFlowStage.signIn);
|
|
expect(f.browser.startedIntent, {'type': 'account'});
|
|
f.expired = false;
|
|
f.subject = 'other-owner';
|
|
f.rows = [f.row('b-one')];
|
|
await f.store.create(f.keys.nsec);
|
|
await f.store.confirmBackup(
|
|
BackupReceipt(
|
|
backend: BuilderlabAccountClient.origin.toString(),
|
|
subject: f.subject,
|
|
pubkey: f.keys.public,
|
|
backupId: 'other-backup',
|
|
),
|
|
);
|
|
final completed = Completer<void>();
|
|
f.flow.addListener(() {
|
|
if (f.flow.stage == HostedFlowStage.chooseCommunities &&
|
|
!completed.isCompleted) {
|
|
completed.complete();
|
|
}
|
|
});
|
|
f.browser.callbacks.add(
|
|
BrowserAuthCode('other-code', 'a' * 43, {'type': 'account'}),
|
|
);
|
|
await completed.future.timeout(const Duration(seconds: 2));
|
|
await f.flow.addSelectedCommunities();
|
|
expect(f.admitted, [
|
|
'a-one.communities.buzz.xyz',
|
|
'b-one.communities.buzz.xyz',
|
|
]);
|
|
});
|
|
}
|