Added test-utils module

commit_hash:b2f6363829ef137dd628946b6ce84f3a6d76ad23
This commit is contained in:
pkurchatov
2026-03-23 17:30:44 +03:00
parent 16b015a2cc
commit e3bd6069a4
52 changed files with 694 additions and 710 deletions
+16 -9
View File
@@ -650,9 +650,7 @@
"client/android/compose/src/main/kotlin/com/yandex/div/compose/views/modifiers/BorderModifiers.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/views/modifiers/BorderModifiers.kt",
"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/DivDataUtils.kt":"divkit/public/client/android/compose/src/test/kotlin/com/yandex/div/compose/DivDataUtils.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/ExpressionUtils.kt":"divkit/public/client/android/compose/src/test/kotlin/com/yandex/div/compose/ExpressionUtils.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",
@@ -838,12 +836,12 @@
"client/android/div-evaluable/src/main/java/com/yandex/div/evaluable/types/Url.kt":"divkit/public/client/android/div-evaluable/src/main/java/com/yandex/div/evaluable/types/Url.kt",
"client/android/div-evaluable/src/test/java/com/yandex/div/evaluable/EvaluableTest.kt":"divkit/public/client/android/div-evaluable/src/test/java/com/yandex/div/evaluable/EvaluableTest.kt",
"client/android/div-evaluable/src/test/java/com/yandex/div/evaluable/FunctionTest.kt":"divkit/public/client/android/div-evaluable/src/test/java/com/yandex/div/evaluable/FunctionTest.kt",
"client/android/div-evaluable/src/test/java/com/yandex/div/evaluable/Utils.kt":"divkit/public/client/android/div-evaluable/src/test/java/com/yandex/div/evaluable/Utils.kt",
"client/android/div-evaluable/src/test/java/com/yandex/div/evaluable/function/DateTimeFunctionsTest.kt":"divkit/public/client/android/div-evaluable/src/test/java/com/yandex/div/evaluable/function/DateTimeFunctionsTest.kt",
"client/android/div-evaluable/src/test/java/com/yandex/div/evaluable/function/FunctionImpl.kt":"divkit/public/client/android/div-evaluable/src/test/java/com/yandex/div/evaluable/function/FunctionImpl.kt",
"client/android/div-evaluable/src/test/java/com/yandex/div/evaluable/function/FunctionValidatorTest.kt":"divkit/public/client/android/div-evaluable/src/test/java/com/yandex/div/evaluable/function/FunctionValidatorTest.kt",
"client/android/div-evaluable/src/test/java/com/yandex/div/evaluable/internal/LiteralsEscaperTest.kt":"divkit/public/client/android/div-evaluable/src/test/java/com/yandex/div/evaluable/internal/LiteralsEscaperTest.kt",
"client/android/div-evaluable/src/test/java/com/yandex/div/evaluable/internal/TokenizerTest.kt":"divkit/public/client/android/div-evaluable/src/test/java/com/yandex/div/evaluable/internal/TokenizerTest.kt",
"client/android/div-evaluable/src/test/java/com/yandex/div/evaluable/multiplatform/SignaturesMultiplatformTest.kt":"divkit/public/client/android/div-evaluable/src/test/java/com/yandex/div/evaluable/multiplatform/SignaturesMultiplatformTest.kt",
"client/android/div-evaluable/src/test/java/com/yandex/div/evaluable/repl/Command.kt":"divkit/public/client/android/div-evaluable/src/test/java/com/yandex/div/evaluable/repl/Command.kt",
"client/android/div-evaluable/src/test/java/com/yandex/div/evaluable/repl/EvaluableRepl.kt":"divkit/public/client/android/div-evaluable/src/test/java/com/yandex/div/evaluable/repl/EvaluableRepl.kt",
"client/android/div-evaluable/src/test/java/com/yandex/div/evaluable/repl/EvaluableReplRuntime.kt":"divkit/public/client/android/div-evaluable/src/test/java/com/yandex/div/evaluable/repl/EvaluableReplRuntime.kt",
@@ -1633,6 +1631,7 @@
"client/android/div/src/test/java/com/yandex/div/core/expression/ExpressionResolverImplTest.kt":"divkit/public/client/android/div/src/test/java/com/yandex/div/core/expression/ExpressionResolverImplTest.kt",
"client/android/div/src/test/java/com/yandex/div/core/expression/ExpressionTestCase.kt":"divkit/public/client/android/div/src/test/java/com/yandex/div/core/expression/ExpressionTestCase.kt",
"client/android/div/src/test/java/com/yandex/div/core/expression/ExpressionTestCaseUtils.kt":"divkit/public/client/android/div/src/test/java/com/yandex/div/core/expression/ExpressionTestCaseUtils.kt",
"client/android/div/src/test/java/com/yandex/div/core/expression/SignaturesMultiplatformTest.kt":"divkit/public/client/android/div/src/test/java/com/yandex/div/core/expression/SignaturesMultiplatformTest.kt",
"client/android/div/src/test/java/com/yandex/div/core/expression/local/RuntimeStoreFillerTest.kt":"divkit/public/client/android/div/src/test/java/com/yandex/div/core/expression/local/RuntimeStoreFillerTest.kt",
"client/android/div/src/test/java/com/yandex/div/core/expression/local/RuntimeStoreImplTest.kt":"divkit/public/client/android/div/src/test/java/com/yandex/div/core/expression/local/RuntimeStoreImplTest.kt",
"client/android/div/src/test/java/com/yandex/div/core/expression/triggers/ConditionPartTest.kt":"divkit/public/client/android/div/src/test/java/com/yandex/div/core/expression/triggers/ConditionPartTest.kt",
@@ -1714,7 +1713,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/IntegrationTestCase.kt":"divkit/public/client/android/div/src/test/java/com/yandex/div/interactive/IntegrationTestCase.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",
@@ -16552,11 +16550,6 @@
"client/android/divkit-regression-testing/src/main/res/values/styles.xml":"divkit/public/client/android/divkit-regression-testing/src/main/res/values/styles.xml",
"client/android/divkit-regression-testing/src/test/java/com/yandex/divkit/regression/data/FakeScenariosDataSource.kt":"divkit/public/client/android/divkit-regression-testing/src/test/java/com/yandex/divkit/regression/data/FakeScenariosDataSource.kt",
"client/android/divkit-regression-testing/src/test/java/com/yandex/divkit/regression/data/ScenariosRepositoryTest.kt":"divkit/public/client/android/divkit-regression-testing/src/test/java/com/yandex/divkit/regression/data/ScenariosRepositoryTest.kt",
"client/android/expression-test-common/build.gradle":"divkit/public/client/android/expression-test-common/build.gradle",
"client/android/expression-test-common/src/main/java/com/yandex/div/test/expression/MultiplatformTestUtils.kt":"divkit/public/client/android/expression-test-common/src/main/java/com/yandex/div/test/expression/MultiplatformTestUtils.kt",
"client/android/expression-test-common/src/main/java/com/yandex/div/test/expression/TestCaseOrError.kt":"divkit/public/client/android/expression-test-common/src/main/java/com/yandex/div/test/expression/TestCaseOrError.kt",
"client/android/expression-test-common/src/main/java/com/yandex/div/test/expression/TestCaseParsingError.kt":"divkit/public/client/android/expression-test-common/src/main/java/com/yandex/div/test/expression/TestCaseParsingError.kt",
"client/android/expression-test-common/src/main/java/com/yandex/div/test/expression/Utils.kt":"divkit/public/client/android/expression-test-common/src/main/java/com/yandex/div/test/expression/Utils.kt",
"client/android/fonts/build.gradle":"divkit/public/client/android/fonts/build.gradle",
"client/android/fonts/proguard-rules.pro":"divkit/public/client/android/fonts/proguard-rules.pro",
"client/android/fonts/src/main/java/com/yandex/div/font/YandexSansDisplayDivTypefaceProvider.kt":"divkit/public/client/android/fonts/src/main/java/com/yandex/div/font/YandexSansDisplayDivTypefaceProvider.kt",
@@ -16679,6 +16672,20 @@
"client/android/screenshot-test-runtime/src/main/java/com/yandex/test/screenshot/TestFile.kt":"divkit/public/client/android/screenshot-test-runtime/src/main/java/com/yandex/test/screenshot/TestFile.kt",
"client/android/screenshot-test-runtime/src/main/java/com/yandex/test/screenshot/ViewRasterizer.kt":"divkit/public/client/android/screenshot-test-runtime/src/main/java/com/yandex/test/screenshot/ViewRasterizer.kt",
"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/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",
"client/android/test-utils/src/main/kotlin/com/yandex/div/test/data/DivContainerUtils.kt":"divkit/public/client/android/test-utils/src/main/kotlin/com/yandex/div/test/data/DivContainerUtils.kt",
"client/android/test-utils/src/main/kotlin/com/yandex/div/test/data/DivDataUtils.kt":"divkit/public/client/android/test-utils/src/main/kotlin/com/yandex/div/test/data/DivDataUtils.kt",
"client/android/test-utils/src/main/kotlin/com/yandex/div/test/data/DivTextUtils.kt":"divkit/public/client/android/test-utils/src/main/kotlin/com/yandex/div/test/data/DivTextUtils.kt",
"client/android/test-utils/src/main/kotlin/com/yandex/div/test/data/DivTriggerUtils.kt":"divkit/public/client/android/test-utils/src/main/kotlin/com/yandex/div/test/data/DivTriggerUtils.kt",
"client/android/test-utils/src/main/kotlin/com/yandex/div/test/data/DivTypedValueUtils.kt":"divkit/public/client/android/test-utils/src/main/kotlin/com/yandex/div/test/data/DivTypedValueUtils.kt",
"client/android/test-utils/src/main/kotlin/com/yandex/div/test/data/DivVariableUtils.kt":"divkit/public/client/android/test-utils/src/main/kotlin/com/yandex/div/test/data/DivVariableUtils.kt",
"client/android/test-utils/src/main/kotlin/com/yandex/div/test/data/ExpressionUtils.kt":"divkit/public/client/android/test-utils/src/main/kotlin/com/yandex/div/test/data/ExpressionUtils.kt",
"client/android/test-utils/src/main/kotlin/com/yandex/div/test/data/Utils.kt":"divkit/public/client/android/test-utils/src/main/kotlin/com/yandex/div/test/data/Utils.kt",
"client/android/ui-test-common/build.gradle":"divkit/public/client/android/ui-test-common/build.gradle",
"client/android/ui-test-common/src/main/java/com/yandex/test/idling/ActivityIdlingResource.kt":"divkit/public/client/android/ui-test-common/src/main/java/com/yandex/test/idling/ActivityIdlingResource.kt",
"client/android/ui-test-common/src/main/java/com/yandex/test/idling/IdlingResources.kt":"divkit/public/client/android/ui-test-common/src/main/java/com/yandex/test/idling/IdlingResources.kt",
+1 -1
View File
@@ -131,10 +131,10 @@ apiValidation {
"divkit-demo-app",
"divkit-perftests",
"divkit-regression-testing",
"expression-test-common",
"lint-rules",
"sample",
"screenshot-test-runtime",
"test-utils",
"ui-test-common",
]
ignoredPackages += ["com.yandex.div.internal"]
+1
View File
@@ -29,6 +29,7 @@ dependencies {
debugImplementation(libs.androidx.compose.ui.tooling)
testImplementation(project(":test-utils"))
testImplementation(libs.androidx.compose.ui.test.junit4)
testImplementation(libs.androidx.compose.ui.test.manifest)
}
@@ -1,76 +0,0 @@
package com.yandex.div.compose
import androidx.core.net.toUri
import com.yandex.div.evaluable.types.Color
import com.yandex.div2.ArrayValue
import com.yandex.div2.ColorValue
import com.yandex.div2.DivAction
import com.yandex.div2.DivActionSetVariable
import com.yandex.div2.DivActionTyped
import com.yandex.div2.DivTypedValue
import com.yandex.div2.DivTrigger
import com.yandex.div2.DivTrigger.Mode
import com.yandex.div2.DivVariable
import com.yandex.div2.IntegerValue
import com.yandex.div2.IntegerVariable
import com.yandex.div2.StrVariable
import com.yandex.div2.StrValue
import org.json.JSONArray
import org.json.JSONObject
fun variable(name: String, value: Long): DivVariable {
return DivVariable.Integer(IntegerVariable(name = name, value = constant(value)))
}
fun variable(name: String, value: String): DivVariable {
return DivVariable.Str(StrVariable(name = name, value = constant(value)))
}
fun action(
typed: DivActionTyped? = null,
payload: JSONObject? = null,
url: String? = null,
): DivAction {
return DivAction(
logId = constant("test"),
payload = payload,
typed = typed,
url = url?.let { constant(it.toUri()) }
)
}
fun setVariableAction(name: String, value: DivTypedValue): DivActionTyped {
return DivActionTyped.SetVariable(
DivActionSetVariable(value = value, variableName = constant(name))
)
}
fun typedValue(value: String): DivTypedValue {
return DivTypedValue.Str(StrValue(value = constant(value)))
}
fun typedValue(value: Long): DivTypedValue {
return DivTypedValue.Integer(IntegerValue(value = constant(value)))
}
fun typedValue(value: JSONArray): DivTypedValue {
return DivTypedValue.Array(ArrayValue(value = constant(value)))
}
fun typedColorValue(value: Long): DivTypedValue {
return DivTypedValue.Color(ColorValue(value = constant(value.toInt())))
}
fun color(value: Long) = Color(value.toInt())
fun trigger(
action: DivAction,
condition: String,
mode: Mode = Mode.ON_CONDITION
): DivTrigger {
return DivTrigger(
actions = listOf(action),
condition = booleanExpression(condition),
mode = constant(mode)
)
}
@@ -11,12 +11,15 @@ import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.yandex.div.core.expression.variables.DivVariableController
import com.yandex.div.data.Variable
import com.yandex.div.json.expressions.Expression
import com.yandex.div.test.data.action
import com.yandex.div.test.data.constant
import com.yandex.div.test.data.container
import com.yandex.div.test.data.data
import com.yandex.div.test.data.expression
import com.yandex.div.test.data.text
import com.yandex.div.test.data.trigger
import com.yandex.div.test.data.variable
import com.yandex.div2.Div
import com.yandex.div2.DivAction
import com.yandex.div2.DivContainer
import com.yandex.div2.DivData
import com.yandex.div2.DivText
import com.yandex.div2.DivTrigger
import com.yandex.div2.DivVariable
import org.junit.Rule
@@ -210,51 +213,3 @@ class DivViewTest {
}
}
}
private fun data(
content: Div,
triggers: List<DivTrigger>? = null,
variables: List<DivVariable>? = null
): DivData {
return DivData(
logId = "test",
states = listOf(
DivData.State(
stateId = 0,
div = content
)
),
variables = variables,
variableTriggers = triggers,
)
}
private fun text(
action: DivAction? = null,
id: String? = null,
text: Expression<String>,
triggers: List<DivTrigger>? = null,
variables: List<DivVariable>? = null
): Div {
return Div.Text(
value = DivText(
action = action,
id = id,
text = text,
variables = variables,
variableTriggers = triggers
)
)
}
private fun container(
items: List<Div>,
variables: List<DivVariable>? = null
): Div {
return Div.Container(
value = DivContainer(
items = items,
variables = variables
)
)
}
@@ -2,9 +2,9 @@ package com.yandex.div.compose.actions
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.yandex.div.compose.TestReporter
import com.yandex.div.compose.action
import com.yandex.div.compose.expressions.DivComposeExpressionResolver
import com.yandex.div.core.expression.variables.DivVariableController
import com.yandex.div.test.data.action
import com.yandex.div2.DivAction
import com.yandex.div2.DivActionCustom
import com.yandex.div2.DivActionTyped
@@ -2,14 +2,14 @@ package com.yandex.div.compose.actions
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.yandex.div.compose.TestReporter
import com.yandex.div.compose.action
import com.yandex.div.compose.color
import com.yandex.div.compose.expressions.DivComposeExpressionResolver
import com.yandex.div.compose.setVariableAction
import com.yandex.div.compose.typedColorValue
import com.yandex.div.compose.typedValue
import com.yandex.div.core.expression.variables.DivVariableController
import com.yandex.div.data.Variable
import com.yandex.div.test.data.action
import com.yandex.div.test.data.color
import com.yandex.div.test.data.setVariableAction
import com.yandex.div.test.data.typedColorValue
import com.yandex.div.test.data.typedValue
import com.yandex.div2.DivAction
import org.json.JSONArray
import org.junit.Assert.assertEquals
@@ -2,9 +2,9 @@ package com.yandex.div.compose.expressions
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.yandex.div.compose.TestReporter
import com.yandex.div.compose.expression
import com.yandex.div.core.expression.variables.DivVariableController
import com.yandex.div.data.Variable
import com.yandex.div.test.data.expression
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
@@ -1,13 +1,13 @@
package com.yandex.div.compose.triggers
import com.yandex.div.compose.TestReporter
import com.yandex.div.compose.action
import com.yandex.div.compose.actions.DivActionHandler
import com.yandex.div.compose.actions.DivActionHandlingContext
import com.yandex.div.compose.expressions.DivComposeExpressionResolver
import com.yandex.div.compose.trigger
import com.yandex.div.core.expression.variables.DivVariableController
import com.yandex.div.data.Variable
import com.yandex.div.test.data.action
import com.yandex.div.test.data.trigger
import com.yandex.div2.DivTrigger.Mode
import org.junit.Test
import org.junit.runner.RunWith
@@ -10,12 +10,12 @@ import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.yandex.div.compose.TestReporter
import com.yandex.div.compose.expressions.DivComposeExpressionResolver
import com.yandex.div.compose.intExpression
import com.yandex.div.compose.views.DivLocalContext
import com.yandex.div.compose.views.LocalDivContext
import com.yandex.div.core.expression.variables.DivVariableController
import com.yandex.div.data.Variable
import com.yandex.div.json.expressions.Expression
import com.yandex.div.test.data.intExpression
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
@@ -74,8 +74,6 @@ dependencies {
generatorCompileOnly libs.json
generatorImplementation libs.kotlinpoet
testImplementation project(path: ':expression-test-common')
testImplementation libs.json
testImplementation libs.junit
testImplementation libs.mockito.kotlin
@@ -2,7 +2,6 @@ package com.yandex.div.evaluable.function
import com.yandex.div.evaluable.EvaluableType
import com.yandex.div.evaluable.Function
import com.yandex.div.evaluable.FunctionArgument
import com.yandex.div.evaluable.FunctionProvider
@Deprecated(
@@ -318,11 +317,4 @@ internal object BuiltinFunctionProvider : FunctionProvider {
override fun getMethod(name: String, args: List<EvaluableType>): Function {
return registry.getMethod(name, args)
}
internal fun ensureFunctionRegistered(name: String,
args: List<FunctionArgument>,
resultType: EvaluableType,
isMethod: Boolean,) {
registry.ensureRegistered(name, args, resultType, isMethod)
}
}
@@ -1,7 +1,6 @@
package com.yandex.div.evaluable
import com.yandex.div.evaluable.function.GeneratedBuiltinFunctionProvider
import com.yandex.div.test.expression.withEvaluator
import org.junit.Assert.assertEquals
import org.junit.Test
import org.mockito.kotlin.mock
@@ -0,0 +1,25 @@
package com.yandex.div.evaluable
import org.junit.Assert.assertTrue
fun <T> withEvaluator(
evaluationContext: EvaluationContext,
block: Evaluator.() -> T
) = run {
val warnings = mutableListOf<String>()
val evaluator = Evaluator(
EvaluationContext(
variableProvider = evaluationContext.variableProvider,
storedValueProvider = evaluationContext.storedValueProvider,
functionProvider = evaluationContext.functionProvider,
warningSender = { expressionContext, message ->
warnings.add(message)
evaluationContext.warningSender.send(expressionContext, message)
}
)
)
block(evaluator).also {
assertTrue(warnings.isEmpty())
}
}
@@ -9,8 +9,13 @@ import com.yandex.div.evaluable.function.GeneratedBuiltinFunctionProvider
import com.yandex.div.evaluable.internal.Parser
import com.yandex.div.evaluable.internal.Token
import com.yandex.div.evaluable.internal.Tokenizer
import com.yandex.div.test.expression.parseAsUTC
import com.yandex.div.test.expression.withEvaluator
import com.yandex.div.evaluable.types.DateTime
import com.yandex.div.evaluable.withEvaluator
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
import java.util.Locale
import java.util.TimeZone
internal object EvaluableReplRuntime {
private val variableProvider = VariableProvider { variableName -> variableList[variableName] }
@@ -65,12 +70,7 @@ internal object EvaluableReplRuntime {
fun evaluateExpression(expression: String) {
val evaluable = parseEvaluable(expression) ?: return
try {
withEvaluator(
evaluationContext,
warningsValidator = { warnings ->
warnings.forEach { println("Warning: $it") }
}
) {
withEvaluator(evaluationContext) {
eval<Any?>(evaluable).also { println(it) }
}
} catch (t: Throwable) {
@@ -109,7 +109,7 @@ internal object EvaluableReplRuntime {
private fun String.getNewVariableValue(): Pair<String, Any> {
val (_, name, type, value) = variableAssigningRegex.matchEntire(this.minifySpaces())!!.groupValues
val evaluableType = EvaluableType.values().find { it.typeName.lowercase() == type.lowercase() }
val evaluableType = EvaluableType.values().find { it.typeName.equals(type, ignoreCase = true) }
?: throw RuntimeException("Unknown type $type.")
return try {
val convertedValue = when (evaluableType) {
@@ -118,7 +118,7 @@ internal object EvaluableReplRuntime {
EvaluableType.BOOLEAN -> value.toBoolean()
EvaluableType.STRING -> value
EvaluableType.COLOR -> value
EvaluableType.DATETIME -> parseAsUTC(value)
EvaluableType.DATETIME -> parseDateTime(value)
EvaluableType.URL -> value
EvaluableType.DICT -> value
EvaluableType.ARRAY -> value
@@ -151,3 +151,12 @@ internal object EvaluableReplRuntime {
}
}
}
private fun parseDateTime(utcString: String): DateTime {
val dateFormat = SimpleDateFormat("yyyy-MM-dd hh:mm:ss", Locale.getDefault())
val date: Date = dateFormat.parse(utcString)!!
return DateTime(
timestampMillis = date.time + Calendar.getInstance().timeZone.rawOffset,
timezone = TimeZone.getTimeZone("UTC"),
)
}
@@ -1,6 +1,5 @@
package com.yandex.div.evaluable.types
import com.yandex.div.test.expression.parseAsUTC
import org.junit.Assert
import org.junit.Test
import java.util.TimeZone
@@ -23,19 +22,6 @@ class DateTimeTest {
DateTime(timestampMillis = 101010, TimeZone.getDefault()))
}
@Test
fun `parsing initial date from string`() {
val dateTime = parseAsUTC("1970-01-01 00:00:00")
Assert.assertEquals(0, dateTime.timezoneMinutes)
Assert.assertEquals(0, dateTime.timestampMillis)
}
@Test
fun `timestamp stored in millis`() {
val dateTime = parseAsUTC("1970-01-01 00:00:01")
Assert.assertEquals(1000, dateTime.timestampMillis)
}
@Test
fun `string representation does not include timezone in it`() {
val dateTime = DateTime(timestampMillis = 5000, TimeZone.getTimeZone("UTC"))
+1 -1
View File
@@ -69,7 +69,7 @@ dependencies {
exclude group: "androidx.fragment", module: "fragment"
}
testImplementation project(path: ':expression-test-common')
testImplementation project(path: ':test-utils')
testImplementation libs.androidx.test.coreKtx
testImplementation libs.json
@@ -8,19 +8,16 @@ import com.yandex.div.core.view2.Div2View
import com.yandex.div.core.view2.disableAssertions
import com.yandex.div.data.Variable
import com.yandex.div.json.expressions.Expression
import com.yandex.div2.Div
import com.yandex.div2.DivAction
import com.yandex.div.test.data.action
import com.yandex.div.test.data.container
import com.yandex.div.test.data.setVariableAction
import com.yandex.div.test.data.typedValue
import com.yandex.div2.DivActionArrayInsertValue
import com.yandex.div2.DivActionArrayRemoveValue
import com.yandex.div2.DivActionArraySetValue
import com.yandex.div2.DivActionDictSetValue
import com.yandex.div2.DivActionSetVariable
import com.yandex.div2.DivActionTyped
import com.yandex.div2.DivContainer
import com.yandex.div2.DivData
import com.yandex.div2.DivTypedValue
import com.yandex.div2.IntegerValue
import com.yandex.div2.StrValue
import org.json.JSONArray
import org.json.JSONObject
import org.junit.Assert
@@ -44,7 +41,7 @@ class DivActionHandlerTest {
)
).apply {
setData(
DivData(logId = "id", states = listOf(DivData.State(Div.Container(DivContainer()), 0))),
DivData(logId = "id", states = listOf(DivData.State(container(), 0))),
DivDataTag("id")
)
}
@@ -96,12 +93,7 @@ class DivActionHandlerTest {
setVariable("string_var", "value")
val isHandled = handleTypedAction(
DivActionTyped.SetVariable(
DivActionSetVariable(
value = typedValue("new value"),
variableName = Expression.constant("string_var")
)
)
setVariableAction(name = "string_var", value = typedValue("new value"))
)
Assert.assertTrue(isHandled)
@@ -111,12 +103,7 @@ class DivActionHandlerTest {
@Test
fun `SetVariable action does not add new variable`() {
val isHandled = handleTypedAction(
DivActionTyped.SetVariable(
DivActionSetVariable(
value = typedValue("new value"),
variableName = Expression.constant("string_var")
)
)
setVariableAction(name = "string_var", value = typedValue("new value"))
)
Assert.assertTrue(isHandled)
@@ -128,12 +115,7 @@ class DivActionHandlerTest {
setVariable("string_var", "value")
val isHandled = handleTypedAction(
DivActionTyped.SetVariable(
DivActionSetVariable(
value = typedValue(123),
variableName = Expression.constant("string_var")
)
)
setVariableAction(name = "string_var", value = typedValue(123))
)
Assert.assertTrue(isHandled)
@@ -514,10 +496,7 @@ class DivActionHandlerTest {
private fun handleTypedAction(action: DivActionTyped): Boolean {
return underTest.handleAction(
DivAction(
logId = Expression.constant("log_id"),
typed = action
),
action(typed = action),
divView,
divView.expressionResolver
)
@@ -544,12 +523,4 @@ class DivActionHandlerTest {
Variable.DictVariable(name = name, defaultValue = value)
)
}
private fun typedValue(value: String): DivTypedValue {
return DivTypedValue.Str(StrValue(Expression.constant(value)))
}
private fun typedValue(value: Long): DivTypedValue {
return DivTypedValue.Integer(IntegerValue(Expression.constant(value)))
}
}
@@ -8,13 +8,14 @@ import com.yandex.div.core.player.DivPlayerPreloader
import com.yandex.div.core.preload.PreloadResult
import com.yandex.div.core.view2.DivImagePreloader
import com.yandex.div.json.expressions.Expression
import com.yandex.div.test.data.constant
import com.yandex.div.test.data.text
import com.yandex.div2.Div
import com.yandex.div2.DivContainer
import com.yandex.div2.DivCustom
import com.yandex.div2.DivExtension
import com.yandex.div2.DivInput
import com.yandex.div2.DivSeparator
import com.yandex.div2.DivText
import com.yandex.div2.DivVideo
import com.yandex.div2.DivVideoSource
import org.junit.Test
@@ -43,8 +44,7 @@ class DivPreloaderTest {
private val extensionHandlers = listOf<DivExtensionHandler>(mock(), mock())
private val extensionHandlersController = DivExtensionController(extensionHandlers)
private val text = DivText(text = Expression.constant("test"))
private val divText = Div.Text(text)
private val divText = text(text = constant("test"))
private val custom = DivCustom(customType = "test")
private val divCustom = Div.Custom(custom)
@@ -12,11 +12,11 @@ import com.yandex.div.core.view2.divs.DivActionBinder
import com.yandex.div.core.view2.errors.ErrorCollector
import com.yandex.div.internal.expressions.DivExpressionParser
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.expression.MultiplatformTestUtils
import com.yandex.div.test.expression.MultiplatformTestUtils.toSortedList
import com.yandex.div.test.expression.TestCaseOrError
import com.yandex.div.test.crossplatform.MultiplatformTestUtils
import com.yandex.div.test.crossplatform.ParsingResult
import org.json.JSONArray
import org.json.JSONObject
import org.junit.After
@@ -26,16 +26,17 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.any
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
@RunWith(Parameterized::class)
class EvaluableMultiplatformTest(private val caseOrError: TestCaseOrError<ExpressionTestCase>) {
class EvaluableMultiplatformTest(
private val caseParsingResult: ParsingResult<ExpressionTestCase>
) {
@Rule
@JvmField
@get:Rule
val localeRule = LocaleRule()
private lateinit var testCase: ExpressionTestCase
@@ -49,10 +50,9 @@ class EvaluableMultiplatformTest(private val caseOrError: TestCaseOrError<Expres
private val warnings = mutableListOf<String>()
private val errors = mutableListOf<String>()
private val warningCaptor = argumentCaptor<Throwable>()
private val errorCollector = mock<ErrorCollector> {
on { logWarning(warningCaptor.capture()) } doAnswer {
warningCaptor.lastValue.cause?.message?.let { warnings += it }
on { logWarning(any()) } doAnswer { answer ->
(answer.arguments.first() as Throwable).cause?.message?.let { warnings += it }
}
}
private val testParsingLogger = ParsingErrorLogger { e ->
@@ -72,7 +72,12 @@ class EvaluableMultiplatformTest(private val caseOrError: TestCaseOrError<Expres
warnings.clear()
errors.clear()
testCase = caseOrError.getCaseOrThrow()
when (caseParsingResult) {
is ParsingResult.Success -> testCase = caseParsingResult.value
is ParsingResult.Error -> caseParsingResult.throwException()
}
val testDivData = createDivDataFromTestVars(testCase.variables, testCase.functions, testParsingLogger)
runtimeProvider = ExpressionsRuntimeProvider(
@@ -111,8 +116,8 @@ class EvaluableMultiplatformTest(private val caseOrError: TestCaseOrError<Expres
is JSONArray, is JSONObject -> {
if (testCase.expectedType == VALUE_TYPE_UNORDERED_ARRAY){
checkEquality(testCase) { message, expected, actual ->
val expectedList = (expected as JSONArray).toSortedList()
val actualList = (actual as JSONArray).toSortedList()
val expectedList = (expected as JSONArray).map { toString() }.sorted()
val actualList = (actual as JSONArray).map { toString() }.sorted()
Assert.assertEquals(message, expectedList.toString(), actualList.toString())
}
} else {
@@ -172,17 +177,16 @@ class EvaluableMultiplatformTest(private val caseOrError: TestCaseOrError<Expres
@JvmStatic
@Parameterized.Parameters(name = "{0}")
fun cases(): List<TestCaseOrError<ExpressionTestCase>> {
val cases = mutableListOf<TestCaseOrError<ExpressionTestCase>>()
val errors = MultiplatformTestUtils.walkJSONs(TEST_CASES_FILE_PATH) { file, jsonString ->
val newCases = ExpressionTestCaseUtils.parseTestCases(JSONObject(jsonString), file.name)
cases.addAll(newCases)
}.map { TestCaseOrError<ExpressionTestCase>(it) }
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
}
}
@@ -6,16 +6,14 @@ 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
import com.yandex.div.interactive.IntegrationTestCase
import com.yandex.div.internal.parser.ANY_TO_BOOLEAN
import com.yandex.div.internal.util.map
import com.yandex.div.json.ParsingErrorLogger
import com.yandex.div.test.expression.MultiplatformTestUtils.isForAndroidPlatform
import com.yandex.div.test.expression.MultiplatformTestUtils.parsePlatform
import com.yandex.div.test.expression.MultiplatformTestUtils.toListOfJSONObject
import com.yandex.div.test.expression.TestCaseOrError
import com.yandex.div.test.expression.TestCaseParsingError
import com.yandex.div.test.expression.parseAsUTC
import com.yandex.div.test.crossplatform.ParsingResult
import com.yandex.div.test.crossplatform.isForAndroid
import com.yandex.div.test.crossplatform.parseDateTime
import com.yandex.div.test.crossplatform.platforms
import com.yandex.div.test.crossplatform.toObjectList
import com.yandex.div2.DivData
import com.yandex.div2.DivEvaluableType
import com.yandex.div2.DivFunction
@@ -39,46 +37,51 @@ object ExpressionTestCaseUtils {
const val VALUE_TYPE_UNORDERED_ARRAY = "unordered_array"
private const val VALUE_TYPE_UNIT = "unit"
private const val VALUE_TYPE_ERROR = "error"
private const val VALUE_TYPE_VARIABLE = "variable"
private const val CASES_FIELD = "cases"
private const val CASE_VARIABLES_FIELD = "variables"
private const val CASE_FUNCTIONS_FIELD = "functions"
private const val CASE_VARIABLE_NAME_FIELD = "variable_name"
private const val CASE_EXPECTED_VALUE_FIELD = "expected"
private const val CASE_EXPECTED_WARNINGS_FIELD = "expected_warnings"
private const val CASE_EXPRESSION_VALUE_FIELD = "expression"
private const val TYPE_FIELD = "type"
private const val VALUE_FIELD = "value"
fun parseTestCases(json: JSONObject, fileName: String): List<TestCaseOrError<ExpressionTestCase>> {
fun parseTestCases(
json: JSONObject,
fileName: String
): List<ParsingResult<ExpressionTestCase>> {
return json.optJSONArray(CASES_FIELD)
.toListOfJSONObject()
.filter { isForAndroidPlatform(parsePlatform(it)) }
.toObjectList()
.filter { it.isForAndroid }
.map { parseTestCase(it, fileName) }
}
private fun parseTestCase(json: JSONObject, fileName: String): TestCaseOrError<ExpressionTestCase> {
private fun parseTestCase(
json: JSONObject,
fileName: String
): ParsingResult<ExpressionTestCase> {
try {
val testCase = ExpressionTestCase(
fileName,
json.getString(CASE_EXPRESSION_VALUE_FIELD),
json.optJSONArray(CASE_VARIABLES_FIELD).toListOfJSONObject(),
json.optJSONArray(CASE_FUNCTIONS_FIELD).toListOfJSONObject(),
parsePlatform(json),
json.optJSONArray(CASE_VARIABLES_FIELD).toObjectList(),
json.optJSONArray(CASE_FUNCTIONS_FIELD).toObjectList(),
json.platforms,
json.getJSONObject(CASE_EXPECTED_VALUE_FIELD).type,
json.getJSONObject(CASE_EXPECTED_VALUE_FIELD).getValue(),
json.optJSONArray(CASE_EXPECTED_WARNINGS_FIELD)?.map { it as String } ?: emptyList(),
)
return TestCaseOrError(testCase)
return ParsingResult.Success(testCase)
} catch (e: JSONException) {
return TestCaseOrError(TestCaseParsingError(fileName, json, e))
return ParsingResult.Error(fileName, json, e)
}
}
fun checkDuplicates(cases: Sequence<TestCaseOrError<ExpressionTestCase>>) {
val duplicate = cases.mapNotNull { it.testCase }
fun checkDuplicates(cases: Sequence<ParsingResult<ExpressionTestCase>>) {
val duplicate = cases
.filterIsInstance<ParsingResult.Success<ExpressionTestCase>>()
.map { it.value }
.groupingBy { it.fileName to it.description }
.eachCount()
.filterValues { it > 1 }
@@ -102,7 +105,7 @@ object ExpressionTestCaseUtils {
VALUE_TYPE_ARRAY -> getJSONArray(VALUE_FIELD)
VALUE_TYPE_UNORDERED_ARRAY -> getJSONArray(VALUE_FIELD)
VALUE_TYPE_BOOLEAN, VALUE_TYPE_BOOL_INT -> ANY_TO_BOOLEAN(get(VALUE_FIELD))
VALUE_TYPE_DATE_TIME -> parseAsUTC(getString(VALUE_FIELD))
VALUE_TYPE_DATE_TIME -> parseDateTime(getString(VALUE_FIELD))
VALUE_TYPE_UNIT -> Unit
VALUE_TYPE_ERROR -> EvaluableException(optString(VALUE_FIELD))
else -> throw IllegalAccessException("Unknown variable type: $type")
@@ -112,21 +115,6 @@ object ExpressionTestCaseUtils {
val JSONObject.type: String get() = getString(TYPE_FIELD)
fun JSONObject.getVariableValue(type: String): Any {
return when (type) {
VALUE_TYPE_STRING -> getString(VALUE_FIELD)
VALUE_TYPE_INTEGER -> getLong(VALUE_FIELD)
VALUE_TYPE_DECIMAL ->getDouble(VALUE_FIELD)
VALUE_TYPE_BOOLEAN -> get(VALUE_FIELD)
VALUE_TYPE_COLOR -> Color.parse(getString(VALUE_FIELD))
VALUE_TYPE_URL -> Uri.parse(getString(VALUE_FIELD))
VALUE_TYPE_DICT -> getJSONObject(VALUE_FIELD)
VALUE_TYPE_ARRAY -> getJSONArray(VALUE_FIELD)
VALUE_TYPE_DATE_TIME -> parseAsUTC(getString(VALUE_FIELD))
else -> throw IllegalAccessException("Unknown variable type: $type")
}
}
fun createVariable(type: String, name: String, value: Any?): Variable {
return when (type) {
VALUE_TYPE_STRING -> Variable.StringVariable(name, value as String? ?: "")
@@ -141,48 +129,6 @@ object ExpressionTestCaseUtils {
}
}
fun parseIntegrationTestCase(fileName: String, jsonString: String): List<TestCaseOrError<IntegrationTestCase>> {
val json = JSONObject(jsonString)
return json.getJSONArray(CASES_FIELD).toListOfJSONObject().mapIndexedNotNull { index, jsonObject ->
try {
val data = JSONObject(jsonString).getJSONObject("div_data")
jsonObject.parseStep(fileName, data, index)?.let { TestCaseOrError(it) }
} catch (e: JSONException) {
TestCaseOrError(TestCaseParsingError(fileName, json, e))
}
}
}
private fun JSONObject.parseStep(
fileName: String,
data: JSONObject,
index: Int,
): IntegrationTestCase? {
if (!isForAndroidPlatform(parsePlatform(this))) return null
val actions = optJSONArray("div_actions")?.toListOfJSONObject()
var name = "$fileName Case $index"
actions?.forEach {
name += ", ${it.getString("log_id")}"
}
val expected = getJSONArray(CASE_EXPECTED_VALUE_FIELD).toListOfJSONObject().map {
when (val type = it.type) {
VALUE_TYPE_VARIABLE -> {
IntegrationTestCase.ExpectedResult.Variable(
it.getString(CASE_VARIABLE_NAME_FIELD),
it.getJSONObject(VALUE_FIELD)
)
}
VALUE_TYPE_ERROR -> IntegrationTestCase.ExpectedResult.Error(it.getString(VALUE_FIELD))
else -> throw JSONException("Unknown expected result type: $type")
}
}
return IntegrationTestCase(name, data, actions, expected)
}
fun createDivDataFromTestVars(
vars: List<JSONObject>,
functions: List<JSONObject>,
@@ -1,36 +1,37 @@
package com.yandex.div.evaluable.multiplatform
package com.yandex.div.core.expression
import com.yandex.div.evaluable.EvaluableException
import com.yandex.div.evaluable.EvaluableType
import com.yandex.div.evaluable.FunctionArgument
import com.yandex.div.evaluable.function.BuiltinFunctionProvider
import com.yandex.div.test.expression.MultiplatformTestUtils
import com.yandex.div.test.expression.MultiplatformTestUtils.isForAndroidPlatform
import com.yandex.div.test.expression.MultiplatformTestUtils.parsePlatform
import com.yandex.div.test.expression.MultiplatformTestUtils.toListOfJSONObject
import com.yandex.div.test.expression.TestCaseOrError
import com.yandex.div.test.expression.TestCaseParsingError
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.toObjectList
import org.json.JSONException
import org.json.JSONObject
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import java.io.File
@RunWith(Parameterized::class)
class SignaturesMultiplatformTest(caseOrError: TestCaseOrError<SignatureTestCase>) {
private val functionProvider = BuiltinFunctionProvider
private val signature = caseOrError.getCaseOrThrow()
class SignaturesMultiplatformTest(testCaseParsingResult: ParsingResult<SignatureTestCase>) {
private val functionProvider = GeneratedBuiltinFunctionProvider
private val signature = testCaseParsingResult.getOrThrow()
@Test
fun runSignatureTests() {
try {
functionProvider.ensureFunctionRegistered(
signature.functionName,
signature.arguments,
signature.returnType,
signature.isMethod
)
val name = signature.functionName
val argumentTypes = signature.arguments.map { it.type }
val function = if (signature.isMethod) {
functionProvider.getMethod(name, argumentTypes)
} else {
functionProvider.get(name, argumentTypes)
}
assertEquals(signature.returnType, function.resultType)
} catch (e: EvaluableException) {
throw RuntimeException("Test for signature \"$signature\" failed.", e)
}
@@ -61,20 +62,22 @@ class SignaturesMultiplatformTest(caseOrError: TestCaseOrError<SignatureTestCase
@JvmStatic
@Parameterized.Parameters(name = "{0}")
fun signatures(): List<TestCaseOrError<SignatureTestCase>> {
val cases = mutableListOf<TestCaseOrError<SignatureTestCase>>()
val errors = MultiplatformTestUtils.walkJSONs(File(SIGNATURES_DIR_PATH)) { file, jsonString ->
val newCases = JSONObject(jsonString).optJSONArray(FIELD_SIGNATURE).toListOfJSONObject()
.filter { isForAndroidPlatform(parsePlatform(it)) }
.map { parseSignature(file, it) }
.filter { it.error == null }
cases.addAll(newCases)
}
return errors.map { TestCaseOrError<SignatureTestCase>(it) } + cases
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
}
private fun parseSignature(file: File, json: JSONObject): TestCaseOrError<SignatureTestCase> {
private fun parseSignature(file: File, json: JSONObject): ParsingResult<SignatureTestCase> {
val functionName = json.getString(FIELD_SIGNATURE_NAME)
val arguments = json.optJSONArray(FIELD_SIGNATURE_ARGUMENTS)?.let { array ->
val result = mutableListOf<FunctionArgument>()
@@ -91,10 +94,10 @@ class SignaturesMultiplatformTest(caseOrError: TestCaseOrError<SignatureTestCase
val resultType = try {
EvaluableType.valueOf(json.getString(FIELD_SIGNATURE_RETURN_TYPE).uppercase())
} catch (e: JSONException) {
return TestCaseOrError(TestCaseParsingError(file.name, json, e))
return ParsingResult.Error(file.name, json, e)
}
val isMethod = json.optBoolean(FIELD_SIGNATURE_IS_METHOD)
return TestCaseOrError(SignatureTestCase(
return ParsingResult.Success(SignatureTestCase(
"$functionName(${arguments ?: ""}) $resultType",
functionName,
arguments ?: emptyList(),
@@ -5,6 +5,8 @@ import android.net.Uri
import com.yandex.div.core.expression.ExpressionResolverImpl
import com.yandex.div.core.expression.ExpressionsRuntime
import com.yandex.div.json.expressions.Expression
import com.yandex.div.test.data.data
import com.yandex.div.test.data.variable
import com.yandex.div2.Div
import com.yandex.div2.DivCircleShape
import com.yandex.div2.DivCollectionItemBuilder
@@ -35,7 +37,6 @@ import com.yandex.div2.DivText
import com.yandex.div2.DivTrigger
import com.yandex.div2.DivVariable
import com.yandex.div2.DivVideo
import com.yandex.div2.StrVariable
import org.json.JSONArray
import org.json.JSONObject
import org.junit.Assert.assertEquals
@@ -100,7 +101,7 @@ class RuntimeStoreFillerTest {
val text3 = createText()
val container1 = createContainer(items = listOf(text3))
val container2 = createContainer(items = listOf(text1, text2, container1))
val data = createData(container2)
val data = data(content = container2)
underTest.fillStore(store, data)
@@ -110,7 +111,7 @@ class RuntimeStoreFillerTest {
@Test
fun `create runtime for root div with id`() {
val text = createText("root_id")
val data = createData(text)
val data = data(content = text)
underTest.fillStore(store, data)
@@ -120,7 +121,7 @@ class RuntimeStoreFillerTest {
@Test
fun `create runtime for root div without id`() {
val text = createText()
val data = createData(text)
val data = data(content = text)
underTest.fillStore(store, data)
@@ -131,7 +132,7 @@ class RuntimeStoreFillerTest {
fun `create runtime for div with id`() {
val text = createText("text_id")
val container = createContainer(items = listOf(text))
val data = createData(container)
val data = data(content = container)
underTest.fillStore(store, data)
@@ -142,7 +143,7 @@ class RuntimeStoreFillerTest {
fun `create runtime for div without id`() {
val text = createText()
val container = createContainer(items = listOf(text))
val data = createData(container)
val data = data(content = container)
underTest.fillStore(store, data)
@@ -154,7 +155,7 @@ class RuntimeStoreFillerTest {
val text1 = createText("item1")
val text2 = createText()
val gallery = createGallery(items = listOf(text1, text2))
val data = createData(gallery)
val data = data(content = gallery)
underTest.fillStore(store, data)
@@ -166,7 +167,7 @@ class RuntimeStoreFillerTest {
val text1 = createText("page1")
val text2 = createText()
val pager = createPager(items = listOf(text1, text2))
val data = createData(pager)
val data = data(content = pager)
underTest.fillStore(store, data)
@@ -178,7 +179,7 @@ class RuntimeStoreFillerTest {
val text1 = createText("item1")
val text2 = createText()
val grid = Div.Grid(DivGrid(columnCount = Expression.constant(2), items = listOf(text1, text2)))
val data = createData(grid)
val data = data(content = grid)
underTest.fillStore(store, data)
@@ -191,7 +192,7 @@ class RuntimeStoreFillerTest {
val text2 = createText()
val items = listOf(text1, text2).map { DivTabs.Item(div = it, title = Expression.constant("Tab")) }
val tabs = Div.Tabs(DivTabs(items = items))
val data = createData(tabs)
val data = data(content = tabs)
underTest.fillStore(store, data)
@@ -204,7 +205,7 @@ class RuntimeStoreFillerTest {
DivState.State(div = div, stateId = "state$index")
}
val state = Div.State(DivState(id = "state_id", states = states))
val data = createData(state)
val data = data(content = state)
underTest.fillStore(store, data)
@@ -216,7 +217,7 @@ class RuntimeStoreFillerTest {
val text1 = createText("custom_item1")
val text2 = createText()
val custom = Div.Custom(DivCustom(customType = "custom", id = "custom_id", items = listOf(text1, text2)))
val data = createData(custom)
val data = data(content = custom)
underTest.fillStore(store, data)
@@ -246,7 +247,7 @@ class RuntimeStoreFillerTest {
val container = createContainer("container_id", listOf(
image, gifImage, separator, indicator, slider, input, select, video, switch
))
val data = createData(container)
val data = data(content = container)
underTest.fillStore(store, data)
@@ -263,7 +264,7 @@ class RuntimeStoreFillerTest {
val innerContainer = createContainer("inner", listOf(text1, text2))
val text3 = createText("text3")
val outerContainer = createContainer(items = listOf(innerContainer, text3))
val data = createData(outerContainer)
val data = data(content = outerContainer)
underTest.fillStore(store, data)
@@ -276,7 +277,7 @@ class RuntimeStoreFillerTest {
val text2 = createText("duplicate")
val text3 = createText("duplicate")
val container = createContainer("container", listOf(text1, text2, text3))
val data = createData(container)
val data = data(content = container)
underTest.fillStore(store, data)
@@ -286,15 +287,14 @@ class RuntimeStoreFillerTest {
@Test
fun `fillStore returns root runtime`() {
val data = createData(createText())
val data = data(content = createText())
val result = underTest.fillStore(store, data)
assertSame(rootRuntime, result)
}
@Test
fun `create runtime for container with null items`() {
val container = createContainer()
val data = createData(container)
val data = data(content = createContainer())
underTest.fillStore(store, data)
@@ -304,7 +304,7 @@ class RuntimeStoreFillerTest {
@Test
fun `create runtime for container with empty items`() {
val container = createContainer(items = emptyList())
val data = createData(container)
val data = data(content = container)
underTest.fillStore(store, data)
@@ -313,9 +313,9 @@ class RuntimeStoreFillerTest {
@Test
fun `create child runtime for div with variables`() {
val text = createText("var_div", variables = listOf(DivVariable.Str(StrVariable("x", Expression.constant("")))))
val text = createText("var_div", variables = listOf(variable("x", "")))
val container = createContainer(items = listOf(text))
val data = createData(container)
val data = data(content = container)
underTest.fillStore(store, data)
@@ -327,7 +327,7 @@ class RuntimeStoreFillerTest {
fun `create child runtime for div with functions`() {
val text = createText("var_div", functions = listOf(DivFunction(emptyList(), "", "", DivEvaluableType.INTEGER)))
val container = createContainer(items = listOf(text))
val data = createData(container)
val data = data(content = container)
underTest.fillStore(store, data)
@@ -339,7 +339,7 @@ class RuntimeStoreFillerTest {
fun `copy resolver for div with triggers`() {
val text = createText("var_div", triggers = listOf(DivTrigger(emptyList(), Expression.constant(true))))
val container = createContainer(items = listOf(text))
val data = createData(container)
val data = data(content = container)
underTest.fillStore(store, data)
@@ -351,7 +351,7 @@ class RuntimeStoreFillerTest {
fun `create runtime for items from builder in container`() {
val builder = createItemBuilder()
val container = createContainer(builder = builder)
val data = createData(container)
val data = data(content = container)
underTest.fillStore(store, data)
@@ -362,7 +362,7 @@ class RuntimeStoreFillerTest {
fun `create runtime for items from builder in gallery`() {
val builder = createItemBuilder()
val gallery = createGallery(builder = builder)
val data = createData(gallery)
val data = data(content = gallery)
underTest.fillStore(store, data)
@@ -373,7 +373,7 @@ class RuntimeStoreFillerTest {
fun `create runtime for items from builder in pager`() {
val builder = createItemBuilder()
val pager = createPager(builder = builder)
val data = createData(pager)
val data = data(content = pager)
underTest.fillStore(store, data)
@@ -390,7 +390,7 @@ class RuntimeStoreFillerTest {
DivState.State(stateId = "state_1")
)
))
val data = createData(state)
val data = data(content = state)
underTest.fillStore(store, data)
@@ -456,8 +456,6 @@ class RuntimeStoreFillerTest {
private fun createData(states: List<DivData.State>) = DivData(logId = "test", states = states)
private fun createData(rootDiv: Div) = createData(listOf(DivData.State(rootDiv, 0)))
private fun assertPaths(expectedSize: Int, vararg paths: String) {
assertEquals(expectedSize, runtimePaths.size)
paths.forEach { assertTrue(runtimePaths.contains(it)) }
@@ -3,11 +3,9 @@ package com.yandex.div.core.expression.local
import com.yandex.div.core.expression.ExpressionResolverImpl
import com.yandex.div.core.expression.ExpressionsRuntime
import com.yandex.div.core.state.DivStatePath
import com.yandex.div.json.expressions.Expression
import com.yandex.div.test.data.variable
import com.yandex.div2.Div
import com.yandex.div2.DivBase
import com.yandex.div2.DivVariable
import com.yandex.div2.IntegerVariable
import org.junit.Assert
import org.junit.Test
import org.mockito.kotlin.any
@@ -90,7 +88,7 @@ class RuntimeStoreImplTest {
}
private fun setVariable() {
val variables = listOf(DivVariable.Integer(IntegerVariable(CHILD_VARIABLE, Expression.constant(123))))
whenever(divBase.variables).doReturn(variables)
val variables = listOf(variable(CHILD_VARIABLE, 123))
whenever(divBase.variables) doReturn variables
}
}
@@ -3,8 +3,8 @@ package com.yandex.div.core.util
import android.net.Uri
import com.yandex.div.core.asExpression
import com.yandex.div.core.mockExpressionResolver
import com.yandex.div.test.data.container
import com.yandex.div2.Div
import com.yandex.div2.DivContainer
import com.yandex.div2.DivGallery
import com.yandex.div2.DivImage
import com.yandex.div2.DivText
@@ -30,7 +30,7 @@ class DivWalkTreeTest {
@Test
fun `walking multiple node hierarchy`() {
val rootDiv = divContainer(
val rootDiv = container(
listOf(
divText("lorem ipsum"),
divImage("https://none")
@@ -45,9 +45,9 @@ class DivWalkTreeTest {
@Test
fun `walking uses depth-first order`() {
val rootDiv = divContainer(
val rootDiv = container(
listOf(
divContainer(
container(
listOf(
divText("lorem ipsum"),
divImage("https://none")
@@ -65,7 +65,7 @@ class DivWalkTreeTest {
@Test
fun `onEnter excludes subtree from walking`() {
val rootDiv = divContainer(
val rootDiv = container(
listOf(
divGallery(
listOf(
@@ -86,10 +86,10 @@ class DivWalkTreeTest {
@Test
fun `maxDepth limits walk depth`() {
val rootDiv = divContainer(
val rootDiv = container(
listOf(
divText("lorem ipsum"),
divContainer(
container(
listOf(
divText("lorem ipsum"),
divImage("https://none")
@@ -107,7 +107,7 @@ class DivWalkTreeTest {
@Test
fun `onEnter() called only for branch nodes`() {
val rootDiv = divContainer(
val rootDiv = container(
listOf(
divText("lorem ipsum"),
divGallery(
@@ -132,7 +132,7 @@ class DivWalkTreeTest {
@Test
fun `onLeave() called only for branch nodes`() {
val rootDiv = divContainer(
val rootDiv = container(
listOf(
divText("lorem ipsum"),
divGallery(
@@ -160,10 +160,6 @@ class DivWalkTreeTest {
return Div.Image(DivImage(imageUrl = Uri.parse(imageUrl).asExpression()))
}
private fun divContainer(items: List<Div>): Div {
return Div.Container(DivContainer(items = items))
}
private fun divGallery(items: List<Div>): Div {
return Div.Gallery(DivGallery(items = items))
}
@@ -1,13 +1,13 @@
package com.yandex.div.core.view2
import android.view.View
import com.yandex.div.DivDataTag
import com.yandex.div.core.asExpression
import com.yandex.div.json.expressions.ExpressionResolver
import com.yandex.div.test.data.constant
import com.yandex.div.test.data.text
import com.yandex.div2.Div
import com.yandex.div2.DivDisappearAction
import com.yandex.div2.DivText
import com.yandex.div2.DivVisibilityAction
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
@@ -65,16 +65,12 @@ class DivVisibilityActionTrackerTest {
visibilityDuration = delay.asExpression()
)
}.toList()
private val divBase1 = DivText(text = "test1".asExpression(), visibilityActions = listOf(action1))
private val divBase2 = DivText(text = "test2".asExpression(), visibilityActions = listOf(action2))
private val divBase3 = DivText(text = "test3".asExpression(), visibilityActions = lottaActions)
private val divBase4 = DivText(text = "test4".asExpression(), visibilityActions = actionsWithThreeDifferentDelays)
private val divBase5 = DivText(text = "test5".asExpression(), visibilityActions = listOf(action1), disappearActions = listOf(disappearAction1))
private val div1 = Div.Text(divBase1)
private val div2 = Div.Text(divBase2)
private val div3 = Div.Text(divBase3)
private val div4 = Div.Text(divBase4)
private val div5 = Div.Text(divBase5)
private val div1 = text(text = constant("test1"), visibilityActions = listOf(action1))
private val div2 = text(text = constant("test2"), visibilityActions = listOf(action2))
private val div3 = text(text = constant("test3"), visibilityActions = lottaActions)
private val div4 = text(text = constant("test4"), visibilityActions = actionsWithThreeDifferentDelays)
private val div5 = text(text = constant("test5"), visibilityActions = listOf(action1), disappearActions = listOf(disappearAction1))
private val visibilityActionTracker = DivVisibilityActionTracker(
viewVisibilityCalculator,
@@ -8,6 +8,7 @@ import com.yandex.div.core.view2.Div2View
import com.yandex.div.core.view2.divs.widgets.DivLineHeightTextView
import com.yandex.div.json.expressions.Expression
import com.yandex.div.json.expressions.ExpressionResolver
import com.yandex.div.test.data.action
import com.yandex.div2.DivAction
import com.yandex.div2.DivBorder
import com.yandex.div2.DivCornersRadius
@@ -58,8 +59,8 @@ class DivFocusBinderTest {
private val resolver = mock<ExpressionResolver>()
private val context = BindingContext(divView, resolver)
private val defaultBorder = DivBorder(hasShadow = Expression.constant(true))
private val focusActions = listOf(mockDivAction("focus"))
private val blurActions = listOf(mockDivAction("blur"))
private val focusActions = listOf(action(url = "focus"))
private val blurActions = listOf(action(url = "blur"))
private val underTest = DivFocusBinder(actionBinder)
@@ -339,7 +340,7 @@ class DivFocusBinderTest {
@Test
fun `handle focus actions on focus after rebind actions`() {
val newActions = listOf(mockDivAction("new_focus"))
val newActions = listOf(action(url = "new_focus"))
bindActions(focusActions, null)
bindActions(newActions, null)
@@ -351,7 +352,7 @@ class DivFocusBinderTest {
@Test
fun `handle blur actions on blur after rebind actions`() {
val newActions = listOf(mockDivAction("new_blur"))
val newActions = listOf(action(url = "new_blur"))
bindActions(null, blurActions)
bindActions(null, newActions)
@@ -383,13 +384,6 @@ class DivFocusBinderTest {
private fun onFocusChange(hasFocus: Boolean) = focusListener?.onFocusChange(view, hasFocus)
private fun mockDivAction(id: String): DivAction {
return DivAction(
logId = Expression.constant(id),
url = mock()
)
}
private fun verifyBorderSet(
border: DivBorder = defaultBorder,
mode: VerificationMode = times(1)
@@ -1,7 +1,6 @@
package com.yandex.div.core.view2.local
import android.app.Activity
import android.net.Uri
import android.widget.TextView
import com.yandex.div.DivDataTag
import com.yandex.div.core.Div2Context
@@ -10,8 +9,7 @@ import com.yandex.div.core.view2.Div2View
import com.yandex.div.core.view2.divs.widgets.DivLinearLayout
import com.yandex.div.data.DivParsingEnvironment
import com.yandex.div.internal.util.textString
import com.yandex.div.json.expressions.Expression
import com.yandex.div2.DivAction
import com.yandex.div.test.data.action
import com.yandex.div2.DivData
import org.json.JSONObject
import org.junit.Assert
@@ -116,21 +114,18 @@ class LocalTriggersTest {
private fun setState(stateNum: Int) {
when (stateNum) {
1 -> handleAction(Uri.parse("div-action://set_state?state_id=0/sample/first"))
2 -> handleAction(Uri.parse("div-action://set_state?state_id=0/sample/second"))
1 -> handleAction("div-action://set_state?state_id=0/sample/first")
2 -> handleAction("div-action://set_state?state_id=0/sample/second")
else -> Unit
}
}
private fun setVariableValue(value: Int) {
handleAction(Uri.parse("div-action://set_variable?name=counter&value=$value"))
handleAction("div-action://set_variable?name=counter&value=$value")
}
private fun handleAction(uri: Uri) {
div2View.handleAction(DivAction(
logId = Expression.constant("id"),
url = Expression.constant(uri))
)
private fun handleAction(url: String) {
div2View.handleAction(action(url = url))
}
private fun assertTextShown(expected: String, view: TextView) {
@@ -1,7 +1,6 @@
package com.yandex.div.core.view2.local
import android.app.Activity
import android.net.Uri
import android.widget.EditText
import android.widget.TextView
import com.yandex.div.DivDataTag
@@ -12,9 +11,8 @@ import com.yandex.div.core.view2.divs.widgets.DivLinearLayout
import com.yandex.div.core.view2.divs.widgets.DivStateLayout
import com.yandex.div.data.DivParsingEnvironment
import com.yandex.div.internal.util.textString
import com.yandex.div.json.expressions.Expression
import com.yandex.div.test.data.action
import com.yandex.div2.Div
import com.yandex.div2.DivAction
import com.yandex.div2.DivBase
import com.yandex.div2.DivData
import org.json.JSONObject
@@ -115,14 +113,11 @@ class LocalVariablesTest {
}
private fun setState(stateNum: Int) {
handleAction(Uri.parse("div-action://set_state?state_id=0/label/state_$stateNum"))
handleAction("div-action://set_state?state_id=0/label/state_$stateNum")
}
private fun handleAction(uri: Uri) {
div2View.handleAction(DivAction(
logId = Expression.constant("id"),
url = Expression.constant(uri))
)
private fun handleAction(url: String) {
div2View.handleAction(action(url = url))
}
private fun setVariable(name: String, value: String, path: String) {
@@ -5,7 +5,6 @@ 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
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
@@ -16,12 +15,13 @@ 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.expression.MultiplatformTestUtils
import com.yandex.div.test.expression.TestCaseOrError
import com.yandex.div.test.crossplatform.IntegrationTestCase
import com.yandex.div.test.crossplatform.MultiplatformTestUtils
import com.yandex.div.test.crossplatform.ParsingResult
import com.yandex.div2.DivAction
import com.yandex.div2.DivData
import org.junit.After
import org.junit.Assert
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.ParameterizedRobolectricTestRunner
@@ -29,23 +29,24 @@ import org.robolectric.Robolectric
import java.util.UUID
@RunWith(ParameterizedRobolectricTestRunner::class)
class IntegrationMultiplatformTest(testCase: TestCaseOrError<IntegrationTestCase>) {
class IntegrationMultiplatformTest(testCaseParsingResult: ParsingResult<IntegrationTestCase>) {
private val case = testCase.getCaseOrThrow()
private val expected = case.expected
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 runTestCase() {
val env = DivParsingEnvironment(LOGGER)
case.divData.optJSONObject("templates")?.let {
fun run() {
val env = DivParsingEnvironment(logger)
testCase.divData.optJSONObject("templates")?.let {
env.parseTemplates(it)
}
val divData = runCatching {
DivData(env, case.divData.getJSONObject("card"))
DivData(env, testCase.divData.getJSONObject("card"))
}.getOrElse {
var errorIsExpected = false
expected.forEach { e ->
expectedResults.forEach { e ->
if (e !is IntegrationTestCase.ExpectedResult.Error) return@forEach
checkError(e)
errorIsExpected = true
@@ -58,34 +59,44 @@ class IntegrationMultiplatformTest(testCase: TestCaseOrError<IntegrationTestCase
}
val context = Div2Context(activity, DivConfiguration.Builder(IMAGE_LOADER_STUB).build())
expected.forEach { result ->
val variable = result as? IntegrationTestCase.ExpectedResult.Variable ?: return@forEach
if (divData.variables?.any { it.name == variable.name } == true) return@forEach
context.divVariableController.declare(createVariable(variable.type, variable.name, null))
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
}
}
val divView = Div2View(context)
divView.setData(divData, DivDataTag(UUID.randomUUID().toString()))
divView.observeErrors { errors, _ ->
errors.forEach { error ->
LOGGER.logErrorDirectly(error)
logger.logErrorDirectly(error)
}
}
case.actions?.forEach {
testCase.actions.forEach {
runCatching { divView.handleAction(DivAction(env, it)) }
}
expected.forEach {
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()
val actualValue = divView.expressionResolver
.getVariable(it.name)
?.getWrappedValue()
if (it.type == VALUE_TYPE_DICT || it.type == VALUE_TYPE_ARRAY) {
Assert.assertEquals(expectedValue.toString(), actualValue.toString())
assertEquals(expectedValue.toString(), actualValue.toString())
} else {
Assert.assertEquals(expectedValue, actualValue)
assertEquals(expectedValue, actualValue)
}
}
}
@@ -93,35 +104,33 @@ class IntegrationMultiplatformTest(testCase: TestCaseOrError<IntegrationTestCase
}
private fun checkError(expected: IntegrationTestCase.ExpectedResult.Error) {
Assert.assertTrue(
"Expected: <${expected.message}> but was: <${LOGGER.messages.toSet().joinToString(", ")}>",
LOGGER.messages.contains(expected.message)
assertTrue(
"Expected: <${expected.message}> but was: <${
logger.messages.toSet().joinToString(", ")
}>",
logger.messages.contains(expected.message)
)
}
@After
fun clear() = LOGGER.clear()
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 }
private val LOGGER = IntegrationTestLogger()
private val CASES = getCases()
// 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
}
@JvmStatic
@Suppress("unused")
@ParameterizedRobolectricTestRunner.Parameters(name = "{0}")
fun cases() = CASES
private fun getCases(): List<TestCaseOrError<IntegrationTestCase>> {
val cases = mutableListOf<TestCaseOrError<IntegrationTestCase>>()
val errors = MultiplatformTestUtils.walkJSONs(TEST_CASES_FILE_PATH) { file, json ->
val steps = ExpressionTestCaseUtils.parseIntegrationTestCase(file.name, json)
cases.addAll(steps)
}.map { TestCaseOrError<IntegrationTestCase>(it) }
return errors + cases
}
fun cases() = cases
}
}
@@ -1,25 +0,0 @@
package com.yandex.div.interactive
import com.yandex.div.core.expression.ExpressionTestCaseUtils.getVariableValue
import com.yandex.div.core.expression.ExpressionTestCaseUtils.type
import org.json.JSONObject
class IntegrationTestCase(
val name: String,
val divData: JSONObject,
val actions: List<JSONObject>?,
val expected: List<ExpectedResult>,
) {
sealed interface ExpectedResult {
class Variable(val name: String, json: JSONObject): ExpectedResult {
val type: String = json.type
val value = json.getVariableValue(type)
}
class Error(val message: String): ExpectedResult
}
override fun toString() = name
}
@@ -20,6 +20,4 @@ class IntegrationTestLogger : ParsingErrorLogger {
.mapNotNull { it.message }
.forEach { _messages.add(it) }
}
fun clear() = _messages.clear()
}
@@ -1,16 +0,0 @@
plugins {
id 'java-library'
id 'org.jetbrains.kotlin.jvm'
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
dependencies {
implementation project(path: ':div-evaluable')
implementation libs.json
implementation libs.junit
}
@@ -1,89 +0,0 @@
package com.yandex.div.test.expression
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import java.io.File
private const val PLATFORM_FIELD = "platforms"
private const val VALUE_PLATFORM_ANDROID = "android"
private const val JSON_EXTENSION = "json"
private const val DIV2_JSON_PATH = "../../../test_data/"
object MultiplatformTestUtils {
fun walkJSONs(
relativePath: String,
parseAction: (file: File, json: String) -> Unit
): List<TestCaseParsingError> {
return walkJSONs(directory = File(DIV2_JSON_PATH, relativePath), parseAction)
}
fun walkJSONs(
directory: File,
parseAction: (file: File, json: String) -> Unit
): List<TestCaseParsingError> {
val errors = mutableListOf<TestCaseParsingError>()
getFiles(directory)
.forEach { file ->
val json = try {
file.readText(Charsets.UTF_8)
} catch (e: Exception) {
errors.add(
TestCaseParsingError(fileName = file.name, json = null, error = e)
)
return@forEach
}
try {
parseAction(file, json)
} catch (e: JSONException) {
errors.add(
TestCaseParsingError(fileName = file.name, json = null, error = e)
)
}
}
return errors
}
fun parsePlatform(json: JSONObject): List<String> {
return json.getJSONArray(PLATFORM_FIELD).toListOfString()
}
fun isForAndroidPlatform(platform: List<String>?): Boolean {
return platform?.contains(VALUE_PLATFORM_ANDROID) == true
}
fun JSONArray?.toListOfJSONObject(): List<JSONObject> {
if (this == null) {
return emptyList()
}
val result = mutableListOf<JSONObject>()
for (i in 0 until this.length()) {
result.add(this.getJSONObject(i))
}
return result
}
fun JSONArray.toSortedList(): List<String> {
return (0 until length()).map { get(it).toString() }.sorted()
}
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) })
}
}
private fun JSONArray.toListOfString(): List<String> {
val result = mutableListOf<String>()
for (i in 0 until this.length()) {
result.add(this.getString(i))
}
return result
}
}
@@ -1,20 +0,0 @@
package com.yandex.div.test.expression
class TestCaseOrError<T> private constructor(
val testCase: T?,
val error: TestCaseParsingError?,
) {
fun getCaseOrThrow(): T {
error?.throwError()
return testCase!!
}
override fun toString(): String {
return testCase?.toString() ?: error?.toString() ?: "???"
}
companion object {
operator fun <T> invoke(testCase: T) = TestCaseOrError(testCase, null)
operator fun <T> invoke(error: TestCaseParsingError) = TestCaseOrError<T>(null, error)
}
}
@@ -1,23 +0,0 @@
package com.yandex.div.test.expression
import org.json.JSONException
import org.json.JSONObject
class TestCaseParsingError(
private val fileName: String,
private val json: JSONObject?,
private val error: Exception,
) {
override fun toString() = "${fileName}/${error.message?:"?"}"
@Suppress("NewApi")
fun throwError(): Nothing {
if (json == null) {
throw JSONException("$fileName parsing failed!", error)
}
throw JSONException(
"Test case parsing failed: $fileName, $json",
error
)
}
}
@@ -1,45 +0,0 @@
package com.yandex.div.test.expression
import com.yandex.div.evaluable.EvaluationContext
import com.yandex.div.evaluable.Evaluator
import com.yandex.div.evaluable.types.DateTime
import org.junit.Assert
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
import java.util.Locale
import java.util.TimeZone
private const val DEFAULT_FORMAT_PATTERN = "yyyy-MM-dd hh:mm:ss"
fun <T> withEvaluator(
evaluationContext: EvaluationContext,
warningsValidator: (List<String>) -> Unit = { Assert.assertTrue(it.isEmpty()) },
block: Evaluator.() -> T
) = run {
val warnings = mutableListOf<String>()
val evaluator = Evaluator(
EvaluationContext(
variableProvider = evaluationContext.variableProvider,
storedValueProvider = evaluationContext.storedValueProvider,
functionProvider = evaluationContext.functionProvider,
warningSender = { expressionContext, message ->
warnings.add(message)
evaluationContext.warningSender.send(expressionContext, message)
}
)
)
block(evaluator).also {
warningsValidator(warnings)
}
}
fun parseAsUTC(source: String): DateTime {
val dateFormat = SimpleDateFormat(DEFAULT_FORMAT_PATTERN, Locale.getDefault())
val date: Date = dateFormat.parse(source)!!
return DateTime(
timestampMillis = date.time + Calendar.getInstance().timeZone.rawOffset,
timezone = TimeZone.getTimeZone("UTC"),
)
}
+1 -1
View File
@@ -51,7 +51,6 @@ include ':div-video-m3'
include ':divkit-demo-app'
include ':divkit-perftests'
include ':divkit-regression-testing'
include ':expression-test-common'
include ':fonts'
include ':glide'
include ':lint-rules'
@@ -59,6 +58,7 @@ include ':logging'
include ':picasso'
include ':sample'
include ':screenshot-test-runtime'
include ':test-utils'
include ':ui-test-common'
include ':utils'
include ':video-custom'
@@ -0,0 +1,18 @@
plugins {
alias(libs.plugins.android.library)
}
apply(from = "../div-library.gradle")
android {
namespace = "com.yandex.div.test"
}
dependencies {
implementation(project(":div-data"))
implementation(project(":div-evaluable"))
implementation(libs.androidx.core)
implementation(libs.androidx.coreKtx)
implementation(libs.junit)
}
@@ -0,0 +1,104 @@
package com.yandex.div.test.crossplatform
import androidx.core.net.toUri
import com.yandex.div.evaluable.types.Color
import org.json.JSONException
import org.json.JSONObject
class IntegrationTestCase(
val name: String,
val divData: JSONObject,
val actions: List<JSONObject>,
val expectedResults: List<ExpectedResult>
) {
sealed interface ExpectedResult {
class Variable(val name: String, val type: String, val value: Any) : ExpectedResult
class Error(val message: String) : ExpectedResult
}
override fun toString() = name
companion object {
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)
}
}
}
}
}
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()
else -> throw IllegalAccessException("Unknown variable type: $type")
}
}
@@ -0,0 +1,50 @@
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) })
}
}
}
@@ -0,0 +1,35 @@
package com.yandex.div.test.crossplatform
import org.json.JSONObject
sealed class ParsingResult<out T> {
class Success<out T>(val value: T) : ParsingResult<T>() {
override fun toString() = value.toString()
}
class Error(
private val fileName: String,
private val json: JSONObject? = null,
private val error: Exception
) : ParsingResult<Nothing>() {
fun throwException(): Nothing {
if (json == null) {
throw Exception("$fileName parsing failed", error)
}
throw Exception(
"Test case parsing failed: $fileName, $json",
error
)
}
override fun toString() = "$fileName/${error.message ?: "?"}"
}
fun getOrThrow(): T {
when (this) {
is Success -> return value
is Error -> throwException()
}
}
}
@@ -0,0 +1,37 @@
package com.yandex.div.test.crossplatform
import com.yandex.div.evaluable.types.DateTime
import com.yandex.div.internal.util.map
import org.json.JSONArray
import org.json.JSONObject
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
import java.util.Locale
import java.util.TimeZone
val JSONObject.platforms: List<String>
get() = getJSONArray("platforms").map { it as String }
val JSONObject.isForAndroid: Boolean
get() = platforms.contains("android")
fun JSONArray?.toObjectList(): List<JSONObject> {
if (this == null) {
return emptyList()
}
val result = mutableListOf<JSONObject>()
for (i in 0 until this.length()) {
result.add(this.getJSONObject(i))
}
return result
}
fun parseDateTime(utcString: String): DateTime {
val dateFormat = SimpleDateFormat("yyyy-MM-dd hh:mm:ss", Locale.getDefault())
val date: Date = dateFormat.parse(utcString)!!
return DateTime(
timestampMillis = date.time + Calendar.getInstance().timeZone.rawOffset,
timezone = TimeZone.getTimeZone("UTC")
)
}
@@ -0,0 +1,27 @@
package com.yandex.div.test.data
import androidx.core.net.toUri
import com.yandex.div2.DivAction
import com.yandex.div2.DivActionSetVariable
import com.yandex.div2.DivActionTyped
import com.yandex.div2.DivTypedValue
import org.json.JSONObject
fun action(
typed: DivActionTyped? = null,
payload: JSONObject? = null,
url: String? = null,
): DivAction {
return DivAction(
logId = constant("test"),
payload = payload,
typed = typed,
url = url?.let { constant(it.toUri()) }
)
}
fun setVariableAction(name: String, value: DivTypedValue): DivActionTyped {
return DivActionTyped.SetVariable(
DivActionSetVariable(value = value, variableName = constant(name))
)
}
@@ -0,0 +1,17 @@
package com.yandex.div.test.data
import com.yandex.div2.Div
import com.yandex.div2.DivContainer
import com.yandex.div2.DivVariable
fun container(
items: List<Div> = emptyList(),
variables: List<DivVariable>? = null
): Div {
return Div.Container(
value = DivContainer(
items = items,
variables = variables
)
)
}
@@ -0,0 +1,24 @@
package com.yandex.div.test.data
import com.yandex.div2.Div
import com.yandex.div2.DivData
import com.yandex.div2.DivTrigger
import com.yandex.div2.DivVariable
fun data(
content: Div,
triggers: List<DivTrigger>? = null,
variables: List<DivVariable>? = null
): DivData {
return DivData(
logId = "test",
states = listOf(
DivData.State(
stateId = 0,
div = content
)
),
variables = variables,
variableTriggers = triggers,
)
}
@@ -0,0 +1,32 @@
package com.yandex.div.test.data
import com.yandex.div.json.expressions.Expression
import com.yandex.div2.Div
import com.yandex.div2.DivAction
import com.yandex.div2.DivDisappearAction
import com.yandex.div2.DivText
import com.yandex.div2.DivTrigger
import com.yandex.div2.DivVariable
import com.yandex.div2.DivVisibilityAction
fun text(
action: DivAction? = null,
disappearActions: List<DivDisappearAction>? = null,
id: String? = null,
text: Expression<String>,
triggers: List<DivTrigger>? = null,
variables: List<DivVariable>? = null,
visibilityActions: List<DivVisibilityAction>? = null
): Div {
return Div.Text(
value = DivText(
action = action,
disappearActions = disappearActions,
id = id,
text = text,
variables = variables,
variableTriggers = triggers,
visibilityActions = visibilityActions
)
)
}
@@ -0,0 +1,17 @@
package com.yandex.div.test.data
import com.yandex.div2.DivAction
import com.yandex.div2.DivTrigger
import com.yandex.div2.DivTrigger.Mode
fun trigger(
action: DivAction,
condition: String,
mode: Mode = Mode.ON_CONDITION
): DivTrigger {
return DivTrigger(
actions = listOf(action),
condition = booleanExpression(condition),
mode = constant(mode)
)
}
@@ -0,0 +1,24 @@
package com.yandex.div.test.data
import com.yandex.div2.ArrayValue
import com.yandex.div2.ColorValue
import com.yandex.div2.DivTypedValue
import com.yandex.div2.IntegerValue
import com.yandex.div2.StrValue
import org.json.JSONArray
fun typedValue(value: String): DivTypedValue {
return DivTypedValue.Str(StrValue(value = constant(value)))
}
fun typedValue(value: Long): DivTypedValue {
return DivTypedValue.Integer(IntegerValue(value = constant(value)))
}
fun typedValue(value: JSONArray): DivTypedValue {
return DivTypedValue.Array(ArrayValue(value = constant(value)))
}
fun typedColorValue(value: Long): DivTypedValue {
return DivTypedValue.Color(ColorValue(value = constant(value.toInt())))
}
@@ -0,0 +1,13 @@
package com.yandex.div.test.data
import com.yandex.div2.DivVariable
import com.yandex.div2.IntegerVariable
import com.yandex.div2.StrVariable
fun variable(name: String, value: Long): DivVariable {
return DivVariable.Integer(IntegerVariable(name = name, value = constant(value)))
}
fun variable(name: String, value: String): DivVariable {
return DivVariable.Str(StrVariable(name = name, value = constant(value)))
}
@@ -1,4 +1,4 @@
package com.yandex.div.compose
package com.yandex.div.test.data
import com.yandex.div.internal.parser.TYPE_HELPER_BOOLEAN
import com.yandex.div.internal.parser.TYPE_HELPER_INT
@@ -0,0 +1,5 @@
package com.yandex.div.test.data
import com.yandex.div.evaluable.types.Color
fun color(value: Long) = Color(value.toInt())