Merge pull request #3758 from element-hq/feature/bma/editorStateFixture

Editor state fixture and preview improvement
This commit is contained in:
Benoit Marty
2024-10-31 10:36:01 +01:00
committed by GitHub
40 changed files with 225 additions and 214 deletions

View File

@@ -37,9 +37,8 @@ import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.textcomposer.aRichTextEditorState
import io.element.android.libraries.textcomposer.model.MessageComposerMode
import io.element.android.libraries.textcomposer.model.TextEditorState
import io.element.android.libraries.textcomposer.model.aTextEditorStateRich
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentSetOf
@@ -97,7 +96,7 @@ fun aMessagesState(
roomAvatar: AsyncData<AvatarData> = AsyncData.Success(AvatarData("!id:domain", "Room name", size = AvatarSize.TimelineRoom)),
userEventPermissions: UserEventPermissions = aUserEventPermissions(),
composerState: MessageComposerState = aMessageComposerState(
textEditorState = TextEditorState.Rich(aRichTextEditorState(initialText = "Hello", initialFocus = true)),
textEditorState = aTextEditorStateRich(initialText = "Hello", initialFocus = true),
isFullScreen = false,
mode = MessageComposerMode.Normal,
),

View File

@@ -14,8 +14,7 @@ import io.element.android.features.messages.impl.aMessagesState
import io.element.android.features.messages.impl.messagecomposer.aMessageComposerState
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.textcomposer.model.MarkdownTextEditorState
import io.element.android.libraries.textcomposer.model.TextEditorState
import io.element.android.libraries.textcomposer.model.aTextEditorStateMarkdown
@PreviewsDayNight
@Composable
@@ -25,11 +24,9 @@ internal fun MessagesViewWithIdentityChangePreview(
MessagesView(
state = aMessagesState(
composerState = aMessageComposerState(
textEditorState = TextEditorState.Markdown(
state = MarkdownTextEditorState(
initialText = "",
initialFocus = false,
)
textEditorState = aTextEditorStateMarkdown(
initialText = "",
initialFocus = false,
)
),
identityChangeState = identityChangeState,

View File

@@ -8,10 +8,10 @@
package io.element.android.features.messages.impl.messagecomposer
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.textcomposer.aRichTextEditorState
import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion
import io.element.android.libraries.textcomposer.model.MessageComposerMode
import io.element.android.libraries.textcomposer.model.TextEditorState
import io.element.android.libraries.textcomposer.model.aTextEditorStateRich
import io.element.android.wysiwyg.display.TextDisplay
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@@ -24,7 +24,7 @@ open class MessageComposerStateProvider : PreviewParameterProvider<MessageCompos
}
fun aMessageComposerState(
textEditorState: TextEditorState = TextEditorState.Rich(aRichTextEditorState()),
textEditorState: TextEditorState = aTextEditorStateRich(),
isFullScreen: Boolean = false,
mode: MessageComposerMode = MessageComposerMode.Normal,
showTextFormatting: Boolean = false,

View File

@@ -69,9 +69,9 @@ import io.element.android.libraries.matrix.test.room.aRoomInfo
import io.element.android.libraries.matrix.test.room.aRoomMember
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails
import io.element.android.libraries.textcomposer.model.MarkdownTextEditorState
import io.element.android.libraries.textcomposer.model.MessageComposerMode
import io.element.android.libraries.textcomposer.model.TextEditorState
import io.element.android.libraries.textcomposer.model.aTextEditorStateMarkdown
import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.WarmUpRule
@@ -1005,7 +1005,7 @@ class MessagesPresenterTest {
messageComposerPresenter: Presenter<MessageComposerState> = Presenter {
aMessageComposerState(
// Use TextEditorState.Markdown, so that we can request focus manually.
textEditorState = TextEditorState.Markdown(MarkdownTextEditorState(initialText = "", initialFocus = false))
textEditorState = aTextEditorStateMarkdown(initialText = "", initialFocus = false)
)
},
actionListEventSink: (ActionListEvents) -> Unit = {},

View File

@@ -57,7 +57,6 @@ import io.element.android.libraries.textcomposer.components.VoiceMessagePreview
import io.element.android.libraries.textcomposer.components.VoiceMessageRecorderButton
import io.element.android.libraries.textcomposer.components.VoiceMessageRecording
import io.element.android.libraries.textcomposer.components.markdown.MarkdownTextInput
import io.element.android.libraries.textcomposer.components.markdown.aMarkdownTextEditorState
import io.element.android.libraries.textcomposer.components.textInputRoundedCornerShape
import io.element.android.libraries.textcomposer.model.MessageComposerMode
import io.element.android.libraries.textcomposer.model.Suggestion
@@ -65,6 +64,8 @@ import io.element.android.libraries.textcomposer.model.TextEditorState
import io.element.android.libraries.textcomposer.model.VoiceMessagePlayerEvent
import io.element.android.libraries.textcomposer.model.VoiceMessageRecorderEvent
import io.element.android.libraries.textcomposer.model.VoiceMessageState
import io.element.android.libraries.textcomposer.model.aTextEditorStateMarkdown
import io.element.android.libraries.textcomposer.model.aTextEditorStateRich
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.wysiwyg.compose.RichTextEditor
import io.element.android.wysiwyg.compose.RichTextEditorState
@@ -493,185 +494,156 @@ private fun TextInput(
}
}
private fun aTextEditorStateMarkdownList() = persistentListOf(
aTextEditorStateMarkdown(initialText = "", initialFocus = true),
aTextEditorStateMarkdown(initialText = "A message", initialFocus = true),
aTextEditorStateMarkdown(
initialText = "A message\nWith several lines\nTo preview larger textfields and long lines with overflow",
initialFocus = true,
),
aTextEditorStateMarkdown(initialText = "A message without focus", initialFocus = false),
)
private fun aTextEditorStateRichList() = persistentListOf(
aTextEditorStateRich(initialFocus = true),
aTextEditorStateRich(initialText = "A message", initialFocus = true),
aTextEditorStateRich(
initialText = "A message\nWith several lines\nTo preview larger textfields and long lines with overflow",
initialFocus = true
),
aTextEditorStateRich(initialText = "A message without focus", initialFocus = false),
)
@PreviewsDayNight
@Composable
internal fun TextComposerSimplePreview() = ElementPreview {
PreviewColumn(
items = persistentListOf(
{
ATextComposer(
TextEditorState.Markdown(aMarkdownTextEditorState(initialText = "", initialFocus = true)),
voiceMessageState = VoiceMessageState.Idle,
composerMode = MessageComposerMode.Normal,
enableVoiceMessages = true,
)
},
{
ATextComposer(
TextEditorState.Markdown(aMarkdownTextEditorState(initialText = "A message", initialFocus = true)),
voiceMessageState = VoiceMessageState.Idle,
composerMode = MessageComposerMode.Normal,
enableVoiceMessages = true,
)
},
{
ATextComposer(
TextEditorState.Markdown(
aMarkdownTextEditorState(
initialText = "A message\nWith several lines\nTo preview larger textfields and long lines with overflow",
initialFocus = true
)
),
voiceMessageState = VoiceMessageState.Idle,
composerMode = MessageComposerMode.Normal,
enableVoiceMessages = true,
)
},
{
ATextComposer(
TextEditorState.Markdown(aMarkdownTextEditorState(initialText = "A message without focus", initialFocus = false)),
voiceMessageState = VoiceMessageState.Idle,
composerMode = MessageComposerMode.Normal,
enableVoiceMessages = true,
)
}
items = aTextEditorStateMarkdownList()
) { textEditorState ->
ATextComposer(
state = textEditorState,
voiceMessageState = VoiceMessageState.Idle,
composerMode = MessageComposerMode.Normal,
enableVoiceMessages = true,
)
)
}
}
@PreviewsDayNight
@Composable
internal fun TextComposerFormattingPreview() = ElementPreview {
PreviewColumn(items = persistentListOf({
PreviewColumn(
items = aTextEditorStateRichList()
) { textEditorState ->
ATextComposer(
TextEditorState.Rich(aRichTextEditorState()),
state = textEditorState,
voiceMessageState = VoiceMessageState.Idle,
showTextFormatting = true,
composerMode = MessageComposerMode.Normal,
enableVoiceMessages = true,
)
}, {
ATextComposer(
TextEditorState.Rich(aRichTextEditorState(initialText = "A message")),
voiceMessageState = VoiceMessageState.Idle,
showTextFormatting = true,
composerMode = MessageComposerMode.Normal,
enableVoiceMessages = true,
)
}, {
ATextComposer(
TextEditorState.Rich(
aRichTextEditorState(
initialText = "A message\nWith several lines\nTo preview larger textfields and long lines with overflow",
)
),
voiceMessageState = VoiceMessageState.Idle,
showTextFormatting = true,
composerMode = MessageComposerMode.Normal,
enableVoiceMessages = true,
)
}))
}
}
@PreviewsDayNight
@Composable
internal fun TextComposerEditPreview() = ElementPreview {
PreviewColumn(items = persistentListOf({
PreviewColumn(
items = aTextEditorStateRichList()
) { textEditorState ->
ATextComposer(
TextEditorState.Rich(aRichTextEditorState(initialText = "A message", initialFocus = true)),
state = textEditorState,
voiceMessageState = VoiceMessageState.Idle,
composerMode = aMessageComposerModeEdit(),
enableVoiceMessages = true,
)
}))
}
}
@PreviewsDayNight
@Composable
internal fun MarkdownTextComposerEditPreview() = ElementPreview {
PreviewColumn(items = persistentListOf({
PreviewColumn(
items = aTextEditorStateMarkdownList()
) { textEditorState ->
ATextComposer(
TextEditorState.Markdown(aMarkdownTextEditorState(initialText = "A message", initialFocus = true)),
state = textEditorState,
voiceMessageState = VoiceMessageState.Idle,
composerMode = aMessageComposerModeEdit(),
enableVoiceMessages = true,
)
}))
}
}
@PreviewsDayNight
@Composable
internal fun TextComposerReplyPreview(@PreviewParameter(InReplyToDetailsProvider::class) inReplyToDetails: InReplyToDetails) = ElementPreview {
ATextComposer(
state = TextEditorState.Rich(aRichTextEditorState()),
voiceMessageState = VoiceMessageState.Idle,
composerMode = aMessageComposerModeReply(
replyToDetails = inReplyToDetails,
),
enableVoiceMessages = true,
)
PreviewColumn(
items = aTextEditorStateRichList()
) { textEditorState ->
ATextComposer(
state = textEditorState,
voiceMessageState = VoiceMessageState.Idle,
composerMode = aMessageComposerModeReply(
replyToDetails = inReplyToDetails,
),
enableVoiceMessages = true,
)
}
}
@PreviewsDayNight
@Composable
internal fun TextComposerVoicePreview() = ElementPreview {
@Composable
fun VoicePreview(
voiceMessageState: VoiceMessageState
) = ATextComposer(
TextEditorState.Rich(aRichTextEditorState(initialFocus = true)),
voiceMessageState = voiceMessageState,
composerMode = MessageComposerMode.Normal,
enableVoiceMessages = true,
)
PreviewColumn(items = persistentListOf({
VoicePreview(voiceMessageState = VoiceMessageState.Recording(61.seconds, createFakeWaveform()))
}, {
VoicePreview(
voiceMessageState = VoiceMessageState.Preview(
PreviewColumn(
items = persistentListOf(
VoiceMessageState.Recording(61.seconds, createFakeWaveform()),
VoiceMessageState.Preview(
isSending = false,
isPlaying = false,
showCursor = false,
waveform = createFakeWaveform(),
time = 0.seconds,
playbackProgress = 0.0f
)
)
}, {
VoicePreview(
voiceMessageState = VoiceMessageState.Preview(
),
VoiceMessageState.Preview(
isSending = false,
isPlaying = true,
showCursor = true,
waveform = createFakeWaveform(),
time = 3.seconds,
playbackProgress = 0.2f
)
)
}, {
VoicePreview(
voiceMessageState = VoiceMessageState.Preview(
),
VoiceMessageState.Preview(
isSending = true,
isPlaying = false,
showCursor = false,
waveform = createFakeWaveform(),
time = 61.seconds,
playbackProgress = 0.0f
)
),
)
}))
) { voiceMessageState ->
ATextComposer(
state = aTextEditorStateRich(initialFocus = true),
voiceMessageState = voiceMessageState,
composerMode = MessageComposerMode.Normal,
enableVoiceMessages = true,
)
}
}
@Composable
private fun PreviewColumn(
items: ImmutableList<@Composable () -> Unit>,
private fun <T> PreviewColumn(
items: ImmutableList<T>,
view: @Composable (T) -> Unit,
) {
Column {
items.forEach { item ->
Box(
modifier = Modifier.height(IntrinsicSize.Min)
) {
item()
view(item)
}
}
}
@@ -708,17 +680,6 @@ private fun ATextComposer(
)
}
fun aRichTextEditorState(
initialText: String = "",
initialHtml: String = initialText,
initialMarkdown: String = initialText,
initialFocus: Boolean = false,
) = RichTextEditorState(
initialHtml = initialHtml,
initialMarkdown = initialMarkdown,
initialFocus = initialFocus,
)
fun aMessageComposerModeEdit(
eventOrTransactionId: EventOrTransactionId = EventId("$1234").toEventOrTransactionId(),
content: String = "Some text",

View File

@@ -26,7 +26,7 @@ import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.textcomposer.R
import io.element.android.libraries.textcomposer.TextComposerLinkDialog
import io.element.android.libraries.textcomposer.aRichTextEditorState
import io.element.android.libraries.textcomposer.model.aRichTextEditorState
import io.element.android.wysiwyg.compose.RichTextEditorState
import io.element.android.wysiwyg.view.models.InlineFormat
import io.element.android.wysiwyg.view.models.LinkAction

View File

@@ -36,6 +36,7 @@ import io.element.android.libraries.textcomposer.mentions.updateMentionStyles
import io.element.android.libraries.textcomposer.model.MarkdownTextEditorState
import io.element.android.libraries.textcomposer.model.Suggestion
import io.element.android.libraries.textcomposer.model.SuggestionType
import io.element.android.libraries.textcomposer.model.aMarkdownTextEditorState
import io.element.android.wysiwyg.compose.RichTextEditorStyle
import io.element.android.wysiwyg.compose.internal.applyStyleInCompose
@@ -184,7 +185,7 @@ internal fun MarkdownTextInputPreview() {
ElementPreview {
val style = ElementRichTextEditorStyle.composerStyle(hasFocus = true)
MarkdownTextInput(
state = aMarkdownTextEditorState(),
state = aMarkdownTextEditorState(initialText = "Hello, World!"),
subcomposing = false,
onTyping = {},
onReceiveSuggestion = {},
@@ -193,11 +194,3 @@ internal fun MarkdownTextInputPreview() {
)
}
}
internal fun aMarkdownTextEditorState(
initialText: String = "Hello, World!",
initialFocus: Boolean = true,
) = MarkdownTextEditorState(
initialText = initialText,
initialFocus = initialFocus,
)

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.textcomposer.model
import io.element.android.wysiwyg.compose.RichTextEditorState
fun aTextEditorStateMarkdown(
initialText: String? = "",
initialFocus: Boolean = false,
): TextEditorState {
return TextEditorState.Markdown(
aMarkdownTextEditorState(
initialText = initialText,
initialFocus = initialFocus,
)
)
}
fun aMarkdownTextEditorState(
initialText: String? = "",
initialFocus: Boolean = false,
): MarkdownTextEditorState {
return MarkdownTextEditorState(
initialText = initialText,
initialFocus = initialFocus,
)
}
fun aTextEditorStateRich(
initialText: String = "",
initialHtml: String = initialText,
initialMarkdown: String = initialText,
initialFocus: Boolean = false,
): TextEditorState {
return TextEditorState.Rich(
aRichTextEditorState(
initialText = initialText,
initialHtml = initialHtml,
initialMarkdown = initialMarkdown,
initialFocus = initialFocus,
)
)
}
fun aRichTextEditorState(
initialText: String = "",
initialHtml: String = initialText,
initialMarkdown: String = initialText,
initialFocus: Boolean = false,
): RichTextEditorState {
return RichTextEditorState(
initialHtml = initialHtml,
initialMarkdown = initialMarkdown,
initialFocus = initialFocus,
)
}

View File

@@ -22,13 +22,13 @@ import io.element.android.libraries.matrix.test.room.aRoomMember
import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.textcomposer.ElementRichTextEditorStyle
import io.element.android.libraries.textcomposer.components.markdown.MarkdownTextInput
import io.element.android.libraries.textcomposer.components.markdown.aMarkdownTextEditorState
import io.element.android.libraries.textcomposer.mentions.MentionSpan
import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider
import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion
import io.element.android.libraries.textcomposer.model.MarkdownTextEditorState
import io.element.android.libraries.textcomposer.model.Suggestion
import io.element.android.libraries.textcomposer.model.SuggestionType
import io.element.android.libraries.textcomposer.model.aMarkdownTextEditorState
import io.element.android.tests.testutils.EnsureCalledOnceWithParam
import io.element.android.tests.testutils.EventsRecorder
import kotlinx.coroutines.test.runTest

View File

@@ -23,9 +23,9 @@ import io.element.android.libraries.matrix.test.room.aRoomMember
import io.element.android.libraries.textcomposer.mentions.MentionSpan
import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider
import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion
import io.element.android.libraries.textcomposer.model.MarkdownTextEditorState
import io.element.android.libraries.textcomposer.model.Suggestion
import io.element.android.libraries.textcomposer.model.SuggestionType
import io.element.android.libraries.textcomposer.model.aMarkdownTextEditorState
import org.junit.Test
import org.junit.runner.RunWith
@@ -33,7 +33,7 @@ import org.junit.runner.RunWith
class MarkdownTextEditorStateTest {
@Test
fun `insertMention - room alias - getMentions return empty list`() {
val state = MarkdownTextEditorState(initialText = "Hello @", initialFocus = true)
val state = aMarkdownTextEditorState(initialText = "Hello @", initialFocus = true)
val suggestion = aRoomAliasSuggestion()
val permalinkBuilder = FakePermalinkBuilder()
val mentionSpanProvider = aMentionSpanProvider()
@@ -43,7 +43,7 @@ class MarkdownTextEditorStateTest {
@Test
fun `insertSuggestion - room alias - with member but failed PermalinkBuilder result`() {
val state = MarkdownTextEditorState(initialText = "Hello #", initialFocus = true).apply {
val state = aMarkdownTextEditorState(initialText = "Hello #", initialFocus = true).apply {
currentSuggestion = Suggestion(start = 6, end = 7, type = SuggestionType.Room, text = "")
}
val suggestion = aRoomAliasSuggestion()
@@ -55,7 +55,7 @@ class MarkdownTextEditorStateTest {
@Test
fun `insertSuggestion - room alias`() {
val state = MarkdownTextEditorState(initialText = "Hello #", initialFocus = true).apply {
val state = aMarkdownTextEditorState(initialText = "Hello #", initialFocus = true).apply {
currentSuggestion = Suggestion(start = 6, end = 7, type = SuggestionType.Room, text = "")
}
val suggestion = aRoomAliasSuggestion()
@@ -67,7 +67,7 @@ class MarkdownTextEditorStateTest {
@Test
fun `insertSuggestion - with no currentMentionSuggestion does nothing`() {
val state = MarkdownTextEditorState(initialText = "Hello @", initialFocus = true)
val state = aMarkdownTextEditorState(initialText = "Hello @", initialFocus = true)
val member = aRoomMember()
val mention = ResolvedSuggestion.Member(member)
val permalinkBuilder = FakePermalinkBuilder()
@@ -80,7 +80,7 @@ class MarkdownTextEditorStateTest {
@Test
fun `insertSuggestion - with member but failed PermalinkBuilder result`() {
val state = MarkdownTextEditorState(initialText = "Hello @", initialFocus = true).apply {
val state = aMarkdownTextEditorState(initialText = "Hello @", initialFocus = true).apply {
currentSuggestion = Suggestion(start = 6, end = 7, type = SuggestionType.Mention, text = "")
}
val member = aRoomMember()
@@ -97,7 +97,7 @@ class MarkdownTextEditorStateTest {
@Test
fun `insertSuggestion - with member`() {
val state = MarkdownTextEditorState(initialText = "Hello @", initialFocus = true).apply {
val state = aMarkdownTextEditorState(initialText = "Hello @", initialFocus = true).apply {
currentSuggestion = Suggestion(start = 6, end = 7, type = SuggestionType.Mention, text = "")
}
val member = aRoomMember()
@@ -115,7 +115,7 @@ class MarkdownTextEditorStateTest {
@Test
fun `insertSuggestion - with @room`() {
val state = MarkdownTextEditorState(initialText = "Hello @", initialFocus = true).apply {
val state = aMarkdownTextEditorState(initialText = "Hello @", initialFocus = true).apply {
currentSuggestion = Suggestion(start = 6, end = 7, type = SuggestionType.Mention, text = "")
}
val mention = ResolvedSuggestion.AtRoom
@@ -133,7 +133,7 @@ class MarkdownTextEditorStateTest {
@Test
fun `getMessageMarkdown - when there are no MentionSpans returns the same text`() {
val text = "No mentions here"
val state = MarkdownTextEditorState(initialText = text, initialFocus = true)
val state = aMarkdownTextEditorState(initialText = text, initialFocus = true)
val markdown = state.getMessageMarkdown(FakePermalinkBuilder())
@@ -147,7 +147,7 @@ class MarkdownTextEditorStateTest {
permalinkForUserLambda = { Result.success("https://matrix.to/#/$it") },
permalinkForRoomAliasLambda = { Result.success("https://matrix.to/#/$it") },
)
val state = MarkdownTextEditorState(initialText = text, initialFocus = true)
val state = aMarkdownTextEditorState(initialText = text, initialFocus = true)
state.text.update(aMarkdownTextWithMentions(), needsDisplaying = false)
val markdown = state.getMessageMarkdown(permalinkBuilder = permalinkBuilder)
@@ -160,14 +160,14 @@ class MarkdownTextEditorStateTest {
@Test
fun `getMentions - when there are no MentionSpans returns empty list of mentions`() {
val state = MarkdownTextEditorState(initialText = "Hello @", initialFocus = true)
val state = aMarkdownTextEditorState(initialText = "Hello @", initialFocus = true)
assertThat(state.getMentions()).isEmpty()
}
@Test
fun `getMentions - when there are MentionSpans returns a list of mentions`() {
val state = MarkdownTextEditorState(initialText = "Hello @", initialFocus = true)
val state = aMarkdownTextEditorState(initialText = "Hello @", initialFocus = true)
state.text.update(aMarkdownTextWithMentions(), needsDisplaying = false)
val mentions = state.getMentions()