mirror of
https://github.com/divkit/divkit.git
synced 2026-05-07 20:02:32 +00:00
Added compose integration tests
commit_hash:b8423b44ec162dc4882e3be7cf8fc61fab2acbb3
This commit is contained in:
+5
-4
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -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
|
||||
|
||||
+4
-1
@@ -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)
|
||||
|
||||
-22
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+26
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -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",
|
||||
)
|
||||
+30
@@ -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,
|
||||
|
||||
+1
-1
@@ -8,4 +8,4 @@ fun interface VariableProvider {
|
||||
* @return variable value or null if it is missing.
|
||||
*/
|
||||
fun get(name: String): Any?
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -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()
|
||||
|
||||
+1
-15
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
+4
-1
@@ -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<String>,
|
||||
|
||||
-7
@@ -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<Variable> = emptyList()
|
||||
}
|
||||
|
||||
internal fun Any?.wrapVariableValue() = when(this) {
|
||||
is Uri -> Url(this.toString())
|
||||
else -> this
|
||||
}
|
||||
|
||||
internal fun VariableController.declare(
|
||||
divVariable: DivVariable,
|
||||
resolver: ExpressionResolver,
|
||||
|
||||
+4
-1
@@ -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<String>,
|
||||
|
||||
+14
-16
@@ -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<ParsingResult<ExpressionTestCase>> {
|
||||
val cases = mutableListOf<ParsingResult<ExpressionTestCase>>()
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-20
@@ -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<JSONObject>,
|
||||
|
||||
+17
-19
@@ -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<Signature
|
||||
@JvmStatic
|
||||
@Parameterized.Parameters(name = "{0}")
|
||||
fun signatures(): List<ParsingResult<SignatureTestCase>> {
|
||||
val cases = mutableListOf<ParsingResult<SignatureTestCase>>()
|
||||
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<ParsingResult.Success<SignatureTestCase>>()
|
||||
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<ParsingResult.Success<SignatureTestCase>>()
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseSignature(file: File, json: JSONObject): ParsingResult<SignatureTestCase> {
|
||||
@@ -97,12 +93,14 @@ class SignaturesMultiplatformTest(testCaseParsingResult: ParsingResult<Signature
|
||||
return ParsingResult.Error(file.name, json, e)
|
||||
}
|
||||
val isMethod = json.optBoolean(FIELD_SIGNATURE_IS_METHOD)
|
||||
return ParsingResult.Success(SignatureTestCase(
|
||||
"$functionName(${arguments ?: ""}) $resultType",
|
||||
functionName,
|
||||
arguments ?: emptyList(),
|
||||
resultType,
|
||||
isMethod)
|
||||
return ParsingResult.Success(
|
||||
SignatureTestCase(
|
||||
"$functionName(${arguments ?: ""}) $resultType",
|
||||
functionName,
|
||||
arguments ?: emptyList(),
|
||||
resultType,
|
||||
isMethod
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+15
-79
@@ -5,23 +5,13 @@ import com.yandex.div.DivDataTag
|
||||
import com.yandex.div.core.Div2Context
|
||||
import com.yandex.div.core.DivConfiguration
|
||||
import com.yandex.div.core.actions.observeErrors
|
||||
import com.yandex.div.core.expression.ExpressionTestCaseUtils.VALUE_TYPE_ARRAY
|
||||
import com.yandex.div.core.expression.ExpressionTestCaseUtils.VALUE_TYPE_DICT
|
||||
import com.yandex.div.core.expression.ExpressionTestCaseUtils.createVariable
|
||||
import com.yandex.div.core.expression.getWrappedValue
|
||||
import com.yandex.div.core.expression.name
|
||||
import com.yandex.div.core.expression.variables.wrapVariableValue
|
||||
import com.yandex.div.core.images.DivImageLoader
|
||||
import com.yandex.div.core.images.LoadReference
|
||||
import com.yandex.div.core.view2.Div2View
|
||||
import com.yandex.div.data.DivParsingEnvironment
|
||||
import com.yandex.div.test.crossplatform.IntegrationTestCase
|
||||
import com.yandex.div.test.crossplatform.MultiplatformTestUtils
|
||||
import com.yandex.div.test.crossplatform.IntegrationTestCaseParser
|
||||
import com.yandex.div.test.crossplatform.ParsingResult
|
||||
import com.yandex.div2.DivAction
|
||||
import com.yandex.div2.DivData
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import com.yandex.div.test.crossplatform.ParsingUtils
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.ParameterizedRobolectricTestRunner
|
||||
@@ -32,100 +22,46 @@ import java.util.UUID
|
||||
class IntegrationMultiplatformTest(testCaseParsingResult: ParsingResult<IntegrationTestCase>) {
|
||||
|
||||
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<ParsingResult<IntegrationTestCase>> = run {
|
||||
val cases = mutableListOf<ParsingResult<IntegrationTestCase>>()
|
||||
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
|
||||
|
||||
+1
-1
@@ -73,7 +73,7 @@ class DivComposeScreenshotActivity : ComponentActivity() {
|
||||
|
||||
fun performActions(actions: List<DivAction>) {
|
||||
actions.forEach {
|
||||
divContext.actionPerformer.perform(it)
|
||||
divContext.debugFeatures.performAction(it)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+105
-81
@@ -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<JSONObject>,
|
||||
val expectedResults: List<ExpectedResult>
|
||||
private val name: String,
|
||||
private val divData: JSONObject,
|
||||
private val actions: List<JSONObject>,
|
||||
private val expectedResults: List<ExpectedResult>
|
||||
) {
|
||||
|
||||
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<String>) {
|
||||
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<ParsingResult<IntegrationTestCase>> {
|
||||
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<ExpectedResult.Error>()
|
||||
.forEach { result ->
|
||||
result.check(logger.messages)
|
||||
isErrorExpected = true
|
||||
}
|
||||
|
||||
if (!isErrorExpected) {
|
||||
fail("Unexpected parsing error: ${throwable.message}")
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
fun parseActions(): List<DivAction> {
|
||||
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<DivVariable>,
|
||||
variableController: DivVariableController
|
||||
) {
|
||||
expectedResults
|
||||
.filterIsInstance<ExpectedResult.Variable>()
|
||||
.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")
|
||||
}
|
||||
}
|
||||
|
||||
+86
@@ -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<ParsingResult<IntegrationTestCase>> {
|
||||
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")
|
||||
}
|
||||
}
|
||||
+2
-6
@@ -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) }
|
||||
}
|
||||
-50
@@ -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<ParsingResult.Error> {
|
||||
return walkJSONs(directory = File(TEST_DATA_PATH, relativePath), parseAction)
|
||||
}
|
||||
|
||||
fun walkJSONs(
|
||||
directory: File,
|
||||
parseAction: (file: File, json: String) -> Unit
|
||||
): List<ParsingResult.Error> {
|
||||
val errors = mutableListOf<ParsingResult.Error>()
|
||||
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<File> {
|
||||
val (directories, files) = dir.listFiles().orEmpty()
|
||||
.partition { it.isDirectory }
|
||||
return arrayListOf<File>().apply {
|
||||
addAll(files.filter { file -> file.extension == JSON_EXTENSION })
|
||||
addAll(directories.flatMap { getFiles(it) })
|
||||
}
|
||||
}
|
||||
}
|
||||
+45
@@ -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 <T : Any> parseFiles(
|
||||
relativePath: String,
|
||||
parseAction: (file: File, json: String) -> List<ParsingResult<T>>
|
||||
): List<ParsingResult<T>> {
|
||||
return parseFiles(directory = File(TEST_DATA_PATH, relativePath), parseAction)
|
||||
}
|
||||
|
||||
fun <T : Any> parseFiles(
|
||||
directory: File,
|
||||
parseAction: (file: File, json: String) -> List<ParsingResult<T>>
|
||||
): List<ParsingResult<T>> {
|
||||
val results = mutableListOf<ParsingResult<T>>()
|
||||
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<File> {
|
||||
val (directories, files) = dir.listFiles().orEmpty()
|
||||
.partition { it.isDirectory }
|
||||
return arrayListOf<File>().apply {
|
||||
addAll(files.filter { file -> file.extension == "json" })
|
||||
addAll(directories.flatMap { getFiles(it) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val JSONObject.platforms: List<String>
|
||||
get() = getJSONArray("platforms").map { it as String }
|
||||
|
||||
Reference in New Issue
Block a user