@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 ) }