dismissible_test.dart 13.1 KB
Newer Older
Hixie's avatar
Hixie committed
1 2 3 4
// Copyright 2015 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.

Adam Barth's avatar
Adam Barth committed
5
import 'package:flutter_test/flutter_test.dart';
6 7
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
8

9
const double itemExtent = 100.0;
10
Axis scrollDirection = Axis.vertical;
11
DismissDirection dismissDirection = DismissDirection.horizontal;
12
DismissDirection reportedDismissDirection;
13
List<int> dismissedItems = <int>[];
14
Widget background;
15

16
Widget buildTest({ double startToEndThreshold }) {
Ian Hickson's avatar
Ian Hickson committed
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
  return new Directionality(
    textDirection: TextDirection.ltr,
    child: new StatefulBuilder(
      builder: (BuildContext context, StateSetter setState) {
        Widget buildDismissibleItem(int item) {
          return new Dismissible(
            key: new ValueKey<int>(item),
            direction: dismissDirection,
            onDismissed: (DismissDirection direction) {
              setState(() {
                reportedDismissDirection = direction;
                expect(dismissedItems.contains(item), isFalse);
                dismissedItems.add(item);
              });
            },
            onResize: () {
33
              expect(dismissedItems.contains(item), isFalse);
Ian Hickson's avatar
Ian Hickson committed
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
            },
            background: background,
            dismissThresholds: startToEndThreshold == null
                ? <DismissDirection, double>{}
                : <DismissDirection, double>{DismissDirection.startToEnd: startToEndThreshold},
            child: new Container(
              width: itemExtent,
              height: itemExtent,
              child: new Text(item.toString()),
            ),
          );
        }

        return new Directionality(
          textDirection: TextDirection.ltr,
49
          child: new Container(
Ian Hickson's avatar
Ian Hickson committed
50 51 52 53 54 55 56 57
            padding: const EdgeInsets.all(10.0),
            child: new ListView(
              scrollDirection: scrollDirection,
              itemExtent: itemExtent,
              children: <int>[0, 1, 2, 3, 4]
                .where((int i) => !dismissedItems.contains(i))
                .map(buildDismissibleItem).toList(),
            ),
58 59
          ),
        );
Ian Hickson's avatar
Ian Hickson committed
60 61
      },
    ),
62 63 64
  );
}

65
Future<Null> dismissElement(WidgetTester tester, Finder finder, { DismissDirection gestureDirection }) async {
66
  assert(tester.any(finder));
67 68 69
  assert(gestureDirection != DismissDirection.horizontal);
  assert(gestureDirection != DismissDirection.vertical);

70 71
  Offset downLocation;
  Offset upLocation;
72
  switch (gestureDirection) {
73
    case DismissDirection.endToStart:
74
      // getTopRight() returns a point that's just beyond itemWidget's right
75
      // edge and outside the Dismissible event listener's bounds.
76 77
      downLocation = tester.getTopRight(finder) + const Offset(-0.1, 0.0);
      upLocation = tester.getTopLeft(finder);
78
      break;
79
    case DismissDirection.startToEnd:
80
      // we do the same thing here to keep the test symmetric
81 82
      downLocation = tester.getTopLeft(finder) + const Offset(0.1, 0.0);
      upLocation = tester.getTopRight(finder);
83 84
      break;
    case DismissDirection.up:
85
      // getBottomLeft() returns a point that's just below itemWidget's bottom
86
      // edge and outside the Dismissible event listener's bounds.
87 88
      downLocation = tester.getBottomLeft(finder) + const Offset(0.0, -0.1);
      upLocation = tester.getTopLeft(finder);
89 90
      break;
    case DismissDirection.down:
91
      // again with doing the same here for symmetry
92 93
      downLocation = tester.getTopLeft(finder) + const Offset(0.1, 0.0);
      upLocation = tester.getBottomLeft(finder);
94
      break;
95
    default:
Ian Hickson's avatar
Ian Hickson committed
96
      fail('unsupported gestureDirection');
97 98
  }

99
  final TestGesture gesture = await tester.startGesture(downLocation, pointer: 5);
100 101
  await gesture.moveTo(upLocation);
  await gesture.up();
Hixie's avatar
Hixie committed
102 103
}

104
Future<Null> dismissItem(WidgetTester tester, int item, { DismissDirection gestureDirection }) async {
Hixie's avatar
Hixie committed
105 106 107
  assert(gestureDirection != DismissDirection.horizontal);
  assert(gestureDirection != DismissDirection.vertical);

108
  final Finder itemFinder = find.text(item.toString());
109
  expect(itemFinder, findsOneWidget);
Hixie's avatar
Hixie committed
110

111
  await dismissElement(tester, itemFinder, gestureDirection: gestureDirection);
112

113 114 115 116 117
  await tester.pump(); // start the slide
  await tester.pump(const Duration(seconds: 1)); // finish the slide and start shrinking...
  await tester.pump(); // first frame of shrinking animation
  await tester.pump(const Duration(seconds: 1)); // finish the shrinking and call the callback...
  await tester.pump(); // rebuild after the callback removes the entry
118 119
}

120
class Test1215DismissibleWidget extends StatelessWidget {
121
  const Test1215DismissibleWidget(this.text);
122

Hixie's avatar
Hixie committed
123
  final String text;
124 125

  @override
Hixie's avatar
Hixie committed
126
  Widget build(BuildContext context) {
127
    return new Dismissible(
128
      key: new ObjectKey(text),
Hixie's avatar
Hixie committed
129 130
      child: new AspectRatio(
        aspectRatio: 1.0,
131
        child: new Text(text),
132
      ),
Hixie's avatar
Hixie committed
133 134 135 136
    );
  }
}

137
void main() {
138 139 140 141 142
  setUp(() {
    dismissedItems = <int>[];
    background = null;
  });

143
  testWidgets('Horizontal drag triggers dismiss scrollDirection=vertical', (WidgetTester tester) async {
144 145
    scrollDirection = Axis.vertical;
    dismissDirection = DismissDirection.horizontal;
146

147
    await tester.pumpWidget(buildTest());
148
    expect(dismissedItems, isEmpty);
149

150
    await dismissItem(tester, 0, gestureDirection: DismissDirection.startToEnd);
151
    expect(find.text('0'), findsNothing);
152
    expect(dismissedItems, equals(<int>[0]));
153
    expect(reportedDismissDirection, DismissDirection.startToEnd);
154

155
    await dismissItem(tester, 1, gestureDirection: DismissDirection.endToStart);
156
    expect(find.text('1'), findsNothing);
157
    expect(dismissedItems, equals(<int>[0, 1]));
158
    expect(reportedDismissDirection, DismissDirection.endToStart);
159 160
  });

161
  testWidgets('Vertical drag triggers dismiss scrollDirection=horizontal', (WidgetTester tester) async {
162 163
    scrollDirection = Axis.horizontal;
    dismissDirection = DismissDirection.vertical;
164

165
    await tester.pumpWidget(buildTest());
166
    expect(dismissedItems, isEmpty);
167

168
    await dismissItem(tester, 0, gestureDirection: DismissDirection.up);
169
    expect(find.text('0'), findsNothing);
170
    expect(dismissedItems, equals(<int>[0]));
171
    expect(reportedDismissDirection, DismissDirection.up);
172

173
    await dismissItem(tester, 1, gestureDirection: DismissDirection.down);
174
    expect(find.text('1'), findsNothing);
175
    expect(dismissedItems, equals(<int>[0, 1]));
176
    expect(reportedDismissDirection, DismissDirection.down);
177 178
  });

179
  testWidgets('drag-left with DismissDirection.left triggers dismiss', (WidgetTester tester) async {
180 181
    scrollDirection = Axis.vertical;
    dismissDirection = DismissDirection.endToStart;
182

183
    await tester.pumpWidget(buildTest());
184
    expect(dismissedItems, isEmpty);
185

186
    await dismissItem(tester, 0, gestureDirection: DismissDirection.startToEnd);
187 188
    expect(find.text('0'), findsOneWidget);
    expect(dismissedItems, isEmpty);
189
    await dismissItem(tester, 1, gestureDirection: DismissDirection.startToEnd);
190

191
    await dismissItem(tester, 0, gestureDirection: DismissDirection.endToStart);
192
    expect(find.text('0'), findsNothing);
193
    expect(dismissedItems, equals(<int>[0]));
194
    await dismissItem(tester, 1, gestureDirection: DismissDirection.endToStart);
195 196
  });

197
  testWidgets('drag-right with DismissDirection.right triggers dismiss', (WidgetTester tester) async {
198 199
    scrollDirection = Axis.vertical;
    dismissDirection = DismissDirection.startToEnd;
200

201
    await tester.pumpWidget(buildTest());
202
    expect(dismissedItems, isEmpty);
203

204
    await dismissItem(tester, 0, gestureDirection: DismissDirection.endToStart);
205 206
    expect(find.text('0'), findsOneWidget);
    expect(dismissedItems, isEmpty);
207

208
    await dismissItem(tester, 0, gestureDirection: DismissDirection.startToEnd);
209
    expect(find.text('0'), findsNothing);
210
    expect(dismissedItems, equals(<int>[0]));
211 212
  });

213
  testWidgets('drag-up with DismissDirection.up triggers dismiss', (WidgetTester tester) async {
214 215
    scrollDirection = Axis.horizontal;
    dismissDirection = DismissDirection.up;
216

217
    await tester.pumpWidget(buildTest());
218
    expect(dismissedItems, isEmpty);
219

220
    await dismissItem(tester, 0, gestureDirection: DismissDirection.down);
221 222
    expect(find.text('0'), findsOneWidget);
    expect(dismissedItems, isEmpty);
223

224
    await dismissItem(tester, 0, gestureDirection: DismissDirection.up);
225
    expect(find.text('0'), findsNothing);
226
    expect(dismissedItems, equals(<int>[0]));
227 228
  });

229
  testWidgets('drag-down with DismissDirection.down triggers dismiss', (WidgetTester tester) async {
230 231
    scrollDirection = Axis.horizontal;
    dismissDirection = DismissDirection.down;
232

233
    await tester.pumpWidget(buildTest());
234
    expect(dismissedItems, isEmpty);
235

236
    await dismissItem(tester, 0, gestureDirection: DismissDirection.up);
237 238
    expect(find.text('0'), findsOneWidget);
    expect(dismissedItems, isEmpty);
239

240
    await dismissItem(tester, 0, gestureDirection: DismissDirection.down);
241
    expect(find.text('0'), findsNothing);
242
    expect(dismissedItems, equals(<int>[0]));
243
  });
244

245
  testWidgets('drag-left has no effect on dismissible with a high dismiss threshold', (WidgetTester tester) async {
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
    scrollDirection = Axis.vertical;
    dismissDirection = DismissDirection.horizontal;

    await tester.pumpWidget(buildTest(startToEndThreshold: 1.0));
    expect(dismissedItems, isEmpty);

    await dismissItem(tester, 0, gestureDirection: DismissDirection.startToEnd);
    expect(find.text('0'), findsOneWidget);
    expect(dismissedItems, isEmpty);

    await dismissItem(tester, 0, gestureDirection: DismissDirection.endToStart);
    expect(find.text('0'), findsNothing);
    expect(dismissedItems, equals(<int>[0]));
  });

261 262 263 264 265 266
  // This is a regression test for an fn2 bug where dragging a card caused an
  // assert "'!_disqualifiedFromEverAppearingAgain' is not true". The old URL
  // was https://github.com/domokit/sky_engine/issues/1068 but that issue is 404
  // now since we migrated to the new repo. The bug was fixed by
  // https://github.com/flutter/engine/pull/1134 at the time, and later made
  // irrelevant by fn3, but just in case...
267
  testWidgets('Verify that drag-move events do not assert', (WidgetTester tester) async {
268 269 270
    scrollDirection = Axis.horizontal;
    dismissDirection = DismissDirection.down;

271
    await tester.pumpWidget(buildTest());
272
    final Offset location = tester.getTopLeft(find.text('0'));
273 274
    final Offset offset = const Offset(0.0, 5.0);
    final TestGesture gesture = await tester.startGesture(location, pointer: 5);
275
    await gesture.moveBy(offset);
276
    await tester.pumpWidget(buildTest());
277
    await gesture.moveBy(offset);
278
    await tester.pumpWidget(buildTest());
279
    await gesture.moveBy(offset);
280
    await tester.pumpWidget(buildTest());
281
    await gesture.moveBy(offset);
282
    await tester.pumpWidget(buildTest());
283
    await gesture.up();
284
  });
Hixie's avatar
Hixie committed
285

286
  // This one is for a case where dismissing a widget above a previously
287
  // dismissed widget threw an exception, which was documented at the
288 289 290
  // now-obsolete URL https://github.com/flutter/engine/issues/1215 (the URL
  // died in the migration to the new repo). Don't copy this test; it doesn't
  // actually remove the dismissed widget, which is a violation of the
291
  // Dismissible contract. This is not an example of good practice.
292
  testWidgets('dismissing bottom then top (smoketest)', (WidgetTester tester) async {
Ian Hickson's avatar
Ian Hickson committed
293 294 295 296 297 298 299 300 301 302 303 304 305 306
    await tester.pumpWidget(
      new Directionality(
        textDirection: TextDirection.ltr,
        child: new Center(
          child: new Container(
            width: 100.0,
            height: 1000.0,
            child: new Column(
              children: <Widget>[
                const Test1215DismissibleWidget('1'),
                const Test1215DismissibleWidget('2'),
              ],
            ),
          ),
307 308
        ),
      ),
Ian Hickson's avatar
Ian Hickson committed
309
    );
310 311
    expect(find.text('1'), findsOneWidget);
    expect(find.text('2'), findsOneWidget);
312 313
    await dismissElement(tester, find.text('2'), gestureDirection: DismissDirection.startToEnd);
    await tester.pump(); // start the slide away
314
    await tester.pump(const Duration(seconds: 1)); // finish the slide away
315 316
    expect(find.text('1'), findsOneWidget);
    expect(find.text('2'), findsNothing);
317 318
    await dismissElement(tester, find.text('1'), gestureDirection: DismissDirection.startToEnd);
    await tester.pump(); // start the slide away
319
    await tester.pump(const Duration(seconds: 1)); // finish the slide away (at which point the child is no longer included in the tree)
320 321
    expect(find.text('1'), findsNothing);
    expect(find.text('2'), findsNothing);
Hixie's avatar
Hixie committed
322
  });
323

324
  testWidgets('Dismissible starts from the full size when collapsing', (WidgetTester tester) async {
325 326
    scrollDirection = Axis.vertical;
    dismissDirection = DismissDirection.horizontal;
327
    background = const Text('background');
328

329
    await tester.pumpWidget(buildTest());
330
    expect(dismissedItems, isEmpty);
331

332
    final Finder itemFinder = find.text('0');
333
    expect(itemFinder, findsOneWidget);
334 335
    await dismissElement(tester, itemFinder, gestureDirection: DismissDirection.startToEnd);
    await tester.pump();
336

337
    expect(find.text('background'), findsOneWidget); // The other four have been culled.
338
    final RenderBox backgroundBox = tester.firstRenderObject(find.text('background'));
339
    expect(backgroundBox.size.height, equals(100.0));
340
  });
341
}