1816 lines
64 KiB
Dart
1816 lines
64 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
// This file is run as part of a reduced test set in CI on Mac and Windows
|
|
// machines.
|
|
@Tags(<String>['reduced-test-set'])
|
|
library;
|
|
|
|
import 'dart:math';
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/gestures.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
void main() {
|
|
testWidgets('Navigation bar updates destinations when tapped', (WidgetTester tester) async {
|
|
var mutatedIndex = -1;
|
|
final Widget widget = _buildWidget(
|
|
NavigationBar(
|
|
destinations: const <Widget>[
|
|
NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'),
|
|
NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'),
|
|
],
|
|
onDestinationSelected: (int i) {
|
|
mutatedIndex = i;
|
|
},
|
|
),
|
|
);
|
|
|
|
await tester.pumpWidget(widget);
|
|
|
|
expect(find.text('AC'), findsOneWidget);
|
|
expect(find.text('Alarm'), findsOneWidget);
|
|
|
|
await tester.tap(find.text('Alarm'));
|
|
expect(mutatedIndex, 1);
|
|
|
|
await tester.tap(find.text('AC'));
|
|
expect(mutatedIndex, 0);
|
|
});
|
|
|
|
testWidgets('NavigationBar can update background color', (WidgetTester tester) async {
|
|
const Color color = Colors.yellow;
|
|
|
|
await tester.pumpWidget(
|
|
_buildWidget(
|
|
NavigationBar(
|
|
backgroundColor: color,
|
|
destinations: const <Widget>[
|
|
NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'),
|
|
NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'),
|
|
],
|
|
onDestinationSelected: (int i) {},
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(_getMaterial(tester).color, equals(color));
|
|
});
|
|
|
|
testWidgets('NavigationBar can update elevation', (WidgetTester tester) async {
|
|
const elevation = 42.0;
|
|
|
|
await tester.pumpWidget(
|
|
_buildWidget(
|
|
NavigationBar(
|
|
elevation: elevation,
|
|
destinations: const <Widget>[
|
|
NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'),
|
|
NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'),
|
|
],
|
|
onDestinationSelected: (int i) {},
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(_getMaterial(tester).elevation, equals(elevation));
|
|
});
|
|
|
|
testWidgets('NavigationBar adds bottom padding to height', (WidgetTester tester) async {
|
|
const bottomPadding = 40.0;
|
|
|
|
await tester.pumpWidget(
|
|
_buildWidget(
|
|
NavigationBar(
|
|
destinations: const <Widget>[
|
|
NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'),
|
|
NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'),
|
|
],
|
|
onDestinationSelected: (int i) {},
|
|
),
|
|
),
|
|
);
|
|
|
|
final double defaultSize = tester.getSize(find.byType(NavigationBar)).height;
|
|
expect(defaultSize, 80);
|
|
|
|
await tester.pumpWidget(
|
|
_buildWidget(
|
|
MediaQuery(
|
|
data: const MediaQueryData(padding: EdgeInsets.only(bottom: bottomPadding)),
|
|
child: NavigationBar(
|
|
destinations: const <Widget>[
|
|
NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'),
|
|
NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'),
|
|
],
|
|
onDestinationSelected: (int i) {},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final double expectedHeight = defaultSize + bottomPadding;
|
|
expect(tester.getSize(find.byType(NavigationBar)).height, expectedHeight);
|
|
});
|
|
|
|
testWidgets('NavigationBar respects the notch/system navigation bar in landscape mode', (
|
|
WidgetTester tester,
|
|
) async {
|
|
const safeAreaPadding = 40.0;
|
|
Widget navigationBar() {
|
|
return NavigationBar(
|
|
destinations: const <Widget>[
|
|
NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'),
|
|
NavigationDestination(
|
|
key: Key('Center'),
|
|
icon: Icon(Icons.center_focus_strong),
|
|
label: 'Center',
|
|
),
|
|
NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'),
|
|
],
|
|
onDestinationSelected: (int i) {},
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(_buildWidget(navigationBar()));
|
|
final double defaultWidth = tester.getSize(find.byType(NavigationBar)).width;
|
|
final Finder defaultCenterItem = find.byKey(const Key('Center'));
|
|
final Offset center = tester.getCenter(defaultCenterItem);
|
|
expect(center.dx, defaultWidth / 2);
|
|
|
|
await tester.pumpWidget(
|
|
_buildWidget(
|
|
MediaQuery(
|
|
data: const MediaQueryData(padding: EdgeInsets.only(left: safeAreaPadding)),
|
|
child: navigationBar(),
|
|
),
|
|
),
|
|
);
|
|
|
|
// The position of center item of navigation bar should indicate whether
|
|
// the safe area is sufficiently respected, when safe area is on the left side.
|
|
// e.g. Android device with system navigation bar in landscape mode.
|
|
final Finder leftPaddedCenterItem = find.byKey(const Key('Center'));
|
|
final Offset leftPaddedCenter = tester.getCenter(leftPaddedCenterItem);
|
|
expect(
|
|
leftPaddedCenter.dx,
|
|
closeTo((defaultWidth + safeAreaPadding) / 2.0, precisionErrorTolerance),
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
_buildWidget(
|
|
MediaQuery(
|
|
data: const MediaQueryData(padding: EdgeInsets.only(right: safeAreaPadding)),
|
|
child: navigationBar(),
|
|
),
|
|
),
|
|
);
|
|
|
|
// The position of center item of navigation bar should indicate whether
|
|
// the safe area is sufficiently respected, when safe area is on the right side.
|
|
// e.g. Android device with system navigation bar in landscape mode.
|
|
final Finder rightPaddedCenterItem = find.byKey(const Key('Center'));
|
|
final Offset rightPaddedCenter = tester.getCenter(rightPaddedCenterItem);
|
|
expect(
|
|
rightPaddedCenter.dx,
|
|
closeTo((defaultWidth - safeAreaPadding) / 2, precisionErrorTolerance),
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
_buildWidget(
|
|
MediaQuery(
|
|
data: const MediaQueryData(
|
|
padding: EdgeInsets.fromLTRB(safeAreaPadding, 0, safeAreaPadding, safeAreaPadding),
|
|
),
|
|
child: navigationBar(),
|
|
),
|
|
),
|
|
);
|
|
|
|
// The position of center item of navigation bar should indicate whether
|
|
// the safe area is sufficiently respected, when safe areas are on both sides.
|
|
// e.g. iOS device with both sides of round corner.
|
|
final Finder paddedCenterItem = find.byKey(const Key('Center'));
|
|
final Offset paddedCenter = tester.getCenter(paddedCenterItem);
|
|
expect(paddedCenter.dx, closeTo(defaultWidth / 2, precisionErrorTolerance));
|
|
});
|
|
|
|
testWidgets('Material2 - NavigationBar uses proper defaults when no parameters are given', (
|
|
WidgetTester tester,
|
|
) async {
|
|
// M2 settings that were hand coded.
|
|
await tester.pumpWidget(
|
|
_buildWidget(
|
|
NavigationBar(
|
|
destinations: const <Widget>[
|
|
NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'),
|
|
NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'),
|
|
],
|
|
onDestinationSelected: (int i) {},
|
|
),
|
|
useMaterial3: false,
|
|
),
|
|
);
|
|
|
|
expect(_getMaterial(tester).color, const Color(0xffeaeaea));
|
|
expect(_getMaterial(tester).surfaceTintColor, null);
|
|
expect(_getMaterial(tester).elevation, 0);
|
|
expect(tester.getSize(find.byType(NavigationBar)).height, 80);
|
|
expect(_getIndicatorDecoration(tester)?.color, const Color(0x3d2196f3));
|
|
expect(
|
|
_getIndicatorDecoration(tester)?.shape,
|
|
RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
|
);
|
|
});
|
|
|
|
testWidgets('Material3 - NavigationBar uses proper defaults when no parameters are given', (
|
|
WidgetTester tester,
|
|
) async {
|
|
// M3 settings from the token database.
|
|
final theme = ThemeData();
|
|
await tester.pumpWidget(
|
|
_buildWidget(
|
|
NavigationBar(
|
|
destinations: const <Widget>[
|
|
NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'),
|
|
NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'),
|
|
],
|
|
onDestinationSelected: (int i) {},
|
|
),
|
|
useMaterial3: theme.useMaterial3,
|
|
),
|
|
);
|
|
|
|
expect(_getMaterial(tester).color, theme.colorScheme.surfaceContainer);
|
|
expect(_getMaterial(tester).surfaceTintColor, Colors.transparent);
|
|
expect(_getMaterial(tester).elevation, 3);
|
|
expect(tester.getSize(find.byType(NavigationBar)).height, 80);
|
|
expect(_getIndicatorDecoration(tester)?.color, theme.colorScheme.secondaryContainer);
|
|
expect(_getIndicatorDecoration(tester)?.shape, const StadiumBorder());
|
|
});
|
|
|
|
testWidgets('Material2 - NavigationBar shows tooltips with text scaling', (
|
|
WidgetTester tester,
|
|
) async {
|
|
const label = 'A';
|
|
|
|
Widget buildApp({required TextScaler textScaler}) {
|
|
return MediaQuery(
|
|
data: MediaQueryData(textScaler: textScaler),
|
|
child: Localizations(
|
|
locale: const Locale('en', 'US'),
|
|
delegates: const <LocalizationsDelegate<dynamic>>[
|
|
DefaultMaterialLocalizations.delegate,
|
|
DefaultWidgetsLocalizations.delegate,
|
|
],
|
|
child: MaterialApp(
|
|
theme: ThemeData(useMaterial3: false),
|
|
home: Navigator(
|
|
onGenerateRoute: (RouteSettings settings) {
|
|
return MaterialPageRoute<void>(
|
|
builder: (BuildContext context) {
|
|
return Scaffold(
|
|
bottomNavigationBar: NavigationBar(
|
|
destinations: const <NavigationDestination>[
|
|
NavigationDestination(
|
|
label: label,
|
|
icon: Icon(Icons.ac_unit),
|
|
tooltip: label,
|
|
),
|
|
NavigationDestination(label: 'B', icon: Icon(Icons.battery_alert)),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildApp(textScaler: TextScaler.noScaling));
|
|
expect(find.text(label), findsOneWidget);
|
|
await tester.longPress(find.text(label));
|
|
expect(find.text(label), findsNWidgets(2));
|
|
|
|
// The default size of a tooltip with the text A.
|
|
const defaultTooltipSize = Size(14.0, 14.0);
|
|
expect(tester.getSize(find.text(label).last), defaultTooltipSize);
|
|
// The duration is needed to ensure the tooltip disappears.
|
|
await tester.pumpAndSettle(const Duration(seconds: 2));
|
|
|
|
await tester.pumpWidget(buildApp(textScaler: const TextScaler.linear(4.0)));
|
|
expect(find.text(label), findsOneWidget);
|
|
await tester.longPress(find.text(label));
|
|
expect(
|
|
tester.getSize(find.text(label).last),
|
|
Size(defaultTooltipSize.width * 4, defaultTooltipSize.height * 4),
|
|
);
|
|
});
|
|
|
|
testWidgets('Material3 - NavigationBar shows tooltips with text scaling', (
|
|
WidgetTester tester,
|
|
) async {
|
|
const label = 'A';
|
|
|
|
Widget buildApp({required TextScaler textScaler}) {
|
|
return MediaQuery(
|
|
data: MediaQueryData(textScaler: textScaler),
|
|
child: Localizations(
|
|
locale: const Locale('en', 'US'),
|
|
delegates: const <LocalizationsDelegate<dynamic>>[
|
|
DefaultMaterialLocalizations.delegate,
|
|
DefaultWidgetsLocalizations.delegate,
|
|
],
|
|
child: MaterialApp(
|
|
home: Navigator(
|
|
onGenerateRoute: (RouteSettings settings) {
|
|
return MaterialPageRoute<void>(
|
|
builder: (BuildContext context) {
|
|
return Scaffold(
|
|
bottomNavigationBar: NavigationBar(
|
|
destinations: const <NavigationDestination>[
|
|
NavigationDestination(
|
|
label: label,
|
|
icon: Icon(Icons.ac_unit),
|
|
tooltip: label,
|
|
),
|
|
NavigationDestination(label: 'B', icon: Icon(Icons.battery_alert)),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildApp(textScaler: TextScaler.noScaling));
|
|
expect(find.text(label), findsOneWidget);
|
|
await tester.longPress(find.text(label));
|
|
expect(find.text(label), findsNWidgets(2));
|
|
|
|
expect(tester.getSize(find.text(label).last), const Size(14.25, 20.0));
|
|
// The duration is needed to ensure the tooltip disappears.
|
|
await tester.pumpAndSettle(const Duration(seconds: 2));
|
|
|
|
await tester.pumpWidget(buildApp(textScaler: const TextScaler.linear(4.0)));
|
|
expect(find.text(label), findsOneWidget);
|
|
await tester.longPress(find.text(label));
|
|
|
|
expect(tester.getSize(find.text(label).last), const Size(56.25, 80.0));
|
|
});
|
|
|
|
testWidgets('Material3 - NavigationBar label can scale and has maxScaleFactor', (
|
|
WidgetTester tester,
|
|
) async {
|
|
const label = 'A';
|
|
|
|
Widget buildApp({required TextScaler textScaler}) {
|
|
return MediaQuery(
|
|
data: MediaQueryData(textScaler: textScaler),
|
|
child: Localizations(
|
|
locale: const Locale('en', 'US'),
|
|
delegates: const <LocalizationsDelegate<dynamic>>[
|
|
DefaultMaterialLocalizations.delegate,
|
|
DefaultWidgetsLocalizations.delegate,
|
|
],
|
|
child: MaterialApp(
|
|
home: Navigator(
|
|
onGenerateRoute: (RouteSettings settings) {
|
|
return MaterialPageRoute<void>(
|
|
builder: (BuildContext context) {
|
|
return Scaffold(
|
|
bottomNavigationBar: NavigationBar(
|
|
destinations: const <NavigationDestination>[
|
|
NavigationDestination(label: label, icon: Icon(Icons.ac_unit)),
|
|
NavigationDestination(label: 'B', icon: Icon(Icons.battery_alert)),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildApp(textScaler: TextScaler.noScaling));
|
|
expect(find.text(label), findsOneWidget);
|
|
expect(_sizeAlmostEqual(tester.getSize(find.text(label)), const Size(12.5, 16.0)), true);
|
|
|
|
await tester.pumpWidget(buildApp(textScaler: const TextScaler.linear(1.1)));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(_sizeAlmostEqual(tester.getSize(find.text(label)), const Size(13.7, 18.0)), true);
|
|
|
|
await tester.pumpWidget(buildApp(textScaler: const TextScaler.linear(1.3)));
|
|
|
|
expect(_sizeAlmostEqual(tester.getSize(find.text(label)), const Size(16.1, 21.0)), true);
|
|
|
|
await tester.pumpWidget(buildApp(textScaler: const TextScaler.linear(4)));
|
|
expect(_sizeAlmostEqual(tester.getSize(find.text(label)), const Size(16.1, 21.0)), true);
|
|
});
|
|
|
|
testWidgets('Custom tooltips in NavigationBarDestination', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
bottomNavigationBar: NavigationBar(
|
|
destinations: const <NavigationDestination>[
|
|
NavigationDestination(label: 'A', tooltip: 'A tooltip', icon: Icon(Icons.ac_unit)),
|
|
NavigationDestination(label: 'B', icon: Icon(Icons.battery_alert)),
|
|
NavigationDestination(label: 'C', icon: Icon(Icons.cake), tooltip: ''),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.text('A'), findsOneWidget);
|
|
await tester.longPress(find.text('A'));
|
|
expect(find.byTooltip('A tooltip'), findsOneWidget);
|
|
|
|
expect(find.text('B'), findsOneWidget);
|
|
await tester.longPress(find.text('B'));
|
|
expect(find.byTooltip('B'), findsOneWidget);
|
|
|
|
expect(find.text('C'), findsOneWidget);
|
|
await tester.longPress(find.text('C'));
|
|
expect(find.byTooltip('C'), findsNothing);
|
|
});
|
|
|
|
testWidgets('Navigation bar semantics', (WidgetTester tester) async {
|
|
Widget widget({int selectedIndex = 0}) {
|
|
return _buildWidget(
|
|
NavigationBar(
|
|
selectedIndex: selectedIndex,
|
|
destinations: const <Widget>[
|
|
NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'),
|
|
NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'),
|
|
NavigationDestination(icon: Icon(Icons.abc), label: 'ABC'),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(widget());
|
|
|
|
expect(
|
|
tester.getSemantics(find.text('AC')),
|
|
matchesSemantics(
|
|
label: 'AC${kIsWeb ? '' : '\nTab 1 of 3'}',
|
|
textDirection: TextDirection.ltr,
|
|
isFocusable: true,
|
|
isSelected: true,
|
|
isButton: true,
|
|
hasSelectedState: true,
|
|
hasEnabledState: true,
|
|
isEnabled: true,
|
|
hasTapAction: true,
|
|
hasFocusAction: true,
|
|
),
|
|
);
|
|
expect(
|
|
tester.getSemantics(find.text('Alarm')),
|
|
matchesSemantics(
|
|
label: 'Alarm${kIsWeb ? '' : '\nTab 2 of 3'}',
|
|
textDirection: TextDirection.ltr,
|
|
isFocusable: true,
|
|
isButton: true,
|
|
hasSelectedState: true,
|
|
hasEnabledState: true,
|
|
isEnabled: true,
|
|
hasTapAction: true,
|
|
hasFocusAction: true,
|
|
),
|
|
);
|
|
expect(
|
|
tester.getSemantics(find.text('ABC')),
|
|
matchesSemantics(
|
|
label: 'ABC${kIsWeb ? '' : '\nTab 3 of 3'}',
|
|
textDirection: TextDirection.ltr,
|
|
isFocusable: true,
|
|
isButton: true,
|
|
hasSelectedState: true,
|
|
hasEnabledState: true,
|
|
isEnabled: true,
|
|
hasTapAction: true,
|
|
hasFocusAction: true,
|
|
),
|
|
);
|
|
|
|
await tester.pumpWidget(widget(selectedIndex: 1));
|
|
|
|
expect(
|
|
tester.getSemantics(find.text('AC')),
|
|
matchesSemantics(
|
|
label: 'AC${kIsWeb ? '' : '\nTab 1 of 3'}',
|
|
textDirection: TextDirection.ltr,
|
|
isFocusable: true,
|
|
isButton: true,
|
|
hasEnabledState: true,
|
|
hasSelectedState: true,
|
|
isEnabled: true,
|
|
hasTapAction: true,
|
|
hasFocusAction: true,
|
|
),
|
|
);
|
|
expect(
|
|
tester.getSemantics(find.text('Alarm')),
|
|
matchesSemantics(
|
|
label: 'Alarm${kIsWeb ? '' : '\nTab 2 of 3'}',
|
|
textDirection: TextDirection.ltr,
|
|
isFocusable: true,
|
|
isSelected: true,
|
|
isButton: true,
|
|
hasEnabledState: true,
|
|
hasSelectedState: true,
|
|
isEnabled: true,
|
|
hasTapAction: true,
|
|
hasFocusAction: true,
|
|
),
|
|
);
|
|
expect(
|
|
tester.getSemantics(find.text('ABC')),
|
|
matchesSemantics(
|
|
label: 'ABC${kIsWeb ? '' : '\nTab 3 of 3'}',
|
|
textDirection: TextDirection.ltr,
|
|
isFocusable: true,
|
|
isButton: true,
|
|
hasEnabledState: true,
|
|
hasSelectedState: true,
|
|
isEnabled: true,
|
|
hasTapAction: true,
|
|
hasFocusAction: true,
|
|
),
|
|
);
|
|
});
|
|
testWidgets('Navigation bar disabled semantics', (WidgetTester tester) async {
|
|
Widget widget({int selectedIndex = 0}) {
|
|
return _buildWidget(
|
|
NavigationBar(
|
|
selectedIndex: selectedIndex,
|
|
destinations: const <Widget>[
|
|
NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC', enabled: false),
|
|
NavigationDestination(icon: Icon(Icons.ac_unit), label: 'Another'),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(widget());
|
|
|
|
expect(
|
|
tester.getSemantics(find.text('AC')),
|
|
matchesSemantics(
|
|
label: 'AC${kIsWeb ? '' : '\nTab 1 of 2'}',
|
|
textDirection: TextDirection.ltr,
|
|
isSelected: true,
|
|
hasSelectedState: true,
|
|
hasEnabledState: true,
|
|
isButton: true,
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets('Navigation bar semantics with some labels hidden', (WidgetTester tester) async {
|
|
Widget widget({int selectedIndex = 0}) {
|
|
return _buildWidget(
|
|
NavigationBar(
|
|
labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected,
|
|
selectedIndex: selectedIndex,
|
|
destinations: const <Widget>[
|
|
NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'),
|
|
NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(widget());
|
|
|
|
expect(
|
|
tester.getSemantics(find.text('AC')),
|
|
matchesSemantics(
|
|
label: 'AC${kIsWeb ? '' : '\nTab 1 of 2'}',
|
|
textDirection: TextDirection.ltr,
|
|
isFocusable: true,
|
|
isSelected: true,
|
|
isButton: true,
|
|
hasEnabledState: true,
|
|
hasSelectedState: true,
|
|
isEnabled: true,
|
|
hasTapAction: true,
|
|
hasFocusAction: true,
|
|
),
|
|
);
|
|
expect(
|
|
tester.getSemantics(find.text('Alarm')),
|
|
matchesSemantics(
|
|
label: 'Alarm${kIsWeb ? '' : '\nTab 2 of 2'}',
|
|
textDirection: TextDirection.ltr,
|
|
isFocusable: true,
|
|
isButton: true,
|
|
hasEnabledState: true,
|
|
hasSelectedState: true,
|
|
isEnabled: true,
|
|
hasTapAction: true,
|
|
hasFocusAction: true,
|
|
),
|
|
);
|
|
|
|
await tester.pumpWidget(widget(selectedIndex: 1));
|
|
|
|
expect(
|
|
tester.getSemantics(find.text('AC')),
|
|
matchesSemantics(
|
|
label: 'AC${kIsWeb ? '' : '\nTab 1 of 2'}',
|
|
textDirection: TextDirection.ltr,
|
|
isFocusable: true,
|
|
isButton: true,
|
|
hasEnabledState: true,
|
|
hasSelectedState: true,
|
|
isEnabled: true,
|
|
hasTapAction: true,
|
|
hasFocusAction: true,
|
|
),
|
|
);
|
|
expect(
|
|
tester.getSemantics(find.text('Alarm')),
|
|
matchesSemantics(
|
|
label: 'Alarm${kIsWeb ? '' : '\nTab 2 of 2'}',
|
|
textDirection: TextDirection.ltr,
|
|
isFocusable: true,
|
|
hasEnabledState: true,
|
|
hasSelectedState: true,
|
|
isEnabled: true,
|
|
isSelected: true,
|
|
isButton: true,
|
|
hasTapAction: true,
|
|
hasFocusAction: true,
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets('Navigation bar does not grow with text scale factor', (WidgetTester tester) async {
|
|
const animationMilliseconds = 800;
|
|
|
|
Widget widget({TextScaler textScaler = TextScaler.noScaling}) {
|
|
return _buildWidget(
|
|
MediaQuery(
|
|
data: MediaQueryData(textScaler: textScaler),
|
|
child: NavigationBar(
|
|
animationDuration: const Duration(milliseconds: animationMilliseconds),
|
|
destinations: const <NavigationDestination>[
|
|
NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'),
|
|
NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(widget());
|
|
final double initialHeight = tester.getSize(find.byType(NavigationBar)).height;
|
|
|
|
await tester.pumpWidget(widget(textScaler: const TextScaler.linear(2)));
|
|
final double newHeight = tester.getSize(find.byType(NavigationBar)).height;
|
|
|
|
expect(newHeight, equals(initialHeight));
|
|
});
|
|
|
|
testWidgets('Material3 - Navigation indicator renders ripple', (WidgetTester tester) async {
|
|
// This is a regression test for https://github.com/flutter/flutter/issues/116751.
|
|
var selectedIndex = 0;
|
|
|
|
Widget buildWidget({NavigationDestinationLabelBehavior? labelBehavior}) {
|
|
return MaterialApp(
|
|
home: Scaffold(
|
|
bottomNavigationBar: Center(
|
|
child: NavigationBar(
|
|
selectedIndex: selectedIndex,
|
|
labelBehavior: labelBehavior,
|
|
destinations: const <Widget>[
|
|
NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'),
|
|
NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'),
|
|
],
|
|
onDestinationSelected: (int i) {},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildWidget());
|
|
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await gesture.addPointer();
|
|
await gesture.moveTo(tester.getCenter(find.byIcon(Icons.access_alarm)));
|
|
await tester.pumpAndSettle();
|
|
|
|
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere(
|
|
(RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures',
|
|
);
|
|
var indicatorCenter = const Offset(600, 30);
|
|
const includedIndicatorSize = Size(64, 32);
|
|
const excludedIndicatorSize = Size(74, 40);
|
|
|
|
// Test ripple when NavigationBar is using `NavigationDestinationLabelBehavior.alwaysShow` (default).
|
|
expect(
|
|
inkFeatures,
|
|
paints
|
|
..clipPath(
|
|
pathMatcher: isPathThat(
|
|
includes: <Offset>[
|
|
// Left center.
|
|
Offset(indicatorCenter.dx - (includedIndicatorSize.width / 2), indicatorCenter.dy),
|
|
// Top center.
|
|
Offset(indicatorCenter.dx, indicatorCenter.dy - (includedIndicatorSize.height / 2)),
|
|
// Right center.
|
|
Offset(indicatorCenter.dx + (includedIndicatorSize.width / 2), indicatorCenter.dy),
|
|
// Bottom center.
|
|
Offset(indicatorCenter.dx, indicatorCenter.dy + (includedIndicatorSize.height / 2)),
|
|
],
|
|
excludes: <Offset>[
|
|
// Left center.
|
|
Offset(indicatorCenter.dx - (excludedIndicatorSize.width / 2), indicatorCenter.dy),
|
|
// Top center.
|
|
Offset(indicatorCenter.dx, indicatorCenter.dy - (excludedIndicatorSize.height / 2)),
|
|
// Right center.
|
|
Offset(indicatorCenter.dx + (excludedIndicatorSize.width / 2), indicatorCenter.dy),
|
|
// Bottom center.
|
|
Offset(indicatorCenter.dx, indicatorCenter.dy + (excludedIndicatorSize.height / 2)),
|
|
],
|
|
),
|
|
)
|
|
..circle(
|
|
x: indicatorCenter.dx,
|
|
y: indicatorCenter.dy,
|
|
radius: 35.0,
|
|
color: const Color(0x0a000000),
|
|
),
|
|
);
|
|
|
|
// Test ripple when NavigationBar is using `NavigationDestinationLabelBehavior.alwaysHide`.
|
|
await tester.pumpWidget(
|
|
buildWidget(labelBehavior: NavigationDestinationLabelBehavior.alwaysHide),
|
|
);
|
|
await gesture.moveTo(tester.getCenter(find.byIcon(Icons.access_alarm)));
|
|
await tester.pumpAndSettle();
|
|
|
|
indicatorCenter = const Offset(600, 40);
|
|
|
|
expect(
|
|
inkFeatures,
|
|
paints
|
|
..clipPath(
|
|
pathMatcher: isPathThat(
|
|
includes: <Offset>[
|
|
// Left center.
|
|
Offset(indicatorCenter.dx - (includedIndicatorSize.width / 2), indicatorCenter.dy),
|
|
// Top center.
|
|
Offset(indicatorCenter.dx, indicatorCenter.dy - (includedIndicatorSize.height / 2)),
|
|
// Right center.
|
|
Offset(indicatorCenter.dx + (includedIndicatorSize.width / 2), indicatorCenter.dy),
|
|
// Bottom center.
|
|
Offset(indicatorCenter.dx, indicatorCenter.dy + (includedIndicatorSize.height / 2)),
|
|
],
|
|
excludes: <Offset>[
|
|
// Left center.
|
|
Offset(indicatorCenter.dx - (excludedIndicatorSize.width / 2), indicatorCenter.dy),
|
|
// Top center.
|
|
Offset(indicatorCenter.dx, indicatorCenter.dy - (excludedIndicatorSize.height / 2)),
|
|
// Right center.
|
|
Offset(indicatorCenter.dx + (excludedIndicatorSize.width / 2), indicatorCenter.dy),
|
|
// Bottom center.
|
|
Offset(indicatorCenter.dx, indicatorCenter.dy + (excludedIndicatorSize.height / 2)),
|
|
],
|
|
),
|
|
)
|
|
..circle(
|
|
x: indicatorCenter.dx,
|
|
y: indicatorCenter.dy,
|
|
radius: 35.0,
|
|
color: const Color(0x0a000000),
|
|
),
|
|
);
|
|
|
|
// Test ripple when NavigationBar is using `NavigationDestinationLabelBehavior.onlyShowSelected`.
|
|
await tester.pumpWidget(
|
|
buildWidget(labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected),
|
|
);
|
|
await gesture.moveTo(tester.getCenter(find.byIcon(Icons.access_alarm)));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
inkFeatures,
|
|
paints
|
|
..clipPath(
|
|
pathMatcher: isPathThat(
|
|
includes: <Offset>[
|
|
// Left center.
|
|
Offset(indicatorCenter.dx - (includedIndicatorSize.width / 2), indicatorCenter.dy),
|
|
// Top center.
|
|
Offset(indicatorCenter.dx, indicatorCenter.dy - (includedIndicatorSize.height / 2)),
|
|
// Right center.
|
|
Offset(indicatorCenter.dx + (includedIndicatorSize.width / 2), indicatorCenter.dy),
|
|
// Bottom center.
|
|
Offset(indicatorCenter.dx, indicatorCenter.dy + (includedIndicatorSize.height / 2)),
|
|
],
|
|
excludes: <Offset>[
|
|
// Left center.
|
|
Offset(indicatorCenter.dx - (excludedIndicatorSize.width / 2), indicatorCenter.dy),
|
|
// Top center.
|
|
Offset(indicatorCenter.dx, indicatorCenter.dy - (excludedIndicatorSize.height / 2)),
|
|
// Right center.
|
|
Offset(indicatorCenter.dx + (excludedIndicatorSize.width / 2), indicatorCenter.dy),
|
|
// Bottom center.
|
|
Offset(indicatorCenter.dx, indicatorCenter.dy + (excludedIndicatorSize.height / 2)),
|
|
],
|
|
),
|
|
)
|
|
..circle(
|
|
x: indicatorCenter.dx,
|
|
y: indicatorCenter.dy,
|
|
radius: 35.0,
|
|
color: const Color(0x0a000000),
|
|
),
|
|
);
|
|
|
|
// Make sure ripple is shifted when selectedIndex changes.
|
|
selectedIndex = 1;
|
|
await tester.pumpWidget(
|
|
buildWidget(labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
indicatorCenter = const Offset(600, 30);
|
|
|
|
expect(
|
|
inkFeatures,
|
|
paints
|
|
..clipPath(
|
|
pathMatcher: isPathThat(
|
|
includes: <Offset>[
|
|
// Left center.
|
|
Offset(indicatorCenter.dx - (includedIndicatorSize.width / 2), indicatorCenter.dy),
|
|
// Top center.
|
|
Offset(indicatorCenter.dx, indicatorCenter.dy - (includedIndicatorSize.height / 2)),
|
|
// Right center.
|
|
Offset(indicatorCenter.dx + (includedIndicatorSize.width / 2), indicatorCenter.dy),
|
|
// Bottom center.
|
|
Offset(indicatorCenter.dx, indicatorCenter.dy + (includedIndicatorSize.height / 2)),
|
|
],
|
|
excludes: <Offset>[
|
|
// Left center.
|
|
Offset(indicatorCenter.dx - (excludedIndicatorSize.width / 2), indicatorCenter.dy),
|
|
// Top center.
|
|
Offset(indicatorCenter.dx, indicatorCenter.dy - (excludedIndicatorSize.height / 2)),
|
|
// Right center.
|
|
Offset(indicatorCenter.dx + (excludedIndicatorSize.width / 2), indicatorCenter.dy),
|
|
// Bottom center.
|
|
Offset(indicatorCenter.dx, indicatorCenter.dy + (excludedIndicatorSize.height / 2)),
|
|
],
|
|
),
|
|
)
|
|
..circle(
|
|
x: indicatorCenter.dx,
|
|
y: indicatorCenter.dy,
|
|
radius: 35.0,
|
|
color: const Color(0x0a000000),
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets('Material3 - Navigation indicator ripple golden test', (WidgetTester tester) async {
|
|
// This is a regression test for https://github.com/flutter/flutter/issues/117420.
|
|
|
|
Widget buildWidget({NavigationDestinationLabelBehavior? labelBehavior}) {
|
|
return MaterialApp(
|
|
home: Scaffold(
|
|
bottomNavigationBar: Center(
|
|
child: NavigationBar(
|
|
labelBehavior: labelBehavior,
|
|
destinations: const <Widget>[
|
|
NavigationDestination(icon: SizedBox(), label: 'AC'),
|
|
NavigationDestination(icon: SizedBox(), label: 'Alarm'),
|
|
],
|
|
onDestinationSelected: (int i) {},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildWidget());
|
|
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await gesture.addPointer();
|
|
await gesture.moveTo(tester.getCenter(find.byType(NavigationDestination).last));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Test ripple when NavigationBar is using `NavigationDestinationLabelBehavior.alwaysShow` (default).
|
|
await expectLater(find.byType(NavigationBar), matchesGoldenFile('indicator_alwaysShow_m3.png'));
|
|
|
|
// Test ripple when NavigationBar is using `NavigationDestinationLabelBehavior.alwaysHide`.
|
|
await tester.pumpWidget(
|
|
buildWidget(labelBehavior: NavigationDestinationLabelBehavior.alwaysHide),
|
|
);
|
|
await gesture.moveTo(tester.getCenter(find.byType(NavigationDestination).last));
|
|
await tester.pumpAndSettle();
|
|
|
|
await expectLater(find.byType(NavigationBar), matchesGoldenFile('indicator_alwaysHide_m3.png'));
|
|
|
|
// Test ripple when NavigationBar is using `NavigationDestinationLabelBehavior.onlyShowSelected`.
|
|
await tester.pumpWidget(
|
|
buildWidget(labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected),
|
|
);
|
|
await gesture.moveTo(tester.getCenter(find.byType(NavigationDestination).first));
|
|
await tester.pumpAndSettle();
|
|
|
|
await expectLater(
|
|
find.byType(NavigationBar),
|
|
matchesGoldenFile('indicator_onlyShowSelected_selected_m3.png'),
|
|
);
|
|
|
|
await gesture.moveTo(tester.getCenter(find.byType(NavigationDestination).last));
|
|
await tester.pumpAndSettle();
|
|
|
|
await expectLater(
|
|
find.byType(NavigationBar),
|
|
matchesGoldenFile('indicator_onlyShowSelected_unselected_m3.png'),
|
|
);
|
|
});
|
|
|
|
// Regression test for https://github.com/flutter/flutter/issues/169249.
|
|
testWidgets('Material3 - Navigation indicator moves to selected item', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final theme = ThemeData();
|
|
var index = 0;
|
|
|
|
Widget buildNavigationBar({Color? indicatorColor, ShapeBorder? indicatorShape}) {
|
|
return MaterialApp(
|
|
theme: theme,
|
|
home: Scaffold(
|
|
bottomNavigationBar: RepaintBoundary(
|
|
child: NavigationBar(
|
|
indicatorColor: indicatorColor,
|
|
indicatorShape: indicatorShape,
|
|
selectedIndex: index,
|
|
destinations: const <Widget>[
|
|
NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'),
|
|
NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'),
|
|
],
|
|
onDestinationSelected: (int i) {},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildNavigationBar());
|
|
|
|
// Move the selection to the second destination.
|
|
index = 1;
|
|
await tester.pumpWidget(buildNavigationBar());
|
|
await tester.pumpAndSettle();
|
|
|
|
// The navigation indicator should be on the second item.
|
|
await expectLater(
|
|
find.byType(NavigationBar),
|
|
matchesGoldenFile('m3.navigation_bar.indicator.ink.position.png'),
|
|
);
|
|
});
|
|
|
|
testWidgets('Navigation indicator scale transform', (WidgetTester tester) async {
|
|
var selectedIndex = 0;
|
|
|
|
Widget buildNavigationBar() {
|
|
return MaterialApp(
|
|
home: Scaffold(
|
|
bottomNavigationBar: Center(
|
|
child: NavigationBar(
|
|
selectedIndex: selectedIndex,
|
|
destinations: const <Widget>[
|
|
NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'),
|
|
NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'),
|
|
],
|
|
onDestinationSelected: (int i) {},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildNavigationBar());
|
|
await tester.pumpAndSettle();
|
|
final Finder transformFinder = find
|
|
.descendant(of: find.byType(NavigationIndicator), matching: find.byType(Transform))
|
|
.last;
|
|
Matrix4 transform = tester.widget<Transform>(transformFinder).transform;
|
|
expect(transform.getColumn(0)[0], 0.0);
|
|
|
|
selectedIndex = 1;
|
|
await tester.pumpWidget(buildNavigationBar());
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
transform = tester.widget<Transform>(transformFinder).transform;
|
|
expect(transform.getColumn(0)[0], closeTo(0.7805849514007568, precisionErrorTolerance));
|
|
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
transform = tester.widget<Transform>(transformFinder).transform;
|
|
expect(transform.getColumn(0)[0], closeTo(0.9473570239543915, precisionErrorTolerance));
|
|
|
|
await tester.pumpAndSettle();
|
|
transform = tester.widget<Transform>(transformFinder).transform;
|
|
expect(transform.getColumn(0)[0], 1.0);
|
|
});
|
|
|
|
testWidgets('Material3 - Navigation destination updates indicator color and shape', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final theme = ThemeData();
|
|
const color = Color(0xff0000ff);
|
|
const ShapeBorder shape = RoundedRectangleBorder();
|
|
|
|
Widget buildNavigationBar({Color? indicatorColor, ShapeBorder? indicatorShape}) {
|
|
return MaterialApp(
|
|
theme: theme,
|
|
home: Scaffold(
|
|
bottomNavigationBar: RepaintBoundary(
|
|
child: NavigationBar(
|
|
indicatorColor: indicatorColor,
|
|
indicatorShape: indicatorShape,
|
|
destinations: const <Widget>[
|
|
NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'),
|
|
NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'),
|
|
],
|
|
onDestinationSelected: (int i) {},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildNavigationBar());
|
|
|
|
// Test default indicator color and shape.
|
|
expect(_getIndicatorDecoration(tester)?.color, theme.colorScheme.secondaryContainer);
|
|
expect(_getIndicatorDecoration(tester)?.shape, const StadiumBorder());
|
|
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await gesture.addPointer();
|
|
await gesture.moveTo(tester.getCenter(find.byType(NavigationIndicator).last));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Test default indicator color and shape with ripple.
|
|
await expectLater(
|
|
find.byType(NavigationBar),
|
|
matchesGoldenFile('m3.navigation_bar.default.indicator.inkwell.shape.png'),
|
|
);
|
|
|
|
await tester.pumpWidget(buildNavigationBar(indicatorColor: color, indicatorShape: shape));
|
|
|
|
// Test custom indicator color and shape.
|
|
expect(_getIndicatorDecoration(tester)?.color, color);
|
|
expect(_getIndicatorDecoration(tester)?.shape, shape);
|
|
|
|
// Test custom indicator color and shape with ripple.
|
|
await expectLater(
|
|
find.byType(NavigationBar),
|
|
matchesGoldenFile('m3.navigation_bar.custom.indicator.inkwell.shape.png'),
|
|
);
|
|
});
|
|
|
|
testWidgets('Destinations respect their disabled state', (WidgetTester tester) async {
|
|
var selectedIndex = 0;
|
|
|
|
await tester.pumpWidget(
|
|
_buildWidget(
|
|
NavigationBar(
|
|
destinations: const <Widget>[
|
|
NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'),
|
|
NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'),
|
|
NavigationDestination(icon: Icon(Icons.bookmark), label: 'Bookmark', enabled: false),
|
|
],
|
|
onDestinationSelected: (int i) => selectedIndex = i,
|
|
selectedIndex: selectedIndex,
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.tap(find.text('AC'));
|
|
expect(selectedIndex, 0);
|
|
|
|
await tester.tap(find.text('Alarm'));
|
|
expect(selectedIndex, 1);
|
|
|
|
await tester.tap(find.text('Bookmark'));
|
|
expect(selectedIndex, 1);
|
|
});
|
|
|
|
testWidgets('NavigationBar respects overlayColor in active/pressed/hovered states', (
|
|
WidgetTester tester,
|
|
) async {
|
|
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
|
const hoverColor = Color(0xff0000ff);
|
|
const focusColor = Color(0xff00ffff);
|
|
const pressedColor = Color(0xffff00ff);
|
|
final WidgetStateProperty<Color?> overlayColor = WidgetStateProperty.resolveWith<Color>((
|
|
Set<WidgetState> states,
|
|
) {
|
|
if (states.contains(WidgetState.hovered)) {
|
|
return hoverColor;
|
|
}
|
|
if (states.contains(WidgetState.focused)) {
|
|
return focusColor;
|
|
}
|
|
if (states.contains(WidgetState.pressed)) {
|
|
return pressedColor;
|
|
}
|
|
return Colors.transparent;
|
|
});
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
bottomNavigationBar: RepaintBoundary(
|
|
child: NavigationBar(
|
|
overlayColor: overlayColor,
|
|
destinations: const <Widget>[
|
|
NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'),
|
|
NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'),
|
|
],
|
|
onDestinationSelected: (int i) {},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await gesture.addPointer();
|
|
await gesture.moveTo(tester.getCenter(find.byType(NavigationIndicator).last));
|
|
await tester.pumpAndSettle();
|
|
|
|
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere(
|
|
(RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures',
|
|
);
|
|
|
|
// Test hovered state.
|
|
expect(
|
|
inkFeatures,
|
|
kIsWeb
|
|
? (paints
|
|
..rrect()
|
|
..rrect()
|
|
..circle(color: hoverColor))
|
|
: (paints..circle(color: hoverColor)),
|
|
);
|
|
|
|
await gesture.down(tester.getCenter(find.byType(NavigationIndicator).last));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Test pressed state.
|
|
expect(
|
|
inkFeatures,
|
|
kIsWeb
|
|
? (paints
|
|
..circle()
|
|
..circle()
|
|
..circle(color: pressedColor))
|
|
: (paints
|
|
..circle()
|
|
..circle(color: pressedColor)),
|
|
);
|
|
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
|
|
// Press tab to focus the navigation bar.
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.tab);
|
|
await tester.pumpAndSettle();
|
|
|
|
// Test focused state.
|
|
expect(
|
|
inkFeatures,
|
|
kIsWeb
|
|
? (paints
|
|
..circle()
|
|
..circle(color: focusColor))
|
|
: (paints
|
|
..circle()
|
|
..circle(color: focusColor)),
|
|
);
|
|
});
|
|
|
|
testWidgets('NavigationBar.labelPadding overrides NavigationDestination.label padding', (
|
|
WidgetTester tester,
|
|
) async {
|
|
const EdgeInsetsGeometry labelPadding = EdgeInsets.all(8);
|
|
Widget buildNavigationBar({EdgeInsetsGeometry? labelPadding}) {
|
|
return MaterialApp(
|
|
home: Scaffold(
|
|
bottomNavigationBar: NavigationBar(
|
|
labelPadding: labelPadding,
|
|
destinations: const <Widget>[
|
|
NavigationDestination(icon: Icon(Icons.home), label: 'Home'),
|
|
NavigationDestination(icon: Icon(Icons.settings), label: 'Settings'),
|
|
],
|
|
onDestinationSelected: (int i) {},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildNavigationBar());
|
|
expect(_getLabelPadding(tester, 'Home'), const EdgeInsets.only(top: 4));
|
|
expect(_getLabelPadding(tester, 'Settings'), const EdgeInsets.only(top: 4));
|
|
|
|
await tester.pumpWidget(buildNavigationBar(labelPadding: labelPadding));
|
|
expect(_getLabelPadding(tester, 'Home'), labelPadding);
|
|
expect(_getLabelPadding(tester, 'Settings'), labelPadding);
|
|
});
|
|
|
|
group('Material 2', () {
|
|
// These tests are only relevant for Material 2. Once Material 2
|
|
// support is deprecated and the APIs are removed, these tests
|
|
// can be deleted.
|
|
|
|
testWidgets('Material2 - Navigation destination updates indicator color and shape', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final theme = ThemeData(useMaterial3: false);
|
|
const color = Color(0xff0000ff);
|
|
const ShapeBorder shape = RoundedRectangleBorder();
|
|
|
|
Widget buildNavigationBar({Color? indicatorColor, ShapeBorder? indicatorShape}) {
|
|
return MaterialApp(
|
|
theme: theme,
|
|
home: Scaffold(
|
|
bottomNavigationBar: NavigationBar(
|
|
indicatorColor: indicatorColor,
|
|
indicatorShape: indicatorShape,
|
|
destinations: const <Widget>[
|
|
NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'),
|
|
NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'),
|
|
],
|
|
onDestinationSelected: (int i) {},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildNavigationBar());
|
|
|
|
// Test default indicator color and shape.
|
|
expect(_getIndicatorDecoration(tester)?.color, theme.colorScheme.secondary.withOpacity(0.24));
|
|
expect(
|
|
_getIndicatorDecoration(tester)?.shape,
|
|
const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16))),
|
|
);
|
|
|
|
await tester.pumpWidget(buildNavigationBar(indicatorColor: color, indicatorShape: shape));
|
|
|
|
// Test custom indicator color and shape.
|
|
expect(_getIndicatorDecoration(tester)?.color, color);
|
|
expect(_getIndicatorDecoration(tester)?.shape, shape);
|
|
});
|
|
|
|
testWidgets('Material2 - Navigation indicator renders ripple', (WidgetTester tester) async {
|
|
// This is a regression test for https://github.com/flutter/flutter/issues/116751.
|
|
var selectedIndex = 0;
|
|
|
|
Widget buildWidget({NavigationDestinationLabelBehavior? labelBehavior}) {
|
|
return MaterialApp(
|
|
theme: ThemeData(useMaterial3: false),
|
|
home: Scaffold(
|
|
bottomNavigationBar: Center(
|
|
child: NavigationBar(
|
|
selectedIndex: selectedIndex,
|
|
labelBehavior: labelBehavior,
|
|
destinations: const <Widget>[
|
|
NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'),
|
|
NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'),
|
|
],
|
|
onDestinationSelected: (int i) {},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildWidget());
|
|
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await gesture.addPointer();
|
|
await gesture.moveTo(tester.getCenter(find.byIcon(Icons.access_alarm)));
|
|
await tester.pumpAndSettle();
|
|
|
|
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere(
|
|
(RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures',
|
|
);
|
|
var indicatorCenter = const Offset(600, 33);
|
|
const includedIndicatorSize = Size(64, 32);
|
|
const excludedIndicatorSize = Size(74, 40);
|
|
|
|
// Test ripple when NavigationBar is using `NavigationDestinationLabelBehavior.alwaysShow` (default).
|
|
expect(
|
|
inkFeatures,
|
|
paints
|
|
..clipPath(
|
|
pathMatcher: isPathThat(
|
|
includes: <Offset>[
|
|
// Left center.
|
|
Offset(indicatorCenter.dx - (includedIndicatorSize.width / 2), indicatorCenter.dy),
|
|
// Top center.
|
|
Offset(indicatorCenter.dx, indicatorCenter.dy - (includedIndicatorSize.height / 2)),
|
|
// Right center.
|
|
Offset(indicatorCenter.dx + (includedIndicatorSize.width / 2), indicatorCenter.dy),
|
|
// Bottom center.
|
|
Offset(indicatorCenter.dx, indicatorCenter.dy + (includedIndicatorSize.height / 2)),
|
|
],
|
|
excludes: <Offset>[
|
|
// Left center.
|
|
Offset(indicatorCenter.dx - (excludedIndicatorSize.width / 2), indicatorCenter.dy),
|
|
// Top center.
|
|
Offset(indicatorCenter.dx, indicatorCenter.dy - (excludedIndicatorSize.height / 2)),
|
|
// Right center.
|
|
Offset(indicatorCenter.dx + (excludedIndicatorSize.width / 2), indicatorCenter.dy),
|
|
// Bottom center.
|
|
Offset(indicatorCenter.dx, indicatorCenter.dy + (excludedIndicatorSize.height / 2)),
|
|
],
|
|
),
|
|
)
|
|
..circle(
|
|
x: indicatorCenter.dx,
|
|
y: indicatorCenter.dy,
|
|
radius: 35.0,
|
|
color: const Color(0x0a000000),
|
|
),
|
|
);
|
|
|
|
// Test ripple when NavigationBar is using `NavigationDestinationLabelBehavior.alwaysHide`.
|
|
await tester.pumpWidget(
|
|
buildWidget(labelBehavior: NavigationDestinationLabelBehavior.alwaysHide),
|
|
);
|
|
await gesture.moveTo(tester.getCenter(find.byIcon(Icons.access_alarm)));
|
|
await tester.pumpAndSettle();
|
|
|
|
indicatorCenter = const Offset(600, 40);
|
|
|
|
expect(
|
|
inkFeatures,
|
|
paints
|
|
..clipPath(
|
|
pathMatcher: isPathThat(
|
|
includes: <Offset>[
|
|
// Left center.
|
|
Offset(indicatorCenter.dx - (includedIndicatorSize.width / 2), indicatorCenter.dy),
|
|
// Top center.
|
|
Offset(indicatorCenter.dx, indicatorCenter.dy - (includedIndicatorSize.height / 2)),
|
|
// Right center.
|
|
Offset(indicatorCenter.dx + (includedIndicatorSize.width / 2), indicatorCenter.dy),
|
|
// Bottom center.
|
|
Offset(indicatorCenter.dx, indicatorCenter.dy + (includedIndicatorSize.height / 2)),
|
|
],
|
|
excludes: <Offset>[
|
|
// Left center.
|
|
Offset(indicatorCenter.dx - (excludedIndicatorSize.width / 2), indicatorCenter.dy),
|
|
// Top center.
|
|
Offset(indicatorCenter.dx, indicatorCenter.dy - (excludedIndicatorSize.height / 2)),
|
|
// Right center.
|
|
Offset(indicatorCenter.dx + (excludedIndicatorSize.width / 2), indicatorCenter.dy),
|
|
// Bottom center.
|
|
Offset(indicatorCenter.dx, indicatorCenter.dy + (excludedIndicatorSize.height / 2)),
|
|
],
|
|
),
|
|
)
|
|
..circle(
|
|
x: indicatorCenter.dx,
|
|
y: indicatorCenter.dy,
|
|
radius: 35.0,
|
|
color: const Color(0x0a000000),
|
|
),
|
|
);
|
|
|
|
// Test ripple when NavigationBar is using `NavigationDestinationLabelBehavior.onlyShowSelected`.
|
|
await tester.pumpWidget(
|
|
buildWidget(labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected),
|
|
);
|
|
await gesture.moveTo(tester.getCenter(find.byIcon(Icons.access_alarm)));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
inkFeatures,
|
|
paints
|
|
..clipPath(
|
|
pathMatcher: isPathThat(
|
|
includes: <Offset>[
|
|
// Left center.
|
|
Offset(indicatorCenter.dx - (includedIndicatorSize.width / 2), indicatorCenter.dy),
|
|
// Top center.
|
|
Offset(indicatorCenter.dx, indicatorCenter.dy - (includedIndicatorSize.height / 2)),
|
|
// Right center.
|
|
Offset(indicatorCenter.dx + (includedIndicatorSize.width / 2), indicatorCenter.dy),
|
|
// Bottom center.
|
|
Offset(indicatorCenter.dx, indicatorCenter.dy + (includedIndicatorSize.height / 2)),
|
|
],
|
|
excludes: <Offset>[
|
|
// Left center.
|
|
Offset(indicatorCenter.dx - (excludedIndicatorSize.width / 2), indicatorCenter.dy),
|
|
// Top center.
|
|
Offset(indicatorCenter.dx, indicatorCenter.dy - (excludedIndicatorSize.height / 2)),
|
|
// Right center.
|
|
Offset(indicatorCenter.dx + (excludedIndicatorSize.width / 2), indicatorCenter.dy),
|
|
// Bottom center.
|
|
Offset(indicatorCenter.dx, indicatorCenter.dy + (excludedIndicatorSize.height / 2)),
|
|
],
|
|
),
|
|
)
|
|
..circle(
|
|
x: indicatorCenter.dx,
|
|
y: indicatorCenter.dy,
|
|
radius: 35.0,
|
|
color: const Color(0x0a000000),
|
|
),
|
|
);
|
|
|
|
// Make sure ripple is shifted when selectedIndex changes.
|
|
selectedIndex = 1;
|
|
await tester.pumpWidget(
|
|
buildWidget(labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
indicatorCenter = const Offset(600, 33);
|
|
|
|
expect(
|
|
inkFeatures,
|
|
paints
|
|
..clipPath(
|
|
pathMatcher: isPathThat(
|
|
includes: <Offset>[
|
|
// Left center.
|
|
Offset(indicatorCenter.dx - (includedIndicatorSize.width / 2), indicatorCenter.dy),
|
|
// Top center.
|
|
Offset(indicatorCenter.dx, indicatorCenter.dy - (includedIndicatorSize.height / 2)),
|
|
// Right center.
|
|
Offset(indicatorCenter.dx + (includedIndicatorSize.width / 2), indicatorCenter.dy),
|
|
// Bottom center.
|
|
Offset(indicatorCenter.dx, indicatorCenter.dy + (includedIndicatorSize.height / 2)),
|
|
],
|
|
excludes: <Offset>[
|
|
// Left center.
|
|
Offset(indicatorCenter.dx - (excludedIndicatorSize.width / 2), indicatorCenter.dy),
|
|
// Top center.
|
|
Offset(indicatorCenter.dx, indicatorCenter.dy - (excludedIndicatorSize.height / 2)),
|
|
// Right center.
|
|
Offset(indicatorCenter.dx + (excludedIndicatorSize.width / 2), indicatorCenter.dy),
|
|
// Bottom center.
|
|
Offset(indicatorCenter.dx, indicatorCenter.dy + (excludedIndicatorSize.height / 2)),
|
|
],
|
|
),
|
|
)
|
|
..circle(
|
|
x: indicatorCenter.dx,
|
|
y: indicatorCenter.dy,
|
|
radius: 35.0,
|
|
color: const Color(0x0a000000),
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets('Material2 - Navigation indicator ripple golden test', (WidgetTester tester) async {
|
|
// This is a regression test for https://github.com/flutter/flutter/issues/117420.
|
|
|
|
Widget buildWidget({NavigationDestinationLabelBehavior? labelBehavior}) {
|
|
return MaterialApp(
|
|
theme: ThemeData(useMaterial3: false),
|
|
home: Scaffold(
|
|
bottomNavigationBar: Center(
|
|
child: NavigationBar(
|
|
labelBehavior: labelBehavior,
|
|
destinations: const <Widget>[
|
|
NavigationDestination(icon: SizedBox(), label: 'AC'),
|
|
NavigationDestination(icon: SizedBox(), label: 'Alarm'),
|
|
],
|
|
onDestinationSelected: (int i) {},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildWidget());
|
|
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await gesture.addPointer();
|
|
await gesture.moveTo(tester.getCenter(find.byType(NavigationDestination).last));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Test ripple when NavigationBar is using `NavigationDestinationLabelBehavior.alwaysShow` (default).
|
|
await expectLater(
|
|
find.byType(NavigationBar),
|
|
matchesGoldenFile('indicator_alwaysShow_m2.png'),
|
|
);
|
|
|
|
// Test ripple when NavigationBar is using `NavigationDestinationLabelBehavior.alwaysHide`.
|
|
await tester.pumpWidget(
|
|
buildWidget(labelBehavior: NavigationDestinationLabelBehavior.alwaysHide),
|
|
);
|
|
await gesture.moveTo(tester.getCenter(find.byType(NavigationDestination).last));
|
|
await tester.pumpAndSettle();
|
|
|
|
await expectLater(
|
|
find.byType(NavigationBar),
|
|
matchesGoldenFile('indicator_alwaysHide_m2.png'),
|
|
);
|
|
|
|
// Test ripple when NavigationBar is using `NavigationDestinationLabelBehavior.onlyShowSelected`.
|
|
await tester.pumpWidget(
|
|
buildWidget(labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected),
|
|
);
|
|
await gesture.moveTo(tester.getCenter(find.byType(NavigationDestination).first));
|
|
await tester.pumpAndSettle();
|
|
|
|
await expectLater(
|
|
find.byType(NavigationBar),
|
|
matchesGoldenFile('indicator_onlyShowSelected_selected_m2.png'),
|
|
);
|
|
|
|
await gesture.moveTo(tester.getCenter(find.byType(NavigationDestination).last));
|
|
await tester.pumpAndSettle();
|
|
|
|
await expectLater(
|
|
find.byType(NavigationBar),
|
|
matchesGoldenFile('indicator_onlyShowSelected_unselected_m2.png'),
|
|
);
|
|
});
|
|
|
|
testWidgets('Destination icon does not rebuild when tapped', (WidgetTester tester) async {
|
|
// This is a regression test for https://github.com/flutter/flutter/issues/122811.
|
|
|
|
Widget buildNavigationBar() {
|
|
return MaterialApp(
|
|
home: Scaffold(
|
|
bottomNavigationBar: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
var selectedIndex = 0;
|
|
return NavigationBar(
|
|
selectedIndex: selectedIndex,
|
|
destinations: const <Widget>[
|
|
NavigationDestination(
|
|
icon: IconWithRandomColor(icon: Icons.ac_unit),
|
|
label: 'AC',
|
|
),
|
|
NavigationDestination(
|
|
icon: IconWithRandomColor(icon: Icons.access_alarm),
|
|
label: 'Alarm',
|
|
),
|
|
],
|
|
onDestinationSelected: (int i) {
|
|
setState(() {
|
|
selectedIndex = i;
|
|
});
|
|
},
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildNavigationBar());
|
|
Icon icon = tester.widget<Icon>(find.byType(Icon).last);
|
|
final Color initialColor = icon.color!;
|
|
|
|
// Trigger a rebuild.
|
|
await tester.tap(find.text('Alarm'));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Icon color should be the same as before the rebuild.
|
|
icon = tester.widget<Icon>(find.byType(Icon).last);
|
|
expect(icon.color, initialColor);
|
|
});
|
|
});
|
|
|
|
testWidgets('NavigationBar.labelPadding overrides NavigationDestination.label padding', (
|
|
WidgetTester tester,
|
|
) async {
|
|
const selectedText = 'Home';
|
|
const unselectedText = 'Settings';
|
|
const EdgeInsetsGeometry labelPadding = EdgeInsets.all(8);
|
|
Widget buildNavigationBar({EdgeInsetsGeometry? labelPadding}) {
|
|
return MaterialApp(
|
|
home: Scaffold(
|
|
bottomNavigationBar: NavigationBar(
|
|
labelPadding: labelPadding,
|
|
destinations: const <Widget>[
|
|
NavigationDestination(icon: Icon(Icons.home), label: selectedText),
|
|
NavigationDestination(icon: Icon(Icons.settings), label: unselectedText),
|
|
],
|
|
onDestinationSelected: (int i) {},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildNavigationBar());
|
|
expect(_getLabelPadding(tester, selectedText), const EdgeInsets.only(top: 4));
|
|
expect(_getLabelPadding(tester, unselectedText), const EdgeInsets.only(top: 4));
|
|
|
|
await tester.pumpWidget(buildNavigationBar(labelPadding: labelPadding));
|
|
expect(_getLabelPadding(tester, selectedText), labelPadding);
|
|
expect(_getLabelPadding(tester, unselectedText), labelPadding);
|
|
});
|
|
|
|
testWidgets('NavigationBar.labelTextStyle overrides NavigationDestination.label text style', (
|
|
WidgetTester tester,
|
|
) async {
|
|
const selectedText = 'Home';
|
|
const unselectedText = 'Settings';
|
|
const disabledText = 'Bookmark';
|
|
final theme = ThemeData();
|
|
Widget buildNavigationBar({WidgetStateProperty<TextStyle?>? labelTextStyle}) {
|
|
return MaterialApp(
|
|
theme: theme,
|
|
home: Scaffold(
|
|
bottomNavigationBar: NavigationBar(
|
|
labelTextStyle: labelTextStyle,
|
|
destinations: const <Widget>[
|
|
NavigationDestination(icon: Icon(Icons.home), label: selectedText),
|
|
NavigationDestination(icon: Icon(Icons.settings), label: unselectedText),
|
|
NavigationDestination(
|
|
enabled: false,
|
|
icon: Icon(Icons.bookmark),
|
|
label: disabledText,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildNavigationBar());
|
|
|
|
// Test selected label text style.
|
|
expect(_getLabelStyle(tester, selectedText).fontSize, equals(12.0));
|
|
expect(_getLabelStyle(tester, selectedText).color, equals(theme.colorScheme.onSurface));
|
|
|
|
// Test unselected label text style.
|
|
expect(_getLabelStyle(tester, unselectedText).fontSize, equals(12.0));
|
|
expect(
|
|
_getLabelStyle(tester, unselectedText).color,
|
|
equals(theme.colorScheme.onSurfaceVariant),
|
|
);
|
|
|
|
// Test disabled label text style.
|
|
expect(_getLabelStyle(tester, disabledText).fontSize, equals(12.0));
|
|
expect(
|
|
_getLabelStyle(tester, disabledText).color,
|
|
equals(theme.colorScheme.onSurfaceVariant.withOpacity(0.38)),
|
|
);
|
|
|
|
const selectedTextStyle = TextStyle(fontSize: 15, color: Color(0xFF00FF00));
|
|
const unselectedTextStyle = TextStyle(fontSize: 15, color: Color(0xFF0000FF));
|
|
const disabledTextStyle = TextStyle(fontSize: 16, color: Color(0xFFFF0000));
|
|
await tester.pumpWidget(
|
|
buildNavigationBar(
|
|
labelTextStyle:
|
|
const WidgetStateProperty<TextStyle?>.fromMap(<WidgetStatesConstraint, TextStyle?>{
|
|
WidgetState.disabled: disabledTextStyle,
|
|
WidgetState.selected: selectedTextStyle,
|
|
WidgetState.any: unselectedTextStyle,
|
|
}),
|
|
),
|
|
);
|
|
|
|
// Test selected label text style.
|
|
expect(_getLabelStyle(tester, selectedText).fontSize, equals(selectedTextStyle.fontSize));
|
|
expect(_getLabelStyle(tester, selectedText).color, equals(selectedTextStyle.color));
|
|
|
|
// Test unselected label text style.
|
|
expect(_getLabelStyle(tester, unselectedText).fontSize, equals(unselectedTextStyle.fontSize));
|
|
expect(_getLabelStyle(tester, unselectedText).color, equals(unselectedTextStyle.color));
|
|
|
|
// Test disabled label text style.
|
|
expect(_getLabelStyle(tester, disabledText).fontSize, equals(disabledTextStyle.fontSize));
|
|
expect(_getLabelStyle(tester, disabledText).color, equals(disabledTextStyle.color));
|
|
});
|
|
|
|
testWidgets('NavigationBar.maintainBottomViewPadding can consume bottom MediaQuery.padding', (
|
|
WidgetTester tester,
|
|
) async {
|
|
const double bottomPadding = 40;
|
|
const TextDirection textDirection = TextDirection.ltr;
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Directionality(
|
|
textDirection: textDirection,
|
|
child: MediaQuery(
|
|
data: const MediaQueryData(padding: EdgeInsets.only(bottom: bottomPadding)),
|
|
child: Scaffold(
|
|
bottomNavigationBar: NavigationBar(
|
|
maintainBottomViewPadding: true,
|
|
destinations: const <Widget>[
|
|
NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'),
|
|
NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final double safeAreaBottomPadding = tester
|
|
.widget<Padding>(find.byType(Padding).first)
|
|
.padding
|
|
.resolve(textDirection)
|
|
.bottom;
|
|
expect(safeAreaBottomPadding, equals(0));
|
|
});
|
|
|
|
testWidgets('NavigationBar does not crash at zero area', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Center(
|
|
child: SizedBox.shrink(
|
|
child: NavigationBar(
|
|
destinations: const <Widget>[
|
|
NavigationDestination(icon: Icon(Icons.add), label: 'X'),
|
|
NavigationDestination(icon: Icon(Icons.abc), label: 'Y'),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
expect(tester.getSize(find.byType(NavigationBar)), Size.zero);
|
|
});
|
|
}
|
|
|
|
Widget _buildWidget(Widget child, {bool? useMaterial3}) {
|
|
return MaterialApp(
|
|
theme: ThemeData(useMaterial3: useMaterial3),
|
|
home: Scaffold(bottomNavigationBar: Center(child: child)),
|
|
);
|
|
}
|
|
|
|
Material _getMaterial(WidgetTester tester) {
|
|
return tester.firstWidget<Material>(
|
|
find.descendant(of: find.byType(NavigationBar), matching: find.byType(Material)),
|
|
);
|
|
}
|
|
|
|
ShapeDecoration? _getIndicatorDecoration(WidgetTester tester) {
|
|
return tester
|
|
.firstWidget<Ink>(
|
|
find.descendant(of: find.byType(FadeTransition), matching: find.byType(Ink)),
|
|
)
|
|
.decoration
|
|
as ShapeDecoration?;
|
|
}
|
|
|
|
class IconWithRandomColor extends StatelessWidget {
|
|
const IconWithRandomColor({super.key, required this.icon});
|
|
|
|
final IconData icon;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final Color randomColor = Color(
|
|
(Random().nextDouble() * 0xFFFFFF).toInt(),
|
|
).withValues(alpha: 1.0);
|
|
return Icon(icon, color: randomColor);
|
|
}
|
|
}
|
|
|
|
bool _sizeAlmostEqual(Size a, Size b, {double maxDiff = 0.05}) {
|
|
return (a.width - b.width).abs() <= maxDiff && (a.height - b.height).abs() <= maxDiff;
|
|
}
|
|
|
|
EdgeInsetsGeometry _getLabelPadding(WidgetTester tester, String text) {
|
|
return tester
|
|
.widget<Padding>(find.ancestor(of: find.text(text), matching: find.byType(Padding)).first)
|
|
.padding;
|
|
}
|
|
|
|
TextStyle _getLabelStyle(WidgetTester tester, String text) {
|
|
return tester
|
|
.widget<RichText>(find.descendant(of: find.text(text), matching: find.byType(RichText)))
|
|
.text
|
|
.style!;
|
|
}
|