feat: move scrollable positioned list to internal

src
This commit is contained in:
Gordon Hayes
2021-10-15 15:13:59 +02:00
parent b4e544930e
commit 3a7343904d
22 changed files with 6223 additions and 6 deletions
@@ -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));
});
}
@@ -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);
});
}
@@ -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);
});
}
@@ -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)));
});
}
@@ -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;
@@ -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;
@@ -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;