blob: df77c584a4b2b24df25ce20be21051ec4523bd04 [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 focusNodes = <FocusNode>[];
for (var i = 0; i < 2; i++) {
final focusNode = FocusNode();
focusNodes.add(focusNode);
addTearDown(focusNode.dispose);
}
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 focusNodes = <FocusNode>[];
for (var i = 0; i < 4; i++) {
final focusNode = FocusNode();
focusNodes.add(focusNode);
addTearDown(focusNode.dispose);
}
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();
},
),
),
);
var 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 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.
var 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.withClampedTextScaling(
minScaleFactor: 99,
maxScaleFactor: 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,
isNot(contains(predicate((RichText t) => t.textScaler != TextScaler.noScaling))),
);
expect(contents.length, greaterThan(0));
expect(
contents,
isNot(contains(predicate((RichText t) => t.textScaler != const TextScaler.linear(99.0)))),
);
imageCache.clear();
});
}
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),
);
}