Skip to content

Commit

Permalink
Add unit tests for test-case scenarios
Browse files Browse the repository at this point in the history
  • Loading branch information
cp-megh-l committed Mar 5, 2024
1 parent 557a0d7 commit ee072da
Show file tree
Hide file tree
Showing 8 changed files with 484 additions and 17 deletions.
29 changes: 22 additions & 7 deletions editor/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}

Expand All @@ -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) ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class QuillTextManager(quillSpan: QuillSpan) {
get() = editable.toString()

private var selection = TextRange(0, 0)
private val currentStyles = mutableStateListOf<TextSpanStyle>()
internal val currentStyles = mutableStateListOf<TextSpanStyle>()
private var rawText: String = editableText

internal fun setEditable(editable: Editable) {
Expand Down Expand Up @@ -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()
Expand All @@ -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(
Expand All @@ -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<QuillTextSpan>()
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)
Expand Down
38 changes: 38 additions & 0 deletions editor/src/test/assets/android-quill-sample.json
Original file line number Diff line number Diff line change
@@ -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
}
}
]
}
26 changes: 26 additions & 0 deletions editor/src/test/java/com/canopas/editor/MainCoroutineRule.kt
Original file line number Diff line number Diff line change
@@ -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()
}
}
Original file line number Diff line number Diff line change
@@ -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<QuillSpan>() {}.type)
}

override fun decode(editorValue: QuillSpan): String {
return gson.toJson(editorValue)
}
}
Original file line number Diff line number Diff line change
@@ -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<Span>, JsonDeserializer<Span> {
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<String>(insert, String::class.java),
attributes = context?.deserialize<Attributes>(attributes, Attributes::class.java)
)
} catch (e: Exception) {
throw JsonParseException("Invalid JSON")
}
}
}
Loading

0 comments on commit ee072da

Please sign in to comment.