feat(ui): update scrollable_positioned_list with the latest changes.

Signed-off-by: xsahil03x <[email protected]>
This commit is contained in:
Sahil Kumar
2023-05-03 15:47:53 +05:30
committed by xsahil03x
parent c50a435515
commit b4413be47d
14 changed files with 2333 additions and 397 deletions
@@ -2,13 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pedantic/pedantic.dart';
import 'package:stream_chat_flutter/scrollable_positioned_list/scrollable_positioned_list.dart';
const screenHeight = 400.0;
const screenHeight = 100.0;
const screenWidth = 400.0;
const itemWidth = screenWidth / 10.0;
const itemCount = 500;
@@ -46,6 +45,11 @@ void main() {
);
}
final fadeTransitionFinder = find.descendant(
of: find.byType(ScrollablePositionedList),
matching: find.byType(FadeTransition),
);
testWidgets('List positioned with 0 at left', (WidgetTester tester) async {
final itemPositionsListener = ItemPositionsListener.create();
await setUpWidgetTest(tester, itemPositionsListener: itemPositionsListener);
@@ -172,7 +176,7 @@ void main() {
await tester.pumpAndSettle();
expect(tester.getTopLeft(find.text('Item 100')).dx, 0);
expect(tester.getBottomRight(find.text('Item 109')).dy, screenWidth);
expect(tester.getBottomRight(find.text('Item 109')).dy, screenHeight);
expect(
itemPositionsListener.itemPositions.value
@@ -196,6 +200,31 @@ void main() {
1);
});
testWidgets('Scroll to 20 without fading', (WidgetTester tester) async {
final itemScrollController = ItemScrollController();
final itemPositionsListener = ItemPositionsListener.create();
await setUpWidgetTest(tester,
itemScrollController: itemScrollController,
itemPositionsListener: itemPositionsListener);
var fadeTransition = tester.widget<FadeTransition>(fadeTransitionFinder);
final initialOpacity = fadeTransition.opacity;
unawaited(
itemScrollController.scrollTo(index: 20, duration: scrollDuration));
await tester.pump();
await tester.pump();
await tester.pump(scrollDuration ~/ 2);
fadeTransition = tester.widget<FadeTransition>(fadeTransitionFinder);
expect(fadeTransition.opacity, initialOpacity);
await tester.pumpAndSettle();
expect(find.text('Item 14'), findsNothing);
expect(find.text('Item 20'), findsOneWidget);
});
testWidgets('padding test - centered sliver at left',
(WidgetTester tester) async {
final itemScrollController = ItemScrollController();
@@ -360,4 +360,55 @@ void main() {
.itemTrailingEdge,
1);
});
testWidgets('Does not crash when updated offscreen',
(WidgetTester tester) async {
late StateSetter setState;
var updated = false;
// There's 0 relayout boundaries in this subtree.
final widget = StatefulBuilder(builder: (context, stateSetter) {
setState = stateSetter;
return Positioned(
left: 0,
right: 0,
child: PositionedList(
shrinkWrap: true,
itemCount: 1,
// When `updated` becomes true this line inserts a
// RenderIndexedSemantics to the render tree.
addSemanticIndexes: updated,
itemBuilder: (context, index) => const SizedBox(height: itemHeight),
));
});
await tester.pumpWidget(Directionality(
textDirection: TextDirection.ltr,
child: Overlay(
initialEntries: [
OverlayEntry(builder: (context) => widget, maintainState: true),
],
),
));
// Insert a new opaque OverlayEntry that would prevent the first OverlayEntry
// from doing re-layout. Since there's no relayout boundaries in the first
// OverlayEntry, no dirty RenderObjects in its render subtree can update
// layout.
final newOverlay =
OverlayEntry(builder: (context) => const SizedBox.expand(), opaque: true);
tester.state<OverlayState>(find.byType(Overlay)).insert(newOverlay);
await tester.pump();
// Update the list item's render tree. A new RenderObjectElement is
// inflated, registeredElement.renderObject will point to this new
// RenderObjectElement's RenderObject (RenderIndexedSemantics), which has
// never been laid out.
setState(() {
updated = true;
});
await tester.pump();
expect(tester.takeException(), isNull);
});
}
@@ -2,10 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pedantic/pedantic.dart';
import 'package:stream_chat_flutter/scrollable_positioned_list/scrollable_positioned_list.dart';
const screenHeight = 400.0;
@@ -2,12 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pedantic/pedantic.dart';
import 'package:stream_chat_flutter/scrollable_positioned_list/scrollable_positioned_list.dart';
import 'package:stream_chat_flutter/scrollable_positioned_list/src/scroll_view.dart';
@@ -48,8 +48,7 @@ void main() {
itemCount: itemCount,
itemScrollController: itemScrollController,
itemBuilder: (context, index) {
assert(index >= 0 && index <= itemCount - 1,
'''index needs to be bigger or equal to 0 and smallert than itemCount -1''');
assert(index >= 0 && index <= itemCount - 1);
return SizedBox(
height:
variableHeight ? (itemHeight + (index % 13) * 5) : itemHeight,
@@ -71,6 +70,11 @@ void main() {
);
}
final fadeTransitionFinder = find.descendant(
of: find.byType(ScrollablePositionedList),
matching: find.byType(FadeTransition),
);
testWidgets('List positioned with 0 at top', (WidgetTester tester) async {
final itemPositionsListener = ItemPositionsListener.create();
await setUpWidgetTest(tester, itemPositionsListener: itemPositionsListener);
@@ -394,11 +398,7 @@ void main() {
itemScrollController: itemScrollController,
itemPositionsListener: itemPositionsListener);
var fadeTransition = tester.widget<FadeTransition>(find
.descendant(
of: find.byType(ScrollablePositionedList),
matching: find.byType(FadeTransition))
.last);
var fadeTransition = tester.widget<FadeTransition>(fadeTransitionFinder);
final initialOpacity = fadeTransition.opacity;
unawaited(
@@ -407,11 +407,7 @@ void main() {
await tester.pump();
await tester.pump(scrollDuration ~/ 2);
fadeTransition = tester.widget<FadeTransition>(find
.descendant(
of: find.byType(ScrollablePositionedList),
matching: find.byType(FadeTransition))
.last);
fadeTransition = tester.widget<FadeTransition>(fadeTransitionFinder);
expect(fadeTransition.opacity, initialOpacity);
await tester.pumpAndSettle();
@@ -456,10 +452,6 @@ void main() {
final itemScrollController = ItemScrollController();
await setUpWidgetTest(tester, itemScrollController: itemScrollController);
final fadeTransitionFinder = find.descendant(
of: find.byType(ScrollablePositionedList),
matching: find.byType(FadeTransition));
unawaited(
itemScrollController.scrollTo(index: 100, duration: scrollDuration));
await tester.pump();
@@ -533,26 +525,14 @@ void main() {
await tester.pump();
await tester.pump();
expect(
tester
.widget<FadeTransition>(find
.descendant(
of: find.byType(ScrollablePositionedList),
matching: find.byType(FadeTransition))
.last)
.opacity
.value,
closeTo(0, 0.01));
tester.widget<FadeTransition>(fadeTransitionFinder.last).opacity.value,
closeTo(0, 0.01),
);
await tester.pump(scrollDuration + scrollDurationTolerance);
expect(
tester
.widget<FadeTransition>(find
.descendant(
of: find.byType(ScrollablePositionedList),
matching: find.byType(FadeTransition))
.last)
.opacity
.value,
closeTo(1, 0.01));
tester.widget<FadeTransition>(fadeTransitionFinder.last).opacity.value,
closeTo(1, 0.01),
);
expect(find.text('Item 0'), findsOneWidget);
expect(tester.getTopLeft(find.text('Item 0')).dy, 0);
@@ -610,15 +590,9 @@ void main() {
expect(tester.getTopLeft(find.text('Item 10')).dy, 0);
expect(tester.getBottomLeft(find.text('Item 19')).dy, screenHeight);
expect(
tester
.widget<FadeTransition>(find
.descendant(
of: find.byType(ScrollablePositionedList),
matching: find.byType(FadeTransition))
.last)
.opacity
.value,
closeTo(0.5, 0.01));
tester.widget<FadeTransition>(fadeTransitionFinder.last).opacity.value,
closeTo(0.5, 0.01),
);
await tester.pumpAndSettle();
});
@@ -899,11 +873,7 @@ void main() {
await tester.pump();
expect(tester.getTopLeft(find.text('Item 9')).dy, 0);
final fadeTransition = tester.widget<FadeTransition>(find
.descendant(
of: find.byType(ScrollablePositionedList),
matching: find.byType(FadeTransition))
.last);
final fadeTransition = tester.widget<FadeTransition>(fadeTransitionFinder);
expect(fadeTransition.opacity.value, 1.0);
await tester.pumpAndSettle();
@@ -923,21 +893,12 @@ void main() {
await tester.pump();
expect(tester.getTopLeft(find.text('Item 10')).dy, 0);
final fadeTransition = tester.widget<FadeTransition>(find
.descendant(
of: find.byType(ScrollablePositionedList),
matching: find.byType(FadeTransition))
.last);
final fadeTransition = tester.widget<FadeTransition>(fadeTransitionFinder);
expect(fadeTransition.opacity.value, 1.0);
await tester.pumpAndSettle();
});
final fadeTransitionFinder = find.descendant(
of: find.byType(ScrollablePositionedList),
matching: find.byType(FadeTransition),
);
testWidgets('Scroll to 0 stop before half way', (WidgetTester tester) async {
final itemScrollController = ItemScrollController();
await setUpWidgetTest(tester, itemScrollController: itemScrollController);
@@ -1022,14 +983,13 @@ void main() {
itemScrollController.scrollTo(index: 0, duration: scrollDuration));
await tester.pump();
await tester.pump();
await tester.pump(scrollDuration ~/ 2 + scrollDuration ~/ 20);
await tester.pump(scrollDuration ~/ 2);
await tester.tap(find.byType(ScrollablePositionedList));
await tester.pump();
expect(tester.getTopLeft(find.text('Item 9')).dy, closeTo(0, tolerance));
final fadeTransition = tester.widget<FadeTransition>(fadeTransitionFinder);
expect(fadeTransition.opacity.value, 1.0);
expect(tester.getTopLeft(find.text('Item 90')).dy, 0);
expect(fadeTransitionFinder, findsNWidgets(1));
await tester.pumpAndSettle();
});
@@ -1098,6 +1058,34 @@ void main() {
expect(find.text('Item 100'), findsNothing);
});
testWidgets("Second scroll future doesn't complete until scroll is done",
(WidgetTester tester) async {
final itemScrollController = ItemScrollController();
await setUpWidgetTest(tester, itemScrollController: itemScrollController);
unawaited(
itemScrollController.scrollTo(index: 100, duration: scrollDuration));
await tester.pump();
await tester.pump();
await tester.pump(scrollDuration ~/ 2);
final scrollFuture2 =
itemScrollController.scrollTo(index: 250, duration: scrollDuration);
var futureComplete = false;
unawaited(scrollFuture2.then((_) => futureComplete = true));
await tester.pump();
await tester.pump();
await tester.pump(scrollDuration ~/ 2);
expect(futureComplete, isFalse);
await tester.pumpAndSettle();
expect(futureComplete, isTrue);
});
testWidgets('Scroll to 250, scroll to 100, scroll to 0 half way',
(WidgetTester tester) async {
final itemScrollController = ItemScrollController();
@@ -1145,7 +1133,7 @@ void main() {
}, skip: true);
testWidgets(
'''Jump to 400 at bottom, manually scroll, scroll to 100 at bottom and back''',
'Jump to 400 at bottom, manually scroll, scroll to 100 at bottom and back',
(WidgetTester tester) async {
final itemScrollController = ItemScrollController();
final itemPositionsListener = ItemPositionsListener.create();
@@ -1664,7 +1652,7 @@ void main() {
});
testWidgets(
'''Maintain programmatic and user position (9 half way off top) in page view''',
'Maintain programmatic and user position (9 half way off top) in page view',
(WidgetTester tester) async {
final itemPositionsListener = ItemPositionsListener.create();
final itemScrollController = ItemScrollController();
@@ -1751,21 +1739,21 @@ void main() {
MaterialApp(
home: ValueListenableBuilder<int>(
valueListenable: itemCount,
builder: (context, itemCount, child) =>
ScrollablePositionedList.builder(
initialScrollIndex: min(100, itemCount),
itemCount: itemCount,
itemScrollController: itemScrollController,
itemPositionsListener: itemPositionsListener,
itemBuilder: (context, index) {
assert(index >= 0 && index <= itemCount - 1,
'index not bigger than 0 and smaller than itemCount - 1');
return SizedBox(
height: itemHeight,
child: Text('Item $index'),
);
},
),
builder: (context, itemCount, child) {
return ScrollablePositionedList.builder(
initialScrollIndex: min(100, itemCount),
itemCount: itemCount,
itemScrollController: itemScrollController,
itemPositionsListener: itemPositionsListener,
itemBuilder: (context, index) {
assert(index >= 0 && index <= itemCount - 1);
return SizedBox(
height: itemHeight,
child: Text('Item $index'),
);
},
);
},
),
),
);
@@ -1795,19 +1783,19 @@ void main() {
MaterialApp(
home: ValueListenableBuilder<int>(
valueListenable: itemCount,
builder: (context, itemCount, child) =>
ScrollablePositionedList.builder(
initialScrollIndex: min(100, itemCount - 1),
itemCount: itemCount,
itemBuilder: (context, index) {
assert(index >= 0 && index <= itemCount - 1,
'index not bigger than 0 and smaller than itemCount -1');
return SizedBox(
height: itemHeight,
child: Text('Item $index'),
);
},
),
builder: (context, itemCount, child) {
return ScrollablePositionedList.builder(
initialScrollIndex: min(100, itemCount - 1),
itemCount: itemCount,
itemBuilder: (context, index) {
assert(index >= 0 && index <= itemCount - 1);
return SizedBox(
height: itemHeight,
child: Text('Item $index'),
);
},
);
},
),
),
);
@@ -1834,19 +1822,19 @@ void main() {
MaterialApp(
home: ValueListenableBuilder<int>(
valueListenable: itemCount,
builder: (context, itemCount, child) =>
ScrollablePositionedList.builder(
initialScrollIndex: itemCount - 1,
itemCount: itemCount,
itemBuilder: (context, index) {
assert(index >= 0 && index <= itemCount - 1,
'index not bigger than 0 and smaller than itemCount -1');
return SizedBox(
height: itemHeight,
child: Text('Item $index'),
);
},
),
builder: (context, itemCount, child) {
return ScrollablePositionedList.builder(
initialScrollIndex: itemCount - 1,
itemCount: itemCount,
itemBuilder: (context, index) {
assert(index >= 0 && index <= itemCount - 1);
return SizedBox(
height: itemHeight,
child: Text('Item $index'),
);
},
);
},
),
),
);
@@ -1878,11 +1866,7 @@ void main() {
minCacheExtent: 10,
);
var fadeTransition = tester.widget<FadeTransition>(find
.descendant(
of: find.byType(ScrollablePositionedList),
matching: find.byType(FadeTransition))
.last);
var fadeTransition = tester.widget<FadeTransition>(fadeTransitionFinder);
final initialOpacity = fadeTransition.opacity;
unawaited(
@@ -1891,11 +1875,7 @@ void main() {
await tester.pump();
await tester.pump(scrollDuration ~/ 2);
fadeTransition = tester.widget<FadeTransition>(find
.descendant(
of: find.byType(ScrollablePositionedList),
matching: find.byType(FadeTransition))
.last);
fadeTransition = tester.widget<FadeTransition>(fadeTransitionFinder);
expect(fadeTransition.opacity, initialOpacity);
await tester.pumpAndSettle();
@@ -1914,11 +1894,9 @@ void main() {
minCacheExtent: itemHeight * 200,
);
var fadeTransition = tester.widget<FadeTransition>(find
.descendant(
of: find.byType(ScrollablePositionedList),
matching: find.byType(FadeTransition))
.last);
var fadeTransition = tester.widget<FadeTransition>(
fadeTransitionFinder,
);
final initialOpacity = fadeTransition.opacity;
unawaited(
@@ -1927,11 +1905,7 @@ void main() {
await tester.pump();
await tester.pump(scrollDuration ~/ 2);
fadeTransition = tester.widget<FadeTransition>(find
.descendant(
of: find.byType(ScrollablePositionedList),
matching: find.byType(FadeTransition))
.last);
fadeTransition = tester.widget<FadeTransition>(fadeTransitionFinder);
expect(fadeTransition.opacity, initialOpacity);
await tester.pumpAndSettle();
@@ -1965,17 +1939,21 @@ void main() {
MaterialApp(
home: ValueListenableBuilder<Key>(
valueListenable: key,
builder: (context, key, child) => Container(
key: key,
child: ScrollablePositionedList.builder(
itemCount: 200,
itemScrollController: itemScrollController,
itemBuilder: (context, index) => SizedBox(
height: itemHeight,
child: Text('Item $index'),
builder: (context, key, child) {
return Container(
key: key,
child: ScrollablePositionedList.builder(
itemCount: 200,
itemScrollController: itemScrollController,
itemBuilder: (context, index) {
return SizedBox(
height: itemHeight,
child: Text('Item $index'),
);
},
),
),
),
);
},
),
),
);
@@ -2054,15 +2032,19 @@ void main() {
MaterialApp(
home: ValueListenableBuilder<Key>(
valueListenable: key,
builder: (context, key, child) => ScrollablePositionedList.builder(
key: key,
itemCount: 10,
itemScrollController: itemScrollController,
itemBuilder: (context, index) => SizedBox(
height: itemHeight,
child: Text('Item $index'),
),
),
builder: (context, key, child) {
return ScrollablePositionedList.builder(
key: key,
itemCount: 10,
itemScrollController: itemScrollController,
itemBuilder: (context, index) {
return SizedBox(
height: itemHeight,
child: Text('Item $index'),
);
},
);
},
),
),
);
@@ -2084,17 +2066,21 @@ void main() {
MaterialApp(
home: ValueListenableBuilder<Key>(
valueListenable: key,
builder: (context, key, child) => Container(
key: key,
child: ScrollablePositionedList.builder(
itemCount: 100,
itemScrollController: itemScrollController,
itemBuilder: (context, index) => SizedBox(
height: itemHeight,
child: Text('Item $index'),
builder: (context, key, child) {
return Container(
key: key,
child: ScrollablePositionedList.builder(
itemCount: 100,
itemScrollController: itemScrollController,
itemBuilder: (context, index) {
return SizedBox(
height: itemHeight,
child: Text('Item $index'),
);
},
),
),
),
);
},
),
),
);
@@ -2124,18 +2110,22 @@ void main() {
MaterialApp(
home: ValueListenableBuilder<Key>(
valueListenable: containerKey,
builder: (context, key, child) => Container(
key: key,
child: ScrollablePositionedList.builder(
key: scrollKey,
itemCount: 100,
itemScrollController: itemScrollController,
itemBuilder: (context, index) => SizedBox(
height: itemHeight,
child: Text('Item $index'),
builder: (context, key, child) {
return Container(
key: key,
child: ScrollablePositionedList.builder(
key: scrollKey,
itemCount: 100,
itemScrollController: itemScrollController,
itemBuilder: (context, index) {
return SizedBox(
height: itemHeight,
child: Text('Item $index'),
);
},
),
),
),
);
},
),
),
);
@@ -2166,15 +2156,18 @@ void main() {
MaterialApp(
home: ValueListenableBuilder<ItemScrollController>(
valueListenable: itemScrollControllerListenable,
builder: (context, itemScrollController, child) =>
ScrollablePositionedList.builder(
itemCount: 100,
itemScrollController: itemScrollController,
itemBuilder: (context, index) => SizedBox(
height: itemHeight,
child: Text('Item $index'),
),
),
builder: (context, itemScrollController, child) {
return ScrollablePositionedList.builder(
itemCount: 100,
itemScrollController: itemScrollController,
itemBuilder: (context, index) {
return SizedBox(
height: itemHeight,
child: Text('Item $index'),
);
},
);
},
),
),
);
@@ -2215,29 +2208,35 @@ void main() {
Expanded(
child: ValueListenableBuilder<ItemScrollController>(
valueListenable: topItemScrollControllerListenable,
builder: (context, itemScrollController, child) =>
ScrollablePositionedList.builder(
itemCount: 100,
itemScrollController: itemScrollController,
itemBuilder: (context, index) => SizedBox(
height: itemHeight,
child: Text('Item $index'),
),
),
builder: (context, itemScrollController, child) {
return ScrollablePositionedList.builder(
itemCount: 100,
itemScrollController: itemScrollController,
itemBuilder: (context, index) {
return SizedBox(
height: itemHeight,
child: Text('Item $index'),
);
},
);
},
),
),
Expanded(
child: ValueListenableBuilder<ItemScrollController>(
valueListenable: bottomItemScrollControllerListenable,
builder: (context, itemScrollController, child) =>
ScrollablePositionedList.builder(
itemCount: 100,
itemScrollController: itemScrollController,
itemBuilder: (context, index) => SizedBox(
height: itemHeight,
child: Text('Item $index'),
),
),
builder: (context, itemScrollController, child) {
return ScrollablePositionedList.builder(
itemCount: 100,
itemScrollController: itemScrollController,
itemBuilder: (context, index) {
return SizedBox(
height: itemHeight,
child: Text('Item $index'),
);
},
);
},
),
),
],
@@ -2,10 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pedantic/pedantic.dart';
import 'package:stream_chat_flutter/scrollable_positioned_list/scrollable_positioned_list.dart';
import 'package:stream_chat_flutter/scrollable_positioned_list/src/scroll_view.dart';
@@ -497,20 +496,21 @@ void main() {
MaterialApp(
home: ValueListenableBuilder<int>(
valueListenable: itemCount,
builder: (context, itemCount, child) =>
ScrollablePositionedList.separated(
itemCount: itemCount,
itemScrollController: itemScrollController,
itemPositionsListener: itemPositionsListener,
itemBuilder: (context, index) => SizedBox(
height: itemHeight,
child: Text('Item $index'),
),
separatorBuilder: (context, index) => SizedBox(
height: separatorHeight,
child: Text('Separator $index'),
),
),
builder: (context, itemCount, child) {
return ScrollablePositionedList.separated(
itemCount: itemCount,
itemScrollController: itemScrollController,
itemPositionsListener: itemPositionsListener,
itemBuilder: (context, index) => SizedBox(
height: itemHeight,
child: Text('Item $index'),
),
separatorBuilder: (context, index) => SizedBox(
height: separatorHeight,
child: Text('Separator $index'),
),
);
},
),
),
);
@@ -538,20 +538,21 @@ void main() {
MaterialApp(
home: ValueListenableBuilder<int>(
valueListenable: itemCount,
builder: (context, itemCount, child) =>
ScrollablePositionedList.separated(
itemCount: itemCount,
itemScrollController: itemScrollController,
itemPositionsListener: itemPositionsListener,
itemBuilder: (context, index) => SizedBox(
height: itemHeight,
child: Text('Item $index'),
),
separatorBuilder: (context, index) => SizedBox(
height: separatorHeight,
child: Text('Separator $index'),
),
),
builder: (context, itemCount, child) {
return ScrollablePositionedList.separated(
itemCount: itemCount,
itemScrollController: itemScrollController,
itemPositionsListener: itemPositionsListener,
itemBuilder: (context, index) => SizedBox(
height: itemHeight,
child: Text('Item $index'),
),
separatorBuilder: (context, index) => SizedBox(
height: separatorHeight,
child: Text('Separator $index'),
),
);
},
),
),
);
@@ -2,10 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pedantic/pedantic.dart';
import 'package:stream_chat_flutter/scrollable_positioned_list/scrollable_positioned_list.dart';
const screenHeight = 400.0;
@@ -0,0 +1,473 @@
// Copyright 2019 The Fuchsia 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/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:stream_chat_flutter/scrollable_positioned_list/scrollable_positioned_list.dart';
import 'package:stream_chat_flutter/scrollable_positioned_list/src/item_positions_notifier.dart';
import 'package:stream_chat_flutter/scrollable_positioned_list/src/positioned_list.dart';
const screenHeight = 400.0;
const screenWidth = 400.0;
const itemHeight = screenHeight / 10.0;
const defaultItemCount = 500;
void main() {
final itemPositionsNotifier = ItemPositionsListener.create();
Future<void> setUpWidgetTest(
WidgetTester tester, {
int topItem = 0,
Key? key,
ScrollController? scrollController,
double anchor = 0,
int itemCount = defaultItemCount,
bool reverse = false,
}) async {
tester.binding.window.devicePixelRatioTestValue = 1.0;
tester.binding.window.physicalSizeTestValue =
const Size(screenWidth, screenHeight);
await tester.pumpWidget(
MaterialApp(
// Use flex layout to ensure that the minimum height is not limited to screenHeight
home: Column(children: [
// Use Constrained to make max height not more than screenHeight
ConstrainedBox(
constraints:
const BoxConstraints(maxHeight: screenHeight, maxWidth: screenWidth),
child: PositionedList(
key: key,
itemCount: itemCount,
positionedIndex: topItem,
alignment: anchor,
controller: scrollController,
itemBuilder: (context, index) => SizedBox(
height: itemHeight,
child: Text('Item $index'),
),
itemPositionsNotifier:
itemPositionsNotifier as ItemPositionsNotifier,
shrinkWrap: true,
reverse: reverse,
),
),
]),
),
);
}
testWidgets('short list with shrink wrap', (WidgetTester tester) async {
const itemCount = 5;
const key = Key('short_list');
await setUpWidgetTest(tester, itemCount: itemCount, key: key);
await tester.pump();
expect(
tester.getBottomRight(find.text('Item 4')).dy, itemHeight * itemCount);
expect(find.text('Item 4'), findsOneWidget);
expect(find.text('Item 5'), findsNothing);
final positionList = find.byKey(key);
expect(tester.getBottomRight(positionList).dy, itemHeight * itemCount);
expect(
itemPositionsNotifier.itemPositions.value
.firstWhere((position) => position.index == 0)
.itemLeadingEdge,
0);
expect(
itemPositionsNotifier.itemPositions.value
.firstWhere((position) => position.index == 4)
.itemTrailingEdge,
1.0);
});
testWidgets('List positioned with 0 at top and shrink wrap',
(WidgetTester tester) async {
await setUpWidgetTest(tester);
await tester.pump();
expect(find.text('Item 0'), findsOneWidget);
expect(find.text('Item 9'), findsOneWidget);
expect(find.text('Item 10'), findsNothing);
expect(
itemPositionsNotifier.itemPositions.value
.firstWhere((position) => position.index == 0)
.itemLeadingEdge,
0);
expect(
itemPositionsNotifier.itemPositions.value
.firstWhere((position) => position.index == 9)
.itemTrailingEdge,
1);
expect(
itemPositionsNotifier.itemPositions.value
.firstWhere((position) => position.index == 10)
.itemLeadingEdge,
1);
expect(
itemPositionsNotifier.itemPositions.value
.firstWhere((position) => position.index == 10)
.itemTrailingEdge,
11 / 10);
});
testWidgets('List positioned with 5 at top and shrink wrap',
(WidgetTester tester) async {
await setUpWidgetTest(tester, topItem: 5);
await tester.pump();
expect(find.text('Item 4'), findsNothing);
expect(find.text('Item 5'), findsOneWidget);
expect(find.text('Item 14'), findsOneWidget);
expect(find.text('Item 15'), findsNothing);
expect(
itemPositionsNotifier.itemPositions.value
.firstWhere((position) => position.index == 4)
.itemLeadingEdge,
-1 / 10);
expect(
itemPositionsNotifier.itemPositions.value
.firstWhere((position) => position.index == 4)
.itemTrailingEdge,
0);
expect(
itemPositionsNotifier.itemPositions.value
.firstWhere((position) => position.index == 5)
.itemLeadingEdge,
0);
expect(
itemPositionsNotifier.itemPositions.value
.firstWhere((position) => position.index == 14)
.itemTrailingEdge,
1);
});
testWidgets('List positioned with 20 at bottom and shrink wrap',
(WidgetTester tester) async {
await setUpWidgetTest(tester, topItem: 20, anchor: 1);
await tester.pump();
expect(find.text('Item 20'), findsNothing);
expect(find.text('Item 19'), findsOneWidget);
expect(find.text('Item 10'), findsOneWidget);
expect(
itemPositionsNotifier.itemPositions.value
.firstWhere((position) => position.index == 10)
.itemLeadingEdge,
0);
expect(
itemPositionsNotifier.itemPositions.value
.firstWhere((position) => position.index == 19)
.itemLeadingEdge,
9 / 10);
expect(
itemPositionsNotifier.itemPositions.value
.firstWhere((position) => position.index == 19)
.itemTrailingEdge,
1);
expect(
itemPositionsNotifier.itemPositions.value
.firstWhere((position) => position.index == 20)
.itemLeadingEdge,
1);
});
testWidgets('List positioned with 20 at halfway and shrink wrap',
(WidgetTester tester) async {
await setUpWidgetTest(tester, topItem: 20, anchor: 0.5);
await tester.pump();
expect(
itemPositionsNotifier.itemPositions.value
.firstWhere((position) => position.index == 20)
.itemLeadingEdge,
0.5);
expect(
itemPositionsNotifier.itemPositions.value
.firstWhere((position) => position.index == 20)
.itemTrailingEdge,
0.5 + itemHeight / screenHeight);
});
testWidgets('List positioned with 20 half off top of screen and shrink wrap',
(WidgetTester tester) async {
await setUpWidgetTest(tester,
topItem: 20, anchor: -(itemHeight / screenHeight) / 2);
await tester.pump();
expect(
itemPositionsNotifier.itemPositions.value
.firstWhere((position) => position.index == 20)
.itemLeadingEdge,
-(itemHeight / screenHeight) / 2);
expect(
itemPositionsNotifier.itemPositions.value
.firstWhere((position) => position.index == 20)
.itemTrailingEdge,
(itemHeight / screenHeight) / 2);
});
testWidgets('List positioned with 5 at top then scroll up 2 and shrink wrap',
(WidgetTester tester) async {
await setUpWidgetTest(tester, topItem: 5);
await tester.drag(
find.byType(PositionedList), const Offset(0, itemHeight * 2));
await tester.pump();
expect(find.text('Item 2'), findsNothing);
expect(find.text('Item 3'), findsOneWidget);
expect(find.text('Item 12'), findsOneWidget);
expect(find.text('Item 13'), findsNothing);
expect(
itemPositionsNotifier.itemPositions.value
.firstWhere((position) => position.index == 2)
.itemLeadingEdge,
-1 / 10);
expect(
itemPositionsNotifier.itemPositions.value
.firstWhere((position) => position.index == 3)
.itemLeadingEdge,
0);
expect(
itemPositionsNotifier.itemPositions.value
.firstWhere((position) => position.index == 12)
.itemTrailingEdge,
1);
});
testWidgets(
'List positioned with 5 at top then scroll down 1/2 and shrink wrap',
(WidgetTester tester) async {
await setUpWidgetTest(tester, topItem: 5);
await tester.drag(
find.byType(PositionedList), const Offset(0, -1 / 2 * itemHeight));
await tester.pump();
expect(
itemPositionsNotifier.itemPositions.value
.firstWhere((position) => position.index == 5)
.itemTrailingEdge,
1 / 20);
expect(
itemPositionsNotifier.itemPositions.value
.firstWhere((position) => position.index == 14)
.itemLeadingEdge,
17 / 20);
});
testWidgets('List positioned with 0 at top scroll up 5 and shrink wrap',
(WidgetTester tester) async {
final scrollController = ScrollController();
await setUpWidgetTest(tester, scrollController: scrollController);
await tester.pump();
scrollController.jumpTo(itemHeight * 5);
await tester.pump();
await tester.pumpAndSettle();
expect(find.text('Item 4'), findsNothing);
expect(find.text('Item 5'), findsOneWidget);
expect(find.text('Item 14'), findsOneWidget);
expect(find.text('Item 15'), findsNothing);
expect(
itemPositionsNotifier.itemPositions.value
.firstWhere((position) => position.index == 5)
.itemLeadingEdge,
0);
expect(
itemPositionsNotifier.itemPositions.value
.firstWhere((position) => position.index == 4)
.itemLeadingEdge,
-1 / 10);
});
testWidgets(
'List positioned with 5 at top then scroll up 2 programatically and shrink wrap',
(WidgetTester tester) async {
final scrollController = ScrollController();
await setUpWidgetTest(tester,
topItem: 5, scrollController: scrollController);
scrollController.jumpTo(-2 * itemHeight);
await tester.pump();
expect(find.text('Item 2'), findsNothing);
expect(find.text('Item 3'), findsOneWidget);
expect(find.text('Item 12'), findsOneWidget);
expect(find.text('Item 13'), findsNothing);
expect(
itemPositionsNotifier.itemPositions.value
.firstWhere((position) => position.index == 2)
.itemLeadingEdge,
-1 / 10);
expect(
itemPositionsNotifier.itemPositions.value
.firstWhere((position) => position.index == 3)
.itemLeadingEdge,
0);
expect(
itemPositionsNotifier.itemPositions.value
.firstWhere((position) => position.index == 12)
.itemTrailingEdge,
1);
});
testWidgets(
'List positioned with 5 at top then scroll down 20 programatically and shrink wrap',
(WidgetTester tester) async {
final scrollController = ScrollController();
await setUpWidgetTest(tester,
topItem: 5, scrollController: scrollController);
scrollController.jumpTo(itemHeight * 20);
await tester.pump();
expect(
itemPositionsNotifier.itemPositions.value
.firstWhere((position) => position.index == 23)
.itemLeadingEdge,
-2 / 10);
expect(
itemPositionsNotifier.itemPositions.value
.firstWhere((position) => position.index == 24)
.itemLeadingEdge,
-1 / 10);
expect(
itemPositionsNotifier.itemPositions.value
.firstWhere((position) => position.index == 25)
.itemLeadingEdge,
0);
expect(
itemPositionsNotifier.itemPositions.value
.firstWhere((position) => position.index == 4)
.itemLeadingEdge,
-21 / 10);
expect(
itemPositionsNotifier.itemPositions.value
.firstWhere((position) => position.index == 5)
.itemLeadingEdge,
-20 / 10);
});
testWidgets(
'List positioned with 5 at top and initial scroll offset and shrink wrap',
(WidgetTester tester) async {
final scrollController =
ScrollController(initialScrollOffset: -2 * itemHeight);
await setUpWidgetTest(tester,
topItem: 5, scrollController: scrollController);
expect(find.text('Item 2'), findsNothing);
expect(find.text('Item 3'), findsOneWidget);
expect(find.text('Item 12'), findsOneWidget);
expect(find.text('Item 13'), findsNothing);
expect(
itemPositionsNotifier.itemPositions.value
.firstWhere((position) => position.index == 3)
.itemLeadingEdge,
0);
expect(
itemPositionsNotifier.itemPositions.value
.firstWhere((position) => position.index == 12)
.itemTrailingEdge,
1);
});
testWidgets('short List with reverse and shrink wrap',
(WidgetTester tester) async {
const itemCount = 5;
const key = Key('short_list');
await setUpWidgetTest(tester,
itemCount: itemCount, key: key, reverse: true);
await tester.pump();
expect(find.text('Item 4'), findsOneWidget);
expect(find.text('Item 5'), findsNothing);
expect(
tester.getBottomRight(find.text('Item 0')).dy, itemHeight * itemCount);
expect(tester.getTopLeft(find.text('Item 4')).dy, 0);
final positionList = find.byKey(key);
expect(tester.getBottomRight(positionList).dy, itemHeight * itemCount);
expect(tester.getTopLeft(positionList).dy, 0);
expect(
itemPositionsNotifier.itemPositions.value
.firstWhere((position) => position.index == 0)
.itemLeadingEdge,
0);
expect(
itemPositionsNotifier.itemPositions.value
.firstWhere((position) => position.index == 4)
.itemTrailingEdge,
1.0);
});
testWidgets('test nested positioned list', (WidgetTester tester) async {
const itemCount = 50;
const key = Key('short_list');
tester.binding.window.devicePixelRatioTestValue = 1.0;
tester.binding.window.physicalSizeTestValue =
const Size(screenWidth, screenHeight);
await tester.pumpWidget(
MaterialApp(
// Use flex layout to ensure that the minimum height is not limited to screenHeight
home: PositionedList(
itemCount: 5,
itemBuilder: (context, index) {
if (index == 0) {
return PositionedList(
key: key,
itemCount: itemCount,
shrinkWrap: true,
itemBuilder: (context, idx) => SizedBox(
height: itemHeight,
child: Text('Item $idx'),
));
} else {
return SizedBox(
height: itemHeight,
child: Text('Item ${itemCount + index - 1}'),
);
}
},
itemPositionsNotifier: itemPositionsNotifier as ItemPositionsNotifier,
),
),
);
await tester.pump();
expect(find.text('Item 0'), findsOneWidget);
expect(find.text('Item 50'), findsNothing);
expect(tester.getTopLeft(find.text('Item 0')).dy, 0);
expect(tester.getBottomRight(find.text('Item 9')).dy, screenHeight);
final positionList = find.byKey(key);
expect(tester.getBottomRight(positionList).dy, itemHeight * itemCount);
expect(tester.getTopLeft(positionList).dy, 0);
expect(
itemPositionsNotifier.itemPositions.value
.firstWhere((position) => position.index == 0)
.itemLeadingEdge,
0);
expect(
itemPositionsNotifier.itemPositions.value
.firstWhere((position) => position.index == 0)
.itemTrailingEdge,
5.0);
});
}
@@ -0,0 +1,246 @@
// Copyright 2019 The Fuchsia 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/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pedantic/pedantic.dart';
import 'package:stream_chat_flutter/scrollable_positioned_list/scrollable_positioned_list.dart';
const screenHeight = 400.0;
const screenWidth = 400.0;
const itemHeight = screenHeight / 10.0;
const itemCount = 500;
const scrollDuration = Duration(seconds: 1);
void main() {
Future<void> setUpWidgetTest(
WidgetTester tester, {
ItemScrollController? itemScrollController,
ItemPositionsListener? itemPositionsListener,
EdgeInsets? padding,
int initialIndex = 0,
}) async {
tester.binding.window.devicePixelRatioTestValue = 1.0;
tester.binding.window.physicalSizeTestValue =
const Size(screenWidth, screenHeight);
await tester.pumpWidget(
MaterialApp(
// Use flex layout to ensure that the minimum height is not limited to screenHeight
home: Column(children: [
// Use Constrained to make max height not more than screenHeight
ConstrainedBox(
constraints:
const BoxConstraints(maxHeight: screenHeight, maxWidth: screenWidth),
child: ScrollablePositionedList.builder(
itemCount: itemCount,
initialScrollIndex: initialIndex,
itemScrollController: itemScrollController,
itemBuilder: (context, index) => SizedBox(
height: itemHeight,
child: Text('Item $index'),
),
itemPositionsListener: itemPositionsListener,
shrinkWrap: true,
padding: padding,
),
),
]),
),
);
}
testWidgets('List positioned with 0 at top and shrink wrap',
(WidgetTester tester) async {
final itemPositionsListener = ItemPositionsListener.create();
await setUpWidgetTest(tester, itemPositionsListener: itemPositionsListener);
expect(tester.getTopLeft(find.text('Item 0')).dy, 0);
expect(tester.getBottomRight(find.text('Item 9')).dy, screenHeight);
expect(find.text('Item 10'), findsNothing);
expect(
itemPositionsListener.itemPositions.value
.firstWhere((position) => position.index == 0)
.itemLeadingEdge,
0);
expect(
itemPositionsListener.itemPositions.value
.firstWhere((position) => position.index == 9)
.itemTrailingEdge,
1);
});
testWidgets('Scroll to 1 then 2 (both already on screen) with shrink wrap',
(WidgetTester tester) async {
final itemScrollController = ItemScrollController();
final itemPositionsListener = ItemPositionsListener.create();
await setUpWidgetTest(tester,
itemScrollController: itemScrollController,
itemPositionsListener: itemPositionsListener);
unawaited(
itemScrollController.scrollTo(index: 1, duration: scrollDuration));
await tester.pump();
await tester.pump(scrollDuration);
expect(find.text('Item 0'), findsNothing);
expect(
itemPositionsListener.itemPositions.value
.firstWhere((position) => position.index == 1)
.itemLeadingEdge,
0);
expect(tester.getTopLeft(find.text('Item 1')).dy, 0);
unawaited(
itemScrollController.scrollTo(index: 2, duration: scrollDuration));
await tester.pump();
await tester.pump(scrollDuration);
expect(find.text('Item 1'), findsNothing);
expect(tester.getTopLeft(find.text('Item 2')).dy, 0);
expect(
itemPositionsListener.itemPositions.value
.firstWhere((position) => position.index == 2)
.itemLeadingEdge,
0);
expect(
itemPositionsListener.itemPositions.value
.firstWhere((position) => position.index == 11)
.itemTrailingEdge,
1);
});
testWidgets(
'Scroll to 5 (already on screen) and then back to 0 with shrink wrap',
(WidgetTester tester) async {
final itemScrollController = ItemScrollController();
final itemPositionsListener = ItemPositionsListener.create();
await setUpWidgetTest(tester,
itemScrollController: itemScrollController,
itemPositionsListener: itemPositionsListener);
unawaited(
itemScrollController.scrollTo(index: 5, duration: scrollDuration));
await tester.pumpAndSettle();
unawaited(
itemScrollController.scrollTo(index: 0, duration: scrollDuration));
await tester.pumpAndSettle();
expect(find.text('Item 0'), findsOneWidget);
expect(find.text('Item 9'), findsOneWidget);
expect(find.text('Item 10'), findsNothing);
expect(
itemPositionsListener.itemPositions.value
.firstWhere((position) => position.index == 0)
.itemLeadingEdge,
0);
expect(
itemPositionsListener.itemPositions.value
.firstWhere((position) => position.index == 9)
.itemTrailingEdge,
1);
});
testWidgets('Scroll to 100 (not already on screen) with shrink wrap',
(WidgetTester tester) async {
final itemScrollController = ItemScrollController();
final itemPositionsListener = ItemPositionsListener.create();
await setUpWidgetTest(tester,
itemScrollController: itemScrollController,
itemPositionsListener: itemPositionsListener);
unawaited(
itemScrollController.scrollTo(index: 100, duration: scrollDuration));
await tester.pumpAndSettle();
expect(find.text('Item 99'), findsNothing);
expect(find.text('Item 100'), findsOneWidget);
expect(
itemPositionsListener.itemPositions.value
.firstWhere((position) => position.index == 100)
.itemLeadingEdge,
0);
expect(
itemPositionsListener.itemPositions.value
.firstWhere((position) => position.index == 109)
.itemTrailingEdge,
1);
});
testWidgets('Jump to 100 with shrink wrap', (WidgetTester tester) async {
final itemScrollController = ItemScrollController();
final itemPositionsListener = ItemPositionsListener.create();
await setUpWidgetTest(tester,
itemScrollController: itemScrollController,
itemPositionsListener: itemPositionsListener);
itemScrollController.jumpTo(index: 100);
await tester.pumpAndSettle();
expect(tester.getTopLeft(find.text('Item 100')).dy, 0);
expect(tester.getBottomRight(find.text('Item 109')).dy, screenHeight);
expect(
itemPositionsListener.itemPositions.value
.firstWhere((position) => position.index == 100)
.itemLeadingEdge,
0);
expect(
itemPositionsListener.itemPositions.value
.firstWhere((position) => position.index == 109)
.itemTrailingEdge,
1);
});
testWidgets('padding test - centered sliver at bottom with shrink wrap',
(WidgetTester tester) async {
final itemScrollController = ItemScrollController();
await setUpWidgetTest(
tester,
itemScrollController: itemScrollController,
padding: const EdgeInsets.all(10),
);
expect(tester.getTopLeft(find.text('Item 0')), const Offset(10, 10));
expect(tester.getTopLeft(find.text('Item 1')),
const Offset(10, itemHeight + 10));
expect(tester.getBottomRight(find.text('Item 1')),
const Offset(screenWidth - 10, 10 + itemHeight * 2));
unawaited(
itemScrollController.scrollTo(index: 490, duration: scrollDuration));
await tester.pumpAndSettle();
await tester.drag(
find.byType(ScrollablePositionedList), const Offset(0, -100));
await tester.pumpAndSettle();
expect(tester.getTopLeft(find.text('Item 499')),
const Offset(10, screenHeight - itemHeight - 10));
});
testWidgets('padding test - centered sliver not at bottom',
(WidgetTester tester) async {
final itemScrollController = ItemScrollController();
await setUpWidgetTest(
tester,
itemScrollController: itemScrollController,
initialIndex: 2,
padding: const EdgeInsets.all(10),
);
await tester.drag(
find.byType(ScrollablePositionedList), const Offset(0, 200));
await tester.pumpAndSettle();
expect(tester.getTopLeft(find.text('Item 0')), const Offset(10, 10));
expect(tester.getTopLeft(find.text('Item 2')),
const Offset(10, 10 + itemHeight * 2));
expect(tester.getTopLeft(find.text('Item 3')),
const Offset(10, 10 + itemHeight * 3));
});
}