| // 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_test/flutter_test.dart'; |
| |
| import 'rendering_tester.dart'; |
| |
| void main() { |
| test('RenderSliverFixedExtentList layout test - rounding error', () { |
| final List<RenderBox> children = <RenderBox>[ |
| RenderSizedBox(const Size(400.0, 100.0)), |
| RenderSizedBox(const Size(400.0, 100.0)), |
| RenderSizedBox(const Size(400.0, 100.0)), |
| ]; |
| final TestRenderSliverBoxChildManager childManager = TestRenderSliverBoxChildManager( |
| children: children, |
| ); |
| final RenderViewport root = RenderViewport( |
| axisDirection: AxisDirection.down, |
| crossAxisDirection: AxisDirection.right, |
| offset: ViewportOffset.zero(), |
| cacheExtent: 0, |
| children: <RenderSliver>[ |
| childManager.createRenderSliverFillViewport(), |
| ], |
| ); |
| layout(root); |
| expect(children[0].attached, true); |
| expect(children[1].attached, false); |
| |
| root.offset = ViewportOffset.fixed(600); |
| pumpFrame(); |
| expect(children[0].attached, false); |
| expect(children[1].attached, true); |
| |
| // Simulate double precision error. |
| root.offset = ViewportOffset.fixed(1199.999999999998); |
| pumpFrame(); |
| expect(children[1].attached, false); |
| expect(children[2].attached, true); |
| }); |
| |
| group('getMaxChildIndexForScrollOffset', () { |
| // Regression test for https://github.com/flutter/flutter/issues/68182 |
| |
| const double genericItemExtent = 600.0; |
| const double extraValueToNotHaveRoundingIssues = 0.0000001; // 6 zeros |
| const double extraValueToHaveRoundingIssues = 0.00000001; // 7 zeros |
| |
| test('should be 0 when item extent is 0', () { |
| const double offsetValueWhichDoesntCare = 1234; |
| final int actual = testGetMaxChildIndexForScrollOffset(offsetValueWhichDoesntCare, 0); |
| expect(actual, 0); |
| }); |
| |
| test('should be 0 when offset is 0', () { |
| final int actual = testGetMaxChildIndexForScrollOffset(0, genericItemExtent); |
| expect(actual, 0); |
| }); |
| |
| test('should be 0 when offset is equal to item extent', () { |
| final int actual = testGetMaxChildIndexForScrollOffset(genericItemExtent, genericItemExtent); |
| expect(actual, 0); |
| }); |
| |
| test('should be 1 when offset is greater than item extent', () { |
| final int actual = testGetMaxChildIndexForScrollOffset(genericItemExtent + 1, genericItemExtent); |
| expect(actual, 1); |
| }); |
| |
| test('should be 1 when offset is slightly greater than item extent', () { |
| final int actual = testGetMaxChildIndexForScrollOffset( |
| genericItemExtent + extraValueToNotHaveRoundingIssues, |
| genericItemExtent, |
| ); |
| expect(actual, 1); |
| }); |
| |
| test('should be 4 when offset is four times and a half greater than item extent', () { |
| final int actual = testGetMaxChildIndexForScrollOffset(genericItemExtent * 4.5, genericItemExtent); |
| expect(actual, 4); |
| }); |
| |
| test('should be 5 when offset is 6 times greater than item extent', () { |
| const double anotherGenericItemExtent = 414.0; |
| final int actual = testGetMaxChildIndexForScrollOffset( |
| anotherGenericItemExtent * 6, |
| anotherGenericItemExtent, |
| ); |
| expect(actual, 5); |
| }); |
| |
| test('should be 5 when offset is 6 times greater than a specific item extent where the division will return more than 13 zero decimals', () { |
| const double itemExtentSpecificForAProblematicScreenSize = 411.42857142857144; |
| final int actual = testGetMaxChildIndexForScrollOffset( |
| itemExtentSpecificForAProblematicScreenSize * 6 + extraValueToHaveRoundingIssues, |
| itemExtentSpecificForAProblematicScreenSize, |
| ); |
| expect(actual, 5); |
| }); |
| |
| test('should be 0 when offset is 0.00000001 times greater than item extent where the division will return more than 13 zero decimals', () { |
| final int actual = testGetMaxChildIndexForScrollOffset( |
| genericItemExtent + extraValueToHaveRoundingIssues, |
| genericItemExtent, |
| ); |
| expect(actual, 0); |
| }); |
| }); |
| } |
| |
| int testGetMaxChildIndexForScrollOffset(double scrollOffset, double itemExtent) { |
| final TestRenderSliverFixedExtentBoxAdaptor renderSliver = TestRenderSliverFixedExtentBoxAdaptor(); |
| return renderSliver.getMaxChildIndexForScrollOffset(scrollOffset, itemExtent); |
| } |
| |
| class TestRenderSliverBoxChildManager extends RenderSliverBoxChildManager { |
| TestRenderSliverBoxChildManager({ |
| required this.children, |
| }); |
| |
| RenderSliverMultiBoxAdaptor? _renderObject; |
| List<RenderBox> children; |
| |
| RenderSliverFillViewport createRenderSliverFillViewport() { |
| assert(_renderObject == null); |
| _renderObject = RenderSliverFillViewport( |
| childManager: this, |
| ); |
| return _renderObject! as RenderSliverFillViewport; |
| } |
| |
| 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 TestRenderSliverFixedExtentBoxAdaptor extends RenderSliverFixedExtentBoxAdaptor { |
| TestRenderSliverFixedExtentBoxAdaptor() |
| :super(childManager: TestRenderSliverBoxChildManager(children: <RenderBox>[])); |
| |
| @override |
| // ignore: unnecessary_overrides |
| int getMaxChildIndexForScrollOffset(double scrollOffset, double itemExtent) { |
| return super.getMaxChildIndexForScrollOffset(scrollOffset, itemExtent); |
| } |
| |
| @override |
| double get itemExtent => throw UnimplementedError(); |
| } |