parent_data_test.dart 11.1 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.

Adam Barth's avatar
Adam Barth committed
5
import 'package:flutter_test/flutter_test.dart';
6 7
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
Adam Barth's avatar
Adam Barth committed
8 9 10 11 12 13 14 15 16 17 18 19 20

import 'test_widgets.dart';

class TestParentData {
  TestParentData({ this.top, this.right, this.bottom, this.left });

  final double top;
  final double right;
  final double bottom;
  final double left;
}

void checkTree(WidgetTester tester, List<TestParentData> expectedParentData) {
21
  final MultiChildRenderObjectElement element = tester.element(
22 23
    find.byElementPredicate((Element element) => element is MultiChildRenderObjectElement)
  );
Adam Barth's avatar
Adam Barth committed
24 25
  expect(element, isNotNull);
  expect(element.renderObject is RenderStack, isTrue);
26
  final RenderStack renderObject = element.renderObject as RenderStack;
Adam Barth's avatar
Adam Barth committed
27 28 29 30
  try {
    RenderObject child = renderObject.firstChild;
    for (TestParentData expected in expectedParentData) {
      expect(child is RenderDecoratedBox, isTrue);
31
      final RenderDecoratedBox decoratedBox = child as RenderDecoratedBox;
Adam Barth's avatar
Adam Barth committed
32
      expect(decoratedBox.parentData is StackParentData, isTrue);
33
      final StackParentData parentData = decoratedBox.parentData as StackParentData;
Adam Barth's avatar
Adam Barth committed
34 35 36 37
      expect(parentData.top, equals(expected.top));
      expect(parentData.right, equals(expected.right));
      expect(parentData.bottom, equals(expected.bottom));
      expect(parentData.left, equals(expected.left));
38
      final StackParentData decoratedBoxParentData = decoratedBox.parentData as StackParentData;
Hixie's avatar
Hixie committed
39
      child = decoratedBoxParentData.nextSibling;
Adam Barth's avatar
Adam Barth committed
40 41 42 43 44 45 46 47
    }
    expect(child, isNull);
  } catch (e) {
    print(renderObject.toStringDeep());
    rethrow;
  }
}

48
final TestParentData kNonPositioned = TestParentData();
Adam Barth's avatar
Adam Barth committed
49 50

void main() {
51
  testWidgets('ParentDataWidget control test', (WidgetTester tester) async {
52

53
    await tester.pumpWidget(
54
      Stack(
55
        textDirection: TextDirection.ltr,
56
        children: const <Widget>[
57 58
          DecoratedBox(decoration: kBoxDecorationA),
          Positioned(
59 60
            top: 10.0,
            left: 10.0,
61
            child: DecoratedBox(decoration: kBoxDecorationB),
62
          ),
63
          DecoratedBox(decoration: kBoxDecorationC),
64 65
        ],
      ),
66 67 68 69
    );

    checkTree(tester, <TestParentData>[
      kNonPositioned,
70
      TestParentData(top: 10.0, left: 10.0),
71 72 73
      kNonPositioned,
    ]);

74
    await tester.pumpWidget(
75
      Stack(
76
        textDirection: TextDirection.ltr,
77
        children: const <Widget>[
78
          Positioned(
79 80
            bottom: 5.0,
            right: 7.0,
81
            child: DecoratedBox(decoration: kBoxDecorationA),
82
          ),
83
          Positioned(
84 85
            top: 10.0,
            left: 10.0,
86
            child: DecoratedBox(decoration: kBoxDecorationB),
87
          ),
88
          DecoratedBox(decoration: kBoxDecorationC),
89 90
        ],
      ),
91 92 93
    );

    checkTree(tester, <TestParentData>[
94 95
      TestParentData(bottom: 5.0, right: 7.0),
      TestParentData(top: 10.0, left: 10.0),
96 97 98
      kNonPositioned,
    ]);

99 100 101
    const DecoratedBox kDecoratedBoxA = DecoratedBox(decoration: kBoxDecorationA);
    const DecoratedBox kDecoratedBoxB = DecoratedBox(decoration: kBoxDecorationB);
    const DecoratedBox kDecoratedBoxC = DecoratedBox(decoration: kBoxDecorationC);
102

103
    await tester.pumpWidget(
104
      Stack(
105
        textDirection: TextDirection.ltr,
106
        children: const <Widget>[
107
          Positioned(
108 109
            bottom: 5.0,
            right: 7.0,
110
            child: kDecoratedBoxA,
111
          ),
112
          Positioned(
113 114
            top: 10.0,
            left: 10.0,
115
            child: kDecoratedBoxB,
116 117
          ),
          kDecoratedBoxC,
118 119
        ],
      ),
120 121 122
    );

    checkTree(tester, <TestParentData>[
123 124
      TestParentData(bottom: 5.0, right: 7.0),
      TestParentData(top: 10.0, left: 10.0),
125 126 127
      kNonPositioned,
    ]);

128
    await tester.pumpWidget(
129
      Stack(
130
        textDirection: TextDirection.ltr,
131
        children: const <Widget>[
132
          Positioned(
133 134
            bottom: 6.0,
            right: 8.0,
135
            child: kDecoratedBoxA,
136
          ),
137
          Positioned(
138 139
            left: 10.0,
            right: 10.0,
140
            child: kDecoratedBoxB,
141 142
          ),
          kDecoratedBoxC,
143 144
        ],
      ),
145 146 147
    );

    checkTree(tester, <TestParentData>[
148 149
      TestParentData(bottom: 6.0, right: 8.0),
      TestParentData(left: 10.0, right: 10.0),
150 151 152
      kNonPositioned,
    ]);

153
    await tester.pumpWidget(
154
      Stack(
155
        textDirection: TextDirection.ltr,
156 157
        children: <Widget>[
          kDecoratedBoxA,
158
          Positioned(
159 160
            left: 11.0,
            right: 12.0,
161
            child: Container(child: kDecoratedBoxB),
162 163
          ),
          kDecoratedBoxC,
164 165
        ],
      ),
166 167 168 169
    );

    checkTree(tester, <TestParentData>[
      kNonPositioned,
170
      TestParentData(left: 11.0, right: 12.0),
171 172 173
      kNonPositioned,
    ]);

174
    await tester.pumpWidget(
175
      Stack(
176
        textDirection: TextDirection.ltr,
177 178
        children: <Widget>[
          kDecoratedBoxA,
179
          Positioned(
180
            right: 10.0,
181
            child: Container(child: kDecoratedBoxB),
182
          ),
183
          Container(
184
            child: const Positioned(
185
              top: 8.0,
186 187 188 189 190
              child: kDecoratedBoxC,
            ),
          ),
        ],
      ),
191 192 193 194
    );

    checkTree(tester, <TestParentData>[
      kNonPositioned,
195 196
      TestParentData(right: 10.0),
      TestParentData(top: 8.0),
197 198
    ]);

199
    await tester.pumpWidget(
200
      Stack(
201
        textDirection: TextDirection.ltr,
202
        children: const <Widget>[
203
          Positioned(
204
            right: 10.0,
205
            child: FlipWidget(left: kDecoratedBoxA, right: kDecoratedBoxB),
206
          ),
207 208
        ],
      ),
209 210 211
    );

    checkTree(tester, <TestParentData>[
212
      TestParentData(right: 10.0),
213 214 215
    ]);

    flipStatefulWidget(tester);
216
    await tester.pump();
217 218

    checkTree(tester, <TestParentData>[
219
      TestParentData(right: 10.0),
220 221
    ]);

222
    await tester.pumpWidget(
223
      Stack(
224
        textDirection: TextDirection.ltr,
225
        children: const <Widget>[
226
          Positioned(
227
            top: 7.0,
228
            child: FlipWidget(left: kDecoratedBoxA, right: kDecoratedBoxB),
229
          ),
230 231
        ],
      ),
232 233 234
    );

    checkTree(tester, <TestParentData>[
235
      TestParentData(top: 7.0),
236 237 238
    ]);

    flipStatefulWidget(tester);
239
    await tester.pump();
240 241

    checkTree(tester, <TestParentData>[
242
      TestParentData(top: 7.0),
243 244
    ]);

245
    await tester.pumpWidget(
246
      Stack(textDirection: TextDirection.ltr)
247 248 249
    );

    checkTree(tester, <TestParentData>[]);
Adam Barth's avatar
Adam Barth committed
250 251
  });

252 253
  testWidgets('ParentDataWidget conflicting data', (WidgetTester tester) async {
    await tester.pumpWidget(
254
      Stack(
255
        textDirection: TextDirection.ltr,
256
        children: const <Widget>[
257
          Positioned(
258 259
            top: 5.0,
            bottom: 8.0,
260
            child: Positioned(
261 262
              top: 6.0,
              left: 7.0,
263
              child: DecoratedBox(decoration: kBoxDecorationB),
264 265 266 267
            ),
          ),
        ],
      ),
268
    );
269 270 271 272 273 274 275 276 277 278 279 280 281 282
    dynamic exception = tester.takeException();
    expect(exception, isFlutterError);
    expect(
      exception.toString(),
      equalsIgnoringHashCodes(
        'Incorrect use of ParentDataWidget.\n'
        'Positioned widgets must be placed directly inside Stack widgets.\n'
        'Positioned(no depth, left: 7.0, top: 6.0, dirty) has a Stack ancestor, but there are other widgets between them:\n'
        '- Positioned(top: 5.0, bottom: 8.0) (this is a different Positioned than the one with the problem)\n'
        'These widgets cannot come between a Positioned and its Stack.\n'
        'The ownership chain for the parent of the offending Positioned was:\n'
        '  Positioned ← Stack ← [root]'
      ),
    );
283

284
    await tester.pumpWidget(Stack(textDirection: TextDirection.ltr));
285

286
    checkTree(tester, <TestParentData>[]);
287

288
    await tester.pumpWidget(
289 290
      Container(
        child: Row(
291
          children: const <Widget>[
292
            Positioned(
293 294
              top: 6.0,
              left: 7.0,
295
              child: DecoratedBox(decoration: kBoxDecorationB),
296 297 298 299
            ),
          ],
        ),
      ),
300
    );
301 302 303 304 305 306 307 308 309 310
    exception = tester.takeException();
    expect(exception, isFlutterError);
    expect(
      exception.toString(),
      equalsIgnoringHashCodes(
        'Incorrect use of ParentDataWidget.\n'
        'Positioned widgets must be placed inside Stack widgets.\n'
        'Positioned(no depth, left: 7.0, top: 6.0, dirty) has no Stack ancestor at all.\n'
        'The ownership chain for the parent of the offending Positioned was:\n'
        '  Row ← Container ← [root]'
311
      ),
312
    );
313

314
    await tester.pumpWidget(
315
      Stack(textDirection: TextDirection.ltr)
316
    );
317

318 319
    checkTree(tester, <TestParentData>[]);
  });
320

321
  testWidgets('ParentDataWidget interacts with global keys', (WidgetTester tester) async {
322
    final GlobalKey key = GlobalKey();
323

324
    await tester.pumpWidget(
325
      Stack(
326
        textDirection: TextDirection.ltr,
327
        children: <Widget>[
328
          Positioned(
329 330
            top: 10.0,
            left: 10.0,
331
            child: DecoratedBox(key: key, decoration: kBoxDecorationA),
332 333 334
          ),
        ],
      ),
335 336 337
    );

    checkTree(tester, <TestParentData>[
338
      TestParentData(top: 10.0, left: 10.0),
339 340
    ]);

341
    await tester.pumpWidget(
342
      Stack(
343
        textDirection: TextDirection.ltr,
344
        children: <Widget>[
345
          Positioned(
346 347
            top: 10.0,
            left: 10.0,
348
            child: DecoratedBox(
349
              decoration: kBoxDecorationB,
350
              child: DecoratedBox(key: key, decoration: kBoxDecorationA),
351 352 353 354
            ),
          ),
        ],
      ),
355 356 357
    );

    checkTree(tester, <TestParentData>[
358
      TestParentData(top: 10.0, left: 10.0),
359 360
    ]);

361
    await tester.pumpWidget(
362
      Stack(
363
        textDirection: TextDirection.ltr,
364
        children: <Widget>[
365
          Positioned(
366 367
            top: 10.0,
            left: 10.0,
368
            child: DecoratedBox(key: key, decoration: kBoxDecorationA),
369 370 371
          ),
        ],
      ),
372
    );
373

374
    checkTree(tester, <TestParentData>[
375
      TestParentData(top: 10.0, left: 10.0),
376
    ]);
377
  });
378 379

  testWidgets('Parent data invalid ancestor', (WidgetTester tester) async {
380
    await tester.pumpWidget(Row(
381
      children: <Widget>[
382
        Stack(
383
          textDirection: TextDirection.ltr,
384
          children: <Widget>[
385
            Expanded(
386
              child: Container(),
387 388 389 390 391 392
            ),
          ],
        ),
      ],
    ));

393 394 395 396 397 398 399 400 401 402 403 404 405 406
    final dynamic exception = tester.takeException();
    expect(exception, isFlutterError);
    expect(
      exception.toString(),
      equalsIgnoringHashCodes(
        'Incorrect use of ParentDataWidget.\n'
        'Expanded widgets must be placed directly inside Flex widgets.\n'
        'Expanded(no depth, flex: 1, dirty) has a Flex ancestor, but there are other widgets between them:\n'
        '- Stack(alignment: AlignmentDirectional.topStart, textDirection: ltr, fit: loose, overflow: clip)\n'
        'These widgets cannot come between a Expanded and its Flex.\n'
        'The ownership chain for the parent of the offending Expanded was:\n'
        '  Stack ← Row ← [root]'
      ),
    );
407
  });
Adam Barth's avatar
Adam Barth committed
408
}