Files
divkit/client/ios/DivKitTests/Extensions/DivContainerExtensionsTests.swift
booster 6ca6f7068c Refined match_parent inside wrap_content layout
commit_hash:38abb7f9e9f06b0407ea79074ba775ec5b9db34a
2026-06-01 00:36:15 +03:00

737 lines
20 KiB
Swift

@testable import DivKit
import DivKitTestsSupport
@testable import LayoutKit
import XCTest
final class DivContainerExtensionsTests: XCTestCase {
func test_NilItems() throws {
let block = makeBlock(
divContainer(items: nil)
)
let expectedBlock = try StateBlock(
child: DecoratingBlock(
child: ContainerBlock(
layoutDirection: .vertical,
children: [],
path: defaultContainerPath
),
accessibilityElement: .default
),
ids: []
)
assertEqual(block, expectedBlock)
}
func test_EmptyItems() throws {
let block = makeBlock(
divContainer(items: [])
)
let expectedBlock = try StateBlock(
child: DecoratingBlock(
child: ContainerBlock(
layoutDirection: .vertical,
children: [],
path: defaultContainerPath
),
accessibilityElement: .default
),
ids: []
)
assertEqual(block, expectedBlock)
}
func test_EmptyItems_WrapContent() throws {
let block = makeBlock(
divContainer(
items: [],
width: wrapContentSize()
)
)
let expectedBlock = try StateBlock(
child: DecoratingBlock(
child: ContainerBlock(
layoutDirection: .vertical,
widthTrait: .intrinsic,
children: [],
path: defaultContainerPath
),
accessibilityElement: .default
),
ids: []
)
assertEqual(block, expectedBlock)
}
func test_WithItems() throws {
let block = makeBlock(
divContainer(
items: [
divSeparator(),
divText(text: "Hello!"),
]
)
)
let expectedBlock = try StateBlock(
child: DecoratingBlock(
child: ContainerBlock(
layoutDirection: .vertical,
children: [
DecoratingBlock(
child: SeparatorBlock(
color: color("#14000000")
),
accessibilityElement: .default
),
DecoratingBlock(
child: textBlock(
text: "Hello!",
path: defaultContainerPath + 1 + "text"
),
accessibilityElement: accessibility(
traits: .staticText,
label: "Hello!"
)
),
],
path: defaultContainerPath
),
accessibilityElement: .default
),
ids: []
)
assertEqual(block, expectedBlock)
}
func test_NoClipping() throws {
let block = makeBlock(
divContainer(
clipToBounds: false,
items: [
divSeparator(),
]
)
)
let expectedBlock = try StateBlock(
child: DecoratingBlock(
child: ContainerBlock(
layoutDirection: .vertical,
children: [
DecoratingBlock(
child: SeparatorBlock(
color: color("#14000000")
),
accessibilityElement: .default
),
],
clipContent: false,
path: defaultContainerPath
),
boundary: .noClip,
accessibilityElement: .default
),
ids: []
)
assertEqual(block, expectedBlock)
}
func test_WithItemBuilder() throws {
let block = makeBlock(
divContainer(
itemBuilder: DivCollectionItemBuilder(
data: .value([
["text": "Item 1"],
["text": "Item 2"],
]),
prototypes: [
DivCollectionItemBuilder.Prototype(
div: divText(textExpression: "@{getStringFromDict(it, 'text')}")
),
]
)
)
)
let expectedBlock = try StateBlock(
child: DecoratingBlock(
child: ContainerBlock(
layoutDirection: .vertical,
children: [
DecoratingBlock(
child: textBlock(
text: "Item 1",
path: defaultContainerPath + 0 + "text"
),
accessibilityElement: accessibility(
traits: .staticText,
label: "Item 1"
)
),
DecoratingBlock(
child: textBlock(
text: "Item 2",
path: defaultContainerPath + 1 + "text"
),
accessibilityElement: accessibility(
traits: .staticText,
label: "Item 2"
)
),
],
path: defaultContainerPath
),
accessibilityElement: .default
),
ids: []
)
assertEqual(block, expectedBlock)
}
func test_WithItemBuilderAndPrototypeId() throws {
let block = makeBlock(
divContainer(
itemBuilder: DivCollectionItemBuilder(
data: .value([
[],
[],
]),
prototypes: [
DivCollectionItemBuilder.Prototype(
div: divText(
id: "must_be_overloaded",
textExpression: "Index = @{index}"
),
id: expression("item_@{index}")
),
]
)
)
)
let expectedBlock = try StateBlock(
child: DecoratingBlock(
child: ContainerBlock(
layoutDirection: .vertical,
children: [
DecoratingBlock(
child: textBlock(
text: "Index = 0",
path: defaultContainerPath + 0 + "item_0"
),
accessibilityElement: accessibility(
traits: .staticText,
label: "Index = 0",
identifier: "item_0"
)
),
DecoratingBlock(
child: textBlock(
text: "Index = 1",
path: defaultContainerPath + 1 + "item_1"
),
accessibilityElement: accessibility(
traits: .staticText,
label: "Index = 1",
identifier: "item_1"
)
),
],
path: defaultContainerPath
),
accessibilityElement: .default
),
ids: []
)
assertEqual(block, expectedBlock)
}
func test_ItemBuilder_PathsWithPrototypeId() throws {
let context = DivBlockModelingContext(
cardId: "custom_card_id"
)
let block = makeBlock(
divContainer(
itemBuilder: DivCollectionItemBuilder(
data: .value([
[],
[],
]),
prototypes: [
DivCollectionItemBuilder.Prototype(
div: divText(
textExpression: "Index = @{index}"
),
id: expression("item_@{index}")
),
]
)
),
context: context,
stateId: 777
)
let customContainerPath = UIElementPath("custom_card_id") + 777 + "container"
let expectedBlock = try StateBlock(
child: DecoratingBlock(
child: ContainerBlock(
layoutDirection: .vertical,
children: [
DecoratingBlock(
child: textBlock(
text: "Index = 0",
path: customContainerPath + 0 + "item_0"
),
accessibilityElement: accessibility(
traits: .staticText,
label: "Index = 0",
identifier: "item_0"
)
),
DecoratingBlock(
child: textBlock(
text: "Index = 1",
path: customContainerPath + 1 + "item_1"
),
accessibilityElement: accessibility(
traits: .staticText,
label: "Index = 1",
identifier: "item_1"
)
),
],
path: UIElementPath("custom_card_id") + "777" + "container"
),
accessibilityElement: .default
),
ids: []
)
assertEqual(block, expectedBlock)
}
func test_ItemBuilder_HasHigherPriorityThanItems() throws {
let block = makeBlock(
divContainer(
itemBuilder: DivCollectionItemBuilder(
data: .value([[]]),
prototypes: [
DivCollectionItemBuilder.Prototype(
div: divText(text: "itemBuilder")
),
]
),
items: [
divText(text: "items"),
]
)
)
let expectedBlock = try StateBlock(
child: DecoratingBlock(
child: ContainerBlock(
layoutDirection: .vertical,
children: [
DecoratingBlock(
child: textBlock(
text: "itemBuilder",
path: defaultContainerPath + 0 + "text"
),
accessibilityElement: accessibility(
traits: .staticText,
label: "itemBuilder"
)
),
],
path: defaultContainerPath
),
accessibilityElement: .default
),
ids: []
)
assertEqual(block, expectedBlock)
}
func test_WithMergeAccessibility() throws {
let block = makeBlock(
divContainer(
accessibility: DivAccessibility(
mode: .value(.merge),
type: .button
),
items: [
divText(text: "Hello!"),
divText(
accessibility: DivAccessibility(mode: .value(.exclude)),
text: "Excluded item"
),
divContainer(
items: [
divText(text: "Nested item"),
]
),
]
)
)
let nestedTextPath = UIElementPath.root + 0 + "container" + 2 + "container" + 0 + "text"
let expectedBlock = try StateBlock(
child: DecoratingBlock(
child: ContainerBlock(
layoutDirection: .vertical,
children: [
DecoratingBlock(
child: textBlock(
text: "Hello!",
path: defaultContainerPath + 0 + "text"
),
accessibilityElement: accessibility(
traits: .staticText,
label: "Hello!"
)
),
DecoratingBlock(
child: textBlock(
text: "Excluded item",
path: defaultContainerPath + 1 + "text"
),
accessibilityElement: accessibility(hideElementWithChildren: true)
),
DecoratingBlock(
child: ContainerBlock(
layoutDirection: .vertical,
children: [
DecoratingBlock(
child: textBlock(
text: "Nested item",
path: nestedTextPath
),
accessibilityElement: accessibility(
traits: .staticText,
label: "Nested item"
)
),
],
path: defaultContainerPath + "2" + "container"
),
accessibilityElement: .default
),
],
path: defaultContainerPath
),
accessibilityElement: accessibility(
traits: .button,
label: "Hello! Nested item"
)
),
ids: []
)
assertEqual(block, expectedBlock)
}
func test_WrapContent_MatchParentItem() throws {
let context = DivBlockModelingContext()
let block = makeBlock(
divContainer(
items: [
divText(
text: "Hello!",
width: matchParentSize()
),
],
width: wrapContentSize()
),
context: context,
ignoreErrors: true
)
let expectedBlock = try StateBlock(
child: DecoratingBlock(
child: ContainerBlock(
layoutDirection: .vertical,
widthTrait: .intrinsic,
children: [
ContainerBlock.Child(
content: DecoratingBlock(
child: textBlock(
widthTrait: .intrinsic(
constrained: true,
minSize: 0,
maxSize: .infinity
),
text: "Hello!",
path: defaultContainerPath + 0 + "text"
),
accessibilityElement: accessibility(
traits: .staticText,
label: "Hello!"
)
),
fillsCrossAxis: true
),
],
path: defaultContainerPath
),
accessibilityElement: .default
),
ids: []
)
assertEqual(block, expectedBlock)
assertEqual(context.errorsStorage.errors.count, 1)
assertEqual(
context.errorsStorage.errors.first?.message,
"All items in DivContainer with wrap_content width has match_parent width"
)
}
func test_AddsIndexedParentPathToItems() throws {
let block = try makeBlock(
fromFile: "item_with_action"
) as! WrapperBlock
let container = block.child as! ContainerBlock
let wrappedContainer = container.children[0].content as! WrapperBlock
let gallery = wrappedContainer.child as! GalleryBlock
XCTAssertEqual(
gallery.model.path,
UIElementPath.root + "container" + 0 + "gallery"
)
}
func test_PathForMultipleItems_withId() throws {
let textWithIdDiv = divText(
id: "text_id"
)
let textWithoutIdDiv = divText(
id: nil
)
let block = makeBlock(
divContainer(
id: "container_id",
items: [
textWithIdDiv,
textWithoutIdDiv,
divGallery(
items: [textWithIdDiv, textWithoutIdDiv],
id: "gallery_id"
),
]
),
stateId: 777
)
let container: ContainerBlock = try block.child.unwrap()
let items = container.children.map(\.content)
let textWithIdBlock: TextBlock = try items[0].unwrap()
let textWithoutIdBlock: TextBlock = try items[1].unwrap()
let gallery: GalleryBlock = try items[2].unwrap()
let containerPath = UIElementPath.root + 777 + "container_id"
XCTAssertEqual(
textWithIdBlock.path,
containerPath + 0 + "text_id"
)
XCTAssertEqual(
textWithoutIdBlock.path,
containerPath + 1 + "text"
)
XCTAssertEqual(
gallery.model.path,
containerPath + 2 + "gallery_id"
)
}
func test_PathForMultipleItems_noId() throws {
let textWithIdDiv = divText(
id: "text_id"
)
let textWithoutIdDiv = divText(
id: nil
)
let block = makeBlock(
divContainer(
id: nil,
items: [
textWithIdDiv,
textWithoutIdDiv,
divGallery(
items: [textWithIdDiv, textWithoutIdDiv]
),
]
),
stateId: 777
)
let container: ContainerBlock = try block.child.unwrap()
let items = container.children.map(\.content)
let textWithIdBlock: TextBlock = try items[0].unwrap()
let textWithoutIdBlock: TextBlock = try items[1].unwrap()
let gallery: GalleryBlock = try items[2].unwrap()
let containerPath = UIElementPath.root + 777 + "container"
XCTAssertEqual(
textWithIdBlock.path,
containerPath + 0 + "text_id"
)
XCTAssertEqual(
textWithoutIdBlock.path,
containerPath + 1 + "text"
)
XCTAssertEqual(
gallery.model.path,
containerPath + 2 + "gallery"
)
}
func test_HorizontalWrapContainer_HasVerticallyResizableItem_withoutFallbackHeight(
) throws {
do {
_ = try makeBlock(
fromFile: "horizontal_wrap_container_match_parent_height_item"
) as? WrapperBlock
} catch {
XCTAssertTrue(error is DivBlockModelingError)
}
}
func test_VerticalWrapContainer_HasHorizontallyResizableItem_withoutFallbackWidth(
) throws {
do {
_ = try makeBlock(
fromFile: "vertical_wrap_container_match_parent_width_item"
) as? WrapperBlock
} catch {
XCTAssertTrue(error is DivBlockModelingError)
}
}
func test_Container_WithIntrinsicWidth_OnlyMatchParentSingleItemFillsCrossAxis(
) throws {
let matchParent = try containerBlock(fromFile: "wrap_content_width_match_parent_items")
let constrained = try containerBlock(
fromFile: "wrap_content_width_wrap_content_constrained_items"
)
XCTAssertTrue(matchParent.children.allSatisfy(\.fillsCrossAxis))
XCTAssertTrue(constrained.children.allSatisfy { !$0.fillsCrossAxis })
}
func test_HorizontalContainer_WithIntrinsicWidth_AndHasHorizontallyResizableItem_FallbackWidth(
) throws {
try assertBlocksAreEqual(
in: "horizontal_wrap_content_width_match_parent_item",
"horizontal_wrap_content_width_wrap_content_constrained_item"
)
}
func test_HorizontalContainer_WithIntrinsicHeight_OnlyMatchParentItemsFillCrossAxis(
) throws {
let matchParent =
try containerBlock(fromFile: "horizontal_wrap_content_height_match_parent_items")
let constrained = try containerBlock(
fromFile: "horizontal_wrap_content_height_wrap_content_constrained_items"
)
XCTAssertTrue(matchParent.children.allSatisfy(\.fillsCrossAxis))
XCTAssertTrue(constrained.children.allSatisfy { !$0.fillsCrossAxis })
}
func test_VerticalContainer_WithIntrinsicWidth_OnlyMatchParentItemsFillCrossAxis(
) throws {
let matchParent = try containerBlock(fromFile: "vertical_wrap_content_width_match_parent_items")
let constrained = try containerBlock(
fromFile: "vertical_wrap_content_width_wrap_content_constrained_items"
)
XCTAssertTrue(matchParent.children.allSatisfy(\.fillsCrossAxis))
XCTAssertTrue(constrained.children.allSatisfy { !$0.fillsCrossAxis })
}
func test_VerticalContainer_WithIntrinsicHeight_AndHasVerticallyResizableItem_FallbackHeight(
) throws {
try assertBlocksAreEqual(
in: "vertical_wrap_content_height_match_parent_item",
"vertical_wrap_content_height_wrap_content_constrained_item"
)
}
func test_OverlapContainer_WithIntrinsicWidth_AndAllItemsAreHorizontallyResizable_FallbackWidth(
) throws {
try assertBlocksAreEqual(
in: "overlap_wrap_content_width_match_parent_items",
"overlap_wrap_content_width_wrap_content_constrained_items"
)
}
func test_OverlapContainer_WithIntrinsicHeight_AndAllItemsAreVerticallyResizable_FallbackHeight(
) throws {
try assertBlocksAreEqual(
in: "overlap_wrap_content_height_match_parent_items",
"overlap_wrap_content_height_wrap_content_constrained_items"
)
}
func test_AxialItemsAlignmentsAreIgnoredInHorizontalContainer() throws {
try assertBlocksAreEqual(
in: "horizontal_orientation_axial_alignments",
"horizontal_orientation_no_alignments"
)
}
func test_AxialItemsAlignmentsAreIgnoredInVerticalContainer() throws {
try assertBlocksAreEqual(
in: "vertical_orientation_axial_alignments",
"vertical_orientation_no_alignments"
)
}
private func assertBlocksAreEqual(in files: String...) throws {
let first = try makeBlock(fromFile: files[0])
for file in files.dropFirst() {
XCTAssertTrue(try first == makeBlock(fromFile: file))
}
}
private func containerBlock(fromFile filename: String) throws -> ContainerBlock {
let block = try makeBlock(fromFile: filename) as! WrapperBlock
return block.child as! ContainerBlock
}
}
private func makeBlock(fromFile filename: String) throws -> Block {
try DivContainerTemplate.make(
fromFile: filename,
subdirectory: "div-container",
context: .default
)
}
extension DivBlockModelingError: Swift.Equatable {
public static func ==(lhs: DivBlockModelingError, rhs: DivBlockModelingError) -> Bool {
lhs.message == rhs.message
&& lhs.path == rhs.path
&& lhs.causes.map(\.description) == rhs.causes.map(\.description)
}
}
private let defaultContainerPath = UIElementPath.root + 0 + "container"