| // Copyright 2017 The Chromium 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 'package:flutter/cupertino.dart'; |
| import 'package:flutter/material.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| import '../painting/mocks_for_image_cache.dart'; |
| import '../rendering/rendering_tester.dart'; |
| |
| List<int> selectedTabs; |
| |
| void main() { |
| setUp(() { |
| selectedTabs = <int>[]; |
| }); |
| |
| testWidgets('Tab switching', (WidgetTester tester) async { |
| final List<int> tabsPainted = <int>[]; |
| |
| await tester.pumpWidget( |
| new WidgetsApp( |
| color: const Color(0xFFFFFFFF), |
| onGenerateRoute: (RouteSettings settings) { |
| return new CupertinoPageRoute<void>( |
| settings: settings, |
| builder: (BuildContext context) { |
| return new CupertinoTabScaffold( |
| tabBar: _buildTabBar(), |
| tabBuilder: (BuildContext context, int index) { |
| return new CustomPaint( |
| child: new Text('Page ${index + 1}'), |
| painter: new TestCallbackPainter( |
| onPaint: () { tabsPainted.add(index); } |
| ) |
| ); |
| }, |
| ); |
| }, |
| ); |
| }, |
| ), |
| ); |
| |
| expect(tabsPainted, <int>[0]); |
| RichText tab1 = tester.widget(find.descendant( |
| of: find.text('Tab 1'), |
| matching: find.byType(RichText), |
| )); |
| expect(tab1.text.style.color, CupertinoColors.activeBlue); |
| RichText tab2 = tester.widget(find.descendant( |
| of: find.text('Tab 2'), |
| matching: find.byType(RichText), |
| )); |
| expect(tab2.text.style.color, CupertinoColors.inactiveGray); |
| |
| await tester.tap(find.text('Tab 2')); |
| await tester.pump(); |
| |
| expect(tabsPainted, <int>[0, 1]); |
| tab1 = tester.widget(find.descendant( |
| of: find.text('Tab 1'), |
| matching: find.byType(RichText), |
| )); |
| expect(tab1.text.style.color, CupertinoColors.inactiveGray); |
| tab2 = tester.widget(find.descendant( |
| of: find.text('Tab 2'), |
| matching: find.byType(RichText), |
| )); |
| expect(tab2.text.style.color, CupertinoColors.activeBlue); |
| |
| await tester.tap(find.text('Tab 1')); |
| await tester.pump(); |
| |
| expect(tabsPainted, <int>[0, 1, 0]); |
| // CupertinoTabBar's onTap callbacks are passed on. |
| expect(selectedTabs, <int>[1, 0]); |
| }); |
| |
| testWidgets('Tabs are lazy built and moved offstage when inactive', (WidgetTester tester) async { |
| final List<int> tabsBuilt = <int>[]; |
| |
| await tester.pumpWidget( |
| new WidgetsApp( |
| color: const Color(0xFFFFFFFF), |
| onGenerateRoute: (RouteSettings settings) { |
| return new CupertinoPageRoute<void>( |
| settings: settings, |
| builder: (BuildContext context) { |
| return new CupertinoTabScaffold( |
| tabBar: _buildTabBar(), |
| tabBuilder: (BuildContext context, int index) { |
| tabsBuilt.add(index); |
| return new Text('Page ${index + 1}'); |
| }, |
| ); |
| }, |
| ); |
| }, |
| ), |
| ); |
| |
| expect(tabsBuilt, <int>[0]); |
| expect(find.text('Page 1'), findsOneWidget); |
| expect(find.text('Page 2'), findsNothing); |
| |
| await tester.tap(find.text('Tab 2')); |
| await tester.pump(); |
| |
| // Both tabs are built but only one is onstage. |
| expect(tabsBuilt, <int>[0, 0, 1]); |
| expect(find.text('Page 1', skipOffstage: false), isOffstage); |
| expect(find.text('Page 2'), findsOneWidget); |
| |
| await tester.tap(find.text('Tab 1')); |
| await tester.pump(); |
| |
| expect(tabsBuilt, <int>[0, 0, 1, 0, 1]); |
| expect(find.text('Page 1'), findsOneWidget); |
| expect(find.text('Page 2', skipOffstage: false), isOffstage); |
| }); |
| |
| testWidgets('Last tab gets focus', (WidgetTester tester) async { |
| // 2 nodes for 2 tabs |
| final List<FocusNode> focusNodes = <FocusNode>[new FocusNode(), new FocusNode()]; |
| |
| await tester.pumpWidget( |
| new WidgetsApp( |
| color: const Color(0xFFFFFFFF), |
| onGenerateRoute: (RouteSettings settings) { |
| return new CupertinoPageRoute<void>( |
| settings: settings, |
| builder: (BuildContext context) { |
| return new Material( |
| child: new CupertinoTabScaffold( |
| tabBar: _buildTabBar(), |
| tabBuilder: (BuildContext context, int index) { |
| return new 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>[ |
| new FocusNode(), new FocusNode(), new FocusNode(), new FocusNode(), |
| ]; |
| |
| await tester.pumpWidget( |
| new WidgetsApp( |
| color: const Color(0xFFFFFFFF), |
| onGenerateRoute: (RouteSettings settings) { |
| return new CupertinoPageRoute<void>( |
| settings: settings, |
| builder: (BuildContext context) { |
| return new Material( |
| child: new CupertinoTabScaffold( |
| tabBar: _buildTabBar(), |
| tabBuilder: (BuildContext context, int index) { |
| return new Column( |
| children: <Widget>[ |
| new TextField( |
| focusNode: focusNodes[index * 2], |
| decoration: const InputDecoration( |
| hintText: 'TextField 1', |
| ), |
| ), |
| new 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('Programmatic tab switching', (WidgetTester tester) async { |
| final List<int> tabsPainted = <int>[]; |
| |
| await tester.pumpWidget( |
| new WidgetsApp( |
| color: const Color(0xFFFFFFFF), |
| builder: (BuildContext context, Widget child) { |
| return new CupertinoTabScaffold( |
| tabBar: _buildTabBar(), |
| tabBuilder: (BuildContext context, int index) { |
| return new CustomPaint( |
| child: new Text('Page ${index + 1}'), |
| painter: new TestCallbackPainter( |
| onPaint: () { tabsPainted.add(index); } |
| ) |
| ); |
| }, |
| ); |
| }, |
| ), |
| ); |
| |
| expect(tabsPainted, <int>[0]); |
| |
| await tester.pumpWidget( |
| new WidgetsApp( |
| color: const Color(0xFFFFFFFF), |
| builder: (BuildContext context, Widget child) { |
| return new CupertinoTabScaffold( |
| tabBar: _buildTabBar(selectedTab: 1), // Programmatically change the tab now. |
| tabBuilder: (BuildContext context, int index) { |
| return new CustomPaint( |
| child: new Text('Page ${index + 1}'), |
| painter: new TestCallbackPainter( |
| onPaint: () { tabsPainted.add(index); } |
| ) |
| ); |
| }, |
| ); |
| }, |
| ), |
| ); |
| |
| expect(tabsPainted, <int>[0, 1]); |
| // onTap is not called when changing tabs programmatically. |
| expect(selectedTabs, isEmpty); |
| |
| // Can still tap out of the programmatically selected tab. |
| await tester.tap(find.text('Tab 1')); |
| await tester.pump(); |
| |
| expect(tabsPainted, <int>[0, 1, 0]); |
| expect(selectedTabs, <int>[0]); |
| }); |
| } |
| |
| CupertinoTabBar _buildTabBar({ int selectedTab: 0 }) { |
| return new CupertinoTabBar( |
| items: const <BottomNavigationBarItem>[ |
| const BottomNavigationBarItem( |
| icon: const ImageIcon(const TestImageProvider(24, 24)), |
| title: const Text('Tab 1'), |
| ), |
| const BottomNavigationBarItem( |
| icon: const ImageIcon(const TestImageProvider(24, 24)), |
| title: const Text('Tab 2'), |
| ), |
| ], |
| backgroundColor: CupertinoColors.white, |
| currentIndex: selectedTab, |
| onTap: (int newTab) => selectedTabs.add(newTab), |
| ); |
| } |