blob: 3fd56575645a6a77df44478fe471c0d9b5962705 [file] [log] [blame]
// 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 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
test('copyWith, ==, hashCode basics', () {
expect(const NavigationBarThemeData(), const NavigationBarThemeData().copyWith());
expect(
const NavigationBarThemeData().hashCode,
const NavigationBarThemeData().copyWith().hashCode,
);
});
test('NavigationBarThemeData lerp special cases', () {
expect(NavigationBarThemeData.lerp(null, null, 0), null);
const NavigationBarThemeData data = NavigationBarThemeData();
expect(identical(NavigationBarThemeData.lerp(data, data, 0.5), data), true);
});
testWidgets('Default debugFillProperties', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
const NavigationBarThemeData().debugFillProperties(builder);
final List<String> description =
builder.properties
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
.map((DiagnosticsNode node) => node.toString())
.toList();
expect(description, <String>[]);
});
testWidgets('NavigationBarThemeData implements debugFillProperties', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
const NavigationBarThemeData(
height: 200.0,
backgroundColor: Color(0x00000099),
elevation: 20.0,
shadowColor: Color(0x00000098),
surfaceTintColor: Color(0x00000097),
indicatorColor: Color(0x00000096),
indicatorShape: CircleBorder(),
labelTextStyle: MaterialStatePropertyAll<TextStyle>(TextStyle(fontSize: 7.0)),
iconTheme: MaterialStatePropertyAll<IconThemeData>(IconThemeData(color: Color(0x00000097))),
labelBehavior: NavigationDestinationLabelBehavior.alwaysHide,
overlayColor: MaterialStatePropertyAll<Color>(Color(0x00000095)),
labelPadding: EdgeInsets.all(8),
).debugFillProperties(builder);
final List<String> description =
builder.properties
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
.map((DiagnosticsNode node) => node.toString())
.toList();
expect(
description,
equalsIgnoringHashCodes(<String>[
'height: 200.0',
'backgroundColor: Color(alpha: 0.0000, red: 0.0000, green: 0.0000, blue: 0.6000, colorSpace: ColorSpace.sRGB)',
'elevation: 20.0',
'shadowColor: Color(alpha: 0.0000, red: 0.0000, green: 0.0000, blue: 0.5961, colorSpace: ColorSpace.sRGB)',
'surfaceTintColor: Color(alpha: 0.0000, red: 0.0000, green: 0.0000, blue: 0.5922, colorSpace: ColorSpace.sRGB)',
'indicatorColor: Color(alpha: 0.0000, red: 0.0000, green: 0.0000, blue: 0.5882, colorSpace: ColorSpace.sRGB)',
'indicatorShape: CircleBorder(BorderSide(width: 0.0, style: none))',
'labelTextStyle: WidgetStatePropertyAll(TextStyle(inherit: true, size: 7.0))',
'iconTheme: WidgetStatePropertyAll(IconThemeData#fd5c3(color: Color(alpha: 0.0000, red: 0.0000, green: 0.0000, blue: 0.5922, colorSpace: ColorSpace.sRGB)))',
'labelBehavior: NavigationDestinationLabelBehavior.alwaysHide',
'overlayColor: WidgetStatePropertyAll(Color(alpha: 0.0000, red: 0.0000, green: 0.0000, blue: 0.5843, colorSpace: ColorSpace.sRGB))',
'labelPadding: EdgeInsets.all(8.0)',
]),
);
});
testWidgets(
'NavigationBarThemeData values are used when no NavigationBar properties are specified',
(WidgetTester tester) async {
const double height = 200.0;
const Color backgroundColor = Color(0x00000001);
const double elevation = 42.0;
const Color indicatorColor = Color(0x00000002);
const ShapeBorder indicatorShape = CircleBorder();
const double selectedIconSize = 25.0;
const double unselectedIconSize = 23.0;
const Color selectedIconColor = Color(0x00000003);
const Color unselectedIconColor = Color(0x00000004);
const double selectedIconOpacity = 0.99;
const double unselectedIconOpacity = 0.98;
const double selectedLabelFontSize = 13.0;
const double unselectedLabelFontSize = 11.0;
const NavigationDestinationLabelBehavior labelBehavior =
NavigationDestinationLabelBehavior.alwaysShow;
const EdgeInsetsGeometry labelPadding = EdgeInsets.all(8);
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
bottomNavigationBar: NavigationBarTheme(
data: NavigationBarThemeData(
height: height,
backgroundColor: backgroundColor,
elevation: elevation,
indicatorColor: indicatorColor,
indicatorShape: indicatorShape,
iconTheme: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
return const IconThemeData(
size: selectedIconSize,
color: selectedIconColor,
opacity: selectedIconOpacity,
);
}
return const IconThemeData(
size: unselectedIconSize,
color: unselectedIconColor,
opacity: unselectedIconOpacity,
);
}),
labelTextStyle: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
return const TextStyle(fontSize: selectedLabelFontSize);
}
return const TextStyle(fontSize: unselectedLabelFontSize);
}),
labelBehavior: labelBehavior,
labelPadding: labelPadding,
),
child: NavigationBar(destinations: _destinations()),
),
),
),
);
expect(_barHeight(tester), height);
expect(_barMaterial(tester).color, backgroundColor);
expect(_barMaterial(tester).elevation, elevation);
expect(_indicator(tester)?.color, indicatorColor);
expect(_indicator(tester)?.shape, indicatorShape);
expect(_selectedIconTheme(tester).size, selectedIconSize);
expect(_selectedIconTheme(tester).color, selectedIconColor);
expect(_selectedIconTheme(tester).opacity, selectedIconOpacity);
expect(_unselectedIconTheme(tester).size, unselectedIconSize);
expect(_unselectedIconTheme(tester).color, unselectedIconColor);
expect(_unselectedIconTheme(tester).opacity, unselectedIconOpacity);
expect(_selectedLabelStyle(tester).fontSize, selectedLabelFontSize);
expect(_unselectedLabelStyle(tester).fontSize, unselectedLabelFontSize);
expect(_labelBehavior(tester), labelBehavior);
expect(_getLabelPadding(tester, 'Abc'), labelPadding);
expect(_getLabelPadding(tester, 'Def'), labelPadding);
},
);
testWidgets(
'NavigationBar values take priority over NavigationBarThemeData values when both properties are specified',
(WidgetTester tester) async {
const double height = 200.0;
const Color backgroundColor = Color(0x00000001);
const double elevation = 42.0;
const NavigationDestinationLabelBehavior labelBehavior =
NavigationDestinationLabelBehavior.alwaysShow;
const EdgeInsetsGeometry labelPadding = EdgeInsets.symmetric(horizontal: 16.0);
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
bottomNavigationBar: NavigationBarTheme(
data: const NavigationBarThemeData(
height: 100.0,
elevation: 18.0,
backgroundColor: Color(0x00000099),
labelBehavior: NavigationDestinationLabelBehavior.alwaysHide,
labelPadding: EdgeInsets.all(8),
),
child: NavigationBar(
height: height,
elevation: elevation,
backgroundColor: backgroundColor,
labelBehavior: labelBehavior,
labelPadding: labelPadding,
destinations: _destinations(),
),
),
),
),
);
expect(_barHeight(tester), height);
expect(_barMaterial(tester).color, backgroundColor);
expect(_barMaterial(tester).elevation, elevation);
expect(_labelBehavior(tester), labelBehavior);
expect(_getLabelPadding(tester, 'Abc'), labelPadding);
expect(_getLabelPadding(tester, 'Def'), labelPadding);
},
);
testWidgets('Custom label style renders ink ripple properly', (WidgetTester tester) async {
Widget buildWidget({NavigationDestinationLabelBehavior? labelBehavior}) {
return MaterialApp(
theme: ThemeData(
navigationBarTheme: const NavigationBarThemeData(
labelTextStyle: MaterialStatePropertyAll<TextStyle>(
TextStyle(fontSize: 25, color: Color(0xff0000ff)),
),
),
useMaterial3: true,
),
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();
await expectLater(
find.byType(NavigationBar),
matchesGoldenFile('indicator_custom_label_style.png'),
);
});
testWidgets(
'NavigationBar respects NavigationBarTheme.overlayColor in active/pressed/hovered states',
(WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
const Color hoverColor = Color(0xff0000ff);
const Color focusColor = Color(0xff00ffff);
const Color pressedColor = Color(0xffff00ff);
final MaterialStateProperty<Color?> overlayColor = MaterialStateProperty.resolveWith<Color>((
Set<MaterialState> states,
) {
if (states.contains(MaterialState.hovered)) {
return hoverColor;
}
if (states.contains(MaterialState.focused)) {
return focusColor;
}
if (states.contains(MaterialState.pressed)) {
return pressedColor;
}
return Colors.transparent;
});
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(navigationBarTheme: NavigationBarThemeData(overlayColor: overlayColor)),
home: Scaffold(
bottomNavigationBar: RepaintBoundary(
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 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)),
);
},
);
}
List<NavigationDestination> _destinations() {
return const <NavigationDestination>[
NavigationDestination(
icon: Icon(Icons.favorite_border),
selectedIcon: Icon(Icons.favorite),
label: 'Abc',
),
NavigationDestination(
icon: Icon(Icons.star_border),
selectedIcon: Icon(Icons.star),
label: 'Def',
),
];
}
double _barHeight(WidgetTester tester) {
return tester.getRect(find.byType(NavigationBar)).height;
}
Material _barMaterial(WidgetTester tester) {
return tester.firstWidget<Material>(
find.descendant(of: find.byType(NavigationBar), matching: find.byType(Material)),
);
}
ShapeDecoration? _indicator(WidgetTester tester) {
return tester
.firstWidget<Container>(
find.descendant(of: find.byType(FadeTransition), matching: find.byType(Container)),
)
.decoration
as ShapeDecoration?;
}
IconThemeData _selectedIconTheme(WidgetTester tester) {
return _iconTheme(tester, Icons.favorite);
}
IconThemeData _unselectedIconTheme(WidgetTester tester) {
return _iconTheme(tester, Icons.star_border);
}
IconThemeData _iconTheme(WidgetTester tester, IconData icon) {
return tester
.firstWidget<IconTheme>(
find.ancestor(of: find.byIcon(icon), matching: find.byType(IconTheme)),
)
.data;
}
TextStyle _selectedLabelStyle(WidgetTester tester) {
return tester
.widget<RichText>(find.descendant(of: find.text('Abc'), matching: find.byType(RichText)))
.text
.style!;
}
TextStyle _unselectedLabelStyle(WidgetTester tester) {
return tester
.widget<RichText>(find.descendant(of: find.text('Def'), matching: find.byType(RichText)))
.text
.style!;
}
NavigationDestinationLabelBehavior _labelBehavior(WidgetTester tester) {
if (_opacityAboveLabel('Abc').evaluate().isNotEmpty &&
_opacityAboveLabel('Def').evaluate().isNotEmpty) {
return _labelOpacity(tester, 'Abc') == 1
? NavigationDestinationLabelBehavior.onlyShowSelected
: NavigationDestinationLabelBehavior.alwaysHide;
} else {
return NavigationDestinationLabelBehavior.alwaysShow;
}
}
Finder _opacityAboveLabel(String text) {
return find.ancestor(of: find.text(text), matching: find.byType(Opacity));
}
// Only valid when labelBehavior != alwaysShow.
double _labelOpacity(WidgetTester tester, String text) {
final Opacity opacityWidget = tester.widget<Opacity>(
find.ancestor(of: find.text(text), matching: find.byType(Opacity)),
);
return opacityWidget.opacity;
}
EdgeInsetsGeometry _getLabelPadding(WidgetTester tester, String text) {
return tester
.widget<Padding>(find.ancestor(of: find.text(text), matching: find.byType(Padding)).first)
.padding;
}