flow_test.dart 6.45 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
Adam Barth's avatar
Adam Barth 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 'package:flutter/rendering.dart';
6 7
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
8
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
Adam Barth's avatar
Adam Barth committed
9 10

class TestFlowDelegate extends FlowDelegate {
11
  TestFlowDelegate({required this.startOffset}) : super(repaint: startOffset);
Adam Barth's avatar
Adam Barth committed
12 13 14 15 16 17 18 19 20 21 22 23

  final Animation<double> startOffset;

  @override
  BoxConstraints getConstraintsForChild(int i, BoxConstraints constraints) {
    return constraints.loosen();
  }

  @override
  void paintChildren(FlowPaintingContext context) {
    double dy = startOffset.value;
    for (int i = 0; i < context.childCount; ++i) {
24
      context.paintChild(i, transform: Matrix4.translationValues(0.0, dy, 0.0));
25
      dy += 0.75 * context.getChildSize(i)!.height;
Adam Barth's avatar
Adam Barth committed
26 27 28 29 30 31 32
    }
  }

  @override
  bool shouldRepaint(TestFlowDelegate oldDelegate) => startOffset == oldDelegate.startOffset;
}

33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
class OpacityFlowDelegate extends FlowDelegate {
  OpacityFlowDelegate(this.opacity);

  double opacity;

  @override
  void paintChildren(FlowPaintingContext context) {
    for (int i = 0; i < context.childCount; ++i) {
      context.paintChild(i, opacity: opacity);
    }
  }

  @override
  bool shouldRepaint(OpacityFlowDelegate oldDelegate) => opacity != oldDelegate.opacity;
}

49 50
// OpacityFlowDelegate that paints one of its children twice
class DuplicatePainterOpacityFlowDelegate extends OpacityFlowDelegate {
51
  DuplicatePainterOpacityFlowDelegate(super.opacity);
52 53 54 55 56 57 58 59 60 61 62 63

  @override
  void paintChildren(FlowPaintingContext context) {
    for (int i = 0; i < context.childCount; ++i) {
      context.paintChild(i, opacity: opacity);
    }
    if (context.childCount > 0) {
      context.paintChild(0, opacity: opacity);
    }
  }
}

Adam Barth's avatar
Adam Barth committed
64
void main() {
65
  testWidgetsWithLeakTracking('Flow control test', (WidgetTester tester) async {
66
    final AnimationController startOffset = AnimationController.unbounded(
67 68
      vsync: tester,
    );
69
    final List<int> log = <int>[];
Adam Barth's avatar
Adam Barth committed
70

71
    Widget buildBox(int i) {
72
      return GestureDetector(
73 74 75
        onTap: () {
          log.add(i);
        },
76
        child: Container(
77 78
          width: 100.0,
          height: 100.0,
79
          color: const Color(0xFF0000FF),
80 81
          child: Text('$i', textDirection: TextDirection.ltr),
        ),
Adam Barth's avatar
Adam Barth committed
82
      );
83 84
    }

85
    await tester.pumpWidget(
86 87
      Flow(
        delegate: TestFlowDelegate(startOffset: startOffset),
88 89 90 91 92 93 94 95
        children: <Widget>[
          buildBox(0),
          buildBox(1),
          buildBox(2),
          buildBox(3),
          buildBox(4),
          buildBox(5),
          buildBox(6),
96
        ],
97
      ),
98
    );
Adam Barth's avatar
Adam Barth committed
99

100
    await tester.tap(find.text('0'));
101
    expect(log, equals(<int>[0]));
102
    await tester.tap(find.text('1'));
103
    expect(log, equals(<int>[0, 1]));
104
    await tester.tap(find.text('2'));
105
    expect(log, equals(<int>[0, 1, 2]));
Adam Barth's avatar
Adam Barth committed
106

107
    log.clear();
108
    await tester.tapAt(const Offset(20.0, 90.0));
109
    expect(log, equals(<int>[1]));
Adam Barth's avatar
Adam Barth committed
110

111
    startOffset.value = 50.0;
112
    await tester.pump();
Adam Barth's avatar
Adam Barth committed
113

114
    log.clear();
115
    await tester.tapAt(const Offset(20.0, 90.0));
116
    expect(log, equals(<int>[0]));
Adam Barth's avatar
Adam Barth committed
117
  });
118

119
  testWidgetsWithLeakTracking('paintChild gets called twice', (WidgetTester tester) async {
120 121 122
    await tester.pumpWidget(
      Flow(
        delegate: DuplicatePainterOpacityFlowDelegate(1.0),
123 124 125
        children: const <Widget>[
          SizedBox(width: 100.0, height: 100.0),
          SizedBox(width: 100.0, height: 100.0),
126 127 128 129 130
        ],
      ),
    );
    final dynamic exception = tester.takeException();
    expect(exception, isFlutterError);
131
    final FlutterError error = exception as FlutterError;
132 133 134 135 136
    expect(error.toStringDeep(), equalsIgnoringHashCodes(
      'FlutterError\n'
      '   Cannot call paintChild twice for the same child.\n'
      '   The flow delegate of type DuplicatePainterOpacityFlowDelegate\n'
      '   attempted to paint child 0 multiple times, which is not\n'
137
      '   permitted.\n',
138 139 140
    ));
  });

141
  testWidgetsWithLeakTracking('Flow opacity layer', (WidgetTester tester) async {
142 143
    const double opacity = 0.2;
    await tester.pumpWidget(
144 145
      Flow(
        delegate: OpacityFlowDelegate(opacity),
146 147
        children: const <Widget>[
          SizedBox(width: 100.0, height: 100.0),
148
        ],
149
      ),
150
    );
151
    ContainerLayer? layer = RendererBinding.instance.renderView.debugLayer;
152
    while (layer != null && layer is! OpacityLayer) {
153
      layer = layer.firstChild as ContainerLayer?;
154
    }
Dan Field's avatar
Dan Field committed
155
    expect(layer, isA<OpacityLayer>());
156 157 158
    final OpacityLayer? opacityLayer = layer as OpacityLayer?;
    expect(opacityLayer!.alpha, equals(opacity * 255));
    expect(layer!.firstChild, isA<TransformLayer>());
159
  });
160

161
  testWidgetsWithLeakTracking('Flow can set and update clipBehavior', (WidgetTester tester) async {
162 163 164 165 166 167 168 169 170 171 172 173 174 175
    const double opacity = 0.2;
    await tester.pumpWidget(
      Flow(
        delegate: OpacityFlowDelegate(opacity),
        children: const <Widget>[
          SizedBox(width: 100.0, height: 100.0),
        ],
      ),
    );

    // By default, clipBehavior should be Clip.hardEdge
    final RenderFlow renderObject = tester.renderObject(find.byType(Flow));
    expect(renderObject.clipBehavior, equals(Clip.hardEdge));

176
    for (final Clip clip in Clip.values) {
177 178 179
      await tester.pumpWidget(
        Flow(
          delegate: OpacityFlowDelegate(opacity),
180
          clipBehavior: clip,
181 182 183 184 185 186 187 188 189
          children: const <Widget>[
            SizedBox(width: 100.0, height: 100.0),
          ],
        ),
      );
      expect(renderObject.clipBehavior, clip);
    }
  });

190
  testWidgetsWithLeakTracking('Flow.unwrapped can set and update clipBehavior', (WidgetTester tester) async {
191 192 193 194 195 196 197 198 199 200 201 202 203 204
    const double opacity = 0.2;
    await tester.pumpWidget(
      Flow.unwrapped(
        delegate: OpacityFlowDelegate(opacity),
        children: const <Widget>[
          SizedBox(width: 100.0, height: 100.0),
        ],
      ),
    );

    // By default, clipBehavior should be Clip.hardEdge
    final RenderFlow renderObject = tester.renderObject(find.byType(Flow));
    expect(renderObject.clipBehavior, equals(Clip.hardEdge));

205
    for (final Clip clip in Clip.values) {
206 207 208
      await tester.pumpWidget(
        Flow.unwrapped(
          delegate: OpacityFlowDelegate(opacity),
209
          clipBehavior: clip,
210 211 212 213 214 215 216 217
          children: const <Widget>[
            SizedBox(width: 100.0, height: 100.0),
          ],
        ),
      );
      expect(renderObject.clipBehavior, clip);
    }
  });
Adam Barth's avatar
Adam Barth committed
218
}