Skip to content

Commit ee072da

Browse files
committed
Add unit tests for test-case scenarios
1 parent 557a0d7 commit ee072da

File tree

8 files changed

+484
-17
lines changed

8 files changed

+484
-17
lines changed

editor/build.gradle

+22-7
Original file line numberDiff line numberDiff line change
@@ -54,31 +54,46 @@ android {
5454
withSourcesJar()
5555
}
5656
}
57+
testOptions {
58+
unitTests {
59+
includeAndroidResources = true
60+
}
61+
unitTests.all {
62+
jvmArgs '-noverify'
63+
}
64+
}
5765
}
5866

5967
dependencies {
6068

6169
implementation 'androidx.core:core-ktx:1.12.0'
6270
implementation 'androidx.appcompat:appcompat:1.6.1'
63-
implementation 'com.google.android.material:material:1.10.0'
71+
implementation 'com.google.android.material:material:1.11.0'
6472
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.2'
65-
implementation 'androidx.activity:activity-compose:1.8.0'
73+
implementation 'androidx.activity:activity-compose:1.8.2'
6674
implementation platform('androidx.compose:compose-bom:2023.10.00')
6775
implementation 'androidx.compose.ui:ui'
6876
implementation 'androidx.compose.ui:ui-graphics'
6977
implementation 'androidx.compose.ui:ui-tooling-preview'
7078
implementation 'androidx.compose.material3:material3'
71-
testImplementation 'junit:junit:4.13.2'
72-
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
73-
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
79+
implementation 'androidx.test:core-ktx:1.5.0'
80+
implementation 'androidx.test.ext:junit-ktx:1.1.5'
81+
testImplementation("junit:junit:4.13.2")
82+
testImplementation "org.robolectric:robolectric:4.11.1"
83+
testImplementation("org.junit.jupiter:junit-jupiter:5.8.1")
84+
androidTestImplementation("androidx.test.ext:junit:1.1.5")
85+
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
86+
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
7487
androidTestImplementation platform('androidx.compose:compose-bom:2023.10.00')
75-
androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
7688
debugImplementation 'androidx.compose.ui:ui-tooling'
7789
debugImplementation 'androidx.compose.ui:ui-test-manifest'
7890

7991
implementation("io.coil-kt:coil-compose:2.4.0")
8092
implementation 'androidx.media3:media3-ui:1.1.0'
8193
implementation 'androidx.media3:media3-exoplayer:1.1.0'
94+
testImplementation ("org.mockito.kotlin:mockito-kotlin:4.0.0")
95+
testImplementation ("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4")
96+
testImplementation ("org.mockito:mockito-inline:2.13.0")
8297

83-
98+
implementation 'com.google.code.gson:gson:2.10.1'
8499
}

editor/src/main/java/com/canopas/editor/ui/data/QuillEditorState.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class QuillEditorState internal constructor(
2222
manager = QuillTextManager(getQuillSpan())
2323
}
2424

25-
private fun getQuillSpan(): QuillSpan {
25+
fun getQuillSpan(): QuillSpan {
2626
return if (input.isNotEmpty()) adapter.encode(input) else QuillSpan(emptyList())
2727
}
2828

@@ -44,7 +44,7 @@ class QuillEditorState internal constructor(
4444
manager.setStyle(style)
4545
}
4646

47-
private fun getRichText() : QuillSpan {
47+
internal fun getRichText() : QuillSpan {
4848
val quillGroupedSpans = manager.quillTextSpans.groupBy { it.from to it.to }
4949
val quillTextSpans =
5050
quillGroupedSpans.map { (fromTo, spanList) ->

editor/src/main/java/com/canopas/editor/ui/data/QuillTextManager.kt

+34-8
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ class QuillTextManager(quillSpan: QuillSpan) {
5555
get() = editable.toString()
5656

5757
private var selection = TextRange(0, 0)
58-
private val currentStyles = mutableStateListOf<TextSpanStyle>()
58+
internal val currentStyles = mutableStateListOf<TextSpanStyle>()
5959
private var rawText: String = editableText
6060

6161
internal fun setEditable(editable: Editable) {
@@ -364,12 +364,19 @@ class QuillTextManager(quillSpan: QuillSpan) {
364364
this.rawText = newText.toString()
365365
}
366366

367-
private fun handleAddingCharacters(newValue: Editable) {
367+
internal fun handleAddingCharacters(newValue: Editable) {
368368
val typedCharsCount = newValue.length - rawText.length
369369
val startTypeIndex = selection.min - typedCharsCount
370370

371371
if (newValue.getOrNull(startTypeIndex) == '\n' && currentStyles.any { it.isHeaderStyle() }) {
372372
currentStyles.clear()
373+
quillTextSpans.find { it.from <= startTypeIndex && it.to >= startTypeIndex }
374+
?.let { span ->
375+
val index = quillTextSpans.indexOf(span)
376+
val styles = span.style.filterNot { it.isHeaderStyle() }
377+
val updatedSpan = span.copy(style = styles)
378+
quillTextSpans[index] = updatedSpan
379+
}
373380
}
374381

375382
val selectedStyles = currentStyles.distinct()
@@ -388,7 +395,7 @@ class QuillTextManager(quillSpan: QuillSpan) {
388395
when {
389396
span.style == selectedStyles -> {
390397
if (isBulletStyle && newValue.getOrNull(startTypeIndex) == '\n') {
391-
if (newValue.getOrNull(startTypeIndex - 1) != '\n') {
398+
if (newValue.getOrNull(startTypeIndex - 1) != '\n' && startTypeIndex == to) {
392399
quillTextSpans.add(
393400
index + 1,
394401
span.copy(
@@ -406,11 +413,30 @@ class QuillTextManager(quillSpan: QuillSpan) {
406413
)
407414
)
408415
} else {
409-
val updatedSpan = span.copy(to = to + typedCharsCount,
410-
style = selectedStyles.filterNot { it == TextSpanStyle.BulletStyle }
411-
)
412-
quillTextSpans[index - 1] = updatedSpan
413-
quillTextSpans[index] = updatedSpan
416+
if (startTypeIndex in (from + 1) until to) {
417+
val newSpans = mutableListOf<QuillTextSpan>()
418+
newSpans.add(span.copy(to = startTypeIndex - 1, style = styles))
419+
newSpans.add(
420+
span.copy(
421+
from = startTypeIndex,
422+
to = startTypeIndex + typedCharsCount - 1,
423+
style = selectedStyles
424+
)
425+
)
426+
newSpans.add(
427+
span.copy(
428+
from = startTypeIndex + typedCharsCount,
429+
to = to + typedCharsCount,
430+
style = styles
431+
)
432+
)
433+
quillTextSpans.removeAt(index)
434+
quillTextSpans.addAll(index, newSpans)
435+
} else {
436+
val updatedSpan = span.copy(to = to + typedCharsCount, style = selectedStyles)
437+
quillTextSpans[index] = updatedSpan
438+
quillTextSpans.add(index + 1, updatedSpan)
439+
}
414440
}
415441
} else {
416442
quillTextSpans[index] = span.copy(to = to + typedCharsCount, style = styles)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"spans": [
3+
{
4+
"insert": "RichEditor",
5+
"attributes": {
6+
"bold": true,
7+
"header": 1
8+
}
9+
},
10+
{
11+
"insert": "\nf",
12+
"attributes": {
13+
"bold": true
14+
}
15+
},
16+
{
17+
"insert": "o",
18+
"attributes": {}
19+
},
20+
{
21+
"insert": "r ",
22+
"attributes": {
23+
"bold": true
24+
}
25+
},
26+
{
27+
"insert": "\nAndroid ",
28+
"attributes": {}
29+
},
30+
{
31+
"insert": "WYSIWYG ",
32+
"attributes": {
33+
"bold": true,
34+
"italic": true
35+
}
36+
}
37+
]
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.canopas.editor
2+
3+
import kotlinx.coroutines.Dispatchers
4+
import kotlinx.coroutines.ExperimentalCoroutinesApi
5+
import kotlinx.coroutines.test.TestDispatcher
6+
import kotlinx.coroutines.test.UnconfinedTestDispatcher
7+
import kotlinx.coroutines.test.resetMain
8+
import kotlinx.coroutines.test.setMain
9+
import org.junit.rules.TestWatcher
10+
import org.junit.runner.Description
11+
12+
class MainCoroutineRule @OptIn(ExperimentalCoroutinesApi::class) constructor(
13+
private val dispatcher: TestDispatcher = UnconfinedTestDispatcher()
14+
) : TestWatcher() {
15+
@OptIn(ExperimentalCoroutinesApi::class)
16+
override fun starting(description: Description) {
17+
super.starting(description)
18+
Dispatchers.setMain(dispatcher)
19+
}
20+
21+
@OptIn(ExperimentalCoroutinesApi::class)
22+
override fun finished(description: Description) {
23+
super.finished(description)
24+
Dispatchers.resetMain()
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.canopas.editor.jsonparser
2+
3+
import com.canopas.editor.ui.model.QuillSpan
4+
import com.canopas.editor.ui.model.Span
5+
import com.canopas.editor.ui.parser.QuillEditorAdapter
6+
import com.google.common.reflect.TypeToken
7+
import com.google.gson.Gson
8+
import com.google.gson.GsonBuilder
9+
10+
class QuillJsonEditorParser : QuillEditorAdapter {
11+
12+
private val gson: Gson = GsonBuilder()
13+
.registerTypeAdapter(Span::class.java, QuillRichTextStateAdapter())
14+
.create()
15+
16+
override fun encode(input: String): QuillSpan {
17+
return gson.fromJson(input, object : TypeToken<QuillSpan>() {}.type)
18+
}
19+
20+
override fun decode(editorValue: QuillSpan): String {
21+
return gson.toJson(editorValue)
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.canopas.editor.jsonparser
2+
3+
import com.canopas.editor.ui.model.Attributes
4+
import com.canopas.editor.ui.model.Span
5+
import com.google.gson.JsonDeserializationContext
6+
import com.google.gson.JsonDeserializer
7+
import com.google.gson.JsonElement
8+
import com.google.gson.JsonObject
9+
import com.google.gson.JsonParseException
10+
import com.google.gson.JsonSerializationContext
11+
import com.google.gson.JsonSerializer
12+
import java.lang.reflect.Type
13+
14+
class QuillRichTextStateAdapter : JsonSerializer<Span>, JsonDeserializer<Span> {
15+
override fun serialize(
16+
src: Span?,
17+
typeOfSrc: Type?,
18+
context: JsonSerializationContext?
19+
): JsonElement {
20+
val jsonObject = JsonObject()
21+
jsonObject.add("insert", context?.serialize(src?.insert))
22+
jsonObject.add("attributes", context?.serialize(src?.attributes))
23+
return jsonObject
24+
}
25+
26+
override fun deserialize(
27+
json: JsonElement?,
28+
typeOfT: Type?,
29+
context: JsonDeserializationContext?
30+
): Span {
31+
try {
32+
val jsonObject = json?.asJsonObject ?: throw JsonParseException("Invalid JSON")
33+
val insert = jsonObject.get("insert")
34+
val attributes = jsonObject.get("attributes")
35+
return Span(
36+
insert = context?.deserialize<String>(insert, String::class.java),
37+
attributes = context?.deserialize<Attributes>(attributes, Attributes::class.java)
38+
)
39+
} catch (e: Exception) {
40+
throw JsonParseException("Invalid JSON")
41+
}
42+
}
43+
}

0 commit comments

Comments
 (0)