mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-11 10:18:05 +09:00
Enable hot keys for different editor patterns (#332)
This commit is contained in:
parent
f1d082e829
commit
dbdce727f7
28 changed files with 711 additions and 129 deletions
|
@ -5,6 +5,8 @@ import androidx.lifecycle.MutableLiveData
|
|||
import androidx.lifecycle.viewModelScope
|
||||
import com.agileburo.anytype.core_ui.common.Markup
|
||||
import com.agileburo.anytype.core_ui.features.page.BlockView
|
||||
import com.agileburo.anytype.core_ui.features.page.pattern.Matcher
|
||||
import com.agileburo.anytype.core_ui.features.page.pattern.Pattern
|
||||
import com.agileburo.anytype.core_ui.state.ControlPanelState
|
||||
import com.agileburo.anytype.core_utils.common.EventWrapper
|
||||
import com.agileburo.anytype.core_utils.ext.*
|
||||
|
@ -30,6 +32,7 @@ import com.agileburo.anytype.presentation.common.SupportCommand
|
|||
import com.agileburo.anytype.presentation.navigation.AppNavigation
|
||||
import com.agileburo.anytype.presentation.navigation.SupportNavigation
|
||||
import com.agileburo.anytype.presentation.page.ControlPanelMachine.Interactor
|
||||
import com.agileburo.anytype.presentation.page.model.TextUpdate
|
||||
import com.agileburo.anytype.presentation.page.render.BlockViewRenderer
|
||||
import com.agileburo.anytype.presentation.page.render.DefaultBlockViewRenderer
|
||||
import com.agileburo.anytype.presentation.page.toggle.ToggleStateHolder
|
||||
|
@ -49,8 +52,9 @@ class PageViewModel(
|
|||
private val archiveDocument: ArchiveDocument,
|
||||
private val undo: Undo,
|
||||
private val redo: Redo,
|
||||
private val updateBlock: UpdateBlock,
|
||||
private val updateText: UpdateText,
|
||||
private val createBlock: CreateBlock,
|
||||
private val replaceBlock: ReplaceBlock,
|
||||
private val interceptEvents: InterceptEvents,
|
||||
private val updateCheckbox: UpdateCheckbox,
|
||||
private val unlinkBlocks: UnlinkBlocks,
|
||||
|
@ -67,7 +71,8 @@ class PageViewModel(
|
|||
private val documentExternalEventReducer: StateReducer<List<Block>, Event>,
|
||||
private val urlBuilder: UrlBuilder,
|
||||
private val renderer: DefaultBlockViewRenderer,
|
||||
private val counter: Counter
|
||||
private val counter: Counter,
|
||||
private val patternMatcher: Matcher<Pattern>
|
||||
) : ViewStateViewModel<PageViewModel.ViewState>(),
|
||||
SupportNavigation<EventWrapper<AppNavigation.Command>>,
|
||||
SupportCommand<PageViewModel.Command>,
|
||||
|
@ -79,13 +84,16 @@ class PageViewModel(
|
|||
val controlPanelViewState = MutableLiveData<ControlPanelState>()
|
||||
|
||||
private val renderingChannel = Channel<List<Block>>()
|
||||
|
||||
private val renderings = renderingChannel.consumeAsFlow()
|
||||
|
||||
private val focusChannel = ConflatedBroadcastChannel(EMPTY_FOCUS_ID)
|
||||
private val focusChanges = focusChannel.asFlow()
|
||||
|
||||
private val textChannel = Channel<Triple<Id, String, List<Content.Text.Mark>>>()
|
||||
private val textChannel = Channel<TextUpdate>()
|
||||
private val textUpdateChannel = Channel<TextUpdate>()
|
||||
private val textChanges = textChannel.consumeAsFlow()
|
||||
private val textUpdateChanges = textUpdateChannel.consumeAsFlow()
|
||||
|
||||
private val selectionChannel = Channel<Pair<Id, IntRange>>()
|
||||
private val selectionsChanges = selectionChannel.consumeAsFlow()
|
||||
|
@ -146,16 +154,17 @@ class PageViewModel(
|
|||
private fun proceedWithInitialFocusing(events: List<Event>) {
|
||||
val event = events.find { event -> event is Event.Command.ShowBlock }
|
||||
if (event is Event.Command.ShowBlock) {
|
||||
val title = event.blocks.first { block ->
|
||||
event.blocks.find { block ->
|
||||
block.content is Content.Text
|
||||
&& block.content<Content.Text>().style == Content.Text.Style.TITLE
|
||||
}
|
||||
updateFocus(title.id)
|
||||
controlPanelInteractor.onEvent(
|
||||
ControlPanelMachine.Event.OnFocusChanged(
|
||||
id = title.id, style = Content.Text.Style.TITLE
|
||||
}?.let { title ->
|
||||
updateFocus(title.id)
|
||||
controlPanelInteractor.onEvent(
|
||||
ControlPanelMachine.Event.OnFocusChanged(
|
||||
id = title.id, style = Content.Text.Style.TITLE
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -217,8 +226,8 @@ class PageViewModel(
|
|||
val newContent = targetContent.copy(marks = it)
|
||||
val newBlock = targetBlock.copy(content = newContent)
|
||||
rerenderingBlocks(newBlock)
|
||||
proceedWithUpdatingBlock(
|
||||
params = UpdateBlock.Params(
|
||||
proceedWithUpdatingText(
|
||||
params = UpdateText.Params(
|
||||
contextId = context,
|
||||
text = newBlock.content.asText().text,
|
||||
blockId = targetBlock.id,
|
||||
|
@ -270,8 +279,8 @@ class PageViewModel(
|
|||
|
||||
refresh()
|
||||
|
||||
proceedWithUpdatingBlock(
|
||||
params = UpdateBlock.Params(
|
||||
proceedWithUpdatingText(
|
||||
params = UpdateText.Params(
|
||||
contextId = context,
|
||||
blockId = newBlock.id,
|
||||
text = newContent.text,
|
||||
|
@ -311,35 +320,96 @@ class PageViewModel(
|
|||
|
||||
private fun startHandlingTextChanges() {
|
||||
textChanges
|
||||
.onEach { update ->
|
||||
when {
|
||||
update.patterns.isEmpty() -> textUpdateChannel.send(update)
|
||||
update.patterns.contains(Pattern.NUMBERED) -> replaceBy(
|
||||
target = update.target,
|
||||
prototype = Prototype.Text(
|
||||
style = Content.Text.Style.NUMBERED
|
||||
)
|
||||
)
|
||||
update.patterns.contains(Pattern.DIVIDER) -> replaceBy(
|
||||
target = update.target,
|
||||
prototype = Prototype.Divider
|
||||
)
|
||||
update.patterns.contains(Pattern.CHECKBOX) -> replaceBy(
|
||||
target = update.target,
|
||||
prototype = Prototype.Text(
|
||||
style = Content.Text.Style.CHECKBOX
|
||||
)
|
||||
)
|
||||
update.patterns.contains(Pattern.BULLET) -> replaceBy(
|
||||
target = update.target,
|
||||
prototype = Prototype.Text(
|
||||
style = Content.Text.Style.BULLET
|
||||
)
|
||||
)
|
||||
update.patterns.contains(Pattern.H1) -> replaceBy(
|
||||
target = update.target,
|
||||
prototype = Prototype.Text(
|
||||
style = Content.Text.Style.H1
|
||||
)
|
||||
)
|
||||
update.patterns.contains(Pattern.H2) -> replaceBy(
|
||||
target = update.target,
|
||||
prototype = Prototype.Text(
|
||||
style = Content.Text.Style.H2
|
||||
)
|
||||
)
|
||||
update.patterns.contains(Pattern.H3) -> replaceBy(
|
||||
target = update.target,
|
||||
prototype = Prototype.Text(
|
||||
style = Content.Text.Style.H3
|
||||
)
|
||||
)
|
||||
update.patterns.contains(Pattern.QUOTE) -> replaceBy(
|
||||
target = update.target,
|
||||
prototype = Prototype.Text(
|
||||
style = Content.Text.Style.QUOTE
|
||||
)
|
||||
)
|
||||
update.patterns.contains(Pattern.TOGGLE) -> replaceBy(
|
||||
target = update.target,
|
||||
prototype = Prototype.Text(
|
||||
style = Content.Text.Style.TOGGLE
|
||||
)
|
||||
)
|
||||
else -> textUpdateChannel.send(update)
|
||||
}
|
||||
}
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
textUpdateChanges
|
||||
.debounce(TEXT_CHANGES_DEBOUNCE_DURATION)
|
||||
.map { (id, text, marks) ->
|
||||
.map { update ->
|
||||
|
||||
blocks = blocks.map { block ->
|
||||
if (block.id == id) {
|
||||
if (block.id == update.target) {
|
||||
block.copy(
|
||||
content = block.content.asText().copy(
|
||||
text = text,
|
||||
marks = marks.filter { it.range.first != it.range.last }
|
||||
text = update.text,
|
||||
marks = update.markup.filter { it.range.first != it.range.last }
|
||||
)
|
||||
)
|
||||
} else
|
||||
block
|
||||
}
|
||||
|
||||
UpdateBlock.Params(
|
||||
UpdateText.Params(
|
||||
contextId = context,
|
||||
blockId = id,
|
||||
text = text,
|
||||
marks = marks.filter { it.range.first != it.range.last }
|
||||
blockId = update.target,
|
||||
text = update.text,
|
||||
marks = update.markup.filter { it.range.first != it.range.last }
|
||||
)
|
||||
}
|
||||
.onEach { params -> proceedWithUpdatingBlock(params) }
|
||||
.onEach { params -> proceedWithUpdatingText(params) }
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
private fun proceedWithUpdatingBlock(params: UpdateBlock.Params) {
|
||||
private fun proceedWithUpdatingText(params: UpdateText.Params) {
|
||||
Timber.d("Starting updating block with params: $params")
|
||||
updateBlock.invoke(viewModelScope, params) { result ->
|
||||
updateText.invoke(viewModelScope, params) { result ->
|
||||
result.either(
|
||||
fnL = { Timber.e(it, "Error while updating text: $params") },
|
||||
fnR = { Timber.d("Text has been updated") }
|
||||
|
@ -347,6 +417,26 @@ class PageViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
private fun replaceBy(
|
||||
target: Id,
|
||||
prototype: Prototype
|
||||
) {
|
||||
replaceBlock.invoke(
|
||||
scope = viewModelScope,
|
||||
params = ReplaceBlock.Params(
|
||||
context = context,
|
||||
target = target,
|
||||
prototype = prototype
|
||||
),
|
||||
onResult = { result ->
|
||||
result.either(
|
||||
fnL = { Timber.e(it, "Error while converting $target to: $prototype") },
|
||||
fnR = { id -> updateFocus(id) }
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun open(id: String) {
|
||||
|
||||
context = id
|
||||
|
@ -356,7 +446,7 @@ class PageViewModel(
|
|||
openPage.invoke(viewModelScope, OpenPage.Params(id)) { result ->
|
||||
result.either(
|
||||
fnR = { Timber.d("Page with id $id has been opened") },
|
||||
fnL = { Timber.e(it, "Error while openining page with id: $id") }
|
||||
fnL = { Timber.e(it, "Error while opening page with id: $id") }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -379,8 +469,8 @@ class PageViewModel(
|
|||
val newContent = targetContent.copy(marks = it)
|
||||
val newBlock = targetBlock.copy(content = newContent)
|
||||
rerenderingBlocks(newBlock)
|
||||
proceedWithUpdatingBlock(
|
||||
params = UpdateBlock.Params(
|
||||
proceedWithUpdatingText(
|
||||
params = UpdateText.Params(
|
||||
contextId = context,
|
||||
text = newBlock.content.asText().text,
|
||||
blockId = targetBlock.id,
|
||||
|
@ -422,9 +512,29 @@ class PageViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
fun onTextChanged(id: String, text: String, marks: List<Content.Text.Mark>) {
|
||||
Timber.d("onTextChanged: $id\nNew text: $text\nMarks: $marks")
|
||||
viewModelScope.launch { textChannel.send(Triple(id, text, marks)) }
|
||||
fun onTextChanged(
|
||||
id: String,
|
||||
text: String,
|
||||
marks: List<Content.Text.Mark>
|
||||
) {
|
||||
val update = TextUpdate(target = id, text = text, markup = marks, patterns = emptyList())
|
||||
Timber.d("onTextChanged: $update")
|
||||
viewModelScope.launch { textChannel.send(update) }
|
||||
}
|
||||
|
||||
fun onParagraphTextChanged(
|
||||
id: String,
|
||||
text: String,
|
||||
marks: List<Content.Text.Mark>
|
||||
) {
|
||||
val update = TextUpdate(
|
||||
target = id,
|
||||
text = text,
|
||||
markup = marks,
|
||||
patterns = patternMatcher.match(text)
|
||||
)
|
||||
Timber.d("onParagraphTextChanged: $update")
|
||||
viewModelScope.launch { textChannel.send(update) }
|
||||
}
|
||||
|
||||
fun onSelectionChanged(id: String, selection: IntRange) {
|
||||
|
|
|
@ -2,6 +2,8 @@ package com.agileburo.anytype.presentation.page
|
|||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.agileburo.anytype.core_ui.features.page.pattern.Matcher
|
||||
import com.agileburo.anytype.core_ui.features.page.pattern.Pattern
|
||||
import com.agileburo.anytype.core_utils.tools.Counter
|
||||
import com.agileburo.anytype.domain.block.interactor.*
|
||||
import com.agileburo.anytype.domain.block.model.Block
|
||||
|
@ -21,8 +23,9 @@ open class PageViewModelFactory(
|
|||
private val archiveDocument: ArchiveDocument,
|
||||
private val redo: Redo,
|
||||
private val undo: Undo,
|
||||
private val updateBlock: UpdateBlock,
|
||||
private val updateText: UpdateText,
|
||||
private val createBlock: CreateBlock,
|
||||
private val replaceBlock: ReplaceBlock,
|
||||
private val interceptEvents: InterceptEvents,
|
||||
private val updateCheckbox: UpdateCheckbox,
|
||||
private val unlinkBlocks: UnlinkBlocks,
|
||||
|
@ -39,7 +42,8 @@ open class PageViewModelFactory(
|
|||
private val urlBuilder: UrlBuilder,
|
||||
private val downloadFile: DownloadFile,
|
||||
private val renderer: DefaultBlockViewRenderer,
|
||||
private val counter: Counter
|
||||
private val counter: Counter,
|
||||
private val patternMatcher: Matcher<Pattern>
|
||||
) : ViewModelProvider.Factory {
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
|
@ -49,7 +53,7 @@ open class PageViewModelFactory(
|
|||
closePage = closePage,
|
||||
undo = undo,
|
||||
redo = redo,
|
||||
updateBlock = updateBlock,
|
||||
updateText = updateText,
|
||||
createBlock = createBlock,
|
||||
archiveDocument = archiveDocument,
|
||||
interceptEvents = interceptEvents,
|
||||
|
@ -70,7 +74,9 @@ open class PageViewModelFactory(
|
|||
downloadFile = downloadFile,
|
||||
renderer = renderer,
|
||||
counter = counter,
|
||||
createDocument = createDocument
|
||||
createDocument = createDocument,
|
||||
replaceBlock = replaceBlock,
|
||||
patternMatcher = patternMatcher
|
||||
) as T
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package com.agileburo.anytype.presentation.page.model
|
||||
|
||||
import com.agileburo.anytype.core_ui.features.page.pattern.Pattern
|
||||
import com.agileburo.anytype.domain.block.model.Block
|
||||
import com.agileburo.anytype.domain.common.Id
|
||||
|
||||
/**
|
||||
* Editor text update event data.
|
||||
* @property target id of the text block, in which this change occurs
|
||||
* @property text new text for this [target]
|
||||
* @property markup markup, associated with this [text]
|
||||
* @property patterns editor patterns found in this [text]
|
||||
*/
|
||||
class TextUpdate(
|
||||
val target: Id,
|
||||
val text: String,
|
||||
val markup: List<Block.Content.Text.Mark>,
|
||||
val patterns: List<Pattern>
|
||||
)
|
|
@ -4,6 +4,7 @@ import MockDataFactory
|
|||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||
import com.agileburo.anytype.core_ui.common.Markup
|
||||
import com.agileburo.anytype.core_ui.features.page.BlockView
|
||||
import com.agileburo.anytype.core_ui.features.page.pattern.DefaultPatternMatcher
|
||||
import com.agileburo.anytype.core_ui.state.ControlPanelState
|
||||
import com.agileburo.anytype.core_utils.tools.Counter
|
||||
import com.agileburo.anytype.domain.base.Either
|
||||
|
@ -60,7 +61,7 @@ class PageViewModelTest {
|
|||
lateinit var createBlock: CreateBlock
|
||||
|
||||
@Mock
|
||||
lateinit var updateBlock: UpdateBlock
|
||||
lateinit var updateText: UpdateText
|
||||
|
||||
@Mock
|
||||
lateinit var updateCheckbox: UpdateCheckbox
|
||||
|
@ -116,6 +117,9 @@ class PageViewModelTest {
|
|||
@Mock
|
||||
lateinit var archiveDocument: ArchiveDocument
|
||||
|
||||
@Mock
|
||||
lateinit var replaceBlock: ReplaceBlock
|
||||
|
||||
private lateinit var vm: PageViewModel
|
||||
|
||||
@Before
|
||||
|
@ -297,7 +301,7 @@ class PageViewModelTest {
|
|||
|
||||
coroutineTestRule.advanceTime(PageViewModel.TEXT_CHANGES_DEBOUNCE_DURATION)
|
||||
|
||||
verify(updateBlock, times(1)).invoke(
|
||||
verify(updateText, times(1)).invoke(
|
||||
any(),
|
||||
argThat { this.contextId == pageId && this.blockId == blockId && this.text == text },
|
||||
any()
|
||||
|
@ -326,7 +330,7 @@ class PageViewModelTest {
|
|||
|
||||
coroutineTestRule.advanceTime(PageViewModel.TEXT_CHANGES_DEBOUNCE_DURATION)
|
||||
|
||||
verify(updateBlock, times(2)).invoke(
|
||||
verify(updateText, times(2)).invoke(
|
||||
any(),
|
||||
argThat { this.contextId == pageId && this.blockId == blockId && this.text == text },
|
||||
any()
|
||||
|
@ -906,10 +910,10 @@ class PageViewModelTest {
|
|||
)
|
||||
)
|
||||
|
||||
verify(updateBlock, times(1)).invoke(
|
||||
verify(updateText, times(1)).invoke(
|
||||
scope = any(),
|
||||
params = eq(
|
||||
UpdateBlock.Params(
|
||||
UpdateText.Params(
|
||||
blockId = paragraph.id,
|
||||
marks = marks,
|
||||
contextId = page.id,
|
||||
|
@ -1174,10 +1178,10 @@ class PageViewModelTest {
|
|||
|
||||
coroutineTestRule.advanceTime(PageViewModel.TEXT_CHANGES_DEBOUNCE_DURATION)
|
||||
|
||||
verify(updateBlock, times(1)).invoke(
|
||||
verify(updateText, times(1)).invoke(
|
||||
scope = any(),
|
||||
params = eq(
|
||||
UpdateBlock.Params(
|
||||
UpdateText.Params(
|
||||
blockId = paragraph.id,
|
||||
text = userInput,
|
||||
marks = marks,
|
||||
|
@ -1196,7 +1200,9 @@ class PageViewModelTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `add-block-or-turn-into panel should be opened on add-block-toolbar-clicked event`() {
|
||||
fun `should dispatch open-add-block-panel command on add-block-toolbar-clicked event`() {
|
||||
|
||||
// SETUP
|
||||
|
||||
val root = MockDataFactory.randomUuid()
|
||||
val child = MockDataFactory.randomUuid()
|
||||
|
@ -1223,39 +1229,23 @@ class PageViewModelTest {
|
|||
|
||||
coroutineTestRule.advanceTime(1001)
|
||||
|
||||
// TESTING
|
||||
|
||||
vm.onBlockFocusChanged(
|
||||
id = child,
|
||||
hasFocus = true
|
||||
)
|
||||
|
||||
val commands = vm.commands.test()
|
||||
|
||||
vm.onAddBlockToolbarClicked()
|
||||
|
||||
val expected = ControlPanelState(
|
||||
colorToolbar = ControlPanelState.Toolbar.Color(
|
||||
isVisible = false
|
||||
),
|
||||
blockToolbar = ControlPanelState.Toolbar.Block(
|
||||
isVisible = true,
|
||||
selectedAction = ControlPanelState.Toolbar.Block.Action.ADD
|
||||
),
|
||||
addBlockToolbar = ControlPanelState.Toolbar.AddBlock(
|
||||
isVisible = true
|
||||
),
|
||||
markupToolbar = ControlPanelState.Toolbar.Markup(
|
||||
isVisible = false
|
||||
),
|
||||
actionToolbar = ControlPanelState.Toolbar.BlockAction(
|
||||
isVisible = false
|
||||
),
|
||||
turnIntoToolbar = ControlPanelState.Toolbar.TurnInto(
|
||||
isVisible = false
|
||||
),
|
||||
focus = ControlPanelState.Focus(
|
||||
id = child,
|
||||
type = ControlPanelState.Focus.Type.P
|
||||
)
|
||||
)
|
||||
val result = commands.value()
|
||||
|
||||
vm.controlPanelViewState.test().assertValue(expected)
|
||||
assertEquals(
|
||||
expected = PageViewModel.Command.OpenAddBlockPanel,
|
||||
actual = result.peekContent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -3319,7 +3309,7 @@ class PageViewModelTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `should start closing page after succesful archive operation`() {
|
||||
fun `should start closing page after successful archive operation`() {
|
||||
|
||||
// SETUP
|
||||
|
||||
|
@ -3393,6 +3383,232 @@ class PageViewModelTest {
|
|||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should convert paragraph to numbered list without any delay when regex matches`() {
|
||||
|
||||
// SETUP
|
||||
|
||||
val root = MockDataFactory.randomUuid()
|
||||
val paragraph = MockBlockFactory.makeParagraphBlock()
|
||||
val title = MockBlockFactory.makeTitleBlock()
|
||||
|
||||
val page = listOf(
|
||||
Block(
|
||||
id = root,
|
||||
fields = Block.Fields.empty(),
|
||||
content = Block.Content.Page(
|
||||
style = Block.Content.Page.Style.SET
|
||||
),
|
||||
children = listOf(title.id, paragraph.id)
|
||||
),
|
||||
title,
|
||||
paragraph
|
||||
)
|
||||
|
||||
val flow: Flow<List<Event.Command>> = flow {
|
||||
delay(100)
|
||||
emit(
|
||||
listOf(
|
||||
Event.Command.ShowBlock(
|
||||
rootId = root,
|
||||
blocks = page,
|
||||
context = root
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
stubObserveEvents(flow)
|
||||
stubOpenPage()
|
||||
buildViewModel()
|
||||
|
||||
vm.open(root)
|
||||
|
||||
coroutineTestRule.advanceTime(100)
|
||||
|
||||
// TESTING
|
||||
|
||||
val update = "1. "
|
||||
|
||||
vm.onParagraphTextChanged(
|
||||
id = paragraph.id,
|
||||
marks = paragraph.content<Block.Content.Text>().marks,
|
||||
text = update
|
||||
)
|
||||
|
||||
verify(replaceBlock, times(1)).invoke(
|
||||
scope = any(),
|
||||
params = eq(
|
||||
ReplaceBlock.Params(
|
||||
context = root,
|
||||
target = paragraph.id,
|
||||
prototype = Block.Prototype.Text(
|
||||
style = Block.Content.Text.Style.NUMBERED
|
||||
)
|
||||
)
|
||||
),
|
||||
onResult = any()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should ignore create-numbered-list-item pattern and update text with delay`() {
|
||||
|
||||
// SETUP
|
||||
|
||||
val root = MockDataFactory.randomUuid()
|
||||
val numbered = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
fields = Block.Fields.empty(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
marks = emptyList(),
|
||||
style = Block.Content.Text.Style.NUMBERED
|
||||
),
|
||||
children = emptyList()
|
||||
)
|
||||
val title = MockBlockFactory.makeTitleBlock()
|
||||
|
||||
val page = listOf(
|
||||
Block(
|
||||
id = root,
|
||||
fields = Block.Fields.empty(),
|
||||
content = Block.Content.Page(
|
||||
style = Block.Content.Page.Style.SET
|
||||
),
|
||||
children = listOf(title.id, numbered.id)
|
||||
),
|
||||
title,
|
||||
numbered
|
||||
)
|
||||
|
||||
val flow: Flow<List<Event.Command>> = flow {
|
||||
delay(100)
|
||||
emit(
|
||||
listOf(
|
||||
Event.Command.ShowBlock(
|
||||
rootId = root,
|
||||
blocks = page,
|
||||
context = root
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
stubObserveEvents(flow)
|
||||
stubOpenPage()
|
||||
buildViewModel()
|
||||
|
||||
vm.open(root)
|
||||
|
||||
coroutineTestRule.advanceTime(100)
|
||||
|
||||
// TESTING
|
||||
|
||||
val update = "1. "
|
||||
|
||||
vm.onTextChanged(
|
||||
id = numbered.id,
|
||||
marks = numbered.content<Block.Content.Text>().marks,
|
||||
text = update
|
||||
)
|
||||
|
||||
verify(updateText, never()).invoke(
|
||||
scope = any(),
|
||||
params = any(),
|
||||
onResult = any()
|
||||
)
|
||||
|
||||
verify(replaceBlock, never()).invoke(
|
||||
scope = any(),
|
||||
params = any(),
|
||||
onResult = any()
|
||||
)
|
||||
|
||||
coroutineTestRule.advanceTime(PageViewModel.TEXT_CHANGES_DEBOUNCE_DURATION)
|
||||
|
||||
verify(replaceBlock, never()).invoke(
|
||||
scope = any(),
|
||||
params = any(),
|
||||
onResult = any()
|
||||
)
|
||||
|
||||
verify(updateText, times(1)).invoke(
|
||||
scope = any(),
|
||||
params = eq(
|
||||
UpdateText.Params(
|
||||
contextId = root,
|
||||
blockId = numbered.id,
|
||||
marks = numbered.content<Block.Content.Text>().marks,
|
||||
text = update
|
||||
)
|
||||
),
|
||||
onResult = any()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should not update text while processing paragraph-to-numbered-list editor pattern`() {
|
||||
|
||||
// SETUP
|
||||
|
||||
val root = MockDataFactory.randomUuid()
|
||||
val paragraph = MockBlockFactory.makeParagraphBlock()
|
||||
val title = MockBlockFactory.makeTitleBlock()
|
||||
|
||||
val page = listOf(
|
||||
Block(
|
||||
id = root,
|
||||
fields = Block.Fields.empty(),
|
||||
content = Block.Content.Page(
|
||||
style = Block.Content.Page.Style.SET
|
||||
),
|
||||
children = listOf(title.id, paragraph.id)
|
||||
),
|
||||
title,
|
||||
paragraph
|
||||
)
|
||||
|
||||
val flow: Flow<List<Event.Command>> = flow {
|
||||
delay(100)
|
||||
emit(
|
||||
listOf(
|
||||
Event.Command.ShowBlock(
|
||||
rootId = root,
|
||||
blocks = page,
|
||||
context = root
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
stubObserveEvents(flow)
|
||||
stubOpenPage()
|
||||
buildViewModel()
|
||||
|
||||
vm.open(root)
|
||||
|
||||
coroutineTestRule.advanceTime(100)
|
||||
|
||||
// TESTING
|
||||
|
||||
val update = "1. "
|
||||
|
||||
vm.onParagraphTextChanged(
|
||||
id = paragraph.id,
|
||||
marks = paragraph.content<Block.Content.Text>().marks,
|
||||
text = update
|
||||
)
|
||||
|
||||
coroutineTestRule.advanceTime(PageViewModel.TEXT_CHANGES_DEBOUNCE_DURATION)
|
||||
|
||||
verify(updateText, never()).invoke(
|
||||
scope = any(),
|
||||
params = any(),
|
||||
onResult = any()
|
||||
)
|
||||
}
|
||||
|
||||
private fun simulateNormalPageOpeningFlow() {
|
||||
|
||||
val root = MockDataFactory.randomUuid()
|
||||
|
@ -3455,7 +3671,7 @@ class PageViewModelTest {
|
|||
openPage = openPage,
|
||||
closePage = closePage,
|
||||
createPage = createPage,
|
||||
updateBlock = updateBlock,
|
||||
updateText = updateText,
|
||||
undo = undo,
|
||||
redo = redo,
|
||||
interceptEvents = interceptEvents,
|
||||
|
@ -3481,7 +3697,9 @@ class PageViewModelTest {
|
|||
toggleStateHolder = ToggleStateHolder.Default()
|
||||
),
|
||||
archiveDocument = archiveDocument,
|
||||
createDocument = createDocument
|
||||
createDocument = createDocument,
|
||||
replaceBlock = replaceBlock,
|
||||
patternMatcher = DefaultPatternMatcher()
|
||||
)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue