picker_test.dart 15 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
Ian Hickson's avatar
Ian Hickson committed
4

5
import 'package:flutter/cupertino.dart';
6
import 'package:flutter/rendering.dart';
7
import 'package:flutter/services.dart';
8 9
import 'package:flutter_test/flutter_test.dart';

10 11
import '../rendering/mock_canvas.dart';

12
void main() {
13 14 15 16 17 18 19 20 21 22 23 24
  testWidgets('Picker respects theme styling', (WidgetTester tester) async {
    await tester.pumpWidget(
      CupertinoApp(
        home: Align(
          alignment: Alignment.topLeft,
          child: SizedBox(
            height: 300.0,
            width: 300.0,
            child: CupertinoPicker(
              itemExtent: 50.0,
              onSelectedItemChanged: (_) { },
              children: List<Widget>.generate(3, (int index) {
25
                return SizedBox(
26 27 28 29 30 31 32 33 34 35 36 37 38
                  height: 50.0,
                  width: 300.0,
                  child: Text(index.toString()),
                );
              }),
            ),
          ),
        ),
      ),
    );

    final RenderParagraph paragraph = tester.renderObject(find.text('1'));

39 40
    expect(paragraph.text.style!.color, isSameColorAs(CupertinoColors.black));
    expect(paragraph.text.style!.copyWith(color: CupertinoColors.black), const TextStyle(
41 42
      inherit: false,
      fontFamily: '.SF Pro Display',
43
      fontSize: 21.0,
44
      fontWeight: FontWeight.w400,
45
      letterSpacing: -0.6,
46 47 48 49
      color: CupertinoColors.black,
    ));
  });

50
  group('layout', () {
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
    // Regression test for https://github.com/flutter/flutter/issues/22999
    testWidgets('CupertinoPicker.builder test', (WidgetTester tester) async {
      Widget buildFrame(int childCount) {
        return Directionality(
          textDirection: TextDirection.ltr,
          child: CupertinoPicker.builder(
            itemExtent: 50.0,
            onSelectedItemChanged: (_) { },
            itemBuilder: (BuildContext context, int index) {
              return Text('$index');
            },
            childCount: childCount,
          ),
        );
      }

      await tester.pumpWidget(buildFrame(1));
      expect(tester.renderObject(find.text('0')).attached, true);

      await tester.pumpWidget(buildFrame(2));
      expect(tester.renderObject(find.text('0')).attached, true);
      expect(tester.renderObject(find.text('1')).attached, true);
    });

75
    testWidgets('selected item is in the middle', (WidgetTester tester) async {
76
      final FixedExtentScrollController controller = FixedExtentScrollController(initialItem: 1);
77 78

      await tester.pumpWidget(
79
        Directionality(
80
          textDirection: TextDirection.ltr,
81
          child: Align(
82
            alignment: Alignment.topLeft,
83
            child: SizedBox(
84 85
              height: 300.0,
              width: 300.0,
86
              child: CupertinoPicker(
87 88
                scrollController: controller,
                itemExtent: 50.0,
89
                onSelectedItemChanged: (_) { },
90
                children: List<Widget>.generate(3, (int index) {
91
                  return SizedBox(
92 93
                    height: 50.0,
                    width: 300.0,
94
                    child: Text(index.toString()),
95 96 97 98 99 100 101 102 103
                  );
                }),
              ),
            ),
          ),
        ),
      );

      expect(
104
        tester.getTopLeft(find.widgetWithText(SizedBox, '1').first),
105 106 107 108 109 110 111
        const Offset(0.0, 125.0),
      );

      controller.jumpToItem(0);
      await tester.pump();

      expect(
112
        tester.getTopLeft(find.widgetWithText(SizedBox, '1').first),
113 114 115
        const Offset(0.0, 175.0),
      );
      expect(
116
        tester.getTopLeft(find.widgetWithText(SizedBox, '0').first),
117 118 119 120 121
        const Offset(0.0, 125.0),
      );
    });
  });

122 123 124 125 126 127 128 129 130 131 132 133 134
  testWidgets('picker dark mode', (WidgetTester tester) async {
    await tester.pumpWidget(
      CupertinoApp(
        theme: const CupertinoThemeData(brightness: Brightness.light),
        home: Align(
          alignment: Alignment.topLeft,
          child: SizedBox(
            height: 300.0,
            width: 300.0,
            child: CupertinoPicker(
              backgroundColor: const CupertinoDynamicColor.withBrightness(
                color: Color(0xFF123456), // Set alpha channel to FF to disable under magnifier painting.
                darkColor: Color(0xFF654321),
135
              ),
136 137 138
              itemExtent: 15.0,
              children: const <Widget>[Text('1'), Text('1')],
              onSelectedItemChanged: (int i) { },
139 140 141
            ),
          ),
        ),
142 143
      ),
    );
144

145
    expect(find.byType(CupertinoPicker), paints..rrect(color: const Color.fromARGB(30, 118, 118, 128)));
146
    expect(find.byType(CupertinoPicker), paints..rect(color: const Color(0xFF123456)));
147

148 149 150 151 152 153 154 155 156 157 158 159
    await tester.pumpWidget(
      CupertinoApp(
        theme: const CupertinoThemeData(brightness: Brightness.dark),
        home: Align(
          alignment: Alignment.topLeft,
          child: SizedBox(
            height: 300.0,
            width: 300.0,
            child: CupertinoPicker(
              backgroundColor: const CupertinoDynamicColor.withBrightness(
                color: Color(0xFF123456),
                darkColor: Color(0xFF654321),
160
              ),
161 162 163
              itemExtent: 15.0,
              children: const <Widget>[Text('1'), Text('1')],
              onSelectedItemChanged: (int i) { },
164 165 166
            ),
          ),
        ),
167 168 169
      ),
    );

170
    expect(find.byType(CupertinoPicker), paints..rrect(color: const Color.fromARGB(61,118, 118, 128)));
171
    expect(find.byType(CupertinoPicker), paints..rect(color: const Color(0xFF654321)));
172 173
  });

174 175 176 177 178 179 180 181 182 183 184 185
  testWidgets('picker selectionOverlay', (WidgetTester tester) async {
    await tester.pumpWidget(
      CupertinoApp(
        theme: const CupertinoThemeData(brightness: Brightness.light),
        home: Align(
          alignment: Alignment.topLeft,
          child: SizedBox(
            height: 300.0,
            width: 300.0,
            child: CupertinoPicker(
              itemExtent: 15.0,
              onSelectedItemChanged: (int i) {},
186
              selectionOverlay: const CupertinoPickerDefaultSelectionOverlay(background: Color(0x12345678)),
187
              children: const <Widget>[Text('1'), Text('1')],
188 189 190 191 192 193 194 195 196
            ),
          ),
        ),
      ),
    );

    expect(find.byType(CupertinoPicker), paints..rrect(color: const Color(0x12345678)));
  });

197 198 199 200 201 202 203 204 205 206 207 208 209
  testWidgets('CupertinoPicker.selectionOverlay is nullable', (WidgetTester tester) async {
    await tester.pumpWidget(
      CupertinoApp(
        theme: const CupertinoThemeData(brightness: Brightness.light),
        home: Align(
          alignment: Alignment.topLeft,
          child: SizedBox(
            height: 300.0,
            width: 300.0,
            child: CupertinoPicker(
              itemExtent: 15.0,
              onSelectedItemChanged: (int i) {},
              selectionOverlay: null,
210
              children: const <Widget>[Text('1'), Text('1')],
211 212 213 214 215 216 217 218 219
            ),
          ),
        ),
      ),
    );

    expect(find.byType(CupertinoPicker), isNot(paints..rrect()));
  });

220
  group('scroll', () {
221 222 223 224 225 226
    testWidgets(
      'scrolling calls onSelectedItemChanged and triggers haptic feedback',
      (WidgetTester tester) async {
        final List<int> selectedItems = <int>[];
        final List<MethodCall> systemCalls = <MethodCall>[];

227
        tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async {
228
          systemCalls.add(methodCall);
229
          return null;
230 231 232
        });

        await tester.pumpWidget(
233
          Directionality(
234
            textDirection: TextDirection.ltr,
235
            child: CupertinoPicker(
236 237
              itemExtent: 100.0,
              onSelectedItemChanged: (int index) { selectedItems.add(index); },
238 239
              children: List<Widget>.generate(100, (int index) {
                return Center(
240
                  child: SizedBox(
241 242
                    width: 400.0,
                    height: 100.0,
243
                    child: Text(index.toString()),
244 245 246 247 248 249 250
                  ),
                );
              }),
            ),
          ),
        );

251
        await tester.drag(find.text('0'), const Offset(0.0, -100.0), warnIfMissed: false); // has an IgnorePointer
252 253 254 255 256 257 258 259 260
        expect(selectedItems, <int>[1]);
        expect(
          systemCalls.single,
          isMethodCall(
            'HapticFeedback.vibrate',
            arguments: 'HapticFeedbackType.selectionClick',
          ),
        );

261
        await tester.drag(find.text('0'), const Offset(0.0, 100.0), warnIfMissed: false); // has an IgnorePointer
262 263 264 265 266 267 268 269 270
        expect(selectedItems, <int>[1, 0]);
        expect(systemCalls, hasLength(2));
        expect(
          systemCalls.last,
          isMethodCall(
            'HapticFeedback.vibrate',
            arguments: 'HapticFeedbackType.selectionClick',
          ),
        );
271 272 273
      },
      variant: TargetPlatformVariant.only(TargetPlatform.iOS),
    );
274 275 276 277 278 279 280

    testWidgets(
      'do not trigger haptic effects on non-iOS devices',
      (WidgetTester tester) async {
        final List<int> selectedItems = <int>[];
        final List<MethodCall> systemCalls = <MethodCall>[];

281
        tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async {
282
          systemCalls.add(methodCall);
283
          return null;
284 285 286
        });

        await tester.pumpWidget(
287
          Directionality(
288
            textDirection: TextDirection.ltr,
289
            child: CupertinoPicker(
290 291
              itemExtent: 100.0,
              onSelectedItemChanged: (int index) { selectedItems.add(index); },
292 293
              children: List<Widget>.generate(100, (int index) {
                return Center(
294
                  child: SizedBox(
295 296
                    width: 400.0,
                    height: 100.0,
297
                    child: Text(index.toString()),
298 299 300 301 302 303 304
                  ),
                );
              }),
            ),
          ),
        );

305
        await tester.drag(find.text('0'), const Offset(0.0, -100.0), warnIfMissed: false); // has an IgnorePointer
306 307
        expect(selectedItems, <int>[1]);
        expect(systemCalls, isEmpty);
308 309 310
      },
      variant: TargetPlatformVariant(TargetPlatform.values.where((TargetPlatform platform) => platform != TargetPlatform.iOS).toSet()),
    );
311

312
    testWidgets('a drag in between items settles back', (WidgetTester tester) async {
313
      final FixedExtentScrollController controller = FixedExtentScrollController(initialItem: 10);
314 315 316
      final List<int> selectedItems = <int>[];

      await tester.pumpWidget(
317
        Directionality(
318
          textDirection: TextDirection.ltr,
319
          child: CupertinoPicker(
320 321 322
            scrollController: controller,
            itemExtent: 100.0,
            onSelectedItemChanged: (int index) { selectedItems.add(index); },
323 324
            children: List<Widget>.generate(100, (int index) {
              return Center(
325
                child: SizedBox(
326 327
                  width: 400.0,
                  height: 100.0,
328
                  child: Text(index.toString()),
329 330 331 332 333 334 335 336
                ),
              );
            }),
          ),
        ),
      );

      // Drag it by a bit but not enough to move to the next item.
337
      await tester.drag(find.text('10'), const Offset(0.0, 30.0), touchSlopY: 0.0, warnIfMissed: false); // has an IgnorePointer
338 339 340

      // The item that was in the center now moved a bit.
      expect(
341
        tester.getTopLeft(find.widgetWithText(SizedBox, '10')),
342 343 344 345 346 347
        const Offset(200.0, 280.0),
      );

      await tester.pumpAndSettle();

      expect(
348
        tester.getTopLeft(find.widgetWithText(SizedBox, '10')).dy,
349 350 351 352 353
        moreOrLessEquals(250.0, epsilon: 0.5),
      );
      expect(selectedItems.isEmpty, true);

      // Drag it by enough to move to the next item.
354
      await tester.drag(find.text('10'), const Offset(0.0, 70.0), touchSlopY: 0.0, warnIfMissed: false); // has an IgnorePointer
355 356 357 358

      await tester.pumpAndSettle();

      expect(
359
        tester.getTopLeft(find.widgetWithText(SizedBox, '10')).dy,
360 361 362 363
        // It's down by 100.0 now.
        moreOrLessEquals(350.0, epsilon: 0.5),
      );
      expect(selectedItems, <int>[9]);
Dan Field's avatar
Dan Field committed
364
    }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS,  TargetPlatform.macOS }));
365 366 367

    testWidgets('a big fling that overscrolls springs back', (WidgetTester tester) async {
      final FixedExtentScrollController controller =
368
          FixedExtentScrollController(initialItem: 10);
369 370 371
      final List<int> selectedItems = <int>[];

      await tester.pumpWidget(
372
        Directionality(
373
          textDirection: TextDirection.ltr,
374
          child: CupertinoPicker(
375 376 377
            scrollController: controller,
            itemExtent: 100.0,
            onSelectedItemChanged: (int index) { selectedItems.add(index); },
378 379
            children: List<Widget>.generate(100, (int index) {
              return Center(
380
                child: SizedBox(
381 382
                  width: 400.0,
                  height: 100.0,
383
                  child: Text(index.toString()),
384 385 386 387 388 389 390 391 392 393 394 395
                ),
              );
            }),
          ),
        ),
      );

      // A wild throw appears.
      await tester.fling(
        find.text('10'),
        const Offset(0.0, 10000.0),
        1000.0,
396
        warnIfMissed: false, // has an IgnorePointer
397 398
      );

399 400
      // Should have been flung far enough that even the first item goes off
      // screen and gets removed.
401
      expect(find.widgetWithText(SizedBox, '0').evaluate().isEmpty, true);
402

403 404 405 406 407 408 409 410 411 412 413
      expect(
        selectedItems,
        // This specific throw was fast enough that each scroll update landed
        // on every second item.
        <int>[8, 6, 4, 2, 0],
      );

      // Let it spring back.
      await tester.pumpAndSettle();

      expect(
414
        tester.getTopLeft(find.widgetWithText(SizedBox, '0')).dy,
415 416 417 418 419 420 421 422
        // Should have sprung back to the middle now.
        moreOrLessEquals(250.0),
      );
      expect(
        selectedItems,
        // Falling back to 0 shouldn't produce more callbacks.
        <int>[8, 6, 4, 2, 0],
      );
Dan Field's avatar
Dan Field committed
423
    }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS,  TargetPlatform.macOS }));
424
  });
425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448

  group('CupertinoPickerDefaultSelectionOverlay', () {
    testWidgets('should be using directional decoration', (WidgetTester tester) async {
      await tester.pumpWidget(
        CupertinoApp(
          theme: const CupertinoThemeData(brightness: Brightness.light),
          home: CupertinoPicker(
            itemExtent: 15.0,
            onSelectedItemChanged: (int i) {},
            selectionOverlay: const CupertinoPickerDefaultSelectionOverlay(background: Color(0x12345678)),
            children: const <Widget>[Text('1'), Text('1')],
          ),
        ),
      );

      final Finder selectionContainer = find.byType(Container);
      final Container container = tester.firstWidget<Container>(selectionContainer);
      final EdgeInsetsGeometry? margin = container.margin;
      final BorderRadiusGeometry? borderRadius = (container.decoration as BoxDecoration?)?.borderRadius;

      expect(margin, isA<EdgeInsetsDirectional>());
      expect(borderRadius, isA<BorderRadiusDirectional>());
    });
  });
449
}