feat: adjust the text command

This commit is contained in:
Lucas.Xu 2023-04-30 15:01:09 +08:00
commit 1d0055d0b1
9 changed files with 399 additions and 304 deletions

View file

@ -44,7 +44,7 @@ class TextRobot {
// insert new line // insert new line
if (lines.length > 1) { if (lines.length > 1) {
await editorState.insertNewLineAtCurrentSelection(); await editorState.insertNewLine();
} }
} }
} }

View file

@ -41,7 +41,7 @@ export 'src/plugins/markdown/encoder/parser/image_node_parser.dart';
export 'src/plugins/markdown/decoder/delta_markdown_decoder.dart'; export 'src/plugins/markdown/decoder/delta_markdown_decoder.dart';
export 'src/plugins/markdown/document_markdown.dart'; export 'src/plugins/markdown/document_markdown.dart';
export 'src/plugins/quill_delta/delta_document_encoder.dart'; export 'src/plugins/quill_delta/delta_document_encoder.dart';
export 'src/commands/text/text_commands.dart'; // export 'src/commands/text/text_commands.dart';
export 'src/commands/command_extension.dart'; export 'src/commands/command_extension.dart';
export 'src/render/toolbar/toolbar_item.dart'; export 'src/render/toolbar/toolbar_item.dart';
export 'src/render/action_menu/action_menu.dart'; export 'src/render/action_menu/action_menu.dart';

View file

@ -190,4 +190,36 @@ extension TextTransforms on EditorState {
return apply(transaction); return apply(transaction);
} }
/// Insert text at the given index of the given [TextNode] or the [Path].
///
/// [Path] and [TextNode] are mutually exclusive.
/// One of these two parameters must have a value.
Future<void> insertText(
int index,
String text, {
Path? path,
Node? node,
}) async {
node ??= getNodeAtPath(path!);
if (node == null) {
assert(false, 'node is null');
return;
}
return apply(
transaction..insertText(node, index, text),
);
}
Future<void> insertTextAtCurrentSelection(String text) async {
final selection = this.selection;
if (selection == null || !selection.isCollapsed) {
return;
}
return insertText(
selection.startIndex,
text,
path: selection.end.path,
);
}
} }

View file

@ -71,10 +71,10 @@ class _CheckboxNodeWidgetState extends State<CheckboxNodeWidget>
GestureDetector( GestureDetector(
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
onTap: () async { onTap: () async {
await widget.editorState.formatTextToCheckbox( // await widget.editorState.formatTextToCheckbox(
!check, // !check,
textNode: widget.textNode, // textNode: widget.textNode,
); // );
}, },
child: icon, child: icon,
), ),

View file

@ -429,10 +429,10 @@ void showLinkMenu(
await safeLaunchUrl(linkText); await safeLaunchUrl(linkText);
}, },
onSubmitted: (text) async { onSubmitted: (text) async {
await editorState.formatLinkInText( // await editorState.formatLinkInText(
text, // text,
textNode: textNode, // textNode: textNode,
); // );
_dismissOverlay(); _dismissOverlay();
}, },

View file

@ -17,7 +17,7 @@ ShortcutEventHandler spaceOnWebHandler = (editorState, event) {
editorState.insertText( editorState.insertText(
selection.startIndex, selection.startIndex,
' ', ' ',
textNode: textNodes.first, node: textNodes.first,
); );
return KeyEventResult.handled; return KeyEventResult.handled;

View file

@ -1,218 +1,218 @@
import 'package:appflowy_editor/appflowy_editor.dart'; // import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter_test/flutter_test.dart'; // import 'package:flutter_test/flutter_test.dart';
import '../infra/test_editor.dart'; // import '../infra/test_editor.dart';
void main() { // void main() {
group('TextCommands extension tests', () { // group('TextCommands extension tests', () {
testWidgets('insertText', (tester) async { // testWidgets('insertText', (tester) async {
final editor = tester.editor // final editor = tester.editor
..insertEmptyTextNode() // ..insertEmptyTextNode()
..insertTextNode('World'); // ..insertTextNode('World');
await editor.startTesting(); // await editor.startTesting();
editor.editorState.insertText(0, 'Hello', path: [0]); // editor.editorState.insertText(0, 'Hello', path: [0]);
await tester.pumpAndSettle(); // await tester.pumpAndSettle();
expect( // expect(
(editor.editorState.getTextNode(path: [0]).delta.first as TextInsert) // (editor.editorState.getTextNode(path: [0]).delta.first as TextInsert)
.text, // .text,
'Hello', // 'Hello',
); // );
}); // });
testWidgets('insertTextAtCurrentSelection', (tester) async { // testWidgets('insertTextAtCurrentSelection', (tester) async {
final editor = tester.editor..insertTextNode('Helrld!'); // final editor = tester.editor..insertTextNode('Helrld!');
await editor.startTesting(); // await editor.startTesting();
final selection = Selection( // final selection = Selection(
start: Position(path: [0], offset: 3), // start: Position(path: [0], offset: 3),
end: Position(path: [0], offset: 3), // end: Position(path: [0], offset: 3),
); // );
await editor.updateSelection(selection); // await editor.updateSelection(selection);
editor.editorState.insertTextAtCurrentSelection('lo Wo'); // editor.editorState.insertTextAtCurrentSelection('lo Wo');
await tester.pumpAndSettle(); // await tester.pumpAndSettle();
expect( // expect(
(editor.editorState.getTextNode(path: [0]).delta.first as TextInsert) // (editor.editorState.getTextNode(path: [0]).delta.first as TextInsert)
.text, // .text,
'Hello World!', // 'Hello World!',
); // );
}); // });
testWidgets('formatText', (tester) async { // testWidgets('formatText', (tester) async {
final editor = tester.editor..insertTextNode('Hello'); // final editor = tester.editor..insertTextNode('Hello');
await editor.startTesting(); // await editor.startTesting();
final selection = Selection( // final selection = Selection(
start: Position(path: [0]), // start: Position(path: [0]),
end: Position(path: [0], offset: 5), // end: Position(path: [0], offset: 5),
); // );
await editor.updateSelection(selection); // await editor.updateSelection(selection);
editor.editorState // editor.editorState
.formatText(null, {BuiltInAttributeKey.bold: true}, path: [0]); // .formatText(null, {BuiltInAttributeKey.bold: true}, path: [0]);
await tester.pumpAndSettle(); // await tester.pumpAndSettle();
final textNode = editor.editorState.getTextNode(path: [0]); // final textNode = editor.editorState.getTextNode(path: [0]);
final textInsert = textNode.delta.first as TextInsert; // final textInsert = textNode.delta.first as TextInsert;
expect(textInsert.text, 'Hello'); // expect(textInsert.text, 'Hello');
expect(textInsert.attributes?[BuiltInAttributeKey.bold], true); // expect(textInsert.attributes?[BuiltInAttributeKey.bold], true);
}); // });
testWidgets('formatTextWithBuiltInAttribute w/ Partial Style Key', // testWidgets('formatTextWithBuiltInAttribute w/ Partial Style Key',
(tester) async { // (tester) async {
final editor = tester.editor..insertTextNode('Hello'); // final editor = tester.editor..insertTextNode('Hello');
await editor.startTesting(); // await editor.startTesting();
final selection = Selection( // final selection = Selection(
start: Position(path: [0]), // start: Position(path: [0]),
end: Position(path: [0], offset: 5), // end: Position(path: [0], offset: 5),
); // );
await editor.updateSelection(selection); // await editor.updateSelection(selection);
editor.editorState.formatTextWithBuiltInAttribute( // editor.editorState.formatTextWithBuiltInAttribute(
BuiltInAttributeKey.underline, // BuiltInAttributeKey.underline,
{BuiltInAttributeKey.underline: true}, // {BuiltInAttributeKey.underline: true},
path: [0], // path: [0],
); // );
await tester.pumpAndSettle(); // await tester.pumpAndSettle();
final textNode = editor.editorState.getTextNode(path: [0]); // final textNode = editor.editorState.getTextNode(path: [0]);
final textInsert = textNode.delta.first as TextInsert; // final textInsert = textNode.delta.first as TextInsert;
expect(textInsert.text, 'Hello'); // expect(textInsert.text, 'Hello');
expect(textInsert.attributes?[BuiltInAttributeKey.underline], true); // expect(textInsert.attributes?[BuiltInAttributeKey.underline], true);
}); // });
testWidgets('formatTextWithBuiltInAttribute w/ Global Style Key', // testWidgets('formatTextWithBuiltInAttribute w/ Global Style Key',
(tester) async { // (tester) async {
final editor = tester.editor // final editor = tester.editor
..insertTextNode( // ..insertTextNode(
'Hello', // 'Hello',
/// Formatting global style over another global style, // /// Formatting global style over another global style,
/// will remove the existing one before adding the new one // /// will remove the existing one before adding the new one
attributes: {BuiltInAttributeKey.checkbox: true}, // attributes: {BuiltInAttributeKey.checkbox: true},
); // );
await editor.startTesting(); // await editor.startTesting();
editor.editorState.formatTextWithBuiltInAttribute( // editor.editorState.formatTextWithBuiltInAttribute(
BuiltInAttributeKey.heading, // BuiltInAttributeKey.heading,
{BuiltInAttributeKey.heading: BuiltInAttributeKey.h1}, // {BuiltInAttributeKey.heading: BuiltInAttributeKey.h1},
path: [0], // path: [0],
); // );
await tester.pumpAndSettle(); // await tester.pumpAndSettle();
final textNode = editor.editorState.getTextNode(path: [0]); // final textNode = editor.editorState.getTextNode(path: [0]);
final textInsert = textNode.delta.first as TextInsert; // final textInsert = textNode.delta.first as TextInsert;
expect(textInsert.text, 'Hello'); // expect(textInsert.text, 'Hello');
expect(textNode.attributes.heading, BuiltInAttributeKey.h1); // expect(textNode.attributes.heading, BuiltInAttributeKey.h1);
expect(textNode.attributes['subtype'], BuiltInAttributeKey.heading); // expect(textNode.attributes['subtype'], BuiltInAttributeKey.heading);
}); // });
testWidgets('formatTextToCheckbox', (tester) async { // testWidgets('formatTextToCheckbox', (tester) async {
final editor = tester.editor..insertTextNode('TextNode to Checkbox'); // final editor = tester.editor..insertTextNode('TextNode to Checkbox');
await editor.startTesting(); // await editor.startTesting();
editor.editorState.formatTextToCheckbox(false, path: [0]); // editor.editorState.formatTextToCheckbox(false, path: [0]);
await tester.pumpAndSettle(); // await tester.pumpAndSettle();
final checkboxNode = editor.editorState.getNode(path: [0]); // final checkboxNode = editor.editorState.getNode(path: [0]);
expect(checkboxNode.attributes.check, false); // expect(checkboxNode.attributes.check, false);
expect(checkboxNode.attributes['subtype'], BuiltInAttributeKey.checkbox); // expect(checkboxNode.attributes['subtype'], BuiltInAttributeKey.checkbox);
}); // });
testWidgets('formatLinkInText', (tester) async { // testWidgets('formatLinkInText', (tester) async {
const href = "https://appflowy.io/"; // const href = "https://appflowy.io/";
final editor = tester.editor..insertTextNode('TextNode to Link'); // final editor = tester.editor..insertTextNode('TextNode to Link');
await editor.startTesting(); // await editor.startTesting();
final selection = Selection( // final selection = Selection(
start: Position(path: [0]), // start: Position(path: [0]),
end: Position(path: [0], offset: 5), // end: Position(path: [0], offset: 5),
); // );
await editor.updateSelection(selection); // await editor.updateSelection(selection);
editor.editorState.formatLinkInText(href, path: [0]); // editor.editorState.formatLinkInText(href, path: [0]);
await tester.pumpAndSettle(); // await tester.pumpAndSettle();
final textNode = editor.editorState.getTextNode(path: [0]); // final textNode = editor.editorState.getTextNode(path: [0]);
final textInsert = textNode.delta.first as TextInsert; // final textInsert = textNode.delta.first as TextInsert;
expect(textInsert.attributes?[BuiltInAttributeKey.href], href); // expect(textInsert.attributes?[BuiltInAttributeKey.href], href);
}); // });
testWidgets('insertNewLine', (tester) async { // testWidgets('insertNewLine', (tester) async {
final editor = tester.editor; // final editor = tester.editor;
await editor.startTesting(); // await editor.startTesting();
expect(editor.documentLength, 0); // expect(editor.documentLength, 0);
editor.editorState.insertNewLine(path: [0]); // editor.editorState.insertNewLine(path: [0]);
await tester.pumpAndSettle(); // await tester.pumpAndSettle();
expect(editor.documentLength, 1); // expect(editor.documentLength, 1);
}); // });
testWidgets('insertNewLine without path', (tester) async { // testWidgets('insertNewLine without path', (tester) async {
final editor = tester.editor..insertTextNode('Hello World'); // final editor = tester.editor..insertTextNode('Hello World');
await editor.startTesting(); // await editor.startTesting();
expect(editor.documentLength, 1); // expect(editor.documentLength, 1);
final selection = Selection( // final selection = Selection(
start: Position(path: [0], offset: 5), // start: Position(path: [0], offset: 5),
end: Position(path: [0], offset: 5), // end: Position(path: [0], offset: 5),
); // );
await editor.updateSelection(selection); // await editor.updateSelection(selection);
editor.editorState.insertNewLine(path: null); // editor.editorState.insertNewLine(path: null);
await tester.pumpAndSettle(); // await tester.pumpAndSettle();
expect(editor.documentLength, 2); // expect(editor.documentLength, 2);
final textNode = editor.editorState.getTextNode(path: [0]); // final textNode = editor.editorState.getTextNode(path: [0]);
final textInsert = textNode.delta.first as TextInsert; // final textInsert = textNode.delta.first as TextInsert;
expect(textInsert.text, 'Hello World'); // expect(textInsert.text, 'Hello World');
}); // });
testWidgets('insertNewLineAtCurrentSelection', (tester) async { // testWidgets('insertNewLineAtCurrentSelection', (tester) async {
final editor = tester.editor..insertTextNode('HelloWorld'); // final editor = tester.editor..insertTextNode('HelloWorld');
await editor.startTesting(); // await editor.startTesting();
final selection = Selection( // final selection = Selection(
start: Position(path: [0], offset: 5), // start: Position(path: [0], offset: 5),
end: Position(path: [0], offset: 5), // end: Position(path: [0], offset: 5),
); // );
await editor.updateSelection(selection); // await editor.updateSelection(selection);
expect(editor.documentLength, 1); // expect(editor.documentLength, 1);
editor.editorState.insertNewLineAtCurrentSelection(); // editor.editorState.insertNewLineAtCurrentSelection();
await tester.pumpAndSettle(); // await tester.pumpAndSettle();
expect(editor.documentLength, 2); // expect(editor.documentLength, 2);
final firstTextNode = editor.editorState.getTextNode(path: [0]); // final firstTextNode = editor.editorState.getTextNode(path: [0]);
final firstTextInsert = firstTextNode.delta.first as TextInsert; // final firstTextInsert = firstTextNode.delta.first as TextInsert;
expect(firstTextInsert.text, 'Hello'); // expect(firstTextInsert.text, 'Hello');
final secondTextNode = editor.editorState.getTextNode(path: [1]); // final secondTextNode = editor.editorState.getTextNode(path: [1]);
final secondTextInsert = secondTextNode.delta.first as TextInsert; // final secondTextInsert = secondTextNode.delta.first as TextInsert;
expect(secondTextInsert.text, 'World'); // expect(secondTextInsert.text, 'World');
}); // });
}); // });
} // }

View file

@ -177,4 +177,67 @@ void main() async {
expect(editorState.getNodeAtPath([1, 0])?.delta?.toPlainText(), text); expect(editorState.getNodeAtPath([1, 0])?.delta?.toPlainText(), text);
}); });
}); });
group('insertText', () {
const text = 'Welcome to AppFlowy Editor 🔥!';
/// Before
/// |
/// Welcome to AppFlowy Editor 🔥!
///
/// After
/// Hello|
/// Welcome to AppFlowy Editor 🔥!
test('insertText', () async {
final document = Document.blank()
.addParagraph(
initialText: '',
)
.addParagraph(
initialText: text,
decorator: (index, node) {
node.addParagraph(
initialText: text,
);
},
);
final editorState = EditorState(document: document);
const hello = 'Hello';
await editorState.insertText(0, hello, path: [0]);
expect(editorState.getNodeAtPath([0])?.delta?.toPlainText(), hello);
});
test('insertTextAtCurrentSelection', () async {
final document = Document.blank()
.addParagraph(
initialText: '',
)
.addParagraph(
initialText: text,
decorator: (index, node) {
node.addParagraph(
initialText: text,
);
},
);
final selection = Selection.collapsed(
Position(path: [0], offset: 0),
);
final editorState = EditorState(document: document);
editorState.selection = selection;
const hello = 'Hello';
await editorState.insertTextAtCurrentSelection(hello);
expect(editorState.getNodeAtPath([0])?.delta?.toPlainText(), hello);
expect(
editorState.selection,
Selection.collapsed(
Position(path: [0], offset: hello.length),
),
);
});
});
} }

View file

@ -1,133 +1,133 @@
import 'package:appflowy_editor/appflowy_editor.dart'; // import 'package:appflowy_editor/appflowy_editor.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 '../../infra/test_editor.dart'; // import '../../infra/test_editor.dart';
void main() async { // void main() async {
setUpAll(() { // setUpAll(() {
TestWidgetsFlutterBinding.ensureInitialized(); // TestWidgetsFlutterBinding.ensureInitialized();
}); // });
group('checkbox_text_handler.dart', () { // group('checkbox_text_handler.dart', () {
testWidgets('Click checkbox icon', (tester) async { // testWidgets('Click checkbox icon', (tester) async {
// Before // // Before
// // //
// [BIUS]Welcome to Appflowy 😁[BIUS] // // [BIUS]Welcome to Appflowy 😁[BIUS]
// // //
// After // // After
// // //
// [checkbox]Welcome to Appflowy 😁 // // [checkbox]Welcome to Appflowy 😁
// // //
const text = 'Welcome to Appflowy 😁'; // const text = 'Welcome to Appflowy 😁';
final editor = tester.editor // final editor = tester.editor
..insertTextNode( // ..insertTextNode(
'', // '',
attributes: { // attributes: {
BuiltInAttributeKey.subtype: BuiltInAttributeKey.checkbox, // BuiltInAttributeKey.subtype: BuiltInAttributeKey.checkbox,
BuiltInAttributeKey.checkbox: false, // BuiltInAttributeKey.checkbox: false,
}, // },
delta: Delta( // delta: Delta(
operations: [ // operations: [
TextInsert( // TextInsert(
text, // text,
attributes: { // attributes: {
BuiltInAttributeKey.bold: true, // BuiltInAttributeKey.bold: true,
BuiltInAttributeKey.italic: true, // BuiltInAttributeKey.italic: true,
BuiltInAttributeKey.underline: true, // BuiltInAttributeKey.underline: true,
BuiltInAttributeKey.strikethrough: true, // BuiltInAttributeKey.strikethrough: true,
}, // },
), // ),
], // ],
), // ),
); // );
await editor.startTesting(); // await editor.startTesting();
await editor.updateSelection( // await editor.updateSelection(
Selection.single(path: [0], startOffset: 0), // Selection.single(path: [0], startOffset: 0),
); // );
final selection = // final selection =
Selection.single(path: [0], startOffset: 0, endOffset: text.length); // Selection.single(path: [0], startOffset: 0, endOffset: text.length);
var node = editor.nodeAtPath([0]) as TextNode; // var node = editor.nodeAtPath([0]) as TextNode;
var state = node.key.currentState as DefaultSelectable; // var state = node.key.currentState as DefaultSelectable;
var checkboxWidget = find.byKey(state.iconKey!); // var checkboxWidget = find.byKey(state.iconKey!);
await tester.tap(checkboxWidget); // await tester.tap(checkboxWidget);
await tester.pumpAndSettle(); // await tester.pumpAndSettle();
expect(node.attributes.check, true); // expect(node.attributes.check, true);
expect(node.allSatisfyBoldInSelection(selection), true); // expect(node.allSatisfyBoldInSelection(selection), true);
expect(node.allSatisfyItalicInSelection(selection), true); // expect(node.allSatisfyItalicInSelection(selection), true);
expect(node.allSatisfyUnderlineInSelection(selection), true); // expect(node.allSatisfyUnderlineInSelection(selection), true);
expect(node.allSatisfyStrikethroughInSelection(selection), true); // expect(node.allSatisfyStrikethroughInSelection(selection), true);
node = editor.nodeAtPath([0]) as TextNode; // node = editor.nodeAtPath([0]) as TextNode;
state = node.key.currentState as DefaultSelectable; // state = node.key.currentState as DefaultSelectable;
await tester.ensureVisible(find.byKey(state.iconKey!)); // await tester.ensureVisible(find.byKey(state.iconKey!));
await tester.tap(find.byKey(state.iconKey!)); // await tester.tap(find.byKey(state.iconKey!));
await tester.pump(); // await tester.pump();
expect(node.attributes.check, false); // expect(node.attributes.check, false);
expect(node.allSatisfyBoldInSelection(selection), true); // expect(node.allSatisfyBoldInSelection(selection), true);
expect(node.allSatisfyItalicInSelection(selection), true); // expect(node.allSatisfyItalicInSelection(selection), true);
expect(node.allSatisfyUnderlineInSelection(selection), true); // expect(node.allSatisfyUnderlineInSelection(selection), true);
expect(node.allSatisfyStrikethroughInSelection(selection), true); // expect(node.allSatisfyStrikethroughInSelection(selection), true);
}); // });
// https://github.com/AppFlowy-IO/AppFlowy/issues/1763 // // https://github.com/AppFlowy-IO/AppFlowy/issues/1763
// // [Bug] Mouse unable to click a certain area #1763 // // // [Bug] Mouse unable to click a certain area #1763
testWidgets('insert a new checkbox after an existing checkbox', // testWidgets('insert a new checkbox after an existing checkbox',
(tester) async { // (tester) async {
// Before // // Before
// // //
// [checkbox] Welcome to Appflowy 😁 // // [checkbox] Welcome to Appflowy 😁
// // //
// After // // After
// // //
// [checkbox] Welcome to Appflowy 😁 // // [checkbox] Welcome to Appflowy 😁
// // //
// [checkbox] Welcome to Appflowy 😁 // // [checkbox] Welcome to Appflowy 😁
// // //
const text = 'Welcome to Appflowy 😁'; // const text = 'Welcome to Appflowy 😁';
final editor = tester.editor // final editor = tester.editor
..insertTextNode( // ..insertTextNode(
'', // '',
attributes: { // attributes: {
BuiltInAttributeKey.subtype: BuiltInAttributeKey.checkbox, // BuiltInAttributeKey.subtype: BuiltInAttributeKey.checkbox,
BuiltInAttributeKey.checkbox: false, // BuiltInAttributeKey.checkbox: false,
}, // },
delta: Delta( // delta: Delta(
operations: [TextInsert(text)], // operations: [TextInsert(text)],
), // ),
); // );
await editor.startTesting(); // await editor.startTesting();
await editor.updateSelection( // await editor.updateSelection(
Selection.single(path: [0], startOffset: text.length), // Selection.single(path: [0], startOffset: text.length),
); // );
await editor.pressLogicKey(key: LogicalKeyboardKey.enter); // await editor.pressLogicKey(key: LogicalKeyboardKey.enter);
await editor.pressLogicKey(key: LogicalKeyboardKey.enter); // await editor.pressLogicKey(key: LogicalKeyboardKey.enter);
await editor.pressLogicKey(key: LogicalKeyboardKey.enter); // await editor.pressLogicKey(key: LogicalKeyboardKey.enter);
expect( // expect(
editor.documentSelection, // editor.documentSelection,
Selection.single(path: [2], startOffset: 0), // Selection.single(path: [2], startOffset: 0),
); // );
await editor.pressLogicKey(key: LogicalKeyboardKey.slash); // await editor.pressLogicKey(key: LogicalKeyboardKey.slash);
await tester.pumpAndSettle(const Duration(milliseconds: 1000)); // await tester.pumpAndSettle(const Duration(milliseconds: 1000));
expect( // expect(
find.byType(SelectionMenuWidget, skipOffstage: false), // find.byType(SelectionMenuWidget, skipOffstage: false),
findsOneWidget, // findsOneWidget,
); // );
final checkboxMenuItem = find.text('Checkbox', findRichText: true); // final checkboxMenuItem = find.text('Checkbox', findRichText: true);
await tester.tap(checkboxMenuItem); // await tester.tap(checkboxMenuItem);
await tester.pumpAndSettle(); // await tester.pumpAndSettle();
final checkboxNode = editor.nodeAtPath([2]) as TextNode; // final checkboxNode = editor.nodeAtPath([2]) as TextNode;
expect(checkboxNode.subtype, BuiltInAttributeKey.checkbox); // expect(checkboxNode.subtype, BuiltInAttributeKey.checkbox);
}); // });
}); // });
} // }