Added compose integration tests

commit_hash:b8423b44ec162dc4882e3be7cf8fc61fab2acbb3
This commit is contained in:
pkurchatov
2026-03-25 14:46:47 +03:00
parent b80ce9d8f0
commit fa8c8bbbf4
28 changed files with 487 additions and 341 deletions
@@ -4,8 +4,8 @@ import android.content.ContextWrapper
import androidx.annotation.MainThread
import androidx.annotation.VisibleForTesting
import com.yandex.div.compose.dagger.DivContextComponent
import com.yandex.div.compose.internal.DivDebugFeatures
import com.yandex.div.compose.views.DivLocalContext
import com.yandex.div.compose.internal.DivActionPerformer
import com.yandex.div.core.annotations.InternalApi
import com.yandex.div.core.annotations.PublicApi
import com.yandex.div.core.expression.variables.DivVariableController
@@ -20,8 +20,8 @@ class DivContext @Inject @MainThread internal constructor(
@InternalApi
@VisibleForTesting
val actionPerformer: DivActionPerformer
get() = component.actionPerformer
val debugFeatures: DivDebugFeatures
get() = component.debugFeatures
internal fun createLocalContext(
variableController: DivVariableController,
@@ -42,8 +42,6 @@ class DivContext @Inject @MainThread internal constructor(
localComponent.triggerStorage.add(it)
}
val context = localComponent.context
actionPerformer.actionHandlingContext = context.actionHandlingContext
return context
return localComponent.context
}
}
@@ -6,7 +6,7 @@ import com.yandex.div.compose.DivComposeConfiguration
import com.yandex.div.compose.DivReporter
import com.yandex.div.compose.actions.DivActionHandler
import com.yandex.div.core.expression.variables.DivVariableController
import com.yandex.div.compose.internal.DivActionPerformer
import com.yandex.div.compose.internal.DivDebugFeatures
import com.yandex.yatagan.BindsInstance
import com.yandex.yatagan.Component
import javax.inject.Named
@@ -21,10 +21,10 @@ import javax.inject.Named
internal interface DivContextComponent {
val actionHandler: DivActionHandler
val actionPerformer: DivActionPerformer
val baseContext: Context
val reporter: DivReporter
val debugFeatures: DivDebugFeatures
val imageLoader: ImageLoader
val reporter: DivReporter
@get:Named(Names.HOST_VARIABLES)
val variableController: DivVariableController
@@ -15,6 +15,7 @@ import com.yandex.div.evaluable.function.GeneratedBuiltinFunctionProvider
import com.yandex.div.internal.parser.Converter
import com.yandex.div.internal.parser.TypeHelper
import com.yandex.div.internal.parser.ValueValidator
import com.yandex.div.internal.variables.variableValueToEvaluableValue
import com.yandex.div.json.ParsingErrorLogger
import com.yandex.div.json.expressions.ExpressionResolver
import com.yandex.div.json.invalidValue
@@ -37,7 +38,9 @@ internal class DivComposeExpressionResolver @Inject constructor(
init {
val evaluationContext = EvaluationContext(
variableProvider = { name -> variableController.get(name)?.getValue() },
variableProvider = { name ->
variableController.get(name)?.getValue().variableValueToEvaluableValue()
},
storedValueProvider = { _ -> null },
functionProvider = GeneratedBuiltinFunctionProvider,
warningSender = EvaluatorWarningSender(reporter)
@@ -1,22 +0,0 @@
package com.yandex.div.compose.internal
import com.yandex.div.compose.actions.DivActionHandler
import com.yandex.div.compose.actions.DivActionHandlingContext
import com.yandex.div.compose.dagger.DivContextScope
import com.yandex.div.core.annotations.InternalApi
import com.yandex.div2.DivAction
import javax.inject.Inject
@DivContextScope
@InternalApi
class DivActionPerformer @Inject internal constructor(
private val actionHandler: DivActionHandler
) {
internal var actionHandlingContext: DivActionHandlingContext? = null
fun perform(action: DivAction) {
actionHandlingContext?.let {
actionHandler.handle(context = it, action = action)
}
}
}
@@ -0,0 +1,26 @@
package com.yandex.div.compose.internal
import com.yandex.div.compose.actions.DivActionHandler
import com.yandex.div.compose.dagger.DivContextScope
import com.yandex.div.compose.views.DivLocalContext
import com.yandex.div.core.annotations.InternalApi
import com.yandex.div.json.expressions.ExpressionResolver
import com.yandex.div2.DivAction
import javax.inject.Inject
@DivContextScope
@InternalApi
class DivDebugFeatures @Inject internal constructor(
private val actionHandler: DivActionHandler
) {
internal var lastViewLocalContext: DivLocalContext? = null
val expressionResolver: ExpressionResolver?
get() = lastViewLocalContext?.expressionResolver
fun performAction(action: DivAction) {
lastViewLocalContext?.let {
actionHandler.handle(context = it.actionHandlingContext, action = action)
}
}
}
@@ -33,7 +33,9 @@ internal fun WithLocalDivContext(data: DivData, content: @Composable () -> Unit)
variableController = DivVariableController(divContext.component.variableController),
triggers = data.variableTriggers.orEmpty(),
variables = data.variables.orEmpty()
)
).also {
divContext.debugFeatures.lastViewLocalContext = it
}
}
CompositionLocalProvider(LocalDivContext provides localContext, content)
}
@@ -0,0 +1,110 @@
package com.yandex.div.compose
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.core.app.ApplicationProvider.getApplicationContext
import com.yandex.div.core.expression.variables.DivVariableController
import com.yandex.div.test.crossplatform.IntegrationTestCase
import com.yandex.div.test.crossplatform.IntegrationTestCaseParser
import com.yandex.div.test.crossplatform.ParsingResult
import com.yandex.div.test.crossplatform.ParsingUtils
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.ParameterizedRobolectricTestRunner
@RunWith(ParameterizedRobolectricTestRunner::class)
class IntegrationTest(testCaseParsingResult: ParsingResult<IntegrationTestCase>) {
private val testCase = testCaseParsingResult.getOrThrow()
@get:Rule
val rule = createComposeRule()
@Test
fun run() {
val divData = testCase.parseDivData() ?: return
val variableController = DivVariableController()
val divContext = DivComposeConfiguration(
reporter = TestReporter(),
variableController = variableController
).createContext(baseContext = getApplicationContext())
testCase.declareResultVariables(
variables = divData.variables ?: emptyList(),
variableController = variableController
)
rule.setContent {
CompositionLocalProvider(LocalContext provides divContext) {
DivView(data = divData)
}
}
testCase.parseActions().forEach {
divContext.debugFeatures.performAction(it)
}
testCase.checkResult(
expressionResolver = divContext.debugFeatures.expressionResolver!!
)
}
companion object {
// Store parsed test cases to prevent multiple parsing by
// ParameterizedRobolectricTestRunner
private val cases: List<ParsingResult<IntegrationTestCase>> = run {
ParsingUtils.parseFiles("integration_test_data") { file, json ->
if (ignoredFiles.contains(file.name)) {
emptyList()
} else {
IntegrationTestCaseParser.parseCases(file.name, json)
}
}
}
@JvmStatic
@Suppress("unused")
@ParameterizedRobolectricTestRunner.Parameters(name = "{0}")
fun cases() = cases
}
}
private val ignoredFiles = listOf(
"array_variable_mutation.json",
"decl_expressions_item_builder.json",
"decl_expressions_item_builder_override.json",
"dict_set_value.json",
"expression_with_several_local_functions.json",
"item_builder_variable_triggers.json",
"local_functions_array.json",
"local_functions_datetime.json",
"local_functions_color.json",
"local_functions_dict.json",
"local_functions_div_data.json",
"local_functions_int.json",
"local_functions_number.json",
"local_functions_string.json",
"local_functions_url.json",
"local-triggers-gallery.json",
"local-triggers-gallery-with-item-builder.json",
"local-triggers-states.json",
"local-triggers-tabs.json",
"properties_cycled.json",
"property_boolean_value_from_dict.json",
"property_color_value_from_array.json",
"property_color_value_from_dict.json",
"property_cycle.json",
"property_integer_value_from_dict.json",
"property_new_value_variable_name.json",
"property_number_value_from_dict.json",
"property_string_value_and_variable_from_dict.json",
"property_string_value_from_dict.json",
"property_string_value_from_variable.json",
"property_url_value_from_dict.json",
"property_without_setter.json",
"update_structure.json",
"wrap_content_constraints_warning.json",
)