render_object_widget_test.dart 9.03 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
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
7
import 'package:flutter_test/flutter_test.dart';
Adam Barth's avatar
Adam Barth committed
8

9 10
import '../rendering/recording_canvas.dart';

11 12 13
final BoxDecoration kBoxDecorationA = BoxDecoration(border: nonconst(null));
final BoxDecoration kBoxDecorationB = BoxDecoration(border: nonconst(null));
final BoxDecoration kBoxDecorationC = BoxDecoration(border: nonconst(null));
Adam Barth's avatar
Adam Barth committed
14

15
class TestWidget extends StatelessWidget {
16
  const TestWidget({
17
    super.key,
18
    required this.child,
19
  });
20

21
  final Widget child;
22 23

  @override
24
  Widget build(BuildContext context) => child;
25 26
}

27
class TestOrientedBox extends SingleChildRenderObjectWidget {
28
  const TestOrientedBox({ super.key, super.child });
29 30

  Decoration _getDecoration(BuildContext context) {
31
    final Orientation orientation = MediaQuery.orientationOf(context);
pq's avatar
pq committed
32
    switch (orientation) {
33
      case Orientation.landscape:
34
        return const BoxDecoration(color: Color(0xFF00FF00));
35
      case Orientation.portrait:
36
        return const BoxDecoration(color: Color(0xFF0000FF));
37 38 39 40
    }
  }

  @override
41
  RenderDecoratedBox createRenderObject(BuildContext context) => RenderDecoratedBox(decoration: _getDecoration(context));
42 43 44 45 46 47 48

  @override
  void updateRenderObject(BuildContext context, RenderDecoratedBox renderObject) {
    renderObject.decoration = _getDecoration(context);
  }
}

49
class TestNonVisitingWidget extends SingleChildRenderObjectWidget {
50
  const TestNonVisitingWidget({ super.key, required Widget super.child });
51 52 53 54 55 56

  @override
  RenderObject createRenderObject(BuildContext context) => TestNonVisitingRenderObject();
}

class TestNonVisitingRenderObject extends RenderBox with RenderObjectWithChildMixin<RenderBox> {
57 58 59 60 61
  @override
  Size computeDryLayout(BoxConstraints constraints) {
    return child!.getDryLayout(constraints);
  }

62 63
  @override
  void performLayout() {
64 65
    child!.layout(constraints, parentUsesSize: true);
    size = child!.size;
66 67 68 69
  }

  @override
  void paint(PaintingContext context, Offset offset) {
70
    context.paintChild(child!, offset);
71 72 73 74 75 76 77 78
  }

  @override
  void visitChildren(RenderObjectVisitor visitor) {
    // oops!
  }
}

Adam Barth's avatar
Adam Barth committed
79
void main() {
80
  testWidgets('RenderObjectWidget smoke test', (WidgetTester tester) async {
81
    await tester.pumpWidget(DecoratedBox(decoration: kBoxDecorationA));
82 83 84
    SingleChildRenderObjectElement element =
        tester.element(find.byElementType(SingleChildRenderObjectElement));
    expect(element, isNotNull);
Dan Field's avatar
Dan Field committed
85
    expect(element.renderObject, isA<RenderDecoratedBox>());
86
    RenderDecoratedBox renderObject = element.renderObject as RenderDecoratedBox;
87 88 89
    expect(renderObject.decoration, equals(kBoxDecorationA));
    expect(renderObject.position, equals(DecorationPosition.background));

90
    await tester.pumpWidget(DecoratedBox(decoration: kBoxDecorationB));
91 92
    element = tester.element(find.byElementType(SingleChildRenderObjectElement));
    expect(element, isNotNull);
Dan Field's avatar
Dan Field committed
93
    expect(element.renderObject, isA<RenderDecoratedBox>());
94
    renderObject = element.renderObject as RenderDecoratedBox;
95 96 97 98
    expect(renderObject.decoration, equals(kBoxDecorationB));
    expect(renderObject.position, equals(DecorationPosition.background));
  });

99
  testWidgets('RenderObjectWidget can add and remove children', (WidgetTester tester) async {
100 101

    void checkFullTree() {
102
      final SingleChildRenderObjectElement element =
103
          tester.firstElement(find.byElementType(SingleChildRenderObjectElement));
Adam Barth's avatar
Adam Barth committed
104
      expect(element, isNotNull);
Dan Field's avatar
Dan Field committed
105
      expect(element.renderObject, isA<RenderDecoratedBox>());
106
      final RenderDecoratedBox renderObject = element.renderObject as RenderDecoratedBox;
Adam Barth's avatar
Adam Barth committed
107
      expect(renderObject.decoration, equals(kBoxDecorationA));
108
      expect(renderObject.position, equals(DecorationPosition.background));
109
      expect(renderObject.child, isNotNull);
Dan Field's avatar
Dan Field committed
110
      expect(renderObject.child, isA<RenderDecoratedBox>());
111
      final RenderDecoratedBox child = renderObject.child! as RenderDecoratedBox;
112 113 114 115
      expect(child.decoration, equals(kBoxDecorationB));
      expect(child.position, equals(DecorationPosition.background));
      expect(child.child, isNull);
    }
Adam Barth's avatar
Adam Barth committed
116

117
    void childBareTree() {
118
      final SingleChildRenderObjectElement element =
119
          tester.element(find.byElementType(SingleChildRenderObjectElement));
Adam Barth's avatar
Adam Barth committed
120
      expect(element, isNotNull);
Dan Field's avatar
Dan Field committed
121
      expect(element.renderObject, isA<RenderDecoratedBox>());
122
      final RenderDecoratedBox renderObject = element.renderObject as RenderDecoratedBox;
123
      expect(renderObject.decoration, equals(kBoxDecorationA));
124
      expect(renderObject.position, equals(DecorationPosition.background));
125 126
      expect(renderObject.child, isNull);
    }
Adam Barth's avatar
Adam Barth committed
127

128
    await tester.pumpWidget(DecoratedBox(
129
      decoration: kBoxDecorationA,
130
      child: DecoratedBox(
131
        decoration: kBoxDecorationB,
132
      ),
133
    ));
134

135
    checkFullTree();
136

137
    await tester.pumpWidget(DecoratedBox(
138
      decoration: kBoxDecorationA,
139 140
      child: TestWidget(
        child: DecoratedBox(
141
          decoration: kBoxDecorationB,
142 143
        ),
      ),
144 145 146
    ));

    checkFullTree();
Adam Barth's avatar
Adam Barth committed
147

148
    await tester.pumpWidget(DecoratedBox(
149
      decoration: kBoxDecorationA,
150
      child: DecoratedBox(
151
        decoration: kBoxDecorationB,
152
      ),
153
    ));
Adam Barth's avatar
Adam Barth committed
154

155
    checkFullTree();
Adam Barth's avatar
Adam Barth committed
156

157
    await tester.pumpWidget(DecoratedBox(
158
      decoration: kBoxDecorationA,
159
    ));
Adam Barth's avatar
Adam Barth committed
160

161 162
    childBareTree();

163
    await tester.pumpWidget(DecoratedBox(
164
      decoration: kBoxDecorationA,
165 166 167
      child: TestWidget(
        child: TestWidget(
          child: DecoratedBox(
168
            decoration: kBoxDecorationB,
169 170 171
          ),
        ),
      ),
172
    ));
Adam Barth's avatar
Adam Barth committed
173

174
    checkFullTree();
Adam Barth's avatar
Adam Barth committed
175

176
    await tester.pumpWidget(DecoratedBox(
177
      decoration: kBoxDecorationA,
178
    ));
Adam Barth's avatar
Adam Barth committed
179

180
    childBareTree();
Adam Barth's avatar
Adam Barth committed
181 182
  });

183
  testWidgets('Detached render tree is intact', (WidgetTester tester) async {
Adam Barth's avatar
Adam Barth committed
184

185
    await tester.pumpWidget(DecoratedBox(
186
      decoration: kBoxDecorationA,
187
      child: DecoratedBox(
188
        decoration: kBoxDecorationB,
189
        child: DecoratedBox(
190
          decoration: kBoxDecorationC,
191 192
        ),
      ),
193 194 195 196
    ));

    SingleChildRenderObjectElement element =
        tester.firstElement(find.byElementType(SingleChildRenderObjectElement));
Dan Field's avatar
Dan Field committed
197
    expect(element.renderObject, isA<RenderDecoratedBox>());
198
    final RenderDecoratedBox parent = element.renderObject as RenderDecoratedBox;
Dan Field's avatar
Dan Field committed
199
    expect(parent.child, isA<RenderDecoratedBox>());
200
    final RenderDecoratedBox child = parent.child! as RenderDecoratedBox;
201
    expect(child.decoration, equals(kBoxDecorationB));
Dan Field's avatar
Dan Field committed
202
    expect(child.child, isA<RenderDecoratedBox>());
203
    final RenderDecoratedBox grandChild = child.child! as RenderDecoratedBox;
204 205 206
    expect(grandChild.decoration, equals(kBoxDecorationC));
    expect(grandChild.child, isNull);

207
    await tester.pumpWidget(DecoratedBox(
208
      decoration: kBoxDecorationA,
209 210 211 212
    ));

    element =
        tester.element(find.byElementType(SingleChildRenderObjectElement));
Dan Field's avatar
Dan Field committed
213
    expect(element.renderObject, isA<RenderDecoratedBox>());
214 215 216 217 218 219 220 221 222
    expect(element.renderObject, equals(parent));
    expect(parent.child, isNull);

    expect(child.parent, isNull);
    expect(child.decoration, equals(kBoxDecorationB));
    expect(child.child, equals(grandChild));
    expect(grandChild.parent, equals(child));
    expect(grandChild.decoration, equals(kBoxDecorationC));
    expect(grandChild.child, isNull);
Adam Barth's avatar
Adam Barth committed
223
  });
224

225
  testWidgets('Can watch inherited widgets', (WidgetTester tester) async {
226 227
    final Key boxKey = UniqueKey();
    final TestOrientedBox box = TestOrientedBox(key: boxKey);
228

229
    await tester.pumpWidget(MediaQuery(
230
      data: const MediaQueryData(size: Size(400.0, 300.0)),
231
      child: box,
232
    ));
233

234
    final RenderDecoratedBox renderBox = tester.renderObject(find.byKey(boxKey));
235
    BoxDecoration decoration = renderBox.decoration as BoxDecoration;
236
    expect(decoration.color, equals(const Color(0xFF00FF00)));
237

238
    await tester.pumpWidget(MediaQuery(
239
      data: const MediaQueryData(size: Size(300.0, 400.0)),
240
      child: box,
241
    ));
242

243
    decoration = renderBox.decoration as BoxDecoration;
244
    expect(decoration.color, equals(const Color(0xFF0000FF)));
245
  });
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266

  testWidgets('RenderObject not visiting children provides helpful error message', (WidgetTester tester) async {
    await tester.pumpWidget(
      TestNonVisitingWidget(
        child: Container(color: const Color(0xFFED1D7F)),
      ),
    );

    final RenderObject renderObject = tester.renderObject(find.byType(TestNonVisitingWidget));
    final Canvas testCanvas = TestRecordingCanvas();
    final PaintingContext testContext = TestRecordingPaintingContext(testCanvas);

    // When a parent fails to visit a child in visitChildren, the child's compositing
    // bits won't be cleared properly, leading to an exception during paint.
    renderObject.paint(testContext, Offset.zero);

    final dynamic error = tester.takeException();
    expect(error, isNotNull, reason: 'RenderObject did not throw when painting');
    expect(error, isFlutterError);
    expect(error.toString(), contains("A RenderObject was not visited by the parent's visitChildren"));
  });
Adam Barth's avatar
Adam Barth committed
267
}