blob: 1c4f05c7b90445c1e9aa1ae4e6dbfd06b8730da1 [file] [log] [blame]
// 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.
// reduced-test-set:
// This file is run as part of a reduced test set in CI on Mac and Windows
// machines.
@Tags(<String>['reduced-test-set'])
library;
import 'package:flutter/cupertino.dart';
import 'package:flutter/semantics.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
const expansionDuration = Duration(milliseconds: 250);
const infinitesimalDuration = Duration(microseconds: 1);
testWidgets('Toggles expansion on tap', (WidgetTester tester) async {
await tester.pumpWidget(
const CupertinoApp(
home: CupertinoPageScaffold(
child: CupertinoExpansionTile(title: Text('Title'), child: Text('Content')),
),
),
);
expect(find.text('Content'), findsNothing);
await tester.tap(find.text('Title'));
await tester.pump();
// The child animating its height and a clone fading in.
expect(find.text('Content'), findsNWidgets(2));
await tester.tap(find.text('Title'));
await tester.pump();
expect(find.text('Content'), findsNothing);
});
testWidgets('Can be controlled by ExpansibleController', (WidgetTester tester) async {
final controller = ExpansibleController();
await tester.pumpWidget(
CupertinoApp(
home: CupertinoPageScaffold(
child: CupertinoExpansionTile(
controller: controller,
title: const Text('Title'),
child: const Text('Content'),
),
),
),
);
expect(controller.isExpanded, isFalse);
expect(find.text('Content'), findsNothing);
controller.expand();
await tester.pump();
expect(controller.isExpanded, isTrue);
expect(find.text('Content'), findsOneWidget);
controller.collapse();
await tester.pump();
expect(controller.isExpanded, isFalse);
expect(find.text('Content'), findsNothing);
controller.dispose();
});
testWidgets('Controller can set the tile to be initially expanded', (WidgetTester tester) async {
final controller = ExpansibleController();
await tester.pumpWidget(
CupertinoApp(
home: CupertinoPageScaffold(
child: CupertinoExpansionTile(
controller: controller,
title: const Text('Title'),
child: const Text('Content'),
),
),
),
);
controller.expand();
await tester.pump();
expect(controller.isExpanded, isTrue);
expect(find.text('Content'), findsOneWidget);
await tester.tap(find.text('Title'));
await tester.pump();
expect(controller.isExpanded, isFalse);
expect(find.text('Content'), findsNothing);
controller.dispose();
});
testWidgets('Nested expansion tile', (WidgetTester tester) async {
await tester.pumpWidget(
const CupertinoApp(
home: CupertinoPageScaffold(
child: CupertinoExpansionTile(
title: Text('Outer'),
child: CupertinoExpansionTile(title: Text('Inner'), child: Text('Content')),
),
),
),
);
expect(find.text('Content'), findsNothing);
await tester.tap(find.text('Outer'));
await tester.pump();
await tester.pump(expansionDuration + infinitesimalDuration);
expect(find.text('Content'), findsNothing);
await tester.tap(find.text('Inner'));
await tester.pump();
await tester.pump(expansionDuration + infinitesimalDuration);
expect(find.text('Content'), findsOneWidget);
await tester.tap(find.text('Inner'));
await tester.pump();
await tester.pump(expansionDuration + infinitesimalDuration);
expect(find.text('Content'), findsNothing);
await tester.tap(find.text('Outer'));
await tester.pump();
await tester.pump(expansionDuration + infinitesimalDuration);
expect(find.text('Content'), findsNothing);
});
testWidgets('Default expansion animation and icon rotation', (WidgetTester tester) async {
await tester.pumpWidget(
const CupertinoApp(
home: Center(
child: RepaintBoundary(
child: CupertinoExpansionTile(
title: Text('Title'),
child: SizedBox(height: 50.0, child: ColoredBox(color: Color(0xffff0000))),
),
),
),
),
);
await expectLater(
find.byType(CupertinoExpansionTile),
matchesGoldenFile('expansion_tile.default.collapsed.png'),
);
await tester.tap(find.text('Title'));
await tester.pump();
// Pump until halfway through the animation.
await tester.pump(expansionDuration ~/ 2);
await expectLater(
find.byType(CupertinoExpansionTile),
matchesGoldenFile('expansion_tile.default.forward.png'),
);
await tester.pumpAndSettle();
await expectLater(
find.byType(CupertinoExpansionTile),
matchesGoldenFile('expansion_tile.default.expanded.png'),
);
await tester.tap(find.text('Title'));
await tester.pump();
// Pump until halfway through the animation.
await tester.pump(expansionDuration ~/ 2);
await expectLater(
find.byType(CupertinoExpansionTile),
matchesGoldenFile('expansion_tile.default.reverse.png'),
);
});
testWidgets('Expansion animation in scroll mode', (WidgetTester tester) async {
await tester.pumpWidget(
const CupertinoApp(
home: Center(
child: RepaintBoundary(
child: CupertinoExpansionTile(
title: Text('Title'),
transitionMode: ExpansionTileTransitionMode.scroll,
child: SizedBox(height: 50.0, child: ColoredBox(color: Color(0xffff0000))),
),
),
),
),
);
await tester.tap(find.text('Title'));
await tester.pump();
// Pump until halfway through the animation.
await tester.pump(expansionDuration ~/ 2);
await expectLater(
find.byType(CupertinoExpansionTile),
matchesGoldenFile('expansion_tile.scroll_mode.forward.png'),
);
await tester.pumpAndSettle();
await tester.tap(find.text('Title'));
await tester.pump();
// Pump until halfway through the animation.
await tester.pump(expansionDuration ~/ 2);
await expectLater(
find.byType(CupertinoExpansionTile),
matchesGoldenFile('expansion_tile.scroll_mode.reverse.png'),
);
await tester.pumpAndSettle();
});
testWidgets('Nested CupertinoListTile Semantics', (WidgetTester tester) async {
final controller = ExpansibleController();
addTearDown(controller.dispose);
final SemanticsHandle handle = tester.ensureSemantics();
await tester.pumpWidget(
CupertinoApp(
home: Column(
children: <Widget>[
const CupertinoExpansionTile(
title: Text('First Expansion Tile'),
child: SizedBox(height: 50.0),
),
CupertinoExpansionTile(
controller: controller,
title: const Text('Second Expansion Tile'),
child: const SizedBox(height: 50.0),
),
],
),
),
);
controller.expand();
await tester.pumpAndSettle();
expect(
tester.getSemantics(find.byType(CupertinoListTile).first),
matchesSemantics(
hasTapAction: true,
label: 'First Expansion Tile',
textDirection: TextDirection.ltr,
),
);
expect(
tester.getSemantics(find.byType(CupertinoListTile).last),
matchesSemantics(
hasTapAction: true,
label: 'Second Expansion Tile',
textDirection: TextDirection.ltr,
),
);
handle.dispose();
});
testWidgets('Semantics with the onTapHint is an ancestor of CupertinoListTile', (
WidgetTester tester,
) async {
final controller = ExpansibleController();
addTearDown(controller.dispose);
final SemanticsHandle handle = tester.ensureSemantics();
const localizations = DefaultCupertinoLocalizations();
await tester.pumpWidget(
CupertinoApp(
home: Column(
children: <Widget>[
const CupertinoExpansionTile(
title: Text('First Expansion Tile'),
child: SizedBox(height: 100, width: 100),
),
CupertinoExpansionTile(
controller: controller,
title: const Text('Second Expansion Tile'),
child: const SizedBox(height: 100, width: 100),
),
],
),
),
);
controller.expand();
await tester.pumpAndSettle();
SemanticsNode semantics = tester.getSemantics(
find
.ancestor(of: find.byType(CupertinoListTile).first, matching: find.byType(Semantics))
.first,
);
expect(semantics, isNotNull);
// The onTapHint is passed to semantics properties's hintOverrides.
expect(semantics.hintOverrides, isNotNull);
// The hint should be the opposite of the current state.
// The first CupertinoExpansionTile is collapsed, so the hint should be
// "double tap to expand".
expect(semantics.hintOverrides!.onTapHint, localizations.expansionTileCollapsedTapHint);
semantics = tester.getSemantics(
find
.ancestor(of: find.byType(CupertinoListTile).last, matching: find.byType(Semantics))
.first,
);
expect(semantics, isNotNull);
// The onTapHint is passed to semantics properties's hintOverrides.
expect(semantics.hintOverrides, isNotNull);
// The hint should be the opposite of the current state.
// The second CupertinoExpansionTile is expanded, so the hint should be
// "double tap to collapse".
expect(semantics.hintOverrides!.onTapHint, localizations.expansionTileExpandedTapHint);
handle.dispose();
});
testWidgets(
'Semantics hint for iOS and macOS',
(WidgetTester tester) async {
final controller = ExpansibleController();
addTearDown(controller.dispose);
final SemanticsHandle handle = tester.ensureSemantics();
const localizations = DefaultCupertinoLocalizations();
await tester.pumpWidget(
CupertinoApp(
home: Column(
children: <Widget>[
const CupertinoExpansionTile(
title: Text('First Expansion Tile'),
child: SizedBox(height: 100, width: 100),
),
CupertinoExpansionTile(
controller: controller,
title: const Text('Second Expansion Tile'),
child: const SizedBox(height: 100, width: 100),
),
],
),
),
);
controller.expand();
await tester.pumpAndSettle();
SemanticsNode semantics = tester.getSemantics(
find
.ancestor(of: find.byType(CupertinoListTile).first, matching: find.byType(Semantics))
.first,
);
expect(semantics, isNotNull);
expect(
semantics.hint,
'${localizations.expandedHint}\n ${localizations.expansionTileCollapsedHint}',
);
semantics = tester.getSemantics(
find
.ancestor(of: find.byType(CupertinoListTile).last, matching: find.byType(Semantics))
.first,
);
expect(semantics, isNotNull);
expect(
semantics.hint,
'${localizations.collapsedHint}\n ${localizations.expansionTileExpandedHint}',
);
handle.dispose();
},
variant: const TargetPlatformVariant(<TargetPlatform>{
TargetPlatform.iOS,
TargetPlatform.macOS,
}),
);
testWidgets('CupertinoExpansionTile does not crash at zero area', (WidgetTester tester) async {
tester.view.physicalSize = Size.zero;
final controller = ExpansibleController();
addTearDown(tester.view.reset);
addTearDown(controller.dispose);
await tester.pumpWidget(
CupertinoApp(
home: Center(
child: CupertinoExpansionTile(
controller: controller,
title: const Text('X'),
child: const Text('Y'),
),
),
),
);
expect(tester.getSize(find.byType(CupertinoExpansionTile)), Size.zero);
controller.expand();
await tester.pumpAndSettle();
});
}