Skip to content

Commit 9416bdf

Browse files
committed
Added unit tests
Added load from assets from compose
1 parent cfb1c99 commit 9416bdf

File tree

12 files changed

+301
-72
lines changed

12 files changed

+301
-72
lines changed

app/build.gradle.kts

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,36 +61,64 @@ android {
6161
excludes += "/META-INF/{AL2.0,LGPL2.1}"
6262
}
6363
}
64+
65+
testOptions {
66+
managedDevices {
67+
localDevices {
68+
create("pixel2api30") {
69+
device = "Pixel 2"
70+
apiLevel = 30
71+
}
72+
create("api27Pixel") {
73+
device = "Pixel 3a"
74+
apiLevel = 27
75+
systemImageSource = "aosp"
76+
}
77+
create("api35Pixel") {
78+
device = "Pixel 5"
79+
apiLevel = 35
80+
systemImageSource = "aosp"
81+
}
82+
}
83+
// Create a group to test across all defined devices
84+
groups {
85+
create("allApis") {
86+
targetDevices.addAll(localDevices)
87+
}
88+
}
89+
}
90+
}
91+
6492
}
6593

94+
6695
dependencies {
6796

6897
implementation("com.google.android.material:material:1.12.0")
98+
implementation("androidx.test.espresso:espresso-contrib:3.6.1")
6999
val kotlin_version = "1.9.21"
70100
implementation(kotlin("stdlib"))
71101
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
102+
//noinspection GradleDependency
72103
implementation("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version")
73104
implementation("androidx.core:core-ktx:1.15.0")
74105
implementation("androidx.appcompat:appcompat:1.7.0")
75-
implementation("androidx.constraintlayout:constraintlayout:2.2.0")
106+
implementation("androidx.constraintlayout:constraintlayout:2.2.1")
76107
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7")
77-
implementation(platform("androidx.compose:compose-bom:2025.02.00"))
78108
implementation("androidx.compose.ui:ui-graphics")
79109
testImplementation("junit:junit:4.13.2")
80110
androidTestImplementation("androidx.test.ext:junit:1.2.1")
81111
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
82112
implementation(project(":pdfViewer"))
83113
// implementation("io.github.afreakyelf:Pdf-Viewer:2.1.1")
84114
testImplementation("androidx.test:core:1.6.1")
85-
androidTestImplementation("androidx.test:runner:1.6.2")
86115
androidTestImplementation("androidx.test:rules:1.6.1")
87116
androidTestImplementation("androidx.test.ext:junit-ktx:1.2.1")
88117

89118
implementation("androidx.recyclerview:recyclerview:1.4.0") // Check for the latest version available
90119

91120
// compose
92-
implementation(platform("androidx.compose:compose-bom:2025.02.00"))
93-
androidTestImplementation(platform("androidx.compose:compose-bom:2025.02.00"))
121+
implementation(platform("androidx.compose:compose-bom:2025.03.00"))
94122

95123
// Choose one of the following:
96124
// Material Design 3
@@ -105,18 +133,20 @@ dependencies {
105133

106134
// Android Studio Preview support
107135
implementation("androidx.compose.ui:ui-tooling-preview")
108-
androidTestImplementation(platform("androidx.compose:compose-bom:2025.02.00"))
136+
androidTestImplementation(platform("androidx.compose:compose-bom:2025.03.00"))
109137
debugImplementation("androidx.compose.ui:ui-tooling")
110138

111139
// UI Tests
112140
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
113141
debugImplementation("androidx.compose.ui:ui-test-manifest")
114142
androidTestImplementation("androidx.test.ext:junit:1.2.1")
115143
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
144+
androidTestImplementation("androidx.test:rules:1.6.1")
145+
androidTestImplementation("androidx.test:runner:1.6.1")
116146

117147

118148
// Optional - Integration with activities
119-
implementation("androidx.activity:activity-compose:1.10.0")
149+
implementation("androidx.activity:activity-compose:1.10.1")
120150

121151

122152
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package com.rajat.pdfviewer
2+
3+
import android.content.Context
4+
import androidx.test.core.app.ActivityScenario
5+
import androidx.test.core.app.ApplicationProvider
6+
import com.rajat.pdfviewer.util.saveTo
7+
import java.io.File
8+
9+
abstract class BasePdfViewerTest {
10+
11+
protected val context: Context = ApplicationProvider.getApplicationContext()
12+
protected val samplePdf = "quote.pdf"
13+
protected val sampleUrl = "https://css4.pub/2015/usenix/example.pdf"
14+
15+
protected fun copyAssetPdfToCache(assetName: String): File {
16+
val file = File(context.cacheDir, assetName)
17+
context.assets.open(assetName).use { input ->
18+
file.outputStream().use { output -> input.copyTo(output) }
19+
}
20+
return file
21+
}
22+
23+
protected fun launchPdfFromUrl(
24+
url: String = sampleUrl,
25+
title: String = "Remote PDF",
26+
enableDownload: Boolean = true
27+
): ActivityScenario<PdfViewerActivity> {
28+
val intent = PdfViewerActivity.launchPdfFromUrl(
29+
context = context,
30+
pdfUrl = url,
31+
pdfTitle = title,
32+
saveTo = saveTo.DOWNLOADS,
33+
enableDownload = enableDownload
34+
)
35+
return ActivityScenario.launch(intent)
36+
}
37+
38+
protected fun launchPdfFromAssets(
39+
assetName: String = samplePdf,
40+
title: String = "PDF From Asset",
41+
enableZoom: Boolean = true
42+
): ActivityScenario<PdfViewerActivity> {
43+
val file = copyAssetPdfToCache(assetName)
44+
val intent = PdfViewerActivity.launchPdfFromPath(
45+
context = context,
46+
path = file.absolutePath,
47+
pdfTitle = title,
48+
saveTo = saveTo.DOWNLOADS,
49+
fromAssets = false,
50+
enableZoom = enableZoom
51+
)
52+
return ActivityScenario.launch(intent)
53+
}
54+
}

app/src/androidTest/java/com/rajat/pdfviewer/ExampleInstrumentedTest.kt

Lines changed: 0 additions & 22 deletions
This file was deleted.
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package com.rajat.pdfviewer
2+
3+
import android.graphics.drawable.ColorDrawable
4+
import android.view.View
5+
import androidx.core.content.ContextCompat
6+
import androidx.recyclerview.widget.RecyclerView
7+
import androidx.test.core.app.ActivityScenario
8+
import androidx.test.espresso.Espresso.onView
9+
import androidx.test.espresso.assertion.ViewAssertions.matches
10+
import androidx.test.espresso.contrib.RecyclerViewActions
11+
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
12+
import androidx.test.espresso.matcher.ViewMatchers.withId
13+
import androidx.test.espresso.matcher.ViewMatchers.withText
14+
import androidx.test.ext.junit.runners.AndroidJUnit4
15+
import androidx.test.filters.LargeTest
16+
import com.rajat.pdfviewer.util.saveTo
17+
import junit.framework.TestCase.assertEquals
18+
import org.junit.Test
19+
import org.junit.runner.RunWith
20+
21+
@RunWith(AndroidJUnit4::class)
22+
@LargeTest
23+
class PdfViewerInstrumentedTest : BasePdfViewerTest() {
24+
25+
@Test
26+
fun test_pdf_renders_successfully_from_assets() {
27+
launchPdfFromAssets().use {
28+
onView(withId(R.id.recyclerView)).perform(
29+
RecyclerViewActions.scrollToPosition<RecyclerView.ViewHolder>(2)
30+
)
31+
onView(withId(R.id.pageNumber)).check(matches(isDisplayed()))
32+
}
33+
}
34+
35+
@Test
36+
fun test_jump_to_page_from_assets() {
37+
launchPdfFromAssets(title = "Jump Test").use { scenario ->
38+
scenario.onActivity {
39+
it.findViewById<PdfRendererView>(R.id.pdfView).jumpToPage(3)
40+
}
41+
42+
onView(withId(R.id.recyclerView))
43+
.perform(RecyclerViewActions.scrollToPosition<RecyclerView.ViewHolder>(3))
44+
.check(matches(isDisplayed()))
45+
}
46+
}
47+
48+
@Test
49+
fun test_download_button_is_visible_when_loading_from_url() {
50+
launchPdfFromUrl(enableDownload = true).use {
51+
onView(withId(R.id.download)).check(matches(isDisplayed()))
52+
}
53+
}
54+
55+
@Test
56+
fun test_error_dialog_shown_for_invalid_path() {
57+
val intent = PdfViewerActivity.launchPdfFromPath(
58+
context, "/invalid/path/file.pdf", "Invalid PDF", saveTo.DOWNLOADS
59+
)
60+
61+
ActivityScenario.launch<PdfViewerActivity>(intent).use {
62+
Thread.sleep(1000)
63+
onView(withId(android.R.id.message)).check(matches(isDisplayed()))
64+
}
65+
}
66+
67+
@Test
68+
fun test_pdf_renders_successfully_from_url() {
69+
launchPdfFromUrl().use {
70+
onView(withId(R.id.recyclerView))
71+
.perform(RecyclerViewActions.scrollToPosition<RecyclerView.ViewHolder>(1))
72+
onView(withId(R.id.pageNumber)).check(matches(isDisplayed()))
73+
}
74+
}
75+
76+
@Test
77+
fun test_download_button_is_not_visible_when_disabled() {
78+
launchPdfFromUrl(enableDownload = false).use { scenario ->
79+
scenario.onActivity {
80+
assert(!it.isDownloadButtonVisible())
81+
}
82+
}
83+
}
84+
85+
@Test
86+
fun test_pdf_does_not_crash_on_orientation_change() {
87+
launchPdfFromAssets().use {
88+
onView(withId(R.id.recyclerView)).check(matches(isDisplayed()))
89+
it.recreate()
90+
onView(withId(R.id.recyclerView)).check(matches(isDisplayed()))
91+
}
92+
}
93+
94+
@Test
95+
fun test_toolbar_title_matches_intent_value() {
96+
val customTitle = "Testing Title"
97+
val intent = PdfViewerActivity.launchPdfFromUrl(
98+
context = context,
99+
pdfUrl = sampleUrl,
100+
pdfTitle = customTitle,
101+
saveTo = saveTo.DOWNLOADS
102+
)
103+
104+
ActivityScenario.launch<PdfViewerActivity>(intent).use {
105+
onView(withId(R.id.toolbar_title)).check(matches(withText(customTitle)))
106+
}
107+
}
108+
109+
@Test
110+
fun test_zoom_is_disabled_when_flag_false() {
111+
launchPdfFromAssets(enableZoom = false).use { scenario ->
112+
scenario.onActivity {
113+
val zoomEnabled = it.findViewById<PdfRendererView>(R.id.pdfView).getZoomEnabled()
114+
assert(!zoomEnabled)
115+
}
116+
}
117+
}
118+
119+
@Test
120+
fun test_total_page_count_is_correct() {
121+
launchPdfFromAssets().use { scenario ->
122+
scenario.onActivity {
123+
val totalPages = it.findViewById<PdfRendererView>(R.id.pdfView).totalPageCount
124+
assert(totalPages >= 1)
125+
}
126+
}
127+
}
128+
129+
@Test
130+
fun test_file_loaded_triggers_onPdfLoadSuccess() {
131+
launchPdfFromAssets().use { scenario ->
132+
scenario.onActivity {
133+
val isInitialized = it.findViewById<PdfRendererView>(R.id.pdfView)
134+
.getLoadedBitmaps().isNotEmpty()
135+
assert(isInitialized)
136+
}
137+
}
138+
}
139+
140+
@Test
141+
fun test_toolbar_color_matches_theme() {
142+
launchPdfFromAssets().use { scenario ->
143+
scenario.onActivity {
144+
val toolbar = it.findViewById<View>(R.id.my_toolbar)
145+
val expectedColor = ContextCompat.getColor(context, R.color.colorPrimary)
146+
val actualColor = (toolbar.background as ColorDrawable).color
147+
assertEquals(expectedColor, actualColor)
148+
}
149+
}
150+
}
151+
152+
@Test
153+
fun test_error_retry_button_is_shown_on_download_failure() {
154+
launchPdfFromUrl(url = "https://invalid.pdf.url/404.pdf",
155+
title = "Fail PDF",).use {
156+
Thread.sleep(5000)
157+
onView(withText(R.string.pdf_viewer_retry)).check(matches(isDisplayed()))
158+
}
159+
}
160+
}

app/src/main/java/com/rajat/sample/pdfviewer/ComposeActivity.kt

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import androidx.compose.ui.tooling.preview.Preview
2626
import androidx.compose.ui.unit.dp
2727
import com.rajat.pdfviewer.PdfRendererView
2828
import com.rajat.pdfviewer.compose.PdfRendererViewCompose
29+
import com.rajat.pdfviewer.compose.PdfRendererViewComposeFromAsset
2930
import com.rajat.sample.pdfviewer.ui.theme.AndroidpdfviewerTheme
3031
import java.io.File
3132

@@ -46,9 +47,7 @@ class ComposeActivity : ComponentActivity() {
4647
modifier = Modifier.fillMaxSize(),
4748
color = MaterialTheme.colorScheme.background
4849
) {
49-
MyPdfScreenFromUri(
50-
modifier = Modifier.fillMaxSize()
51-
)
50+
MyPdfScreenFromAsset()
5251
}
5352
}
5453
}
@@ -174,6 +173,14 @@ fun MyPdfScreenFromFile() {
174173
)
175174
}
176175

176+
@Composable
177+
fun MyPdfScreenFromAsset(modifier: Modifier = Modifier) {
178+
PdfRendererViewComposeFromAsset(
179+
assetFileName = "quote.pdf",
180+
modifier = modifier
181+
)
182+
}
183+
177184
@Preview(showBackground = true)
178185
@Composable
179186
fun GreetingPreview() {

0 commit comments

Comments
 (0)