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 6
// @dart = 2.8

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

import 'package:flutter/foundation.dart';
13
import 'package:flutter/material.dart';
14
import 'package:flutter/painting.dart';
15
import 'package:flutter/widgets.dart';
16
import 'package:flutter_test/flutter_test.dart';
17

18
import '../image_data.dart';
19 20
import '../rendering/mock_canvas.dart';

21 22 23
class TestImageProvider extends ImageProvider<TestImageProvider> {
  TestImageProvider(this.future);

24
  final Future<void> future;
25 26 27 28 29

  static ui.Image image;

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

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

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

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

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

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

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

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

142
    const Color black = Color(0xFF000000);
143

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

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

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

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

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

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

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

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

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

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

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

    List<int> itemsTapped;

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

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

306 307 308 309 310 311
  });

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

    List<int> itemsTapped;

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

331
    await tester.pumpWidget(buildFrame(Border.all()));
332 333 334 335 336 337 338 339 340 341 342 343 344
    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]);
345

346 347
  });

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 389 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 449 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 497 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 560 561
  testWidgets('Can hit test on BoxDecoration border', (WidgetTester tester) async {
    List<int> itemsTapped;
    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;

    List<int> itemsTapped;
    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;

    List<int> itemsTapped;
    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;

    List<int> itemsTapped;
    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');
  });
}