test: add test for image_node_widget and image_node_builder

This commit is contained in:
Lucas.Xu 2022-08-24 12:02:50 +08:00
parent 012c3a851a
commit 3832af5fa8
4 changed files with 245 additions and 150 deletions

View File

@ -41,24 +41,20 @@ class ImageNodeBuilder extends NodeWidgetBuilder<Node> {
}); });
Alignment _textToAlignment(String text) { Alignment _textToAlignment(String text) {
if (text == 'center') { if (text == 'left') {
return Alignment.center;
} else if (text == 'left') {
return Alignment.centerLeft; return Alignment.centerLeft;
} else if (text == 'right') { } else if (text == 'right') {
return Alignment.centerRight; return Alignment.centerRight;
} }
throw UnimplementedError(); return Alignment.center;
} }
String _alignmentToText(Alignment alignment) { String _alignmentToText(Alignment alignment) {
if (alignment == Alignment.center) { if (alignment == Alignment.centerLeft) {
return 'center';
} else if (alignment == Alignment.centerLeft) {
return 'left'; return 'left';
} else if (alignment == Alignment.centerRight) { } else if (alignment == Alignment.centerRight) {
return 'right'; return 'right';
} }
throw UnimplementedError(); return 'center';
} }
} }

View File

@ -54,25 +54,7 @@ class _ImageNodeWidgetState extends State<ImageNodeWidget> {
widget.src, widget.src,
width: imageWidth == null ? null : imageWidth! - _distance, width: imageWidth == null ? null : imageWidth! - _distance,
loadingBuilder: (context, child, loadingProgress) => loadingBuilder: (context, child, loadingProgress) =>
loadingProgress == null loadingProgress == null ? child : _buildLoading(context),
? child
: SizedBox(
width: imageWidth,
height: 300,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox.fromSize(
size: const Size(18, 18),
child: const CircularProgressIndicator(),
),
SizedBox.fromSize(
size: const Size(10, 10),
),
const Text('Loading'),
],
),
),
); );
if (imageWidth == null) { if (imageWidth == null) {
networkImage.image.resolve(const ImageConfiguration()).addListener( networkImage.image.resolve(const ImageConfiguration()).addListener(
@ -111,16 +93,39 @@ class _ImageNodeWidgetState extends State<ImageNodeWidget> {
}, },
), ),
if (_onFocus) if (_onFocus)
_buildImageToolbar( ImageToolbar(
context,
top: 8, top: 8,
right: 8, right: 8,
height: 30, height: 30,
), alignment: widget.alignment,
onAlign: widget.onAlign,
onCopy: widget.onCopy,
onDelete: widget.onDelete,
)
], ],
); );
} }
Widget _buildLoading(BuildContext context) {
return SizedBox(
width: imageWidth,
height: 300,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox.fromSize(
size: const Size(18, 18),
child: const CircularProgressIndicator(),
),
SizedBox.fromSize(
size: const Size(10, 10),
),
const Text('Loading'),
],
),
);
}
Widget _buildEdgeGesture( Widget _buildEdgeGesture(
BuildContext context, { BuildContext context, {
double? top, double? top,
@ -169,20 +174,34 @@ class _ImageNodeWidgetState extends State<ImageNodeWidget> {
), ),
); );
} }
}
Widget _buildImageToolbar( @visibleForTesting
BuildContext context, { class ImageToolbar extends StatelessWidget {
double? top, const ImageToolbar({
double? left, Key? key,
double? right, required this.top,
double? width, required this.right,
double? height, required this.height,
}) { required this.alignment,
required this.onCopy,
required this.onDelete,
required this.onAlign,
}) : super(key: key);
final double top;
final double right;
final double height;
final Alignment alignment;
final VoidCallback onCopy;
final VoidCallback onDelete;
final void Function(Alignment alignment) onAlign;
@override
Widget build(BuildContext context) {
return Positioned( return Positioned(
top: top, top: top,
left: left,
right: right, right: right,
width: width,
height: height, height: height,
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
@ -205,12 +224,12 @@ class _ImageNodeWidgetState extends State<ImageNodeWidget> {
padding: const EdgeInsets.fromLTRB(6.0, 4.0, 0.0, 4.0), padding: const EdgeInsets.fromLTRB(6.0, 4.0, 0.0, 4.0),
icon: FlowySvg( icon: FlowySvg(
name: 'image_toolbar/align_left', name: 'image_toolbar/align_left',
color: widget.alignment == Alignment.centerLeft color: alignment == Alignment.centerLeft
? const Color(0xFF00BCF0) ? const Color(0xFF00BCF0)
: null, : null,
), ),
onPressed: () { onPressed: () {
widget.onAlign(Alignment.centerLeft); onAlign(Alignment.centerLeft);
}, },
), ),
IconButton( IconButton(
@ -219,12 +238,12 @@ class _ImageNodeWidgetState extends State<ImageNodeWidget> {
padding: const EdgeInsets.fromLTRB(0.0, 4.0, 0.0, 4.0), padding: const EdgeInsets.fromLTRB(0.0, 4.0, 0.0, 4.0),
icon: FlowySvg( icon: FlowySvg(
name: 'image_toolbar/align_center', name: 'image_toolbar/align_center',
color: widget.alignment == Alignment.center color: alignment == Alignment.center
? const Color(0xFF00BCF0) ? const Color(0xFF00BCF0)
: null, : null,
), ),
onPressed: () { onPressed: () {
widget.onAlign(Alignment.center); onAlign(Alignment.center);
}, },
), ),
IconButton( IconButton(
@ -233,12 +252,12 @@ class _ImageNodeWidgetState extends State<ImageNodeWidget> {
padding: const EdgeInsets.fromLTRB(0.0, 4.0, 4.0, 4.0), padding: const EdgeInsets.fromLTRB(0.0, 4.0, 4.0, 4.0),
icon: FlowySvg( icon: FlowySvg(
name: 'image_toolbar/align_right', name: 'image_toolbar/align_right',
color: widget.alignment == Alignment.centerRight color: alignment == Alignment.centerRight
? const Color(0xFF00BCF0) ? const Color(0xFF00BCF0)
: null, : null,
), ),
onPressed: () { onPressed: () {
widget.onAlign(Alignment.centerRight); onAlign(Alignment.centerRight);
}, },
), ),
const Center( const Center(
@ -254,7 +273,7 @@ class _ImageNodeWidgetState extends State<ImageNodeWidget> {
name: 'image_toolbar/copy', name: 'image_toolbar/copy',
), ),
onPressed: () { onPressed: () {
widget.onCopy(); onCopy();
}, },
), ),
IconButton( IconButton(
@ -265,7 +284,7 @@ class _ImageNodeWidgetState extends State<ImageNodeWidget> {
name: 'image_toolbar/delete', name: 'image_toolbar/delete',
), ),
onPressed: () { onPressed: () {
widget.onDelete(); onDelete();
}, },
), ),
], ],

View File

@ -0,0 +1,131 @@
import 'package:appflowy_editor/src/render/image/image_node_widget.dart';
import 'package:appflowy_editor/src/service/editor_service.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:network_image_mock/network_image_mock.dart';
import '../../infra/test_editor.dart';
void main() async {
setUpAll(() {
TestWidgetsFlutterBinding.ensureInitialized();
});
group('image_node_builder.dart', () {
testWidgets('render image node', (tester) async {
mockNetworkImagesFor(() async {
const text = 'Welcome to Appflowy 😁';
const src =
'https://images.unsplash.com/photo-1471897488648-5eae4ac6686b?ixlib=rb-1.2.1&dl=sarah-dorweiler-QeVmJxZOv3k-unsplash.jpg&w=640&q=80&fm=jpg&crop=entropy&cs=tinysrgb';
final editor = tester.editor
..insertTextNode(text)
..insertImageNode(src)
..insertTextNode(text);
await editor.startTesting();
expect(editor.documentLength, 3);
expect(find.byType(Image), findsOneWidget);
});
});
testWidgets('render image align', (tester) async {
mockNetworkImagesFor(() async {
const text = 'Welcome to Appflowy 😁';
const src =
'https://images.unsplash.com/photo-1471897488648-5eae4ac6686b?ixlib=rb-1.2.1&dl=sarah-dorweiler-QeVmJxZOv3k-unsplash.jpg&w=640&q=80&fm=jpg&crop=entropy&cs=tinysrgb';
final editor = tester.editor
..insertTextNode(text)
..insertImageNode(src, align: 'left')
..insertImageNode(src, align: 'center')
..insertImageNode(src, align: 'right')
..insertTextNode(text);
await editor.startTesting();
expect(editor.documentLength, 5);
final imageFinder = find.byType(Image);
expect(imageFinder, findsNWidgets(3));
final editorFinder = find.byType(AppFlowyEditor);
final editorRect = tester.getRect(editorFinder);
final leftImageRect = tester.getRect(imageFinder.at(0));
expect(leftImageRect.left, editorRect.left);
final rightImageRect = tester.getRect(imageFinder.at(2));
expect(rightImageRect.right, editorRect.right);
final centerImageRect = tester.getRect(imageFinder.at(1));
expect(centerImageRect.left,
(leftImageRect.left + rightImageRect.left) / 2.0);
expect(leftImageRect.size, centerImageRect.size);
expect(rightImageRect.size, centerImageRect.size);
final imageNodeWidgetFinder = find.byType(ImageNodeWidget);
final leftImage =
tester.firstWidget(imageNodeWidgetFinder) as ImageNodeWidget;
leftImage.onAlign(Alignment.center);
await tester.pump(const Duration(milliseconds: 100));
expect(
tester.getRect(imageFinder.at(0)).left,
centerImageRect.left,
);
leftImage.onAlign(Alignment.centerRight);
await tester.pump(const Duration(milliseconds: 100));
expect(
tester.getRect(imageFinder.at(0)).left,
rightImageRect.left,
);
});
});
testWidgets('render image copy', (tester) async {
mockNetworkImagesFor(() async {
const text = 'Welcome to Appflowy 😁';
const src =
'https://images.unsplash.com/photo-1471897488648-5eae4ac6686b?ixlib=rb-1.2.1&dl=sarah-dorweiler-QeVmJxZOv3k-unsplash.jpg&w=640&q=80&fm=jpg&crop=entropy&cs=tinysrgb';
final editor = tester.editor
..insertTextNode(text)
..insertImageNode(src)
..insertTextNode(text);
await editor.startTesting();
expect(editor.documentLength, 3);
final imageFinder = find.byType(Image);
expect(imageFinder, findsOneWidget);
final imageNodeWidgetFinder = find.byType(ImageNodeWidget);
final image =
tester.firstWidget(imageNodeWidgetFinder) as ImageNodeWidget;
image.onCopy();
});
});
testWidgets('render image delete', (tester) async {
mockNetworkImagesFor(() async {
const text = 'Welcome to Appflowy 😁';
const src =
'https://images.unsplash.com/photo-1471897488648-5eae4ac6686b?ixlib=rb-1.2.1&dl=sarah-dorweiler-QeVmJxZOv3k-unsplash.jpg&w=640&q=80&fm=jpg&crop=entropy&cs=tinysrgb';
final editor = tester.editor
..insertTextNode(text)
..insertImageNode(src)
..insertImageNode(src)
..insertTextNode(text);
await editor.startTesting();
expect(editor.documentLength, 4);
final imageFinder = find.byType(Image);
expect(imageFinder, findsNWidgets(2));
final imageNodeWidgetFinder = find.byType(ImageNodeWidget);
final image =
tester.firstWidget(imageNodeWidgetFinder) as ImageNodeWidget;
image.onDelete();
await tester.pump(const Duration(milliseconds: 100));
expect(editor.documentLength, 3);
expect(find.byType(Image), findsNWidgets(1));
});
});
});
}

View File

@ -1,130 +1,79 @@
import 'package:appflowy_editor/src/render/image/image_node_widget.dart'; import 'package:appflowy_editor/src/render/image/image_node_widget.dart';
import 'package:appflowy_editor/src/service/editor_service.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.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 'package:network_image_mock/network_image_mock.dart';
import '../../infra/test_editor.dart';
void main() async { void main() async {
setUpAll(() { setUpAll(() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
}); });
group('image_node_widget.dart', () { group('image_node_widget.dart', () {
testWidgets('render image node', (tester) async { testWidgets('build the image node widget', (tester) async {
mockNetworkImagesFor(() async { mockNetworkImagesFor(() async {
const text = 'Welcome to Appflowy 😁'; var onCopyHit = false;
var onDeleteHit = false;
var onAlignHit = false;
const src = const src =
'https://images.unsplash.com/photo-1471897488648-5eae4ac6686b?ixlib=rb-1.2.1&dl=sarah-dorweiler-QeVmJxZOv3k-unsplash.jpg&w=640&q=80&fm=jpg&crop=entropy&cs=tinysrgb'; 'https://images.unsplash.com/photo-1471897488648-5eae4ac6686b?ixlib=rb-1.2.1&dl=sarah-dorweiler-QeVmJxZOv3k-unsplash.jpg&w=640&q=80&fm=jpg&crop=entropy&cs=tinysrgb';
final editor = tester.editor
..insertTextNode(text)
..insertImageNode(src)
..insertTextNode(text);
await editor.startTesting();
expect(editor.documentLength, 3); final widget = ImageNodeWidget(
expect(find.byType(Image), findsOneWidget); src: src,
}); alignment: Alignment.center,
}); onCopy: () {
onCopyHit = true;
testWidgets('render image align', (tester) async { },
mockNetworkImagesFor(() async { onDelete: () {
const text = 'Welcome to Appflowy 😁'; onDeleteHit = true;
const src = },
'https://images.unsplash.com/photo-1471897488648-5eae4ac6686b?ixlib=rb-1.2.1&dl=sarah-dorweiler-QeVmJxZOv3k-unsplash.jpg&w=640&q=80&fm=jpg&crop=entropy&cs=tinysrgb'; onAlign: (alignment) {
final editor = tester.editor onAlignHit = true;
..insertTextNode(text) },
..insertImageNode(src, align: 'left')
..insertImageNode(src, align: 'center')
..insertImageNode(src, align: 'right')
..insertTextNode(text);
await editor.startTesting();
expect(editor.documentLength, 5);
final imageFinder = find.byType(Image);
expect(imageFinder, findsNWidgets(3));
final editorFinder = find.byType(AppFlowyEditor);
final editorRect = tester.getRect(editorFinder);
final leftImageRect = tester.getRect(imageFinder.at(0));
expect(leftImageRect.left, editorRect.left);
final rightImageRect = tester.getRect(imageFinder.at(2));
expect(rightImageRect.right, editorRect.right);
final centerImageRect = tester.getRect(imageFinder.at(1));
expect(centerImageRect.left,
(leftImageRect.left + rightImageRect.left) / 2.0);
expect(leftImageRect.size, centerImageRect.size);
expect(rightImageRect.size, centerImageRect.size);
final imageNodeWidgetFinder = find.byType(ImageNodeWidget);
final leftImage =
tester.firstWidget(imageNodeWidgetFinder) as ImageNodeWidget;
leftImage.onAlign(Alignment.center);
await tester.pump(const Duration(milliseconds: 100));
expect(
tester.getRect(imageFinder.at(0)).left,
centerImageRect.left,
); );
leftImage.onAlign(Alignment.centerRight); await tester.pumpWidget(
await tester.pump(const Duration(milliseconds: 100)); MaterialApp(
expect( home: Material(
tester.getRect(imageFinder.at(0)).left, child: widget,
rightImageRect.left, ),
),
); );
}); expect(find.byType(ImageNodeWidget), findsOneWidget);
});
testWidgets('render image copy', (tester) async { final gesture =
mockNetworkImagesFor(() async { await tester.createGesture(kind: PointerDeviceKind.mouse);
const text = 'Welcome to Appflowy 😁'; await gesture.addPointer(location: Offset.zero);
const src =
'https://images.unsplash.com/photo-1471897488648-5eae4ac6686b?ixlib=rb-1.2.1&dl=sarah-dorweiler-QeVmJxZOv3k-unsplash.jpg&w=640&q=80&fm=jpg&crop=entropy&cs=tinysrgb';
final editor = tester.editor
..insertTextNode(text)
..insertImageNode(src)
..insertTextNode(text);
await editor.startTesting();
expect(editor.documentLength, 3); expect(find.byType(ImageToolbar), findsNothing);
final imageFinder = find.byType(Image);
expect(imageFinder, findsOneWidget);
final imageNodeWidgetFinder = find.byType(ImageNodeWidget); addTearDown(gesture.removePointer);
final image = await tester.pump();
tester.firstWidget(imageNodeWidgetFinder) as ImageNodeWidget; await gesture.moveTo(tester.getCenter(find.byType(ImageNodeWidget)));
image.onCopy(); await tester.pump();
});
});
testWidgets('render image delete', (tester) async { expect(find.byType(ImageToolbar), findsOneWidget);
mockNetworkImagesFor(() async {
const text = 'Welcome to Appflowy 😁';
const src =
'https://images.unsplash.com/photo-1471897488648-5eae4ac6686b?ixlib=rb-1.2.1&dl=sarah-dorweiler-QeVmJxZOv3k-unsplash.jpg&w=640&q=80&fm=jpg&crop=entropy&cs=tinysrgb';
final editor = tester.editor
..insertTextNode(text)
..insertImageNode(src)
..insertImageNode(src)
..insertTextNode(text);
await editor.startTesting();
expect(editor.documentLength, 4); final iconFinder = find.byType(IconButton);
final imageFinder = find.byType(Image); expect(iconFinder, findsNWidgets(5));
expect(imageFinder, findsNWidgets(2));
final imageNodeWidgetFinder = find.byType(ImageNodeWidget); await tester.tap(iconFinder.at(0));
final image = expect(onAlignHit, true);
tester.firstWidget(imageNodeWidgetFinder) as ImageNodeWidget; onAlignHit = false;
image.onDelete();
await tester.pump(const Duration(milliseconds: 100)); await tester.tap(iconFinder.at(1));
expect(editor.documentLength, 3); expect(onAlignHit, true);
expect(find.byType(Image), findsNWidgets(1)); onAlignHit = false;
await tester.tap(iconFinder.at(2));
expect(onAlignHit, true);
onAlignHit = false;
await tester.tap(iconFinder.at(3));
expect(onCopyHit, true);
await tester.tap(iconFinder.at(4));
expect(onDeleteHit, true);
}); });
}); });
}); });