// Copyright 2016 The Chromium 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_test/flutter_test.dart'; import 'package:flutter/gestures.dart'; import '../rendering/mock_canvas.dart'; import '../widgets/semantics_tester.dart'; import 'feedback_tester.dart'; void main() { testWidgets('InkWell gestures control test', (WidgetTester tester) async { final List<String> log = <String>[]; await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: Material( child: Center( child: InkWell( onTap: () { log.add('tap'); }, onDoubleTap: () { log.add('double-tap'); }, onLongPress: () { log.add('long-press'); }, onTapDown: (TapDownDetails details) { log.add('tap-down'); }, onTapCancel: () { log.add('tap-cancel'); }, ), ), ), )); await tester.tap(find.byType(InkWell), pointer: 1); expect(log, isEmpty); await tester.pump(const Duration(seconds: 1)); expect(log, equals(<String>['tap-down', 'tap'])); log.clear(); await tester.tap(find.byType(InkWell), pointer: 2); await tester.pump(const Duration(milliseconds: 100)); await tester.tap(find.byType(InkWell), pointer: 3); expect(log, equals(<String>['double-tap'])); log.clear(); await tester.longPress(find.byType(InkWell), pointer: 4); expect(log, equals(<String>['tap-down', 'tap-cancel', 'long-press'])); log.clear(); TestGesture gesture = await tester.startGesture(tester.getRect(find.byType(InkWell)).center); await tester.pump(const Duration(milliseconds: 100)); expect(log, equals(<String>['tap-down'])); await gesture.up(); await tester.pump(const Duration(seconds: 1)); log.clear(); gesture = await tester.startGesture(tester.getRect(find.byType(InkWell)).center); await tester.pump(const Duration(milliseconds: 100)); await gesture.moveBy(const Offset(0.0, 200.0)); await gesture.cancel(); expect(log, equals(<String>['tap-down', 'tap-cancel'])); }); testWidgets('long-press and tap on disabled should not throw', (WidgetTester tester) async { await tester.pumpWidget(const Material( child: Directionality( textDirection: TextDirection.ltr, child: Center( child: InkWell(), ), ), )); await tester.tap(find.byType(InkWell), pointer: 1); await tester.pump(const Duration(seconds: 1)); await tester.longPress(find.byType(InkWell), pointer: 1); await tester.pump(const Duration(seconds: 1)); }); testWidgets('ink well changes color on hover', (WidgetTester tester) async { await tester.pumpWidget(Material( child: Directionality( textDirection: TextDirection.ltr, child: Center( child: Container( width: 100, height: 100, child: InkWell( hoverColor: const Color(0xff00ff00), splashColor: const Color(0xffff0000), focusColor: const Color(0xff0000ff), highlightColor: const Color(0xf00fffff), onTap: () {}, onLongPress: () {}, onHover: (bool hover) {}, ), ), ), ), )); final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); await gesture.addPointer(); addTearDown(gesture.removePointer); await gesture.moveTo(tester.getCenter(find.byType(Container))); await tester.pumpAndSettle(); final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); expect(inkFeatures, paints..rect(rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), color: const Color(0xff00ff00))); }); testWidgets('ink response changes color on focus', (WidgetTester tester) async { WidgetsBinding.instance.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; final FocusNode focusNode = FocusNode(debugLabel: 'Ink Focus'); await tester.pumpWidget(Material( child: Directionality( textDirection: TextDirection.ltr, child: Center( child: Focus( focusNode: focusNode, child: Container( width: 100, height: 100, child: InkWell( hoverColor: const Color(0xff00ff00), splashColor: const Color(0xffff0000), focusColor: const Color(0xff0000ff), highlightColor: const Color(0xf00fffff), onTap: () {}, onLongPress: () {}, onHover: (bool hover) {}, ), ), ), ), ), )); await tester.pumpAndSettle(); final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); expect(inkFeatures, paintsExactlyCountTimes(#rect, 0)); focusNode.requestFocus(); await tester.pumpAndSettle(); expect(inkFeatures, paints ..rect(rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), color: const Color(0xff0000ff))); }); testWidgets("ink response doesn't change color on focus when on touch device", (WidgetTester tester) async { WidgetsBinding.instance.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTouch; final FocusNode focusNode = FocusNode(debugLabel: 'Ink Focus'); await tester.pumpWidget(Material( child: Directionality( textDirection: TextDirection.ltr, child: Center( child: Focus( focusNode: focusNode, child: Container( width: 100, height: 100, child: InkWell( hoverColor: const Color(0xff00ff00), splashColor: const Color(0xffff0000), focusColor: const Color(0xff0000ff), highlightColor: const Color(0xf00fffff), onTap: () {}, onLongPress: () {}, onHover: (bool hover) {}, ), ), ), ), ), )); await tester.pumpAndSettle(); final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); expect(inkFeatures, paintsExactlyCountTimes(#rect, 0)); focusNode.requestFocus(); await tester.pumpAndSettle(); expect(inkFeatures, paintsExactlyCountTimes(#rect, 0)); }); group('feedback', () { FeedbackTester feedback; setUp(() { feedback = FeedbackTester(); }); tearDown(() { feedback?.dispose(); }); testWidgets('enabled (default)', (WidgetTester tester) async { await tester.pumpWidget(Material( child: Directionality( textDirection: TextDirection.ltr, child: Center( child: InkWell( onTap: () {}, onLongPress: () {}, ), ), ), )); await tester.tap(find.byType(InkWell), pointer: 1); await tester.pump(const Duration(seconds: 1)); expect(feedback.clickSoundCount, 1); expect(feedback.hapticCount, 0); await tester.tap(find.byType(InkWell), pointer: 1); await tester.pump(const Duration(seconds: 1)); expect(feedback.clickSoundCount, 2); expect(feedback.hapticCount, 0); await tester.longPress(find.byType(InkWell), pointer: 1); await tester.pump(const Duration(seconds: 1)); expect(feedback.clickSoundCount, 2); expect(feedback.hapticCount, 1); }); testWidgets('disabled', (WidgetTester tester) async { await tester.pumpWidget(Material( child: Directionality( textDirection: TextDirection.ltr, child: Center( child: InkWell( onTap: () {}, onLongPress: () {}, enableFeedback: false, ), ), ), )); await tester.tap(find.byType(InkWell), pointer: 1); await tester.pump(const Duration(seconds: 1)); expect(feedback.clickSoundCount, 0); expect(feedback.hapticCount, 0); await tester.longPress(find.byType(InkWell), pointer: 1); await tester.pump(const Duration(seconds: 1)); expect(feedback.clickSoundCount, 0); expect(feedback.hapticCount, 0); }); }); testWidgets('splashing survives scrolling when keep-alive is enabled', (WidgetTester tester) async { Future<void> runTest(bool keepAlive) async { await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Material( child: CompositedTransformFollower( // forces a layer, which makes the paints easier to separate out link: LayerLink(), child: ListView( addAutomaticKeepAlives: keepAlive, dragStartBehavior: DragStartBehavior.down, children: <Widget>[ Container(height: 500.0, child: InkWell(onTap: () {}, child: const Placeholder())), Container(height: 500.0), Container(height: 500.0), ], ), ), ), ), ); expect(tester.renderObject<RenderProxyBox>(find.byType(PhysicalModel)).child, isNot(paints..circle())); await tester.tap(find.byType(InkWell)); await tester.pump(); await tester.pump(const Duration(milliseconds: 10)); expect(tester.renderObject<RenderProxyBox>(find.byType(PhysicalModel)).child, paints..circle()); await tester.drag(find.byType(ListView), const Offset(0.0, -1000.0)); await tester.pump(const Duration(milliseconds: 10)); await tester.drag(find.byType(ListView), const Offset(0.0, 1000.0)); await tester.pump(const Duration(milliseconds: 10)); expect( tester.renderObject<RenderProxyBox>(find.byType(PhysicalModel)).child, keepAlive ? (paints..circle()) : isNot(paints..circle()), ); } await runTest(true); await runTest(false); }); testWidgets('excludeFromSemantics', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: Material( child: InkWell( onTap: () {}, child: const Text('Button'), ), ), )); expect(semantics, includesNodeWith(label: 'Button', actions: <SemanticsAction>[SemanticsAction.tap])); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: Material( child: InkWell( onTap: () {}, child: const Text('Button'), excludeFromSemantics: true, ), ), )); expect(semantics, isNot(includesNodeWith(label: 'Button', actions: <SemanticsAction>[SemanticsAction.tap]))); semantics.dispose(); }); }