Skip to content

Commit

Permalink
test: add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
LucasXu0 committed Aug 18, 2023
1 parent 4cabb43 commit 2c73869
Show file tree
Hide file tree
Showing 8 changed files with 238 additions and 39 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import 'dart:io';

import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
Expand All @@ -12,36 +14,197 @@ void main() {

group('copy and paste in document', () {
testWidgets('paste multiple lines at the first line', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();

// create a new document
await tester.createNewPageWithName();

// mock the clipboard
const lines = 3;
AppFlowyClipboard.mockSetData(
AppFlowyClipboardData(
text: List.generate(lines, (index) => 'line $index').join('\n'),
),
await tester.pasteContent(
plainText: List.generate(lines, (index) => 'line $index').join('\n'),
(editorState) {
expect(editorState.document.root.children.length, 3);
for (var i = 0; i < lines; i++) {
expect(
editorState.getNodeAtPath([i])!.delta!.toPlainText(),
'line $i',
);
}
},
);
});

// paste the text
await tester.simulateKeyEvent(
LogicalKeyboardKey.keyV,
isControlPressed: Platform.isLinux || Platform.isWindows,
isMetaPressed: Platform.isMacOS,
// ## **User Installation**
// - [Windows/Mac/Linux](https://appflowy.gitbook.io/docs/essential-documentation/install-appflowy/installation-methods/mac-windows-linux-packages)
// - [Docker](https://appflowy.gitbook.io/docs/essential-documentation/install-appflowy/installation-methods/installing-with-docker)
// - [Source](https://appflowy.gitbook.io/docs/essential-documentation/install-appflowy/installation-methods/from-source)
testWidgets('paste content from html, sample 1', (tester) async {
await tester.pasteContent(
html:
'''<meta charset='utf-8'><h2><strong>User Installation</strong></h2>
<ul>
<li><a href="https://appflowy.gitbook.io/docs/essential-documentation/install-appflowy/installation-methods/mac-windows-linux-packages">Windows/Mac/Linux</a></li>
<li><a href="https://appflowy.gitbook.io/docs/essential-documentation/install-appflowy/installation-methods/installing-with-docker">Docker</a></li>
<li><a href="https://appflowy.gitbook.io/docs/essential-documentation/install-appflowy/installation-methods/from-source">Source</a></li>
</ul>''',
(editorState) {
expect(editorState.document.root.children.length, 4);
final node1 = editorState.getNodeAtPath([0])!;
final node2 = editorState.getNodeAtPath([1])!;
final node3 = editorState.getNodeAtPath([2])!;
final node4 = editorState.getNodeAtPath([3])!;
expect(node1.delta!.toJson(), [
{
"insert": "User Installation",
"attributes": {"bold": true}
}
]);
expect(node2.delta!.toJson(), [
{
"insert": "Windows/Mac/Linux",
"attributes": {
"href":
"https://appflowy.gitbook.io/docs/essential-documentation/install-appflowy/installation-methods/mac-windows-linux-packages"
}
}
]);
expect(
node3.delta!.toJson(),
[
{
"insert": "Docker",
"attributes": {
"href":
"https://appflowy.gitbook.io/docs/essential-documentation/install-appflowy/installation-methods/installing-with-docker"
}
}
],
);
expect(
node4.delta!.toJson(),
[
{
"insert": "Source",
"attributes": {
"href":
"https://appflowy.gitbook.io/docs/essential-documentation/install-appflowy/installation-methods/from-source"
}
}
],
);
},
);
await tester.pumpAndSettle();

final editorState = tester.editor.getCurrentEditorState();
expect(editorState.document.root.children.length, 4);
for (var i = 0; i < lines; i++) {
expect(
editorState.getNodeAtPath([i])!.delta!.toPlainText(),
'line $i',
);
}
});

testWidgets('paste code from VSCode', (tester) async {
await tester.pasteContent(
html:
'''<meta charset='utf-8'><div style="color: #bbbbbb;background-color: #262335;font-family: Consolas, 'JetBrains Mono', monospace, 'cascadia code', Menlo, Monaco, 'Courier New', monospace;font-weight: normal;font-size: 14px;line-height: 21px;white-space: pre;"><div><span style="color: #fede5d;">void</span><span style="color: #ff7edb;"> </span><span style="color: #36f9f6;">main</span><span style="color: #ff7edb;">() {</span></div><div><span style="color: #ff7edb;"> </span><span style="color: #36f9f6;">runApp</span><span style="color: #ff7edb;">(</span><span style="color: #fede5d;">const</span><span style="color: #ff7edb;"> </span><span style="color: #fe4450;">MyApp</span><span style="color: #ff7edb;">());</span></div><div><span style="color: #ff7edb;">}</span></div></div>''',
(editorState) {
expect(editorState.document.root.children.length, 3);
final node1 = editorState.getNodeAtPath([0])!;
final node2 = editorState.getNodeAtPath([1])!;
final node3 = editorState.getNodeAtPath([2])!;
expect(node1.type, ParagraphBlockKeys.type);
expect(node2.type, ParagraphBlockKeys.type);
expect(node3.type, ParagraphBlockKeys.type);
expect(node1.delta!.toJson(), [
{
"insert": "void",
"attributes": {"font_color": "0xfffede5d"}
},
{
"insert": " ",
"attributes": {"font_color": "0xffff7edb"}
},
{
"insert": "main",
"attributes": {"font_color": "0xff36f9f6"}
},
{
"insert": "() {",
"attributes": {"font_color": "0xffff7edb"}
}
]);
expect(node2.delta!.toJson(), [
{
"insert": " ",
"attributes": {"font_color": "0xffff7edb"}
},
{
"insert": "runApp",
"attributes": {"font_color": "0xff36f9f6"}
},
{
"insert": "(",
"attributes": {"font_color": "0xffff7edb"}
},
{
"insert": "const",
"attributes": {"font_color": "0xfffede5d"}
},
{
"insert": " ",
"attributes": {"font_color": "0xffff7edb"}
},
{
"insert": "MyApp",
"attributes": {"font_color": "0xfffe4450"}
},
{
"insert": "());",
"attributes": {"font_color": "0xffff7edb"}
}
]);
expect(node3.delta!.toJson(), [
{
"insert": "}",
"attributes": {"font_color": "0xffff7edb"}
}
]);
});
});
});

testWidgets('paste image from memory', (tester) async {
final image = await rootBundle.load('assets/test/images/sample.png');
final bytes = image.buffer.asUint8List();
await tester.pasteContent(image: ('png', bytes), (editorState) {
expect(editorState.document.root.children.length, 2);
final node = editorState.getNodeAtPath([0])!;
expect(node.type, ImageBlockKeys.type);
expect(node.attributes[ImageBlockKeys.url], isNotNull);
});
});
}

extension on WidgetTester {
Future<void> pasteContent(
void Function(EditorState editorState) test, {
String? plainText,
String? html,
(String, Uint8List?)? image,
}) async {
await initializeAppFlowy();
await tapGoButton();

// create a new document
await createNewPageWithName();

// mock the clipboard
getIt<ClipboardService>().setData(
ClipboardServiceData(
plainText: plainText,
html: html,
image: image,
),
);

// paste the text
await simulateKeyEvent(
LogicalKeyboardKey.keyV,
isControlPressed: Platform.isLinux || Platform.isWindows,
isMetaPressed: Platform.isMacOS,
);
await pumpAndSettle();

final editorState = editor.getCurrentEditorState();
test(editorState);
}
}
1 change: 1 addition & 0 deletions frontend/appflowy_flutter/lib/env/env.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ abstract class Env {
}

bool get isSupabaseEnabled {
return false;
// Only enable supabase in release and develop mode.
if (integrationEnv().isRelease || integrationEnv().isDevelop) {
return Env.supabaseUrl.isNotEmpty &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ final inAppJsonFormat = CustomValueFormat<String>(
},
);

class ClipboardData {
const ClipboardData({
class ClipboardServiceData {
const ClipboardServiceData({
this.plainText,
this.html,
this.image,
Expand All @@ -38,12 +38,11 @@ class ClipboardData {
}

class ClipboardService {
Future<void> setData(ClipboardData data) async {
Future<void> setData(ClipboardServiceData data) async {
final plainText = data.plainText;
final html = data.html;
final inAppJson = data.inAppJson;

assert(data.image == null, 'not support image yet');
final image = data.image;

final item = DataWriterItem();
if (plainText != null) {
Expand All @@ -55,10 +54,25 @@ class ClipboardService {
if (inAppJson != null) {
item.add(inAppJsonFormat(inAppJson));

Check warning on line 55 in frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart

View check run for this annotation

Codecov / codecov/patch

frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart#L55

Added line #L55 was not covered by tests
}
if (image != null && image.$2?.isNotEmpty == true) {
switch (image.$1) {
case 'png':
item.add(Formats.png(image.$2!));
break;
case 'jpeg':
item.add(Formats.jpeg(image.$2!));

Check warning on line 63 in frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart

View check run for this annotation

Codecov / codecov/patch

frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart#L62-L63

Added lines #L62 - L63 were not covered by tests
break;
case 'gif':
item.add(Formats.gif(image.$2!));

Check warning on line 66 in frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart

View check run for this annotation

Codecov / codecov/patch

frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart#L65-L66

Added lines #L65 - L66 were not covered by tests
break;
default:
throw Exception('unsupported image format: ${image.$1}');

Check warning on line 69 in frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart

View check run for this annotation

Codecov / codecov/patch

frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart#L69

Added line #L69 was not covered by tests
}
}
await ClipboardWriter.instance.write([item]);
}

Future<ClipboardData> getData() async {
Future<ClipboardServiceData> getData() async {
final reader = await ClipboardReader.readClipboard();
final plainText = await reader.readValue(Formats.plainText);
final html = await reader.readValue(Formats.htmlText);
Expand All @@ -72,7 +86,7 @@ class ClipboardService {
image = ('gif', await reader.readFile(Formats.gif));

Check warning on line 86 in frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart

View check run for this annotation

Codecov / codecov/patch

frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart#L86

Added line #L86 was not covered by tests
}

return ClipboardData(
return ClipboardServiceData(
plainText: plainText,
html: html,
image: image,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ CommandShortcutEventHandler _copyCommandHandler = (editorState) {

() async {
await getIt<ClipboardService>().setData(
ClipboardData(
ClipboardServiceData(

Check warning on line 42 in frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_copy_command.dart

View check run for this annotation

Codecov / codecov/patch

frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_copy_command.dart#L40-L42

Added lines #L40 - L42 were not covered by tests
plainText: text,
html: html,
inAppJson: inAppJson,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,16 @@ extension PasteNodes on EditorState {
final insertedDelta = insertedNode.delta;

Check warning on line 15 in frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/editor_state_paste_node_extension.dart

View check run for this annotation

Codecov / codecov/patch

frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/editor_state_paste_node_extension.dart#L14-L15

Added lines #L14 - L15 were not covered by tests
// if the node is empty, replace it with the inserted node.
if (delta.isEmpty || insertedDelta == null) {
transaction.insertNode(selection.end.path.next, insertedNode);
transaction.insertNode(
selection.end.path.next,
node.copyWith(
type: node.type,
attributes: {
...node.attributes,
...insertedNode.attributes,

Check warning on line 24 in frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/editor_state_paste_node_extension.dart

View check run for this annotation

Codecov / codecov/patch

frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/editor_state_paste_node_extension.dart#L17-L24

Added lines #L17 - L24 were not covered by tests
},
),
);
transaction.deleteNode(node);
transaction.afterSelection = Selection.collapsed(
Position(
Expand Down Expand Up @@ -51,17 +60,21 @@ extension PasteNodes on EditorState {
delta.slice(0, selection.startIndex),

Check warning on line 60 in frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/editor_state_paste_node_extension.dart

View check run for this annotation

Codecov / codecov/patch

frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/editor_state_paste_node_extension.dart#L59-L60

Added lines #L59 - L60 were not covered by tests
insertAfter: false,
);

nodes.last.insertDelta(
delta.slice(selection.endIndex),

Check warning on line 65 in frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/editor_state_paste_node_extension.dart

View check run for this annotation

Codecov / codecov/patch

frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/editor_state_paste_node_extension.dart#L64-L65

Added lines #L64 - L65 were not covered by tests
insertAfter: true,
);
}

if (delta.isEmpty && node.type != ParagraphBlockKeys.type) {
nodes[0] = nodes.first.copyWith(
type: node.type,
attributes: {
...node.attributes,
...nodes.first.attributes,

Check warning on line 75 in frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/editor_state_paste_node_extension.dart

View check run for this annotation

Codecov / codecov/patch

frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/editor_state_paste_node_extension.dart#L71-L75

Added lines #L71 - L75 were not covered by tests
},
);
nodes.last.insertDelta(
delta.slice(selection.endIndex),
insertAfter: true,
);
}

for (final child in node.children) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@ import 'package:appflowy_editor/appflowy_editor.dart';

extension PasteFromHtml on EditorState {
Future<void> pasteHtml(String html) async {
final nodes = htmlToDocument(html).root.children;
final nodes = htmlToDocument(html).root.children.toList();
// remove the front and back empty line
while (nodes.isNotEmpty && nodes.first.delta?.isEmpty == true) {
nodes.removeAt(0);
}
while (nodes.isNotEmpty && nodes.last.delta?.isEmpty == true) {
nodes.removeLast();

Check warning on line 12 in frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_html.dart

View check run for this annotation

Codecov / codecov/patch

frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_html.dart#L12

Added line #L12 was not covered by tests
}
if (nodes.isEmpty) {
return;
}
Expand Down
1 change: 1 addition & 0 deletions frontend/appflowy_flutter/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ flutter:
# The following assets will be excluded in release.
# BEGIN: EXCLUDE_IN_RELEASE
- assets/test/workspaces/
- assets/test/images/
- assets/template/
- assets/test/workspaces/markdowns/
- assets/test/workspaces/database/
Expand Down

0 comments on commit 2c73869

Please sign in to comment.