mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-08 13:57:10 +09:00
Title as simple block (#949)
This commit is contained in:
parent
98e9689cd4
commit
ce955a7a44
49 changed files with 2005 additions and 759 deletions
|
@ -1,6 +1,6 @@
|
|||
# Change log for Android @Anytype app.
|
||||
|
||||
## Version 0.0.50 (WIP)
|
||||
## Version 0.0.50
|
||||
|
||||
### New features 🚀
|
||||
|
||||
|
@ -24,6 +24,10 @@
|
|||
* Clicking on empty space before document is loaded should not crash application (#930)
|
||||
* Focusing on start may crash application by some users (#931)
|
||||
|
||||
### Middleware ⚙
|
||||
|
||||
* Updated middleware protocol to `0.13.18` (#851)
|
||||
|
||||
## Version 0.0.49
|
||||
|
||||
### Fixes & tech 🚒
|
||||
|
|
|
@ -2,10 +2,12 @@ package com.anytypeio.anytype.di.feature
|
|||
|
||||
import com.anytypeio.anytype.core_utils.di.scope.PerScreen
|
||||
import com.anytypeio.anytype.domain.block.repo.BlockRepository
|
||||
import com.anytypeio.anytype.domain.event.model.Payload
|
||||
import com.anytypeio.anytype.domain.icon.SetDocumentEmojiIcon
|
||||
import com.anytypeio.anytype.emojifier.data.Emoji
|
||||
import com.anytypeio.anytype.emojifier.suggest.EmojiSuggester
|
||||
import com.anytypeio.anytype.presentation.page.picker.DocumentEmojiIconPickerViewModelFactory
|
||||
import com.anytypeio.anytype.presentation.util.Bridge
|
||||
import com.anytypeio.anytype.ui.page.modals.DocumentEmojiIconPickerFragment
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
@ -31,11 +33,13 @@ class DocumentEmojiIconPickerModule {
|
|||
@PerScreen
|
||||
fun provideDocumentEmojiIconPickerViewModel(
|
||||
setEmojiIcon: SetDocumentEmojiIcon,
|
||||
emojiSuggester: EmojiSuggester
|
||||
emojiSuggester: EmojiSuggester,
|
||||
bridge: Bridge<Payload>
|
||||
): DocumentEmojiIconPickerViewModelFactory = DocumentEmojiIconPickerViewModelFactory(
|
||||
setEmojiIcon = setEmojiIcon,
|
||||
emojiSuggester = emojiSuggester,
|
||||
emojiProvider = Emoji
|
||||
emojiProvider = Emoji,
|
||||
bridge = bridge
|
||||
)
|
||||
|
||||
@Provides
|
||||
|
|
|
@ -2,9 +2,11 @@ package com.anytypeio.anytype.di.feature
|
|||
|
||||
import com.anytypeio.anytype.core_utils.di.scope.PerScreen
|
||||
import com.anytypeio.anytype.domain.block.repo.BlockRepository
|
||||
import com.anytypeio.anytype.domain.event.model.Payload
|
||||
import com.anytypeio.anytype.domain.icon.SetDocumentEmojiIcon
|
||||
import com.anytypeio.anytype.domain.icon.SetDocumentImageIcon
|
||||
import com.anytypeio.anytype.presentation.page.picker.DocumentIconActionMenuViewModelFactory
|
||||
import com.anytypeio.anytype.presentation.util.Bridge
|
||||
import com.anytypeio.anytype.ui.page.modals.actions.DocumentIconActionMenuFragment
|
||||
import com.anytypeio.anytype.ui.page.modals.actions.ProfileIconActionMenuFragment
|
||||
import dagger.Module
|
||||
|
@ -32,10 +34,12 @@ class DocumentIconActionMenuModule {
|
|||
@PerScreen
|
||||
fun provideDocumentIconActionMenuViewModelFactory(
|
||||
setEmojiIcon: SetDocumentEmojiIcon,
|
||||
setImageIcon: SetDocumentImageIcon
|
||||
setImageIcon: SetDocumentImageIcon,
|
||||
bridge: Bridge<Payload>
|
||||
): DocumentIconActionMenuViewModelFactory = DocumentIconActionMenuViewModelFactory(
|
||||
setEmojiIcon = setEmojiIcon,
|
||||
setImageIcon = setImageIcon
|
||||
setImageIcon = setImageIcon,
|
||||
bridge = bridge
|
||||
)
|
||||
|
||||
@Provides
|
||||
|
|
|
@ -13,6 +13,7 @@ import com.anytypeio.anytype.domain.download.DownloadFile
|
|||
import com.anytypeio.anytype.domain.download.Downloader
|
||||
import com.anytypeio.anytype.domain.event.interactor.EventChannel
|
||||
import com.anytypeio.anytype.domain.event.interactor.InterceptEvents
|
||||
import com.anytypeio.anytype.domain.event.model.Payload
|
||||
import com.anytypeio.anytype.domain.icon.DocumentEmojiIconProvider
|
||||
import com.anytypeio.anytype.domain.misc.UrlBuilder
|
||||
import com.anytypeio.anytype.domain.page.*
|
||||
|
@ -26,6 +27,7 @@ import com.anytypeio.anytype.presentation.page.editor.Orchestrator
|
|||
import com.anytypeio.anytype.presentation.page.render.DefaultBlockViewRenderer
|
||||
import com.anytypeio.anytype.presentation.page.selection.SelectionStateHolder
|
||||
import com.anytypeio.anytype.presentation.page.toggle.ToggleStateHolder
|
||||
import com.anytypeio.anytype.presentation.util.Bridge
|
||||
import com.anytypeio.anytype.ui.page.PageFragment
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
@ -89,7 +91,8 @@ object EditorSessionModule {
|
|||
archiveDocument: ArchiveDocument,
|
||||
interactor: Orchestrator,
|
||||
getListPages: GetListPages,
|
||||
analytics: Analytics
|
||||
analytics: Analytics,
|
||||
bridge: Bridge<Payload>
|
||||
): PageViewModelFactory = PageViewModelFactory(
|
||||
openPage = openPage,
|
||||
closePage = closePage,
|
||||
|
@ -105,7 +108,8 @@ object EditorSessionModule {
|
|||
archiveDocument = archiveDocument,
|
||||
interactor = interactor,
|
||||
getListPages = getListPages,
|
||||
analytics = analytics
|
||||
analytics = analytics,
|
||||
bridge = bridge
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package com.anytypeio.anytype.di.main
|
||||
|
||||
import com.anytypeio.anytype.domain.event.model.Payload
|
||||
import com.anytypeio.anytype.presentation.util.Bridge
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
||||
@Module
|
||||
object BridgeModule {
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providePayloadPridge(): Bridge<Payload> = Bridge()
|
||||
}
|
|
@ -16,7 +16,8 @@ import javax.inject.Singleton
|
|||
UtilModule::class,
|
||||
EmojiModule::class,
|
||||
ClipboardModule::class,
|
||||
AnalyticsModule::class
|
||||
AnalyticsModule::class,
|
||||
BridgeModule::class
|
||||
]
|
||||
)
|
||||
interface MainComponent {
|
||||
|
|
|
@ -51,8 +51,10 @@ class DocumentIconActionMenuFragment : BaseFragment(R.layout.action_toolbar_page
|
|||
|
||||
@Inject
|
||||
lateinit var factory: DocumentIconActionMenuViewModelFactory
|
||||
|
||||
@Inject
|
||||
lateinit var analytics: Analytics
|
||||
|
||||
private val vm by viewModels<DocumentIconActionMenuViewModel> { factory }
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
|
|
|
@ -58,7 +58,7 @@ data class BlockEntity(
|
|||
}
|
||||
|
||||
data class Layout(val type: Type) : Content() {
|
||||
enum class Type { ROW, COLUMN, DIV }
|
||||
enum class Type { ROW, COLUMN, DIV, HEADER }
|
||||
}
|
||||
|
||||
data class Icon(
|
||||
|
|
|
@ -129,11 +129,11 @@ class BlockDataRepository(
|
|||
|
||||
override suspend fun setDocumentEmojiIcon(
|
||||
command: Command.SetDocumentEmojiIcon
|
||||
) = factory.remote.setDocumentEmojiIcon(command.toEntity())
|
||||
): Payload = factory.remote.setDocumentEmojiIcon(command.toEntity()).toDomain()
|
||||
|
||||
override suspend fun setDocumentImageIcon(
|
||||
command: Command.SetDocumentImageIcon
|
||||
) = factory.remote.setDocumentImageIcon(command.toEntity())
|
||||
): Payload = factory.remote.setDocumentImageIcon(command.toEntity()).toDomain()
|
||||
|
||||
override suspend fun setupBookmark(
|
||||
command: Command.SetupBookmark
|
||||
|
|
|
@ -32,8 +32,8 @@ interface BlockDataStore {
|
|||
suspend fun openProfile(id: String): PayloadEntity
|
||||
suspend fun closePage(id: String)
|
||||
suspend fun closeDashboard(id: String)
|
||||
suspend fun setDocumentEmojiIcon(command: CommandEntity.SetDocumentEmojiIcon)
|
||||
suspend fun setDocumentImageIcon(command: CommandEntity.SetDocumentImageIcon)
|
||||
suspend fun setDocumentEmojiIcon(command: CommandEntity.SetDocumentEmojiIcon): PayloadEntity
|
||||
suspend fun setDocumentImageIcon(command: CommandEntity.SetDocumentImageIcon): PayloadEntity
|
||||
suspend fun setupBookmark(command: CommandEntity.SetupBookmark) : PayloadEntity
|
||||
suspend fun undo(command: CommandEntity.Undo) : PayloadEntity
|
||||
suspend fun redo(command: CommandEntity.Redo) : PayloadEntity
|
||||
|
|
|
@ -31,8 +31,8 @@ interface BlockRemote {
|
|||
suspend fun closePage(id: String)
|
||||
suspend fun openDashboard(contextId: String, id: String): PayloadEntity
|
||||
suspend fun closeDashboard(id: String)
|
||||
suspend fun setDocumentEmojiIcon(command: CommandEntity.SetDocumentEmojiIcon)
|
||||
suspend fun setDocumentImageIcon(command: CommandEntity.SetDocumentImageIcon)
|
||||
suspend fun setDocumentEmojiIcon(command: CommandEntity.SetDocumentEmojiIcon): PayloadEntity
|
||||
suspend fun setDocumentImageIcon(command: CommandEntity.SetDocumentImageIcon): PayloadEntity
|
||||
suspend fun uploadBlock(command: CommandEntity.UploadBlock): PayloadEntity
|
||||
suspend fun setupBookmark(command: CommandEntity.SetupBookmark) : PayloadEntity
|
||||
suspend fun undo(command: CommandEntity.Undo) : PayloadEntity
|
||||
|
|
|
@ -91,11 +91,11 @@ class BlockRemoteDataStore(private val remote: BlockRemote) : BlockDataStore {
|
|||
|
||||
override suspend fun setDocumentEmojiIcon(
|
||||
command: CommandEntity.SetDocumentEmojiIcon
|
||||
) = remote.setDocumentEmojiIcon(command)
|
||||
): PayloadEntity = remote.setDocumentEmojiIcon(command)
|
||||
|
||||
override suspend fun setDocumentImageIcon(
|
||||
command: CommandEntity.SetDocumentImageIcon
|
||||
) = remote.setDocumentImageIcon(command)
|
||||
): PayloadEntity = remote.setDocumentImageIcon(command)
|
||||
|
||||
override suspend fun setupBookmark(
|
||||
command: CommandEntity.SetupBookmark
|
||||
|
|
|
@ -135,7 +135,7 @@ data class Block(
|
|||
}
|
||||
|
||||
data class Layout(val type: Type) : Content() {
|
||||
enum class Type { ROW, COLUMN, DIV }
|
||||
enum class Type { ROW, COLUMN, DIV, HEADER }
|
||||
}
|
||||
|
||||
data class Page(val style: Style) : Content() {
|
||||
|
|
|
@ -89,8 +89,8 @@ interface BlockRepository {
|
|||
*/
|
||||
suspend fun uploadBlock(command: Command.UploadBlock): Payload
|
||||
|
||||
suspend fun setDocumentEmojiIcon(command: Command.SetDocumentEmojiIcon)
|
||||
suspend fun setDocumentImageIcon(command: Command.SetDocumentImageIcon)
|
||||
suspend fun setDocumentEmojiIcon(command: Command.SetDocumentEmojiIcon): Payload
|
||||
suspend fun setDocumentImageIcon(command: Command.SetDocumentImageIcon): Payload
|
||||
|
||||
suspend fun setupBookmark(command: Command.SetupBookmark): Payload
|
||||
|
||||
|
|
|
@ -27,6 +27,29 @@ fun Map<Id, List<Block>>.descendants(parent: Id): List<Id> {
|
|||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds title block for a [Document]
|
||||
* @return title block
|
||||
* @throws NoSuchElementException if there was no title block in this document.
|
||||
*/
|
||||
fun Document.title(): Block {
|
||||
val header = first { block ->
|
||||
val cnt = block.content
|
||||
cnt is Content.Layout && cnt.type == Content.Layout.Type.HEADER
|
||||
}
|
||||
val children = filter { header.children.contains(it.id) }
|
||||
return children.first { child ->
|
||||
val cnt = child.content
|
||||
cnt is Content.Text && cnt.style == Content.Text.Style.TITLE
|
||||
}
|
||||
}
|
||||
|
||||
fun Document.titleId(): Id? {
|
||||
return find { block ->
|
||||
block.content is Content.Text && block.content.isTitle()
|
||||
}?.id
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform block structure for rendering purposes.
|
||||
* @param anchor a root or a parent block for some children blocks.
|
||||
|
|
|
@ -4,13 +4,14 @@ import com.anytypeio.anytype.domain.base.BaseUseCase
|
|||
import com.anytypeio.anytype.domain.block.model.Command
|
||||
import com.anytypeio.anytype.domain.block.repo.BlockRepository
|
||||
import com.anytypeio.anytype.domain.common.Id
|
||||
import com.anytypeio.anytype.domain.event.model.Payload
|
||||
|
||||
/**
|
||||
* Use-case for setting emoji icon.
|
||||
*/
|
||||
class SetDocumentEmojiIcon(
|
||||
private val repo: BlockRepository
|
||||
) : BaseUseCase<Any, SetDocumentEmojiIcon.Params>() {
|
||||
) : BaseUseCase<Payload, SetDocumentEmojiIcon.Params>() {
|
||||
|
||||
override suspend fun run(params: Params) = safe {
|
||||
repo.setDocumentEmojiIcon(
|
||||
|
|
|
@ -4,10 +4,11 @@ import com.anytypeio.anytype.domain.base.BaseUseCase
|
|||
import com.anytypeio.anytype.domain.block.model.Block
|
||||
import com.anytypeio.anytype.domain.block.model.Command
|
||||
import com.anytypeio.anytype.domain.block.repo.BlockRepository
|
||||
import com.anytypeio.anytype.domain.event.model.Payload
|
||||
|
||||
class SetDocumentImageIcon(
|
||||
private val repo: BlockRepository
|
||||
) : BaseUseCase<Unit, SetDocumentImageIcon.Params>() {
|
||||
) : BaseUseCase<Payload, SetDocumentImageIcon.Params>() {
|
||||
|
||||
override suspend fun run(params: Params) = safe {
|
||||
val hash = repo.uploadFile(
|
||||
|
|
|
@ -893,4 +893,127 @@ class BlockExtensionTest {
|
|||
actual = childrenIdsList
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return title block for document`() {
|
||||
|
||||
val root = MockDataFactory.randomUuid()
|
||||
|
||||
val title = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
style = Block.Content.Text.Style.TITLE,
|
||||
marks = emptyList()
|
||||
),
|
||||
children = emptyList(),
|
||||
fields = Block.Fields.empty()
|
||||
)
|
||||
|
||||
val header = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Layout(
|
||||
type = Block.Content.Layout.Type.HEADER
|
||||
),
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf(title.id)
|
||||
)
|
||||
|
||||
val a = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
fields = Block.Fields.empty(),
|
||||
children = emptyList(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
marks = emptyList(),
|
||||
style = Block.Content.Text.Style.NUMBERED
|
||||
)
|
||||
)
|
||||
|
||||
val page = Block(
|
||||
id = root,
|
||||
fields = Block.Fields(emptyMap()),
|
||||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf(header.id, a.id)
|
||||
)
|
||||
|
||||
val document = listOf(page, header, title, a)
|
||||
|
||||
val result = document.title()
|
||||
|
||||
val expected = title
|
||||
|
||||
assertEquals(expected = expected, actual = result)
|
||||
}
|
||||
|
||||
@Test(expected = NoSuchElementException::class)
|
||||
fun `should throw NoSuchElementException when title block is not present in header childs`() {
|
||||
|
||||
val root = MockDataFactory.randomUuid()
|
||||
|
||||
val header = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Layout(
|
||||
type = Block.Content.Layout.Type.HEADER
|
||||
),
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf()
|
||||
)
|
||||
|
||||
val a = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
fields = Block.Fields.empty(),
|
||||
children = emptyList(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
marks = emptyList(),
|
||||
style = Block.Content.Text.Style.NUMBERED
|
||||
)
|
||||
)
|
||||
|
||||
val page = Block(
|
||||
id = root,
|
||||
fields = Block.Fields(emptyMap()),
|
||||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf(header.id, a.id)
|
||||
)
|
||||
|
||||
val document = listOf(page, header, a)
|
||||
|
||||
val result = document.title()
|
||||
}
|
||||
|
||||
@Test(expected = NoSuchElementException::class)
|
||||
fun `should throw NoSuchElementException when header is not present`() {
|
||||
|
||||
val root = MockDataFactory.randomUuid()
|
||||
|
||||
val a = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
fields = Block.Fields.empty(),
|
||||
children = emptyList(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
marks = emptyList(),
|
||||
style = Block.Content.Text.Style.NUMBERED
|
||||
)
|
||||
)
|
||||
|
||||
val page = Block(
|
||||
id = root,
|
||||
fields = Block.Fields(emptyMap()),
|
||||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf(a.id)
|
||||
)
|
||||
|
||||
val document = listOf(page, a)
|
||||
|
||||
val result = document.title()
|
||||
}
|
||||
}
|
|
@ -109,11 +109,11 @@ class BlockMiddleware(
|
|||
|
||||
override suspend fun setDocumentEmojiIcon(
|
||||
command: CommandEntity.SetDocumentEmojiIcon
|
||||
) = middleware.setDocumentEmojiIcon(command)
|
||||
): PayloadEntity = middleware.setDocumentEmojiIcon(command)
|
||||
|
||||
override suspend fun setDocumentImageIcon(
|
||||
command: CommandEntity.SetDocumentImageIcon
|
||||
) = middleware.setDocumentImageIcon(command)
|
||||
): PayloadEntity = middleware.setDocumentImageIcon(command)
|
||||
|
||||
override suspend fun setupBookmark(
|
||||
command: CommandEntity.SetupBookmark
|
||||
|
|
|
@ -101,6 +101,7 @@ fun Block.layout(): BlockEntity.Content.Layout = BlockEntity.Content.Layout(
|
|||
Block.Content.Layout.Style.Column -> BlockEntity.Content.Layout.Type.COLUMN
|
||||
Block.Content.Layout.Style.Row -> BlockEntity.Content.Layout.Type.ROW
|
||||
Block.Content.Layout.Style.Div -> BlockEntity.Content.Layout.Type.DIV
|
||||
Block.Content.Layout.Style.Header -> BlockEntity.Content.Layout.Type.HEADER
|
||||
else -> throw IllegalStateException("Unexpected layout style: ${layout.style}")
|
||||
}
|
||||
)
|
||||
|
|
|
@ -765,7 +765,7 @@ public class Middleware {
|
|||
return new Pair<>(response.getBlockId(), mapper.toPayload(response.getEvent()));
|
||||
}
|
||||
|
||||
public void setDocumentEmojiIcon(CommandEntity.SetDocumentEmojiIcon command) throws Exception {
|
||||
public PayloadEntity setDocumentEmojiIcon(CommandEntity.SetDocumentEmojiIcon command) throws Exception {
|
||||
|
||||
Value emojiValue = Value.newBuilder().setStringValue(command.getEmoji()).build();
|
||||
Value imageValue = Value.newBuilder().setStringValue("").build();
|
||||
|
@ -798,9 +798,11 @@ public class Middleware {
|
|||
if (BuildConfig.DEBUG) {
|
||||
Timber.d(response.getClass().getName() + "\n" + response.toString());
|
||||
}
|
||||
|
||||
return mapper.toPayload(response.getEvent());
|
||||
}
|
||||
|
||||
public void setDocumentImageIcon(CommandEntity.SetDocumentImageIcon command) throws Exception {
|
||||
public PayloadEntity setDocumentImageIcon(CommandEntity.SetDocumentImageIcon command) throws Exception {
|
||||
|
||||
Value imageValue = Value.newBuilder().setStringValue(command.getHash()).build();
|
||||
Value emojiValue = Value.newBuilder().setStringValue("").build();
|
||||
|
@ -833,6 +835,8 @@ public class Middleware {
|
|||
if (BuildConfig.DEBUG) {
|
||||
Timber.d(response.getClass().getName() + "\n" + response.toString());
|
||||
}
|
||||
|
||||
return mapper.toPayload(response.getEvent());
|
||||
}
|
||||
|
||||
public PayloadEntity setupBookmark(CommandEntity.SetupBookmark command) throws Exception {
|
||||
|
|
|
@ -74,6 +74,7 @@ import com.anytypeio.anytype.presentation.page.render.BlockViewRenderer
|
|||
import com.anytypeio.anytype.presentation.page.render.DefaultBlockViewRenderer
|
||||
import com.anytypeio.anytype.presentation.page.selection.SelectionStateHolder
|
||||
import com.anytypeio.anytype.presentation.page.toggle.ToggleStateHolder
|
||||
import com.anytypeio.anytype.presentation.util.Bridge
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.delay
|
||||
|
@ -96,7 +97,8 @@ class PageViewModel(
|
|||
private val renderer: DefaultBlockViewRenderer,
|
||||
private val orchestrator: Orchestrator,
|
||||
private val getListPages: GetListPages,
|
||||
private val analytics: Analytics
|
||||
private val analytics: Analytics,
|
||||
private val bridge: Bridge<Payload>
|
||||
) : ViewStateViewModel<ViewState>(),
|
||||
SupportNavigation<EventWrapper<AppNavigation.Command>>,
|
||||
SupportCommand<Command>,
|
||||
|
@ -110,7 +112,7 @@ class PageViewModel(
|
|||
|
||||
private val views: List<BlockView> get() = orchestrator.stores.views.current()
|
||||
|
||||
private var eventSubscription: Job? = null
|
||||
private val jobs = mutableListOf<Job>()
|
||||
|
||||
private var mode = EditorMode.EDITING
|
||||
|
||||
|
@ -192,7 +194,6 @@ class PageViewModel(
|
|||
}
|
||||
|
||||
private fun proceedWithUpdatingDocumentTitle(update: String) {
|
||||
|
||||
viewModelScope.launch {
|
||||
orchestrator.proxies.intents.send(
|
||||
Intent.Document.UpdateTitle(
|
||||
|
@ -471,13 +472,20 @@ class PageViewModel(
|
|||
|
||||
stateData.postValue(ViewState.Loading)
|
||||
|
||||
eventSubscription = viewModelScope.launch {
|
||||
jobs += viewModelScope.launch {
|
||||
interceptEvents
|
||||
.build(InterceptEvents.Params(context))
|
||||
.map { events -> processEvents(events) }
|
||||
.collect { refresh() }
|
||||
}
|
||||
|
||||
jobs += viewModelScope.launch {
|
||||
bridge
|
||||
.flow()
|
||||
.filter { it.context == context }
|
||||
.collect { orchestrator.proxies.payloads.send(it) }
|
||||
}
|
||||
|
||||
viewModelScope.launch {
|
||||
openPage(OpenPage.Params(id)).proceed(
|
||||
success = { result ->
|
||||
|
@ -506,16 +514,23 @@ class PageViewModel(
|
|||
val event = payload.events.find { it is Event.Command.ShowBlock }
|
||||
if (event is Event.Command.ShowBlock) {
|
||||
val root = event.blocks.find { it.id == context }
|
||||
if (root == null) {
|
||||
Timber.e("Could not find the root block on initial focusing")
|
||||
} else if (root.children.isEmpty()) {
|
||||
val focus = Editor.Focus(id = root.id, cursor = Editor.Cursor.End)
|
||||
viewModelScope.launch { orchestrator.stores.focus.update(focus) }
|
||||
controlPanelInteractor.onEvent(
|
||||
ControlPanelMachine.Event.OnFocusChanged(
|
||||
id = root.id, style = Content.Text.Style.TITLE
|
||||
)
|
||||
)
|
||||
when {
|
||||
root == null -> Timber.e("Could not find the root block on initial focusing")
|
||||
root.children.size == 1 -> {
|
||||
val first = event.blocks.first { it.id == root.children.first() }
|
||||
val content = first.content
|
||||
if (content is Content.Layout && content.type == Content.Layout.Type.HEADER) {
|
||||
val title = event.blocks.title()
|
||||
val focus = Editor.Focus(id = title.id, cursor = Editor.Cursor.End)
|
||||
viewModelScope.launch { orchestrator.stores.focus.update(focus) }
|
||||
controlPanelInteractor.onEvent(
|
||||
ControlPanelMachine.Event.OnFocusChanged(
|
||||
id = title.id, style = Content.Text.Style.TITLE
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> Timber.d("Skipping initial focusing, document is not empty.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -859,6 +874,7 @@ class PageViewModel(
|
|||
target = id
|
||||
)
|
||||
} else {
|
||||
if (previous is BlockView.Title) _toasts.offer("Merging with title currently not supported")
|
||||
Timber.d("Skipping merge because previous block is not a text block")
|
||||
}
|
||||
} else {
|
||||
|
@ -1301,26 +1317,10 @@ class PageViewModel(
|
|||
)
|
||||
}
|
||||
} else {
|
||||
|
||||
var id = target.id
|
||||
|
||||
val position: Position
|
||||
|
||||
if (target.id == context) {
|
||||
if (target.children.isEmpty())
|
||||
position = Position.INNER
|
||||
else {
|
||||
position = Position.TOP
|
||||
id = target.children.first()
|
||||
}
|
||||
} else {
|
||||
position = Position.BOTTOM
|
||||
}
|
||||
|
||||
proceedWithCreatingNewTextBlock(
|
||||
id = id,
|
||||
id = target.id,
|
||||
style = style,
|
||||
position = position
|
||||
position = Position.BOTTOM
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1403,37 +1403,18 @@ class PageViewModel(
|
|||
}
|
||||
|
||||
fun onAddFileBlockClicked(type: Content.File.Type) {
|
||||
|
||||
val focused = blocks.first { it.id == orchestrator.stores.focus.current().id }
|
||||
|
||||
val content = focused.content
|
||||
|
||||
if (content is Content.Text && content.text.isEmpty()) {
|
||||
proceedWithReplacingByEmptyFileBlock(
|
||||
id = focused.id,
|
||||
type = type
|
||||
)
|
||||
} else {
|
||||
|
||||
val position: Position
|
||||
|
||||
var target: Id = focused.id
|
||||
|
||||
if (focused.id == context) {
|
||||
if (focused.children.isEmpty()) {
|
||||
position = Position.INNER
|
||||
} else {
|
||||
position = Position.TOP
|
||||
target = focused.children.first()
|
||||
}
|
||||
} else {
|
||||
position = Position.BOTTOM
|
||||
}
|
||||
|
||||
proceedWithCreatingEmptyFileBlock(
|
||||
id = target,
|
||||
id = focused.id,
|
||||
type = type,
|
||||
position = position
|
||||
position = Position.BOTTOM
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1511,8 +1492,10 @@ class PageViewModel(
|
|||
}
|
||||
|
||||
fun onBlockToolbarStyleClicked() {
|
||||
if (orchestrator.stores.focus.current().id == context) {
|
||||
_toasts.offer("Changing style for title currently not supported")
|
||||
val target = orchestrator.stores.focus.current().id
|
||||
val view = views.first { it.id == target }
|
||||
if (view is BlockView.Title) {
|
||||
_toasts.offer(CANNOT_OPEN_STYLE_PANEL_FOR_TITLE_ERROR)
|
||||
} else {
|
||||
val textSelection = orchestrator.stores.textSelection.current()
|
||||
controlPanelInteractor.onEvent(
|
||||
|
@ -1534,14 +1517,12 @@ class PageViewModel(
|
|||
}
|
||||
|
||||
fun onBlockToolbarBlockActionsClicked() {
|
||||
if (orchestrator.stores.focus.current().id == context) {
|
||||
_toasts.offer("Not implemented for title")
|
||||
val target = orchestrator.stores.focus.current().id
|
||||
val view = views.first { it.id == target }
|
||||
if (view is BlockView.Title) {
|
||||
_toasts.offer(CANNOT_OPEN_ACTION_MENU_FOR_TITLE_ERROR)
|
||||
} else {
|
||||
dispatch(
|
||||
Command.Measure(
|
||||
target = orchestrator.stores.focus.current().id
|
||||
)
|
||||
)
|
||||
dispatch(Command.Measure(target = target))
|
||||
viewModelScope.sendEvent(
|
||||
analytics = analytics,
|
||||
eventName = EventsDictionary.BTN_BLOCK_ACTIONS
|
||||
|
@ -1708,7 +1689,7 @@ class PageViewModel(
|
|||
|
||||
private fun onSelectAllClicked(state: ViewState.Success) =
|
||||
state.blocks.map { block ->
|
||||
if (block.id != context) select(block.id)
|
||||
if (block.id != blocks.titleId()) select(block.id)
|
||||
block.updateSelection(newSelection = true)
|
||||
}.let {
|
||||
onMultiSelectModeBlockClicked()
|
||||
|
@ -1717,7 +1698,7 @@ class PageViewModel(
|
|||
|
||||
private fun onUnselectAllClicked(state: ViewState.Success) =
|
||||
state.blocks.map { block ->
|
||||
if (block.id != context) unselect(block.id)
|
||||
unselect(block.id)
|
||||
block.updateSelection(newSelection = false)
|
||||
}.let {
|
||||
onMultiSelectModeBlockClicked()
|
||||
|
@ -1892,6 +1873,7 @@ class PageViewModel(
|
|||
}
|
||||
|
||||
fun onOutsideClicked() {
|
||||
|
||||
val root = blocks.find { it.id == context } ?: return
|
||||
|
||||
if (root.children.isEmpty()) {
|
||||
|
@ -1928,6 +1910,9 @@ class PageViewModel(
|
|||
is Content.Divider -> {
|
||||
addNewBlockAtTheEnd()
|
||||
}
|
||||
is Content.Layout -> {
|
||||
addNewBlockAtTheEnd()
|
||||
}
|
||||
else -> {
|
||||
Timber.d("Outside-click has been ignored.")
|
||||
}
|
||||
|
@ -2740,6 +2725,11 @@ class PageViewModel(
|
|||
const val CANNOT_MOVE_PARENT_INTO_CHILD =
|
||||
"Cannot move parent into child. Please, check selected blocks."
|
||||
const val MENTION_TITLE_EMPTY = "Untitled"
|
||||
|
||||
const val CANNOT_OPEN_ACTION_MENU_FOR_TITLE_ERROR =
|
||||
"Opening action menu for title currently not supported"
|
||||
const val CANNOT_OPEN_STYLE_PANEL_FOR_TITLE_ERROR =
|
||||
"Opening style panel for title currently not supported"
|
||||
}
|
||||
|
||||
data class MarkupAction(
|
||||
|
@ -2767,7 +2757,10 @@ class PageViewModel(
|
|||
|
||||
fun onStop() {
|
||||
Timber.d("onStop")
|
||||
eventSubscription?.cancel()
|
||||
jobs.apply {
|
||||
forEach { it.cancel() }
|
||||
clear()
|
||||
}
|
||||
}
|
||||
|
||||
enum class Session { IDLE, OPEN, ERROR }
|
||||
|
|
|
@ -8,12 +8,14 @@ import com.anytypeio.anytype.domain.block.interactor.UpdateLinkMarks
|
|||
import com.anytypeio.anytype.domain.block.model.Block
|
||||
import com.anytypeio.anytype.domain.event.interactor.InterceptEvents
|
||||
import com.anytypeio.anytype.domain.event.model.Event
|
||||
import com.anytypeio.anytype.domain.event.model.Payload
|
||||
import com.anytypeio.anytype.domain.misc.UrlBuilder
|
||||
import com.anytypeio.anytype.domain.page.*
|
||||
import com.anytypeio.anytype.domain.page.navigation.GetListPages
|
||||
import com.anytypeio.anytype.presentation.common.StateReducer
|
||||
import com.anytypeio.anytype.presentation.page.editor.Orchestrator
|
||||
import com.anytypeio.anytype.presentation.page.render.DefaultBlockViewRenderer
|
||||
import com.anytypeio.anytype.presentation.util.Bridge
|
||||
|
||||
open class PageViewModelFactory(
|
||||
private val openPage: OpenPage,
|
||||
|
@ -30,7 +32,8 @@ open class PageViewModelFactory(
|
|||
private val renderer: DefaultBlockViewRenderer,
|
||||
private val interactor: Orchestrator,
|
||||
private val getListPages: GetListPages,
|
||||
private val analytics: Analytics
|
||||
private val analytics: Analytics,
|
||||
private val bridge: Bridge<Payload>
|
||||
) : ViewModelProvider.Factory {
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
|
@ -50,7 +53,8 @@ open class PageViewModelFactory(
|
|||
createNewDocument = createNewDocument,
|
||||
orchestrator = interactor,
|
||||
getListPages = getListPages,
|
||||
analytics = analytics
|
||||
analytics = analytics,
|
||||
bridge = bridge
|
||||
) as T
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ package com.anytypeio.anytype.presentation.page.picker
|
|||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.anytypeio.anytype.domain.common.Id
|
||||
import com.anytypeio.anytype.domain.event.model.Payload
|
||||
import com.anytypeio.anytype.domain.icon.SetDocumentEmojiIcon
|
||||
import com.anytypeio.anytype.emojifier.data.Emoji
|
||||
import com.anytypeio.anytype.emojifier.data.EmojiProvider
|
||||
|
@ -10,6 +11,7 @@ import com.anytypeio.anytype.emojifier.suggest.EmojiSuggester
|
|||
import com.anytypeio.anytype.emojifier.suggest.model.EmojiSuggest
|
||||
import com.anytypeio.anytype.library_page_icon_picker_widget.model.EmojiPickerView
|
||||
import com.anytypeio.anytype.presentation.page.editor.Proxy
|
||||
import com.anytypeio.anytype.presentation.util.Bridge
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -19,7 +21,8 @@ import timber.log.Timber
|
|||
class DocumentEmojiIconPickerViewModel(
|
||||
private val setEmojiIcon: SetDocumentEmojiIcon,
|
||||
private val provider: EmojiProvider,
|
||||
private val suggester: EmojiSuggester
|
||||
private val suggester: EmojiSuggester,
|
||||
private val bridge: Bridge<Payload>
|
||||
) : ViewModel() {
|
||||
|
||||
/**
|
||||
|
@ -126,7 +129,10 @@ class DocumentEmojiIconPickerViewModel(
|
|||
)
|
||||
).proceed(
|
||||
failure = { Timber.e(it, "Error while setting emoji") },
|
||||
success = { state.apply { value = ViewState.Exit } }
|
||||
success = {
|
||||
bridge.send(it)
|
||||
state.apply { value = ViewState.Exit }
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,14 +2,17 @@ package com.anytypeio.anytype.presentation.page.picker
|
|||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.anytypeio.anytype.domain.event.model.Payload
|
||||
import com.anytypeio.anytype.domain.icon.SetDocumentEmojiIcon
|
||||
import com.anytypeio.anytype.emojifier.data.EmojiProvider
|
||||
import com.anytypeio.anytype.emojifier.suggest.EmojiSuggester
|
||||
import com.anytypeio.anytype.presentation.util.Bridge
|
||||
|
||||
class DocumentEmojiIconPickerViewModelFactory(
|
||||
private val setEmojiIcon: SetDocumentEmojiIcon,
|
||||
private val emojiSuggester: EmojiSuggester,
|
||||
private val emojiProvider: EmojiProvider
|
||||
private val emojiProvider: EmojiProvider,
|
||||
private val bridge: Bridge<Payload>
|
||||
) : ViewModelProvider.Factory {
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
|
@ -17,7 +20,8 @@ class DocumentEmojiIconPickerViewModelFactory(
|
|||
return DocumentEmojiIconPickerViewModel(
|
||||
setEmojiIcon = setEmojiIcon,
|
||||
suggester = emojiSuggester,
|
||||
provider = emojiProvider
|
||||
provider = emojiProvider,
|
||||
bridge = bridge
|
||||
) as T
|
||||
}
|
||||
}
|
|
@ -2,12 +2,14 @@ package com.anytypeio.anytype.presentation.page.picker
|
|||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.anytypeio.anytype.core_utils.ui.ViewStateViewModel
|
||||
import com.anytypeio.anytype.domain.event.model.Payload
|
||||
import com.anytypeio.anytype.domain.icon.SetDocumentEmojiIcon
|
||||
import com.anytypeio.anytype.domain.icon.SetDocumentImageIcon
|
||||
import com.anytypeio.anytype.emojifier.data.Emoji
|
||||
import com.anytypeio.anytype.presentation.common.StateReducer
|
||||
import com.anytypeio.anytype.presentation.page.picker.DocumentIconActionMenuViewModel.Contract.*
|
||||
import com.anytypeio.anytype.presentation.page.picker.DocumentIconActionMenuViewModel.ViewState
|
||||
import com.anytypeio.anytype.presentation.util.Bridge
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.ConflatedBroadcastChannel
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
@ -15,7 +17,8 @@ import kotlinx.coroutines.launch
|
|||
|
||||
class DocumentIconActionMenuViewModel(
|
||||
private val setEmojiIcon: SetDocumentEmojiIcon,
|
||||
private val setImageIcon: SetDocumentImageIcon
|
||||
private val setImageIcon: SetDocumentImageIcon,
|
||||
private val bridge: Bridge<Payload>
|
||||
) : ViewStateViewModel<ViewState>(), StateReducer<State, Event> {
|
||||
|
||||
private val events = ConflatedBroadcastChannel<Event>()
|
||||
|
@ -49,7 +52,10 @@ class DocumentIconActionMenuViewModel(
|
|||
context = action.context
|
||||
)
|
||||
).proceed(
|
||||
success = { events.send(Event.OnCompleted) },
|
||||
success = {
|
||||
bridge.send(it)
|
||||
events.send(Event.OnCompleted)
|
||||
},
|
||||
failure = { events.send(Event.Failure(it)) }
|
||||
)
|
||||
is Action.ClearEmoji -> setEmojiIcon(
|
||||
|
@ -59,7 +65,10 @@ class DocumentIconActionMenuViewModel(
|
|||
context = action.context
|
||||
)
|
||||
).proceed(
|
||||
success = { events.send(Event.OnCompleted) },
|
||||
success = {
|
||||
bridge.send(it)
|
||||
events.send(Event.OnCompleted)
|
||||
},
|
||||
failure = { events.send(Event.Failure(it)) }
|
||||
)
|
||||
is Action.SetImageIcon -> setImageIcon(
|
||||
|
@ -69,7 +78,10 @@ class DocumentIconActionMenuViewModel(
|
|||
)
|
||||
).proceed(
|
||||
failure = { events.send(Event.Failure(it)) },
|
||||
success = { events.send(Event.OnCompleted) }
|
||||
success = {
|
||||
bridge.send(it)
|
||||
events.send(Event.OnCompleted)
|
||||
}
|
||||
)
|
||||
is Action.PickRandomEmoji -> {
|
||||
val random = Emoji.DATA.random().random()
|
||||
|
|
|
@ -2,17 +2,21 @@ package com.anytypeio.anytype.presentation.page.picker
|
|||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.anytypeio.anytype.domain.event.model.Payload
|
||||
import com.anytypeio.anytype.domain.icon.SetDocumentEmojiIcon
|
||||
import com.anytypeio.anytype.domain.icon.SetDocumentImageIcon
|
||||
import com.anytypeio.anytype.presentation.util.Bridge
|
||||
|
||||
class DocumentIconActionMenuViewModelFactory(
|
||||
private val setEmojiIcon: SetDocumentEmojiIcon,
|
||||
private val setImageIcon: SetDocumentImageIcon
|
||||
private val setImageIcon: SetDocumentImageIcon,
|
||||
private val bridge: Bridge<Payload>
|
||||
) : ViewModelProvider.Factory {
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T = DocumentIconActionMenuViewModel(
|
||||
setEmojiIcon = setEmojiIcon,
|
||||
setImageIcon = setImageIcon
|
||||
setImageIcon = setImageIcon,
|
||||
bridge = bridge
|
||||
) as T
|
||||
}
|
|
@ -11,6 +11,7 @@ import com.anytypeio.anytype.domain.misc.UrlBuilder
|
|||
import com.anytypeio.anytype.domain.page.EditorMode
|
||||
import com.anytypeio.anytype.presentation.mapper.*
|
||||
import com.anytypeio.anytype.presentation.page.toggle.ToggleStateHolder
|
||||
import timber.log.Timber
|
||||
|
||||
class DefaultBlockViewRenderer(
|
||||
private val counter: Counter,
|
||||
|
@ -31,14 +32,19 @@ class DefaultBlockViewRenderer(
|
|||
|
||||
val result = mutableListOf<BlockView>()
|
||||
|
||||
buildTitle(
|
||||
mode = mode,
|
||||
anchor = anchor,
|
||||
root = root,
|
||||
result = result,
|
||||
details = details,
|
||||
focus = focus
|
||||
)
|
||||
if (anchor == root.id) {
|
||||
root.content.let { cnt ->
|
||||
if (cnt is Content.Smart && cnt.type == Content.Smart.Type.ARCHIVE) {
|
||||
result.add(
|
||||
BlockView.Title.Archive(
|
||||
mode = BlockView.Mode.READ,
|
||||
id = anchor,
|
||||
text = details.details[root.id]?.name
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
counter.reset()
|
||||
|
||||
|
@ -48,7 +54,16 @@ class DefaultBlockViewRenderer(
|
|||
when (content.style) {
|
||||
Content.Text.Style.TITLE -> {
|
||||
counter.reset()
|
||||
result.add(title(mode, block, content, root, focus))
|
||||
result.add(
|
||||
title(
|
||||
mode = mode,
|
||||
block = block,
|
||||
content = content,
|
||||
focus = focus,
|
||||
root = root,
|
||||
details = details
|
||||
)
|
||||
)
|
||||
}
|
||||
Content.Text.Style.P -> {
|
||||
counter.reset()
|
||||
|
@ -330,83 +345,6 @@ class DefaultBlockViewRenderer(
|
|||
return result
|
||||
}
|
||||
|
||||
private fun buildTitle(
|
||||
mode: EditorMode,
|
||||
anchor: Id,
|
||||
root: Block,
|
||||
result: MutableList<BlockView>,
|
||||
details: Block.Details,
|
||||
focus: Focus
|
||||
) {
|
||||
if (anchor == root.id) {
|
||||
|
||||
val cursor = if (anchor == focus.id) {
|
||||
focus.cursor?.let { cursor ->
|
||||
when (cursor) {
|
||||
is Cursor.Start -> 0
|
||||
is Cursor.End -> details.details[root.id]?.name?.length ?: 0
|
||||
is Cursor.Range -> cursor.range.first
|
||||
}
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val viewMode =
|
||||
if (mode == EditorMode.EDITING)
|
||||
BlockView.Mode.EDIT
|
||||
else
|
||||
BlockView.Mode.READ
|
||||
val text = details.details[root.id]?.name
|
||||
val isFocused = anchor == focus.id
|
||||
val type = (root.content as? Content.Smart)?.type
|
||||
result.add(
|
||||
when (type) {
|
||||
Content.Smart.Type.PROFILE -> {
|
||||
BlockView.Title.Profile(
|
||||
mode = viewMode,
|
||||
id = anchor,
|
||||
isFocused = isFocused,
|
||||
text = text,
|
||||
image = details.details[root.id]?.iconImage?.let { name ->
|
||||
if (name.isNotEmpty()) urlBuilder.thumbnail(name) else null
|
||||
},
|
||||
cursor = cursor
|
||||
)
|
||||
}
|
||||
Content.Smart.Type.ARCHIVE -> {
|
||||
BlockView.Title.Archive(
|
||||
mode = viewMode,
|
||||
id = anchor,
|
||||
text = text
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
BlockView.Title.Document(
|
||||
mode = viewMode,
|
||||
id = anchor,
|
||||
isFocused = isFocused,
|
||||
text = text,
|
||||
emoji = details.details[root.id]?.iconEmoji?.let { name ->
|
||||
if (name.isNotEmpty())
|
||||
name
|
||||
else
|
||||
null
|
||||
},
|
||||
image = details.details[root.id]?.iconImage?.let { name ->
|
||||
if (name.isNotEmpty())
|
||||
urlBuilder.thumbnail(name)
|
||||
else
|
||||
null
|
||||
},
|
||||
cursor = cursor
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun paragraph(
|
||||
mode: EditorMode,
|
||||
block: Block,
|
||||
|
@ -673,19 +611,66 @@ class DefaultBlockViewRenderer(
|
|||
block: Block,
|
||||
content: Content.Text,
|
||||
root: Block,
|
||||
focus: Focus
|
||||
): BlockView.Title.Document = BlockView.Title.Document(
|
||||
mode = if (mode == EditorMode.EDITING) BlockView.Mode.EDIT else BlockView.Mode.READ,
|
||||
id = block.id,
|
||||
text = content.text,
|
||||
emoji = root.fields.iconEmoji?.let { name ->
|
||||
if (name.isNotEmpty())
|
||||
name
|
||||
else
|
||||
null
|
||||
},
|
||||
isFocused = block.id == focus.id
|
||||
)
|
||||
focus: Focus,
|
||||
details: Block.Details
|
||||
): BlockView.Title {
|
||||
|
||||
Timber.d("Focus while building title: $focus")
|
||||
|
||||
val cursor: Int?
|
||||
|
||||
cursor = if (focus.id == block.id) {
|
||||
focus.cursor?.let { crs ->
|
||||
when (crs) {
|
||||
is Cursor.Start -> 0
|
||||
is Cursor.End -> content.text.length
|
||||
is Cursor.Range -> crs.range.first
|
||||
}
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val rootContent = root.content
|
||||
|
||||
check(rootContent is Content.Smart)
|
||||
|
||||
return when (rootContent.type) {
|
||||
Content.Smart.Type.PAGE -> BlockView.Title.Document(
|
||||
mode = if (mode == EditorMode.EDITING) BlockView.Mode.EDIT else BlockView.Mode.READ,
|
||||
id = block.id,
|
||||
text = content.text,
|
||||
emoji = details.details[root.id]?.iconEmoji?.let { name ->
|
||||
if (name.isNotEmpty())
|
||||
name
|
||||
else
|
||||
null
|
||||
},
|
||||
image = details.details[root.id]?.iconImage?.let { name ->
|
||||
if (name.isNotEmpty())
|
||||
urlBuilder.thumbnail(name)
|
||||
else
|
||||
null
|
||||
},
|
||||
isFocused = block.id == focus.id,
|
||||
cursor = cursor
|
||||
)
|
||||
Content.Smart.Type.PROFILE -> BlockView.Title.Profile(
|
||||
mode = if (mode == EditorMode.EDITING) BlockView.Mode.EDIT else BlockView.Mode.READ,
|
||||
id = block.id,
|
||||
text = content.text,
|
||||
image = details.details[root.id]?.iconImage?.let { name ->
|
||||
if (name.isNotEmpty())
|
||||
urlBuilder.thumbnail(name)
|
||||
else
|
||||
null
|
||||
},
|
||||
isFocused = block.id == focus.id,
|
||||
cursor = cursor
|
||||
)
|
||||
else -> throw IllegalStateException("Unexpected root block content: ${root.content}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun toPages(
|
||||
block: Block,
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package com.anytypeio.anytype.presentation.util
|
||||
|
||||
import kotlinx.coroutines.channels.BroadcastChannel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.asFlow
|
||||
|
||||
/**
|
||||
* Event bus for passing data between view models.
|
||||
*/
|
||||
class Bridge<T> {
|
||||
private val channel = BroadcastChannel<T>(1)
|
||||
suspend fun send(t: T) = channel.send(t)
|
||||
fun flow(): Flow<T> = channel.asFlow()
|
||||
}
|
|
@ -2,16 +2,9 @@ package com.anytypeio.anytype.presentation
|
|||
|
||||
import MockDataFactory
|
||||
import com.anytypeio.anytype.domain.block.model.Block
|
||||
import java.util.concurrent.ThreadLocalRandom
|
||||
|
||||
object MockBlockFactory {
|
||||
|
||||
fun randomStyle() : Block.Content.Text.Style {
|
||||
val styles = Block.Content.Text.Style.values()
|
||||
val random = ThreadLocalRandom.current().nextInt(styles.size)
|
||||
return styles[random]
|
||||
}
|
||||
|
||||
fun makeOnePageWithOneTextBlock(
|
||||
root: String,
|
||||
child: String,
|
||||
|
@ -20,8 +13,8 @@ object MockBlockFactory {
|
|||
Block(
|
||||
id = root,
|
||||
fields = Block.Fields(emptyMap()),
|
||||
content = Block.Content.Page(
|
||||
style = Block.Content.Page.Style.SET
|
||||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf(child)
|
||||
),
|
||||
|
|
|
@ -9,6 +9,7 @@ import com.anytypeio.anytype.domain.block.model.Block
|
|||
import com.anytypeio.anytype.domain.event.model.Event
|
||||
import com.anytypeio.anytype.domain.ext.content
|
||||
import com.anytypeio.anytype.presentation.page.editor.ViewState
|
||||
import com.anytypeio.anytype.presentation.util.TXT
|
||||
import com.jraska.livedata.test
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
@ -45,12 +46,12 @@ class BlockReadModeTest : PageViewModelTest() {
|
|||
Block(
|
||||
id = root,
|
||||
fields = Block.Fields.empty(),
|
||||
content = Block.Content.Page(
|
||||
style = Block.Content.Page.Style.SET
|
||||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = blocks.map { it.id }
|
||||
children = listOf(header.id) + blocks.map { it.id }
|
||||
)
|
||||
) + blocks
|
||||
) + listOf(header, title) + blocks
|
||||
|
||||
private val blockViewsReadMode = listOf<BlockView>(
|
||||
blocks[0].let { p ->
|
||||
|
@ -90,6 +91,20 @@ class BlockReadModeTest : PageViewModelTest() {
|
|||
}
|
||||
)
|
||||
|
||||
private val titleEditModeView = BlockView.Title.Document(
|
||||
id = title.id,
|
||||
text = title.content<TXT>().text,
|
||||
isFocused = false,
|
||||
mode = BlockView.Mode.EDIT
|
||||
)
|
||||
|
||||
private val titleReadModeView = BlockView.Title.Document(
|
||||
id = title.id,
|
||||
text = title.content<TXT>().text,
|
||||
isFocused = false,
|
||||
mode = BlockView.Mode.READ
|
||||
)
|
||||
|
||||
private val flow: Flow<List<Event.Command>> = flow {
|
||||
delay(100)
|
||||
emit(
|
||||
|
@ -126,18 +141,11 @@ class BlockReadModeTest : PageViewModelTest() {
|
|||
|
||||
val testObserver = vm.state.test()
|
||||
|
||||
val title = BlockView.Title.Document(
|
||||
id = root,
|
||||
text = null,
|
||||
isFocused = false,
|
||||
mode = BlockView.Mode.READ
|
||||
)
|
||||
|
||||
val initial = blockViewsReadMode
|
||||
|
||||
testObserver.assertValue(
|
||||
ViewState.Success(
|
||||
blocks = listOf(title) + initial
|
||||
blocks = listOf(titleReadModeView) + initial
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -167,18 +175,11 @@ class BlockReadModeTest : PageViewModelTest() {
|
|||
|
||||
val testObserver = vm.state.test()
|
||||
|
||||
val title = BlockView.Title.Document(
|
||||
id = root,
|
||||
text = null,
|
||||
isFocused = false,
|
||||
mode = BlockView.Mode.EDIT
|
||||
)
|
||||
|
||||
val initial = blockViewsEditMode
|
||||
|
||||
testObserver.assertValue(
|
||||
ViewState.Success(
|
||||
blocks = listOf(title) + initial
|
||||
blocks = listOf(titleEditModeView) + initial
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -208,18 +209,11 @@ class BlockReadModeTest : PageViewModelTest() {
|
|||
|
||||
val testObserver = vm.state.test()
|
||||
|
||||
val title = BlockView.Title.Document(
|
||||
id = root,
|
||||
text = null,
|
||||
isFocused = false,
|
||||
mode = BlockView.Mode.EDIT
|
||||
)
|
||||
|
||||
val initial = blockViewsEditMode
|
||||
|
||||
testObserver.assertValue(
|
||||
ViewState.Success(
|
||||
blocks = listOf(title) + initial
|
||||
blocks = listOf(titleEditModeView) + initial
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -249,18 +243,11 @@ class BlockReadModeTest : PageViewModelTest() {
|
|||
|
||||
val testObserver = vm.state.test()
|
||||
|
||||
val title = BlockView.Title.Document(
|
||||
id = root,
|
||||
text = null,
|
||||
isFocused = false,
|
||||
mode = BlockView.Mode.EDIT
|
||||
)
|
||||
|
||||
val initial = blockViewsEditMode
|
||||
|
||||
testObserver.assertValue(
|
||||
ViewState.Success(
|
||||
blocks = listOf(title) + initial
|
||||
blocks = listOf(titleEditModeView) + initial
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -290,18 +277,11 @@ class BlockReadModeTest : PageViewModelTest() {
|
|||
|
||||
val testObserver = vm.state.test()
|
||||
|
||||
val title = BlockView.Title.Document(
|
||||
id = root,
|
||||
text = null,
|
||||
isFocused = false,
|
||||
mode = BlockView.Mode.EDIT
|
||||
)
|
||||
|
||||
val initial = blockViewsEditMode
|
||||
|
||||
testObserver.assertValue(
|
||||
ViewState.Success(
|
||||
blocks = listOf(title) + initial
|
||||
blocks = listOf(titleEditModeView) + initial
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -331,18 +311,11 @@ class BlockReadModeTest : PageViewModelTest() {
|
|||
|
||||
val testObserver = vm.state.test()
|
||||
|
||||
val title = BlockView.Title.Document(
|
||||
id = root,
|
||||
text = null,
|
||||
isFocused = false,
|
||||
mode = BlockView.Mode.EDIT
|
||||
)
|
||||
|
||||
val initial = blockViewsEditMode
|
||||
|
||||
testObserver.assertValue(
|
||||
ViewState.Success(
|
||||
blocks = listOf(title) + initial
|
||||
blocks = listOf(titleEditModeView) + initial
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -372,13 +345,6 @@ class BlockReadModeTest : PageViewModelTest() {
|
|||
|
||||
val testObserver = vm.state.test()
|
||||
|
||||
val title = BlockView.Title.Document(
|
||||
id = root,
|
||||
text = null,
|
||||
isFocused = false,
|
||||
mode = BlockView.Mode.EDIT
|
||||
)
|
||||
|
||||
val initial = blockViewsEditMode
|
||||
|
||||
coroutineTestRule.advanceTime(PageViewModel.TEXT_CHANGES_DEBOUNCE_DURATION)
|
||||
|
@ -386,7 +352,7 @@ class BlockReadModeTest : PageViewModelTest() {
|
|||
runBlockingTest {
|
||||
testObserver.assertValue(
|
||||
ViewState.Success(
|
||||
blocks = listOf(title) + initial
|
||||
blocks = listOf(titleEditModeView) + initial
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -67,6 +67,26 @@ class DefaultBlockViewRendererTest {
|
|||
@Test
|
||||
fun `should return title, paragraph, toggle with its indented inner checkbox`() {
|
||||
|
||||
val title = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
style = Block.Content.Text.Style.TITLE,
|
||||
marks = emptyList()
|
||||
),
|
||||
children = emptyList(),
|
||||
fields = Block.Fields.empty()
|
||||
)
|
||||
|
||||
val header = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Layout(
|
||||
type = Block.Content.Layout.Type.HEADER
|
||||
),
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf(title.id)
|
||||
)
|
||||
|
||||
val paragraph = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
children = emptyList(),
|
||||
|
@ -103,14 +123,14 @@ class DefaultBlockViewRendererTest {
|
|||
|
||||
val page = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
children = listOf(paragraph.id, toggle.id),
|
||||
children = listOf(header.id, paragraph.id, toggle.id),
|
||||
fields = Block.Fields.empty(),
|
||||
content = Block.Content.Page(
|
||||
style = Block.Content.Page.Style.SET
|
||||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
)
|
||||
)
|
||||
|
||||
val blocks = listOf(page, paragraph, toggle, checkbox)
|
||||
val blocks = listOf(page, header, title, paragraph, toggle, checkbox)
|
||||
|
||||
val map = blocks.asMap()
|
||||
|
||||
|
@ -135,9 +155,9 @@ class DefaultBlockViewRendererTest {
|
|||
|
||||
val expected = listOf(
|
||||
BlockView.Title.Document(
|
||||
id = page.id,
|
||||
id = title.id,
|
||||
isFocused = false,
|
||||
text = null,
|
||||
text = title.content<Block.Content.Text>().text,
|
||||
emoji = null
|
||||
),
|
||||
BlockView.Text.Paragraph(
|
||||
|
@ -168,6 +188,26 @@ class DefaultBlockViewRendererTest {
|
|||
@Test
|
||||
fun `should return title, paragraph, toggle without its inner checkbox`() {
|
||||
|
||||
val title = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
style = Block.Content.Text.Style.TITLE,
|
||||
marks = emptyList()
|
||||
),
|
||||
children = emptyList(),
|
||||
fields = Block.Fields.empty()
|
||||
)
|
||||
|
||||
val header = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Layout(
|
||||
type = Block.Content.Layout.Type.HEADER
|
||||
),
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf(title.id)
|
||||
)
|
||||
|
||||
val paragraph = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
children = emptyList(),
|
||||
|
@ -204,14 +244,14 @@ class DefaultBlockViewRendererTest {
|
|||
|
||||
val page = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
children = listOf(paragraph.id, toggle.id),
|
||||
children = listOf(header.id, paragraph.id, toggle.id),
|
||||
fields = Block.Fields.empty(),
|
||||
content = Block.Content.Page(
|
||||
style = Block.Content.Page.Style.SET
|
||||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
)
|
||||
)
|
||||
|
||||
val blocks = listOf(page, paragraph, toggle, checkbox)
|
||||
val blocks = listOf(page, header, title, paragraph, toggle, checkbox)
|
||||
|
||||
val map = blocks.asMap()
|
||||
|
||||
|
@ -236,9 +276,9 @@ class DefaultBlockViewRendererTest {
|
|||
|
||||
val expected = listOf(
|
||||
BlockView.Title.Document(
|
||||
id = page.id,
|
||||
id = title.id,
|
||||
isFocused = false,
|
||||
text = null,
|
||||
text = title.content<Block.Content.Text>().text,
|
||||
emoji = null
|
||||
),
|
||||
BlockView.Text.Paragraph(
|
||||
|
@ -277,6 +317,27 @@ class DefaultBlockViewRendererTest {
|
|||
|
||||
@Test
|
||||
fun `should return paragraph with null alignment`() {
|
||||
|
||||
val title = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
style = Block.Content.Text.Style.TITLE,
|
||||
marks = emptyList()
|
||||
),
|
||||
children = emptyList(),
|
||||
fields = Block.Fields.empty()
|
||||
)
|
||||
|
||||
val header = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Layout(
|
||||
type = Block.Content.Layout.Type.HEADER
|
||||
),
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf(title.id)
|
||||
)
|
||||
|
||||
val paragraph = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
children = emptyList(),
|
||||
|
@ -291,14 +352,14 @@ class DefaultBlockViewRendererTest {
|
|||
|
||||
val page = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
children = listOf(paragraph.id),
|
||||
children = listOf(header.id, paragraph.id),
|
||||
fields = Block.Fields.empty(),
|
||||
content = Block.Content.Page(
|
||||
style = Block.Content.Page.Style.SET
|
||||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
)
|
||||
)
|
||||
|
||||
val blocks = listOf(page, paragraph)
|
||||
val blocks = listOf(page, header, title, paragraph)
|
||||
|
||||
val map = blocks.asMap()
|
||||
|
||||
|
@ -319,9 +380,9 @@ class DefaultBlockViewRendererTest {
|
|||
|
||||
val expected = listOf(
|
||||
BlockView.Title.Document(
|
||||
id = page.id,
|
||||
id = title.id,
|
||||
isFocused = false,
|
||||
text = null,
|
||||
text = title.content<Block.Content.Text>().text,
|
||||
emoji = null
|
||||
),
|
||||
BlockView.Text.Paragraph(
|
||||
|
@ -340,6 +401,27 @@ class DefaultBlockViewRendererTest {
|
|||
|
||||
@Test
|
||||
fun `should return paragraph with proper alignment`() {
|
||||
|
||||
val title = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
style = Block.Content.Text.Style.TITLE,
|
||||
marks = emptyList()
|
||||
),
|
||||
children = emptyList(),
|
||||
fields = Block.Fields.empty()
|
||||
)
|
||||
|
||||
val header = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Layout(
|
||||
type = Block.Content.Layout.Type.HEADER
|
||||
),
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf(title.id)
|
||||
)
|
||||
|
||||
val paragraph = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
children = emptyList(),
|
||||
|
@ -354,14 +436,14 @@ class DefaultBlockViewRendererTest {
|
|||
|
||||
val page = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
children = listOf(paragraph.id),
|
||||
children = listOf(header.id, paragraph.id),
|
||||
fields = Block.Fields.empty(),
|
||||
content = Block.Content.Page(
|
||||
style = Block.Content.Page.Style.SET
|
||||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
)
|
||||
)
|
||||
|
||||
val blocks = listOf(page, paragraph)
|
||||
val blocks = listOf(page, header, title, paragraph)
|
||||
|
||||
val map = blocks.asMap()
|
||||
|
||||
|
@ -382,9 +464,9 @@ class DefaultBlockViewRendererTest {
|
|||
|
||||
val expected = listOf(
|
||||
BlockView.Title.Document(
|
||||
id = page.id,
|
||||
id = title.id,
|
||||
isFocused = false,
|
||||
text = null,
|
||||
text = title.content<Block.Content.Text>().text,
|
||||
emoji = null
|
||||
),
|
||||
BlockView.Text.Paragraph(
|
||||
|
@ -403,6 +485,27 @@ class DefaultBlockViewRendererTest {
|
|||
|
||||
@Test
|
||||
fun `should add profile title when smart block is profile`() {
|
||||
|
||||
val title = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
style = Block.Content.Text.Style.TITLE,
|
||||
marks = emptyList()
|
||||
),
|
||||
children = emptyList(),
|
||||
fields = Block.Fields.empty()
|
||||
)
|
||||
|
||||
val header = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Layout(
|
||||
type = Block.Content.Layout.Type.HEADER
|
||||
),
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf(title.id)
|
||||
)
|
||||
|
||||
val paragraph = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
children = emptyList(),
|
||||
|
@ -425,16 +528,17 @@ class DefaultBlockViewRendererTest {
|
|||
)
|
||||
)
|
||||
val details = mapOf(pageId to fields)
|
||||
|
||||
val page = Block(
|
||||
id = pageId,
|
||||
children = listOf(paragraph.id),
|
||||
children = listOf(header.id, paragraph.id),
|
||||
fields = fields,
|
||||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PROFILE
|
||||
)
|
||||
)
|
||||
|
||||
val blocks = listOf(page, paragraph)
|
||||
val blocks = listOf(page, header, title, paragraph)
|
||||
|
||||
val map = blocks.asMap()
|
||||
|
||||
|
@ -455,9 +559,9 @@ class DefaultBlockViewRendererTest {
|
|||
|
||||
val expected = listOf(
|
||||
BlockView.Title.Profile(
|
||||
id = page.id,
|
||||
id = title.id,
|
||||
isFocused = false,
|
||||
text = name,
|
||||
text = title.content<Block.Content.Text>().text,
|
||||
image = UrlBuilder(gateway).thumbnail(imageName)
|
||||
),
|
||||
BlockView.Text.Paragraph(
|
||||
|
@ -476,6 +580,27 @@ class DefaultBlockViewRendererTest {
|
|||
|
||||
@Test
|
||||
fun `should add title when smart block is page`() {
|
||||
|
||||
val title = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
style = Block.Content.Text.Style.TITLE,
|
||||
marks = emptyList()
|
||||
),
|
||||
children = emptyList(),
|
||||
fields = Block.Fields.empty()
|
||||
)
|
||||
|
||||
val header = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Layout(
|
||||
type = Block.Content.Layout.Type.HEADER
|
||||
),
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf(title.id)
|
||||
)
|
||||
|
||||
val paragraph = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
children = emptyList(),
|
||||
|
@ -498,16 +623,17 @@ class DefaultBlockViewRendererTest {
|
|||
)
|
||||
)
|
||||
val details = mapOf(pageId to fields)
|
||||
|
||||
val page = Block(
|
||||
id = pageId,
|
||||
children = listOf(paragraph.id),
|
||||
children = listOf(header.id, paragraph.id),
|
||||
fields = fields,
|
||||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
)
|
||||
)
|
||||
|
||||
val blocks = listOf(page, paragraph)
|
||||
val blocks = listOf(page, header, title, paragraph)
|
||||
|
||||
val map = blocks.asMap()
|
||||
|
||||
|
@ -528,9 +654,9 @@ class DefaultBlockViewRendererTest {
|
|||
|
||||
val expected = listOf(
|
||||
BlockView.Title.Document(
|
||||
id = page.id,
|
||||
id = title.id,
|
||||
isFocused = false,
|
||||
text = name,
|
||||
text = title.content<Block.Content.Text>().text,
|
||||
image = UrlBuilder(gateway).thumbnail(imageName)
|
||||
),
|
||||
BlockView.Text.Paragraph(
|
||||
|
@ -547,8 +673,28 @@ class DefaultBlockViewRendererTest {
|
|||
assertEquals(expected = expected, actual = result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should add title when page is not smart block`() {
|
||||
@Test(expected = IllegalStateException::class)
|
||||
fun `should throw exception when smart block type is unexpected`() {
|
||||
|
||||
val title = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
style = Block.Content.Text.Style.TITLE,
|
||||
marks = emptyList()
|
||||
),
|
||||
children = emptyList(),
|
||||
fields = Block.Fields.empty()
|
||||
)
|
||||
|
||||
val header = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Layout(
|
||||
type = Block.Content.Layout.Type.HEADER
|
||||
),
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf(title.id)
|
||||
)
|
||||
|
||||
val paragraph = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
|
@ -565,21 +711,24 @@ class DefaultBlockViewRendererTest {
|
|||
val name = MockDataFactory.randomString()
|
||||
val imageName = MockDataFactory.randomString()
|
||||
val pageId = MockDataFactory.randomUuid()
|
||||
|
||||
val fields = Block.Fields(
|
||||
map = mapOf(
|
||||
"name" to name,
|
||||
"iconImage" to imageName
|
||||
)
|
||||
)
|
||||
|
||||
val details = mapOf(pageId to fields)
|
||||
|
||||
val page = Block(
|
||||
id = pageId,
|
||||
children = listOf(paragraph.id),
|
||||
children = listOf(header.id, paragraph.id),
|
||||
fields = fields,
|
||||
content = Block.Content.Page(style = Block.Content.Page.Style.TASK)
|
||||
)
|
||||
|
||||
val blocks = listOf(page, paragraph)
|
||||
val blocks = listOf(page, header, title, paragraph)
|
||||
|
||||
val map = blocks.asMap()
|
||||
|
||||
|
@ -588,7 +737,7 @@ class DefaultBlockViewRendererTest {
|
|||
renderer = renderer
|
||||
)
|
||||
|
||||
val result = runBlocking {
|
||||
runBlocking {
|
||||
wrapper.render(
|
||||
root = page,
|
||||
anchor = page.id,
|
||||
|
@ -597,31 +746,31 @@ class DefaultBlockViewRendererTest {
|
|||
details = Block.Details(details)
|
||||
)
|
||||
}
|
||||
|
||||
val expected = listOf(
|
||||
BlockView.Title.Document(
|
||||
id = page.id,
|
||||
isFocused = false,
|
||||
text = name,
|
||||
image = UrlBuilder(gateway).thumbnail(imageName)
|
||||
),
|
||||
BlockView.Text.Paragraph(
|
||||
isFocused = true,
|
||||
id = paragraph.id,
|
||||
marks = emptyList(),
|
||||
backgroundColor = paragraph.content<Block.Content.Text>().backgroundColor,
|
||||
color = paragraph.content<Block.Content.Text>().color,
|
||||
text = paragraph.content<Block.Content.Text>().text,
|
||||
alignment = Alignment.CENTER
|
||||
)
|
||||
)
|
||||
|
||||
assertEquals(expected = expected, actual = result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should render nested paragraphs`() {
|
||||
|
||||
val title = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
style = Block.Content.Text.Style.TITLE,
|
||||
marks = emptyList()
|
||||
),
|
||||
children = emptyList(),
|
||||
fields = Block.Fields.empty()
|
||||
)
|
||||
|
||||
val header = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Layout(
|
||||
type = Block.Content.Layout.Type.HEADER
|
||||
),
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf(title.id)
|
||||
)
|
||||
|
||||
val c = Block(
|
||||
id = "C",
|
||||
children = listOf(),
|
||||
|
@ -662,14 +811,14 @@ class DefaultBlockViewRendererTest {
|
|||
|
||||
val page = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
children = listOf(a.id),
|
||||
children = listOf(header.id, a.id),
|
||||
fields = fields,
|
||||
content = Block.Content.Smart(type = Block.Content.Smart.Type.PAGE)
|
||||
)
|
||||
|
||||
val details = mapOf(page.id to fields)
|
||||
|
||||
val blocks = listOf(page, a, b, c)
|
||||
val blocks = listOf(page, header, title, a, b, c)
|
||||
|
||||
val map = blocks.asMap()
|
||||
|
||||
|
@ -690,9 +839,9 @@ class DefaultBlockViewRendererTest {
|
|||
|
||||
val expected = listOf(
|
||||
BlockView.Title.Document(
|
||||
id = page.id,
|
||||
id = title.id,
|
||||
isFocused = false,
|
||||
text = null,
|
||||
text = title.content<Block.Content.Text>().text,
|
||||
image = null
|
||||
),
|
||||
BlockView.Text.Paragraph(
|
||||
|
@ -733,6 +882,26 @@ class DefaultBlockViewRendererTest {
|
|||
@Test
|
||||
fun `should render nested checkboxes`() {
|
||||
|
||||
val title = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
style = Block.Content.Text.Style.TITLE,
|
||||
marks = emptyList()
|
||||
),
|
||||
children = emptyList(),
|
||||
fields = Block.Fields.empty()
|
||||
)
|
||||
|
||||
val header = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Layout(
|
||||
type = Block.Content.Layout.Type.HEADER
|
||||
),
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf(title.id)
|
||||
)
|
||||
|
||||
val c = Block(
|
||||
id = "C",
|
||||
children = listOf(),
|
||||
|
@ -773,14 +942,14 @@ class DefaultBlockViewRendererTest {
|
|||
|
||||
val page = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
children = listOf(a.id),
|
||||
children = listOf(header.id, a.id),
|
||||
fields = fields,
|
||||
content = Block.Content.Smart(type = Block.Content.Smart.Type.PAGE)
|
||||
)
|
||||
|
||||
val details = mapOf(page.id to fields)
|
||||
|
||||
val blocks = listOf(page, a, b, c)
|
||||
val blocks = listOf(page, header, title, a, b, c)
|
||||
|
||||
val map = blocks.asMap()
|
||||
|
||||
|
@ -801,9 +970,9 @@ class DefaultBlockViewRendererTest {
|
|||
|
||||
val expected = listOf(
|
||||
BlockView.Title.Document(
|
||||
id = page.id,
|
||||
id = title.id,
|
||||
isFocused = false,
|
||||
text = null,
|
||||
text = title.content<Block.Content.Text>().text,
|
||||
image = null
|
||||
),
|
||||
BlockView.Text.Checkbox(
|
||||
|
@ -841,6 +1010,26 @@ class DefaultBlockViewRendererTest {
|
|||
@Test
|
||||
fun `should render nested bulleted items`() {
|
||||
|
||||
val title = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
style = Block.Content.Text.Style.TITLE,
|
||||
marks = emptyList()
|
||||
),
|
||||
children = emptyList(),
|
||||
fields = Block.Fields.empty()
|
||||
)
|
||||
|
||||
val header = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Layout(
|
||||
type = Block.Content.Layout.Type.HEADER
|
||||
),
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf(title.id)
|
||||
)
|
||||
|
||||
val c = Block(
|
||||
id = "C",
|
||||
children = listOf(),
|
||||
|
@ -881,14 +1070,14 @@ class DefaultBlockViewRendererTest {
|
|||
|
||||
val page = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
children = listOf(a.id),
|
||||
children = listOf(header.id, a.id),
|
||||
fields = fields,
|
||||
content = Block.Content.Smart(type = Block.Content.Smart.Type.PAGE)
|
||||
)
|
||||
|
||||
val details = mapOf(page.id to fields)
|
||||
|
||||
val blocks = listOf(page, a, b, c)
|
||||
val blocks = listOf(page, header, title, a, b, c)
|
||||
|
||||
val map = blocks.asMap()
|
||||
|
||||
|
@ -909,9 +1098,9 @@ class DefaultBlockViewRendererTest {
|
|||
|
||||
val expected = listOf(
|
||||
BlockView.Title.Document(
|
||||
id = page.id,
|
||||
id = title.id,
|
||||
isFocused = false,
|
||||
text = null,
|
||||
text = title.content<Block.Content.Text>().text,
|
||||
image = null
|
||||
),
|
||||
BlockView.Text.Bulleted(
|
||||
|
@ -949,6 +1138,26 @@ class DefaultBlockViewRendererTest {
|
|||
@Test
|
||||
fun `should render nested numbered lists`() {
|
||||
|
||||
val title = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
style = Block.Content.Text.Style.TITLE,
|
||||
marks = emptyList()
|
||||
),
|
||||
children = emptyList(),
|
||||
fields = Block.Fields.empty()
|
||||
)
|
||||
|
||||
val header = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Layout(
|
||||
type = Block.Content.Layout.Type.HEADER
|
||||
),
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf(title.id)
|
||||
)
|
||||
|
||||
val d = Block(
|
||||
id = "D",
|
||||
children = listOf(),
|
||||
|
@ -1001,14 +1210,14 @@ class DefaultBlockViewRendererTest {
|
|||
|
||||
val page = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
children = listOf(a.id),
|
||||
children = listOf(header.id, a.id),
|
||||
fields = fields,
|
||||
content = Block.Content.Smart(type = Block.Content.Smart.Type.PAGE)
|
||||
)
|
||||
|
||||
val details = mapOf(page.id to fields)
|
||||
|
||||
val blocks = listOf(page, a, b, c, d)
|
||||
val blocks = listOf(page, header, title, a, b, c, d)
|
||||
|
||||
val map = blocks.asMap()
|
||||
|
||||
|
@ -1029,9 +1238,9 @@ class DefaultBlockViewRendererTest {
|
|||
|
||||
val expected = listOf(
|
||||
BlockView.Title.Document(
|
||||
id = page.id,
|
||||
id = title.id,
|
||||
isFocused = false,
|
||||
text = null,
|
||||
text = title.content<Block.Content.Text>().text,
|
||||
image = null
|
||||
),
|
||||
BlockView.Text.Numbered(
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -50,8 +50,8 @@ class EditorAddBlockTest : EditorPresentationTestSetup() {
|
|||
Block(
|
||||
id = root,
|
||||
fields = Block.Fields(emptyMap()),
|
||||
content = Block.Content.Page(
|
||||
style = Block.Content.Page.Style.SET
|
||||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf(block.id)
|
||||
),
|
||||
|
@ -113,8 +113,8 @@ class EditorAddBlockTest : EditorPresentationTestSetup() {
|
|||
Block(
|
||||
id = root,
|
||||
fields = Block.Fields(emptyMap()),
|
||||
content = Block.Content.Page(
|
||||
style = Block.Content.Page.Style.SET
|
||||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf(block.id)
|
||||
),
|
||||
|
|
|
@ -33,6 +33,26 @@ class EditorBackspaceNestedDeleteTest : EditorPresentationTestSetup() {
|
|||
|
||||
// SETUP
|
||||
|
||||
val title = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
style = Block.Content.Text.Style.TITLE,
|
||||
marks = emptyList()
|
||||
),
|
||||
children = emptyList(),
|
||||
fields = Block.Fields.empty()
|
||||
)
|
||||
|
||||
val header = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Layout(
|
||||
type = Block.Content.Layout.Type.HEADER
|
||||
),
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf(title.id)
|
||||
)
|
||||
|
||||
val child = Block(
|
||||
id = "CHILD",
|
||||
fields = Block.Fields.empty(),
|
||||
|
@ -61,10 +81,10 @@ class EditorBackspaceNestedDeleteTest : EditorPresentationTestSetup() {
|
|||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf(parent.id)
|
||||
children = listOf(header.id, parent.id)
|
||||
)
|
||||
|
||||
val document = listOf(page, parent, child)
|
||||
val document = listOf(page, header, title, parent, child)
|
||||
|
||||
val params = UnlinkBlocks.Params(
|
||||
context = root,
|
||||
|
@ -109,9 +129,9 @@ class EditorBackspaceNestedDeleteTest : EditorPresentationTestSetup() {
|
|||
ViewState.Success(
|
||||
listOf(
|
||||
BlockView.Title.Document(
|
||||
id = root,
|
||||
id = title.id,
|
||||
isFocused = false,
|
||||
text = null
|
||||
text = title.content<Block.Content.Text>().text
|
||||
),
|
||||
BlockView.Text.Paragraph(
|
||||
id = parent.id,
|
||||
|
@ -129,6 +149,26 @@ class EditorBackspaceNestedDeleteTest : EditorPresentationTestSetup() {
|
|||
|
||||
// SETUP
|
||||
|
||||
val title = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
style = Block.Content.Text.Style.TITLE,
|
||||
marks = emptyList()
|
||||
),
|
||||
children = emptyList(),
|
||||
fields = Block.Fields.empty()
|
||||
)
|
||||
|
||||
val header = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Layout(
|
||||
type = Block.Content.Layout.Type.HEADER
|
||||
),
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf(title.id)
|
||||
)
|
||||
|
||||
val child1 = Block(
|
||||
id = "CHILD1",
|
||||
fields = Block.Fields.empty(),
|
||||
|
@ -168,10 +208,10 @@ class EditorBackspaceNestedDeleteTest : EditorPresentationTestSetup() {
|
|||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf(parent.id)
|
||||
children = listOf(header.id, parent.id)
|
||||
)
|
||||
|
||||
val document = listOf(page, parent, child1, child2)
|
||||
val document = listOf(page, header, title, parent, child1, child2)
|
||||
|
||||
val params = UnlinkBlocks.Params(
|
||||
context = root,
|
||||
|
@ -216,9 +256,9 @@ class EditorBackspaceNestedDeleteTest : EditorPresentationTestSetup() {
|
|||
ViewState.Success(
|
||||
listOf(
|
||||
BlockView.Title.Document(
|
||||
id = root,
|
||||
id = title.id,
|
||||
isFocused = false,
|
||||
text = null
|
||||
text = title.content<Block.Content.Text>().text
|
||||
),
|
||||
BlockView.Text.Paragraph(
|
||||
id = parent.id,
|
||||
|
@ -242,6 +282,26 @@ class EditorBackspaceNestedDeleteTest : EditorPresentationTestSetup() {
|
|||
|
||||
// SETUP
|
||||
|
||||
val title = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
style = Block.Content.Text.Style.TITLE,
|
||||
marks = emptyList()
|
||||
),
|
||||
children = emptyList(),
|
||||
fields = Block.Fields.empty()
|
||||
)
|
||||
|
||||
val header = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Layout(
|
||||
type = Block.Content.Layout.Type.HEADER
|
||||
),
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf(title.id)
|
||||
)
|
||||
|
||||
val child1 = Block(
|
||||
id = "CHILD1-TEXT",
|
||||
fields = Block.Fields.empty(),
|
||||
|
@ -292,10 +352,10 @@ class EditorBackspaceNestedDeleteTest : EditorPresentationTestSetup() {
|
|||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf(parent.id)
|
||||
children = listOf(header.id, parent.id)
|
||||
)
|
||||
|
||||
val document = listOf(page, parent, child1, child2, child3)
|
||||
val document = listOf(page, header, title, parent, child1, child2, child3)
|
||||
|
||||
val params = UnlinkBlocks.Params(
|
||||
context = root,
|
||||
|
@ -340,9 +400,9 @@ class EditorBackspaceNestedDeleteTest : EditorPresentationTestSetup() {
|
|||
ViewState.Success(
|
||||
listOf(
|
||||
BlockView.Title.Document(
|
||||
id = root,
|
||||
id = title.id,
|
||||
isFocused = false,
|
||||
text = null
|
||||
text = title.content<Block.Content.Text>().text
|
||||
),
|
||||
BlockView.Text.Paragraph(
|
||||
id = parent.id,
|
||||
|
@ -371,6 +431,26 @@ class EditorBackspaceNestedDeleteTest : EditorPresentationTestSetup() {
|
|||
|
||||
// SETUP
|
||||
|
||||
val title = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
style = Block.Content.Text.Style.TITLE,
|
||||
marks = emptyList()
|
||||
),
|
||||
children = emptyList(),
|
||||
fields = Block.Fields.empty()
|
||||
)
|
||||
|
||||
val header = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Layout(
|
||||
type = Block.Content.Layout.Type.HEADER
|
||||
),
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf(title.id)
|
||||
)
|
||||
|
||||
val child1 = Block(
|
||||
id = "CHILD1-TEXT",
|
||||
fields = Block.Fields.empty(),
|
||||
|
@ -421,10 +501,10 @@ class EditorBackspaceNestedDeleteTest : EditorPresentationTestSetup() {
|
|||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf(parent.id)
|
||||
children = listOf(header.id, parent.id)
|
||||
)
|
||||
|
||||
val document = listOf(page, parent, child1, child2, child3)
|
||||
val document = listOf(page, header, title, parent, child1, child2, child3)
|
||||
|
||||
val params = UnlinkBlocks.Params(
|
||||
context = root,
|
||||
|
@ -469,9 +549,9 @@ class EditorBackspaceNestedDeleteTest : EditorPresentationTestSetup() {
|
|||
ViewState.Success(
|
||||
listOf(
|
||||
BlockView.Title.Document(
|
||||
id = root,
|
||||
id = title.id,
|
||||
isFocused = false,
|
||||
text = null
|
||||
text = title.content<Block.Content.Text>().text
|
||||
),
|
||||
BlockView.Text.Paragraph(
|
||||
id = parent.id,
|
||||
|
|
|
@ -152,7 +152,7 @@ class EditorCheckboxTest : EditorPresentationTestSetup() {
|
|||
}
|
||||
}
|
||||
|
||||
fun stubUpdateCheckbox(
|
||||
private fun stubUpdateCheckbox(
|
||||
payload: Payload = Payload(
|
||||
context = MockDataFactory.randomUuid(),
|
||||
events = emptyList()
|
||||
|
|
|
@ -20,6 +20,26 @@ import org.mockito.MockitoAnnotations
|
|||
|
||||
class EditorEmptySpaceInteractionTest : EditorPresentationTestSetup() {
|
||||
|
||||
val title = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
style = Block.Content.Text.Style.TITLE,
|
||||
marks = emptyList()
|
||||
),
|
||||
children = emptyList(),
|
||||
fields = Block.Fields.empty()
|
||||
)
|
||||
|
||||
val header = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Layout(
|
||||
type = Block.Content.Layout.Type.HEADER
|
||||
),
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf(title.id)
|
||||
)
|
||||
|
||||
@get:Rule
|
||||
val rule = InstantTaskExecutorRule()
|
||||
|
||||
|
@ -39,19 +59,78 @@ class EditorEmptySpaceInteractionTest : EditorPresentationTestSetup() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `should create a new paragraph on outside-clicked event if page contains only title and icon`() {
|
||||
fun `should create a new paragraph on outside-clicked event if page contains only title with icon`() {
|
||||
|
||||
// SETUP
|
||||
|
||||
val child = MockDataFactory.randomUuid()
|
||||
val page = MockBlockFactory.makeOnePageWithOneTextBlock(
|
||||
root = root,
|
||||
child = child,
|
||||
style = Block.Content.Text.Style.TITLE
|
||||
val page = Block(
|
||||
id = root,
|
||||
fields = Block.Fields(emptyMap()),
|
||||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf(header.id)
|
||||
)
|
||||
|
||||
val doc = listOf(page, header, title)
|
||||
|
||||
stubInterceptEvents()
|
||||
stubOpenDocument(page)
|
||||
stubOpenDocument(doc)
|
||||
stubCreateBlock(root)
|
||||
|
||||
val vm = buildViewModel()
|
||||
|
||||
vm.onStart(root)
|
||||
|
||||
// TESTING
|
||||
|
||||
vm.onOutsideClicked()
|
||||
|
||||
verifyBlocking(createBlock, times(1)) {
|
||||
invoke(
|
||||
params = eq(
|
||||
CreateBlock.Params(
|
||||
context = root,
|
||||
target = "",
|
||||
position = Position.INNER,
|
||||
prototype = Block.Prototype.Text(
|
||||
style = Block.Content.Text.Style.P
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should create a new paragraph on outside-clicked event if page contains only title with icon and one non-empty paragraph`() {
|
||||
|
||||
// SETUP
|
||||
|
||||
val block = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
style = Block.Content.Text.Style.P,
|
||||
marks = emptyList()
|
||||
),
|
||||
children = emptyList(),
|
||||
fields = Block.Fields.empty()
|
||||
)
|
||||
|
||||
val page = Block(
|
||||
id = root,
|
||||
fields = Block.Fields(emptyMap()),
|
||||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf(header.id, block.id)
|
||||
)
|
||||
|
||||
val doc = listOf(page, header, title, block)
|
||||
|
||||
stubInterceptEvents()
|
||||
stubOpenDocument(doc)
|
||||
stubCreateBlock(root)
|
||||
|
||||
val vm = buildViewModel()
|
||||
|
|
|
@ -46,9 +46,7 @@ class EditorFocusTest : EditorPresentationTestSetup() {
|
|||
Block(
|
||||
id = root,
|
||||
fields = Block.Fields(emptyMap()),
|
||||
content = Block.Content.Page(
|
||||
style = Block.Content.Page.Style.SET
|
||||
),
|
||||
content = Block.Content.Smart(Block.Content.Smart.Type.PAGE),
|
||||
children = listOf(block.id)
|
||||
),
|
||||
block
|
||||
|
|
|
@ -35,6 +35,26 @@ class EditorGranularChangeTest : EditorPresentationTestSetup() {
|
|||
|
||||
val delay = 1000L
|
||||
|
||||
val title = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
style = Block.Content.Text.Style.TITLE,
|
||||
marks = emptyList()
|
||||
),
|
||||
children = emptyList(),
|
||||
fields = Block.Fields.empty()
|
||||
)
|
||||
|
||||
val header = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Layout(
|
||||
type = Block.Content.Layout.Type.HEADER
|
||||
),
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf(title.id)
|
||||
)
|
||||
|
||||
val checkbox = Block(
|
||||
id = MockDataFactory.randomString(),
|
||||
fields = Block.Fields.empty(),
|
||||
|
@ -51,11 +71,13 @@ class EditorGranularChangeTest : EditorPresentationTestSetup() {
|
|||
Block(
|
||||
id = root,
|
||||
fields = Block.Fields(emptyMap()),
|
||||
content = Block.Content.Page(
|
||||
style = Block.Content.Page.Style.SET
|
||||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf(checkbox.id)
|
||||
children = listOf(header.id, checkbox.id)
|
||||
),
|
||||
header,
|
||||
title,
|
||||
checkbox
|
||||
)
|
||||
|
||||
|
@ -82,8 +104,8 @@ class EditorGranularChangeTest : EditorPresentationTestSetup() {
|
|||
val before = ViewState.Success(
|
||||
blocks = listOf(
|
||||
BlockView.Title.Document(
|
||||
id = root,
|
||||
text = null,
|
||||
id = title.id,
|
||||
text = title.content<Block.Content.Text>().text,
|
||||
isFocused = false
|
||||
),
|
||||
BlockView.Text.Checkbox(
|
||||
|
@ -98,8 +120,8 @@ class EditorGranularChangeTest : EditorPresentationTestSetup() {
|
|||
val after = ViewState.Success(
|
||||
blocks = listOf(
|
||||
BlockView.Title.Document(
|
||||
id = root,
|
||||
text = null,
|
||||
id = title.id,
|
||||
text = title.content<Block.Content.Text>().text,
|
||||
isFocused = false
|
||||
),
|
||||
BlockView.Text.Checkbox(
|
||||
|
|
|
@ -11,6 +11,7 @@ import com.anytypeio.anytype.domain.event.model.Event
|
|||
import com.anytypeio.anytype.domain.ext.content
|
||||
import com.anytypeio.anytype.presentation.MockBlockFactory
|
||||
import com.anytypeio.anytype.presentation.util.CoroutinesTestRule
|
||||
import com.anytypeio.anytype.presentation.util.TXT
|
||||
import com.jraska.livedata.test
|
||||
import com.nhaarman.mockitokotlin2.eq
|
||||
import com.nhaarman.mockitokotlin2.times
|
||||
|
@ -61,7 +62,7 @@ class EditorListBlockTest : EditorPresentationTestSetup() {
|
|||
|
||||
vm.onEndLineEnterClicked(
|
||||
id = child,
|
||||
text = page.last().content<Block.Content.Text>().text,
|
||||
text = page.last().content<TXT>().text,
|
||||
marks = emptyList()
|
||||
)
|
||||
|
||||
|
@ -219,6 +220,26 @@ class EditorListBlockTest : EditorPresentationTestSetup() {
|
|||
|
||||
// SETUP
|
||||
|
||||
val title = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
style = Block.Content.Text.Style.TITLE,
|
||||
marks = emptyList()
|
||||
),
|
||||
children = emptyList(),
|
||||
fields = Block.Fields.empty()
|
||||
)
|
||||
|
||||
val header = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Layout(
|
||||
type = Block.Content.Layout.Type.HEADER
|
||||
),
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf(title.id)
|
||||
)
|
||||
|
||||
val style = Block.Content.Text.Style.CHECKBOX
|
||||
val child = MockDataFactory.randomUuid()
|
||||
|
||||
|
@ -237,11 +258,13 @@ class EditorListBlockTest : EditorPresentationTestSetup() {
|
|||
Block(
|
||||
id = root,
|
||||
fields = Block.Fields(emptyMap()),
|
||||
content = Block.Content.Page(
|
||||
style = Block.Content.Page.Style.SET
|
||||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf(child)
|
||||
children = listOf(header.id, child)
|
||||
),
|
||||
header,
|
||||
title,
|
||||
checkbox
|
||||
)
|
||||
|
||||
|
@ -275,8 +298,8 @@ class EditorListBlockTest : EditorPresentationTestSetup() {
|
|||
val before = ViewState.Success(
|
||||
blocks = listOf(
|
||||
BlockView.Title.Document(
|
||||
id = root,
|
||||
text = null,
|
||||
id = title.id,
|
||||
text = title.content<TXT>().text,
|
||||
isFocused = false
|
||||
),
|
||||
BlockView.Text.Checkbox(
|
||||
|
@ -316,8 +339,8 @@ class EditorListBlockTest : EditorPresentationTestSetup() {
|
|||
val after = ViewState.Success(
|
||||
blocks = listOf(
|
||||
BlockView.Title.Document(
|
||||
id = root,
|
||||
text = null,
|
||||
id = title.id,
|
||||
text = title.content<TXT>().text,
|
||||
isFocused = false
|
||||
),
|
||||
BlockView.Text.Paragraph(
|
||||
|
@ -336,6 +359,26 @@ class EditorListBlockTest : EditorPresentationTestSetup() {
|
|||
|
||||
// SETUP
|
||||
|
||||
val title = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
style = Block.Content.Text.Style.TITLE,
|
||||
marks = emptyList()
|
||||
),
|
||||
children = emptyList(),
|
||||
fields = Block.Fields.empty()
|
||||
)
|
||||
|
||||
val header = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Layout(
|
||||
type = Block.Content.Layout.Type.HEADER
|
||||
),
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf(title.id)
|
||||
)
|
||||
|
||||
val style = Block.Content.Text.Style.BULLET
|
||||
val child = MockDataFactory.randomUuid()
|
||||
|
||||
|
@ -354,11 +397,13 @@ class EditorListBlockTest : EditorPresentationTestSetup() {
|
|||
Block(
|
||||
id = root,
|
||||
fields = Block.Fields(emptyMap()),
|
||||
content = Block.Content.Page(
|
||||
style = Block.Content.Page.Style.SET
|
||||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf(child)
|
||||
children = listOf(header.id, child)
|
||||
),
|
||||
header,
|
||||
title,
|
||||
checkbox
|
||||
)
|
||||
|
||||
|
@ -392,8 +437,8 @@ class EditorListBlockTest : EditorPresentationTestSetup() {
|
|||
val before = ViewState.Success(
|
||||
blocks = listOf(
|
||||
BlockView.Title.Document(
|
||||
id = root,
|
||||
text = null,
|
||||
id = title.id,
|
||||
text = title.content<TXT>().text,
|
||||
isFocused = false
|
||||
),
|
||||
BlockView.Text.Bulleted(
|
||||
|
@ -432,8 +477,8 @@ class EditorListBlockTest : EditorPresentationTestSetup() {
|
|||
val after = ViewState.Success(
|
||||
blocks = listOf(
|
||||
BlockView.Title.Document(
|
||||
id = root,
|
||||
text = null,
|
||||
id = title.id,
|
||||
text = title.content<TXT>().text,
|
||||
isFocused = false
|
||||
),
|
||||
BlockView.Text.Paragraph(
|
||||
|
@ -455,6 +500,26 @@ class EditorListBlockTest : EditorPresentationTestSetup() {
|
|||
val style = Block.Content.Text.Style.TOGGLE
|
||||
val child = MockDataFactory.randomUuid()
|
||||
|
||||
val title = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
style = Block.Content.Text.Style.TITLE,
|
||||
marks = emptyList()
|
||||
),
|
||||
children = emptyList(),
|
||||
fields = Block.Fields.empty()
|
||||
)
|
||||
|
||||
val header = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Layout(
|
||||
type = Block.Content.Layout.Type.HEADER
|
||||
),
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf(title.id)
|
||||
)
|
||||
|
||||
val checkbox = Block(
|
||||
id = child,
|
||||
fields = Block.Fields(emptyMap()),
|
||||
|
@ -470,11 +535,13 @@ class EditorListBlockTest : EditorPresentationTestSetup() {
|
|||
Block(
|
||||
id = root,
|
||||
fields = Block.Fields(emptyMap()),
|
||||
content = Block.Content.Page(
|
||||
style = Block.Content.Page.Style.SET
|
||||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf(child)
|
||||
children = listOf(header.id, child)
|
||||
),
|
||||
header,
|
||||
title,
|
||||
checkbox
|
||||
)
|
||||
|
||||
|
@ -508,8 +575,8 @@ class EditorListBlockTest : EditorPresentationTestSetup() {
|
|||
val before = ViewState.Success(
|
||||
blocks = listOf(
|
||||
BlockView.Title.Document(
|
||||
id = root,
|
||||
text = null,
|
||||
id = title.id,
|
||||
text = title.content<Block.Content.Text>().text,
|
||||
isFocused = false
|
||||
),
|
||||
BlockView.Text.Toggle(
|
||||
|
@ -549,8 +616,8 @@ class EditorListBlockTest : EditorPresentationTestSetup() {
|
|||
val after = ViewState.Success(
|
||||
blocks = listOf(
|
||||
BlockView.Title.Document(
|
||||
id = root,
|
||||
text = null,
|
||||
id = title.id,
|
||||
text = title.content<Block.Content.Text>().text,
|
||||
isFocused = false
|
||||
),
|
||||
BlockView.Text.Paragraph(
|
||||
|
@ -569,6 +636,26 @@ class EditorListBlockTest : EditorPresentationTestSetup() {
|
|||
|
||||
// SETUP
|
||||
|
||||
val title = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
style = Block.Content.Text.Style.TITLE,
|
||||
marks = emptyList()
|
||||
),
|
||||
children = emptyList(),
|
||||
fields = Block.Fields.empty()
|
||||
)
|
||||
|
||||
val header = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Layout(
|
||||
type = Block.Content.Layout.Type.HEADER
|
||||
),
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf(title.id)
|
||||
)
|
||||
|
||||
val style = Block.Content.Text.Style.NUMBERED
|
||||
val child = MockDataFactory.randomUuid()
|
||||
|
||||
|
@ -587,11 +674,13 @@ class EditorListBlockTest : EditorPresentationTestSetup() {
|
|||
Block(
|
||||
id = root,
|
||||
fields = Block.Fields(emptyMap()),
|
||||
content = Block.Content.Page(
|
||||
style = Block.Content.Page.Style.SET
|
||||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf(child)
|
||||
children = listOf(header.id, child)
|
||||
),
|
||||
header,
|
||||
title,
|
||||
checkbox
|
||||
)
|
||||
|
||||
|
@ -625,8 +714,8 @@ class EditorListBlockTest : EditorPresentationTestSetup() {
|
|||
val before = ViewState.Success(
|
||||
blocks = listOf(
|
||||
BlockView.Title.Document(
|
||||
id = root,
|
||||
text = null,
|
||||
id = title.id,
|
||||
text = title.content<Block.Content.Text>().text,
|
||||
isFocused = false
|
||||
),
|
||||
BlockView.Text.Numbered(
|
||||
|
@ -644,7 +733,7 @@ class EditorListBlockTest : EditorPresentationTestSetup() {
|
|||
vm.onEndLineEnterClicked(
|
||||
id = child,
|
||||
marks = emptyList(),
|
||||
text = page.last().content<Block.Content.Text>().text
|
||||
text = page.last().content<TXT>().text
|
||||
)
|
||||
|
||||
verifyBlocking(updateTextStyle, times(1)) {
|
||||
|
@ -666,8 +755,8 @@ class EditorListBlockTest : EditorPresentationTestSetup() {
|
|||
val after = ViewState.Success(
|
||||
blocks = listOf(
|
||||
BlockView.Title.Document(
|
||||
id = root,
|
||||
text = null,
|
||||
id = title.id,
|
||||
text = title.content<Block.Content.Text>().text,
|
||||
isFocused = false
|
||||
),
|
||||
BlockView.Text.Paragraph(
|
||||
|
|
|
@ -9,14 +9,15 @@ import com.anytypeio.anytype.core_ui.state.ControlPanelState
|
|||
import com.anytypeio.anytype.core_ui.widgets.toolbar.adapter.Mention
|
||||
import com.anytypeio.anytype.domain.base.Either
|
||||
import com.anytypeio.anytype.domain.block.model.Block
|
||||
import com.anytypeio.anytype.domain.ext.content
|
||||
import com.anytypeio.anytype.domain.icon.DocumentEmojiIconProvider
|
||||
import com.anytypeio.anytype.domain.page.CreateNewDocument
|
||||
import com.anytypeio.anytype.domain.page.navigation.GetListPages
|
||||
import com.anytypeio.anytype.presentation.page.PageViewModel
|
||||
import com.anytypeio.anytype.presentation.util.CoroutinesTestRule
|
||||
import com.anytypeio.anytype.presentation.util.TXT
|
||||
import com.jraska.livedata.test
|
||||
import com.nhaarman.mockitokotlin2.*
|
||||
import kotlinx.coroutines.test.runBlockingTest
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
@ -43,6 +44,26 @@ class EditorMentionTest : EditorPresentationTestSetup() {
|
|||
@Test
|
||||
fun `should update text with cursor position`() {
|
||||
|
||||
val title = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
style = Block.Content.Text.Style.TITLE,
|
||||
marks = emptyList()
|
||||
),
|
||||
children = emptyList(),
|
||||
fields = Block.Fields.empty()
|
||||
)
|
||||
|
||||
val header = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Layout(
|
||||
type = Block.Content.Layout.Type.HEADER
|
||||
),
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf(title.id)
|
||||
)
|
||||
|
||||
val mentionTrigger = "@a"
|
||||
val from = 11
|
||||
val givenText = "page about $mentionTrigger music"
|
||||
|
@ -88,10 +109,10 @@ class EditorMentionTest : EditorPresentationTestSetup() {
|
|||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf(a.id)
|
||||
children = listOf(header.id, a.id)
|
||||
)
|
||||
|
||||
val document = listOf(page, a)
|
||||
val document = listOf(page, header, title, a)
|
||||
|
||||
stubOpenDocument(document)
|
||||
stubInterceptEvents()
|
||||
|
@ -139,9 +160,9 @@ class EditorMentionTest : EditorPresentationTestSetup() {
|
|||
ViewState.Success(
|
||||
blocks = listOf(
|
||||
BlockView.Title.Document(
|
||||
id = root,
|
||||
id = title.id,
|
||||
isFocused = false,
|
||||
text = null,
|
||||
text = title.content<TXT>().text,
|
||||
mode = BlockView.Mode.EDIT
|
||||
),
|
||||
BlockView.Text.Paragraph(
|
||||
|
@ -192,6 +213,27 @@ class EditorMentionTest : EditorPresentationTestSetup() {
|
|||
|
||||
@Test
|
||||
fun `should create new page with proper name and add new mention with page id`() {
|
||||
|
||||
val title = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
style = Block.Content.Text.Style.TITLE,
|
||||
marks = emptyList()
|
||||
),
|
||||
children = emptyList(),
|
||||
fields = Block.Fields.empty()
|
||||
)
|
||||
|
||||
val header = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Layout(
|
||||
type = Block.Content.Layout.Type.HEADER
|
||||
),
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf(title.id)
|
||||
)
|
||||
|
||||
val mentionTrigger = "@Jazz"
|
||||
val from = 11
|
||||
val givenText = "page about $mentionTrigger music"
|
||||
|
@ -224,10 +266,10 @@ class EditorMentionTest : EditorPresentationTestSetup() {
|
|||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf(a.id)
|
||||
children = listOf(header.id, a.id)
|
||||
)
|
||||
|
||||
val document = listOf(page, a)
|
||||
val document = listOf(page, header, title, a)
|
||||
|
||||
stubOpenDocument(document)
|
||||
stubInterceptEvents()
|
||||
|
@ -282,10 +324,12 @@ class EditorMentionTest : EditorPresentationTestSetup() {
|
|||
)
|
||||
}
|
||||
|
||||
runBlockingTest {
|
||||
verify(createNewDocument, times(1)).invoke(CreateNewDocument.Params(
|
||||
name = newPageName
|
||||
))
|
||||
verifyBlocking(createNewDocument, times(1)) {
|
||||
invoke(
|
||||
CreateNewDocument.Params(
|
||||
name = newPageName
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
vm.state.test().apply {
|
||||
|
@ -293,9 +337,9 @@ class EditorMentionTest : EditorPresentationTestSetup() {
|
|||
ViewState.Success(
|
||||
blocks = listOf(
|
||||
BlockView.Title.Document(
|
||||
id = root,
|
||||
id = title.id,
|
||||
isFocused = false,
|
||||
text = null,
|
||||
text = title.content<TXT>().text,
|
||||
mode = BlockView.Mode.EDIT
|
||||
),
|
||||
BlockView.Text.Paragraph(
|
||||
|
@ -336,6 +380,27 @@ class EditorMentionTest : EditorPresentationTestSetup() {
|
|||
|
||||
@Test
|
||||
fun `should create new page with untitled name and add new mention with page id`() {
|
||||
|
||||
val title = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
style = Block.Content.Text.Style.TITLE,
|
||||
marks = emptyList()
|
||||
),
|
||||
children = emptyList(),
|
||||
fields = Block.Fields.empty()
|
||||
)
|
||||
|
||||
val header = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Layout(
|
||||
type = Block.Content.Layout.Type.HEADER
|
||||
),
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf(title.id)
|
||||
)
|
||||
|
||||
val mentionTrigger = "@"
|
||||
val from = 11
|
||||
val givenText = "page about $mentionTrigger music"
|
||||
|
@ -368,10 +433,10 @@ class EditorMentionTest : EditorPresentationTestSetup() {
|
|||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf(a.id)
|
||||
children = listOf(header.id, a.id)
|
||||
)
|
||||
|
||||
val document = listOf(page, a)
|
||||
val document = listOf(page, header, title, a)
|
||||
|
||||
stubOpenDocument(document)
|
||||
stubInterceptEvents()
|
||||
|
@ -426,10 +491,12 @@ class EditorMentionTest : EditorPresentationTestSetup() {
|
|||
)
|
||||
}
|
||||
|
||||
runBlockingTest {
|
||||
verify(createNewDocument, times(1)).invoke(CreateNewDocument.Params(
|
||||
name = newPageName
|
||||
))
|
||||
verifyBlocking(createNewDocument, times(1)) {
|
||||
invoke(
|
||||
CreateNewDocument.Params(
|
||||
name = newPageName
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
vm.state.test().apply {
|
||||
|
@ -437,9 +504,9 @@ class EditorMentionTest : EditorPresentationTestSetup() {
|
|||
ViewState.Success(
|
||||
blocks = listOf(
|
||||
BlockView.Title.Document(
|
||||
id = root,
|
||||
id = title.id,
|
||||
isFocused = false,
|
||||
text = null,
|
||||
text = title.content<TXT>().text,
|
||||
mode = BlockView.Mode.EDIT
|
||||
),
|
||||
BlockView.Text.Paragraph(
|
||||
|
|
|
@ -6,7 +6,6 @@ import com.anytypeio.anytype.domain.block.interactor.MergeBlocks
|
|||
import com.anytypeio.anytype.domain.block.interactor.UpdateText
|
||||
import com.anytypeio.anytype.domain.block.model.Block
|
||||
import com.anytypeio.anytype.domain.ext.content
|
||||
import com.anytypeio.anytype.presentation.MockBlockFactory
|
||||
import com.anytypeio.anytype.presentation.page.PageViewModel
|
||||
import com.anytypeio.anytype.presentation.util.CoroutinesTestRule
|
||||
import com.nhaarman.mockitokotlin2.eq
|
||||
|
@ -34,22 +33,61 @@ class EditorMergeTest : EditorPresentationTestSetup() {
|
|||
@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
|
||||
val title = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
style = Block.Content.Text.Style.TITLE,
|
||||
marks = emptyList()
|
||||
),
|
||||
children = emptyList(),
|
||||
fields = Block.Fields.empty()
|
||||
)
|
||||
|
||||
val header = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Layout(
|
||||
type = Block.Content.Layout.Type.HEADER
|
||||
),
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf(title.id)
|
||||
)
|
||||
|
||||
val first = 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 second = 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.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf(header.id, first.id, second.id)
|
||||
)
|
||||
|
||||
val doc = listOf(page, header, title, first, second)
|
||||
|
||||
stubInterceptEvents()
|
||||
stubOpenDocument(page)
|
||||
stubOpenDocument(doc)
|
||||
stubMergeBlocks(root)
|
||||
stubUpdateText()
|
||||
|
||||
|
@ -58,20 +96,20 @@ class EditorMergeTest : EditorPresentationTestSetup() {
|
|||
vm.onStart(root)
|
||||
|
||||
vm.onBlockFocusChanged(
|
||||
id = thirdChild,
|
||||
id = second.id,
|
||||
hasFocus = true
|
||||
)
|
||||
|
||||
val text = MockDataFactory.randomString()
|
||||
|
||||
vm.onTextChanged(
|
||||
id = thirdChild,
|
||||
id = second.id,
|
||||
marks = emptyList(),
|
||||
text = text
|
||||
)
|
||||
|
||||
vm.onNonEmptyBlockBackspaceClicked(
|
||||
id = thirdChild,
|
||||
id = second.id,
|
||||
marks = emptyList(),
|
||||
text = text
|
||||
)
|
||||
|
@ -85,7 +123,7 @@ class EditorMergeTest : EditorPresentationTestSetup() {
|
|||
context = root,
|
||||
text = text,
|
||||
marks = emptyList(),
|
||||
target = thirdChild
|
||||
target = second.id
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -96,7 +134,7 @@ class EditorMergeTest : EditorPresentationTestSetup() {
|
|||
params = eq(
|
||||
MergeBlocks.Params(
|
||||
context = root,
|
||||
pair = Pair(secondChild, thirdChild)
|
||||
pair = Pair(first.id, second.id)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
|
@ -13,6 +13,7 @@ import com.anytypeio.anytype.domain.ext.content
|
|||
import com.anytypeio.anytype.presentation.page.PageViewModel.Companion.DELAY_REFRESH_DOCUMENT_TO_ENTER_MULTI_SELECT_MODE
|
||||
import com.anytypeio.anytype.presentation.page.PageViewModel.Companion.TEXT_CHANGES_DEBOUNCE_DURATION
|
||||
import com.anytypeio.anytype.presentation.util.CoroutinesTestRule
|
||||
import com.anytypeio.anytype.presentation.util.TXT
|
||||
import com.jraska.livedata.test
|
||||
import com.nhaarman.mockitokotlin2.times
|
||||
import com.nhaarman.mockitokotlin2.verifyBlocking
|
||||
|
@ -39,6 +40,26 @@ class EditorMultiSelectModeTest : EditorPresentationTestSetup() {
|
|||
|
||||
// SETUP
|
||||
|
||||
val title = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
style = Block.Content.Text.Style.TITLE,
|
||||
marks = emptyList()
|
||||
),
|
||||
children = emptyList(),
|
||||
fields = Block.Fields.empty()
|
||||
)
|
||||
|
||||
val header = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Layout(
|
||||
type = Block.Content.Layout.Type.HEADER
|
||||
),
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf(title.id)
|
||||
)
|
||||
|
||||
val a = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
fields = Block.Fields.empty(),
|
||||
|
@ -56,10 +77,10 @@ class EditorMultiSelectModeTest : EditorPresentationTestSetup() {
|
|||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf(a.id)
|
||||
children = listOf(header.id, a.id)
|
||||
)
|
||||
|
||||
val document = listOf(page, a)
|
||||
val document = listOf(page, header, title, a)
|
||||
|
||||
stubOpenDocument(document)
|
||||
stubInterceptEvents()
|
||||
|
@ -130,9 +151,9 @@ class EditorMultiSelectModeTest : EditorPresentationTestSetup() {
|
|||
ViewState.Success(
|
||||
blocks = listOf(
|
||||
BlockView.Title.Document(
|
||||
id = root,
|
||||
id = title.id,
|
||||
isFocused = false,
|
||||
text = null,
|
||||
text = title.content<TXT>().text,
|
||||
mode = BlockView.Mode.READ
|
||||
),
|
||||
BlockView.Text.Numbered(
|
||||
|
@ -164,9 +185,9 @@ class EditorMultiSelectModeTest : EditorPresentationTestSetup() {
|
|||
ViewState.Success(
|
||||
blocks = listOf(
|
||||
BlockView.Title.Document(
|
||||
id = root,
|
||||
id = title.id,
|
||||
isFocused = false,
|
||||
text = null,
|
||||
text = title.content<TXT>().text,
|
||||
mode = BlockView.Mode.READ
|
||||
),
|
||||
BlockView.Text.Numbered(
|
||||
|
@ -228,9 +249,9 @@ class EditorMultiSelectModeTest : EditorPresentationTestSetup() {
|
|||
ViewState.Success(
|
||||
blocks = listOf(
|
||||
BlockView.Title.Document(
|
||||
id = root,
|
||||
id = title.id,
|
||||
isFocused = false,
|
||||
text = null,
|
||||
text = title.content<TXT>().text,
|
||||
mode = BlockView.Mode.READ
|
||||
),
|
||||
BlockView.Text.Highlight(
|
||||
|
@ -397,6 +418,26 @@ class EditorMultiSelectModeTest : EditorPresentationTestSetup() {
|
|||
|
||||
// SETUP
|
||||
|
||||
val ttl = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
style = Block.Content.Text.Style.TITLE,
|
||||
marks = emptyList()
|
||||
),
|
||||
children = emptyList(),
|
||||
fields = Block.Fields.empty()
|
||||
)
|
||||
|
||||
val header = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Layout(
|
||||
type = Block.Content.Layout.Type.HEADER
|
||||
),
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf(ttl.id)
|
||||
)
|
||||
|
||||
val grandchild1 = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
fields = Block.Fields.empty(),
|
||||
|
@ -458,10 +499,10 @@ class EditorMultiSelectModeTest : EditorPresentationTestSetup() {
|
|||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf(parent.id)
|
||||
children = listOf(header.id, parent.id)
|
||||
)
|
||||
|
||||
val document = listOf(page, parent, child1, child2, grandchild1, grandchild2)
|
||||
val document = listOf(page, header, ttl, parent, child1, child2, grandchild1, grandchild2)
|
||||
|
||||
stubOpenDocument(document)
|
||||
stubInterceptEvents()
|
||||
|
@ -484,9 +525,9 @@ class EditorMultiSelectModeTest : EditorPresentationTestSetup() {
|
|||
coroutineTestRule.advanceTime(DELAY_REFRESH_DOCUMENT_TO_ENTER_MULTI_SELECT_MODE)
|
||||
|
||||
val title = BlockView.Title.Document(
|
||||
id = root,
|
||||
id = ttl.id,
|
||||
isFocused = false,
|
||||
text = null,
|
||||
text = ttl.content<TXT>().text,
|
||||
mode = BlockView.Mode.READ
|
||||
)
|
||||
|
||||
|
@ -614,6 +655,26 @@ class EditorMultiSelectModeTest : EditorPresentationTestSetup() {
|
|||
|
||||
// SETUP
|
||||
|
||||
val title = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
style = Block.Content.Text.Style.TITLE,
|
||||
marks = emptyList()
|
||||
),
|
||||
children = emptyList(),
|
||||
fields = Block.Fields.empty()
|
||||
)
|
||||
|
||||
val header = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Layout(
|
||||
type = Block.Content.Layout.Type.HEADER
|
||||
),
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf(title.id)
|
||||
)
|
||||
|
||||
val child1 = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
fields = Block.Fields.empty(),
|
||||
|
@ -653,10 +714,10 @@ class EditorMultiSelectModeTest : EditorPresentationTestSetup() {
|
|||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf(parent.id)
|
||||
children = listOf(header.id, parent.id)
|
||||
)
|
||||
|
||||
val document = listOf(page, parent, child1, child2)
|
||||
val document = listOf(page, header, title, parent, child1, child2)
|
||||
|
||||
stubOpenDocument(document)
|
||||
stubInterceptEvents()
|
||||
|
@ -678,10 +739,10 @@ class EditorMultiSelectModeTest : EditorPresentationTestSetup() {
|
|||
|
||||
coroutineTestRule.advanceTime(DELAY_REFRESH_DOCUMENT_TO_ENTER_MULTI_SELECT_MODE)
|
||||
|
||||
val title = BlockView.Title.Document(
|
||||
id = root,
|
||||
val titleView = BlockView.Title.Document(
|
||||
id = title.id,
|
||||
isFocused = false,
|
||||
text = null,
|
||||
text = title.content<TXT>().text,
|
||||
mode = BlockView.Mode.READ
|
||||
)
|
||||
|
||||
|
@ -722,7 +783,7 @@ class EditorMultiSelectModeTest : EditorPresentationTestSetup() {
|
|||
assertValue(
|
||||
ViewState.Success(
|
||||
blocks = listOf(
|
||||
title,
|
||||
titleView,
|
||||
parentView,
|
||||
child1View,
|
||||
child2View
|
||||
|
@ -743,7 +804,7 @@ class EditorMultiSelectModeTest : EditorPresentationTestSetup() {
|
|||
assertValue(
|
||||
ViewState.Success(
|
||||
blocks = listOf(
|
||||
title,
|
||||
titleView,
|
||||
parentView.copy(isSelected = true),
|
||||
child1View.copy(isSelected = true),
|
||||
child2View.copy(isSelected = true)
|
||||
|
@ -764,7 +825,7 @@ class EditorMultiSelectModeTest : EditorPresentationTestSetup() {
|
|||
assertValue(
|
||||
ViewState.Success(
|
||||
blocks = listOf(
|
||||
title,
|
||||
titleView,
|
||||
parentView.copy(isSelected = true),
|
||||
child1View.copy(isSelected = true),
|
||||
child2View.copy(isSelected = true)
|
||||
|
@ -781,6 +842,26 @@ class EditorMultiSelectModeTest : EditorPresentationTestSetup() {
|
|||
|
||||
// SETUP
|
||||
|
||||
val title = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
style = Block.Content.Text.Style.TITLE,
|
||||
marks = emptyList()
|
||||
),
|
||||
children = emptyList(),
|
||||
fields = Block.Fields.empty()
|
||||
)
|
||||
|
||||
val header = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Layout(
|
||||
type = Block.Content.Layout.Type.HEADER
|
||||
),
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf(title.id)
|
||||
)
|
||||
|
||||
val grandchild1 = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
fields = Block.Fields.empty(),
|
||||
|
@ -842,10 +923,10 @@ class EditorMultiSelectModeTest : EditorPresentationTestSetup() {
|
|||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf(parent.id)
|
||||
children = listOf(header.id, parent.id)
|
||||
)
|
||||
|
||||
val document = listOf(page, parent, child1, child2, grandchild1, grandchild2)
|
||||
val document = listOf(page, header, title, parent, child1, child2, grandchild1, grandchild2)
|
||||
|
||||
stubOpenDocument(document)
|
||||
stubInterceptEvents()
|
||||
|
@ -887,6 +968,81 @@ class EditorMultiSelectModeTest : EditorPresentationTestSetup() {
|
|||
clearPendingCoroutines()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should not select title when trying to select all blocks`() {
|
||||
|
||||
// SETUP
|
||||
|
||||
val title = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
style = Block.Content.Text.Style.TITLE,
|
||||
marks = emptyList()
|
||||
),
|
||||
children = emptyList(),
|
||||
fields = Block.Fields.empty()
|
||||
)
|
||||
|
||||
val header = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Layout(
|
||||
type = Block.Content.Layout.Type.HEADER
|
||||
),
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf(title.id)
|
||||
)
|
||||
|
||||
val a = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
fields = Block.Fields.empty(),
|
||||
children = emptyList(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
marks = emptyList(),
|
||||
style = Block.Content.Text.Style.NUMBERED
|
||||
)
|
||||
)
|
||||
|
||||
val focus = listOf(a.id, title.id).random()
|
||||
|
||||
val page = Block(
|
||||
id = root,
|
||||
fields = Block.Fields(emptyMap()),
|
||||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf(header.id, a.id)
|
||||
)
|
||||
|
||||
val document = listOf(page, header, title, a)
|
||||
|
||||
stubOpenDocument(document)
|
||||
stubInterceptEvents()
|
||||
|
||||
val vm = buildViewModel()
|
||||
|
||||
vm.onStart(root)
|
||||
|
||||
// TESTING
|
||||
|
||||
vm.apply {
|
||||
onBlockFocusChanged(id = focus, hasFocus = true)
|
||||
onEnterMultiSelectModeClicked()
|
||||
onMultiSelectModeSelectAllClicked()
|
||||
}
|
||||
|
||||
coroutineTestRule.advanceTime(DELAY_REFRESH_DOCUMENT_TO_ENTER_MULTI_SELECT_MODE)
|
||||
|
||||
vm.controlPanelViewState.test().assertValue { state ->
|
||||
state.multiSelect.isVisible
|
||||
&& !state.multiSelect.isScrollAndMoveEnabled
|
||||
&& state.multiSelect.count == 1
|
||||
}
|
||||
|
||||
clearPendingCoroutines()
|
||||
}
|
||||
|
||||
|
||||
private fun clearPendingCoroutines() {
|
||||
coroutineTestRule.advanceTime(TEXT_CHANGES_DEBOUNCE_DURATION)
|
||||
|
|
|
@ -26,6 +26,7 @@ import com.anytypeio.anytype.presentation.page.PageViewModel
|
|||
import com.anytypeio.anytype.presentation.page.render.DefaultBlockViewRenderer
|
||||
import com.anytypeio.anytype.presentation.page.selection.SelectionStateHolder
|
||||
import com.anytypeio.anytype.presentation.page.toggle.ToggleStateHolder
|
||||
import com.anytypeio.anytype.presentation.util.Bridge
|
||||
import com.nhaarman.mockitokotlin2.any
|
||||
import com.nhaarman.mockitokotlin2.doReturn
|
||||
import com.nhaarman.mockitokotlin2.stub
|
||||
|
@ -200,7 +201,8 @@ open class EditorPresentationTestSetup {
|
|||
move = move,
|
||||
turnIntoDocument = turnIntoDocument,
|
||||
analytics = analytics
|
||||
)
|
||||
),
|
||||
bridge = Bridge()
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -32,6 +32,26 @@ class EditorTitleAddBlockTest : EditorPresentationTestSetup() {
|
|||
MockitoAnnotations.initMocks(this)
|
||||
}
|
||||
|
||||
val title = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
style = Block.Content.Text.Style.TITLE,
|
||||
marks = emptyList()
|
||||
),
|
||||
children = emptyList(),
|
||||
fields = Block.Fields.empty()
|
||||
)
|
||||
|
||||
val header = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Layout(
|
||||
type = Block.Content.Layout.Type.HEADER
|
||||
),
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf(title.id)
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `should create a new text block after title with INNER position if document has only title block`() {
|
||||
|
||||
|
@ -43,19 +63,19 @@ class EditorTitleAddBlockTest : EditorPresentationTestSetup() {
|
|||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf()
|
||||
children = listOf(header.id)
|
||||
)
|
||||
|
||||
val style = Block.Content.Text.Style.values().random()
|
||||
|
||||
val params = CreateBlock.Params(
|
||||
context = root,
|
||||
target = root,
|
||||
position = Position.INNER,
|
||||
target = title.id,
|
||||
position = Position.BOTTOM,
|
||||
prototype = Block.Prototype.Text(style)
|
||||
)
|
||||
|
||||
val document = listOf(page)
|
||||
val document = listOf(page, header, title)
|
||||
|
||||
stubOpenDocument(document = document)
|
||||
stubInterceptEvents(InterceptEvents.Params(context = root))
|
||||
|
@ -69,7 +89,7 @@ class EditorTitleAddBlockTest : EditorPresentationTestSetup() {
|
|||
vm.apply {
|
||||
onStart(root)
|
||||
onBlockFocusChanged(
|
||||
id = root,
|
||||
id = title.id,
|
||||
hasFocus = true
|
||||
)
|
||||
onAddTextBlockClicked(style)
|
||||
|
@ -79,7 +99,7 @@ class EditorTitleAddBlockTest : EditorPresentationTestSetup() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `should create a new text block at the TOP of the document's first block`() {
|
||||
fun `should create a new text block below title`() {
|
||||
|
||||
// SETUP
|
||||
|
||||
|
@ -100,19 +120,19 @@ class EditorTitleAddBlockTest : EditorPresentationTestSetup() {
|
|||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf(block.id)
|
||||
children = listOf(header.id, block.id)
|
||||
)
|
||||
|
||||
val style = Block.Content.Text.Style.values().random()
|
||||
|
||||
val params = CreateBlock.Params(
|
||||
context = root,
|
||||
target = block.id,
|
||||
position = Position.TOP,
|
||||
target = title.id,
|
||||
position = Position.BOTTOM,
|
||||
prototype = Block.Prototype.Text(style)
|
||||
)
|
||||
|
||||
val document = listOf(page)
|
||||
val document = listOf(page, header, title)
|
||||
|
||||
stubOpenDocument(document = document)
|
||||
stubInterceptEvents(InterceptEvents.Params(context = root))
|
||||
|
@ -126,7 +146,7 @@ class EditorTitleAddBlockTest : EditorPresentationTestSetup() {
|
|||
vm.apply {
|
||||
onStart(root)
|
||||
onBlockFocusChanged(
|
||||
id = root,
|
||||
id = title.id,
|
||||
hasFocus = true
|
||||
)
|
||||
onAddTextBlockClicked(style)
|
||||
|
@ -136,7 +156,7 @@ class EditorTitleAddBlockTest : EditorPresentationTestSetup() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `should create a new document after title with INNER position if document has only title block`() {
|
||||
fun `should create a new document after title if document has only title block`() {
|
||||
|
||||
// SETUP
|
||||
|
||||
|
@ -146,16 +166,16 @@ class EditorTitleAddBlockTest : EditorPresentationTestSetup() {
|
|||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf()
|
||||
children = listOf(header.id)
|
||||
)
|
||||
|
||||
val params = CreateDocument.Params(
|
||||
context = root,
|
||||
target = root,
|
||||
position = Position.INNER
|
||||
target = title.id,
|
||||
position = Position.BOTTOM
|
||||
)
|
||||
|
||||
val document = listOf(page)
|
||||
val document = listOf(page, header, title)
|
||||
|
||||
stubOpenDocument(document = document)
|
||||
stubInterceptEvents(InterceptEvents.Params(context = root))
|
||||
|
@ -168,7 +188,7 @@ class EditorTitleAddBlockTest : EditorPresentationTestSetup() {
|
|||
vm.apply {
|
||||
onStart(root)
|
||||
onBlockFocusChanged(
|
||||
id = root,
|
||||
id = title.id,
|
||||
hasFocus = true
|
||||
)
|
||||
onAddNewPageClicked()
|
||||
|
@ -178,7 +198,7 @@ class EditorTitleAddBlockTest : EditorPresentationTestSetup() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `should create a new document at the TOP of the document's first block`() {
|
||||
fun `should create a new document below document title`() {
|
||||
|
||||
// SETUP
|
||||
|
||||
|
@ -199,16 +219,16 @@ class EditorTitleAddBlockTest : EditorPresentationTestSetup() {
|
|||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf(block.id)
|
||||
children = listOf(header.id, block.id)
|
||||
)
|
||||
|
||||
val params = CreateDocument.Params(
|
||||
context = root,
|
||||
target = block.id,
|
||||
position = Position.TOP
|
||||
target = title.id,
|
||||
position = Position.BOTTOM
|
||||
)
|
||||
|
||||
val document = listOf(page)
|
||||
val document = listOf(page, header, title)
|
||||
|
||||
stubOpenDocument(document = document)
|
||||
stubInterceptEvents(InterceptEvents.Params(context = root))
|
||||
|
@ -221,7 +241,7 @@ class EditorTitleAddBlockTest : EditorPresentationTestSetup() {
|
|||
vm.apply {
|
||||
onStart(root)
|
||||
onBlockFocusChanged(
|
||||
id = root,
|
||||
id = title.id,
|
||||
hasFocus = true
|
||||
)
|
||||
onAddNewPageClicked()
|
||||
|
@ -231,7 +251,7 @@ class EditorTitleAddBlockTest : EditorPresentationTestSetup() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `should create a new file block after title with INNER position if document has only title block`() {
|
||||
fun `should create a new file block below title if document has only title block`() {
|
||||
|
||||
// SETUP
|
||||
|
||||
|
@ -241,7 +261,7 @@ class EditorTitleAddBlockTest : EditorPresentationTestSetup() {
|
|||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf()
|
||||
children = listOf(header.id)
|
||||
)
|
||||
|
||||
val types = Block.Content.File.Type.values().filter { it != Block.Content.File.Type.NONE }
|
||||
|
@ -250,15 +270,15 @@ class EditorTitleAddBlockTest : EditorPresentationTestSetup() {
|
|||
|
||||
val params = CreateBlock.Params(
|
||||
context = root,
|
||||
target = root,
|
||||
position = Position.INNER,
|
||||
target = title.id,
|
||||
position = Position.BOTTOM,
|
||||
prototype = Block.Prototype.File(
|
||||
type = type,
|
||||
state = Block.Content.File.State.EMPTY
|
||||
)
|
||||
)
|
||||
|
||||
val document = listOf(page)
|
||||
val document = listOf(page, header, title)
|
||||
|
||||
stubOpenDocument(document = document)
|
||||
stubInterceptEvents(InterceptEvents.Params(context = root))
|
||||
|
@ -271,7 +291,7 @@ class EditorTitleAddBlockTest : EditorPresentationTestSetup() {
|
|||
vm.apply {
|
||||
onStart(root)
|
||||
onBlockFocusChanged(
|
||||
id = root,
|
||||
id = title.id,
|
||||
hasFocus = true
|
||||
)
|
||||
onAddFileBlockClicked(type = type)
|
||||
|
@ -281,7 +301,7 @@ class EditorTitleAddBlockTest : EditorPresentationTestSetup() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `should create a new file block at the TOP of the document's first block`() {
|
||||
fun `should create a new file block below document title`() {
|
||||
|
||||
// SETUP
|
||||
|
||||
|
@ -302,7 +322,7 @@ class EditorTitleAddBlockTest : EditorPresentationTestSetup() {
|
|||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf(block.id)
|
||||
children = listOf(header.id, block.id)
|
||||
)
|
||||
|
||||
val types = Block.Content.File.Type.values().filter { it != Block.Content.File.Type.NONE }
|
||||
|
@ -311,15 +331,15 @@ class EditorTitleAddBlockTest : EditorPresentationTestSetup() {
|
|||
|
||||
val params = CreateBlock.Params(
|
||||
context = root,
|
||||
target = block.id,
|
||||
position = Position.TOP,
|
||||
target = title.id,
|
||||
position = Position.BOTTOM,
|
||||
prototype = Block.Prototype.File(
|
||||
type = type,
|
||||
state = Block.Content.File.State.EMPTY
|
||||
)
|
||||
)
|
||||
|
||||
val document = listOf(page)
|
||||
val document = listOf(page, header, title)
|
||||
|
||||
stubOpenDocument(document = document)
|
||||
stubInterceptEvents(InterceptEvents.Params(context = root))
|
||||
|
@ -332,7 +352,7 @@ class EditorTitleAddBlockTest : EditorPresentationTestSetup() {
|
|||
vm.apply {
|
||||
onStart(root)
|
||||
onBlockFocusChanged(
|
||||
id = root,
|
||||
id = title.id,
|
||||
hasFocus = true
|
||||
)
|
||||
onAddFileBlockClicked(type = type)
|
||||
|
@ -352,17 +372,17 @@ class EditorTitleAddBlockTest : EditorPresentationTestSetup() {
|
|||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf()
|
||||
children = listOf(header.id)
|
||||
)
|
||||
|
||||
val params = CreateBlock.Params(
|
||||
context = root,
|
||||
target = root,
|
||||
position = Position.INNER,
|
||||
target = title.id,
|
||||
position = Position.BOTTOM,
|
||||
prototype = Block.Prototype.Bookmark
|
||||
)
|
||||
|
||||
val document = listOf(page)
|
||||
val document = listOf(page, header, title)
|
||||
|
||||
stubOpenDocument(document = document)
|
||||
stubInterceptEvents(InterceptEvents.Params(context = root))
|
||||
|
@ -375,7 +395,7 @@ class EditorTitleAddBlockTest : EditorPresentationTestSetup() {
|
|||
vm.apply {
|
||||
onStart(root)
|
||||
onBlockFocusChanged(
|
||||
id = root,
|
||||
id = title.id,
|
||||
hasFocus = true
|
||||
)
|
||||
onAddBookmarkBlockClicked()
|
||||
|
@ -385,7 +405,7 @@ class EditorTitleAddBlockTest : EditorPresentationTestSetup() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `should create a new bookmark block at the TOP of the document's first block`() {
|
||||
fun `should create a new bookmark block below title block`() {
|
||||
|
||||
// SETUP
|
||||
|
||||
|
@ -406,17 +426,17 @@ class EditorTitleAddBlockTest : EditorPresentationTestSetup() {
|
|||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf(block.id)
|
||||
children = listOf(header.id, block.id)
|
||||
)
|
||||
|
||||
val params = CreateBlock.Params(
|
||||
context = root,
|
||||
target = block.id,
|
||||
position = Position.TOP,
|
||||
target = title.id,
|
||||
position = Position.BOTTOM,
|
||||
prototype = Block.Prototype.Bookmark
|
||||
)
|
||||
|
||||
val document = listOf(page)
|
||||
val document = listOf(page, header, title)
|
||||
|
||||
stubOpenDocument(document = document)
|
||||
stubInterceptEvents(InterceptEvents.Params(context = root))
|
||||
|
@ -429,7 +449,7 @@ class EditorTitleAddBlockTest : EditorPresentationTestSetup() {
|
|||
vm.apply {
|
||||
onStart(root)
|
||||
onBlockFocusChanged(
|
||||
id = root,
|
||||
id = title.id,
|
||||
hasFocus = true
|
||||
)
|
||||
onAddBookmarkBlockClicked()
|
||||
|
@ -439,7 +459,7 @@ class EditorTitleAddBlockTest : EditorPresentationTestSetup() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `should create a new divider block after title with INNER position if document has only title block`() {
|
||||
fun `should create a new divider block after title if document has only title block`() {
|
||||
|
||||
// SETUP
|
||||
|
||||
|
@ -449,17 +469,17 @@ class EditorTitleAddBlockTest : EditorPresentationTestSetup() {
|
|||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf()
|
||||
children = listOf(header.id)
|
||||
)
|
||||
|
||||
val params = CreateBlock.Params(
|
||||
context = root,
|
||||
target = root,
|
||||
position = Position.INNER,
|
||||
target = title.id,
|
||||
position = Position.BOTTOM,
|
||||
prototype = Block.Prototype.Divider
|
||||
)
|
||||
|
||||
val document = listOf(page)
|
||||
val document = listOf(page, header, title)
|
||||
|
||||
stubOpenDocument(document = document)
|
||||
stubInterceptEvents(InterceptEvents.Params(context = root))
|
||||
|
@ -472,7 +492,7 @@ class EditorTitleAddBlockTest : EditorPresentationTestSetup() {
|
|||
vm.apply {
|
||||
onStart(root)
|
||||
onBlockFocusChanged(
|
||||
id = root,
|
||||
id = title.id,
|
||||
hasFocus = true
|
||||
)
|
||||
onAddDividerBlockClicked()
|
||||
|
@ -482,7 +502,7 @@ class EditorTitleAddBlockTest : EditorPresentationTestSetup() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `should create a new divider block at the TOP of the document's first block`() {
|
||||
fun `should create a new divider block after document title`() {
|
||||
|
||||
// SETUP
|
||||
|
||||
|
@ -503,17 +523,17 @@ class EditorTitleAddBlockTest : EditorPresentationTestSetup() {
|
|||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf(block.id)
|
||||
children = listOf(header.id, block.id)
|
||||
)
|
||||
|
||||
val params = CreateBlock.Params(
|
||||
context = root,
|
||||
target = block.id,
|
||||
position = Position.TOP,
|
||||
target = title.id,
|
||||
position = Position.BOTTOM,
|
||||
prototype = Block.Prototype.Divider
|
||||
)
|
||||
|
||||
val document = listOf(page)
|
||||
val document = listOf(page, header, title)
|
||||
|
||||
stubOpenDocument(document = document)
|
||||
stubInterceptEvents(InterceptEvents.Params(context = root))
|
||||
|
@ -526,7 +546,7 @@ class EditorTitleAddBlockTest : EditorPresentationTestSetup() {
|
|||
vm.apply {
|
||||
onStart(root)
|
||||
onBlockFocusChanged(
|
||||
id = root,
|
||||
id = title.id,
|
||||
hasFocus = true
|
||||
)
|
||||
onAddDividerBlockClicked()
|
||||
|
|
|
@ -0,0 +1,171 @@
|
|||
package com.anytypeio.anytype.presentation.page.editor
|
||||
|
||||
import MockDataFactory
|
||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||
import com.anytypeio.anytype.core_ui.state.ControlPanelState
|
||||
import com.anytypeio.anytype.domain.block.model.Block
|
||||
import com.anytypeio.anytype.domain.event.interactor.InterceptEvents
|
||||
import com.anytypeio.anytype.presentation.page.PageViewModel
|
||||
import com.anytypeio.anytype.presentation.util.CoroutinesTestRule
|
||||
import com.jraska.livedata.test
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.runBlockingTest
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mockito.MockitoAnnotations
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class EditorTitleTest : EditorPresentationTestSetup() {
|
||||
|
||||
@get:Rule
|
||||
val rule = InstantTaskExecutorRule()
|
||||
|
||||
@get:Rule
|
||||
val coroutineTestRule = CoroutinesTestRule()
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
}
|
||||
|
||||
val title = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Text(
|
||||
text = MockDataFactory.randomString(),
|
||||
style = Block.Content.Text.Style.TITLE,
|
||||
marks = emptyList()
|
||||
),
|
||||
children = emptyList(),
|
||||
fields = Block.Fields.empty()
|
||||
)
|
||||
|
||||
val header = Block(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
content = Block.Content.Layout(
|
||||
type = Block.Content.Layout.Type.HEADER
|
||||
),
|
||||
fields = Block.Fields.empty(),
|
||||
children = listOf(title.id)
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `should not open action menu for title block`() {
|
||||
|
||||
// SETUP
|
||||
|
||||
val page = Block(
|
||||
id = root,
|
||||
fields = Block.Fields(emptyMap()),
|
||||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf(header.id)
|
||||
)
|
||||
|
||||
val document = listOf(page, header, title)
|
||||
|
||||
stubOpenDocument(document = document)
|
||||
stubInterceptEvents(InterceptEvents.Params(context = root))
|
||||
|
||||
val vm = buildViewModel()
|
||||
|
||||
// TESTING
|
||||
|
||||
val toasts = mutableListOf<String>()
|
||||
|
||||
runBlockingTest {
|
||||
|
||||
val toastSubscription = launch { vm.toasts.collect { toasts.add(it) } }
|
||||
val commandTestObserver = vm.commands.test()
|
||||
|
||||
vm.apply {
|
||||
onStart(root)
|
||||
onBlockFocusChanged(title.id, true)
|
||||
onBlockToolbarBlockActionsClicked()
|
||||
}
|
||||
|
||||
commandTestObserver.assertNoValue().assertHistorySize(0)
|
||||
|
||||
assertEquals(
|
||||
expected = 1,
|
||||
actual = toasts.size
|
||||
)
|
||||
assertEquals(
|
||||
expected = PageViewModel.CANNOT_OPEN_ACTION_MENU_FOR_TITLE_ERROR,
|
||||
actual = toasts.first()
|
||||
)
|
||||
|
||||
toastSubscription.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should not open style panel for title block`() {
|
||||
|
||||
// SETUP
|
||||
|
||||
val page = Block(
|
||||
id = root,
|
||||
fields = Block.Fields(emptyMap()),
|
||||
content = Block.Content.Smart(
|
||||
type = Block.Content.Smart.Type.PAGE
|
||||
),
|
||||
children = listOf(header.id)
|
||||
)
|
||||
|
||||
val document = listOf(page, header, title)
|
||||
|
||||
stubOpenDocument(document = document)
|
||||
stubInterceptEvents(InterceptEvents.Params(context = root))
|
||||
|
||||
val vm = buildViewModel()
|
||||
|
||||
// TESTING
|
||||
|
||||
val toasts = mutableListOf<String>()
|
||||
|
||||
runBlockingTest {
|
||||
|
||||
val toastSubscription = launch { vm.toasts.collect { toasts.add(it) } }
|
||||
|
||||
val commandTestObserver = vm.commands.test()
|
||||
val controlPanelObserver = vm.controlPanelViewState.test()
|
||||
|
||||
vm.apply {
|
||||
onStart(root)
|
||||
onBlockFocusChanged(title.id, true)
|
||||
onBlockToolbarStyleClicked()
|
||||
}
|
||||
|
||||
commandTestObserver.assertNoValue().assertHistorySize(0)
|
||||
controlPanelObserver.assertValue(
|
||||
ControlPanelState(
|
||||
mainToolbar = ControlPanelState.Toolbar.Main(
|
||||
isVisible = true
|
||||
),
|
||||
stylingToolbar = ControlPanelState.Toolbar.Styling.reset(),
|
||||
multiSelect = ControlPanelState.Toolbar.MultiSelect(
|
||||
isVisible = false,
|
||||
isScrollAndMoveEnabled = false
|
||||
),
|
||||
mentionToolbar = ControlPanelState.Toolbar.MentionToolbar.reset(),
|
||||
navigationToolbar = ControlPanelState.Toolbar.Navigation(isVisible = false)
|
||||
)
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
expected = 1,
|
||||
actual = toasts.size
|
||||
)
|
||||
assertEquals(
|
||||
expected = PageViewModel.CANNOT_OPEN_STYLE_PANEL_FOR_TITLE_ERROR,
|
||||
actual = toasts.first()
|
||||
)
|
||||
|
||||
toastSubscription.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package com.anytypeio.anytype.presentation.util
|
||||
|
||||
import com.anytypeio.anytype.domain.block.model.Block
|
||||
|
||||
typealias TXT = Block.Content.Text
|
|
@ -457,6 +457,8 @@ message Rpc {
|
|||
TOP = 1;
|
||||
// new block will be created as the first children of existing
|
||||
INNER = 2;
|
||||
// new block will be created after header (not required for set at client side, will auto set for title block)
|
||||
TITLE = 3;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -736,6 +738,7 @@ message Rpc {
|
|||
}
|
||||
message Response {
|
||||
Error error = 1;
|
||||
ResponseEvent event = 2;
|
||||
|
||||
message Error {
|
||||
Code code = 1;
|
||||
|
@ -812,6 +815,7 @@ message Rpc {
|
|||
|
||||
message Response {
|
||||
Error error = 1;
|
||||
ResponseEvent event = 2;
|
||||
|
||||
message Error {
|
||||
Code code = 1;
|
||||
|
@ -2234,6 +2238,94 @@ message Rpc {
|
|||
}
|
||||
}
|
||||
|
||||
message History {
|
||||
// returns list of versions (changes)
|
||||
message Versions {
|
||||
message Version {
|
||||
string id = 1;
|
||||
repeated string previousIds = 2;
|
||||
string authorId = 3;
|
||||
string authorName = 4;
|
||||
int64 time = 5;
|
||||
int64 groupId = 6;
|
||||
}
|
||||
|
||||
message Request {
|
||||
string pageId = 1;
|
||||
// when indicated, results will include versions before given id
|
||||
string lastVersionId = 2;
|
||||
// desired count of versions
|
||||
int32 limit = 3;
|
||||
}
|
||||
|
||||
message Response {
|
||||
Error error = 1;
|
||||
repeated Version versions = 2;
|
||||
|
||||
message Error {
|
||||
Code code = 1;
|
||||
string description = 2;
|
||||
|
||||
enum Code {
|
||||
NULL = 0;
|
||||
UNKNOWN_ERROR = 1;
|
||||
BAD_INPUT = 2;
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// returns blockShow event for given version
|
||||
message Show {
|
||||
message Request {
|
||||
string pageId = 1;
|
||||
string versionId = 2;
|
||||
}
|
||||
|
||||
message Response {
|
||||
Error error = 1;
|
||||
Event.Block.Show blockShow = 2;
|
||||
History.Versions.Version version = 3;
|
||||
|
||||
message Error {
|
||||
Code code = 1;
|
||||
string description = 2;
|
||||
|
||||
enum Code {
|
||||
NULL = 0;
|
||||
UNKNOWN_ERROR = 1;
|
||||
BAD_INPUT = 2;
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
message SetVersion {
|
||||
message Request {
|
||||
string pageId = 1;
|
||||
string versionId = 2;
|
||||
}
|
||||
|
||||
message Response {
|
||||
Error error = 1;
|
||||
|
||||
message Error {
|
||||
Code code = 1;
|
||||
string description = 2;
|
||||
|
||||
enum Code {
|
||||
NULL = 0;
|
||||
UNKNOWN_ERROR = 1;
|
||||
BAD_INPUT = 2;
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
message Page {
|
||||
message Create {
|
||||
message Request {
|
||||
|
|
|
@ -69,6 +69,7 @@ message Block {
|
|||
Row = 0;
|
||||
Column = 1;
|
||||
Div = 2;
|
||||
Header = 3;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue