- New Settings > Appearance > "Navigation menu" toggle (SettingsController.navMenuStyle, NooNavMenuStyle.drawer/avatarMenu): the hamburger-opens-a-Drawer pattern stays the default, or the avatar button opens a full-width dropdown (showAvatarMenu) instead, holding the same hidden-tabs + Settings content the drawer does. Removes one of the two top-corner targets the shell asks a thumb to reach, with no permanent chrome added. - NooTopBar gains androidTitleReplacement so Android's top bar can swap its title for an inline search-field-styled launcher (ShellSearchLauncher) when search isn't in the bottom bar, instead of a bare search icon beside it. - ShellAvatarButton's onTap/label are now overridable so AppTopBar can repoint it per the new setting without this file depending on avatar_menu.dart (which already depends on it for shared helpers). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
178 lines
6.4 KiB
Dart
178 lines
6.4 KiB
Dart
import 'dart:convert';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:google_fonts/google_fonts.dart';
|
|
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
import 'package:noo/models/app_tab.dart';
|
|
import 'package:noo/models/saved_account.dart';
|
|
import 'package:noo/providers/connectivity_controller.dart';
|
|
import 'package:noo/providers/files_controller.dart';
|
|
import 'package:noo/providers/session_controller.dart';
|
|
import 'package:noo/providers/settings_controller.dart';
|
|
import 'package:noo/providers/sync_status_controller.dart';
|
|
import 'package:noo/providers/trash_controller.dart';
|
|
import 'package:noo/theme/app_theme.dart';
|
|
import 'package:noo/widgets/app_top_bar.dart';
|
|
import 'package:noo/widgets/noo/nav/noo_top_bar.dart';
|
|
|
|
/// Covers `SettingsController.navMenuStyle`'s two options as wired through
|
|
/// `AppTopBar`/`ShellAvatarButton`: [NooNavMenuStyle.drawer] (today's
|
|
/// hamburger-opens-a-left-Drawer pattern, unchanged) vs
|
|
/// [NooNavMenuStyle.avatarMenu] (no hamburger at all - the avatar opens
|
|
/// `showAvatarMenu`'s dropdown instead of the account switcher).
|
|
void main() {
|
|
const secureStorageChannel = MethodChannel(
|
|
'plugins.it_nomads.com/flutter_secure_storage',
|
|
);
|
|
const connectivityChannel = MethodChannel(
|
|
'dev.fluttercommunity.plus/connectivity',
|
|
);
|
|
const connectivityStatusChannel = EventChannel(
|
|
'dev.fluttercommunity.plus/connectivity_status',
|
|
);
|
|
|
|
const accountId = 'server_example_com__alice';
|
|
const passwordKey = 'nc_app_password_$accountId';
|
|
const password = 'app-password';
|
|
|
|
setUpAll(() {
|
|
GoogleFonts.config.allowRuntimeFetching = false;
|
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
|
.setMockMethodCallHandler(secureStorageChannel, (call) async {
|
|
if (call.method == 'readAll') {
|
|
return <String, String>{passwordKey: password};
|
|
}
|
|
if (call.method == 'read') {
|
|
final key = (call.arguments as Map)['key'] as String?;
|
|
return key == passwordKey ? password : null;
|
|
}
|
|
return null;
|
|
});
|
|
// Offline, same as `account_view_test.dart`'s setup: keeps
|
|
// SessionController in a provisional login and stops
|
|
// FilesController/SyncStatusController/TrashController's network/timer
|
|
// machinery from hanging the test.
|
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
|
.setMockMethodCallHandler(connectivityChannel, (call) async {
|
|
if (call.method == 'check') return <String>['none'];
|
|
return null;
|
|
});
|
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
|
.setMockStreamHandler(
|
|
connectivityStatusChannel,
|
|
MockStreamHandler.inline(onListen: (arguments, events) {}),
|
|
);
|
|
});
|
|
|
|
setUp(() {
|
|
SharedPreferences.setMockInitialValues({
|
|
'account_migration_v1_done': true,
|
|
'accounts_list': jsonEncode([
|
|
const SavedAccount(
|
|
id: accountId,
|
|
serverUrl: 'https://server.example.com',
|
|
username: 'alice',
|
|
).toJson(),
|
|
]),
|
|
'active_account_id': accountId,
|
|
});
|
|
});
|
|
|
|
Future<void> pumpTopBar(
|
|
WidgetTester tester, {
|
|
required NooNavMenuStyle navMenuStyle,
|
|
}) async {
|
|
await tester.binding.setSurfaceSize(const Size(400, 800));
|
|
addTearDown(() => tester.binding.setSurfaceSize(null));
|
|
|
|
await tester.pumpWidget(
|
|
MultiProvider(
|
|
providers: [
|
|
ChangeNotifierProvider(create: (_) => ConnectivityController()),
|
|
ChangeNotifierProvider(
|
|
create: (context) => SessionController(context.read()),
|
|
),
|
|
ChangeNotifierProvider(create: (_) => SettingsController()),
|
|
ChangeNotifierProvider(
|
|
create: (context) => FilesController(context.read()),
|
|
),
|
|
ChangeNotifierProvider(
|
|
create: (context) =>
|
|
SyncStatusController(context.read(), context.read()),
|
|
),
|
|
ChangeNotifierProvider(
|
|
create: (context) => TrashController(context.read()),
|
|
),
|
|
],
|
|
child: MaterialApp(
|
|
theme: AppTheme.light(AppTheme.defaultAccent, useDynamicColor: false),
|
|
home: Scaffold(
|
|
drawer: const Drawer(child: Text('drawer open')),
|
|
appBar: AppTopBar(
|
|
style: NooNavStyle.android,
|
|
tab: AppTab.files,
|
|
navMenuStyle: navMenuStyle,
|
|
),
|
|
body: const SizedBox(),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Not `pumpAndSettle` - the same reasoning as `account_view_test.dart`:
|
|
// the active account's dependent controllers start periodic
|
|
// timers/futures that would spin it forever.
|
|
for (var i = 0; i < 30; i++) {
|
|
await tester.pump(const Duration(milliseconds: 10));
|
|
}
|
|
}
|
|
|
|
testWidgets('avatarMenu style: no hamburger, avatar opens the dropdown', (
|
|
tester,
|
|
) async {
|
|
await pumpTopBar(tester, navMenuStyle: NooNavMenuStyle.avatarMenu);
|
|
|
|
expect(find.byTooltip('Menu'), findsOneWidget);
|
|
expect(find.byIcon(LucideIcons.menu), findsNothing);
|
|
|
|
await tester.tap(find.byTooltip('Menu'));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 200));
|
|
|
|
// The dropdown's own header + Settings row - not the account
|
|
// switcher's "Add account"/"Manage accounts" rows.
|
|
expect(find.text('alice'), findsOneWidget);
|
|
expect(find.text('Settings'), findsOneWidget);
|
|
expect(find.text('Add account'), findsNothing);
|
|
});
|
|
|
|
testWidgets('drawer style (default): hamburger opens the real drawer', (
|
|
tester,
|
|
) async {
|
|
await pumpTopBar(tester, navMenuStyle: NooNavMenuStyle.drawer);
|
|
|
|
expect(find.byTooltip('Menu'), findsOneWidget);
|
|
await tester.tap(find.byTooltip('Menu'));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('drawer open'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets(
|
|
'drawer style (default): avatar still opens the account switcher',
|
|
(tester) async {
|
|
await pumpTopBar(tester, navMenuStyle: NooNavMenuStyle.drawer);
|
|
|
|
expect(find.byTooltip('Accounts'), findsOneWidget);
|
|
await tester.tap(find.byTooltip('Accounts'));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 200));
|
|
|
|
expect(find.text('Add account'), findsOneWidget);
|
|
},
|
|
);
|
|
}
|