diff --git a/editor/build.gradle b/editor/build.gradle index 15878d3..09166a3 100644 --- a/editor/build.gradle +++ b/editor/build.gradle @@ -54,31 +54,46 @@ android { withSourcesJar() } } + testOptions { + unitTests { + includeAndroidResources = true + } + unitTests.all { + jvmArgs '-noverify' + } + } } dependencies { implementation 'androidx.core:core-ktx:1.12.0' implementation 'androidx.appcompat:appcompat:1.6.1' - implementation 'com.google.android.material:material:1.10.0' + implementation 'com.google.android.material:material:1.11.0' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.2' - implementation 'androidx.activity:activity-compose:1.8.0' + implementation 'androidx.activity:activity-compose:1.8.2' implementation platform('androidx.compose:compose-bom:2023.10.00') implementation 'androidx.compose.ui:ui' implementation 'androidx.compose.ui:ui-graphics' implementation 'androidx.compose.ui:ui-tooling-preview' implementation 'androidx.compose.material3:material3' - testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.1.5' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + implementation 'androidx.test:core-ktx:1.5.0' + implementation 'androidx.test.ext:junit-ktx:1.1.5' + testImplementation("junit:junit:4.13.2") + testImplementation "org.robolectric:robolectric:4.11.1" + testImplementation("org.junit.jupiter:junit-jupiter:5.8.1") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") + androidTestImplementation("androidx.compose.ui:ui-test-junit4") androidTestImplementation platform('androidx.compose:compose-bom:2023.10.00') - androidTestImplementation 'androidx.compose.ui:ui-test-junit4' debugImplementation 'androidx.compose.ui:ui-tooling' debugImplementation 'androidx.compose.ui:ui-test-manifest' implementation("io.coil-kt:coil-compose:2.4.0") implementation 'androidx.media3:media3-ui:1.1.0' implementation 'androidx.media3:media3-exoplayer:1.1.0' + testImplementation ("org.mockito.kotlin:mockito-kotlin:4.0.0") + testImplementation ("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4") + testImplementation ("org.mockito:mockito-inline:2.13.0") - + implementation 'com.google.code.gson:gson:2.10.1' } \ No newline at end of file diff --git a/editor/src/main/java/com/canopas/editor/ui/data/QuillEditorState.kt b/editor/src/main/java/com/canopas/editor/ui/data/QuillEditorState.kt index cf7a7d2..be3f078 100644 --- a/editor/src/main/java/com/canopas/editor/ui/data/QuillEditorState.kt +++ b/editor/src/main/java/com/canopas/editor/ui/data/QuillEditorState.kt @@ -22,7 +22,7 @@ class QuillEditorState internal constructor( manager = QuillTextManager(getQuillSpan()) } - private fun getQuillSpan(): QuillSpan { + fun getQuillSpan(): QuillSpan { return if (input.isNotEmpty()) adapter.encode(input) else QuillSpan(emptyList()) } @@ -44,7 +44,7 @@ class QuillEditorState internal constructor( manager.setStyle(style) } - private fun getRichText() : QuillSpan { + internal fun getRichText() : QuillSpan { val quillGroupedSpans = manager.quillTextSpans.groupBy { it.from to it.to } val quillTextSpans = quillGroupedSpans.map { (fromTo, spanList) -> diff --git a/editor/src/main/java/com/canopas/editor/ui/data/QuillTextManager.kt b/editor/src/main/java/com/canopas/editor/ui/data/QuillTextManager.kt index d1e0b32..be0cd94 100644 --- a/editor/src/main/java/com/canopas/editor/ui/data/QuillTextManager.kt +++ b/editor/src/main/java/com/canopas/editor/ui/data/QuillTextManager.kt @@ -55,7 +55,7 @@ class QuillTextManager(quillSpan: QuillSpan) { get() = editable.toString() private var selection = TextRange(0, 0) - private val currentStyles = mutableStateListOf() + internal val currentStyles = mutableStateListOf() private var rawText: String = editableText internal fun setEditable(editable: Editable) { @@ -364,12 +364,19 @@ class QuillTextManager(quillSpan: QuillSpan) { this.rawText = newText.toString() } - private fun handleAddingCharacters(newValue: Editable) { + internal fun handleAddingCharacters(newValue: Editable) { val typedCharsCount = newValue.length - rawText.length val startTypeIndex = selection.min - typedCharsCount if (newValue.getOrNull(startTypeIndex) == '\n' && currentStyles.any { it.isHeaderStyle() }) { currentStyles.clear() + quillTextSpans.find { it.from <= startTypeIndex && it.to >= startTypeIndex } + ?.let { span -> + val index = quillTextSpans.indexOf(span) + val styles = span.style.filterNot { it.isHeaderStyle() } + val updatedSpan = span.copy(style = styles) + quillTextSpans[index] = updatedSpan + } } val selectedStyles = currentStyles.distinct() @@ -388,7 +395,7 @@ class QuillTextManager(quillSpan: QuillSpan) { when { span.style == selectedStyles -> { if (isBulletStyle && newValue.getOrNull(startTypeIndex) == '\n') { - if (newValue.getOrNull(startTypeIndex - 1) != '\n') { + if (newValue.getOrNull(startTypeIndex - 1) != '\n' && startTypeIndex == to) { quillTextSpans.add( index + 1, span.copy( @@ -406,11 +413,30 @@ class QuillTextManager(quillSpan: QuillSpan) { ) ) } else { - val updatedSpan = span.copy(to = to + typedCharsCount, - style = selectedStyles.filterNot { it == TextSpanStyle.BulletStyle } - ) - quillTextSpans[index - 1] = updatedSpan - quillTextSpans[index] = updatedSpan + if (startTypeIndex in (from + 1) until to) { + val newSpans = mutableListOf() + newSpans.add(span.copy(to = startTypeIndex - 1, style = styles)) + newSpans.add( + span.copy( + from = startTypeIndex, + to = startTypeIndex + typedCharsCount - 1, + style = selectedStyles + ) + ) + newSpans.add( + span.copy( + from = startTypeIndex + typedCharsCount, + to = to + typedCharsCount, + style = styles + ) + ) + quillTextSpans.removeAt(index) + quillTextSpans.addAll(index, newSpans) + } else { + val updatedSpan = span.copy(to = to + typedCharsCount, style = selectedStyles) + quillTextSpans[index] = updatedSpan + quillTextSpans.add(index + 1, updatedSpan) + } } } else { quillTextSpans[index] = span.copy(to = to + typedCharsCount, style = styles) diff --git a/editor/src/test/assets/android-quill-sample.json b/editor/src/test/assets/android-quill-sample.json new file mode 100644 index 0000000..b9c8b43 --- /dev/null +++ b/editor/src/test/assets/android-quill-sample.json @@ -0,0 +1,38 @@ +{ + "spans": [ + { + "insert": "RichEditor", + "attributes": { + "bold": true, + "header": 1 + } + }, + { + "insert": "\nf", + "attributes": { + "bold": true + } + }, + { + "insert": "o", + "attributes": {} + }, + { + "insert": "r ", + "attributes": { + "bold": true + } + }, + { + "insert": "\nAndroid ", + "attributes": {} + }, + { + "insert": "WYSIWYG ", + "attributes": { + "bold": true, + "italic": true + } + } + ] +} \ No newline at end of file diff --git a/editor/src/test/java/com/canopas/editor/MainCoroutineRule.kt b/editor/src/test/java/com/canopas/editor/MainCoroutineRule.kt new file mode 100644 index 0000000..c4de81e --- /dev/null +++ b/editor/src/test/java/com/canopas/editor/MainCoroutineRule.kt @@ -0,0 +1,26 @@ +package com.canopas.editor + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestDispatcher +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.setMain +import org.junit.rules.TestWatcher +import org.junit.runner.Description + +class MainCoroutineRule @OptIn(ExperimentalCoroutinesApi::class) constructor( + private val dispatcher: TestDispatcher = UnconfinedTestDispatcher() +) : TestWatcher() { + @OptIn(ExperimentalCoroutinesApi::class) + override fun starting(description: Description) { + super.starting(description) + Dispatchers.setMain(dispatcher) + } + + @OptIn(ExperimentalCoroutinesApi::class) + override fun finished(description: Description) { + super.finished(description) + Dispatchers.resetMain() + } +} diff --git a/editor/src/test/java/com/canopas/editor/jsonparser/QuillJsonEditorParser.kt b/editor/src/test/java/com/canopas/editor/jsonparser/QuillJsonEditorParser.kt new file mode 100644 index 0000000..1b04eea --- /dev/null +++ b/editor/src/test/java/com/canopas/editor/jsonparser/QuillJsonEditorParser.kt @@ -0,0 +1,23 @@ +package com.canopas.editor.jsonparser + +import com.canopas.editor.ui.model.QuillSpan +import com.canopas.editor.ui.model.Span +import com.canopas.editor.ui.parser.QuillEditorAdapter +import com.google.common.reflect.TypeToken +import com.google.gson.Gson +import com.google.gson.GsonBuilder + +class QuillJsonEditorParser : QuillEditorAdapter { + + private val gson: Gson = GsonBuilder() + .registerTypeAdapter(Span::class.java, QuillRichTextStateAdapter()) + .create() + + override fun encode(input: String): QuillSpan { + return gson.fromJson(input, object : TypeToken() {}.type) + } + + override fun decode(editorValue: QuillSpan): String { + return gson.toJson(editorValue) + } +} diff --git a/editor/src/test/java/com/canopas/editor/jsonparser/QuillRichTextStateAdapter.kt b/editor/src/test/java/com/canopas/editor/jsonparser/QuillRichTextStateAdapter.kt new file mode 100644 index 0000000..46c2d17 --- /dev/null +++ b/editor/src/test/java/com/canopas/editor/jsonparser/QuillRichTextStateAdapter.kt @@ -0,0 +1,43 @@ +package com.canopas.editor.jsonparser + +import com.canopas.editor.ui.model.Attributes +import com.canopas.editor.ui.model.Span +import com.google.gson.JsonDeserializationContext +import com.google.gson.JsonDeserializer +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import com.google.gson.JsonParseException +import com.google.gson.JsonSerializationContext +import com.google.gson.JsonSerializer +import java.lang.reflect.Type + +class QuillRichTextStateAdapter : JsonSerializer, JsonDeserializer { + override fun serialize( + src: Span?, + typeOfSrc: Type?, + context: JsonSerializationContext? + ): JsonElement { + val jsonObject = JsonObject() + jsonObject.add("insert", context?.serialize(src?.insert)) + jsonObject.add("attributes", context?.serialize(src?.attributes)) + return jsonObject + } + + override fun deserialize( + json: JsonElement?, + typeOfT: Type?, + context: JsonDeserializationContext? + ): Span { + try { + val jsonObject = json?.asJsonObject ?: throw JsonParseException("Invalid JSON") + val insert = jsonObject.get("insert") + val attributes = jsonObject.get("attributes") + return Span( + insert = context?.deserialize(insert, String::class.java), + attributes = context?.deserialize(attributes, Attributes::class.java) + ) + } catch (e: Exception) { + throw JsonParseException("Invalid JSON") + } + } +} diff --git a/editor/src/test/java/com/canopas/editor/quilltextmanager/QuillTextManagerTest.kt b/editor/src/test/java/com/canopas/editor/quilltextmanager/QuillTextManagerTest.kt new file mode 100644 index 0000000..d6f5aa4 --- /dev/null +++ b/editor/src/test/java/com/canopas/editor/quilltextmanager/QuillTextManagerTest.kt @@ -0,0 +1,296 @@ +package com.canopas.editor.quilltextmanager + +import android.content.Context +import android.text.Editable +import androidx.compose.ui.text.TextRange +import androidx.test.core.app.ApplicationProvider +import com.canopas.editor.MainCoroutineRule +import com.canopas.editor.jsonparser.QuillJsonEditorParser +import com.canopas.editor.ui.data.QuillEditorState +import com.canopas.editor.ui.model.QuillTextSpan +import com.canopas.editor.ui.utils.TextSpanStyle +import kotlinx.coroutines.ExperimentalCoroutinesApi +import org.junit.Assert +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +@ExperimentalCoroutinesApi +class QuillTextManagerTest { + + @get:Rule + var mainCoroutineRule = MainCoroutineRule() + private lateinit var quillEditorState: QuillEditorState + private val context = ApplicationProvider.getApplicationContext() + private lateinit var editableInstance: Editable.Factory + private var sampleSpansListSize = 0 + + @Before + fun setup() { + val input = + context.assets.open("android-quill-sample.json").bufferedReader().use { it.readText() } + editableInstance = Editable.Factory.getInstance() + quillEditorState = QuillEditorState.Builder() + .setInput(input) + .adapter(QuillJsonEditorParser()) + .build() + sampleSpansListSize = quillEditorState.manager.quillTextSpans.size + } + + @Test + fun `test getQuillSpan`() { + val quillSpan = quillEditorState.getQuillSpan() + Assert.assertNotNull(quillSpan) + } + + @Test + fun `test output`() { + val output = quillEditorState.output() + Assert.assertNotNull(output) + } + + @Test + fun `test reset`() { + quillEditorState.reset() + Assert.assertTrue(quillEditorState.manager.quillTextSpans.isEmpty()) + } + + @Test + fun `test hasStyle`() { + val hasStyle = quillEditorState.hasStyle(TextSpanStyle.BoldStyle) + Assert.assertFalse(hasStyle) + } + + @Test + fun `test toggleStyle`() { + quillEditorState.toggleStyle(TextSpanStyle.BoldStyle) + val hasStyle = quillEditorState.hasStyle(TextSpanStyle.BoldStyle) + Assert.assertTrue(hasStyle) + } + + @Test + fun `test updateStyle`() { + quillEditorState.updateStyle(TextSpanStyle.BoldStyle) + val hasStyle = quillEditorState.hasStyle(TextSpanStyle.BoldStyle) + Assert.assertTrue(hasStyle) + } + + @Test + fun `scenario 1 - test span added successfully when text is selected and style is added`() { + quillEditorState.manager.adjustSelection(TextRange(0, 5)) + val spansSize = quillEditorState.manager.quillTextSpans.size + quillEditorState.toggleStyle(TextSpanStyle.ItalicStyle) + Assert.assertEquals(1, quillEditorState.manager.quillTextSpans.size - spansSize) + } + + @Test + fun `scenario 2 - test span removed successfully when text is selected and style is removed`() { + quillEditorState.manager.adjustSelection(TextRange(0, 5)) + val spansSize = quillEditorState.manager.quillTextSpans.size + quillEditorState.toggleStyle(TextSpanStyle.ItalicStyle) + // Verify span added + Assert.assertEquals(1, quillEditorState.manager.quillTextSpans.size - spansSize) + val newSpanSize = quillEditorState.manager.quillTextSpans.size + quillEditorState.toggleStyle(TextSpanStyle.ItalicStyle) + // Verify span removed + Assert.assertFalse(newSpanSize == spansSize) + Assert.assertEquals(0, quillEditorState.manager.quillTextSpans.size - newSpanSize) + } + + @Test + fun `scenario 3 - test span created successfully when user selects style and starts typing`() { + quillEditorState.manager.adjustSelection(TextRange(2, 2)) + val spansSize = quillEditorState.manager.quillTextSpans.size + quillEditorState.toggleStyle(TextSpanStyle.ItalicStyle) + quillEditorState.manager.handleAddingCharacters(editableInstance.newEditable("t")) + Assert.assertEquals(1, quillEditorState.manager.quillTextSpans.size - spansSize) + } + + @Test + fun `scenario 4 - test other span created successfully when user deselects style and starts typing`() { + quillEditorState.manager.adjustSelection(TextRange(2, 2)) + val spansSize = quillEditorState.manager.quillTextSpans.size + quillEditorState.toggleStyle(TextSpanStyle.ItalicStyle) + quillEditorState.manager.handleAddingCharacters(editableInstance.newEditable("t")) + // Verify span added + Assert.assertEquals(1, quillEditorState.manager.quillTextSpans.size - spansSize) + val newSpanSize = quillEditorState.manager.quillTextSpans.size + quillEditorState.toggleStyle(TextSpanStyle.ItalicStyle) + quillEditorState.manager.handleAddingCharacters(editableInstance.newEditable("t")) + // Verify other span added + Assert.assertEquals(1, quillEditorState.manager.quillTextSpans.size - newSpanSize) + // Verify 2 new spans added compared to android-quill-sample.json as original has 3 spans + Assert.assertEquals(sampleSpansListSize + 2, quillEditorState.manager.quillTextSpans.size) + } + + @Test + fun `scenario 5 - extend current span if user starts typing in middle of the word which have style on it`() { + val originalInsert = quillEditorState.manager.editableText + val newInsert = originalInsert.replace("RichEditor", "RichTEditor") + quillEditorState.manager.adjustSelection(TextRange(4, 4)) + quillEditorState.manager.setEditable(editableInstance.newEditable(newInsert)) + val to = quillEditorState.manager.quillTextSpans[0].to + val expectedSpan = + QuillTextSpan(0, 9, listOf(TextSpanStyle.H1Style, TextSpanStyle.BoldStyle)) + Assert.assertEquals(10, to + 1) + Assert.assertEquals( + "RichTEditor", + quillEditorState.manager.editableText.substringBefore("\n") + ) + Assert.assertEquals(expectedSpan, quillEditorState.manager.quillTextSpans[0]) + } + + @Test + fun `scenario 6 - extend current span if user starts typing just after styles text`() { + val originalInsert = quillEditorState.manager.editableText + val newInsert = originalInsert.replace("RichEditor", "RichEditorT") + quillEditorState.manager.adjustSelection(TextRange(9, 9)) + quillEditorState.manager.setEditable(editableInstance.newEditable(newInsert)) + val to = quillEditorState.manager.quillTextSpans[0].to + Assert.assertEquals(10, to + 1) + Assert.assertEquals( + "RichEditorT", + quillEditorState.manager.editableText.substringBefore("\n") + ) + } + + @Test + fun `scenario 7 - test span is moved by typed character if user starts typing just before styles text`() { + val originalInsert = quillEditorState.manager.editableText + val newInsert = originalInsert.replace("RichEditor", "TRichEditor") + quillEditorState.manager.adjustSelection(TextRange(0, 0)) + quillEditorState.manager.setEditable(editableInstance.newEditable(newInsert)) + val to = quillEditorState.manager.quillTextSpans[0].to + Assert.assertEquals(10, to + 1) + Assert.assertEquals( + "TRichEditor", + quillEditorState.manager.editableText.substringBefore("\n") + ) + } + + @Test + fun `scenario 8 - add span with selected style if user starts typing at initial position`() { + quillEditorState.toggleStyle(TextSpanStyle.ItalicStyle) + quillEditorState.manager.onTextFieldValueChange( + editableInstance.newEditable("t"), + TextRange(0, 0) + ) + Assert.assertTrue(quillEditorState.manager.quillTextSpans[0].style.contains(TextSpanStyle.ItalicStyle)) + } + + @Test + fun `scenario 9 - break spans into 2 when user removes style from middle of word by selection text`() { + val currentSpansSize = quillEditorState.manager.quillTextSpans.size + quillEditorState.manager.adjustSelection(TextRange(4, 6)) + quillEditorState.toggleStyle(TextSpanStyle.BoldStyle) + Assert.assertEquals(currentSpansSize + 2, quillEditorState.manager.quillTextSpans.size) + } + + @Test + fun `scenario 10 - break spans into 2 when user deselects style and starts typing in middle of any word which have style`() { + val currentSpansSize = quillEditorState.manager.quillTextSpans.size + quillEditorState.manager.adjustSelection(TextRange(4, 4)) + quillEditorState.toggleStyle(TextSpanStyle.BoldStyle) + val originalInsert = quillEditorState.manager.editableText + val newInsert = originalInsert.replace("RichEditor", "RichTEditor") + quillEditorState.manager.handleAddingCharacters(editableInstance.newEditable(newInsert)) + Assert.assertEquals(currentSpansSize + 2, quillEditorState.manager.quillTextSpans.size) + } + + @Test + fun `scenario 11 - update span length when any character is removed from it`() { + val previousToIndex = quillEditorState.manager.quillTextSpans[0].to + quillEditorState.manager.adjustSelection(TextRange(4, 4)) + val oldInsert = quillEditorState.manager.editableText + val newInsert = oldInsert.replace("RichEditor", "RichEdtor") + quillEditorState.manager.onTextFieldValueChange( + editableInstance.newEditable(newInsert), + TextRange(4, 4) + ) + val newToIndex = quillEditorState.manager.quillTextSpans[0].to + Assert.assertEquals(previousToIndex - 1, newToIndex) + } + + @Test + fun `scenario 12 - Move span by n position forward when user adds n character before styled text anywhere before that text`() { + val previousToIndex = quillEditorState.manager.quillTextSpans[0].to + quillEditorState.manager.adjustSelection(TextRange(0, 0)) + val oldInsert = quillEditorState.manager.editableText + val newInsert = oldInsert.replace("RichEditor", "TextRichEditor") + quillEditorState.manager.onTextFieldValueChange( + editableInstance.newEditable(newInsert), + TextRange(0, 0) + ) + val newToIndex = quillEditorState.manager.quillTextSpans[0].to + Assert.assertEquals(previousToIndex + 4, newToIndex) + } + + @Test + fun `scenario 13 - Move span by n position backward when user removes n character before styled text anywhere before that text`() { + val previousToIndex = quillEditorState.manager.quillTextSpans[1].to + quillEditorState.manager.adjustSelection(TextRange(0, 0)) + val oldInsert = quillEditorState.manager.editableText + val newInsert = oldInsert.replace("RichEditor", "Editor") + quillEditorState.manager.onTextFieldValueChange( + editableInstance.newEditable(newInsert), + TextRange(0, 0) + ) + val newToIndex = quillEditorState.manager.quillTextSpans[1].to + // Verify if 2nd span from list is moved by 4 positions + Assert.assertEquals(previousToIndex - 4, newToIndex) + } + + @Test + fun `scenario 14 - remove header style when user add new line`() { + quillEditorState.manager.adjustSelection(TextRange(0, 0)) + quillEditorState.toggleStyle(TextSpanStyle.H1Style) + quillEditorState.manager.onTextFieldValueChange( + editableInstance.newEditable("\n"), + TextRange(0, 0) + ) + // Verify if header style is removed + Assert.assertFalse(quillEditorState.manager.quillTextSpans[0].style.contains(TextSpanStyle.H1Style)) + // Verify that other styles are not removed + Assert.assertTrue(quillEditorState.manager.quillTextSpans[0].style.contains(TextSpanStyle.BoldStyle)) + } + + @Test + fun `scenario 15 - merge spans if style applied to selected text is equivalent to previous and next span`() { + println("Spans Text:${quillEditorState.manager.editableText.substring(12, 13)}") + val previousSpanSize = quillEditorState.getRichText().spans.size + quillEditorState.manager.adjustSelection(TextRange(12, 13)) + quillEditorState.updateStyle(TextSpanStyle.BoldStyle) + val newSize = quillEditorState.getRichText().spans.size + // Verify that 3 spans are merged into 1 + Assert.assertEquals(previousSpanSize - 2, newSize) + } + + @Test + fun `scenario 16 - if new line is entered in between text then remove header if available and split span into two`() { + quillEditorState.manager.adjustSelection(TextRange(4, 4)) + val previousSpanSize = quillEditorState.manager.quillTextSpans.size + val oldInsert = quillEditorState.manager.editableText + val newInsert = oldInsert.replace("RichEditor", "Rich\nEditor") + quillEditorState.manager.onTextFieldValueChange( + editableInstance.newEditable(newInsert), + TextRange(0, 0) + ) + Assert.assertEquals(previousSpanSize + 1, quillEditorState.manager.quillTextSpans.size) + Assert.assertTrue(quillEditorState.manager.quillTextSpans[0].style.contains(TextSpanStyle.BoldStyle)) + Assert.assertTrue(quillEditorState.manager.quillTextSpans[0].style.contains(TextSpanStyle.H1Style)) + Assert.assertFalse(quillEditorState.manager.quillTextSpans[1].style.contains(TextSpanStyle.H1Style)) + Assert.assertTrue(quillEditorState.manager.quillTextSpans[1].style.contains(TextSpanStyle.BoldStyle)) + } + + @Test + fun `scenario 17 - test remove all styles if selection range is 0,0`() { + quillEditorState.manager.onTextFieldValueChange( + editableInstance.newEditable("t"), + TextRange(0, 0) + ) + Assert.assertEquals(0, quillEditorState.manager.currentStyles.size) + } +} \ No newline at end of file