blob: 9dd722d073bfc150903601a7a64c9ec33cff8701 [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math' as math;
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import 'rendering_tester.dart';
void main() {
TestRenderingFlutterBinding.ensureInitialized();
test('Overconstrained flex', () {
final RenderDecoratedBox box = RenderDecoratedBox(decoration: const BoxDecoration());
final RenderFlex flex = RenderFlex(textDirection: TextDirection.ltr, children: <RenderBox>[box]);
layout(flex, constraints: const BoxConstraints(
minWidth: 200.0,
maxWidth: 200.0,
minHeight: 200.0,
maxHeight: 200.0,
));
expect(flex.size.width, equals(200.0), reason: 'flex width');
expect(flex.size.height, equals(200.0), reason: 'flex height');
});
test('Inconsequential overflow is ignored', () {
// These values are meant to simulate slight rounding errors in addition
// or subtraction in the layout code for Flex.
const double slightlyLarger = 438.8571428571429;
const double slightlySmaller = 438.85714285714283;
final List<dynamic> exceptions = <dynamic>[];
final FlutterExceptionHandler? oldHandler = FlutterError.onError;
FlutterError.onError = (FlutterErrorDetails details) {
exceptions.add(details.exception);
};
const BoxConstraints square = BoxConstraints.tightFor(width: slightlyLarger, height: 100.0);
final RenderConstrainedBox box1 = RenderConstrainedBox(additionalConstraints: square);
final RenderFlex flex = RenderFlex(
textDirection: TextDirection.ltr,
mainAxisSize: MainAxisSize.min,
);
final RenderConstrainedOverflowBox parent = RenderConstrainedOverflowBox(
minWidth: 0.0,
maxWidth: slightlySmaller,
minHeight: 0.0,
maxHeight: 400.0,
child: flex,
);
flex.add(box1);
layout(parent);
expect(flex.size, const Size(slightlySmaller, 100.0));
pumpFrame(phase: EnginePhase.paint);
expect(exceptions, isEmpty);
FlutterError.onError = oldHandler;
});
test('Clip behavior is respected', () {
const BoxConstraints viewport = BoxConstraints(maxHeight: 100.0, maxWidth: 100.0);
final TestClipPaintingContext context = TestClipPaintingContext();
bool hadErrors = false;
for (final Clip? clip in <Clip?>[null, ...Clip.values]) {
final RenderFlex flex;
switch (clip) {
case Clip.none:
case Clip.hardEdge:
case Clip.antiAlias:
case Clip.antiAliasWithSaveLayer:
flex = RenderFlex(direction: Axis.vertical, children: <RenderBox>[box200x200], clipBehavior: clip!);
case null:
flex = RenderFlex(direction: Axis.vertical, children: <RenderBox>[box200x200]);
}
layout(flex, constraints: viewport, phase: EnginePhase.composite, onErrors: () {
absorbOverflowedErrors();
hadErrors = true;
});
context.paintChild(flex, Offset.zero);
// By default, clipBehavior should be Clip.none
expect(context.clipBehavior, equals(clip ?? Clip.none));
expect(hadErrors, isTrue);
hadErrors = false;
}
});
test('Vertical Overflow', () {
final RenderConstrainedBox flexible = RenderConstrainedBox(
additionalConstraints: const BoxConstraints.expand(),
);
final RenderFlex flex = RenderFlex(
direction: Axis.vertical,
children: <RenderBox>[
RenderConstrainedBox(additionalConstraints: const BoxConstraints.tightFor(height: 200.0)),
flexible,
],
);
final FlexParentData flexParentData = flexible.parentData! as FlexParentData;
flexParentData.flex = 1;
const BoxConstraints viewport = BoxConstraints(maxHeight: 100.0, maxWidth: 100.0);
layout(flex, constraints: viewport);
expect(flexible.size.height, equals(0.0));
expect(flex.getMinIntrinsicHeight(100.0), equals(200.0));
expect(flex.getMaxIntrinsicHeight(100.0), equals(200.0));
expect(flex.getMinIntrinsicWidth(100.0), equals(0.0));
expect(flex.getMaxIntrinsicWidth(100.0), equals(0.0));
});
test('Horizontal Overflow', () {
final RenderConstrainedBox flexible = RenderConstrainedBox(
additionalConstraints: const BoxConstraints.expand(),
);
final RenderFlex flex = RenderFlex(
textDirection: TextDirection.ltr,
children: <RenderBox>[
RenderConstrainedBox(additionalConstraints: const BoxConstraints.tightFor(width: 200.0)),
flexible,
],
);
final FlexParentData flexParentData = flexible.parentData! as FlexParentData;
flexParentData.flex = 1;
const BoxConstraints viewport = BoxConstraints(maxHeight: 100.0, maxWidth: 100.0);
layout(flex, constraints: viewport);
expect(flexible.size.width, equals(0.0));
expect(flex.getMinIntrinsicHeight(100.0), equals(0.0));
expect(flex.getMaxIntrinsicHeight(100.0), equals(0.0));
expect(flex.getMinIntrinsicWidth(100.0), equals(200.0));
expect(flex.getMaxIntrinsicWidth(100.0), equals(200.0));
});
test('Vertical Flipped Constraints', () {
final RenderFlex flex = RenderFlex(
direction: Axis.vertical,
children: <RenderBox>[
RenderAspectRatio(aspectRatio: 1.0),
],
);
const BoxConstraints viewport = BoxConstraints(maxHeight: 200.0, maxWidth: 1000.0);
layout(flex, constraints: viewport);
expect(flex.getMaxIntrinsicWidth(200.0), equals(0.0));
});
// We can't write a horizontal version of the above test due to
// RenderAspectRatio being height-in, width-out.
test('Defaults', () {
final RenderFlex flex = RenderFlex();
expect(flex.crossAxisAlignment, equals(CrossAxisAlignment.center));
expect(flex.direction, equals(Axis.horizontal));
expect(flex, hasAGoodToStringDeep);
expect(
flex.toStringDeep(minLevel: DiagnosticLevel.info),
equalsIgnoringHashCodes(
'RenderFlex#00000 NEEDS-LAYOUT NEEDS-PAINT DETACHED\n'
' parentData: MISSING\n'
' constraints: MISSING\n'
' size: MISSING\n'
' direction: horizontal\n'
' mainAxisAlignment: start\n'
' mainAxisSize: max\n'
' crossAxisAlignment: center\n'
' verticalDirection: down\n',
),
);
});
test('Parent data', () {
final RenderDecoratedBox box1 = RenderDecoratedBox(decoration: const BoxDecoration());
final RenderDecoratedBox box2 = RenderDecoratedBox(decoration: const BoxDecoration());
final RenderFlex flex = RenderFlex(textDirection: TextDirection.ltr, children: <RenderBox>[box1, box2]);
layout(flex, constraints: const BoxConstraints(
maxWidth: 100.0,
maxHeight: 100.0,
));
expect(box1.size.width, equals(0.0));
expect(box1.size.height, equals(0.0));
expect(box2.size.width, equals(0.0));
expect(box2.size.height, equals(0.0));
final FlexParentData box2ParentData = box2.parentData! as FlexParentData;
box2ParentData.flex = 1;
flex.markNeedsLayout();
pumpFrame();
expect(box1.size.width, equals(0.0));
expect(box1.size.height, equals(0.0));
expect(box2.size.width, equals(100.0));
expect(box2.size.height, equals(0.0));
});
test('Stretch', () {
final RenderDecoratedBox box1 = RenderDecoratedBox(decoration: const BoxDecoration());
final RenderDecoratedBox box2 = RenderDecoratedBox(decoration: const BoxDecoration());
final RenderFlex flex = RenderFlex(textDirection: TextDirection.ltr);
flex.setupParentData(box2);
final FlexParentData box2ParentData = box2.parentData! as FlexParentData;
box2ParentData.flex = 2;
flex.addAll(<RenderBox>[box1, box2]);
layout(flex, constraints: const BoxConstraints(
maxWidth: 100.0,
maxHeight: 100.0,
));
expect(box1.size.width, equals(0.0));
expect(box1.size.height, equals(0.0));
expect(box2.size.width, equals(100.0));
expect(box2.size.height, equals(0.0));
flex.crossAxisAlignment = CrossAxisAlignment.stretch;
pumpFrame();
expect(box1.size.width, equals(0.0));
expect(box1.size.height, equals(100.0));
expect(box2.size.width, equals(100.0));
expect(box2.size.height, equals(100.0));
flex.direction = Axis.vertical;
pumpFrame();
expect(box1.size.width, equals(100.0));
expect(box1.size.height, equals(0.0));
expect(box2.size.width, equals(100.0));
expect(box2.size.height, equals(100.0));
});
test('Space evenly', () {
final RenderConstrainedBox box1 = RenderConstrainedBox(additionalConstraints: const BoxConstraints.tightFor(width: 100.0, height: 100.0));
final RenderConstrainedBox box2 = RenderConstrainedBox(additionalConstraints: const BoxConstraints.tightFor(width: 100.0, height: 100.0));
final RenderConstrainedBox box3 = RenderConstrainedBox(additionalConstraints: const BoxConstraints.tightFor(width: 100.0, height: 100.0));
final RenderFlex flex = RenderFlex(textDirection: TextDirection.ltr, mainAxisAlignment: MainAxisAlignment.spaceEvenly);
flex.addAll(<RenderBox>[box1, box2, box3]);
layout(flex, constraints: const BoxConstraints(
maxWidth: 500.0,
maxHeight: 400.0,
));
Offset getOffset(RenderBox box) {
final FlexParentData parentData = box.parentData! as FlexParentData;
return parentData.offset;
}
expect(getOffset(box1).dx, equals(50.0));
expect(box1.size.width, equals(100.0));
expect(getOffset(box2).dx, equals(200.0));
expect(box2.size.width, equals(100.0));
expect(getOffset(box3).dx, equals(350.0));
expect(box3.size.width, equals(100.0));
flex.direction = Axis.vertical;
pumpFrame();
expect(getOffset(box1).dy, equals(25.0));
expect(box1.size.height, equals(100.0));
expect(getOffset(box2).dy, equals(150.0));
expect(box2.size.height, equals(100.0));
expect(getOffset(box3).dy, equals(275.0));
expect(box3.size.height, equals(100.0));
});
test('Fit.loose', () {
final RenderConstrainedBox box1 = RenderConstrainedBox(additionalConstraints: const BoxConstraints.tightFor(width: 100.0, height: 100.0));
final RenderConstrainedBox box2 = RenderConstrainedBox(additionalConstraints: const BoxConstraints.tightFor(width: 100.0, height: 100.0));
final RenderConstrainedBox box3 = RenderConstrainedBox(additionalConstraints: const BoxConstraints.tightFor(width: 100.0, height: 100.0));
final RenderFlex flex = RenderFlex(textDirection: TextDirection.ltr, mainAxisAlignment: MainAxisAlignment.spaceBetween);
flex.addAll(<RenderBox>[box1, box2, box3]);
layout(flex, constraints: const BoxConstraints(
maxWidth: 500.0,
maxHeight: 400.0,
));
Offset getOffset(RenderBox box) {
final FlexParentData parentData = box.parentData! as FlexParentData;
return parentData.offset;
}
expect(getOffset(box1).dx, equals(0.0));
expect(box1.size.width, equals(100.0));
expect(getOffset(box2).dx, equals(200.0));
expect(box2.size.width, equals(100.0));
expect(getOffset(box3).dx, equals(400.0));
expect(box3.size.width, equals(100.0));
void setFit(RenderBox box, FlexFit fit) {
final FlexParentData parentData = box.parentData! as FlexParentData;
parentData.flex = 1;
parentData.fit = fit;
}
setFit(box1, FlexFit.loose);
flex.markNeedsLayout();
pumpFrame();
expect(getOffset(box1).dx, equals(0.0));
expect(box1.size.width, equals(100.0));
expect(getOffset(box2).dx, equals(200.0));
expect(box2.size.width, equals(100.0));
expect(getOffset(box3).dx, equals(400.0));
expect(box3.size.width, equals(100.0));
box1.additionalConstraints = const BoxConstraints.tightFor(width: 1000.0, height: 100.0);
pumpFrame();
expect(getOffset(box1).dx, equals(0.0));
expect(box1.size.width, equals(300.0));
expect(getOffset(box2).dx, equals(300.0));
expect(box2.size.width, equals(100.0));
expect(getOffset(box3).dx, equals(400.0));
expect(box3.size.width, equals(100.0));
});
test('Flexible with MainAxisSize.min', () {
final RenderConstrainedBox box1 = RenderConstrainedBox(additionalConstraints: const BoxConstraints.tightFor(width: 100.0, height: 100.0));
final RenderConstrainedBox box2 = RenderConstrainedBox(additionalConstraints: const BoxConstraints.tightFor(width: 100.0, height: 100.0));
final RenderConstrainedBox box3 = RenderConstrainedBox(additionalConstraints: const BoxConstraints.tightFor(width: 100.0, height: 100.0));
final RenderFlex flex = RenderFlex(
textDirection: TextDirection.ltr,
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
);
flex.addAll(<RenderBox>[box1, box2, box3]);
layout(flex, constraints: const BoxConstraints(
maxWidth: 500.0,
maxHeight: 400.0,
));
Offset getOffset(RenderBox box) {
final FlexParentData parentData = box.parentData! as FlexParentData;
return parentData.offset;
}
expect(getOffset(box1).dx, equals(0.0));
expect(box1.size.width, equals(100.0));
expect(getOffset(box2).dx, equals(100.0));
expect(box2.size.width, equals(100.0));
expect(getOffset(box3).dx, equals(200.0));
expect(box3.size.width, equals(100.0));
expect(flex.size.width, equals(300.0));
void setFit(RenderBox box, FlexFit fit) {
final FlexParentData parentData = box.parentData! as FlexParentData;
parentData.flex = 1;
parentData.fit = fit;
}
setFit(box1, FlexFit.tight);
flex.markNeedsLayout();
pumpFrame();
expect(getOffset(box1).dx, equals(0.0));
expect(box1.size.width, equals(300.0));
expect(getOffset(box2).dx, equals(300.0));
expect(box2.size.width, equals(100.0));
expect(getOffset(box3).dx, equals(400.0));
expect(box3.size.width, equals(100.0));
expect(flex.size.width, equals(500.0));
setFit(box1, FlexFit.loose);
flex.markNeedsLayout();
pumpFrame();
expect(getOffset(box1).dx, equals(0.0));
expect(box1.size.width, equals(100.0));
expect(getOffset(box2).dx, equals(100.0));
expect(box2.size.width, equals(100.0));
expect(getOffset(box3).dx, equals(200.0));
expect(box3.size.width, equals(100.0));
expect(flex.size.width, equals(300.0));
});
test('MainAxisSize.min inside unconstrained', () {
FlutterError.onError = (FlutterErrorDetails details) => throw details.exception;
const BoxConstraints square = BoxConstraints.tightFor(width: 100.0, height: 100.0);
final RenderConstrainedBox box1 = RenderConstrainedBox(additionalConstraints: square);
final RenderConstrainedBox box2 = RenderConstrainedBox(additionalConstraints: square);
final RenderConstrainedBox box3 = RenderConstrainedBox(additionalConstraints: square);
final RenderFlex flex = RenderFlex(
textDirection: TextDirection.ltr,
mainAxisSize: MainAxisSize.min,
);
final RenderConstrainedOverflowBox parent = RenderConstrainedOverflowBox(
minWidth: 0.0,
maxWidth: double.infinity,
minHeight: 0.0,
maxHeight: 400.0,
child: flex,
);
flex.addAll(<RenderBox>[box1, box2, box3]);
layout(parent);
expect(flex.size, const Size(300.0, 100.0));
final FlexParentData box2ParentData = box2.parentData! as FlexParentData;
box2ParentData.flex = 1;
box2ParentData.fit = FlexFit.loose;
flex.markNeedsLayout();
pumpFrame();
expect(flex.size, const Size(300.0, 100.0));
parent.maxWidth = 500.0; // NOW WITH CONSTRAINED BOUNDARIES
pumpFrame();
expect(flex.size, const Size(300.0, 100.0));
flex.mainAxisSize = MainAxisSize.max;
pumpFrame();
expect(flex.size, const Size(500.0, 100.0));
flex.mainAxisSize = MainAxisSize.min;
box2ParentData.fit = FlexFit.tight;
flex.markNeedsLayout();
pumpFrame();
expect(flex.size, const Size(500.0, 100.0));
parent.maxWidth = 505.0;
pumpFrame();
expect(flex.size, const Size(505.0, 100.0));
});
test('MainAxisSize.min inside unconstrained', () {
const BoxConstraints square = BoxConstraints.tightFor(width: 100.0, height: 100.0);
final RenderConstrainedBox box1 = RenderConstrainedBox(additionalConstraints: square);
final RenderConstrainedBox box2 = RenderConstrainedBox(additionalConstraints: square);
final RenderConstrainedBox box3 = RenderConstrainedBox(additionalConstraints: square);
final RenderFlex flex = RenderFlex(
textDirection: TextDirection.ltr,
mainAxisSize: MainAxisSize.min,
);
final RenderConstrainedOverflowBox parent = RenderConstrainedOverflowBox(
minWidth: 0.0,
maxWidth: double.infinity,
minHeight: 0.0,
maxHeight: 400.0,
child: flex,
);
flex.addAll(<RenderBox>[box1, box2, box3]);
final FlexParentData box2ParentData = box2.parentData! as FlexParentData;
box2ParentData.flex = 1;
final List<dynamic> exceptions = <dynamic>[];
layout(parent, onErrors: () {
exceptions.addAll(TestRenderingFlutterBinding.instance.takeAllFlutterExceptions());
});
expect(exceptions, isNotEmpty);
expect(exceptions.first, isFlutterError);
});
test('MainAxisSize.min inside unconstrained', () {
const BoxConstraints square = BoxConstraints.tightFor(width: 100.0, height: 100.0);
final RenderConstrainedBox box1 = RenderConstrainedBox(additionalConstraints: square);
final RenderConstrainedBox box2 = RenderConstrainedBox(additionalConstraints: square);
final RenderConstrainedBox box3 = RenderConstrainedBox(additionalConstraints: square);
final RenderFlex flex = RenderFlex(
textDirection: TextDirection.ltr,
);
final RenderConstrainedOverflowBox parent = RenderConstrainedOverflowBox(
minWidth: 0.0,
maxWidth: double.infinity,
minHeight: 0.0,
maxHeight: 400.0,
child: flex,
);
flex.addAll(<RenderBox>[box1, box2, box3]);
final FlexParentData box2ParentData = box2.parentData! as FlexParentData;
box2ParentData.flex = 1;
box2ParentData.fit = FlexFit.loose;
final List<dynamic> exceptions = <dynamic>[];
layout(parent, onErrors: () {
exceptions.addAll(TestRenderingFlutterBinding.instance.takeAllFlutterExceptions());
});
expect(exceptions, isNotEmpty);
expect(exceptions.first, isFlutterError);
});
test('MainAxisSize.min inside tightly constrained', () {
const BoxConstraints square = BoxConstraints.tightFor(width: 100.0, height: 100.0);
final RenderConstrainedBox box1 = RenderConstrainedBox(additionalConstraints: square);
final RenderConstrainedBox box2 = RenderConstrainedBox(additionalConstraints: square);
final RenderConstrainedBox box3 = RenderConstrainedBox(additionalConstraints: square);
final RenderFlex flex = RenderFlex(
textDirection: TextDirection.rtl,
mainAxisSize: MainAxisSize.min,
);
flex.addAll(<RenderBox>[box1, box2, box3]);
layout(flex);
expect(flex.constraints.hasTightWidth, isTrue);
expect(box1.localToGlobal(Offset.zero), const Offset(700.0, 250.0));
expect(box2.localToGlobal(Offset.zero), const Offset(600.0, 250.0));
expect(box3.localToGlobal(Offset.zero), const Offset(500.0, 250.0));
expect(box1.size, const Size(100.0, 100.0));
expect(box2.size, const Size(100.0, 100.0));
expect(box3.size, const Size(100.0, 100.0));
});
test('Flex RTL', () {
const BoxConstraints square = BoxConstraints.tightFor(width: 100.0, height: 100.0);
final RenderConstrainedBox box1 = RenderConstrainedBox(additionalConstraints: square);
final RenderConstrainedBox box2 = RenderConstrainedBox(additionalConstraints: square);
final RenderConstrainedBox box3 = RenderConstrainedBox(additionalConstraints: square);
final RenderFlex flex = RenderFlex(textDirection: TextDirection.ltr, children: <RenderBox>[box1, box2, box3]);
layout(flex);
expect(box1.localToGlobal(Offset.zero), const Offset(0.0, 250.0));
expect(box2.localToGlobal(Offset.zero), const Offset(100.0, 250.0));
expect(box3.localToGlobal(Offset.zero), const Offset(200.0, 250.0));
expect(box1.size, const Size(100.0, 100.0));
expect(box2.size, const Size(100.0, 100.0));
expect(box3.size, const Size(100.0, 100.0));
flex.mainAxisAlignment = MainAxisAlignment.end;
pumpFrame();
expect(box1.localToGlobal(Offset.zero), const Offset(500.0, 250.0));
expect(box2.localToGlobal(Offset.zero), const Offset(600.0, 250.0));
expect(box3.localToGlobal(Offset.zero), const Offset(700.0, 250.0));
expect(box1.size, const Size(100.0, 100.0));
expect(box2.size, const Size(100.0, 100.0));
expect(box3.size, const Size(100.0, 100.0));
flex.textDirection = TextDirection.rtl;
pumpFrame();
expect(box1.localToGlobal(Offset.zero), const Offset(200.0, 250.0));
expect(box2.localToGlobal(Offset.zero), const Offset(100.0, 250.0));
expect(box3.localToGlobal(Offset.zero), const Offset(0.0, 250.0));
expect(box1.size, const Size(100.0, 100.0));
expect(box2.size, const Size(100.0, 100.0));
expect(box3.size, const Size(100.0, 100.0));
flex.mainAxisAlignment = MainAxisAlignment.start;
pumpFrame();
expect(box1.localToGlobal(Offset.zero), const Offset(700.0, 250.0));
expect(box2.localToGlobal(Offset.zero), const Offset(600.0, 250.0));
expect(box3.localToGlobal(Offset.zero), const Offset(500.0, 250.0));
expect(box1.size, const Size(100.0, 100.0));
expect(box2.size, const Size(100.0, 100.0));
expect(box3.size, const Size(100.0, 100.0));
flex.crossAxisAlignment = CrossAxisAlignment.start; // vertical direction is down
pumpFrame();
expect(box1.localToGlobal(Offset.zero), const Offset(700.0, 0.0));
expect(box2.localToGlobal(Offset.zero), const Offset(600.0, 0.0));
expect(box3.localToGlobal(Offset.zero), const Offset(500.0, 0.0));
expect(box1.size, const Size(100.0, 100.0));
expect(box2.size, const Size(100.0, 100.0));
expect(box3.size, const Size(100.0, 100.0));
flex.crossAxisAlignment = CrossAxisAlignment.end;
pumpFrame();
expect(box1.localToGlobal(Offset.zero), const Offset(700.0, 500.0));
expect(box2.localToGlobal(Offset.zero), const Offset(600.0, 500.0));
expect(box3.localToGlobal(Offset.zero), const Offset(500.0, 500.0));
expect(box1.size, const Size(100.0, 100.0));
expect(box2.size, const Size(100.0, 100.0));
expect(box3.size, const Size(100.0, 100.0));
flex.verticalDirection = VerticalDirection.up;
pumpFrame();
expect(box1.localToGlobal(Offset.zero), const Offset(700.0, 0.0));
expect(box2.localToGlobal(Offset.zero), const Offset(600.0, 0.0));
expect(box3.localToGlobal(Offset.zero), const Offset(500.0, 0.0));
expect(box1.size, const Size(100.0, 100.0));
expect(box2.size, const Size(100.0, 100.0));
expect(box3.size, const Size(100.0, 100.0));
flex.crossAxisAlignment = CrossAxisAlignment.start;
pumpFrame();
expect(box1.localToGlobal(Offset.zero), const Offset(700.0, 500.0));
expect(box2.localToGlobal(Offset.zero), const Offset(600.0, 500.0));
expect(box3.localToGlobal(Offset.zero), const Offset(500.0, 500.0));
expect(box1.size, const Size(100.0, 100.0));
expect(box2.size, const Size(100.0, 100.0));
expect(box3.size, const Size(100.0, 100.0));
flex.direction = Axis.vertical; // and main=start, cross=start, up, rtl
pumpFrame();
expect(box1.localToGlobal(Offset.zero), const Offset(700.0, 500.0));
expect(box2.localToGlobal(Offset.zero), const Offset(700.0, 400.0));
expect(box3.localToGlobal(Offset.zero), const Offset(700.0, 300.0));
expect(box1.size, const Size(100.0, 100.0));
expect(box2.size, const Size(100.0, 100.0));
expect(box3.size, const Size(100.0, 100.0));
flex.crossAxisAlignment = CrossAxisAlignment.end;
pumpFrame();
expect(box1.localToGlobal(Offset.zero), const Offset(0.0, 500.0));
expect(box2.localToGlobal(Offset.zero), const Offset(0.0, 400.0));
expect(box3.localToGlobal(Offset.zero), const Offset(0.0, 300.0));
expect(box1.size, const Size(100.0, 100.0));
expect(box2.size, const Size(100.0, 100.0));
expect(box3.size, const Size(100.0, 100.0));
flex.crossAxisAlignment = CrossAxisAlignment.stretch;
pumpFrame();
expect(box1.localToGlobal(Offset.zero), const Offset(0.0, 500.0));
expect(box2.localToGlobal(Offset.zero), const Offset(0.0, 400.0));
expect(box3.localToGlobal(Offset.zero), const Offset(0.0, 300.0));
expect(box1.size, const Size(800.0, 100.0));
expect(box2.size, const Size(800.0, 100.0));
expect(box3.size, const Size(800.0, 100.0));
flex.textDirection = TextDirection.ltr;
pumpFrame();
expect(box1.localToGlobal(Offset.zero), const Offset(0.0, 500.0));
expect(box2.localToGlobal(Offset.zero), const Offset(0.0, 400.0));
expect(box3.localToGlobal(Offset.zero), const Offset(0.0, 300.0));
expect(box1.size, const Size(800.0, 100.0));
expect(box2.size, const Size(800.0, 100.0));
expect(box3.size, const Size(800.0, 100.0));
flex.crossAxisAlignment = CrossAxisAlignment.start;
pumpFrame();
expect(box1.localToGlobal(Offset.zero), const Offset(0.0, 500.0));
expect(box2.localToGlobal(Offset.zero), const Offset(0.0, 400.0));
expect(box3.localToGlobal(Offset.zero), const Offset(0.0, 300.0));
expect(box1.size, const Size(100.0, 100.0));
expect(box2.size, const Size(100.0, 100.0));
expect(box3.size, const Size(100.0, 100.0));
flex.crossAxisAlignment = CrossAxisAlignment.end;
pumpFrame();
expect(box1.localToGlobal(Offset.zero), const Offset(700.0, 500.0));
expect(box2.localToGlobal(Offset.zero), const Offset(700.0, 400.0));
expect(box3.localToGlobal(Offset.zero), const Offset(700.0, 300.0));
expect(box1.size, const Size(100.0, 100.0));
expect(box2.size, const Size(100.0, 100.0));
expect(box3.size, const Size(100.0, 100.0));
flex.verticalDirection = VerticalDirection.down;
pumpFrame();
expect(box1.localToGlobal(Offset.zero), const Offset(700.0, 0.0));
expect(box2.localToGlobal(Offset.zero), const Offset(700.0, 100.0));
expect(box3.localToGlobal(Offset.zero), const Offset(700.0, 200.0));
expect(box1.size, const Size(100.0, 100.0));
expect(box2.size, const Size(100.0, 100.0));
expect(box3.size, const Size(100.0, 100.0));
flex.mainAxisAlignment = MainAxisAlignment.end;
pumpFrame();
expect(box1.localToGlobal(Offset.zero), const Offset(700.0, 300.0));
expect(box2.localToGlobal(Offset.zero), const Offset(700.0, 400.0));
expect(box3.localToGlobal(Offset.zero), const Offset(700.0, 500.0));
expect(box1.size, const Size(100.0, 100.0));
expect(box2.size, const Size(100.0, 100.0));
expect(box3.size, const Size(100.0, 100.0));
});
test('children with no baselines are top-aligned', () {
const BoxConstraints square = BoxConstraints.tightFor(width: 100.0, height: 100.0);
final RenderConstrainedBox box1 = RenderConstrainedBox(additionalConstraints: square);
final RenderConstrainedBox box2 = RenderConstrainedBox(additionalConstraints: square);
final RenderFlex flex = RenderFlex(
textDirection: TextDirection.ltr,
children: <RenderBox>[box1, box2],
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
verticalDirection: VerticalDirection.up,
);
layout(flex);
// Not start-aligned.
expect(box1.localToGlobal(Offset.zero).dy, 0.0);
expect(box2.localToGlobal(Offset.zero).dy, 0.0);
flex.verticalDirection = VerticalDirection.down;
pumpFrame();
expect(box1.localToGlobal(Offset.zero).dy, 0.0);
expect(box2.localToGlobal(Offset.zero).dy, 0.0);
});
group('Intrinsics', () {
test('main axis intrinsics with RenderAspectRatio 1', () {
const BoxConstraints square = BoxConstraints.tightFor(width: 100.0, height: 100.0);
final RenderConstrainedBox box1 = RenderConstrainedBox(additionalConstraints: square);
final RenderConstrainedBox box2 = RenderConstrainedBox(additionalConstraints: square);
final RenderAspectRatio box3 = RenderAspectRatio(aspectRatio: 1.0, child: RenderConstrainedBox(additionalConstraints: square));
final RenderFlex flex = RenderFlex(
textDirection: TextDirection.ltr,
);
flex.addAll(<RenderBox>[box1, box2, box3]);
final FlexParentData box2ParentData = box2.parentData! as FlexParentData;
box2ParentData.flex = 1;
box2ParentData.fit = FlexFit.tight; // In intrinsics FlexFit.tight should have no effect.
expect(flex.getMinIntrinsicWidth(double.infinity), 300.0);
expect(flex.getMaxIntrinsicWidth(double.infinity), 300.0);
expect(flex.getMinIntrinsicWidth(300.0), 200.0 + 300.0);
expect(flex.getMaxIntrinsicWidth(300.0), 200.0 + 300.0);
expect(flex.getMinIntrinsicWidth(500.0), 200.0 + 500.0);
expect(flex.getMaxIntrinsicWidth(500.0), 200.0 + 500.0);
});
test('cross axis intrinsics, with ascending flex flow layout', () {
const BoxConstraints square = BoxConstraints.tightFor(width: 5.0, height: 5.0);
// 3 'A's separated by zero-width spaces. Max intrinsic width = 30, min intrinsic width = 10
final TextSpan textSpan = TextSpan(text: List<String>.filled(3, 'A').join('\u200B') , style: const TextStyle(fontSize: 10));
final RenderConstrainedBox box1 = RenderConstrainedBox(additionalConstraints: square);
final RenderParagraph box2 = RenderParagraph(textSpan, textDirection: TextDirection.ltr);
final RenderParagraph box3 = RenderParagraph(textSpan, textDirection: TextDirection.ltr);
final RenderFlex flex = RenderFlex(textDirection: TextDirection.ltr);
flex.addAll(<RenderBox>[box1, box2, box3]);
final FlexParentData box2ParentData = box2.parentData! as FlexParentData;
box2ParentData.flex = 1;
box2ParentData.fit = FlexFit.tight; // In intrinsics FlexFit.tight should have no effect.
final FlexParentData box3ParentData = box3.parentData! as FlexParentData;
box3ParentData.flex = 2;
box3ParentData.fit = FlexFit.tight; // In intrinsics FlexFit.tight should have no effect.
expect(flex.getMinIntrinsicHeight(double.infinity), 10.0);
expect(flex.getMaxIntrinsicHeight(double.infinity), 10.0);
// 95.0 is the max intrinsic width of the RenderFlex.
// width distribution = 5, 30, 60.
expect(flex.getMinIntrinsicHeight(95.0), 10.0);
expect(flex.getMaxIntrinsicHeight(95.0), 10.0);
expect(flex.getMinIntrinsicHeight(94.0), 20.0);
expect(flex.getMaxIntrinsicHeight(94.0), 20.0);
// width distribution = 5, 20, 40
expect(flex.getMinIntrinsicHeight(65.0), 20.0);
expect(flex.getMaxIntrinsicHeight(65.0), 20.0);
// width distribution = 5, 10, 20
expect(flex.getMinIntrinsicHeight(35.0), 30.0);
expect(flex.getMaxIntrinsicHeight(35.0), 30.0);
});
test('cross axis intrinsics, with descending flex flow layout', () {
const BoxConstraints square = BoxConstraints.tightFor(width: 5.0, height: 5.0);
// 3 'A's separated by zero-width spaces. Max intrinsic width = 30, min intrinsic width = 10
final TextSpan textSpan = TextSpan(text: List<String>.filled(3, 'A').join('\u200B') , style: const TextStyle(fontSize: 10));
final RenderConstrainedBox box1 = RenderConstrainedBox(additionalConstraints: square);
final RenderParagraph box2 = RenderParagraph(textSpan, textDirection: TextDirection.ltr);
final RenderParagraph box3 = RenderParagraph(textSpan, textDirection: TextDirection.ltr);
final RenderFlex flex = RenderFlex(textDirection: TextDirection.ltr);
flex.addAll(<RenderBox>[box1, box2, box3]);
final FlexParentData box2ParentData = box2.parentData! as FlexParentData;
box2ParentData.flex = 2;
box2ParentData.fit = FlexFit.tight; // In intrinsics FlexFit.tight should have no effect.
final FlexParentData box3ParentData = box3.parentData! as FlexParentData;
box3ParentData.flex = 1;
box3ParentData.fit = FlexFit.tight; // In intrinsics FlexFit.tight should have no effect.
// The setup is exactly the same as the previous test, but the flex factors
// are swapped.
expect(flex.getMinIntrinsicHeight(double.infinity), 10.0);
expect(flex.getMaxIntrinsicHeight(double.infinity), 10.0);
// 95.0 is the max intrinsic width of the RenderFlex.
expect(flex.getMinIntrinsicHeight(95.0), 10.0);
expect(flex.getMaxIntrinsicHeight(95.0), 10.0);
// width distribution = 5, 40, 20.
expect(flex.getMinIntrinsicHeight(65.0), 20.0);
expect(flex.getMaxIntrinsicHeight(65.0), 20.0);
// width distribution = 5, 20, 10
expect(flex.getMinIntrinsicHeight(35.0), 30.0);
expect(flex.getMaxIntrinsicHeight(35.0), 30.0);
});
test('baseline aligned flex flow computeDryLayout', () {
// box1 has its baseline placed at the top of the box.
final RenderFlowBaselineTestBox box1 = RenderFlowBaselineTestBox()
..baselinePlacer = ((double height) => 0.0)
..gridCount = 10;
// box2 has its baseline placed at the bottom of the box.
final RenderFlowBaselineTestBox box2 = RenderFlowBaselineTestBox()
..baselinePlacer = ((double height) => height)
..gridCount = 10;
final RenderFlex flex = RenderFlex(
textDirection: TextDirection.ltr,
textBaseline: TextBaseline.alphabetic,
crossAxisAlignment: CrossAxisAlignment.baseline,
children: <RenderBox>[box1, box2],
);
final FlexParentData box1ParentData = box1.parentData! as FlexParentData;
box1ParentData.flex = 2;
box1ParentData.fit = FlexFit.tight;
final FlexParentData box2ParentData = box2.parentData! as FlexParentData;
box2ParentData.flex = 1;
box2ParentData.fit = FlexFit.loose;
Size size = const Size(200, 100);
// box 1 one line, box 2 two lines.
expect(flex.getDryLayout(BoxConstraints.loose(size)), const Size(200.0, 30.0));
expect(flex.getDryBaseline(BoxConstraints.loose(size), TextBaseline.alphabetic), 20.0);
size = const Size(300, 100);
// box 1 one line, box 2 one line.
expect(flex.getDryLayout(BoxConstraints.loose(size)), const Size(300.0, 20.0));
expect(flex.getDryBaseline(BoxConstraints.loose(size), TextBaseline.alphabetic), 10.0);
});
test('baseline aligned children cross intrinsic size', () {
// box1 has its baseline placed at the top of the box.
final RenderFlowBaselineTestBox box1 = RenderFlowBaselineTestBox()
..baselinePlacer = ((double height) => 0.0)
..gridCount = 10;
// box2 has its baseline placed at the bottom of the box.
final RenderFlowBaselineTestBox box2 = RenderFlowBaselineTestBox()
..baselinePlacer = ((double height) => height)
..gridCount = 10;
final RenderFlex flex = RenderFlex(
textDirection: TextDirection.ltr,
textBaseline: TextBaseline.alphabetic,
crossAxisAlignment: CrossAxisAlignment.baseline,
children: <RenderBox>[box1, box2],
);
final FlexParentData box1ParentData = box1.parentData! as FlexParentData;
box1ParentData.flex = 2;
box1ParentData.fit = FlexFit.tight;
final FlexParentData box2ParentData = box2.parentData! as FlexParentData;
box2ParentData.flex = 1;
box2ParentData.fit = FlexFit.loose;
// box 1 one line, box 2 two lines.
expect(flex.getMaxIntrinsicHeight(200), 30);
expect(flex.getMinIntrinsicHeight(200), 30);
// box 1 one line, box 2 one line.
expect(flex.getMaxIntrinsicHeight(300), 20);
expect(flex.getMinIntrinsicHeight(300), 20);
});
test('children with no baselines do not affect the baseline location', () {
// box1 has its baseline placed at the bottom of the box.
final RenderFlowBaselineTestBox box1 = RenderFlowBaselineTestBox()
..baselinePlacer = ((double height) => height)
..gridCount = 10;
// box2 has its baseline placed at the bottom of the box.
final RenderFlowBaselineTestBox box2 = RenderFlowBaselineTestBox()
..baselinePlacer = ((double height) => null)
..gridCount = 10;
final RenderFlex flex = RenderFlex(
textDirection: TextDirection.ltr,
textBaseline: TextBaseline.alphabetic,
crossAxisAlignment: CrossAxisAlignment.baseline,
children: <RenderBox>[box1, box2],
);
final FlexParentData box1ParentData = box1.parentData! as FlexParentData;
box1ParentData.flex = 2;
box1ParentData.fit = FlexFit.tight;
final FlexParentData box2ParentData = box2.parentData! as FlexParentData;
box2ParentData.flex = 1;
box2ParentData.fit = FlexFit.loose;
Size size = const Size(200, 100);
// box 1 one line, box 2 two lines.
expect(flex.getDryLayout(BoxConstraints.loose(size)), const Size(200.0, 20.0));
expect(flex.getDryBaseline(BoxConstraints.loose(size), TextBaseline.alphabetic), 10.0);
size = const Size(300, 100);
// box 1 one line, box 2 one.
expect(flex.getDryLayout(BoxConstraints.loose(size)), const Size(300.0, 10.0));
expect(flex.getDryBaseline(BoxConstraints.loose(size), TextBaseline.alphabetic), 10.0);
});
});
test('Can call methods that check overflow even if overflow value is not set', () {
final List<dynamic> exceptions = <dynamic>[];
final RenderFlex flex = RenderFlex(children: const <RenderBox>[]);
// This forces a check for _hasOverflow
expect(flex.toStringShort(), isNot(contains('OVERFLOWING')));
layout(flex, phase: EnginePhase.paint, onErrors: () {
exceptions.addAll(TestRenderingFlutterBinding.instance.takeAllFlutterExceptions());
});
// We expect the RenderFlex to throw during performLayout() for not having
// a text direction, thus leaving it with a null overflow value. It'll then
// try to paint(), which also checks _hasOverflow, and it should be able to
// do so without an ancillary error.
expect(exceptions, hasLength(1));
// ignore: avoid_dynamic_calls
expect(exceptions.first.message, isNot(contains('Null check operator')));
});
}
class RenderFlowBaselineTestBox extends RenderBox {
static const Size gridSize = Size(10, 10);
int gridCount = 0;
int lineGridCount(double width) {
final int gridsPerLine = width >= gridCount * gridSize.width
? gridCount
: width ~/ gridSize.width;
return math.max(1, gridsPerLine);
}
int lineCount(double width) => (gridCount / lineGridCount(width)).ceil();
double? Function(double height) baselinePlacer = (double height) => null;
@override
double computeMinIntrinsicWidth(double height) => gridSize.width;
@override
double computeMaxIntrinsicWidth(double height) => gridSize.width * gridCount;
@override
double computeMinIntrinsicHeight(double width) => gridSize.height * lineCount(width);
@override
double computeMaxIntrinsicHeight(double width) => computeMinIntrinsicHeight(width);
@override
Size computeDryLayout(covariant BoxConstraints constraints) {
return constraints.constrain(Size(
gridSize.width * lineGridCount(constraints.maxWidth),
gridSize.height * lineCount(constraints.maxWidth),
));
}
@override
double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) => baselinePlacer(getDryLayout(constraints).height);
@override
double? computeDistanceToActualBaseline(TextBaseline baseline) => baselinePlacer(size.height);
@override
void performLayout() {
size = computeDryLayout(constraints);
}
}