From 1d68e50782bcbe2b14ef2a119b746a506c98d739 Mon Sep 17 00:00:00 2001 From: babaevmm Date: Tue, 25 Mar 2025 01:43:33 +0300 Subject: [PATCH] support patch properties commit_hash:8863bfcf57ccb5a8b9ea4986010f4770821772db --- .../ios/DivKit/DivBlockModelingContext.swift | 3 +- .../DivData/DivDataPatchExtensions.swift | 37 ++++++ .../ios/DivKit/Views/DivBlockProvider.swift | 26 ++-- .../DivDataPatchExtensionsTests.swift | 124 +++++++++++++++++- .../MockDivBlockModelingContext.swift | 2 + schema/div-patch.json | 2 + test_data/regression_test_data/index.json | 3 + .../patch/actions_failed.json | 4 +- 8 files changed, 180 insertions(+), 21 deletions(-) diff --git a/client/ios/DivKit/DivBlockModelingContext.swift b/client/ios/DivKit/DivBlockModelingContext.swift index c1fbad023..df327b558 100644 --- a/client/ios/DivKit/DivBlockModelingContext.swift +++ b/client/ios/DivKit/DivBlockModelingContext.swift @@ -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, diff --git a/client/ios/DivKit/Extensions/DivData/DivDataPatchExtensions.swift b/client/ios/DivKit/Extensions/DivData/DivDataPatchExtensions.swift index 1f2be87a5..26cf0dfc6 100644 --- a/client/ios/DivKit/Extensions/DivData/DivDataPatchExtensions.swift +++ b/client/ios/DivKit/Extensions/DivData/DivDataPatchExtensions.swift @@ -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) diff --git a/client/ios/DivKit/Views/DivBlockProvider.swift b/client/ios/DivKit/Views/DivBlockProvider.swift index 555278a34..4e04f0cb9 100644 --- a/client/ios/DivKit/Views/DivBlockProvider.swift +++ b/client/ios/DivKit/Views/DivBlockProvider.swift @@ -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 diff --git a/client/ios/DivKitTests/Extensions/DivDataPatchExtensionsTests.swift b/client/ios/DivKitTests/Extensions/DivDataPatchExtensionsTests.swift index f7f97ad95..a8b7deefd 100644 --- a/client/ios/DivKitTests/Extensions/DivDataPatchExtensionsTests.swift +++ b/client/ios/DivKitTests/Extensions/DivDataPatchExtensionsTests.swift @@ -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") diff --git a/client/ios/DivKitTestsSupport/MockDivBlockModelingContext.swift b/client/ios/DivKitTestsSupport/MockDivBlockModelingContext.swift index aea44988c..81894cb3d 100644 --- a/client/ios/DivKitTestsSupport/MockDivBlockModelingContext.swift +++ b/client/ios/DivKitTestsSupport/MockDivBlockModelingContext.swift @@ -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, diff --git a/schema/div-patch.json b/schema/div-patch.json index eac374587..ab13b9108 100644 --- a/schema/div-patch.json +++ b/schema/div-patch.json @@ -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" ] } diff --git a/test_data/regression_test_data/index.json b/test_data/regression_test_data/index.json index 148c3e50e..e1d4572fb 100644 --- a/test_data/regression_test_data/index.json +++ b/test_data/regression_test_data/index.json @@ -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": [ diff --git a/test_data/regression_test_data/patch/actions_failed.json b/test_data/regression_test_data/patch/actions_failed.json index d8e1288b2..9f74fa469 100644 --- a/test_data/regression_test_data/patch/actions_failed.json +++ b/test_data/regression_test_data/patch/actions_failed.json @@ -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" }, {