Added array_set_value action

11883c3007bea2a24f07a4eecb29560e8fb4c786
This commit is contained in:
pkurchatov
2024-05-31 15:38:17 +03:00
parent 531b672e0b
commit e66b08fc59
15 changed files with 736 additions and 250 deletions
+4 -2
View File
@@ -10315,8 +10315,7 @@
"client/ios/DivKit.xcodeproj/xcshareddata/xcschemes/SnapshotTests.xcscheme":"divkit/public/client/ios/DivKit.xcodeproj/xcshareddata/xcschemes/SnapshotTests.xcscheme",
"client/ios/DivKit.xcodeproj/xcshareddata/xcschemes/SnapshotTestsHostApp.xcscheme":"divkit/public/client/ios/DivKit.xcodeproj/xcshareddata/xcschemes/SnapshotTestsHostApp.xcscheme",
"client/ios/DivKit.xcodeproj/xcshareddata/xcschemes/UnitTests.xcscheme":"divkit/public/client/ios/DivKit.xcodeproj/xcshareddata/xcschemes/UnitTests.xcscheme",
"client/ios/DivKit/Actions/ArrayInsertValueActionHandler.swift":"divkit/public/client/ios/DivKit/Actions/ArrayInsertValueActionHandler.swift",
"client/ios/DivKit/Actions/ArrayRemoveValueActionHandler.swift":"divkit/public/client/ios/DivKit/Actions/ArrayRemoveValueActionHandler.swift",
"client/ios/DivKit/Actions/ArrayActionsHandler.swift":"divkit/public/client/ios/DivKit/Actions/ArrayActionsHandler.swift",
"client/ios/DivKit/Actions/ClearFocusActionHandler.swift":"divkit/public/client/ios/DivKit/Actions/ClearFocusActionHandler.swift",
"client/ios/DivKit/Actions/CopyToClipboardActionHandler.swift":"divkit/public/client/ios/DivKit/Actions/CopyToClipboardActionHandler.swift",
"client/ios/DivKit/Actions/DictSetValueActionHandler.swift":"divkit/public/client/ios/DivKit/Actions/DictSetValueActionHandler.swift",
@@ -10511,6 +10510,8 @@
"client/ios/DivKit/generated_sources/DivActionArrayInsertValueTemplate.swift":"divkit/public/client/ios/DivKit/generated_sources/DivActionArrayInsertValueTemplate.swift",
"client/ios/DivKit/generated_sources/DivActionArrayRemoveValue.swift":"divkit/public/client/ios/DivKit/generated_sources/DivActionArrayRemoveValue.swift",
"client/ios/DivKit/generated_sources/DivActionArrayRemoveValueTemplate.swift":"divkit/public/client/ios/DivKit/generated_sources/DivActionArrayRemoveValueTemplate.swift",
"client/ios/DivKit/generated_sources/DivActionArraySetValue.swift":"divkit/public/client/ios/DivKit/generated_sources/DivActionArraySetValue.swift",
"client/ios/DivKit/generated_sources/DivActionArraySetValueTemplate.swift":"divkit/public/client/ios/DivKit/generated_sources/DivActionArraySetValueTemplate.swift",
"client/ios/DivKit/generated_sources/DivActionClearFocus.swift":"divkit/public/client/ios/DivKit/generated_sources/DivActionClearFocus.swift",
"client/ios/DivKit/generated_sources/DivActionClearFocusTemplate.swift":"divkit/public/client/ios/DivKit/generated_sources/DivActionClearFocusTemplate.swift",
"client/ios/DivKit/generated_sources/DivActionCopyToClipboard.swift":"divkit/public/client/ios/DivKit/generated_sources/DivActionCopyToClipboard.swift",
@@ -15978,6 +15979,7 @@
"schema/div-accessibility.json":"divkit/public/schema/div-accessibility.json",
"schema/div-action-array-insert-value.json":"divkit/public/schema/div-action-array-insert-value.json",
"schema/div-action-array-remove-value.json":"divkit/public/schema/div-action-array-remove-value.json",
"schema/div-action-array-set-value.json":"divkit/public/schema/div-action-array-set-value.json",
"schema/div-action-base.json":"divkit/public/schema/div-action-base.json",
"schema/div-action-clear-focus.json":"divkit/public/schema/div-action-clear-focus.json",
"schema/div-action-copy-to-clipboard-content.json":"divkit/public/schema/div-action-copy-to-clipboard-content.json",
@@ -6,6 +6,7 @@ import com.yandex.div.internal.util.asList
import com.yandex.div.json.expressions.ExpressionResolver
import com.yandex.div2.DivActionArrayInsertValue
import com.yandex.div2.DivActionArrayRemoveValue
import com.yandex.div2.DivActionArraySetValue
import com.yandex.div2.DivActionTyped
import org.json.JSONArray
import javax.inject.Inject
@@ -20,21 +21,22 @@ internal class DivActionTypedArrayMutationHandler @Inject constructor()
view: Div2View,
resolver: ExpressionResolver,
): Boolean = when (action) {
is DivActionTyped.ArrayInsertValue -> {
handleInsert(action.value, view, resolver)
handle(action.value, view, resolver)
true
}
is DivActionTyped.ArrayRemoveValue -> {
handleRemove(action.value, view, resolver)
handle(action.value, view, resolver)
true
}
is DivActionTyped.ArraySetValue -> {
handle(action.value, view, resolver)
true
}
else -> false
}
private fun handleInsert(
private fun handle(
action: DivActionArrayInsertValue,
view: Div2View,
resolver: ExpressionResolver
@@ -42,67 +44,91 @@ internal class DivActionTypedArrayMutationHandler @Inject constructor()
val variableName = action.variableName.evaluate(resolver)
val index = action.index?.evaluate(resolver)?.toInt()
val newValue = action.value.evaluate(resolver)
view.setVariable(variableName) { variable: Variable ->
if (variable !is Variable.ArrayVariable) {
view.logError(
IllegalArgumentException("array_insert_value action requires array variable")
)
return@setVariable variable
view.updateVariable(variableName) { array ->
val length = array.length()
when (index) {
null, length -> array.mutate { add(newValue) }
in 0 until length -> array.mutate { add(index, newValue) }
else -> {
view.logError(IndexOutOfBoundsException(
"Index out of bound ($index) for mutation $variableName ($length)"
))
array
}
}
val value = variable.getValue() as? JSONArray
if (value == null) {
view.logError(IllegalArgumentException("Invalid variable value"))
return@setVariable variable
}
val length = value.length()
when (val indexToInsert = index ?: length) {
in 0..length -> variable.set(value.mutate { add(indexToInsert, newValue) })
else -> view.logError(IndexOutOfBoundsException(
"Index out of bound ($indexToInsert) for mutation ${variable.name} (${length})"
))
}
return@setVariable variable
}
}
private fun handleRemove(
private fun handle(
action: DivActionArrayRemoveValue,
view: Div2View,
resolver: ExpressionResolver
) {
val variableName = action.variableName.evaluate(resolver)
val index = action.index.evaluate(resolver).toInt()
view.setVariable(variableName) { variable: Variable ->
if (variable !is Variable.ArrayVariable) {
view.logError(
IllegalArgumentException("array_remove_value action requires array variable")
)
return@setVariable variable
}
val value = variable.getValue() as? JSONArray
if (value == null) {
view.logError(IllegalArgumentException("Invalid variable value"))
return@setVariable variable
}
val length = value.length()
view.updateVariable(variableName) { array ->
val length = array.length()
when (index) {
in 0 until length -> variable.set(value.mutate { removeAt(index) })
else -> view.logError(IndexOutOfBoundsException(
"Index out of bound ($index) for mutation ${variable.name} (${length})"
))
in 0 until length -> array.mutate { removeAt(index) }
else -> {
view.logError(IndexOutOfBoundsException(
"Index out of bound ($index) for mutation $variableName ($length)"
))
array
}
}
return@setVariable variable
}
}
private fun JSONArray.mutate(action: MutableList<Any>.() -> Unit): JSONArray {
return asList<Any>()
.toMutableList()
.apply(action::invoke)
.let(::JSONArray)
private fun handle(
action: DivActionArraySetValue,
view: Div2View,
resolver: ExpressionResolver
) {
val variableName = action.variableName.evaluate(resolver)
val index = action.index.evaluate(resolver).toInt()
val newValue = action.value.evaluate(resolver)
view.updateVariable(variableName) { array ->
val length = array.length()
when (index) {
in 0 until length -> array.mutate { this[index] = newValue }
else -> {
view.logError(IndexOutOfBoundsException(
"Index out of bound ($index) for mutation $variableName ($length)"
))
array
}
}
}
}
}
private fun JSONArray.mutate(action: MutableList<Any>.() -> Unit): JSONArray {
return asList<Any>()
.toMutableList()
.apply(action::invoke)
.let(::JSONArray)
}
private fun Div2View.updateVariable(
name: String,
valueMutation: (JSONArray) -> JSONArray
) {
view.setVariable(name) { variable: Variable ->
if (variable !is Variable.ArrayVariable) {
view.logError(
IllegalArgumentException("Action requires array variable")
)
return@setVariable variable
}
val value = variable.getValue() as? JSONArray
if (value == null) {
view.logError(IllegalArgumentException("Invalid variable value"))
return@setVariable variable
}
variable.set(valueMutation(value))
return@setVariable variable
}
}
@@ -12,6 +12,7 @@ import com.yandex.div2.Div
import com.yandex.div2.DivAction
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
@@ -184,6 +185,45 @@ class DivActionHandlerTest {
)
}
@Test
fun `ArrayInsertValue action with index equal to length inserts value`() {
setVariable("array_var", JSONArray().put("value 1"))
val isHandled = handleTypedAction(
DivActionTyped.ArrayInsertValue(
DivActionArrayInsertValue(
index = Expression.constant(1),
value = typedValue("new value"),
variableName = Expression.constant("array_var")
)
)
)
Assert.assertTrue(isHandled)
Assert.assertEquals(
JSONArray().put("value 1").put("new value"),
getVariableValue("array_var")
)
}
@Test
fun `ArrayInsertValue action does not change original value`() {
val array = JSONArray().put("value 1").put("value 2")
setVariable("array_var", array)
handleTypedAction(
DivActionTyped.ArrayInsertValue(
DivActionArrayInsertValue(
value = typedValue("new value"),
variableName = Expression.constant("array_var")
)
)
)
Assert.assertEquals(JSONArray().put("value 1").put("value 2"), array)
}
@Test
fun `ArrayInsertValue action does nothing for invalid index`() {
setVariable("array_var", JSONArray().put("value 1"))
@@ -236,6 +276,23 @@ class DivActionHandlerTest {
Assert.assertEquals(JSONArray().put("value 2"), getVariableValue("array_var"))
}
@Test
fun `ArrayRemoveValue action does not change original array`() {
val array = JSONArray().put("value 1").put("value 2")
setVariable("array_var", array)
handleTypedAction(
DivActionTyped.ArrayRemoveValue(
DivActionArrayRemoveValue(
index = Expression.constant(0),
variableName = Expression.constant("array_var")
)
)
)
Assert.assertEquals(JSONArray().put("value 1").put("value 2"), array)
}
@Test
fun `ArrayRemoveValue action does nothing for invalid index`() {
setVariable("array_var", JSONArray().put("value 1"))
@@ -270,6 +327,86 @@ class DivActionHandlerTest {
Assert.assertEquals("value", getVariableValue("string_var"))
}
@Test
fun `ArraySetValue action sets value`() {
setVariable("array_var", JSONArray().put("value 1").put("value 2"))
val isHandled = handleTypedAction(
DivActionTyped.ArraySetValue(
DivActionArraySetValue(
index = Expression.constant(1),
value = typedValue("new value"),
variableName = Expression.constant("array_var")
)
)
)
Assert.assertTrue(isHandled)
Assert.assertEquals(
JSONArray().put("value 1").put("new value"),
getVariableValue("array_var")
)
}
@Test
fun `ArraySetValue action does not change original array`() {
val array = JSONArray().put("value 1").put("value 2")
setVariable("array_var", array)
handleTypedAction(
DivActionTyped.ArraySetValue(
DivActionArraySetValue(
index = Expression.constant(1),
value = typedValue("new value"),
variableName = Expression.constant("array_var")
)
)
)
Assert.assertEquals(JSONArray().put("value 1").put("value 2"), array)
}
@Test
fun `ArraySetValue action does nothing for invalid index`() {
setVariable("array_var", JSONArray().put("value 1").put("value 2"))
val isHandled = handleTypedAction(
DivActionTyped.ArraySetValue(
DivActionArraySetValue(
index = Expression.constant(2),
value = typedValue("new value"),
variableName = Expression.constant("array_var")
)
)
)
Assert.assertTrue(isHandled)
Assert.assertEquals(
JSONArray().put("value 1").put("value 2"),
getVariableValue("array_var")
)
}
@Test
fun `ArraySetValue action does nothing for not array variable`() {
setVariable("string_var", "value")
val isHandled = handleTypedAction(
DivActionTyped.ArraySetValue(
DivActionArraySetValue(
index = Expression.constant(0),
value = typedValue("new value"),
variableName = Expression.constant("string_var")
)
)
)
Assert.assertTrue(isHandled)
Assert.assertEquals("value", getVariableValue("string_var"))
}
@Test
fun `DictSetValue action removes value`() {
setVariable("dict_var", JSONObject().put("key", "value"))
@@ -0,0 +1,94 @@
import Foundation
final class ArrayActionsHandler {
func handle(_ action: DivActionArrayInsertValue, context: DivActionHandlingContext) {
let expressionResolver = context.expressionResolver
guard let variableName = action.resolveName(expressionResolver),
let value = action.value.resolve(expressionResolver),
var array = context.getVariableValue(variableName) else {
return
}
if let index = action.resolveIndex(expressionResolver) {
guard index >= 0, index <= array.count else {
DivKitLogger.error(
"Failed to insert value in array \(variableName). Index out of bounds: \(index)."
)
return
}
array.insert(value, at: index)
} else {
array.append(value)
}
context.setVariableValue(variableName, array)
}
func handle(_ action: DivActionArrayRemoveValue, context: DivActionHandlingContext) {
let expressionResolver = context.expressionResolver
guard let variableName = action.resolveName(expressionResolver),
let index = action.resolveIndex(expressionResolver),
var array = context.getVariableValue(variableName) else {
return
}
guard index >= 0, index < array.count else {
DivKitLogger.error(
"Failed to remove value from array \(variableName). Index out of bounds: \(index)."
)
return
}
array.remove(at: index)
context.setVariableValue(variableName, array)
}
func handle(_ action: DivActionArraySetValue, context: DivActionHandlingContext) {
let expressionResolver = context.expressionResolver
guard let variableName = action.resolveName(expressionResolver),
let index = action.resolveIndex(expressionResolver),
let value = action.value.resolve(expressionResolver),
var array = context.getVariableValue(variableName) else {
return
}
guard index >= 0, index < array.count else {
DivKitLogger.error(
"Failed to set value in array \(variableName). Index out of bounds: \(index)."
)
return
}
array[index] = value
context.setVariableValue(variableName, array)
}
}
extension DivActionHandlingContext {
fileprivate func getVariableValue(_ name: DivVariableName) -> [AnyHashable]? {
variablesStorage.getVariableValue(cardId: cardId, name: name)
}
fileprivate func setVariableValue(_ name: DivVariableName, _ value: [AnyHashable]) {
variablesStorage.update(cardId: cardId, name: name, value: .array(value))
}
}
private protocol ArrayAction {
func resolveVariableName(_ resolver: ExpressionResolver) -> String?
}
extension ArrayAction {
func resolveName(_ resolver: ExpressionResolver) -> DivVariableName? {
if let name = resolveVariableName(resolver) {
return DivVariableName(rawValue: name)
}
return nil
}
}
extension DivActionArrayInsertValue: ArrayAction {}
extension DivActionArrayRemoveValue: ArrayAction {}
extension DivActionArraySetValue: ArrayAction {}
@@ -1,33 +0,0 @@
import Foundation
final class ArrayInsertValueActionHandler {
func handle(_ action: DivActionArrayInsertValue, context: DivActionHandlingContext) {
let expressionResolver = context.expressionResolver
guard let variableName = action.resolveVariableName(expressionResolver),
let value = action.value.resolve(expressionResolver),
var array: [AnyHashable] = context.variablesStorage.getVariableValue(
cardId: context.cardId,
name: DivVariableName(rawValue: variableName)
) else {
return
}
if let index = action.resolveIndex(expressionResolver) {
guard index >= 0, index <= array.count else {
DivKitLogger.error(
"Index out of bound \(index) for inserting value in \(variableName) with length \(array.count)"
)
return
}
array.insert(value, at: index)
} else {
array.append(value)
}
context.variablesStorage.update(
cardId: context.cardId,
name: DivVariableName(rawValue: variableName),
value: .array(array)
)
}
}
@@ -1,30 +0,0 @@
import Foundation
final class ArrayRemoveValueActionHandler {
func handle(_ action: DivActionArrayRemoveValue, context: DivActionHandlingContext) {
let expressionResolver = context.expressionResolver
guard let variableName = action.resolveVariableName(expressionResolver),
let index = action.resolveIndex(expressionResolver),
var array: [AnyHashable] = context.variablesStorage.getVariableValue(
cardId: context.cardId,
name: DivVariableName(rawValue: variableName)
) else {
return
}
guard index >= 0, index < array.count else {
DivKitLogger.error(
"Index out of bound \(index) for removing value of \(variableName) with length \(array.count)"
)
return
}
array.remove(at: index)
context.variablesStorage.update(
cardId: context.cardId,
name: DivVariableName(rawValue: variableName),
value: .array(array)
)
}
}
@@ -16,8 +16,7 @@ public final class DivActionHandler {
private let updateCard: DivActionURLHandler.UpdateCardAction
private let reporter: DivReporter
private let arrayInsertValueActionHandler = ArrayInsertValueActionHandler()
private let arrayRemoveValueActionHandler = ArrayRemoveValueActionHandler()
private let arrayActionsHandler = ArrayActionsHandler()
private let dictSetValueActionHandler = DictSetValueActionHandler()
private let clearFocusActionHandler = ClearFocusActionHandler()
private let copyToClipboardActionHandler = CopyToClipboardActionHandler()
@@ -135,9 +134,11 @@ public final class DivActionHandler {
var isHandled = true
switch action.typed {
case let .divActionArrayInsertValue(action):
arrayInsertValueActionHandler.handle(action, context: context)
arrayActionsHandler.handle(action, context: context)
case let .divActionArrayRemoveValue(action):
arrayRemoveValueActionHandler.handle(action, context: context)
arrayActionsHandler.handle(action, context: context)
case let .divActionArraySetValue(action):
arrayActionsHandler.handle(action, context: context)
case let .divActionDictSetValue(action):
dictSetValueActionHandler.handle(action, context: context)
case .divActionClearFocus:
@@ -0,0 +1,56 @@
// Generated code. Do not modify.
import CommonCorePublic
import Foundation
import Serialization
public final class DivActionArraySetValue {
public static let type: String = "array_set_value"
public let index: Expression<Int>
public let value: DivTypedValue
public let variableName: Expression<String>
public func resolveIndex(_ resolver: ExpressionResolver) -> Int? {
resolver.resolveNumeric(index)
}
public func resolveVariableName(_ resolver: ExpressionResolver) -> String? {
resolver.resolveString(variableName)
}
init(
index: Expression<Int>,
value: DivTypedValue,
variableName: Expression<String>
) {
self.index = index
self.value = value
self.variableName = variableName
}
}
#if DEBUG
extension DivActionArraySetValue: Equatable {
public static func ==(lhs: DivActionArraySetValue, rhs: DivActionArraySetValue) -> Bool {
guard
lhs.index == rhs.index,
lhs.value == rhs.value,
lhs.variableName == rhs.variableName
else {
return false
}
return true
}
}
#endif
extension DivActionArraySetValue: Serializable {
public func toDictionary() -> [String: ValidSerializationValue] {
var result: [String: ValidSerializationValue] = [:]
result["type"] = Self.type
result["index"] = index.toValidSerializationValue()
result["value"] = value.toDictionary()
result["variable_name"] = variableName.toValidSerializationValue()
return result
}
}
@@ -0,0 +1,149 @@
// Generated code. Do not modify.
import CommonCorePublic
import Foundation
import Serialization
public final class DivActionArraySetValueTemplate: TemplateValue {
public static let type: String = "array_set_value"
public let parent: String?
public let index: Field<Expression<Int>>?
public let value: Field<DivTypedValueTemplate>?
public let variableName: Field<Expression<String>>?
public convenience init(dictionary: [String: Any], templateToType: [TemplateName: String]) throws {
self.init(
parent: dictionary["type"] as? String,
index: dictionary.getOptionalExpressionField("index"),
value: dictionary.getOptionalField("value", templateToType: templateToType),
variableName: dictionary.getOptionalExpressionField("variable_name")
)
}
init(
parent: String?,
index: Field<Expression<Int>>? = nil,
value: Field<DivTypedValueTemplate>? = nil,
variableName: Field<Expression<String>>? = nil
) {
self.parent = parent
self.index = index
self.value = value
self.variableName = variableName
}
private static func resolveOnlyLinks(context: TemplatesContext, parent: DivActionArraySetValueTemplate?) -> DeserializationResult<DivActionArraySetValue> {
let indexValue = parent?.index?.resolveValue(context: context) ?? .noValue
let valueValue = parent?.value?.resolveValue(context: context, useOnlyLinks: true) ?? .noValue
let variableNameValue = parent?.variableName?.resolveValue(context: context) ?? .noValue
var errors = mergeErrors(
indexValue.errorsOrWarnings?.map { .nestedObjectError(field: "index", error: $0) },
valueValue.errorsOrWarnings?.map { .nestedObjectError(field: "value", error: $0) },
variableNameValue.errorsOrWarnings?.map { .nestedObjectError(field: "variable_name", error: $0) }
)
if case .noValue = indexValue {
errors.append(.requiredFieldIsMissing(field: "index"))
}
if case .noValue = valueValue {
errors.append(.requiredFieldIsMissing(field: "value"))
}
if case .noValue = variableNameValue {
errors.append(.requiredFieldIsMissing(field: "variable_name"))
}
guard
let indexNonNil = indexValue.value,
let valueNonNil = valueValue.value,
let variableNameNonNil = variableNameValue.value
else {
return .failure(NonEmptyArray(errors)!)
}
let result = DivActionArraySetValue(
index: indexNonNil,
value: valueNonNil,
variableName: variableNameNonNil
)
return errors.isEmpty ? .success(result) : .partialSuccess(result, warnings: NonEmptyArray(errors)!)
}
public static func resolveValue(context: TemplatesContext, parent: DivActionArraySetValueTemplate?, useOnlyLinks: Bool) -> DeserializationResult<DivActionArraySetValue> {
if useOnlyLinks {
return resolveOnlyLinks(context: context, parent: parent)
}
var indexValue: DeserializationResult<Expression<Int>> = parent?.index?.value() ?? .noValue
var valueValue: DeserializationResult<DivTypedValue> = .noValue
var variableNameValue: DeserializationResult<Expression<String>> = parent?.variableName?.value() ?? .noValue
context.templateData.forEach { key, __dictValue in
switch key {
case "index":
indexValue = deserialize(__dictValue).merged(with: indexValue)
case "value":
valueValue = deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, type: DivTypedValueTemplate.self).merged(with: valueValue)
case "variable_name":
variableNameValue = deserialize(__dictValue).merged(with: variableNameValue)
case parent?.index?.link:
indexValue = indexValue.merged(with: { deserialize(__dictValue) })
case parent?.value?.link:
valueValue = valueValue.merged(with: { deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, type: DivTypedValueTemplate.self) })
case parent?.variableName?.link:
variableNameValue = variableNameValue.merged(with: { deserialize(__dictValue) })
default: break
}
}
if let parent = parent {
valueValue = valueValue.merged(with: { parent.value?.resolveValue(context: context, useOnlyLinks: true) })
}
var errors = mergeErrors(
indexValue.errorsOrWarnings?.map { .nestedObjectError(field: "index", error: $0) },
valueValue.errorsOrWarnings?.map { .nestedObjectError(field: "value", error: $0) },
variableNameValue.errorsOrWarnings?.map { .nestedObjectError(field: "variable_name", error: $0) }
)
if case .noValue = indexValue {
errors.append(.requiredFieldIsMissing(field: "index"))
}
if case .noValue = valueValue {
errors.append(.requiredFieldIsMissing(field: "value"))
}
if case .noValue = variableNameValue {
errors.append(.requiredFieldIsMissing(field: "variable_name"))
}
guard
let indexNonNil = indexValue.value,
let valueNonNil = valueValue.value,
let variableNameNonNil = variableNameValue.value
else {
return .failure(NonEmptyArray(errors)!)
}
let result = DivActionArraySetValue(
index: indexNonNil,
value: valueNonNil,
variableName: variableNameNonNil
)
return errors.isEmpty ? .success(result) : .partialSuccess(result, warnings: NonEmptyArray(errors)!)
}
private func mergedWithParent(templates: [TemplateName: Any]) throws -> DivActionArraySetValueTemplate {
guard let parent = parent, parent != Self.type else { return self }
guard let parentTemplate = templates[parent] as? DivActionArraySetValueTemplate else {
throw DeserializationError.unknownType(type: parent)
}
let mergedParent = try parentTemplate.mergedWithParent(templates: templates)
return DivActionArraySetValueTemplate(
parent: nil,
index: index ?? mergedParent.index,
value: value ?? mergedParent.value,
variableName: variableName ?? mergedParent.variableName
)
}
public func resolveParent(templates: [TemplateName: Any]) throws -> DivActionArraySetValueTemplate {
let merged = try mergedWithParent(templates: templates)
return DivActionArraySetValueTemplate(
parent: nil,
index: merged.index,
value: try merged.value?.resolveParent(templates: templates),
variableName: merged.variableName
)
}
}
@@ -8,6 +8,7 @@ import Serialization
public enum DivActionTyped {
case divActionArrayInsertValue(DivActionArrayInsertValue)
case divActionArrayRemoveValue(DivActionArrayRemoveValue)
case divActionArraySetValue(DivActionArraySetValue)
case divActionClearFocus(DivActionClearFocus)
case divActionCopyToClipboard(DivActionCopyToClipboard)
case divActionDictSetValue(DivActionDictSetValue)
@@ -20,6 +21,8 @@ public enum DivActionTyped {
return value
case let .divActionArrayRemoveValue(value):
return value
case let .divActionArraySetValue(value):
return value
case let .divActionClearFocus(value):
return value
case let .divActionCopyToClipboard(value):
@@ -42,6 +45,8 @@ extension DivActionTyped: Equatable {
return l == r
case let (.divActionArrayRemoveValue(l), .divActionArrayRemoveValue(r)):
return l == r
case let (.divActionArraySetValue(l), .divActionArraySetValue(r)):
return l == r
case let (.divActionClearFocus(l), .divActionClearFocus(r)):
return l == r
case let (.divActionCopyToClipboard(l), .divActionCopyToClipboard(r)):
@@ -8,6 +8,7 @@ import Serialization
public enum DivActionTypedTemplate: TemplateValue {
case divActionArrayInsertValueTemplate(DivActionArrayInsertValueTemplate)
case divActionArrayRemoveValueTemplate(DivActionArrayRemoveValueTemplate)
case divActionArraySetValueTemplate(DivActionArraySetValueTemplate)
case divActionClearFocusTemplate(DivActionClearFocusTemplate)
case divActionCopyToClipboardTemplate(DivActionCopyToClipboardTemplate)
case divActionDictSetValueTemplate(DivActionDictSetValueTemplate)
@@ -20,6 +21,8 @@ public enum DivActionTypedTemplate: TemplateValue {
return value
case let .divActionArrayRemoveValueTemplate(value):
return value
case let .divActionArraySetValueTemplate(value):
return value
case let .divActionClearFocusTemplate(value):
return value
case let .divActionCopyToClipboardTemplate(value):
@@ -39,6 +42,8 @@ public enum DivActionTypedTemplate: TemplateValue {
return .divActionArrayInsertValueTemplate(try value.resolveParent(templates: templates))
case let .divActionArrayRemoveValueTemplate(value):
return .divActionArrayRemoveValueTemplate(try value.resolveParent(templates: templates))
case let .divActionArraySetValueTemplate(value):
return .divActionArraySetValueTemplate(try value.resolveParent(templates: templates))
case let .divActionClearFocusTemplate(value):
return .divActionClearFocusTemplate(try value.resolveParent(templates: templates))
case let .divActionCopyToClipboardTemplate(value):
@@ -78,6 +83,14 @@ public enum DivActionTypedTemplate: TemplateValue {
case let .failure(errors): return .failure(errors)
case .noValue: return .noValue
}
case let .divActionArraySetValueTemplate(value):
let result = value.resolveValue(context: context, useOnlyLinks: useOnlyLinks)
switch result {
case let .success(value): return .success(.divActionArraySetValue(value))
case let .partialSuccess(value, warnings): return .partialSuccess(.divActionArraySetValue(value), warnings: warnings)
case let .failure(errors): return .failure(errors)
case .noValue: return .noValue
}
case let .divActionClearFocusTemplate(value):
let result = value.resolveValue(context: context, useOnlyLinks: useOnlyLinks)
switch result {
@@ -143,6 +156,14 @@ public enum DivActionTypedTemplate: TemplateValue {
case let .failure(errors): return .failure(errors)
case .noValue: return .noValue
}
case DivActionArraySetValue.type:
let result = DivActionArraySetValueTemplate.resolveValue(context: context, useOnlyLinks: useOnlyLinks)
switch result {
case let .success(value): return .success(.divActionArraySetValue(value))
case let .partialSuccess(value, warnings): return .partialSuccess(.divActionArraySetValue(value), warnings: warnings)
case let .failure(errors): return .failure(errors)
case .noValue: return .noValue
}
case DivActionClearFocus.type:
let result = DivActionClearFocusTemplate.resolveValue(context: context, useOnlyLinks: useOnlyLinks)
switch result {
@@ -198,6 +219,8 @@ extension DivActionTypedTemplate {
self = .divActionArrayInsertValueTemplate(try DivActionArrayInsertValueTemplate(dictionary: dictionary, templateToType: templateToType))
case DivActionArrayRemoveValueTemplate.type:
self = .divActionArrayRemoveValueTemplate(try DivActionArrayRemoveValueTemplate(dictionary: dictionary, templateToType: templateToType))
case DivActionArraySetValueTemplate.type:
self = .divActionArraySetValueTemplate(try DivActionArraySetValueTemplate(dictionary: dictionary, templateToType: templateToType))
case DivActionClearFocusTemplate.type:
self = .divActionClearFocusTemplate(try DivActionClearFocusTemplate(dictionary: dictionary, templateToType: templateToType))
case DivActionCopyToClipboardTemplate.type:
@@ -53,17 +53,12 @@ final class DivActionHandlerTests: XCTestCase {
func test_ArrayInsertValueAction_AppendsValue() {
setVariableValue("array_var", .array([1, "two"]))
handle(
divAction(
logId: "test_log_id",
typed: .divActionArrayInsertValue(
DivActionArrayInsertValue(
value: stringValue("new value"),
variableName: .value("array_var")
)
)
handle(.divActionArrayInsertValue(
DivActionArrayInsertValue(
value: stringValue("new value"),
variableName: .value("array_var")
)
)
))
XCTAssertEqual([1, "two", "new value"] as [AnyHashable], getVariableValue("array_var"))
}
@@ -71,55 +66,54 @@ final class DivActionHandlerTests: XCTestCase {
func test_ArrayInsertValueAction_InsertsValue() {
setVariableValue("array_var", .array([1, "two"]))
handle(
divAction(
logId: "test_log_id",
typed: .divActionArrayInsertValue(
DivActionArrayInsertValue(
index: .value(1),
value: stringValue("new value"),
variableName: .value("array_var")
)
)
handle(.divActionArrayInsertValue(
DivActionArrayInsertValue(
index: .value(1),
value: stringValue("new value"),
variableName: .value("array_var")
)
)
))
XCTAssertEqual([1, "new value", "two"] as [AnyHashable], getVariableValue("array_var"))
}
func test_ArrayInsertValueAction_WithIndexEqualLength_InsertsValue() {
setVariableValue("array_var", .array(["one", "two"]))
handle(.divActionArrayInsertValue(
DivActionArrayInsertValue(
index: .value(2),
value: stringValue("new value"),
variableName: .value("array_var")
)
))
XCTAssertEqual(["one", "two", "new value"], getVariableValue("array_var"))
}
func test_ArrayInsertValueAction_DoesNothingForInvalidIndex() {
setVariableValue("array_var", .array([1, "two"]))
handle(
divAction(
logId: "test_log_id",
typed: .divActionArrayInsertValue(
DivActionArrayInsertValue(
index: .value(10),
value: stringValue("new value"),
variableName: .value("array_var")
)
)
handle(.divActionArrayInsertValue(
DivActionArrayInsertValue(
index: .value(10),
value: stringValue("new value"),
variableName: .value("array_var")
)
)
))
XCTAssertEqual([1, "two"] as [AnyHashable], getVariableValue("array_var"))
}
func test_ArrayInsertValueAction__DoesNothingForDifferentTypeVar() {
func test_ArrayInsertValueAction_DoesNothingForNotArrayVar() {
setVariableValue("string_var", .string("value"))
handle(
divAction(
logId: "test_log_id",
typed: .divActionArrayInsertValue(
DivActionArrayInsertValue(
value: stringValue("new value"),
variableName: .value("string_var")
)
)
handle(.divActionArrayInsertValue(
DivActionArrayInsertValue(
value: stringValue("new value"),
variableName: .value("string_var")
)
)
))
XCTAssertEqual("value", getVariableValue("string_var"))
}
@@ -127,17 +121,12 @@ final class DivActionHandlerTests: XCTestCase {
func test_ArrayRemoveValueAction_RemovesValue() {
setVariableValue("array_var", .array([1, "two"]))
handle(
divAction(
logId: "test_log_id",
typed: .divActionArrayRemoveValue(
DivActionArrayRemoveValue(
index: .value(1),
variableName: .value("array_var")
)
)
handle(.divActionArrayRemoveValue(
DivActionArrayRemoveValue(
index: .value(1),
variableName: .value("array_var")
)
)
))
XCTAssertEqual([1] as [AnyHashable], getVariableValue("array_var"))
}
@@ -145,36 +134,81 @@ final class DivActionHandlerTests: XCTestCase {
func test_ArrayRemoveValueAction_DoesNothingForInvalidIndex() {
setVariableValue("array_var", .array([1, "two"]))
handle(
divAction(
logId: "test_log_id",
typed: .divActionArrayRemoveValue(
DivActionArrayRemoveValue(
index: .value(10),
variableName: .value("array_var")
)
)
handle(.divActionArrayRemoveValue(
DivActionArrayRemoveValue(
index: .value(10),
variableName: .value("array_var")
)
)
))
XCTAssertEqual([1, "two"] as [AnyHashable], getVariableValue("array_var"))
}
func test_ArrayRemoveValueAction_DoesNothingForNotArrayVar() {
setVariableValue("string_var", .string("value"))
handle(.divActionArrayRemoveValue(
DivActionArrayRemoveValue(
index: .value(0),
variableName: .value("string_var")
)
))
XCTAssertEqual("value", getVariableValue("string_var"))
}
func test_ArraySetValueAction_SetsValue() {
setVariableValue("array_var", .array(["one", "two"]))
handle(.divActionArraySetValue(
DivActionArraySetValue(
index: .value(1),
value: stringValue("new value"),
variableName: .value("array_var")
)
))
XCTAssertEqual(["one", "new value"], getVariableValue("array_var"))
}
func test_ArraySetValueAction_DoesNothingForInvalidIndex() {
setVariableValue("array_var", .array(["one", "two"]))
handle(.divActionArraySetValue(
DivActionArraySetValue(
index: .value(2),
value: stringValue("new value"),
variableName: .value("array_var")
)
))
XCTAssertEqual(["one", "two"], getVariableValue("array_var"))
}
func test_ArraySetValueAction_DoesNothingForNotArrayVar() {
setVariableValue("string_var", .string("value"))
handle(.divActionArraySetValue(
DivActionArraySetValue(
index: .value(0),
value: stringValue("new value"),
variableName: .value("string_var")
)
))
XCTAssertEqual("value", getVariableValue("string_var"))
}
func test_DictSetValueAction_AddsValue() {
setVariableValue("dict_var", .dict([:]))
handle(
divAction(
logId: "test_log_id",
typed: .divActionDictSetValue(
DivActionDictSetValue(
key: .value("key"),
value: stringValue("new value"),
variableName: .value("dict_var")
)
)
handle(.divActionDictSetValue(
DivActionDictSetValue(
key: .value("key"),
value: stringValue("new value"),
variableName: .value("dict_var")
)
)
))
XCTAssertEqual(["key": "new value"], getVariableValue("dict_var"))
}
@@ -182,18 +216,13 @@ final class DivActionHandlerTests: XCTestCase {
func test_DictSetValueAction_UpdatesValue() {
setVariableValue("dict_var", .dict(["key": "value"]))
handle(
divAction(
logId: "test_log_id",
typed: .divActionDictSetValue(
DivActionDictSetValue(
key: .value("key"),
value: .dictValue(DictValue(value: ["new_key": "new value"])),
variableName: .value("dict_var")
)
)
handle(.divActionDictSetValue(
DivActionDictSetValue(
key: .value("key"),
value: .dictValue(DictValue(value: ["new_key": "new value"])),
variableName: .value("dict_var")
)
)
))
XCTAssertEqual(["key": ["new_key": "new value"]], getVariableValue("dict_var"))
}
@@ -201,36 +230,26 @@ final class DivActionHandlerTests: XCTestCase {
func test_DictSetValueAction_RemovesValue() {
setVariableValue("dict_var", .dict(["key": "value"]))
handle(
divAction(
logId: "test_log_id",
typed: .divActionDictSetValue(
DivActionDictSetValue(
key: .value("key"),
variableName: .value("dict_var")
)
)
handle(.divActionDictSetValue(
DivActionDictSetValue(
key: .value("key"),
variableName: .value("dict_var")
)
)
))
XCTAssertEqual([String: AnyHashable](), getVariableValue("dict_var"))
}
func test_DictSetValueAction_DoesNothingForDifferentTypeVar() {
func test_DictSetValueAction_DoesNothingForNotDictVar() {
setVariableValue("array_var", .array(["one", "two"]))
handle(
divAction(
logId: "test_log_id",
typed: .divActionDictSetValue(
DivActionDictSetValue(
key: .value("key"),
value: stringValue("new value"),
variableName: .value("array_var")
)
)
handle(.divActionDictSetValue(
DivActionDictSetValue(
key: .value("key"),
value: stringValue("new value"),
variableName: .value("array_var")
)
)
))
XCTAssertEqual(["one", "two"], getVariableValue("array_var"))
}
@@ -238,17 +257,12 @@ final class DivActionHandlerTests: XCTestCase {
func test_SetVariableAction_SetsStringVariable() {
setVariableValue("string_var", .string("default"))
handle(
divAction(
logId: "test_log_id",
typed: .divActionSetVariable(
DivActionSetVariable(
value: stringValue("new value"),
variableName: .value("string_var")
)
)
handle(.divActionSetVariable(
DivActionSetVariable(
value: stringValue("new value"),
variableName: .value("string_var")
)
)
))
XCTAssertEqual("new value", getVariableValue("string_var"))
}
@@ -256,17 +270,12 @@ final class DivActionHandlerTests: XCTestCase {
func test_SetVariableAction_SetsArrayVariable() {
setVariableValue("array_var", .array([]))
handle(
divAction(
logId: "test_log_id",
typed: .divActionSetVariable(
DivActionSetVariable(
value: .arrayValue(ArrayValue(value: .value(["value 1", "value 2"]))),
variableName: .value("array_var")
)
)
handle(.divActionSetVariable(
DivActionSetVariable(
value: .arrayValue(ArrayValue(value: .value(["value 1", "value 2"]))),
variableName: .value("array_var")
)
)
))
XCTAssertEqual(["value 1", "value 2"], getVariableValue("array_var"))
}
@@ -314,10 +323,19 @@ final class DivActionHandlerTests: XCTestCase {
)
}
private func handle(_ action: DivActionTyped) {
actionHandler.handle(
divAction(logId: "log_id", typed: action),
cardId: cardId,
source: .tap,
sender: nil
)
}
private func getVariableValue<T>(_ name: DivVariableName) -> T? {
variablesStorage.makeVariables(for: cardId)[name]?.typedValue()
}
private func setVariableValue(_ name: DivVariableName, _ value: DivVariableValue) {
variablesStorage.set(cardId: cardId, variables: [name: value])
}
+31
View File
@@ -0,0 +1,31 @@
{
"type": "object",
"$description": "translations.json#/div_action_array_set_value",
"properties": {
"type": {
"type": "string",
"enum": [
"array_set_value"
]
},
"variable_name": {
"type": "string"
},
"index": {
"type": "integer"
},
"value": {
"$ref": "div-typed-value.json"
}
},
"platforms": [
"android",
"ios"
],
"required": [
"type",
"variable_name",
"index",
"value"
]
}
+3
View File
@@ -6,6 +6,9 @@
{
"$ref": "div-action-array-remove-value.json"
},
{
"$ref": "div-action-array-set-value.json"
},
{
"$ref": "div-action-clear-focus.json"
},
+4
View File
@@ -43,6 +43,10 @@
"en": "Deletes a value from the array",
"ru": "Удаляет значение из массива"
},
"div_action_array_set_value": {
"en": "Sets value in the array",
"ru": "Устанавливает значение в массиве по индексу"
},
"div_action_base_download_callbacks": {
"en": "Callbacks that are called after [data loading](../../interaction#loading-data).",
"ru": "Колбэки, которые вызываются после [дозагрузки данных](../../interaction#loading-data)."