Skip to content

Instantly share code, notes, and snippets.

@jourdanrodrigues
Last active June 8, 2024 23:38
Show Gist options
  • Save jourdanrodrigues/574b7874ba1c6c3c11358c2fd44769d3 to your computer and use it in GitHub Desktop.
Save jourdanrodrigues/574b7874ba1c6c3c11358c2fd44769d3 to your computer and use it in GitHub Desktop.
Diff algorithm for comparison between 2 SlateJS content objects
from typing import List, Iterable, Mapping
from diff_match_patch import DiffMatchPatch # https://github.com/google/diff-match-patch/blob/master/python3/diff_match_patch.py
def _index_or_none(items: list, index: int):
try:
return items[index]
except IndexError:
return None
def _is_iterable(value) -> bool:
return isinstance(value, Iterable) and not isinstance(value, (str, bytes))
class Block(dict):
__nodes = None
__hash = None
# Using tuples for it to be immutable
__object_nodes_map = (
('block', 'nodes'),
('document', 'nodes'),
('text', 'leaves'),
('inline', 'nodes'),
)
def __setitem__(self, key, value):
raise NotImplementedError
def update(self, __m: Mapping, **kwargs) -> None:
raise NotImplementedError
def __hash__(self):
if not self.__hash:
items = ((key, tuple(value) if _is_iterable(value) else value) for key, value in self.items())
self.__hash = hash(tuple(sorted(items)))
return self.__hash
@classmethod
def from_dict(cls, dict_: Mapping, **kwargs):
result = kwargs.copy()
for key, value in dict_.items():
extra = {} if key in ['data', 'marks'] else kwargs
if isinstance(value, Mapping):
result[key] = cls.from_dict(value, **extra)
elif _is_iterable(value):
result[key] = type(value)(cls.from_dict(item, **extra) for item in value)
else:
result[key] = value
return cls(**result)
def have_same_attributes(self, block: 'Block') -> bool:
return (
self['object'] == block['object'] and
self.get('type') == block.get('type') and
self.get('data') == block.get('data') and
self.get('marks') == block.get('marks') and
self.get('text') == block.get('text')
)
def has_different_text(self, block: 'Block') -> bool:
return self.get('text') != block.get('text') and self.get('marks') == block.get('marks')
def get_nodes(self) -> List[dict] or None:
if self.__nodes is None:
self.__nodes = self.get(self.get_nodes_key())
return self.__nodes
def get_nodes_key(self) -> List[dict] or None:
return dict(self.__object_nodes_map).get(self['object'])
class ContentDiffer(dict):
def __init__(self, old: dict, new: dict):
old_ = Block.from_dict(old)
new_ = Block.from_dict(new)
document = self._diff_document_root(old_['document'], new_['document'])
super().__init__(new_, document=document)
@classmethod
def _diff_document_root(cls, old: Block, new: Block) -> Block:
return new if old == new else Block(new, nodes=cls._get_nodes_diff(old.get_nodes(), new.get_nodes()))
@classmethod
def _get_nodes_diff(cls, old_nodes: List[Block], new_nodes: List[Block]) -> List[Block]:
if old_nodes == new_nodes: # Shortcut
return new_nodes
old_i = 0
new_i = 0
result_nodes = []
while old_i < len(old_nodes) or new_i < len(new_nodes):
# Without type notation, Block's methods aren't discovered
old_node: Block = _index_or_none(old_nodes, old_i)
new_node: Block = _index_or_none(new_nodes, new_i)
if None not in [old_node, new_node] and old_node == new_node:
result_nodes.append(new_node)
elif old_node is None:
result_nodes.append(Block.from_dict(new_node, isAdded=True))
elif new_node is None:
result_nodes.append(Block.from_dict(old_node, isDeleted=True))
else:
if old_node in new_nodes or new_node in old_nodes:
if new_node in old_nodes[old_i:]:
if cls._has_item_in_between(new_nodes, new_i, old_nodes, old_i):
result_nodes.append(Block.from_dict(new_node, isAdded=True))
new_i += 1
else:
result_nodes.append(Block.from_dict(old_node, isDeleted=True))
old_i += 1
continue
elif old_node in new_nodes[new_i:]:
if cls._has_item_in_between(old_nodes, old_i, new_nodes, new_i):
result_nodes.append(Block.from_dict(old_node, isDeleted=True))
old_i += 1
else:
result_nodes.append(Block.from_dict(new_node, isAdded=True))
new_i += 1
continue
if not old_node.have_same_attributes(new_node):
if old_node.has_different_text(new_node):
result_nodes += cls._get_text_diff_nodes(old_node, new_node)
else:
result_nodes += [
Block.from_dict(new_node, isAdded=True),
Block.from_dict(old_node, isDeleted=True),
]
elif old_node.get_nodes() != new_node.get_nodes():
new_inner_nodes = cls._get_nodes_diff(old_node.get_nodes(), new_node.get_nodes())
nodes_key = new_node.get_nodes_key()
result_nodes.append(Block(new_node, **{nodes_key: new_inner_nodes}))
old_i += 1
new_i += 1
return result_nodes
@staticmethod
def _has_item_in_between(source_nodes, source_i, target_nodes, target_i) -> bool:
target_limit = target_nodes.index(source_nodes[source_i])
# i + 1 to ignore current nodes
return bool(set(source_nodes[source_i + 1:]).intersection(target_nodes[target_i + 1:target_limit]))
@classmethod
def _get_text_diff_nodes(cls, old_node: Block, new_node: Block) -> List[Block]:
dmp = DiffMatchPatch()
dmp.Diff_EditCost = 7
# "new_node" comes first for "added" to come first
diff = dmp.diff_main(new_node['text'], old_node['text'])
dmp.diff_cleanupEfficiency(diff)
return [
Block(new_node, text=text, **cls._get_flag_from_diff_index(diff_index))
for diff_index, text in diff
]
@staticmethod
def _get_flag_from_diff_index(index: int) -> dict:
# Switched for "added" to come first (see comment in ContentDiffer._get_text_diff_nodes)
if index == DiffMatchPatch.DIFF_EQUAL:
return {}
elif index == DiffMatchPatch.DIFF_INSERT:
return {'isDeleted': True}
elif index == DiffMatchPatch.DIFF_DELETE:
return {'isAdded': True}
from typing import List
from unittest import TestCase
from content_differ import ContentDiffer
class TestInit(TestCase):
maxDiff = None
def test_when_text_changed_completely_then_returns_added_and_deleted(self):
def generate_document(*, paragraph_text: str) -> dict:
return {
'object': 'value',
'document': {
'object': 'document',
'data': {},
'nodes': [{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': paragraph_text, 'marks': []}]
}]
}]
}
}
old_text = 'some normal text'
new_text = 'completely different string'
old = generate_document(paragraph_text=old_text)
new = generate_document(paragraph_text=new_text)
output = ContentDiffer(old, new)
self.assertDictEqual(output, {
'object': 'value',
'document': {
'object': 'document',
'data': {},
'nodes': [{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [
{'object': 'leaf', 'text': new_text, 'marks': [], 'isAdded': True},
{'object': 'leaf', 'text': old_text, 'marks': [], 'isDeleted': True},
],
}],
}],
},
})
def test_when_text_changed_partially_then_returns_correct_output(self):
def generate_document(*, paragraph_text: str) -> dict:
return {
'object': 'value',
'document': {
'object': 'document',
'data': {},
'nodes': [{
'type': 'paragraph',
'object': 'block',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': paragraph_text, 'marks': []}]
}]
}]
}
}
old = generate_document(paragraph_text='some normal text')
new = generate_document(paragraph_text='some updated text')
output = ContentDiffer(old, new)
self.assertDictEqual(output, {
'object': 'value',
'document': {
'object': 'document',
'data': {},
'nodes': [{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [
{'object': 'leaf', 'text': 'some ', 'marks': []},
{'object': 'leaf', 'text': 'updated', 'marks': [], 'isAdded': True},
{'object': 'leaf', 'text': 'normal', 'marks': [], 'isDeleted': True},
{'object': 'leaf', 'text': ' text', 'marks': []},
],
}],
}],
},
})
def test_when_marks_are_changed_then_label_the_block_as_changed(self):
text = 'some text'
def generate_document(*, marks: List[dict] = None) -> dict:
return {
'object': 'value',
'document': {
'object': 'document',
'data': {},
'nodes': [{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': text, 'marks': marks or []}]
}]
}]
}
}
old_marks = [{'object': 'mark', 'type': 'strong', 'data': {}}]
old = generate_document(marks=old_marks)
new = generate_document()
output = ContentDiffer(old, new)
self.assertDictEqual(output, {
'object': 'value',
'document': {
'object': 'document',
'data': {},
'nodes': [{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [
{'object': 'leaf', 'text': text, 'marks': [], 'isAdded': True},
{'object': 'leaf', 'text': text, 'marks': old_marks, 'isDeleted': True},
],
}],
}],
},
})
def test_when_block_is_added_then_returns_correct_output(self):
def generate_document(*, paragraphs: int):
return {
'object': 'value',
'document': {
'object': 'document',
'data': {},
'nodes': [{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': f'Paragraph {i + 1}', 'marks': []}],
}],
} for i in range(paragraphs)],
},
}
old = generate_document(paragraphs=1)
new = generate_document(paragraphs=2)
output = ContentDiffer(old, new)
self.assertDictEqual(output, {
'object': 'value',
'document': {
'object': 'document',
'data': {},
'nodes': [
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'Paragraph 1', 'marks': []}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'isAdded': True,
'data': {},
'nodes': [{
'object': 'text',
'isAdded': True,
'leaves': [{'object': 'leaf', 'isAdded': True, 'text': 'Paragraph 2', 'marks': []}],
}],
},
],
},
})
def test_when_block_is_removed_then_returns_correct_output(self):
def generate_document(*, paragraphs: int):
return {
'object': 'value',
'document': {
'data': {},
'object': 'document',
'nodes': [{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': f'Paragraph {i + 1}', 'marks': []}],
}],
} for i in range(paragraphs)],
},
}
old = generate_document(paragraphs=2)
new = generate_document(paragraphs=1)
output = ContentDiffer(old, new)
self.assertDictEqual(output, {
'object': 'value',
'document': {
'object': 'document',
'data': {},
'nodes': [
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'Paragraph 1', 'marks': []}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'isDeleted': True,
'data': {},
'nodes': [{
'object': 'text',
'isDeleted': True,
'leaves': [{'object': 'leaf', 'isDeleted': True, 'text': 'Paragraph 2', 'marks': []}],
}],
},
],
},
})
def test_when_nested_block_is_removed_then_returns_correct_output(self):
old = {
'object': 'value',
'document': {
'object': 'document',
'data': {},
'nodes': [
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'My first paragraph', 'marks': []}],
}],
},
{
'object': 'block',
'type': 'bulleted-list',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'block',
'type': 'list-item',
'isVoid': False,
'data': {},
'nodes': [
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'my item 1', 'marks': []}],
}],
},
{
'object': 'block',
'type': 'bulleted-list',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'block',
'type': 'list-item',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'my item 1.1', 'marks': []}],
}],
}],
}],
},
],
}],
},
],
},
}
new = {
'object': 'value',
'document': {
'object': 'document',
'data': {},
'nodes': [{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'My first paragraph', 'marks': []}],
}],
}],
},
}
output = ContentDiffer(old, new)
self.assertDictEqual(output, {
'object': 'value',
'document': {
'object': 'document',
'data': {},
'nodes': [
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'My first paragraph', 'marks': []}],
}],
},
{
'object': 'block',
'type': 'bulleted-list',
'isVoid': False,
'isDeleted': True,
'data': {},
'nodes': [{
'object': 'block',
'type': 'list-item',
'isVoid': False,
'isDeleted': True,
'data': {},
'nodes': [
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'isDeleted': True,
'data': {},
'nodes': [{
'object': 'text',
'isDeleted': True,
'leaves': [{
'object': 'leaf',
'text': 'my item 1',
'marks': [],
'isDeleted': True,
}],
}],
},
{
'object': 'block',
'type': 'bulleted-list',
'isVoid': False,
'isDeleted': True,
'data': {},
'nodes': [{
'object': 'block',
'type': 'list-item',
'isVoid': False,
'isDeleted': True,
'data': {},
'nodes': [{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'isDeleted': True,
'data': {},
'nodes': [{
'object': 'text',
'isDeleted': True,
'leaves': [{
'object': 'leaf',
'text': 'my item 1.1',
'marks': [],
'isDeleted': True,
}],
}],
}],
}],
},
],
}],
},
],
},
})
def test_when_middle_block_is_removed_then_returns_correct_output(self):
old = {
'object': 'value',
'document': {
'object': 'document',
'data': {},
'nodes': [
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'First paragraph', 'marks': []}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'Second paragraph', 'marks': []}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'Third paragraph', 'marks': []}],
}],
},
],
},
}
new = {
'object': 'value',
'document': {
'object': 'document',
'data': {},
'nodes': [
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'First paragraph', 'marks': []}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'Third paragraph', 'marks': []}],
}],
},
],
},
}
output = ContentDiffer(old, new)
self.assertDictEqual(output, {
'object': 'value',
'document': {
'object': 'document',
'data': {},
'nodes': [
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'First paragraph', 'marks': []}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'isDeleted': True,
'nodes': [{
'object': 'text',
'isDeleted': True,
'leaves': [{'object': 'leaf', 'text': 'Second paragraph', 'marks': [], 'isDeleted': True}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'Third paragraph', 'marks': []}],
}],
},
],
},
})
def test_when_multiple_middle_blocks_are_removed_then_returns_correct_output(self):
old = {
'object': 'value',
'document': {
'object': 'document',
'data': {},
'nodes': [
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'First paragraph', 'marks': []}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'Second paragraph', 'marks': []}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'Third paragraph', 'marks': []}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'Fourth paragraph', 'marks': []}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'Fifth paragraph', 'marks': []}],
}],
},
],
},
}
new = {
'object': 'value',
'document': {
'object': 'document',
'data': {},
'nodes': [
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'First paragraph', 'marks': []}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'Third paragraph', 'marks': []}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'Fifth paragraph', 'marks': []}],
}],
},
],
},
}
output = ContentDiffer(old, new)
self.assertDictEqual(output, {
'object': 'value',
'document': {
'object': 'document',
'data': {},
'nodes': [
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'First paragraph', 'marks': []}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'isDeleted': True,
'nodes': [{
'object': 'text',
'isDeleted': True,
'leaves': [{'object': 'leaf', 'text': 'Second paragraph', 'marks': [], 'isDeleted': True}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'Third paragraph', 'marks': []}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'isDeleted': True,
'nodes': [{
'object': 'text',
'isDeleted': True,
'leaves': [{'object': 'leaf', 'text': 'Fourth paragraph', 'marks': [], 'isDeleted': True}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'Fifth paragraph', 'marks': []}],
}],
},
],
},
})
def test_when_middle_block_is_added_then_returns_correct_output(self):
old = {
'object': 'value',
'document': {
'data': {},
'object': 'document',
'nodes': [
{
'object': 'block',
'type': 'paragraph',
'data': {},
'isVoid': False,
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'First paragraph', 'marks': []}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'Third paragraph', 'marks': []}],
}],
},
],
},
}
new = {
'object': 'value',
'document': {
'data': {},
'object': 'document',
'nodes': [
{
'object': 'block',
'type': 'paragraph',
'data': {},
'isVoid': False,
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'First paragraph', 'marks': []}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'Second paragraph', 'marks': []}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'Third paragraph', 'marks': []}],
}],
},
],
},
}
output = ContentDiffer(old, new)
self.assertDictEqual(output, {
'object': 'value',
'document': {
'object': 'document',
'data': {},
'nodes': [
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'First paragraph', 'marks': []}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'isAdded': True,
'nodes': [{
'object': 'text',
'isAdded': True,
'leaves': [{'object': 'leaf', 'text': 'Second paragraph', 'marks': [], 'isAdded': True}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'Third paragraph', 'marks': []}],
}],
},
],
},
})
def test_when_multiple_middle_blocks_are_added_then_returns_correct_output(self):
old = {
'object': 'value',
'document': {
'data': {},
'object': 'document',
'nodes': [
{
'object': 'block',
'type': 'paragraph',
'data': {},
'isVoid': False,
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'First paragraph', 'marks': []}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'Third paragraph', 'marks': []}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'Fifth paragraph', 'marks': []}],
}],
},
],
},
}
new = {
'object': 'value',
'document': {
'data': {},
'object': 'document',
'nodes': [
{
'object': 'block',
'type': 'paragraph',
'data': {},
'isVoid': False,
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'First paragraph', 'marks': []}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'Second paragraph', 'marks': []}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'Third paragraph', 'marks': []}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'Fourth paragraph', 'marks': []}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'Fifth paragraph', 'marks': []}],
}],
},
],
},
}
output = ContentDiffer(old, new)
self.assertDictEqual(output, {
'object': 'value',
'document': {
'object': 'document',
'data': {},
'nodes': [
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'First paragraph', 'marks': []}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'isAdded': True,
'nodes': [{
'object': 'text',
'isAdded': True,
'leaves': [{'object': 'leaf', 'text': 'Second paragraph', 'marks': [], 'isAdded': True}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'Third paragraph', 'marks': []}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'isAdded': True,
'nodes': [{
'object': 'text',
'isAdded': True,
'leaves': [{'object': 'leaf', 'text': 'Fourth paragraph', 'marks': [], 'isAdded': True}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'Fifth paragraph', 'marks': []}],
}],
},
],
},
})
def test_when_block_is_moved_backwards_then_returns_correct_output(self):
old = {
'object': 'value',
'document': {
'object': 'document',
'data': {},
'nodes': [
{
'object': 'block',
'isVoid': False,
'type': 'paragraph',
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'First paragraph', 'marks': []}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'Second paragraph', 'marks': []}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'Third paragraph', 'marks': []}],
}],
},
],
},
}
new = {
'object': 'value',
'document': {
'object': 'document',
'data': {},
'nodes': [
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'Third paragraph', 'marks': []}]
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'First paragraph', 'marks': []}]
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'Second paragraph', 'marks': []}],
}],
},
],
},
}
output = ContentDiffer(old, new)
self.assertDictEqual(output, {
'object': 'value',
'document': {
'object': 'document',
'data': {},
'nodes': [
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'isAdded': True,
'data': {},
'nodes': [{
'object': 'text',
'isAdded': True,
'leaves': [{'object': 'leaf', 'text': 'Third paragraph', 'marks': [], 'isAdded': True}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'First paragraph', 'marks': []}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'Second paragraph', 'marks': []}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'isDeleted': True,
'data': {},
'nodes': [{
'object': 'text',
'isDeleted': True,
'leaves': [
{'object': 'leaf', 'text': 'Third paragraph', 'marks': [], 'isDeleted': True},
],
}],
},
],
},
})
def test_when_block_is_moved_forwards_then_returns_correct_output(self):
old = {
'object': 'value',
'document': {
'object': 'document',
'data': {},
'nodes': [
{
'isVoid': False,
'object': 'block',
'type': 'paragraph',
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'First paragraph', 'marks': []}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'Second paragraph', 'marks': []}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'Third paragraph', 'marks': []}],
}],
},
],
},
}
new = {
'object': 'value',
'document': {
'object': 'document',
'data': {},
'nodes': [
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'Second paragraph', 'marks': []}],
}],
},
{
'type': 'paragraph',
'object': 'block',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'Third paragraph', 'marks': []}]
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'First paragraph', 'marks': []}]
}],
},
],
},
}
output = ContentDiffer(old, new)
self.assertDictEqual(output, {
'object': 'value',
'document': {
'object': 'document',
'data': {},
'nodes': [
{
'object': 'block',
'type': 'paragraph',
'isDeleted': True,
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'isDeleted': True,
'leaves': [{'object': 'leaf', 'text': 'First paragraph', 'marks': [], 'isDeleted': True}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [{'object': 'leaf', 'text': 'Second paragraph', 'marks': []}],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'leaves': [
{'object': 'leaf', 'text': 'Third paragraph', 'marks': []},
],
}],
},
{
'object': 'block',
'type': 'paragraph',
'isAdded': True,
'isVoid': False,
'data': {},
'nodes': [{
'object': 'text',
'isAdded': True,
'leaves': [
{'object': 'leaf', 'text': 'First paragraph', 'marks': [], 'isAdded': True},
],
}],
},
],
},
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment