// 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 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; import '../rendering/rendering_tester.dart' show TestCallbackPainter; class TestPaintingContext implements PaintingContext { final List invocations = []; @override void noSuchMethod(Invocation invocation) { invocations.add(invocation); } } void main() { testWidgets('Can construct an empty Stack', (WidgetTester tester) async { await tester.pumpWidget(const Directionality(textDirection: TextDirection.ltr, child: Stack())); }); testWidgets('Can construct an empty Centered Stack', (WidgetTester tester) async { await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: Center(child: Stack()), ), ); }); testWidgets('Can change position data', (WidgetTester tester) async { const key = Key('container'); await tester.pumpWidget( const Stack( alignment: Alignment.topLeft, children: [ Positioned(left: 10.0, child: SizedBox(key: key, width: 10.0, height: 10.0)), ], ), ); Element container; StackParentData parentData; container = tester.element(find.byKey(key)); parentData = container.renderObject!.parentData! as StackParentData; expect(parentData.top, isNull); expect(parentData.right, isNull); expect(parentData.bottom, isNull); expect(parentData.left, equals(10.0)); expect(parentData.width, isNull); expect(parentData.height, isNull); await tester.pumpWidget( const Stack( alignment: Alignment.topLeft, children: [ Positioned(right: 10.0, child: SizedBox(key: key, width: 10.0, height: 10.0)), ], ), ); container = tester.element(find.byKey(key)); parentData = container.renderObject!.parentData! as StackParentData; expect(parentData.top, isNull); expect(parentData.right, equals(10.0)); expect(parentData.bottom, isNull); expect(parentData.left, isNull); expect(parentData.width, isNull); expect(parentData.height, isNull); }); testWidgets('Can remove parent data', (WidgetTester tester) async { const key = Key('container'); const sizedBox = SizedBox(key: key, width: 10.0, height: 10.0); await tester.pumpWidget( const Stack( textDirection: TextDirection.ltr, children: [Positioned(left: 10.0, child: sizedBox)], ), ); Element containerElement = tester.element(find.byKey(key)); StackParentData parentData; parentData = containerElement.renderObject!.parentData! as StackParentData; expect(parentData.top, isNull); expect(parentData.right, isNull); expect(parentData.bottom, isNull); expect(parentData.left, equals(10.0)); expect(parentData.width, isNull); expect(parentData.height, isNull); await tester.pumpWidget( const Stack(textDirection: TextDirection.ltr, children: [sizedBox]), ); containerElement = tester.element(find.byKey(key)); parentData = containerElement.renderObject!.parentData! as StackParentData; expect(parentData.top, isNull); expect(parentData.right, isNull); expect(parentData.bottom, isNull); expect(parentData.left, isNull); expect(parentData.width, isNull); expect(parentData.height, isNull); }); testWidgets('Can align non-positioned children (LTR)', (WidgetTester tester) async { const child0Key = Key('child0'); const child1Key = Key('child1'); await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: Center( child: Stack( alignment: Alignment.center, children: [ SizedBox(key: child0Key, width: 20.0, height: 20.0), SizedBox(key: child1Key, width: 10.0, height: 10.0), ], ), ), ), ); final Element child0 = tester.element(find.byKey(child0Key)); final child0RenderObjectParentData = child0.renderObject!.parentData! as StackParentData; expect(child0RenderObjectParentData.offset, equals(Offset.zero)); final Element child1 = tester.element(find.byKey(child1Key)); final child1RenderObjectParentData = child1.renderObject!.parentData! as StackParentData; expect(child1RenderObjectParentData.offset, equals(const Offset(5.0, 5.0))); await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: Center( child: Stack( alignment: AlignmentDirectional.bottomEnd, children: [ SizedBox(key: child0Key, width: 20.0, height: 20.0), SizedBox(key: child1Key, width: 10.0, height: 10.0), ], ), ), ), ); expect(child0RenderObjectParentData.offset, equals(Offset.zero)); expect(child1RenderObjectParentData.offset, equals(const Offset(10.0, 10.0))); }); testWidgets('Can align non-positioned children (RTL)', (WidgetTester tester) async { const child0Key = Key('child0'); const child1Key = Key('child1'); await tester.pumpWidget( const Directionality( textDirection: TextDirection.rtl, child: Center( child: Stack( alignment: Alignment.center, children: [ SizedBox(key: child0Key, width: 20.0, height: 20.0), SizedBox(key: child1Key, width: 10.0, height: 10.0), ], ), ), ), ); final Element child0 = tester.element(find.byKey(child0Key)); final child0RenderObjectParentData = child0.renderObject!.parentData! as StackParentData; expect(child0RenderObjectParentData.offset, equals(Offset.zero)); final Element child1 = tester.element(find.byKey(child1Key)); final child1RenderObjectParentData = child1.renderObject!.parentData! as StackParentData; expect(child1RenderObjectParentData.offset, equals(const Offset(5.0, 5.0))); await tester.pumpWidget( const Directionality( textDirection: TextDirection.rtl, child: Center( child: Stack( alignment: AlignmentDirectional.bottomEnd, children: [ SizedBox(key: child0Key, width: 20.0, height: 20.0), SizedBox(key: child1Key, width: 10.0, height: 10.0), ], ), ), ), ); expect(child0RenderObjectParentData.offset, equals(Offset.zero)); expect(child1RenderObjectParentData.offset, equals(const Offset(0.0, 10.0))); }); testWidgets('Can construct an empty IndexedStack', (WidgetTester tester) async { await tester.pumpWidget( const Directionality(textDirection: TextDirection.ltr, child: IndexedStack()), ); }); testWidgets('Can construct an empty Centered IndexedStack', (WidgetTester tester) async { await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: Center(child: IndexedStack()), ), ); }); testWidgets('Can construct an IndexedStack', (WidgetTester tester) async { const itemCount = 3; late List itemsPainted; Widget buildFrame(int index) { itemsPainted = []; final items = List.generate(itemCount, (int i) { return CustomPaint( painter: TestCallbackPainter( onPaint: () { itemsPainted.add(i); }, ), child: Text('$i', textDirection: TextDirection.ltr), ); }); return Center( child: IndexedStack(alignment: Alignment.topLeft, index: index, children: items), ); } void expectFindsChild(int n) { for (var i = 0; i < 3; i++) { expect(find.text('$i', skipOffstage: false), findsOneWidget); if (i == n) { expect(find.text('$i'), findsOneWidget); } else { expect(find.text('$i'), findsNothing); } } } await tester.pumpWidget(buildFrame(0)); expectFindsChild(0); expect(itemsPainted, equals([0])); await tester.pumpWidget(buildFrame(1)); expectFindsChild(1); expect(itemsPainted, equals([1])); await tester.pumpWidget(buildFrame(2)); expectFindsChild(2); expect(itemsPainted, equals([2])); }); testWidgets('Can hit test an IndexedStack', (WidgetTester tester) async { const key = Key('indexedStack'); const itemCount = 3; late List itemsTapped; Widget buildFrame(int index) { itemsTapped = []; final items = List.generate(itemCount, (int i) { return GestureDetector( child: Text('$i', textDirection: TextDirection.ltr), onTap: () { itemsTapped.add(i); }, ); }); return Center( child: IndexedStack(alignment: Alignment.topLeft, key: key, index: index, children: items), ); } await tester.pumpWidget(buildFrame(0)); expect(itemsTapped, isEmpty); await tester.tap(find.byKey(key)); expect(itemsTapped, [0]); await tester.pumpWidget(buildFrame(2)); expect(itemsTapped, isEmpty); await tester.tap(find.byKey(key)); expect(itemsTapped, [2]); }); testWidgets('IndexedStack sets non-selected indexes to visible=false', ( WidgetTester tester, ) async { Widget buildStack({required int itemCount, required int? selectedIndex}) { final children = List.generate(itemCount, (int i) { return _ShowVisibility(index: i); }); return Directionality( textDirection: TextDirection.ltr, child: IndexedStack(index: selectedIndex, children: children), ); } await tester.pumpWidget(buildStack(itemCount: 3, selectedIndex: null)); expect(find.text('index 0 is visible ? false', skipOffstage: false), findsOneWidget); expect(find.text('index 1 is visible ? false', skipOffstage: false), findsOneWidget); expect(find.text('index 2 is visible ? false', skipOffstage: false), findsOneWidget); await tester.pumpWidget(buildStack(itemCount: 3, selectedIndex: 0)); expect(find.text('index 0 is visible ? true', skipOffstage: false), findsOneWidget); expect(find.text('index 1 is visible ? false', skipOffstage: false), findsOneWidget); expect(find.text('index 2 is visible ? false', skipOffstage: false), findsOneWidget); await tester.pumpWidget(buildStack(itemCount: 3, selectedIndex: 1)); expect(find.text('index 0 is visible ? false', skipOffstage: false), findsOneWidget); expect(find.text('index 1 is visible ? true', skipOffstage: false), findsOneWidget); expect(find.text('index 2 is visible ? false', skipOffstage: false), findsOneWidget); await tester.pumpWidget(buildStack(itemCount: 3, selectedIndex: 2)); expect(find.text('index 0 is visible ? false', skipOffstage: false), findsOneWidget); expect(find.text('index 1 is visible ? false', skipOffstage: false), findsOneWidget); expect(find.text('index 2 is visible ? true', skipOffstage: false), findsOneWidget); }); testWidgets('Can set width and height', (WidgetTester tester) async { const key = Key('container'); const kBoxDecoration = BoxDecoration(color: Color(0xFF00FF00)); await tester.pumpWidget( const Stack( textDirection: TextDirection.ltr, children: [ Positioned( left: 10.0, width: 11.0, height: 12.0, child: DecoratedBox(key: key, decoration: kBoxDecoration), ), ], ), ); Element box; RenderBox renderBox; StackParentData parentData; box = tester.element(find.byKey(key)); renderBox = box.renderObject! as RenderBox; parentData = renderBox.parentData! as StackParentData; expect(parentData.top, isNull); expect(parentData.right, isNull); expect(parentData.bottom, isNull); expect(parentData.left, equals(10.0)); expect(parentData.width, equals(11.0)); expect(parentData.height, equals(12.0)); expect(parentData.offset.dx, equals(10.0)); expect(parentData.offset.dy, equals(0.0)); expect(renderBox.size.width, equals(11.0)); expect(renderBox.size.height, equals(12.0)); await tester.pumpWidget( const Stack( textDirection: TextDirection.ltr, children: [ Positioned( right: 10.0, width: 11.0, height: 12.0, child: DecoratedBox(key: key, decoration: kBoxDecoration), ), ], ), ); box = tester.element(find.byKey(key)); renderBox = box.renderObject! as RenderBox; parentData = renderBox.parentData! as StackParentData; expect(parentData.top, isNull); expect(parentData.right, equals(10.0)); expect(parentData.bottom, isNull); expect(parentData.left, isNull); expect(parentData.width, equals(11.0)); expect(parentData.height, equals(12.0)); expect(parentData.offset.dx, equals(779.0)); expect(parentData.offset.dy, equals(0.0)); expect(renderBox.size.width, equals(11.0)); expect(renderBox.size.height, equals(12.0)); }); testWidgets('Can set and update clipBehavior', (WidgetTester tester) async { await tester.pumpWidget(const Stack(textDirection: TextDirection.ltr)); final RenderStack renderObject = tester.allRenderObjects.whereType().first; expect(renderObject.clipBehavior, equals(Clip.hardEdge)); await tester.pumpWidget(const Stack(textDirection: TextDirection.ltr)); expect(renderObject.clipBehavior, equals(Clip.hardEdge)); }); testWidgets('Clip.none is respected by describeApproximateClip', (WidgetTester tester) async { await tester.pumpWidget( const Stack( textDirection: TextDirection.ltr, children: [ Positioned(left: 1000, right: 2000, child: SizedBox(width: 2000, height: 2000)), ], ), ); final RenderStack renderObject = tester.allRenderObjects.whereType().first; expect(renderObject.clipBehavior, equals(Clip.hardEdge)); var visited = false; renderObject.visitChildren((RenderObject child) { visited = true; expect( renderObject.describeApproximatePaintClip(child), const Rect.fromLTRB(0.0, 0.0, 800.0, 600.0), ); }); expect(visited, true); visited = false; renderObject.clipBehavior = Clip.none; renderObject.visitChildren((RenderObject child) { visited = true; expect(renderObject.describeApproximatePaintClip(child), null); }); expect(visited, true); }); testWidgets('IndexedStack with null index', (WidgetTester tester) async { bool? tapped; await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Center( child: IndexedStack( index: null, children: [ GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { tapped = true; }, child: const SizedBox(width: 200.0, height: 200.0), ), ], ), ), ), ); await tester.tap(find.byType(IndexedStack), warnIfMissed: false); final RenderBox box = tester.renderObject(find.byType(IndexedStack)); expect(box.size, equals(const Size(200.0, 200.0))); expect(tapped, isNull); }); testWidgets('IndexedStack reports hidden children as offstage', (WidgetTester tester) async { final children = [for (int i = 0; i < 5; i++) Text('child $i')]; Future pumpIndexedStack(int? activeIndex) async { await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: IndexedStack(index: activeIndex, children: children), ), ); } final Finder finder = find.byType(Text); final Finder finderIncludingOffstage = find.byType(Text, skipOffstage: false); await pumpIndexedStack(null); expect(finder, findsNothing); // IndexedStack with null index shows nothing expect(finderIncludingOffstage, findsNWidgets(5)); for (var i = 0; i < 5; i++) { await pumpIndexedStack(i); expect(finder, findsOneWidget); expect(finderIncludingOffstage, findsNWidgets(5)); expect(find.text('child $i'), findsOneWidget); } }); testWidgets('IndexedStack excludes focus for hidden children', (WidgetTester tester) async { const children = [Focus(child: Text('child 0')), Focus(child: Text('child 1'))]; Future pumpIndexedStack(int? activeIndex) async { await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: IndexedStack(index: activeIndex, children: children), ), ); } Future requestFocusAndPump(FocusNode node) async { node.requestFocus(); await tester.pump(); } await pumpIndexedStack(0); final Element child0 = tester.element(find.text('child 0', skipOffstage: false)); final Element child1 = tester.element(find.text('child 1', skipOffstage: false)); final FocusNode child0FocusNode = Focus.of(child0); final FocusNode child1FocusNode = Focus.of(child1); await requestFocusAndPump(child0FocusNode); expect(child0FocusNode.hasFocus, true); expect(child1FocusNode.hasFocus, false); await requestFocusAndPump(child1FocusNode); expect(child0FocusNode.hasFocus, true); expect(child1FocusNode.hasFocus, false); await pumpIndexedStack(1); await requestFocusAndPump(child1FocusNode); expect(child0FocusNode.hasFocus, false); expect(child1FocusNode.hasFocus, true); await requestFocusAndPump(child0FocusNode); expect(child0FocusNode.hasFocus, false); expect(child1FocusNode.hasFocus, true); }); testWidgets('IndexedStack: hidden children can not receive tap events', ( WidgetTester tester, ) async { var tapped = false; final children = [ const Text('child'), GestureDetector(onTap: () => tapped = true, child: const Text('hiddenChild')), ]; await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: IndexedStack(children: children), ), ); await tester.tap(find.text('hiddenChild', skipOffstage: false), warnIfMissed: false); await tester.pump(); expect(tapped, false); }); testWidgets('Stack clip test', (WidgetTester tester) async { await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: Center( child: Stack( children: [ SizedBox(width: 100.0, height: 100.0), Positioned(top: 0.0, left: 0.0, child: SizedBox(width: 200.0, height: 200.0)), ], ), ), ), ); RenderBox box = tester.renderObject(find.byType(Stack)); var context = TestPaintingContext(); box.paint(context, Offset.zero); expect(context.invocations.first.memberName, equals(#pushClipRect)); await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: Center( child: Stack( clipBehavior: Clip.none, children: [ SizedBox(width: 100.0, height: 100.0), Positioned(top: 0.0, left: 0.0, child: SizedBox(width: 200.0, height: 200.0)), ], ), ), ), ); box = tester.renderObject(find.byType(Stack)); context = TestPaintingContext(); box.paint(context, Offset.zero); expect(context.invocations.first.memberName, equals(#paintChild)); }); testWidgets('Stack sizing: default', (WidgetTester tester) async { final logs = []; await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Center( child: ConstrainedBox( constraints: const BoxConstraints( minWidth: 2.0, maxWidth: 3.0, minHeight: 5.0, maxHeight: 7.0, ), child: Stack( children: [ LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { logs.add(constraints.toString()); return const Placeholder(); }, ), ], ), ), ), ), ); expect(logs, ['BoxConstraints(0.0<=w<=3.0, 0.0<=h<=7.0)']); }); testWidgets('Stack sizing: explicit', (WidgetTester tester) async { final logs = []; Widget buildStack(StackFit sizing) { return Directionality( textDirection: TextDirection.ltr, child: Center( child: ConstrainedBox( constraints: const BoxConstraints( minWidth: 2.0, maxWidth: 3.0, minHeight: 5.0, maxHeight: 7.0, ), child: Stack( fit: sizing, children: [ LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { logs.add(constraints.toString()); return const Placeholder(); }, ), ], ), ), ), ); } await tester.pumpWidget(buildStack(StackFit.loose)); logs.add('=1='); await tester.pumpWidget(buildStack(StackFit.expand)); logs.add('=2='); await tester.pumpWidget(buildStack(StackFit.passthrough)); expect(logs, [ 'BoxConstraints(0.0<=w<=3.0, 0.0<=h<=7.0)', '=1=', 'BoxConstraints(w=3.0, h=7.0)', '=2=', 'BoxConstraints(2.0<=w<=3.0, 5.0<=h<=7.0)', ]); }); testWidgets('Positioned.directional control test', (WidgetTester tester) async { final Key key = UniqueKey(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Stack( children: [ Positioned.directional( textDirection: TextDirection.rtl, start: 50.0, child: SizedBox(key: key, width: 75.0, height: 175.0), ), ], ), ), ); expect(tester.getTopLeft(find.byKey(key)), const Offset(675.0, 0.0)); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Stack( children: [ Positioned.directional( textDirection: TextDirection.ltr, start: 50.0, child: SizedBox(key: key, width: 75.0, height: 175.0), ), ], ), ), ); expect(tester.getTopLeft(find.byKey(key)), const Offset(50.0, 0.0)); }); testWidgets('PositionedDirectional control test', (WidgetTester tester) async { final Key key = UniqueKey(); await tester.pumpWidget( Directionality( textDirection: TextDirection.rtl, child: Stack( children: [ PositionedDirectional( start: 50.0, child: SizedBox(key: key, width: 75.0, height: 175.0), ), ], ), ), ); expect(tester.getTopLeft(find.byKey(key)), const Offset(675.0, 0.0)); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Stack( children: [ PositionedDirectional( start: 50.0, child: SizedBox(key: key, width: 75.0, height: 175.0), ), ], ), ), ); expect(tester.getTopLeft(find.byKey(key)), const Offset(50.0, 0.0)); }); testWidgets('Can change the text direction of a Stack', (WidgetTester tester) async { await tester.pumpWidget(const Stack(alignment: Alignment.center)); await tester.pumpWidget(const Stack(textDirection: TextDirection.rtl)); await tester.pumpWidget(const Stack(alignment: Alignment.center)); }); testWidgets('Alignment with partially-positioned children', (WidgetTester tester) async { await tester.pumpWidget( const Directionality( textDirection: TextDirection.rtl, child: Stack( alignment: Alignment.center, children: [ SizedBox(width: 100.0, height: 100.0), Positioned(left: 0.0, child: SizedBox(width: 100.0, height: 100.0)), Positioned(right: 0.0, child: SizedBox(width: 100.0, height: 100.0)), Positioned(top: 0.0, child: SizedBox(width: 100.0, height: 100.0)), Positioned(bottom: 0.0, child: SizedBox(width: 100.0, height: 100.0)), PositionedDirectional(start: 0.0, child: SizedBox(width: 100.0, height: 100.0)), PositionedDirectional(end: 0.0, child: SizedBox(width: 100.0, height: 100.0)), PositionedDirectional(top: 0.0, child: SizedBox(width: 100.0, height: 100.0)), PositionedDirectional(bottom: 0.0, child: SizedBox(width: 100.0, height: 100.0)), ], ), ), ); expect( tester.getRect(find.byType(SizedBox).at(0)), const Rect.fromLTWH(350.0, 250.0, 100.0, 100.0), ); expect( tester.getRect(find.byType(SizedBox).at(1)), const Rect.fromLTWH(0.0, 250.0, 100.0, 100.0), ); expect( tester.getRect(find.byType(SizedBox).at(2)), const Rect.fromLTWH(700.0, 250.0, 100.0, 100.0), ); expect( tester.getRect(find.byType(SizedBox).at(3)), const Rect.fromLTWH(350.0, 0.0, 100.0, 100.0), ); expect( tester.getRect(find.byType(SizedBox).at(4)), const Rect.fromLTWH(350.0, 500.0, 100.0, 100.0), ); expect( tester.getRect(find.byType(SizedBox).at(5)), const Rect.fromLTWH(700.0, 250.0, 100.0, 100.0), ); expect( tester.getRect(find.byType(SizedBox).at(6)), const Rect.fromLTWH(0.0, 250.0, 100.0, 100.0), ); expect( tester.getRect(find.byType(SizedBox).at(7)), const Rect.fromLTWH(350.0, 0.0, 100.0, 100.0), ); expect( tester.getRect(find.byType(SizedBox).at(8)), const Rect.fromLTWH(350.0, 500.0, 100.0, 100.0), ); await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: Stack( alignment: Alignment.center, children: [ SizedBox(width: 100.0, height: 100.0), Positioned(left: 0.0, child: SizedBox(width: 100.0, height: 100.0)), Positioned(right: 0.0, child: SizedBox(width: 100.0, height: 100.0)), Positioned(top: 0.0, child: SizedBox(width: 100.0, height: 100.0)), Positioned(bottom: 0.0, child: SizedBox(width: 100.0, height: 100.0)), PositionedDirectional(start: 0.0, child: SizedBox(width: 100.0, height: 100.0)), PositionedDirectional(end: 0.0, child: SizedBox(width: 100.0, height: 100.0)), PositionedDirectional(top: 0.0, child: SizedBox(width: 100.0, height: 100.0)), PositionedDirectional(bottom: 0.0, child: SizedBox(width: 100.0, height: 100.0)), ], ), ), ); expect( tester.getRect(find.byType(SizedBox).at(0)), const Rect.fromLTWH(350.0, 250.0, 100.0, 100.0), ); expect( tester.getRect(find.byType(SizedBox).at(1)), const Rect.fromLTWH(0.0, 250.0, 100.0, 100.0), ); expect( tester.getRect(find.byType(SizedBox).at(2)), const Rect.fromLTWH(700.0, 250.0, 100.0, 100.0), ); expect( tester.getRect(find.byType(SizedBox).at(3)), const Rect.fromLTWH(350.0, 0.0, 100.0, 100.0), ); expect( tester.getRect(find.byType(SizedBox).at(4)), const Rect.fromLTWH(350.0, 500.0, 100.0, 100.0), ); expect( tester.getRect(find.byType(SizedBox).at(5)), const Rect.fromLTWH(0.0, 250.0, 100.0, 100.0), ); expect( tester.getRect(find.byType(SizedBox).at(6)), const Rect.fromLTWH(700.0, 250.0, 100.0, 100.0), ); expect( tester.getRect(find.byType(SizedBox).at(7)), const Rect.fromLTWH(350.0, 0.0, 100.0, 100.0), ); expect( tester.getRect(find.byType(SizedBox).at(8)), const Rect.fromLTWH(350.0, 500.0, 100.0, 100.0), ); await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: Stack( alignment: Alignment.bottomRight, children: [ SizedBox(width: 100.0, height: 100.0), Positioned(left: 0.0, child: SizedBox(width: 100.0, height: 100.0)), Positioned(right: 0.0, child: SizedBox(width: 100.0, height: 100.0)), Positioned(top: 0.0, child: SizedBox(width: 100.0, height: 100.0)), Positioned(bottom: 0.0, child: SizedBox(width: 100.0, height: 100.0)), PositionedDirectional(start: 0.0, child: SizedBox(width: 100.0, height: 100.0)), PositionedDirectional(end: 0.0, child: SizedBox(width: 100.0, height: 100.0)), PositionedDirectional(top: 0.0, child: SizedBox(width: 100.0, height: 100.0)), PositionedDirectional(bottom: 0.0, child: SizedBox(width: 100.0, height: 100.0)), ], ), ), ); expect( tester.getRect(find.byType(SizedBox).at(0)), const Rect.fromLTWH(700.0, 500.0, 100.0, 100.0), ); expect( tester.getRect(find.byType(SizedBox).at(1)), const Rect.fromLTWH(0.0, 500.0, 100.0, 100.0), ); expect( tester.getRect(find.byType(SizedBox).at(2)), const Rect.fromLTWH(700.0, 500.0, 100.0, 100.0), ); expect( tester.getRect(find.byType(SizedBox).at(3)), const Rect.fromLTWH(700.0, 0.0, 100.0, 100.0), ); expect( tester.getRect(find.byType(SizedBox).at(4)), const Rect.fromLTWH(700.0, 500.0, 100.0, 100.0), ); expect( tester.getRect(find.byType(SizedBox).at(5)), const Rect.fromLTWH(0.0, 500.0, 100.0, 100.0), ); expect( tester.getRect(find.byType(SizedBox).at(6)), const Rect.fromLTWH(700.0, 500.0, 100.0, 100.0), ); expect( tester.getRect(find.byType(SizedBox).at(7)), const Rect.fromLTWH(700.0, 0.0, 100.0, 100.0), ); expect( tester.getRect(find.byType(SizedBox).at(8)), const Rect.fromLTWH(700.0, 500.0, 100.0, 100.0), ); await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: Stack( alignment: Alignment.topLeft, children: [ SizedBox(width: 100.0, height: 100.0), Positioned(left: 0.0, child: SizedBox(width: 100.0, height: 100.0)), Positioned(right: 0.0, child: SizedBox(width: 100.0, height: 100.0)), Positioned(top: 0.0, child: SizedBox(width: 100.0, height: 100.0)), Positioned(bottom: 0.0, child: SizedBox(width: 100.0, height: 100.0)), PositionedDirectional(start: 0.0, child: SizedBox(width: 100.0, height: 100.0)), PositionedDirectional(end: 0.0, child: SizedBox(width: 100.0, height: 100.0)), PositionedDirectional(top: 0.0, child: SizedBox(width: 100.0, height: 100.0)), PositionedDirectional(bottom: 0.0, child: SizedBox(width: 100.0, height: 100.0)), ], ), ), ); expect( tester.getRect(find.byType(SizedBox).at(0)), const Rect.fromLTWH(0.0, 0.0, 100.0, 100.0), ); expect( tester.getRect(find.byType(SizedBox).at(1)), const Rect.fromLTWH(0.0, 0.0, 100.0, 100.0), ); expect( tester.getRect(find.byType(SizedBox).at(2)), const Rect.fromLTWH(700.0, 0.0, 100.0, 100.0), ); expect( tester.getRect(find.byType(SizedBox).at(3)), const Rect.fromLTWH(0.0, 0.0, 100.0, 100.0), ); expect( tester.getRect(find.byType(SizedBox).at(4)), const Rect.fromLTWH(0.0, 500.0, 100.0, 100.0), ); expect( tester.getRect(find.byType(SizedBox).at(5)), const Rect.fromLTWH(0.0, 0.0, 100.0, 100.0), ); expect( tester.getRect(find.byType(SizedBox).at(6)), const Rect.fromLTWH(700.0, 0.0, 100.0, 100.0), ); expect( tester.getRect(find.byType(SizedBox).at(7)), const Rect.fromLTWH(0.0, 0.0, 100.0, 100.0), ); expect( tester.getRect(find.byType(SizedBox).at(8)), const Rect.fromLTWH(0.0, 500.0, 100.0, 100.0), ); }); testWidgets( 'Stack error messages', experimentalLeakTesting: LeakTesting.settings .withIgnoredAll(), // leaking by design because of exception (WidgetTester tester) async { await tester.pumpWidget(const Stack()); final exception = tester.takeException().toString(); expect( exception, startsWith( 'No Directionality widget found.\n' "Stack widgets require a Directionality widget ancestor to resolve the 'alignment' argument.\n" "The default value for 'alignment' is AlignmentDirectional.topStart, which requires a text direction.\n" 'The specific widget that could not find a Directionality ancestor was:\n' ' Stack\n' 'The ownership chain for the affected widget is: "Stack ← ', // Omitted full ownership chain because it is not relevant for the test. ), ); expect( exception, endsWith( '_ViewScope ← ⋯"\n' // End of ownership chain. 'Typically, the Directionality widget is introduced by the MaterialApp or WidgetsApp widget at the ' 'top of your application widget tree. It determines the ambient reading direction and is used, for ' 'example, to determine how to lay out text, how to interpret "start" and "end" values, and to resolve ' 'EdgeInsetsDirectional, AlignmentDirectional, and other *Directional objects.\n' 'Instead of providing a Directionality widget, another solution would be passing a non-directional ' "'alignment', or an explicit 'textDirection', to the Stack.", ), ); }, ); testWidgets('Can update clipBehavior of IndexedStack', (WidgetTester tester) async { await tester.pumpWidget(const IndexedStack(textDirection: TextDirection.ltr)); final RenderIndexedStack renderObject = tester.renderObject( find.byType(IndexedStack), ); expect(renderObject.clipBehavior, equals(Clip.hardEdge)); // Update clipBehavior to Clip.antiAlias await tester.pumpWidget( const IndexedStack(textDirection: TextDirection.ltr, clipBehavior: Clip.antiAlias), ); final RenderIndexedStack renderIndexedObject = tester.renderObject( find.byType(IndexedStack), ); expect(renderIndexedObject.clipBehavior, equals(Clip.antiAlias)); }); testWidgets('IndexedStack sizing: explicit', (WidgetTester tester) async { final logs = []; Widget buildIndexedStack(StackFit sizing) { return Directionality( textDirection: TextDirection.ltr, child: Center( child: ConstrainedBox( constraints: const BoxConstraints( minWidth: 2.0, maxWidth: 3.0, minHeight: 5.0, maxHeight: 7.0, ), child: IndexedStack( sizing: sizing, children: [ LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { logs.add(constraints.toString()); return const Placeholder(); }, ), ], ), ), ), ); } await tester.pumpWidget(buildIndexedStack(StackFit.loose)); logs.add('=1='); await tester.pumpWidget(buildIndexedStack(StackFit.expand)); logs.add('=2='); await tester.pumpWidget(buildIndexedStack(StackFit.passthrough)); expect(logs, [ 'BoxConstraints(0.0<=w<=3.0, 0.0<=h<=7.0)', '=1=', 'BoxConstraints(w=3.0, h=7.0)', '=2=', 'BoxConstraints(2.0<=w<=3.0, 5.0<=h<=7.0)', ]); }); testWidgets('IndexedStack does not assert with the default parameters', ( WidgetTester tester, ) async { await tester.pumpWidget( const Directionality(textDirection: TextDirection.ltr, child: IndexedStack()), ); expect(tester.takeException(), isNull); }); testWidgets('IndexedStack does not assert when index is null', (WidgetTester tester) async { await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: IndexedStack(index: null, children: [SizedBox.shrink(), SizedBox.shrink()]), ), ); expect(tester.takeException(), isNull); }); testWidgets('IndexedStack asserts when index is negative', (WidgetTester tester) async { await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: IndexedStack(index: -1, children: [SizedBox.shrink(), SizedBox.shrink()]), ), ); expect(tester.takeException(), isA()); }); testWidgets('IndexedStack asserts when index is not in children range', ( WidgetTester tester, ) async { await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: IndexedStack(index: 2, children: [SizedBox.shrink(), SizedBox.shrink()]), ), ); expect(tester.takeException(), isA()); }); } class _ShowVisibility extends StatelessWidget { const _ShowVisibility({required this.index}); final int index; @override Widget build(BuildContext context) { return Text('index $index is visible ? ${Visibility.of(context)}'); } }