Unverified Commit 55173169 authored by Taha Tesser's avatar Taha Tesser Committed by GitHub

Add `tabs_utils.dart` class (#143937)

This a test utility class for `tabs_test.dart` to prepare the class for Material 3 tests updates.

More info in https://github.com/flutter/flutter/issues/139076
parent e707f0de
...@@ -10,6 +10,7 @@ import 'package:flutter_test/flutter_test.dart'; ...@@ -10,6 +10,7 @@ import 'package:flutter_test/flutter_test.dart';
import '../widgets/semantics_tester.dart'; import '../widgets/semantics_tester.dart';
import 'feedback_tester.dart'; import 'feedback_tester.dart';
import 'tabs_utils.dart';
Widget boilerplate({ Widget? child, TextDirection textDirection = TextDirection.ltr, bool? useMaterial3, TabBarTheme? tabBarTheme }) { Widget boilerplate({ Widget? child, TextDirection textDirection = TextDirection.ltr, bool? useMaterial3, TabBarTheme? tabBarTheme }) {
return Theme( return Theme(
...@@ -30,78 +31,6 @@ Widget boilerplate({ Widget? child, TextDirection textDirection = TextDirection. ...@@ -30,78 +31,6 @@ Widget boilerplate({ Widget? child, TextDirection textDirection = TextDirection.
); );
} }
class StateMarker extends StatefulWidget {
const StateMarker({ super.key, this.child });
final Widget? child;
@override
StateMarkerState createState() => StateMarkerState();
}
class StateMarkerState extends State<StateMarker> {
String? marker;
@override
Widget build(BuildContext context) {
return widget.child ?? Container();
}
}
class AlwaysKeepAliveWidget extends StatefulWidget {
const AlwaysKeepAliveWidget({ super.key});
static String text = 'AlwaysKeepAlive';
@override
AlwaysKeepAliveState createState() => AlwaysKeepAliveState();
}
class AlwaysKeepAliveState extends State<AlwaysKeepAliveWidget>
with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
super.build(context);
return Text(AlwaysKeepAliveWidget.text);
}
}
class _NestedTabBarContainer extends StatelessWidget {
const _NestedTabBarContainer({
this.tabController,
});
final TabController? tabController;
@override
Widget build(BuildContext context) {
return ColoredBox(
color: Colors.blue,
child: Column(
children: <Widget>[
TabBar(
controller: tabController,
tabs: const <Tab>[
Tab(text: 'Yellow'),
Tab(text: 'Grey'),
],
),
Expanded(
child: TabBarView(
controller: tabController,
children: <Widget>[
Container(color: Colors.yellow),
Container(color: Colors.grey),
],
),
),
],
),
);
}
}
Widget buildFrame({ Widget buildFrame({
Key? tabBarKey, Key? tabBarKey,
bool secondaryTabBar = false, bool secondaryTabBar = false,
...@@ -157,49 +86,6 @@ Widget buildFrame({ ...@@ -157,49 +86,6 @@ Widget buildFrame({
); );
} }
typedef TabControllerFrameBuilder = Widget Function(BuildContext context, TabController controller);
class TabControllerFrame extends StatefulWidget {
const TabControllerFrame({
super.key,
required this.length,
this.initialIndex = 0,
required this.builder,
});
final int length;
final int initialIndex;
final TabControllerFrameBuilder builder;
@override
TabControllerFrameState createState() => TabControllerFrameState();
}
class TabControllerFrameState extends State<TabControllerFrame> with SingleTickerProviderStateMixin {
late TabController _controller;
@override
void initState() {
super.initState();
_controller = TabController(
vsync: this,
length: widget.length,
initialIndex: widget.initialIndex,
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return widget.builder(context, _controller);
}
}
Widget buildLeftRightApp({required List<String> tabs, required String value, bool automaticIndicatorColorAdjustment = true, ThemeData? themeData}) { Widget buildLeftRightApp({required List<String> tabs, required String value, bool automaticIndicatorColorAdjustment = true, ThemeData? themeData}) {
return MaterialApp( return MaterialApp(
theme: themeData ?? ThemeData(platform: TargetPlatform.android), theme: themeData ?? ThemeData(platform: TargetPlatform.android),
...@@ -225,55 +111,6 @@ Widget buildLeftRightApp({required List<String> tabs, required String value, boo ...@@ -225,55 +111,6 @@ Widget buildLeftRightApp({required List<String> tabs, required String value, boo
); );
} }
class TabIndicatorRecordingCanvas extends TestRecordingCanvas {
TabIndicatorRecordingCanvas(this.indicatorColor);
final Color indicatorColor;
late Rect indicatorRect;
@override
void drawLine(Offset p1, Offset p2, Paint paint) {
// Assuming that the indicatorWeight is 2.0, the default.
const double indicatorWeight = 2.0;
if (paint.color == indicatorColor) {
indicatorRect = Rect.fromPoints(p1, p2).inflate(indicatorWeight / 2.0);
}
}
}
class TestScrollPhysics extends ScrollPhysics {
const TestScrollPhysics({ super.parent });
@override
TestScrollPhysics applyTo(ScrollPhysics? ancestor) {
return TestScrollPhysics(parent: buildParent(ancestor));
}
@override
double applyPhysicsToUserOffset(ScrollMetrics position, double offset) {
return offset == 10 ? 20 : offset;
}
static final SpringDescription _kDefaultSpring = SpringDescription.withDampingRatio(
mass: 0.5,
stiffness: 500.0,
ratio: 1.1,
);
@override
SpringDescription get spring => _kDefaultSpring;
}
RenderParagraph _getText(WidgetTester tester, String text) {
return tester.renderObject<RenderParagraph>(find.text(text));
}
TabController _tabController({required int length, required TickerProvider vsync, int initialIndex = 0, Duration? animationDuration}) {
final TabController result = TabController(length: length, vsync: vsync, initialIndex: initialIndex, animationDuration: animationDuration);
addTearDown(result.dispose);
return result;
}
void main() { void main() {
setUp(() { setUp(() {
debugResetSemanticsIdCounter(); debugResetSemanticsIdCounter();
...@@ -425,7 +262,7 @@ void main() { ...@@ -425,7 +262,7 @@ void main() {
testWidgets('Tab color - normal', (WidgetTester tester) async { testWidgets('Tab color - normal', (WidgetTester tester) async {
final ThemeData theme = ThemeData(fontFamily: 'FlutterTest'); final ThemeData theme = ThemeData(fontFamily: 'FlutterTest');
final bool material3 = theme.useMaterial3; final bool material3 = theme.useMaterial3;
final Widget tabBar = TabBar(tabs: const <Widget>[SizedBox.shrink()], controller: _tabController(length: 1, vsync: tester)); final Widget tabBar = TabBar(tabs: const <Widget>[SizedBox.shrink()], controller: createTabController(length: 1, vsync: tester));
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp(theme: theme, home: Material(child: tabBar)), MaterialApp(theme: theme, home: Material(child: tabBar)),
); );
...@@ -435,7 +272,7 @@ void main() { ...@@ -435,7 +272,7 @@ void main() {
testWidgets('Tab color - match', (WidgetTester tester) async { testWidgets('Tab color - match', (WidgetTester tester) async {
final ThemeData theme = ThemeData(); final ThemeData theme = ThemeData();
final bool material3 = theme.useMaterial3; final bool material3 = theme.useMaterial3;
final Widget tabBar = TabBar(tabs: const <Widget>[SizedBox.shrink()], controller: _tabController(length: 1, vsync: tester)); final Widget tabBar = TabBar(tabs: const <Widget>[SizedBox.shrink()], controller: createTabController(length: 1, vsync: tester));
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp(theme: theme, home: Material(color: const Color(0xff2196f3), child: tabBar)), MaterialApp(theme: theme, home: Material(color: const Color(0xff2196f3), child: tabBar)),
); );
...@@ -445,7 +282,7 @@ void main() { ...@@ -445,7 +282,7 @@ void main() {
testWidgets('Tab color - transparency', (WidgetTester tester) async { testWidgets('Tab color - transparency', (WidgetTester tester) async {
final ThemeData theme = ThemeData(); final ThemeData theme = ThemeData();
final bool material3 = theme.useMaterial3; final bool material3 = theme.useMaterial3;
final Widget tabBar = TabBar(tabs: const <Widget>[SizedBox.shrink()], controller: _tabController(length: 1, vsync: tester)); final Widget tabBar = TabBar(tabs: const <Widget>[SizedBox.shrink()], controller: createTabController(length: 1, vsync: tester));
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp(theme: theme, home: Material(type: MaterialType.transparency, child: tabBar)), MaterialApp(theme: theme, home: Material(type: MaterialType.transparency, child: tabBar)),
); );
...@@ -466,13 +303,13 @@ void main() { ...@@ -466,13 +303,13 @@ void main() {
expect(find.text('C'), findsOneWidget); expect(find.text('C'), findsOneWidget);
// Test selected label text style. // Test selected label text style.
final RenderParagraph selectedLabel = _getText(tester, selectedValue); final RenderParagraph selectedLabel = getTabText(tester, selectedValue);
expect(selectedLabel.text.style!.fontFamily, 'Roboto'); expect(selectedLabel.text.style!.fontFamily, 'Roboto');
expect(selectedLabel.text.style!.fontSize, 14.0); expect(selectedLabel.text.style!.fontSize, 14.0);
expect(selectedLabel.text.style!.color, theme.colorScheme.primary); expect(selectedLabel.text.style!.color, theme.colorScheme.primary);
// Test unselected label text style. // Test unselected label text style.
final RenderParagraph unselectedLabel = _getText(tester, unselectedValue); final RenderParagraph unselectedLabel = getTabText(tester, unselectedValue);
expect(unselectedLabel.text.style!.fontFamily, 'Roboto'); expect(unselectedLabel.text.style!.fontFamily, 'Roboto');
expect(unselectedLabel.text.style!.fontSize, 14.0); expect(unselectedLabel.text.style!.fontSize, 14.0);
expect(unselectedLabel.text.style!.color, theme.colorScheme.onSurfaceVariant); expect(unselectedLabel.text.style!.color, theme.colorScheme.onSurfaceVariant);
...@@ -492,13 +329,13 @@ void main() { ...@@ -492,13 +329,13 @@ void main() {
expect(find.text('C'), findsOneWidget); expect(find.text('C'), findsOneWidget);
// Test selected label text style. // Test selected label text style.
final RenderParagraph selectedLabel = _getText(tester, selectedValue); final RenderParagraph selectedLabel = getTabText(tester, selectedValue);
expect(selectedLabel.text.style!.fontFamily, 'Roboto'); expect(selectedLabel.text.style!.fontFamily, 'Roboto');
expect(selectedLabel.text.style!.fontSize, 14.0); expect(selectedLabel.text.style!.fontSize, 14.0);
expect(selectedLabel.text.style!.color, theme.colorScheme.onSurface); expect(selectedLabel.text.style!.color, theme.colorScheme.onSurface);
// Test unselected label text style. // Test unselected label text style.
final RenderParagraph unselectedLabel = _getText(tester, unselectedValue); final RenderParagraph unselectedLabel = getTabText(tester, unselectedValue);
expect(unselectedLabel.text.style!.fontFamily, 'Roboto'); expect(unselectedLabel.text.style!.fontFamily, 'Roboto');
expect(unselectedLabel.text.style!.fontSize, 14.0); expect(unselectedLabel.text.style!.fontSize, 14.0);
expect(unselectedLabel.text.style!.color, theme.colorScheme.onSurfaceVariant); expect(unselectedLabel.text.style!.color, theme.colorScheme.onSurfaceVariant);
...@@ -510,7 +347,7 @@ void main() { ...@@ -510,7 +347,7 @@ void main() {
return Tab(text: 'Tab $index'); return Tab(text: 'Tab $index');
}); });
final TabController controller = _tabController( final TabController controller = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: tabs.length, length: tabs.length,
); );
...@@ -559,7 +396,7 @@ void main() { ...@@ -559,7 +396,7 @@ void main() {
return Tab(text: 'Tab $index'); return Tab(text: 'Tab $index');
}); });
final TabController controller = _tabController( final TabController controller = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: tabs.length, length: tabs.length,
); );
...@@ -961,7 +798,7 @@ void main() { ...@@ -961,7 +798,7 @@ void main() {
length: tabs.length, length: tabs.length,
child: TabBarView( child: TabBarView(
children: tabs.map<Widget>((String name) { children: tabs.map<Widget>((String name) {
return StateMarker( return TabStateMarker(
child: Text(name), child: Text(name),
); );
}).toList(), }).toList(),
...@@ -970,8 +807,8 @@ void main() { ...@@ -970,8 +807,8 @@ void main() {
); );
} }
StateMarkerState findStateMarkerState(String name) { TabStateMarkerState findStateMarkerState(String name) {
return tester.state(find.widgetWithText(StateMarker, name, skipOffstage: false)); return tester.state(find.widgetWithText(TabStateMarker, name, skipOffstage: false));
} }
await tester.pumpWidget(builder()); await tester.pumpWidget(builder());
...@@ -1011,7 +848,7 @@ void main() { ...@@ -1011,7 +848,7 @@ void main() {
gesture = await tester.startGesture(tester.getCenter(find.text(tabs[2]))); gesture = await tester.startGesture(tester.getCenter(find.text(tabs[2])));
await gesture.moveBy(const Offset(600.0, 0.0)); await gesture.moveBy(const Offset(600.0, 0.0));
await tester.pump(); await tester.pump();
final StateMarkerState markerState = findStateMarkerState(tabs[1]); final TabStateMarkerState markerState = findStateMarkerState(tabs[1]);
expect(markerState.marker, isNull); expect(markerState.marker, isNull);
markerState.marker = 'marked'; markerState.marker = 'marked';
await gesture.up(); await gesture.up();
...@@ -1358,7 +1195,7 @@ void main() { ...@@ -1358,7 +1195,7 @@ void main() {
}); });
testWidgets('TabBar unselectedLabelColor control test', (WidgetTester tester) async { testWidgets('TabBar unselectedLabelColor control test', (WidgetTester tester) async {
final TabController controller = _tabController( final TabController controller = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: 2, length: 2,
); );
...@@ -1395,7 +1232,7 @@ void main() { ...@@ -1395,7 +1232,7 @@ void main() {
}); });
testWidgets('TabBarView page left and right test', (WidgetTester tester) async { testWidgets('TabBarView page left and right test', (WidgetTester tester) async {
final TabController controller = _tabController( final TabController controller = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: 2, length: 2,
); );
...@@ -1486,7 +1323,7 @@ void main() { ...@@ -1486,7 +1323,7 @@ void main() {
const Duration animationDuration = Duration(milliseconds: 100); const Duration animationDuration = Duration(milliseconds: 100);
final List<String> tabs = <String>['A', 'B', 'C']; final List<String> tabs = <String>['A', 'B', 'C'];
final TabController tabController = _tabController( final TabController tabController = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
initialIndex: 1, initialIndex: 1,
length: tabs.length, length: tabs.length,
...@@ -1536,7 +1373,7 @@ void main() { ...@@ -1536,7 +1373,7 @@ void main() {
const Duration animationDuration = Duration(seconds: 2); const Duration animationDuration = Duration(seconds: 2);
final List<String> tabs = <String>['A', 'B', 'C']; final List<String> tabs = <String>['A', 'B', 'C'];
final TabController tabController = _tabController( final TabController tabController = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: tabs.length, length: tabs.length,
animationDuration: animationDuration, animationDuration: animationDuration,
...@@ -1595,7 +1432,7 @@ void main() { ...@@ -1595,7 +1432,7 @@ void main() {
const Duration animationDuration = Duration(milliseconds: 100); const Duration animationDuration = Duration(milliseconds: 100);
final List<String> tabs = <String>['A', 'B', 'C']; final List<String> tabs = <String>['A', 'B', 'C'];
final TabController tabController = _tabController( final TabController tabController = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
initialIndex: 1, initialIndex: 1,
length: tabs.length, length: tabs.length,
...@@ -1639,7 +1476,7 @@ void main() { ...@@ -1639,7 +1476,7 @@ void main() {
const Duration animationDuration = Duration(milliseconds: 100); const Duration animationDuration = Duration(milliseconds: 100);
final List<String> tabs = <String>['A', 'B', 'C']; final List<String> tabs = <String>['A', 'B', 'C'];
final TabController tabController = _tabController( final TabController tabController = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
initialIndex: 1, initialIndex: 1,
length: tabs.length, length: tabs.length,
...@@ -1684,7 +1521,7 @@ void main() { ...@@ -1684,7 +1521,7 @@ void main() {
TabController? controller; TabController? controller;
Widget buildFrame(double viewportFraction) { Widget buildFrame(double viewportFraction) {
controller = _tabController( controller = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: tabs.length, length: tabs.length,
initialIndex: 1, initialIndex: 1,
...@@ -1865,7 +1702,7 @@ void main() { ...@@ -1865,7 +1702,7 @@ void main() {
testWidgets('TabBarView skips animation when disabled in controller', (WidgetTester tester) async { testWidgets('TabBarView skips animation when disabled in controller', (WidgetTester tester) async {
final List<String> tabs = <String>['A', 'B', 'C']; final List<String> tabs = <String>['A', 'B', 'C'];
final TabController tabController = _tabController( final TabController tabController = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
initialIndex: 1, initialIndex: 1,
length: tabs.length, length: tabs.length,
...@@ -1910,7 +1747,7 @@ void main() { ...@@ -1910,7 +1747,7 @@ void main() {
testWidgets('TabBarView skips animation when disabled in controller - skip tabs', (WidgetTester tester) async { testWidgets('TabBarView skips animation when disabled in controller - skip tabs', (WidgetTester tester) async {
final List<String> tabs = <String>['A', 'B', 'C']; final List<String> tabs = <String>['A', 'B', 'C'];
final TabController tabController = _tabController( final TabController tabController = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: tabs.length, length: tabs.length,
animationDuration: Duration.zero, animationDuration: Duration.zero,
...@@ -1955,7 +1792,7 @@ void main() { ...@@ -1955,7 +1792,7 @@ void main() {
testWidgets('TabBarView skips animation when disabled in controller - skip tabs twice', (WidgetTester tester) async { testWidgets('TabBarView skips animation when disabled in controller - skip tabs twice', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/110970 // Regression test for https://github.com/flutter/flutter/issues/110970
final List<String> tabs = <String>['A', 'B', 'C']; final List<String> tabs = <String>['A', 'B', 'C'];
final TabController tabController = _tabController( final TabController tabController = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: tabs.length, length: tabs.length,
animationDuration: Duration.zero, animationDuration: Duration.zero,
...@@ -2004,7 +1841,7 @@ void main() { ...@@ -2004,7 +1841,7 @@ void main() {
testWidgets('TabBarView skips animation when disabled in controller - skip tabs followed by single tab navigation', (WidgetTester tester) async { testWidgets('TabBarView skips animation when disabled in controller - skip tabs followed by single tab navigation', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/110970 // Regression test for https://github.com/flutter/flutter/issues/110970
final List<String> tabs = <String>['A', 'B', 'C']; final List<String> tabs = <String>['A', 'B', 'C'];
final TabController tabController = _tabController( final TabController tabController = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: tabs.length, length: tabs.length,
animationDuration: Duration.zero, animationDuration: Duration.zero,
...@@ -2056,7 +1893,7 @@ void main() { ...@@ -2056,7 +1893,7 @@ void main() {
testWidgets('TabBarView skips animation when disabled in controller - two tabs', (WidgetTester tester) async { testWidgets('TabBarView skips animation when disabled in controller - two tabs', (WidgetTester tester) async {
final List<String> tabs = <String>['A', 'B']; final List<String> tabs = <String>['A', 'B'];
final TabController tabController = _tabController( final TabController tabController = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: tabs.length, length: tabs.length,
animationDuration: Duration.zero, animationDuration: Duration.zero,
...@@ -2136,7 +1973,7 @@ void main() { ...@@ -2136,7 +1973,7 @@ void main() {
// This is a regression test for this patch: // This is a regression test for this patch:
// https://github.com/flutter/flutter/pull/9015 // https://github.com/flutter/flutter/pull/9015
final TabController controller = _tabController( final TabController controller = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: 2, length: 2,
); );
...@@ -2253,7 +2090,7 @@ void main() { ...@@ -2253,7 +2090,7 @@ void main() {
testWidgets('TabBarView scrolls end close to a new page', (WidgetTester tester) async { testWidgets('TabBarView scrolls end close to a new page', (WidgetTester tester) async {
// This is a regression test for https://github.com/flutter/flutter/issues/9375 // This is a regression test for https://github.com/flutter/flutter/issues/9375
final TabController tabController = _tabController( final TabController tabController = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
initialIndex: 1, initialIndex: 1,
length: 3, length: 3,
...@@ -2311,7 +2148,7 @@ void main() { ...@@ -2311,7 +2148,7 @@ void main() {
// This is a regression test for https://github.com/flutter/flutter/issues/132293. // This is a regression test for https://github.com/flutter/flutter/issues/132293.
final List<String> tabs = <String>['A', 'B', 'C']; final List<String> tabs = <String>['A', 'B', 'C'];
final TabController tabController = _tabController(length: tabs.length, vsync: const TestVSync()); final TabController tabController = createTabController(length: tabs.length, vsync: const TestVSync());
await tester.pumpWidget(boilerplate( await tester.pumpWidget(boilerplate(
child: Column( child: Column(
...@@ -2358,8 +2195,8 @@ void main() { ...@@ -2358,8 +2195,8 @@ void main() {
testWidgets('Can switch to non-neighboring tab in nested TabBarView without crashing', (WidgetTester tester) async { testWidgets('Can switch to non-neighboring tab in nested TabBarView without crashing', (WidgetTester tester) async {
// This is a regression test for https://github.com/flutter/flutter/issues/18756 // This is a regression test for https://github.com/flutter/flutter/issues/18756
final TabController mainTabController = _tabController(length: 4, vsync: const TestVSync()); final TabController mainTabController = createTabController(length: 4, vsync: const TestVSync());
final TabController nestedTabController = _tabController(length: 2, vsync: const TestVSync()); final TabController nestedTabController = createTabController(length: 2, vsync: const TestVSync());
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
...@@ -2380,7 +2217,29 @@ void main() { ...@@ -2380,7 +2217,29 @@ void main() {
controller: mainTabController, controller: mainTabController,
children: <Widget>[ children: <Widget>[
Container(color: Colors.red), Container(color: Colors.red),
_NestedTabBarContainer(tabController: nestedTabController), ColoredBox(
color: Colors.blue,
child: Column(
children: <Widget>[
TabBar(
controller: nestedTabController,
tabs: const <Tab>[
Tab(text: 'Yellow'),
Tab(text: 'Grey'),
],
),
Expanded(
child: TabBarView(
controller: nestedTabController,
children: <Widget>[
Container(color: Colors.yellow),
Container(color: Colors.grey),
],
),
),
],
),
),
Container(color: Colors.green), Container(color: Colors.green),
Container(color: Colors.indigo), Container(color: Colors.indigo),
], ],
...@@ -2402,7 +2261,7 @@ void main() { ...@@ -2402,7 +2261,7 @@ void main() {
testWidgets('TabBarView can warp when child is kept alive and contains ink', (WidgetTester tester) async { testWidgets('TabBarView can warp when child is kept alive and contains ink', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/57662. // Regression test for https://github.com/flutter/flutter/issues/57662.
final TabController controller = _tabController( final TabController controller = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: 3, length: 3,
); );
...@@ -2414,7 +2273,7 @@ void main() { ...@@ -2414,7 +2273,7 @@ void main() {
children: const <Widget>[ children: const <Widget>[
Text('Page 1'), Text('Page 1'),
Text('Page 2'), Text('Page 2'),
KeepAliveInk('Page 3'), TabKeepAliveInk(title: 'Page 3'),
], ],
), ),
), ),
...@@ -2438,7 +2297,7 @@ void main() { ...@@ -2438,7 +2297,7 @@ void main() {
}); });
testWidgets('TabBarView scrolls end close to a new page with custom physics', (WidgetTester tester) async { testWidgets('TabBarView scrolls end close to a new page with custom physics', (WidgetTester tester) async {
final TabController tabController = _tabController( final TabController tabController = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
initialIndex: 1, initialIndex: 1,
length: 3, length: 3,
...@@ -2453,7 +2312,7 @@ void main() { ...@@ -2453,7 +2312,7 @@ void main() {
height: 400.0, height: 400.0,
child: TabBarView( child: TabBarView(
controller: tabController, controller: tabController,
physics: const TestScrollPhysics(), physics: const TabBarTestScrollPhysics(),
children: const <Widget>[ children: const <Widget>[
Center(child: Text('0')), Center(child: Text('0')),
Center(child: Text('1')), Center(child: Text('1')),
...@@ -2498,7 +2357,7 @@ void main() { ...@@ -2498,7 +2357,7 @@ void main() {
return Tab(text: 'TAB #$index'); return Tab(text: 'TAB #$index');
}); });
final TabController controller = _tabController( final TabController controller = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: tabs.length, length: tabs.length,
initialIndex: tabs.length - 1, initialIndex: tabs.length - 1,
...@@ -2510,13 +2369,13 @@ void main() { ...@@ -2510,13 +2369,13 @@ void main() {
isScrollable: true, isScrollable: true,
controller: controller, controller: controller,
tabs: tabs, tabs: tabs,
physics: const TestScrollPhysics(), physics: const TabBarTestScrollPhysics(),
), ),
), ),
); );
final TabBar tabBar = tester.widget(find.byType(TabBar)); final TabBar tabBar = tester.widget(find.byType(TabBar));
final double position = tabBar.physics!.applyPhysicsToUserOffset(MockScrollMetrics(), 10); final double position = tabBar.physics!.applyPhysicsToUserOffset(TabMockScrollMetrics(), 10);
expect(position, equals(20)); expect(position, equals(20));
}); });
...@@ -2528,7 +2387,7 @@ void main() { ...@@ -2528,7 +2387,7 @@ void main() {
return Tab(text: 'TAB #$index'); return Tab(text: 'TAB #$index');
}); });
final TabController controller = _tabController( final TabController controller = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: tabs.length, length: tabs.length,
initialIndex: tabs.length - 1, initialIndex: tabs.length - 1,
...@@ -2562,7 +2421,7 @@ void main() { ...@@ -2562,7 +2421,7 @@ void main() {
child: const SizedBox(width: indicatorWidth)); child: const SizedBox(width: indicatorWidth));
}); });
final TabController controller = _tabController( final TabController controller = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: tabs.length, length: tabs.length,
); );
...@@ -2624,7 +2483,7 @@ void main() { ...@@ -2624,7 +2483,7 @@ void main() {
return Tab(text: 'Tab $index'); return Tab(text: 'Tab $index');
}); });
final TabController controller = _tabController( final TabController controller = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: tabs.length, length: tabs.length,
); );
...@@ -2684,7 +2543,7 @@ void main() { ...@@ -2684,7 +2543,7 @@ void main() {
return Tab(text: 'Tab $index'); return Tab(text: 'Tab $index');
}); });
final TabController controller = _tabController( final TabController controller = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: tabs.length, length: tabs.length,
); );
...@@ -2741,7 +2600,7 @@ void main() { ...@@ -2741,7 +2600,7 @@ void main() {
return Tab(text: 'Tab $index'); return Tab(text: 'Tab $index');
}); });
final TabController controller = _tabController( final TabController controller = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: tabs.length, length: tabs.length,
); );
...@@ -2813,7 +2672,7 @@ void main() { ...@@ -2813,7 +2672,7 @@ void main() {
const double indicatorWeight = 2.0; // the default const double indicatorWeight = 2.0; // the default
final TabController controller = _tabController( final TabController controller = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: tabs.length, length: tabs.length,
); );
...@@ -2883,7 +2742,7 @@ void main() { ...@@ -2883,7 +2742,7 @@ void main() {
const double indicatorWeight = 2.0; // the default const double indicatorWeight = 2.0; // the default
final TabController controller = _tabController( final TabController controller = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: tabs.length, length: tabs.length,
); );
...@@ -2955,7 +2814,7 @@ void main() { ...@@ -2955,7 +2814,7 @@ void main() {
return Tab(text: 'Tab $index'); return Tab(text: 'Tab $index');
}); });
final TabController controller = _tabController( final TabController controller = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: tabs.length, length: tabs.length,
); );
...@@ -3026,7 +2885,7 @@ void main() { ...@@ -3026,7 +2885,7 @@ void main() {
return Tab(text: 'Tab $index'); return Tab(text: 'Tab $index');
}); });
final TabController controller = _tabController( final TabController controller = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: tabs.length, length: tabs.length,
); );
...@@ -3099,7 +2958,7 @@ void main() { ...@@ -3099,7 +2958,7 @@ void main() {
const Decoration indicator = BoxDecoration(color: indicatorColor); const Decoration indicator = BoxDecoration(color: indicatorColor);
const double indicatorWeight = 2.0; // the default const double indicatorWeight = 2.0; // the default
final TabController controller = _tabController( final TabController controller = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: tabs.length, length: tabs.length,
); );
...@@ -3178,7 +3037,7 @@ void main() { ...@@ -3178,7 +3037,7 @@ void main() {
const Decoration indicator = BoxDecoration(color: indicatorColor); const Decoration indicator = BoxDecoration(color: indicatorColor);
const double indicatorWeight = 2.0; // the default const double indicatorWeight = 2.0; // the default
final TabController controller = _tabController( final TabController controller = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: tabs.length, length: tabs.length,
); );
...@@ -3255,7 +3114,7 @@ void main() { ...@@ -3255,7 +3114,7 @@ void main() {
SizedBox(key: UniqueKey(), width: double.infinity, height: 40.0), SizedBox(key: UniqueKey(), width: double.infinity, height: 40.0),
]; ];
final TabController controller = _tabController( final TabController controller = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: tabs.length, length: tabs.length,
); );
...@@ -3311,7 +3170,7 @@ void main() { ...@@ -3311,7 +3170,7 @@ void main() {
SizedBox(key: UniqueKey(), width: 150.0, height: 50.0), SizedBox(key: UniqueKey(), width: 150.0, height: 50.0),
]; ];
final TabController controller = _tabController( final TabController controller = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: tabs.length, length: tabs.length,
); );
...@@ -3375,7 +3234,7 @@ void main() { ...@@ -3375,7 +3234,7 @@ void main() {
SizedBox(key: UniqueKey(), width: 150.0, height: 50.0), SizedBox(key: UniqueKey(), width: 150.0, height: 50.0),
]; ];
final TabController controller = _tabController( final TabController controller = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: tabs.length, length: tabs.length,
); );
...@@ -3446,7 +3305,7 @@ void main() { ...@@ -3446,7 +3305,7 @@ void main() {
SizedBox(key: UniqueKey(), width: 150.0, height: 50.0), SizedBox(key: UniqueKey(), width: 150.0, height: 50.0),
]; ];
final TabController controller = _tabController( final TabController controller = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: tabs.length, length: tabs.length,
); );
...@@ -3514,7 +3373,7 @@ void main() { ...@@ -3514,7 +3373,7 @@ void main() {
SizedBox(key: UniqueKey(), width: 68.0, height: 40.0), SizedBox(key: UniqueKey(), width: 68.0, height: 40.0),
); );
final TabController controller = _tabController( final TabController controller = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: tabs.length, length: tabs.length,
); );
...@@ -3579,7 +3438,7 @@ void main() { ...@@ -3579,7 +3438,7 @@ void main() {
return Tab(text: 'Tab $index'); return Tab(text: 'Tab $index');
}); });
final TabController controller = _tabController( final TabController controller = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: tabs.length, length: tabs.length,
); );
...@@ -3656,7 +3515,7 @@ void main() { ...@@ -3656,7 +3515,7 @@ void main() {
return Tab(text: 'TAB #$index'); return Tab(text: 'TAB #$index');
}); });
final TabController controller = _tabController( final TabController controller = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: tabs.length, length: tabs.length,
); );
...@@ -3730,7 +3589,7 @@ void main() { ...@@ -3730,7 +3589,7 @@ void main() {
return Tab(text: 'This is a very wide tab #$index'); return Tab(text: 'This is a very wide tab #$index');
}); });
final TabController controller = _tabController( final TabController controller = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: tabs.length, length: tabs.length,
); );
...@@ -3779,7 +3638,7 @@ void main() { ...@@ -3779,7 +3638,7 @@ void main() {
}); });
testWidgets('TabBar etc with zero tabs', (WidgetTester tester) async { testWidgets('TabBar etc with zero tabs', (WidgetTester tester) async {
final TabController controller = _tabController( final TabController controller = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: 0, length: 0,
); );
...@@ -3819,7 +3678,7 @@ void main() { ...@@ -3819,7 +3678,7 @@ void main() {
}); });
testWidgets('TabBar etc with one tab', (WidgetTester tester) async { testWidgets('TabBar etc with one tab', (WidgetTester tester) async {
final TabController controller = _tabController( final TabController controller = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: 1, length: 1,
); );
...@@ -3874,7 +3733,7 @@ void main() { ...@@ -3874,7 +3733,7 @@ void main() {
}); });
testWidgets('can tap on indicator at very bottom of TabBar to switch tabs', (WidgetTester tester) async { testWidgets('can tap on indicator at very bottom of TabBar to switch tabs', (WidgetTester tester) async {
final TabController controller = _tabController( final TabController controller = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: 2, length: 2,
); );
...@@ -3923,7 +3782,7 @@ void main() { ...@@ -3923,7 +3782,7 @@ void main() {
); );
}); });
final TabController controller = _tabController( final TabController controller = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: tabs.length, length: tabs.length,
); );
...@@ -4009,7 +3868,7 @@ void main() { ...@@ -4009,7 +3868,7 @@ void main() {
} }
final List<String> tabs = <String>['A', 'B', 'C']; final List<String> tabs = <String>['A', 'B', 'C'];
final TabController controller = _tabController( final TabController controller = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: tabs.length, length: tabs.length,
initialIndex: tabs.indexOf('C'), initialIndex: tabs.indexOf('C'),
...@@ -4133,12 +3992,12 @@ void main() { ...@@ -4133,12 +3992,12 @@ void main() {
); );
} }
final TabController controller1 = _tabController( final TabController controller1 = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: 2, length: 2,
); );
final TabController controller2 = _tabController( final TabController controller2 = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: 2, length: 2,
); );
...@@ -4211,12 +4070,12 @@ void main() { ...@@ -4211,12 +4070,12 @@ void main() {
); );
} }
final TabController controller1 = _tabController( final TabController controller1 = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: 2, length: 2,
); );
final TabController controller2 = _tabController( final TabController controller2 = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: 3, length: 3,
); );
...@@ -4245,7 +4104,7 @@ void main() { ...@@ -4245,7 +4104,7 @@ void main() {
TabController? controller; TabController? controller;
Widget buildFrame(int length) { Widget buildFrame(int length) {
controller = _tabController( controller = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: length, length: length,
initialIndex: length - 1, initialIndex: length - 1,
...@@ -4290,7 +4149,7 @@ void main() { ...@@ -4290,7 +4149,7 @@ void main() {
length: length, length: length,
initialIndex: length - 1, initialIndex: length - 1,
child: TabBarView( child: TabBarView(
physics: const TestScrollPhysics(), physics: const TabBarTestScrollPhysics(),
children: List<Widget>.generate( children: List<Widget>.generate(
length, length,
(int index) { (int index) {
...@@ -4322,11 +4181,11 @@ void main() { ...@@ -4322,11 +4181,11 @@ void main() {
testWidgets('Do not throw when switching between a scrollable TabBar and a non-scrollable TabBar', (WidgetTester tester) async { testWidgets('Do not throw when switching between a scrollable TabBar and a non-scrollable TabBar', (WidgetTester tester) async {
// This is a regression test for https://github.com/flutter/flutter/issues/120649 // This is a regression test for https://github.com/flutter/flutter/issues/120649
final TabController controller1 = _tabController( final TabController controller1 = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: 2, length: 2,
); );
final TabController controller2 = _tabController( final TabController controller2 = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: 2, length: 2,
); );
...@@ -4524,7 +4383,7 @@ void main() { ...@@ -4524,7 +4383,7 @@ void main() {
'Tab3', 'Tab3',
'Tab4', 'Tab4',
]; ];
final TabController controller = _tabController( final TabController controller = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: tabs.length, length: tabs.length,
); );
...@@ -4575,7 +4434,7 @@ void main() { ...@@ -4575,7 +4434,7 @@ void main() {
'Tab4', 'Tab4',
'Tab5', 'Tab5',
]; ];
final TabController controller = _tabController( final TabController controller = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: tabs.length, length: tabs.length,
); );
...@@ -4597,7 +4456,7 @@ void main() { ...@@ -4597,7 +4456,7 @@ void main() {
body: TabBarView( body: TabBarView(
controller: controller, controller: controller,
children: <Widget>[ children: <Widget>[
AlwaysKeepAliveWidget(key: UniqueKey()), TabAlwaysKeepAliveWidget(key: UniqueKey()),
const Text('2'), const Text('2'),
const Text('3'), const Text('3'),
const Text('4'), const Text('4'),
...@@ -4609,13 +4468,13 @@ void main() { ...@@ -4609,13 +4468,13 @@ void main() {
), ),
), ),
); );
expect(find.text(AlwaysKeepAliveWidget.text), findsOneWidget); expect(find.text(TabAlwaysKeepAliveWidget.text), findsOneWidget);
expect(find.text('4'), findsNothing); expect(find.text('4'), findsNothing);
await tester.tap(find.text('Tab4')); await tester.tap(find.text('Tab4'));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
await tester.pump(); await tester.pump();
expect(controller.index, 3); expect(controller.index, 3);
expect(find.text(AlwaysKeepAliveWidget.text, skipOffstage: false), findsOneWidget); expect(find.text(TabAlwaysKeepAliveWidget.text, skipOffstage: false), findsOneWidget);
expect(find.text('4'), findsOneWidget); expect(find.text('4'), findsOneWidget);
}); });
...@@ -4627,7 +4486,7 @@ void main() { ...@@ -4627,7 +4486,7 @@ void main() {
Tab(text: 'GABBA'), Tab(text: 'GABBA'),
Tab(text: 'HEY'), Tab(text: 'HEY'),
]; ];
final TabController controller = _tabController(vsync: const TestVSync(), length: tabs.length); final TabController controller = createTabController(vsync: const TestVSync(), length: tabs.length);
Widget buildTestWidget({double? width, double? height}) { Widget buildTestWidget({double? width, double? height}) {
return MaterialApp( return MaterialApp(
...@@ -4862,7 +4721,7 @@ void main() { ...@@ -4862,7 +4721,7 @@ void main() {
onPressed: () { onPressed: () {
setState(() { setState(() {
controller.dispose(); controller.dispose();
controller = _tabController(vsync: const TestVSync(), length: 3); controller = createTabController(vsync: const TestVSync(), length: 3);
}); });
}, },
), ),
...@@ -5042,19 +4901,19 @@ void main() { ...@@ -5042,19 +4901,19 @@ void main() {
testWidgets('TabBar - updating to and from zero tabs', (WidgetTester tester) async { testWidgets('TabBar - updating to and from zero tabs', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/68962. // Regression test for https://github.com/flutter/flutter/issues/68962.
final List<String> tabTitles = <String>[]; final List<String> tabTitles = <String>[];
TabController tabController = _tabController(length: tabTitles.length, vsync: const TestVSync()); TabController tabController = createTabController(length: tabTitles.length, vsync: const TestVSync());
void onTabAdd(StateSetter setState) { void onTabAdd(StateSetter setState) {
setState(() { setState(() {
tabTitles.add('Tab ${tabTitles.length + 1}'); tabTitles.add('Tab ${tabTitles.length + 1}');
tabController = _tabController(length: tabTitles.length, vsync: const TestVSync()); tabController = createTabController(length: tabTitles.length, vsync: const TestVSync());
}); });
} }
void onTabRemove(StateSetter setState) { void onTabRemove(StateSetter setState) {
setState(() { setState(() {
tabTitles.removeLast(); tabTitles.removeLast();
tabController = _tabController(length: tabTitles.length, vsync: const TestVSync()); tabController = createTabController(length: tabTitles.length, vsync: const TestVSync());
}); });
} }
...@@ -5203,7 +5062,7 @@ void main() { ...@@ -5203,7 +5062,7 @@ void main() {
}); });
testWidgets('TabController.offset changes reflect labelColor', (WidgetTester tester) async { testWidgets('TabController.offset changes reflect labelColor', (WidgetTester tester) async {
final TabController controller = _tabController( final TabController controller = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: 2, length: 2,
); );
...@@ -5287,15 +5146,40 @@ void main() { ...@@ -5287,15 +5146,40 @@ void main() {
await testLabelColor(selectedColor: Colors.white, unselectedColor: Colors.transparent); await testLabelColor(selectedColor: Colors.white, unselectedColor: Colors.transparent);
}); });
testWidgets('Crash on dispose', (WidgetTester tester) async { testWidgets('No crash on dispose', (WidgetTester tester) async {
await tester.pumpWidget(const Padding(padding: EdgeInsets.only(right: 200.0), child: TabBarDemo())); await tester.pumpWidget(
MaterialApp(
home: DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
bottom: const TabBar(
tabs: <Widget>[
Tab(icon: Icon(Icons.directions_car)),
Tab(icon: Icon(Icons.directions_transit)),
Tab(icon: Icon(Icons.directions_bike)),
],
),
title: const Text('Tabs Demo'),
),
body: const TabBarView(
children: <Widget>[
Icon(Icons.directions_car),
Icon(Icons.directions_transit),
Icon(Icons.directions_bike),
],
),
),
),
),
);
await tester.tap(find.byIcon(Icons.directions_bike)); await tester.tap(find.byIcon(Icons.directions_bike));
// There was a time where this would throw an exception // No crash on dispose.
// because we tried to send a notification on dispose. expect(tester.takeException(), isNull);
}); });
testWidgets("TabController's animation value should be in sync with TabBarView's scroll value when user interrupts ballistic scroll", (WidgetTester tester) async { testWidgets("TabController's animation value should be in sync with TabBarView's scroll value when user interrupts ballistic scroll", (WidgetTester tester) async {
final TabController tabController = _tabController( final TabController tabController = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: 3, length: 3,
); );
...@@ -5529,7 +5413,7 @@ void main() { ...@@ -5529,7 +5413,7 @@ void main() {
home: Scaffold( home: Scaffold(
appBar: AppBar( appBar: AppBar(
bottom: TabBar( bottom: TabBar(
controller: _tabController(length: 3, vsync: const TestVSync()), controller: createTabController(length: 3, vsync: const TestVSync()),
tabs: const <Widget>[ tabs: const <Widget>[
Tab(text: 'Tab 1', icon: Icon(Icons.plus_one)), Tab(text: 'Tab 1', icon: Icon(Icons.plus_one)),
Tab(text: 'Tab 2'), Tab(text: 'Tab 2'),
...@@ -5561,7 +5445,7 @@ void main() { ...@@ -5561,7 +5445,7 @@ void main() {
appBar: AppBar( appBar: AppBar(
bottom: TabBar( bottom: TabBar(
labelPadding: labelPadding, labelPadding: labelPadding,
controller: _tabController(length: 3, vsync: const TestVSync()), controller: createTabController(length: 3, vsync: const TestVSync()),
tabs: const <Widget>[ tabs: const <Widget>[
Tab(text: 'Tab 1', icon: Icon(Icons.plus_one)), Tab(text: 'Tab 1', icon: Icon(Icons.plus_one)),
Tab(text: 'Tab 2'), Tab(text: 'Tab 2'),
...@@ -5595,7 +5479,7 @@ void main() { ...@@ -5595,7 +5479,7 @@ void main() {
home: Scaffold( home: Scaffold(
appBar: AppBar( appBar: AppBar(
bottom: TabBar( bottom: TabBar(
controller: _tabController(length: 3, vsync: const TestVSync()), controller: createTabController(length: 3, vsync: const TestVSync()),
tabs: const <Widget>[ tabs: const <Widget>[
Tab(text: 'Tab 1', icon: Icon(Icons.plus_one)), Tab(text: 'Tab 1', icon: Icon(Icons.plus_one)),
Tab(text: 'Tab 2'), Tab(text: 'Tab 2'),
...@@ -5685,7 +5569,7 @@ void main() { ...@@ -5685,7 +5569,7 @@ void main() {
testWidgets('Test semantics of TabPageSelector', (WidgetTester tester) async { testWidgets('Test semantics of TabPageSelector', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester); final SemanticsTester semantics = SemanticsTester(tester);
final TabController controller = _tabController( final TabController controller = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: 2, length: 2,
); );
...@@ -5794,17 +5678,17 @@ void main() { ...@@ -5794,17 +5678,17 @@ void main() {
); );
} }
final TabController controller1 = _tabController( final TabController controller1 = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: 3, length: 3,
); );
final TabController controller2 = _tabController( final TabController controller2 = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: 2, length: 2,
); );
final TabController controller3 = _tabController( final TabController controller3 = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: 3, length: 3,
); );
...@@ -5866,12 +5750,12 @@ void main() { ...@@ -5866,12 +5750,12 @@ void main() {
); );
} }
final TabController controller1 = _tabController( final TabController controller1 = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: 3, length: 3,
); );
final TabController controller2 = _tabController( final TabController controller2 = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: 2, length: 2,
); );
...@@ -6564,7 +6448,7 @@ void main() { ...@@ -6564,7 +6448,7 @@ void main() {
home: Scaffold( home: Scaffold(
appBar: AppBar( appBar: AppBar(
bottom: TabBar( bottom: TabBar(
controller: _tabController(length: 3, vsync: const TestVSync()), controller: createTabController(length: 3, vsync: const TestVSync()),
tabs: const <Widget>[ tabs: const <Widget>[
Tab(text: 'Tab 1'), Tab(text: 'Tab 1'),
Tab(text: 'Tab 2'), Tab(text: 'Tab 2'),
...@@ -6923,7 +6807,7 @@ void main() { ...@@ -6923,7 +6807,7 @@ void main() {
return Tab(text: 'Tab $index'); return Tab(text: 'Tab $index');
}); });
final TabController controller = _tabController( final TabController controller = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: tabs.length, length: tabs.length,
); );
...@@ -6969,7 +6853,7 @@ void main() { ...@@ -6969,7 +6853,7 @@ void main() {
return Tab(text: 'Tab $index'); return Tab(text: 'Tab $index');
}); });
final TabController controller = _tabController( final TabController controller = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: tabs.length, length: tabs.length,
); );
...@@ -7019,7 +6903,7 @@ void main() { ...@@ -7019,7 +6903,7 @@ void main() {
SizedBox(key: UniqueKey(), width: 150.0, height: 50.0), SizedBox(key: UniqueKey(), width: 150.0, height: 50.0),
]; ];
final TabController controller = _tabController( final TabController controller = createTabController(
vsync: const TestVSync(), vsync: const TestVSync(),
length: tabs.length, length: tabs.length,
); );
...@@ -7110,100 +6994,3 @@ void main() { ...@@ -7110,100 +6994,3 @@ void main() {
}); });
}); });
} }
class KeepAliveInk extends StatefulWidget {
const KeepAliveInk(this.title, {super.key});
final String title;
@override
State<StatefulWidget> createState() {
return _KeepAliveInkState();
}
}
class _KeepAliveInkState extends State<KeepAliveInk> with AutomaticKeepAliveClientMixin {
@override
Widget build(BuildContext context) {
super.build(context);
return Ink(
child: Text(widget.title),
);
}
@override
bool get wantKeepAlive => true;
}
class TabBarDemo extends StatelessWidget {
const TabBarDemo({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
bottom: const TabBar(
tabs: <Widget>[
Tab(icon: Icon(Icons.directions_car)),
Tab(icon: Icon(Icons.directions_transit)),
Tab(icon: Icon(Icons.directions_bike)),
],
),
title: const Text('Tabs Demo'),
),
body: const TabBarView(
children: <Widget>[
Icon(Icons.directions_car),
Icon(Icons.directions_transit),
Icon(Icons.directions_bike),
],
),
),
),
);
}
}
class MockScrollMetrics extends Fake implements ScrollMetrics { }
class TabBody extends StatefulWidget {
const TabBody({ super.key, required this.index, required this.log, this.marker = '' });
final int index;
final List<String> log;
final String marker;
@override
State<TabBody> createState() => TabBodyState();
}
class TabBodyState extends State<TabBody> {
@override
void initState() {
widget.log.add('init: ${widget.index}');
super.initState();
}
@override
void didUpdateWidget(TabBody oldWidget) {
super.didUpdateWidget(oldWidget);
// To keep the logging straight, widgets must not change their index.
assert(oldWidget.index == widget.index);
}
@override
void dispose() {
widget.log.add('dispose: ${widget.index}');
super.dispose();
}
@override
Widget build(BuildContext context) {
return Center(
child: widget.marker.isEmpty
? Text('${widget.index}')
: Text('${widget.index}-${widget.marker}'),
);
}
}
// 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 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
// This returns render paragraph of the Tab label text.
RenderParagraph getTabText(WidgetTester tester, String text) {
return tester.renderObject<RenderParagraph>(find.descendant(
of: find.byWidgetPredicate((Widget widget) => widget.runtimeType.toString() == '_TabStyle'),
matching: find.text(text),
));
}
// This creates and returns a TabController.
TabController createTabController({
required int length,
required TickerProvider vsync,
int initialIndex = 0,
Duration? animationDuration,
}) {
final TabController result = TabController(
length: length,
vsync: vsync,
initialIndex: initialIndex,
animationDuration: animationDuration,
);
addTearDown(result.dispose);
return result;
}
// This widget is used to test widget state in the tabs_test.dart file.
class TabStateMarker extends StatefulWidget {
const TabStateMarker({ super.key, this.child });
final Widget? child;
@override
TabStateMarkerState createState() => TabStateMarkerState();
}
class TabStateMarkerState extends State<TabStateMarker> {
String? marker;
@override
Widget build(BuildContext context) {
return widget.child ?? Container();
}
}
// Tab controller builder for TabControllerFrame widget.
typedef TabControllerFrameBuilder = Widget Function(BuildContext context, TabController controller);
// This widget creates a TabController and passes it to the builder.
class TabControllerFrame extends StatefulWidget {
const TabControllerFrame({
super.key,
required this.length,
this.initialIndex = 0,
required this.builder,
});
final int length;
final int initialIndex;
final TabControllerFrameBuilder builder;
@override
TabControllerFrameState createState() => TabControllerFrameState();
}
class TabControllerFrameState extends State<TabControllerFrame> with SingleTickerProviderStateMixin {
late TabController _controller;
@override
void initState() {
super.initState();
_controller = TabController(
vsync: this,
length: widget.length,
initialIndex: widget.initialIndex,
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return widget.builder(context, _controller);
}
}
// Test utility class to test tab indicator drawing.
class TabIndicatorRecordingCanvas extends TestRecordingCanvas {
TabIndicatorRecordingCanvas(this.indicatorColor);
final Color indicatorColor;
late Rect indicatorRect;
@override
void drawLine(Offset p1, Offset p2, Paint paint) {
// Assuming that the indicatorWeight is 2.0, the default.
const double indicatorWeight = 2.0;
if (paint.color == indicatorColor) {
indicatorRect = Rect.fromPoints(p1, p2).inflate(indicatorWeight / 2.0);
}
}
}
// This creates a Fake implementation of ScrollMetrics.
class TabMockScrollMetrics extends Fake implements ScrollMetrics { }
class TabBarTestScrollPhysics extends ScrollPhysics {
const TabBarTestScrollPhysics({ super.parent });
@override
TabBarTestScrollPhysics applyTo(ScrollPhysics? ancestor) {
return TabBarTestScrollPhysics(parent: buildParent(ancestor));
}
@override
double applyPhysicsToUserOffset(ScrollMetrics position, double offset) {
return offset == 10 ? 20 : offset;
}
static final SpringDescription _kDefaultSpring = SpringDescription.withDampingRatio(
mass: 0.5,
stiffness: 500.0,
ratio: 1.1,
);
@override
SpringDescription get spring => _kDefaultSpring;
}
// This widget is used to log the lifecycle of the TabBarView children.
class TabBody extends StatefulWidget {
const TabBody({
super.key,
required this.index,
required this.log,
this.marker = '',
});
final int index;
final List<String> log;
final String marker;
@override
State<TabBody> createState() => TabBodyState();
}
class TabBodyState extends State<TabBody> {
@override
void initState() {
widget.log.add('init: ${widget.index}');
super.initState();
}
@override
void didUpdateWidget(TabBody oldWidget) {
super.didUpdateWidget(oldWidget);
// To keep the logging straight, widgets must not change their index.
assert(oldWidget.index == widget.index);
}
@override
void dispose() {
widget.log.add('dispose: ${widget.index}');
super.dispose();
}
@override
Widget build(BuildContext context) {
return Center(
child: widget.marker.isEmpty
? Text('${widget.index}')
: Text('${widget.index}-${widget.marker}'),
);
}
}
// This widget is used to test the lifecycle of the TabBarView children with Ink widget.
class TabKeepAliveInk extends StatefulWidget {
const TabKeepAliveInk({ super.key, required this.title });
final String title;
@override
State<StatefulWidget> createState() => _TabKeepAliveInkState();
}
class _TabKeepAliveInkState extends State<TabKeepAliveInk> with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
super.build(context);
return Ink(
child: Text(widget.title),
);
}
}
// This widget is used to test the lifecycle of the TabBarView children.
class TabAlwaysKeepAliveWidget extends StatefulWidget {
const TabAlwaysKeepAliveWidget({super.key});
static String text = 'AlwaysKeepAlive';
@override
State<TabAlwaysKeepAliveWidget> createState() => _TabAlwaysKeepAliveWidgetState();
}
class _TabAlwaysKeepAliveWidgetState extends State<TabAlwaysKeepAliveWidget> with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
super.build(context);
return Text(TabAlwaysKeepAliveWidget.text);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment