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 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/painting.dart';
13
import 'package:flutter/widgets.dart';
14
import 'package:flutter_test/flutter_test.dart';
15

16
import '../image_data.dart';
17 18
import '../rendering/mock_canvas.dart';

19 20 21
class TestImageProvider extends ImageProvider<TestImageProvider> {
  TestImageProvider(this.future);

22
  final Future<void> future;
23

24
  static late ui.Image image;
25 26 27

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

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

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

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

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

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

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

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

140
    const Color black = Color(0xFF000000);
141

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

146
    await tester.pumpWidget(buildFrame(Border.all(width: 0.0)));
147
    expect(find.byKey(key), paints
148
      ..rect(color: black, style: PaintingStyle.stroke, strokeWidth: 0.0));
149

150 151
    const Color green = Color(0xFF00FF00);
    const BorderSide greenSide = BorderSide(color: green, width: 10.0);
152

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

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

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

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

165 166
    const Color blue = Color(0xFF0000FF);
    const BorderSide blueSide = BorderSide(color: blue, width: 0.0);
167

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

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

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

271
    late List<int> itemsTapped;
272

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

292
    await tester.pumpWidget(buildFrame(Border.all()));
293 294 295 296 297 298 299 300 301 302
    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]);
303

304 305 306 307
  });

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

308
    late List<int> itemsTapped;
309

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

329
    await tester.pumpWidget(buildFrame(Border.all()));
330 331 332 333 334 335 336 337 338 339 340 341 342
    expect(itemsTapped, isEmpty);

    await tester.tapAt(const Offset(0.0, 0.0));
    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]);
343

344 345
  });

346
  testWidgets('Can hit test on BoxDecoration border', (WidgetTester tester) async {
347
    late List<int> itemsTapped;
348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388
    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,
            decoration: BoxDecoration(border: border, shape: BoxShape.rectangle, borderRadius: BorderRadius.circular(20.0)),
          ),
          onTap: () {
            itemsTapped.add(1);
          },
        ),
      );
    }

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

    expect(itemsTapped, isEmpty);

    await tester.tapAt(const Offset(0.0, 0.0));
    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;

389
    late List<int> itemsTapped;
390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 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
    const Key key = Key('Container with BoxDecoration');
    Widget buildFrame(Border border) {
      itemsTapped = <int>[];
      return Align(
        alignment: Alignment.topLeft,
        child:GestureDetector(
          behavior: HitTestBehavior.deferToChild,
          child: Container(
            key: key,
            width: width,
            height: height,
            decoration: BoxDecoration(border: border, shape: BoxShape.rectangle,borderRadius: BorderRadius.circular(radius))
          ),
          onTap: () {
            itemsTapped.add(1);
          },
        )
      );
    }

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

    expect(itemsTapped, isEmpty);
    // x, y
    const Offset topLeft = Offset(0.0, 0.0);
    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);
    expect(itemsTapped, isEmpty,reason: 'top left tapped');

    await tester.tapAt(borderTopTangent);
    expect(itemsTapped, isEmpty,reason: 'border top tapped');

    await tester.tapAt(borderLeftTangent);
    expect(itemsTapped, isEmpty,reason: 'border left tapped');

    await tester.tapAt(fartherBorderRadiusPoint);
    expect(itemsTapped, isEmpty,reason: 'border center tapped');

    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;

449
    late List<int> itemsTapped;
450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496
    const Key key = Key('Container with BoxDecoration');
    Widget buildFrame(Border border) {
      itemsTapped = <int>[];
      return Align(
          alignment: Alignment.topLeft,
          child:GestureDetector(
            behavior: HitTestBehavior.deferToChild,
            child: Container(
                key: key,
                width: width,
                height: height,
                decoration: BoxDecoration(border: border, shape: BoxShape.rectangle,borderRadius: BorderRadius.circular(radius))
            ),
            onTap: () {
              itemsTapped.add(1);
            },
          )
      );
    }

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

    await tester.tapAt(borderLeftTangent);
    expect(itemsTapped, <int>[1,1],reason: 'border Left not tapped');

    await tester.tapAt(fartherBorderRadiusPoint);
    expect(itemsTapped, <int>[1,1,1],reason: 'border center not tapped');

    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;

497
    late List<int> itemsTapped;
498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559
    const Key key = Key('Container with BoxDecoration');
    Widget buildFrame(Border border) {
      itemsTapped = <int>[];
      return Align(
          alignment: Alignment.topLeft,
          child:GestureDetector(
            behavior: HitTestBehavior.deferToChild,
            child: Container(
                key: key,
                width: width,
                height: height,
                decoration: BoxDecoration(border: border, shape: BoxShape.rectangle,borderRadius: BorderRadius.circular(radius))
            ),
            onTap: () {
              itemsTapped.add(1);
            },
          )
      );
    }

    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);
    const Offset topLeftOutside = Offset(0, 0);
    const Offset topLeftInside = Offset(radius, radius);

    await tester.tapAt(topRightInside);
    expect(itemsTapped, <int>[1,1],reason: 'top right not tapped');

    await tester.tapAt(topRightOutside);
    expect(itemsTapped, <int>[1,1],reason: 'top right tapped');

    await tester.tapAt(bottomRightInside);
    expect(itemsTapped, <int>[1,1,1],reason: 'bottom right not tapped');

    await tester.tapAt(bottomRightOutside);
    expect(itemsTapped, <int>[1,1,1],reason: 'bottom right tapped');

    await tester.tapAt(bottomLeftInside);
    expect(itemsTapped, <int>[1,1,1,1],reason: 'bottom left not tapped');

    await tester.tapAt(bottomLeftOutside);
    expect(itemsTapped, <int>[1,1,1,1],reason: 'bottom left tapped');

    await tester.tapAt(topLeftInside);
    expect(itemsTapped, <int>[1,1,1,1,1],reason: 'top left not tapped');

    await tester.tapAt(topLeftOutside);
    expect(itemsTapped, <int>[1,1,1,1,1],reason: 'top left tapped');
  });
}