Files
divkit/client/ios/DivKitTests/Extensions/DivDataPatchExtensionsTests.swift
babaevmm 95b2190de9 fix LottieExtensionHandler getPreloadURLs
commit_hash:6be2bcdc26bfcc2123469b5970df6b3ed05cfbfc
2025-06-21 14:57:11 +03:00

593 lines
15 KiB
Swift

@testable @_spi(Internal) import DivKit
import DivKitTestsSupport
import LayoutKit
import VGSL
import XCTest
final class DivDataPatchExtensionsTests: XCTestCase {
private var callbacksCount = 0
private lazy var divPatchCallbacks = Callbacks { _ in
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(
items: [
divText(id: "some_div", text: "Old text"),
divSeparator(),
]
)
)
let patchedData = originalData.applyPatch(
("div_to_replace", divSeparator())
)
XCTAssertEqual(patchedData, originalData)
}
func test_WhenReplacingRootDiv_ReplacesDiv() throws {
let originalData = divData(
divText(id: "div_to_replace", text: "Old text")
)
let patchedData = originalData.applyPatch(
("div_to_replace", newDivText1)
)
let expectedData = divData(
newDivText1
)
XCTAssertEqual(patchedData, expectedData)
}
func test_WhenReplacingDivWithDifferentType_ReplacesDiv() throws {
let oridinalData = divData(
divText(id: "div_to_replace", text: "Old text")
)
let patchedData = oridinalData.applyPatch(
("div_to_replace", divSeparator())
)
let expectedData = divData(
divSeparator()
)
XCTAssertEqual(patchedData, expectedData)
}
func test_WhenDeletingRootDiv_DoesNothing() throws {
let originalData = divData(
divText(id: "div_to_delete", text: "Old text")
)
let patchedData = originalData.applyPatch(
("div_to_delete", nil)
)
XCTAssertEqual(patchedData, originalData)
}
func test_WhenReplacingRootDivWithMultipleDivs_DoesNothing() throws {
let originalData = divData(
divText(id: "div_to_replace", text: "Old text")
)
let patchedData = originalData.applyPatch(
("div_to_replace", [newDivText1, newDivText2])
)
XCTAssertEqual(patchedData, originalData)
}
func test_WhenReplacingItemInContainer_ReplacesDiv() throws {
let originalData = divData(
divContainer(
items: [
divSeparator(),
divText(id: "div_to_replace", text: "Old text"),
divSeparator(),
]
)
)
let patchedData = originalData.applyPatch(
("div_to_replace", newDivText1)
)
let expectedData = divData(
divContainer(
items: [
divSeparator(),
newDivText1,
divSeparator(),
]
)
)
XCTAssertEqual(patchedData, expectedData)
}
func test_WhenReplacingItemsInContainer_ReplacesDivs() throws {
let originalData = divData(
divContainer(
items: [
divSeparator(),
divText(id: "div_to_replace", text: "Old text"),
divSeparator(),
]
)
)
let patchedData = originalData.applyPatch(
("div_to_replace", [newDivText1, newDivText2])
)
let expectedData = divData(
divContainer(
items: [
divSeparator(),
newDivText1,
newDivText2,
divSeparator(),
]
)
)
XCTAssertEqual(patchedData, expectedData)
}
func test_WhenDeletingItemInContainer_RemovesDiv() throws {
let originalData = divData(
divContainer(
items: [
divSeparator(),
divText(id: "div_to_delete", text: "Old text"),
divSeparator(),
]
)
)
let patchedData = originalData.applyPatch(
("div_to_delete", nil)
)
let expectedData = divData(
divContainer(
items: [
divSeparator(),
divSeparator(),
]
)
)
XCTAssertEqual(patchedData, expectedData)
}
func test_WhenHaveMultipleChanges_AppliesAll() throws {
let originalData = divData(
divContainer(
items: [
divSeparator(),
divText(id: "div_to_replace", text: "Old text"),
divText(id: "div_to_delete", text: "Text to delete"),
divSeparator(),
]
)
)
let patchedData = originalData.applyPatch(
("div_to_replace", [newDivText1, newDivText2]),
("div_to_delete", nil)
)
let expectedData = divData(
divContainer(
items: [
divSeparator(),
newDivText1,
newDivText2,
divSeparator(),
]
)
)
XCTAssertEqual(patchedData, expectedData)
}
func test_WhenReplacingItemsInGallery_ReplacesDivs() throws {
let originalData = divData(
divGallery(
items: [
divSeparator(),
divText(id: "div_to_replace", text: "Old text"),
divSeparator(),
]
)
)
let patchedData = originalData.applyPatch(
("div_to_replace", [newDivText1, newDivText2])
)
let expectedData = divData(
divGallery(
items: [
divSeparator(),
newDivText1,
newDivText2,
divSeparator(),
]
)
)
XCTAssertEqual(patchedData, expectedData)
}
func test_WhenDeletingItemInGallery_RemovesDiv() throws {
let originalData = divData(
divGallery(
items: [
divSeparator(),
divText(id: "div_to_delete", text: "Old text"),
divSeparator(),
]
)
)
let patchedData = originalData.applyPatch(
("div_to_delete", nil)
)
let expectedData = divData(
divGallery(
items: [
divSeparator(),
divSeparator(),
]
)
)
XCTAssertEqual(patchedData, expectedData)
}
func test_WhenReplacingAndDeletingItemsInGrid_AppliesAll() throws {
let originalData = divData(
divGrid(
columnCount: 1,
items: [
divSeparator(),
divText(id: "div_to_replace", text: "Old text"),
divSeparator(id: "div_to_delete"),
divSeparator(),
]
)
)
let patchedData = originalData.applyPatch(
("div_to_replace", [newDivText1, newDivText2]),
("div_to_delete", nil)
)
let expectedData = divData(
divGrid(
columnCount: 1,
items: [
divSeparator(),
newDivText1,
newDivText2,
divSeparator(),
]
)
)
XCTAssertEqual(patchedData, expectedData)
}
func test_WhenReplacingAndDeletingItemsInPager_AppliesAll() throws {
let originalData = divData(
makePager(
items: [
divSeparator(),
divText(id: "div_to_replace", text: "Old text"),
divSeparator(id: "div_to_delete"),
divSeparator(),
]
)
)
let patchedData = originalData.applyPatch(
("div_to_replace", [newDivText1, newDivText2]),
("div_to_delete", nil)
)
let expectedData = divData(
makePager(
items: [
divSeparator(),
newDivText1,
newDivText2,
divSeparator(),
]
)
)
XCTAssertEqual(patchedData, expectedData)
}
func test_WhenReplacingStateDiv_ReplacesDiv() throws {
let originalData = divData(
makeState(
states: [
divText(id: "div_to_replace", text: "Old text"),
]
)
)
let patchedData = originalData.applyPatch(
("div_to_replace", newDivText1)
)
let expectedData = divData(
makeState(
states: [
newDivText1,
]
)
)
XCTAssertEqual(patchedData, expectedData)
}
func test_WhenDeletingStateDiv_KeepsEmptyState() throws {
let originalData = divData(
makeState(
states: [
divText(id: "div_to_delete", text: "Old text"),
]
)
)
let patchedData = originalData.applyPatch(
("div_to_delete", nil)
)
let expectedData = divData(
makeState(
states: [nil]
)
)
XCTAssertEqual(patchedData, expectedData)
}
func test_WhenReplacingStateDivWithMultipleDivs_DoesNothing() throws {
let originalData = divData(
makeState(
states: [
divText(id: "div_to_replace", text: "Old text"),
]
)
)
let patchedData = originalData.applyPatch(
("div_to_replace", [newDivText1, newDivText2])
)
XCTAssertEqual(patchedData, originalData)
}
func test_WhenReplacingTabsDiv_ReplacesDiv() throws {
let originalData = divData(
makeTabs(
items: [
divText(id: "div_to_replace", text: "Old text"),
]
)
)
let patchedData = originalData.applyPatch(
("div_to_replace", newDivText1)
)
let expectedData = divData(
makeTabs(
items: [
newDivText1,
]
)
)
XCTAssertEqual(patchedData, expectedData)
}
func test_WhenDeletingTabsDiv_DoesNothing() throws {
let originalData = divData(
makeTabs(
items: [
divText(id: "div_to_delete", text: "Old text"),
]
)
)
let patchedData = originalData.applyPatch(
("div_to_delete", nil)
)
XCTAssertEqual(patchedData, originalData)
}
func test_WhenReplacesSuitableDiv_CallElementChangedCallback() throws {
let originalData = divData(
divContainer(
items: [
divText(id: "some_div", text: "Old text"),
divSeparator(),
]
)
)
_ = originalData.applyPatch(
("some_div", divSeparator()),
callbacks: divPatchCallbacks
)
XCTAssertEqual(callbacksCount, 1)
}
func test_WhenNoSuitableChanges_DoesNotCallElementChangedCallback() throws {
let originalData = divData(
divContainer(
items: [
divText(id: "some_div", text: "Old text"),
divSeparator(),
]
)
)
_ = originalData.applyPatch(
("div_to_replace", divSeparator()),
callbacks: divPatchCallbacks
)
XCTAssertEqual(callbacksCount, 0)
}
func test_WhenReplacingDivInMultipleItemsContainer_CallOnlyElementChangedCallbacks() throws {
let originalData = divData(
divContainer(
items: [
divSeparator(),
divText(id: "div_to_replace", text: "Old text"),
divText(id: "some_div", text: "Old text 2"),
divSeparator(),
]
)
)
_ = originalData.applyPatch(
("div_to_replace", [newDivText1]), ("div_to_replace_2", [newDivText1]),
callbacks: divPatchCallbacks
)
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")
private let newDivText2 = divText(text: "New text 2")
private func makePager(items: [Div]) -> Div {
divPager(
items: items,
layoutMode: .divPageSize(DivPageSize(pageWidth: DivPercentageSize(value: .value(10))))
)
}
private func makeState(states: [Div?]) -> Div {
divState(
divId: "state",
states: states.enumerated().map { index, div in
divStateState(div: div, stateId: "state_\(index)")
}
)
}
private func makeTabs(items: [Div]) -> Div {
divTabs(
items: items.enumerated().map { index, div in
divTabsItem(div: div, title: "Tab \(index)")
}
)
}
extension DivData {
fileprivate func applyPatch(
_ changes: (String, [Div]?)...,
callbacks: DivPatchCallbacks = Callbacks.empty
) -> DivData {
let patch = DivPatch(
changes: changes.map { id, items in
DivPatch.Change(id: id, items: items)
}
)
return applyPatch(patch, callbacks: callbacks)
}
fileprivate func applyPatch(
_ change: (String, Div),
callbacks: DivPatchCallbacks = Callbacks.empty
) -> DivData {
applyPatch((change.0, [change.1]), callbacks: callbacks)
}
}