feat: move scrollable positioned list to internal
src
This commit is contained in:
+298
@@ -0,0 +1,298 @@
|
||||
// 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 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:pedantic/pedantic.dart';
|
||||
import 'package:stream_chat_flutter/src/scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||
|
||||
const screenHeight = 400.0;
|
||||
const screenWidth = 400.0;
|
||||
const itemWidth = screenWidth / 10.0;
|
||||
const itemCount = 500;
|
||||
const scrollDuration = Duration(seconds: 1);
|
||||
|
||||
void main() {
|
||||
Future<void> setUpWidgetTest(
|
||||
WidgetTester tester, {
|
||||
ItemScrollController? itemScrollController,
|
||||
ItemPositionsListener? itemPositionsListener,
|
||||
bool reverse = false,
|
||||
EdgeInsets? padding,
|
||||
int initialScrollIndex = 0,
|
||||
}) async {
|
||||
tester.binding.window.devicePixelRatioTestValue = 1.0;
|
||||
tester.binding.window.physicalSizeTestValue =
|
||||
const Size(screenWidth, screenHeight);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: ScrollablePositionedList.builder(
|
||||
itemCount: itemCount,
|
||||
itemScrollController: itemScrollController,
|
||||
itemBuilder: (context, index) => SizedBox(
|
||||
width: itemWidth,
|
||||
child: Text('Item $index'),
|
||||
),
|
||||
itemPositionsListener: itemPositionsListener,
|
||||
scrollDirection: Axis.horizontal,
|
||||
reverse: reverse,
|
||||
padding: padding,
|
||||
initialScrollIndex: initialScrollIndex,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('List positioned with 0 at left', (WidgetTester tester) async {
|
||||
final itemPositionsListener = ItemPositionsListener.create();
|
||||
await setUpWidgetTest(tester, itemPositionsListener: itemPositionsListener);
|
||||
|
||||
expect(tester.getTopLeft(find.text('Item 0')).dx, 0);
|
||||
expect(tester.getBottomRight(find.text('Item 9')).dx, screenWidth);
|
||||
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 == 0)
|
||||
.itemTrailingEdge,
|
||||
1 / 10);
|
||||
expect(
|
||||
itemPositionsListener.itemPositions.value
|
||||
.firstWhere((position) => position.index == 9)
|
||||
.itemTrailingEdge,
|
||||
1);
|
||||
});
|
||||
|
||||
testWidgets('List positioned with 0 at right', (WidgetTester tester) async {
|
||||
final itemPositionsListener = ItemPositionsListener.create();
|
||||
await setUpWidgetTest(tester,
|
||||
itemPositionsListener: itemPositionsListener, reverse: true);
|
||||
|
||||
expect(tester.getBottomRight(find.text('Item 0')).dx, screenWidth);
|
||||
expect(tester.getTopLeft(find.text('Item 9')).dx, 0);
|
||||
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 == 0)
|
||||
.itemTrailingEdge,
|
||||
1 / 10);
|
||||
expect(
|
||||
itemPositionsListener.itemPositions.value
|
||||
.firstWhere((position) => position.index == 9)
|
||||
.itemTrailingEdge,
|
||||
1);
|
||||
});
|
||||
|
||||
testWidgets('Scroll to 2 (already on screen)', (WidgetTester tester) async {
|
||||
final itemScrollController = ItemScrollController();
|
||||
final itemPositionsListener = ItemPositionsListener.create();
|
||||
await setUpWidgetTest(tester,
|
||||
itemScrollController: itemScrollController,
|
||||
itemPositionsListener: itemPositionsListener);
|
||||
|
||||
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')).dx, 0);
|
||||
|
||||
expect(
|
||||
itemPositionsListener.itemPositions.value
|
||||
.firstWhere((position) => position.index == 2)
|
||||
.itemLeadingEdge,
|
||||
0);
|
||||
expect(
|
||||
itemPositionsListener.itemPositions.value
|
||||
.firstWhere((position) => position.index == 2)
|
||||
.itemTrailingEdge,
|
||||
1 / 10);
|
||||
expect(
|
||||
itemPositionsListener.itemPositions.value
|
||||
.firstWhere((position) => position.index == 11)
|
||||
.itemTrailingEdge,
|
||||
1);
|
||||
});
|
||||
|
||||
testWidgets('Scroll to 100 (not already on screen)',
|
||||
(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 == 100)
|
||||
.itemTrailingEdge,
|
||||
1 / 10);
|
||||
expect(
|
||||
itemPositionsListener.itemPositions.value
|
||||
.firstWhere((position) => position.index == 109)
|
||||
.itemTrailingEdge,
|
||||
1);
|
||||
});
|
||||
|
||||
testWidgets('Jump to 100', (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')).dx, 0);
|
||||
expect(tester.getBottomRight(find.text('Item 109')).dy, screenWidth);
|
||||
|
||||
expect(
|
||||
itemPositionsListener.itemPositions.value
|
||||
.firstWhere((position) => position.index == 100)
|
||||
.itemLeadingEdge,
|
||||
0);
|
||||
expect(
|
||||
itemPositionsListener.itemPositions.value
|
||||
.firstWhere((position) => position.index == 100)
|
||||
.itemTrailingEdge,
|
||||
1 / 10);
|
||||
expect(
|
||||
itemPositionsListener.itemPositions.value
|
||||
.firstWhere((position) => position.index == 109)
|
||||
.itemLeadingEdge,
|
||||
9 / 10);
|
||||
expect(
|
||||
itemPositionsListener.itemPositions.value
|
||||
.firstWhere((position) => position.index == 109)
|
||||
.itemTrailingEdge,
|
||||
1);
|
||||
});
|
||||
|
||||
testWidgets('padding test - centered sliver at left',
|
||||
(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 + itemWidth, 10));
|
||||
expect(tester.getBottomRight(find.text('Item 1')),
|
||||
const Offset(10 + 2 * itemWidth, screenHeight - 10));
|
||||
|
||||
unawaited(
|
||||
itemScrollController.scrollTo(index: 490, duration: scrollDuration));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.drag(
|
||||
find.byType(ScrollablePositionedList), const Offset(-100, 0));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(tester.getBottomRight(find.text('Item 499')),
|
||||
const Offset(screenWidth - 10, screenHeight - 10));
|
||||
});
|
||||
|
||||
testWidgets('padding test - centered sliver not at left',
|
||||
(WidgetTester tester) async {
|
||||
final itemScrollController = ItemScrollController();
|
||||
await setUpWidgetTest(
|
||||
tester,
|
||||
itemScrollController: itemScrollController,
|
||||
initialScrollIndex: 2,
|
||||
padding: const EdgeInsets.all(10),
|
||||
);
|
||||
|
||||
await tester.drag(
|
||||
find.byType(ScrollablePositionedList), const Offset(200, 0));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(tester.getTopLeft(find.text('Item 0')), const Offset(10, 10));
|
||||
expect(tester.getTopLeft(find.text('Item 2')),
|
||||
const Offset(10 + 2 * itemWidth, 10));
|
||||
expect(tester.getTopLeft(find.text('Item 3')),
|
||||
const Offset(10 + 3 * itemWidth, 10));
|
||||
});
|
||||
|
||||
testWidgets('padding test - reversed - centered sliver at right',
|
||||
(WidgetTester tester) async {
|
||||
final itemScrollController = ItemScrollController();
|
||||
await setUpWidgetTest(
|
||||
tester,
|
||||
itemScrollController: itemScrollController,
|
||||
padding: const EdgeInsets.all(10),
|
||||
reverse: true,
|
||||
);
|
||||
|
||||
expect(tester.getTopRight(find.text('Item 0')),
|
||||
const Offset(screenWidth - 10, 10));
|
||||
expect(tester.getTopRight(find.text('Item 1')),
|
||||
const Offset(screenWidth - (10 + itemWidth), 10));
|
||||
expect(tester.getBottomLeft(find.text('Item 1')),
|
||||
const Offset(screenWidth - (10 + 2 * itemWidth), screenHeight - 10));
|
||||
|
||||
unawaited(
|
||||
itemScrollController.scrollTo(index: 490, duration: scrollDuration));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.drag(
|
||||
find.byType(ScrollablePositionedList), const Offset(100, 0));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(tester.getTopLeft(find.text('Item 499')), const Offset(10, 10));
|
||||
});
|
||||
|
||||
testWidgets('padding test - reversed - centered sliver not at right',
|
||||
(WidgetTester tester) async {
|
||||
final itemScrollController = ItemScrollController();
|
||||
await setUpWidgetTest(
|
||||
tester,
|
||||
itemScrollController: itemScrollController,
|
||||
initialScrollIndex: 2,
|
||||
padding: const EdgeInsets.all(10),
|
||||
reverse: true,
|
||||
);
|
||||
|
||||
await tester.drag(
|
||||
find.byType(ScrollablePositionedList), const Offset(-200, 0));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(tester.getTopRight(find.text('Item 0')),
|
||||
const Offset(screenWidth - 10, 10));
|
||||
expect(tester.getTopRight(find.text('Item 2')),
|
||||
const Offset(screenWidth - (10 + 2 * itemWidth), 10));
|
||||
expect(tester.getTopRight(find.text('Item 3')),
|
||||
const Offset(screenWidth - (10 + 3 * itemWidth), 10));
|
||||
});
|
||||
}
|
||||
+363
@@ -0,0 +1,363 @@
|
||||
// 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/src/scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||
import 'package:stream_chat_flutter/src/scrollable_positioned_list/src/item_positions_notifier.dart';
|
||||
import 'package:stream_chat_flutter/src/scrollable_positioned_list/src/positioned_list.dart';
|
||||
|
||||
const screenHeight = 400.0;
|
||||
const screenWidth = 400.0;
|
||||
const itemHeight = screenHeight / 10.0;
|
||||
const defaultItemCount = 500;
|
||||
const cacheExtent = itemHeight * 2;
|
||||
|
||||
void main() {
|
||||
final itemPositionsNotifier = ItemPositionsListener.create();
|
||||
|
||||
Future<void> setUpWidgetTest(
|
||||
WidgetTester tester, {
|
||||
int topItem = 0,
|
||||
ScrollController? scrollController,
|
||||
double anchor = 0,
|
||||
int itemCount = defaultItemCount,
|
||||
}) async {
|
||||
tester.binding.window.devicePixelRatioTestValue = 1.0;
|
||||
tester.binding.window.physicalSizeTestValue =
|
||||
const Size(screenWidth, screenHeight);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: PositionedList(
|
||||
itemCount: itemCount,
|
||||
positionedIndex: topItem,
|
||||
alignment: anchor,
|
||||
controller: scrollController,
|
||||
itemBuilder: (context, index) => SizedBox(
|
||||
height: itemHeight,
|
||||
child: Text('Item $index'),
|
||||
),
|
||||
itemPositionsNotifier: itemPositionsNotifier as ItemPositionsNotifier,
|
||||
cacheExtent: cacheExtent,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('short list', (WidgetTester tester) async {
|
||||
await setUpWidgetTest(tester, itemCount: 5);
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('Item 0'), findsOneWidget);
|
||||
expect(find.text('Item 4'), findsOneWidget);
|
||||
expect(find.text('Item 5'), findsNothing);
|
||||
|
||||
expect(
|
||||
itemPositionsNotifier.itemPositions.value
|
||||
.firstWhere((position) => position.index == 0)
|
||||
.itemLeadingEdge,
|
||||
0);
|
||||
expect(
|
||||
itemPositionsNotifier.itemPositions.value
|
||||
.firstWhere((position) => position.index == 4)
|
||||
.itemTrailingEdge,
|
||||
1 / 2);
|
||||
});
|
||||
|
||||
testWidgets('List positioned with 0 at top', (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', (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', (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',
|
||||
(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',
|
||||
(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',
|
||||
(WidgetTester tester) async {
|
||||
await setUpWidgetTest(tester, topItem: 5);
|
||||
|
||||
await tester.drag(
|
||||
find.byType(PositionedList), const Offset(0, 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 1/2',
|
||||
(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',
|
||||
(WidgetTester tester) async {
|
||||
final scrollController = ScrollController();
|
||||
await setUpWidgetTest(tester, scrollController: scrollController);
|
||||
await tester.pump();
|
||||
|
||||
scrollController.jumpTo(5 * itemHeight);
|
||||
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',
|
||||
(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',
|
||||
(WidgetTester tester) async {
|
||||
final scrollController = ScrollController();
|
||||
await setUpWidgetTest(tester,
|
||||
topItem: 5, scrollController: scrollController);
|
||||
|
||||
scrollController.jumpTo(20 * itemHeight);
|
||||
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',
|
||||
(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);
|
||||
});
|
||||
}
|
||||
+270
@@ -0,0 +1,270 @@
|
||||
// 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/src/scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||
import 'package:stream_chat_flutter/src/scrollable_positioned_list/src/item_positions_notifier.dart';
|
||||
import 'package:stream_chat_flutter/src/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,
|
||||
ScrollController? scrollController,
|
||||
double anchor = 0,
|
||||
int itemCount = defaultItemCount,
|
||||
}) async {
|
||||
tester.binding.window.devicePixelRatioTestValue = 1.0;
|
||||
tester.binding.window.physicalSizeTestValue =
|
||||
const Size(screenWidth, screenHeight);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: PositionedList(
|
||||
itemCount: itemCount,
|
||||
positionedIndex: topItem,
|
||||
alignment: anchor,
|
||||
controller: scrollController,
|
||||
itemBuilder: (context, index) => SizedBox(
|
||||
height: itemHeight,
|
||||
child: Text('Item $index'),
|
||||
),
|
||||
itemPositionsNotifier: itemPositionsNotifier as ItemPositionsNotifier,
|
||||
reverse: true,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('short list', (WidgetTester tester) async {
|
||||
await setUpWidgetTest(tester, itemCount: 5);
|
||||
await tester.pump();
|
||||
|
||||
expect(tester.getBottomRight(find.text('Item 0')).dy, screenHeight);
|
||||
expect(find.text('Item 4'), findsOneWidget);
|
||||
expect(find.text('Item 5'), findsNothing);
|
||||
|
||||
expect(
|
||||
itemPositionsNotifier.itemPositions.value
|
||||
.firstWhere((position) => position.index == 0)
|
||||
.itemLeadingEdge,
|
||||
0);
|
||||
expect(
|
||||
itemPositionsNotifier.itemPositions.value
|
||||
.firstWhere((position) => position.index == 4)
|
||||
.itemTrailingEdge,
|
||||
1 / 2);
|
||||
});
|
||||
|
||||
testWidgets('List positioned with 0 at bottom', (WidgetTester tester) async {
|
||||
await setUpWidgetTest(tester);
|
||||
await tester.pump();
|
||||
|
||||
expect(tester.getBottomRight(find.text('Item 0')).dy, screenHeight);
|
||||
expect(tester.getTopLeft(find.text('Item 9')).dy, 0);
|
||||
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);
|
||||
});
|
||||
|
||||
testWidgets('List positioned with 5 at bottom', (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 15 at bottom', (WidgetTester tester) async {
|
||||
await setUpWidgetTest(tester, topItem: 15, anchor: 0);
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('Item 14'), findsNothing);
|
||||
expect(find.text('Item 15'), findsOneWidget);
|
||||
expect(find.text('Item 24'), findsOneWidget);
|
||||
expect(find.text('Item 25'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('List positioned with 15 at top', (WidgetTester tester) async {
|
||||
await setUpWidgetTest(tester, topItem: 15, anchor: 1);
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('Item 15'), findsNothing);
|
||||
expect(find.text('Item 14'), findsOneWidget);
|
||||
expect(find.text('Item 5'), findsOneWidget);
|
||||
expect(find.text('Item 4'), findsNothing);
|
||||
|
||||
expect(
|
||||
itemPositionsNotifier.itemPositions.value
|
||||
.firstWhere((position) => position.index == 15)
|
||||
.itemLeadingEdge,
|
||||
1);
|
||||
expect(
|
||||
itemPositionsNotifier.itemPositions.value
|
||||
.firstWhere((position) => position.index == 14)
|
||||
.itemTrailingEdge,
|
||||
1);
|
||||
expect(
|
||||
itemPositionsNotifier.itemPositions.value
|
||||
.firstWhere((position) => position.index == 14)
|
||||
.itemLeadingEdge,
|
||||
9 / 10);
|
||||
expect(
|
||||
itemPositionsNotifier.itemPositions.value
|
||||
.firstWhere((position) => position.index == 5)
|
||||
.itemLeadingEdge,
|
||||
0);
|
||||
});
|
||||
|
||||
testWidgets('List positioned with 5 at bottom then scroll up 2',
|
||||
(WidgetTester tester) async {
|
||||
await setUpWidgetTest(tester, topItem: 5);
|
||||
|
||||
await tester.drag(
|
||||
find.byType(PositionedList), const Offset(0, 2 * itemHeight));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('Item 6'), findsNothing);
|
||||
expect(find.text('Item 7'), findsOneWidget);
|
||||
|
||||
expect(
|
||||
itemPositionsNotifier.itemPositions.value
|
||||
.firstWhere((position) => position.index == 7)
|
||||
.itemLeadingEdge,
|
||||
0);
|
||||
expect(
|
||||
itemPositionsNotifier.itemPositions.value
|
||||
.firstWhere((position) => position.index == 7)
|
||||
.itemTrailingEdge,
|
||||
1 / 10);
|
||||
});
|
||||
|
||||
testWidgets('List positioned with 0 at bottom scroll to item 5',
|
||||
(WidgetTester tester) async {
|
||||
final scrollController = ScrollController();
|
||||
await setUpWidgetTest(tester, scrollController: scrollController);
|
||||
await tester.pump();
|
||||
|
||||
scrollController.jumpTo(5 * itemHeight);
|
||||
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 bottom then scroll up 2 programatically',
|
||||
(WidgetTester tester) async {
|
||||
final scrollController = ScrollController();
|
||||
await setUpWidgetTest(tester,
|
||||
topItem: 5, scrollController: scrollController);
|
||||
|
||||
scrollController.jumpTo(2 * itemHeight);
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('Item 6'), findsNothing);
|
||||
expect(find.text('Item 7'), findsOneWidget);
|
||||
expect(find.text('Item 16'), findsOneWidget);
|
||||
expect(find.text('Item 17'), findsNothing);
|
||||
|
||||
expect(
|
||||
itemPositionsNotifier.itemPositions.value
|
||||
.firstWhere((position) => position.index == 6)
|
||||
.itemLeadingEdge,
|
||||
-1 / 10);
|
||||
expect(
|
||||
itemPositionsNotifier.itemPositions.value
|
||||
.firstWhere((position) => position.index == 7)
|
||||
.itemLeadingEdge,
|
||||
0);
|
||||
expect(
|
||||
itemPositionsNotifier.itemPositions.value
|
||||
.firstWhere((position) => position.index == 16)
|
||||
.itemTrailingEdge,
|
||||
1);
|
||||
});
|
||||
|
||||
testWidgets('List positioned with 5 at bottom and initial scroll offset',
|
||||
(WidgetTester tester) async {
|
||||
final scrollController =
|
||||
ScrollController(initialScrollOffset: 2 * itemHeight);
|
||||
await setUpWidgetTest(tester,
|
||||
topItem: 5, scrollController: scrollController);
|
||||
|
||||
expect(find.text('Item 6'), findsNothing);
|
||||
expect(find.text('Item 7'), findsOneWidget);
|
||||
expect(find.text('Item 16'), findsOneWidget);
|
||||
expect(find.text('Item 17'), findsNothing);
|
||||
|
||||
expect(
|
||||
itemPositionsNotifier.itemPositions.value
|
||||
.firstWhere((position) => position.index == 6)
|
||||
.itemLeadingEdge,
|
||||
-1 / 10);
|
||||
expect(
|
||||
itemPositionsNotifier.itemPositions.value
|
||||
.firstWhere((position) => position.index == 7)
|
||||
.itemLeadingEdge,
|
||||
0);
|
||||
expect(
|
||||
itemPositionsNotifier.itemPositions.value
|
||||
.firstWhere((position) => position.index == 16)
|
||||
.itemTrailingEdge,
|
||||
1);
|
||||
});
|
||||
}
|
||||
+239
@@ -0,0 +1,239 @@
|
||||
// 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 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:pedantic/pedantic.dart';
|
||||
import 'package:stream_chat_flutter/src/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(
|
||||
home: ScrollablePositionedList.builder(
|
||||
itemCount: itemCount,
|
||||
initialScrollIndex: initialIndex,
|
||||
itemScrollController: itemScrollController,
|
||||
itemBuilder: (context, index) => SizedBox(
|
||||
height: itemHeight,
|
||||
child: Text('Item $index'),
|
||||
),
|
||||
itemPositionsListener: itemPositionsListener,
|
||||
reverse: true,
|
||||
padding: padding,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('List positioned with 0 at bottom', (WidgetTester tester) async {
|
||||
final itemPositionsListener = ItemPositionsListener.create();
|
||||
await setUpWidgetTest(tester, itemPositionsListener: itemPositionsListener);
|
||||
|
||||
expect(tester.getBottomRight(find.text('Item 0')).dy, screenHeight);
|
||||
expect(tester.getTopLeft(find.text('Item 9')).dy, 0);
|
||||
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)',
|
||||
(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.getBottomRight(find.text('Item 1')).dy, screenHeight);
|
||||
|
||||
unawaited(
|
||||
itemScrollController.scrollTo(index: 2, duration: scrollDuration));
|
||||
await tester.pump();
|
||||
await tester.pump(scrollDuration);
|
||||
|
||||
expect(find.text('Item 1'), findsNothing);
|
||||
expect(tester.getBottomRight(find.text('Item 2')).dy, screenHeight);
|
||||
|
||||
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',
|
||||
(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)',
|
||||
(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', (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.getBottomRight(find.text('Item 100')).dy, screenHeight);
|
||||
expect(tester.getTopLeft(find.text('Item 109')).dy, 0);
|
||||
|
||||
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',
|
||||
(WidgetTester tester) async {
|
||||
final itemScrollController = ItemScrollController();
|
||||
await setUpWidgetTest(
|
||||
tester,
|
||||
itemScrollController: itemScrollController,
|
||||
padding: const EdgeInsets.all(10),
|
||||
);
|
||||
|
||||
expect(tester.getBottomLeft(find.text('Item 0')),
|
||||
const Offset(10, screenHeight - 10));
|
||||
expect(tester.getBottomLeft(find.text('Item 1')),
|
||||
const Offset(10, screenHeight - (10 + itemHeight)));
|
||||
expect(tester.getTopRight(find.text('Item 1')),
|
||||
const Offset(screenWidth - 10, screenHeight - (10 + 2 * itemHeight)));
|
||||
|
||||
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, 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.getBottomLeft(find.text('Item 0')),
|
||||
const Offset(10, screenHeight - 10));
|
||||
expect(tester.getBottomLeft(find.text('Item 2')),
|
||||
const Offset(10, screenHeight - (10 + 2 * itemHeight)));
|
||||
expect(tester.getBottomLeft(find.text('Item 3')),
|
||||
const Offset(10, screenHeight - (10 + 3 * itemHeight)));
|
||||
});
|
||||
}
|
||||
+2301
File diff suppressed because it is too large
Load Diff
+261
@@ -0,0 +1,261 @@
|
||||
// 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/src/scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||
import 'package:stream_chat_flutter/src/scrollable_positioned_list/src/item_positions_notifier.dart';
|
||||
import 'package:stream_chat_flutter/src/scrollable_positioned_list/src/positioned_list.dart';
|
||||
|
||||
const screenHeight = 400.0;
|
||||
const screenWidth = 400.0;
|
||||
const itemHeight = screenHeight / 10.0;
|
||||
const separatorHeight = screenHeight / 20.0;
|
||||
const defaultItemCount = 500;
|
||||
const cacheExtent = itemHeight * 2;
|
||||
|
||||
void main() {
|
||||
final itemPositionsNotifier = ItemPositionsListener.create();
|
||||
|
||||
Future<void> setUpWidgetTest(
|
||||
WidgetTester tester, {
|
||||
int topItem = 0,
|
||||
ScrollController? scrollController,
|
||||
double anchor = 0,
|
||||
int itemCount = defaultItemCount,
|
||||
}) async {
|
||||
tester.binding.window.devicePixelRatioTestValue = 1.0;
|
||||
tester.binding.window.physicalSizeTestValue =
|
||||
const Size(screenWidth, screenHeight);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: PositionedList(
|
||||
itemCount: itemCount,
|
||||
positionedIndex: topItem,
|
||||
alignment: anchor,
|
||||
controller: scrollController,
|
||||
itemBuilder: (context, index) => SizedBox(
|
||||
height: itemHeight,
|
||||
child: Text('Item $index'),
|
||||
),
|
||||
separatorBuilder: (context, index) => SizedBox(
|
||||
height: separatorHeight,
|
||||
child: Text('Separator $index'),
|
||||
),
|
||||
itemPositionsNotifier: itemPositionsNotifier as ItemPositionsNotifier,
|
||||
cacheExtent: cacheExtent,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('Empty list', (WidgetTester tester) async {
|
||||
await setUpWidgetTest(tester, itemCount: 0);
|
||||
|
||||
expect(find.text('Item 0'), findsNothing);
|
||||
expect(find.text('Separator 0'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('Short list', (WidgetTester tester) async {
|
||||
await setUpWidgetTest(tester, itemCount: 3);
|
||||
|
||||
expect(find.text('Item 0'), findsOneWidget);
|
||||
expect(find.text('Separator 0'), findsOneWidget);
|
||||
expect(find.text('Item 1'), findsOneWidget);
|
||||
expect(find.text('Separator 1'), findsOneWidget);
|
||||
expect(find.text('Item 2'), findsOneWidget);
|
||||
expect(find.text('Separator 2'), findsNothing);
|
||||
expect(find.text('Item 3'), findsNothing);
|
||||
|
||||
expect(
|
||||
itemPositionsNotifier.itemPositions.value
|
||||
.firstWhere((position) => position.index == 0)
|
||||
.itemLeadingEdge,
|
||||
0);
|
||||
expect(
|
||||
itemPositionsNotifier.itemPositions.value
|
||||
.firstWhere((position) => position.index == 2)
|
||||
.itemTrailingEdge,
|
||||
_screenProportion(numberOfItems: 3, numberOfSeparators: 2));
|
||||
});
|
||||
|
||||
testWidgets('Short list centered at 1 scrolled up',
|
||||
(WidgetTester tester) async {
|
||||
await setUpWidgetTest(tester, itemCount: 3, topItem: 1);
|
||||
|
||||
await tester.drag(
|
||||
find.byType(PositionedList), const Offset(0, 2 * itemHeight));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Item 0'), findsOneWidget);
|
||||
expect(find.text('Separator 0'), findsOneWidget);
|
||||
expect(find.text('Item 1'), findsOneWidget);
|
||||
expect(find.text('Separator 1'), findsOneWidget);
|
||||
expect(find.text('Item 2'), findsOneWidget);
|
||||
expect(find.text('Separator 2'), findsNothing);
|
||||
expect(find.text('Item 3'), findsNothing);
|
||||
|
||||
expect(
|
||||
itemPositionsNotifier.itemPositions.value
|
||||
.firstWhere((position) => position.index == 0)
|
||||
.itemLeadingEdge,
|
||||
0);
|
||||
expect(
|
||||
itemPositionsNotifier.itemPositions.value
|
||||
.firstWhere((position) => position.index == 2)
|
||||
.itemTrailingEdge,
|
||||
_screenProportion(numberOfItems: 3, numberOfSeparators: 2));
|
||||
});
|
||||
|
||||
testWidgets('List positioned with 0 at top', (WidgetTester tester) async {
|
||||
await setUpWidgetTest(tester);
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('Item 0'), findsOneWidget);
|
||||
expect(find.text('Separator 5'), findsOneWidget);
|
||||
expect(find.text('Item 6'), findsOneWidget);
|
||||
expect(find.text('Separator 6'), findsNothing);
|
||||
expect(find.text('Item 7'), findsNothing);
|
||||
|
||||
expect(
|
||||
itemPositionsNotifier.itemPositions.value
|
||||
.firstWhere((position) => position.index == 0)
|
||||
.itemLeadingEdge,
|
||||
0);
|
||||
expect(
|
||||
itemPositionsNotifier.itemPositions.value
|
||||
.firstWhere((position) => position.index == 5)
|
||||
.itemTrailingEdge,
|
||||
1 - _screenProportion(numberOfItems: 1, numberOfSeparators: 1));
|
||||
|
||||
expect(
|
||||
itemPositionsNotifier.itemPositions.value
|
||||
.firstWhere((position) => position.index == 6)
|
||||
.itemTrailingEdge,
|
||||
1);
|
||||
});
|
||||
|
||||
testWidgets('List positioned with 5 at top', (WidgetTester tester) async {
|
||||
await setUpWidgetTest(tester, topItem: 5);
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('Item 4'), findsNothing);
|
||||
expect(find.text('Separator 4'), findsNothing);
|
||||
expect(find.text('Item 5'), findsOneWidget);
|
||||
expect(find.text('Separator 5'), findsOneWidget);
|
||||
|
||||
expect(find.text('Separator 10'), findsOneWidget);
|
||||
expect(find.text('Item 11'), findsOneWidget);
|
||||
expect(find.text('Separator 11'), findsNothing);
|
||||
|
||||
expect(
|
||||
itemPositionsNotifier.itemPositions.value
|
||||
.firstWhere((position) => position.index == 5)
|
||||
.itemLeadingEdge,
|
||||
0);
|
||||
expect(
|
||||
itemPositionsNotifier.itemPositions.value
|
||||
.firstWhere((position) => position.index == 6)
|
||||
.itemLeadingEdge,
|
||||
_screenProportion(numberOfItems: 1, numberOfSeparators: 1));
|
||||
expect(
|
||||
itemPositionsNotifier.itemPositions.value
|
||||
.firstWhere((position) => position.index == 11)
|
||||
.itemTrailingEdge,
|
||||
1);
|
||||
});
|
||||
|
||||
testWidgets('List positioned with 20 at bottom', (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('Separator 19'), findsOneWidget);
|
||||
expect(find.text('Item 14'), findsOneWidget);
|
||||
expect(find.text('Separator 13'), findsOneWidget);
|
||||
expect(find.text('Item 13'), findsOneWidget);
|
||||
expect(find.text('Separator 12'), findsNothing);
|
||||
|
||||
expect(
|
||||
itemPositionsNotifier.itemPositions.value
|
||||
.firstWhere((position) => position.index == 19)
|
||||
.itemTrailingEdge,
|
||||
1 - _screenProportion(numberOfItems: 0, numberOfSeparators: 1));
|
||||
expect(
|
||||
itemPositionsNotifier.itemPositions.value
|
||||
.firstWhere((position) => position.index == 20)
|
||||
.itemLeadingEdge,
|
||||
1);
|
||||
expect(
|
||||
itemPositionsNotifier.itemPositions.value
|
||||
.firstWhere((position) => position.index == 13)
|
||||
.itemLeadingEdge,
|
||||
_screenProportion(numberOfItems: -0.5, numberOfSeparators: 0));
|
||||
});
|
||||
|
||||
testWidgets('List positioned with item 20 at halfway',
|
||||
(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 item 20 half off top of screen',
|
||||
(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,
|
||||
_screenProportion(numberOfItems: -0.5, numberOfSeparators: 0));
|
||||
expect(
|
||||
itemPositionsNotifier.itemPositions.value
|
||||
.firstWhere((position) => position.index == 20)
|
||||
.itemTrailingEdge,
|
||||
_screenProportion(numberOfItems: 0.5, numberOfSeparators: 0));
|
||||
});
|
||||
|
||||
testWidgets('List positioned with 5 at top then scroll up 2 items',
|
||||
(WidgetTester tester) async {
|
||||
await setUpWidgetTest(tester, topItem: 5);
|
||||
|
||||
await tester.drag(find.byType(PositionedList),
|
||||
const Offset(0, 2 * (itemHeight + separatorHeight)));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('Separator 2'), findsNothing);
|
||||
expect(find.text('Item 3'), findsOneWidget);
|
||||
|
||||
expect(
|
||||
itemPositionsNotifier.itemPositions.value
|
||||
.firstWhere((position) => position.index == 2)
|
||||
.itemLeadingEdge,
|
||||
_screenProportion(numberOfItems: -1, numberOfSeparators: -1));
|
||||
expect(
|
||||
itemPositionsNotifier.itemPositions.value
|
||||
.firstWhere((position) => position.index == 3)
|
||||
.itemLeadingEdge,
|
||||
0);
|
||||
});
|
||||
}
|
||||
|
||||
double _screenProportion(
|
||||
{required double numberOfItems, required double numberOfSeparators}) =>
|
||||
(numberOfItems * itemHeight + numberOfSeparators * separatorHeight) /
|
||||
screenHeight;
|
||||
+608
@@ -0,0 +1,608 @@
|
||||
// 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 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:pedantic/pedantic.dart';
|
||||
import 'package:stream_chat_flutter/src/scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||
import 'package:stream_chat_flutter/src/scrollable_positioned_list/src/scroll_view.dart';
|
||||
|
||||
const screenHeight = 400.0;
|
||||
const screenWidth = 400.0;
|
||||
const itemHeight = screenHeight / 10.0;
|
||||
const separatorHeight = screenHeight / 20.0;
|
||||
const defaultItemCount = 500;
|
||||
const scrollDuration = Duration(seconds: 1);
|
||||
const scrollDurationTolerance = Duration(milliseconds: 1);
|
||||
const tolerance = 1e-3;
|
||||
|
||||
void main() {
|
||||
Future<void> setUpWidgetTest(
|
||||
WidgetTester tester, {
|
||||
Key? key,
|
||||
ItemScrollController? itemScrollController,
|
||||
ItemPositionsListener? itemPositionsListener,
|
||||
int initialIndex = 0,
|
||||
double initialAlignment = 0.0,
|
||||
int? itemCount,
|
||||
ScrollPhysics? physics,
|
||||
bool addSemanticIndexes = true,
|
||||
int? semanticChildCount,
|
||||
EdgeInsets? padding,
|
||||
bool addRepaintBoundaries = true,
|
||||
bool addAutomaticKeepAlives = true,
|
||||
}) async {
|
||||
tester.binding.window.devicePixelRatioTestValue = 1.0;
|
||||
tester.binding.window.physicalSizeTestValue =
|
||||
const Size(screenWidth, screenHeight);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: ScrollablePositionedList.separated(
|
||||
itemCount: itemCount ?? defaultItemCount,
|
||||
itemScrollController: itemScrollController,
|
||||
itemBuilder: (context, index) => SizedBox(
|
||||
height: itemHeight,
|
||||
child: Text('Item $index'),
|
||||
),
|
||||
separatorBuilder: (context, index) => SizedBox(
|
||||
height: separatorHeight,
|
||||
child: Text('Separator $index'),
|
||||
),
|
||||
key: key,
|
||||
itemPositionsListener: itemPositionsListener,
|
||||
initialScrollIndex: initialIndex,
|
||||
initialAlignment: initialAlignment,
|
||||
physics: physics,
|
||||
addSemanticIndexes: addSemanticIndexes,
|
||||
semanticChildCount: semanticChildCount,
|
||||
padding: padding,
|
||||
addAutomaticKeepAlives: addAutomaticKeepAlives,
|
||||
addRepaintBoundaries: addRepaintBoundaries,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('List positioned with 0 at top', (WidgetTester tester) async {
|
||||
final itemPositionsListener = ItemPositionsListener.create();
|
||||
await setUpWidgetTest(tester, itemPositionsListener: itemPositionsListener);
|
||||
|
||||
expect(find.text('Item 0'), findsOneWidget);
|
||||
expect(find.text('Separator 5'), findsOneWidget);
|
||||
expect(find.text('Item 6'), findsOneWidget);
|
||||
expect(find.text('Separator 6'), findsNothing);
|
||||
expect(find.text('Item 7'), findsNothing);
|
||||
|
||||
expect(
|
||||
itemPositionsListener.itemPositions.value
|
||||
.firstWhere((position) => position.index == 0)
|
||||
.itemLeadingEdge,
|
||||
0);
|
||||
expect(
|
||||
itemPositionsListener.itemPositions.value
|
||||
.firstWhere((position) => position.index == 5)
|
||||
.itemTrailingEdge,
|
||||
1 - _screenProportion(numberOfItems: 1, numberOfSeparators: 1));
|
||||
|
||||
expect(
|
||||
itemPositionsListener.itemPositions.value
|
||||
.firstWhere((position) => position.index == 6)
|
||||
.itemTrailingEdge,
|
||||
1);
|
||||
expect(
|
||||
itemPositionsListener.itemPositions.value
|
||||
.where((position) => position.index == 7),
|
||||
isEmpty);
|
||||
});
|
||||
|
||||
testWidgets('List positioned with 0 at top - use default values',
|
||||
(WidgetTester tester) async {
|
||||
final itemPositionsListener = ItemPositionsListener.create();
|
||||
tester.binding.window.devicePixelRatioTestValue = 1.0;
|
||||
tester.binding.window.physicalSizeTestValue =
|
||||
const Size(screenWidth, screenHeight);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: ScrollablePositionedList.separated(
|
||||
itemCount: defaultItemCount,
|
||||
itemBuilder: (context, index) => SizedBox(
|
||||
height: itemHeight,
|
||||
child: Text('Item $index'),
|
||||
),
|
||||
separatorBuilder: (context, index) => SizedBox(
|
||||
height: separatorHeight,
|
||||
child: Text('Separator $index'),
|
||||
),
|
||||
itemPositionsListener: itemPositionsListener,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text('Item 0'), findsOneWidget);
|
||||
expect(find.text('Separator 5'), findsOneWidget);
|
||||
expect(find.text('Item 6'), findsOneWidget);
|
||||
expect(find.text('Separator 6'), findsNothing);
|
||||
expect(find.text('Item 7'), findsNothing);
|
||||
|
||||
expect(
|
||||
itemPositionsListener.itemPositions.value
|
||||
.firstWhere((position) => position.index == 0)
|
||||
.itemLeadingEdge,
|
||||
0);
|
||||
expect(
|
||||
itemPositionsListener.itemPositions.value
|
||||
.firstWhere((position) => position.index == 5)
|
||||
.itemTrailingEdge,
|
||||
1 - _screenProportion(numberOfItems: 1, numberOfSeparators: 1));
|
||||
|
||||
expect(
|
||||
itemPositionsListener.itemPositions.value
|
||||
.firstWhere((position) => position.index == 6)
|
||||
.itemTrailingEdge,
|
||||
1);
|
||||
expect(
|
||||
itemPositionsListener.itemPositions.value
|
||||
.where((position) => position.index == 7),
|
||||
isEmpty);
|
||||
});
|
||||
|
||||
testWidgets('List positioned with 5 at top', (WidgetTester tester) async {
|
||||
final itemPositionsListener = ItemPositionsListener.create();
|
||||
await setUpWidgetTest(tester,
|
||||
itemPositionsListener: itemPositionsListener, initialIndex: 5);
|
||||
|
||||
expect(find.text('Item 4'), findsNothing);
|
||||
expect(find.text('Separator 4'), findsNothing);
|
||||
expect(find.text('Item 5'), findsOneWidget);
|
||||
expect(find.text('Separator 5'), findsOneWidget);
|
||||
expect(find.text('Separator 10'), findsOneWidget);
|
||||
expect(find.text('Item 11'), findsOneWidget);
|
||||
expect(find.text('Separator 11'), findsNothing);
|
||||
|
||||
expect(
|
||||
itemPositionsListener.itemPositions.value
|
||||
.where((position) => position.index == 4),
|
||||
isEmpty);
|
||||
expect(
|
||||
itemPositionsListener.itemPositions.value
|
||||
.firstWhere((position) => position.index == 5)
|
||||
.itemLeadingEdge,
|
||||
0);
|
||||
});
|
||||
|
||||
testWidgets('List positioned with 9 at middle', (WidgetTester tester) async {
|
||||
final itemPositionsListener = ItemPositionsListener.create();
|
||||
await setUpWidgetTest(tester,
|
||||
itemPositionsListener: itemPositionsListener,
|
||||
initialIndex: 9,
|
||||
initialAlignment: 0.5);
|
||||
|
||||
expect(tester.getTopLeft(find.text('Item 9')).dy, screenHeight / 2);
|
||||
expect(tester.getTopLeft(find.text('Item 8')).dy,
|
||||
screenHeight / 2 - itemHeight - separatorHeight);
|
||||
expect(tester.getTopLeft(find.text('Item 10')).dy,
|
||||
screenHeight / 2 + itemHeight + separatorHeight);
|
||||
|
||||
expect(
|
||||
itemPositionsListener.itemPositions.value
|
||||
.firstWhere((position) => position.index == 9)
|
||||
.itemLeadingEdge,
|
||||
0.5);
|
||||
expect(
|
||||
itemPositionsListener.itemPositions.value
|
||||
.firstWhere((position) => position.index == 8)
|
||||
.itemLeadingEdge,
|
||||
0.5 - _screenProportion(numberOfItems: 1, numberOfSeparators: 1));
|
||||
expect(
|
||||
itemPositionsListener.itemPositions.value
|
||||
.firstWhere((position) => position.index == 10)
|
||||
.itemLeadingEdge,
|
||||
0.5 + _screenProportion(numberOfItems: 1, numberOfSeparators: 1));
|
||||
});
|
||||
|
||||
testWidgets('Scroll to 9 half way off top', (WidgetTester tester) async {
|
||||
final itemPositionsListener = ItemPositionsListener.create();
|
||||
final itemScrollController = ItemScrollController();
|
||||
await setUpWidgetTest(tester,
|
||||
itemPositionsListener: itemPositionsListener,
|
||||
itemScrollController: itemScrollController);
|
||||
|
||||
unawaited(itemScrollController.scrollTo(
|
||||
index: 9,
|
||||
duration: scrollDuration,
|
||||
alignment: -(itemHeight / screenHeight) / 2));
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
await tester.pump(scrollDuration + scrollDurationTolerance);
|
||||
|
||||
expect(tester.getTopLeft(find.text('Item 9')).dy, -itemHeight / 2);
|
||||
|
||||
expect(
|
||||
itemPositionsListener.itemPositions.value
|
||||
.firstWhere((position) => position.index == 9)
|
||||
.itemLeadingEdge,
|
||||
_screenProportion(numberOfItems: -0.5, numberOfSeparators: 0));
|
||||
expect(
|
||||
itemPositionsListener.itemPositions.value
|
||||
.firstWhere((position) => position.index == 9)
|
||||
.itemTrailingEdge,
|
||||
_screenProportion(numberOfItems: 0.5, numberOfSeparators: 0));
|
||||
});
|
||||
|
||||
testWidgets('Jump to 9 half way off top', (WidgetTester tester) async {
|
||||
final itemPositionsListener = ItemPositionsListener.create();
|
||||
final itemScrollController = ItemScrollController();
|
||||
await setUpWidgetTest(tester,
|
||||
itemPositionsListener: itemPositionsListener,
|
||||
itemScrollController: itemScrollController);
|
||||
|
||||
itemScrollController.jumpTo(
|
||||
index: 9, alignment: -(itemHeight / screenHeight) / 2);
|
||||
await tester.pump();
|
||||
|
||||
expect(tester.getTopLeft(find.text('Item 9')).dy, -itemHeight / 2);
|
||||
|
||||
expect(
|
||||
itemPositionsListener.itemPositions.value
|
||||
.firstWhere((position) => position.index == 9)
|
||||
.itemLeadingEdge,
|
||||
_screenProportion(numberOfItems: -0.5, numberOfSeparators: 0));
|
||||
expect(
|
||||
itemPositionsListener.itemPositions.value
|
||||
.firstWhere((position) => position.index == 9)
|
||||
.itemTrailingEdge,
|
||||
_screenProportion(numberOfItems: 0.5, numberOfSeparators: 0));
|
||||
});
|
||||
|
||||
testWidgets('List positioned with 9 at middle scroll to 16 at bottom',
|
||||
(WidgetTester tester) async {
|
||||
final itemScrollController = ItemScrollController();
|
||||
final itemPositionsListener = ItemPositionsListener.create();
|
||||
await setUpWidgetTest(tester,
|
||||
itemScrollController: itemScrollController,
|
||||
itemPositionsListener: itemPositionsListener,
|
||||
initialIndex: 9,
|
||||
initialAlignment: 0.5);
|
||||
|
||||
unawaited(itemScrollController.scrollTo(
|
||||
index: 16, duration: scrollDuration, alignment: 1.0));
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
await tester.pump(scrollDuration + scrollDurationTolerance);
|
||||
|
||||
expect(tester.getBottomRight(find.text('Item 15')).dy,
|
||||
screenHeight - separatorHeight);
|
||||
|
||||
expect(
|
||||
itemPositionsListener.itemPositions.value
|
||||
.firstWhere((position) => position.index == 15)
|
||||
.itemTrailingEdge,
|
||||
1 - _screenProportion(numberOfItems: 0, numberOfSeparators: 1));
|
||||
});
|
||||
|
||||
testWidgets('physics', (WidgetTester tester) async {
|
||||
final itemScrollController = ItemScrollController();
|
||||
await setUpWidgetTest(tester,
|
||||
itemScrollController: itemScrollController,
|
||||
physics: const BouncingScrollPhysics());
|
||||
|
||||
await tester.drag(
|
||||
find.byType(ScrollablePositionedList), const Offset(0, 50));
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
|
||||
expect(tester.getTopLeft(find.text('Item 0')).dy, greaterThan(0));
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
expect(tester.getTopLeft(find.text('Item 0')).dy, 0);
|
||||
|
||||
unawaited(
|
||||
itemScrollController.scrollTo(index: 100, duration: scrollDuration));
|
||||
await tester.pumpAndSettle();
|
||||
itemScrollController.jumpTo(index: 0);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.drag(
|
||||
find.byType(ScrollablePositionedList), const Offset(0, 50));
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
|
||||
expect(tester.getTopLeft(find.text('Item 0')).dy, greaterThan(0));
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
expect(tester.getTopLeft(find.text('Item 0')).dy, 0);
|
||||
});
|
||||
|
||||
testWidgets('correct index semantics', (WidgetTester tester) async {
|
||||
await setUpWidgetTest(tester, initialIndex: 5);
|
||||
|
||||
await tester.drag(
|
||||
find.byType(ScrollablePositionedList), const Offset(0, 4 * itemHeight));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final indexSemantics3 = tester.widget<IndexedSemantics>(find.ancestor(
|
||||
of: find.text('Item 3'), matching: find.byType(IndexedSemantics)));
|
||||
expect(indexSemantics3.index, 3);
|
||||
final indexSemantics4 = tester.widget<IndexedSemantics>(find.ancestor(
|
||||
of: find.text('Item 4'), matching: find.byType(IndexedSemantics)));
|
||||
expect(indexSemantics4.index, 4);
|
||||
});
|
||||
|
||||
testWidgets('addIndexSemantics = false', (WidgetTester tester) async {
|
||||
final itemScrollController = ItemScrollController();
|
||||
await setUpWidgetTest(
|
||||
tester,
|
||||
itemScrollController: itemScrollController,
|
||||
initialIndex: 5,
|
||||
addSemanticIndexes: false,
|
||||
);
|
||||
|
||||
expect(find.byType(IndexedSemantics), findsNothing);
|
||||
|
||||
unawaited(
|
||||
itemScrollController.scrollTo(index: 100, duration: scrollDuration));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(IndexedSemantics), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('semanticChildCount specified', (WidgetTester tester) async {
|
||||
final itemScrollController = ItemScrollController();
|
||||
|
||||
await setUpWidgetTest(
|
||||
tester,
|
||||
semanticChildCount: 30,
|
||||
itemScrollController: itemScrollController,
|
||||
);
|
||||
|
||||
final customScrollView =
|
||||
tester.widget<CustomScrollView>(find.byType(UnboundedCustomScrollView));
|
||||
expect(customScrollView.semanticChildCount, 30);
|
||||
|
||||
unawaited(
|
||||
itemScrollController.scrollTo(index: 100, duration: scrollDuration));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final customScrollView2 =
|
||||
tester.widget<CustomScrollView>(find.byType(UnboundedCustomScrollView));
|
||||
expect(customScrollView2.semanticChildCount, 30);
|
||||
});
|
||||
|
||||
testWidgets('semanticChildCount not specified', (WidgetTester tester) async {
|
||||
final itemScrollController = ItemScrollController();
|
||||
await setUpWidgetTest(
|
||||
tester,
|
||||
itemScrollController: itemScrollController,
|
||||
);
|
||||
|
||||
final customScrollView =
|
||||
tester.widget<CustomScrollView>(find.byType(UnboundedCustomScrollView));
|
||||
expect(customScrollView.semanticChildCount, defaultItemCount);
|
||||
|
||||
unawaited(
|
||||
itemScrollController.scrollTo(index: 100, duration: scrollDuration));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final customScrollView2 =
|
||||
tester.widget<CustomScrollView>(find.byType(UnboundedCustomScrollView));
|
||||
expect(customScrollView2.semanticChildCount, defaultItemCount);
|
||||
});
|
||||
|
||||
testWidgets('padding test - centered at top', (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, 10 + itemHeight + separatorHeight));
|
||||
expect(tester.getTopRight(find.text('Item 1')),
|
||||
const Offset(screenWidth - 10, 10 + itemHeight + separatorHeight));
|
||||
|
||||
unawaited(
|
||||
itemScrollController.scrollTo(index: 494, duration: scrollDuration));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.drag(
|
||||
find.byType(ScrollablePositionedList), const Offset(0, -500));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(tester.getBottomRight(find.text('Item 499')),
|
||||
const Offset(screenWidth - 10, screenHeight - 10));
|
||||
});
|
||||
|
||||
testWidgets('padding test - centered sliver not at top',
|
||||
(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 + 2 * (separatorHeight + itemHeight)));
|
||||
expect(
|
||||
tester.getTopRight(find.text('Item 3')),
|
||||
const Offset(
|
||||
screenWidth - 10, 10 + 3 * (itemHeight + separatorHeight)));
|
||||
});
|
||||
|
||||
testWidgets('no repaint bounderies', (WidgetTester tester) async {
|
||||
final itemScrollController = ItemScrollController();
|
||||
await setUpWidgetTest(
|
||||
tester,
|
||||
itemScrollController: itemScrollController,
|
||||
initialIndex: 2,
|
||||
padding: const EdgeInsets.all(10),
|
||||
addRepaintBoundaries: false,
|
||||
);
|
||||
|
||||
expect(
|
||||
tester
|
||||
.widgetList(find.descendant(
|
||||
of: find.byType(ScrollablePositionedList),
|
||||
matching: find.byType(RepaintBoundary)))
|
||||
.length,
|
||||
lessThan(5));
|
||||
});
|
||||
|
||||
testWidgets('no automatic keep alives', (WidgetTester tester) async {
|
||||
final itemScrollController = ItemScrollController();
|
||||
await setUpWidgetTest(
|
||||
tester,
|
||||
itemScrollController: itemScrollController,
|
||||
initialIndex: 2,
|
||||
padding: const EdgeInsets.all(10),
|
||||
addAutomaticKeepAlives: false,
|
||||
);
|
||||
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(ScrollablePositionedList),
|
||||
matching: find.byType(AutomaticKeepAlive)),
|
||||
findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('List can be keyed', (WidgetTester tester) async {
|
||||
final key = ValueKey('key');
|
||||
|
||||
await setUpWidgetTest(tester, key: key);
|
||||
|
||||
expect(find.byKey(key), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('Empty list then update to single item list',
|
||||
(WidgetTester tester) async {
|
||||
tester.binding.window.devicePixelRatioTestValue = 1.0;
|
||||
tester.binding.window.physicalSizeTestValue =
|
||||
const Size(screenWidth, screenHeight);
|
||||
|
||||
final itemScrollController = ItemScrollController();
|
||||
final itemPositionsListener = ItemPositionsListener.create();
|
||||
final itemCount = ValueNotifier<int>(0);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: ValueListenableBuilder<int>(
|
||||
valueListenable: itemCount,
|
||||
builder: (context, itemCount, child) {
|
||||
return ScrollablePositionedList.separated(
|
||||
initialScrollIndex: 0,
|
||||
initialAlignment: 0,
|
||||
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'),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
itemCount.value = 1;
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Item 0'), findsOneWidget);
|
||||
expect(find.text('Separator 0'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('ItemPositions: Empty list then update to 10 items list',
|
||||
(WidgetTester tester) async {
|
||||
tester.binding.window.devicePixelRatioTestValue = 1.0;
|
||||
tester.binding.window.physicalSizeTestValue =
|
||||
const Size(screenWidth, screenHeight);
|
||||
|
||||
final itemScrollController = ItemScrollController();
|
||||
final itemPositionsListener = ItemPositionsListener.create();
|
||||
final itemCount = ValueNotifier<int>(0);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: ValueListenableBuilder<int>(
|
||||
valueListenable: itemCount,
|
||||
builder: (context, itemCount, child) {
|
||||
return ScrollablePositionedList.separated(
|
||||
initialScrollIndex: 0,
|
||||
initialAlignment: 0,
|
||||
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'),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Item 0'), findsNothing);
|
||||
expect(find.text('Separator 0'), findsNothing);
|
||||
expect(itemPositionsListener.itemPositions.value, []);
|
||||
|
||||
itemCount.value = 10;
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Item 0'), findsOneWidget);
|
||||
expect(find.text('Separator 5'), findsOneWidget);
|
||||
expect(find.text('Item 6'), findsOneWidget);
|
||||
expect(find.text('Separator 6'), findsNothing);
|
||||
expect(find.text('Item 7'), findsNothing);
|
||||
|
||||
expect(itemPositionsListener.itemPositions.value, isNotEmpty);
|
||||
expect(
|
||||
itemPositionsListener.itemPositions.value
|
||||
.firstWhere((position) => position.index == 0)
|
||||
.itemLeadingEdge,
|
||||
0);
|
||||
expect(
|
||||
itemPositionsListener.itemPositions.value
|
||||
.firstWhere((position) => position.index == 5)
|
||||
.itemTrailingEdge,
|
||||
1 - _screenProportion(numberOfItems: 1, numberOfSeparators: 1));
|
||||
|
||||
expect(
|
||||
itemPositionsListener.itemPositions.value
|
||||
.firstWhere((position) => position.index == 6)
|
||||
.itemTrailingEdge,
|
||||
1);
|
||||
expect(
|
||||
itemPositionsListener.itemPositions.value
|
||||
.where((position) => position.index == 7),
|
||||
isEmpty);
|
||||
});
|
||||
}
|
||||
|
||||
double _screenProportion(
|
||||
{required double numberOfItems, required double numberOfSeparators}) =>
|
||||
(numberOfItems * itemHeight + numberOfSeparators * separatorHeight) /
|
||||
screenHeight;
|
||||
+220
@@ -0,0 +1,220 @@
|
||||
// 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 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:pedantic/pedantic.dart';
|
||||
import 'package:stream_chat_flutter/src/scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||
|
||||
const screenHeight = 400.0;
|
||||
const screenWidth = 400.0;
|
||||
const itemWidth = screenWidth / 10.0;
|
||||
const separatorWidth = screenWidth / 20.0;
|
||||
const itemCount = 500;
|
||||
const scrollDuration = Duration(seconds: 1);
|
||||
const tolerance = 10e-5;
|
||||
|
||||
void main() {
|
||||
Future<void> setUpWidgetTest(
|
||||
WidgetTester tester, {
|
||||
ItemScrollController? itemScrollController,
|
||||
ItemPositionsListener? itemPositionsListener,
|
||||
bool reverse = false,
|
||||
EdgeInsets? padding,
|
||||
int initialScrollIndex = 0,
|
||||
}) async {
|
||||
tester.binding.window.devicePixelRatioTestValue = 1.0;
|
||||
tester.binding.window.physicalSizeTestValue =
|
||||
const Size(screenWidth, screenHeight);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: ScrollablePositionedList.separated(
|
||||
itemCount: itemCount,
|
||||
itemScrollController: itemScrollController,
|
||||
itemBuilder: (context, index) => SizedBox(
|
||||
width: itemWidth,
|
||||
child: Text('Item $index'),
|
||||
),
|
||||
separatorBuilder: (context, index) => SizedBox(
|
||||
width: separatorWidth,
|
||||
child: Text('Separator $index'),
|
||||
),
|
||||
itemPositionsListener: itemPositionsListener,
|
||||
scrollDirection: Axis.horizontal,
|
||||
reverse: reverse,
|
||||
padding: padding,
|
||||
initialScrollIndex: initialScrollIndex,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('List positioned with 0 at left', (WidgetTester tester) async {
|
||||
final itemPositionsListener = ItemPositionsListener.create();
|
||||
await setUpWidgetTest(tester, itemPositionsListener: itemPositionsListener);
|
||||
|
||||
expect(tester.getTopLeft(find.text('Item 0')).dx, 0);
|
||||
expect(tester.getBottomLeft(find.text('Item 1')).dx,
|
||||
itemWidth + separatorWidth);
|
||||
|
||||
expect(
|
||||
itemPositionsListener.itemPositions.value
|
||||
.firstWhere((position) => position.index == 0)
|
||||
.itemLeadingEdge,
|
||||
0);
|
||||
expect(
|
||||
itemPositionsListener.itemPositions.value
|
||||
.firstWhere((position) => position.index == 1)
|
||||
.itemLeadingEdge,
|
||||
_screenProportion(numberOfItems: 1, numberOfSeparators: 1));
|
||||
});
|
||||
|
||||
testWidgets('Scroll to 2 (already on screen)', (WidgetTester tester) async {
|
||||
final itemScrollController = ItemScrollController();
|
||||
final itemPositionsListener = ItemPositionsListener.create();
|
||||
await setUpWidgetTest(tester,
|
||||
itemScrollController: itemScrollController,
|
||||
itemPositionsListener: itemPositionsListener);
|
||||
|
||||
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')).dx, 0);
|
||||
expect(
|
||||
tester.getTopLeft(find.text('Item 3')).dx, itemWidth + separatorWidth);
|
||||
|
||||
expect(
|
||||
itemPositionsListener.itemPositions.value
|
||||
.firstWhere((position) => position.index == 2)
|
||||
.itemLeadingEdge,
|
||||
0);
|
||||
expect(
|
||||
itemPositionsListener.itemPositions.value
|
||||
.firstWhere((position) => position.index == 3)
|
||||
.itemLeadingEdge,
|
||||
_screenProportion(numberOfItems: 1, numberOfSeparators: 1));
|
||||
});
|
||||
|
||||
testWidgets('Scroll to 100 (not already on screen)',
|
||||
(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 == 101)
|
||||
.itemLeadingEdge,
|
||||
_screenProportion(numberOfItems: 1, numberOfSeparators: 1));
|
||||
});
|
||||
|
||||
testWidgets('Jump to 100', (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')).dx, 0);
|
||||
expect(tester.getTopLeft(find.text('Item 101')).dx,
|
||||
itemWidth + separatorWidth);
|
||||
|
||||
expect(
|
||||
itemPositionsListener.itemPositions.value
|
||||
.firstWhere((position) => position.index == 100)
|
||||
.itemLeadingEdge,
|
||||
0);
|
||||
expect(
|
||||
itemPositionsListener.itemPositions.value
|
||||
.firstWhere((position) => position.index == 101)
|
||||
.itemLeadingEdge,
|
||||
_screenProportion(numberOfItems: 1, numberOfSeparators: 1));
|
||||
});
|
||||
|
||||
testWidgets('padding test - centered sliver at left',
|
||||
(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 + itemWidth + separatorWidth, 10));
|
||||
expect(tester.getBottomRight(find.text('Item 1')),
|
||||
const Offset(10 + 2 * itemWidth + separatorWidth, screenHeight - 10));
|
||||
|
||||
unawaited(
|
||||
itemScrollController.scrollTo(index: 494, duration: scrollDuration));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.drag(
|
||||
find.byType(ScrollablePositionedList), const Offset(-500, 0));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(tester.getBottomRight(find.text('Item 499')),
|
||||
const Offset(screenWidth - 10, screenHeight - 10));
|
||||
});
|
||||
|
||||
testWidgets('padding test - centered sliver not at left',
|
||||
(WidgetTester tester) async {
|
||||
final itemScrollController = ItemScrollController();
|
||||
final itemPositionsListener = ItemPositionsListener.create();
|
||||
await setUpWidgetTest(
|
||||
tester,
|
||||
itemScrollController: itemScrollController,
|
||||
itemPositionsListener: itemPositionsListener,
|
||||
initialScrollIndex: 2,
|
||||
padding: const EdgeInsets.all(10),
|
||||
);
|
||||
|
||||
await tester.drag(
|
||||
find.byType(ScrollablePositionedList), const Offset(300, 0));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(tester.getTopLeft(find.text('Item 0')), const Offset(10, 10));
|
||||
expect(tester.getTopLeft(find.text('Item 2')),
|
||||
const Offset(10 + 2 * (itemWidth + separatorWidth), 10));
|
||||
expect(tester.getTopLeft(find.text('Item 3')),
|
||||
const Offset(10 + 3 * (itemWidth + separatorWidth), 10));
|
||||
|
||||
expect(
|
||||
itemPositionsListener.itemPositions.value
|
||||
.firstWhere((position) => position.index == 2)
|
||||
.itemLeadingEdge,
|
||||
closeTo(
|
||||
10 / screenWidth + 2 * ((itemWidth + separatorWidth) / screenWidth),
|
||||
tolerance));
|
||||
});
|
||||
}
|
||||
|
||||
double _screenProportion(
|
||||
{required double numberOfItems, required double numberOfSeparators}) =>
|
||||
(numberOfItems * itemWidth + numberOfSeparators * separatorWidth) /
|
||||
screenHeight;
|
||||
Reference in New Issue
Block a user