mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Add test for tab_controller.1.dart API example. (#148189)
This PR contributes to https://github.com/flutter/flutter/issues/130459 ### Description - Updates `examples/api/lib/material/tab_controller/tab_controller.1.dart` to properly remove the listener from the `TabController` - Adds tests for `examples/api/lib/material/tab_controller/tab_controller.1.dart`
This commit is contained in:
parent
6067d8f219
commit
03e6cfa7b1
@ -346,7 +346,6 @@ final Set<String> _knownMissingTests = <String>{
|
|||||||
'examples/api/test/material/search_anchor/search_anchor.1_test.dart',
|
'examples/api/test/material/search_anchor/search_anchor.1_test.dart',
|
||||||
'examples/api/test/material/search_anchor/search_anchor.2_test.dart',
|
'examples/api/test/material/search_anchor/search_anchor.2_test.dart',
|
||||||
'examples/api/test/material/about/about_list_tile.0_test.dart',
|
'examples/api/test/material/about/about_list_tile.0_test.dart',
|
||||||
'examples/api/test/material/tab_controller/tab_controller.1_test.dart',
|
|
||||||
'examples/api/test/material/selection_area/selection_area.0_test.dart',
|
'examples/api/test/material/selection_area/selection_area.0_test.dart',
|
||||||
'examples/api/test/material/scaffold/scaffold.end_drawer.0_test.dart',
|
'examples/api/test/material/scaffold/scaffold.end_drawer.0_test.dart',
|
||||||
'examples/api/test/material/scaffold/scaffold.drawer.0_test.dart',
|
'examples/api/test/material/scaffold/scaffold.drawer.0_test.dart',
|
||||||
|
@ -11,42 +11,39 @@ void main() => runApp(const TabControllerExampleApp());
|
|||||||
class TabControllerExampleApp extends StatelessWidget {
|
class TabControllerExampleApp extends StatelessWidget {
|
||||||
const TabControllerExampleApp({super.key});
|
const TabControllerExampleApp({super.key});
|
||||||
|
|
||||||
@override
|
static const List<Tab> tabs = <Tab>[
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return const MaterialApp(
|
|
||||||
home: TabControllerExample(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const List<Tab> tabs = <Tab>[
|
|
||||||
Tab(text: 'Zeroth'),
|
Tab(text: 'Zeroth'),
|
||||||
Tab(text: 'First'),
|
Tab(text: 'First'),
|
||||||
Tab(text: 'Second'),
|
Tab(text: 'Second'),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return const MaterialApp(
|
||||||
|
home: TabControllerExample(tabs: tabs),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class TabControllerExample extends StatelessWidget {
|
class TabControllerExample extends StatelessWidget {
|
||||||
const TabControllerExample({super.key});
|
const TabControllerExample({
|
||||||
|
required this.tabs,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<Tab> tabs;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return DefaultTabController(
|
return DefaultTabController(
|
||||||
length: tabs.length,
|
length: tabs.length,
|
||||||
// The Builder widget is used to have a different BuildContext to access
|
child: DefaultTabControllerListener(
|
||||||
// closest DefaultTabController.
|
onTabChanged: (int index) {
|
||||||
child: Builder(builder: (BuildContext context) {
|
debugPrint('tab changed: $index');
|
||||||
final TabController tabController = DefaultTabController.of(context);
|
},
|
||||||
tabController.addListener(() {
|
child: Scaffold(
|
||||||
if (!tabController.indexIsChanging) {
|
|
||||||
// Your code goes here.
|
|
||||||
// To get index of current tab use tabController.index
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
bottom: const TabBar(
|
bottom: TabBar(tabs: tabs),
|
||||||
tabs: tabs,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
body: TabBarView(
|
body: TabBarView(
|
||||||
children: tabs.map((Tab tab) {
|
children: tabs.map((Tab tab) {
|
||||||
@ -58,8 +55,75 @@ class TabControllerExample extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
}),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DefaultTabControllerListener extends StatefulWidget {
|
||||||
|
const DefaultTabControllerListener({
|
||||||
|
required this.onTabChanged,
|
||||||
|
required this.child,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final ValueChanged<int> onTabChanged;
|
||||||
|
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DefaultTabControllerListener> createState() =>
|
||||||
|
_DefaultTabControllerListenerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DefaultTabControllerListenerState
|
||||||
|
extends State<DefaultTabControllerListener> {
|
||||||
|
TabController? _controller;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
|
||||||
|
final TabController? defaultTabController =
|
||||||
|
DefaultTabController.maybeOf(context);
|
||||||
|
|
||||||
|
assert(() {
|
||||||
|
if (defaultTabController == null) {
|
||||||
|
throw FlutterError(
|
||||||
|
'No DefaultTabController for ${widget.runtimeType}.\n'
|
||||||
|
'When creating a ${widget.runtimeType}, you must ensure that there '
|
||||||
|
'is a DefaultTabController above the ${widget.runtimeType}.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
|
||||||
|
if (defaultTabController != _controller) {
|
||||||
|
_controller?.removeListener(_listener);
|
||||||
|
_controller = defaultTabController;
|
||||||
|
_controller?.addListener(_listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _listener() {
|
||||||
|
final TabController? controller = _controller;
|
||||||
|
|
||||||
|
if (controller == null || controller.indexIsChanging) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
widget.onTabChanged(controller.index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_controller?.removeListener(_listener);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return widget.child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -0,0 +1,96 @@
|
|||||||
|
// 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/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_api_samples/material/tab_controller/tab_controller.1.dart'
|
||||||
|
as example;
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('Verify first tab is selected by default', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
const example.TabControllerExampleApp(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final Tab firstTab = example.TabControllerExampleApp.tabs.first;
|
||||||
|
|
||||||
|
expect(
|
||||||
|
find.descendant(
|
||||||
|
of: find.byType(TabBarView),
|
||||||
|
matching: find.text('${firstTab.text} Tab'),
|
||||||
|
),
|
||||||
|
findsOneWidget,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Verify tabs can be changed', (WidgetTester tester) async {
|
||||||
|
final List<String?> log = <String?>[];
|
||||||
|
|
||||||
|
final DebugPrintCallback originalDebugPrint = debugPrint;
|
||||||
|
debugPrint = (String? message, {int? wrapWidth}) {
|
||||||
|
log.add(message);
|
||||||
|
};
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
const example.TabControllerExampleApp(),
|
||||||
|
);
|
||||||
|
|
||||||
|
const List<Tab> tabs = example.TabControllerExampleApp.tabs;
|
||||||
|
final List<Tab> tabsTraversalOrder = <Tab>[];
|
||||||
|
|
||||||
|
// The traverse order is from the second tab from the start to the last,
|
||||||
|
// and then from the second tab from the end to the first.
|
||||||
|
tabsTraversalOrder.addAll(tabs.skip(1));
|
||||||
|
tabsTraversalOrder.addAll(tabs.reversed.skip(1));
|
||||||
|
|
||||||
|
for (final Tab tab in tabsTraversalOrder) {
|
||||||
|
// Tap on the TabBar's tab to select it.
|
||||||
|
await tester.tap(find.descendant(
|
||||||
|
of: find.byType(TabBar),
|
||||||
|
matching: find.text(tab.text!),
|
||||||
|
));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
find.descendant(
|
||||||
|
of: find.byType(TabBarView),
|
||||||
|
matching: find.text('${tab.text} Tab'),
|
||||||
|
),
|
||||||
|
findsOneWidget,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(log.length, equals(1));
|
||||||
|
expect(log.last, equals('tab changed: ${tabs.indexOf(tab)}'));
|
||||||
|
|
||||||
|
log.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
debugPrint = originalDebugPrint;
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('DefaultTabControllerListener throws when no DefaultTabController above', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
example.DefaultTabControllerListener(
|
||||||
|
onTabChanged: (_) {},
|
||||||
|
child: const SizedBox.shrink(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final dynamic exception = tester.takeException();
|
||||||
|
expect(exception, isFlutterError);
|
||||||
|
|
||||||
|
final FlutterError error = exception as FlutterError;
|
||||||
|
expect(
|
||||||
|
error.toStringDeep(),
|
||||||
|
equalsIgnoringHashCodes(
|
||||||
|
'FlutterError\n'
|
||||||
|
' No DefaultTabController for DefaultTabControllerListener.\n'
|
||||||
|
' When creating a DefaultTabControllerListener, you must ensure\n'
|
||||||
|
' that there is a DefaultTabController above the\n'
|
||||||
|
' DefaultTabControllerListener.\n',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user