mirror of
https://github.com/divkit/divkit.git
synced 2026-05-07 20:02:32 +00:00
support patch properties
commit_hash:8863bfcf57ccb5a8b9ea4986010f4770821772db
This commit is contained in:
@@ -58,6 +58,7 @@ public struct DivBlockModelingContext {
|
||||
cardId: DivCardID,
|
||||
additionalId: String? = nil,
|
||||
stateManager: DivStateManager = DivStateManager(),
|
||||
actionHandler: DivActionHandler? = nil,
|
||||
blockStateStorage: DivBlockStateStorage = DivBlockStateStorage(),
|
||||
imageHolderFactory: DivImageHolderFactory,
|
||||
extensionHandlers: [DivExtensionHandler] = [],
|
||||
@@ -67,7 +68,7 @@ public struct DivBlockModelingContext {
|
||||
self.init(
|
||||
viewId: DivViewId(cardId: cardId, additionalId: additionalId),
|
||||
stateManager: stateManager,
|
||||
actionHandler: nil,
|
||||
actionHandler: actionHandler,
|
||||
blockStateStorage: blockStateStorage,
|
||||
visibilityCounter: nil,
|
||||
lastVisibleBoundsCache: nil,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import LayoutKitInterface
|
||||
import VGSL
|
||||
|
||||
protocol DivPatchCallbacks {
|
||||
@@ -19,6 +20,42 @@ extension DivData {
|
||||
applyPatch(patch, callbacks: Callbacks.empty)
|
||||
}
|
||||
|
||||
public func applyPatchWithActions(
|
||||
_ patch: DivPatch,
|
||||
context: DivBlockModelingContext
|
||||
) -> DivData {
|
||||
var changedElementIds: [String] = []
|
||||
|
||||
let callbacks = Callbacks(
|
||||
elementChanged: { id in
|
||||
changedElementIds.append(id)
|
||||
}
|
||||
)
|
||||
|
||||
let patchedData = applyPatch(patch, callbacks: callbacks)
|
||||
|
||||
let allChangesApplied = changedElementIds.count == patch.changes.count
|
||||
let isTransactional = patch.resolveMode(context.expressionResolver) == .transactional
|
||||
let isPatchApplied = !isTransactional || allChangesApplied
|
||||
|
||||
if isPatchApplied {
|
||||
changedElementIds.forEach { id in
|
||||
context.triggersStorage?.reset(elementId: id)
|
||||
}
|
||||
}
|
||||
let actions = isPatchApplied ? patch.onAppliedActions : patch.onFailedActions
|
||||
actions?.forEach { action in
|
||||
context.actionHandler?.handle(
|
||||
action,
|
||||
path: UIElementPath(context.cardId.rawValue),
|
||||
source: .callback,
|
||||
sender: nil
|
||||
)
|
||||
}
|
||||
|
||||
return isPatchApplied ? patchedData : self
|
||||
}
|
||||
|
||||
func applyPatch(_ patch: DivPatch, callbacks: DivPatchCallbacks) -> DivData {
|
||||
let states = states.map {
|
||||
State(div: $0.div.applySingleItemPatch(patch, callbacks: callbacks), stateId: $0.stateId)
|
||||
|
||||
@@ -193,23 +193,6 @@ final class DivBlockProvider {
|
||||
return
|
||||
}
|
||||
|
||||
reasons.compactMap { $0.patch(for: self.cardId) }.forEach {
|
||||
divData = divData.applyPatch(
|
||||
$0,
|
||||
callbacks: Callbacks(elementChanged: { [weak self] id in
|
||||
self?.divKitComponents.triggersStorage.reset(elementId: id)
|
||||
})
|
||||
)
|
||||
$0.onAppliedActions?.forEach { action in
|
||||
divKitComponents.actionHandler.handle(
|
||||
action,
|
||||
path: UIElementPath(cardId.rawValue),
|
||||
source: .callback,
|
||||
sender: nil
|
||||
)
|
||||
}
|
||||
}
|
||||
self.divData = divData
|
||||
let context = divKitComponents.makeContext(
|
||||
cardId: cardId,
|
||||
additionalId: id.additionalId,
|
||||
@@ -218,6 +201,15 @@ final class DivBlockProvider {
|
||||
parentScrollView: parentScrollView
|
||||
)
|
||||
|
||||
reasons.compactMap { $0.patch(for: self.cardId) }.forEach { patch in
|
||||
divData = divData.applyPatchWithActions(
|
||||
patch,
|
||||
context: context
|
||||
)
|
||||
}
|
||||
|
||||
self.divData = divData
|
||||
|
||||
if reasons.filter(\.isVariable).isEmpty {
|
||||
context.layoutProviderHandler?.resetUpdatedVariables()
|
||||
shouldInvokeOnVisibleBoundsChanged = true
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@testable import DivKit
|
||||
@testable @_spi(Internal) import DivKit
|
||||
import LayoutKit
|
||||
import VGSL
|
||||
import XCTest
|
||||
|
||||
@@ -8,6 +9,20 @@ final class DivDataPatchExtensionsTests: XCTestCase {
|
||||
self.callbacksCount += 1
|
||||
}
|
||||
|
||||
private var flags: DivFlagsInfo = .default
|
||||
private var isUpdateCardCalled = false
|
||||
|
||||
private lazy var handledUrls = [URL]()
|
||||
private lazy var actionHandler = DivActionHandler(
|
||||
urlHandler: DivUrlHandlerDelegate { [unowned self] url, _ in
|
||||
handledUrls.append(url)
|
||||
}
|
||||
)
|
||||
|
||||
private lazy var context = DivBlockModelingContext(
|
||||
actionHandler: actionHandler
|
||||
)
|
||||
|
||||
func test_WhenNoSuitableChanges_DoesNothing() throws {
|
||||
let originalData = divData(
|
||||
divContainer(
|
||||
@@ -418,6 +433,113 @@ final class DivDataPatchExtensionsTests: XCTestCase {
|
||||
)
|
||||
XCTAssertEqual(callbacksCount, 1)
|
||||
}
|
||||
|
||||
func test_ApplyPatchWithActions_DefaultMode_AppliedPartialDataAndExecuteAppliedActions() throws {
|
||||
let originalData = divData(
|
||||
divContainer(
|
||||
items: [
|
||||
divText(id: "div_to_replace", text: "Old text"),
|
||||
divSeparator(),
|
||||
]
|
||||
)
|
||||
)
|
||||
let expectedData = divData(
|
||||
divContainer(
|
||||
items: [
|
||||
newDivText1,
|
||||
divSeparator(),
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
let patch = DivPatch(
|
||||
changes: [
|
||||
DivPatch.Change(id: "div_to_replace", items: [newDivText1]),
|
||||
DivPatch.Change(id: "error_div_id", items: [newDivText2]),
|
||||
],
|
||||
mode: .value(.partial),
|
||||
onAppliedActions: [divAction(logId: "applied", url: "action://applied")],
|
||||
onFailedActions: [divAction(logId: "failed", url: "action://failed")]
|
||||
)
|
||||
|
||||
let result = originalData.applyPatchWithActions(
|
||||
patch,
|
||||
context: context
|
||||
)
|
||||
|
||||
XCTAssertEqual(result, expectedData)
|
||||
XCTAssertEqual(handledUrls, [URL(string: "action://applied")!])
|
||||
}
|
||||
|
||||
func test_ApplyPatchWithActions_TransactionalMode_AllChangesApplied_ExecutesOnAppliedActions(
|
||||
) throws {
|
||||
let originalData = divData(
|
||||
divContainer(
|
||||
items: [
|
||||
divText(id: "div_to_replace", text: "Old text"),
|
||||
divSeparator(),
|
||||
divText(id: "div_to_replace_2", text: "Old text 2"),
|
||||
]
|
||||
)
|
||||
)
|
||||
let expectedData = divData(
|
||||
divContainer(
|
||||
items: [
|
||||
newDivText1,
|
||||
divSeparator(),
|
||||
newDivText2,
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
let patch = DivPatch(
|
||||
changes: [
|
||||
DivPatch.Change(id: "div_to_replace", items: [newDivText1]),
|
||||
DivPatch.Change(id: "div_to_replace_2", items: [newDivText2]),
|
||||
],
|
||||
mode: .value(.transactional),
|
||||
onAppliedActions: [divAction(logId: "applied", url: "action://applied")],
|
||||
onFailedActions: [divAction(logId: "failed", url: "action://failed")]
|
||||
)
|
||||
|
||||
let result = originalData.applyPatchWithActions(
|
||||
patch,
|
||||
context: context
|
||||
)
|
||||
|
||||
XCTAssertEqual(result, expectedData)
|
||||
XCTAssertEqual(handledUrls, [URL(string: "action://applied")!])
|
||||
}
|
||||
|
||||
func test_ApplyPatchWithActions_TransactionalMode_NotAllChangesApplied_ExecutesOnFailedActions(
|
||||
) throws {
|
||||
let originalData = divData(
|
||||
divContainer(
|
||||
items: [
|
||||
divText(id: "div_to_replace", text: "Old text"),
|
||||
divSeparator(),
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
let patch = DivPatch(
|
||||
changes: [
|
||||
DivPatch.Change(id: "div_to_replace", items: [newDivText1]),
|
||||
DivPatch.Change(id: "non_existent_div", items: [newDivText2]),
|
||||
],
|
||||
mode: .value(.transactional),
|
||||
onAppliedActions: [divAction(logId: "applied", url: "action://applied")],
|
||||
onFailedActions: [divAction(logId: "failed", url: "action://failed")]
|
||||
)
|
||||
|
||||
let result = originalData.applyPatchWithActions(
|
||||
patch,
|
||||
context: context
|
||||
)
|
||||
|
||||
XCTAssertEqual(result, originalData)
|
||||
XCTAssertEqual(handledUrls, [URL(string: "action://failed")!])
|
||||
}
|
||||
}
|
||||
|
||||
private let newDivText1 = divText(text: "New text 1")
|
||||
|
||||
@@ -11,6 +11,7 @@ extension DivBlockModelingContext {
|
||||
public init(
|
||||
cardId: DivCardID = Self.testCardId,
|
||||
additionalId: String? = nil,
|
||||
actionHandler: DivActionHandler? = nil,
|
||||
blockStateStorage: DivBlockStateStorage = DivBlockStateStorage(),
|
||||
extensionHandlers: [DivExtensionHandler] = [],
|
||||
scheduler: Scheduling? = nil,
|
||||
@@ -19,6 +20,7 @@ extension DivBlockModelingContext {
|
||||
self = DivBlockModelingContext(
|
||||
cardId: cardId,
|
||||
additionalId: additionalId,
|
||||
actionHandler: actionHandler,
|
||||
blockStateStorage: blockStateStorage,
|
||||
imageHolderFactory: FakeImageHolderFactory(),
|
||||
extensionHandlers: extensionHandlers,
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
"$description": "translations.json#/div_patch_mode",
|
||||
"platforms": [
|
||||
"android",
|
||||
"ios",
|
||||
"web"
|
||||
]
|
||||
},
|
||||
@@ -71,6 +72,7 @@
|
||||
"$description": "translations.json#/div_patch_on_failed_actions",
|
||||
"platforms": [
|
||||
"android",
|
||||
"ios",
|
||||
"web"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -2867,6 +2867,7 @@
|
||||
"case_id": 192,
|
||||
"platforms": [
|
||||
"android",
|
||||
"ios",
|
||||
"web"
|
||||
],
|
||||
"tags": [
|
||||
@@ -3160,6 +3161,7 @@
|
||||
],
|
||||
"platforms": [
|
||||
"web",
|
||||
"ios",
|
||||
"android"
|
||||
],
|
||||
"steps": [
|
||||
@@ -3455,6 +3457,7 @@
|
||||
"case_id": 139,
|
||||
"platforms": [
|
||||
"web",
|
||||
"ios",
|
||||
"android"
|
||||
],
|
||||
"tags": [
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
"type": "container",
|
||||
"visibility_action": {
|
||||
"log_id": "visibility",
|
||||
"url": "div-action://download?url=https%3A%2F%2Fyastatic.net%2Fs3%2Fhome%2Fdivkit%2Ftranscation-callbacks.json"
|
||||
"url": "div-action://download?url=https%3A%2F%2Fdivkit.tech%2Fplayground%2Fapi%2Fjson%3Fuuid%3Dd05dd997-ac93-426e-9dc2-ceb0e3c19183"
|
||||
},
|
||||
"width": {
|
||||
"type": "match_parent"
|
||||
@@ -72,7 +72,7 @@
|
||||
"items": [
|
||||
{
|
||||
"type": "alert_card",
|
||||
"id": "item1",
|
||||
"id": "item2",
|
||||
"alert_text": "1 item"
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user