Merge pull request #857 from LucasXu0/test/internal_key_event_handlers

implement white_space_handler & update_text_style_by_command_X test
This commit is contained in:
Nathan.fooo 2022-08-17 08:18:40 +08:00 committed by GitHub
commit b3acb4f9e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 371 additions and 14 deletions

View File

@ -41,6 +41,30 @@ extension TextNodeExtension on TextNode {
}
return true;
}
bool allNotSatisfyInSelection(String styleKey, Selection selection) {
final ops = delta.whereType<TextInsert>();
final startOffset =
selection.isBackward ? selection.start.offset : selection.end.offset;
final endOffset =
selection.isBackward ? selection.end.offset : selection.start.offset;
var start = 0;
for (final op in ops) {
if (start >= endOffset) {
break;
}
final length = op.length;
if (start < endOffset && start + length > startOffset) {
if (op.attributes != null &&
op.attributes!.containsKey(styleKey) &&
op.attributes![styleKey] == true) {
return false;
}
}
start += length;
}
return true;
}
}
extension TextNodesExtension on List<TextNode> {

View File

@ -72,8 +72,8 @@ class _CheckboxNodeWidgetState extends State<CheckboxNodeWidget>
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GestureDetector(
key: iconKey,
child: FlowySvg(
key: iconKey,
size: Size.square(_iconSize),
padding: EdgeInsets.only(
top: topPadding, right: _iconRightPadding),
@ -149,7 +149,11 @@ class _CheckboxNodeWidgetState extends State<CheckboxNodeWidget>
style: widget.textNode.attributes.check
? span.style?.copyWith(
color: Colors.grey,
decoration: TextDecoration.lineThrough,
decoration: TextDecoration.combine([
TextDecoration.lineThrough,
if (span.style?.decoration != null)
span.style!.decoration!
]),
)
: span.style,
recognizer: span.recognizer,

View File

@ -9,6 +9,13 @@ import 'package:flowy_editor/src/operation/transaction_builder.dart';
import 'package:flowy_editor/src/render/rich_text/rich_text_style.dart';
import 'package:flowy_editor/src/service/keyboard_service.dart';
@visibleForTesting
List<String> get checkboxListSymbols => _checkboxListSymbols;
@visibleForTesting
List<String> get unCheckboxListSymbols => _unCheckboxListSymbols;
@visibleForTesting
List<String> get bulletedListSymbols => _bulletedListSymbols;
const _bulletedListSymbols = ['*', '-'];
const _checkboxListSymbols = ['[x]', '-[x]'];
const _unCheckboxListSymbols = ['[]', '-[]'];

View File

@ -1,7 +1,4 @@
import 'package:flowy_editor/src/service/keyboard_service.dart';
import 'package:flowy_editor/src/service/render_plugin_service.dart';
import 'package:flowy_editor/src/service/scroll_service.dart';
import 'package:flowy_editor/src/service/selection_service.dart';
import 'package:flowy_editor/flowy_editor.dart';
import 'package:flowy_editor/src/service/toolbar_service.dart';
import 'package:flutter/material.dart';
@ -26,6 +23,13 @@ class FlowyService {
// input service
final inputServiceKey = GlobalKey(debugLabel: 'flowy_input_service');
FlowyInputService? get inputService {
if (inputServiceKey.currentState != null &&
inputServiceKey.currentState is FlowyInputService) {
return inputServiceKey.currentState! as FlowyInputService;
}
return null;
}
// render plugin service
late FlowyRenderPlugin renderPluginService;

View File

@ -47,13 +47,11 @@ class EditorWidgetTester {
insert(TextNode.empty());
}
void insertTextNode(String? text, {Attributes? attributes}) {
void insertTextNode(String? text, {Attributes? attributes, Delta? delta}) {
insert(
TextNode(
type: 'text',
delta: Delta(
[TextInsert(text ?? 'Test')],
),
delta: delta ?? Delta([TextInsert(text ?? 'Test')]),
attributes: attributes,
),
);
@ -70,6 +68,31 @@ class EditorWidgetTester {
_editorState.service.selectionService.updateSelection(selection);
}
await tester.pumpAndSettle();
expect(_editorState.service.selectionService.currentSelection.value,
selection);
}
Future<void> insertText(TextNode textNode, String text, int offset,
{Selection? selection}) async {
await apply([
TextEditingDeltaInsertion(
oldText: textNode.toRawString(),
textInserted: text,
insertionOffset: offset,
selection: selection != null
? TextSelection(
baseOffset: selection.start.offset,
extentOffset: selection.end.offset)
: TextSelection.collapsed(offset: offset),
composing: TextRange.empty,
)
]);
}
Future<void> apply(List<TextEditingDelta> deltas) async {
_editorState.service.inputService?.apply(deltas);
await tester.pumpAndSettle();
}
Future<void> pressLogicKey(

View File

@ -79,6 +79,9 @@ extension on LogicalKeyboardKey {
if (this == LogicalKeyboardKey.enter) {
return PhysicalKeyboardKey.enter;
}
if (this == LogicalKeyboardKey.space) {
return PhysicalKeyboardKey.space;
}
if (this == LogicalKeyboardKey.backspace) {
return PhysicalKeyboardKey.backspace;
}

View File

@ -0,0 +1,73 @@
import 'package:flowy_editor/flowy_editor.dart';
import 'package:flowy_editor/src/render/rich_text/default_selectable.dart';
import 'package:flowy_editor/src/render/rich_text/rich_text_style.dart';
import 'package:flowy_editor/src/extensions/text_node_extensions.dart';
import 'package:flutter_test/flutter_test.dart';
import '../../infra/test_editor.dart';
void main() async {
setUpAll(() {
TestWidgetsFlutterBinding.ensureInitialized();
});
group('delete_text_handler.dart', () {
testWidgets('Presses backspace key in empty document', (tester) async {
// Before
//
// [BIUS]Welcome to Appflowy 😁[BIUS]
//
// After
//
// [checkbox]Welcome to Appflowy 😁
//
const text = 'Welcome to Appflowy 😁';
final editor = tester.editor
..insertTextNode(
'',
attributes: {
StyleKey.subtype: StyleKey.checkbox,
StyleKey.checkbox: false,
},
delta: Delta([
TextInsert(text, {
StyleKey.bold: true,
StyleKey.italic: true,
StyleKey.underline: true,
StyleKey.strikethrough: true,
}),
]),
);
await editor.startTesting();
await editor.updateSelection(
Selection.single(path: [0], startOffset: 0),
);
final selection =
Selection.single(path: [0], startOffset: 0, endOffset: text.length);
var node = editor.nodeAtPath([0]) as TextNode;
var state = node.key?.currentState as DefaultSelectable;
var checkboxWidget = find.byKey(state.iconKey!);
await tester.tap(checkboxWidget);
await tester.pumpAndSettle();
expect(node.attributes.check, true);
expect(node.allSatisfyBoldInSelection(selection), true);
expect(node.allSatisfyItalicInSelection(selection), true);
expect(node.allSatisfyUnderlineInSelection(selection), true);
expect(node.allSatisfyStrikethroughInSelection(selection), true);
node = editor.nodeAtPath([0]) as TextNode;
state = node.key?.currentState as DefaultSelectable;
await tester.ensureVisible(find.byKey(state.iconKey!));
await tester.tap(find.byKey(state.iconKey!));
await tester.pump();
expect(node.attributes.check, false);
expect(node.allSatisfyBoldInSelection(selection), true);
expect(node.allSatisfyItalicInSelection(selection), true);
expect(node.allSatisfyUnderlineInSelection(selection), true);
expect(node.allSatisfyStrikethroughInSelection(selection), true);
});
});
}

View File

@ -18,7 +18,6 @@ void main() async {
LogicalKeyboardKey.keyB,
);
});
testWidgets('Presses Command + I to update text style', (tester) async {
await _testUpdateTextStyleByCommandX(
tester,
@ -26,7 +25,6 @@ void main() async {
LogicalKeyboardKey.keyI,
);
});
testWidgets('Presses Command + U to update text style', (tester) async {
await _testUpdateTextStyleByCommandX(
tester,
@ -34,7 +32,6 @@ void main() async {
LogicalKeyboardKey.keyU,
);
});
testWidgets('Presses Command + S to update text style', (tester) async {
await _testUpdateTextStyleByCommandX(
tester,
@ -83,5 +80,49 @@ Future<void> _testUpdateTextStyleByCommandX(
isMetaPressed: true,
);
textNode = editor.nodeAtPath([1]) as TextNode;
expect(textNode.allSatisfyInSelection(matchStyle, selection), false);
expect(textNode.allNotSatisfyInSelection(matchStyle, selection), true);
selection = Selection(
start: Position(path: [0], offset: 0),
end: Position(path: [2], offset: text.length),
);
await editor.updateSelection(selection);
await editor.pressLogicKey(
key,
isShiftPressed: key == LogicalKeyboardKey.keyS,
isMetaPressed: true,
);
var nodes = editor.editorState.service.selectionService.currentSelectedNodes
.whereType<TextNode>();
expect(nodes.length, 3);
for (final node in nodes) {
expect(
node.allSatisfyInSelection(
matchStyle,
Selection.single(
path: node.path, startOffset: 0, endOffset: text.length),
),
true,
);
}
await editor.updateSelection(selection);
await editor.pressLogicKey(
key,
isShiftPressed: key == LogicalKeyboardKey.keyS,
isMetaPressed: true,
);
nodes = editor.editorState.service.selectionService.currentSelectedNodes
.whereType<TextNode>();
expect(nodes.length, 3);
for (final node in nodes) {
expect(
node.allNotSatisfyInSelection(
matchStyle,
Selection.single(
path: node.path, startOffset: 0, endOffset: text.length),
),
true,
);
}
}

View File

@ -0,0 +1,178 @@
import 'package:flowy_editor/flowy_editor.dart';
import 'package:flowy_editor/src/render/rich_text/rich_text_style.dart';
import 'package:flowy_editor/src/service/internal_key_event_handlers/whitespace_handler.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import '../../infra/test_editor.dart';
void main() async {
setUpAll(() {
TestWidgetsFlutterBinding.ensureInitialized();
});
group('white_space_handler.dart', () {
// Before
//
// Welcome to Appflowy 😁
// Welcome to Appflowy 😁
// Welcome to Appflowy 😁
// Welcome to Appflowy 😁
// Welcome to Appflowy 😁
// Welcome to Appflowy 😁
//
// After
// [h1]Welcome to Appflowy 😁
// [h2]Welcome to Appflowy 😁
// [h3]Welcome to Appflowy 😁
// [h4]Welcome to Appflowy 😁
// [h5]Welcome to Appflowy 😁
// [h6]Welcome to Appflowy 😁
//
testWidgets('Presses whitespace key after #*', (tester) async {
const maxSignCount = 6;
const text = 'Welcome to Appflowy 😁';
final editor = tester.editor;
for (var i = 1; i <= maxSignCount; i++) {
editor.insertTextNode('${'#' * i}$text');
}
await editor.startTesting();
for (var i = 1; i <= maxSignCount; i++) {
await editor.updateSelection(
Selection.single(path: [i - 1], startOffset: i),
);
await editor.pressLogicKey(LogicalKeyboardKey.space);
final textNode = (editor.nodeAtPath([i - 1]) as TextNode);
expect(textNode.subtype, StyleKey.heading);
// StyleKey.h1 ~ StyleKey.h6
expect(textNode.attributes.heading, 'h$i');
}
});
// Before
//
// Welcome to Appflowy 😁
// Welcome to Appflowy 😁
// Welcome to Appflowy 😁
// Welcome to Appflowy 😁
// Welcome to Appflowy 😁
// Welcome to Appflowy 😁
//
// After
// [h1]##Welcome to Appflowy 😁
// [h2]##Welcome to Appflowy 😁
// [h3]##Welcome to Appflowy 😁
// [h4]##Welcome to Appflowy 😁
// [h5]##Welcome to Appflowy 😁
// [h6]##Welcome to Appflowy 😁
//
testWidgets('Presses whitespace key inside #*', (tester) async {
const maxSignCount = 6;
const text = 'Welcome to Appflowy 😁';
final editor = tester.editor;
for (var i = 1; i <= maxSignCount; i++) {
editor.insertTextNode('${'###' * i}$text');
}
await editor.startTesting();
for (var i = 1; i <= maxSignCount; i++) {
await editor.updateSelection(
Selection.single(path: [i - 1], startOffset: i),
);
await editor.pressLogicKey(LogicalKeyboardKey.space);
final textNode = (editor.nodeAtPath([i - 1]) as TextNode);
expect(textNode.subtype, StyleKey.heading);
// StyleKey.h1 ~ StyleKey.h6
expect(textNode.attributes.heading, 'h$i');
expect(textNode.toRawString().startsWith('##'), true);
}
});
// Before
//
// Welcome to Appflowy 😁
//
// After
// [h1 ~ h6]##Welcome to Appflowy 😁
//
testWidgets('Presses whitespace key in heading styled text',
(tester) async {
const text = 'Welcome to Appflowy 😁';
final editor = tester.editor..insertTextNode(text);
await editor.startTesting();
const maxSignCount = 6;
for (var i = 1; i <= maxSignCount; i++) {
await editor.updateSelection(
Selection.single(path: [0], startOffset: 0),
);
final textNode = (editor.nodeAtPath([0]) as TextNode);
await editor.insertText(textNode, '#' * i, 0);
await editor.pressLogicKey(LogicalKeyboardKey.space);
expect(textNode.subtype, StyleKey.heading);
// StyleKey.h2 ~ StyleKey.h6
expect(textNode.attributes.heading, 'h$i');
}
});
testWidgets('Presses whitespace key after (un)checkbox symbols',
(tester) async {
const text = 'Welcome to Appflowy 😁';
final editor = tester.editor..insertTextNode(text);
await editor.startTesting();
final textNode = editor.nodeAtPath([0]) as TextNode;
for (final symbol in unCheckboxListSymbols) {
await editor.updateSelection(
Selection.single(path: [0], startOffset: 0),
);
await editor.insertText(textNode, symbol, 0);
await editor.pressLogicKey(LogicalKeyboardKey.space);
expect(textNode.subtype, StyleKey.checkbox);
expect(textNode.attributes.check, false);
}
});
testWidgets('Presses whitespace key after checkbox symbols',
(tester) async {
const text = 'Welcome to Appflowy 😁';
final editor = tester.editor..insertTextNode(text);
await editor.startTesting();
final textNode = editor.nodeAtPath([0]) as TextNode;
for (final symbol in checkboxListSymbols) {
await editor.updateSelection(
Selection.single(path: [0], startOffset: 0),
);
await editor.insertText(textNode, symbol, 0);
await editor.pressLogicKey(LogicalKeyboardKey.space);
expect(textNode.subtype, StyleKey.checkbox);
expect(textNode.attributes.check, true);
}
});
testWidgets('Presses whitespace key after bulleted list', (tester) async {
const text = 'Welcome to Appflowy 😁';
final editor = tester.editor..insertTextNode(text);
await editor.startTesting();
final textNode = editor.nodeAtPath([0]) as TextNode;
for (final symbol in bulletedListSymbols) {
await editor.updateSelection(
Selection.single(path: [0], startOffset: 0),
);
await editor.insertText(textNode, symbol, 0);
await editor.pressLogicKey(LogicalKeyboardKey.space);
expect(textNode.subtype, StyleKey.bulletedList);
}
});
});
}