blob: 3e7f9fa65ecce25e393cdd4cc56f5c7a0b34b0e0 [file] [log] [blame]
// 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() {
TestRenderingFlutterBinding.ensureInitialized();
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(
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 = 1e-10;
const double extraValueToHaveRoundingIssues = 1e-11;
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 a bit greater than item extent', () {
final int actual = testGetMaxChildIndexForScrollOffset(
genericItemExtent + extraValueToHaveRoundingIssues,
genericItemExtent,
);
expect(actual, 0);
});
});
test('Implements paintsChild correctly', () {
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(
crossAxisDirection: AxisDirection.right,
offset: ViewportOffset.zero(),
cacheExtent: 0,
children: <RenderSliver>[
childManager.createRenderSliverFillViewport(),
],
);
layout(root);
expect(children.first.parent, isA<RenderSliverMultiBoxAdaptor>());
final RenderSliverMultiBoxAdaptor parent = children.first.parent! as RenderSliverMultiBoxAdaptor;
expect(parent.paintsChild(children[0]), true);
expect(parent.paintsChild(children[1]), false);
expect(parent.paintsChild(children[2]), false);
root.offset = ViewportOffset.fixed(600);
pumpFrame();
expect(parent.paintsChild(children[0]), false);
expect(parent.paintsChild(children[1]), true);
expect(parent.paintsChild(children[2]), false);
root.offset = ViewportOffset.fixed(1200);
pumpFrame();
expect(parent.paintsChild(children[0]), false);
expect(parent.paintsChild(children[1]), false);
expect(parent.paintsChild(children[2]), true);
});
}
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
int getMaxChildIndexForScrollOffset(double scrollOffset, double itemExtent) {
return super.getMaxChildIndexForScrollOffset(scrollOffset, itemExtent);
}
@override
double get itemExtent => throw UnimplementedError();
}