box_decoration_test.dart 17.6 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
import 'dart:ui' as ui show Image;

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

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

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

19
  final Future<void> future;
20

21
  static late ui.Image image;
22 23 24

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

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

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

  testWidgets('DecoratedBox handles loading images', (WidgetTester tester) async {
41
    final GlobalKey key = GlobalKey();
42
    final Completer<void> completer = Completer<void>();
43
    await tester.pumpWidget(
44
      KeyedSubtree(
45
        key: key,
46 47 48 49
        child: DecoratedBox(
          decoration: BoxDecoration(
            image: DecorationImage(
              image: TestImageProvider(completer.future),
50 51 52 53 54 55 56 57 58 59 60 61 62 63
            ),
          ),
        ),
      ),
    );
    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 {
64
    final Completer<void> completer = Completer<void>();
65 66 67 68 69 70 71
    final Widget subtree = KeyedSubtree(
      key: GlobalKey(),
      child: RepaintBoundary(
        child: DecoratedBox(
          decoration: BoxDecoration(
            image: DecorationImage(
              image: TestImageProvider(completer.future),
72 73 74 75 76 77 78 79
            ),
          ),
        ),
      ),
    );
    await tester.pumpWidget(subtree);
    await tester.idle();
    expect(tester.binding.hasScheduledFrame, isFalse);
80
    await tester.pumpWidget(Container(child: subtree));
81 82 83 84 85 86 87 88 89 90 91
    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);
  });

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

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

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

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

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

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

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

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

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

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

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

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

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

163
    await tester.pumpWidget(buildFrame(const Border(top: blueSide, right: greenSide, bottom: greenSide)));
164 165 166 167 168 169 170
    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).
    );
171 172
  });

173 174 175
  testWidgets('BoxDecoration paints its border correctly', (WidgetTester tester) async {
    // Regression test for https://github.com/flutter/flutter/issues/12165
    await tester.pumpWidget(
176
      Column(
177
        children: <Widget>[
178
          Container(
179 180 181 182 183
            // 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(
184 185
              border: Border(
                top: BorderSide(
186
                  width: 10.0,
187
                  color: Color(0xFFEEEEEE),
188
                ),
189
                left: BorderSide(
190
                  width: 10.0,
191
                  color: Color(0xFFFFFFFF),
192
                ),
193
                right: BorderSide(
194
                  width: 10.0,
195
                  color: Color(0xFFFFFFFF),
196
                ),
197
                bottom: BorderSide(
198
                  width: 10.0,
199
                  color: Color(0xFFFFFFFF),
200 201 202 203
                ),
              ),
            ),
          ),
204
          Container(
205 206
            width: 100.0,
            height: 100.0,
207 208
            decoration: BoxDecoration(
              border: Border.all(
209 210 211 212 213
                width: 10.0,
                color: const Color(0xFFFFFFFF),
              ),
            ),
          ),
214
          Container(
215 216
            width: 100.0,
            height: 100.0,
217 218
            decoration: BoxDecoration(
              border: Border.all(
219 220 221 222
                width: 10.0,
                color: const Color(0xFFFFFFFF),
              ),
              borderRadius: const BorderRadius.only(
223
                topRight: Radius.circular(10.0),
224 225 226
              ),
            ),
          ),
227
          Container(
228 229
            width: 100.0,
            height: 100.0,
230 231
            decoration: BoxDecoration(
              border: Border.all(
232 233 234 235 236 237 238 239 240 241 242 243 244 245
                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
246
      ..rect(rect: const Rect.fromLTRB(355.0, 105.0, 445.0, 195.0))
247
      ..drrect(
248
        outer: RRect.fromLTRBAndCorners(
249 250 251
          350.0, 200.0, 450.0, 300.0,
          topRight: const Radius.circular(10.0),
        ),
252
        inner: RRect.fromLTRBAndCorners(360.0, 210.0, 440.0, 290.0),
253
      )
254
      ..circle(x: 400.0, y: 350.0, radius: 45.0),
255 256
    );
  });
257 258 259

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

260
    late List<int> itemsTapped;
261

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

281
    await tester.pumpWidget(buildFrame(Border.all()));
282 283 284 285 286 287 288 289 290 291
    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]);
292

293 294 295 296
  });

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

297
    late List<int> itemsTapped;
298

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

318
    await tester.pumpWidget(buildFrame(Border.all()));
319 320
    expect(itemsTapped, isEmpty);

321
    await tester.tapAt(Offset.zero);
322 323 324 325 326 327 328 329 330 331
    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]);
332

333 334
  });

335
  testWidgets('Can hit test on BoxDecoration border', (WidgetTester tester) async {
336
    late List<int> itemsTapped;
337 338 339 340 341 342 343 344 345 346
    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,
347
            decoration: BoxDecoration(border: border, borderRadius: const BorderRadius.all(Radius.circular(20.0))),
348 349 350 351 352 353 354 355 356 357 358 359
          ),
          onTap: () {
            itemsTapped.add(1);
          },
        ),
      );
    }

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

    expect(itemsTapped, isEmpty);

360
    await tester.tapAt(Offset.zero);
361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377
    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;

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

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

    expect(itemsTapped, isEmpty);
    // x, y
403
    const Offset topLeft = Offset.zero;
404 405 406 407 408 409 410 411 412 413 414 415 416
    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
417
    expect(itemsTapped, isEmpty, reason: 'top left tapped');
418 419

    await tester.tapAt(borderTopTangent);
nt4f04uNd's avatar
nt4f04uNd committed
420
    expect(itemsTapped, isEmpty, reason: 'border top tapped');
421 422

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

    await tester.tapAt(fartherBorderRadiusPoint);
nt4f04uNd's avatar
nt4f04uNd committed
426
    expect(itemsTapped, isEmpty, reason: 'border center tapped');
427 428 429 430 431 432 433 434 435 436 437

    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;

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

    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
469
    expect(itemsTapped, <int>[1], reason: 'border Top not tapped');
470 471

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

    await tester.tapAt(fartherBorderRadiusPoint);
nt4f04uNd's avatar
nt4f04uNd committed
475
    expect(itemsTapped, <int>[1,1,1], reason: 'border center not tapped');
476 477 478 479 480 481 482 483 484 485

    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;

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

    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);
521
    const Offset topLeftOutside = Offset.zero;
522 523 524
    const Offset topLeftInside = Offset(radius, radius);

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

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

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

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

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

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

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

    await tester.tapAt(topLeftOutside);
nt4f04uNd's avatar
nt4f04uNd committed
546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564
    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),
            ),
          ),
        ),
      ),
    );
565
  });
566
}