| // 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/animation.dart'; |
| import 'package:meta/meta.dart'; |
| |
| import '../flutter_test_alternative.dart'; |
| |
| import 'rendering_tester.dart'; |
| |
| class TestRenderSliverBoxChildManager extends RenderSliverBoxChildManager { |
| TestRenderSliverBoxChildManager({ |
| this.children, |
| }); |
| |
| RenderSliverList _renderObject; |
| List<RenderBox> children; |
| |
| RenderSliverList createRenderObject() { |
| assert(_renderObject == null); |
| _renderObject = RenderSliverList(childManager: this); |
| return _renderObject; |
| } |
| |
| int _currentlyUpdatingChildIndex; |
| |
| @override |
| void createChild(int index, { @required RenderBox after }) { |
| if (index < 0 || index >= children.length) |
| return; |
| try { |
| _currentlyUpdatingChildIndex = index; |
| _renderObject.insert(children[index], after: after); |
| } finally { |
| _currentlyUpdatingChildIndex = null; |
| } |
| } |
| |
| @override |
| void removeChild(RenderBox child) { |
| _renderObject.remove(child); |
| } |
| |
| @override |
| double estimateMaxScrollOffset( |
| SliverConstraints constraints, { |
| int firstIndex, |
| int lastIndex, |
| double leadingScrollOffset, |
| double trailingScrollOffset, |
| }) { |
| assert(lastIndex >= firstIndex); |
| return children.length * (trailingScrollOffset - leadingScrollOffset) / (lastIndex - firstIndex + 1); |
| } |
| |
| @override |
| int get childCount => children.length; |
| |
| @override |
| void didAdoptChild(RenderBox child) { |
| assert(_currentlyUpdatingChildIndex != null); |
| final SliverMultiBoxAdaptorParentData childParentData = child.parentData as SliverMultiBoxAdaptorParentData; |
| childParentData.index = _currentlyUpdatingChildIndex; |
| } |
| |
| @override |
| void setDidUnderflow(bool value) { } |
| } |
| |
| class ViewportOffsetSpy extends ViewportOffset { |
| ViewportOffsetSpy(this._pixels); |
| |
| double _pixels; |
| |
| @override |
| double get pixels => _pixels; |
| |
| bool corrected = false; |
| |
| @override |
| bool applyViewportDimension(double viewportDimension) => true; |
| |
| @override |
| bool applyContentDimensions(double minScrollExtent, double maxScrollExtent) => true; |
| |
| @override |
| void correctBy(double correction) { |
| _pixels += correction; |
| corrected = true; |
| } |
| |
| @override |
| void jumpTo(double pixels) { |
| // Do nothing, not required in test. |
| } |
| |
| @override |
| Future<void> animateTo( |
| double to, { |
| @required Duration duration, |
| @required Curve curve, |
| }) async { |
| // Do nothing, not required in test. |
| } |
| |
| @override |
| ScrollDirection get userScrollDirection => ScrollDirection.idle; |
| |
| @override |
| bool get allowImplicitScrolling => false; |
| } |
| |
| void main() { |
| test('RenderSliverList basic test - down', () { |
| RenderObject inner; |
| RenderBox a, b, c, d, e; |
| final TestRenderSliverBoxChildManager childManager = TestRenderSliverBoxChildManager( |
| children: <RenderBox>[ |
| a = RenderSizedBox(const Size(100.0, 400.0)), |
| b = RenderSizedBox(const Size(100.0, 400.0)), |
| c = RenderSizedBox(const Size(100.0, 400.0)), |
| d = RenderSizedBox(const Size(100.0, 400.0)), |
| e = RenderSizedBox(const Size(100.0, 400.0)), |
| ], |
| ); |
| final RenderViewport root = RenderViewport( |
| axisDirection: AxisDirection.down, |
| crossAxisDirection: AxisDirection.right, |
| offset: ViewportOffset.zero(), |
| cacheExtent: 0.0, |
| children: <RenderSliver>[ |
| inner = childManager.createRenderObject(), |
| ], |
| ); |
| layout(root); |
| |
| expect(root.size.width, equals(800.0)); |
| expect(root.size.height, equals(600.0)); |
| |
| expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 0.0)); |
| expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 400.0)); |
| expect(c.attached, false); |
| expect(d.attached, false); |
| expect(e.attached, false); |
| |
| // make sure that layout is stable by laying out again |
| inner.markNeedsLayout(); |
| pumpFrame(); |
| expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 0.0)); |
| expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 400.0)); |
| expect(c.attached, false); |
| expect(d.attached, false); |
| expect(e.attached, false); |
| |
| // now try various scroll offsets |
| root.offset = ViewportOffset.fixed(200.0); |
| pumpFrame(); |
| expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -200.0)); |
| expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 200.0)); |
| expect(c.attached, false); |
| expect(d.attached, false); |
| expect(e.attached, false); |
| |
| root.offset = ViewportOffset.fixed(600.0); |
| pumpFrame(); |
| expect(a.attached, false); |
| expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -200.0)); |
| expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 200.0)); |
| expect(d.attached, false); |
| expect(e.attached, false); |
| |
| root.offset = ViewportOffset.fixed(900.0); |
| pumpFrame(); |
| expect(a.attached, false); |
| expect(b.attached, false); |
| expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -100.0)); |
| expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 300.0)); |
| expect(e.attached, false); |
| |
| // try going back up |
| root.offset = ViewportOffset.fixed(200.0); |
| pumpFrame(); |
| expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -200.0)); |
| expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 200.0)); |
| expect(c.attached, false); |
| expect(d.attached, false); |
| expect(e.attached, false); |
| }); |
| |
| test('RenderSliverList basic test - up', () { |
| RenderObject inner; |
| RenderBox a, b, c, d, e; |
| final TestRenderSliverBoxChildManager childManager = TestRenderSliverBoxChildManager( |
| children: <RenderBox>[ |
| a = RenderSizedBox(const Size(100.0, 400.0)), |
| b = RenderSizedBox(const Size(100.0, 400.0)), |
| c = RenderSizedBox(const Size(100.0, 400.0)), |
| d = RenderSizedBox(const Size(100.0, 400.0)), |
| e = RenderSizedBox(const Size(100.0, 400.0)), |
| ], |
| ); |
| final RenderViewport root = RenderViewport( |
| axisDirection: AxisDirection.up, |
| crossAxisDirection: AxisDirection.right, |
| offset: ViewportOffset.zero(), |
| children: <RenderSliver>[ |
| inner = childManager.createRenderObject(), |
| ], |
| cacheExtent: 0.0, |
| ); |
| layout(root); |
| |
| expect(root.size.width, equals(800.0)); |
| expect(root.size.height, equals(600.0)); |
| |
| expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 200.0)); |
| expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -200.0)); |
| expect(c.attached, false); |
| expect(d.attached, false); |
| expect(e.attached, false); |
| |
| // make sure that layout is stable by laying out again |
| inner.markNeedsLayout(); |
| pumpFrame(); |
| expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 200.0)); |
| expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -200.0)); |
| expect(c.attached, false); |
| expect(d.attached, false); |
| expect(e.attached, false); |
| |
| // now try various scroll offsets |
| root.offset = ViewportOffset.fixed(200.0); |
| pumpFrame(); |
| expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 400.0)); |
| expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 0.0)); |
| expect(c.attached, false); |
| expect(d.attached, false); |
| expect(e.attached, false); |
| |
| root.offset = ViewportOffset.fixed(600.0); |
| pumpFrame(); |
| expect(a.attached, false); |
| expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 400.0)); |
| expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 0.0)); |
| expect(d.attached, false); |
| expect(e.attached, false); |
| |
| root.offset = ViewportOffset.fixed(900.0); |
| pumpFrame(); |
| expect(a.attached, false); |
| expect(b.attached, false); |
| expect(c.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 300.0)); |
| expect(d.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, -100.0)); |
| expect(e.attached, false); |
| |
| // try going back up |
| root.offset = ViewportOffset.fixed(200.0); |
| pumpFrame(); |
| expect(a.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 400.0)); |
| expect(b.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 0.0)); |
| expect(c.attached, false); |
| expect(d.attached, false); |
| expect(e.attached, false); |
| }); |
| |
| test('SliverList - no zero scroll offset correction', () { |
| RenderSliverList inner; |
| RenderBox a; |
| final TestRenderSliverBoxChildManager childManager = TestRenderSliverBoxChildManager( |
| children: <RenderBox>[ |
| a = RenderSizedBox(const Size(100.0, 400.0)), |
| RenderSizedBox(const Size(100.0, 400.0)), |
| RenderSizedBox(const Size(100.0, 400.0)), |
| RenderSizedBox(const Size(100.0, 400.0)), |
| RenderSizedBox(const Size(100.0, 400.0)), |
| ], |
| ); |
| final RenderViewport root = RenderViewport( |
| axisDirection: AxisDirection.down, |
| crossAxisDirection: AxisDirection.right, |
| offset: ViewportOffset.zero(), |
| children: <RenderSliver>[ |
| inner = childManager.createRenderObject(), |
| ], |
| ); |
| layout(root); |
| |
| final SliverMultiBoxAdaptorParentData parentData = a.parentData as SliverMultiBoxAdaptorParentData; |
| parentData.layoutOffset = 0.001; |
| |
| root.offset = ViewportOffset.fixed(900.0); |
| pumpFrame(); |
| |
| root.offset = ViewportOffset.fixed(0.0); |
| pumpFrame(); |
| |
| expect(inner.geometry.scrollOffsetCorrection, isNull); |
| }); |
| |
| test('SliverList - no correction when tiny double precision error', () { |
| RenderSliverList inner; |
| RenderBox a; |
| final TestRenderSliverBoxChildManager childManager = TestRenderSliverBoxChildManager( |
| children: <RenderBox>[ |
| a = RenderSizedBox(const Size(100.0, 400.0)), |
| RenderSizedBox(const Size(100.0, 400.0)), |
| RenderSizedBox(const Size(100.0, 400.0)), |
| RenderSizedBox(const Size(100.0, 400.0)), |
| RenderSizedBox(const Size(100.0, 400.0)), |
| ], |
| ); |
| inner = childManager.createRenderObject(); |
| final RenderViewport root = RenderViewport( |
| axisDirection: AxisDirection.down, |
| crossAxisDirection: AxisDirection.right, |
| offset: ViewportOffset.zero(), |
| children: <RenderSliver>[ |
| inner, |
| ], |
| ); |
| layout(root); |
| |
| final SliverMultiBoxAdaptorParentData parentData = a.parentData as SliverMultiBoxAdaptorParentData; |
| // Simulate double precision error. |
| parentData.layoutOffset = -0.0000000000001; |
| |
| root.offset = ViewportOffset.fixed(900.0); |
| pumpFrame(); |
| |
| final ViewportOffsetSpy spy = ViewportOffsetSpy(0.0); |
| root.offset = spy; |
| pumpFrame(); |
| |
| expect(spy.corrected, false); |
| }); |
| |
| test('SliverMultiBoxAdaptorParentData.toString', () { |
| final SliverMultiBoxAdaptorParentData candidate = SliverMultiBoxAdaptorParentData(); |
| expect(candidate.keepAlive, isFalse); |
| expect(candidate.index, isNull); |
| expect(candidate.toString(), 'index=null; layoutOffset=None'); |
| candidate.keepAlive = null; |
| expect(candidate.toString(), 'index=null; layoutOffset=None'); |
| candidate.keepAlive = true; |
| expect(candidate.toString(), 'index=null; keepAlive; layoutOffset=None'); |
| candidate.keepAlive = false; |
| expect(candidate.toString(), 'index=null; layoutOffset=None'); |
| candidate.index = 0; |
| expect(candidate.toString(), 'index=0; layoutOffset=None'); |
| candidate.index = 1; |
| expect(candidate.toString(), 'index=1; layoutOffset=None'); |
| candidate.index = -1; |
| expect(candidate.toString(), 'index=-1; layoutOffset=None'); |
| candidate.layoutOffset = 100.0; |
| expect(candidate.toString(), 'index=-1; layoutOffset=100.0'); |
| }); |
| } |