1
0
Fork 0
mirror of https://github.com/anyproto/anytype-kotlin.git synced 2025-06-08 05:47:05 +09:00

Fix/block merge for docs containing divs (#912)

* block-merge reworked to take into account document's divs

* tests

* fixes

* updated change log

* run ci

* fix tests

* Update workflow.yml

Co-authored-by: Ivanov Konstantin <ki@agileburo.com>
Co-authored-by: Konstantin Ivanov <54908981+konstantiniiv@users.noreply.github.com>
This commit is contained in:
Evgenii Kozlov 2020-09-24 19:54:10 +03:00 committed by GitHub
parent 16cf97ea59
commit 8560b512e1
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 385 additions and 108 deletions

View file

@ -13,6 +13,7 @@
### Fixes & tech 🚒
* Block-merge operations for documents containing sections (aka divs) (#912)
* App crashes when opening action menu for link block, which was created by turning a text block into a page (#910)
* Should create a new toogle on enter press at the end of the non-empty toggle block (#907)
* Should convert toggle block to paragraph on enter press if toggle block's text is empty (#886)

View file

@ -822,18 +822,18 @@ class PageViewModel(
)
}
// TODO should take into account that previous block could be a Block.Content.Layout!
val page = blocks.first { it.id == context }
val index = page.children.indexOf(id)
val index = views.indexOfFirst { it.id == id }
if (index > 0) {
val previous = page.children[index.dec()]
proceedWithMergingBlocks(
previous = previous,
target = id
)
val previous = views[index.dec()]
if (previous is BlockView.Text) {
proceedWithMergingBlocks(
previous = previous.id,
target = id
)
} else {
Timber.d("Skipping merge because previous block is not a text block")
}
} else {
Timber.d("Skipping merge on non-empty-block-backspace-pressed event")
}

View file

@ -2143,93 +2143,6 @@ open class PageViewModelTest {
}
}
@Test
fun `should update text and proceed with merging the first paragraph with the second on non-empty-block-backspace-pressed event`() {
val root = MockDataFactory.randomUuid()
val firstChild = MockDataFactory.randomUuid()
val secondChild = MockDataFactory.randomUuid()
val thirdChild = MockDataFactory.randomUuid()
val page = MockBlockFactory.makeOnePageWithThreeTextBlocks(
root = root,
firstChild = firstChild,
secondChild = secondChild,
thirdChild = thirdChild,
firstChildStyle = Block.Content.Text.Style.TITLE,
secondChildStyle = Block.Content.Text.Style.P,
thirdChildStyle = Block.Content.Text.Style.P
)
val flow: Flow<List<Event.Command>> = flow {
delay(100)
emit(
listOf(
Event.Command.ShowBlock(
root = root,
blocks = page,
context = root
)
)
)
}
stubObserveEvents(flow)
stubOpenPage()
stubMergeBlocks(root)
stubUpdateText()
buildViewModel()
vm.onStart(root)
coroutineTestRule.advanceTime(100)
vm.onBlockFocusChanged(
id = thirdChild,
hasFocus = true
)
val text = MockDataFactory.randomString()
vm.onTextChanged(
id = thirdChild,
marks = emptyList(),
text = text
)
vm.onNonEmptyBlockBackspaceClicked(
id = thirdChild,
marks = emptyList(),
text = text
)
coroutineTestRule.advanceTime(PageViewModel.TEXT_CHANGES_DEBOUNCE_DURATION)
runBlockingTest {
verify(updateText, times(1)).invoke(
params = eq(
UpdateText.Params(
context = root,
text = text,
marks = emptyList(),
target = thirdChild
)
)
)
}
runBlockingTest {
verify(mergeBlocks, times(1)).invoke(
params = eq(
MergeBlocks.Params(
context = root,
pair = Pair(secondChild, thirdChild)
)
)
)
}
}
@Test
fun `should turn a list item with empty text into a paragraph on endline-enter-pressed event`() {
@ -3975,17 +3888,6 @@ open class PageViewModelTest {
}
}
private fun stubMergeBlocks(root: String) {
mergeBlocks.stub {
onBlocking { invoke(any()) } doReturn Either.Right(
Payload(
context = root,
events = emptyList()
)
)
}
}
fun buildViewModel(urlBuilder: UrlBuilder = builder) {
val storage = Editor.Storage()

View file

@ -0,0 +1,357 @@
package com.agileburo.anytype.presentation.page.editor
import MockDataFactory
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.agileburo.anytype.domain.block.interactor.MergeBlocks
import com.agileburo.anytype.domain.block.interactor.UpdateText
import com.agileburo.anytype.domain.block.model.Block
import com.agileburo.anytype.domain.ext.content
import com.agileburo.anytype.presentation.MockBlockFactory
import com.agileburo.anytype.presentation.page.PageViewModel
import com.agileburo.anytype.presentation.util.CoroutinesTestRule
import com.nhaarman.mockitokotlin2.eq
import com.nhaarman.mockitokotlin2.times
import com.nhaarman.mockitokotlin2.verifyBlocking
import com.nhaarman.mockitokotlin2.verifyZeroInteractions
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mockito.MockitoAnnotations
class EditorMergeTest : EditorPresentationTestSetup() {
@get:Rule
val rule = InstantTaskExecutorRule()
@get:Rule
val coroutineTestRule = CoroutinesTestRule()
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
}
@Test
fun `should update text and proceed with merging the first paragraph with the second on non-empty-block-backspace-pressed event`() {
val firstChild = MockDataFactory.randomUuid()
val secondChild = MockDataFactory.randomUuid()
val thirdChild = MockDataFactory.randomUuid()
val page = MockBlockFactory.makeOnePageWithThreeTextBlocks(
root = root,
firstChild = firstChild,
secondChild = secondChild,
thirdChild = thirdChild,
firstChildStyle = Block.Content.Text.Style.TITLE,
secondChildStyle = Block.Content.Text.Style.P,
thirdChildStyle = Block.Content.Text.Style.P
)
stubInterceptEvents()
stubOpenDocument(page)
stubMergeBlocks(root)
stubUpdateText()
val vm = buildViewModel()
vm.onStart(root)
vm.onBlockFocusChanged(
id = thirdChild,
hasFocus = true
)
val text = MockDataFactory.randomString()
vm.onTextChanged(
id = thirdChild,
marks = emptyList(),
text = text
)
vm.onNonEmptyBlockBackspaceClicked(
id = thirdChild,
marks = emptyList(),
text = text
)
coroutineTestRule.advanceTime(PageViewModel.TEXT_CHANGES_DEBOUNCE_DURATION)
verifyBlocking(updateText, times(1)) {
invoke(
params = eq(
UpdateText.Params(
context = root,
text = text,
marks = emptyList(),
target = thirdChild
)
)
)
}
verifyBlocking(mergeBlocks, times(1)) {
invoke(
params = eq(
MergeBlocks.Params(
context = root,
pair = Pair(secondChild, thirdChild)
)
)
)
}
}
@Test
fun `should merge two text blocks from two different divs`() {
// SETUP
val a = Block(
id = MockDataFactory.randomUuid(),
fields = Block.Fields(emptyMap()),
content = Block.Content.Text(
text = MockDataFactory.randomString(),
marks = emptyList(),
style = Block.Content.Text.Style.P
),
children = emptyList()
)
val b = Block(
id = MockDataFactory.randomUuid(),
fields = Block.Fields(emptyMap()),
content = Block.Content.Text(
text = MockDataFactory.randomString(),
marks = emptyList(),
style = Block.Content.Text.Style.P
),
children = emptyList()
)
val div1 = Block(
id = MockDataFactory.randomUuid(),
fields = Block.Fields(emptyMap()),
children = listOf(a.id),
content = Block.Content.Layout(
type = Block.Content.Layout.Type.DIV
)
)
val div2 = Block(
id = MockDataFactory.randomUuid(),
fields = Block.Fields(emptyMap()),
children = listOf(b.id),
content = Block.Content.Layout(
type = Block.Content.Layout.Type.DIV
)
)
val page = Block(
id = root,
fields = Block.Fields(emptyMap()),
content = Block.Content.Page(
style = Block.Content.Page.Style.SET
),
children = listOf(div1.id, div2.id)
)
val doc = listOf(page, div1, div2, a, b)
stubOpenDocument(doc)
stubInterceptEvents()
stubUpdateText()
stubMergeBlocks(root)
val vm = buildViewModel()
vm.onStart(root)
vm.onBlockFocusChanged(b.id, true)
vm.onNonEmptyBlockBackspaceClicked(
id = b.id,
text = b.content<Block.Content.Text>().text,
marks = b.content<Block.Content.Text>().marks
)
coroutineTestRule.advanceTime(PageViewModel.TEXT_CHANGES_DEBOUNCE_DURATION)
verifyBlocking(mergeBlocks, times(1)) {
invoke(
params = eq(
MergeBlocks.Params(
context = root,
pair = Pair(a.id, b.id)
)
)
)
}
}
@Test
fun `should merge two text blocks from the first of two divs`() {
// SETUP
val a = Block(
id = MockDataFactory.randomUuid(),
fields = Block.Fields(emptyMap()),
content = Block.Content.Text(
text = MockDataFactory.randomString(),
marks = emptyList(),
style = Block.Content.Text.Style.P
),
children = emptyList()
)
val b = Block(
id = MockDataFactory.randomUuid(),
fields = Block.Fields(emptyMap()),
content = Block.Content.Text(
text = MockDataFactory.randomString(),
marks = emptyList(),
style = Block.Content.Text.Style.P
),
children = emptyList()
)
val c = Block(
id = MockDataFactory.randomUuid(),
fields = Block.Fields(emptyMap()),
content = Block.Content.Text(
text = MockDataFactory.randomString(),
marks = emptyList(),
style = Block.Content.Text.Style.P
),
children = emptyList()
)
val d = Block(
id = MockDataFactory.randomUuid(),
fields = Block.Fields(emptyMap()),
content = Block.Content.Text(
text = MockDataFactory.randomString(),
marks = emptyList(),
style = Block.Content.Text.Style.P
),
children = emptyList()
)
val div1 = Block(
id = MockDataFactory.randomUuid(),
fields = Block.Fields(emptyMap()),
children = listOf(a.id, b.id),
content = Block.Content.Layout(
type = Block.Content.Layout.Type.DIV
)
)
val div2 = Block(
id = MockDataFactory.randomUuid(),
fields = Block.Fields(emptyMap()),
children = listOf(c.id, d.id),
content = Block.Content.Layout(
type = Block.Content.Layout.Type.DIV
)
)
val page = Block(
id = root,
fields = Block.Fields(emptyMap()),
content = Block.Content.Page(
style = Block.Content.Page.Style.SET
),
children = listOf(div1.id, div2.id)
)
val doc = listOf(page, div1, div2, a, b, c, d)
stubOpenDocument(doc)
stubInterceptEvents()
stubUpdateText()
stubMergeBlocks(root)
val vm = buildViewModel()
vm.onStart(root)
vm.onBlockFocusChanged(d.id, true)
vm.onNonEmptyBlockBackspaceClicked(
id = d.id,
text = d.content<Block.Content.Text>().text,
marks = d.content<Block.Content.Text>().marks
)
coroutineTestRule.advanceTime(PageViewModel.TEXT_CHANGES_DEBOUNCE_DURATION)
verifyBlocking(mergeBlocks, times(1)) {
invoke(
params = eq(
MergeBlocks.Params(
context = root,
pair = Pair(c.id, d.id)
)
)
)
}
}
@Test
fun `should not merge text block with the previous block if this previous block is not a text block`() {
// SETUP
val a = Block(
id = MockDataFactory.randomUuid(),
fields = Block.Fields(emptyMap()),
content = Block.Content.Divider,
children = emptyList()
)
val b = Block(
id = MockDataFactory.randomUuid(),
fields = Block.Fields(emptyMap()),
content = Block.Content.Text(
text = MockDataFactory.randomString(),
marks = emptyList(),
style = Block.Content.Text.Style.P
),
children = emptyList()
)
val page = Block(
id = root,
fields = Block.Fields(emptyMap()),
content = Block.Content.Page(
style = Block.Content.Page.Style.SET
),
children = listOf(a.id, b.id)
)
val doc = listOf(page, a, b)
stubOpenDocument(doc)
stubInterceptEvents()
stubUpdateText()
stubMergeBlocks(root)
val vm = buildViewModel()
vm.onStart(root)
vm.onBlockFocusChanged(b.id, true)
vm.onNonEmptyBlockBackspaceClicked(
id = b.id,
text = b.content<Block.Content.Text>().text,
marks = b.content<Block.Content.Text>().marks
)
coroutineTestRule.advanceTime(PageViewModel.TEXT_CHANGES_DEBOUNCE_DURATION)
verifyZeroInteractions(mergeBlocks)
}
}

View file

@ -295,4 +295,21 @@ open class EditorPresentationTestSetup {
)
}
}
fun stubMergeBlocks(root: String) {
mergeBlocks.stub {
onBlocking { invoke(any()) } doReturn Either.Right(
Payload(
context = root,
events = emptyList()
)
)
}
}
fun stubUpdateText() {
updateText.stub {
onBlocking { invoke(any()) } doReturn Either.Right(Unit)
}
}
}