Make resolver the only source of truth

commit_hash:d1e09b695ddeab7dd443a0002811c5252b7e24fd
This commit is contained in:
grechka62
2025-05-26 17:39:26 +03:00
parent c9b7ecee31
commit a1393f8317
18 changed files with 119 additions and 150 deletions
@@ -2,12 +2,15 @@ package com.yandex.div.core.actions
import com.yandex.div.core.DivActionHandler
import com.yandex.div.core.DivRequestExecutor
import com.yandex.div.core.expression.local.variableController
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
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.json.expressions.ExpressionResolver
import com.yandex.div2.Div
import com.yandex.div2.DivAction
@@ -30,7 +33,7 @@ class DivActionTypedSubmitHandler @Inject constructor(
val submitAction = (action as? DivActionTyped.Submit)?.value ?: return false
val containerId = submitAction.containerId.evaluate(resolver)
val (containerDiv, context, path) = ContainerFinder(containerId).findContainer(view) ?: return false
val container = ContainerFinder(containerId).findContainer(view) ?: return false
val headers = submitAction.request.headers?.map {
DivRequestExecutor.Header(it.name.evaluate(resolver), it.value.evaluate(resolver))
@@ -40,7 +43,7 @@ class DivActionTypedSubmitHandler @Inject constructor(
submitAction.request.url.evaluate(resolver),
submitAction.request.method.evaluate(resolver).toString(),
headers,
createBody(containerDiv, context, path, view),
createBody(container, view),
)
val callback = createCallback(submitAction.onSuccessActions, submitAction.onFailActions, view, resolver)
@@ -49,12 +52,11 @@ class DivActionTypedSubmitHandler @Inject constructor(
return true
}
private fun createBody(div: Div, context: BindingContext, path: DivStatePath, view: Div2View): String {
val variables = div.value().variables
private fun createBody(container: DivItemBuilderResult, view: Div2View): String {
val variables = container.div.value().variables
if (variables.isNullOrEmpty()) return ""
val variableController = context.runtimeStore
?.getOrCreateRuntime(path.fullPath, div, context.expressionResolver)?.variableController ?: return ""
val variableController = container.expressionResolver.variableController ?: return ""
val body = JSONObject()
variables.forEach {
@@ -93,9 +95,9 @@ class DivActionTypedSubmitHandler @Inject constructor(
private class ContainerFinder(private val id: String) : DivTreeVisitor<Unit>() {
private val containers = mutableListOf<Triple<Div, BindingContext, DivStatePath>>()
private val containers = mutableListOf<DivItemBuilderResult>()
fun findContainer(view: Div2View): Triple<Div, BindingContext, DivStatePath>? {
fun findContainer(view: Div2View): DivItemBuilderResult? {
val data = view.divData ?: return null
data.states.forEach { state ->
visit(state.div, view.bindingContext, DivStatePath.fromState(state))
@@ -120,7 +122,7 @@ class DivActionTypedSubmitHandler @Inject constructor(
override fun defaultVisit(data: Div, context: BindingContext, path: DivStatePath) {
if (data.value().id == id) {
containers.add(Triple(data, context, path))
containers.add(data.toItemBuilderResult(context.expressionResolver))
}
}
}
@@ -10,6 +10,7 @@ import com.yandex.div.core.actions.colorIntValue
import com.yandex.div.core.actions.doubleValue
import com.yandex.div.core.actions.logError
import com.yandex.div.core.actions.longValue
import com.yandex.div.core.expression.local.variableController
import com.yandex.div.core.util.androidInterpolator
import com.yandex.div.core.util.isAlternated
import com.yandex.div.core.util.isReversed
@@ -50,7 +51,7 @@ internal object DivVariableAnimatorBuilder {
startAction: DivActionAnimatorStart,
resolver: ExpressionResolver
): Animator? {
return when (val variable = findVariable<Variable>(divView, animator.variableName, resolver)) {
return when (val variable = findVariable<Variable>(animator.variableName, resolver)) {
is Variable.IntegerVariable -> buildIntegerAnimator(divView, animator, startAction, resolver, variable)
is Variable.DoubleVariable -> buildDoubleAnimator(divView, animator, startAction, resolver, variable)
@@ -103,7 +104,7 @@ internal object DivVariableAnimatorBuilder {
startAction: DivActionAnimatorStart,
resolver: ExpressionResolver
): Animator? {
val variable = findVariable<Variable.ColorVariable>(divView, animator.variableName, resolver)
val variable = findVariable<Variable.ColorVariable>(animator.variableName, resolver)
if (variable == null) {
divView.logError(MissingVariableException("Unable to find color variable with name '${animator.variableName}'"))
return null
@@ -152,12 +153,6 @@ internal object DivVariableAnimatorBuilder {
return this
}
private inline fun <reified T : Variable> findVariable(
divView: Div2View,
name: String,
resolver: ExpressionResolver,
): T? {
val runtime = divView.runtimeStore?.getRuntimeWithOrNull(resolver) ?: divView.expressionsRuntime
return runtime?.variableController?.getMutableVariable(name) as? T
}
private inline fun <reified T : Variable> findVariable(name: String, resolver: ExpressionResolver) =
resolver.variableController?.getMutableVariable(name) as? T
}
@@ -30,10 +30,10 @@ internal class ExpressionResolverImpl(
/**
* Unique path by which resolver could be found at [RuntimeStore].
*/
internal val path: String,
internal val runtimeStore: RuntimeStore,
private val variableController: VariableController,
private val evaluator: Evaluator,
val path: String,
val runtimeStore: RuntimeStore,
val variableController: VariableController,
val evaluator: Evaluator,
private val errorCollector: ErrorCollector,
private val onCreateCallback: OnCreateCallback,
) : ExpressionResolver {
@@ -46,11 +46,7 @@ internal class ExpressionResolverImpl(
var suppressMissingVariableException: Boolean = false
init {
onCreateCallback.onCreate(
this,
variableController,
evaluator.evaluationContext.functionProvider as FunctionProviderDecorator
)
onCreateCallback.onCreate(this)
}
override fun <R, T : Any> get(
@@ -289,10 +285,6 @@ internal class ExpressionResolverImpl(
* as a new ExpressionRuntime we are using OnCreateCallback.
*/
internal fun interface OnCreateCallback {
fun onCreate(
resolver: ExpressionResolverImpl,
variableController: VariableController,
functionProvider: FunctionProviderDecorator
)
fun onCreate(resolver: ExpressionResolverImpl)
}
}
@@ -1,19 +1,17 @@
package com.yandex.div.core.expression
import com.yandex.div.core.DivViewFacade
import com.yandex.div.core.expression.local.RuntimeStore
import com.yandex.div.core.expression.triggers.TriggersController
import com.yandex.div.core.expression.variables.VariableController
internal class ExpressionsRuntime(
val expressionResolver: ExpressionResolverImpl,
val variableController: VariableController,
val triggersController: TriggersController? = null,
val functionProvider: FunctionProviderDecorator,
val runtimeStore: RuntimeStore,
) {
private var unsubscribed = true
val variableController = expressionResolver.variableController
val runtimeStore = expressionResolver.runtimeStore
fun clearBinding(view: DivViewFacade) {
triggersController?.clearBinding(view)
}
@@ -8,7 +8,6 @@ import com.yandex.div.core.expression.local.RuntimeStore
import com.yandex.div.core.expression.storedvalues.StoredValuesController
import com.yandex.div.core.expression.triggers.TriggersController
import com.yandex.div.core.expression.variables.DivVariableController
import com.yandex.div.core.expression.variables.VariableController
import com.yandex.div.core.expression.variables.VariableControllerImpl
import com.yandex.div.core.expression.variables.toVariable
import com.yandex.div.core.view2.Div2View
@@ -20,7 +19,6 @@ import com.yandex.div.data.VariableDeclarationException
import com.yandex.div.evaluable.EvaluationContext
import com.yandex.div.evaluable.Evaluator
import com.yandex.div.evaluable.function.GeneratedBuiltinFunctionProvider
import com.yandex.div.json.expressions.ExpressionResolver
import com.yandex.div2.DivData
import com.yandex.div2.DivVariable
import java.util.Collections
@@ -46,7 +44,7 @@ internal class ExpressionsRuntimeProvider @Inject constructor(
val result = runtimes.getOrPut(tag.id) { createRuntimeFor(data, tag) }
val errorCollector = errorCollectors.getOrCreate(tag, data)
divDataTags.getOrPut(div2View, ::mutableSetOf).add(tag.id)
ensureVariablesSynced(result.variableController, result.expressionResolver, data, errorCollector)
ensureVariablesSynced(result.expressionResolver, data, errorCollector)
result.triggersController?.ensureTriggersSynced(data.variableTriggers ?: emptyList())
return result
}
@@ -69,11 +67,11 @@ internal class ExpressionsRuntimeProvider @Inject constructor(
}
private fun ensureVariablesSynced(
v: VariableController,
resolver: ExpressionResolver,
resolver: ExpressionResolverImpl,
data: DivData,
errorCollector: ErrorCollector
) {
val v = resolver.variableController
data.variables?.forEach {
val existingVariable = v.getMutableVariable(it.name) ?: run {
try {
@@ -133,9 +131,9 @@ internal class ExpressionsRuntimeProvider @Inject constructor(
val evaluator = Evaluator(evaluationContext)
val runtimeStore = RuntimeStore(evaluator, errorCollector, logger, divActionBinder)
val callback = ExpressionResolverImpl.OnCreateCallback { resolver, variableController, functionProvider ->
val callback = ExpressionResolverImpl.OnCreateCallback { resolver ->
runtimeStore.putRuntime(
runtime = ExpressionsRuntime(resolver, variableController, null, functionProvider, runtimeStore)
runtime = ExpressionsRuntime(resolver, null)
)
}
@@ -165,13 +163,9 @@ internal class ExpressionsRuntimeProvider @Inject constructor(
divActionBinder
)
return ExpressionsRuntime(
expressionResolver,
variableController,
triggersController,
functionProvider,
runtimeStore,
).also { runtimeStore.rootRuntime = it }
return ExpressionsRuntime(expressionResolver, triggersController).also {
runtimeStore.rootRuntime = it
}
}
}
@@ -2,6 +2,7 @@ package com.yandex.div.core.expression.local
import com.yandex.div.core.annotations.Mockable
import com.yandex.div.core.dagger.DivScope
import com.yandex.div.core.expression.ExpressionResolverImpl
import com.yandex.div.core.expression.ExpressionsRuntime
import com.yandex.div.core.state.DivPathUtils.getId
import com.yandex.div.core.state.DivStatePath
@@ -160,7 +161,7 @@ internal class DivRuntimeVisitor @Inject constructor(
div: DivState,
divView: Div2View,
states: MutableList<String>,
parentRuntime: ExpressionsRuntime,
resolver: ExpressionResolverImpl,
): String? {
val statePath = states.joinToString("/")
val cardId = divView.divTag.id
@@ -168,9 +169,9 @@ internal class DivRuntimeVisitor @Inject constructor(
return temporaryStateCache.getState(cardId, statePath)
?: divStateCache.getState(cardId, statePath)
?: div.stateIdVariable?.let {
parentRuntime.variableController.getMutableVariable(it)?.getValue().toString()
resolver.variableController.getMutableVariable(it)?.getValue().toString()
}
?: parentRuntime.let { div.defaultStateId?.evaluate(it.expressionResolver) }
?: div.defaultStateId?.evaluate(resolver)
?: div.states.firstOrNull()?.stateId
}
@@ -182,7 +183,7 @@ internal class DivRuntimeVisitor @Inject constructor(
parentRuntime: ExpressionsRuntime,
) {
states.add(div.getId())
val activeStateId = getActiveStateId(div, divView, states, parentRuntime)
val activeStateId = getActiveStateId(div, divView, states, parentRuntime.expressionResolver)
div.states.forEach {
val childDiv = it.div ?: return@forEach
val childPath = path.appendChild(it.stateId)
@@ -5,6 +5,7 @@ import com.yandex.div.core.DivViewFacade
import com.yandex.div.core.ObserverList
import com.yandex.div.core.expression.ExpressionResolverImpl
import com.yandex.div.core.expression.ExpressionsRuntime
import com.yandex.div.core.expression.FunctionProviderDecorator
import com.yandex.div.core.expression.triggers.TriggersController
import com.yandex.div.core.expression.variables.VariableController
import com.yandex.div.core.expression.variables.VariableControllerImpl
@@ -46,8 +47,8 @@ internal class RuntimeStore(
}
private val onCreateCallback by lazy {
ExpressionResolverImpl.OnCreateCallback { resolver, variableController, functionProvider ->
ExpressionsRuntime(resolver, variableController, null, functionProvider, this).also {
ExpressionResolverImpl.OnCreateCallback { resolver ->
ExpressionsRuntime(resolver, null).also {
/**
* we cannot provide path here, otherwise descendants of ExpressionResolver will
* receive the same callback and override runtime for provided path.
@@ -155,7 +156,8 @@ internal class RuntimeStore(
val localVariableController = VariableControllerImpl(baseRuntime.variableController)
val functions = div.functions
var functionProvider = baseRuntime.functionProvider
var functionProvider =
baseRuntime.expressionResolver.evaluator.evaluationContext.functionProvider as FunctionProviderDecorator
if (!functions.isNullOrEmpty()) {
functionProvider += functions.toLocalFunctions()
}
@@ -183,7 +185,7 @@ internal class RuntimeStore(
val triggerController = div.variableTriggers.toTriggersController(localVariableController, resolver, evaluator)
return ExpressionsRuntime(resolver, localVariableController, triggerController, functionProvider, this).also {
return ExpressionsRuntime(resolver, triggerController).also {
putRuntime(it, path, parentRuntime)
}
}
@@ -1,6 +1,12 @@
package com.yandex.div.core.expression.local
import com.yandex.div.core.expression.ExpressionResolverImpl
import com.yandex.div.json.expressions.ExpressionResolver
import com.yandex.div2.Div
internal val Div.needLocalRuntime get() =
!value().run { variables.isNullOrEmpty() && variableTriggers.isNullOrEmpty() && functions.isNullOrEmpty() }
internal val ExpressionResolver.asImpl get() = this as? ExpressionResolverImpl
internal val ExpressionResolver.variableController get() = asImpl?.variableController
@@ -4,7 +4,7 @@ import androidx.annotation.MainThread
import com.yandex.div.core.Disposable
import com.yandex.div.core.annotations.Mockable
import com.yandex.div.core.dagger.DivScope
import com.yandex.div.core.expression.ExpressionsRuntimeProvider
import com.yandex.div.core.expression.local.variableController
import com.yandex.div.core.state.DivStatePath
import com.yandex.div.core.view2.BindingContext
import com.yandex.div.core.view2.errors.ErrorCollectors
@@ -16,8 +16,7 @@ import javax.inject.Inject
@Mockable
internal class TwoWayStringVariableBinder @Inject constructor(
errorCollectors: ErrorCollectors,
expressionsRuntimeProvider: ExpressionsRuntimeProvider
) : TwoWayVariableBinder<String>(errorCollectors, expressionsRuntimeProvider) {
) : TwoWayVariableBinder<String>(errorCollectors) {
interface Callbacks : TwoWayVariableBinder.Callbacks<String>
@@ -28,8 +27,7 @@ internal class TwoWayStringVariableBinder @Inject constructor(
@Mockable
internal class TwoWayIntegerVariableBinder @Inject constructor(
errorCollectors: ErrorCollectors,
expressionsRuntimeProvider: ExpressionsRuntimeProvider
) : TwoWayVariableBinder<Long>(errorCollectors, expressionsRuntimeProvider) {
) : TwoWayVariableBinder<Long>(errorCollectors) {
interface Callbacks : TwoWayVariableBinder.Callbacks<Long>
@@ -40,8 +38,7 @@ internal class TwoWayIntegerVariableBinder @Inject constructor(
@Mockable
internal class TwoWayBooleanVariableBinder @Inject constructor(
errorCollectors: ErrorCollectors,
expressionsRuntimeProvider: ExpressionsRuntimeProvider
) : TwoWayVariableBinder<Boolean>(errorCollectors, expressionsRuntimeProvider) {
) : TwoWayVariableBinder<Boolean>(errorCollectors) {
interface Callbacks : TwoWayVariableBinder.Callbacks<Boolean>
@@ -49,10 +46,7 @@ internal class TwoWayBooleanVariableBinder @Inject constructor(
}
@Mockable
internal abstract class TwoWayVariableBinder<T>(
private val errorCollectors: ErrorCollectors,
private val expressionsRuntimeProvider: ExpressionsRuntimeProvider
) {
internal abstract class TwoWayVariableBinder<T>(private val errorCollectors: ErrorCollectors) {
@Mockable
interface Callbacks<T> {
@@ -72,9 +66,7 @@ internal abstract class TwoWayVariableBinder<T>(
var pendingValue: T? = null
val tag = divView.dataTag
val variableController = bindingContext.runtimeStore?.let { runtimeStore ->
runtimeStore.getRuntimeWithOrNull(bindingContext.expressionResolver)?.variableController
} ?: expressionsRuntimeProvider.getOrCreate(tag, data, divView).variableController
val variableController = bindingContext.expressionResolver.variableController ?: return Disposable.NULL
callbacks.setViewStateChangeListener { value ->
if (pendingValue == value) return@setViewStateChangeListener
@@ -1,20 +1,19 @@
package com.yandex.div.core.view2
import com.yandex.div.core.expression.local.RuntimeStore
import com.yandex.div.core.expression.local.asImpl
import com.yandex.div.json.expressions.ExpressionResolver
internal class BindingContext private constructor(
val divView: Div2View,
val expressionResolver: ExpressionResolver,
val runtimeStore: RuntimeStore?,
) {
fun getFor(resolver: ExpressionResolver) =
if (expressionResolver == resolver) this else BindingContext(divView, resolver, runtimeStore)
fun getFor(resolver: ExpressionResolver, runtimeStore: RuntimeStore?) =
if (expressionResolver == resolver) this else BindingContext(divView, resolver, runtimeStore)
val runtimeStore = expressionResolver.asImpl?.runtimeStore
fun getFor(resolver: ExpressionResolver) =
if (expressionResolver == resolver) this else BindingContext(divView, resolver)
companion object {
fun createEmpty(divView: Div2View) = BindingContext(divView, ExpressionResolver.EMPTY, null)
fun createEmpty(divView: Div2View) = BindingContext(divView, ExpressionResolver.EMPTY)
}
}
@@ -225,7 +225,7 @@ class Div2View private constructor(
runtimeStore?.clearBindings(this)
}
runtimeStore = expressionsRuntime?.runtimeStore
bindingContext = bindingContext.getFor(expressionResolver, runtimeStore)
bindingContext = bindingContext.getFor(expressionResolver)
}
private fun attachVariableTriggers(data: DivData) {
@@ -5,7 +5,7 @@ import androidx.recyclerview.widget.ListUpdateCallback
import androidx.recyclerview.widget.RecyclerView
import com.yandex.div.core.downloader.DivPatchApply
import com.yandex.div.core.downloader.DivPatchCache
import com.yandex.div.core.expression.ExpressionResolverImpl
import com.yandex.div.core.expression.local.asImpl
import com.yandex.div.core.view2.BindingContext
import com.yandex.div.internal.core.DivItemBuilderResult
import com.yandex.div.internal.core.toDivItemBuilderResult
@@ -131,7 +131,7 @@ internal abstract class DivCollectionAdapter<VH: DivCollectionViewHolder>(
}
private fun DivItemBuilderResult.suppressMissingVariableException(suppress: Boolean) {
(expressionResolver as? ExpressionResolverImpl)?.suppressMissingVariableException = suppress
expressionResolver.asImpl?.suppressMissingVariableException = suppress
}
}
@@ -5,6 +5,7 @@ import android.view.ViewGroup
import android.view.animation.AnimationSet
import androidx.core.view.children
import androidx.core.view.doOnNextLayout
import androidx.core.view.isNotEmpty
import androidx.transition.Transition
import androidx.transition.TransitionManager
import androidx.transition.TransitionSet
@@ -15,6 +16,7 @@ import com.yandex.div.core.dagger.DivScope
import com.yandex.div.core.downloader.DivPatchCache
import com.yandex.div.core.downloader.DivPatchManager
import com.yandex.div.core.expression.local.DivRuntimeVisitor
import com.yandex.div.core.expression.local.variableController
import com.yandex.div.core.expression.variables.TwoWayStringVariableBinder
import com.yandex.div.core.state.DivPathUtils.getId
import com.yandex.div.core.state.DivStatePath
@@ -152,7 +154,7 @@ internal class DivStateBinder @Inject constructor(
val newStateDiv = newState.div
val newStateDivValue = newStateDiv?.value()
val outgoing = if (childCount > 0) getChildAt(0) else null
val outgoing = if (isNotEmpty()) getChildAt(0) else null
val incoming: View?
val reusableIncomingView = newStateDiv?.let {
divView.currentRebindReusableList?.getUniqueViewForDiv(newStateDiv)
@@ -271,9 +273,7 @@ internal class DivStateBinder @Inject constructor(
}
private fun getValueFromVariable(context: BindingContext, variableName: String): String? {
val variableController = context.runtimeStore?.getRuntimeWithOrNull(context.expressionResolver)?.variableController
?: context.divView.expressionsRuntime?.variableController ?: return null
val variableController = context.expressionResolver.variableController ?: return null
return variableController.getMutableVariable(variableName)?.getValue()?.toString()
}
@@ -2,7 +2,7 @@ package com.yandex.div.internal.core
import com.yandex.div.core.DivViewFacade
import com.yandex.div.core.annotations.InternalApi
import com.yandex.div.core.expression.ExpressionResolverImpl
import com.yandex.div.core.expression.local.asImpl
import com.yandex.div.core.expression.variables.ConstantsProvider
import com.yandex.div.internal.Assert
import com.yandex.div.internal.util.forEach
@@ -85,7 +85,7 @@ private fun DivCollectionItemBuilder.getItemResolver(
index: Int,
resolver: ExpressionResolver,
): ExpressionResolver? {
val resolverImpl = resolver as? ExpressionResolverImpl ?: return resolver
val resolverImpl = resolver.asImpl ?: return resolver
val validElement = resolverImpl.validateItemBuilderDataElement(dataElement, index) ?: return null
val localDataProvider = ConstantsProvider(mapOf(
dataElementName to validElement,
@@ -2,6 +2,7 @@ package com.yandex.div.internal.core
import com.yandex.div.core.actions.logError
import com.yandex.div.core.annotations.InternalApi
import com.yandex.div.core.expression.local.variableController
import com.yandex.div.core.view2.Div2View
import com.yandex.div.data.Variable
import com.yandex.div.data.VariableMutationException
@@ -21,7 +22,7 @@ class VariableMutationHandler {
value: String,
resolver: ExpressionResolver
): VariableMutationException? {
val mutableVariable = findVariable(div2View, name, resolver)
val mutableVariable = findVariable(name, resolver)
?: return createAndReportError(null, div2View, "Variable '$name' not defined!")
runCatching {
@@ -45,7 +46,7 @@ class VariableMutationHandler {
resolver: ExpressionResolver,
valueMutation: (T) -> T,
): VariableMutationException? {
val mutableVariable = findVariable(div2View, name, resolver)
val mutableVariable = findVariable(name, resolver)
?: return createAndReportError(null, div2View, "Variable '$name' not defined!")
runCatching {
@@ -58,15 +59,8 @@ class VariableMutationHandler {
return null
}
private fun findVariable(
div2View: Div2View,
name: String,
resolver: ExpressionResolver,
): Variable? {
val runtime = div2View.runtimeStore?.getRuntimeWithOrNull(resolver) ?: div2View.expressionsRuntime
return runtime?.variableController?.getMutableVariable(name)
}
private fun findVariable(name: String, resolver: ExpressionResolver) =
resolver.variableController?.getMutableVariable(name)
private fun createAndReportError(
e: Throwable?,
@@ -86,7 +86,7 @@ class ExpressionResolverImplTest {
externalVariables,
Evaluator(evaluationContext),
mock()
) { _, _, _ -> }
) { _ -> }
private val withFuncGetCallback = { callback: () -> Unit ->
ExpressionResolverImpl(
@@ -112,7 +112,7 @@ class ExpressionResolverImplTest {
)
),
mock()
) { _, _, _ -> }
) { _ -> }
}
@Before
@@ -47,24 +47,20 @@ class RuntimeStoreTest {
private val underTest = RuntimeStore(evaluator, errorCollector, div2Logger, divActionBinder)
private var runtimeFromCallback: ExpressionsRuntime? = null
private val callback = ExpressionResolverImpl.OnCreateCallback { resolver, variableController, functionProvider ->
runtimeFromCallback = ExpressionsRuntime(
resolver, variableController, null, functionProvider, underTest
)
private val callback = ExpressionResolverImpl.OnCreateCallback { resolver ->
runtimeFromCallback = ExpressionsRuntime(resolver, null)
underTest.putRuntime(runtimeFromCallback!!)
}
private val resolver = ExpressionResolverImpl("", mock(), mock(), evaluator, errorCollector, callback)
private val rootVariableController: VariableController = mock<VariableController>()
private val functionProvider = mock<FunctionProviderDecorator>()
private val divBase = mock<DivBase>()
private val divView = mock<DivViewFacade>()
private val div = mock<Div> {
on { value() } doReturn divBase
}
private val rootResolver = ExpressionResolverImpl("", mock(), rootVariableController, evaluator, errorCollector, callback)
private val rootRuntime: ExpressionsRuntime =
ExpressionsRuntime(rootResolver, rootVariableController, null, functionProvider, underTest)
private val rootRuntime: ExpressionsRuntime = ExpressionsRuntime(rootResolver, null)
@Before
fun putRootResolver() {
@@ -104,7 +100,7 @@ class RuntimeStoreTest {
@Test
fun `getOrCreateRuntime returns runtime for path if exist`() {
val resolver = ExpressionResolverImpl("", mock(), mock(), evaluator, errorCollector, callback)
val runtime = ExpressionsRuntime(resolver, mock(), null, functionProvider, underTest)
val runtime = ExpressionsRuntime(resolver, null)
underTest.putRuntime(runtime, PATH, rootRuntime)
Assert.assertEquals(runtime, underTest.getOrCreateRuntime(path.fullPath, div, resolver))
@@ -115,8 +111,7 @@ class RuntimeStoreTest {
@Test
fun `getOrCreateRuntime returns parent runtime for path if no variables provided`() {
val resolver = ExpressionResolverImpl("", mock(), mock(), evaluator, errorCollector, callback)
val parentVariableController = mock<VariableController>()
val runtime = ExpressionsRuntime(resolver, parentVariableController, null, functionProvider, underTest)
val runtime = ExpressionsRuntime(resolver, null)
underTest.putRuntime(runtime, PARENT_PATH, rootRuntime)
Assert.assertEquals(runtime, underTest.getOrCreateRuntime(path.fullPath, div, parentResolver = resolver))
@@ -125,11 +120,12 @@ class RuntimeStoreTest {
@Test
fun `getOrCreateRuntime returns wrapped parent runtime with new variables if new variables provided`() {
val resolver = ExpressionResolverImpl("", mock(), mock(), evaluator, errorCollector, callback)
val parentVariableController = mock<VariableController> {
on { getMutableVariable(PARENT_VARIABLE) } doReturn Variable.StringVariable(PARENT_VARIABLE, "123")
}
val runtime = ExpressionsRuntime(resolver, parentVariableController, null, functionProvider, underTest)
val resolver =
ExpressionResolverImpl("", mock(), parentVariableController, evaluator, errorCollector, callback)
val runtime = ExpressionsRuntime(resolver, null)
setVariable()
underTest.putRuntime(runtime, PARENT_PATH, rootRuntime)
@@ -1,23 +1,16 @@
package com.yandex.div.core.expression.variables
import com.yandex.div.core.Disposable
import com.yandex.div.core.expression.ExpressionResolverImpl
import com.yandex.div.core.expression.ExpressionsRuntime
import com.yandex.div.core.expression.ExpressionsRuntimeProvider
import com.yandex.div.core.expression.FunctionProviderDecorator
import com.yandex.div.core.expression.local.RuntimeStore
import com.yandex.div.core.state.DivStatePath
import com.yandex.div.core.view2.BindingContext
import com.yandex.div.core.view2.Div2View
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.evaluable.EvaluationContext
import com.yandex.div.evaluable.Evaluator
import com.yandex.div2.DivData
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.doReturn
@@ -34,30 +27,32 @@ private const val ANOTHER_VALUE = "another_value"
@RunWith(RobolectricTestRunner::class)
class TwoWayVariableBinderTest {
private val errorCollector = mock<ErrorCollector>()
private val errorCollectors = mock<ErrorCollectors> {
on { getOrCreate(any(), any()) } doReturn errorCollector
}
private val variable = Variable.StringVariable(VARIABLE_NAME, INITIAL_VALUE)
private val variableController = VariableControllerImpl().apply {
declare(variable)
private val variableUpdateCaptor = argumentCaptor<(Variable?) -> Unit>()
private val invokeOnSubscriptionCaptor = argumentCaptor<Boolean>()
private val variableController = mock<VariableController> {
on { getMutableVariable(VARIABLE_NAME) } doReturn variable
on {
subscribeToVariableChange(
any(),
anyOrNull(),
invokeOnSubscriptionCaptor.capture(),
variableUpdateCaptor.capture()
)
} doAnswer {
if (invokeOnSubscriptionCaptor.firstValue) {
variableUpdateCaptor.firstValue.invoke(variable)
}
Disposable.NULL
}
}
private val path = DivStatePath(0)
private val store = RuntimeStore(mock(), mock(), mock(), mock())
private val evaluationContext = EvaluationContext(mock(), mock(), mock<FunctionProviderDecorator>(), mock())
private val evaluator = mock<Evaluator> {
on { evaluationContext } doReturn evaluationContext
}
private val expressionResolver = ExpressionResolverImpl("", mock(), mock(), evaluator, mock(), mock())
private val expressionsRuntime = ExpressionsRuntime(expressionResolver, variableController, mock(), mock(), store)
private val expressionsRuntimeProvider = mock<ExpressionsRuntimeProvider> {
on { getOrCreate(any(), any(), any()) } doReturn expressionsRuntime
private val expressionResolver = mock<ExpressionResolverImpl> {
on { variableController } doReturn variableController
}
private val divView = mock<Div2View> {
on { dataTag } doReturn mock()
on { divData } doReturn DivData(logId = "test", states = emptyList())
on { runtimeStore } doReturn store
}
private val updateCaptor = argumentCaptor<(String) -> Unit>()
private val callbacks = mock<TwoWayStringVariableBinder.Callbacks> {
@@ -65,10 +60,8 @@ class TwoWayVariableBinderTest {
}
init {
store.rootRuntime = expressionsRuntime
val bindingContext = BindingContext.createEmpty(divView).getFor(expressionResolver, store)
TwoWayStringVariableBinder(errorCollectors, expressionsRuntimeProvider)
.bindVariable(bindingContext, VARIABLE_NAME, callbacks, path)
val bindingContext = BindingContext.createEmpty(divView).getFor(expressionResolver)
TwoWayStringVariableBinder(mock()).bindVariable(bindingContext, VARIABLE_NAME, callbacks, mock())
}
@Test
@@ -78,7 +71,7 @@ class TwoWayVariableBinderTest {
@Test
fun `invoke callback on variable change`() {
variable.set(NEW_VALUE)
updateVariable()
verify(callbacks).onVariableChanged(NEW_VALUE)
}
@@ -91,13 +84,13 @@ class TwoWayVariableBinderTest {
@Test
fun `not invoke callback on variable change after view state change with same value`() {
updateCaptor.firstValue.invoke(NEW_VALUE)
variable.set(NEW_VALUE)
updateVariable()
verify(callbacks, never()).onVariableChanged(NEW_VALUE)
}
@Test
fun `not set variable value on view state change after variable change with same value`() {
variable.set(NEW_VALUE)
updateVariable()
val variableObserver = mock<(Variable) -> Unit>()
variable.addObserver(variableObserver)
@@ -109,14 +102,19 @@ class TwoWayVariableBinderTest {
@Test
fun `invoke callback on variable change after view state change with another value`() {
updateCaptor.firstValue.invoke(NEW_VALUE)
variable.set(ANOTHER_VALUE)
updateVariable(ANOTHER_VALUE)
verify(callbacks).onVariableChanged(ANOTHER_VALUE)
}
@Test
fun `set variable value on view state change after variable change with another value`() {
variable.set(NEW_VALUE)
updateVariable()
updateCaptor.firstValue.invoke(ANOTHER_VALUE)
assertEquals(ANOTHER_VALUE, variable.getValue())
}
private fun updateVariable(value: String = NEW_VALUE) {
variable.set(value)
variableUpdateCaptor.firstValue.invoke(variable)
}
}