blob: afc0eb29bf624c53be4771ccfd25122e1392baeb [file] [log] [blame]
// Copyright 2019 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:animations/src/open_container.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets(
'Container opens - Fade (by default)',
(WidgetTester tester) async {
const ShapeBorder shape = RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8.0)),
);
bool closedBuilderCalled = false;
bool openBuilderCalled = false;
await tester.pumpWidget(_boilerplate(
child: Center(
child: OpenContainer(
closedColor: Colors.green,
openColor: Colors.blue,
closedElevation: 4.0,
openElevation: 8.0,
closedShape: shape,
closedBuilder: (BuildContext context, VoidCallback _) {
closedBuilderCalled = true;
return const Text('Closed');
},
openBuilder: (BuildContext context, VoidCallback _) {
openBuilderCalled = true;
return const Text('Open');
},
),
),
));
// Closed container has the expected properties.
final StatefulElement srcMaterialElement = tester.firstElement(
find.ancestor(
of: find.text('Closed'),
matching: find.byType(Material),
),
);
final Material srcMaterial = srcMaterialElement.widget as Material;
expect(srcMaterial.color, Colors.green);
expect(srcMaterial.elevation, 4.0);
expect(srcMaterial.shape, shape);
expect(find.text('Closed'), findsOneWidget);
expect(find.text('Open'), findsNothing);
expect(closedBuilderCalled, isTrue);
expect(openBuilderCalled, isFalse);
final Rect srcMaterialRect = tester.getRect(
find.byElementPredicate((Element e) => e == srcMaterialElement),
);
// Open the container.
await tester.tap(find.text('Closed'));
expect(find.text('Closed'), findsOneWidget);
expect(find.text('Open'), findsNothing);
await tester.pump();
// On the first frame of the animation everything still looks like before.
final StatefulElement destMaterialElement = tester.firstElement(
find.ancestor(
of: find.text('Closed'),
matching: find.byType(Material),
),
);
final Material closedMaterial = destMaterialElement.widget as Material;
expect(closedMaterial.color, Colors.green);
expect(closedMaterial.elevation, 4.0);
expect(closedMaterial.shape, shape);
expect(find.text('Closed'), findsOneWidget);
expect(find.text('Open'), findsOneWidget);
final Rect closedMaterialRect = tester.getRect(
find.byElementPredicate((Element e) => e == destMaterialElement),
);
expect(closedMaterialRect, srcMaterialRect);
expect(_getOpacity(tester, 'Open'), 0.0);
expect(_getOpacity(tester, 'Closed'), 1.0);
final _TrackedData dataClosed = _TrackedData(
closedMaterial,
closedMaterialRect,
);
// Jump to the start of the fade in.
await tester.pump(const Duration(milliseconds: 60)); // 300ms * 1/5 = 60ms
final _TrackedData dataPreFade = _TrackedData(
destMaterialElement.widget as Material,
tester.getRect(
find.byElementPredicate((Element e) => e == destMaterialElement),
),
);
_expectMaterialPropertiesHaveAdvanced(
smallerMaterial: dataClosed,
biggerMaterial: dataPreFade,
tester: tester,
);
expect(_getOpacity(tester, 'Open'), moreOrLessEquals(0.0));
expect(_getOpacity(tester, 'Closed'), 1.0);
// Jump to the middle of the fade in.
await tester
.pump(const Duration(milliseconds: 30)); // 300ms * 3/10 = 90ms
final _TrackedData dataMidFadeIn = _TrackedData(
destMaterialElement.widget as Material,
tester.getRect(
find.byElementPredicate((Element e) => e == destMaterialElement),
),
);
_expectMaterialPropertiesHaveAdvanced(
smallerMaterial: dataPreFade,
biggerMaterial: dataMidFadeIn,
tester: tester,
);
expect(dataMidFadeIn.material.color, isNot(dataPreFade.material.color));
expect(_getOpacity(tester, 'Open'), lessThan(1.0));
expect(_getOpacity(tester, 'Open'), greaterThan(0.0));
expect(_getOpacity(tester, 'Closed'), 1.0);
// Jump to the end of the fade in at 2/5 of 300ms.
await tester.pump(
const Duration(milliseconds: 30),
); // 300ms * 2/5 = 120ms
final _TrackedData dataPostFadeIn = _TrackedData(
destMaterialElement.widget as Material,
tester.getRect(
find.byElementPredicate((Element e) => e == destMaterialElement),
),
);
_expectMaterialPropertiesHaveAdvanced(
smallerMaterial: dataMidFadeIn,
biggerMaterial: dataPostFadeIn,
tester: tester,
);
expect(_getOpacity(tester, 'Open'), moreOrLessEquals(1.0));
expect(_getOpacity(tester, 'Closed'), 1.0);
// Jump almost to the end of the transition.
await tester.pump(const Duration(milliseconds: 180));
final _TrackedData dataTransitionDone = _TrackedData(
destMaterialElement.widget as Material,
tester.getRect(
find.byElementPredicate((Element e) => e == destMaterialElement),
),
);
_expectMaterialPropertiesHaveAdvanced(
smallerMaterial: dataMidFadeIn,
biggerMaterial: dataTransitionDone,
tester: tester,
);
expect(_getOpacity(tester, 'Open'), 1.0);
expect(dataTransitionDone.material.color, Colors.blue);
expect(dataTransitionDone.material.elevation, 8.0);
expect(dataTransitionDone.radius, 0.0);
expect(dataTransitionDone.rect, const Rect.fromLTRB(0, 0, 800, 600));
await tester.pump(const Duration(milliseconds: 1));
expect(find.text('Closed'), findsNothing); // No longer in the tree.
expect(find.text('Open'), findsOneWidget);
final StatefulElement finalMaterialElement = tester.firstElement(
find.ancestor(
of: find.text('Open'),
matching: find.byType(Material),
),
);
final _TrackedData dataOpen = _TrackedData(
finalMaterialElement.widget as Material,
tester.getRect(
find.byElementPredicate((Element e) => e == finalMaterialElement),
),
);
expect(dataOpen.material.color, dataTransitionDone.material.color);
expect(
dataOpen.material.elevation, dataTransitionDone.material.elevation);
expect(dataOpen.radius, dataTransitionDone.radius);
expect(dataOpen.rect, dataTransitionDone.rect);
},
);
testWidgets(
'Container closes - Fade (by default)',
(WidgetTester tester) async {
const ShapeBorder shape = RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8.0)),
);
await tester.pumpWidget(_boilerplate(
child: Center(
child: OpenContainer(
closedColor: Colors.green,
openColor: Colors.blue,
closedElevation: 4.0,
openElevation: 8.0,
closedShape: shape,
closedBuilder: (BuildContext context, VoidCallback _) {
return const Text('Closed');
},
openBuilder: (BuildContext context, VoidCallback _) {
return const Text('Open');
},
),
),
));
await tester.tap(find.text('Closed'));
await tester.pumpAndSettle();
// Open container has the expected properties.
expect(find.text('Closed'), findsNothing);
expect(find.text('Open'), findsOneWidget);
final StatefulElement initialMaterialElement = tester.firstElement(
find.ancestor(
of: find.text('Open'),
matching: find.byType(Material),
),
);
final _TrackedData dataOpen = _TrackedData(
initialMaterialElement.widget as Material,
tester.getRect(
find.byElementPredicate((Element e) => e == initialMaterialElement),
),
);
expect(dataOpen.material.color, Colors.blue);
expect(dataOpen.material.elevation, 8.0);
expect(dataOpen.radius, 0.0);
expect(dataOpen.rect, const Rect.fromLTRB(0, 0, 800, 600));
// Close the container.
final NavigatorState navigator = tester.state(find.byType(Navigator));
navigator.pop();
await tester.pump();
expect(find.text('Closed'), findsOneWidget);
expect(find.text('Open'), findsOneWidget);
final StatefulElement materialElement = tester.firstElement(
find.ancestor(
of: find.text('Open'),
matching: find.byType(Material),
),
);
final _TrackedData dataTransitionStart = _TrackedData(
materialElement.widget as Material,
tester.getRect(
find.byElementPredicate((Element e) => e == materialElement),
),
);
expect(dataTransitionStart.material.color, dataOpen.material.color);
expect(
dataTransitionStart.material.elevation, dataOpen.material.elevation);
expect(dataTransitionStart.radius, dataOpen.radius);
expect(dataTransitionStart.rect, dataOpen.rect);
expect(_getOpacity(tester, 'Open'), 1.0);
await tester.pump(const Duration(microseconds: 1)); // 300 * 1/5 = 60
// Jump to start of fade out: 1/5 of 300.
await tester.pump(const Duration(milliseconds: 60)); // 300 * 1/5 = 60
final _TrackedData dataPreFadeOut = _TrackedData(
materialElement.widget as Material,
tester.getRect(
find.byElementPredicate((Element e) => e == materialElement),
),
);
_expectMaterialPropertiesHaveAdvanced(
smallerMaterial: dataPreFadeOut,
biggerMaterial: dataTransitionStart,
tester: tester,
);
expect(_getOpacity(tester, 'Open'), moreOrLessEquals(1.0));
expect(_getOpacity(tester, 'Closed'), 1.0);
// Jump to the middle of the fade out.
await tester.pump(const Duration(milliseconds: 30)); // 300 * 3/10 = 90
final _TrackedData dataMidpoint = _TrackedData(
materialElement.widget as Material,
tester.getRect(
find.byElementPredicate((Element e) => e == materialElement),
),
);
_expectMaterialPropertiesHaveAdvanced(
smallerMaterial: dataMidpoint,
biggerMaterial: dataPreFadeOut,
tester: tester,
);
expect(dataMidpoint.material.color, isNot(dataPreFadeOut.material.color));
expect(_getOpacity(tester, 'Open'), lessThan(1.0));
expect(_getOpacity(tester, 'Open'), greaterThan(0.0));
expect(_getOpacity(tester, 'Closed'), 1.0);
// Jump to the end of the fade out.
await tester.pump(const Duration(milliseconds: 30)); // 300 * 2/5 = 120
final _TrackedData dataPostFadeOut = _TrackedData(
materialElement.widget as Material,
tester.getRect(
find.byElementPredicate((Element e) => e == materialElement),
),
);
_expectMaterialPropertiesHaveAdvanced(
smallerMaterial: dataPostFadeOut,
biggerMaterial: dataMidpoint,
tester: tester,
);
expect(_getOpacity(tester, 'Open'), moreOrLessEquals(0.0));
expect(_getOpacity(tester, 'Closed'), 1.0);
// Jump almost to the end of the transition.
await tester.pump(const Duration(milliseconds: 180));
final _TrackedData dataTransitionDone = _TrackedData(
materialElement.widget as Material,
tester.getRect(
find.byElementPredicate((Element e) => e == materialElement),
),
);
_expectMaterialPropertiesHaveAdvanced(
smallerMaterial: dataTransitionDone,
biggerMaterial: dataPostFadeOut,
tester: tester,
);
expect(_getOpacity(tester, 'Closed'), 1.0);
expect(_getOpacity(tester, 'Open'), 0.0);
expect(dataTransitionDone.material.color, Colors.green);
expect(dataTransitionDone.material.elevation, 4.0);
expect(dataTransitionDone.radius, 8.0);
await tester.pump(const Duration(milliseconds: 1));
expect(find.text('Open'), findsNothing); // No longer in the tree.
expect(find.text('Closed'), findsOneWidget);
final StatefulElement finalMaterialElement = tester.firstElement(
find.ancestor(
of: find.text('Closed'),
matching: find.byType(Material),
),
);
final _TrackedData dataClosed = _TrackedData(
finalMaterialElement.widget as Material,
tester.getRect(
find.byElementPredicate((Element e) => e == finalMaterialElement),
),
);
expect(dataClosed.material.color, dataTransitionDone.material.color);
expect(
dataClosed.material.elevation,
dataTransitionDone.material.elevation,
);
expect(dataClosed.radius, dataTransitionDone.radius);
expect(dataClosed.rect, dataTransitionDone.rect);
},
);
testWidgets('Container opens - Fade through', (WidgetTester tester) async {
const ShapeBorder shape = RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8.0)),
);
bool closedBuilderCalled = false;
bool openBuilderCalled = false;
await tester.pumpWidget(_boilerplate(
child: Center(
child: OpenContainer(
closedColor: Colors.green,
openColor: Colors.blue,
middleColor: Colors.red,
closedElevation: 4.0,
openElevation: 8.0,
closedShape: shape,
closedBuilder: (BuildContext context, VoidCallback _) {
closedBuilderCalled = true;
return const Text('Closed');
},
openBuilder: (BuildContext context, VoidCallback _) {
openBuilderCalled = true;
return const Text('Open');
},
transitionType: ContainerTransitionType.fadeThrough,
),
),
));
// Closed container has the expected properties.
final StatefulElement srcMaterialElement = tester.firstElement(
find.ancestor(
of: find.text('Closed'),
matching: find.byType(Material),
),
);
final Material srcMaterial = srcMaterialElement.widget as Material;
expect(srcMaterial.color, Colors.green);
expect(srcMaterial.elevation, 4.0);
expect(srcMaterial.shape, shape);
expect(find.text('Closed'), findsOneWidget);
expect(find.text('Open'), findsNothing);
expect(closedBuilderCalled, isTrue);
expect(openBuilderCalled, isFalse);
final Rect srcMaterialRect = tester.getRect(
find.byElementPredicate((Element e) => e == srcMaterialElement),
);
// Open the container.
await tester.tap(find.text('Closed'));
expect(find.text('Closed'), findsOneWidget);
expect(find.text('Open'), findsNothing);
await tester.pump();
// On the first frame of the animation everything still looks like before.
final StatefulElement destMaterialElement = tester.firstElement(
find.ancestor(
of: find.text('Closed'),
matching: find.byType(Material),
),
);
final Material closedMaterial = destMaterialElement.widget as Material;
expect(closedMaterial.color, Colors.green);
expect(closedMaterial.elevation, 4.0);
expect(closedMaterial.shape, shape);
expect(find.text('Closed'), findsOneWidget);
expect(find.text('Open'), findsOneWidget);
final Rect closedMaterialRect = tester.getRect(
find.byElementPredicate((Element e) => e == destMaterialElement),
);
expect(closedMaterialRect, srcMaterialRect);
expect(_getOpacity(tester, 'Open'), 0.0);
expect(_getOpacity(tester, 'Closed'), 1.0);
final _TrackedData dataClosed = _TrackedData(
closedMaterial,
closedMaterialRect,
);
// The fade-out takes 1/5 of 300ms. Let's jump to the midpoint of that.
await tester.pump(const Duration(milliseconds: 30)); // 300ms * 1/10 = 30ms
final _TrackedData dataMidFadeOut = _TrackedData(
destMaterialElement.widget as Material,
tester.getRect(
find.byElementPredicate((Element e) => e == destMaterialElement),
),
);
_expectMaterialPropertiesHaveAdvanced(
smallerMaterial: dataClosed,
biggerMaterial: dataMidFadeOut,
tester: tester,
);
expect(dataMidFadeOut.material.color, isNot(dataClosed.material.color));
expect(_getOpacity(tester, 'Open'), 0.0);
expect(_getOpacity(tester, 'Closed'), lessThan(1.0));
expect(_getOpacity(tester, 'Closed'), greaterThan(0.0));
// Let's jump to the crossover point at 1/5 of 300ms.
await tester.pump(const Duration(milliseconds: 30)); // 300ms * 1/5 = 60ms
final _TrackedData dataMidpoint = _TrackedData(
destMaterialElement.widget as Material,
tester.getRect(
find.byElementPredicate((Element e) => e == destMaterialElement),
),
);
_expectMaterialPropertiesHaveAdvanced(
smallerMaterial: dataMidFadeOut,
biggerMaterial: dataMidpoint,
tester: tester,
);
expect(dataMidpoint.material.color, Colors.red);
expect(_getOpacity(tester, 'Open'), moreOrLessEquals(0.0));
expect(_getOpacity(tester, 'Closed'), moreOrLessEquals(0.0));
// Let's jump to the middle of the fade-in at 3/5 of 300ms
await tester.pump(const Duration(milliseconds: 120)); // 300ms * 3/5 = 180ms
final _TrackedData dataMidFadeIn = _TrackedData(
destMaterialElement.widget as Material,
tester.getRect(
find.byElementPredicate((Element e) => e == destMaterialElement),
),
);
_expectMaterialPropertiesHaveAdvanced(
smallerMaterial: dataMidpoint,
biggerMaterial: dataMidFadeIn,
tester: tester,
);
expect(dataMidFadeIn.material.color, isNot(dataMidpoint.material.color));
expect(_getOpacity(tester, 'Open'), lessThan(1.0));
expect(_getOpacity(tester, 'Open'), greaterThan(0.0));
expect(_getOpacity(tester, 'Closed'), 0.0);
// Let's jump almost to the end of the transition.
await tester.pump(const Duration(milliseconds: 120));
final _TrackedData dataTransitionDone = _TrackedData(
destMaterialElement.widget as Material,
tester.getRect(
find.byElementPredicate((Element e) => e == destMaterialElement),
),
);
_expectMaterialPropertiesHaveAdvanced(
smallerMaterial: dataMidFadeIn,
biggerMaterial: dataTransitionDone,
tester: tester,
);
expect(
dataTransitionDone.material.color,
isNot(dataMidFadeIn.material.color),
);
expect(_getOpacity(tester, 'Open'), 1.0);
expect(_getOpacity(tester, 'Closed'), 0.0);
expect(dataTransitionDone.material.color, Colors.blue);
expect(dataTransitionDone.material.elevation, 8.0);
expect(dataTransitionDone.radius, 0.0);
expect(dataTransitionDone.rect, const Rect.fromLTRB(0, 0, 800, 600));
await tester.pump(const Duration(milliseconds: 1));
expect(find.text('Closed'), findsNothing); // No longer in the tree.
expect(find.text('Open'), findsOneWidget);
final StatefulElement finalMaterialElement = tester.firstElement(
find.ancestor(
of: find.text('Open'),
matching: find.byType(Material),
),
);
final _TrackedData dataOpen = _TrackedData(
finalMaterialElement.widget as Material,
tester.getRect(
find.byElementPredicate((Element e) => e == finalMaterialElement),
),
);
expect(dataOpen.material.color, dataTransitionDone.material.color);
expect(dataOpen.material.elevation, dataTransitionDone.material.elevation);
expect(dataOpen.radius, dataTransitionDone.radius);
expect(dataOpen.rect, dataTransitionDone.rect);
});
testWidgets('Container closes - Fade through', (WidgetTester tester) async {
const ShapeBorder shape = RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8.0)),
);
await tester.pumpWidget(_boilerplate(
child: Center(
child: OpenContainer(
closedColor: Colors.green,
openColor: Colors.blue,
middleColor: Colors.red,
closedElevation: 4.0,
openElevation: 8.0,
closedShape: shape,
closedBuilder: (BuildContext context, VoidCallback _) {
return const Text('Closed');
},
openBuilder: (BuildContext context, VoidCallback _) {
return const Text('Open');
},
transitionType: ContainerTransitionType.fadeThrough,
),
),
));
await tester.tap(find.text('Closed'));
await tester.pumpAndSettle();
// Open container has the expected properties.
expect(find.text('Closed'), findsNothing);
expect(find.text('Open'), findsOneWidget);
final StatefulElement initialMaterialElement = tester.firstElement(
find.ancestor(
of: find.text('Open'),
matching: find.byType(Material),
),
);
final _TrackedData dataOpen = _TrackedData(
initialMaterialElement.widget as Material,
tester.getRect(
find.byElementPredicate((Element e) => e == initialMaterialElement),
),
);
expect(dataOpen.material.color, Colors.blue);
expect(dataOpen.material.elevation, 8.0);
expect(dataOpen.radius, 0.0);
expect(dataOpen.rect, const Rect.fromLTRB(0, 0, 800, 600));
// Close the container.
final NavigatorState navigator = tester.state(find.byType(Navigator));
navigator.pop();
await tester.pump();
expect(find.text('Closed'), findsOneWidget);
expect(find.text('Open'), findsOneWidget);
final StatefulElement materialElement = tester.firstElement(
find.ancestor(
of: find.text('Open'),
matching: find.byType(Material),
),
);
final _TrackedData dataTransitionStart = _TrackedData(
materialElement.widget as Material,
tester.getRect(
find.byElementPredicate((Element e) => e == materialElement),
),
);
expect(dataTransitionStart.material.color, dataOpen.material.color);
expect(dataTransitionStart.material.elevation, dataOpen.material.elevation);
expect(dataTransitionStart.radius, dataOpen.radius);
expect(dataTransitionStart.rect, dataOpen.rect);
expect(_getOpacity(tester, 'Open'), 1.0);
expect(_getOpacity(tester, 'Closed'), 0.0);
// Jump to mid-point of fade-out: 1/10 of 300ms.
await tester.pump(const Duration(milliseconds: 30)); // 300ms * 1/10 = 30ms
final _TrackedData dataMidFadeOut = _TrackedData(
materialElement.widget as Material,
tester.getRect(
find.byElementPredicate((Element e) => e == materialElement),
),
);
_expectMaterialPropertiesHaveAdvanced(
smallerMaterial: dataMidFadeOut,
biggerMaterial: dataTransitionStart,
tester: tester,
);
expect(
dataMidFadeOut.material.color,
isNot(dataTransitionStart.material.color),
);
expect(_getOpacity(tester, 'Closed'), 0.0);
expect(_getOpacity(tester, 'Open'), lessThan(1.0));
expect(_getOpacity(tester, 'Open'), greaterThan(0.0));
// Let's jump to the crossover point at 1/5 of 300ms.
await tester.pump(const Duration(milliseconds: 30)); // 300ms * 1/5 = 60ms
final _TrackedData dataMidpoint = _TrackedData(
materialElement.widget as Material,
tester.getRect(
find.byElementPredicate((Element e) => e == materialElement),
),
);
_expectMaterialPropertiesHaveAdvanced(
smallerMaterial: dataMidpoint,
biggerMaterial: dataMidFadeOut,
tester: tester,
);
expect(dataMidpoint.material.color, Colors.red);
expect(_getOpacity(tester, 'Open'), moreOrLessEquals(0.0));
expect(_getOpacity(tester, 'Closed'), moreOrLessEquals(0.0));
// Let's jump to the middle of the fade-in at 3/5 of 300ms
await tester.pump(const Duration(milliseconds: 120)); // 300ms * 3/5 = 180ms
final _TrackedData dataMidFadeIn = _TrackedData(
materialElement.widget as Material,
tester.getRect(
find.byElementPredicate((Element e) => e == materialElement),
),
);
_expectMaterialPropertiesHaveAdvanced(
smallerMaterial: dataMidFadeIn,
biggerMaterial: dataMidpoint,
tester: tester,
);
expect(dataMidFadeIn.material.color, isNot(dataMidpoint.material.color));
expect(_getOpacity(tester, 'Closed'), lessThan(1.0));
expect(_getOpacity(tester, 'Closed'), greaterThan(0.0));
expect(_getOpacity(tester, 'Open'), 0.0);
// Let's jump almost to the end of the transition.
await tester.pump(const Duration(milliseconds: 120));
final _TrackedData dataTransitionDone = _TrackedData(
materialElement.widget as Material,
tester.getRect(
find.byElementPredicate((Element e) => e == materialElement),
),
);
_expectMaterialPropertiesHaveAdvanced(
smallerMaterial: dataTransitionDone,
biggerMaterial: dataMidFadeIn,
tester: tester,
);
expect(
dataTransitionDone.material.color,
isNot(dataMidFadeIn.material.color),
);
expect(_getOpacity(tester, 'Closed'), 1.0);
expect(_getOpacity(tester, 'Open'), 0.0);
expect(dataTransitionDone.material.color, Colors.green);
expect(dataTransitionDone.material.elevation, 4.0);
expect(dataTransitionDone.radius, 8.0);
await tester.pump(const Duration(milliseconds: 1));
expect(find.text('Open'), findsNothing); // No longer in the tree.
expect(find.text('Closed'), findsOneWidget);
final StatefulElement finalMaterialElement = tester.firstElement(
find.ancestor(
of: find.text('Closed'),
matching: find.byType(Material),
),
);
final _TrackedData dataClosed = _TrackedData(
finalMaterialElement.widget as Material,
tester.getRect(
find.byElementPredicate((Element e) => e == finalMaterialElement),
),
);
expect(dataClosed.material.color, dataTransitionDone.material.color);
expect(
dataClosed.material.elevation,
dataTransitionDone.material.elevation,
);
expect(dataClosed.radius, dataTransitionDone.radius);
expect(dataClosed.rect, dataTransitionDone.rect);
});
testWidgets('Cannot tap container if tappable=false',
(WidgetTester tester) async {
await tester.pumpWidget(_boilerplate(
child: Center(
child: OpenContainer(
tappable: false,
closedBuilder: (BuildContext context, VoidCallback _) {
return const Text('Closed');
},
openBuilder: (BuildContext context, VoidCallback _) {
return const Text('Open');
},
),
),
));
expect(find.text('Open'), findsNothing);
expect(find.text('Closed'), findsOneWidget);
await tester.tap(find.text('Closed'));
await tester.pumpAndSettle();
expect(find.text('Open'), findsNothing);
expect(find.text('Closed'), findsOneWidget);
});
testWidgets('Action callbacks work', (WidgetTester tester) async {
late VoidCallback open, close;
await tester.pumpWidget(_boilerplate(
child: Center(
child: OpenContainer(
tappable: false,
closedBuilder: (BuildContext context, VoidCallback action) {
open = action;
return const Text('Closed');
},
openBuilder: (BuildContext context, VoidCallback action) {
close = action;
return const Text('Open');
},
),
),
));
expect(find.text('Open'), findsNothing);
expect(find.text('Closed'), findsOneWidget);
expect(open, isNotNull);
open();
await tester.pumpAndSettle();
expect(find.text('Open'), findsOneWidget);
expect(find.text('Closed'), findsNothing);
expect(close, isNotNull);
close();
await tester.pumpAndSettle();
expect(find.text('Open'), findsNothing);
expect(find.text('Closed'), findsOneWidget);
});
testWidgets('open widget keeps state', (WidgetTester tester) async {
await tester.pumpWidget(_boilerplate(
child: Center(
child: OpenContainer(
closedBuilder: (BuildContext context, VoidCallback action) {
return const Text('Closed');
},
openBuilder: (BuildContext context, VoidCallback action) {
return Switch(
value: true,
onChanged: (bool v) {},
);
},
),
),
));
await tester.tap(find.text('Closed'));
await tester.pump(const Duration(milliseconds: 200));
final State stateOpening = tester.state(find.byType(Switch));
expect(stateOpening, isNotNull);
await tester.pumpAndSettle();
expect(find.text('Closed'), findsNothing);
final State stateOpen = tester.state(find.byType(Switch));
expect(stateOpen, isNotNull);
expect(stateOpen, same(stateOpening));
final NavigatorState navigator = tester.state(find.byType(Navigator));
navigator.pop();
await tester.pump(const Duration(milliseconds: 200));
expect(find.text('Closed'), findsOneWidget);
final State stateClosing = tester.state(find.byType(Switch));
expect(stateClosing, isNotNull);
expect(stateClosing, same(stateOpen));
});
testWidgets('closed widget keeps state', (WidgetTester tester) async {
late VoidCallback open;
await tester.pumpWidget(_boilerplate(
child: Center(
child: OpenContainer(
closedBuilder: (BuildContext context, VoidCallback action) {
open = action;
return Switch(
value: true,
onChanged: (bool v) {},
);
},
openBuilder: (BuildContext context, VoidCallback action) {
return const Text('Open');
},
),
),
));
final State stateClosed = tester.state(find.byType(Switch));
expect(stateClosed, isNotNull);
open();
await tester.pump(const Duration(milliseconds: 200));
expect(find.text('Open'), findsOneWidget);
final State stateOpening = tester.state(find.byType(Switch));
expect(stateOpening, same(stateClosed));
await tester.pumpAndSettle();
expect(find.byType(Switch), findsNothing);
expect(find.text('Open'), findsOneWidget);
final State stateOpen = tester.state(find.byType(
Switch,
skipOffstage: false,
));
expect(stateOpen, same(stateOpening));
final NavigatorState navigator = tester.state(find.byType(Navigator));
navigator.pop();
await tester.pump(const Duration(milliseconds: 200));
expect(find.text('Open'), findsOneWidget);
final State stateClosing = tester.state(find.byType(Switch));
expect(stateClosing, same(stateOpen));
await tester.pumpAndSettle();
expect(find.text('Open'), findsNothing);
final State stateClosedAgain = tester.state(find.byType(Switch));
expect(stateClosedAgain, same(stateClosing));
});
testWidgets('closes to the right location when src position has changed',
(WidgetTester tester) async {
final Widget openContainer = OpenContainer(
closedBuilder: (BuildContext context, VoidCallback action) {
return Container(
height: 100,
width: 100,
child: const Text('Closed'),
);
},
openBuilder: (BuildContext context, VoidCallback action) {
return GestureDetector(
onTap: action,
child: const Text('Open'),
);
},
);
await tester.pumpWidget(_boilerplate(
child: Align(
alignment: Alignment.topLeft,
child: openContainer,
),
));
final Rect originTextRect = tester.getRect(find.text('Closed'));
expect(originTextRect.topLeft, Offset.zero);
await tester.tap(find.text('Closed'));
await tester.pumpAndSettle();
expect(find.text('Open'), findsOneWidget);
expect(find.text('Closed'), findsNothing);
await tester.pumpWidget(_boilerplate(
child: Align(
alignment: Alignment.bottomLeft,
child: openContainer,
),
));
expect(find.text('Open'), findsOneWidget);
expect(find.text('Closed'), findsNothing);
await tester.tap(find.text('Open'));
await tester.pump(); // Need one frame to measure things in the old route.
await tester.pump(const Duration(milliseconds: 300));
expect(find.text('Open'), findsOneWidget);
expect(find.text('Closed'), findsOneWidget);
final Rect transitionEndTextRect = tester.getRect(find.text('Open'));
expect(transitionEndTextRect.topLeft, const Offset(0.0, 600.0 - 100.0));
await tester.pump(const Duration(milliseconds: 1));
expect(find.text('Open'), findsNothing);
expect(find.text('Closed'), findsOneWidget);
final Rect finalTextRect = tester.getRect(find.text('Closed'));
expect(finalTextRect.topLeft, transitionEndTextRect.topLeft);
});
testWidgets('src changes size while open', (WidgetTester tester) async {
final Widget openContainer = _boilerplate(
child: Center(
child: OpenContainer(
closedBuilder: (BuildContext context, VoidCallback action) {
return const _SizableContainer(
initialSize: 100,
child: Text('Closed'),
);
},
openBuilder: (BuildContext context, VoidCallback action) {
return GestureDetector(
onTap: action,
child: const Text('Open'),
);
},
),
),
);
await tester.pumpWidget(openContainer);
final Size orignalClosedRect = tester.getSize(find
.ancestor(
of: find.text('Closed'),
matching: find.byType(Material),
)
.first);
expect(orignalClosedRect, const Size(100, 100));
await tester.tap(find.text('Closed'));
await tester.pumpAndSettle();
expect(find.text('Open'), findsOneWidget);
expect(find.text('Closed'), findsNothing);
final _SizableContainerState containerState = tester.state(find.byType(
_SizableContainer,
skipOffstage: false,
));
containerState.size = 200;
expect(find.text('Open'), findsOneWidget);
expect(find.text('Closed'), findsNothing);
await tester.tap(find.text('Open'));
await tester.pump(); // Need one frame to measure things in the old route.
await tester.pump(const Duration(milliseconds: 300));
expect(find.text('Open'), findsOneWidget);
expect(find.text('Closed'), findsOneWidget);
final Size transitionEndSize = tester.getSize(find
.ancestor(
of: find.text('Open'),
matching: find.byType(Material),
)
.first);
expect(transitionEndSize, const Size(200, 200));
await tester.pump(const Duration(milliseconds: 1));
expect(find.text('Open'), findsNothing);
expect(find.text('Closed'), findsOneWidget);
final Size finalSize = tester.getSize(find
.ancestor(
of: find.text('Closed'),
matching: find.byType(Material),
)
.first);
expect(finalSize, const Size(200, 200));
});
testWidgets('transition is interrupted and should not jump',
(WidgetTester tester) async {
await tester.pumpWidget(_boilerplate(
child: Center(
child: OpenContainer(
closedBuilder: (BuildContext context, VoidCallback action) {
return const Text('Closed');
},
openBuilder: (BuildContext context, VoidCallback action) {
return const Text('Open');
},
),
),
));
await tester.tap(find.text('Closed'));
await tester.pump();
await tester.pump(const Duration(milliseconds: 150));
expect(find.text('Open'), findsOneWidget);
expect(find.text('Closed'), findsOneWidget);
final Material openingMaterial = tester.firstWidget(find.ancestor(
of: find.text('Closed'),
matching: find.byType(Material),
));
final Rect openingRect = tester.getRect(
find.byWidgetPredicate((Widget w) => w == openingMaterial),
);
// Close the container while it is half way to open.
final NavigatorState navigator = tester.state(find.byType(Navigator));
navigator.pop();
await tester.pump();
final Material closingMaterial = tester.firstWidget(find.ancestor(
of: find.text('Open'),
matching: find.byType(Material),
));
final Rect closingRect = tester.getRect(
find.byWidgetPredicate((Widget w) => w == closingMaterial),
);
expect(closingMaterial.elevation, openingMaterial.elevation);
expect(closingMaterial.color, openingMaterial.color);
expect(closingMaterial.shape, openingMaterial.shape);
expect(closingRect, openingRect);
});
testWidgets('navigator is not full size', (WidgetTester tester) async {
await tester.pumpWidget(Center(
child: SizedBox(
width: 300,
height: 400,
child: _boilerplate(
child: Center(
child: OpenContainer(
closedBuilder: (BuildContext context, VoidCallback action) {
return const Text('Closed');
},
openBuilder: (BuildContext context, VoidCallback action) {
return const Text('Open');
},
),
),
),
),
));
const Rect fullNavigator = Rect.fromLTWH(250, 100, 300, 400);
expect(tester.getRect(find.byType(Navigator)), fullNavigator);
final Rect materialRectClosed = tester.getRect(find
.ancestor(
of: find.text('Closed'),
matching: find.byType(Material),
)
.first);
await tester.tap(find.text('Closed'));
await tester.pump();
expect(find.text('Open'), findsOneWidget);
expect(find.text('Closed'), findsOneWidget);
final Rect materialRectTransitionStart = tester.getRect(find.ancestor(
of: find.text('Closed'),
matching: find.byType(Material),
));
expect(materialRectTransitionStart, materialRectClosed);
await tester.pump(const Duration(milliseconds: 300));
expect(find.text('Open'), findsOneWidget);
expect(find.text('Closed'), findsOneWidget);
final Rect materialRectTransitionEnd = tester.getRect(find.ancestor(
of: find.text('Open'),
matching: find.byType(Material),
));
expect(materialRectTransitionEnd, fullNavigator);
await tester.pumpAndSettle();
expect(find.text('Open'), findsOneWidget);
expect(find.text('Closed'), findsNothing);
final Rect materialRectOpen = tester.getRect(find.ancestor(
of: find.text('Open'),
matching: find.byType(Material),
));
expect(materialRectOpen, fullNavigator);
});
testWidgets('does not crash when disposed right after pop',
(WidgetTester tester) async {
await tester.pumpWidget(Center(
child: SizedBox(
width: 300,
height: 400,
child: _boilerplate(
child: Center(
child: OpenContainer(
closedBuilder: (BuildContext context, VoidCallback action) {
return const Text('Closed');
},
openBuilder: (BuildContext context, VoidCallback action) {
return const Text('Open');
},
),
),
),
),
));
await tester.tap(find.text('Closed'));
await tester.pumpAndSettle();
final NavigatorState navigator = tester.state(find.byType(Navigator));
navigator.pop();
await tester.pumpWidget(const Placeholder());
expect(tester.takeException(), isNull);
await tester.pumpAndSettle();
expect(tester.takeException(), isNull);
});
testWidgets('can specify a duration', (WidgetTester tester) async {
await tester.pumpWidget(Center(
child: SizedBox(
width: 300,
height: 400,
child: _boilerplate(
child: Center(
child: OpenContainer(
transitionDuration: const Duration(seconds: 2),
closedBuilder: (BuildContext context, VoidCallback action) {
return const Text('Closed');
},
openBuilder: (BuildContext context, VoidCallback action) {
return const Text('Open');
},
),
),
),
),
));
expect(find.text('Open'), findsNothing);
expect(find.text('Closed'), findsOneWidget);
await tester.tap(find.text('Closed'));
await tester.pump();
// Jump to the end of the transition.
await tester.pump(const Duration(seconds: 2));
expect(find.text('Open'), findsOneWidget); // faded in
expect(find.text('Closed'), findsOneWidget); // faded out
await tester.pump(const Duration(milliseconds: 1));
expect(find.text('Open'), findsOneWidget);
expect(find.text('Closed'), findsNothing);
final NavigatorState navigator = tester.state(find.byType(Navigator));
navigator.pop();
await tester.pump();
// Jump to the end of the transition.
await tester.pump(const Duration(seconds: 2));
expect(find.text('Open'), findsOneWidget); // faded out
expect(find.text('Closed'), findsOneWidget); // faded in
await tester.pump(const Duration(milliseconds: 1));
expect(find.text('Open'), findsNothing);
expect(find.text('Closed'), findsOneWidget);
});
testWidgets('can specify an open shape', (WidgetTester tester) async {
await tester.pumpWidget(Center(
child: SizedBox(
width: 300,
height: 400,
child: _boilerplate(
child: Center(
child: OpenContainer(
closedShape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
openShape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(40),
),
closedBuilder: (BuildContext context, VoidCallback action) {
return const Text('Closed');
},
openBuilder: (BuildContext context, VoidCallback action) {
return const Text('Open');
},
),
),
),
),
));
expect(find.text('Open'), findsNothing);
expect(find.text('Closed'), findsOneWidget);
final double closedRadius = _getRadius(tester.firstWidget(find.ancestor(
of: find.text('Closed'),
matching: find.byType(Material),
)));
expect(closedRadius, 10.0);
await tester.tap(find.text('Closed'));
await tester.pump();
expect(find.text('Open'), findsOneWidget);
expect(find.text('Closed'), findsOneWidget);
final double openingRadius = _getRadius(tester.firstWidget(find.ancestor(
of: find.text('Open'),
matching: find.byType(Material),
)));
expect(openingRadius, 10.0);
await tester.pump(const Duration(milliseconds: 150));
expect(find.text('Open'), findsOneWidget);
expect(find.text('Closed'), findsOneWidget);
final double halfwayRadius = _getRadius(tester.firstWidget(find.ancestor(
of: find.text('Open'),
matching: find.byType(Material),
)));
expect(halfwayRadius, greaterThan(10.0));
expect(halfwayRadius, lessThan(40.0));
await tester.pump(const Duration(milliseconds: 150));
expect(find.text('Open'), findsOneWidget);
expect(find.text('Closed'), findsOneWidget);
final double openRadius = _getRadius(tester.firstWidget(find.ancestor(
of: find.text('Open'),
matching: find.byType(Material),
)));
expect(openRadius, 40.0);
await tester.pump(const Duration(milliseconds: 1));
expect(find.text('Closed'), findsNothing);
expect(find.text('Open'), findsOneWidget);
final double finalRadius = _getRadius(tester.firstWidget(find.ancestor(
of: find.text('Open'),
matching: find.byType(Material),
)));
expect(finalRadius, 40.0);
});
testWidgets('Scrim', (WidgetTester tester) async {
const ShapeBorder shape = RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8.0)),
);
await tester.pumpWidget(_boilerplate(
child: Center(
child: OpenContainer(
closedColor: Colors.green,
openColor: Colors.blue,
closedElevation: 4.0,
openElevation: 8.0,
closedShape: shape,
closedBuilder: (BuildContext context, VoidCallback _) {
return const Text('Closed');
},
openBuilder: (BuildContext context, VoidCallback _) {
return const Text('Open');
},
),
),
));
expect(find.text('Closed'), findsOneWidget);
expect(find.text('Open'), findsNothing);
await tester.tap(find.text('Closed'));
await tester.pump();
expect(_getScrimColor(tester), Colors.transparent);
await tester.pump(const Duration(milliseconds: 50));
final Color halfwayFadeInColor = _getScrimColor(tester);
expect(halfwayFadeInColor, isNot(Colors.transparent));
expect(halfwayFadeInColor, isNot(Colors.black54));
// Scrim is done fading in early.
await tester.pump(const Duration(milliseconds: 50));
expect(_getScrimColor(tester), Colors.black54);
await tester.pump(const Duration(milliseconds: 200));
expect(_getScrimColor(tester), Colors.black54);
await tester.pumpAndSettle();
// Close the container
final NavigatorState navigator = tester.state(find.byType(Navigator));
navigator.pop();
await tester.pump();
expect(_getScrimColor(tester), Colors.black54);
// Scrim takes longer to fade out (vs. fade in).
await tester.pump(const Duration(milliseconds: 200));
final Color halfwayFadeOutColor = _getScrimColor(tester);
expect(halfwayFadeOutColor, isNot(Colors.transparent));
expect(halfwayFadeOutColor, isNot(Colors.black54));
await tester.pump(const Duration(milliseconds: 100));
expect(_getScrimColor(tester), Colors.transparent);
});
testWidgets(
'Container partly offscreen can be opened without crash - vertical',
(WidgetTester tester) async {
final ScrollController controller =
ScrollController(initialScrollOffset: 50);
await tester.pumpWidget(Center(
child: SizedBox(
height: 200,
width: 200,
child: _boilerplate(
child: ListView.builder(
cacheExtent: 0,
controller: controller,
itemBuilder: (BuildContext context, int index) {
return OpenContainer(
closedBuilder: (BuildContext context, VoidCallback _) {
return SizedBox(
height: 100,
width: 100,
child: Text('Closed $index'),
);
},
openBuilder: (BuildContext context, VoidCallback _) {
return Text('Open $index');
},
);
},
),
),
),
));
void expectClosedState() {
expect(find.text('Closed 0'), findsOneWidget);
expect(find.text('Closed 1'), findsOneWidget);
expect(find.text('Closed 2'), findsOneWidget);
expect(find.text('Closed 3'), findsNothing);
expect(find.text('Open 0'), findsNothing);
expect(find.text('Open 1'), findsNothing);
expect(find.text('Open 2'), findsNothing);
expect(find.text('Open 3'), findsNothing);
}
expectClosedState();
// Open container that's partly visible at top.
await tester.tapAt(
tester.getBottomRight(find.text('Closed 0')) - const Offset(20, 20),
);
await tester.pump();
await tester.pumpAndSettle();
expect(find.text('Closed 0'), findsNothing);
expect(find.text('Open 0'), findsOneWidget);
final NavigatorState navigator = tester.state(find.byType(Navigator));
navigator.pop();
await tester.pump();
await tester.pumpAndSettle();
expectClosedState();
// Open container that's partly visible at bottom.
await tester.tapAt(
tester.getTopLeft(find.text('Closed 2')) + const Offset(20, 20),
);
await tester.pump();
await tester.pumpAndSettle();
expect(find.text('Closed 2'), findsNothing);
expect(find.text('Open 2'), findsOneWidget);
});
testWidgets(
'Container partly offscreen can be opened without crash - horizontal',
(WidgetTester tester) async {
final ScrollController controller =
ScrollController(initialScrollOffset: 50);
await tester.pumpWidget(Center(
child: SizedBox(
height: 200,
width: 200,
child: _boilerplate(
child: ListView.builder(
scrollDirection: Axis.horizontal,
cacheExtent: 0,
controller: controller,
itemBuilder: (BuildContext context, int index) {
return OpenContainer(
closedBuilder: (BuildContext context, VoidCallback _) {
return SizedBox(
height: 100,
width: 100,
child: Text('Closed $index'),
);
},
openBuilder: (BuildContext context, VoidCallback _) {
return Text('Open $index');
},
);
},
),
),
),
));
void expectClosedState() {
expect(find.text('Closed 0'), findsOneWidget);
expect(find.text('Closed 1'), findsOneWidget);
expect(find.text('Closed 2'), findsOneWidget);
expect(find.text('Closed 3'), findsNothing);
expect(find.text('Open 0'), findsNothing);
expect(find.text('Open 1'), findsNothing);
expect(find.text('Open 2'), findsNothing);
expect(find.text('Open 3'), findsNothing);
}
expectClosedState();
// Open container that's partly visible at left edge.
await tester.tapAt(
tester.getBottomRight(find.text('Closed 0')) - const Offset(20, 20),
);
await tester.pump();
await tester.pumpAndSettle();
expect(find.text('Closed 0'), findsNothing);
expect(find.text('Open 0'), findsOneWidget);
final NavigatorState navigator = tester.state(find.byType(Navigator));
navigator.pop();
await tester.pump();
await tester.pumpAndSettle();
expectClosedState();
// Open container that's partly visible at right edge.
await tester.tapAt(
tester.getTopLeft(find.text('Closed 2')) + const Offset(20, 20),
);
await tester.pump();
await tester.pumpAndSettle();
expect(find.text('Closed 2'), findsNothing);
expect(find.text('Open 2'), findsOneWidget);
});
testWidgets(
'Container can be dismissed after container widget itself is removed without crash',
(WidgetTester tester) async {
await tester.pumpWidget(_boilerplate(child: _RemoveOpenContainerExample()));
expect(find.text('Closed'), findsOneWidget);
expect(find.text('Closed', skipOffstage: false), findsOneWidget);
expect(find.text('Open'), findsNothing);
await tester.tap(find.text('Open the container'));
await tester.pumpAndSettle();
expect(find.text('Closed'), findsNothing);
expect(find.text('Closed', skipOffstage: false), findsOneWidget);
expect(find.text('Open'), findsOneWidget);
await tester.tap(find.text('Remove the container'));
await tester.pump();
expect(find.text('Closed'), findsNothing);
expect(find.text('Closed', skipOffstage: false), findsNothing);
expect(find.text('Open'), findsOneWidget);
await tester.tap(find.text('Close the container'));
await tester.pumpAndSettle();
expect(find.text('Closed'), findsNothing);
expect(find.text('Closed', skipOffstage: false), findsNothing);
expect(find.text('Open'), findsNothing);
expect(find.text('Container has been removed'), findsOneWidget);
});
testWidgets('onClosed callback is called when container has closed',
(WidgetTester tester) async {
bool hasClosed = false;
final Widget openContainer = OpenContainer(
onClosed: (dynamic _) {
hasClosed = true;
},
closedBuilder: (BuildContext context, VoidCallback action) {
return GestureDetector(
onTap: action,
child: const Text('Closed'),
);
},
openBuilder: (BuildContext context, VoidCallback action) {
return GestureDetector(
onTap: action,
child: const Text('Open'),
);
},
);
await tester.pumpWidget(
_boilerplate(child: openContainer),
);
expect(find.text('Open'), findsNothing);
expect(find.text('Closed'), findsOneWidget);
expect(hasClosed, isFalse);
await tester.tap(find.text('Closed'));
await tester.pumpAndSettle();
expect(find.text('Open'), findsOneWidget);
expect(find.text('Closed'), findsNothing);
await tester.tap(find.text('Open'));
await tester.pumpAndSettle();
expect(find.text('Open'), findsNothing);
expect(find.text('Closed'), findsOneWidget);
expect(hasClosed, isTrue);
});
testWidgets(
'onClosed callback receives popped value when container has closed',
(WidgetTester tester) async {
bool? value = false;
final Widget openContainer = OpenContainer<bool>(
onClosed: (bool? poppedValue) {
value = poppedValue;
},
closedBuilder: (BuildContext context, VoidCallback action) {
return GestureDetector(
onTap: action,
child: const Text('Closed'),
);
},
openBuilder:
(BuildContext context, CloseContainerActionCallback<bool> action) {
return GestureDetector(
onTap: () => action(returnValue: true),
child: const Text('Open'),
);
},
);
await tester.pumpWidget(
_boilerplate(child: openContainer),
);
expect(find.text('Open'), findsNothing);
expect(find.text('Closed'), findsOneWidget);
expect(value, isFalse);
await tester.tap(find.text('Closed'));
await tester.pumpAndSettle();
expect(find.text('Open'), findsOneWidget);
expect(find.text('Closed'), findsNothing);
await tester.tap(find.text('Open'));
await tester.pumpAndSettle();
expect(find.text('Open'), findsNothing);
expect(find.text('Closed'), findsOneWidget);
expect(value, isTrue);
});
testWidgets('closedBuilder has anti-alias clip by default',
(WidgetTester tester) async {
final GlobalKey closedBuilderKey = GlobalKey();
final Widget openContainer = OpenContainer(
closedBuilder: (BuildContext context, VoidCallback action) {
return Text('Close', key: closedBuilderKey);
},
openBuilder:
(BuildContext context, CloseContainerActionCallback<bool> action) {
return const Text('Open');
},
);
await tester.pumpWidget(
_boilerplate(child: openContainer),
);
final Finder closedBuilderMaterial = find
.ancestor(
of: find.byKey(closedBuilderKey),
matching: find.byType(Material),
)
.first;
final Material material = tester.widget<Material>(closedBuilderMaterial);
expect(material.clipBehavior, Clip.antiAlias);
});
testWidgets('closedBuilder has no clip', (WidgetTester tester) async {
final GlobalKey closedBuilderKey = GlobalKey();
final Widget openContainer = OpenContainer(
closedBuilder: (BuildContext context, VoidCallback action) {
return Text('Close', key: closedBuilderKey);
},
openBuilder:
(BuildContext context, CloseContainerActionCallback<bool> action) {
return const Text('Open');
},
clipBehavior: Clip.none,
);
await tester.pumpWidget(
_boilerplate(child: openContainer),
);
final Finder closedBuilderMaterial = find
.ancestor(
of: find.byKey(closedBuilderKey),
matching: find.byType(Material),
)
.first;
final Material material = tester.widget<Material>(closedBuilderMaterial);
expect(material.clipBehavior, Clip.none);
});
Widget _createRootNavigatorTest({
required Key appKey,
required Key nestedNavigatorKey,
required bool useRootNavigator,
}) {
return Center(
child: SizedBox(
width: 100,
height: 100,
child: MaterialApp(
key: appKey,
// a nested navigator
home: Center(
child: SizedBox(
width: 50,
height: 50,
child: Navigator(
key: nestedNavigatorKey,
onGenerateRoute: (RouteSettings route) {
return MaterialPageRoute<dynamic>(
settings: route,
builder: (BuildContext context) {
return OpenContainer(
useRootNavigator: useRootNavigator,
closedBuilder: (BuildContext context, _) {
return const Text('Closed');
},
openBuilder: (BuildContext context, _) {
return const Text('Opened');
},
);
},
);
},
),
),
),
),
),
);
}
testWidgets(
'Verify that "useRootNavigator: false" uses the correct navigator',
(WidgetTester tester) async {
const Key appKey = Key('App');
const Key nestedNavigatorKey = Key('Nested Navigator');
await tester.pumpWidget(_createRootNavigatorTest(
appKey: appKey,
nestedNavigatorKey: nestedNavigatorKey,
useRootNavigator: false));
await tester.tap(find.text('Closed'));
await tester.pumpAndSettle();
expect(
find.descendant(of: find.byKey(appKey), matching: find.text('Opened')),
findsOneWidget);
expect(
find.descendant(
of: find.byKey(nestedNavigatorKey), matching: find.text('Opened')),
findsOneWidget);
});
testWidgets('Verify that "useRootNavigator: true" uses the correct navigator',
(WidgetTester tester) async {
const Key appKey = Key('App');
const Key nestedNavigatorKey = Key('Nested Navigator');
await tester.pumpWidget(_createRootNavigatorTest(
appKey: appKey,
nestedNavigatorKey: nestedNavigatorKey,
useRootNavigator: true));
await tester.tap(find.text('Closed'));
await tester.pumpAndSettle();
expect(
find.descendant(of: find.byKey(appKey), matching: find.text('Opened')),
findsOneWidget);
expect(
find.descendant(
of: find.byKey(nestedNavigatorKey), matching: find.text('Opened')),
findsNothing);
});
testWidgets('Verify correct opened size when "useRootNavigator: false"',
(WidgetTester tester) async {
const Key appKey = Key('App');
const Key nestedNavigatorKey = Key('Nested Navigator');
await tester.pumpWidget(_createRootNavigatorTest(
appKey: appKey,
nestedNavigatorKey: nestedNavigatorKey,
useRootNavigator: false));
await tester.tap(find.text('Closed'));
await tester.pumpAndSettle();
expect(tester.getSize(find.text('Opened')),
equals(tester.getSize(find.byKey(nestedNavigatorKey))));
});
testWidgets('Verify correct opened size when "useRootNavigator: true"',
(WidgetTester tester) async {
const Key appKey = Key('App');
const Key nestedNavigatorKey = Key('Nested Navigator');
await tester.pumpWidget(_createRootNavigatorTest(
appKey: appKey,
nestedNavigatorKey: nestedNavigatorKey,
useRootNavigator: true));
await tester.tap(find.text('Closed'));
await tester.pumpAndSettle();
expect(tester.getSize(find.text('Opened')),
equals(tester.getSize(find.byKey(appKey))));
});
testWidgets(
'Verify routeSettings passed to Navigator',
(WidgetTester tester) async {
const RouteSettings routeSettings = RouteSettings(
name: 'route-name',
arguments: 'arguments',
);
final Widget openContainer = OpenContainer(
routeSettings: routeSettings,
closedBuilder: (BuildContext context, VoidCallback action) {
return GestureDetector(
onTap: action,
child: const Text('Closed'),
);
},
openBuilder: (BuildContext context, VoidCallback action) {
return GestureDetector(
onTap: action,
child: const Text('Open'),
);
},
);
await tester.pumpWidget(_boilerplate(child: openContainer));
// Open the container
await tester.tap(find.text('Closed'));
await tester.pumpAndSettle();
// Expect the last route pushed to the navigator to contain RouteSettings
// equal to the RouteSettings passed to the OpenContainer
final ModalRoute<dynamic> modalRoute = ModalRoute.of(
tester.element(find.text('Open')),
)!;
expect(modalRoute.settings, routeSettings);
},
);
// Regression test for https://github.com/flutter/flutter/issues/72238.
testWidgets(
'OpenContainer\'s source widget is visible in closed container route if '
'open container route is pushed from not using the OpenContainer itself',
(WidgetTester tester) async {
final Widget openContainer = OpenContainer(
closedBuilder: (BuildContext context, VoidCallback action) {
return GestureDetector(
onTap: action,
child: const Text('Closed'),
);
},
openBuilder: (BuildContext context, VoidCallback action) {
return GestureDetector(
onTap: action,
child: const Text('Open'),
);
},
);
await tester.pumpWidget(_boilerplate(child: openContainer));
expect(_getOpacity(tester, 'Closed'), 1.0);
// Open the container
await tester.tap(find.text('Closed'));
await tester.pumpAndSettle();
final Element container = tester.element(
find.byType(OpenContainer, skipOffstage: false),
);
// Replace the open container route.
Navigator.pushReplacement<void, void>(container,
MaterialPageRoute<void>(builder: (_) => const Placeholder()));
await tester.pumpAndSettle();
// Go back to the main page and verify the closed builder is showed.
Navigator.pop(container);
await tester.pumpAndSettle();
expect(_getOpacity(tester, 'Closed'), 1.0);
},
);
}
Color _getScrimColor(WidgetTester tester) {
return tester.widget<ColoredBox>(find.byType(ColoredBox)).color;
}
void _expectMaterialPropertiesHaveAdvanced({
required _TrackedData biggerMaterial,
required _TrackedData smallerMaterial,
required WidgetTester tester,
}) {
expect(
biggerMaterial.material.elevation,
greaterThan(smallerMaterial.material.elevation),
);
expect(biggerMaterial.radius, lessThan(smallerMaterial.radius));
expect(biggerMaterial.rect.height, greaterThan(smallerMaterial.rect.height));
expect(biggerMaterial.rect.width, greaterThan(smallerMaterial.rect.width));
expect(biggerMaterial.rect.top, lessThan(smallerMaterial.rect.top));
expect(biggerMaterial.rect.left, lessThan(smallerMaterial.rect.left));
}
double _getOpacity(WidgetTester tester, String label) {
final Opacity widget = tester.firstWidget(find.ancestor(
of: find.text(label),
matching: find.byType(Opacity),
));
return widget.opacity;
}
class _TrackedData {
_TrackedData(this.material, this.rect);
final Material material;
final Rect rect;
double get radius => _getRadius(material);
}
double _getRadius(Material material) {
final RoundedRectangleBorder? shape =
material.shape as RoundedRectangleBorder?;
if (shape == null) {
return 0.0;
}
final BorderRadius radius = shape.borderRadius as BorderRadius;
return radius.topRight.x;
}
Widget _boilerplate({required Widget child}) {
return MaterialApp(
home: Scaffold(
body: child,
),
);
}
class _SizableContainer extends StatefulWidget {
const _SizableContainer({required this.initialSize, required this.child});
final double initialSize;
final Widget child;
@override
State<_SizableContainer> createState() =>
_SizableContainerState(size: initialSize);
}
class _SizableContainerState extends State<_SizableContainer> {
_SizableContainerState({required double size}) : _size = size;
double get size => _size;
double _size;
set size(double value) {
if (value == _size) {
return;
}
setState(() {
_size = value;
});
}
@override
Widget build(BuildContext context) {
return Container(
height: size,
width: size,
child: widget.child,
);
}
}
class _RemoveOpenContainerExample extends StatefulWidget {
@override
__RemoveOpenContainerExampleState createState() =>
__RemoveOpenContainerExampleState();
}
class __RemoveOpenContainerExampleState
extends State<_RemoveOpenContainerExample> {
bool removeOpenContainerWidget = false;
@override
Widget build(BuildContext context) {
return removeOpenContainerWidget
? const Text('Container has been removed')
: OpenContainer(
closedBuilder: (BuildContext context, VoidCallback action) =>
Column(
children: <Widget>[
const Text('Closed'),
ElevatedButton(
onPressed: action,
child: const Text('Open the container'),
),
],
),
openBuilder: (BuildContext context, VoidCallback action) => Column(
children: <Widget>[
const Text('Open'),
ElevatedButton(
onPressed: action,
child: const Text('Close the container'),
),
ElevatedButton(
onPressed: () {
setState(() {
removeOpenContainerWidget = true;
});
},
child: const Text('Remove the container')),
],
),
);
}
}