From f6a8d9bc9db995087da70ae3bd8b96583d37d4ad Mon Sep 17 00:00:00 2001 From: Angelo Silvestre Date: Sat, 21 Oct 2023 13:47:17 -0300 Subject: [PATCH 01/13] [SuperEditor][SuperReader][Android] Fix scroll physics (Resolves #1539) --- .../document_gestures_touch_android.dart | 2 +- ...nly_document_android_touch_interactor.dart | 2 +- .../supereditor_scrolling_test.dart | 171 ++++++++++++++++++ .../super_reader_scrolling_test.dart | 167 +++++++++++++++++ 4 files changed, 340 insertions(+), 2 deletions(-) diff --git a/super_editor/lib/src/default_editor/document_gestures_touch_android.dart b/super_editor/lib/src/default_editor/document_gestures_touch_android.dart index 2eb3c1d61c..477798187f 100644 --- a/super_editor/lib/src/default_editor/document_gestures_touch_android.dart +++ b/super_editor/lib/src/default_editor/document_gestures_touch_android.dart @@ -785,7 +785,7 @@ class _AndroidDocumentTouchInteractorState extends State MaterialApp( + home: Scaffold( + body: ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 200), + child: CustomScrollView( + controller: scrollController, + slivers: [ + SliverToBoxAdapter( + child: superEditor, + ), + ], + ), + ), + ), + ), + ) + .pump(); + + // Ensure the scrollview didn't start scrolled. + expect(scrollController.offset, 0); + + final editorRect = tester.getRect(find.byType(SuperEditor)); + + const dragFrameCount = 10; + final dragAmountPerFrame = editorRect.height / dragFrameCount; + + // Drag from the top all the way up to the bottom of the editor. + final dragGesture = await tester.startGesture(editorRect.topCenter); + for (int i = 0; i < dragFrameCount; i += 1) { + await dragGesture.moveBy(Offset(0, dragAmountPerFrame)); + await tester.pump(); + + // Ensure we don't scroll. + expect(scrollController.offset, 0); + } + + // End the gesture. + await dragGesture.up(); + await dragGesture.removePointer(); + await tester.pumpAndSettle(); + }); + + testWidgetsOnAndroid("doesn't overscroll when dragging up", (tester) async { + final scrollController = ScrollController(); + + await tester + .createDocument() + .withSingleParagraph() + .withInputSource(TextInputSource.ime) + .withCustomWidgetTreeBuilder( + (superEditor) => MaterialApp( + home: Scaffold( + body: ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 200), + child: CustomScrollView( + controller: scrollController, + slivers: [ + SliverToBoxAdapter( + child: superEditor, + ), + ], + ), + ), + ), + ), + ) + .pump(); + + // Jump to the bottom. + scrollController.jumpTo(scrollController.position.maxScrollExtent); + + final editorRect = tester.getRect(find.byType(SuperEditor)); + + const dragFrameCount = 10; + final dragAmountPerFrame = editorRect.height / dragFrameCount; + + // Drag from the bottom all the way up to the top of the editor. + final dragGesture = await tester.startGesture(editorRect.topCenter); + addTearDown(() => dragGesture.removePointer()); + for (int i = 0; i < dragFrameCount; i += 1) { + await dragGesture.moveBy(Offset(0, -dragAmountPerFrame)); + await tester.pump(); + + // Ensure we don't scroll. + expect(scrollController.offset, scrollController.position.maxScrollExtent); + } + + // End the gesture. + await dragGesture.up(); + await dragGesture.removePointer(); + await tester.pumpAndSettle(); + }); + group('respects horizontal scrolling', () { testWidgetsOnAllPlatforms('inside a TabBar', (tester) async { final tabController = TabController(length: 2, vsync: tester); diff --git a/super_editor/test/super_reader/super_reader_scrolling_test.dart b/super_editor/test/super_reader/super_reader_scrolling_test.dart index ad40a74b43..4cc9582e4a 100644 --- a/super_editor/test/super_reader/super_reader_scrolling_test.dart +++ b/super_editor/test/super_reader/super_reader_scrolling_test.dart @@ -220,6 +220,74 @@ void main() { ); }); + testWidgetsOnAndroid("doesn't underscroll when dragging down", (tester) async { + final scrollController = ScrollController(); + + await tester + .createDocument() + .withSingleParagraph() + .withEditorSize(const Size(300, 300)) + .withScrollController(scrollController) + .pump(); + + // Ensure the scrollview didn't start scrolled. + expect(scrollController.offset, 0); + + final editorRect = tester.getRect(find.byType(SuperReader)); + + const dragFrameCount = 10; + final dragAmountPerFrame = editorRect.height / dragFrameCount; + + // Drag from the top all the way up to the bottom of the editor. + final dragGesture = await tester.startGesture(editorRect.topCenter); + for (int i = 0; i < dragFrameCount; i += 1) { + await dragGesture.moveBy(Offset(0, dragAmountPerFrame)); + await tester.pump(); + + // Ensure we don't scroll. + expect(scrollController.offset, 0); + } + + // End the gesture. + await dragGesture.up(); + await dragGesture.removePointer(); + await tester.pumpAndSettle(); + }); + + testWidgetsOnAndroid("doesn't overscroll when dragging up", (tester) async { + final scrollController = ScrollController(); + + await tester + .createDocument() + .withSingleParagraph() + .withEditorSize(const Size(300, 300)) + .withScrollController(scrollController) + .pump(); + + // Jump to the bottom. + scrollController.jumpTo(scrollController.position.maxScrollExtent); + + final readerRect = tester.getRect(find.byType(SuperReader)); + + const dragFrameCount = 10; + final dragAmountPerFrame = readerRect.height / dragFrameCount; + + // Drag from the bottom all the way up to the top of the reader. + final dragGesture = await tester.startGesture(readerRect.topCenter); + for (int i = 0; i < dragFrameCount; i += 1) { + await dragGesture.moveBy(Offset(0, -dragAmountPerFrame)); + await tester.pump(); + + // Ensure we don't scroll. + expect(scrollController.offset, scrollController.position.maxScrollExtent); + } + + // End the gesture. + await dragGesture.up(); + await dragGesture.removePointer(); + await tester.pumpAndSettle(); + }); + group("when all content fits in the viewport", () { testWidgetsOnDesktop( "trackpad doesn't scroll content", @@ -424,6 +492,105 @@ void main() { expect(scrollController.offset, greaterThan(0)); expect(SuperReaderInspector.findDocumentSelection(), isNull); }); + + testWidgetsOnAndroid("doesn't underscroll when dragging down", (tester) async { + final scrollController = ScrollController(); + + await tester + .createDocument() + .withSingleParagraph() + .withCustomWidgetTreeBuilder( + (superReader) => MaterialApp( + home: Scaffold( + body: ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 200), + child: CustomScrollView( + controller: scrollController, + slivers: [ + SliverToBoxAdapter( + child: superReader, + ), + ], + ), + ), + ), + ), + ) + .pump(); + + // Ensure the scrollview didn't start scrolled. + expect(scrollController.offset, 0); + + final readerRect = tester.getRect(find.byType(SuperReader)); + + const dragFrameCount = 10; + final dragAmountPerFrame = readerRect.height / dragFrameCount; + + // Drag from the top all the way up to the bottom of the reader. + final dragGesture = await tester.startGesture(readerRect.topCenter); + for (int i = 0; i < dragFrameCount; i += 1) { + await dragGesture.moveBy(Offset(0, dragAmountPerFrame)); + await tester.pump(); + + // Ensure we don't scroll. + expect(scrollController.offset, 0); + } + + // End the gesture. + await dragGesture.up(); + await dragGesture.removePointer(); + await tester.pumpAndSettle(); + }); + + testWidgetsOnAndroid("doesn't overscroll when dragging up", (tester) async { + final scrollController = ScrollController(); + + await tester + .createDocument() + .withSingleParagraph() + .withCustomWidgetTreeBuilder( + (superReader) => MaterialApp( + home: Scaffold( + body: ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 200), + child: CustomScrollView( + controller: scrollController, + slivers: [ + SliverToBoxAdapter( + child: superReader, + ), + ], + ), + ), + ), + ), + ) + .pump(); + + // Jump to the bottom. + scrollController.jumpTo(scrollController.position.maxScrollExtent); + + final readerRect = tester.getRect(find.byType(SuperReader)); + + const dragFrameCount = 10; + final dragAmountPerFrame = readerRect.height / dragFrameCount; + + // Drag from the bottom all the way up to the top of the reader. + final dragGesture = await tester.startGesture(readerRect.topCenter); + addTearDown(() => dragGesture.removePointer()); + for (int i = 0; i < dragFrameCount; i += 1) { + await dragGesture.moveBy(Offset(0, -dragAmountPerFrame)); + await tester.pump(); + + // Ensure we don't scroll. + expect(scrollController.offset, scrollController.position.maxScrollExtent); + } + + // End the gesture. + await dragGesture.up(); + await dragGesture.removePointer(); + await tester.pumpAndSettle(); + }); }); }); } From f4c2f36672699e6529773133ecf7068cb33e8121 Mon Sep 17 00:00:00 2001 From: Angelo Silvestre Date: Tue, 24 Oct 2023 00:10:03 -0300 Subject: [PATCH 02/13] User ScrollPosition.drag to perform scrolling --- .../document_gestures_touch_android.dart | 29 ++- .../document_gestures_touch_ios.dart | 33 ++- ...nly_document_android_touch_interactor.dart | 28 ++- ...ad_only_document_ios_touch_interactor.dart | 35 +++- .../supereditor_scrolling_test.dart | 194 +++++++++++++++++- .../super_reader_scrolling_test.dart | 192 ++++++++++++++++- 6 files changed, 485 insertions(+), 26 deletions(-) diff --git a/super_editor/lib/src/default_editor/document_gestures_touch_android.dart b/super_editor/lib/src/default_editor/document_gestures_touch_android.dart index 477798187f..4cce2200b1 100644 --- a/super_editor/lib/src/default_editor/document_gestures_touch_android.dart +++ b/super_editor/lib/src/default_editor/document_gestures_touch_android.dart @@ -144,6 +144,9 @@ class _AndroidDocumentTouchInteractorState extends State(null); + /// Holds the drag gesture that scrolls the document. + Drag? _drag; + @override void initState() { super.initState(); @@ -740,6 +743,11 @@ class _AndroidDocumentTouchInteractorState extends State( - () => PanGestureRecognizer(), - (PanGestureRecognizer recognizer) { + VerticalDragGestureRecognizer: GestureRecognizerFactoryWithHandlers( + () => VerticalDragGestureRecognizer(), + (VerticalDragGestureRecognizer recognizer) { recognizer + ..dragStartBehavior = DragStartBehavior.down ..onStart = _onPanStart ..onUpdate = _onPanUpdate ..onEnd = _onPanEnd diff --git a/super_editor/lib/src/default_editor/document_gestures_touch_ios.dart b/super_editor/lib/src/default_editor/document_gestures_touch_ios.dart index 256792dbde..6fabe5db2b 100644 --- a/super_editor/lib/src/default_editor/document_gestures_touch_ios.dart +++ b/super_editor/lib/src/default_editor/document_gestures_touch_ios.dart @@ -292,6 +292,9 @@ class _IosDocumentTouchInteractorState extends State bool get _isLongPressInProgress => _longPressStrategy != null; IosLongPressSelectionStrategy? _longPressStrategy; + /// Holds the drag gesture that scrolls the document. + Drag? _drag; + @override void initState() { super.initState(); @@ -809,15 +812,12 @@ class _IosDocumentTouchInteractorState extends State // placement during onTapDown, and then pick that up here. I think the little // bit of slop might be the problem. final selection = widget.selection.value; - if (selection == null) { - return; - } if (_isLongPressInProgress) { _dragMode = DragMode.longPress; _dragHandleType = null; _longPressStrategy!.onLongPressDragStart(); - } else if (selection.isCollapsed && _isOverCollapsedHandle(details.localPosition)) { + } else if (selection != null && selection.isCollapsed && _isOverCollapsedHandle(details.localPosition)) { _dragMode = DragMode.collapsed; _dragHandleType = HandleType.collapsed; } else if (_isOverBaseHandle(details.localPosition)) { @@ -827,12 +827,20 @@ class _IosDocumentTouchInteractorState extends State _dragMode = DragMode.extent; _dragHandleType = HandleType.downstream; } else { + _drag = scrollPosition.drag(details, () { + // Allows receiving touches while scrolling due to scroll momentum. + // This is needed to allow the user to stop scrolling by tapping down. + scrollPosition.context.setIgnorePointer(false); + }); return; } _controlsController!.doNotBlinkCaret(); _controlsController!.hideToolbar(); _controlsController!.showToolbar(); + if (selection == null) { + return; + } _globalStartDragOffset = details.globalPosition; final interactorBox = context.findRenderObject() as RenderBox; @@ -911,8 +919,9 @@ class _IosDocumentTouchInteractorState extends State void _onPanUpdate(DragUpdateDetails details) { // If the user isn't dragging a handle, then the user is trying to // scroll the document. Scroll it, accordingly. - if (_dragMode == null) { - scrollPosition.jumpTo(scrollPosition.pixels - details.delta.dy); + if (_dragMode == null && _drag != null) { + _drag!.update(details); + _positionToolbar(); return; } @@ -990,6 +999,13 @@ class _IosDocumentTouchInteractorState extends State ..hideMagnifier() ..blinkCaret(); + if (_drag != null) { + _drag!.end(details); + _drag = null; + + return; + } + if (_dragMode == null) { // User was dragging the scroll area. Go ballistic. if (scrollPosition is ScrollPositionWithSingleContext) { @@ -1011,6 +1027,11 @@ class _IosDocumentTouchInteractorState extends State void _onPanCancel() { _magnifierOffset.value = null; + if (_drag != null) { + _drag!.cancel(); + _drag = null; + } + if (_dragMode != null) { _onDragSelectionEnd(); } diff --git a/super_editor/lib/src/super_reader/read_only_document_android_touch_interactor.dart b/super_editor/lib/src/super_reader/read_only_document_android_touch_interactor.dart index a797455c0b..76c4281f47 100644 --- a/super_editor/lib/src/super_reader/read_only_document_android_touch_interactor.dart +++ b/super_editor/lib/src/super_reader/read_only_document_android_touch_interactor.dart @@ -135,6 +135,9 @@ class _ReadOnlyAndroidDocumentTouchInteractorState extends State(null); + /// Holds the drag gesture that scrolls the document. + Drag? _drag; + @override void initState() { super.initState(); @@ -674,6 +677,11 @@ class _ReadOnlyAndroidDocumentTouchInteractorState extends State( - () => PanGestureRecognizer(), - (PanGestureRecognizer recognizer) { + VerticalDragGestureRecognizer: GestureRecognizerFactoryWithHandlers( + () => VerticalDragGestureRecognizer(), + (VerticalDragGestureRecognizer recognizer) { recognizer + ..dragStartBehavior = DragStartBehavior.down ..onStart = _onPanStart ..onUpdate = _onPanUpdate ..onEnd = _onPanEnd diff --git a/super_editor/lib/src/super_reader/read_only_document_ios_touch_interactor.dart b/super_editor/lib/src/super_reader/read_only_document_ios_touch_interactor.dart index 84684ff79b..590609063c 100644 --- a/super_editor/lib/src/super_reader/read_only_document_ios_touch_interactor.dart +++ b/super_editor/lib/src/super_reader/read_only_document_ios_touch_interactor.dart @@ -254,6 +254,9 @@ class _SuperReaderIosDocumentTouchInteractorState extends State _longPressStrategy != null; IosLongPressSelectionStrategy? _longPressStrategy; + /// Holds the drag gesture that scrolls the document. + Drag? _drag; + @override void initState() { super.initState(); @@ -635,25 +638,30 @@ class _SuperReaderIosDocumentTouchInteractorState extends State MaterialApp( + home: Scaffold( + body: ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 200), + child: CustomScrollView( + controller: scrollController, + slivers: [ + SliverToBoxAdapter( + child: superEditor, + ), + ], + ), + ), + ), + ), + ) + .pump(); + + // Ensure the scrollview didn't start scrolled. + expect(scrollController.offset, 0); + + // Start a drag gesture a few pixels below the top of the editor. + final dragGesture = + await tester.startGesture(tester.getRect(find.byType(CustomScrollView)).topCenter + const Offset(0, 5)); + + // Drag an arbitrary amount, smaller than the editor size. + const dragFrameCount = 10; + const dragAmountPerFrame = (80 / dragFrameCount); + for (int i = 0; i < dragFrameCount; i += 1) { + await dragGesture.moveBy(const Offset(0, dragAmountPerFrame)); + await tester.pump(); + } + + // Ensure we are overscrolling while holding the pointer down. + await tester.pumpAndSettle(); + expect(scrollController.offset, lessThan(0.0)); + + // Release the pointer to end the gesture. + await dragGesture.up(); + await dragGesture.removePointer(); + await tester.pumpAndSettle(); + + // Ensure the we scrolled back to the top. + expect(scrollController.offset, 0.0); + }); + + testWidgetsOnIos('overscrolls when dragging up', (tester) async { + final scrollController = ScrollController(); + + // Pump an editor inside a CustomScrollView without enough room to display + // the whole content. + await tester + .createDocument() // + .withLongTextContent() + .withCustomWidgetTreeBuilder( + (superEditor) => MaterialApp( + home: Scaffold( + body: ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 200), + child: CustomScrollView( + controller: scrollController, + slivers: [ + SliverToBoxAdapter( + child: superEditor, + ), + ], + ), + ), + ), + ), + ) + .pump(); + + // Jump to the bottom. + scrollController.jumpTo(scrollController.position.maxScrollExtent); + await tester.pumpAndSettle(); + + // Start a drag gesture a few pixels above the top of the editor. + final dragGesture = + await tester.startGesture(tester.getRect(find.byType(CustomScrollView)).bottomCenter - const Offset(0, 5)); + + // Drag up an arbitrary amount, smaller than the editor size. + const dragFrameCount = 10; + const dragAmountPerFrame = (200 / dragFrameCount); + for (int i = 0; i < dragFrameCount; i += 1) { + await dragGesture.moveBy(const Offset(0, -dragAmountPerFrame)); + await tester.pump(); + } + + // Ensure we are overscrolling while holding the pointer down. + await tester.pumpAndSettle(); + expect(scrollController.offset, greaterThan(scrollController.position.maxScrollExtent)); + + // Release the pointer to end the gesture. + await dragGesture.up(); + await dragGesture.removePointer(); + await tester.pumpAndSettle(); + + // Ensure the we scrolled back to the end. + expect(scrollController.offset, scrollController.position.maxScrollExtent); + }); + group('respects horizontal scrolling', () { testWidgetsOnAllPlatforms('inside a TabBar', (tester) async { final tabController = TabController(length: 2, vsync: tester); diff --git a/super_editor/test/super_reader/super_reader_scrolling_test.dart b/super_editor/test/super_reader/super_reader_scrolling_test.dart index 4cc9582e4a..edb7fd9fae 100644 --- a/super_editor/test/super_reader/super_reader_scrolling_test.dart +++ b/super_editor/test/super_reader/super_reader_scrolling_test.dart @@ -220,7 +220,7 @@ void main() { ); }); - testWidgetsOnAndroid("doesn't underscroll when dragging down", (tester) async { + testWidgetsOnAndroid("doesn't overscroll when dragging down", (tester) async { final scrollController = ScrollController(); await tester @@ -288,6 +288,83 @@ void main() { await tester.pumpAndSettle(); }); + testWidgetsOnIos('overscrolls when dragging down', (tester) async { + final scrollController = ScrollController(); + + await tester + .createDocument() + .withSingleParagraph() + .withEditorSize(const Size(300, 300)) + .withScrollController(scrollController) + .pump(); + + // Ensure the scrollview didn't start scrolled. + expect(scrollController.offset, 0); + + // Start a drag gesture a few pixels below the top of the reader. + final dragGesture = + await tester.startGesture(tester.getRect(find.byType(SuperReader)).topCenter + const Offset(0, 5)); + + // Drag an arbitrary amount, smaller than the reader size. + const dragFrameCount = 10; + const dragAmountPerFrame = (80 / dragFrameCount); + for (int i = 0; i < dragFrameCount; i += 1) { + await dragGesture.moveBy(const Offset(0, dragAmountPerFrame)); + await tester.pump(); + } + + // Ensure we are overscrolling while holding the pointer down. + await tester.pumpAndSettle(); + expect(scrollController.offset, lessThan(0.0)); + + // Release the pointer to end the gesture. + await dragGesture.up(); + await dragGesture.removePointer(); + await tester.pumpAndSettle(); + + // Ensure the we scrolled back to the top. + expect(scrollController.offset, 0.0); + }); + + testWidgetsOnIos('overscrolls when dragging up', (tester) async { + final scrollController = ScrollController(); + + await tester + .createDocument() + .withSingleParagraph() + .withEditorSize(const Size(300, 300)) + .withScrollController(scrollController) + .pump(); + + // Jump to the bottom. + scrollController.jumpTo(scrollController.position.maxScrollExtent); + await tester.pumpAndSettle(); + + // Start a drag gesture a few pixels above the top of the reader. + final dragGesture = + await tester.startGesture(tester.getRect(find.byType(SuperReader)).bottomCenter - const Offset(0, 5)); + + // Drag up an arbitrary amount, smaller than the reader size. + const dragFrameCount = 10; + const dragAmountPerFrame = (200 / dragFrameCount); + for (int i = 0; i < dragFrameCount; i += 1) { + await dragGesture.moveBy(const Offset(0, -dragAmountPerFrame)); + await tester.pump(); + } + + // Ensure we are overscrolling while holding the pointer down. + await tester.pumpAndSettle(); + expect(scrollController.offset, greaterThan(scrollController.position.maxScrollExtent)); + + // Release the pointer to end the gesture. + await dragGesture.up(); + await dragGesture.removePointer(); + await tester.pumpAndSettle(); + + // Ensure the we scrolled back to the end. + expect(scrollController.offset, scrollController.position.maxScrollExtent); + }); + group("when all content fits in the viewport", () { testWidgetsOnDesktop( "trackpad doesn't scroll content", @@ -493,7 +570,7 @@ void main() { expect(SuperReaderInspector.findDocumentSelection(), isNull); }); - testWidgetsOnAndroid("doesn't underscroll when dragging down", (tester) async { + testWidgetsOnAndroid("doesn't overscroll when dragging down", (tester) async { final scrollController = ScrollController(); await tester @@ -591,6 +668,117 @@ void main() { await dragGesture.removePointer(); await tester.pumpAndSettle(); }); + + testWidgetsOnIos('overscrolls when dragging down', (tester) async { + final scrollController = ScrollController(); + + // Pump a reader inside a CustomScrollView without enough room to display + // the whole content. + await tester + .createDocument() // + .withLongTextContent() + .withCustomWidgetTreeBuilder( + (superReader) => MaterialApp( + home: Scaffold( + body: ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 200), + child: CustomScrollView( + controller: scrollController, + slivers: [ + SliverToBoxAdapter( + child: superReader, + ), + ], + ), + ), + ), + ), + ) + .pump(); + + // Ensure the scrollview didn't start scrolled. + expect(scrollController.offset, 0); + + // Start a drag gesture a few pixels below the top of the reader. + final dragGesture = + await tester.startGesture(tester.getRect(find.byType(CustomScrollView)).topCenter + const Offset(0, 5)); + + // Drag an arbitrary amount, smaller than the reader size. + const dragFrameCount = 10; + const dragAmountPerFrame = (80 / dragFrameCount); + for (int i = 0; i < dragFrameCount; i += 1) { + await dragGesture.moveBy(const Offset(0, dragAmountPerFrame)); + await tester.pump(); + } + + // Ensure we are overscrolling while holding the pointer down. + await tester.pumpAndSettle(); + expect(scrollController.offset, lessThan(0.0)); + + // Release the pointer to end the gesture. + await dragGesture.up(); + await dragGesture.removePointer(); + await tester.pumpAndSettle(); + + // Ensure the we scrolled back to the top. + expect(scrollController.offset, 0.0); + }); + + testWidgetsOnIos('overscrolls when dragging up', (tester) async { + final scrollController = ScrollController(); + + // Pump a reader inside a CustomScrollView without enough room to display + // the whole content. + await tester + .createDocument() // + .withLongTextContent() + .withCustomWidgetTreeBuilder( + (superReader) => MaterialApp( + home: Scaffold( + body: ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 200), + child: CustomScrollView( + controller: scrollController, + slivers: [ + SliverToBoxAdapter( + child: superReader, + ), + ], + ), + ), + ), + ), + ) + .pump(); + + // Jump to the bottom. + scrollController.jumpTo(scrollController.position.maxScrollExtent); + await tester.pumpAndSettle(); + + // Start a drag gesture a few pixels above the top of the reader. + final dragGesture = + await tester.startGesture(tester.getRect(find.byType(CustomScrollView)).bottomCenter - const Offset(0, 5)); + + // Drag up an arbitrary amount, smaller than the reader size. + const dragFrameCount = 10; + const dragAmountPerFrame = (200 / dragFrameCount); + for (int i = 0; i < dragFrameCount; i += 1) { + await dragGesture.moveBy(const Offset(0, -dragAmountPerFrame)); + await tester.pump(); + } + + // Ensure we are overscrolling while holding the pointer down. + await tester.pumpAndSettle(); + expect(scrollController.offset, greaterThan(scrollController.position.maxScrollExtent)); + + // Release the pointer to end the gesture. + await dragGesture.up(); + await dragGesture.removePointer(); + await tester.pumpAndSettle(); + + // Ensure the we scrolled back to the end. + expect(scrollController.offset, scrollController.position.maxScrollExtent); + }); }); }); } From 164899040519329b00d9652e0afc0b96cbdf1a3f Mon Sep 17 00:00:00 2001 From: Angelo Silvestre Date: Tue, 24 Oct 2023 00:19:42 -0300 Subject: [PATCH 03/13] Fix merge conflicts --- .../lib/src/default_editor/document_gestures_touch_ios.dart | 1 - .../super_reader/read_only_document_ios_touch_interactor.dart | 1 - 2 files changed, 2 deletions(-) diff --git a/super_editor/lib/src/default_editor/document_gestures_touch_ios.dart b/super_editor/lib/src/default_editor/document_gestures_touch_ios.dart index 6fabe5db2b..50c5110678 100644 --- a/super_editor/lib/src/default_editor/document_gestures_touch_ios.dart +++ b/super_editor/lib/src/default_editor/document_gestures_touch_ios.dart @@ -921,7 +921,6 @@ class _IosDocumentTouchInteractorState extends State // scroll the document. Scroll it, accordingly. if (_dragMode == null && _drag != null) { _drag!.update(details); - _positionToolbar(); return; } diff --git a/super_editor/lib/src/super_reader/read_only_document_ios_touch_interactor.dart b/super_editor/lib/src/super_reader/read_only_document_ios_touch_interactor.dart index 590609063c..9f1c44e759 100644 --- a/super_editor/lib/src/super_reader/read_only_document_ios_touch_interactor.dart +++ b/super_editor/lib/src/super_reader/read_only_document_ios_touch_interactor.dart @@ -729,7 +729,6 @@ class _SuperReaderIosDocumentTouchInteractorState extends State Date: Tue, 24 Oct 2023 20:07:28 -0300 Subject: [PATCH 04/13] PR updates --- .../document_gestures_touch_android.dart | 26 +++++----- .../document_gestures_touch_ios.dart | 51 +++++++++--------- ...nly_document_android_touch_interactor.dart | 26 +++++----- ...ad_only_document_ios_touch_interactor.dart | 52 ++++++++++--------- 4 files changed, 77 insertions(+), 78 deletions(-) diff --git a/super_editor/lib/src/default_editor/document_gestures_touch_android.dart b/super_editor/lib/src/default_editor/document_gestures_touch_android.dart index 4cce2200b1..3c0c5a646f 100644 --- a/super_editor/lib/src/default_editor/document_gestures_touch_android.dart +++ b/super_editor/lib/src/default_editor/document_gestures_touch_android.dart @@ -145,7 +145,7 @@ class _AndroidDocumentTouchInteractorState extends State(null); /// Holds the drag gesture that scrolls the document. - Drag? _drag; + Drag? _dragToScroll; @override void initState() { @@ -743,7 +743,7 @@ class _AndroidDocumentTouchInteractorState extends State IosLongPressSelectionStrategy? _longPressStrategy; /// Holds the drag gesture that scrolls the document. - Drag? _drag; + Drag? _dragToScroll; @override void initState() { @@ -827,7 +827,10 @@ class _IosDocumentTouchInteractorState extends State _dragMode = DragMode.extent; _dragHandleType = HandleType.downstream; } else { - _drag = scrollPosition.drag(details, () { + _dragMode = DragMode.scroll; + + // Start a drag gesture to scroll the document. + _dragToScroll = scrollPosition.drag(details, () { // Allows receiving touches while scrolling due to scroll momentum. // This is needed to allow the user to stop scrolling by tapping down. scrollPosition.context.setIgnorePointer(false); @@ -837,11 +840,13 @@ class _IosDocumentTouchInteractorState extends State _controlsController!.doNotBlinkCaret(); _controlsController!.hideToolbar(); - _controlsController!.showToolbar(); + if (selection == null) { return; } + _controlsController!.showToolbar(); + _globalStartDragOffset = details.globalPosition; final interactorBox = context.findRenderObject() as RenderBox; final handleOffsetInInteractor = interactorBox.globalToLocal(details.globalPosition); @@ -917,10 +922,9 @@ class _IosDocumentTouchInteractorState extends State } void _onPanUpdate(DragUpdateDetails details) { - // If the user isn't dragging a handle, then the user is trying to - // scroll the document. Scroll it, accordingly. - if (_dragMode == null && _drag != null) { - _drag!.update(details); + if (_dragMode == DragMode.scroll && _dragToScroll != null) { + // The user is trying to scroll the document. Scroll it, accordingly. + _dragToScroll!.update(details); return; } @@ -998,25 +1002,17 @@ class _IosDocumentTouchInteractorState extends State ..hideMagnifier() ..blinkCaret(); - if (_drag != null) { - _drag!.end(details); - _drag = null; + if (_dragToScroll != null) { + // The user was performing a drag gesture to scroll the document. + // End the drag gesture. + _dragToScroll!.end(details); + _dragToScroll = null; + _dragMode = null; return; } - if (_dragMode == null) { - // User was dragging the scroll area. Go ballistic. - if (scrollPosition is ScrollPositionWithSingleContext) { - (scrollPosition as ScrollPositionWithSingleContext).goBallistic(-details.velocity.pixelsPerSecond.dy); - - if (_activeScrollPosition != scrollPosition) { - // We add the scroll change listener again, because going ballistic - // seems to switch out the scroll position. - _activeScrollPosition = scrollPosition; - } - } - } else { + if (_dragMode != null) { // The user was dragging a selection change in some way, either with handles // or with a long-press. Finish that interaction. _onDragSelectionEnd(); @@ -1026,9 +1022,12 @@ class _IosDocumentTouchInteractorState extends State void _onPanCancel() { _magnifierOffset.value = null; - if (_drag != null) { - _drag!.cancel(); - _drag = null; + if (_dragToScroll != null) { + // The user was performing a drag gesture to scroll the document. + // Cancel the drag gesture. + _dragToScroll!.cancel(); + _dragToScroll = null; + return; } if (_dragMode != null) { @@ -1319,6 +1318,8 @@ enum DragMode { // Dragging after a long-press, which selects by the word // around the selected word. longPress, + // Dragging to scroll the document. + scroll } /// Adds and removes an iOS-style editor toolbar, as dictated by an ancestor diff --git a/super_editor/lib/src/super_reader/read_only_document_android_touch_interactor.dart b/super_editor/lib/src/super_reader/read_only_document_android_touch_interactor.dart index 76c4281f47..da40063a02 100644 --- a/super_editor/lib/src/super_reader/read_only_document_android_touch_interactor.dart +++ b/super_editor/lib/src/super_reader/read_only_document_android_touch_interactor.dart @@ -136,7 +136,7 @@ class _ReadOnlyAndroidDocumentTouchInteractorState extends State(null); /// Holds the drag gesture that scrolls the document. - Drag? _drag; + Drag? _dragToScroll; @override void initState() { @@ -677,7 +677,7 @@ class _ReadOnlyAndroidDocumentTouchInteractorState extends State Date: Wed, 25 Oct 2023 20:07:40 -0300 Subject: [PATCH 05/13] PR updates --- .../document_gestures_touch_android.dart | 23 ++++---- .../document_gestures_touch_ios.dart | 57 +++++++++++-------- ...nly_document_android_touch_interactor.dart | 18 +++--- ...ad_only_document_ios_touch_interactor.dart | 52 ++++++++++------- 4 files changed, 84 insertions(+), 66 deletions(-) diff --git a/super_editor/lib/src/default_editor/document_gestures_touch_android.dart b/super_editor/lib/src/default_editor/document_gestures_touch_android.dart index 3c0c5a646f..64c49fb77c 100644 --- a/super_editor/lib/src/default_editor/document_gestures_touch_android.dart +++ b/super_editor/lib/src/default_editor/document_gestures_touch_android.dart @@ -136,6 +136,10 @@ class _AndroidDocumentTouchInteractorState extends State(null); - /// Holds the drag gesture that scrolls the document. - Drag? _dragToScroll; - @override void initState() { super.initState(); @@ -743,7 +744,7 @@ class _AndroidDocumentTouchInteractorState extends State // not collapsed/upstream/downstream. Change the type once it's working. HandleType? _dragHandleType; + /// Holds the drag gesture that scrolls the document. + Drag? _scrollingDrag; + Timer? _tapDownLongPressTimer; Offset? _globalTapDownOffset; bool get _isLongPressInProgress => _longPressStrategy != null; IosLongPressSelectionStrategy? _longPressStrategy; - /// Holds the drag gesture that scrolls the document. - Drag? _dragToScroll; - @override void initState() { super.initState(); @@ -812,12 +812,17 @@ class _IosDocumentTouchInteractorState extends State // placement during onTapDown, and then pick that up here. I think the little // bit of slop might be the problem. final selection = widget.selection.value; + if (selection == null) { + // There isn't a selection, the user is dragging to scroll the document. + _startDragScrolling(details); + return; + } if (_isLongPressInProgress) { _dragMode = DragMode.longPress; _dragHandleType = null; _longPressStrategy!.onLongPressDragStart(); - } else if (selection != null && selection.isCollapsed && _isOverCollapsedHandle(details.localPosition)) { + } else if (selection.isCollapsed && _isOverCollapsedHandle(details.localPosition)) { _dragMode = DragMode.collapsed; _dragHandleType = HandleType.collapsed; } else if (_isOverBaseHandle(details.localPosition)) { @@ -827,24 +832,15 @@ class _IosDocumentTouchInteractorState extends State _dragMode = DragMode.extent; _dragHandleType = HandleType.downstream; } else { - _dragMode = DragMode.scroll; + // The user isn't dragging over a handle. + // Start scrolling the document. + _startDragScrolling(details); - // Start a drag gesture to scroll the document. - _dragToScroll = scrollPosition.drag(details, () { - // Allows receiving touches while scrolling due to scroll momentum. - // This is needed to allow the user to stop scrolling by tapping down. - scrollPosition.context.setIgnorePointer(false); - }); return; } _controlsController!.doNotBlinkCaret(); _controlsController!.hideToolbar(); - - if (selection == null) { - return; - } - _controlsController!.showToolbar(); _globalStartDragOffset = details.globalPosition; @@ -922,9 +918,9 @@ class _IosDocumentTouchInteractorState extends State } void _onPanUpdate(DragUpdateDetails details) { - if (_dragMode == DragMode.scroll && _dragToScroll != null) { + if (_dragMode == DragMode.scroll) { // The user is trying to scroll the document. Scroll it, accordingly. - _dragToScroll!.update(details); + _scrollingDrag!.update(details); return; } @@ -1002,11 +998,11 @@ class _IosDocumentTouchInteractorState extends State ..hideMagnifier() ..blinkCaret(); - if (_dragToScroll != null) { + if (_dragMode == DragMode.scroll) { // The user was performing a drag gesture to scroll the document. - // End the drag gesture. - _dragToScroll!.end(details); - _dragToScroll = null; + // End the scroll activity and let the document scrolling with momentum. + _scrollingDrag!.end(details); + _scrollingDrag = null; _dragMode = null; return; @@ -1022,11 +1018,11 @@ class _IosDocumentTouchInteractorState extends State void _onPanCancel() { _magnifierOffset.value = null; - if (_dragToScroll != null) { + if (_scrollingDrag != null) { // The user was performing a drag gesture to scroll the document. // Cancel the drag gesture. - _dragToScroll!.cancel(); - _dragToScroll = null; + _scrollingDrag!.cancel(); + _scrollingDrag = null; return; } @@ -1226,6 +1222,17 @@ class _IosDocumentTouchInteractorState extends State return ancestorScrollable; } + /// Starts a drag activity to scroll the document. + void _startDragScrolling(DragStartDetails details) { + _dragMode = DragMode.scroll; + + _scrollingDrag = scrollPosition.drag(details, () { + // Allows receiving touches while scrolling due to scroll momentum. + // This is needed to allow the user to stop scrolling by tapping down. + scrollPosition.context.setIgnorePointer(false); + }); + } + @override Widget build(BuildContext context) { if (widget.scrollController.hasClients) { diff --git a/super_editor/lib/src/super_reader/read_only_document_android_touch_interactor.dart b/super_editor/lib/src/super_reader/read_only_document_android_touch_interactor.dart index da40063a02..50f0b77e4d 100644 --- a/super_editor/lib/src/super_reader/read_only_document_android_touch_interactor.dart +++ b/super_editor/lib/src/super_reader/read_only_document_android_touch_interactor.dart @@ -136,7 +136,7 @@ class _ReadOnlyAndroidDocumentTouchInteractorState extends State(null); /// Holds the drag gesture that scrolls the document. - Drag? _dragToScroll; + Drag? _scrollingDrag; @override void initState() { @@ -677,7 +677,7 @@ class _ReadOnlyAndroidDocumentTouchInteractorState extends State Date: Mon, 30 Oct 2023 19:59:21 -0300 Subject: [PATCH 06/13] Refactor iOS pan end --- .../document_gestures_touch_ios.dart | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/super_editor/lib/src/default_editor/document_gestures_touch_ios.dart b/super_editor/lib/src/default_editor/document_gestures_touch_ios.dart index 1a16ad04dc..439cc5be39 100644 --- a/super_editor/lib/src/default_editor/document_gestures_touch_ios.dart +++ b/super_editor/lib/src/default_editor/document_gestures_touch_ios.dart @@ -998,20 +998,25 @@ class _IosDocumentTouchInteractorState extends State ..hideMagnifier() ..blinkCaret(); - if (_dragMode == DragMode.scroll) { - // The user was performing a drag gesture to scroll the document. - // End the scroll activity and let the document scrolling with momentum. - _scrollingDrag!.end(details); - _scrollingDrag = null; - _dragMode = null; - - return; - } - - if (_dragMode != null) { - // The user was dragging a selection change in some way, either with handles - // or with a long-press. Finish that interaction. - _onDragSelectionEnd(); + switch (_dragMode) { + case DragMode.scroll: + // The user was performing a drag gesture to scroll the document. + // End the scroll activity and let the document scrolling with momentum. + _scrollingDrag!.end(details); + _scrollingDrag = null; + _dragMode = null; + break; + case DragMode.collapsed: + case DragMode.base: + case DragMode.extent: + case DragMode.longPress: + // The user was dragging a selection change in some way, either with handles + // or with a long-press. Finish that interaction. + _onDragSelectionEnd(); + break; + case null: + // The user wasn't dragging over a selection. Do nothing. + break; } } From 1e8f286d53c9ce5718fb4d432f09fff7b5f80fa5 Mon Sep 17 00:00:00 2001 From: Angelo Silvestre Date: Mon, 30 Oct 2023 21:15:57 -0300 Subject: [PATCH 07/13] Create extension for drag gestures --- .../supereditor_scrolling_test.dart | 199 ++++++++---------- .../super_reader_scrolling_test.dart | 191 +++++++---------- super_editor/test/test_tools.dart | 24 +++ 3 files changed, 185 insertions(+), 229 deletions(-) diff --git a/super_editor/test/super_editor/supereditor_scrolling_test.dart b/super_editor/test/super_editor/supereditor_scrolling_test.dart index de4a259da0..1eaca4d876 100644 --- a/super_editor/test/super_editor/supereditor_scrolling_test.dart +++ b/super_editor/test/super_editor/supereditor_scrolling_test.dart @@ -8,6 +8,7 @@ import 'package:super_editor/src/infrastructure/blinking_caret.dart'; import 'package:super_editor/super_editor.dart'; import 'package:super_editor/super_editor_test.dart'; +import '../test_tools.dart'; import 'supereditor_test_tools.dart'; import 'test_documents.dart'; @@ -503,98 +504,80 @@ void main() { testWidgetsOnAndroid("doesn't overscroll when dragging down", (tester) async { final scrollController = ScrollController(); - await tester + await tester // .createDocument() .withSingleParagraph() - .withInputSource(TextInputSource.ime) - .withEditorSize(const Size(300, 300)) .withScrollController(scrollController) .pump(); - // Ensure the scrollview didn't start scrolled. + // Ensure the editor didn't start scrolled. expect(scrollController.offset, 0); - final editorRect = tester.getRect(find.byType(SuperEditor)); - - const dragFrameCount = 10; - final dragAmountPerFrame = editorRect.height / dragFrameCount; - - // Drag from the top all the way up to the bottom of the editor. - final dragGesture = await tester.startGesture(editorRect.topCenter); - for (int i = 0; i < dragFrameCount; i += 1) { - await dragGesture.moveBy(Offset(0, dragAmountPerFrame)); - await tester.pump(); + // Drag an amount of pixels chosen experimentally from the top of the editor. + final dragGesture = await tester.dragInMultipleFrames( + startLocation: tester.getTopLeft(find.byType(SuperEditor)), + dragAmount: const Offset(0, 200.0), + frameCount: 10, + ); - // Ensure we don't scroll. - expect(scrollController.offset, 0); - } + // Ensure the drag gesture didn't scroll the editor. + expect(scrollController.offset, 0); // End the gesture. await dragGesture.up(); - await dragGesture.removePointer(); + + // Let any pending timers resolve. await tester.pumpAndSettle(); }); testWidgetsOnAndroid("doesn't overscroll when dragging up", (tester) async { final scrollController = ScrollController(); - await tester + await tester // .createDocument() .withSingleParagraph() - .withInputSource(TextInputSource.ime) - .withEditorSize(const Size(300, 300)) .withScrollController(scrollController) .pump(); // Jump to the bottom. scrollController.jumpTo(scrollController.position.maxScrollExtent); - final editorRect = tester.getRect(find.byType(SuperEditor)); - - const dragFrameCount = 10; - final dragAmountPerFrame = editorRect.height / dragFrameCount; - - // Drag from the bottom all the way up to the top of the editor. - final dragGesture = await tester.startGesture(editorRect.topCenter); - for (int i = 0; i < dragFrameCount; i += 1) { - await dragGesture.moveBy(Offset(0, -dragAmountPerFrame)); - await tester.pump(); + // Drag an amount of pixels chosen experimentally from the bottom of the editor. + // The gesture starts with a small margin from the bottom, also chosen experimentally. + final dragGesture = await tester.dragInMultipleFrames( + startLocation: tester.getBottomLeft(find.byType(SuperEditor)) - const Offset(0, 10), + dragAmount: const Offset(0, -200.0), + frameCount: 10, + ); - // Ensure we don't scroll. - expect(scrollController.offset, scrollController.position.maxScrollExtent); - } + // Ensure we don't scroll. + expect(scrollController.offset, scrollController.position.maxScrollExtent); // End the gesture. await dragGesture.up(); - await dragGesture.removePointer(); + + // Let any pending timers resolve. await tester.pumpAndSettle(); }); testWidgetsOnIos('overscrolls when dragging down', (tester) async { final scrollController = ScrollController(); - await tester + await tester // .createDocument() .withSingleParagraph() - .withInputSource(TextInputSource.ime) - .withEditorSize(const Size(300, 300)) .withScrollController(scrollController) .pump(); // Ensure the scrollview didn't start scrolled. expect(scrollController.offset, 0); - // Start a drag gesture a few pixels below the top of the editor. - final dragGesture = - await tester.startGesture(tester.getRect(find.byType(SuperEditor)).topCenter + const Offset(0, 5)); - - // Drag an arbitrary amount, smaller than the editor size. - const dragFrameCount = 10; - const dragAmountPerFrame = (80 / dragFrameCount); - for (int i = 0; i < dragFrameCount; i += 1) { - await dragGesture.moveBy(const Offset(0, dragAmountPerFrame)); - await tester.pump(); - } + // Drag an amount of pixels chosen experimentally a few pixels below the top of the editor. + final dragGesture = await tester.dragInMultipleFrames( + startLocation: tester.getRect(find.byType(SuperEditor)).topCenter + const Offset(0, 5), + dragAmount: const Offset(0, 80.0), + frameCount: 10, + ); // Ensure we are overscrolling while holding the pointer down. await tester.pumpAndSettle(); @@ -602,7 +585,8 @@ void main() { // Release the pointer to end the gesture. await dragGesture.up(); - await dragGesture.removePointer(); + + // Let any pending timers resolve. await tester.pumpAndSettle(); // Ensure the we scrolled back to the top. @@ -612,11 +596,9 @@ void main() { testWidgetsOnIos('overscrolls when dragging up', (tester) async { final scrollController = ScrollController(); - await tester + await tester // .createDocument() .withSingleParagraph() - .withInputSource(TextInputSource.ime) - .withEditorSize(const Size(300, 300)) .withScrollController(scrollController) .pump(); @@ -624,17 +606,13 @@ void main() { scrollController.jumpTo(scrollController.position.maxScrollExtent); await tester.pumpAndSettle(); - // Start a drag gesture a few pixels above the top of the editor. - final dragGesture = - await tester.startGesture(tester.getRect(find.byType(SuperEditor)).bottomCenter - const Offset(0, 5)); - - // Drag up an arbitrary amount, smaller than the editor size. - const dragFrameCount = 10; - const dragAmountPerFrame = (200 / dragFrameCount); - for (int i = 0; i < dragFrameCount; i += 1) { - await dragGesture.moveBy(const Offset(0, -dragAmountPerFrame)); - await tester.pump(); - } + // Drag an amount of pixels chosen experimentally from the bottom of the editor. + // The gesture starts with a small margin from the bottom, also chosen experimentally. + final dragGesture = await tester.dragInMultipleFrames( + startLocation: tester.getRect(find.byType(SuperEditor)).bottomCenter - const Offset(0, 5), + dragAmount: const Offset(0, -200.0), + frameCount: 10, + ); // Ensure we are overscrolling while holding the pointer down. await tester.pumpAndSettle(); @@ -642,7 +620,8 @@ void main() { // Release the pointer to end the gesture. await dragGesture.up(); - await dragGesture.removePointer(); + + // Let any pending timers resolve. await tester.pumpAndSettle(); // Ensure the we scrolled back to the end. @@ -849,7 +828,6 @@ void main() { await tester .createDocument() .withSingleParagraph() - .withInputSource(TextInputSource.ime) .withCustomWidgetTreeBuilder( (superEditor) => MaterialApp( home: Scaffold( @@ -872,24 +850,20 @@ void main() { // Ensure the scrollview didn't start scrolled. expect(scrollController.offset, 0); - final editorRect = tester.getRect(find.byType(SuperEditor)); - - const dragFrameCount = 10; - final dragAmountPerFrame = editorRect.height / dragFrameCount; - - // Drag from the top all the way up to the bottom of the editor. - final dragGesture = await tester.startGesture(editorRect.topCenter); - for (int i = 0; i < dragFrameCount; i += 1) { - await dragGesture.moveBy(Offset(0, dragAmountPerFrame)); - await tester.pump(); + // Drag an amount of pixels chosen experimentally from the top of the editor. + final dragGesture = await tester.dragInMultipleFrames( + startLocation: tester.getTopLeft(find.byType(SuperEditor)), + dragAmount: const Offset(0, 400.0), + frameCount: 10, + ); - // Ensure we don't scroll. - expect(scrollController.offset, 0); - } + // Ensure we don't scroll. + expect(scrollController.offset, 0); // End the gesture. await dragGesture.up(); - await dragGesture.removePointer(); + + // Let any pending timers resolve. await tester.pumpAndSettle(); }); @@ -899,7 +873,6 @@ void main() { await tester .createDocument() .withSingleParagraph() - .withInputSource(TextInputSource.ime) .withCustomWidgetTreeBuilder( (superEditor) => MaterialApp( home: Scaffold( @@ -922,25 +895,23 @@ void main() { // Jump to the bottom. scrollController.jumpTo(scrollController.position.maxScrollExtent); - final editorRect = tester.getRect(find.byType(SuperEditor)); - - const dragFrameCount = 10; - final dragAmountPerFrame = editorRect.height / dragFrameCount; + // Drag an amount of pixels chosen experimentally from the bottom of the editor. + final dragGesture = await tester.dragInMultipleFrames( + startLocation: tester.getBottomLeft(find.byType(CustomScrollView)) - const Offset(0, 10), + dragAmount: const Offset(0, -400.0), + frameCount: 10, + ); - // Drag from the bottom all the way up to the top of the editor. - final dragGesture = await tester.startGesture(editorRect.topCenter); - addTearDown(() => dragGesture.removePointer()); - for (int i = 0; i < dragFrameCount; i += 1) { - await dragGesture.moveBy(Offset(0, -dragAmountPerFrame)); - await tester.pump(); + // Ensure we don't scroll. + expect(scrollController.offset, scrollController.position.maxScrollExtent); - // Ensure we don't scroll. - expect(scrollController.offset, scrollController.position.maxScrollExtent); - } + // Ensure we don't scroll. + expect(scrollController.offset, scrollController.position.maxScrollExtent); // End the gesture. await dragGesture.up(); - await dragGesture.removePointer(); + + // Let any pending timers resolve. await tester.pumpAndSettle(); }); @@ -974,17 +945,13 @@ void main() { // Ensure the scrollview didn't start scrolled. expect(scrollController.offset, 0); - // Start a drag gesture a few pixels below the top of the editor. - final dragGesture = - await tester.startGesture(tester.getRect(find.byType(CustomScrollView)).topCenter + const Offset(0, 5)); - // Drag an arbitrary amount, smaller than the editor size. - const dragFrameCount = 10; - const dragAmountPerFrame = (80 / dragFrameCount); - for (int i = 0; i < dragFrameCount; i += 1) { - await dragGesture.moveBy(const Offset(0, dragAmountPerFrame)); - await tester.pump(); - } + // Drag an amount of pixels chosen experimentally from the top of the editor. + final dragGesture = await tester.dragInMultipleFrames( + startLocation: tester.getRect(find.byType(CustomScrollView)).topCenter + const Offset(0, 5), + dragAmount: const Offset(0, 80.0), + frameCount: 10, + ); // Ensure we are overscrolling while holding the pointer down. await tester.pumpAndSettle(); @@ -992,7 +959,8 @@ void main() { // Release the pointer to end the gesture. await dragGesture.up(); - await dragGesture.removePointer(); + + // Let any pending timers resolve. await tester.pumpAndSettle(); // Ensure the we scrolled back to the top. @@ -1030,17 +998,13 @@ void main() { scrollController.jumpTo(scrollController.position.maxScrollExtent); await tester.pumpAndSettle(); - // Start a drag gesture a few pixels above the top of the editor. - final dragGesture = - await tester.startGesture(tester.getRect(find.byType(CustomScrollView)).bottomCenter - const Offset(0, 5)); - // Drag up an arbitrary amount, smaller than the editor size. - const dragFrameCount = 10; - const dragAmountPerFrame = (200 / dragFrameCount); - for (int i = 0; i < dragFrameCount; i += 1) { - await dragGesture.moveBy(const Offset(0, -dragAmountPerFrame)); - await tester.pump(); - } + // Drag an amount of pixels chosen experimentally from the bottom of the editor. + final dragGesture = await tester.dragInMultipleFrames( + startLocation: tester.getRect(find.byType(CustomScrollView)).bottomCenter - const Offset(0, 5), + dragAmount: const Offset(0, -100.0), + frameCount: 10, + ); // Ensure we are overscrolling while holding the pointer down. await tester.pumpAndSettle(); @@ -1048,7 +1012,8 @@ void main() { // Release the pointer to end the gesture. await dragGesture.up(); - await dragGesture.removePointer(); + + // Let any pending timers resolve. await tester.pumpAndSettle(); // Ensure the we scrolled back to the end. diff --git a/super_editor/test/super_reader/super_reader_scrolling_test.dart b/super_editor/test/super_reader/super_reader_scrolling_test.dart index edb7fd9fae..84c52ef205 100644 --- a/super_editor/test/super_reader/super_reader_scrolling_test.dart +++ b/super_editor/test/super_reader/super_reader_scrolling_test.dart @@ -5,6 +5,7 @@ import 'package:flutter_test_runners/flutter_test_runners.dart'; import 'package:super_editor/super_editor.dart'; import 'package:super_editor/super_reader_test.dart'; +import '../test_tools.dart'; import 'reader_test_tools.dart'; import 'test_documents.dart'; @@ -223,95 +224,79 @@ void main() { testWidgetsOnAndroid("doesn't overscroll when dragging down", (tester) async { final scrollController = ScrollController(); - await tester + await tester // .createDocument() .withSingleParagraph() - .withEditorSize(const Size(300, 300)) .withScrollController(scrollController) .pump(); - // Ensure the scrollview didn't start scrolled. + // Ensure the reader didn't start scrolled. expect(scrollController.offset, 0); - final editorRect = tester.getRect(find.byType(SuperReader)); - - const dragFrameCount = 10; - final dragAmountPerFrame = editorRect.height / dragFrameCount; - - // Drag from the top all the way up to the bottom of the editor. - final dragGesture = await tester.startGesture(editorRect.topCenter); - for (int i = 0; i < dragFrameCount; i += 1) { - await dragGesture.moveBy(Offset(0, dragAmountPerFrame)); - await tester.pump(); + // Drag an amount of pixels chosen experimentally from the top of the reader. + final dragGesture = await tester.dragInMultipleFrames( + startLocation: tester.getTopLeft(find.byType(SuperReader)), + dragAmount: const Offset(0, 200.0), + frameCount: 10, + ); - // Ensure we don't scroll. - expect(scrollController.offset, 0); - } + // Ensure we don't scroll. + expect(scrollController.offset, 0); // End the gesture. await dragGesture.up(); - await dragGesture.removePointer(); + + // Let any pending timers resolve. await tester.pumpAndSettle(); }); testWidgetsOnAndroid("doesn't overscroll when dragging up", (tester) async { final scrollController = ScrollController(); - await tester + await tester // .createDocument() .withSingleParagraph() - .withEditorSize(const Size(300, 300)) .withScrollController(scrollController) .pump(); // Jump to the bottom. scrollController.jumpTo(scrollController.position.maxScrollExtent); - final readerRect = tester.getRect(find.byType(SuperReader)); - - const dragFrameCount = 10; - final dragAmountPerFrame = readerRect.height / dragFrameCount; - - // Drag from the bottom all the way up to the top of the reader. - final dragGesture = await tester.startGesture(readerRect.topCenter); - for (int i = 0; i < dragFrameCount; i += 1) { - await dragGesture.moveBy(Offset(0, -dragAmountPerFrame)); - await tester.pump(); + // Drag an amount of pixels chosen experimentally from the bottom of the reader. + final dragGesture = await tester.dragInMultipleFrames( + startLocation: tester.getBottomLeft(find.byType(SuperReader)) - const Offset(0, 5), + dragAmount: const Offset(0, -200.0), + frameCount: 10, + ); - // Ensure we don't scroll. - expect(scrollController.offset, scrollController.position.maxScrollExtent); - } + // Ensure we don't scroll. + expect(scrollController.offset, scrollController.position.maxScrollExtent); // End the gesture. await dragGesture.up(); - await dragGesture.removePointer(); + + // Let any pending timers resolve. await tester.pumpAndSettle(); }); testWidgetsOnIos('overscrolls when dragging down', (tester) async { final scrollController = ScrollController(); - await tester + await tester // .createDocument() .withSingleParagraph() - .withEditorSize(const Size(300, 300)) .withScrollController(scrollController) .pump(); // Ensure the scrollview didn't start scrolled. expect(scrollController.offset, 0); - // Start a drag gesture a few pixels below the top of the reader. - final dragGesture = - await tester.startGesture(tester.getRect(find.byType(SuperReader)).topCenter + const Offset(0, 5)); - - // Drag an arbitrary amount, smaller than the reader size. - const dragFrameCount = 10; - const dragAmountPerFrame = (80 / dragFrameCount); - for (int i = 0; i < dragFrameCount; i += 1) { - await dragGesture.moveBy(const Offset(0, dragAmountPerFrame)); - await tester.pump(); - } + // Drag an amount of pixels chosen experimentally a few pixels below the top of the reader. + final dragGesture = await tester.dragInMultipleFrames( + startLocation: tester.getRect(find.byType(SuperReader)).topCenter + const Offset(0, 5), + dragAmount: const Offset(0, 80.0), + frameCount: 10, + ); // Ensure we are overscrolling while holding the pointer down. await tester.pumpAndSettle(); @@ -319,7 +304,8 @@ void main() { // Release the pointer to end the gesture. await dragGesture.up(); - await dragGesture.removePointer(); + + // Let any pending timers resolve. await tester.pumpAndSettle(); // Ensure the we scrolled back to the top. @@ -329,10 +315,9 @@ void main() { testWidgetsOnIos('overscrolls when dragging up', (tester) async { final scrollController = ScrollController(); - await tester + await tester // .createDocument() .withSingleParagraph() - .withEditorSize(const Size(300, 300)) .withScrollController(scrollController) .pump(); @@ -340,17 +325,13 @@ void main() { scrollController.jumpTo(scrollController.position.maxScrollExtent); await tester.pumpAndSettle(); - // Start a drag gesture a few pixels above the top of the reader. - final dragGesture = - await tester.startGesture(tester.getRect(find.byType(SuperReader)).bottomCenter - const Offset(0, 5)); - - // Drag up an arbitrary amount, smaller than the reader size. - const dragFrameCount = 10; - const dragAmountPerFrame = (200 / dragFrameCount); - for (int i = 0; i < dragFrameCount; i += 1) { - await dragGesture.moveBy(const Offset(0, -dragAmountPerFrame)); - await tester.pump(); - } + // Drag an amount of pixels chosen experimentally from the bottom of the reader. + // The gesture starts with a small margin from the bottom, also chosen experimentally. + final dragGesture = await tester.dragInMultipleFrames( + startLocation: tester.getRect(find.byType(SuperReader)).bottomCenter - const Offset(0, 5), + dragAmount: const Offset(0, -200.0), + frameCount: 10, + ); // Ensure we are overscrolling while holding the pointer down. await tester.pumpAndSettle(); @@ -358,7 +339,8 @@ void main() { // Release the pointer to end the gesture. await dragGesture.up(); - await dragGesture.removePointer(); + + // Let any pending timers resolve. await tester.pumpAndSettle(); // Ensure the we scrolled back to the end. @@ -598,24 +580,20 @@ void main() { // Ensure the scrollview didn't start scrolled. expect(scrollController.offset, 0); - final readerRect = tester.getRect(find.byType(SuperReader)); - - const dragFrameCount = 10; - final dragAmountPerFrame = readerRect.height / dragFrameCount; - - // Drag from the top all the way up to the bottom of the reader. - final dragGesture = await tester.startGesture(readerRect.topCenter); - for (int i = 0; i < dragFrameCount; i += 1) { - await dragGesture.moveBy(Offset(0, dragAmountPerFrame)); - await tester.pump(); + // Drag an amount of pixels chosen experimentally from the top of the reader. + final dragGesture = await tester.dragInMultipleFrames( + startLocation: tester.getRect(find.byType(SuperReader)).topCenter, + dragAmount: const Offset(0, 400.0), + frameCount: 10, + ); - // Ensure we don't scroll. - expect(scrollController.offset, 0); - } + // Ensure we don't scroll. + expect(scrollController.offset, 0); // End the gesture. await dragGesture.up(); - await dragGesture.removePointer(); + + // Let any pending timers resolve. await tester.pumpAndSettle(); }); @@ -647,25 +625,20 @@ void main() { // Jump to the bottom. scrollController.jumpTo(scrollController.position.maxScrollExtent); - final readerRect = tester.getRect(find.byType(SuperReader)); - - const dragFrameCount = 10; - final dragAmountPerFrame = readerRect.height / dragFrameCount; - - // Drag from the bottom all the way up to the top of the reader. - final dragGesture = await tester.startGesture(readerRect.topCenter); - addTearDown(() => dragGesture.removePointer()); - for (int i = 0; i < dragFrameCount; i += 1) { - await dragGesture.moveBy(Offset(0, -dragAmountPerFrame)); - await tester.pump(); + // Drag an amount of pixels chosen experimentally from the bottom of the reader. + final dragGesture = await tester.dragInMultipleFrames( + startLocation: tester.getBottomLeft(find.byType(CustomScrollView)) - const Offset(0, 5), + dragAmount: const Offset(0, -400.0), + frameCount: 10, + ); - // Ensure we don't scroll. - expect(scrollController.offset, scrollController.position.maxScrollExtent); - } + // Ensure we don't scroll. + expect(scrollController.offset, scrollController.position.maxScrollExtent); // End the gesture. await dragGesture.up(); - await dragGesture.removePointer(); + + // Let any pending timers resolve. await tester.pumpAndSettle(); }); @@ -699,17 +672,13 @@ void main() { // Ensure the scrollview didn't start scrolled. expect(scrollController.offset, 0); - // Start a drag gesture a few pixels below the top of the reader. - final dragGesture = - await tester.startGesture(tester.getRect(find.byType(CustomScrollView)).topCenter + const Offset(0, 5)); - // Drag an arbitrary amount, smaller than the reader size. - const dragFrameCount = 10; - const dragAmountPerFrame = (80 / dragFrameCount); - for (int i = 0; i < dragFrameCount; i += 1) { - await dragGesture.moveBy(const Offset(0, dragAmountPerFrame)); - await tester.pump(); - } + // Drag an amount of pixels chosen experimentally from the top of the reader. + final dragGesture = await tester.dragInMultipleFrames( + startLocation: tester.getRect(find.byType(CustomScrollView)).topCenter + const Offset(0, 5), + dragAmount: const Offset(0, 80.0), + frameCount: 10, + ); // Ensure we are overscrolling while holding the pointer down. await tester.pumpAndSettle(); @@ -717,7 +686,8 @@ void main() { // Release the pointer to end the gesture. await dragGesture.up(); - await dragGesture.removePointer(); + + // Let any pending timers resolve. await tester.pumpAndSettle(); // Ensure the we scrolled back to the top. @@ -755,17 +725,13 @@ void main() { scrollController.jumpTo(scrollController.position.maxScrollExtent); await tester.pumpAndSettle(); - // Start a drag gesture a few pixels above the top of the reader. - final dragGesture = - await tester.startGesture(tester.getRect(find.byType(CustomScrollView)).bottomCenter - const Offset(0, 5)); - // Drag up an arbitrary amount, smaller than the reader size. - const dragFrameCount = 10; - const dragAmountPerFrame = (200 / dragFrameCount); - for (int i = 0; i < dragFrameCount; i += 1) { - await dragGesture.moveBy(const Offset(0, -dragAmountPerFrame)); - await tester.pump(); - } + // Drag an amount of pixels chosen experimentally from the top of the reader. + final dragGesture = await tester.dragInMultipleFrames( + startLocation: tester.getRect(find.byType(CustomScrollView)).bottomCenter - const Offset(0, 5), + dragAmount: const Offset(0, -100.0), + frameCount: 10, + ); // Ensure we are overscrolling while holding the pointer down. await tester.pumpAndSettle(); @@ -773,7 +739,8 @@ void main() { // Release the pointer to end the gesture. await dragGesture.up(); - await dragGesture.removePointer(); + + // Let any pending timers resolve. await tester.pumpAndSettle(); // Ensure the we scrolled back to the end. diff --git a/super_editor/test/test_tools.dart b/super_editor/test/test_tools.dart index 6f40440015..cbcd68892d 100644 --- a/super_editor/test/test_tools.dart +++ b/super_editor/test/test_tools.dart @@ -16,3 +16,27 @@ class TestUrlLauncher implements UrlLauncher { return true; } } + +/// Extension on [WidgetTester] to make it easier to perform drag gestures. +extension DragExtensions on WidgetTester { + /// Drags from the [startLocation] by [dragAmount] in multiple frames. + /// + /// The [dragAmount] is distributed evenly between [frameCount] frames. + /// + /// The caller must end the gesture by calling [TestGesture.up]. + Future dragInMultipleFrames({ + required Offset startLocation, + required Offset dragAmount, + required int frameCount, + }) async { + final dragPerFrame = Offset(dragAmount.dx / frameCount, dragAmount.dy / frameCount); + + final dragGesture = await startGesture(startLocation); + for (int i = 0; i < frameCount; i += 1) { + await dragGesture.moveBy(dragPerFrame); + await pump(); + } + + return dragGesture; + } +} From 3a86384f71939fa26dba744a059ae3d199d86e8b Mon Sep 17 00:00:00 2001 From: Angelo Silvestre Date: Thu, 2 Nov 2023 19:21:15 -0300 Subject: [PATCH 08/13] PR Updates --- .../supereditor_scrolling_test.dart | 66 ++++++++----------- .../super_reader_scrolling_test.dart | 64 ++++++++---------- super_editor/test/test_tools.dart | 20 ++++-- 3 files changed, 68 insertions(+), 82 deletions(-) diff --git a/super_editor/test/super_editor/supereditor_scrolling_test.dart b/super_editor/test/super_editor/supereditor_scrolling_test.dart index 1eaca4d876..744869492e 100644 --- a/super_editor/test/super_editor/supereditor_scrolling_test.dart +++ b/super_editor/test/super_editor/supereditor_scrolling_test.dart @@ -513,11 +513,10 @@ void main() { // Ensure the editor didn't start scrolled. expect(scrollController.offset, 0); - // Drag an amount of pixels chosen experimentally from the top of the editor. - final dragGesture = await tester.dragInMultipleFrames( - startLocation: tester.getTopLeft(find.byType(SuperEditor)), - dragAmount: const Offset(0, 200.0), - frameCount: 10, + // Drag an arbitrary amount of pixels from the top of the editor with a small margin. + final dragGesture = await tester.dragByFrameCount( + startLocation: tester.getRect(find.byType(SuperEditor)).topCenter + const Offset(0, 5), + totalDragOffset: const Offset(0, 200.0), ); // Ensure the drag gesture didn't scroll the editor. @@ -542,12 +541,11 @@ void main() { // Jump to the bottom. scrollController.jumpTo(scrollController.position.maxScrollExtent); - // Drag an amount of pixels chosen experimentally from the bottom of the editor. - // The gesture starts with a small margin from the bottom, also chosen experimentally. - final dragGesture = await tester.dragInMultipleFrames( - startLocation: tester.getBottomLeft(find.byType(SuperEditor)) - const Offset(0, 10), - dragAmount: const Offset(0, -200.0), - frameCount: 10, + // Drag an arbitrary amount of pixels from the bottom of the editor. + // The gesture starts with an arbitrary small margin from the bottom. + final dragGesture = await tester.dragByFrameCount( + startLocation: tester.getRect(find.byType(SuperEditor)).bottomCenter - const Offset(0, 10), + totalDragOffset: const Offset(0, -200.0), ); // Ensure we don't scroll. @@ -572,11 +570,10 @@ void main() { // Ensure the scrollview didn't start scrolled. expect(scrollController.offset, 0); - // Drag an amount of pixels chosen experimentally a few pixels below the top of the editor. - final dragGesture = await tester.dragInMultipleFrames( + // Drag an arbitrary amount of pixels a few pixels below the top of the editor. + final dragGesture = await tester.dragByFrameCount( startLocation: tester.getRect(find.byType(SuperEditor)).topCenter + const Offset(0, 5), - dragAmount: const Offset(0, 80.0), - frameCount: 10, + totalDragOffset: const Offset(0, 80.0), ); // Ensure we are overscrolling while holding the pointer down. @@ -606,12 +603,11 @@ void main() { scrollController.jumpTo(scrollController.position.maxScrollExtent); await tester.pumpAndSettle(); - // Drag an amount of pixels chosen experimentally from the bottom of the editor. - // The gesture starts with a small margin from the bottom, also chosen experimentally. - final dragGesture = await tester.dragInMultipleFrames( + // Drag an arbitrary amount of pixels from the bottom of the editor. + // The gesture starts with an arbitrary margin from the bottom. + final dragGesture = await tester.dragByFrameCount( startLocation: tester.getRect(find.byType(SuperEditor)).bottomCenter - const Offset(0, 5), - dragAmount: const Offset(0, -200.0), - frameCount: 10, + totalDragOffset: const Offset(0, -200.0), ); // Ensure we are overscrolling while holding the pointer down. @@ -850,11 +846,10 @@ void main() { // Ensure the scrollview didn't start scrolled. expect(scrollController.offset, 0); - // Drag an amount of pixels chosen experimentally from the top of the editor. - final dragGesture = await tester.dragInMultipleFrames( - startLocation: tester.getTopLeft(find.byType(SuperEditor)), - dragAmount: const Offset(0, 400.0), - frameCount: 10, + // Drag an arbitrary amount of pixels from the top of the editor. + final dragGesture = await tester.dragByFrameCount( + startLocation: tester.getRect(find.byType(SuperEditor)).topCenter + const Offset(0, 5), + totalDragOffset: const Offset(0, 400.0), ); // Ensure we don't scroll. @@ -895,11 +890,10 @@ void main() { // Jump to the bottom. scrollController.jumpTo(scrollController.position.maxScrollExtent); - // Drag an amount of pixels chosen experimentally from the bottom of the editor. - final dragGesture = await tester.dragInMultipleFrames( - startLocation: tester.getBottomLeft(find.byType(CustomScrollView)) - const Offset(0, 10), - dragAmount: const Offset(0, -400.0), - frameCount: 10, + // Drag an arbitrary amount of pixels from the bottom of the editor. + final dragGesture = await tester.dragByFrameCount( + startLocation: tester.getRect(find.byType(CustomScrollView)).bottomCenter - const Offset(0, 10), + totalDragOffset: const Offset(0, -400.0), ); // Ensure we don't scroll. @@ -946,11 +940,9 @@ void main() { expect(scrollController.offset, 0); // Drag an arbitrary amount, smaller than the editor size. - // Drag an amount of pixels chosen experimentally from the top of the editor. - final dragGesture = await tester.dragInMultipleFrames( + final dragGesture = await tester.dragByFrameCount( startLocation: tester.getRect(find.byType(CustomScrollView)).topCenter + const Offset(0, 5), - dragAmount: const Offset(0, 80.0), - frameCount: 10, + totalDragOffset: const Offset(0, 80.0), ); // Ensure we are overscrolling while holding the pointer down. @@ -999,11 +991,9 @@ void main() { await tester.pumpAndSettle(); // Drag up an arbitrary amount, smaller than the editor size. - // Drag an amount of pixels chosen experimentally from the bottom of the editor. - final dragGesture = await tester.dragInMultipleFrames( + final dragGesture = await tester.dragByFrameCount( startLocation: tester.getRect(find.byType(CustomScrollView)).bottomCenter - const Offset(0, 5), - dragAmount: const Offset(0, -100.0), - frameCount: 10, + totalDragOffset: const Offset(0, -100.0), ); // Ensure we are overscrolling while holding the pointer down. diff --git a/super_editor/test/super_reader/super_reader_scrolling_test.dart b/super_editor/test/super_reader/super_reader_scrolling_test.dart index 84c52ef205..015b71c757 100644 --- a/super_editor/test/super_reader/super_reader_scrolling_test.dart +++ b/super_editor/test/super_reader/super_reader_scrolling_test.dart @@ -233,11 +233,10 @@ void main() { // Ensure the reader didn't start scrolled. expect(scrollController.offset, 0); - // Drag an amount of pixels chosen experimentally from the top of the reader. - final dragGesture = await tester.dragInMultipleFrames( - startLocation: tester.getTopLeft(find.byType(SuperReader)), - dragAmount: const Offset(0, 200.0), - frameCount: 10, + // Drag an arbitrary amount of pixels from the top of the reader. + final dragGesture = await tester.dragByFrameCount( + startLocation: tester.getRect(find.byType(SuperReader)).topCenter + const Offset(0, 5), + totalDragOffset: const Offset(0, 200.0), ); // Ensure we don't scroll. @@ -262,11 +261,10 @@ void main() { // Jump to the bottom. scrollController.jumpTo(scrollController.position.maxScrollExtent); - // Drag an amount of pixels chosen experimentally from the bottom of the reader. - final dragGesture = await tester.dragInMultipleFrames( - startLocation: tester.getBottomLeft(find.byType(SuperReader)) - const Offset(0, 5), - dragAmount: const Offset(0, -200.0), - frameCount: 10, + // Drag an arbitrary amount of pixels from the bottom of the reader. + final dragGesture = await tester.dragByFrameCount( + startLocation: tester.getRect(find.byType(SuperReader)).bottomCenter - const Offset(0, 5), + totalDragOffset: const Offset(0, -200.0), ); // Ensure we don't scroll. @@ -291,11 +289,10 @@ void main() { // Ensure the scrollview didn't start scrolled. expect(scrollController.offset, 0); - // Drag an amount of pixels chosen experimentally a few pixels below the top of the reader. - final dragGesture = await tester.dragInMultipleFrames( + // Drag an arbitrary amount of pixels a few pixels below the top of the reader. + final dragGesture = await tester.dragByFrameCount( startLocation: tester.getRect(find.byType(SuperReader)).topCenter + const Offset(0, 5), - dragAmount: const Offset(0, 80.0), - frameCount: 10, + totalDragOffset: const Offset(0, 80.0), ); // Ensure we are overscrolling while holding the pointer down. @@ -325,12 +322,11 @@ void main() { scrollController.jumpTo(scrollController.position.maxScrollExtent); await tester.pumpAndSettle(); - // Drag an amount of pixels chosen experimentally from the bottom of the reader. - // The gesture starts with a small margin from the bottom, also chosen experimentally. - final dragGesture = await tester.dragInMultipleFrames( + // Drag an arbitrary amount of pixels from the bottom of the reader. + // The gesture starts with an arbitrary margin from the bottom. + final dragGesture = await tester.dragByFrameCount( startLocation: tester.getRect(find.byType(SuperReader)).bottomCenter - const Offset(0, 5), - dragAmount: const Offset(0, -200.0), - frameCount: 10, + totalDragOffset: const Offset(0, -200.0), ); // Ensure we are overscrolling while holding the pointer down. @@ -580,11 +576,10 @@ void main() { // Ensure the scrollview didn't start scrolled. expect(scrollController.offset, 0); - // Drag an amount of pixels chosen experimentally from the top of the reader. - final dragGesture = await tester.dragInMultipleFrames( - startLocation: tester.getRect(find.byType(SuperReader)).topCenter, - dragAmount: const Offset(0, 400.0), - frameCount: 10, + // Drag an arbitrary amount of pixels from the top of the reader. + final dragGesture = await tester.dragByFrameCount( + startLocation: tester.getRect(find.byType(SuperReader)).topCenter + const Offset(0, 5), + totalDragOffset: const Offset(0, 400.0), ); // Ensure we don't scroll. @@ -625,11 +620,10 @@ void main() { // Jump to the bottom. scrollController.jumpTo(scrollController.position.maxScrollExtent); - // Drag an amount of pixels chosen experimentally from the bottom of the reader. - final dragGesture = await tester.dragInMultipleFrames( - startLocation: tester.getBottomLeft(find.byType(CustomScrollView)) - const Offset(0, 5), - dragAmount: const Offset(0, -400.0), - frameCount: 10, + // Drag an arbitrary amount of pixels from the bottom of the reader. + final dragGesture = await tester.dragByFrameCount( + startLocation: tester.getRect(find.byType(CustomScrollView)).bottomCenter - const Offset(0, 5), + totalDragOffset: const Offset(0, -400.0), ); // Ensure we don't scroll. @@ -673,11 +667,9 @@ void main() { expect(scrollController.offset, 0); // Drag an arbitrary amount, smaller than the reader size. - // Drag an amount of pixels chosen experimentally from the top of the reader. - final dragGesture = await tester.dragInMultipleFrames( + final dragGesture = await tester.dragByFrameCount( startLocation: tester.getRect(find.byType(CustomScrollView)).topCenter + const Offset(0, 5), - dragAmount: const Offset(0, 80.0), - frameCount: 10, + totalDragOffset: const Offset(0, 80.0), ); // Ensure we are overscrolling while holding the pointer down. @@ -726,11 +718,9 @@ void main() { await tester.pumpAndSettle(); // Drag up an arbitrary amount, smaller than the reader size. - // Drag an amount of pixels chosen experimentally from the top of the reader. - final dragGesture = await tester.dragInMultipleFrames( + final dragGesture = await tester.dragByFrameCount( startLocation: tester.getRect(find.byType(CustomScrollView)).bottomCenter - const Offset(0, 5), - dragAmount: const Offset(0, -100.0), - frameCount: 10, + totalDragOffset: const Offset(0, -100.0), ); // Ensure we are overscrolling while holding the pointer down. diff --git a/super_editor/test/test_tools.dart b/super_editor/test/test_tools.dart index cbcd68892d..f6120df932 100644 --- a/super_editor/test/test_tools.dart +++ b/super_editor/test/test_tools.dart @@ -19,17 +19,23 @@ class TestUrlLauncher implements UrlLauncher { /// Extension on [WidgetTester] to make it easier to perform drag gestures. extension DragExtensions on WidgetTester { - /// Drags from the [startLocation] by [dragAmount] in multiple frames. + /// Simulates a user drag from [startLocation] to `startLocation + totalDragOffset`. /// - /// The [dragAmount] is distributed evenly between [frameCount] frames. + /// Starts a gesture at [startLocation] and repeatedly drags the gesture + /// across [frameCount] frames, pumping a frame between each drag. + /// The gesture moves a distance each frame that's calculated as + /// `totalDragOffset / frameCount`. /// - /// The caller must end the gesture by calling [TestGesture.up]. - Future dragInMultipleFrames({ + /// This method does not call `pumpAndSettle()`, so that the client can inspect + /// the app state immediately after the drag completes. + /// + /// The client must call [TestGesture.up] on the returned [TestGesture]. + Future dragByFrameCount({ required Offset startLocation, - required Offset dragAmount, - required int frameCount, + required Offset totalDragOffset, + int frameCount = 10, }) async { - final dragPerFrame = Offset(dragAmount.dx / frameCount, dragAmount.dy / frameCount); + final dragPerFrame = Offset(totalDragOffset.dx / frameCount, totalDragOffset.dy / frameCount); final dragGesture = await startGesture(startLocation); for (int i = 0; i < frameCount; i += 1) { From e0cf9207e9d9ef8123b12e06b0cda92571af0054 Mon Sep 17 00:00:00 2001 From: Angelo Silvestre Date: Mon, 6 Nov 2023 21:42:01 -0300 Subject: [PATCH 09/13] Update comments --- .../super_editor/supereditor_scrolling_test.dart | 16 ++++++++-------- .../super_reader_scrolling_test.dart | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/super_editor/test/super_editor/supereditor_scrolling_test.dart b/super_editor/test/super_editor/supereditor_scrolling_test.dart index 744869492e..ec5962f530 100644 --- a/super_editor/test/super_editor/supereditor_scrolling_test.dart +++ b/super_editor/test/super_editor/supereditor_scrolling_test.dart @@ -525,7 +525,7 @@ void main() { // End the gesture. await dragGesture.up(); - // Let any pending timers resolve. + // Wait for the long-press timer to resolve. await tester.pumpAndSettle(); }); @@ -554,7 +554,7 @@ void main() { // End the gesture. await dragGesture.up(); - // Let any pending timers resolve. + // Wait for the long-press timer to resolve. await tester.pumpAndSettle(); }); @@ -583,7 +583,7 @@ void main() { // Release the pointer to end the gesture. await dragGesture.up(); - // Let any pending timers resolve. + // Wait for the long-press timer to resolve. await tester.pumpAndSettle(); // Ensure the we scrolled back to the top. @@ -617,7 +617,7 @@ void main() { // Release the pointer to end the gesture. await dragGesture.up(); - // Let any pending timers resolve. + // Wait for the long-press timer to resolve. await tester.pumpAndSettle(); // Ensure the we scrolled back to the end. @@ -858,7 +858,7 @@ void main() { // End the gesture. await dragGesture.up(); - // Let any pending timers resolve. + // Wait for the long-press timer to resolve. await tester.pumpAndSettle(); }); @@ -905,7 +905,7 @@ void main() { // End the gesture. await dragGesture.up(); - // Let any pending timers resolve. + // Wait for the long-press timer to resolve. await tester.pumpAndSettle(); }); @@ -952,7 +952,7 @@ void main() { // Release the pointer to end the gesture. await dragGesture.up(); - // Let any pending timers resolve. + // Wait for the long-press timer to resolve. await tester.pumpAndSettle(); // Ensure the we scrolled back to the top. @@ -1003,7 +1003,7 @@ void main() { // Release the pointer to end the gesture. await dragGesture.up(); - // Let any pending timers resolve. + // Wait for the long-press timer to resolve. await tester.pumpAndSettle(); // Ensure the we scrolled back to the end. diff --git a/super_editor/test/super_reader/super_reader_scrolling_test.dart b/super_editor/test/super_reader/super_reader_scrolling_test.dart index 015b71c757..c1ddda0168 100644 --- a/super_editor/test/super_reader/super_reader_scrolling_test.dart +++ b/super_editor/test/super_reader/super_reader_scrolling_test.dart @@ -245,7 +245,7 @@ void main() { // End the gesture. await dragGesture.up(); - // Let any pending timers resolve. + // Wait for the long-press timer to resolve. await tester.pumpAndSettle(); }); @@ -273,7 +273,7 @@ void main() { // End the gesture. await dragGesture.up(); - // Let any pending timers resolve. + // Wait for the long-press timer to resolve. await tester.pumpAndSettle(); }); @@ -302,7 +302,7 @@ void main() { // Release the pointer to end the gesture. await dragGesture.up(); - // Let any pending timers resolve. + // Wait for the long-press timer to resolve. await tester.pumpAndSettle(); // Ensure the we scrolled back to the top. @@ -336,7 +336,7 @@ void main() { // Release the pointer to end the gesture. await dragGesture.up(); - // Let any pending timers resolve. + // Wait for the long-press timer to resolve. await tester.pumpAndSettle(); // Ensure the we scrolled back to the end. @@ -588,7 +588,7 @@ void main() { // End the gesture. await dragGesture.up(); - // Let any pending timers resolve. + // Wait for the long-press timer to resolve. await tester.pumpAndSettle(); }); @@ -632,7 +632,7 @@ void main() { // End the gesture. await dragGesture.up(); - // Let any pending timers resolve. + // Wait for the long-press timer to resolve. await tester.pumpAndSettle(); }); @@ -679,7 +679,7 @@ void main() { // Release the pointer to end the gesture. await dragGesture.up(); - // Let any pending timers resolve. + // Wait for the long-press timer to resolve. await tester.pumpAndSettle(); // Ensure the we scrolled back to the top. @@ -730,7 +730,7 @@ void main() { // Release the pointer to end the gesture. await dragGesture.up(); - // Let any pending timers resolve. + // Wait for the long-press timer to resolve. await tester.pumpAndSettle(); // Ensure the we scrolled back to the end. From 7097060ce2f0296ef661e76f8ae68f8412085788 Mon Sep 17 00:00:00 2001 From: Angelo Silvestre Date: Mon, 6 Nov 2023 22:10:00 -0300 Subject: [PATCH 10/13] Add test configuration to use a CustomScrollView --- .../supereditor_scrolling_test.dart | 120 +++-------------- .../super_editor/supereditor_test_tools.dart | 36 +++++- .../test/super_reader/reader_test_tools.dart | 75 ++++++++--- .../super_reader_scrolling_test.dart | 122 +++--------------- 4 files changed, 125 insertions(+), 228 deletions(-) diff --git a/super_editor/test/super_editor/supereditor_scrolling_test.dart b/super_editor/test/super_editor/supereditor_scrolling_test.dart index ec5962f530..b9044d23a9 100644 --- a/super_editor/test/super_editor/supereditor_scrolling_test.dart +++ b/super_editor/test/super_editor/supereditor_scrolling_test.dart @@ -713,23 +713,9 @@ void main() { await tester .createDocument() // .withLongTextContent() - .withCustomWidgetTreeBuilder( - (superEditor) => MaterialApp( - home: Scaffold( - body: ConstrainedBox( - constraints: const BoxConstraints(maxHeight: 200), - child: CustomScrollView( - controller: scrollController, - slivers: [ - SliverToBoxAdapter( - child: superEditor, - ), - ], - ), - ), - ), - ), - ) + .withEditorSize(const Size(200, 200)) + .insideCustomScrollView() + .withCustomScrollViewScrollController(scrollController) .pump(); // Ensure the scrollview didn't start scrolled. @@ -769,23 +755,9 @@ void main() { await tester .createDocument() // .withLongTextContent() - .withCustomWidgetTreeBuilder( - (superEditor) => MaterialApp( - home: Scaffold( - body: ConstrainedBox( - constraints: const BoxConstraints(maxHeight: 200), - child: CustomScrollView( - controller: scrollController, - slivers: [ - SliverToBoxAdapter( - child: superEditor, - ), - ], - ), - ), - ), - ), - ) + .withEditorSize(const Size(200, 200)) + .insideCustomScrollView() + .withCustomScrollViewScrollController(scrollController) .pump(); // Ensure the scrollview didn't start scrolled. @@ -824,23 +796,8 @@ void main() { await tester .createDocument() .withSingleParagraph() - .withCustomWidgetTreeBuilder( - (superEditor) => MaterialApp( - home: Scaffold( - body: ConstrainedBox( - constraints: const BoxConstraints(maxHeight: 200), - child: CustomScrollView( - controller: scrollController, - slivers: [ - SliverToBoxAdapter( - child: superEditor, - ), - ], - ), - ), - ), - ), - ) + .insideCustomScrollView() + .withCustomScrollViewScrollController(scrollController) .pump(); // Ensure the scrollview didn't start scrolled. @@ -865,26 +822,14 @@ void main() { testWidgetsOnAndroid("doesn't overscroll when dragging up", (tester) async { final scrollController = ScrollController(); + // Pump an editor inside a CustomScrollView without enough room to display + // the whole content. await tester .createDocument() .withSingleParagraph() - .withCustomWidgetTreeBuilder( - (superEditor) => MaterialApp( - home: Scaffold( - body: ConstrainedBox( - constraints: const BoxConstraints(maxHeight: 200), - child: CustomScrollView( - controller: scrollController, - slivers: [ - SliverToBoxAdapter( - child: superEditor, - ), - ], - ), - ), - ), - ), - ) + .withEditorSize(const Size(200, 200)) + .insideCustomScrollView() + .withCustomScrollViewScrollController(scrollController) .pump(); // Jump to the bottom. @@ -917,23 +862,8 @@ void main() { await tester .createDocument() // .withLongTextContent() - .withCustomWidgetTreeBuilder( - (superEditor) => MaterialApp( - home: Scaffold( - body: ConstrainedBox( - constraints: const BoxConstraints(maxHeight: 200), - child: CustomScrollView( - controller: scrollController, - slivers: [ - SliverToBoxAdapter( - child: superEditor, - ), - ], - ), - ), - ), - ), - ) + .insideCustomScrollView() + .withCustomScrollViewScrollController(scrollController) .pump(); // Ensure the scrollview didn't start scrolled. @@ -967,23 +897,9 @@ void main() { await tester .createDocument() // .withLongTextContent() - .withCustomWidgetTreeBuilder( - (superEditor) => MaterialApp( - home: Scaffold( - body: ConstrainedBox( - constraints: const BoxConstraints(maxHeight: 200), - child: CustomScrollView( - controller: scrollController, - slivers: [ - SliverToBoxAdapter( - child: superEditor, - ), - ], - ), - ), - ), - ), - ) + .withEditorSize(const Size(200, 200)) + .insideCustomScrollView() + .withCustomScrollViewScrollController(scrollController) .pump(); // Jump to the bottom. diff --git a/super_editor/test/super_editor/supereditor_test_tools.dart b/super_editor/test/super_editor/supereditor_test_tools.dart index 0c9c65dc57..660fde3f35 100644 --- a/super_editor/test/super_editor/supereditor_test_tools.dart +++ b/super_editor/test/super_editor/supereditor_test_tools.dart @@ -301,6 +301,20 @@ class TestSuperEditorConfigurator { return this; } + /// Configures the [SuperEditor] to be displayed inside a [CustomScrollView]. + TestSuperEditorConfigurator insideCustomScrollView() { + _config.insideCustomScrollView = true; + return this; + } + + /// Configures the ancestor [CustomScrollView] to use the given [scrollController]. + /// + /// The [CustomScrollView] must be added to the configuration by calling [insideCustomScrollView]. + TestSuperEditorConfigurator withCustomScrollViewScrollController(ScrollController? scrollController) { + _config.customScrollViewScrollController = scrollController; + return this; + } + /// Pumps a [SuperEditor] widget tree with the desired configuration, and returns /// a [TestDocumentContext], which includes the artifacts connected to the widget /// tree, e.g., the [DocumentEditor], [DocumentComposer], etc. @@ -333,7 +347,9 @@ class TestSuperEditorConfigurator { ConfiguredSuperEditorWidget _build([TestDocumentContext? testDocumentContext]) { final context = testDocumentContext ?? _createTestDocumentContext(); final superEditor = _buildConstrainedContent( - _buildSuperEditor(context), + _buildAncestorScrollable( + child: _buildSuperEditor(context), + ), ); return ConfiguredSuperEditorWidget( @@ -397,6 +413,21 @@ class TestSuperEditorConfigurator { return superEditor; } + /// Places [child] inside a [CustomScrollView], based on configurations in this class. + Widget _buildAncestorScrollable({required Widget child}) { + if (_config.insideCustomScrollView) { + return CustomScrollView( + controller: _config.customScrollViewScrollController, + slivers: [ + SliverToBoxAdapter( + child: child, + ), + ], + ); + } + return child; + } + /// Builds a [SuperEditor] widget based on the configuration of the given /// [testDocumentContext], as well as other configurations in this class. Widget _buildSuperEditor(TestDocumentContext testDocumentContext) { @@ -474,6 +505,9 @@ class SuperEditorTestConfiguration { final plugins = {}; WidgetTreeBuilder? widgetTreeBuilder; + + bool insideCustomScrollView = false; + ScrollController? customScrollViewScrollController; } /// Must return a widget tree containing the given [superEditor] diff --git a/super_editor/test/super_reader/reader_test_tools.dart b/super_editor/test/super_reader/reader_test_tools.dart index cc5e8ebba1..f8967697b1 100644 --- a/super_editor/test/super_reader/reader_test_tools.dart +++ b/super_editor/test/super_reader/reader_test_tools.dart @@ -95,6 +95,8 @@ class TestDocumentConfigurator { DocumentSelection? _selection; WidgetBuilder? _androidToolbarBuilder; DocumentFloatingToolbarBuilder? _iOSToolbarBuilder; + bool _insideCustomScrollView = false; + ScrollController? _customScrollViewController; /// Configures the [SuperReader] for standard desktop interactions, /// e.g., mouse and keyboard input. @@ -222,6 +224,20 @@ class TestDocumentConfigurator { return this; } + /// Configures the [SuperReader] to be displayed inside a [CustomScrollView]. + TestDocumentConfigurator insideCustomScrollView() { + _insideCustomScrollView = true; + return this; + } + + /// Configures the ancestor [CustomScrollView] to use the given [scrollController]. + /// + /// The [CustomScrollView] must be added to the configuration by calling [insideCustomScrollView]. + TestDocumentConfigurator withCustomScrollViewScrollController(ScrollController? scrollController) { + _customScrollViewController = scrollController; + return this; + } + /// Pumps a [SuperReader] widget tree with the desired configuration, and returns /// a [TestDocumentContext], which includes the artifacts connected to the widget /// tree, e.g., the [DocumentEditor], [DocumentComposer], etc. @@ -242,26 +258,28 @@ class TestDocumentConfigurator { documentContext: documentContext, ); - final superDocument = _buildContent( - SuperReaderIosControlsScope( - controller: SuperReaderIosControlsController( - toolbarBuilder: _iOSToolbarBuilder, - ), - child: SuperReader( - focusNode: testContext.focusNode, - document: documentContext.document, - documentLayoutKey: layoutKey, - selection: documentContext.selection, - selectionStyle: _selectionStyles, - gestureMode: _gestureMode ?? _defaultGestureMode, - stylesheet: _stylesheet, - componentBuilders: [ - ..._addedComponents, - ...(_componentBuilders ?? defaultComponentBuilders), - ], - autofocus: _autoFocus, - scrollController: _scrollController, - androidToolbarBuilder: _androidToolbarBuilder, + final superDocument = _buildConstrainedContent( + _buildAncestorScrollable( + child: SuperReaderIosControlsScope( + controller: SuperReaderIosControlsController( + toolbarBuilder: _iOSToolbarBuilder, + ), + child: SuperReader( + focusNode: testContext.focusNode, + document: documentContext.document, + documentLayoutKey: layoutKey, + selection: documentContext.selection, + selectionStyle: _selectionStyles, + gestureMode: _gestureMode ?? _defaultGestureMode, + stylesheet: _stylesheet, + componentBuilders: [ + ..._addedComponents, + ...(_componentBuilders ?? defaultComponentBuilders), + ], + autofocus: _autoFocus, + scrollController: _scrollController, + androidToolbarBuilder: _androidToolbarBuilder, + ), ), ), ); @@ -273,7 +291,7 @@ class TestDocumentConfigurator { return testContext; } - Widget _buildContent(Widget superReader) { + Widget _buildConstrainedContent(Widget superReader) { if (_editorSize != null) { return ConstrainedBox( constraints: BoxConstraints( @@ -286,6 +304,21 @@ class TestDocumentConfigurator { return superReader; } + /// Places [child] inside a [CustomScrollView], based on configurations in this class. + Widget _buildAncestorScrollable({required Widget child}) { + if (_insideCustomScrollView) { + return CustomScrollView( + controller: _customScrollViewController, + slivers: [ + SliverToBoxAdapter( + child: child, + ), + ], + ); + } + return child; + } + Widget _buildWidgetTree(Widget superReader) { if (_widgetTreeBuilder != null) { return _widgetTreeBuilder!(superReader); diff --git a/super_editor/test/super_reader/super_reader_scrolling_test.dart b/super_editor/test/super_reader/super_reader_scrolling_test.dart index c1ddda0168..5ca1ed8e3b 100644 --- a/super_editor/test/super_reader/super_reader_scrolling_test.dart +++ b/super_editor/test/super_reader/super_reader_scrolling_test.dart @@ -445,23 +445,9 @@ void main() { await tester .createDocument() // .withLongTextContent() - .withCustomWidgetTreeBuilder( - (superReader) => MaterialApp( - home: Scaffold( - body: ConstrainedBox( - constraints: const BoxConstraints(maxHeight: 200), - child: CustomScrollView( - controller: scrollController, - slivers: [ - SliverToBoxAdapter( - child: superReader, - ), - ], - ), - ), - ), - ), - ) + .withEditorSize(const Size(200, 200)) + .insideCustomScrollView() + .withCustomScrollViewScrollController(scrollController) .pump(); // Ensure the scrollview didn't start scrolled. @@ -500,23 +486,9 @@ void main() { await tester .createDocument() // .withLongTextContent() - .withCustomWidgetTreeBuilder( - (superReader) => MaterialApp( - home: Scaffold( - body: ConstrainedBox( - constraints: const BoxConstraints(maxHeight: 200), - child: CustomScrollView( - controller: scrollController, - slivers: [ - SliverToBoxAdapter( - child: superReader, - ), - ], - ), - ), - ), - ), - ) + .withEditorSize(const Size(200, 200)) + .insideCustomScrollView() + .withCustomScrollViewScrollController(scrollController) .pump(); // Ensure the scrollview didn't start scrolled. @@ -554,23 +526,8 @@ void main() { await tester .createDocument() .withSingleParagraph() - .withCustomWidgetTreeBuilder( - (superReader) => MaterialApp( - home: Scaffold( - body: ConstrainedBox( - constraints: const BoxConstraints(maxHeight: 200), - child: CustomScrollView( - controller: scrollController, - slivers: [ - SliverToBoxAdapter( - child: superReader, - ), - ], - ), - ), - ), - ), - ) + .insideCustomScrollView() + .withCustomScrollViewScrollController(scrollController) .pump(); // Ensure the scrollview didn't start scrolled. @@ -595,26 +552,14 @@ void main() { testWidgetsOnAndroid("doesn't overscroll when dragging up", (tester) async { final scrollController = ScrollController(); + // Pump a reader inside a CustomScrollView without enough room to display + // the whole content. await tester .createDocument() .withSingleParagraph() - .withCustomWidgetTreeBuilder( - (superReader) => MaterialApp( - home: Scaffold( - body: ConstrainedBox( - constraints: const BoxConstraints(maxHeight: 200), - child: CustomScrollView( - controller: scrollController, - slivers: [ - SliverToBoxAdapter( - child: superReader, - ), - ], - ), - ), - ), - ), - ) + .withEditorSize(const Size(200, 200)) + .insideCustomScrollView() + .withCustomScrollViewScrollController(scrollController) .pump(); // Jump to the bottom. @@ -639,28 +584,11 @@ void main() { testWidgetsOnIos('overscrolls when dragging down', (tester) async { final scrollController = ScrollController(); - // Pump a reader inside a CustomScrollView without enough room to display - // the whole content. await tester .createDocument() // .withLongTextContent() - .withCustomWidgetTreeBuilder( - (superReader) => MaterialApp( - home: Scaffold( - body: ConstrainedBox( - constraints: const BoxConstraints(maxHeight: 200), - child: CustomScrollView( - controller: scrollController, - slivers: [ - SliverToBoxAdapter( - child: superReader, - ), - ], - ), - ), - ), - ), - ) + .insideCustomScrollView() + .withCustomScrollViewScrollController(scrollController) .pump(); // Ensure the scrollview didn't start scrolled. @@ -694,23 +622,9 @@ void main() { await tester .createDocument() // .withLongTextContent() - .withCustomWidgetTreeBuilder( - (superReader) => MaterialApp( - home: Scaffold( - body: ConstrainedBox( - constraints: const BoxConstraints(maxHeight: 200), - child: CustomScrollView( - controller: scrollController, - slivers: [ - SliverToBoxAdapter( - child: superReader, - ), - ], - ), - ), - ), - ), - ) + .withEditorSize(const Size(200, 200)) + .insideCustomScrollView() + .withCustomScrollViewScrollController(scrollController) .pump(); // Jump to the bottom. From 9ab59dc99b42e8f62624e1fdaa9598055676dc15 Mon Sep 17 00:00:00 2001 From: Angelo Silvestre Date: Mon, 6 Nov 2023 22:13:09 -0300 Subject: [PATCH 11/13] Add docs --- super_editor/test/super_editor/supereditor_test_tools.dart | 2 ++ super_editor/test/super_reader/reader_test_tools.dart | 2 ++ 2 files changed, 4 insertions(+) diff --git a/super_editor/test/super_editor/supereditor_test_tools.dart b/super_editor/test/super_editor/supereditor_test_tools.dart index 660fde3f35..2b78f0f0cc 100644 --- a/super_editor/test/super_editor/supereditor_test_tools.dart +++ b/super_editor/test/super_editor/supereditor_test_tools.dart @@ -302,6 +302,8 @@ class TestSuperEditorConfigurator { } /// Configures the [SuperEditor] to be displayed inside a [CustomScrollView]. + /// + /// The [CustomScrollView] is constrained by the size provided in [withEditorSize]. TestSuperEditorConfigurator insideCustomScrollView() { _config.insideCustomScrollView = true; return this; diff --git a/super_editor/test/super_reader/reader_test_tools.dart b/super_editor/test/super_reader/reader_test_tools.dart index f8967697b1..bd0a8d30b1 100644 --- a/super_editor/test/super_reader/reader_test_tools.dart +++ b/super_editor/test/super_reader/reader_test_tools.dart @@ -225,6 +225,8 @@ class TestDocumentConfigurator { } /// Configures the [SuperReader] to be displayed inside a [CustomScrollView]. + /// + /// The [CustomScrollView] is constrained by the size provided in [withEditorSize]. TestDocumentConfigurator insideCustomScrollView() { _insideCustomScrollView = true; return this; From 8c1908410087f5afc35e22943be7c5c8349b90e3 Mon Sep 17 00:00:00 2001 From: Angelo Silvestre Date: Mon, 13 Nov 2023 19:25:09 -0300 Subject: [PATCH 12/13] PR updates --- .../supereditor_scrolling_test.dart | 23 ++++-------- .../super_editor/supereditor_test_tools.dart | 35 ++++++++----------- 2 files changed, 21 insertions(+), 37 deletions(-) diff --git a/super_editor/test/super_editor/supereditor_scrolling_test.dart b/super_editor/test/super_editor/supereditor_scrolling_test.dart index b9044d23a9..ed70f4dcce 100644 --- a/super_editor/test/super_editor/supereditor_scrolling_test.dart +++ b/super_editor/test/super_editor/supereditor_scrolling_test.dart @@ -714,8 +714,7 @@ void main() { .createDocument() // .withLongTextContent() .withEditorSize(const Size(200, 200)) - .insideCustomScrollView() - .withCustomScrollViewScrollController(scrollController) + .insideCustomScrollView(scrollController) .pump(); // Ensure the scrollview didn't start scrolled. @@ -756,8 +755,7 @@ void main() { .createDocument() // .withLongTextContent() .withEditorSize(const Size(200, 200)) - .insideCustomScrollView() - .withCustomScrollViewScrollController(scrollController) + .insideCustomScrollView(scrollController) .pump(); // Ensure the scrollview didn't start scrolled. @@ -794,10 +792,9 @@ void main() { final scrollController = ScrollController(); await tester - .createDocument() + .createDocument() // .withSingleParagraph() - .insideCustomScrollView() - .withCustomScrollViewScrollController(scrollController) + .insideCustomScrollView(scrollController) .pump(); // Ensure the scrollview didn't start scrolled. @@ -828,8 +825,7 @@ void main() { .createDocument() .withSingleParagraph() .withEditorSize(const Size(200, 200)) - .insideCustomScrollView() - .withCustomScrollViewScrollController(scrollController) + .insideCustomScrollView(scrollController) .pump(); // Jump to the bottom. @@ -844,9 +840,6 @@ void main() { // Ensure we don't scroll. expect(scrollController.offset, scrollController.position.maxScrollExtent); - // Ensure we don't scroll. - expect(scrollController.offset, scrollController.position.maxScrollExtent); - // End the gesture. await dragGesture.up(); @@ -862,8 +855,7 @@ void main() { await tester .createDocument() // .withLongTextContent() - .insideCustomScrollView() - .withCustomScrollViewScrollController(scrollController) + .insideCustomScrollView(scrollController) .pump(); // Ensure the scrollview didn't start scrolled. @@ -898,8 +890,7 @@ void main() { .createDocument() // .withLongTextContent() .withEditorSize(const Size(200, 200)) - .insideCustomScrollView() - .withCustomScrollViewScrollController(scrollController) + .insideCustomScrollView(scrollController) .pump(); // Jump to the bottom. diff --git a/super_editor/test/super_editor/supereditor_test_tools.dart b/super_editor/test/super_editor/supereditor_test_tools.dart index 2b78f0f0cc..050ef96a60 100644 --- a/super_editor/test/super_editor/supereditor_test_tools.dart +++ b/super_editor/test/super_editor/supereditor_test_tools.dart @@ -304,15 +304,8 @@ class TestSuperEditorConfigurator { /// Configures the [SuperEditor] to be displayed inside a [CustomScrollView]. /// /// The [CustomScrollView] is constrained by the size provided in [withEditorSize]. - TestSuperEditorConfigurator insideCustomScrollView() { + TestSuperEditorConfigurator insideCustomScrollView([ScrollController? scrollController]) { _config.insideCustomScrollView = true; - return this; - } - - /// Configures the ancestor [CustomScrollView] to use the given [scrollController]. - /// - /// The [CustomScrollView] must be added to the configuration by calling [insideCustomScrollView]. - TestSuperEditorConfigurator withCustomScrollViewScrollController(ScrollController? scrollController) { _config.customScrollViewScrollController = scrollController; return this; } @@ -417,17 +410,18 @@ class TestSuperEditorConfigurator { /// Places [child] inside a [CustomScrollView], based on configurations in this class. Widget _buildAncestorScrollable({required Widget child}) { - if (_config.insideCustomScrollView) { - return CustomScrollView( - controller: _config.customScrollViewScrollController, - slivers: [ - SliverToBoxAdapter( - child: child, - ), - ], - ); + if (!_config.insideCustomScrollView) { + return child; } - return child; + + return CustomScrollView( + controller: _config.customScrollViewScrollController, + slivers: [ + SliverToBoxAdapter( + child: child, + ), + ], + ); } /// Builds a [SuperEditor] widget based on the configuration of the given @@ -487,6 +481,8 @@ class SuperEditorTestConfiguration { List? componentBuilders; Stylesheet? stylesheet; ScrollController? scrollController; + bool insideCustomScrollView = false; + ScrollController? customScrollViewScrollController; DocumentGestureMode? gestureMode; TextInputSource? inputSource; SuperEditorSelectionPolicies? selectionPolicies; @@ -507,9 +503,6 @@ class SuperEditorTestConfiguration { final plugins = {}; WidgetTreeBuilder? widgetTreeBuilder; - - bool insideCustomScrollView = false; - ScrollController? customScrollViewScrollController; } /// Must return a widget tree containing the given [superEditor] From 537837387b58f08d1c82111828c94d01938dd8d8 Mon Sep 17 00:00:00 2001 From: Angelo Silvestre Date: Wed, 15 Nov 2023 17:55:09 -0300 Subject: [PATCH 13/13] Use withScrollController to define ancestor scrollable controller --- .../supereditor_scrolling_test.dart | 18 ++++++---- .../super_editor/supereditor_test_tools.dart | 8 ++--- .../test/super_reader/reader_test_tools.dart | 34 ++++++++----------- .../super_reader_scrolling_test.dart | 12 +++---- 4 files changed, 36 insertions(+), 36 deletions(-) diff --git a/super_editor/test/super_editor/supereditor_scrolling_test.dart b/super_editor/test/super_editor/supereditor_scrolling_test.dart index ed70f4dcce..3002c7f657 100644 --- a/super_editor/test/super_editor/supereditor_scrolling_test.dart +++ b/super_editor/test/super_editor/supereditor_scrolling_test.dart @@ -714,7 +714,8 @@ void main() { .createDocument() // .withLongTextContent() .withEditorSize(const Size(200, 200)) - .insideCustomScrollView(scrollController) + .insideCustomScrollView() + .withScrollController(scrollController) .pump(); // Ensure the scrollview didn't start scrolled. @@ -755,7 +756,8 @@ void main() { .createDocument() // .withLongTextContent() .withEditorSize(const Size(200, 200)) - .insideCustomScrollView(scrollController) + .insideCustomScrollView() + .withScrollController(scrollController) .pump(); // Ensure the scrollview didn't start scrolled. @@ -794,7 +796,8 @@ void main() { await tester .createDocument() // .withSingleParagraph() - .insideCustomScrollView(scrollController) + .insideCustomScrollView() + .withScrollController(scrollController) .pump(); // Ensure the scrollview didn't start scrolled. @@ -825,7 +828,8 @@ void main() { .createDocument() .withSingleParagraph() .withEditorSize(const Size(200, 200)) - .insideCustomScrollView(scrollController) + .insideCustomScrollView() + .withScrollController(scrollController) .pump(); // Jump to the bottom. @@ -855,7 +859,8 @@ void main() { await tester .createDocument() // .withLongTextContent() - .insideCustomScrollView(scrollController) + .insideCustomScrollView() + .withScrollController(scrollController) .pump(); // Ensure the scrollview didn't start scrolled. @@ -890,7 +895,8 @@ void main() { .createDocument() // .withLongTextContent() .withEditorSize(const Size(200, 200)) - .insideCustomScrollView(scrollController) + .insideCustomScrollView() + .withScrollController(scrollController) .pump(); // Jump to the bottom. diff --git a/super_editor/test/super_editor/supereditor_test_tools.dart b/super_editor/test/super_editor/supereditor_test_tools.dart index 050ef96a60..ec8b3151f2 100644 --- a/super_editor/test/super_editor/supereditor_test_tools.dart +++ b/super_editor/test/super_editor/supereditor_test_tools.dart @@ -304,9 +304,10 @@ class TestSuperEditorConfigurator { /// Configures the [SuperEditor] to be displayed inside a [CustomScrollView]. /// /// The [CustomScrollView] is constrained by the size provided in [withEditorSize]. - TestSuperEditorConfigurator insideCustomScrollView([ScrollController? scrollController]) { + /// + /// Use [withScrollController] to define the [ScrollController] of the [CustomScrollView]. + TestSuperEditorConfigurator insideCustomScrollView() { _config.insideCustomScrollView = true; - _config.customScrollViewScrollController = scrollController; return this; } @@ -415,7 +416,7 @@ class TestSuperEditorConfigurator { } return CustomScrollView( - controller: _config.customScrollViewScrollController, + controller: _config.scrollController, slivers: [ SliverToBoxAdapter( child: child, @@ -482,7 +483,6 @@ class SuperEditorTestConfiguration { Stylesheet? stylesheet; ScrollController? scrollController; bool insideCustomScrollView = false; - ScrollController? customScrollViewScrollController; DocumentGestureMode? gestureMode; TextInputSource? inputSource; SuperEditorSelectionPolicies? selectionPolicies; diff --git a/super_editor/test/super_reader/reader_test_tools.dart b/super_editor/test/super_reader/reader_test_tools.dart index bd0a8d30b1..7f15457dc0 100644 --- a/super_editor/test/super_reader/reader_test_tools.dart +++ b/super_editor/test/super_reader/reader_test_tools.dart @@ -91,12 +91,11 @@ class TestDocumentConfigurator { List? _componentBuilders; WidgetTreeBuilder? _widgetTreeBuilder; ScrollController? _scrollController; + bool _insideCustomScrollView = false; FocusNode? _focusNode; DocumentSelection? _selection; WidgetBuilder? _androidToolbarBuilder; DocumentFloatingToolbarBuilder? _iOSToolbarBuilder; - bool _insideCustomScrollView = false; - ScrollController? _customScrollViewController; /// Configures the [SuperReader] for standard desktop interactions, /// e.g., mouse and keyboard input. @@ -227,19 +226,13 @@ class TestDocumentConfigurator { /// Configures the [SuperReader] to be displayed inside a [CustomScrollView]. /// /// The [CustomScrollView] is constrained by the size provided in [withEditorSize]. + /// + /// Use [withScrollController] to define the [ScrollController] of the [CustomScrollView]. TestDocumentConfigurator insideCustomScrollView() { _insideCustomScrollView = true; return this; } - /// Configures the ancestor [CustomScrollView] to use the given [scrollController]. - /// - /// The [CustomScrollView] must be added to the configuration by calling [insideCustomScrollView]. - TestDocumentConfigurator withCustomScrollViewScrollController(ScrollController? scrollController) { - _customScrollViewController = scrollController; - return this; - } - /// Pumps a [SuperReader] widget tree with the desired configuration, and returns /// a [TestDocumentContext], which includes the artifacts connected to the widget /// tree, e.g., the [DocumentEditor], [DocumentComposer], etc. @@ -308,17 +301,18 @@ class TestDocumentConfigurator { /// Places [child] inside a [CustomScrollView], based on configurations in this class. Widget _buildAncestorScrollable({required Widget child}) { - if (_insideCustomScrollView) { - return CustomScrollView( - controller: _customScrollViewController, - slivers: [ - SliverToBoxAdapter( - child: child, - ), - ], - ); + if (!_insideCustomScrollView) { + return child; } - return child; + + return CustomScrollView( + controller: _scrollController, + slivers: [ + SliverToBoxAdapter( + child: child, + ), + ], + ); } Widget _buildWidgetTree(Widget superReader) { diff --git a/super_editor/test/super_reader/super_reader_scrolling_test.dart b/super_editor/test/super_reader/super_reader_scrolling_test.dart index 5ca1ed8e3b..fc142e3be1 100644 --- a/super_editor/test/super_reader/super_reader_scrolling_test.dart +++ b/super_editor/test/super_reader/super_reader_scrolling_test.dart @@ -447,7 +447,7 @@ void main() { .withLongTextContent() .withEditorSize(const Size(200, 200)) .insideCustomScrollView() - .withCustomScrollViewScrollController(scrollController) + .withScrollController(scrollController) .pump(); // Ensure the scrollview didn't start scrolled. @@ -488,7 +488,7 @@ void main() { .withLongTextContent() .withEditorSize(const Size(200, 200)) .insideCustomScrollView() - .withCustomScrollViewScrollController(scrollController) + .withScrollController(scrollController) .pump(); // Ensure the scrollview didn't start scrolled. @@ -527,7 +527,7 @@ void main() { .createDocument() .withSingleParagraph() .insideCustomScrollView() - .withCustomScrollViewScrollController(scrollController) + .withScrollController(scrollController) .pump(); // Ensure the scrollview didn't start scrolled. @@ -559,7 +559,7 @@ void main() { .withSingleParagraph() .withEditorSize(const Size(200, 200)) .insideCustomScrollView() - .withCustomScrollViewScrollController(scrollController) + .withScrollController(scrollController) .pump(); // Jump to the bottom. @@ -588,7 +588,7 @@ void main() { .createDocument() // .withLongTextContent() .insideCustomScrollView() - .withCustomScrollViewScrollController(scrollController) + .withScrollController(scrollController) .pump(); // Ensure the scrollview didn't start scrolled. @@ -624,7 +624,7 @@ void main() { .withLongTextContent() .withEditorSize(const Size(200, 200)) .insideCustomScrollView() - .withCustomScrollViewScrollController(scrollController) + .withScrollController(scrollController) .pump(); // Jump to the bottom.