feedback_test.dart 7.14 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5
// 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';
6
import 'package:flutter/services.dart';
7 8
import 'package:flutter_test/flutter_test.dart';

9
import '../widgets/semantics_tester.dart';
10 11 12
import 'feedback_tester.dart';

void main () {
13
  const Duration kWaitDuration = Duration(seconds: 1);
14

15
  late FeedbackTester feedback;
16 17

  setUp(() {
18
    feedback = FeedbackTester();
19 20 21
  });

  tearDown(() {
22
    feedback.dispose();
23 24 25
  });

  group('Feedback on Android', () {
26
    late List<Map<String, Object>> semanticEvents;
27 28 29

    setUp(() {
      semanticEvents = <Map<String, Object>>[];
30
      TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockDecodedMessageHandler<dynamic>(SystemChannels.accessibility, (dynamic message) async {
31
        final Map<dynamic, dynamic> typedMessage = message as Map<dynamic, dynamic>;
32 33 34 35 36
        semanticEvents.add(typedMessage.cast<String, Object>());
      });
    });

    tearDown(() {
37
      TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockDecodedMessageHandler<dynamic>(SystemChannels.accessibility, null);
38
    });
39 40

    testWidgets('forTap', (WidgetTester tester) async {
41
      final SemanticsTester semanticsTester = SemanticsTester(tester);
42

43
      await tester.pumpWidget(TestWidget(
44 45 46 47 48 49 50
        tapHandler: (BuildContext context) {
          return () => Feedback.forTap(context);
        },
      ));
      await tester.pumpAndSettle(kWaitDuration);
      expect(feedback.hapticCount, 0);
      expect(feedback.clickSoundCount, 0);
51
      expect(semanticEvents, isEmpty);
52 53 54

      await tester.tap(find.text('X'));
      await tester.pumpAndSettle(kWaitDuration);
55 56
      final RenderObject object = tester.firstRenderObject(find.byType(GestureDetector));

57 58
      expect(feedback.hapticCount, 0);
      expect(feedback.clickSoundCount, 1);
59 60
      expect(semanticEvents.single, <String, dynamic>{
        'type': 'tap',
61
        'nodeId': object.debugSemantics!.id,
62 63
        'data': <String, dynamic>{},
      });
64
      expect(object.debugSemantics!.getSemanticsData().hasAction(SemanticsAction.tap), true);
65 66

      semanticsTester.dispose();
67 68 69
    });

    testWidgets('forTap Wrapper', (WidgetTester tester) async {
70
      final SemanticsTester semanticsTester = SemanticsTester(tester);
71

72
      int callbackCount = 0;
73
      void callback() {
74
        callbackCount++;
75
      }
76

77
      await tester.pumpWidget(TestWidget(
78
        tapHandler: (BuildContext context) {
79
          return Feedback.wrapForTap(callback, context)!;
80 81 82 83 84 85 86 87 88
        },
      ));
      await tester.pumpAndSettle(kWaitDuration);
      expect(feedback.hapticCount, 0);
      expect(feedback.clickSoundCount, 0);
      expect(callbackCount, 0);

      await tester.tap(find.text('X'));
      await tester.pumpAndSettle(kWaitDuration);
89 90
      final RenderObject object = tester.firstRenderObject(find.byType(GestureDetector));

91 92 93
      expect(feedback.hapticCount, 0);
      expect(feedback.clickSoundCount, 1);
      expect(callbackCount, 1);
94 95
      expect(semanticEvents.single, <String, dynamic>{
        'type': 'tap',
96
        'nodeId': object.debugSemantics!.id,
97 98
        'data': <String, dynamic>{},
      });
99
      expect(object.debugSemantics!.getSemanticsData().hasAction(SemanticsAction.tap), true);
100 101

      semanticsTester.dispose();
102 103 104
    });

    testWidgets('forLongPress', (WidgetTester tester) async {
105
      final SemanticsTester semanticsTester = SemanticsTester(tester);
106

107
      await tester.pumpWidget(TestWidget(
108 109 110 111 112 113 114 115 116 117
        longPressHandler: (BuildContext context) {
          return () => Feedback.forLongPress(context);
        },
      ));
      await tester.pumpAndSettle(kWaitDuration);
      expect(feedback.hapticCount, 0);
      expect(feedback.clickSoundCount, 0);

      await tester.longPress(find.text('X'));
      await tester.pumpAndSettle(kWaitDuration);
118 119
      final RenderObject object = tester.firstRenderObject(find.byType(GestureDetector));

120 121
      expect(feedback.hapticCount, 1);
      expect(feedback.clickSoundCount, 0);
122 123
      expect(semanticEvents.single, <String, dynamic>{
        'type': 'longPress',
124
        'nodeId': object.debugSemantics!.id,
125 126
        'data': <String, dynamic>{},
      });
127
      expect(object.debugSemantics!.getSemanticsData().hasAction(SemanticsAction.longPress), true);
128 129

      semanticsTester.dispose();
130 131 132
    });

    testWidgets('forLongPress Wrapper', (WidgetTester tester) async {
133
      final SemanticsTester semanticsTester = SemanticsTester(tester);
134
      int callbackCount = 0;
135
      void callback() {
136
        callbackCount++;
137
      }
138

139
      await tester.pumpWidget(TestWidget(
140
        longPressHandler: (BuildContext context) {
141
          return Feedback.wrapForLongPress(callback, context)!;
142 143 144
        },
      ));
      await tester.pumpAndSettle(kWaitDuration);
145 146
      final RenderObject object = tester.firstRenderObject(find.byType(GestureDetector));

147 148 149 150 151 152 153 154 155
      expect(feedback.hapticCount, 0);
      expect(feedback.clickSoundCount, 0);
      expect(callbackCount, 0);

      await tester.longPress(find.text('X'));
      await tester.pumpAndSettle(kWaitDuration);
      expect(feedback.hapticCount, 1);
      expect(feedback.clickSoundCount, 0);
      expect(callbackCount, 1);
156 157
      expect(semanticEvents.single, <String, dynamic>{
        'type': 'longPress',
158
        'nodeId': object.debugSemantics!.id,
159 160
        'data': <String, dynamic>{},
      });
161
      expect(object.debugSemantics!.getSemanticsData().hasAction(SemanticsAction.longPress), true);
162 163

      semanticsTester.dispose();
164 165 166 167 168 169
    });

  });

  group('Feedback on iOS', () {
    testWidgets('forTap', (WidgetTester tester) async {
170 171 172
      await tester.pumpWidget(Theme(
        data: ThemeData(platform: TargetPlatform.iOS),
        child: TestWidget(
173 174 175 176 177 178 179 180 181 182 183 184 185
          tapHandler: (BuildContext context) {
            return () => Feedback.forTap(context);
          },
        ),
      ));

      await tester.tap(find.text('X'));
      await tester.pumpAndSettle(kWaitDuration);
      expect(feedback.hapticCount, 0);
      expect(feedback.clickSoundCount, 0);
    });

    testWidgets('forLongPress', (WidgetTester tester) async {
186 187 188
      await tester.pumpWidget(Theme(
        data: ThemeData(platform: TargetPlatform.iOS),
        child: TestWidget(
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
          longPressHandler: (BuildContext context) {
            return () => Feedback.forLongPress(context);
          },
        ),
      ));

      await tester.longPress(find.text('X'));
      await tester.pumpAndSettle(kWaitDuration);
      expect(feedback.hapticCount, 0);
      expect(feedback.clickSoundCount, 0);
    });
  });
}

class TestWidget extends StatelessWidget {
204
  const TestWidget({
205
    Key? key,
206 207
    this.tapHandler = nullHandler,
    this.longPressHandler = nullHandler,
208
  }) : super(key: key);
209 210 211 212

  final HandlerCreator tapHandler;
  final HandlerCreator longPressHandler;

213
  static VoidCallback? nullHandler(BuildContext context) => null;
214 215 216

  @override
  Widget build(BuildContext context) {
217
    return GestureDetector(
218 219
        onTap: tapHandler(context),
        onLongPress: longPressHandler(context),
Ian Hickson's avatar
Ian Hickson committed
220
        child: const Text('X', textDirection: TextDirection.ltr),
221 222 223 224
    );
  }
}

225
typedef HandlerCreator = VoidCallback? Function(BuildContext context);