feedback_test.dart 7.12 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
import 'package:flutter_test/flutter_test.dart';
8
import '../widgets/semantics_tester.dart';
9 10 11
import 'feedback_tester.dart';

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

14
  late FeedbackTester feedback;
15 16

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

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

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

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

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

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

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

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

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

      semanticsTester.dispose();
66 67
    });

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

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

76
      await tester.pumpWidget(TestWidget(
77
        tapHandler: (BuildContext context) {
78
          return Feedback.wrapForTap(callback, context)!;
79 80 81 82 83 84 85 86 87
        },
      ));
      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);
88 89
      final RenderObject object = tester.firstRenderObject(find.byType(GestureDetector));

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

      semanticsTester.dispose();
101 102
    });

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

106
      await tester.pumpWidget(TestWidget(
107 108 109 110 111 112 113 114 115 116
        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);
117 118
      final RenderObject object = tester.firstRenderObject(find.byType(GestureDetector));

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

      semanticsTester.dispose();
129 130
    });

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

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

146 147 148 149 150 151 152 153 154
      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);
155 156
      expect(semanticEvents.single, <String, dynamic>{
        'type': 'longPress',
157
        'nodeId': object.debugSemantics!.id,
158 159
        'data': <String, dynamic>{},
      });
160
      expect(object.debugSemantics!.getSemanticsData().hasAction(SemanticsAction.longPress), true);
161 162

      semanticsTester.dispose();
163 164 165 166 167
    });

  });

  group('Feedback on iOS', () {
168
    testWidgets('forTap', (WidgetTester tester) async {
169 170 171
      await tester.pumpWidget(Theme(
        data: ThemeData(platform: TargetPlatform.iOS),
        child: TestWidget(
172 173 174 175 176 177 178 179 180 181 182 183
          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);
    });

184
    testWidgets('forLongPress', (WidgetTester tester) async {
185 186 187
      await tester.pumpWidget(Theme(
        data: ThemeData(platform: TargetPlatform.iOS),
        child: TestWidget(
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
          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 {
203
  const TestWidget({
204
    super.key,
205 206
    this.tapHandler = nullHandler,
    this.longPressHandler = nullHandler,
207
  });
208 209 210 211

  final HandlerCreator tapHandler;
  final HandlerCreator longPressHandler;

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

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

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