// 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/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; List> _setUpNodes() { return >[ TreeSliverNode('First'), TreeSliverNode( 'Second', children: >[ TreeSliverNode( 'alpha', children: >[ TreeSliverNode('uno'), TreeSliverNode('dos'), TreeSliverNode('tres'), ], ), TreeSliverNode('beta'), TreeSliverNode('kappa'), ], ), TreeSliverNode( 'Third', expanded: true, children: >[ TreeSliverNode('gamma'), TreeSliverNode('delta'), TreeSliverNode('epsilon'), ], ), TreeSliverNode('Fourth'), ]; } List> treeNodes = _setUpNodes(); void main() { testWidgets('asserts proper axis directions', (WidgetTester tester) async { final exceptions = []; final FlutterExceptionHandler? oldHandler = FlutterError.onError; FlutterError.onError = (FlutterErrorDetails details) { exceptions.add(details.exception); }; addTearDown(() { FlutterError.onError = oldHandler; }); await tester.pumpWidget( MaterialApp( home: CustomScrollView( reverse: true, slivers: [TreeSliver(tree: treeNodes)], ), ), ); FlutterError.onError = oldHandler; expect(exceptions.isNotEmpty, isTrue); expect( exceptions[0].toString(), contains('TreeSliver is only supported in Viewports with an AxisDirection.down.'), ); exceptions.clear(); await tester.pumpWidget(Container()); FlutterError.onError = (FlutterErrorDetails details) { exceptions.add(details.exception); }; await tester.pumpWidget( MaterialApp( home: CustomScrollView( scrollDirection: Axis.horizontal, reverse: true, slivers: [TreeSliver(tree: treeNodes)], ), ), ); FlutterError.onError = oldHandler; expect(exceptions.isNotEmpty, isTrue); expect( exceptions[0].toString(), contains('TreeSliver is only supported in Viewports with an AxisDirection.down.'), ); exceptions.clear(); await tester.pumpWidget(Container()); FlutterError.onError = (FlutterErrorDetails details) { exceptions.add(details.exception); }; await tester.pumpWidget( MaterialApp( home: CustomScrollView( scrollDirection: Axis.horizontal, slivers: [TreeSliver(tree: treeNodes)], ), ), ); FlutterError.onError = oldHandler; expect(exceptions.isNotEmpty, isTrue); expect( exceptions[0].toString(), contains('TreeSliver is only supported in Viewports with an AxisDirection.down.'), ); }); testWidgets('Basic layout', (WidgetTester tester) async { treeNodes = _setUpNodes(); // Default layout, custom indentation values, row extents. var treeSliver = TreeSliver(tree: treeNodes); await tester.pumpWidget(MaterialApp(home: CustomScrollView(slivers: [treeSliver]))); await tester.pump(); expect( find.byType(TreeSliver), paints ..paragraph(offset: const Offset(46.0, 8.0)) ..paragraph() // Icon ..paragraph(offset: const Offset(46.0, 48.0)) ..paragraph() // Icon ..paragraph(offset: const Offset(46.0, 88.0)) ..paragraph(offset: const Offset(56.0, 128.0)) ..paragraph(offset: const Offset(56.0, 168.0)) ..paragraph(offset: const Offset(56.0, 208.0)) ..paragraph(offset: const Offset(46.0, 248.0)), ); expect(find.text('First'), findsOneWidget); expect(tester.getRect(find.text('First')), const Rect.fromLTRB(46.0, 8.0, 286.0, 32.0)); expect(find.text('Second'), findsOneWidget); expect(tester.getRect(find.text('Second')), const Rect.fromLTRB(46.0, 48.0, 334.0, 72.0)); expect(find.text('Third'), findsOneWidget); expect(tester.getRect(find.text('Third')), const Rect.fromLTRB(46.0, 88.0, 286.0, 112.0)); expect(find.text('gamma'), findsOneWidget); expect(tester.getRect(find.text('gamma')), const Rect.fromLTRB(56.0, 128.0, 296.0, 152.0)); expect(find.text('delta'), findsOneWidget); expect(tester.getRect(find.text('delta')), const Rect.fromLTRB(56.0, 168.0, 296.0, 192.0)); expect(find.text('epsilon'), findsOneWidget); expect(tester.getRect(find.text('epsilon')), const Rect.fromLTRB(56.0, 208.0, 392.0, 232.0)); expect(find.text('Fourth'), findsOneWidget); expect(tester.getRect(find.text('Fourth')), const Rect.fromLTRB(46.0, 248.0, 334.0, 272.0)); treeSliver = TreeSliver(tree: treeNodes, indentation: TreeSliverIndentationType.none); await tester.pumpWidget(MaterialApp(home: CustomScrollView(slivers: [treeSliver]))); await tester.pump(); expect( find.byType(TreeSliver), paints ..paragraph(offset: const Offset(46.0, 8.0)) ..paragraph() // Icon ..paragraph(offset: const Offset(46.0, 48.0)) ..paragraph() // Icon ..paragraph(offset: const Offset(46.0, 88.0)) ..paragraph(offset: const Offset(46.0, 128.0)) ..paragraph(offset: const Offset(46.0, 168.0)) ..paragraph(offset: const Offset(46.0, 208.0)) ..paragraph(offset: const Offset(46.0, 248.0)), ); expect(find.text('First'), findsOneWidget); expect(tester.getRect(find.text('First')), const Rect.fromLTRB(46.0, 8.0, 286.0, 32.0)); expect(find.text('Second'), findsOneWidget); expect(tester.getRect(find.text('Second')), const Rect.fromLTRB(46.0, 48.0, 334.0, 72.0)); expect(find.text('Third'), findsOneWidget); expect(tester.getRect(find.text('Third')), const Rect.fromLTRB(46.0, 88.0, 286.0, 112.0)); expect(find.text('gamma'), findsOneWidget); expect(tester.getRect(find.text('gamma')), const Rect.fromLTRB(46.0, 128.0, 286.0, 152.0)); expect(find.text('delta'), findsOneWidget); expect(tester.getRect(find.text('delta')), const Rect.fromLTRB(46.0, 168.0, 286.0, 192.0)); expect(find.text('epsilon'), findsOneWidget); expect(tester.getRect(find.text('epsilon')), const Rect.fromLTRB(46.0, 208.0, 382.0, 232.0)); expect(find.text('Fourth'), findsOneWidget); expect(tester.getRect(find.text('Fourth')), const Rect.fromLTRB(46.0, 248.0, 334.0, 272.0)); treeSliver = TreeSliver( tree: treeNodes, indentation: TreeSliverIndentationType.custom(50.0), ); await tester.pumpWidget(MaterialApp(home: CustomScrollView(slivers: [treeSliver]))); await tester.pump(); expect( find.byType(TreeSliver), paints ..paragraph(offset: const Offset(46.0, 8.0)) ..paragraph() // Icon ..paragraph(offset: const Offset(46.0, 48.0)) ..paragraph() // Icon ..paragraph(offset: const Offset(46.0, 88.0)) ..paragraph(offset: const Offset(96.0, 128.0)) ..paragraph(offset: const Offset(96.0, 168.0)) ..paragraph(offset: const Offset(96.0, 208.0)) ..paragraph(offset: const Offset(46.0, 248.0)), ); expect(find.text('First'), findsOneWidget); expect(tester.getRect(find.text('First')), const Rect.fromLTRB(46.0, 8.0, 286.0, 32.0)); expect(find.text('Second'), findsOneWidget); expect(tester.getRect(find.text('Second')), const Rect.fromLTRB(46.0, 48.0, 334.0, 72.0)); expect(find.text('Third'), findsOneWidget); expect(tester.getRect(find.text('Third')), const Rect.fromLTRB(46.0, 88.0, 286.0, 112.0)); expect(find.text('gamma'), findsOneWidget); expect(tester.getRect(find.text('gamma')), const Rect.fromLTRB(96.0, 128.0, 336.0, 152.0)); expect(find.text('delta'), findsOneWidget); expect(tester.getRect(find.text('delta')), const Rect.fromLTRB(96.0, 168.0, 336.0, 192.0)); expect(find.text('epsilon'), findsOneWidget); expect(tester.getRect(find.text('epsilon')), const Rect.fromLTRB(96.0, 208.0, 432.0, 232.0)); expect(find.text('Fourth'), findsOneWidget); expect(tester.getRect(find.text('Fourth')), const Rect.fromLTRB(46.0, 248.0, 334.0, 272.0)); treeSliver = TreeSliver(tree: treeNodes, treeRowExtentBuilder: (_, _) => 100); await tester.pumpWidget(MaterialApp(home: CustomScrollView(slivers: [treeSliver]))); await tester.pump(); expect( find.byType(TreeSliver), paints ..paragraph(offset: const Offset(46.0, 26.0)) ..paragraph() // Icon ..paragraph(offset: const Offset(46.0, 126.0)) ..paragraph() // Icon ..paragraph(offset: const Offset(46.0, 226.0)) ..paragraph(offset: const Offset(56.0, 326.0)) ..paragraph(offset: const Offset(56.0, 426.0)) ..paragraph(offset: const Offset(56.0, 526.0)), ); expect(find.text('First'), findsOneWidget); expect(tester.getRect(find.text('First')), const Rect.fromLTRB(46.0, 26.0, 286.0, 74.0)); expect(find.text('Second'), findsOneWidget); expect(tester.getRect(find.text('Second')), const Rect.fromLTRB(46.0, 126.0, 334.0, 174.0)); expect(find.text('Third'), findsOneWidget); expect(tester.getRect(find.text('Third')), const Rect.fromLTRB(46.0, 226.0, 286.0, 274.0)); expect(find.text('gamma'), findsOneWidget); expect(tester.getRect(find.text('gamma')), const Rect.fromLTRB(56.0, 326.0, 296.0, 374.0)); expect(find.text('delta'), findsOneWidget); expect(tester.getRect(find.text('delta')), const Rect.fromLTRB(56.0, 426.0, 296.0, 474.0)); expect(find.text('epsilon'), findsOneWidget); expect(tester.getRect(find.text('epsilon')), const Rect.fromLTRB(56.0, 526.0, 392.0, 574.0)); expect(find.text('Fourth'), findsNothing); }); testWidgets('Animating node segment', (WidgetTester tester) async { treeNodes = _setUpNodes(); var treeSliver = TreeSliver(tree: treeNodes); await tester.pumpWidget(MaterialApp(home: CustomScrollView(slivers: [treeSliver]))); await tester.pump(); expect( find.byType(TreeSliver), paints ..paragraph(offset: const Offset(46.0, 8.0)) ..paragraph() // Icon ..paragraph(offset: const Offset(46.0, 48.0)) ..paragraph() // Icon ..paragraph(offset: const Offset(46.0, 88.0)) ..paragraph(offset: const Offset(56.0, 128.0)) ..paragraph(offset: const Offset(56.0, 168.0)) ..paragraph(offset: const Offset(56.0, 208.0)) ..paragraph(offset: const Offset(46.0, 248.0)), ); expect(find.text('alpha'), findsNothing); await tester.tap(find.byType(Icon).first); await tester.pump(); expect( find.byType(TreeSliver), paints ..paragraph(offset: const Offset(46.0, 8.0)) ..paragraph() // Icon ..paragraph(offset: const Offset(46.0, 48.0)) ..paragraph(offset: const Offset(56.0, 8.0)) // beta animating in ..paragraph(offset: const Offset(56.0, 48.0)) // kappa animating in ..paragraph() // Icon ..paragraph(offset: const Offset(46.0, 88.0)) ..paragraph(offset: const Offset(56.0, 128.0)) ..paragraph(offset: const Offset(56.0, 168.0)) ..paragraph(offset: const Offset(56.0, 208.0)) ..paragraph(offset: const Offset(46.0, 248.0)), ); // New nodes have been inserted into the tree, alpha // is not visible yet. expect(find.text('alpha'), findsNothing); expect(find.text('beta'), findsOneWidget); expect(tester.getRect(find.text('beta')), const Rect.fromLTRB(56.0, 8.0, 248.0, 32.0)); expect(find.text('kappa'), findsOneWidget); expect(tester.getRect(find.text('kappa')), const Rect.fromLTRB(56.0, 48.0, 296.0, 72.0)); // Progress the animation. await tester.pump(const Duration(milliseconds: 50)); expect( find.byType(TreeSliver), paints ..paragraph(offset: const Offset(46.0, 8.0)) ..paragraph() // Icon ..paragraph(offset: const Offset(46.0, 48.0)) ..paragraph() // alpha icon ..paragraph(offset: const Offset(56.0, 8.0)) // alpha animating in ..paragraph(offset: const Offset(56.0, 48.0)) // beta animating in ..paragraph(offset: const Offset(56.0, 88.0)) // kappa animating in ..paragraph() // Icon ..paragraph(offset: const Offset(46.0, 128.0)) ..paragraph(offset: const Offset(56.0, 168.0)) ..paragraph(offset: const Offset(56.0, 208.0)) ..paragraph(offset: const Offset(56.0, 248.0)) ..paragraph(offset: const Offset(46.0, 288.0)), ); expect(tester.getRect(find.text('alpha')).top.floor(), 8.0); expect(find.text('beta'), findsOneWidget); expect(tester.getRect(find.text('beta')).top.floor(), 48.0); expect(find.text('kappa'), findsOneWidget); expect(tester.getRect(find.text('kappa')).top.floor(), 88.0); // Complete the animation await tester.pumpAndSettle(); expect( find.byType(TreeSliver), paints ..paragraph(offset: const Offset(46.0, 8.0)) // First ..paragraph() // Icon ..paragraph(offset: const Offset(46.0, 48.0)) // Second ..paragraph() // alpha icon ..paragraph(offset: const Offset(56.0, 88.0)) // alpha ..paragraph(offset: const Offset(56.0, 128.0)) // beta ..paragraph(offset: const Offset(56.0, 168.0)) // kappa ..paragraph() // Icon ..paragraph(offset: const Offset(46.0, 208.0)) // Third ..paragraph(offset: const Offset(56.0, 248.0)) // gamma ..paragraph(offset: const Offset(56.0, 288.0)) // delta ..paragraph(offset: const Offset(56.0, 328.0)) // epsilon ..paragraph(offset: const Offset(46.0, 368.0)), // Fourth ); expect(find.text('alpha'), findsOneWidget); expect(tester.getRect(find.text('alpha')), const Rect.fromLTRB(56.0, 88.0, 296.0, 112.0)); expect(find.text('beta'), findsOneWidget); expect(tester.getRect(find.text('beta')), const Rect.fromLTRB(56.0, 128.0, 248.0, 152.0)); expect(find.text('kappa'), findsOneWidget); expect(tester.getRect(find.text('kappa')), const Rect.fromLTRB(56.0, 168.0, 296.0, 192.0)); // Customize the animation treeSliver = TreeSliver( tree: treeNodes, toggleAnimationStyle: const AnimationStyle( duration: Duration(milliseconds: 500), curve: Curves.bounceIn, ), ); await tester.pumpWidget(MaterialApp(home: CustomScrollView(slivers: [treeSliver]))); await tester.pump(); expect( find.byType(TreeSliver), paints ..paragraph(offset: const Offset(46.0, 8.0)) // First ..paragraph() // Icon ..paragraph(offset: const Offset(46.0, 48.0)) // Second ..paragraph() // alpha icon ..paragraph(offset: const Offset(56.0, 88.0)) // alpha ..paragraph(offset: const Offset(56.0, 128.0)) // beta ..paragraph(offset: const Offset(56.0, 168.0)) // kappa ..paragraph() // Icon ..paragraph(offset: const Offset(46.0, 208.0)) // Third ..paragraph(offset: const Offset(56.0, 248.0)) // gamma ..paragraph(offset: const Offset(56.0, 288.0)) // delta ..paragraph(offset: const Offset(56.0, 328.0)) // epsilon ..paragraph(offset: const Offset(46.0, 368.0)), // Fourth ); // Still visible from earlier. expect(find.text('alpha'), findsOneWidget); expect(tester.getRect(find.text('alpha')), const Rect.fromLTRB(56.0, 88.0, 296.0, 112.0)); // Collapse the node now await tester.tap(find.byType(Icon).first); await tester.pump(); await tester.pump(const Duration(milliseconds: 200)); expect(find.text('alpha'), findsOneWidget); expect(tester.getRect(find.text('alpha')).top.floor(), -22); expect(find.text('beta'), findsOneWidget); expect(tester.getRect(find.text('beta')).top.floor(), 18); expect(find.text('kappa'), findsOneWidget); expect(tester.getRect(find.text('kappa')).top.floor(), 58); // Progress the animation. await tester.pump(const Duration(milliseconds: 200)); expect(find.text('alpha'), findsOneWidget); expect(tester.getRect(find.text('alpha')).top.floor(), -25); expect(find.text('beta'), findsOneWidget); expect(tester.getRect(find.text('beta')).top.floor(), 15); expect(find.text('kappa'), findsOneWidget); expect(tester.getRect(find.text('kappa')).top.floor(), 55.0); // Complete the animation await tester.pumpAndSettle(); expect( find.byType(TreeSliver), paints ..paragraph(offset: const Offset(46.0, 8.0)) ..paragraph() // Icon ..paragraph(offset: const Offset(46.0, 48.0)) ..paragraph() // Icon ..paragraph(offset: const Offset(46.0, 88.0)) ..paragraph(offset: const Offset(56.0, 128.0)) ..paragraph(offset: const Offset(56.0, 168.0)) ..paragraph(offset: const Offset(56.0, 208.0)) ..paragraph(offset: const Offset(46.0, 248.0)), ); expect(find.text('alpha'), findsNothing); // Disable the animation treeSliver = TreeSliver( tree: treeNodes, toggleAnimationStyle: AnimationStyle.noAnimation, ); await tester.pumpWidget(MaterialApp(home: CustomScrollView(slivers: [treeSliver]))); await tester.pump(); expect( find.byType(TreeSliver), paints ..paragraph(offset: const Offset(46.0, 8.0)) ..paragraph() // Icon ..paragraph(offset: const Offset(46.0, 48.0)) ..paragraph() // Icon ..paragraph(offset: const Offset(46.0, 88.0)) ..paragraph(offset: const Offset(56.0, 128.0)) ..paragraph(offset: const Offset(56.0, 168.0)) ..paragraph(offset: const Offset(56.0, 208.0)) ..paragraph(offset: const Offset(46.0, 248.0)), ); // Not in the tree. expect(find.text('alpha'), findsNothing); // Collapse the node now await tester.tap(find.byType(Icon).first); await tester.pump(); // No animating, straight to positions. expect( find.byType(TreeSliver), paints ..paragraph(offset: const Offset(46.0, 8.0)) // First ..paragraph() // Icon ..paragraph(offset: const Offset(46.0, 48.0)) // Second ..paragraph() // alpha icon ..paragraph(offset: const Offset(56.0, 88.0)) // alpha ..paragraph(offset: const Offset(56.0, 128.0)) // beta ..paragraph(offset: const Offset(56.0, 168.0)) // kappa ..paragraph() // Icon ..paragraph(offset: const Offset(46.0, 208.0)) // Third ..paragraph(offset: const Offset(56.0, 248.0)) // gamma ..paragraph(offset: const Offset(56.0, 288.0)) // delta ..paragraph(offset: const Offset(56.0, 328.0)) // epsilon ..paragraph(offset: const Offset(46.0, 368.0)), // Fourth ); expect(find.text('alpha'), findsOneWidget); expect(tester.getRect(find.text('alpha')), const Rect.fromLTRB(56.0, 88.0, 296.0, 112.0)); expect(find.text('beta'), findsOneWidget); expect(tester.getRect(find.text('beta')), const Rect.fromLTRB(56.0, 128.0, 248.0, 152.0)); expect(find.text('kappa'), findsOneWidget); expect(tester.getRect(find.text('kappa')), const Rect.fromLTRB(56.0, 168.0, 296.0, 192.0)); }); testWidgets('Multiple animating node segments', (WidgetTester tester) async { treeNodes = _setUpNodes(); final treeSliver = TreeSliver(tree: treeNodes); await tester.pumpWidget(MaterialApp(home: CustomScrollView(slivers: [treeSliver]))); await tester.pump(); expect( find.byType(TreeSliver), paints ..paragraph(offset: const Offset(46.0, 8.0)) ..paragraph() // Icon ..paragraph(offset: const Offset(46.0, 48.0)) ..paragraph() // Icon ..paragraph(offset: const Offset(46.0, 88.0)) ..paragraph(offset: const Offset(56.0, 128.0)) ..paragraph(offset: const Offset(56.0, 168.0)) ..paragraph(offset: const Offset(56.0, 208.0)) ..paragraph(offset: const Offset(46.0, 248.0)), ); expect(find.text('Second'), findsOneWidget); expect(find.text('alpha'), findsNothing); // Second is collapsed expect(find.text('Third'), findsOneWidget); expect(find.text('gamma'), findsOneWidget); // Third is expanded expect(tester.getRect(find.text('Second')), const Rect.fromLTRB(46.0, 48.0, 334.0, 72.0)); expect(tester.getRect(find.text('Third')), const Rect.fromLTRB(46.0, 88.0, 286.0, 112.0)); expect(tester.getRect(find.text('gamma')), const Rect.fromLTRB(56.0, 128.0, 296.0, 152.0)); // Trigger two animations to run together. // Collapse Third await tester.tap(find.byType(Icon).last); // Expand Second await tester.tap(find.byType(Icon).first); await tester.pump(const Duration(milliseconds: 15)); expect( find.byType(TreeSliver), paints ..paragraph(offset: const Offset(46.0, 8.0)) ..paragraph() // Icon ..paragraph(offset: const Offset(46.0, 48.0)) ..paragraph(offset: const Offset(56.0, 8.0)) // beta entering ..paragraph(offset: const Offset(56.0, 48.0)) // kappa entering ..paragraph() // Icon ..paragraph(offset: const Offset(46.0, 88.0)) ..paragraph(offset: const Offset(56.0, 128.0)) ..paragraph(offset: const Offset(56.0, 168.0)) ..paragraph(offset: const Offset(56.0, 208.0)) ..paragraph(offset: const Offset(46.0, 248.0)), ); // Third is collapsing expect(tester.getRect(find.text('Third')), const Rect.fromLTRB(46.0, 88.0, 286.0, 112.0)); expect(tester.getRect(find.text('gamma')), const Rect.fromLTRB(56.0, 128.0, 296.0, 152.0)); // Second is expanding expect(tester.getRect(find.text('Second')), const Rect.fromLTRB(46.0, 48.0, 334.0, 72.0)); // beta has been added and is animating into view. expect(tester.getRect(find.text('beta')).top.floor(), 8.0); await tester.pump(const Duration(milliseconds: 15)); expect( find.byType(TreeSliver), paints ..paragraph(offset: const Offset(46.0, 8.0)) ..paragraph() // Icon ..paragraph(offset: const Offset(46.0, 48.0)) ..paragraph() // alpha icon animating ..paragraph(offset: const Offset(56.0, -20.0)) // alpha animating ..paragraph(offset: const Offset(56.0, 20.0)) // beta ..paragraph(offset: const Offset(56.0, 60.0)) // kappa ..paragraph() // Icon ..paragraph(offset: const Offset(46.0, 100.0)) // Third // Children of Third are animating, but the expand and // collapse counter each other, so their position is unchanged. ..paragraph(offset: const Offset(56.0, 128.0)) ..paragraph(offset: const Offset(56.0, 168.0)) ..paragraph(offset: const Offset(56.0, 208.0)) ..paragraph(offset: const Offset(46.0, 248.0)), ); // Third is still collapsing. Third is sliding down // as Seconds's children slide in, gamma is still exiting. expect(tester.getRect(find.text('Third')).top.floor(), 100.0); // gamma appears to not have moved, this is because it is // intersecting both animations, the positive offset of // Second animation == the negative offset of Third expect(tester.getRect(find.text('gamma')), const Rect.fromLTRB(56.0, 128.0, 296.0, 152.0)); // Second is still expanding expect(tester.getRect(find.text('Second')), const Rect.fromLTRB(46.0, 48.0, 334.0, 72.0)); // alpha is still animating into view. expect(tester.getRect(find.text('alpha')).top.floor(), -20.0); // Progress the animation further await tester.pump(const Duration(milliseconds: 15)); expect( find.byType(TreeSliver), paints ..paragraph(offset: const Offset(46.0, 8.0)) ..paragraph() // Icon ..paragraph(offset: const Offset(46.0, 48.0)) ..paragraph() // alpha icon animating ..paragraph(offset: const Offset(56.0, -8.0)) // alpha animating ..paragraph(offset: const Offset(56.0, 32.0)) // beta ..paragraph(offset: const Offset(56.0, 72.0)) // kappa ..paragraph() // Icon ..paragraph(offset: const Offset(46.0, 112.0)) // Third // Children of Third are animating, but the expand and // collapse counter each other, so their position is unchanged. ..paragraph(offset: const Offset(56.0, 128.0)) ..paragraph(offset: const Offset(56.0, 168.0)) ..paragraph(offset: const Offset(56.0, 208.0)) ..paragraph(offset: const Offset(46.0, 248.0)), ); // Third is still collapsing. Third is sliding down // as Seconds's children slide in, gamma is still exiting. expect(tester.getRect(find.text('Third')).top.floor(), 112.0); // gamma appears to not have moved, this is because it is // intersecting both animations, the positive offset of // Second animation == the negative offset of Third expect(tester.getRect(find.text('gamma')), const Rect.fromLTRB(56.0, 128.0, 296.0, 152.0)); // Second is still expanding expect(tester.getRect(find.text('Second')), const Rect.fromLTRB(46.0, 48.0, 334.0, 72.0)); // alpha is still animating into view. expect(tester.getRect(find.text('alpha')).top.floor(), -8.0); // Complete the animations await tester.pumpAndSettle(); expect( find.byType(TreeSliver), paints ..paragraph(offset: const Offset(46.0, 8.0)) ..paragraph() // Icon ..paragraph(offset: const Offset(46.0, 48.0)) ..paragraph() // Icon ..paragraph(offset: const Offset(56.0, 88.0)) ..paragraph(offset: const Offset(56.0, 128.0)) ..paragraph(offset: const Offset(56.0, 168.0)) ..paragraph() // Icon ..paragraph(offset: const Offset(46.0, 208.0)) ..paragraph(offset: const Offset(46.0, 248.0)), ); expect(tester.getRect(find.text('Third')), const Rect.fromLTRB(46.0, 208.0, 286.0, 232.0)); // gamma has left the building expect(find.text('gamma'), findsNothing); expect(tester.getRect(find.text('Second')), const Rect.fromLTRB(46.0, 48.0, 334.0, 72.0)); // alpha is in place. expect(tester.getRect(find.text('alpha')), const Rect.fromLTRB(56.0, 88.0, 296.0, 112.0)); }); testWidgets('only paints visible rows', (WidgetTester tester) async { treeNodes = _setUpNodes(); final scrollController = ScrollController(); addTearDown(scrollController.dispose); treeNodes = _setUpNodes(); final treeSliver = TreeSliver(treeRowExtentBuilder: (_, _) => 200, tree: treeNodes); await tester.pumpWidget( MaterialApp( home: CustomScrollView(controller: scrollController, slivers: [treeSliver]), ), ); await tester.pump(); expect(scrollController.position.pixels, 0.0); expect(scrollController.position.maxScrollExtent, 800.0); bool rowNeedsPaint(String row) { return find.text(row).evaluate().first.renderObject!.debugNeedsPaint; } expect(rowNeedsPaint('First'), isFalse); expect(rowNeedsPaint('Second'), isFalse); expect(rowNeedsPaint('Third'), isFalse); expect(find.text('gamma'), findsNothing); // Not visible // Change the scroll offset scrollController.jumpTo(200); await tester.pump(); expect(find.text('First'), findsNothing); expect(rowNeedsPaint('Second'), isFalse); expect(rowNeedsPaint('Third'), isFalse); expect(rowNeedsPaint('gamma'), isFalse); // Now visible }); }