This commit is contained in:
parent
6fb7c4a073
commit
19b3195ae5
@ -10,10 +10,10 @@ jobs:
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v4
|
||||
# with:
|
||||
# fetch-depth: 0
|
||||
# - name: Fetch tags
|
||||
# run: git fetch --tags -p
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Fetch tags
|
||||
run: git fetch --tags -p
|
||||
- name: Init compose
|
||||
uses: KengoTODA/actions-setup-docker-compose@v1
|
||||
with:
|
||||
@ -23,18 +23,20 @@ jobs:
|
||||
docker compose -f .gitea/workflows/assets/docker-compose.yml up -d
|
||||
response=$(curl 172.17.0.1:8888/api/about)
|
||||
echo $response
|
||||
# - uses: actions/setup-java@v4
|
||||
# with:
|
||||
# distribution: 'temurin'
|
||||
# java-version: '17'
|
||||
# - name: Setup Android SDK
|
||||
# uses: android-actions/setup-android@v3
|
||||
# - name: Configure gradle...
|
||||
# run: mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=true\nsystemProp.org.gradle.internal.http.connectionTimeout=180000\nsystemProp.org.gradle.internal.http.socketTimeout=180000" >> ~/.gradle/gradle.properties
|
||||
# - name: Build and test
|
||||
# run: ./gradlew build --stacktrace
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
- name: Setup Android SDK
|
||||
uses: android-actions/setup-android@v3
|
||||
- name: Configure gradle...
|
||||
run: mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=true\nsystemProp.org.gradle.internal.http.connectionTimeout=180000\nsystemProp.org.gradle.internal.http.socketTimeout=180000" >> ~/.gradle/gradle.properties
|
||||
- name: ui tests
|
||||
uses: reactivecircus/android-emulator-runner@v2
|
||||
with:
|
||||
api-level: 25
|
||||
script: ./gradlew connectedAndroidTest
|
||||
- name: Clean
|
||||
if: success() || failure()
|
||||
run: |
|
||||
docker compose -f .gitea/workflows/assets/docker-compose.yml stop
|
||||
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -321,3 +321,5 @@ fabric.properties
|
||||
|
||||
|
||||
crowdin.properties
|
||||
|
||||
.kotlin/
|
1
androidApp/.gitignore
vendored
1
androidApp/.gitignore
vendored
@ -1 +1,2 @@
|
||||
/build
|
||||
.kotlin/
|
@ -84,6 +84,7 @@ android {
|
||||
|
||||
// tests
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
testInstrumentationRunnerArguments["clearPackageData"] = "true"
|
||||
}
|
||||
packaging {
|
||||
resources {
|
||||
@ -107,6 +108,10 @@ android {
|
||||
}
|
||||
}
|
||||
namespace = "bou.amine.apps.readerforselfossv2.android"
|
||||
testOptions {
|
||||
animationsDisabled = true
|
||||
execution = "ANDROIDX_TEST_ORCHESTRATOR"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -184,6 +189,12 @@ dependencies {
|
||||
testImplementation("io.mockk:mockk:1.12.0")
|
||||
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
|
||||
androidTestImplementation("androidx.test:runner:1.6.2")
|
||||
androidTestImplementation("androidx.test:rules:1.6.1")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
|
||||
androidTestImplementation("androidx.test.ext:junit-ktx:1.2.1")
|
||||
androidTestUtil("androidx.test:orchestrator:1.5.1")
|
||||
|
||||
|
||||
implementation("ch.acra:acra-http:$acraVersion")
|
||||
implementation("ch.acra:acra-toast:$acraVersion")
|
||||
|
@ -0,0 +1,30 @@
|
||||
package bou.amine.apps.readerforselfossv2.android
|
||||
|
||||
import android.view.View
|
||||
import android.widget.EditText
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.Root
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.action.ViewActions.typeTextIntoFocusedView
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.matcher.RootMatchers.isPlatformPopup
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import org.hamcrest.Description
|
||||
import org.hamcrest.Matcher
|
||||
import org.hamcrest.TypeSafeMatcher
|
||||
|
||||
fun performLogin(url: String) {
|
||||
onView(withId(R.id.urlView)).perform(click()).perform(
|
||||
typeTextIntoFocusedView(url)
|
||||
)
|
||||
onView(withId(R.id.signInButton)).perform(click())
|
||||
}
|
||||
|
||||
fun loginAndInitHome() {
|
||||
performLogin("http://10.0.2.2:8888")
|
||||
Thread.sleep(1000)
|
||||
onView(withText(R.string.gdpr_dialog_title)).check(matches(isDisplayed()))
|
||||
onView(withText("OK")).perform(click())
|
||||
}
|
@ -0,0 +1,161 @@
|
||||
package bou.amine.apps.readerforselfossv2.android
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
|
||||
import androidx.test.espresso.action.ViewActions
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.action.ViewActions.typeTextIntoFocusedView
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isClickable
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isFocused
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isRoot
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isSelected
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import androidx.test.ext.junit.rules.ActivityScenarioRule
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.LargeTest
|
||||
import org.hamcrest.CoreMatchers.not
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@LargeTest
|
||||
class HomeActivityTest {
|
||||
|
||||
@get:Rule
|
||||
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
|
||||
|
||||
private fun getActivity(): Activity? {
|
||||
var activity: Activity? = null
|
||||
activityRule.scenario.onActivity {
|
||||
activity = it
|
||||
}
|
||||
return activity
|
||||
}
|
||||
|
||||
@Before
|
||||
fun init() {
|
||||
loginAndInitHome()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMenu() {
|
||||
onView(withId(R.id.action_search)).check(matches(isDisplayed())).check(
|
||||
matches(
|
||||
isClickable()
|
||||
)
|
||||
)
|
||||
onView(withId(R.id.action_filter)).check(matches(isDisplayed())).check(
|
||||
matches(
|
||||
isClickable()
|
||||
)
|
||||
)
|
||||
openActionBarOverflowOrOptionsMenu(
|
||||
ApplicationProvider.getApplicationContext<Context>()
|
||||
)
|
||||
onView(withText(R.string.readAll)).check(matches(isDisplayed()))
|
||||
onView(withText(R.string.menu_home_sources)).check(matches(isDisplayed()))
|
||||
onView(withText(R.string.title_activity_settings)).check(matches(isDisplayed()))
|
||||
onView(withText(R.string.menu_home_refresh)).check(matches(isDisplayed()))
|
||||
onView(withText(R.string.issue_tracker_link)).check(matches(isDisplayed()))
|
||||
onView(withText(R.string.action_disconnect)).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMenuActions() {
|
||||
onView(withId(R.id.action_search)).perform(click())
|
||||
onView(
|
||||
withId(R.id.search_src_text)
|
||||
).check(matches(isFocused()))
|
||||
onView(isRoot()).perform(ViewActions.pressBack())
|
||||
|
||||
onView(withId(R.id.action_filter)).perform(click())
|
||||
onView(
|
||||
withText(R.string.filter_item_sources)
|
||||
).check(matches(isDisplayed()))
|
||||
onView(
|
||||
withText(R.string.filter_item_tags)
|
||||
).check(matches(isDisplayed()))
|
||||
onView(
|
||||
withId(R.id.floatingActionButton2)
|
||||
).check(matches(isDisplayed()))
|
||||
onView(isRoot()).perform(ViewActions.pressBack())
|
||||
|
||||
openActionBarOverflowOrOptionsMenu(
|
||||
ApplicationProvider.getApplicationContext<Context>()
|
||||
)
|
||||
onView(withText(R.string.readAll)).perform(click())
|
||||
onView(withText(R.string.markall_dialog_message)).check(matches(isDisplayed()))
|
||||
onView(isRoot()).perform(ViewActions.pressBack())
|
||||
openActionBarOverflowOrOptionsMenu(
|
||||
ApplicationProvider.getApplicationContext<Context>()
|
||||
)
|
||||
|
||||
onView(withText(R.string.menu_home_sources)).perform(click())
|
||||
onView(withId(R.id.fab)).check(matches(isDisplayed()))
|
||||
onView(isRoot()).perform(ViewActions.pressBack())
|
||||
openActionBarOverflowOrOptionsMenu(
|
||||
ApplicationProvider.getApplicationContext<Context>()
|
||||
)
|
||||
|
||||
onView(withText(R.string.title_activity_settings)).perform(click())
|
||||
onView(withText(R.string.pref_header_general)).check(matches(isDisplayed()))
|
||||
onView(isRoot()).perform(ViewActions.pressBack())
|
||||
openActionBarOverflowOrOptionsMenu(
|
||||
ApplicationProvider.getApplicationContext<Context>()
|
||||
)
|
||||
|
||||
onView(withText(R.string.menu_home_refresh)).perform(click())
|
||||
onView(withText(R.string.refresh_dialog_message)).check(matches(isDisplayed()))
|
||||
onView(isRoot()).perform(ViewActions.pressBack())
|
||||
openActionBarOverflowOrOptionsMenu(
|
||||
ApplicationProvider.getApplicationContext<Context>()
|
||||
)
|
||||
|
||||
/*onView(withText(R.string.issue_tracker_link)).perform(click())
|
||||
onView(withText(R.string.markall_dialog_message)).check(matches(isDisplayed()))
|
||||
onView(isRoot()).perform(ViewActions.pressBack())
|
||||
openActionBarOverflowOrOptionsMenu(
|
||||
ApplicationProvider.getApplicationContext<Context>()
|
||||
)*/
|
||||
|
||||
onView(withText(R.string.action_disconnect)).perform(click())
|
||||
onView(withText(R.string.confirm_disconnect_title)).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEmptyView() {
|
||||
onView(withId(R.id.emptyText)).check(matches(isDisplayed()))
|
||||
onView(
|
||||
hasBottombarItemText(R.string.tab_new)
|
||||
).check(matches(isDisplayed())).check(matches(isSelected()))
|
||||
onView(
|
||||
hasBottombarItemText(R.string.tab_read)
|
||||
).check(matches(isDisplayed())).check(matches(not(isSelected())))
|
||||
onView(
|
||||
hasBottombarItemText(R.string.tab_favs)
|
||||
).check(matches(isDisplayed())).check(matches(not(isSelected())))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addSource() {
|
||||
openActionBarOverflowOrOptionsMenu(
|
||||
ApplicationProvider.getApplicationContext<Context>()
|
||||
)
|
||||
onView(withText(R.string.menu_home_sources))
|
||||
.perform(click())
|
||||
onView(withId(R.id.fab))
|
||||
.perform(click())
|
||||
onView(withId(R.id.nameInput))
|
||||
.perform(click()).perform(typeTextIntoFocusedView("Source"))
|
||||
Thread.sleep(20000)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
package bou.amine.apps.readerforselfossv2.android
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isClickable
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isNotChecked
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import androidx.test.ext.junit.rules.ActivityScenarioRule
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.LargeTest
|
||||
import org.hamcrest.CoreMatchers.not
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@LargeTest
|
||||
class LoginActivityTest {
|
||||
|
||||
@get:Rule
|
||||
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
|
||||
|
||||
private fun getActivity(): Activity? {
|
||||
var activity: Activity? = null
|
||||
activityRule.scenario.onActivity {
|
||||
activity = it
|
||||
}
|
||||
return activity
|
||||
}
|
||||
|
||||
@Test
|
||||
fun viewIsInitialized() {
|
||||
onView(withId(R.id.urlView)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.selfSigned)).check(matches(isDisplayed())).check(matches(isNotChecked()))
|
||||
.check(
|
||||
matches(isClickable())
|
||||
)
|
||||
onView(withId(R.id.withLogin)).check(matches(isDisplayed()))
|
||||
.check(matches(isNotChecked())).check(
|
||||
matches(isClickable())
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun urlError() {
|
||||
performLogin("172.17.0.1:8888")
|
||||
onView(withId(R.id.urlView)).perform(click())
|
||||
onView(withId(R.id.urlView)).check(matches(withError(getActivity()!!.getString(R.string.wrong_infos))))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun multiError() {
|
||||
onView(withId(R.id.signInButton)).perform(click())
|
||||
Thread.sleep(15000)
|
||||
onView(withId(R.id.signInButton)).perform(click())
|
||||
Thread.sleep(15000)
|
||||
onView(withId(R.id.signInButton)).perform(click())
|
||||
onView(withText(R.string.warning_wrong_url)).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun connect() {
|
||||
performLogin("http://172.17.0.1:8888")
|
||||
onView(withId(R.id.loginProgress)).check(matches(isDisplayed()))
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
package bou.amine.apps.readerforselfossv2.android
|
||||
|
||||
import android.view.View
|
||||
import android.widget.EditText
|
||||
import android.widget.ImageView
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import androidx.test.espresso.Root
|
||||
import androidx.test.espresso.matcher.RootMatchers.isPlatformPopup
|
||||
import androidx.test.espresso.matcher.ViewMatchers.hasSibling
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withParent
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withResourceName
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import org.hamcrest.CoreMatchers.allOf
|
||||
import org.hamcrest.Description
|
||||
import org.hamcrest.Matcher
|
||||
import org.hamcrest.TypeSafeMatcher
|
||||
|
||||
|
||||
fun withError(expected: String): TypeSafeMatcher<View?> {
|
||||
return object : TypeSafeMatcher<View?>() {
|
||||
override fun matchesSafely(view: View?): Boolean {
|
||||
if (view !is EditText) {
|
||||
return false
|
||||
}
|
||||
return view.error.toString() == expected
|
||||
}
|
||||
|
||||
override fun describeTo(description: Description?) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun isPopupWindow(): Matcher<Root> {
|
||||
return isPlatformPopup()
|
||||
}
|
||||
|
||||
fun withDrawable(@DrawableRes id: Int) = object : TypeSafeMatcher<View>() {
|
||||
override fun describeTo(description: Description) {
|
||||
description.appendText("ImageView with drawable same as drawable with id $id")
|
||||
}
|
||||
|
||||
override fun matchesSafely(view: View): Boolean {
|
||||
val context = view.context
|
||||
val expectedBitmap = context.getDrawable(id)!!.toBitmap()
|
||||
try {
|
||||
return view is ImageView && view.drawable.toBitmap().sameAs(expectedBitmap)
|
||||
} catch (e: Exception) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun hasBottombarItemText(@StringRes id: Int): Matcher<View>? {
|
||||
return allOf(
|
||||
withResourceName("fixed_bottom_navigation_icon"),
|
||||
withParent(
|
||||
allOf(
|
||||
withResourceName("fixed_bottom_navigation_icon_container"),
|
||||
hasSibling(withText(id))
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
@ -18,6 +18,7 @@ kotlin.code.style=official
|
||||
#Android
|
||||
android.useAndroidX=true
|
||||
#android.nonTransitiveRClass=true
|
||||
android.enableJetifier=true
|
||||
android.nonTransitiveRClass=false
|
||||
#MPP
|
||||
kotlin.mpp.enableCInteropCommonization=true
|
||||
|
Loading…
Reference in New Issue
Block a user