// 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 instrinsic 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 instrinsic 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);
  }
}
