Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions playground/sandbox-integration-test/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
@Suppress("DSL_SCOPE_VIOLATION")
plugins {
id("convention.sandbox-app")
id("convention.compose")
alias(libs.plugins.kotlin.android)
}

android {
namespace = "com.sdds.playground.integrationtest"
defaultConfig {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
}

dependencies {
implementation("io.github.salute-developers:sdds-serv-compose:+")
implementation("sdds-core:uikit-compose")
implementation("sdds-core:icons")
implementation(libs.base.androidX.compose.foundation)
implementation(libs.base.androidX.activity.compose)
implementation(libs.base.androidX.appcompat)
implementation(libs.base.android.material)
implementation(libs.base.androidX.activity)
implementation(libs.base.androidX.constraintLayout)
androidTestImplementation(libs.base.test.integration.jUnit)
androidTestImplementation(libs.base.test.ui.espresso.core)
androidTestImplementation(libs.base.test.ui.compose.jUnit4)
androidTestImplementation(libs.base.test.integration.rules)
androidTestImplementation(libs.base.test.integration.runner)
androidTestImplementation(platform(libs.base.androidX.compose.bom))

debugImplementation(platform(libs.base.androidX.compose.bom))
debugImplementation(libs.base.test.ui.compose.uiTestManifest)

}
3 changes: 3 additions & 0 deletions playground/sandbox-integration-test/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
versionMajor=0
versionMinor=1
versionPatch=0
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.sdds.playground.integrationtest
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsEnabled
import androidx.compose.ui.test.assertIsNotEnabled
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performImeAction
import androidx.compose.ui.test.performTextInput
import com.sdds.playground.integrationtest.testtags.LoginFormTags
import com.sdds.playground.sandboxhelper.SandboxScenariosIds
import com.sdds.playground.sandboxhelper.createSandboxComposeRule
import org.junit.Rule
import org.junit.Test

class LoginFormTest {

@get:Rule
val composeTestRule = createSandboxComposeRule(SandboxScenariosIds.LOGIN_FORM)

@Test
fun test_input_correct() {
composeTestRule.onNodeWithTag("login_form_open_sheet").performClick()
composeTestRule.onNodeWithTag(LoginFormTags.CONTINUE).assertIsNotEnabled()
composeTestRule.onNodeWithTag(LoginFormTags.EMAIL).performTextInput("demo")
composeTestRule.onNodeWithTag(LoginFormTags.PASSWORD).performTextInput("demo")
composeTestRule.onNodeWithTag(LoginFormTags.CONTINUE).assertIsEnabled()
composeTestRule.onNodeWithTag(LoginFormTags.CONTINUE).performClick()
composeTestRule.onNodeWithTag(LoginFormTags.LOADING_HINT).assertExists()
}

@Test
fun test_input_incorrect() {
composeTestRule.onNodeWithTag("login_form_open_sheet").performClick()
composeTestRule.onNodeWithTag(LoginFormTags.CONTINUE).assertIsNotEnabled()
composeTestRule.onNodeWithTag(LoginFormTags.EMAIL).performTextInput("demodemo")
composeTestRule.onNodeWithTag(LoginFormTags.PASSWORD).performTextInput("demodemo")
composeTestRule.onNodeWithTag(LoginFormTags.CONTINUE).assertIsEnabled()
composeTestRule.onNodeWithTag(LoginFormTags.CONTINUE).performClick()
composeTestRule.onNodeWithTag(LoginFormTags.PASSWORD).performImeAction()
composeTestRule.onNodeWithTag(LoginFormTags.ERROR).assertIsDisplayed()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.sdds.playground.integrationtest

import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.isDisplayed
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTouchInput
import androidx.compose.ui.test.swipeUp
import com.sdds.playground.integrationtest.testtags.ModalTags
import com.sdds.playground.sandboxhelper.SandboxScenariosIds
import com.sdds.playground.sandboxhelper.createSandboxComposeRule
import org.junit.Rule
import org.junit.Test

class ModalScrollbarTest {

@get:Rule
val composeTestRule = createSandboxComposeRule(SandboxScenariosIds.MODAL_SCROLLBAR)

@Test
fun test_modal_scroll_after_close() {
composeTestRule.onNodeWithTag(ModalTags.FIRST_OPEN_BUTTON).performClick()
composeTestRule.onNodeWithTag(ModalTags.CHECK_FIRST_OPENED).isDisplayed()
composeTestRule.onNodeWithTag(ModalTags.FIRST_CLOSE_BUTTON).performClick()
composeTestRule.waitForIdle()
composeTestRule.onNodeWithTag(ModalTags.FIRST_SCROLL_CONTAINER).performTouchInput {
repeat(5) {
swipeUp()
}
}
composeTestRule.onNodeWithText("scroll passed").assertIsDisplayed()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.sdds.playground.integrationtest

import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTextInput
import com.sdds.playground.integrationtest.testtags.LoginFormTags
import com.sdds.playground.integrationtest.testtags.ModalTags
import com.sdds.playground.integrationtest.testtags.ToastTags
import com.sdds.playground.sandboxhelper.SandboxScenariosIds
import com.sdds.playground.sandboxhelper.createSandboxComposeRule
import org.junit.Rule
import org.junit.Test

class ToastModalTest {

@get:Rule
val composeTestRule = createSandboxComposeRule(SandboxScenariosIds.TOAST_MODAL_LOGIN)

@Test
fun test_toast_modal_overlay() {
composeTestRule.onNodeWithTag(ModalTags.FIRST_OPEN_BUTTON).performClick()
composeTestRule.onNodeWithTag(LoginFormTags.EMAIL).performTextInput("demo")
composeTestRule.onNodeWithTag(LoginFormTags.PASSWORD).performTextInput("demo")
composeTestRule.onNodeWithTag(LoginFormTags.CONTINUE).performClick()
composeTestRule.onNodeWithTag(ToastTags.CHECK_TOAST_AFTER_VALID_SUBMIT).assertIsDisplayed()
composeTestRule.onNodeWithTag(ToastTags.TOAST).assertIsDisplayed()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.sdds.playground.integrationtest

import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.click
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTouchInput
import androidx.test.espresso.Espresso.pressBack
import com.sdds.playground.integrationtest.testtags.TooltipTags
import com.sdds.playground.sandboxhelper.SandboxScenariosIds
import com.sdds.playground.sandboxhelper.createSandboxComposeRule
import org.junit.Rule
import org.junit.Test

class TooltipTest {

@get:Rule
val composeTestRule = createSandboxComposeRule(SandboxScenariosIds.TOOLTIP_CLOSE)

@Test
fun test_close_tooltip_with_btn_in_shadow() {
composeTestRule.onNodeWithTag(TooltipTags.FIRST_OPEN_BUTTON).performClick()
composeTestRule.onNodeWithTag(TooltipTags.CHECK_FIRST_OPENED).assertIsDisplayed()
composeTestRule.onNodeWithTag(TooltipTags.FIRST_CLOSE_BUTTON).performClick()
composeTestRule.onNodeWithTag(TooltipTags.FIRST_TOOLTIP).assertIsNotDisplayed()
}

@Test
fun test_close_tooltip_with_tap_outside() {
composeTestRule.onNodeWithTag(TooltipTags.SECOND_OPEN_BUTTON).performClick()
composeTestRule.onNodeWithTag(TooltipTags.SECOND_TOOLTIP).assertIsDisplayed()
composeTestRule.onNodeWithTag(TooltipTags.ROOT).performTouchInput {
click(Offset(x = center.x, y = bottom - 10f))
}
composeTestRule.onNodeWithTag(TooltipTags.SECOND_TOOLTIP).assertIsNotDisplayed()
}

@Test
fun test_close_tooltip_with_pressBack() {
composeTestRule.onNodeWithTag(TooltipTags.SECOND_OPEN_BUTTON).performClick()
composeTestRule.onNodeWithTag(TooltipTags.SECOND_TOOLTIP).assertIsDisplayed()
pressBack()
composeTestRule.onNodeWithTag(TooltipTags.SECOND_TOOLTIP).assertIsNotDisplayed()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.sdds.playground.sandboxhelper

import android.content.Context
import android.content.Intent
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.rules.ActivityScenarioRule
import com.sdds.playground.integrationtest.sandbox.AppActivity

fun createSandboxComposeRule(
scenarioId: String,
): AndroidComposeTestRule<ActivityScenarioRule<AppActivity>, AppActivity> {
val context = ApplicationProvider.getApplicationContext<Context>()

val intent = Intent(context, AppActivity::class.java).apply {
putExtra(AppActivity.EXTRA_SCENARIO_ID, scenarioId)
}

return AndroidComposeTestRule(
activityRule = ActivityScenarioRule<AppActivity>(intent),
activityProvider = { rule ->
var activity: AppActivity? = null
rule.scenario.onActivity { activity = it }
activity ?: error("Activity не запущен")
},
)
}

internal object SandboxScenariosIds {
const val LOGIN_FORM = "login-form-basic"
const val TOOLTIP_CLOSE = "popup-tooltip-basic"
const val MODAL_SCROLLBAR = "modal-scrollbar-basic"
const val TOAST_MODAL_LOGIN = "toast-modal-login-basic"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<application
android:label="@string/app_name"
android:supportsRtl="true">
<activity
android:name=".sandbox.AppActivity"
android:exported="true"
android:launchMode="singleTask"
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package com.sdds.playground.integrationtest.app

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.sdds.compose.uikit.Button
import com.sdds.compose.uikit.Text
import com.sdds.compose.uikit.style.style
import com.sdds.playground.integrationtest.scenarios.catalog.IntegrationScenario
import com.sdds.playground.integrationtest.scenarios.catalog.IntegrationScenarioRegistry
import com.sdds.playground.integrationtest.theme.IntegrationSandboxTheme
import com.sdds.serv.styles.basicbutton.BasicButton
import com.sdds.serv.styles.basicbutton.S
import com.sdds.serv.styles.basicbutton.Secondary

/**
* Приложение для интеграционных сценариев
*/
@Composable
internal fun IntegrationSandboxApp(
initialScenarioId: String? = null,
) {
var selectedScenario by remember {
mutableStateOf(
IntegrationScenarioRegistry.scenarios
.firstOrNull { it.id == initialScenarioId },
)
}

IntegrationSandboxTheme {
Box(
modifier = Modifier
.fillMaxSize(),
) {
if (selectedScenario == null) {
ScenarioListScreen(
scenarios = IntegrationScenarioRegistry.scenarios,
onScenarioClick = { selectedScenario = it },
)
} else {
ScenarioDetailsScreen(
scenario = selectedScenario!!,
onBack = { selectedScenario = null },
)
}
}
}
}

@Composable
private fun ScenarioListScreen(
scenarios: List<IntegrationScenario>,
onScenarioClick: (IntegrationScenario) -> Unit,
) {
Column(
modifier = Modifier
.fillMaxSize()
.statusBarsPadding(),
) {
Text(
text = "Integration Sandbox",
modifier = Modifier.padding(horizontal = 20.dp, vertical = 24.dp),
)
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(horizontal = 20.dp, vertical = 8.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
items(scenarios, key = { it.id }) { scenario ->
ScenarioListItem(
scenario = scenario,
onClick = { onScenarioClick(scenario) },
)
}
}
}
}

@Composable
private fun ScenarioListItem(
scenario: IntegrationScenario,
onClick: () -> Unit,
) {
Column(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onClick)
.padding(vertical = 8.dp),
verticalArrangement = Arrangement.spacedBy(6.dp),
) {
Text(text = scenario.category.title)
Text(text = scenario.title)
Text(text = scenario.description)
}
}

@Composable
private fun ScenarioDetailsScreen(
scenario: IntegrationScenario,
onBack: () -> Unit,
) {
Column(
modifier = Modifier
.fillMaxSize()
.statusBarsPadding()
.padding(20.dp),
verticalArrangement = Arrangement.spacedBy(20.dp),
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(text = scenario.title)
Button(
label = "Back",
style = BasicButton.S.Secondary.style(),
onClick = onBack,
)
}
scenario.screen()
}
}
Loading
Loading