Files
divkit/client/ios/DivKitTests/Extensions/DivStateExtensionsTests.swift
denlvovich afb34a0bf9 Fixed call to 'passVisibleBoundsChanged' on iOS26
commit_hash:0c2df2ccca3cf75f92c594ef124a0dcdbfcc9899
2025-11-19 16:03:04 +03:00

300 lines
7.6 KiB
Swift

@testable import DivKit
import DivKitTestsSupport
@testable import LayoutKit
import XCTest
final class DivStateExtensionsTests: XCTestCase {
private let timerScheduler = TestTimerScheduler()
@MainActor
func test_WhenVisibilityWasTriggeredAndStateChanges_ReportsVisibilityAgain() async throws {
let context = DivBlockModelingContext(scheduler: timerScheduler)
let block = try makeBlock(fromFile: "states_visibility", context: context)
let rect = CGRect(
origin: .zero,
size: CGSize(width: 100, height: block.intrinsicContentHeight(forWidth: 100))
)
let view = block.makeBlockView()
var result = try await getViewVisibilityCallCount(
view: view,
rect: rect,
timerScheduler: timerScheduler
)
XCTAssertEqual(result, 1)
context.stateManager.setState(stateBlockPath: "mystate", stateID: "second")
let block2 = try makeBlock(fromFile: "states_visibility", context: context)
let view2 = block2.reuse(
view,
observer: nil,
overscrollDelegate: nil,
renderingDelegate: nil,
superview: nil
)
result = try await getViewVisibilityCallCount(
view: view2,
rect: rect,
timerScheduler: timerScheduler
)
XCTAssertEqual(result, 1)
}
@MainActor
func test_WhenVisibilityWasTriggeredAndStateDoesNotChange_DoesNotReportVisibilityAgain(
) async throws {
let context = DivBlockModelingContext(scheduler: timerScheduler)
let block = try makeBlock(fromFile: "states_visibility", context: context)
let rect = CGRect(
origin: .zero,
size: CGSize(width: 100, height: block.intrinsicContentHeight(forWidth: 100))
)
let view = block.makeBlockView()
var result = try await getViewVisibilityCallCount(
view: view,
rect: rect,
timerScheduler: timerScheduler
)
XCTAssertEqual(result, 1)
let block2 = try makeBlock(fromFile: "states_visibility", context: context)
let view2 = block2.reuse(
view,
observer: nil,
overscrollDelegate: nil,
renderingDelegate: nil,
superview: nil
)
result = try await getViewVisibilityCallCount(
view: view2,
rect: rect,
timerScheduler: timerScheduler
)
XCTAssertEqual(result, 0)
}
func test_Path_withDivId() throws {
let block = makeBlock(
divState(
divId: "test_div_id",
id: "test_id",
states: defaultStates()
)
)
let expectedPath = UIElementPath.root + 0 + "test_id" + "state_item_1" + "text"
try assertEqual(
block.firstStateElementPath(),
expectedPath
)
}
func test_Path_withId() throws {
let block = makeBlock(
divState(
divId: nil,
id: "test_id",
states: defaultStates()
)
)
try assertEqual(
block.firstStateElementPath(),
.root + 0 + "test_id" + "state_item_1" + "text"
)
}
func test_Path_WithCustomContext() throws {
let context = DivBlockModelingContext(
cardId: "test_card_id"
).modifying(
overridenId: "arbitrary_id"
)
let block = makeBlock(
divState(
divId: "test_div_id",
id: "test_id",
defaultStateId: .value("state_item_2"),
states: defaultStates()
),
context: context
)
try assertEqual(
block.firstStateElementPath(),
.root + 0 + "test_id" + "state_item_2" + "text"
)
}
func test_Path_InsidePrototype() throws {
let overridenPrototypeId = "test_prototype_id"
let block = makeBlock(
divContainer(
itemBuilder: DivCollectionItemBuilder(
data: .value([
["text": "Item 1"],
["text": "Item 2"],
]),
prototypes: [
DivCollectionItemBuilder.Prototype(
div: divState(
divId: "arbitrary_id",
id: "arbitrary_id",
states: defaultStates(textExpression: "@{getStringFromDict(it, 'text')}")
),
id: .value(overridenPrototypeId)
),
]
)
)
)
let containerBlock: ContainerBlock = try block.child.unwrap()
func getPathForText(index: Int) throws -> UIElementPath? {
let wrapperBlock: StateBlock = try containerBlock.children[index].content.unwrap()
let layeredBlock = try XCTUnwrap(wrapperBlock.child as? LayeredBlock)
let textBlock: TextBlock = try layeredBlock.children[0].content.unwrap()
return textBlock.path
}
try assertEqual(
getPathForText(index: 0),
.root + 0 + "container" + 0 + "test_prototype_id" + "state_item_1" + "text"
)
try assertEqual(
getPathForText(index: 1),
.root + 0 + "container" + 1 + "test_prototype_id" + "state_item_1" + "text"
)
}
func test_accessibilityID_overridenId() throws {
let context = DivBlockModelingContext().modifying(
overridenId: "overriden_id"
)
let block = try divState(
divId: "div_id",
id: "id",
states: defaultStates()
).value.makeBlock(context: context)
XCTAssertEqual("overriden_id", block.accessibilityElement?.strings.identifier)
}
func test_accessibilityID_divId() throws {
let context = DivBlockModelingContext().modifying(
overridenId: nil
)
let block = try divState(
divId: "div_id",
id: "id",
states: defaultStates()
).value.makeBlock(context: context)
XCTAssertEqual("id", block.accessibilityElement?.strings.identifier)
}
func test_accessibiltyID_id() throws {
let context = DivBlockModelingContext().modifying(
overridenId: nil
)
let block = try divState(
divId: nil,
id: "id",
states: defaultStates()
).value.makeBlock(context: context)
XCTAssertEqual("id", block.accessibilityElement?.strings.identifier)
}
func test_stateManagerItems_divId() throws {
let context = DivBlockModelingContext()
_ = try divState(
divId: "div_id",
id: "id",
states: defaultStates()
).value.makeBlock(context: context)
let expectedItems = [
DivStatePath(rawValue: .init("div_id")): DivStateManager.Item(
currentStateID: .init(rawValue: "state_item_1"),
previousState: .empty
),
]
XCTAssertEqual(expectedItems, context.stateManager.items)
}
func test_stateManagerItems_id() throws {
let context = DivBlockModelingContext()
_ = try divState(
divId: nil,
id: "id",
states: defaultStates()
).value.makeBlock(context: context)
let expectedItems = [
DivStatePath(rawValue: .init("id")): DivStateManager.Item(
currentStateID: .init(rawValue: "state_item_1"),
previousState: .empty
),
]
XCTAssertEqual(expectedItems, context.stateManager.items)
}
}
private func defaultStates(
textExpression: String? = nil
) -> [DivState.State] {
[
divStateState(
div: divText(
textExpression: textExpression,
height: .divFixedSize(.init(value: .value(100)))
),
stateId: "state_item_1"
),
divStateState(
div: divText(
textExpression: textExpression
),
stateId: "state_item_2"
),
]
}
extension StateBlock {
fileprivate func firstStateElementPath() throws -> UIElementPath? {
let stateBlock: StateBlock = try child.unwrap()
let layeredBlock = try XCTUnwrap(stateBlock.child as? LayeredBlock)
let textBlock: TextBlock = try layeredBlock.children[0].content.unwrap()
return textBlock.path
}
}
private func makeBlock(
fromFile filename: String,
context: DivBlockModelingContext = .default
) throws -> Block {
try DivStateTemplate.make(
fromFile: filename,
subdirectory: "div-state",
context: context
)
}