Files
burstein 493bb12f4b Supported bring_to_top_id in tooltips
commit_hash:20f08a2cf3951c6c497d97a438734112811528b9
2026-01-29 17:14:41 +03:00

328 lines
8.8 KiB
Swift

@testable import LayoutKit
import XCTest
final class BlockTooltipTests: XCTestCase {
private let tooltipSize = CGSize(width: 100.0, height: 100.0)
private let targetRect = CGRect(x: 50.0, y: 50.0, width: 200.0, height: 50.0)
private let boundsRect = CGRect(x: 0.0, y: 0.0, width: 300.0, height: 500.0)
private var testCaseOffsets: [CGPoint] {
[
CGPoint(x: 0.0, y: 0.0), // fits
CGPoint(x: tooltipSize.width, y: 0.0), // doesn't fit horisontally
CGPoint(x: -tooltipSize.width, y: 0.0), // doesn't fit horisontally
CGPoint(x: 0.0, y: tooltipSize.height), // doesn't fit vertically
CGPoint(x: 0.0, y: -tooltipSize.height), // doesn't fit vertically
]
}
func test_TooltipThatFitsCalculation() {
for (index, offset) in testCaseOffsets.enumerated() {
let tooltip = makeTooltip(
offset: offset,
block: EmptyBlock(
widthTrait: .fixed(tooltipSize.width),
heightTrait: .fixed(tooltipSize.height)
)
)
let resultRect = tooltip.calculateFrame(
targeting: targetRect,
constrainedBy: boundsRect
)
XCTAssertTrue(
resultRect.isInside(boundsRect),
"The tooltip with offset \(index) doesn't fit on the screen"
)
}
}
func test_TooltipWindowClose() {
let tooltipView = TooltipContainerView(
tooltip: DefaultTooltipManager.Tooltip(
params: BlockTooltipParams(
id: "tooltip",
mode: .modal,
duration: 0,
closeByTapOutside: true
),
view: TestView(),
substrateView: nil,
bringToTopId: nil
),
handleAction: { _ in },
onCloseAction: {},
getViewById: { _ in nil }
)
let mainView = UIView()
mainView.addSubview(tooltipView)
tooltipView.bounds = CGRect(
x: 0.0, y: 0.0,
width: 20.0, height: 20.0
)
tooltipView.forceLayout()
XCTAssertEqual(tooltipView.superview, mainView)
tooltipView.bounds = CGRect(
x: 0.0, y: 0.0,
width: 100.0, height: 100.0
)
tooltipView.forceLayout()
let predicate = NSPredicate { _, _ in
tooltipView.superview == nil
}
let expectSuperviewChange = expectation(for: predicate, evaluatedWith: nil)
wait(for: [expectSuperviewChange], timeout: 2)
}
func test_TooltipWithSubstrateView() {
let substrateView = TestView()
let tooltipView = TooltipContainerView(
tooltip: DefaultTooltipManager.Tooltip(
params: BlockTooltipParams(
id: "tooltip_with_substrate",
mode: .modal,
duration: 0,
closeByTapOutside: true
),
view: TestView(),
substrateView: substrateView,
bringToTopId: nil
),
handleAction: { _ in },
onCloseAction: {},
getViewById: { _ in nil }
)
let mainView = UIView()
mainView.addSubview(tooltipView)
tooltipView.bounds = CGRect(x: 0.0, y: 0.0, width: 100.0, height: 100.0)
tooltipView.forceLayout()
XCTAssertEqual(
tooltipView.subviews.first,
substrateView,
"Substrate view should be first subview (below tooltip)"
)
XCTAssertEqual(
substrateView.frame,
tooltipView.bounds,
"Substrate view should fill the entire container"
)
}
func test_GetViewById_ReturnsView_CreatesSnapshot() {
let targetView = TestView()
targetView.frame = CGRect(x: 10.0, y: 20.0, width: 50.0, height: 30.0)
var getViewByIdCallCount = 0
let substrateView = TestView()
let tooltipView = TooltipContainerView(
tooltip: DefaultTooltipManager.Tooltip(
params: BlockTooltipParams(
id: "tooltip",
mode: .modal,
duration: 0,
closeByTapOutside: true
),
view: TestView(),
substrateView: substrateView,
bringToTopId: "target_view"
),
handleAction: { _ in },
onCloseAction: {},
getViewById: { viewId in
getViewByIdCallCount += 1
if viewId.rawValue == "target_view" {
return targetView
}
return nil
}
)
let mainView = UIView()
mainView.addSubview(tooltipView)
tooltipView.bounds = CGRect(x: 0.0, y: 0.0, width: 100.0, height: 100.0)
tooltipView.forceLayout()
XCTAssertEqual(
tooltipView.subviews.count,
3,
"Should have substrate, snapshot, and tooltip views"
)
XCTAssertEqual(getViewByIdCallCount, 1)
XCTAssertTrue(tooltipView.subviews[0] === substrateView, "First subview should be substrate")
XCTAssertTrue(
tooltipView.subviews[1] !== targetView,
"Second subview should be snapshot, not original view"
)
XCTAssertFalse(
tooltipView.subviews[1].isUserInteractionEnabled,
"Snapshot should not be user-interactive"
)
}
func test_BringToTopId_WithoutSubstrateView_DoesNotCreateSnapshot() {
let targetView = TestView()
targetView.frame = CGRect(x: 10.0, y: 20.0, width: 50.0, height: 30.0)
var getViewByIdCallCount = 0
let tooltipView = TooltipContainerView(
tooltip: DefaultTooltipManager.Tooltip(
params: BlockTooltipParams(
id: "tooltip",
mode: .modal,
duration: 0,
closeByTapOutside: true
),
view: TestView(),
substrateView: nil,
bringToTopId: "target_view"
),
handleAction: { _ in },
onCloseAction: {},
getViewById: { viewId in
getViewByIdCallCount += 1
if viewId.rawValue == "target_view" {
return targetView
}
return nil
}
)
let mainView = UIView()
mainView.addSubview(tooltipView)
tooltipView.bounds = CGRect(x: 0.0, y: 0.0, width: 100.0, height: 100.0)
tooltipView.forceLayout()
XCTAssertEqual(
getViewByIdCallCount,
0,
"getViewById should not be called without substrate view"
)
XCTAssertEqual(tooltipView.subviews.count, 1, "Should only have tooltip view")
}
func test_BringToTopId_NilValue_DoesNotCallGetViewById() {
var getViewByIdCallCount = 0
let substrateView = TestView()
let tooltipView = TooltipContainerView(
tooltip: DefaultTooltipManager.Tooltip(
params: BlockTooltipParams(
id: "tooltip",
mode: .modal,
duration: 0,
closeByTapOutside: true
),
view: TestView(),
substrateView: substrateView,
bringToTopId: nil
),
handleAction: { _ in },
onCloseAction: {},
getViewById: { _ in
getViewByIdCallCount += 1
return nil
}
)
let mainView = UIView()
mainView.addSubview(tooltipView)
tooltipView.bounds = CGRect(x: 0.0, y: 0.0, width: 100.0, height: 100.0)
tooltipView.forceLayout()
XCTAssertEqual(
getViewByIdCallCount,
0,
"getViewById should not be called when bringToTopId is nil"
)
XCTAssertEqual(tooltipView.subviews.count, 2, "Should have substrate and tooltip views")
}
func test_SnapshotView_UpdatesFrameOnLayout() {
let window = UIWindow(frame: CGRect(x: 0.0, y: 0.0, width: 400.0, height: 600.0))
let targetView = TestView()
targetView.frame = CGRect(x: 50.0, y: 100.0, width: 80.0, height: 40.0)
let containerView = UIView(frame: window.bounds)
window.addSubview(containerView)
containerView.addSubview(targetView)
let substrateView = TestView()
let tooltipView = TooltipContainerView(
tooltip: DefaultTooltipManager.Tooltip(
params: BlockTooltipParams(
id: "tooltip",
mode: .modal,
duration: 0,
closeByTapOutside: true
),
view: TestView(),
substrateView: substrateView,
bringToTopId: "target_view"
),
handleAction: { _ in },
onCloseAction: {},
getViewById: { viewId in
if viewId.rawValue == "target_view" {
return targetView
}
return nil
}
)
window.addSubview(tooltipView)
tooltipView.frame = window.bounds
tooltipView.layoutIfNeeded()
let snapshotView = tooltipView.subviews[1]
let expectedFrame = tooltipView.convert(
targetView.convert(targetView.bounds, to: window),
from: window
)
XCTAssertEqual(
snapshotView.frame,
expectedFrame,
"Snapshot frame should match target view position in window"
)
}
}
extension CGRect {
fileprivate func isInside(_ boundsRect: CGRect) -> Bool {
intersection(boundsRect) == self
}
}
fileprivate func makeTooltip(offset: CGPoint, block: Block) -> BlockTooltip {
BlockTooltip(
block: block,
params: BlockTooltipParams(
id: "tooltip",
mode: .modal,
duration: 0,
closeByTapOutside: true
),
offset: offset,
position: .center
)
}
fileprivate class TestView: UIView, BlockViewProtocol {
var effectiveBackgroundColor: UIColor?
func onVisibleBoundsChanged(from _: CGRect, to _: CGRect) {}
}