Refine iOS navigation, media controls, and background sync
Build APK / build (push) Successful in 5m50s
Build APK / build (push) Successful in 5m50s
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:ui' show ImageFilter;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
@@ -17,7 +18,10 @@ import 'package:noo/providers/trash_controller.dart';
|
||||
import 'package:noo/theme/app_theme.dart';
|
||||
import 'package:noo/theme/design_tokens.dart';
|
||||
import 'package:noo/widgets/noo/core/noo_avatar.dart';
|
||||
import 'package:noo/widgets/noo/core/noo_button.dart';
|
||||
import 'package:noo/widgets/app_top_bar.dart';
|
||||
import 'package:noo/widgets/avatar_menu.dart';
|
||||
import 'package:noo/widgets/bottom_nav_bar.dart';
|
||||
import 'package:noo/widgets/noo/nav/noo_top_bar.dart';
|
||||
|
||||
/// Covers `SettingsController.navMenuStyle`'s two options as wired through
|
||||
@@ -86,7 +90,24 @@ void main() {
|
||||
Future<void> pumpTopBar(
|
||||
WidgetTester tester, {
|
||||
required NooNavMenuStyle navMenuStyle,
|
||||
bool withHost = false,
|
||||
AvatarPosition position = AvatarPosition.top,
|
||||
bool search = false,
|
||||
bool reducedMotion = false,
|
||||
NooNavStyle style = NooNavStyle.android,
|
||||
NooBottomBarStyle barStyle = NooBottomBarStyle.floating,
|
||||
AppTab tab = AppTab.files,
|
||||
FabStyle uploadStyle = FabStyle.auto,
|
||||
bool frosted = false,
|
||||
int settleFrames = 30,
|
||||
}) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString('ui_nav_menu_style', navMenuStyle.name);
|
||||
await prefs.setString('ui_avatar_position', position.name);
|
||||
await prefs.setString('ui_bottom_bar_style', barStyle.name);
|
||||
await prefs.setBool('ui_bottom_bar_frosted', frosted);
|
||||
await prefs.setDouble('ui_bottom_bar_frosted_blur', 19);
|
||||
await prefs.setDouble('ui_bottom_bar_frosted_opacity', 0.45);
|
||||
await tester.binding.setSurfaceSize(const Size(400, 800));
|
||||
addTearDown(() => tester.binding.setSurfaceSize(null));
|
||||
|
||||
@@ -110,15 +131,49 @@ void main() {
|
||||
),
|
||||
],
|
||||
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(),
|
||||
theme: AppTheme.light(AppTheme.defaultAccent, useDynamicColor: false)
|
||||
.copyWith(
|
||||
platform: style == NooNavStyle.ios
|
||||
? TargetPlatform.iOS
|
||||
: TargetPlatform.android,
|
||||
),
|
||||
home: Builder(
|
||||
builder: (context) {
|
||||
final scaffold = Scaffold(
|
||||
drawer: const Drawer(child: Text('drawer open')),
|
||||
appBar: AppTopBar(
|
||||
style: style,
|
||||
tab: tab,
|
||||
uploadButtonStyle: uploadStyle,
|
||||
navMenuStyle: navMenuStyle,
|
||||
avatarPosition: position,
|
||||
searchInBottomBar: search,
|
||||
),
|
||||
body: const AvatarNavigationBody(
|
||||
child: SizedBox.expand(key: ValueKey('page content')),
|
||||
),
|
||||
bottomNavigationBar: withHost
|
||||
? BottomNavBar(
|
||||
style: style,
|
||||
barStyle: barStyle,
|
||||
tabs: const [AppTab.files, AppTab.photos],
|
||||
selectedIndex: 0,
|
||||
onDestinationSelected: (_) {},
|
||||
avatarInBottomBar: position == AvatarPosition.bottom,
|
||||
onSearchTap: search ? () {} : null,
|
||||
)
|
||||
: null,
|
||||
);
|
||||
final child = withHost
|
||||
? AvatarNavigationHost(child: scaffold)
|
||||
: scaffold;
|
||||
return MediaQuery(
|
||||
data: MediaQuery.of(
|
||||
context,
|
||||
).copyWith(disableAnimations: reducedMotion),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -127,7 +182,7 @@ void main() {
|
||||
// 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++) {
|
||||
for (var i = 0; i < settleFrames; i++) {
|
||||
await tester.pump(const Duration(milliseconds: 10));
|
||||
}
|
||||
}
|
||||
@@ -224,4 +279,320 @@ void main() {
|
||||
final screenWidth = tester.getSize(find.byType(MaterialApp)).width;
|
||||
expect(tester.getSize(row).width, screenWidth - 2 * NooSpace.sm - 4);
|
||||
});
|
||||
testWidgets(
|
||||
'top avatar expands and pushes content down; closing restores it',
|
||||
(tester) async {
|
||||
await pumpTopBar(
|
||||
tester,
|
||||
navMenuStyle: NooNavMenuStyle.avatarMenu,
|
||||
withHost: true,
|
||||
);
|
||||
final page = find.byKey(const ValueKey('page content'));
|
||||
final before = tester.getTopLeft(page).dy;
|
||||
await tester.tap(find.byTooltip('Menu'));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 120));
|
||||
final during = tester.getTopLeft(page).dy;
|
||||
expect(during, greaterThan(before));
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
expect(tester.getTopLeft(page).dy, greaterThan(during));
|
||||
expect(find.text('Settings'), findsOneWidget);
|
||||
await tester.tap(find.bySemanticsLabel('Close menu').last);
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 400));
|
||||
expect(tester.getTopLeft(page).dy, before);
|
||||
expect(find.byType(AvatarMenuCard), findsNothing);
|
||||
expect(tester.takeException(), isNull);
|
||||
},
|
||||
);
|
||||
|
||||
for (final search in [false, true]) {
|
||||
testWidgets(
|
||||
'bottom avatar moves into card without shifting content (search=$search)',
|
||||
(tester) async {
|
||||
await pumpTopBar(
|
||||
tester,
|
||||
navMenuStyle: NooNavMenuStyle.avatarMenu,
|
||||
withHost: true,
|
||||
position: AvatarPosition.bottom,
|
||||
search: search,
|
||||
);
|
||||
final page = find.byKey(const ValueKey('page content'));
|
||||
final before = tester.getTopLeft(page);
|
||||
expect(find.byTooltip('Menu'), findsOneWidget);
|
||||
final origin = tester.getCenter(find.byTooltip('Menu'));
|
||||
await tester.tap(find.byTooltip('Menu'));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 120));
|
||||
final moving = tester.getCenter(
|
||||
find.bySemanticsLabel('Close menu').last,
|
||||
);
|
||||
expect(moving.dy, lessThan(origin.dy));
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
expect(tester.getTopLeft(page), before);
|
||||
final card = find.byType(AvatarMenuCard);
|
||||
expect(card, findsOneWidget);
|
||||
expect(tester.getBottomRight(card).dy, lessThan(origin.dy));
|
||||
expect(tester.getSize(card).width, 376);
|
||||
if (search) expect(find.bySemanticsLabel('Search'), findsOneWidget);
|
||||
await tester.tap(find.bySemanticsLabel('Close menu').last);
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 400));
|
||||
expect(find.byType(AvatarMenuCard), findsNothing);
|
||||
expect(tester.getCenter(find.byTooltip('Menu')), origin);
|
||||
expect(tester.takeException(), isNull);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('reduced motion opens immediately and system back dismisses', (
|
||||
tester,
|
||||
) async {
|
||||
await pumpTopBar(
|
||||
tester,
|
||||
navMenuStyle: NooNavMenuStyle.avatarMenu,
|
||||
withHost: true,
|
||||
reducedMotion: true,
|
||||
);
|
||||
await tester.tap(find.byTooltip('Menu'));
|
||||
await tester.pump();
|
||||
expect(find.text('Settings'), findsOneWidget);
|
||||
await tester.binding.handlePopRoute();
|
||||
await tester.pump();
|
||||
expect(find.byType(AvatarMenuCard), findsNothing);
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
for (final position in AvatarPosition.values) {
|
||||
for (final frosted in [false, true]) {
|
||||
testWidgets(
|
||||
'$position popup shares frost toggle and strength ($frosted)',
|
||||
(tester) async {
|
||||
await pumpTopBar(
|
||||
tester,
|
||||
navMenuStyle: NooNavMenuStyle.avatarMenu,
|
||||
withHost: true,
|
||||
position: position,
|
||||
frosted: frosted,
|
||||
);
|
||||
await tester.tap(find.byTooltip('Menu'));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
final card = find.byType(AvatarMenuCard);
|
||||
final backdrop = find.ancestor(
|
||||
of: card,
|
||||
matching: find.byType(BackdropFilter),
|
||||
);
|
||||
expect(backdrop, frosted ? findsOneWidget : findsNothing);
|
||||
if (frosted) {
|
||||
expect(
|
||||
tester.widget<BackdropFilter>(backdrop).filter,
|
||||
ImageFilter.blur(sigmaX: 19, sigmaY: 19),
|
||||
);
|
||||
final decorations = tester.widgetList<DecoratedBox>(
|
||||
find.ancestor(of: card, matching: find.byType(DecoratedBox)),
|
||||
);
|
||||
expect(
|
||||
decorations.any(
|
||||
(box) =>
|
||||
box.decoration is BoxDecoration &&
|
||||
((box.decoration as BoxDecoration).color?.a ?? 0) > 0.44 &&
|
||||
((box.decoration as BoxDecoration).color?.a ?? 0) < 0.46,
|
||||
),
|
||||
isTrue,
|
||||
);
|
||||
final materials = tester.widgetList<Material>(
|
||||
find.descendant(of: card, matching: find.byType(Material)),
|
||||
);
|
||||
expect(
|
||||
materials.every(
|
||||
(material) => material.color == Colors.transparent,
|
||||
),
|
||||
isTrue,
|
||||
);
|
||||
}
|
||||
expect(tester.takeException(), isNull);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
testWidgets('iOS upload width interpolates while its label fades', (
|
||||
tester,
|
||||
) async {
|
||||
await pumpTopBar(
|
||||
tester,
|
||||
navMenuStyle: NooNavMenuStyle.avatarMenu,
|
||||
style: NooNavStyle.ios,
|
||||
uploadStyle: FabStyle.mini,
|
||||
);
|
||||
final button = find.byType(NooButton);
|
||||
final collapsedWidth = tester.getSize(button).width;
|
||||
await pumpTopBar(
|
||||
tester,
|
||||
navMenuStyle: NooNavMenuStyle.avatarMenu,
|
||||
style: NooNavStyle.ios,
|
||||
uploadStyle: FabStyle.expanded,
|
||||
settleFrames: 0,
|
||||
);
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
final middleWidth = tester.getSize(button).width;
|
||||
final labelOpacity = tester
|
||||
.widget<Opacity>(
|
||||
find
|
||||
.ancestor(of: find.text('Upload'), matching: find.byType(Opacity))
|
||||
.first,
|
||||
)
|
||||
.opacity;
|
||||
expect(labelOpacity, greaterThan(0));
|
||||
expect(labelOpacity, lessThan(1));
|
||||
await tester.pump(const Duration(milliseconds: 300));
|
||||
final expandedWidth = tester.getSize(button).width;
|
||||
expect(middleWidth, greaterThan(collapsedWidth));
|
||||
expect(middleWidth, lessThan(expandedWidth));
|
||||
expect(tester.getSize(button).height, 32);
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
|
||||
for (final uploadStyle in FabStyle.values) {
|
||||
for (final tab in [AppTab.files, AppTab.photos, AppTab.favorites]) {
|
||||
testWidgets('iOS title row uses $uploadStyle upload control on $tab', (
|
||||
tester,
|
||||
) async {
|
||||
await pumpTopBar(
|
||||
tester,
|
||||
navMenuStyle: NooNavMenuStyle.avatarMenu,
|
||||
style: NooNavStyle.ios,
|
||||
tab: tab,
|
||||
uploadStyle: uploadStyle,
|
||||
);
|
||||
final title = find.descendant(
|
||||
of: find.byType(AppTopBar),
|
||||
matching: find.text(tab.label),
|
||||
);
|
||||
final titleY = tester.getCenter(title).dy;
|
||||
expect(
|
||||
tester.getCenter(find.byTooltip('Upload')).dy,
|
||||
closeTo(titleY, 1),
|
||||
);
|
||||
expect(tester.getCenter(find.byTooltip('Menu')).dy, closeTo(titleY, 1));
|
||||
final expanded =
|
||||
uploadStyle == FabStyle.expanded ||
|
||||
(uploadStyle == FabStyle.auto && tab != AppTab.favorites);
|
||||
expect(find.text('Upload'), expanded ? findsOneWidget : findsNothing);
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
testWidgets('iOS attached navigation keeps popup above its rail', (
|
||||
tester,
|
||||
) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final accounts = jsonDecode(prefs.getString('accounts_list')!) as List;
|
||||
accounts.add(
|
||||
const SavedAccount(
|
||||
id: 'other_example_org__bob',
|
||||
serverUrl: 'https://other.example.org',
|
||||
username: 'bob',
|
||||
).toJson(),
|
||||
);
|
||||
await prefs.setString('accounts_list', jsonEncode(accounts));
|
||||
await pumpTopBar(
|
||||
tester,
|
||||
navMenuStyle: NooNavMenuStyle.avatarMenu,
|
||||
withHost: true,
|
||||
position: AvatarPosition.bottom,
|
||||
search: true,
|
||||
style: NooNavStyle.ios,
|
||||
barStyle: NooBottomBarStyle.attached,
|
||||
);
|
||||
final origin = tester.getCenter(find.byTooltip('Menu'));
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(BottomNavBar),
|
||||
matching: find.text('Files'),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.text('Photos'), findsNothing);
|
||||
await tester.tap(find.byTooltip('Menu'));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
expect(
|
||||
tester.getBottomLeft(find.byType(AvatarMenuCard)).dy,
|
||||
origin.dy - 25 - 1,
|
||||
);
|
||||
expect(tester.getSize(find.byType(AvatarMenuCard)).width, 400);
|
||||
expect(tester.getCenter(find.byTooltip('Menu')), origin);
|
||||
expect(find.bySemanticsLabel('Search'), findsOneWidget);
|
||||
await tester.tap(find.text('alice'));
|
||||
await tester.pump();
|
||||
for (var i = 0; i < 8; i++) {
|
||||
await tester.pump(const Duration(milliseconds: 50));
|
||||
}
|
||||
expect(
|
||||
tester.getCenter(find.text('Settings')).dy,
|
||||
lessThan(tester.getCenter(find.text('alice')).dy),
|
||||
);
|
||||
expect(
|
||||
tester.getCenter(find.text('alice')).dy,
|
||||
lessThan(tester.getCenter(find.text('bob')).dy),
|
||||
);
|
||||
expect(
|
||||
tester.getCenter(find.text('bob')).dy,
|
||||
lessThan(tester.getCenter(find.text('Add Account')).dy),
|
||||
);
|
||||
await tester.tap(find.byTooltip('Menu'));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 400));
|
||||
expect(find.byType(AvatarMenuCard), findsNothing);
|
||||
expect(tester.getCenter(find.byTooltip('Menu')), origin);
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
|
||||
testWidgets('expanding accounts also grows the top content offset', (
|
||||
tester,
|
||||
) async {
|
||||
await pumpTopBar(
|
||||
tester,
|
||||
navMenuStyle: NooNavMenuStyle.avatarMenu,
|
||||
withHost: true,
|
||||
);
|
||||
await tester.tap(find.byTooltip('Menu'));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
final before = tester
|
||||
.getTopLeft(find.byKey(const ValueKey('page content')))
|
||||
.dy;
|
||||
await tester.tap(find.text('alice'));
|
||||
await tester.pump();
|
||||
for (var i = 0; i < 8; i++) {
|
||||
await tester.pump(const Duration(milliseconds: 50));
|
||||
}
|
||||
expect(find.text('Add Account'), findsOneWidget);
|
||||
expect(
|
||||
tester.getTopLeft(find.byKey(const ValueKey('page content'))).dy,
|
||||
greaterThan(before),
|
||||
);
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
testWidgets('Search satellite closes the bottom navigation popup', (
|
||||
tester,
|
||||
) async {
|
||||
await pumpTopBar(
|
||||
tester,
|
||||
navMenuStyle: NooNavMenuStyle.avatarMenu,
|
||||
withHost: true,
|
||||
position: AvatarPosition.bottom,
|
||||
search: true,
|
||||
);
|
||||
await tester.tap(find.byTooltip('Menu'));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
await tester.tap(find.bySemanticsLabel('Search'));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 400));
|
||||
expect(find.byType(AvatarMenuCard), findsNothing);
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import 'dart:ui' show ImageFilter;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:noo/providers/settings_controller.dart';
|
||||
import 'package:noo/widgets/frosted_glass_container.dart';
|
||||
import 'noo/noo_test_utils.dart';
|
||||
|
||||
void main() {
|
||||
setUpNooTests();
|
||||
testNooWidgets('viewer glass follows toggle, blur and opacity', (
|
||||
tester,
|
||||
theme,
|
||||
colors,
|
||||
) async {
|
||||
SharedPreferences.setMockInitialValues({
|
||||
'ui_bottom_bar_frosted': false,
|
||||
'ui_bottom_bar_frosted_blur': 19.0,
|
||||
'ui_bottom_bar_frosted_opacity': 0.45,
|
||||
});
|
||||
final settings = SettingsController();
|
||||
await pumpNoo(
|
||||
tester,
|
||||
ChangeNotifierProvider.value(
|
||||
value: settings,
|
||||
child: const FrostedGlassContainer(
|
||||
child: SizedBox(width: 200, height: 80),
|
||||
),
|
||||
),
|
||||
theme: theme,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(BackdropFilter), findsNothing);
|
||||
final fill = find.descendant(
|
||||
of: find.byType(FrostedGlassContainer),
|
||||
matching: find.byType(Container),
|
||||
);
|
||||
expect(
|
||||
(tester.widget<Container>(fill).decoration as BoxDecoration).color,
|
||||
colors.surface,
|
||||
);
|
||||
settings.setBottomBarFrosted(true);
|
||||
await tester.pump();
|
||||
expect(
|
||||
tester.widget<BackdropFilter>(find.byType(BackdropFilter)).filter,
|
||||
ImageFilter.blur(sigmaX: 19, sigmaY: 19),
|
||||
);
|
||||
expect(
|
||||
(tester.widget<Container>(fill).decoration as BoxDecoration).color,
|
||||
colors.surface.withValues(alpha: 0.45),
|
||||
);
|
||||
settings.setBottomBarFrosted(false);
|
||||
await tester.pump();
|
||||
expect(find.byType(BackdropFilter), findsNothing);
|
||||
await tester.pumpWidget(const SizedBox());
|
||||
settings.dispose();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:noo/widgets/viewer/media_video_preview.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
import 'noo/noo_test_utils.dart';
|
||||
|
||||
class _Controller extends VideoPlayerController {
|
||||
_Controller() : super.networkUrl(Uri.parse('https://example.com/video.mp4'));
|
||||
Duration? sought;
|
||||
@override
|
||||
Future<void> seekTo(Duration position) async {
|
||||
sought = position;
|
||||
value = value.copyWith(position: position);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> pause() async {
|
||||
value = value.copyWith(isPlaying: false);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> play() async {
|
||||
value = value.copyWith(isPlaying: true);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
setUpNooTests();
|
||||
for (final playing in [false, true]) {
|
||||
testWidgets('iOS scrubbing preserves playing=$playing', (tester) async {
|
||||
final controller = _Controller();
|
||||
controller.value = VideoPlayerValue(
|
||||
duration: const Duration(seconds: 60),
|
||||
position: const Duration(seconds: 15),
|
||||
isInitialized: true,
|
||||
isPlaying: playing,
|
||||
);
|
||||
await pumpNoo(
|
||||
tester,
|
||||
IosSeekBar(controller: controller, color: CupertinoColors.white),
|
||||
);
|
||||
final slider = tester.widget<CupertinoSlider>(
|
||||
find.byType(CupertinoSlider),
|
||||
);
|
||||
expect(slider.value, 0.25);
|
||||
slider.onChangeStart!(0.25);
|
||||
expect(controller.value.isPlaying, false);
|
||||
slider.onChanged!(0.5);
|
||||
await tester.pump();
|
||||
expect(controller.sought, const Duration(seconds: 30));
|
||||
slider.onChangeEnd!(0.5);
|
||||
await tester.pump();
|
||||
expect(controller.value.isPlaying, playing);
|
||||
expect(tester.takeException(), isNull);
|
||||
await tester.pumpWidget(const SizedBox());
|
||||
await controller.dispose();
|
||||
});
|
||||
}
|
||||
testWidgets('iOS scrubber disables seeking before duration is available', (
|
||||
tester,
|
||||
) async {
|
||||
final controller = _Controller();
|
||||
await pumpNoo(
|
||||
tester,
|
||||
IosSeekBar(controller: controller, color: CupertinoColors.white),
|
||||
);
|
||||
expect(
|
||||
tester.widget<CupertinoSlider>(find.byType(CupertinoSlider)).onChanged,
|
||||
isNull,
|
||||
);
|
||||
await tester.pumpWidget(const SizedBox());
|
||||
await controller.dispose();
|
||||
});
|
||||
}
|
||||
@@ -215,7 +215,7 @@ void main() {
|
||||
expect(opacity.opacity, 0);
|
||||
});
|
||||
|
||||
testNooWidgets('ios: unchanged icon-above-label layout, no indicator', (
|
||||
testNooWidgets('ios: only selected tab has a label, no indicator', (
|
||||
tester,
|
||||
theme,
|
||||
c,
|
||||
@@ -238,6 +238,8 @@ void main() {
|
||||
final iconCenter = tester.getCenter(find.byIcon(LucideIcons.images));
|
||||
final labelCenter = tester.getCenter(find.text('Photos'));
|
||||
expect(labelCenter.dy, greaterThan(iconCenter.dy));
|
||||
expect(find.text('Files'), findsNothing);
|
||||
expect(find.text('Favorites'), findsNothing);
|
||||
});
|
||||
|
||||
testNooWidgets('floating: inset, rounded, bordered - not edge to edge', (
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:noo/widgets/selection_bar_overlay.dart';
|
||||
|
||||
void main() {
|
||||
for (final extendsBody in [false, true]) {
|
||||
for (final safeBottom in [0.0, 34.0]) {
|
||||
testWidgets(
|
||||
'action bar clears navigation: extend=$extendsBody, inset=$safeBottom',
|
||||
(tester) async {
|
||||
tester.view.physicalSize = const Size(400, 800);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
tester.view.padding = FakeViewPadding(bottom: safeBottom);
|
||||
addTearDown(tester.view.reset);
|
||||
var taps = 0;
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
extendBody: extendsBody,
|
||||
bottomNavigationBar: SafeArea(
|
||||
top: false,
|
||||
child: SizedBox(
|
||||
key: const ValueKey('navigation'),
|
||||
height: 64,
|
||||
),
|
||||
),
|
||||
body: SelectionBarOverlay(
|
||||
bar: GestureDetector(
|
||||
onTap: () => taps++,
|
||||
child: Container(
|
||||
key: const ValueKey('actions'),
|
||||
color: Colors.blue,
|
||||
),
|
||||
),
|
||||
child: ListView.builder(
|
||||
itemCount: 100,
|
||||
itemBuilder: (_, i) =>
|
||||
SizedBox(height: 48, child: Text('Item $i')),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
final actions = find.byKey(const ValueKey('actions'));
|
||||
final navTop = tester
|
||||
.getTopLeft(find.byKey(const ValueKey('navigation')))
|
||||
.dy;
|
||||
expect(tester.getBottomLeft(actions).dy, navTop - 8);
|
||||
final before = tester.getTopLeft(actions);
|
||||
await tester.drag(find.byType(ListView), const Offset(0, -350));
|
||||
await tester.pumpAndSettle();
|
||||
expect(tester.getTopLeft(actions), before);
|
||||
await tester.tap(actions);
|
||||
expect(taps, 1);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user