blob: ed211846d60e2076cd52538372c05dcf5a9202b2 [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.
import 'dart:typed_data';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import '../../image_data.dart';
late List<int> selectedTabs;
void main() {
setUp(() {
selectedTabs = <int>[];
});
testWidgets('Last tab gets focus', (WidgetTester tester) async {
// 2 nodes for 2 tabs
final List<FocusNode> focusNodes = <FocusNode>[FocusNode(), FocusNode()];
await tester.pumpWidget(
MaterialApp(
home: Material(
child: CupertinoTabScaffold(
tabBar: _buildTabBar(),
tabBuilder: (BuildContext context, int index) {
return TextField(
focusNode: focusNodes[index],
autofocus: true,
);
},
),
),
),
);
expect(focusNodes[0].hasFocus, isTrue);
await tester.tap(find.text('Tab 2'));
await tester.pump();
expect(focusNodes[0].hasFocus, isFalse);
expect(focusNodes[1].hasFocus, isTrue);
await tester.tap(find.text('Tab 1'));
await tester.pump();
expect(focusNodes[0].hasFocus, isTrue);
expect(focusNodes[1].hasFocus, isFalse);
});
testWidgets('Do not affect focus order in the route', (WidgetTester tester) async {
final List<FocusNode> focusNodes = <FocusNode>[
FocusNode(), FocusNode(), FocusNode(), FocusNode(),
];
await tester.pumpWidget(
MaterialApp(
home: Material(
child: CupertinoTabScaffold(
tabBar: _buildTabBar(),
tabBuilder: (BuildContext context, int index) {
return Column(
children: <Widget>[
TextField(
focusNode: focusNodes[index * 2],
decoration: const InputDecoration(
hintText: 'TextField 1',
),
),
TextField(
focusNode: focusNodes[index * 2 + 1],
decoration: const InputDecoration(
hintText: 'TextField 2',
),
),
],
);
},
),
),
),
);
expect(
focusNodes.any((FocusNode node) => node.hasFocus),
isFalse,
);
await tester.tap(find.widgetWithText(TextField, 'TextField 2'));
expect(
focusNodes.indexOf(focusNodes.singleWhere((FocusNode node) => node.hasFocus)),
1,
);
await tester.tap(find.text('Tab 2'));
await tester.pump();
await tester.tap(find.widgetWithText(TextField, 'TextField 1'));
expect(
focusNodes.indexOf(focusNodes.singleWhere((FocusNode node) => node.hasFocus)),
2,
);
await tester.tap(find.text('Tab 1'));
await tester.pump();
// Upon going back to tab 1, the item it tab 1 that previously had the focus
// (TextField 2) gets it back.
expect(
focusNodes.indexOf(focusNodes.singleWhere((FocusNode node) => node.hasFocus)),
1,
);
});
testWidgets('Tab bar respects themes', (WidgetTester tester) async {
await tester.pumpWidget(
CupertinoApp(
home: CupertinoTabScaffold(
tabBar: _buildTabBar(),
tabBuilder: (BuildContext context, int index) {
return const Placeholder();
},
),
),
);
BoxDecoration tabDecoration = tester.widget<DecoratedBox>(find.descendant(
of: find.byType(CupertinoTabBar),
matching: find.byType(DecoratedBox),
)).decoration as BoxDecoration;
expect(tabDecoration.color, isSameColorAs(const Color(0xF0F9F9F9))); // Inherited from theme.
await tester.tap(find.text('Tab 2'));
await tester.pump();
// Pump again but with dark theme.
await tester.pumpWidget(
CupertinoApp(
theme: const CupertinoThemeData(
brightness: Brightness.dark,
primaryColor: CupertinoColors.destructiveRed,
),
home: CupertinoTabScaffold(
tabBar: _buildTabBar(),
tabBuilder: (BuildContext context, int index) {
return const Placeholder();
},
),
),
);
tabDecoration = tester.widget<DecoratedBox>(find.descendant(
of: find.byType(CupertinoTabBar),
matching: find.byType(DecoratedBox),
)).decoration as BoxDecoration;
expect(tabDecoration.color, isSameColorAs(const Color(0xF01D1D1D)));
final RichText tab1 = tester.widget(find.descendant(
of: find.text('Tab 1'),
matching: find.byType(RichText),
));
// Tab 2 should still be selected after changing theme.
expect(tab1.text.style!.color!.value, 0xFF757575);
final RichText tab2 = tester.widget(find.descendant(
of: find.text('Tab 2'),
matching: find.byType(RichText),
));
expect(tab2.text.style!.color!.value, CupertinoColors.systemRed.darkColor.value);
});
testWidgets('dark mode background color', (WidgetTester tester) async {
const CupertinoDynamicColor backgroundColor = CupertinoDynamicColor.withBrightness(
color: Color(0xFF123456),
darkColor: Color(0xFF654321),
);
await tester.pumpWidget(
CupertinoApp(
theme: const CupertinoThemeData(brightness: Brightness.light),
home: CupertinoTabScaffold(
backgroundColor: backgroundColor,
tabBar: _buildTabBar(),
tabBuilder: (BuildContext context, int index) {
return const Placeholder();
},
),
),
);
// The DecoratedBox with the smallest depth is the DecoratedBox of the
// CupertinoTabScaffold.
BoxDecoration tabDecoration = tester.firstWidget<DecoratedBox>(
find.descendant(
of: find.byType(CupertinoTabScaffold),
matching: find.byType(DecoratedBox),
),
).decoration as BoxDecoration;
expect(tabDecoration.color!.value, backgroundColor.color.value);
// Dark mode
await tester.pumpWidget(
CupertinoApp(
theme: const CupertinoThemeData(brightness: Brightness.dark),
home: CupertinoTabScaffold(
backgroundColor: backgroundColor,
tabBar: _buildTabBar(),
tabBuilder: (BuildContext context, int index) {
return const Placeholder();
},
),
),
);
tabDecoration = tester.firstWidget<DecoratedBox>(
find.descendant(
of: find.byType(CupertinoTabScaffold),
matching: find.byType(DecoratedBox),
),
).decoration as BoxDecoration;
expect(tabDecoration.color!.value, backgroundColor.darkColor.value);
});
testWidgets('Does not lose state when focusing on text input', (WidgetTester tester) async {
// Regression testing for https://github.com/flutter/flutter/issues/28457.
await tester.pumpWidget(
MediaQuery(
data: const MediaQueryData(),
child: MaterialApp(
home: Material(
child: CupertinoTabScaffold(
tabBar: _buildTabBar(),
tabBuilder: (BuildContext context, int index) {
return const TextField();
},
),
),
),
),
);
final EditableTextState editableState = tester.state<EditableTextState>(find.byType(EditableText));
await tester.enterText(find.byType(TextField), "don't lose me");
await tester.pumpWidget(
MediaQuery(
data: const MediaQueryData(
viewInsets: EdgeInsets.only(bottom: 100),
),
child: MaterialApp(
home: Material(
child: CupertinoTabScaffold(
tabBar: _buildTabBar(),
tabBuilder: (BuildContext context, int index) {
return const TextField();
},
),
),
),
),
);
// The exact same state instance is still there.
expect(tester.state<EditableTextState>(find.byType(EditableText)), editableState);
expect(find.text("don't lose me"), findsOneWidget);
});
testWidgets('textScaleFactor is set to 1.0', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Builder(builder: (BuildContext context) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(textScaleFactor: 99),
child: CupertinoTabScaffold(
tabBar: CupertinoTabBar(
items: List<BottomNavigationBarItem>.generate(
10,
(int i) => BottomNavigationBarItem(icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))), label: '$i'),
),
),
tabBuilder: (BuildContext context, int index) => const Text('content'),
),
);
}),
),
);
final Iterable<RichText> barItems = tester.widgetList<RichText>(
find.descendant(
of: find.byType(CupertinoTabBar),
matching: find.byType(RichText),
),
);
final Iterable<RichText> contents = tester.widgetList<RichText>(
find.descendant(
of: find.text('content'),
matching: find.byType(RichText),
skipOffstage: false,
),
);
expect(barItems.length, greaterThan(0));
expect(barItems.any((RichText t) => t.textScaleFactor != 1), isFalse);
expect(contents.length, greaterThan(0));
expect(contents.any((RichText t) => t.textScaleFactor != 99), isFalse);
});
}
CupertinoTabBar _buildTabBar({ int selectedTab = 0 }) {
return CupertinoTabBar(
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
label: 'Tab 1',
),
BottomNavigationBarItem(
icon: ImageIcon(MemoryImage(Uint8List.fromList(kTransparentImage))),
label: 'Tab 2',
),
],
currentIndex: selectedTab,
onTap: (int newTab) => selectedTabs.add(newTab),
);
}