mirror of
https://github.com/divkit/divkit.git
synced 2026-06-06 20:07:59 +00:00
593 lines
15 KiB
Swift
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)
|
|
}
|
|
}
|