1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
// Copyright 2017 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/gestures.dart' show kPressTimeout;
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/rendering.dart';
int confirmCount = 0;
int cancelCount = 0;
class TestInkSplash extends InkSplash {
TestInkSplash({
MaterialInkController controller,
RenderBox referenceBox,
Offset position,
Color color,
bool containedInkWell = false,
RectCallback rectCallback,
BorderRadius borderRadius,
ShapeBorder customBorder,
double radius,
VoidCallback onRemoved,
TextDirection textDirection
}) : super(
controller: controller,
referenceBox: referenceBox,
position: position,
color: color,
containedInkWell: containedInkWell,
rectCallback: rectCallback,
borderRadius: borderRadius,
customBorder: customBorder,
radius: radius,
onRemoved: onRemoved,
textDirection: textDirection,
);
@override
void confirm() {
confirmCount += 1;
super.confirm();
}
@override
void cancel() {
cancelCount += 1;
super.cancel();
}
}
class TestInkSplashFactory extends InteractiveInkFeatureFactory {
const TestInkSplashFactory();
@override
InteractiveInkFeature create({
MaterialInkController controller,
RenderBox referenceBox,
Offset position,
Color color,
bool containedInkWell = false,
RectCallback rectCallback,
BorderRadius borderRadius,
ShapeBorder customBorder,
double radius,
VoidCallback onRemoved,
TextDirection textDirection,
}) {
return TestInkSplash(
controller: controller,
referenceBox: referenceBox,
position: position,
color: color,
containedInkWell: containedInkWell,
rectCallback: rectCallback,
borderRadius: borderRadius,
customBorder: customBorder,
radius: radius,
onRemoved: onRemoved,
textDirection: textDirection,
);
}
}
void main() {
testWidgets('Tap and no focus causes a splash', (WidgetTester tester) async {
final Key textField1 = UniqueKey();
final Key textField2 = UniqueKey();
await tester.pumpWidget(
MaterialApp(
home: Theme(
data: ThemeData.light().copyWith(splashFactory: const TestInkSplashFactory()),
child: Material(
child: Container(
alignment: Alignment.topLeft,
child: Column(
children: <Widget>[
TextField(
key: textField1,
decoration: const InputDecoration(
labelText: 'label',
),
),
TextField(
key: textField2,
decoration: const InputDecoration(
labelText: 'label',
),
),
],
),
),
),
),
)
);
confirmCount = 0;
cancelCount = 0;
await tester.tap(find.byKey(textField1));
await tester.pumpAndSettle();
expect(confirmCount, 1);
expect(cancelCount, 0);
// textField1 already has the focus, no new splash
await tester.tap(find.byKey(textField1));
await tester.pumpAndSettle();
expect(confirmCount, 1);
expect(cancelCount, 0);
// textField2 gets the focus and a splash
await tester.tap(find.byKey(textField2));
await tester.pumpAndSettle();
expect(confirmCount, 2);
expect(cancelCount, 0);
// Tap outside of textField1's editable. It still gets focus and splash.
await tester.tapAt(tester.getTopLeft(find.byKey(textField1)));
await tester.pumpAndSettle();
expect(confirmCount, 3);
expect(cancelCount, 0);
// Tap in the center of textField2's editable. It still gets the focus
// and the splash. There is no splash cancel.
await tester.tap(find.byKey(textField2));
await tester.pumpAndSettle();
expect(confirmCount, 4);
expect(cancelCount, 0);
});
testWidgets('Splash cancel', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Theme(
data: ThemeData.light().copyWith(splashFactory: const TestInkSplashFactory()),
child: Material(
child: ListView(
children: <Widget>[
const TextField(
decoration: InputDecoration(
labelText: 'label1',
),
),
const TextField(
decoration: InputDecoration(
labelText: 'label2',
),
),
Container(
height: 1000.0,
color: const Color(0xFF00FF00),
),
],
),
),
),
)
);
confirmCount = 0;
cancelCount = 0;
// Pointer is dragged below the textfield, splash is canceled.
final TestGesture gesture1 = await tester.startGesture(tester.getCenter(find.text('label1')));
// Splashes start on tapDown.
// If the timeout is less than kPressTimeout the recognizer will just trigger
// the onTapCancel callback. If the timeout is greater or equal to kPressTimeout
// and less than kLongPressTimeout then onTapDown, onCancel will be called.
await tester.pump(kPressTimeout);
await gesture1.moveTo(const Offset(400.0, 300.0));
await gesture1.up();
expect(confirmCount, 0);
expect(cancelCount, 1);
// Pointer is dragged upwards causing a scroll, splash is canceled.
final TestGesture gesture2 = await tester.startGesture(tester.getCenter(find.text('label2')));
await tester.pump(kPressTimeout);
await gesture2.moveBy(const Offset(0.0, -200.0));
await gesture2.up();
expect(confirmCount, 0);
expect(cancelCount, 2);
});
}