Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SuperEditor][SuperReader] Adds tests editor's scrolling when present within parent scrollable #1733

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import 'package:super_editor/super_editor.dart';
MutableDocument createInitialDocument() {
return MutableDocument(
nodes: [
ImageNode(
id: "1",
imageUrl: 'https://i.ibb.co/5nvRdx1/flutter-horizon.png',
metadata: const SingleColumnLayoutComponentStyles(
width: double.infinity,
padding: EdgeInsets.zero,
).toMetadata(),
),
// ImageNode(
// id: "1",
// imageUrl: 'https://i.ibb.co/5nvRdx1/flutter-horizon.png',
// metadata: const SingleColumnLayoutComponentStyles(
// width: double.infinity,
// padding: EdgeInsets.zero,
// ).toMetadata(),
// ),
ParagraphNode(
id: Editor.createNodeId(),
text: AttributedText('Welcome to Super Editor 💙 🚀'),
Expand Down
71 changes: 53 additions & 18 deletions super_editor/example/lib/demos/example_editor/example_editor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,43 @@ class _ExampleEditorState extends State<ExampleEditor> {

@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Builder(builder: (context) {
return ListView(
children: [
SizedBox(
height: MediaQuery.of(context).size.height * 0.5,
width: double.infinity,
child: const Placeholder(
child: Center(
child: Text("Content"),
),
),
),
SizedBox(
height: 350,
width: double.infinity,
child: ListView(
children: [
_buildEditor(context),
],
),
),
SizedBox(
height: MediaQuery.of(context).size.height,
width: double.infinity,
child: const Placeholder(
child: Center(
child: Text("Content"),
),
),
),
],
);
}),
),
);
return ValueListenableBuilder(
valueListenable: _brightness,
builder: (context, brightness, child) {
Expand Down Expand Up @@ -374,26 +411,24 @@ class _ExampleEditorState extends State<ExampleEditor> {
key: _viewportKey,
child: SuperEditorIosControlsScope(
controller: _iosControlsController,
child: SuperEditor(
editor: _docEditor,
child: SuperReader(
document: _doc,
composer: _composer,
focusNode: _editorFocusNode,
scrollController: _scrollController,
documentLayoutKey: _docLayoutKey,
documentOverlayBuilders: [
DefaultCaretOverlayBuilder(
caretStyle: const CaretStyle().copyWith(color: isLight ? Colors.black : Colors.redAccent),
),
if (defaultTargetPlatform == TargetPlatform.iOS) ...[
SuperEditorIosHandlesDocumentLayerBuilder(),
SuperEditorIosToolbarFocalPointDocumentLayerBuilder(),
],
if (defaultTargetPlatform == TargetPlatform.android) ...[
SuperEditorAndroidToolbarFocalPointDocumentLayerBuilder(),
SuperEditorAndroidHandlesDocumentLayerBuilder(),
],
],
// documentOverlayBuilders: [
// DefaultCaretOverlayBuilder(
// caretStyle: const CaretStyle().copyWith(color: isLight ? Colors.black : Colors.redAccent),
// ),
// if (defaultTargetPlatform == TargetPlatform.iOS) ...[
// SuperEditorIosHandlesDocumentLayerBuilder(),
// SuperEditorIosToolbarFocalPointDocumentLayerBuilder(),
// ],
// if (defaultTargetPlatform == TargetPlatform.android) ...[
// SuperEditorAndroidToolbarFocalPointDocumentLayerBuilder(),
// SuperEditorAndroidHandlesDocumentLayerBuilder(),
// ],
// ],
selectionLayerLinks: _selectionLayerLinks,
selectionStyle: isLight
? defaultSelectionStyle
Expand All @@ -411,8 +446,8 @@ class _ExampleEditorState extends State<ExampleEditor> {
...defaultComponentBuilders,
],
gestureMode: _gestureMode,
inputSource: _inputSource,
keyboardActions: _inputSource == TextInputSource.ime ? defaultImeKeyboardActions : defaultKeyboardActions,
// inputSource: _inputSource,
// keyboardActions: _inputSource == TextInputSource.ime ? defaultImeKeyboardActions : defaultKeyboardActions,
androidToolbarBuilder: (_) => _buildAndroidFloatingToolbar(),
overlayController: _overlayController,
),
Expand Down
147 changes: 147 additions & 0 deletions super_editor/test/super_editor/supereditor_scrolling_test.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_test_robots/flutter_test_robots.dart';
import 'package:flutter_test_runners/flutter_test_runners.dart';
Expand Down Expand Up @@ -1126,6 +1128,107 @@ void main() {
variant: _scrollDirectionVariant,
);
});

group("when hovering over editor", () {
testWidgets("scroll down scrolls ancestor scrollable after editor's scrollable content is consumed",
(tester) async {
await _pumpSuperEditorWithinScrollable(tester);

final pageScrollable = tester.state<ScrollableState>(find.byType(Scrollable).first);

// Find editor's direct ancestor scrollable
final superEditorScrollable = tester.state<ScrollableState>(
find.ancestor(of: find.byType(SuperEditor), matching: find.byType(Scrollable)).first,
);

final TestPointer testPointer = TestPointer(1, PointerDeviceKind.mouse);

// Hover to the editor's center.
testPointer.hover(tester.getCenter(
find.ancestor(of: find.byType(SuperEditor), matching: find.byType(Scrollable)).first,
));

// Scroll to the editor's bottom.
await tester.sendEventToBinding(
testPointer.scroll(
Offset(0, superEditorScrollable.position.maxScrollExtent),
),
);

// Ensure editor's is scrolled.
expect(
superEditorScrollable.position.pixels,
greaterThan(0),
);

// Ensure parent scrollable isn't scrolled.
expect(pageScrollable.position.pixels, 0);

// Scroll down within the page.
await tester.sendEventToBinding(
testPointer.scroll(
const Offset(0, 200),
),
);

// Ensure page is scrolled.
expect(pageScrollable.position.pixels, 200);
});

testWidgets("scroll up scrolls ancestor scrollable after editor's scrollable content is consumed",
(tester) async {
await _pumpSuperEditorWithinScrollable(tester);

final pageScrollable = tester.state<ScrollableState>(find.byType(Scrollable).first);

// Find editor's direct ancestor scrollable.
final superEditorScrollable = tester.state<ScrollableState>(
find.ancestor(of: find.byType(SuperEditor), matching: find.byType(Scrollable)).first,
);

// Scroll the editor all the way to the bottom.
superEditorScrollable.position.jumpTo(superEditorScrollable.position.maxScrollExtent);

final TestPointer testPointer = TestPointer(1, PointerDeviceKind.mouse);

// Scroll an arbitrary amount in the page. This is to test scrolling up
// in page at the end.
pageScrollable.position.jumpTo(100);

// Hover to the editor's center.
testPointer.hover(
tester.getCenter(
find.ancestor(of: find.byType(SuperEditor), matching: find.byType(Scrollable)).first,
),
);

// Scroll the editor all the way to the top.
await tester.sendEventToBinding(
testPointer.scroll(
Offset(0, -superEditorScrollable.position.maxScrollExtent),
),
);

// Ensure editor's is scrolled all the way to the top.
expect(
superEditorScrollable.position.pixels,
0,
);

// Ensure page isn't scrolled any further than initial page scroll.
expect(pageScrollable.position.pixels, 100);

// Scroll back to the page top.
await tester.sendEventToBinding(
testPointer.scroll(
const Offset(0, -100),
),
);

// Ensure page is scrolled all the way to the top.
expect(pageScrollable.position.pixels, 0);
});
});
});
});
}
Expand Down Expand Up @@ -1218,6 +1321,50 @@ class _SliverTestEditorState extends State<_SliverTestEditor> {
}
}

/// Creates a [SuperEditor] experience within an ancestor scrollable
/// with scrollable editor content.
Future<void> _pumpSuperEditorWithinScrollable(WidgetTester tester) async {
await tester.createDocument().withLongTextContent().withCustomWidgetTreeBuilder((superEditor) {
return MaterialApp(
home: Scaffold(
body: Builder(builder: (context) {
return ListView(
children: [
SizedBox(
height: MediaQuery.of(context).size.height * 0.5,
width: double.infinity,
child: const Placeholder(
child: Center(
child: Text("Content"),
),
),
),
SizedBox(
height: 350,
width: double.infinity,
child: ListView(
children: [
superEditor,
],
),
),
SizedBox(
height: MediaQuery.of(context).size.height,
width: double.infinity,
child: const Placeholder(
child: Center(
child: Text("Content"),
),
),
),
],
);
}),
),
);
}).pump();
}

/// Slowly reduces window size to imitate the appearance of a keyboard.
Future<void> _simulateKeyboardAppearance({
required WidgetTester tester,
Expand Down
Loading
Loading