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.2_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/scaffold/scaffold.end_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 {
|
||||
const TabControllerExampleApp({super.key});
|
||||
|
||||
static const List<Tab> tabs = <Tab>[
|
||||
Tab(text: 'Zeroth'),
|
||||
Tab(text: 'First'),
|
||||
Tab(text: 'Second'),
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const MaterialApp(
|
||||
home: TabControllerExample(),
|
||||
home: TabControllerExample(tabs: tabs),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const List<Tab> tabs = <Tab>[
|
||||
Tab(text: 'Zeroth'),
|
||||
Tab(text: 'First'),
|
||||
Tab(text: 'Second'),
|
||||
];
|
||||
|
||||
class TabControllerExample extends StatelessWidget {
|
||||
const TabControllerExample({super.key});
|
||||
const TabControllerExample({
|
||||
required this.tabs,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final List<Tab> tabs;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DefaultTabController(
|
||||
length: tabs.length,
|
||||
// The Builder widget is used to have a different BuildContext to access
|
||||
// closest DefaultTabController.
|
||||
child: Builder(builder: (BuildContext context) {
|
||||
final TabController tabController = DefaultTabController.of(context);
|
||||
tabController.addListener(() {
|
||||
if (!tabController.indexIsChanging) {
|
||||
// Your code goes here.
|
||||
// To get index of current tab use tabController.index
|
||||
}
|
||||
});
|
||||
return Scaffold(
|
||||
child: DefaultTabControllerListener(
|
||||
onTabChanged: (int index) {
|
||||
debugPrint('tab changed: $index');
|
||||
},
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
bottom: const TabBar(
|
||||
tabs: tabs,
|
||||
),
|
||||
bottom: TabBar(tabs: tabs),
|
||||
),
|
||||
body: TabBarView(
|
||||
children: tabs.map((Tab tab) {
|
||||
@ -58,8 +55,75 @@ class TabControllerExample extends StatelessWidget {
|
||||
);
|
||||
}).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