diff --git a/.mapping.json b/.mapping.json index 4479ee807..67abe9a60 100644 --- a/.mapping.json +++ b/.mapping.json @@ -622,7 +622,7 @@ "client/android/compose/src/main/kotlin/com/yandex/div/compose/dagger/Names.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/dagger/Names.kt", "client/android/compose/src/main/kotlin/com/yandex/div/compose/expressions/DivComposeExpressionResolver.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/expressions/DivComposeExpressionResolver.kt", "client/android/compose/src/main/kotlin/com/yandex/div/compose/expressions/EvaluatorWarningSender.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/expressions/EvaluatorWarningSender.kt", - "client/android/compose/src/main/kotlin/com/yandex/div/compose/internal/DivActionPerformer.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/internal/DivActionPerformer.kt", + "client/android/compose/src/main/kotlin/com/yandex/div/compose/internal/DivDebugFeatures.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/internal/DivDebugFeatures.kt", "client/android/compose/src/main/kotlin/com/yandex/div/compose/internal/ImageLoaderProvider.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/internal/ImageLoaderProvider.kt", "client/android/compose/src/main/kotlin/com/yandex/div/compose/triggers/DivTriggerStorage.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/triggers/DivTriggerStorage.kt", "client/android/compose/src/main/kotlin/com/yandex/div/compose/utils/Alignment.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/utils/Alignment.kt", @@ -660,6 +660,7 @@ "client/android/compose/src/main/kotlin/com/yandex/div/compose/views/modifiers/Modifiers.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/views/modifiers/Modifiers.kt", "client/android/compose/src/main/kotlin/com/yandex/div/compose/views/modifiers/SizeModifiers.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/views/modifiers/SizeModifiers.kt", "client/android/compose/src/test/kotlin/com/yandex/div/compose/DivViewTest.kt":"divkit/public/client/android/compose/src/test/kotlin/com/yandex/div/compose/DivViewTest.kt", + "client/android/compose/src/test/kotlin/com/yandex/div/compose/IntegrationTest.kt":"divkit/public/client/android/compose/src/test/kotlin/com/yandex/div/compose/IntegrationTest.kt", "client/android/compose/src/test/kotlin/com/yandex/div/compose/TestReporter.kt":"divkit/public/client/android/compose/src/test/kotlin/com/yandex/div/compose/TestReporter.kt", "client/android/compose/src/test/kotlin/com/yandex/div/compose/actions/DivActionHandlerTest.kt":"divkit/public/client/android/compose/src/test/kotlin/com/yandex/div/compose/actions/DivActionHandlerTest.kt", "client/android/compose/src/test/kotlin/com/yandex/div/compose/actions/SetVariableActionHandlerTest.kt":"divkit/public/client/android/compose/src/test/kotlin/com/yandex/div/compose/actions/SetVariableActionHandlerTest.kt", @@ -1723,7 +1724,6 @@ "client/android/div/src/test/java/com/yandex/div/core/widget/GridContainerDsl.kt":"divkit/public/client/android/div/src/test/java/com/yandex/div/core/widget/GridContainerDsl.kt", "client/android/div/src/test/java/com/yandex/div/core/widget/GridContainerTest.kt":"divkit/public/client/android/div/src/test/java/com/yandex/div/core/widget/GridContainerTest.kt", "client/android/div/src/test/java/com/yandex/div/interactive/IntegrationMultiplatformTest.kt":"divkit/public/client/android/div/src/test/java/com/yandex/div/interactive/IntegrationMultiplatformTest.kt", - "client/android/div/src/test/java/com/yandex/div/interactive/IntegrationTestLogger.kt":"divkit/public/client/android/div/src/test/java/com/yandex/div/interactive/IntegrationTestLogger.kt", "client/android/div/src/test/java/com/yandex/div/internal/storage/DataStorageTest.kt":"divkit/public/client/android/div/src/test/java/com/yandex/div/internal/storage/DataStorageTest.kt", "client/android/div/src/test/java/com/yandex/div/internal/viewpool/ProfilingSessionExtensionTest.kt":"divkit/public/client/android/div/src/test/java/com/yandex/div/internal/viewpool/ProfilingSessionExtensionTest.kt", "client/android/div/src/test/java/com/yandex/div/internal/widget/AutoEllipsizeHelperTest.kt":"divkit/public/client/android/div/src/test/java/com/yandex/div/internal/widget/AutoEllipsizeHelperTest.kt", @@ -17181,7 +17181,8 @@ "client/android/settings.gradle":"divkit/public/client/android/settings.gradle", "client/android/test-utils/build.gradle.kts":"divkit/public/client/android/test-utils/build.gradle.kts", "client/android/test-utils/src/main/kotlin/com/yandex/div/test/crossplatform/IntegrationTestCase.kt":"divkit/public/client/android/test-utils/src/main/kotlin/com/yandex/div/test/crossplatform/IntegrationTestCase.kt", - "client/android/test-utils/src/main/kotlin/com/yandex/div/test/crossplatform/MultiplatformTestUtils.kt":"divkit/public/client/android/test-utils/src/main/kotlin/com/yandex/div/test/crossplatform/MultiplatformTestUtils.kt", + "client/android/test-utils/src/main/kotlin/com/yandex/div/test/crossplatform/IntegrationTestCaseParser.kt":"divkit/public/client/android/test-utils/src/main/kotlin/com/yandex/div/test/crossplatform/IntegrationTestCaseParser.kt", + "client/android/test-utils/src/main/kotlin/com/yandex/div/test/crossplatform/IntegrationTestLogger.kt":"divkit/public/client/android/test-utils/src/main/kotlin/com/yandex/div/test/crossplatform/IntegrationTestLogger.kt", "client/android/test-utils/src/main/kotlin/com/yandex/div/test/crossplatform/ParsingResult.kt":"divkit/public/client/android/test-utils/src/main/kotlin/com/yandex/div/test/crossplatform/ParsingResult.kt", "client/android/test-utils/src/main/kotlin/com/yandex/div/test/crossplatform/ParsingUtils.kt":"divkit/public/client/android/test-utils/src/main/kotlin/com/yandex/div/test/crossplatform/ParsingUtils.kt", "client/android/test-utils/src/main/kotlin/com/yandex/div/test/data/DivActionUtils.kt":"divkit/public/client/android/test-utils/src/main/kotlin/com/yandex/div/test/data/DivActionUtils.kt", @@ -26607,7 +26608,7 @@ "test_data/integration_test_data/properties/property_color_value_from_dict.json":"divkit/public/test_data/integration_test_data/properties/property_color_value_from_dict.json", "test_data/integration_test_data/properties/property_cycle.json":"divkit/public/test_data/integration_test_data/properties/property_cycle.json", "test_data/integration_test_data/properties/property_integer_value_from_dict.json":"divkit/public/test_data/integration_test_data/properties/property_integer_value_from_dict.json", - "test_data/integration_test_data/properties/property_new_value_varaible_name.json":"divkit/public/test_data/integration_test_data/properties/property_new_value_varaible_name.json", + "test_data/integration_test_data/properties/property_new_value_variable_name.json":"divkit/public/test_data/integration_test_data/properties/property_new_value_variable_name.json", "test_data/integration_test_data/properties/property_number_value_from_dict.json":"divkit/public/test_data/integration_test_data/properties/property_number_value_from_dict.json", "test_data/integration_test_data/properties/property_string_value_and_variable_from_dict.json":"divkit/public/test_data/integration_test_data/properties/property_string_value_and_variable_from_dict.json", "test_data/integration_test_data/properties/property_string_value_from_dict.json":"divkit/public/test_data/integration_test_data/properties/property_string_value_from_dict.json", diff --git a/client/android/compose/build.gradle.kts b/client/android/compose/build.gradle.kts index 2625cb28a..a2f05d465 100644 --- a/client/android/compose/build.gradle.kts +++ b/client/android/compose/build.gradle.kts @@ -38,4 +38,5 @@ dependencies { testImplementation(project(":test-utils")) testImplementation(libs.androidx.compose.ui.test.junit4) testImplementation(libs.androidx.compose.ui.test.manifest) + testImplementation(libs.json) } diff --git a/client/android/compose/src/main/kotlin/com/yandex/div/compose/DivContext.kt b/client/android/compose/src/main/kotlin/com/yandex/div/compose/DivContext.kt index 839ce1b98..003243826 100644 --- a/client/android/compose/src/main/kotlin/com/yandex/div/compose/DivContext.kt +++ b/client/android/compose/src/main/kotlin/com/yandex/div/compose/DivContext.kt @@ -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 } } diff --git a/client/android/compose/src/main/kotlin/com/yandex/div/compose/dagger/DivContextComponent.kt b/client/android/compose/src/main/kotlin/com/yandex/div/compose/dagger/DivContextComponent.kt index 56b152bac..ecf7d5a3d 100644 --- a/client/android/compose/src/main/kotlin/com/yandex/div/compose/dagger/DivContextComponent.kt +++ b/client/android/compose/src/main/kotlin/com/yandex/div/compose/dagger/DivContextComponent.kt @@ -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 diff --git a/client/android/compose/src/main/kotlin/com/yandex/div/compose/expressions/DivComposeExpressionResolver.kt b/client/android/compose/src/main/kotlin/com/yandex/div/compose/expressions/DivComposeExpressionResolver.kt index 3e521b9b8..1d8e1c274 100644 --- a/client/android/compose/src/main/kotlin/com/yandex/div/compose/expressions/DivComposeExpressionResolver.kt +++ b/client/android/compose/src/main/kotlin/com/yandex/div/compose/expressions/DivComposeExpressionResolver.kt @@ -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) diff --git a/client/android/compose/src/main/kotlin/com/yandex/div/compose/internal/DivActionPerformer.kt b/client/android/compose/src/main/kotlin/com/yandex/div/compose/internal/DivActionPerformer.kt deleted file mode 100644 index e22d82a40..000000000 --- a/client/android/compose/src/main/kotlin/com/yandex/div/compose/internal/DivActionPerformer.kt +++ /dev/null @@ -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) - } - } -} diff --git a/client/android/compose/src/main/kotlin/com/yandex/div/compose/internal/DivDebugFeatures.kt b/client/android/compose/src/main/kotlin/com/yandex/div/compose/internal/DivDebugFeatures.kt new file mode 100644 index 000000000..2163539a6 --- /dev/null +++ b/client/android/compose/src/main/kotlin/com/yandex/div/compose/internal/DivDebugFeatures.kt @@ -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) + } + } +} diff --git a/client/android/compose/src/main/kotlin/com/yandex/div/compose/views/DivLocalContext.kt b/client/android/compose/src/main/kotlin/com/yandex/div/compose/views/DivLocalContext.kt index 65530889d..e91a5611a 100644 --- a/client/android/compose/src/main/kotlin/com/yandex/div/compose/views/DivLocalContext.kt +++ b/client/android/compose/src/main/kotlin/com/yandex/div/compose/views/DivLocalContext.kt @@ -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) } diff --git a/client/android/compose/src/test/kotlin/com/yandex/div/compose/IntegrationTest.kt b/client/android/compose/src/test/kotlin/com/yandex/div/compose/IntegrationTest.kt new file mode 100644 index 000000000..731966900 --- /dev/null +++ b/client/android/compose/src/test/kotlin/com/yandex/div/compose/IntegrationTest.kt @@ -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) { + + 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> = 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", +) diff --git a/client/android/div-data/src/main/java/com/yandex/div/internal/variables/DivVariableUtils.kt b/client/android/div-data/src/main/java/com/yandex/div/internal/variables/DivVariableUtils.kt index aa271cda9..f60a571fa 100644 --- a/client/android/div-data/src/main/java/com/yandex/div/internal/variables/DivVariableUtils.kt +++ b/client/android/div-data/src/main/java/com/yandex/div/internal/variables/DivVariableUtils.kt @@ -1,7 +1,9 @@ package com.yandex.div.internal.variables +import android.net.Uri import com.yandex.div.core.annotations.InternalApi import com.yandex.div.data.Variable +import com.yandex.div.evaluable.types.Url import com.yandex.div.internal.data.PropertyDelegate import com.yandex.div.internal.data.PropertyVariableExecutor import com.yandex.div.internal.expressions.DivExpressionParser.readTypedExpression @@ -71,6 +73,34 @@ fun PropertyVariable.parseGet( return expression } +@InternalApi +val DivVariable.name: String + get() { + return when (this) { + is DivVariable.Bool -> this.value.name + is DivVariable.Integer -> this.value.name + is DivVariable.Number -> this.value.name + is DivVariable.Str -> this.value.name + is DivVariable.Color -> this.value.name + is DivVariable.Url -> this.value.name + is DivVariable.Dict -> this.value.name + is DivVariable.Array -> this.value.name + is DivVariable.Property -> this.value.name + } + } + +/** + * Converts [DivVariable] value to the value that can be used in + * [com.yandex.div.evaluable.VariableProvider]. + */ +@InternalApi +fun Any?.variableValueToEvaluableValue(): Any? { + return when(this) { + is Uri -> Url(toString()) + else -> this + } +} + private fun PropertyVariable.toVariable( resolver: ExpressionResolver, propertyVariableExecutor: PropertyVariableExecutor, diff --git a/client/android/div-evaluable/src/main/java/com/yandex/div/evaluable/VariableProvider.kt b/client/android/div-evaluable/src/main/java/com/yandex/div/evaluable/VariableProvider.kt index 07a06512a..7f3f8a7ed 100644 --- a/client/android/div-evaluable/src/main/java/com/yandex/div/evaluable/VariableProvider.kt +++ b/client/android/div-evaluable/src/main/java/com/yandex/div/evaluable/VariableProvider.kt @@ -8,4 +8,4 @@ fun interface VariableProvider { * @return variable value or null if it is missing. */ fun get(name: String): Any? -} \ No newline at end of file +} diff --git a/client/android/div/src/main/java/com/yandex/div/core/actions/DivActionTypedSubmitHandler.kt b/client/android/div/src/main/java/com/yandex/div/core/actions/DivActionTypedSubmitHandler.kt index 45b5ddab1..600a4620c 100644 --- a/client/android/div/src/main/java/com/yandex/div/core/actions/DivActionTypedSubmitHandler.kt +++ b/client/android/div/src/main/java/com/yandex/div/core/actions/DivActionTypedSubmitHandler.kt @@ -2,8 +2,6 @@ package com.yandex.div.core.actions import com.yandex.div.core.DivActionHandler import com.yandex.div.core.DivRequestExecutor -import com.yandex.div.core.expression.getWrappedValue -import com.yandex.div.core.expression.name import com.yandex.div.core.state.DivStatePath import com.yandex.div.core.view2.BindingContext import com.yandex.div.core.view2.Div2View @@ -11,6 +9,8 @@ import com.yandex.div.evaluable.MissingVariableException import com.yandex.div.internal.core.DivItemBuilderResult import com.yandex.div.internal.core.DivTreeVisitor import com.yandex.div.internal.core.toItemBuilderResult +import com.yandex.div.internal.variables.name +import com.yandex.div.internal.variables.variableValueToEvaluableValue import com.yandex.div.json.expressions.ExpressionResolver import com.yandex.div2.Div import com.yandex.div2.DivAction @@ -60,7 +60,7 @@ class DivActionTypedSubmitHandler @Inject constructor( variables.forEach { val name = it.name container.expressionResolver.getVariable(name)?.let { - variable -> body.put(name, variable.getWrappedValue()) + variable -> body.put(name, variable.getValue().variableValueToEvaluableValue()) } ?: view.logError(MissingVariableException(name)) } return body.toString() diff --git a/client/android/div/src/main/java/com/yandex/div/core/expression/RuntimeStoreProvider.kt b/client/android/div/src/main/java/com/yandex/div/core/expression/RuntimeStoreProvider.kt index 28a2f1d86..9173c07c8 100644 --- a/client/android/div/src/main/java/com/yandex/div/core/expression/RuntimeStoreProvider.kt +++ b/client/android/div/src/main/java/com/yandex/div/core/expression/RuntimeStoreProvider.kt @@ -13,6 +13,7 @@ import com.yandex.div.core.view2.errors.ErrorCollector import com.yandex.div.core.view2.errors.ErrorCollectors import com.yandex.div.data.Variable import com.yandex.div.internal.data.PropertyVariableExecutor +import com.yandex.div.internal.variables.name import com.yandex.div.internal.variables.parseGet import com.yandex.div2.DivData import com.yandex.div2.DivVariable @@ -125,18 +126,3 @@ internal class RuntimeStoreProvider @Inject constructor( } } } - -internal val DivVariable.name: String - get() { - return when (this) { - is DivVariable.Bool -> this.value.name - is DivVariable.Integer -> this.value.name - is DivVariable.Number -> this.value.name - is DivVariable.Str -> this.value.name - is DivVariable.Color -> this.value.name - is DivVariable.Url -> this.value.name - is DivVariable.Dict -> this.value.name - is DivVariable.Array -> this.value.name - is DivVariable.Property -> this.value.name - } - } diff --git a/client/android/div/src/main/java/com/yandex/div/core/expression/utils.kt b/client/android/div/src/main/java/com/yandex/div/core/expression/utils.kt index 0274da54b..585b2b5c2 100644 --- a/client/android/div/src/main/java/com/yandex/div/core/expression/utils.kt +++ b/client/android/div/src/main/java/com/yandex/div/core/expression/utils.kt @@ -1,9 +1,5 @@ package com.yandex.div.core.expression -import com.yandex.div.core.expression.variables.wrapVariableValue -import com.yandex.div.data.Variable import com.yandex.div.json.expressions.ExpressionResolver internal val ExpressionResolver.asImpl get() = this as? ExpressionResolverImpl - -internal fun Variable.getWrappedValue() = getValue().wrapVariableValue() diff --git a/client/android/div/src/main/java/com/yandex/div/core/expression/variables/VariableAndConstantController.kt b/client/android/div/src/main/java/com/yandex/div/core/expression/variables/VariableAndConstantController.kt index c7e6b72a9..98168825d 100644 --- a/client/android/div/src/main/java/com/yandex/div/core/expression/variables/VariableAndConstantController.kt +++ b/client/android/div/src/main/java/com/yandex/div/core/expression/variables/VariableAndConstantController.kt @@ -4,6 +4,7 @@ import com.yandex.div.core.Disposable import com.yandex.div.core.view2.errors.ErrorCollector import com.yandex.div.data.Variable import com.yandex.div.internal.variables.VariableSource +import com.yandex.div.internal.variables.variableValueToEvaluableValue import com.yandex.div.json.expressions.ExpressionResolver internal class VariableAndConstantController( @@ -11,7 +12,9 @@ internal class VariableAndConstantController( private val constants: ConstantsProvider, ) : VariableController { - override fun get(name: String) = constants.get(name).wrapVariableValue() ?: delegate.get(name) + override fun get(name: String): Any? { + return constants.get(name).variableValueToEvaluableValue() ?: delegate.get(name) + } override fun subscribeToVariablesChange( names: List, diff --git a/client/android/div/src/main/java/com/yandex/div/core/expression/variables/VariableController.kt b/client/android/div/src/main/java/com/yandex/div/core/expression/variables/VariableController.kt index b9ee6e7e1..f4a5a4207 100644 --- a/client/android/div/src/main/java/com/yandex/div/core/expression/variables/VariableController.kt +++ b/client/android/div/src/main/java/com/yandex/div/core/expression/variables/VariableController.kt @@ -1,12 +1,10 @@ package com.yandex.div.core.expression.variables -import android.net.Uri import com.yandex.div.core.Disposable import com.yandex.div.core.view2.errors.ErrorCollector import com.yandex.div.data.Variable import com.yandex.div.data.VariableDeclarationException import com.yandex.div.evaluable.VariableProvider -import com.yandex.div.evaluable.types.Url import com.yandex.div.internal.data.PropertyVariableExecutor import com.yandex.div.internal.variables.VariableSource import com.yandex.div.internal.variables.toVariable @@ -48,11 +46,6 @@ internal interface VariableController : VariableProvider { fun captureAll(): List = emptyList() } -internal fun Any?.wrapVariableValue() = when(this) { - is Uri -> Url(this.toString()) - else -> this -} - internal fun VariableController.declare( divVariable: DivVariable, resolver: ExpressionResolver, diff --git a/client/android/div/src/main/java/com/yandex/div/core/expression/variables/VariableControllerImpl.kt b/client/android/div/src/main/java/com/yandex/div/core/expression/variables/VariableControllerImpl.kt index 6a57a19b3..0ee6d949c 100644 --- a/client/android/div/src/main/java/com/yandex/div/core/expression/variables/VariableControllerImpl.kt +++ b/client/android/div/src/main/java/com/yandex/div/core/expression/variables/VariableControllerImpl.kt @@ -11,6 +11,7 @@ import com.yandex.div.data.VariableDeclarationException import com.yandex.div.internal.util.UiThreadHandler import com.yandex.div.internal.variables.DeclarationObserver import com.yandex.div.internal.variables.VariableSource +import com.yandex.div.internal.variables.variableValueToEvaluableValue import com.yandex.div.json.expressions.ExpressionResolver import com.yandex.div.json.missingVariable import java.util.Collections @@ -42,7 +43,9 @@ internal class VariableControllerImpl( observers.addObserver(observer) } - override fun get(name: String) = getMutableVariable(name)?.getValue().wrapVariableValue() ?: delegate?.get(name) + override fun get(name: String): Any? { + return getMutableVariable(name)?.getValue().variableValueToEvaluableValue() ?: delegate?.get(name) + } override fun subscribeToVariablesChange( names: List, diff --git a/client/android/div/src/test/java/com/yandex/div/core/expression/EvaluableMultiplatformTest.kt b/client/android/div/src/test/java/com/yandex/div/core/expression/EvaluableMultiplatformTest.kt index a29449056..57a7b9859 100644 --- a/client/android/div/src/test/java/com/yandex/div/core/expression/EvaluableMultiplatformTest.kt +++ b/client/android/div/src/test/java/com/yandex/div/core/expression/EvaluableMultiplatformTest.kt @@ -15,8 +15,8 @@ import com.yandex.div.internal.util.UiThreadHandler import com.yandex.div.internal.util.map import com.yandex.div.json.ParsingErrorLogger import com.yandex.div.rule.LocaleRule -import com.yandex.div.test.crossplatform.MultiplatformTestUtils import com.yandex.div.test.crossplatform.ParsingResult +import com.yandex.div.test.crossplatform.ParsingUtils import org.json.JSONArray import org.json.JSONObject import org.junit.After @@ -78,7 +78,11 @@ class EvaluableMultiplatformTest( is ParsingResult.Error -> caseParsingResult.throwException() } - val testDivData = createDivDataFromTestVars(testCase.variables, testCase.functions, testParsingLogger) + val testDivData = createDivDataFromTestVars( + testCase.variables, + testCase.functions, + testParsingLogger + ) runtimeProvider = ExpressionsRuntimeProvider( mockDivVariableController, @@ -114,7 +118,7 @@ class EvaluableMultiplatformTest( } is JSONArray, is JSONObject -> { - if (testCase.expectedType == VALUE_TYPE_UNORDERED_ARRAY){ + if (testCase.expectedType == VALUE_TYPE_UNORDERED_ARRAY) { checkEquality(testCase) { message, expected, actual -> val expectedList = (expected as JSONArray).map { toString() }.sorted() val actualList = (actual as JSONArray).map { toString() }.sorted() @@ -146,7 +150,8 @@ class EvaluableMultiplatformTest( if (evalExpression is Throwable) { throw AssertionError( "Expecting '${testCase.expectedValue}' at expression '${testCase.expression}' " + - "but got exception instead!", evalExpression) + "but got exception instead!", evalExpression + ) } validate("expression: '${testCase.expression}'", testCase.expectedValue, evalExpression) } @@ -173,21 +178,14 @@ class EvaluableMultiplatformTest( companion object { - private const val TEST_CASES_FILE_PATH = "expression_test_data" - @JvmStatic @Parameterized.Parameters(name = "{0}") fun cases(): List> { - val cases = mutableListOf>() - val errors = MultiplatformTestUtils - .walkJSONs(TEST_CASES_FILE_PATH) { file, jsonString -> - val newCases = ExpressionTestCaseUtils.parseTestCases(JSONObject(jsonString), file.name) - cases.addAll(newCases) - } - - val allCases = errors + cases - ExpressionTestCaseUtils.checkDuplicates(allCases.asSequence()) - return allCases + val cases = ParsingUtils.parseFiles("expression_test_data") { file, json -> + ExpressionTestCaseUtils.parseTestCases(JSONObject(json), file.name) + } + ExpressionTestCaseUtils.checkDuplicates(cases.asSequence()) + return cases } } } diff --git a/client/android/div/src/test/java/com/yandex/div/core/expression/ExpressionTestCaseUtils.kt b/client/android/div/src/test/java/com/yandex/div/core/expression/ExpressionTestCaseUtils.kt index 2fa82c95d..741ab2b4c 100644 --- a/client/android/div/src/test/java/com/yandex/div/core/expression/ExpressionTestCaseUtils.kt +++ b/client/android/div/src/test/java/com/yandex/div/core/expression/ExpressionTestCaseUtils.kt @@ -1,8 +1,6 @@ package com.yandex.div.core.expression -import android.net.Uri import com.yandex.div.data.DivParsingEnvironment -import com.yandex.div.data.Variable import com.yandex.div.evaluable.EvaluableException import com.yandex.div.evaluable.types.Color import com.yandex.div.evaluable.types.Url @@ -18,7 +16,6 @@ import com.yandex.div2.DivData import com.yandex.div2.DivEvaluableType import com.yandex.div2.DivFunction import com.yandex.div2.DivVariable -import org.json.JSONArray import org.json.JSONException import org.json.JSONObject @@ -32,8 +29,8 @@ object ExpressionTestCaseUtils { private const val VALUE_TYPE_DATE_TIME = "datetime" private const val VALUE_TYPE_URL = "url" private const val VALUE_TYPE_COLOR = "color" - const val VALUE_TYPE_DICT = "dict" - const val VALUE_TYPE_ARRAY = "array" + private const val VALUE_TYPE_DICT = "dict" + private const val VALUE_TYPE_ARRAY = "array" const val VALUE_TYPE_UNORDERED_ARRAY = "unordered_array" private const val VALUE_TYPE_UNIT = "unit" private const val VALUE_TYPE_ERROR = "error" @@ -113,21 +110,7 @@ object ExpressionTestCaseUtils { return value } - val JSONObject.type: String get() = getString(TYPE_FIELD) - - fun createVariable(type: String, name: String, value: Any?): Variable { - return when (type) { - VALUE_TYPE_STRING -> Variable.StringVariable(name, value as String? ?: "") - VALUE_TYPE_INTEGER -> Variable.IntegerVariable(name, value as Long? ?: 0) - VALUE_TYPE_DECIMAL -> Variable.DoubleVariable(name, value as Double? ?: 0.0) - VALUE_TYPE_BOOLEAN -> Variable.BooleanVariable(name, ANY_TO_BOOLEAN(value ?: false)) - VALUE_TYPE_COLOR -> Variable.ColorVariable(name, (value as Color?)?.value ?: 0) - VALUE_TYPE_URL -> Variable.UrlVariable(name, value as Uri? ?: Uri.EMPTY) - VALUE_TYPE_DICT -> Variable.DictVariable(name, value as JSONObject? ?: JSONObject()) - VALUE_TYPE_ARRAY -> Variable.ArrayVariable(name, value as JSONArray? ?: JSONArray()) - else -> throw IllegalAccessException("Unknown variable type: $type") - } - } + private val JSONObject.type: String get() = getString(TYPE_FIELD) fun createDivDataFromTestVars( vars: List, diff --git a/client/android/div/src/test/java/com/yandex/div/core/expression/SignaturesMultiplatformTest.kt b/client/android/div/src/test/java/com/yandex/div/core/expression/SignaturesMultiplatformTest.kt index d4b88209b..898f40839 100644 --- a/client/android/div/src/test/java/com/yandex/div/core/expression/SignaturesMultiplatformTest.kt +++ b/client/android/div/src/test/java/com/yandex/div/core/expression/SignaturesMultiplatformTest.kt @@ -5,8 +5,8 @@ import com.yandex.div.evaluable.EvaluableType import com.yandex.div.evaluable.FunctionArgument import com.yandex.div.evaluable.function.GeneratedBuiltinFunctionProvider import com.yandex.div.test.crossplatform.isForAndroid -import com.yandex.div.test.crossplatform.MultiplatformTestUtils import com.yandex.div.test.crossplatform.ParsingResult +import com.yandex.div.test.crossplatform.ParsingUtils import com.yandex.div.test.crossplatform.toObjectList import org.json.JSONException import org.json.JSONObject @@ -63,18 +63,14 @@ class SignaturesMultiplatformTest(testCaseParsingResult: ParsingResult> { - val cases = mutableListOf>() - val errors = MultiplatformTestUtils - .walkJSONs(File(SIGNATURES_DIR_PATH)) { file, jsonString -> - val newCases = JSONObject(jsonString) - .optJSONArray(FIELD_SIGNATURE) - .toObjectList() - .filter { it.isForAndroid } - .map { parseSignature(file, it) } - .filterIsInstance>() - cases.addAll(newCases) - } - return cases + errors + return ParsingUtils.parseFiles(File(SIGNATURES_DIR_PATH)) { file, json -> + JSONObject(json) + .optJSONArray(FIELD_SIGNATURE) + .toObjectList() + .filter { it.isForAndroid } + .map { parseSignature(file, it) } + .filterIsInstance>() + } } private fun parseSignature(file: File, json: JSONObject): ParsingResult { @@ -97,12 +93,14 @@ class SignaturesMultiplatformTest(testCaseParsingResult: ParsingResult) { private val testCase = testCaseParsingResult.getOrThrow() - private val expectedResults = testCase.expectedResults private val activity = Robolectric.buildActivity(Activity::class.java).get() - private val logger = IntegrationTestLogger() @Test fun run() { - val env = DivParsingEnvironment(logger) - testCase.divData.optJSONObject("templates")?.let { - env.parseTemplates(it) - } - val divData = runCatching { - DivData(env, testCase.divData.getJSONObject("card")) - }.getOrElse { - var errorIsExpected = false - expectedResults.forEach { e -> - if (e !is IntegrationTestCase.ExpectedResult.Error) return@forEach - checkError(e) - errorIsExpected = true - } - - if (!errorIsExpected) { - throw AssertionError("Got unexpected error at data parsing!", it) - } - return - } + val divData = testCase.parseDivData() ?: return val context = Div2Context(activity, DivConfiguration.Builder(IMAGE_LOADER_STUB).build()) - expectedResults.forEach { result -> - when (result) { - is IntegrationTestCase.ExpectedResult.Variable -> { - if (divData.variables?.any { it.name == result.name } != true) { - val variable = createVariable(result.type, result.name, null) - context.divVariableController.declare(variable) - } - } - - is IntegrationTestCase.ExpectedResult.Error -> return@forEach - } - } + testCase.declareResultVariables( + variables = divData.variables ?: emptyList(), + variableController = context.divVariableController + ) val divView = Div2View(context) divView.setData(divData, DivDataTag(UUID.randomUUID().toString())) divView.observeErrors { errors, _ -> errors.forEach { error -> - logger.logErrorDirectly(error) + testCase.logger.logErrorDirectly(error) } } - testCase.actions.forEach { - runCatching { divView.handleAction(DivAction(env, it)) } + testCase.parseActions().forEach { + divView.handleAction(it) } - expectedResults.forEach { - when (it) { - is IntegrationTestCase.ExpectedResult.Error -> checkError(it) - is IntegrationTestCase.ExpectedResult.Variable -> { - val expectedValue = it.value.wrapVariableValue() - val actualValue = divView.expressionResolver - .getVariable(it.name) - ?.getWrappedValue() - if (it.type == VALUE_TYPE_DICT || it.type == VALUE_TYPE_ARRAY) { - assertEquals(expectedValue.toString(), actualValue.toString()) - } else { - assertEquals(expectedValue, actualValue) - } - } - } - } - } - - private fun checkError(expected: IntegrationTestCase.ExpectedResult.Error) { - assertTrue( - "Expected: <${expected.message}> but was: <${ - logger.messages.toSet().joinToString(", ") - }>", - logger.messages.contains(expected.message) + testCase.checkResult( + expressionResolver = divView.expressionResolver ) } companion object { - private const val TEST_CASES_FILE_PATH = "integration_test_data" private val EMPTY_REF = LoadReference { } private val IMAGE_LOADER_STUB = DivImageLoader { _, _ -> EMPTY_REF } // Store parsed test cases to prevent multiple parsing by // ParameterizedRobolectricTestRunner private val cases: List> = run { - val cases = mutableListOf>() - val errors = MultiplatformTestUtils - .walkJSONs(TEST_CASES_FILE_PATH) { file, json -> - cases.addAll(IntegrationTestCase.parse(file.name, json)) - } - errors + cases + ParsingUtils.parseFiles("integration_test_data") { file, json -> + IntegrationTestCaseParser.parseCases(file.name, json) + } } @JvmStatic diff --git a/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/screenshot/DivComposeScreenshotActivity.kt b/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/screenshot/DivComposeScreenshotActivity.kt index 98cf0476b..eeb9aa011 100644 --- a/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/screenshot/DivComposeScreenshotActivity.kt +++ b/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/screenshot/DivComposeScreenshotActivity.kt @@ -73,7 +73,7 @@ class DivComposeScreenshotActivity : ComponentActivity() { fun performActions(actions: List) { actions.forEach { - divContext.actionPerformer.perform(it) + divContext.debugFeatures.performAction(it) } } diff --git a/client/android/test-utils/src/main/kotlin/com/yandex/div/test/crossplatform/IntegrationTestCase.kt b/client/android/test-utils/src/main/kotlin/com/yandex/div/test/crossplatform/IntegrationTestCase.kt index 78acfc549..6728d92d0 100644 --- a/client/android/test-utils/src/main/kotlin/com/yandex/div/test/crossplatform/IntegrationTestCase.kt +++ b/client/android/test-utils/src/main/kotlin/com/yandex/div/test/crossplatform/IntegrationTestCase.kt @@ -1,104 +1,128 @@ package com.yandex.div.test.crossplatform -import androidx.core.net.toUri -import com.yandex.div.evaluable.types.Color -import org.json.JSONException +import android.net.Uri +import com.yandex.div.core.expression.variables.DivVariableController +import com.yandex.div.data.DivParsingEnvironment +import com.yandex.div.data.Variable +import com.yandex.div.internal.variables.name +import com.yandex.div.json.expressions.ExpressionResolver +import com.yandex.div2.DivAction +import com.yandex.div2.DivData +import com.yandex.div2.DivVariable +import org.json.JSONArray import org.json.JSONObject +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Assert.fail class IntegrationTestCase( - val name: String, - val divData: JSONObject, - val actions: List, - val expectedResults: List + private val name: String, + private val divData: JSONObject, + private val actions: List, + private val expectedResults: List ) { sealed interface ExpectedResult { - class Variable(val name: String, val type: String, val value: Any) : ExpectedResult + class Variable( + val name: String, + val type: String, + private val value: Any + ) : ExpectedResult { + fun check(expressionResolver: ExpressionResolver) { + val actualValue = expressionResolver.getVariable(name)?.getValue() + if (type == "array" || type == "dict" || type == "url") { + assertEquals(value.toString(), actualValue.toString()) + } else { + assertEquals(value, actualValue) + } + } + } - class Error(val message: String) : ExpectedResult + class Error(private val message: String) : ExpectedResult { + fun check(errors: List) { + assertTrue( + "Expected: <$message> but was: <${errors.toSet().joinToString(", ")}>", + errors.contains(message) + ) + } + } } + val logger = IntegrationTestLogger() + private val parsingEnvironment = DivParsingEnvironment(logger) + override fun toString() = name - companion object { + fun parseDivData(): DivData? { + divData.optJSONObject("templates")?.let { + parsingEnvironment.parseTemplates(it) + } - fun parse( - fileName: String, - jsonString: String - ): List> { - val json = JSONObject(jsonString) - return json.getJSONArray("cases") - .toObjectList() - .mapIndexedNotNull { index, jsonObject -> - if (!jsonObject.isForAndroid) { - return@mapIndexedNotNull null - } - try { - val testCase = jsonObject.parseTestCase( - fileName = fileName, - index = index, - // Fresh instance is required for every test case since JSONObject - // may contain mutable elements (array and dict variables). - divData = JSONObject(jsonString).getJSONObject("div_data") - ) - ParsingResult.Success(testCase) - } catch (e: JSONException) { - ParsingResult.Error(fileName, json, e) - } + try { + return DivData(parsingEnvironment, divData.getJSONObject("card")) + } catch (throwable: Throwable) { + var isErrorExpected = false + expectedResults + .filterIsInstance() + .forEach { result -> + result.check(logger.messages) + isErrorExpected = true } + + if (!isErrorExpected) { + fail("Unexpected parsing error: ${throwable.message}") + } + } + + return null + } + + fun parseActions(): List { + return actions.map { + DivAction(json = it, env = parsingEnvironment) + } + } + + /** + * Declares variables that are used in expected results but no not declared in DivData. + */ + fun declareResultVariables( + variables: List, + variableController: DivVariableController + ) { + expectedResults + .filterIsInstance() + .forEach { variable -> + if (!variables.any { it.name == variable.name }) { + variableController.declare(createVariable(variable.type, variable.name)) + } + } + } + + fun checkResult(expressionResolver: ExpressionResolver) { + expectedResults.forEach { + when (it) { + is ExpectedResult.Error -> + it.check(logger.messages) + + is ExpectedResult.Variable -> + it.check(expressionResolver = expressionResolver) + } } } } -private fun JSONObject.parseTestCase( - fileName: String, - index: Int, - divData: JSONObject -): IntegrationTestCase { - val actions = optJSONArray("div_actions").toObjectList() - var name = "$fileName Case $index" - actions.forEach { - name += ", ${it.getString("log_id")}" - } - - return IntegrationTestCase( - name = name, - divData = divData, - actions = actions, - expectedResults = getJSONArray("expected") - .toObjectList() - .map { it.parseExpectedResult() } - ) -} - -private fun JSONObject.parseExpectedResult(): IntegrationTestCase.ExpectedResult { - return when (val type = getString("type")) { - "variable" -> { - val value = getJSONObject("value") - IntegrationTestCase.ExpectedResult.Variable( - name = getString("variable_name"), - type = value.getString("type"), - value = value.getVariableValue() - ) - } - - "error" -> IntegrationTestCase.ExpectedResult.Error(getString("value")) - else -> throw JSONException("Unknown expected result type: $type") - } -} - -private fun JSONObject.getVariableValue(): Any { - return when (val type = getString("type")) { - "array" -> getJSONArray("value") - "boolean" -> get("value") - "color" -> Color.parse(getString("value")) - "datetime" -> parseDateTime(getString("value")) - "dict" -> getJSONObject("value") - "integer" -> getLong("value") - "number" -> getDouble("value") - "string" -> getString("value") - "url" -> getString("value").toUri() +private fun createVariable(type: String, name: String): Variable { + return when (type) { + "array" -> Variable.ArrayVariable(name, JSONArray()) + "boolean" -> Variable.BooleanVariable(name, false) + "color" -> Variable.ColorVariable(name, 0) + "dict" -> Variable.DictVariable(name, JSONObject()) + "integer" -> Variable.IntegerVariable(name, 0) + "number" -> Variable.DoubleVariable(name, 0.0) + "string" -> Variable.StringVariable(name, "") + "url" -> Variable.UrlVariable(name, Uri.EMPTY) else -> throw IllegalAccessException("Unknown variable type: $type") } } diff --git a/client/android/test-utils/src/main/kotlin/com/yandex/div/test/crossplatform/IntegrationTestCaseParser.kt b/client/android/test-utils/src/main/kotlin/com/yandex/div/test/crossplatform/IntegrationTestCaseParser.kt new file mode 100644 index 000000000..bce9f1e61 --- /dev/null +++ b/client/android/test-utils/src/main/kotlin/com/yandex/div/test/crossplatform/IntegrationTestCaseParser.kt @@ -0,0 +1,86 @@ +package com.yandex.div.test.crossplatform + +import com.yandex.div.evaluable.types.Color +import org.json.JSONException +import org.json.JSONObject + +object IntegrationTestCaseParser { + + fun parseCases( + fileName: String, + jsonString: String + ): List> { + val json = JSONObject(jsonString) + return json.getJSONArray("cases") + .toObjectList() + .mapIndexedNotNull { index, jsonObject -> + if (!jsonObject.isForAndroid) { + return@mapIndexedNotNull null + } + try { + val testCase = jsonObject.parseTestCase( + fileName = fileName, + index = index, + // Fresh instance is required for every test case since JSONObject + // may contain mutable elements (array and dict variables). + divData = JSONObject(jsonString).getJSONObject("div_data") + ) + ParsingResult.Success(testCase) + } catch (e: Exception) { + ParsingResult.Error(fileName = fileName, error = e) + } + } + } +} + +private fun JSONObject.parseTestCase( + fileName: String, + index: Int, + divData: JSONObject +): IntegrationTestCase { + val actions = optJSONArray("div_actions").toObjectList() + var name = "$fileName Case $index" + actions.forEach { + name += ", ${it.getString("log_id")}" + } + + return IntegrationTestCase( + name = name, + divData = divData, + actions = actions, + expectedResults = getJSONArray("expected") + .toObjectList() + .map { it.parseExpectedResult() } + ) +} + +private fun JSONObject.parseExpectedResult(): IntegrationTestCase.ExpectedResult { + return when (val type = getString("type")) { + "variable" -> { + val value = getJSONObject("value") + IntegrationTestCase.ExpectedResult.Variable( + name = getString("variable_name"), + type = value.getString("type"), + value = value.getVariableValue() + ) + } + + "error" -> IntegrationTestCase.ExpectedResult.Error(getString("value")) + else -> throw JSONException("Unknown expected result type: $type") + } +} + +private fun JSONObject.getVariableValue(): Any { + return when (val type = getString("type")) { + "array" -> getJSONArray("value") + "boolean" -> get("value") + "color" -> Color.parse(getString("value")) + "datetime" -> parseDateTime(getString("value")) + "dict" -> getJSONObject("value") + "integer" -> getLong("value") + "number" -> getDouble("value") + "string" -> getString("value") + "url" -> getString("value") + else -> throw IllegalAccessException("Unknown variable type: $type") + } +} diff --git a/client/android/div/src/test/java/com/yandex/div/interactive/IntegrationTestLogger.kt b/client/android/test-utils/src/main/kotlin/com/yandex/div/test/crossplatform/IntegrationTestLogger.kt similarity index 72% rename from client/android/div/src/test/java/com/yandex/div/interactive/IntegrationTestLogger.kt rename to client/android/test-utils/src/main/kotlin/com/yandex/div/test/crossplatform/IntegrationTestLogger.kt index 13b0dab02..e0b1a145f 100644 --- a/client/android/div/src/test/java/com/yandex/div/interactive/IntegrationTestLogger.kt +++ b/client/android/test-utils/src/main/kotlin/com/yandex/div/test/crossplatform/IntegrationTestLogger.kt @@ -1,4 +1,4 @@ -package com.yandex.div.interactive +package com.yandex.div.test.crossplatform import com.yandex.div.json.ParsingErrorLogger @@ -12,11 +12,7 @@ class IntegrationTestLogger : ParsingErrorLogger { } fun logErrorDirectly(e: Throwable) { - collectChainMessages(e) - } - - private fun collectChainMessages(t: Throwable) { - generateSequence(t) { it.cause } + generateSequence(e) { it.cause } .mapNotNull { it.message } .forEach { _messages.add(it) } } diff --git a/client/android/test-utils/src/main/kotlin/com/yandex/div/test/crossplatform/MultiplatformTestUtils.kt b/client/android/test-utils/src/main/kotlin/com/yandex/div/test/crossplatform/MultiplatformTestUtils.kt deleted file mode 100644 index a7bc30109..000000000 --- a/client/android/test-utils/src/main/kotlin/com/yandex/div/test/crossplatform/MultiplatformTestUtils.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.yandex.div.test.crossplatform - -import org.json.JSONException -import java.io.File - -private const val JSON_EXTENSION = "json" -private const val TEST_DATA_PATH = "../../../test_data/" - -object MultiplatformTestUtils { - - fun walkJSONs( - relativePath: String, - parseAction: (file: File, json: String) -> Unit - ): List { - return walkJSONs(directory = File(TEST_DATA_PATH, relativePath), parseAction) - } - - fun walkJSONs( - directory: File, - parseAction: (file: File, json: String) -> Unit - ): List { - val errors = mutableListOf() - getFiles(directory) - .forEach { file -> - val json = try { - file.readText(Charsets.UTF_8) - } catch (e: Exception) { - errors.add(ParsingResult.Error(fileName = file.name, error = e)) - return@forEach - } - - try { - parseAction(file, json) - } catch (e: JSONException) { - errors.add(ParsingResult.Error(fileName = file.name, error = e)) - } - } - - return errors - } - - private fun getFiles(dir: File): List { - val (directories, files) = dir.listFiles().orEmpty() - .partition { it.isDirectory } - return arrayListOf().apply { - addAll(files.filter { file -> file.extension == JSON_EXTENSION }) - addAll(directories.flatMap { getFiles(it) }) - } - } -} diff --git a/client/android/test-utils/src/main/kotlin/com/yandex/div/test/crossplatform/ParsingUtils.kt b/client/android/test-utils/src/main/kotlin/com/yandex/div/test/crossplatform/ParsingUtils.kt index 9895e11b6..65d77fb52 100644 --- a/client/android/test-utils/src/main/kotlin/com/yandex/div/test/crossplatform/ParsingUtils.kt +++ b/client/android/test-utils/src/main/kotlin/com/yandex/div/test/crossplatform/ParsingUtils.kt @@ -4,11 +4,56 @@ import com.yandex.div.evaluable.types.DateTime import com.yandex.div.internal.util.map import org.json.JSONArray import org.json.JSONObject +import java.io.File import java.text.SimpleDateFormat import java.util.Calendar import java.util.Date import java.util.Locale import java.util.TimeZone +import kotlin.collections.filter +import kotlin.collections.flatMap +import kotlin.collections.forEach +import kotlin.collections.orEmpty +import kotlin.collections.partition +import kotlin.io.extension + +private const val TEST_DATA_PATH = "../../../test_data/" + +object ParsingUtils { + + fun parseFiles( + relativePath: String, + parseAction: (file: File, json: String) -> List> + ): List> { + return parseFiles(directory = File(TEST_DATA_PATH, relativePath), parseAction) + } + + fun parseFiles( + directory: File, + parseAction: (file: File, json: String) -> List> + ): List> { + val results = mutableListOf>() + getFiles(directory).forEach { file -> + try { + val json = file.readText(Charsets.UTF_8) + results.addAll(parseAction(file, json)) + } catch (e: Exception) { + results.add(ParsingResult.Error(fileName = file.name, error = e)) + return@forEach + } + } + return results + } + + private fun getFiles(dir: File): List { + val (directories, files) = dir.listFiles().orEmpty() + .partition { it.isDirectory } + return arrayListOf().apply { + addAll(files.filter { file -> file.extension == "json" }) + addAll(directories.flatMap { getFiles(it) }) + } + } +} val JSONObject.platforms: List get() = getJSONArray("platforms").map { it as String } diff --git a/test_data/integration_test_data/properties/property_new_value_varaible_name.json b/test_data/integration_test_data/properties/property_new_value_variable_name.json similarity index 100% rename from test_data/integration_test_data/properties/property_new_value_varaible_name.json rename to test_data/integration_test_data/properties/property_new_value_variable_name.json