feat: add homepage components and API for settings and spielplan options
- Introduced new Vue components for homepage teasers: HomeLinksTeaser, HomeSpielplanTeamWidget, HomeTrainingTeaser, and HomeVereinsmeisterschaftenTeaser. - Created XML layout for tablet app window dump. - Implemented API endpoints for fetching and updating homepage settings. - Added API for retrieving spielplan options, including team extraction logic.
This commit is contained in:
@@ -1,9 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application
|
||||
android:name="dagger.hilt.android.testing.HiltTestApplication"
|
||||
android:allowBackup="false">
|
||||
<application android:allowBackup="false">
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -41,6 +41,7 @@ object TestHiltModules {
|
||||
"authStatus" -> Response.success(AuthStatusResponse(isLoggedIn = false))
|
||||
"publicNews" -> Response.success(de.harheimertc.data.NewsPublicResponse(news = listOf()))
|
||||
"memberNews" -> Response.success(de.harheimertc.data.NewsResponse(success = true, news = listOf()))
|
||||
"passwordResetDiagnostics" -> Response.success(de.harheimertc.data.PasswordResetDiagnosticsResponse())
|
||||
else -> throw UnsupportedOperationException("ApiService method not implemented in test double: ${method.name}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,390 @@
|
||||
package de.harheimertc.ui.screens.cms
|
||||
|
||||
import android.content.Context
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.ui.test.*
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.squareup.moshi.Moshi
|
||||
import de.harheimertc.data.ApiService
|
||||
import de.harheimertc.data.AuthMessageResponse
|
||||
import de.harheimertc.data.AuthStatusResponse
|
||||
import de.harheimertc.data.BirthdaysResponse
|
||||
import de.harheimertc.data.CmsUserDto
|
||||
import de.harheimertc.data.CmsUsersResponse
|
||||
import de.harheimertc.data.ConfigResponse
|
||||
import de.harheimertc.data.ContactRequest
|
||||
import de.harheimertc.data.ContactRequestDto
|
||||
import de.harheimertc.data.ContactResponse
|
||||
import de.harheimertc.data.LoginRequest
|
||||
import de.harheimertc.data.LoginResponse
|
||||
import de.harheimertc.data.LogoutRequest
|
||||
import de.harheimertc.data.MembersResponse
|
||||
import de.harheimertc.data.MembershipRequest
|
||||
import de.harheimertc.data.MembershipResponse
|
||||
import de.harheimertc.data.NewsletterCreateRequest
|
||||
import de.harheimertc.data.NewsletterCreateResponse
|
||||
import de.harheimertc.data.NewsletterDto
|
||||
import de.harheimertc.data.NewsletterGroupDto
|
||||
import de.harheimertc.data.NewsletterGroupsResponse
|
||||
import de.harheimertc.data.NewsletterListResponse
|
||||
import de.harheimertc.data.NewsletterSendResponse
|
||||
import de.harheimertc.data.NewsletterSubscriptionRequest
|
||||
import de.harheimertc.data.NewsPublicResponse
|
||||
import de.harheimertc.data.NewsResponse
|
||||
import de.harheimertc.data.NewsSaveRequest
|
||||
import de.harheimertc.data.PasskeyAuthenticationOptionsRequest
|
||||
import de.harheimertc.data.PasskeyRegistrationOptionsRequest
|
||||
import de.harheimertc.data.PasskeysResponse
|
||||
import de.harheimertc.data.PasswordResetAttemptDto
|
||||
import de.harheimertc.data.PasswordResetDiagnosticsResponse
|
||||
import de.harheimertc.data.ProfileResponse
|
||||
import de.harheimertc.data.ProfileUpdateRequest
|
||||
import de.harheimertc.data.PublicGalleryImageDto
|
||||
import de.harheimertc.data.GalleryListResponse
|
||||
import de.harheimertc.data.GalleryUploadResponse
|
||||
import de.harheimertc.data.RefreshRequest
|
||||
import de.harheimertc.data.RegistrationRequest
|
||||
import de.harheimertc.data.RemovePasskeyRequest
|
||||
import de.harheimertc.data.ResetPasswordRequest
|
||||
import de.harheimertc.data.SaveCsvRequest
|
||||
import de.harheimertc.data.SaveCsvResponse
|
||||
import de.harheimertc.data.SecureOfflineCache
|
||||
import de.harheimertc.data.SpielplanResponse
|
||||
import de.harheimertc.data.TeamTableResponse
|
||||
import de.harheimertc.data.TermineResponse
|
||||
import de.harheimertc.repositories.CmsRepository
|
||||
import de.harheimertc.repositories.MeisterschaftResult
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody
|
||||
import okhttp3.ResponseBody
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import retrofit2.Response
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class CmsExistingScreensSmokeTest {
|
||||
@get:Rule
|
||||
val composeTestRule = createAndroidComposeRule<ComponentActivity>()
|
||||
|
||||
@Test
|
||||
fun cmsDashboard_renders() {
|
||||
val api = RecordingApiService()
|
||||
val viewModel = createViewModel(api)
|
||||
renderWithState(viewModel)
|
||||
composeTestRule.setContent {
|
||||
val nav = rememberNavController()
|
||||
CmsDashboardScreen(nav, showBackNavigation = false, viewModel = viewModel)
|
||||
}
|
||||
composeTestRule.onNodeWithText("Vereinsmeisterschaften", substring = true).assertExists()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cmsStartseite_saveWorks() {
|
||||
val api = RecordingApiService()
|
||||
val viewModel = createViewModel(api)
|
||||
renderWithState(viewModel)
|
||||
|
||||
composeTestRule.setContent {
|
||||
val nav = rememberNavController()
|
||||
CmsStartseiteScreen(nav, showBackNavigation = false, viewModel = viewModel)
|
||||
}
|
||||
composeTestRule.onNodeWithText("Speichern").performClick()
|
||||
composeTestRule.waitForIdle()
|
||||
assertTrue(api.updateConfigCalls >= 1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cmsInhalte_saveWorks() {
|
||||
val api = RecordingApiService()
|
||||
val viewModel = createViewModel(api)
|
||||
renderWithState(viewModel)
|
||||
|
||||
composeTestRule.setContent {
|
||||
val nav = rememberNavController()
|
||||
CmsInhalteScreen(nav, showBackNavigation = false, viewModel = viewModel)
|
||||
}
|
||||
composeTestRule.onNodeWithText("Inhalte speichern").performClick()
|
||||
composeTestRule.waitForIdle()
|
||||
assertTrue(api.updateConfigCalls >= 1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cmsVereinsmeisterschaften_saveWorks() {
|
||||
val api = RecordingApiService()
|
||||
val viewModel = createViewModel(api)
|
||||
renderWithState(viewModel)
|
||||
|
||||
composeTestRule.setContent {
|
||||
val nav = rememberNavController()
|
||||
CmsVereinsmeisterschaftenScreen(nav, showBackNavigation = false, viewModel = viewModel)
|
||||
}
|
||||
composeTestRule.onNodeWithText("Speichern").performClick()
|
||||
composeTestRule.waitForIdle()
|
||||
assertEquals(1, api.saveCsvCalls)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cmsSportbetrieb_saveWorks() {
|
||||
val api = RecordingApiService()
|
||||
val viewModel = createViewModel(api)
|
||||
renderWithState(viewModel)
|
||||
|
||||
composeTestRule.setContent {
|
||||
val nav = rememberNavController()
|
||||
CmsSportbetriebScreen(nav, showBackNavigation = false, viewModel = viewModel)
|
||||
}
|
||||
composeTestRule.onNodeWithText("Speichern").performClick()
|
||||
composeTestRule.waitForIdle()
|
||||
assertTrue(api.updateConfigCalls >= 1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cmsEinstellungen_saveWorks() {
|
||||
val api = RecordingApiService()
|
||||
val viewModel = createViewModel(api)
|
||||
renderWithState(viewModel)
|
||||
|
||||
composeTestRule.setContent {
|
||||
val nav = rememberNavController()
|
||||
CmsEinstellungenScreen(nav, showBackNavigation = false, viewModel = viewModel)
|
||||
}
|
||||
composeTestRule.onNodeWithText("Speichern").performClick()
|
||||
composeTestRule.waitForIdle()
|
||||
assertTrue(api.updateConfigCalls >= 1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cmsMitgliederverwaltung_clickFreischalten() {
|
||||
val api = RecordingApiService()
|
||||
val viewModel = createViewModel(api)
|
||||
renderWithState(viewModel)
|
||||
|
||||
composeTestRule.setContent {
|
||||
val nav = rememberNavController()
|
||||
CmsMitgliederverwaltungScreen(nav, showBackNavigation = false, viewModel = viewModel)
|
||||
}
|
||||
composeTestRule.onNodeWithText("Freischalten").performClick()
|
||||
composeTestRule.waitForIdle()
|
||||
assertEquals(1, api.updateUserActiveCalls)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cmsBenutzer_clickRollenSpeichern() {
|
||||
val api = RecordingApiService()
|
||||
val viewModel = createViewModel(api)
|
||||
renderWithState(viewModel)
|
||||
|
||||
composeTestRule.setContent {
|
||||
val nav = rememberNavController()
|
||||
CmsBenutzerScreen(nav, showBackNavigation = false, viewModel = viewModel)
|
||||
}
|
||||
composeTestRule.onNodeWithText("Rollen").performClick()
|
||||
composeTestRule.onNodeWithText("Speichern").performClick()
|
||||
composeTestRule.waitForIdle()
|
||||
assertEquals(1, api.updateUserRolesCalls)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cmsContactRequests_replySenden() {
|
||||
val api = RecordingApiService()
|
||||
val viewModel = createViewModel(api)
|
||||
renderWithState(viewModel)
|
||||
|
||||
composeTestRule.setContent {
|
||||
val nav = rememberNavController()
|
||||
CmsContactRequestsScreen(nav, showBackNavigation = false, viewModel = viewModel)
|
||||
}
|
||||
composeTestRule.onNodeWithText("Antworten").performClick()
|
||||
composeTestRule.onNode(hasSetTextAction()).performTextInput("Kurze Testantwort")
|
||||
composeTestRule.onNodeWithText("Senden").performClick()
|
||||
composeTestRule.waitForIdle()
|
||||
assertEquals(1, api.replyContactCalls)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cmsNewsletter_createAndSave() {
|
||||
val api = RecordingApiService()
|
||||
val viewModel = createViewModel(api)
|
||||
renderWithState(viewModel)
|
||||
|
||||
composeTestRule.setContent {
|
||||
val nav = rememberNavController()
|
||||
CmsNewsletterScreen(nav, showBackNavigation = false, viewModel = viewModel, canWriteOverride = true)
|
||||
}
|
||||
composeTestRule.onNodeWithText("Newsletter erstellen").performClick()
|
||||
composeTestRule.onNode(hasSetTextAction()).performTextInput("Testnewsletter")
|
||||
composeTestRule.onAllNodes(hasText("Speichern")).onFirst().performClick()
|
||||
composeTestRule.waitForIdle()
|
||||
assertEquals(1, api.createNewsletterCalls)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cmsPasswordResetDiagnostics_renders() {
|
||||
val api = RecordingApiService()
|
||||
val viewModel = createViewModel(api)
|
||||
renderWithState(viewModel)
|
||||
|
||||
composeTestRule.setContent {
|
||||
val nav = rememberNavController()
|
||||
CmsPasswordResetDiagnosticsScreen(nav, showBackNavigation = false, viewModel = viewModel)
|
||||
}
|
||||
composeTestRule.onNodeWithText("Passwort-Reset-Diagnose", substring = true).assertExists()
|
||||
}
|
||||
|
||||
private fun createViewModel(api: RecordingApiService): CmsViewModel {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val cache = SecureOfflineCache(context, Moshi.Builder().build())
|
||||
val repository = CmsRepository(api, cache)
|
||||
return CmsViewModel(repository)
|
||||
}
|
||||
|
||||
private fun renderWithState(viewModel: CmsViewModel) {
|
||||
val readyState = CmsUiState(
|
||||
loading = false,
|
||||
saving = false,
|
||||
error = null,
|
||||
message = null,
|
||||
config = ConfigResponse(),
|
||||
users = listOf(
|
||||
CmsUserDto(id = "pending-1", name = "Pending User", email = "pending@example.com", active = false, roles = emptyList()),
|
||||
CmsUserDto(id = "active-1", name = "Active User", email = "active@example.com", active = true, roles = listOf("vorstand")),
|
||||
),
|
||||
contactRequests = listOf(
|
||||
ContactRequestDto(id = "request-1", name = "Kontakt Test", email = "kontakt@example.com", message = "Bitte melden", status = "offen"),
|
||||
),
|
||||
newsletters = listOf(
|
||||
NewsletterDto(id = "newsletter-1", title = "Sommerinfo", subject = "Sommerinfo", status = "draft"),
|
||||
),
|
||||
newsletterGroups = listOf(
|
||||
NewsletterGroupDto(id = "group-1", name = "Mitglieder", description = "Alle Mitglieder"),
|
||||
),
|
||||
passwordResetAttempts = listOf(
|
||||
PasswordResetAttemptDto(requestId = "diag-1", emailMasked = "m***@example.com", failed = true),
|
||||
),
|
||||
news = emptyList(),
|
||||
meisterschaften = listOf(
|
||||
MeisterschaftResult(year = "2025", category = "Herren", rank = "1", playerOne = "Erika Muster", playerTwo = "", note = "Titel verteidigt", imageOne = "", imageTwo = ""),
|
||||
),
|
||||
)
|
||||
val field = CmsViewModel::class.java.getDeclaredField("_state")
|
||||
field.isAccessible = true
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
(field.get(viewModel) as MutableStateFlow<CmsUiState>).value = readyState
|
||||
}
|
||||
}
|
||||
|
||||
private class RecordingApiService : ApiService {
|
||||
var updateConfigCalls = 0
|
||||
var saveCsvCalls = 0
|
||||
var updateUserActiveCalls = 0
|
||||
var updateUserRolesCalls = 0
|
||||
var replyContactCalls = 0
|
||||
var createNewsletterCalls = 0
|
||||
|
||||
private val config = ConfigResponse()
|
||||
private val users = listOf(
|
||||
CmsUserDto(id = "pending-1", name = "Pending User", email = "pending@example.com", active = false, roles = emptyList()),
|
||||
CmsUserDto(id = "active-1", name = "Active User", email = "active@example.com", active = true, roles = listOf("vorstand")),
|
||||
)
|
||||
private val contactRequests = listOf(
|
||||
ContactRequestDto(id = "request-1", name = "Kontakt Test", email = "kontakt@example.com", message = "Bitte melden", status = "offen"),
|
||||
)
|
||||
private val newsletters = listOf(
|
||||
NewsletterDto(id = "newsletter-1", title = "Sommerinfo", subject = "Sommerinfo", status = "draft"),
|
||||
)
|
||||
private val groups = listOf(
|
||||
NewsletterGroupDto(id = "group-1", name = "Mitglieder", description = "Alle Mitglieder"),
|
||||
)
|
||||
private val diagnostics = listOf(
|
||||
PasswordResetAttemptDto(requestId = "diag-1", emailMasked = "m***@example.com", failed = true),
|
||||
)
|
||||
|
||||
override suspend fun publicGalleryImages(): Response<List<PublicGalleryImageDto>> = Response.success(emptyList())
|
||||
override suspend fun postContact(req: ContactRequest): Response<ContactResponse> = Response.success(ContactResponse(ok = true))
|
||||
override suspend fun galerieList(page: Int, perPage: Int): Response<GalleryListResponse> = Response.success(GalleryListResponse())
|
||||
override suspend fun uploadGalleryImage(image: MultipartBody.Part, title: RequestBody, description: RequestBody, isPublic: RequestBody): Response<GalleryUploadResponse> = Response.success(GalleryUploadResponse())
|
||||
override suspend fun termine(): Response<TermineResponse> = Response.success(TermineResponse())
|
||||
override suspend fun spielplan(season: String?): Response<SpielplanResponse> = Response.success(SpielplanResponse())
|
||||
override suspend fun spielplanTable(team: String, season: String?): Response<TeamTableResponse> = Response.success(TeamTableResponse())
|
||||
override suspend fun publicNews(): Response<NewsPublicResponse> = Response.success(NewsPublicResponse())
|
||||
override suspend fun memberNews(): Response<NewsResponse> = Response.success(NewsResponse(success = true, news = emptyList()))
|
||||
override suspend fun saveNews(request: NewsSaveRequest): Response<AuthMessageResponse> = Response.success(AuthMessageResponse(success = true, message = "ok"))
|
||||
override suspend fun deleteNews(id: Int): Response<AuthMessageResponse> = Response.success(AuthMessageResponse(success = true))
|
||||
override suspend fun mannschaften(season: String?): Response<ResponseBody> = Response.success(null)
|
||||
override suspend fun config(): Response<ConfigResponse> = Response.success(config)
|
||||
override suspend fun updateConfig(request: ConfigResponse): Response<ConfigResponse> {
|
||||
updateConfigCalls++
|
||||
return Response.success(request)
|
||||
}
|
||||
override suspend fun spielsysteme(): Response<ResponseBody> = Response.success(null)
|
||||
override suspend fun vereinsmeisterschaften(): Response<ResponseBody> = Response.success(ResponseBody.create(null, "Jahr,Kategorie,Platz,Spieler1,Spieler2,Bemerkung,imageFilename1,imageFilename2\n\"2025\",\"Herren\",\"1\",\"Erika Muster\",\"\",\"Titel verteidigt\",\"\",\"\""))
|
||||
override suspend fun saveCsv(request: SaveCsvRequest): Response<SaveCsvResponse> {
|
||||
saveCsvCalls++
|
||||
return Response.success(SaveCsvResponse(success = true, message = "CSV gespeichert"))
|
||||
}
|
||||
override suspend fun generateMembershipPdf(request: MembershipRequest): Response<MembershipResponse> = Response.success(MembershipResponse())
|
||||
override suspend fun downloadMembershipPdf(downloadUrl: String): Response<ResponseBody> = Response.success(null)
|
||||
override suspend fun login(request: LoginRequest): Response<LoginResponse> = Response.success(LoginResponse())
|
||||
override suspend fun logout(request: LogoutRequest): Response<Unit> = Response.success(Unit)
|
||||
override suspend fun refresh(request: RefreshRequest): Response<LoginResponse> = Response.success(LoginResponse())
|
||||
override suspend fun authStatus(): Response<AuthStatusResponse> = Response.success(AuthStatusResponse())
|
||||
override suspend fun resetPassword(request: ResetPasswordRequest): Response<AuthMessageResponse> = Response.success(AuthMessageResponse())
|
||||
override suspend fun register(request: RegistrationRequest): Response<AuthMessageResponse> = Response.success(AuthMessageResponse())
|
||||
override suspend fun passkeyAuthenticationOptions(request: PasskeyAuthenticationOptionsRequest): Response<ResponseBody> = Response.success(null)
|
||||
override suspend fun passkeyLogin(request: RequestBody): Response<LoginResponse> = Response.success(LoginResponse())
|
||||
override suspend fun passkeys(): Response<PasskeysResponse> = Response.success(PasskeysResponse())
|
||||
override suspend fun passkeyRegistrationOptions(request: PasskeyRegistrationOptionsRequest): Response<ResponseBody> = Response.success(null)
|
||||
override suspend fun registerPasskey(request: RequestBody): Response<AuthMessageResponse> = Response.success(AuthMessageResponse())
|
||||
override suspend fun removePasskey(request: RemovePasskeyRequest): Response<AuthMessageResponse> = Response.success(AuthMessageResponse())
|
||||
override suspend fun profile(): Response<ProfileResponse> = Response.success(ProfileResponse())
|
||||
override suspend fun updateProfile(request: ProfileUpdateRequest): Response<ProfileResponse> = Response.success(ProfileResponse())
|
||||
override suspend fun birthdays(): Response<BirthdaysResponse> = Response.success(BirthdaysResponse())
|
||||
override suspend fun members(): Response<MembersResponse> = Response.success(MembersResponse())
|
||||
override suspend fun saveMember(request: ApiService.MemberSaveRequest): Response<AuthMessageResponse> = Response.success(AuthMessageResponse())
|
||||
override suspend fun deleteMember(body: Map<String, String>): Response<AuthMessageResponse> = Response.success(AuthMessageResponse())
|
||||
override suspend fun bulkImportMembers(request: ApiService.BulkImportRequest): Response<ApiService.BulkImportResponse> = Response.success(ApiService.BulkImportResponse())
|
||||
override suspend fun toggleMannschaftsspieler(body: Map<String, String>): Response<Map<String, Any>> = Response.success(emptyMap())
|
||||
override suspend fun cmsUsers(): Response<CmsUsersResponse> = Response.success(CmsUsersResponse(users = users))
|
||||
override suspend fun updateUserRoles(request: ApiService.UpdateUserRolesRequest): Response<AuthMessageResponse> {
|
||||
updateUserRolesCalls++
|
||||
return Response.success(AuthMessageResponse(success = true, message = "Rollen aktualisiert"))
|
||||
}
|
||||
override suspend fun updateUserActive(request: ApiService.UpdateUserActiveRequest): Response<AuthMessageResponse> {
|
||||
updateUserActiveCalls++
|
||||
return Response.success(AuthMessageResponse(success = true, message = "Status aktualisiert"))
|
||||
}
|
||||
override suspend fun resendInvite(id: String): Response<AuthMessageResponse> = Response.success(AuthMessageResponse(success = true))
|
||||
override suspend fun contactRequests(): Response<List<ContactRequestDto>> = Response.success(contactRequests)
|
||||
override suspend fun replyToContactRequest(id: String, request: ApiService.ContactReplyRequest): Response<ContactResponse> {
|
||||
replyContactCalls++
|
||||
return Response.success(ContactResponse(ok = true, message = "Antwort versendet"))
|
||||
}
|
||||
override suspend fun toggleContactRequestStatus(id: String): Response<ContactResponse> = Response.success(ContactResponse(ok = true, message = "Status aktualisiert"))
|
||||
override suspend fun newsletters(): Response<NewsletterListResponse> = Response.success(NewsletterListResponse(success = true, newsletters = newsletters))
|
||||
override suspend fun newsletterGroups(): Response<NewsletterGroupsResponse> = Response.success(NewsletterGroupsResponse(success = true, groups = groups))
|
||||
override suspend fun createNewsletterGroup(request: Map<String, Any?>): Response<AuthMessageResponse> = Response.success(AuthMessageResponse(success = true))
|
||||
override suspend fun updateNewsletterGroup(id: String, request: Map<String, Any?>): Response<AuthMessageResponse> = Response.success(AuthMessageResponse(success = true))
|
||||
override suspend fun deleteNewsletterGroup(id: String): Response<AuthMessageResponse> = Response.success(AuthMessageResponse(success = true))
|
||||
override suspend fun createNewsletter(request: NewsletterCreateRequest): Response<NewsletterCreateResponse> {
|
||||
createNewsletterCalls++
|
||||
return Response.success(NewsletterCreateResponse(success = true, message = "Newsletter gespeichert"))
|
||||
}
|
||||
override suspend fun updateNewsletter(id: String, request: Map<String, Any?>): Response<NewsletterCreateResponse> = Response.success(NewsletterCreateResponse(success = true))
|
||||
override suspend fun sendNewsletter(id: String): Response<NewsletterSendResponse> = Response.success(NewsletterSendResponse(success = true))
|
||||
override suspend fun deleteNewsletter(id: String): Response<AuthMessageResponse> = Response.success(AuthMessageResponse(success = true))
|
||||
override suspend fun publicNewsletterGroups(): Response<NewsletterGroupsResponse> = Response.success(NewsletterGroupsResponse(success = true, groups = groups))
|
||||
override suspend fun subscribeNewsletter(request: NewsletterSubscriptionRequest): Response<AuthMessageResponse> = Response.success(AuthMessageResponse(success = true))
|
||||
override suspend fun unsubscribeNewsletter(request: NewsletterSubscriptionRequest): Response<AuthMessageResponse> = Response.success(AuthMessageResponse(success = true))
|
||||
override suspend fun confirmNewsletter(token: String): Response<AuthMessageResponse> = Response.success(AuthMessageResponse(success = true))
|
||||
override suspend fun passwordResetDiagnostics(
|
||||
email: String?,
|
||||
failedOnly: Boolean,
|
||||
): Response<PasswordResetDiagnosticsResponse> = Response.success(PasswordResetDiagnosticsResponse(attempts = diagnostics))
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package de.harheimertc.ui.screens.cms
|
||||
|
||||
import android.content.Context
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.ui.test.*
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.squareup.moshi.Moshi
|
||||
import de.harheimertc.data.ApiService
|
||||
import de.harheimertc.data.CmsUsersResponse
|
||||
import de.harheimertc.data.ConfigResponse
|
||||
import de.harheimertc.data.ContactRequestDto
|
||||
import de.harheimertc.data.NewsDto
|
||||
import de.harheimertc.data.NewsResponse
|
||||
import de.harheimertc.data.NewsletterGroupsResponse
|
||||
import de.harheimertc.data.NewsletterListResponse
|
||||
import de.harheimertc.data.PasswordResetAttemptDto
|
||||
import de.harheimertc.data.PasswordResetDiagnosticsResponse
|
||||
import de.harheimertc.data.PasswordResetStepDto
|
||||
import de.harheimertc.data.SecureOfflineCache
|
||||
import de.harheimertc.repositories.CmsRepository
|
||||
import okhttp3.ResponseBody
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import retrofit2.Response
|
||||
import java.lang.reflect.InvocationHandler
|
||||
import java.lang.reflect.Method
|
||||
import java.lang.reflect.Proxy
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class CmsPasswordResetDiagnosticsScreenTest {
|
||||
@get:Rule
|
||||
val composeTestRule = createAndroidComposeRule<ComponentActivity>()
|
||||
|
||||
@Test
|
||||
fun diagnosticsScreen_showsFilterAndAttemptDetails() {
|
||||
val api = createDiagnosticsApiService()
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val cache = SecureOfflineCache(context, Moshi.Builder().build())
|
||||
val repo = CmsRepository(api, cache)
|
||||
val viewModel = CmsViewModel(repo)
|
||||
|
||||
composeTestRule.setContent {
|
||||
val navController = rememberNavController()
|
||||
CmsPasswordResetDiagnosticsScreen(navController, showBackNavigation = false, viewModel = viewModel)
|
||||
}
|
||||
|
||||
composeTestRule.waitUntil(15_000) {
|
||||
try {
|
||||
composeTestRule.onNodeWithText("Nur Auffälligkeiten", useUnmergedTree = true).assertExists()
|
||||
true
|
||||
} catch (_: Throwable) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText("Nur Auffälligkeiten", useUnmergedTree = true).assertExists()
|
||||
composeTestRule.onNodeWithText("Prüfen", useUnmergedTree = true).assertExists()
|
||||
composeTestRule.onNodeWithText("Reset-Vorgänge", useUnmergedTree = true).assertExists()
|
||||
composeTestRule.onNodeWithText("Aktualisieren", useUnmergedTree = true).assertExists()
|
||||
|
||||
// Trigger a manual refresh to validate the main interaction path.
|
||||
composeTestRule.onNodeWithText("Prüfen", useUnmergedTree = true).performClick()
|
||||
composeTestRule.waitForIdle()
|
||||
}
|
||||
|
||||
private fun createDiagnosticsApiService(): ApiService {
|
||||
val attempt = PasswordResetAttemptDto(
|
||||
requestId = "req-1",
|
||||
startedAt = "2026-05-29T10:15:00Z",
|
||||
emailMasked = "m***@example.com",
|
||||
ip = "127.0.0.1",
|
||||
failed = true,
|
||||
steps = listOf(
|
||||
PasswordResetStepDto(
|
||||
ts = "2026-05-29T10:15:01Z",
|
||||
step = "mail_configuration",
|
||||
status = "failed",
|
||||
reason = "smtp_credentials_missing",
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
val handler = InvocationHandler { _, method: Method, _ ->
|
||||
when (method.name) {
|
||||
"config" -> Response.success(ConfigResponse())
|
||||
"users" -> Response.success(CmsUsersResponse())
|
||||
"cmsUsers" -> Response.success(CmsUsersResponse())
|
||||
"contactRequests" -> Response.success(listOf<ContactRequestDto>())
|
||||
"newsletters" -> Response.success(NewsletterListResponse(success = true, newsletters = emptyList()))
|
||||
"newsletterGroups" -> Response.success(NewsletterGroupsResponse(success = true, groups = emptyList()))
|
||||
"memberNews" -> Response.success(NewsResponse(success = true, news = listOf(NewsDto(id = 1, title = "N", content = "C"))))
|
||||
"passwordResetDiagnostics" -> Response.success(
|
||||
PasswordResetDiagnosticsResponse(
|
||||
retentionHours = 72,
|
||||
searchedEmail = "",
|
||||
matchingUsers = listOf(
|
||||
de.harheimertc.data.PasswordResetMatchingUserDto(
|
||||
id = "u1",
|
||||
name = "Max Muster",
|
||||
email = "max@example.com",
|
||||
active = true,
|
||||
),
|
||||
),
|
||||
attempts = listOf(attempt),
|
||||
),
|
||||
)
|
||||
"vereinsmeisterschaften" -> Response.success(ResponseBody.create(null, "Jahr,Kategorie,Platz,Spieler1,Spieler2,Bemerkung\n"))
|
||||
else -> throw UnsupportedOperationException("Unhandled ApiService method in test: ${method.name}")
|
||||
}
|
||||
}
|
||||
|
||||
return Proxy.newProxyInstance(
|
||||
ApiService::class.java.classLoader,
|
||||
arrayOf(ApiService::class.java),
|
||||
handler,
|
||||
) as ApiService
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package de.harheimertc.ui.screens.cms
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.hasText
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.junit4.ComposeTestRule
|
||||
import androidx.compose.ui.test.*
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import com.squareup.moshi.Moshi
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.runner.RunWith
|
||||
import retrofit2.Response
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody
|
||||
import de.harheimertc.data.*
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import de.harheimertc.repositories.CmsRepository
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class CmsStartseiteSmokeTest {
|
||||
@get:Rule
|
||||
val composeTestRule = createAndroidComposeRule<ComponentActivity>()
|
||||
|
||||
@Test
|
||||
fun cmsStartseite_rendersWithDefaultState() {
|
||||
// prepare a minimal fake ApiService that returns empty/neutral responses
|
||||
val fakeApi = object : ApiService {
|
||||
override suspend fun publicGalleryImages(): Response<List<PublicGalleryImageDto>> = Response.success(emptyList())
|
||||
override suspend fun postContact(req: ContactRequest): Response<ContactResponse> = Response.success(ContactResponse(ok = true))
|
||||
override suspend fun galerieList(page: Int, perPage: Int): Response<GalleryListResponse> = Response.success(GalleryListResponse())
|
||||
override suspend fun uploadGalleryImage(image: MultipartBody.Part, title: RequestBody, description: RequestBody, isPublic: RequestBody): Response<GalleryUploadResponse> = Response.success(GalleryUploadResponse())
|
||||
override suspend fun termine(): Response<TermineResponse> = Response.success(TermineResponse())
|
||||
override suspend fun spielplan(season: String?): Response<SpielplanResponse> = Response.success(SpielplanResponse())
|
||||
override suspend fun spielplanTable(team: String, season: String?): Response<TeamTableResponse> = Response.success(TeamTableResponse())
|
||||
override suspend fun publicNews(): Response<NewsPublicResponse> = Response.success(NewsPublicResponse())
|
||||
override suspend fun memberNews(): Response<NewsResponse> = Response.success(NewsResponse(success = true, news = emptyList()))
|
||||
override suspend fun saveNews(request: NewsSaveRequest): Response<AuthMessageResponse> = Response.success(AuthMessageResponse(success = true, message = "ok"))
|
||||
override suspend fun deleteNews(id: Int): Response<AuthMessageResponse> = Response.success(AuthMessageResponse(success = true))
|
||||
override suspend fun mannschaften(season: String?): Response<okhttp3.ResponseBody> = Response.success(null)
|
||||
override suspend fun config(): Response<ConfigResponse> = Response.success(ConfigResponse())
|
||||
override suspend fun updateConfig(request: ConfigResponse): Response<ConfigResponse> = Response.success(request)
|
||||
override suspend fun spielsysteme(): Response<okhttp3.ResponseBody> = Response.success(null)
|
||||
override suspend fun vereinsmeisterschaften(): Response<okhttp3.ResponseBody> = Response.success(null)
|
||||
override suspend fun saveCsv(request: SaveCsvRequest): Response<SaveCsvResponse> = Response.success(SaveCsvResponse(success = true))
|
||||
override suspend fun generateMembershipPdf(request: MembershipRequest): Response<MembershipResponse> = Response.success(MembershipResponse())
|
||||
override suspend fun downloadMembershipPdf(downloadUrl: String): Response<okhttp3.ResponseBody> = Response.success(null)
|
||||
override suspend fun login(request: LoginRequest): Response<LoginResponse> = Response.success(LoginResponse())
|
||||
override suspend fun logout(request: LogoutRequest): Response<Unit> = Response.success(Unit)
|
||||
override suspend fun refresh(request: RefreshRequest): Response<LoginResponse> = Response.success(LoginResponse())
|
||||
override suspend fun authStatus(): Response<AuthStatusResponse> = Response.success(AuthStatusResponse())
|
||||
override suspend fun resetPassword(request: ResetPasswordRequest): Response<AuthMessageResponse> = Response.success(AuthMessageResponse())
|
||||
override suspend fun register(request: RegistrationRequest): Response<AuthMessageResponse> = Response.success(AuthMessageResponse())
|
||||
override suspend fun passkeyAuthenticationOptions(request: PasskeyAuthenticationOptionsRequest): Response<okhttp3.ResponseBody> = Response.success(null)
|
||||
override suspend fun passkeyLogin(request: okhttp3.RequestBody): Response<LoginResponse> = Response.success(LoginResponse())
|
||||
override suspend fun passkeys(): Response<PasskeysResponse> = Response.success(PasskeysResponse())
|
||||
override suspend fun passkeyRegistrationOptions(request: PasskeyRegistrationOptionsRequest): Response<okhttp3.ResponseBody> = Response.success(null)
|
||||
override suspend fun registerPasskey(request: okhttp3.RequestBody): Response<AuthMessageResponse> = Response.success(AuthMessageResponse())
|
||||
override suspend fun removePasskey(request: RemovePasskeyRequest): Response<AuthMessageResponse> = Response.success(AuthMessageResponse())
|
||||
override suspend fun profile(): Response<ProfileResponse> = Response.success(ProfileResponse())
|
||||
override suspend fun updateProfile(request: ProfileUpdateRequest): Response<ProfileResponse> = Response.success(ProfileResponse())
|
||||
override suspend fun birthdays(): Response<BirthdaysResponse> = Response.success(BirthdaysResponse())
|
||||
override suspend fun members(): Response<MembersResponse> = Response.success(MembersResponse())
|
||||
override suspend fun saveMember(request: ApiService.MemberSaveRequest): Response<AuthMessageResponse> = Response.success(AuthMessageResponse())
|
||||
override suspend fun deleteMember(body: Map<String, String>): Response<AuthMessageResponse> = Response.success(AuthMessageResponse())
|
||||
override suspend fun bulkImportMembers(request: ApiService.BulkImportRequest): Response<ApiService.BulkImportResponse> = Response.success(ApiService.BulkImportResponse())
|
||||
override suspend fun toggleMannschaftsspieler(body: Map<String, String>): Response<Map<String, Any>> = Response.success(emptyMap())
|
||||
override suspend fun cmsUsers(): Response<CmsUsersResponse> = Response.success(CmsUsersResponse())
|
||||
override suspend fun updateUserRoles(request: ApiService.UpdateUserRolesRequest): Response<AuthMessageResponse> = Response.success(AuthMessageResponse())
|
||||
override suspend fun updateUserActive(request: ApiService.UpdateUserActiveRequest): Response<AuthMessageResponse> = Response.success(AuthMessageResponse())
|
||||
override suspend fun resendInvite(id: String): Response<AuthMessageResponse> = Response.success(AuthMessageResponse())
|
||||
override suspend fun contactRequests(): Response<List<ContactRequestDto>> = Response.success(emptyList())
|
||||
override suspend fun replyToContactRequest(id: String, request: ApiService.ContactReplyRequest): Response<de.harheimertc.data.ContactResponse> = Response.success(de.harheimertc.data.ContactResponse(ok = true))
|
||||
override suspend fun toggleContactRequestStatus(id: String): Response<de.harheimertc.data.ContactResponse> = Response.success(de.harheimertc.data.ContactResponse(ok = true))
|
||||
override suspend fun newsletters(): Response<NewsletterListResponse> = Response.success(NewsletterListResponse(success = true, newsletters = emptyList()))
|
||||
override suspend fun newsletterGroups(): Response<NewsletterGroupsResponse> = Response.success(NewsletterGroupsResponse(success = true, groups = emptyList()))
|
||||
override suspend fun createNewsletterGroup(request: Map<String, Any?>): Response<AuthMessageResponse> = Response.success(AuthMessageResponse(success = true))
|
||||
override suspend fun updateNewsletterGroup(id: String, request: Map<String, Any?>): Response<AuthMessageResponse> = Response.success(AuthMessageResponse(success = true))
|
||||
override suspend fun deleteNewsletterGroup(id: String): Response<AuthMessageResponse> = Response.success(AuthMessageResponse(success = true))
|
||||
override suspend fun createNewsletter(request: NewsletterCreateRequest): Response<NewsletterCreateResponse> = Response.success(NewsletterCreateResponse(success = true))
|
||||
override suspend fun updateNewsletter(id: String, request: Map<String, Any?>): Response<NewsletterCreateResponse> = Response.success(NewsletterCreateResponse(success = true))
|
||||
override suspend fun sendNewsletter(id: String): Response<NewsletterSendResponse> = Response.success(NewsletterSendResponse(success = true))
|
||||
override suspend fun deleteNewsletter(id: String): Response<AuthMessageResponse> = Response.success(AuthMessageResponse(success = true))
|
||||
override suspend fun publicNewsletterGroups(): Response<NewsletterGroupsResponse> = Response.success(NewsletterGroupsResponse(success = true))
|
||||
override suspend fun subscribeNewsletter(request: NewsletterSubscriptionRequest): Response<AuthMessageResponse> = Response.success(AuthMessageResponse(success = true))
|
||||
override suspend fun unsubscribeNewsletter(request: NewsletterSubscriptionRequest): Response<AuthMessageResponse> = Response.success(AuthMessageResponse(success = true))
|
||||
override suspend fun confirmNewsletter(token: String): Response<AuthMessageResponse> = Response.success(AuthMessageResponse(success = true))
|
||||
override suspend fun passwordResetDiagnostics(
|
||||
email: String?,
|
||||
failedOnly: Boolean,
|
||||
): Response<PasswordResetDiagnosticsResponse> = Response.success(PasswordResetDiagnosticsResponse())
|
||||
}
|
||||
|
||||
val context = ApplicationProvider.getApplicationContext<android.content.Context>()
|
||||
val moshi = Moshi.Builder().build()
|
||||
val cache = SecureOfflineCache(context, moshi)
|
||||
val repo = CmsRepository(fakeApi, cache)
|
||||
val vm = de.harheimertc.ui.screens.cms.CmsViewModel(repo)
|
||||
|
||||
// set a ready state to avoid waiting for async network loads in Vm.init
|
||||
val readyState = de.harheimertc.ui.screens.cms.CmsUiState(
|
||||
loading = false,
|
||||
saving = false,
|
||||
error = null,
|
||||
message = null,
|
||||
config = ConfigResponse(),
|
||||
users = emptyList(),
|
||||
contactRequests = emptyList(),
|
||||
newsletters = emptyList(),
|
||||
newsletterGroups = emptyList(),
|
||||
passwordResetAttempts = emptyList(),
|
||||
news = emptyList(),
|
||||
)
|
||||
try {
|
||||
val field = de.harheimertc.ui.screens.cms.CmsViewModel::class.java.getDeclaredField("_state")
|
||||
field.isAccessible = true
|
||||
val current = field.get(vm) as? MutableStateFlow<*>
|
||||
if (current is MutableStateFlow<*>) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
(current as MutableStateFlow<de.harheimertc.ui.screens.cms.CmsUiState>).value = readyState
|
||||
}
|
||||
} catch (_: Throwable) { /* best-effort, continue */ }
|
||||
|
||||
composeTestRule.setContent {
|
||||
val nav = rememberNavController()
|
||||
CmsStartseiteScreen(nav, showBackNavigation = false, viewModel = vm)
|
||||
}
|
||||
|
||||
// dump semantics tree for debugging
|
||||
try {
|
||||
composeTestRule.onRoot().printToLog("CmsStartseiteSmokeTest-SEMTREE")
|
||||
} catch (_: Throwable) { }
|
||||
|
||||
// wait for the main title and info rows to appear
|
||||
fun waitForText(text: String, timeoutMs: Long = 20000L) {
|
||||
composeTestRule.waitUntil(timeoutMs) {
|
||||
try {
|
||||
composeTestRule.onAllNodes(hasText(text, substring = true)).fetchSemanticsNodes().isNotEmpty()
|
||||
} catch (_: AssertionError) { false }
|
||||
}
|
||||
}
|
||||
|
||||
waitForText("Startseite")
|
||||
waitForText("Öffentliche")
|
||||
|
||||
// basic assertions (use substring matching)
|
||||
assertTrue(composeTestRule.onAllNodes(hasText("Startseite", substring = true)).fetchSemanticsNodes().isNotEmpty())
|
||||
assertTrue(composeTestRule.onAllNodes(hasText("Öffentliche", substring = true)).fetchSemanticsNodes().isNotEmpty())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package de.harheimertc.ui.screens.cms
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.uiautomator.By
|
||||
import androidx.test.uiautomator.UiDevice
|
||||
import androidx.test.uiautomator.UiObject2
|
||||
import androidx.test.uiautomator.Until
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class CmsUiAutomatorClickTest {
|
||||
|
||||
@Test
|
||||
fun clickThroughExistingCmsPages_andTrySave() {
|
||||
val instrumentation = InstrumentationRegistry.getInstrumentation()
|
||||
val context = instrumentation.targetContext
|
||||
val device = UiDevice.getInstance(instrumentation)
|
||||
val packageName = "de.harheimertc.local"
|
||||
|
||||
val launchIntent = context.packageManager.getLaunchIntentForPackage(packageName)
|
||||
?.apply {
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
}
|
||||
assertNotNull("Launch-Intent fuer de.harheimertc.local nicht gefunden", launchIntent)
|
||||
context.startActivity(launchIntent)
|
||||
|
||||
device.wait(Until.hasObject(By.pkg(packageName).depth(0)), 15000)
|
||||
|
||||
clickText(device, "Intern")
|
||||
clickText(device, "CMS")
|
||||
|
||||
openCmsCard(device, "Startseite")
|
||||
clickIfPresent(device, "Speichern")
|
||||
backToCmsDashboard(device)
|
||||
|
||||
openCmsCard(device, "Inhalte")
|
||||
clickIfPresent(device, "Inhalte speichern")
|
||||
backToCmsDashboard(device)
|
||||
|
||||
openCmsCard(device, "Vereinsmeisterschaften")
|
||||
clickIfPresent(device, "Speichern")
|
||||
backToCmsDashboard(device)
|
||||
|
||||
openCmsCard(device, "Sportbetrieb")
|
||||
clickIfPresent(device, "Speichern")
|
||||
backToCmsDashboard(device)
|
||||
|
||||
openCmsCard(device, "Einstellungen")
|
||||
clickIfPresent(device, "Speichern")
|
||||
backToCmsDashboard(device)
|
||||
|
||||
openCmsCard(device, "Mitgliederverwaltung")
|
||||
clickIfPresent(device, "Freischalten")
|
||||
backToCmsDashboard(device)
|
||||
|
||||
openCmsCard(device, "Kontaktanfragen")
|
||||
clickIfPresent(device, "Antworten")
|
||||
clickIfPresent(device, "Abbrechen")
|
||||
backToCmsDashboard(device)
|
||||
|
||||
openCmsCard(device, "Newsletter")
|
||||
clickIfPresent(device, "Newsletter erstellen")
|
||||
clickIfPresent(device, "Abbrechen")
|
||||
backToCmsDashboard(device)
|
||||
|
||||
if (openCmsCardIfAvailable(device, "Benutzer")) {
|
||||
clickIfPresent(device, "Rollen")
|
||||
clickIfPresent(device, "Abbrechen")
|
||||
backToCmsDashboard(device)
|
||||
}
|
||||
|
||||
openCmsCardIfAvailable(device, "Passwort-Reset-Diagnose")
|
||||
|
||||
// Wenn wir am Ende noch im App-Paket sind, ist der Flow nicht gecrasht.
|
||||
device.wait(Until.hasObject(By.pkg(packageName).depth(0)), 5000)
|
||||
}
|
||||
|
||||
private fun openCmsCard(device: UiDevice, label: String) {
|
||||
if (!clickIfPresent(device, label, 2500) && !clickTextWithScroll(device, label)) {
|
||||
clickText(device, label)
|
||||
}
|
||||
}
|
||||
|
||||
private fun openCmsCardIfAvailable(device: UiDevice, label: String): Boolean {
|
||||
if (clickIfPresent(device, label, 1500)) return true
|
||||
return clickTextWithScroll(device, label)
|
||||
}
|
||||
|
||||
private fun backToCmsDashboard(device: UiDevice) {
|
||||
if (!clickIfPresent(device, "CMS", 3000)) {
|
||||
device.pressBack()
|
||||
device.waitForIdle()
|
||||
}
|
||||
}
|
||||
|
||||
private fun clickText(device: UiDevice, text: String, timeoutMs: Long = 10000): UiObject2 {
|
||||
val obj = device.wait(Until.findObject(By.textContains(text)), timeoutMs)
|
||||
requireNotNull(obj) { "Text nicht gefunden: $text" }
|
||||
obj.click()
|
||||
device.waitForIdle()
|
||||
return obj
|
||||
}
|
||||
|
||||
private fun clickIfPresent(device: UiDevice, text: String, timeoutMs: Long = 1500): Boolean {
|
||||
val obj = device.wait(Until.findObject(By.textContains(text)), timeoutMs)
|
||||
if (obj != null) {
|
||||
obj.click()
|
||||
device.waitForIdle()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun clickTextWithScroll(device: UiDevice, text: String, maxSwipes: Int = 5): Boolean {
|
||||
if (clickIfPresent(device, text, 1500)) return true
|
||||
repeat(maxSwipes) {
|
||||
device.swipe(
|
||||
device.displayWidth / 2,
|
||||
(device.displayHeight * 0.8).toInt(),
|
||||
device.displayWidth / 2,
|
||||
(device.displayHeight * 0.25).toInt(),
|
||||
24,
|
||||
)
|
||||
device.waitForIdle()
|
||||
if (clickIfPresent(device, text, 1200)) return true
|
||||
}
|
||||
repeat(maxSwipes) {
|
||||
device.swipe(
|
||||
device.displayWidth / 2,
|
||||
(device.displayHeight * 0.25).toInt(),
|
||||
device.displayWidth / 2,
|
||||
(device.displayHeight * 0.8).toInt(),
|
||||
24,
|
||||
)
|
||||
device.waitForIdle()
|
||||
if (clickIfPresent(device, text, 1200)) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user