decoration_test.dart 12 KB
Newer Older
1 2 3 4
// Copyright 2016 The Chromium Authors. All rights reserved.
// 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:ui' as ui show Image, ColorFilter;
7 8

import 'package:flutter/foundation.dart';
9
import 'package:flutter/painting.dart';
10
import 'package:quiver/testing/async.dart';
11
import 'package:test/test.dart';
12 13 14

import '../painting/mocks_for_image_cache.dart';
import '../rendering/rendering_tester.dart';
15 16

class TestCanvas implements Canvas {
17 18 19 20
  TestCanvas([this.invocations]);

  final List<Invocation> invocations;

21
  @override
22 23 24
  void noSuchMethod(Invocation invocation) {
    invocations?.add(invocation);
  }
25 26 27 28 29 30 31 32 33 34 35
}

class SynchronousTestImageProvider extends ImageProvider<int> {
  @override
  Future<int> obtainKey(ImageConfiguration configuration) {
    return new SynchronousFuture<int>(1);
  }

  @override
  ImageStreamCompleter load(int key) {
    return new OneFrameImageStreamCompleter(
36
      new SynchronousFuture<ImageInfo>(new TestImageInfo(key, image: new TestImage(), scale: 1.0))
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
    );
  }
}

class AsyncTestImageProvider extends ImageProvider<int> {
  @override
  Future<int> obtainKey(ImageConfiguration configuration) {
    return new Future<int>.value(2);
  }

  @override
  ImageStreamCompleter load(int key) {
    return new OneFrameImageStreamCompleter(
      new Future<ImageInfo>.value(new TestImageInfo(key))
    );
  }
}
54

55
class DelayedImageProvider extends ImageProvider<DelayedImageProvider> {
56 57 58
  final Completer<ImageInfo> _completer = new Completer<ImageInfo>();

  @override
59 60
  Future<DelayedImageProvider> obtainKey(ImageConfiguration configuration) {
    return new SynchronousFuture<DelayedImageProvider>(this);
61 62 63 64 65 66 67 68
  }

  @override
  ImageStream resolve(ImageConfiguration configuration) {
    return super.resolve(configuration);
  }

  @override
69
  ImageStreamCompleter load(DelayedImageProvider key) {
70 71 72 73 74 75 76 77
    return new OneFrameImageStreamCompleter(_completer.future);
  }

  void complete() {
    _completer.complete(new ImageInfo(image: new TestImage()));
  }

  @override
78
  String toString() => '${describeIdentity(this)}}()';
79 80 81 82 83 84 85 86 87 88 89 90 91
}

class TestImage extends ui.Image {
  @override
  int get width => 100;

  @override
  int get height => 100;

  @override
  void dispose() { }
}

92
void main() {
93 94
  new TestRenderingFlutterBinding(); // initializes the imageCache

95
  test('Decoration.lerp()', () {
96 97
    final BoxDecoration a = const BoxDecoration(color: const Color(0xFFFFFFFF));
    final BoxDecoration b = const BoxDecoration(color: const Color(0x00000000));
98 99

    BoxDecoration c = Decoration.lerp(a, b, 0.0);
100
    expect(c.color, equals(a.color));
101 102

    c = Decoration.lerp(a, b, 0.25);
103
    expect(c.color, equals(Color.lerp(const Color(0xFFFFFFFF), const Color(0x00000000), 0.25)));
104 105

    c = Decoration.lerp(a, b, 1.0);
106
    expect(c.color, equals(b.color));
107
  });
108

109
  test('BoxDecorationImageListenerSync', () {
110
    final ImageProvider imageProvider = new SynchronousTestImageProvider();
111
    final DecorationImage backgroundImage = new DecorationImage(image: imageProvider);
112

113
    final BoxDecoration boxDecoration = new BoxDecoration(image: backgroundImage);
114
    bool onChangedCalled = false;
115
    final BoxPainter boxPainter = boxDecoration.createBoxPainter(() {
116 117 118
      onChangedCalled = true;
    });

119 120
    final TestCanvas canvas = new TestCanvas();
    final ImageConfiguration imageConfiguration = const ImageConfiguration(size: Size.zero);
121 122 123 124 125 126
    boxPainter.paint(canvas, Offset.zero, imageConfiguration);

    // The onChanged callback should not be invoked during the call to boxPainter.paint
    expect(onChangedCalled, equals(false));
  });

127
  test('BoxDecorationImageListenerAsync', () {
128
    new FakeAsync().run((FakeAsync async) {
129
      final ImageProvider imageProvider = new AsyncTestImageProvider();
130
      final DecorationImage backgroundImage = new DecorationImage(image: imageProvider);
131

132
      final BoxDecoration boxDecoration = new BoxDecoration(image: backgroundImage);
133
      bool onChangedCalled = false;
134
      final BoxPainter boxPainter = boxDecoration.createBoxPainter(() {
135 136 137
        onChangedCalled = true;
      });

138 139
      final TestCanvas canvas = new TestCanvas();
      final ImageConfiguration imageConfiguration = const ImageConfiguration(size: Size.zero);
140 141 142 143 144 145 146 147
      boxPainter.paint(canvas, Offset.zero, imageConfiguration);

      // The onChanged callback should be invoked asynchronously.
      expect(onChangedCalled, equals(false));
      async.flushMicrotasks();
      expect(onChangedCalled, equals(true));
    });
  });
148 149 150

  // Regression test for https://github.com/flutter/flutter/issues/7289.
  // A reference test would be better.
151
  test('BoxDecoration backgroundImage clip', () {
152 153
    void testDecoration({ BoxShape shape: BoxShape.rectangle, BorderRadius borderRadius, bool expectClip}) {
      assert(shape != null);
154
      new FakeAsync().run((FakeAsync async) {
155 156
        final DelayedImageProvider imageProvider = new DelayedImageProvider();
        final DecorationImage backgroundImage = new DecorationImage(image: imageProvider);
157

158
        final BoxDecoration boxDecoration = new BoxDecoration(
159 160
          shape: shape,
          borderRadius: borderRadius,
161
          image: backgroundImage,
162 163
        );

164 165 166
        final List<Invocation> invocations = <Invocation>[];
        final TestCanvas canvas = new TestCanvas(invocations);
        final ImageConfiguration imageConfiguration = const ImageConfiguration(
167 168 169
            size: const Size(100.0, 100.0)
        );
        bool onChangedCalled = false;
170
        final BoxPainter boxPainter = boxDecoration.createBoxPainter(() {
171 172 173
          onChangedCalled = true;
        });

174
        // _BoxDecorationPainter._paintDecorationImage() resolves the background
175 176 177 178 179 180 181 182 183 184 185
        // image and adds a listener to the resolved image stream.
        boxPainter.paint(canvas, Offset.zero, imageConfiguration);
        imageProvider.complete();

        // Run the listener which calls onChanged() which saves an internal
        // reference to the TestImage.
        async.flushMicrotasks();
        expect(onChangedCalled, isTrue);
        boxPainter.paint(canvas, Offset.zero, imageConfiguration);

        // We expect a clip to preceed the drawImageRect call.
186
        final List<Invocation> commands = canvas.invocations.where((Invocation invocation) {
187 188 189 190 191 192 193 194 195 196 197 198 199 200
          return invocation.memberName == #clipPath || invocation.memberName == #drawImageRect;
        }).toList();
        if (expectClip) { // We expect a clip to preceed the drawImageRect call.
          expect(commands.length, 2);
          expect(commands[0].memberName, equals(#clipPath));
          expect(commands[1].memberName, equals(#drawImageRect));
        } else {
          expect(commands.length, 1);
          expect(commands[0].memberName, equals(#drawImageRect));
        }
      });
    }

    testDecoration(shape: BoxShape.circle, expectClip: true);
201
    testDecoration(borderRadius: const BorderRadius.all(const Radius.circular(16.0)), expectClip: true);
202 203
    testDecoration(expectClip: false);
  });
204 205 206 207 208 209 210

  test('DecorationImage test', () {
    final ColorFilter colorFilter = const ui.ColorFilter.mode(const Color(0xFF00FF00), BlendMode.src);
    final DecorationImage backgroundImage = new DecorationImage(
      image: new SynchronousTestImageProvider(),
      colorFilter: colorFilter,
      fit: BoxFit.contain,
211
      alignment: Alignment.bottomLeft,
212 213 214 215 216 217 218
      centerSlice: new Rect.fromLTWH(10.0, 20.0, 30.0, 40.0),
      repeat: ImageRepeat.repeatY,
    );

    final BoxDecoration boxDecoration = new BoxDecoration(image: backgroundImage);
    final BoxPainter boxPainter = boxDecoration.createBoxPainter(() { assert(false); });
    final TestCanvas canvas = new TestCanvas(<Invocation>[]);
219
    boxPainter.paint(canvas, Offset.zero, const ImageConfiguration(size: const Size(100.0, 100.0)));
220 221 222 223 224 225

    final Invocation call = canvas.invocations.singleWhere((Invocation call) => call.memberName == #drawImageNine);
    expect(call.isMethod, isTrue);
    expect(call.positionalArguments, hasLength(4));
    expect(call.positionalArguments[0], const isInstanceOf<TestImage>());
    expect(call.positionalArguments[1], new Rect.fromLTRB(10.0, 20.0, 40.0, 60.0));
226
    expect(call.positionalArguments[2], new Rect.fromLTRB(0.0, 0.0, 100.0, 100.0));
227 228 229 230 231
    expect(call.positionalArguments[3], const isInstanceOf<Paint>());
    expect(call.positionalArguments[3].isAntiAlias, false);
    expect(call.positionalArguments[3].colorFilter, colorFilter);
    expect(call.positionalArguments[3].filterQuality, FilterQuality.low);
  });
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286

  test('BoxDecoration.lerp - shapes', () {
    // We don't lerp the shape, we just switch from one to the other at t=0.5.
    // (Use a ShapeDecoration and ShapeBorder if you want to lerp the shapes...)
    expect(
      BoxDecoration.lerp(
        const BoxDecoration(shape: BoxShape.rectangle),
        const BoxDecoration(shape: BoxShape.circle),
        -1.0,
      ),
      const BoxDecoration(shape: BoxShape.rectangle)
    );
    expect(
      BoxDecoration.lerp(
        const BoxDecoration(shape: BoxShape.rectangle),
        const BoxDecoration(shape: BoxShape.circle),
        0.0,
      ),
      const BoxDecoration(shape: BoxShape.rectangle)
    );
    expect(
      BoxDecoration.lerp(
        const BoxDecoration(shape: BoxShape.rectangle),
        const BoxDecoration(shape: BoxShape.circle),
        0.25,
      ),
      const BoxDecoration(shape: BoxShape.rectangle)
    );
    expect(
      BoxDecoration.lerp(
        const BoxDecoration(shape: BoxShape.rectangle),
        const BoxDecoration(shape: BoxShape.circle),
        0.75,
      ),
      const BoxDecoration(shape: BoxShape.circle)
    );
    expect(
      BoxDecoration.lerp(
        const BoxDecoration(shape: BoxShape.rectangle),
        const BoxDecoration(shape: BoxShape.circle),
        1.0,
      ),
      const BoxDecoration(shape: BoxShape.circle)
    );
    expect(
      BoxDecoration.lerp(
        const BoxDecoration(shape: BoxShape.rectangle),
        const BoxDecoration(shape: BoxShape.circle),
        2.0,
      ),
      const BoxDecoration(shape: BoxShape.circle)
    );
  });

  test('BoxDecoration.lerp - gradients', () {
287
    final Gradient gradient = const LinearGradient(colors: const <Color>[ const Color(0x00000000), const Color(0xFFFFFFFF) ]);
288 289 290 291 292 293
    expect(
      BoxDecoration.lerp(
        const BoxDecoration(),
        new BoxDecoration(gradient: gradient),
        -1.0,
      ),
294
      const BoxDecoration(gradient: const LinearGradient(colors: const <Color>[ const Color(0x00000000), const Color(0x00FFFFFF) ]))
295 296 297 298 299 300 301 302 303 304 305 306 307 308 309
    );
    expect(
      BoxDecoration.lerp(
        const BoxDecoration(),
        new BoxDecoration(gradient: gradient),
        0.0,
      ),
      const BoxDecoration()
    );
    expect(
      BoxDecoration.lerp(
        const BoxDecoration(),
        new BoxDecoration(gradient: gradient),
        0.25,
      ),
310
      const BoxDecoration(gradient: const LinearGradient(colors: const <Color>[ const Color(0x00000000), const Color(0x40FFFFFF) ]))
311 312 313 314 315 316 317
    );
    expect(
      BoxDecoration.lerp(
        const BoxDecoration(),
        new BoxDecoration(gradient: gradient),
        0.75,
      ),
318
      const BoxDecoration(gradient: const LinearGradient(colors: const <Color>[ const Color(0x00000000), const Color(0xBFFFFFFF) ]))
319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336
    );
    expect(
      BoxDecoration.lerp(
        const BoxDecoration(),
        new BoxDecoration(gradient: gradient),
        1.0,
      ),
      new BoxDecoration(gradient: gradient)
    );
    expect(
      BoxDecoration.lerp(
        const BoxDecoration(),
        new BoxDecoration(gradient: gradient),
        2.0,
      ),
      new BoxDecoration(gradient: gradient)
    );
  });
337 338 339 340 341 342 343

  test('Decoration.lerp with unrelated decorations', () {
    expect(Decoration.lerp(new FlutterLogoDecoration(), const BoxDecoration(), 0.0), const isInstanceOf<FlutterLogoDecoration>());
    expect(Decoration.lerp(new FlutterLogoDecoration(), const BoxDecoration(), 0.25), const isInstanceOf<FlutterLogoDecoration>());
    expect(Decoration.lerp(new FlutterLogoDecoration(), const BoxDecoration(), 0.75), const isInstanceOf<BoxDecoration>());
    expect(Decoration.lerp(new FlutterLogoDecoration(), const BoxDecoration(), 1.0), const isInstanceOf<BoxDecoration>());
  });
344
}