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:
Kostia Sokolovskyi 2024-05-20 17:33:16 +02:00 committed by GitHub
parent 6067d8f219
commit 03e6cfa7b1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 184 additions and 25 deletions

View File

@ -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',

View File

@ -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;
}
}

View File

@ -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',
),
);
});
}