fix card variables case for div submit action

commit_hash:dc29ad2dd629fbe6e9466245569034abe343db18
This commit is contained in:
babaevmm
2025-05-11 18:52:56 +03:00
parent 9c7fba24f7
commit fad580b95b
8 changed files with 401 additions and 16 deletions
+1
View File
@@ -20945,6 +20945,7 @@
"test_data/regression_test_data/state_binding.json":"divkit/public/test_data/regression_test_data/state_binding.json",
"test_data/regression_test_data/states_default_state.json":"divkit/public/test_data/regression_test_data/states_default_state.json",
"test_data/regression_test_data/stored_value.json":"divkit/public/test_data/regression_test_data/stored_value.json",
"test_data/regression_test_data/submit_action.json":"divkit/public/test_data/regression_test_data/submit_action.json",
"test_data/regression_test_data/svg_scale.json":"divkit/public/test_data/regression_test_data/svg_scale.json",
"test_data/regression_test_data/tabs-item-actions.json":"divkit/public/test_data/regression_test_data/tabs-item-actions.json",
"test_data/regression_test_data/tabs_disabled_switch_tabs_by_swipe.json":"divkit/public/test_data/regression_test_data/tabs_disabled_switch_tabs_by_swipe.json",
@@ -19,20 +19,14 @@ final class SubmitActionHandler {
return
}
let containers = context.variablesStorage.getVariables(
guard let containerVariables = context.variablesStorage.getOnlyElementVariables(
cardId: context.cardId,
elementId: containerId
)
guard let container = containers.first else {
DivKitLogger.error("Element with id \(containerId) not found")
return
}
guard containers.count == 1 else {
DivKitLogger.error("Found multiple elements that respond to id: \(containerId)")
) else {
return
}
let containerData = container.map(
let containerData = containerVariables.map(
key: { $0.rawValue },
value: { $0.toString() }
)
@@ -1,4 +1,5 @@
import Foundation
import LayoutKit
import VGSL
/// Stores variables.
@@ -9,6 +10,7 @@ public final class DivVariableStorage {
public let changedVariables: Set<DivVariableName>
}
let initialPath: UIElementPath?
private let outerStorage: DivVariableStorage?
private var _values = DivVariables()
@@ -40,8 +42,18 @@ public final class DivVariableStorage {
/// - outerStorage: Storage that provides outer scope variables. Outer scope variables are
/// accessible via current storage (see `getValue` and `update` methods). Outer scope variables
/// can be shadowed.
public init(outerStorage: DivVariableStorage? = nil) {
public convenience init(
outerStorage: DivVariableStorage? = nil
) {
self.init(outerStorage: outerStorage, initialPath: nil)
}
init(
outerStorage: DivVariableStorage?,
initialPath: UIElementPath?
) {
self.outerStorage = outerStorage
self.initialPath = initialPath
if let outerStorage {
changeEvents = Signal.merge(outerStorage.changeEvents, changeEventsPipe.signal)
@@ -43,7 +43,7 @@ public final class DivVariablesStorage {
}
public init(outerStorage: DivVariableStorage?) {
globalStorage = DivVariableStorage(outerStorage: outerStorage)
globalStorage = DivVariableStorage(outerStorage: outerStorage, initialPath: nil)
let globalStorageEvents: Signal<ChangeEvent> = globalStorage.changeEvents.compactMap {
ChangeEvent(.global($0.changedVariables))
@@ -51,11 +51,26 @@ public final class DivVariablesStorage {
changeEvents = Signal.merge(globalStorageEvents, changeEventsPipe.signal)
}
func getVariables(cardId: DivCardID, elementId: String) -> [DivVariables] {
func getOnlyElementVariables(cardId: DivCardID, elementId: String) -> DivVariables? {
lock.withLock {
localStorages.filter {
let storages = localStorages.filter {
$0.key.leaf == elementId && $0.key.cardId == cardId
}.map(\.value.values)
}.map(\.value)
guard let storage = storages.first else {
DivKitLogger.error("Element with id \(elementId) not found")
return nil
}
guard storages.count == 1 else {
DivKitLogger.error("Found multiple elements that respond to id: \(elementId)")
return nil
}
guard storage.initialPath?.leaf == elementId else {
return [:]
}
return storage.values
}
}
@@ -102,7 +117,7 @@ public final class DivVariablesStorage {
// optimization that allows to access the local storage for one operation
localStorages[path] = nearestStorage
} else {
let localStorage = DivVariableStorage(outerStorage: nearestStorage)
let localStorage = DivVariableStorage(outerStorage: nearestStorage, initialPath: path)
localStorage.replaceAll(variables, notifyObservers: false)
localStorages[path] = localStorage
}
@@ -120,7 +135,7 @@ public final class DivVariablesStorage {
if let localStorage = localStorages[path] {
return localStorage
}
let localStorage = DivVariableStorage(outerStorage: globalStorage)
let localStorage = DivVariableStorage(outerStorage: globalStorage, initialPath: path)
localStorages[path] = localStorage
return localStorage
}
@@ -38,6 +38,10 @@ final class SubmitActionHandlerTests: XCTestCase {
}
func test_Submit_ContainerWithoutVariables() {
variablesStorage.initializeIfNeeded(
path: cardId.path,
variables: ["some_var": .string("some_value")]
)
variablesStorage.initializeIfNeeded(
path: cardId.path + containerId,
variables: [:]
@@ -45,6 +49,7 @@ final class SubmitActionHandlerTests: XCTestCase {
handler.handleSubmit()
XCTAssertNotNil(submitter.lastRequest)
XCTAssertTrue(submitter.lastData?.isEmpty == true)
}
@@ -56,6 +61,7 @@ final class SubmitActionHandlerTests: XCTestCase {
handler.handleSubmit(containerId: "wrong_id")
XCTAssertNil(submitter.lastRequest)
XCTAssertNil(submitter.lastData)
}
@@ -454,6 +454,68 @@ final class DivVariablesStorageTest: XCTestCase {
XCTAssertNil(storage.getVariableValue(path: cardId.path + "element_id", name: "string_var"))
}
func test_getOnlyElementVariables_nonEmptyElementVariables() {
let cardVariables: DivVariables = [
"some_card_var": .string("value"),
]
let elementVariables = variables
let elementId = "element_id"
storage.set(cardId: cardId, variables: cardVariables)
storage.initializeIfNeeded(
path: cardId.path + "some_container" + 0 + elementId,
variables: elementVariables
)
let result = storage.getOnlyElementVariables(cardId: cardId, elementId: elementId)
XCTAssertEqual(result, variables)
}
func test_getOnlyElementVariables_noVariablesDeclaredAtPath() {
let cardVariables = variables
let elementVariables = DivVariables()
let elementId = "element_id"
storage.set(cardId: cardId, variables: cardVariables)
storage.initializeIfNeeded(
path: cardId.path + "some_container" + 0 + elementId,
variables: elementVariables
)
let result = storage.getOnlyElementVariables(cardId: cardId, elementId: elementId)
XCTAssertEqual(result, [:])
}
func test_getOnlyElementVariables_multipleElementsWithSameId() {
let elementId = "element_id"
storage.set(cardId: cardId, variables: variables)
storage.initializeIfNeeded(
path: cardId.path + "some_container" + 0 + elementId,
variables: variables
)
storage.initializeIfNeeded(
path: cardId.path + "other_container" + 0 + elementId,
variables: variables
)
let result = storage.getOnlyElementVariables(cardId: cardId, elementId: elementId)
XCTAssertEqual(result, nil)
}
func test_getOnlyElementVariables_noSuchElement() {
let elementId = "non_existing_element_id"
storage.set(cardId: cardId, variables: variables)
storage.initializeIfNeeded(
path: cardId.path + "some_container" + 0 + "other_id",
variables: variables
)
let result = storage.getVariableValue(cardId: cardId, name: .init(rawValue: elementId))
XCTAssertEqual(result, nil)
}
private func getVariable<T>(_ name: DivVariableName) -> T? {
storage.getVariableValue(cardId: cardId, name: name)
}
+24
View File
@@ -3786,6 +3786,30 @@
"Only last tap doesn't affect input focus"
],
"file": "input_capture_focus.json"
},
{
"title": "Submit action test",
"case_id": 221,
"platforms": [
"android",
"ios",
"web"
],
"tags": [
"DivAction",
"TypedActions"
],
"steps": [
"Tap the 'Submit Form' button",
"Enter a different URL in the API endpoint field",
"Tap the 'Submit Form' button"
],
"expected_results": [
"Form data is submitted to the specified endpoint",
"Status changes to 'Form submitted successfully' on successful submission",
"Status changes to 'Form submission failed' on failed submission"
],
"file": "submit_action.json"
}
]
}
@@ -0,0 +1,271 @@
{
"description": "Submit action test",
"platforms": [
"android",
"ios",
"web"
],
"card": {
"log_id": "submit_action_test",
"variables": [
{
"type": "string",
"name": "submit_status",
"value": "Not submitted"
},
{
"type": "string",
"name": "request_url",
"value": "https://httpbin.org/post"
},
{
"type": "boolean",
"name": "progress",
"value": false
}
],
"states": [
{
"state_id": 0,
"div": {
"type": "container",
"orientation": "vertical",
"width": {
"type": "match_parent"
},
"height": {
"type": "wrap_content"
},
"paddings": {
"top": 16,
"bottom": 16,
"left": 16,
"right": 16
},
"background": [
{
"type": "solid",
"color": "#FFFFFF"
}
],
"items": [
{
"type": "text",
"font_size": 24,
"font_weight": "bold",
"text": "Submit Form Test",
"paddings": {
"bottom": 16
}
},
{
"type": "text",
"font_size": 16,
"font_weight": "bold",
"text": "API Endpoint URL:",
"paddings": {
"bottom": 8
}
},
{
"type": "input",
"font_size": 16,
"hint_text": "Enter API URL",
"hint_color": "#AAAAAA",
"text_color": "#000000",
"text_variable": "request_url",
"width": {
"type": "match_parent"
},
"height": {
"type": "wrap_content"
},
"paddings": {
"top": 8,
"bottom": 8,
"left": 12,
"right": 12
},
"margins": {
"bottom": 16
},
"border": {
"corner_radius": 8,
"stroke": {
"color": "#CCCCCC",
"width": 1
}
}
},
{
"type": "container",
"orientation": "vertical",
"width": {
"type": "match_parent"
},
"height": {
"type": "wrap_content"
},
"border": {
"corner_radius": 8,
"stroke": {
"color": "#EEEEEE",
"width": 1
}
},
"paddings": {
"top": 16,
"bottom": 16,
"left": 16,
"right": 16
},
"margins": {
"bottom": 16
},
"id": "form_container",
"variables": [
{
"type": "string",
"name": "name",
"value": "John Doe"
},
{
"type": "string",
"name": "email",
"value": "john.doe@example.com"
},
{
"type": "dict",
"name": "form_data",
"value": {
"name": "John Doe",
"email": "john.doe@example.com"
}
}
],
"items": [
{
"type": "text",
"font_size": 16,
"font_weight": "bold",
"text": "Name:",
"paddings": {
"bottom": 8
}
},
{
"type": "text",
"font_size": 16,
"text": "@{name}",
"paddings": {
"bottom": 16
}
},
{
"type": "text",
"font_size": 16,
"font_weight": "bold",
"text": "Email:",
"paddings": {
"bottom": 8
}
},
{
"type": "text",
"font_size": 16,
"text": "@{email}",
"paddings": {
"bottom": 16
}
}
]
},
{
"type": "text",
"font_size": 16,
"font_weight": "bold",
"text_color": "#FFFFFF",
"text": "Submit Form",
"paddings": {
"top": 12,
"bottom": 12,
"left": 24,
"right": 24
},
"background": [
{
"type": "solid",
"color": "#4285F4"
}
],
"border": {
"corner_radius": 8
},
"width": {
"type": "wrap_content"
},
"height": {
"type": "fixed",
"value": 44
},
"margins": {
"bottom": 16
},
"visibility": "@{!progress ? 'visible' : 'invisible'}",
"actions": [
{
"log_id": "set_progress_true",
"url": "div-action://set_variable?name=progress&value=true"
},
{
"log_id": "set_status_progress",
"url": "div-action://set_variable?name=submit_status&value=progress"
},
{
"log_id": "submit_form",
"typed": {
"type": "submit",
"container_id": "form_container",
"request": {
"url": "@{request_url}",
"method": "post",
"headers": []
},
"on_success_actions": [
{
"log_id": "submit_success",
"url": "div-action://set_variable?name=submit_status&value=Form%20submitted%20successfully"
},
{
"log_id": "set_progress_false",
"url": "div-action://set_variable?name=progress&value=false"
}
],
"on_fail_actions": [
{
"log_id": "submit_fail",
"url": "div-action://set_variable?name=submit_status&value=Form%20submission%20failed"
},
{
"log_id": "set_progress_false",
"url": "div-action://set_variable?name=progress&value=false"
}
]
}
}
]
},
{
"type": "text",
"font_size": 16,
"text": "Status: @{submit_status}",
"paddings": {
"top": 16
}
}
]
}
}
]
}
}