diff --git a/CHANGELOG.md b/CHANGELOG.md index ecaa81ac89..eea1c79e5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ ### Fixes & tech 🚒 +* 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) * When creating a new document and focusing its title, cursor should be visible (#903) * Should not crash Android client when changing media block's background color on Desktop client (#814) * Stretched background cover affects app's performance on home dashboard screen (#901) diff --git a/presentation/src/main/java/com/agileburo/anytype/presentation/page/PageViewModel.kt b/presentation/src/main/java/com/agileburo/anytype/presentation/page/PageViewModel.kt index 97d5fc64eb..1125a5ae6f 100644 --- a/presentation/src/main/java/com/agileburo/anytype/presentation/page/PageViewModel.kt +++ b/presentation/src/main/java/com/agileburo/anytype/presentation/page/PageViewModel.kt @@ -761,7 +761,7 @@ class PageViewModel( replacement = { old -> old.copy(content = content) } ) { block -> block.id == id } - if (content.isList()) { + if (content.isList() || content.isToggle()) { handleEndlineEnterPressedEventForListItem(content, id) } else { proceedWithCreatingNewTextBlock( diff --git a/presentation/src/test/java/com/agileburo/anytype/presentation/page/PageViewModelTest.kt b/presentation/src/test/java/com/agileburo/anytype/presentation/page/PageViewModelTest.kt index e848295860..de7458d1fd 100644 --- a/presentation/src/test/java/com/agileburo/anytype/presentation/page/PageViewModelTest.kt +++ b/presentation/src/test/java/com/agileburo/anytype/presentation/page/PageViewModelTest.kt @@ -2077,262 +2077,6 @@ open class PageViewModelTest { } } - @Test - fun `should start creating a new bulleted-list item on endline-enter-pressed event inside a bullet block`() { - - val style = Block.Content.Text.Style.BULLET - val root = MockDataFactory.randomUuid() - val child = MockDataFactory.randomUuid() - - val page = MockBlockFactory.makeOnePageWithOneTextBlock( - root = root, - child = child, - style = style - ) - - val flow: Flow> = flow { - delay(100) - emit( - listOf( - Event.Command.ShowBlock( - root = root, - blocks = page, - context = root - ) - ) - ) - } - - stubObserveEvents(flow) - stubOpenPage() - stubCreateBlock(root) - buildViewModel() - - vm.onStart(root) - - coroutineTestRule.advanceTime(100) - - vm.onBlockFocusChanged( - id = child, - hasFocus = true - ) - - vm.onEndLineEnterClicked( - id = child, - text = page.last().content().text, - marks = emptyList() - ) - - runBlockingTest { - verify(createBlock, times(1)).invoke( - params = eq( - CreateBlock.Params( - context = root, - target = child, - prototype = Block.Prototype.Text( - style = style - ), - position = Position.BOTTOM - ) - ) - ) - } - } - - @Test - fun `should start creating a new checkbox item on endline-enter-pressed event inside a bullet block`() { - - val style = Block.Content.Text.Style.CHECKBOX - val root = MockDataFactory.randomUuid() - val child = MockDataFactory.randomUuid() - - val page = MockBlockFactory.makeOnePageWithOneTextBlock( - root = root, - child = child, - style = style - ) - - val flow: Flow> = flow { - delay(100) - emit( - listOf( - Event.Command.ShowBlock( - root = root, - blocks = page, - context = root - ) - ) - ) - } - - stubObserveEvents(flow) - stubOpenPage() - stubCreateBlock(root) - buildViewModel() - - vm.onStart(root) - - coroutineTestRule.advanceTime(100) - - vm.onBlockFocusChanged( - id = child, - hasFocus = true - ) - - vm.onEndLineEnterClicked( - id = child, - marks = emptyList(), - text = page.last().content().text - ) - - runBlockingTest { - verify(createBlock, times(1)).invoke( - params = eq( - CreateBlock.Params( - context = root, - target = child, - prototype = Block.Prototype.Text( - style = style - ), - position = Position.BOTTOM - ) - ) - ) - } - } - - @Test - fun `should convert list block with empty text to paragraph on enter-pressed event`() { - - // SETUP - - val style = Block.Content.Text.Style.CHECKBOX - val root = MockDataFactory.randomUuid() - val child = MockDataFactory.randomUuid() - - val checkbox = Block( - id = child, - fields = Block.Fields(emptyMap()), - content = Block.Content.Text( - text = "", - marks = emptyList(), - style = style - ), - children = emptyList() - ) - - val page = listOf( - Block( - id = root, - fields = Block.Fields(emptyMap()), - content = Block.Content.Page( - style = Block.Content.Page.Style.SET - ), - children = listOf(child) - ), - checkbox - ) - - stubObserveEvents() - - stubOpenPage( - context = root, - events = listOf( - Event.Command.ShowBlock( - root = root, - blocks = page, - context = root - ) - ) - ) - - stubCreateBlock(root) - - stubUpdateTextStyle( - payload = Payload( - context = root, - events = listOf( - Event.Command.GranularChange( - context = root, - id = child, - style = Block.Content.Text.Style.P - ) - ) - ) - ) - - buildViewModel() - - // TESTING - - vm.onStart(root) - - vm.onBlockFocusChanged( - id = child, - hasFocus = true - ) - - // expected state before on-enter-pressed event - - val before = ViewState.Success( - blocks = listOf( - BlockView.Title.Document( - id = root, - text = null, - isFocused = false - ), - BlockView.Text.Checkbox( - id = child, - text = "", - isFocused = false, - isChecked = false, - indent = 0 - ) - ) - ) - - vm.state.test().assertValue(before) - - vm.onEndLineEnterClicked( - id = child, - marks = emptyList(), - text = page.last().content().text - ) - - runBlockingTest { - verify(updateTextStyle, times(1)).invoke( - params = eq( - UpdateTextStyle.Params( - context = root, - targets = listOf(child), - style = Block.Content.Text.Style.P - ) - ) - ) - } - - verifyZeroInteractions(createBlock) - - // expected state after on-enter-pressed event - - val after = ViewState.Success( - blocks = listOf( - BlockView.Title.Document( - id = root, - text = null, - isFocused = false - ), - BlockView.Text.Paragraph( - id = child, - text = "", - isFocused = true - ) - ) - ) - - vm.state.test().assertValue(after) - } - @Test fun `should start creating a new paragraph on endline-enter-pressed event inside a quote block`() { diff --git a/presentation/src/test/java/com/agileburo/anytype/presentation/page/editor/EditorListBlockTest.kt b/presentation/src/test/java/com/agileburo/anytype/presentation/page/editor/EditorListBlockTest.kt new file mode 100644 index 0000000000..645e7127e3 --- /dev/null +++ b/presentation/src/test/java/com/agileburo/anytype/presentation/page/editor/EditorListBlockTest.kt @@ -0,0 +1,683 @@ +package com.agileburo.anytype.presentation.page.editor + +import MockDataFactory +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import com.agileburo.anytype.core_ui.features.page.BlockView +import com.agileburo.anytype.domain.block.interactor.CreateBlock +import com.agileburo.anytype.domain.block.interactor.UpdateTextStyle +import com.agileburo.anytype.domain.block.model.Block +import com.agileburo.anytype.domain.block.model.Position +import com.agileburo.anytype.domain.event.model.Event +import com.agileburo.anytype.domain.ext.content +import com.agileburo.anytype.presentation.MockBlockFactory +import com.agileburo.anytype.presentation.util.CoroutinesTestRule +import com.jraska.livedata.test +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 EditorListBlockTest : EditorPresentationTestSetup() { + + @get:Rule + val rule = InstantTaskExecutorRule() + + @get:Rule + val coroutineTestRule = CoroutinesTestRule() + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + } + + @Test + fun `should start creating a new bulleted-list item on endline-enter-pressed event inside a bullet block`() { + + val style = Block.Content.Text.Style.BULLET + val child = MockDataFactory.randomUuid() + + val page = MockBlockFactory.makeOnePageWithOneTextBlock( + root = root, + child = child, + style = style + ) + + stubInterceptEvents() + stubOpenDocument(page) + stubCreateBlock(root) + + val vm = buildViewModel() + + vm.onStart(root) + + vm.onBlockFocusChanged( + id = child, + hasFocus = true + ) + + vm.onEndLineEnterClicked( + id = child, + text = page.last().content().text, + marks = emptyList() + ) + + verifyBlocking(createBlock, times(1)) { + invoke( + params = CreateBlock.Params( + context = root, + target = child, + prototype = Block.Prototype.Text( + style = style + ), + position = Position.BOTTOM + ) + ) + } + } + + @Test + fun `should start creating a new checkbox item on endline-enter-pressed event inside a bullet block`() { + + val style = Block.Content.Text.Style.CHECKBOX + val child = MockDataFactory.randomUuid() + + val page = MockBlockFactory.makeOnePageWithOneTextBlock( + root = root, + child = child, + style = style + ) + + stubInterceptEvents() + stubOpenDocument(page) + stubCreateBlock(root) + + val vm = buildViewModel() + + vm.onStart(root) + + vm.onBlockFocusChanged( + id = child, + hasFocus = true + ) + + vm.onEndLineEnterClicked( + id = child, + text = page.last().content().text, + marks = emptyList() + ) + + verifyBlocking(createBlock, times(1)) { + invoke( + params = CreateBlock.Params( + context = root, + target = child, + prototype = Block.Prototype.Text( + style = style + ), + position = Position.BOTTOM + ) + ) + } + } + + @Test + fun `should start creating a new numbered item on endline-enter-pressed event inside a bullet block`() { + + val style = Block.Content.Text.Style.NUMBERED + val child = MockDataFactory.randomUuid() + + val page = MockBlockFactory.makeOnePageWithOneTextBlock( + root = root, + child = child, + style = style + ) + + stubInterceptEvents() + stubOpenDocument(page) + stubCreateBlock(root) + + val vm = buildViewModel() + + vm.onStart(root) + + vm.onBlockFocusChanged( + id = child, + hasFocus = true + ) + + vm.onEndLineEnterClicked( + id = child, + text = page.last().content().text, + marks = emptyList() + ) + + verifyBlocking(createBlock, times(1)) { + invoke( + params = CreateBlock.Params( + context = root, + target = child, + prototype = Block.Prototype.Text( + style = style + ), + position = Position.BOTTOM + ) + ) + } + } + + @Test + fun `should start creating a new toggle block on endline-enter-pressed event inside a bullet block`() { + + val style = Block.Content.Text.Style.TOGGLE + val child = MockDataFactory.randomUuid() + + val page = MockBlockFactory.makeOnePageWithOneTextBlock( + root = root, + child = child, + style = style + ) + + stubInterceptEvents() + stubOpenDocument(page) + stubCreateBlock(root) + + val vm = buildViewModel() + + vm.onStart(root) + + vm.onBlockFocusChanged( + id = child, + hasFocus = true + ) + + vm.onEndLineEnterClicked( + id = child, + text = page.last().content().text, + marks = emptyList() + ) + + verifyBlocking(createBlock, times(1)) { + invoke( + params = CreateBlock.Params( + context = root, + target = child, + prototype = Block.Prototype.Text( + style = style + ), + position = Position.BOTTOM + ) + ) + } + } + + @Test + fun `should convert checkbox block with empty text to paragraph on enter-pressed event`() { + + // SETUP + + val style = Block.Content.Text.Style.CHECKBOX + val child = MockDataFactory.randomUuid() + + val checkbox = Block( + id = child, + fields = Block.Fields(emptyMap()), + content = Block.Content.Text( + text = "", + marks = emptyList(), + style = style + ), + children = emptyList() + ) + + val page = listOf( + Block( + id = root, + fields = Block.Fields(emptyMap()), + content = Block.Content.Page( + style = Block.Content.Page.Style.SET + ), + children = listOf(child) + ), + checkbox + ) + + stubInterceptEvents() + stubOpenDocument(page) + stubCreateBlock(root) + + stubUpdateTextStyle( + events = listOf( + Event.Command.GranularChange( + context = root, + id = child, + style = Block.Content.Text.Style.P + ) + ) + ) + + val vm = buildViewModel() + + // TESTING + + vm.onStart(root) + + vm.onBlockFocusChanged( + id = child, + hasFocus = true + ) + + // expected state before on-enter-pressed event + + val before = ViewState.Success( + blocks = listOf( + BlockView.Title.Document( + id = root, + text = null, + isFocused = false + ), + BlockView.Text.Checkbox( + id = child, + text = "", + isFocused = false, + isChecked = false, + indent = 0 + ) + ) + ) + + vm.state.test().assertValue(before) + + vm.onEndLineEnterClicked( + id = child, + marks = emptyList(), + text = page.last().content().text + ) + + verifyBlocking(updateTextStyle, times(1)) { + invoke( + params = eq( + UpdateTextStyle.Params( + context = root, + targets = listOf(child), + style = Block.Content.Text.Style.P + ) + ) + ) + } + + verifyZeroInteractions(createBlock) + + // expected state after on-enter-pressed event + + val after = ViewState.Success( + blocks = listOf( + BlockView.Title.Document( + id = root, + text = null, + isFocused = false + ), + BlockView.Text.Paragraph( + id = child, + text = "", + isFocused = true + ) + ) + ) + + vm.state.test().assertValue(after) + } + + @Test + fun `should convert bullet block with empty text to paragraph on enter-pressed event`() { + + // SETUP + + val style = Block.Content.Text.Style.BULLET + val child = MockDataFactory.randomUuid() + + val checkbox = Block( + id = child, + fields = Block.Fields(emptyMap()), + content = Block.Content.Text( + text = "", + marks = emptyList(), + style = style + ), + children = emptyList() + ) + + val page = listOf( + Block( + id = root, + fields = Block.Fields(emptyMap()), + content = Block.Content.Page( + style = Block.Content.Page.Style.SET + ), + children = listOf(child) + ), + checkbox + ) + + stubInterceptEvents() + stubOpenDocument(page) + stubCreateBlock(root) + + stubUpdateTextStyle( + events = listOf( + Event.Command.GranularChange( + context = root, + id = child, + style = Block.Content.Text.Style.P + ) + ) + ) + + val vm = buildViewModel() + + // TESTING + + vm.onStart(root) + + vm.onBlockFocusChanged( + id = child, + hasFocus = true + ) + + // expected state before on-enter-pressed event + + val before = ViewState.Success( + blocks = listOf( + BlockView.Title.Document( + id = root, + text = null, + isFocused = false + ), + BlockView.Text.Bulleted( + id = child, + text = "", + isFocused = false, + indent = 0 + ) + ) + ) + + vm.state.test().assertValue(before) + + vm.onEndLineEnterClicked( + id = child, + marks = emptyList(), + text = page.last().content().text + ) + + verifyBlocking(updateTextStyle, times(1)) { + invoke( + params = eq( + UpdateTextStyle.Params( + context = root, + targets = listOf(child), + style = Block.Content.Text.Style.P + ) + ) + ) + } + + verifyZeroInteractions(createBlock) + + // expected state after on-enter-pressed event + + val after = ViewState.Success( + blocks = listOf( + BlockView.Title.Document( + id = root, + text = null, + isFocused = false + ), + BlockView.Text.Paragraph( + id = child, + text = "", + isFocused = true + ) + ) + ) + + vm.state.test().assertValue(after) + } + + @Test + fun `should convert toggle block with empty text to paragraph on enter-pressed event`() { + + // SETUP + + val style = Block.Content.Text.Style.TOGGLE + val child = MockDataFactory.randomUuid() + + val checkbox = Block( + id = child, + fields = Block.Fields(emptyMap()), + content = Block.Content.Text( + text = "", + marks = emptyList(), + style = style + ), + children = emptyList() + ) + + val page = listOf( + Block( + id = root, + fields = Block.Fields(emptyMap()), + content = Block.Content.Page( + style = Block.Content.Page.Style.SET + ), + children = listOf(child) + ), + checkbox + ) + + stubInterceptEvents() + stubOpenDocument(page) + stubCreateBlock(root) + + stubUpdateTextStyle( + events = listOf( + Event.Command.GranularChange( + context = root, + id = child, + style = Block.Content.Text.Style.P + ) + ) + ) + + val vm = buildViewModel() + + // TESTING + + vm.onStart(root) + + vm.onBlockFocusChanged( + id = child, + hasFocus = true + ) + + // expected state before on-enter-pressed event + + val before = ViewState.Success( + blocks = listOf( + BlockView.Title.Document( + id = root, + text = null, + isFocused = false + ), + BlockView.Text.Toggle( + id = child, + text = "", + isFocused = false, + indent = 0, + isEmpty = true + ) + ) + ) + + vm.state.test().assertValue(before) + + vm.onEndLineEnterClicked( + id = child, + marks = emptyList(), + text = page.last().content().text + ) + + verifyBlocking(updateTextStyle, times(1)) { + invoke( + params = eq( + UpdateTextStyle.Params( + context = root, + targets = listOf(child), + style = Block.Content.Text.Style.P + ) + ) + ) + } + + verifyZeroInteractions(createBlock) + + // expected state after on-enter-pressed event + + val after = ViewState.Success( + blocks = listOf( + BlockView.Title.Document( + id = root, + text = null, + isFocused = false + ), + BlockView.Text.Paragraph( + id = child, + text = "", + isFocused = true + ) + ) + ) + + vm.state.test().assertValue(after) + } + + @Test + fun `should convert numbered block with empty text to paragraph on enter-pressed event`() { + + // SETUP + + val style = Block.Content.Text.Style.NUMBERED + val child = MockDataFactory.randomUuid() + + val checkbox = Block( + id = child, + fields = Block.Fields(emptyMap()), + content = Block.Content.Text( + text = "", + marks = emptyList(), + style = style + ), + children = emptyList() + ) + + val page = listOf( + Block( + id = root, + fields = Block.Fields(emptyMap()), + content = Block.Content.Page( + style = Block.Content.Page.Style.SET + ), + children = listOf(child) + ), + checkbox + ) + + stubInterceptEvents() + stubOpenDocument(page) + stubCreateBlock(root) + + stubUpdateTextStyle( + events = listOf( + Event.Command.GranularChange( + context = root, + id = child, + style = Block.Content.Text.Style.P + ) + ) + ) + + val vm = buildViewModel() + + // TESTING + + vm.onStart(root) + + vm.onBlockFocusChanged( + id = child, + hasFocus = true + ) + + // expected state before on-enter-pressed event + + val before = ViewState.Success( + blocks = listOf( + BlockView.Title.Document( + id = root, + text = null, + isFocused = false + ), + BlockView.Text.Numbered( + id = child, + text = "", + isFocused = false, + indent = 0, + number = 1 + ) + ) + ) + + vm.state.test().assertValue(before) + + vm.onEndLineEnterClicked( + id = child, + marks = emptyList(), + text = page.last().content().text + ) + + verifyBlocking(updateTextStyle, times(1)) { + invoke( + params = eq( + UpdateTextStyle.Params( + context = root, + targets = listOf(child), + style = Block.Content.Text.Style.P + ) + ) + ) + } + + verifyZeroInteractions(createBlock) + + // expected state after on-enter-pressed event + + val after = ViewState.Success( + blocks = listOf( + BlockView.Title.Document( + id = root, + text = null, + isFocused = false + ), + BlockView.Text.Paragraph( + id = child, + text = "", + isFocused = true + ) + ) + ) + + vm.state.test().assertValue(after) + } +} \ No newline at end of file diff --git a/presentation/src/test/java/com/agileburo/anytype/presentation/page/editor/EditorPresentationTestSetup.kt b/presentation/src/test/java/com/agileburo/anytype/presentation/page/editor/EditorPresentationTestSetup.kt index a6f0f7a49c..9ecc6a3dcc 100644 --- a/presentation/src/test/java/com/agileburo/anytype/presentation/page/editor/EditorPresentationTestSetup.kt +++ b/presentation/src/test/java/com/agileburo/anytype/presentation/page/editor/EditorPresentationTestSetup.kt @@ -276,4 +276,17 @@ open class EditorPresentationTestSetup { onBlocking { invoke(params) } doReturn Either.Right(emptyList()) } } + + fun stubCreateBlock(root: String) { + createBlock.stub { + onBlocking { invoke(any()) } doReturn Either.Right( + Pair( + MockDataFactory.randomString(), Payload( + context = root, + events = listOf() + ) + ) + ) + } + } } \ No newline at end of file