test: operation.dart

This commit is contained in:
Lucas.Xu 2022-10-10 23:33:58 +08:00
parent 19bf8e3b7a
commit b5e9bf6ee3
3 changed files with 165 additions and 6 deletions

View File

@ -4,8 +4,8 @@ import 'package:flutter/material.dart';
import 'package:appflowy_editor/src/core/document/attributes.dart';
import 'package:appflowy_editor/src/core/document/path.dart';
import 'package:appflowy_editor/src/core/legacy/built_in_attribute_keys.dart';
import 'package:appflowy_editor/src/core/document/text_delta.dart';
import 'package:appflowy_editor/src/core/legacy/built_in_attribute_keys.dart';
class Node extends ChangeNotifier with LinkedListEntry<Node> {
Node({
@ -276,3 +276,26 @@ class TextNode extends Node {
String toPlainText() => _delta.toPlainText();
}
extension NodeEquality on Iterable<Node> {
bool equals(Iterable<Node> other) {
if (length != other.length) {
return false;
}
for (var i = 0; i < length; i++) {
if (!_nodeEquals(elementAt(i), other.elementAt(i))) {
return false;
}
}
return true;
}
bool _nodeEquals<T, U>(T base, U other) {
if (identical(this, other)) return true;
return base is Node &&
other is Node &&
other.type == base.type &&
other.children.equals(base.children);
}
}

View File

@ -1,3 +1,5 @@
import 'package:flutter/foundation.dart';
import 'package:appflowy_editor/src/core/document/attributes.dart';
import 'package:appflowy_editor/src/core/document/node.dart';
import 'package:appflowy_editor/src/core/document/path.dart';
@ -33,7 +35,9 @@ class InsertOperation extends Operation {
factory InsertOperation.fromJson(Map<String, dynamic> json) {
final path = json['path'] as Path;
final nodes = (json['nodes'] as List).map((n) => Node.fromJson(n));
final nodes = (json['nodes'] as List)
.map((n) => Node.fromJson(n))
.toList(growable: false);
return InsertOperation(path, nodes);
}
@ -47,7 +51,7 @@ class InsertOperation extends Operation {
return {
'op': 'insert',
'path': path,
'nodes': nodes.map((n) => n.toJson()),
'nodes': nodes.map((n) => n.toJson()).toList(growable: false),
};
}
@ -55,6 +59,18 @@ class InsertOperation extends Operation {
Operation copyWith({Path? path}) {
return InsertOperation(path ?? this.path, nodes);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is InsertOperation &&
other.path.equals(path) &&
other.nodes.equals(nodes);
}
@override
int get hashCode => path.hashCode ^ Object.hashAll(nodes);
}
/// [DeleteOperation] represents a delete operation.
@ -66,7 +82,9 @@ class DeleteOperation extends Operation {
factory DeleteOperation.fromJson(Map<String, dynamic> json) {
final path = json['path'] as Path;
final nodes = (json['nodes'] as List).map((n) => Node.fromJson(n));
final nodes = (json['nodes'] as List)
.map((n) => Node.fromJson(n))
.toList(growable: false);
return DeleteOperation(path, nodes);
}
@ -80,7 +98,7 @@ class DeleteOperation extends Operation {
return {
'op': 'delete',
'path': path,
'nodes': nodes.map((n) => n.toJson()),
'nodes': nodes.map((n) => n.toJson()).toList(growable: false),
};
}
@ -88,6 +106,18 @@ class DeleteOperation extends Operation {
Operation copyWith({Path? path}) {
return DeleteOperation(path ?? this.path, nodes);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is DeleteOperation &&
other.path.equals(path) &&
other.nodes.equals(nodes);
}
@override
int get hashCode => path.hashCode ^ Object.hashAll(nodes);
}
/// [UpdateOperation] represents an attributes update operation.
@ -137,6 +167,20 @@ class UpdateOperation extends Operation {
{...oldAttributes},
);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is UpdateOperation &&
other.path.equals(path) &&
mapEquals(other.attributes, attributes) &&
mapEquals(other.oldAttributes, oldAttributes);
}
@override
int get hashCode =>
path.hashCode ^ attributes.hashCode ^ oldAttributes.hashCode;
}
/// [UpdateTextOperation] represents a text update operation.
@ -150,7 +194,7 @@ class UpdateTextOperation extends Operation {
factory UpdateTextOperation.fromJson(Map<String, dynamic> json) {
final path = json['path'] as Path;
final delta = Delta.fromJson(json['delta']);
final inverted = Delta.fromJson(json['invert']);
final inverted = Delta.fromJson(json['inverted']);
return UpdateTextOperation(path, delta, inverted);
}
@ -174,6 +218,19 @@ class UpdateTextOperation extends Operation {
Operation copyWith({Path? path}) {
return UpdateTextOperation(path ?? this.path, delta, inverted);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is UpdateTextOperation &&
other.path.equals(path) &&
other.delta == delta &&
other.inverted == inverted;
}
@override
int get hashCode => delta.hashCode ^ inverted.hashCode;
}
// TODO(Lucas.Xu): refactor this part

View File

@ -0,0 +1,79 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter_test/flutter_test.dart';
void main() async {
group('operation.dart', () {
test('test insert operation', () {
final node = Node(type: 'example');
final op = InsertOperation([0], [node]);
final json = op.toJson();
expect(json, {
'op': 'insert',
'path': [0],
'nodes': [
{
'type': 'example',
}
]
});
expect(InsertOperation.fromJson(json), op);
expect(op.invert().invert(), op);
expect(op.copyWith(), op);
});
test('test update operation', () {
final op = UpdateOperation([0], {'a': 1}, {'a': 0});
final json = op.toJson();
expect(json, {
'op': 'update',
'path': [0],
'attributes': {'a': 1},
'oldAttributes': {'a': 0}
});
expect(UpdateOperation.fromJson(json), op);
expect(op.invert().invert(), op);
expect(op.copyWith(), op);
});
test('test delete operation', () {
final node = Node(type: 'example');
final op = DeleteOperation([0], [node]);
final json = op.toJson();
expect(json, {
'op': 'delete',
'path': [0],
'nodes': [
{
'type': 'example',
}
]
});
expect(DeleteOperation.fromJson(json), op);
expect(op.invert().invert(), op);
expect(op.copyWith(), op);
});
test('test update text operation', () {
final app = Delta()..insert('App');
final appflowy = Delta()
..retain(3)
..insert('Flowy');
final op = UpdateTextOperation([0], app, appflowy.invert(app));
final json = op.toJson();
expect(json, {
'op': 'update_text',
'path': [0],
'delta': [
{'insert': 'App'}
],
'inverted': [
{'retain': 3},
{'delete': 5}
]
});
expect(UpdateTextOperation.fromJson(json), op);
expect(op.invert().invert(), op);
expect(op.copyWith(), op);
});
});
}