| // 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/material.dart'; |
| import 'package:flutter/rendering.dart'; |
| import 'package:flutter/services.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| import '../rendering/mock_canvas.dart'; |
| |
| void main() { |
| testWidgets('The Ink widget renders a Container by default', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Material( |
| child: Ink(), |
| ), |
| ); |
| expect(tester.getSize(find.byType(Container)).height, 600.0); |
| expect(tester.getSize(find.byType(Container)).width, 800.0); |
| |
| const double height = 150.0; |
| const double width = 200.0; |
| await tester.pumpWidget( |
| Material( |
| child: Center( // used to constrain to child's size |
| child: Ink( |
| height: height, |
| width: width, |
| ), |
| ), |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| expect(tester.getSize(find.byType(Container)).height, height); |
| expect(tester.getSize(find.byType(Container)).width, width); |
| }); |
| |
| testWidgets('The InkWell widget renders an ink splash', (WidgetTester tester) async { |
| const Color highlightColor = Color(0xAAFF0000); |
| const Color splashColor = Color(0xAA0000FF); |
| final BorderRadius borderRadius = BorderRadius.circular(6.0); |
| |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Material( |
| child: Center( |
| child: Container( |
| width: 200.0, |
| height: 60.0, |
| child: InkWell( |
| borderRadius: borderRadius, |
| highlightColor: highlightColor, |
| splashColor: splashColor, |
| onTap: () { }, |
| ), |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| final Offset center = tester.getCenter(find.byType(InkWell)); |
| final TestGesture gesture = await tester.startGesture(center); |
| await tester.pump(); // start gesture |
| await tester.pump(const Duration(milliseconds: 200)); // wait for splash to be well under way |
| |
| final RenderBox box = Material.of(tester.element(find.byType(InkWell)))! as RenderBox; |
| expect( |
| box, |
| paints |
| ..translate(x: 0.0, y: 0.0) |
| ..save() |
| ..translate(x: 300.0, y: 270.0) |
| ..clipRRect(rrect: RRect.fromLTRBR(0.0, 0.0, 200.0, 60.0, const Radius.circular(6.0))) |
| ..circle(x: 100.0, y: 30.0, radius: 21.0, color: splashColor) |
| ..restore() |
| ..rrect( |
| rrect: RRect.fromLTRBR(300.0, 270.0, 500.0, 330.0, const Radius.circular(6.0)), |
| color: highlightColor, |
| ), |
| ); |
| |
| await gesture.up(); |
| }); |
| |
| testWidgets('The InkWell widget renders an ink ripple', (WidgetTester tester) async { |
| const Color highlightColor = Color(0xAAFF0000); |
| const Color splashColor = Color(0xB40000FF); |
| final BorderRadius borderRadius = BorderRadius.circular(6.0); |
| |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Material( |
| child: Center( |
| child: Container( |
| width: 100.0, |
| height: 100.0, |
| child: InkWell( |
| borderRadius: borderRadius, |
| highlightColor: highlightColor, |
| splashColor: splashColor, |
| onTap: () { }, |
| radius: 100.0, |
| splashFactory: InkRipple.splashFactory, |
| ), |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| final Offset tapDownOffset = tester.getTopLeft(find.byType(InkWell)); |
| final Offset inkWellCenter = tester.getCenter(find.byType(InkWell)); |
| //final TestGesture gesture = await tester.startGesture(tapDownOffset); |
| await tester.tapAt(tapDownOffset); |
| await tester.pump(); // start gesture |
| |
| final RenderBox box = Material.of(tester.element(find.byType(InkWell)))! as RenderBox; |
| |
| bool offsetsAreClose(Offset a, Offset b) => (a - b).distance < 1.0; |
| bool radiiAreClose(double a, double b) => (a - b).abs() < 1.0; |
| |
| PaintPattern ripplePattern(Offset expectedCenter, double expectedRadius, int expectedAlpha) { |
| return paints |
| ..translate(x: 0.0, y: 0.0) |
| ..translate(x: tapDownOffset.dx, y: tapDownOffset.dy) |
| ..something((Symbol method, List<dynamic> arguments) { |
| if (method != #drawCircle) |
| return false; |
| final Offset center = arguments[0] as Offset; |
| final double radius = arguments[1] as double; |
| final Paint paint = arguments[2] as Paint; |
| if (offsetsAreClose(center, expectedCenter) && radiiAreClose(radius, expectedRadius) && paint.color.alpha == expectedAlpha) |
| return true; |
| throw ''' |
| Expected: center == $expectedCenter, radius == $expectedRadius, alpha == $expectedAlpha |
| Found: center == $center radius == $radius alpha == ${paint.color.alpha}'''; |
| } |
| ); |
| } |
| |
| // Initially the ripple's center is where the tap occurred; |
| // ripplePattern always add a translation of tapDownOffset. |
| expect(box, ripplePattern(Offset.zero, 30.0, 0)); |
| |
| // The ripple fades in for 75ms. During that time its alpha is eased from |
| // 0 to the splashColor's alpha value and its center moves towards the |
| // center of the ink well. |
| await tester.pump(const Duration(milliseconds: 50)); |
| expect(box, ripplePattern(const Offset(17.0, 17.0), 56.0, 120)); |
| |
| // At 75ms the ripple has fade in: it's alpha matches the splashColor's |
| // alpha and its center has moved closer to the ink well's center. |
| await tester.pump(const Duration(milliseconds: 25)); |
| expect(box, ripplePattern(const Offset(29.0, 29.0), 73.0, 180)); |
| |
| // At this point the splash radius has expanded to its limit: 5 past the |
| // ink well's radius parameter. The splash center has moved to its final |
| // location at the inkwell's center and the fade-out is about to start. |
| // The fade-out begins at 225ms = 50ms + 25ms + 150ms. |
| await tester.pump(const Duration(milliseconds: 150)); |
| expect(box, ripplePattern(inkWellCenter - tapDownOffset, 105.0, 180)); |
| |
| // After another 150ms the fade-out is complete. |
| await tester.pump(const Duration(milliseconds: 150)); |
| expect(box, ripplePattern(inkWellCenter - tapDownOffset, 105.0, 0)); |
| }); |
| |
| testWidgets('Does the Ink widget render anything', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Material( |
| child: Center( |
| child: Ink( |
| color: Colors.blue, |
| width: 200.0, |
| height: 200.0, |
| child: InkWell( |
| splashColor: Colors.green, |
| onTap: () { }, |
| ), |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| final Offset center = tester.getCenter(find.byType(InkWell)); |
| final TestGesture gesture = await tester.startGesture(center); |
| await tester.pump(); // start gesture |
| await tester.pump(const Duration(milliseconds: 200)); // wait for splash to be well under way |
| |
| final RenderBox box = Material.of(tester.element(find.byType(InkWell)))! as RenderBox; |
| expect( |
| box, |
| paints |
| ..rect(rect: const Rect.fromLTRB(300.0, 200.0, 500.0, 400.0), color: Color(Colors.blue.value)) |
| ..circle(color: Color(Colors.green.value)), |
| ); |
| |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Material( |
| child: Center( |
| child: Ink( |
| color: Colors.red, |
| width: 200.0, |
| height: 200.0, |
| child: InkWell( |
| splashColor: Colors.green, |
| onTap: () { }, |
| ), |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| expect(Material.of(tester.element(find.byType(InkWell))), same(box)); |
| |
| expect( |
| box, |
| paints |
| ..rect(rect: const Rect.fromLTRB(300.0, 200.0, 500.0, 400.0), color: Color(Colors.red.value)) |
| ..circle(color: Color(Colors.green.value)), |
| ); |
| |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Material( |
| child: Center( |
| child: InkWell( // this is at a different depth in the tree so it's now a new InkWell |
| splashColor: Colors.green, |
| onTap: () { }, |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| expect(Material.of(tester.element(find.byType(InkWell))), same(box)); |
| |
| expect(box, isNot(paints..rect())); |
| expect(box, isNot(paints..circle())); |
| |
| await gesture.up(); |
| }); |
| |
| testWidgets('The InkWell widget renders an SelectAction or ActivateAction-induced ink ripple', (WidgetTester tester) async { |
| const Color highlightColor = Color(0xAAFF0000); |
| const Color splashColor = Color(0xB40000FF); |
| final BorderRadius borderRadius = BorderRadius.circular(6.0); |
| |
| final FocusNode focusNode = FocusNode(debugLabel: 'Test Node'); |
| Future<void> buildTest(Intent intent) async { |
| return await tester.pumpWidget( |
| Shortcuts( |
| shortcuts: <LogicalKeySet, Intent>{ |
| LogicalKeySet(LogicalKeyboardKey.space): intent, |
| }, |
| child: Directionality( |
| textDirection: TextDirection.ltr, |
| child: Material( |
| child: Center( |
| child: Container( |
| width: 100.0, |
| height: 100.0, |
| child: InkWell( |
| borderRadius: borderRadius, |
| highlightColor: highlightColor, |
| splashColor: splashColor, |
| focusNode: focusNode, |
| onTap: () { }, |
| radius: 100.0, |
| splashFactory: InkRipple.splashFactory, |
| ), |
| ), |
| ), |
| ), |
| ), |
| ), |
| ); |
| } |
| |
| await buildTest(const ActivateIntent()); |
| focusNode.requestFocus(); |
| await tester.pumpAndSettle(); |
| |
| final Offset topLeft = tester.getTopLeft(find.byType(InkWell)); |
| final Offset inkWellCenter = tester.getCenter(find.byType(InkWell)) - topLeft; |
| |
| bool offsetsAreClose(Offset a, Offset b) => (a - b).distance < 1.0; |
| bool radiiAreClose(double a, double b) => (a - b).abs() < 1.0; |
| |
| PaintPattern ripplePattern(double expectedRadius, int expectedAlpha) { |
| return paints |
| ..translate(x: 0.0, y: 0.0) |
| ..translate(x: topLeft.dx, y: topLeft.dy) |
| ..something((Symbol method, List<dynamic> arguments) { |
| if (method != #drawCircle) { |
| return false; |
| } |
| final Offset center = arguments[0] as Offset; |
| final double radius = arguments[1] as double; |
| final Paint paint = arguments[2] as Paint; |
| if (offsetsAreClose(center, inkWellCenter) && |
| radiiAreClose(radius, expectedRadius) && |
| paint.color.alpha == expectedAlpha) { |
| return true; |
| } |
| throw ''' |
| Expected: center == $inkWellCenter, radius == $expectedRadius, alpha == $expectedAlpha |
| Found: center == $center radius == $radius alpha == ${paint.color.alpha}'''; |
| }, |
| ); |
| } |
| |
| await buildTest(const ActivateIntent()); |
| await tester.pumpAndSettle(); |
| await tester.sendKeyEvent(LogicalKeyboardKey.space); |
| await tester.pump(); |
| |
| final RenderBox box = Material.of(tester.element(find.byType(InkWell)))! as RenderBox; |
| |
| // ripplePattern always add a translation of topLeft. |
| expect(box, ripplePattern(30.0, 0)); |
| |
| // The ripple fades in for 75ms. During that time its alpha is eased from |
| // 0 to the splashColor's alpha value. |
| await tester.pump(const Duration(milliseconds: 50)); |
| expect(box, ripplePattern(56.0, 120)); |
| |
| // At 75ms the ripple has faded in: it's alpha matches the splashColor's |
| // alpha. |
| await tester.pump(const Duration(milliseconds: 25)); |
| expect(box, ripplePattern(73.0, 180)); |
| |
| // At this point the splash radius has expanded to its limit: 5 past the |
| // ink well's radius parameter. The fade-out is about to start. |
| // The fade-out begins at 225ms = 50ms + 25ms + 150ms. |
| await tester.pump(const Duration(milliseconds: 150)); |
| expect(box, ripplePattern(105.0, 180)); |
| |
| // After another 150ms the fade-out is complete. |
| await tester.pump(const Duration(milliseconds: 150)); |
| expect(box, ripplePattern(105.0, 0)); |
| }); |
| |
| testWidgets('Cancel an InkRipple that was disposed when its animation ended', (WidgetTester tester) async { |
| // Regression test for https://github.com/flutter/flutter/issues/14391 |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Material( |
| child: Center( |
| child: Container( |
| width: 100.0, |
| height: 100.0, |
| child: InkWell( |
| onTap: () { }, |
| radius: 100.0, |
| splashFactory: InkRipple.splashFactory, |
| ), |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| final Offset tapDownOffset = tester.getTopLeft(find.byType(InkWell)); |
| await tester.tapAt(tapDownOffset); |
| await tester.pump(); // start splash |
| await tester.pump(const Duration(milliseconds: 375)); // _kFadeOutDuration, in_ripple.dart |
| |
| final TestGesture gesture = await tester.startGesture(tapDownOffset); |
| await tester.pump(); // start gesture |
| await gesture.moveTo(const Offset(0.0, 0.0)); |
| await gesture.up(); // generates a tap cancel |
| await tester.pumpAndSettle(); |
| }); |
| |
| testWidgets('Cancel an InkRipple that was disposed when its animation ended', (WidgetTester tester) async { |
| const Color highlightColor = Color(0xAAFF0000); |
| const Color splashColor = Color(0xB40000FF); |
| |
| // Regression test for https://github.com/flutter/flutter/issues/14391 |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Material( |
| child: Center( |
| child: Container( |
| width: 100.0, |
| height: 100.0, |
| child: InkWell( |
| splashColor: splashColor, |
| highlightColor: highlightColor, |
| onTap: () { }, |
| radius: 100.0, |
| splashFactory: InkRipple.splashFactory, |
| ), |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| final Offset tapDownOffset = tester.getTopLeft(find.byType(InkWell)); |
| await tester.tapAt(tapDownOffset); |
| await tester.pump(); // start splash |
| // No delay here so _fadeInController.value=1.0 (InkRipple.dart) |
| |
| // Generate a tap cancel; Will cancel the ink splash before it started |
| final TestGesture gesture = await tester.startGesture(tapDownOffset); |
| await tester.pump(); // start gesture |
| await gesture.moveTo(const Offset(0.0, 0.0)); |
| await gesture.up(); // generates a tap cancel |
| |
| final RenderBox box = Material.of(tester.element(find.byType(InkWell)))! as RenderBox; |
| expect(box, paints..everything((Symbol method, List<dynamic> arguments) { |
| if (method != #drawCircle) |
| return true; |
| final Paint paint = arguments[2] as Paint; |
| if (paint.color.alpha == 0) |
| return true; |
| throw 'Expected: paint.color.alpha == 0, found: ${paint.color.alpha}'; |
| })); |
| }); |
| } |