Skip to content

Commit

Permalink
accounts-ui: Make accounts list scrollable
Browse files Browse the repository at this point in the history
Before this fix, the list of accounts did not scroll
when they were more than a screenful and would
show an overflow error on the screen.

After this fix, the list of accounts scrolls properly
with no overflow error and without shifting other
content offscreen.

Fixes: #100
  • Loading branch information
sm-sayedi authored and gnprice committed Mar 29, 2024
1 parent 81feda5 commit e6c531a
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 7 deletions.
15 changes: 9 additions & 6 deletions lib/widgets/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,15 @@ class ChooseAccountPage extends StatelessWidget {
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 400),
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
for (final (:accountId, :account) in globalStore.accountEntries)
_buildAccountItem(context,
accountId: accountId,
title: Text(account.realmUrl.toString()),
subtitle: Text(account.email)),
child: Column(mainAxisSize: MainAxisSize.min, children: [
Flexible(child: SingleChildScrollView(
child: Column(mainAxisSize: MainAxisSize.min, children: [
for (final (:accountId, :account) in globalStore.accountEntries)
_buildAccountItem(context,
accountId: accountId,
title: Text(account.realmUrl.toString()),
subtitle: Text(account.email)),
]))),
const SizedBox(height: 12),
ElevatedButton(
onPressed: () => Navigator.push(context,
Expand Down
7 changes: 7 additions & 0 deletions test/flutter_checks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ import 'package:checks/checks.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

extension RectChecks on Subject<Rect> {
Subject<double> get top => has((d) => d.top, 'top');
Subject<double> get bottom => has((d) => d.bottom, 'bottom');

// TODO others
}

extension AnimationChecks<T> on Subject<Animation<T>> {
Subject<AnimationStatus> get status => has((d) => d.status, 'status');
Subject<T> get value => has((d) => d.value, 'value');
Expand Down
110 changes: 109 additions & 1 deletion test/widgets/app_test.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import 'package:checks/checks.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/zulip_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:zulip/model/database.dart';
import 'package:zulip/widgets/app.dart';
import 'package:zulip/widgets/inbox.dart';
import 'package:zulip/widgets/page.dart';
import 'package:zulip/widgets/store.dart';

import '../example_data.dart' as eg;
import '../flutter_checks.dart';
import '../model/binding.dart';
import '../test_navigation.dart';
import 'page_checks.dart';
Expand Down Expand Up @@ -51,4 +55,108 @@ void main() {
]);
});
});

group('ChooseAccountPage', () {
Future<void> setupChooseAccountPage(WidgetTester tester, {
required List<Account> accounts,
}) async {
addTearDown(testBinding.reset);

for (final account in accounts) {
await testBinding.globalStore
.insertAccount(account.toCompanion(false));
}

await tester.pumpWidget(
const MaterialApp(
localizationsDelegates: ZulipLocalizations.localizationsDelegates,
supportedLocales: ZulipLocalizations.supportedLocales,
home: GlobalStoreWidget(
child: ChooseAccountPage())));

// global store gets loaded
await tester.pumpAndSettle();
}

List<Account> generateAccounts(int count) {
return List.generate(count, (i) => eg.account(
id: i,
user: eg.user(fullName: 'User $i', email: 'user$i@example'),
apiKey: 'user${i}apikey',
));
}

Finder findAccount(Account account) => find.text(account.email).hitTestable();

Finder findButton<T extends ButtonStyleButton>({required String withText}) {
return find
.descendant(of: find.bySubtype<T>(), matching: find.text(withText))
.hitTestable();
}

void checkAccountShown(Account account, {required bool expected}) {
check(findAccount(account).evaluate()).length.equals(expected ? 1 : 0);
}

void checkButtonShown<T extends ButtonStyleButton>({
required String withText,
required bool expected,
}) {
check(findButton<T>(withText: withText).evaluate())
.length.equals(expected ? 1 : 0);
}

testWidgets('accounts list is scrollable when more than a screenful', (tester) async {
final accounts = generateAccounts(15);
await setupChooseAccountPage(tester, accounts: accounts);

// Accounts list is more than a screenful
// * First account is shown
// * Last account is out of view
checkAccountShown(accounts.first, expected: true);
checkAccountShown(accounts.last, expected: false);

// Button to add an account is visible
// and not moved offscreen by the long list of accounts
checkButtonShown(withText: 'Add an account', expected: true);

// Accounts list is scrollable to the bottom
await tester.scrollUntilVisible(findAccount(accounts.last), 50);
checkAccountShown(accounts.last, expected: true);
});

testWidgets('with just one account, the layout is centered', (tester) async {
final account = eg.selfAccount;
await setupChooseAccountPage(tester, accounts: [account]);

const buttonText = 'Add an account';
checkAccountShown(account, expected: true);
checkButtonShown(withText: buttonText, expected: true);

final screenHeight =
(tester.view.physicalSize / tester.view.devicePixelRatio).height;

check(tester.getRect(findAccount(account)))
..top.isGreaterThan(1 / 3 * screenHeight)
..bottom.isLessThan(2 / 3 * screenHeight);

check(tester.getRect(findButton(withText: buttonText)))
..top.isGreaterThan(1 / 3 * screenHeight)
..bottom.isLessThan(2 / 3 * screenHeight);
});

testWidgets('with no accounts, the Add an Account button is centered', (tester) async {
await setupChooseAccountPage(tester, accounts: []);

const buttonText = 'Add an account';
checkButtonShown(withText: buttonText, expected: true);

final screenHeight =
(tester.view.physicalSize / tester.view.devicePixelRatio).height;

check(tester.getRect(findButton(withText: buttonText)))
..top.isGreaterThan(1 / 3 * screenHeight)
..bottom.isLessThan(2 / 3 * screenHeight);
});
});
}

0 comments on commit e6c531a

Please sign in to comment.