mirror of
https://github.com/divkit/divkit.git
synced 2026-06-06 20:07:59 +00:00
509 lines
15 KiB
Swift
509 lines
15 KiB
Swift
@testable import LayoutKit
|
|
import VGSL
|
|
import XCTest
|
|
|
|
final class ImageBaseBlockTests: XCTestCase {
|
|
func test_intrinsicContentWidth_whenWidthTraitIsFixed_returnsFixedValue() {
|
|
let block = makeImageBlock(
|
|
imageHolder: defaultImage,
|
|
widthTrait: .fixed(100),
|
|
height: .trait(.intrinsic)
|
|
)
|
|
|
|
let width = block.intrinsicContentWidth
|
|
|
|
XCTAssertEqual(width, 100)
|
|
}
|
|
|
|
func test_intrinsicContentWidth_whenWidthTraitIsWeighted_returnsZero() {
|
|
let block = makeImageBlock(
|
|
imageHolder: defaultImage,
|
|
widthTrait: .weighted(1),
|
|
height: .trait(.intrinsic)
|
|
)
|
|
|
|
let width = block.intrinsicContentWidth
|
|
|
|
XCTAssertEqual(width, 0)
|
|
}
|
|
|
|
func test_intrinsicContentWidth_whenWidthTraitIsIntrinsicAndHeightIsFixed_calculatesFromAspectRatio(
|
|
) {
|
|
let fixedHeight: CGFloat = 50
|
|
let block = makeImageBlock(
|
|
imageHolder: defaultImage,
|
|
widthTrait: .intrinsic,
|
|
height: .trait(.fixed(fixedHeight))
|
|
)
|
|
|
|
let width = block.intrinsicContentWidth
|
|
|
|
XCTAssertEqual(width, 100)
|
|
}
|
|
|
|
func test_intrinsicContentWidth_whenWidthTraitIsIntrinsicAndHeightIsIntrinsic_returnsImageWidth() {
|
|
let block = makeImageBlock(
|
|
imageHolder: defaultImage,
|
|
widthTrait: .intrinsic,
|
|
height: .trait(.intrinsic)
|
|
)
|
|
|
|
let width = block.intrinsicContentWidth
|
|
|
|
XCTAssertEqual(width, defaultImageSize.width)
|
|
}
|
|
|
|
func test_intrinsicContentWidth_whenWidthTraitIsIntrinsicAndHeightIsWeighted_returnsImageWidth() {
|
|
let block = makeImageBlock(
|
|
imageHolder: defaultImage,
|
|
widthTrait: .intrinsic,
|
|
height: .trait(.weighted(1))
|
|
)
|
|
|
|
let width = block.intrinsicContentWidth
|
|
|
|
XCTAssertEqual(width, defaultImageSize.width)
|
|
}
|
|
|
|
func test_intrinsicContentWidth_whenWidthTraitIsIntrinsicAndHeightIsRatio_returnsImageWidth() {
|
|
let block = makeImageBlock(
|
|
imageHolder: defaultImage,
|
|
widthTrait: .intrinsic,
|
|
height: .ratio(2)
|
|
)
|
|
|
|
let width = block.intrinsicContentWidth
|
|
|
|
XCTAssertEqual(width, defaultImageSize.width)
|
|
}
|
|
|
|
func test_intrinsicContentWidth_whenWidthTraitIsIntrinsicWithConstraints_respectsMinWidth() {
|
|
let smallImage = makeColoredImage(size: CGSize(width: 50, height: 100))
|
|
let block = makeImageBlock(
|
|
imageHolder: smallImage,
|
|
widthTrait: .intrinsic(constrained: false, minSize: 100, maxSize: 300),
|
|
height: .trait(.intrinsic)
|
|
)
|
|
|
|
let width = block.intrinsicContentWidth
|
|
|
|
XCTAssertEqual(width, 100)
|
|
}
|
|
|
|
func test_intrinsicContentWidth_whenWidthTraitIsIntrinsicWithConstraints_respectsMaxWidth() {
|
|
let largeImage = makeColoredImage(size: CGSize(width: 400, height: 100))
|
|
let block = makeImageBlock(
|
|
imageHolder: largeImage,
|
|
widthTrait: .intrinsic(constrained: false, minSize: 100, maxSize: 300),
|
|
height: .trait(.intrinsic)
|
|
)
|
|
|
|
let width = block.intrinsicContentWidth
|
|
|
|
XCTAssertEqual(width, 300)
|
|
}
|
|
|
|
func test_intrinsicContentWidth_whenHeightIsFixedAndImageHasNoAspectRatio_returnsZero() {
|
|
let emptyImage = UIImage()
|
|
let block = makeImageBlock(
|
|
imageHolder: emptyImage,
|
|
widthTrait: .intrinsic,
|
|
height: .trait(.fixed(100))
|
|
)
|
|
|
|
let width = block.intrinsicContentWidth
|
|
|
|
XCTAssertEqual(width, 0)
|
|
}
|
|
|
|
func test_intrinsicContentHeight_whenHeightTraitIsFixed_returnsFixedValue() {
|
|
let block = makeImageBlock(
|
|
imageHolder: defaultImage,
|
|
widthTrait: .intrinsic,
|
|
height: .trait(.fixed(100))
|
|
)
|
|
|
|
let height = block.intrinsicContentHeight(forWidth: 200)
|
|
|
|
XCTAssertEqual(height, 100)
|
|
}
|
|
|
|
func test_intrinsicContentHeight_whenHeightTraitIsWeighted_returnsZero() {
|
|
let block = makeImageBlock(
|
|
imageHolder: defaultImage,
|
|
widthTrait: .intrinsic,
|
|
height: .trait(.weighted(1))
|
|
)
|
|
|
|
let height = block.intrinsicContentHeight(forWidth: 200)
|
|
|
|
XCTAssertEqual(height, 0)
|
|
}
|
|
|
|
func test_intrinsicContentHeight_whenHeightIsRatio_calculatesFromWidth() {
|
|
let ratio: Double = 2
|
|
let width: CGFloat = 200
|
|
let block = makeImageBlock(
|
|
imageHolder: defaultImage,
|
|
widthTrait: .intrinsic,
|
|
height: .ratio(ratio)
|
|
)
|
|
|
|
let height = block.intrinsicContentHeight(forWidth: width)
|
|
|
|
XCTAssertEqual(height, 100)
|
|
}
|
|
|
|
func test_intrinsicContentHeight_whenHeightIsIntrinsicAndWidthIsFixed_calculatesFromAspectRatio() {
|
|
let fixedWidth: CGFloat = 100
|
|
let block = makeImageBlock(
|
|
imageHolder: defaultImage,
|
|
widthTrait: .fixed(fixedWidth),
|
|
height: .trait(.intrinsic)
|
|
)
|
|
|
|
let height = block.intrinsicContentHeight(forWidth: fixedWidth)
|
|
|
|
XCTAssertEqual(height, 50)
|
|
}
|
|
|
|
func test_intrinsicContentHeight_whenHeightIsIntrinsicAndWidthIsFixedButNoAspectRatio_returnsZero(
|
|
) {
|
|
let emptyImage = UIImage()
|
|
let block = makeImageBlock(
|
|
imageHolder: emptyImage,
|
|
widthTrait: .fixed(100),
|
|
height: .trait(.intrinsic)
|
|
)
|
|
|
|
let height = block.intrinsicContentHeight(forWidth: 100)
|
|
|
|
XCTAssertEqual(height, 0)
|
|
}
|
|
|
|
func test_intrinsicContentHeight_whenHeightIsIntrinsicAndWidthIsIntrinsic_returnsImageHeight() {
|
|
let block = makeImageBlock(
|
|
imageHolder: defaultImage,
|
|
widthTrait: .intrinsic,
|
|
height: .trait(.intrinsic)
|
|
)
|
|
|
|
let height = block.intrinsicContentHeight(forWidth: 0)
|
|
|
|
XCTAssertEqual(height, defaultImageSize.height)
|
|
}
|
|
|
|
func test_intrinsicContentHeight_whenHeightIsIntrinsicAndWidthIsWeighted_returnsImageHeight() {
|
|
let block = makeImageBlock(
|
|
imageHolder: defaultImage,
|
|
widthTrait: .weighted(1),
|
|
height: .trait(.intrinsic)
|
|
)
|
|
|
|
let height = block.intrinsicContentHeight(forWidth: 0)
|
|
|
|
XCTAssertEqual(height, defaultImageSize.height)
|
|
}
|
|
|
|
func test_intrinsicContentHeight_whenHeightIsIntrinsicWithConstraints_respectsMinHeight() {
|
|
let shortImage = makeColoredImage(size: CGSize(width: 200, height: 10))
|
|
let block = makeImageBlock(
|
|
imageHolder: shortImage,
|
|
widthTrait: .intrinsic,
|
|
height: .trait(.intrinsic(constrained: false, minSize: 80, maxSize: 200))
|
|
)
|
|
|
|
let height = block.intrinsicContentHeight(forWidth: 0)
|
|
|
|
XCTAssertEqual(height, 80)
|
|
}
|
|
|
|
func test_intrinsicContentHeight_whenHeightIsIntrinsicWithConstraints_respectsMaxHeight() {
|
|
let tallImage = makeColoredImage(size: CGSize(width: 200, height: 250))
|
|
let block = makeImageBlock(
|
|
imageHolder: tallImage,
|
|
widthTrait: .intrinsic,
|
|
height: .trait(.intrinsic(constrained: false, minSize: 80, maxSize: 200))
|
|
)
|
|
|
|
let height = block.intrinsicContentHeight(forWidth: 0)
|
|
|
|
XCTAssertEqual(height, 200)
|
|
}
|
|
|
|
func test_imageInContainer_withConstrainedWrapContent_respectsContainerBounds() throws {
|
|
let image = makeColoredImage(size: CGSize(width: 400, height: 300))
|
|
let imageBlock = ImageBlock(
|
|
imageHolder: image,
|
|
widthTrait: .intrinsic(constrained: true, minSize: 100, maxSize: 200),
|
|
height: .trait(.intrinsic(constrained: true, minSize: 50, maxSize: 150)),
|
|
contentMode: .default,
|
|
tintColor: nil,
|
|
tintMode: .sourceIn
|
|
)
|
|
|
|
let container = try makeContainer(
|
|
layoutDirection: .vertical,
|
|
widthTrait: .fixed(300),
|
|
heightTrait: .fixed(200),
|
|
children: [imageBlock]
|
|
)
|
|
|
|
let containerWidth = container.widthOfHorizontallyNonResizableBlock
|
|
let containerHeight = container.heightOfVerticallyNonResizableBlock(forWidth: 300)
|
|
|
|
XCTAssertEqual(containerWidth, 300)
|
|
XCTAssertEqual(containerHeight, 200)
|
|
}
|
|
|
|
func test_multipleConstrainedImages_inHorizontalContainer_shareSpaceCorrectly() throws {
|
|
let image1 = makeColoredImage(size: CGSize(width: 300, height: 200))
|
|
let image2 = makeColoredImage(size: CGSize(width: 200, height: 300))
|
|
|
|
let imageBlock1 = ImageBlock(
|
|
imageHolder: image1,
|
|
widthTrait: .intrinsic(constrained: true, minSize: 50, maxSize: 150),
|
|
height: .trait(.fixed(100)),
|
|
contentMode: .default,
|
|
tintColor: nil,
|
|
tintMode: .sourceIn
|
|
)
|
|
|
|
let imageBlock2 = ImageBlock(
|
|
imageHolder: image2,
|
|
widthTrait: .intrinsic(constrained: true, minSize: 50, maxSize: 150),
|
|
height: .trait(.fixed(100)),
|
|
contentMode: .default,
|
|
tintColor: nil,
|
|
tintMode: .sourceIn
|
|
)
|
|
|
|
let container = try makeContainer(
|
|
layoutDirection: .horizontal,
|
|
widthTrait: .fixed(300),
|
|
heightTrait: .fixed(100),
|
|
children: [imageBlock1, imageBlock2],
|
|
gap: 10
|
|
)
|
|
|
|
let containerWidth = container.widthOfHorizontallyNonResizableBlock
|
|
let containerHeight = container.heightOfVerticallyNonResizableBlock(forWidth: 300)
|
|
|
|
XCTAssertEqual(containerWidth, 300)
|
|
XCTAssertEqual(containerHeight, 100)
|
|
}
|
|
|
|
func test_constrainedImage_withAspectRatio_maintainsRatioWithinBounds() throws {
|
|
let wideImage = makeColoredImage(size: CGSize(width: 800, height: 200))
|
|
|
|
let imageBlock = ImageBlock(
|
|
imageHolder: wideImage,
|
|
widthTrait: .intrinsic(constrained: true, minSize: 100, maxSize: 400),
|
|
height: .trait(.intrinsic(constrained: true, minSize: 50, maxSize: 150)),
|
|
contentMode: .default,
|
|
tintColor: nil,
|
|
tintMode: .sourceIn
|
|
)
|
|
|
|
let container = try makeContainer(
|
|
layoutDirection: .vertical,
|
|
widthTrait: .fixed(500),
|
|
heightTrait: .fixed(200),
|
|
children: [imageBlock]
|
|
)
|
|
|
|
let containerWidth = container.widthOfHorizontallyNonResizableBlock
|
|
let containerHeight = container.heightOfVerticallyNonResizableBlock(forWidth: 500)
|
|
|
|
XCTAssertEqual(containerWidth, 500)
|
|
XCTAssertEqual(containerHeight, 200)
|
|
}
|
|
|
|
func test_constrainedImage_inConstrainedContainer_properlyLimitsSize() throws {
|
|
let squareImage = makeColoredImage(size: CGSize(width: 500, height: 500))
|
|
|
|
let imageBlock = ImageBlock(
|
|
imageHolder: squareImage,
|
|
widthTrait: .intrinsic(constrained: true, minSize: 0, maxSize: .infinity),
|
|
height: .trait(.intrinsic(constrained: true, minSize: 0, maxSize: .infinity)),
|
|
contentMode: .default,
|
|
tintColor: nil,
|
|
tintMode: .sourceIn
|
|
)
|
|
|
|
let container = try makeContainer(
|
|
layoutDirection: .vertical,
|
|
widthTrait: .fixed(200),
|
|
heightTrait: .fixed(200),
|
|
children: [imageBlock]
|
|
)
|
|
|
|
let containerWidth = container.widthOfHorizontallyNonResizableBlock
|
|
let containerHeight = container.heightOfVerticallyNonResizableBlock(forWidth: 200)
|
|
|
|
XCTAssertEqual(containerWidth, 200)
|
|
XCTAssertEqual(containerHeight, 200)
|
|
}
|
|
|
|
func test_constrainedImages_inVerticalGallery_maintainConsistentSizes() throws {
|
|
let images = [
|
|
CGSize(width: 300, height: 400),
|
|
CGSize(width: 400, height: 300),
|
|
CGSize(width: 200, height: 200),
|
|
]
|
|
|
|
let imageBlocks = images.map { size in
|
|
ImageBlock(
|
|
imageHolder: makeColoredImage(size: size),
|
|
widthTrait: .intrinsic(constrained: true, minSize: 100, maxSize: 250),
|
|
height: .trait(.intrinsic(constrained: true, minSize: 100, maxSize: 250)),
|
|
contentMode: .default,
|
|
tintColor: nil,
|
|
tintMode: .sourceIn
|
|
)
|
|
}
|
|
|
|
let container = try makeContainer(
|
|
layoutDirection: .vertical,
|
|
widthTrait: .fixed(300),
|
|
heightTrait: .intrinsic,
|
|
children: imageBlocks,
|
|
gap: 10
|
|
)
|
|
|
|
let containerWidth = container.widthOfHorizontallyNonResizableBlock
|
|
let containerHeight = container.heightOfVerticallyNonResizableBlock(forWidth: 300)
|
|
|
|
XCTAssertEqual(containerWidth, 300)
|
|
XCTAssertEqual(containerHeight, 657.5)
|
|
}
|
|
|
|
func test_constrainedImage_stateUpdate_maintainsConstraints() throws {
|
|
let initialImage = makeColoredImage(size: CGSize(width: 200, height: 100))
|
|
let path = UIElementPath("test_image")
|
|
|
|
let block = ImageBlock(
|
|
imageHolder: initialImage,
|
|
widthTrait: .intrinsic(constrained: true, minSize: 100, maxSize: 300),
|
|
height: .trait(.intrinsic(constrained: true, minSize: 50, maxSize: 200)),
|
|
contentMode: .default,
|
|
tintColor: nil,
|
|
tintMode: .sourceIn,
|
|
path: path
|
|
)
|
|
|
|
let newImageSize = CGSize(width: 400, height: 200)
|
|
let newState = ImageBaseBlockState(
|
|
widthTrait: .intrinsic(constrained: true, minSize: 100, maxSize: 300),
|
|
height: .trait(.intrinsic(constrained: true, minSize: 50, maxSize: 200)),
|
|
imageHolder: makeColoredImage(size: newImageSize)
|
|
)
|
|
|
|
let states: BlocksState = [path: newState]
|
|
let updatedBlock = try block.updated(withStates: states)
|
|
|
|
let width = updatedBlock.intrinsicContentWidth
|
|
let height = updatedBlock.intrinsicContentHeight(forWidth: 0)
|
|
|
|
XCTAssertEqual(width, 300)
|
|
XCTAssertEqual(height, 150)
|
|
}
|
|
|
|
func test_veryWideImage_withMinHeightConstraint_usesMinHeightNotMax() throws {
|
|
let veryWideImage = makeColoredImage(size: CGSize(width: 7500, height: 2400))
|
|
|
|
let imageBlock = ImageBlock(
|
|
imageHolder: veryWideImage,
|
|
widthTrait: .intrinsic(constrained: true, minSize: 100, maxSize: 300),
|
|
height: .trait(.intrinsic(constrained: true, minSize: 100, maxSize: 268)),
|
|
contentMode: .default,
|
|
tintColor: nil,
|
|
tintMode: .sourceIn
|
|
)
|
|
|
|
let width = imageBlock.intrinsicContentWidth
|
|
let height = imageBlock.intrinsicContentHeight(forWidth: width)
|
|
|
|
XCTAssertEqual(width, 300, accuracy: 0.1)
|
|
XCTAssertEqual(
|
|
height,
|
|
100,
|
|
accuracy: 0.1,
|
|
"Height should use min constraint (100), not max (268)"
|
|
)
|
|
}
|
|
|
|
func test_veryWideImage_inContainer_respectsMinHeight() throws {
|
|
let veryWideImage = makeColoredImage(size: CGSize(width: 2500, height: 800))
|
|
|
|
let imageBlock = ImageBlock(
|
|
imageHolder: veryWideImage,
|
|
widthTrait: .intrinsic(constrained: true, minSize: 100, maxSize: 300),
|
|
height: .trait(.intrinsic(constrained: true, minSize: 100, maxSize: 268)),
|
|
contentMode: .default,
|
|
tintColor: nil,
|
|
tintMode: .sourceIn
|
|
)
|
|
|
|
let container = try makeContainer(
|
|
layoutDirection: .vertical,
|
|
widthTrait: .fixed(402),
|
|
heightTrait: .fixed(628),
|
|
children: [imageBlock]
|
|
)
|
|
|
|
let containerWidth = container.widthOfHorizontallyNonResizableBlock
|
|
let containerHeight = container.heightOfVerticallyNonResizableBlock(forWidth: 402)
|
|
|
|
XCTAssertEqual(containerWidth, 402)
|
|
XCTAssertEqual(containerHeight, 628)
|
|
}
|
|
}
|
|
|
|
private let defaultImageSize = CGSize(width: 200, height: 100)
|
|
private let defaultImage = makeColoredImage(size: defaultImageSize)
|
|
|
|
private func makeImageBlock(
|
|
imageHolder: ImageHolder,
|
|
widthTrait: LayoutTrait,
|
|
height: ImageBlockHeight
|
|
) -> ImageBlock {
|
|
ImageBlock(
|
|
imageHolder: imageHolder,
|
|
widthTrait: widthTrait,
|
|
height: height,
|
|
contentMode: .default,
|
|
tintColor: nil,
|
|
tintMode: .sourceIn
|
|
)
|
|
}
|
|
|
|
private func makeContainer(
|
|
layoutDirection: ContainerBlock.LayoutDirection,
|
|
widthTrait: LayoutTrait,
|
|
heightTrait: LayoutTrait,
|
|
children: [Block],
|
|
gap: CGFloat = 0
|
|
) throws -> ContainerBlock {
|
|
var gaps = [CGFloat(0)]
|
|
if children.count > 1 {
|
|
gaps += Array(repeating: gap, count: children.count - 1)
|
|
}
|
|
gaps.append(0)
|
|
return try ContainerBlock(
|
|
layoutDirection: layoutDirection,
|
|
layoutMode: .wrap,
|
|
widthTrait: widthTrait,
|
|
heightTrait: heightTrait,
|
|
gaps: gaps,
|
|
children: children
|
|
)
|
|
}
|
|
|
|
private func makeColoredImage(size: CGSize) -> Image {
|
|
Image.imageOfSize(size, opaque: true, scale: 1) { ctx in
|
|
ctx.setFillColor(UIColor.black.cgColor)
|
|
ctx.fill(CGRect(origin: .zero, size: size))
|
|
}!
|
|
}
|