mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
test: add more test cases for image
This commit is contained in:
parent
2b2bae80ef
commit
b52f618b1a
@ -45,6 +45,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
extendBodyBehindAppBar: true,
|
||||||
body: Container(
|
body: Container(
|
||||||
alignment: Alignment.topCenter,
|
alignment: Alignment.topCenter,
|
||||||
child: _buildEditor(context),
|
child: _buildEditor(context),
|
||||||
@ -92,8 +93,8 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||||||
..handler = (message) {
|
..handler = (message) {
|
||||||
debugPrint(message);
|
debugPrint(message);
|
||||||
};
|
};
|
||||||
return Container(
|
return SizedBox(
|
||||||
padding: const EdgeInsets.all(20),
|
width: MediaQuery.of(context).size.width,
|
||||||
child: AppFlowyEditor(
|
child: AppFlowyEditor(
|
||||||
editorState: _editorState,
|
editorState: _editorState,
|
||||||
),
|
),
|
||||||
|
@ -17,6 +17,7 @@ class ImageNodeBuilder extends NodeWidgetBuilder<Node> {
|
|||||||
}
|
}
|
||||||
return ImageNodeWidget(
|
return ImageNodeWidget(
|
||||||
key: context.node.key,
|
key: context.node.key,
|
||||||
|
node: context.node,
|
||||||
src: src,
|
src: src,
|
||||||
width: width,
|
width: width,
|
||||||
alignment: _textToAlignment(align),
|
alignment: _textToAlignment(align),
|
||||||
|
@ -1,10 +1,17 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:appflowy_editor/src/document/node.dart';
|
||||||
|
import 'package:appflowy_editor/src/document/position.dart';
|
||||||
|
import 'package:appflowy_editor/src/document/selection.dart';
|
||||||
import 'package:appflowy_editor/src/infra/flowy_svg.dart';
|
import 'package:appflowy_editor/src/infra/flowy_svg.dart';
|
||||||
import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart';
|
import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart';
|
||||||
|
import 'package:appflowy_editor/src/render/selection/selectable.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class ImageNodeWidget extends StatefulWidget {
|
class ImageNodeWidget extends StatefulWidget {
|
||||||
const ImageNodeWidget({
|
const ImageNodeWidget({
|
||||||
Key? key,
|
Key? key,
|
||||||
|
required this.node,
|
||||||
required this.src,
|
required this.src,
|
||||||
this.width,
|
this.width,
|
||||||
required this.alignment,
|
required this.alignment,
|
||||||
@ -14,6 +21,7 @@ class ImageNodeWidget extends StatefulWidget {
|
|||||||
required this.onResize,
|
required this.onResize,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final Node node;
|
||||||
final String src;
|
final String src;
|
||||||
final double? width;
|
final double? width;
|
||||||
final Alignment alignment;
|
final Alignment alignment;
|
||||||
@ -26,7 +34,7 @@ class ImageNodeWidget extends StatefulWidget {
|
|||||||
State<ImageNodeWidget> createState() => _ImageNodeWidgetState();
|
State<ImageNodeWidget> createState() => _ImageNodeWidgetState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ImageNodeWidgetState extends State<ImageNodeWidget> {
|
class _ImageNodeWidgetState extends State<ImageNodeWidget> with Selectable {
|
||||||
double? _imageWidth;
|
double? _imageWidth;
|
||||||
double _initial = 0;
|
double _initial = 0;
|
||||||
double _distance = 0;
|
double _distance = 0;
|
||||||
@ -42,7 +50,8 @@ class _ImageNodeWidgetState extends State<ImageNodeWidget> {
|
|||||||
_imageWidth = widget.width;
|
_imageWidth = widget.width;
|
||||||
_imageStreamListener = ImageStreamListener(
|
_imageStreamListener = ImageStreamListener(
|
||||||
(image, _) {
|
(image, _) {
|
||||||
_imageWidth = image.image.width.toDouble();
|
_imageWidth =
|
||||||
|
min(defaultMaxTextNodeWidth, image.image.width.toDouble());
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -64,6 +73,43 @@ class _ImageNodeWidgetState extends State<ImageNodeWidget> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Position start() {
|
||||||
|
return Position(path: widget.node.path, offset: 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Position end() {
|
||||||
|
return Position(path: widget.node.path, offset: 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Position getPositionInOffset(Offset start) {
|
||||||
|
return end();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Rect? getCursorRectInPosition(Position position) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Rect> getRectsInSelection(Selection selection) {
|
||||||
|
final renderBox = context.findRenderObject() as RenderBox;
|
||||||
|
return [Offset.zero & renderBox.size];
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Selection getSelectionInRange(Offset start, Offset end) {
|
||||||
|
return Selection(start: this.start(), end: this.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Offset localToGlobal(Offset offset) {
|
||||||
|
final renderBox = context.findRenderObject() as RenderBox;
|
||||||
|
return renderBox.localToGlobal(offset);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildNetworkImage(BuildContext context) {
|
Widget _buildNetworkImage(BuildContext context) {
|
||||||
return Align(
|
return Align(
|
||||||
alignment: widget.alignment,
|
alignment: widget.alignment,
|
||||||
|
@ -11,10 +11,16 @@ KeyEventResult _handleBackspace(EditorState editorState, RawKeyEvent event) {
|
|||||||
var nodes = editorState.service.selectionService.currentSelectedNodes;
|
var nodes = editorState.service.selectionService.currentSelectedNodes;
|
||||||
nodes = selection.isBackward ? nodes : nodes.reversed.toList(growable: false);
|
nodes = selection.isBackward ? nodes : nodes.reversed.toList(growable: false);
|
||||||
selection = selection.isBackward ? selection : selection.reversed;
|
selection = selection.isBackward ? selection : selection.reversed;
|
||||||
// make sure all nodes is [TextNode].
|
|
||||||
final textNodes = nodes.whereType<TextNode>().toList();
|
final textNodes = nodes.whereType<TextNode>().toList();
|
||||||
|
final nonTextNodes =
|
||||||
|
nodes.where((node) => node is! TextNode).toList(growable: false);
|
||||||
|
|
||||||
final transactionBuilder = TransactionBuilder(editorState);
|
final transactionBuilder = TransactionBuilder(editorState);
|
||||||
|
|
||||||
|
if (nonTextNodes.isNotEmpty) {
|
||||||
|
transactionBuilder.deleteNodes(nonTextNodes);
|
||||||
|
}
|
||||||
|
|
||||||
if (textNodes.length == 1) {
|
if (textNodes.length == 1) {
|
||||||
final textNode = textNodes.first;
|
final textNode = textNodes.first;
|
||||||
final index = textNode.delta.prevRunePosition(selection.start.offset);
|
final index = textNode.delta.prevRunePosition(selection.start.offset);
|
||||||
@ -68,7 +74,9 @@ KeyEventResult _handleBackspace(EditorState editorState, RawKeyEvent event) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_deleteNodes(transactionBuilder, textNodes, selection);
|
if (textNodes.isNotEmpty) {
|
||||||
|
_deleteTextNodes(transactionBuilder, textNodes, selection);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (transactionBuilder.operations.isNotEmpty) {
|
if (transactionBuilder.operations.isNotEmpty) {
|
||||||
@ -121,7 +129,7 @@ KeyEventResult _handleDelete(EditorState editorState, RawKeyEvent event) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_deleteNodes(transactionBuilder, textNodes, selection);
|
_deleteTextNodes(transactionBuilder, textNodes, selection);
|
||||||
}
|
}
|
||||||
|
|
||||||
transactionBuilder.commit();
|
transactionBuilder.commit();
|
||||||
@ -129,7 +137,7 @@ KeyEventResult _handleDelete(EditorState editorState, RawKeyEvent event) {
|
|||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _deleteNodes(TransactionBuilder transactionBuilder,
|
void _deleteTextNodes(TransactionBuilder transactionBuilder,
|
||||||
List<TextNode> textNodes, Selection selection) {
|
List<TextNode> textNodes, Selection selection) {
|
||||||
final first = textNodes.first;
|
final first = textNodes.first;
|
||||||
final last = textNodes.last;
|
final last = textNodes.last;
|
@ -1,6 +1,6 @@
|
|||||||
import 'package:appflowy_editor/src/service/internal_key_event_handlers/arrow_keys_handler.dart';
|
import 'package:appflowy_editor/src/service/internal_key_event_handlers/arrow_keys_handler.dart';
|
||||||
import 'package:appflowy_editor/src/service/internal_key_event_handlers/copy_paste_handler.dart';
|
import 'package:appflowy_editor/src/service/internal_key_event_handlers/copy_paste_handler.dart';
|
||||||
import 'package:appflowy_editor/src/service/internal_key_event_handlers/delete_text_handler.dart';
|
import 'package:appflowy_editor/src/service/internal_key_event_handlers/backspace_handler.dart';
|
||||||
import 'package:appflowy_editor/src/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart';
|
import 'package:appflowy_editor/src/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart';
|
||||||
import 'package:appflowy_editor/src/service/internal_key_event_handlers/redo_undo_handler.dart';
|
import 'package:appflowy_editor/src/service/internal_key_event_handlers/redo_undo_handler.dart';
|
||||||
import 'package:appflowy_editor/src/service/internal_key_event_handlers/slash_handler.dart';
|
import 'package:appflowy_editor/src/service/internal_key_event_handlers/slash_handler.dart';
|
||||||
|
@ -90,7 +90,7 @@ class _FlowyToolbarState extends State<FlowyToolbar>
|
|||||||
.where((item) => item.validator(widget.editorState))
|
.where((item) => item.validator(widget.editorState))
|
||||||
.toList(growable: false)
|
.toList(growable: false)
|
||||||
..sort((a, b) => a.type.compareTo(b.type));
|
..sort((a, b) => a.type.compareTo(b.type));
|
||||||
if (items.isEmpty) {
|
if (filterItems.isEmpty) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
final List<ToolbarItem> dividedItems = [filterItems.first];
|
final List<ToolbarItem> dividedItems = [filterItems.first];
|
||||||
|
@ -80,7 +80,7 @@ class EditorWidgetTester {
|
|||||||
} else {
|
} else {
|
||||||
_editorState.service.selectionService.updateSelection(selection);
|
_editorState.service.selectionService.updateSelection(selection);
|
||||||
}
|
}
|
||||||
await tester.pumpAndSettle();
|
await tester.pump(const Duration(milliseconds: 200));
|
||||||
|
|
||||||
expect(_editorState.service.selectionService.currentSelection.value,
|
expect(_editorState.service.selectionService.currentSelection.value,
|
||||||
selection);
|
selection);
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
import 'dart:collection';
|
||||||
|
|
||||||
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:appflowy_editor/src/render/image/image_node_widget.dart';
|
import 'package:appflowy_editor/src/render/image/image_node_widget.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -20,6 +23,14 @@ void main() async {
|
|||||||
|
|
||||||
final widget = ImageNodeWidget(
|
final widget = ImageNodeWidget(
|
||||||
src: src,
|
src: src,
|
||||||
|
node: Node(
|
||||||
|
type: 'image',
|
||||||
|
children: LinkedList(),
|
||||||
|
attributes: {
|
||||||
|
'image_src': src,
|
||||||
|
'align': 'center',
|
||||||
|
},
|
||||||
|
),
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
onCopy: () {
|
onCopy: () {
|
||||||
onCopyHit = true;
|
onCopyHit = true;
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
import 'package:appflowy_editor/src/render/image/image_node_widget.dart';
|
||||||
import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart';
|
import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:network_image_mock/network_image_mock.dart';
|
||||||
import '../../infra/test_editor.dart';
|
import '../../infra/test_editor.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
@ -9,7 +11,7 @@ void main() async {
|
|||||||
TestWidgetsFlutterBinding.ensureInitialized();
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
});
|
});
|
||||||
|
|
||||||
group('delete_text_handler.dart', () {
|
group('backspace_handler.dart', () {
|
||||||
testWidgets('Presses backspace key in empty document', (tester) async {
|
testWidgets('Presses backspace key in empty document', (tester) async {
|
||||||
// Before
|
// Before
|
||||||
//
|
//
|
||||||
@ -167,6 +169,77 @@ void main() async {
|
|||||||
testWidgets('Presses delete key in styled text (quote)', (tester) async {
|
testWidgets('Presses delete key in styled text (quote)', (tester) async {
|
||||||
await _deleteStyledTextByDelete(tester, StyleKey.quote);
|
await _deleteStyledTextByDelete(tester, StyleKey.quote);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Before
|
||||||
|
//
|
||||||
|
// Welcome to Appflowy 😁
|
||||||
|
// Welcome to Appflowy 😁
|
||||||
|
// [Image]
|
||||||
|
// Welcome to Appflowy 😁
|
||||||
|
// Welcome to Appflowy 😁
|
||||||
|
//
|
||||||
|
// After
|
||||||
|
//
|
||||||
|
// Welcome to Appflowy 😁
|
||||||
|
// Welcome to Appflowy 😁
|
||||||
|
//
|
||||||
|
testWidgets('Deletes the image surrounded by text', (tester) async {
|
||||||
|
mockNetworkImagesFor(() async {
|
||||||
|
const text = 'Welcome to Appflowy 😁';
|
||||||
|
const src = 'https://s1.ax1x.com/2022/08/26/v2sSbR.jpg';
|
||||||
|
final editor = tester.editor
|
||||||
|
..insertTextNode(text)
|
||||||
|
..insertTextNode(text)
|
||||||
|
..insertImageNode(src)
|
||||||
|
..insertTextNode(text)
|
||||||
|
..insertTextNode(text);
|
||||||
|
await editor.startTesting();
|
||||||
|
|
||||||
|
expect(editor.documentLength, 5);
|
||||||
|
expect(find.byType(ImageNodeWidget), findsOneWidget);
|
||||||
|
|
||||||
|
await editor.updateSelection(
|
||||||
|
Selection(
|
||||||
|
start: Position(path: [1], offset: 0),
|
||||||
|
end: Position(path: [3], offset: text.length),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await editor.pressLogicKey(LogicalKeyboardKey.backspace);
|
||||||
|
expect(editor.documentLength, 3);
|
||||||
|
expect(find.byType(ImageNodeWidget), findsNothing);
|
||||||
|
expect(
|
||||||
|
editor.documentSelection,
|
||||||
|
Selection.single(path: [1], startOffset: 0),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Deletes the first image', (tester) async {
|
||||||
|
mockNetworkImagesFor(() async {
|
||||||
|
const text = 'Welcome to Appflowy 😁';
|
||||||
|
const src = 'https://s1.ax1x.com/2022/08/26/v2sSbR.jpg';
|
||||||
|
final editor = tester.editor
|
||||||
|
..insertImageNode(src)
|
||||||
|
..insertTextNode(text)
|
||||||
|
..insertTextNode(text);
|
||||||
|
await editor.startTesting();
|
||||||
|
|
||||||
|
expect(editor.documentLength, 3);
|
||||||
|
expect(find.byType(ImageNodeWidget), findsOneWidget);
|
||||||
|
|
||||||
|
await editor.updateSelection(
|
||||||
|
Selection(
|
||||||
|
start: Position(path: [0], offset: 0),
|
||||||
|
end: Position(path: [0], offset: 1),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await editor.pressLogicKey(LogicalKeyboardKey.backspace);
|
||||||
|
expect(editor.documentLength, 2);
|
||||||
|
expect(find.byType(ImageNodeWidget), findsNothing);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _deleteStyledTextByBackspace(
|
Future<void> _deleteStyledTextByBackspace(
|
Loading…
Reference in New Issue
Block a user