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

5
import 'dart:async';
6
import 'dart:math' as math;
7 8 9 10
import 'dart:typed_data';
import 'dart:ui' as ui show Image;

import 'package:flutter/foundation.dart';
11
import 'package:flutter/material.dart';
12
import 'package:flutter_test/flutter_test.dart';
13

14
import '../image_data.dart';
15 16
import '../rendering/mock_canvas.dart';

17 18 19
class TestImageProvider extends ImageProvider<TestImageProvider> {
  TestImageProvider(this.future);

20
  final Future<void> future;
21

22
  static late ui.Image image;
23 24 25

  @override
  Future<TestImageProvider> obtainKey(ImageConfiguration configuration) {
26
    return SynchronousFuture<TestImageProvider>(this);
27 28 29
  }

  @override
30
  ImageStreamCompleter load(TestImageProvider key, DecoderCallback decode) {
31
    return OneFrameImageStreamCompleter(
32
      future.then<ImageInfo>((void value) => ImageInfo(image: image)),
33 34 35 36
    );
  }
}

37
Future<void> main() async {
38
  AutomatedTestWidgetsFlutterBinding();
39
  TestImageProvider.image = await decodeImageFromList(Uint8List.fromList(kTransparentImage));
40 41

  testWidgets('DecoratedBox handles loading images', (WidgetTester tester) async {
42
    final GlobalKey key = GlobalKey();
43
    final Completer<void> completer = Completer<void>();
44
    await tester.pumpWidget(
45
      KeyedSubtree(
46
        key: key,
47 48 49 50
        child: DecoratedBox(
          decoration: BoxDecoration(
            image: DecorationImage(
              image: TestImageProvider(completer.future),
51 52 53 54 55 56 57 58 59 60 61 62 63 64
            ),
          ),
        ),
      ),
    );
    expect(tester.binding.hasScheduledFrame, isFalse);
    completer.complete();
    await tester.idle();
    expect(tester.binding.hasScheduledFrame, isTrue);
    await tester.pump();
    expect(tester.binding.hasScheduledFrame, isFalse);
  });

  testWidgets('Moving a DecoratedBox', (WidgetTester tester) async {
65
    final Completer<void> completer = Completer<void>();
66 67 68 69 70 71 72
    final Widget subtree = KeyedSubtree(
      key: GlobalKey(),
      child: RepaintBoundary(
        child: DecoratedBox(
          decoration: BoxDecoration(
            image: DecorationImage(
              image: TestImageProvider(completer.future),
73 74 75 76 77 78 79 80
            ),
          ),
        ),
      ),
    );
    await tester.pumpWidget(subtree);
    await tester.idle();
    expect(tester.binding.hasScheduledFrame, isFalse);
81
    await tester.pumpWidget(Container(child: subtree));
82 83 84 85 86 87 88 89 90 91 92
    await tester.idle();
    expect(tester.binding.hasScheduledFrame, isFalse);
    completer.complete(); // schedules microtask, does not run it
    expect(tester.binding.hasScheduledFrame, isFalse);
    await tester.idle(); // runs microtask
    expect(tester.binding.hasScheduledFrame, isTrue);
    await tester.pump();
    await tester.idle();
    expect(tester.binding.hasScheduledFrame, isFalse);
  });

93 94
  testWidgets('Circles can have uniform borders', (WidgetTester tester) async {
    await tester.pumpWidget(
95
      Container(
96
        padding: const EdgeInsets.all(50.0),
97
        decoration: BoxDecoration(
98
          shape: BoxShape.circle,
99
          border: Border.all(width: 10.0, color: const Color(0x80FF00FF)),
100 101
          color: Colors.teal[600],
        ),
102
      ),
103
    );
104
  });
105

106
  testWidgets('Bordered Container insets its child', (WidgetTester tester) async {
107
    const Key key = Key('outerContainer');
108
    await tester.pumpWidget(
109 110
      Center(
        child: Container(
111
          key: key,
112
          decoration: BoxDecoration(border: Border.all(width: 10.0)),
113
          child: const SizedBox(
114
            width: 25.0,
115 116 117
            height: 25.0,
          ),
        ),
118
      ),
119 120
    );
    expect(tester.getSize(find.byKey(key)), equals(const Size(45.0, 45.0)));
121
  });
122 123 124 125

  testWidgets('BoxDecoration paints its border correctly', (WidgetTester tester) async {
    // Regression test for https://github.com/flutter/flutter/issues/7672

126
    const Key key = Key('Container with BoxDecoration');
127
    Widget buildFrame(Border border) {
128 129
      return Center(
        child: Container(
130 131 132
          key: key,
          width: 100.0,
          height: 50.0,
133
          decoration: BoxDecoration(border: border),
134 135 136 137
        ),
      );
    }

138
    const Color black = Color(0xFF000000);
139

140
    await tester.pumpWidget(buildFrame(Border.all()));
141
    expect(find.byKey(key), paints..rect(color: black, style: PaintingStyle.stroke, strokeWidth: 1.0));
142

143
    await tester.pumpWidget(buildFrame(Border.all(width: 0.0)));
144
    expect(find.byKey(key), paints..rect(color: black, style: PaintingStyle.stroke, strokeWidth: 0.0));
145

146 147
    const Color green = Color(0xFF00FF00);
    const BorderSide greenSide = BorderSide(color: green, width: 10.0);
148

149
    await tester.pumpWidget(buildFrame(const Border(top: greenSide)));
150 151
    expect(find.byKey(key), paints..path(color: green, style: PaintingStyle.fill));

152
    await tester.pumpWidget(buildFrame(const Border(left: greenSide)));
153 154
    expect(find.byKey(key), paints..path(color: green, style: PaintingStyle.fill));

155
    await tester.pumpWidget(buildFrame(const Border(right: greenSide)));
156 157
    expect(find.byKey(key), paints..path(color: green, style: PaintingStyle.fill));

158
    await tester.pumpWidget(buildFrame(const Border(bottom: greenSide)));
159
    expect(find.byKey(key), paints..path(color: green, style: PaintingStyle.fill));
160

161 162
    const Color blue = Color(0xFF0000FF);
    const BorderSide blueSide = BorderSide(color: blue, width: 0.0);
163

164
    await tester.pumpWidget(buildFrame(const Border(top: blueSide, right: greenSide, bottom: greenSide)));
165 166 167 168 169 170 171
    expect(
      find.byKey(key),
      paints
        ..path() // There's not much point checking the arguments to these calls because paintBorder
        ..path() // reuses the same Paint object each time, configured differently, and so they will
        ..path(), // all appear to have the same settings here (that of the last call).
    );
172 173
  });

174 175 176
  testWidgets('BoxDecoration paints its border correctly', (WidgetTester tester) async {
    // Regression test for https://github.com/flutter/flutter/issues/12165
    await tester.pumpWidget(
177
      Column(
178
        children: <Widget>[
179
          Container(
180 181 182 183 184
            // There's not currently a way to verify that this paints the same size as the others,
            // so the pattern below just asserts that there's four paths but doesn't check the geometry.
            width: 100.0,
            height: 100.0,
            decoration: const BoxDecoration(
185 186
              border: Border(
                top: BorderSide(
187
                  width: 10.0,
188
                  color: Color(0xFFEEEEEE),
189
                ),
190
                left: BorderSide(
191
                  width: 10.0,
192
                  color: Color(0xFFFFFFFF),
193
                ),
194
                right: BorderSide(
195
                  width: 10.0,
196
                  color: Color(0xFFFFFFFF),
197
                ),
198
                bottom: BorderSide(
199
                  width: 10.0,
200
                  color: Color(0xFFFFFFFF),
201 202 203 204
                ),
              ),
            ),
          ),
205
          Container(
206 207
            width: 100.0,
            height: 100.0,
208 209
            decoration: BoxDecoration(
              border: Border.all(
210 211 212 213 214
                width: 10.0,
                color: const Color(0xFFFFFFFF),
              ),
            ),
          ),
215
          Container(
216 217
            width: 100.0,
            height: 100.0,
218 219
            decoration: BoxDecoration(
              border: Border.all(
220 221 222 223
                width: 10.0,
                color: const Color(0xFFFFFFFF),
              ),
              borderRadius: const BorderRadius.only(
224
                topRight: Radius.circular(10.0),
225 226 227
              ),
            ),
          ),
228
          Container(
229 230
            width: 100.0,
            height: 100.0,
231 232
            decoration: BoxDecoration(
              border: Border.all(
233 234 235 236 237 238 239 240 241 242 243 244 245 246
                width: 10.0,
                color: const Color(0xFFFFFFFF),
              ),
              shape: BoxShape.circle,
            ),
          ),
        ],
      ),
    );
    expect(find.byType(Column), paints
      ..path()
      ..path()
      ..path()
      ..path()
Dan Field's avatar
Dan Field committed
247
      ..rect(rect: const Rect.fromLTRB(355.0, 105.0, 445.0, 195.0))
248
      ..drrect(
249
        outer: RRect.fromLTRBAndCorners(
250 251 252
          350.0, 200.0, 450.0, 300.0,
          topRight: const Radius.circular(10.0),
        ),
253
        inner: RRect.fromLTRBAndCorners(
254 255 256 257 258 259
          360.0, 210.0, 440.0, 290.0,
          topLeft: const Radius.circular(-10.0),
          bottomRight: const Radius.circular(-10.0),
          bottomLeft: const Radius.circular(-10.0),
        ),
      )
260
      ..circle(x: 400.0, y: 350.0, radius: 45.0),
261 262
    );
  });
263 264 265

  testWidgets('Can hit test on BoxDecoration', (WidgetTester tester) async {

266
    late List<int> itemsTapped;
267

268
    const Key key = Key('Container with BoxDecoration');
269 270
    Widget buildFrame(Border border) {
      itemsTapped = <int>[];
271 272
      return Center(
        child: GestureDetector(
273
          behavior: HitTestBehavior.deferToChild,
274
          child: Container(
275 276 277
            key: key,
            width: 100.0,
            height: 50.0,
278
            decoration: BoxDecoration(border: border),
279 280 281 282
          ),
          onTap: () {
            itemsTapped.add(1);
          },
283
        ),
284 285 286
      );
    }

287
    await tester.pumpWidget(buildFrame(Border.all()));
288 289 290 291 292 293 294 295 296 297
    expect(itemsTapped, isEmpty);

    await tester.tap(find.byKey(key));
    expect(itemsTapped, <int>[1]);

    await tester.tapAt(const Offset(350.0, 275.0));
    expect(itemsTapped, <int>[1,1]);

    await tester.tapAt(const Offset(449.0, 324.0));
    expect(itemsTapped, <int>[1,1,1]);
298

299 300 301 302
  });

  testWidgets('Can hit test on BoxDecoration circle', (WidgetTester tester) async {

303
    late List<int> itemsTapped;
304

305
    const Key key = Key('Container with BoxDecoration');
306 307
    Widget buildFrame(Border border) {
      itemsTapped = <int>[];
308 309
      return Center(
        child: GestureDetector(
310
            behavior: HitTestBehavior.deferToChild,
311
            child: Container(
312 313 314
            key: key,
            width: 100.0,
            height: 50.0,
315
            decoration: BoxDecoration(border: border, shape: BoxShape.circle),
316 317 318 319
          ),
          onTap: () {
            itemsTapped.add(1);
          },
320
        ),
321 322 323
      );
    }

324
    await tester.pumpWidget(buildFrame(Border.all()));
325 326
    expect(itemsTapped, isEmpty);

327
    await tester.tapAt(Offset.zero);
328 329 330 331 332 333 334 335 336 337
    expect(itemsTapped, isEmpty);

    await tester.tapAt(const Offset(350.0, 275.0));
    expect(itemsTapped, isEmpty);

    await tester.tapAt(const Offset(400.0, 300.0));
    expect(itemsTapped, <int>[1]);

    await tester.tap(find.byKey(key));
    expect(itemsTapped, <int>[1,1]);
338

339 340
  });

341
  testWidgets('Can hit test on BoxDecoration border', (WidgetTester tester) async {
342
    late List<int> itemsTapped;
343 344 345 346 347 348 349 350 351 352
    const Key key = Key('Container with BoxDecoration');
    Widget buildFrame(Border border) {
      itemsTapped = <int>[];
      return Center(
        child: GestureDetector(
          behavior: HitTestBehavior.deferToChild,
          child: Container(
            key: key,
            width: 100.0,
            height: 50.0,
353
            decoration: BoxDecoration(border: border, borderRadius: const BorderRadius.all(Radius.circular(20.0))),
354 355 356 357 358 359 360 361 362 363 364 365
          ),
          onTap: () {
            itemsTapped.add(1);
          },
        ),
      );
    }

    await tester.pumpWidget(buildFrame(Border.all()));

    expect(itemsTapped, isEmpty);

366
    await tester.tapAt(Offset.zero);
367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383
    expect(itemsTapped, isEmpty);

    await tester.tapAt(const Offset(350.0, 275.0));
    expect(itemsTapped, isEmpty);

    await tester.tapAt(const Offset(400.0, 300.0));
    expect(itemsTapped, <int>[1]);

    await tester.tap(find.byKey(key));
    expect(itemsTapped, <int>[1,1]);
  });

  testWidgets('BoxDecoration not tap outside rounded angles - Top Left', (WidgetTester tester) async {
    const double height = 50.0;
    const double width = 50.0;
    const double radius = 12.3;

384
    late List<int> itemsTapped;
385 386 387 388 389
    const Key key = Key('Container with BoxDecoration');
    Widget buildFrame(Border border) {
      itemsTapped = <int>[];
      return Align(
        alignment: Alignment.topLeft,
nt4f04uNd's avatar
nt4f04uNd committed
390
        child: GestureDetector(
391 392 393 394 395
          behavior: HitTestBehavior.deferToChild,
          child: Container(
            key: key,
            width: width,
            height: height,
396
            decoration: BoxDecoration(border: border,borderRadius: BorderRadius.circular(radius)),
397 398 399 400
          ),
          onTap: () {
            itemsTapped.add(1);
          },
401
        ),
402 403 404 405 406 407 408
      );
    }

    await tester.pumpWidget(buildFrame(Border.all()));

    expect(itemsTapped, isEmpty);
    // x, y
409
    const Offset topLeft = Offset.zero;
410 411 412 413 414 415 416 417 418 419 420 421 422
    const Offset borderTopTangent = Offset(radius-1, 0.0);
    const Offset borderLeftTangent = Offset(0.0,radius-1);
    //the borderDiagonalOffset is the backslash line
    //\\######@@@
    //#\\###@####
    //##\\@######
    //##@########
    //@##########
    //@##########
    const double borderDiagonalOffset = radius - radius * math.sqrt1_2;
    const Offset fartherBorderRadiusPoint = Offset(borderDiagonalOffset,borderDiagonalOffset);

    await tester.tapAt(topLeft);
nt4f04uNd's avatar
nt4f04uNd committed
423
    expect(itemsTapped, isEmpty, reason: 'top left tapped');
424 425

    await tester.tapAt(borderTopTangent);
nt4f04uNd's avatar
nt4f04uNd committed
426
    expect(itemsTapped, isEmpty, reason: 'border top tapped');
427 428

    await tester.tapAt(borderLeftTangent);
nt4f04uNd's avatar
nt4f04uNd committed
429
    expect(itemsTapped, isEmpty, reason: 'border left tapped');
430 431

    await tester.tapAt(fartherBorderRadiusPoint);
nt4f04uNd's avatar
nt4f04uNd committed
432
    expect(itemsTapped, isEmpty, reason: 'border center tapped');
433 434 435 436 437 438 439 440 441 442 443

    await tester.tap(find.byKey(key));
    expect(itemsTapped, <int>[1]);

  });

  testWidgets('BoxDecoration tap inside rounded angles - Top Left', (WidgetTester tester) async {
    const double height = 50.0;
    const double width = 50.0;
    const double radius = 12.3;

444
    late List<int> itemsTapped;
445 446 447 448
    const Key key = Key('Container with BoxDecoration');
    Widget buildFrame(Border border) {
      itemsTapped = <int>[];
      return Align(
449
        alignment: Alignment.topLeft,
nt4f04uNd's avatar
nt4f04uNd committed
450
        child: GestureDetector(
451 452 453 454 455
          behavior: HitTestBehavior.deferToChild,
          child: Container(
            key: key,
            width: width,
            height: height,
456
            decoration: BoxDecoration(border: border,borderRadius: BorderRadius.circular(radius)),
457 458 459 460 461
          ),
          onTap: () {
            itemsTapped.add(1);
          },
        ),
462 463 464 465 466 467 468 469 470 471 472 473 474
      );
    }

    await tester.pumpWidget(buildFrame(Border.all()));

    expect(itemsTapped, isEmpty);
    // x, y
    const Offset borderTopTangent = Offset(radius, 0.0);
    const Offset borderLeftTangent = Offset(0.0,radius);
    const double borderDiagonalOffset = radius - radius * math.sqrt1_2;
    const Offset fartherBorderRadiusPoint = Offset(borderDiagonalOffset+1,borderDiagonalOffset+1);

    await tester.tapAt(borderTopTangent);
nt4f04uNd's avatar
nt4f04uNd committed
475
    expect(itemsTapped, <int>[1], reason: 'border Top not tapped');
476 477

    await tester.tapAt(borderLeftTangent);
nt4f04uNd's avatar
nt4f04uNd committed
478
    expect(itemsTapped, <int>[1,1], reason: 'border Left not tapped');
479 480

    await tester.tapAt(fartherBorderRadiusPoint);
nt4f04uNd's avatar
nt4f04uNd committed
481
    expect(itemsTapped, <int>[1,1,1], reason: 'border center not tapped');
482 483 484 485 486 487 488 489 490 491

    await tester.tap(find.byKey(key));
    expect(itemsTapped, <int>[1,1,1,1]);
  });

  testWidgets('BoxDecoration rounded angles other corner works', (WidgetTester tester) async {
    const double height = 50.0;
    const double width = 50.0;
    const double radius = 20;

492
    late List<int> itemsTapped;
493 494 495 496
    const Key key = Key('Container with BoxDecoration');
    Widget buildFrame(Border border) {
      itemsTapped = <int>[];
      return Align(
497
        alignment: Alignment.topLeft,
nt4f04uNd's avatar
nt4f04uNd committed
498
        child: GestureDetector(
499 500 501 502 503
          behavior: HitTestBehavior.deferToChild,
          child: Container(
            key: key,
            width: width,
            height: height,
504
            decoration: BoxDecoration(border: border,borderRadius: BorderRadius.circular(radius)),
505 506 507 508 509
          ),
          onTap: () {
            itemsTapped.add(1);
          },
        ),
510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526
      );
    }

    await tester.pumpWidget(buildFrame(Border.all()));

    expect(itemsTapped, isEmpty);

    await tester.tap(find.byKey(key));
    expect(itemsTapped, <int>[1]);

    // x, y
    const Offset topRightOutside = Offset(width, 0.0);
    const Offset topRightInside = Offset(width-radius, radius);
    const Offset bottomRightOutside = Offset(width, height);
    const Offset bottomRightInside = Offset(width-radius, height-radius);
    const Offset bottomLeftOutside = Offset(0, height);
    const Offset bottomLeftInside = Offset(radius, height-radius);
527
    const Offset topLeftOutside = Offset.zero;
528 529 530
    const Offset topLeftInside = Offset(radius, radius);

    await tester.tapAt(topRightInside);
nt4f04uNd's avatar
nt4f04uNd committed
531
    expect(itemsTapped, <int>[1,1], reason: 'top right not tapped');
532 533

    await tester.tapAt(topRightOutside);
nt4f04uNd's avatar
nt4f04uNd committed
534
    expect(itemsTapped, <int>[1,1], reason: 'top right tapped');
535 536

    await tester.tapAt(bottomRightInside);
nt4f04uNd's avatar
nt4f04uNd committed
537
    expect(itemsTapped, <int>[1,1,1], reason: 'bottom right not tapped');
538 539

    await tester.tapAt(bottomRightOutside);
nt4f04uNd's avatar
nt4f04uNd committed
540
    expect(itemsTapped, <int>[1,1,1], reason: 'bottom right tapped');
541 542

    await tester.tapAt(bottomLeftInside);
nt4f04uNd's avatar
nt4f04uNd committed
543
    expect(itemsTapped, <int>[1,1,1,1], reason: 'bottom left not tapped');
544 545

    await tester.tapAt(bottomLeftOutside);
nt4f04uNd's avatar
nt4f04uNd committed
546
    expect(itemsTapped, <int>[1,1,1,1], reason: 'bottom left tapped');
547 548

    await tester.tapAt(topLeftInside);
nt4f04uNd's avatar
nt4f04uNd committed
549
    expect(itemsTapped, <int>[1,1,1,1,1], reason: 'top left not tapped');
550 551

    await tester.tapAt(topLeftOutside);
nt4f04uNd's avatar
nt4f04uNd committed
552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570
    expect(itemsTapped, <int>[1,1,1,1,1], reason: 'top left tapped');
  });

  testWidgets("BoxDecoration doesn't crash with BorderRadiusDirectional", (WidgetTester tester) async {
    // Regression test for https://github.com/flutter/flutter/issues/88039

    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: Container(
          decoration: BoxDecoration(
            border: Border.all(),
            borderRadius: const BorderRadiusDirectional.all(
              Radius.circular(1.0),
            ),
          ),
        ),
      ),
    );
571
  });
572
}