Files
divkit/client/ios/DivKitTests/Actions/DivActionURLHandlerTests.swift
T
pkurchatov 718eec9f90 Swift Testing
commit_hash:9b1d9b2e0d20d42afde551f6e6bb29a694ce8a4f
2026-01-28 00:59:47 +03:00

421 lines
12 KiB
Swift

@testable @_spi(Internal) import DivKit
import DivKitTestsSupport
import LayoutKit
import Testing
@Suite
struct DivActionURLHandlerTests {
private let actionHandler: DivActionHandler
private let blockStateStorage = DivBlockStateStorage()
init() {
actionHandler = DivActionHandler(
blockStateStorage: blockStateStorage
)
}
@Test
func setNextItemAction() {
setItemTestCases(
beforeStates: SetItemAction.firstElementState,
afterState: SetItemAction.secondElementState,
mode: .next(step: nil, overflow: .clamp)
)
}
@Test
func setNextItemActionWithRingOverflow() {
setItemTestCases(
beforeStates: SetItemAction.lastElementState,
afterState: SetItemAction.firstElementState,
mode: .next(step: nil, overflow: .ring)
)
}
@Test
func setNextItem_WithStep() {
setItemTestCases(
beforeStates: SetItemAction.firstElementState,
afterState: SetItemAction.thirdElementState,
mode: .next(step: 2, overflow: .clamp)
)
}
@Test
func setNextItem_WithStepAndRingOverflow() {
setItemTestCases(
beforeStates: SetItemAction.lastElementState,
afterState: SetItemAction.secondElementState,
mode: .next(step: 2, overflow: .ring)
)
}
@Test
func setNextItemActionWithEmptyState() {
setItemTestCases(
beforeStates: SetItemAction.emptyStates,
afterState: SetItemAction.emptyStates,
mode: .next(step: nil, overflow: .clamp)
)
}
@Test
func setPreviousItemAction() {
setItemTestCases(
beforeStates: SetItemAction.secondElementState,
afterState: SetItemAction.firstElementState,
mode: .previous(step: nil, overflow: .clamp)
)
}
@Test
func setPreviousItemActionWithRingOverflow() {
setItemTestCases(
beforeStates: SetItemAction.firstElementState,
afterState: SetItemAction.lastElementState,
mode: .previous(step: nil, overflow: .ring)
)
}
@Test
func setPreviousItem_WithStep() {
setItemTestCases(
beforeStates: SetItemAction.thirdElementState,
afterState: SetItemAction.firstElementState,
mode: .previous(step: 2, overflow: .clamp)
)
}
@Test
func setPreviousItem_WithStepAndRingOverflow() {
setItemTestCases(
beforeStates: SetItemAction.firstElementState,
afterState: SetItemAction.thirdElementState,
mode: .previous(step: 8, overflow: .ring)
)
}
@Test
func setPreviousItemActionWithEmptyState() {
setItemTestCases(
beforeStates: SetItemAction.emptyStates,
afterState: SetItemAction.emptyStates,
mode: .previous(step: nil, overflow: .clamp)
)
}
@Test
func setCurrentItemAction() {
setItemTestCases(
beforeStates: SetItemAction.firstElementState,
afterState: SetItemAction.firstElementState,
mode: .current(0)
)
setItemTestCases(
beforeStates: SetItemAction.firstElementState,
afterState: SetItemAction.secondElementState,
mode: .current(1)
)
}
@Test
func setCurrentItemActionWithOverflow() {
setItemTestCases(
beforeStates: SetItemAction.firstElementState,
afterState: SetItemAction.firstElementState,
mode: .current(50)
)
}
@Test
func setScrollForwardAction() {
setItemTestCases(
beforeStates: SetItemAction.firstElementStateWithOffset,
afterState: SetItemAction.secondElementStateWithOffset,
mode: .forward(10, .clamp)
)
}
@Test
func setScrollForwardActionWithClampOverflow() {
setItemTestCases(
beforeStates: SetItemAction.firstElementStateWithOffset,
afterState: SetItemAction.endStateWithOffset,
mode: .forward(30, .clamp)
)
}
@Test
func setScrollForwardActionWithRingOverflow() {
setItemTestCases(
beforeStates: SetItemAction.firstElementStateWithOffset,
afterState: SetItemAction.secondElementStateWithOffset,
mode: .forward(30, .ring)
)
}
@Test
func setScrollForwardActionWithEmptyState() {
setItemTestCases(
beforeStates: SetItemAction.emptyStatesWithOffset,
afterState: SetItemAction.emptyStatesWithOffset,
mode: .forward(30, .ring)
)
}
@Test
func setScrollBackwardAction() {
setItemTestCases(
beforeStates: SetItemAction.secondElementStateWithOffset,
afterState: SetItemAction.firstElementStateWithOffset,
mode: .backward(10, .clamp)
)
}
@Test
func setScrollBackwardActionWithClampOverflow() {
setItemTestCases(
beforeStates: SetItemAction.secondElementStateWithOffset,
afterState: SetItemAction.firstElementStateWithOffset,
mode: .backward(40, .clamp)
)
}
@Test
func setScrollBackwardActionWithRingOverflow() {
setItemTestCases(
beforeStates: SetItemAction.secondElementStateWithOffset,
afterState: SetItemAction.firstElementStateWithOffset,
mode: .backward(30, .ring)
)
}
@Test
func setScrollBackwardActionWithEmptyState() {
setItemTestCases(
beforeStates: SetItemAction.emptyStatesWithOffset,
afterState: SetItemAction.emptyStatesWithOffset,
mode: .backward(30, .ring)
)
}
@Test
func setScrollToPositionAction() {
setItemTestCases(
beforeStates: SetItemAction.firstElementStateWithOffset,
afterState: SetItemAction.secondElementStateWithOffset,
mode: .position(10)
)
}
@Test
func setScrollToPositionActionWithOverflow() {
setItemTestCases(
beforeStates: SetItemAction.secondElementStateWithOffset,
afterState: SetItemAction.endStateWithOffset,
mode: .position(30)
)
}
@Test
func setScrollToPositionActionWithEmptyState() {
setItemTestCases(
beforeStates: SetItemAction.emptyStatesWithOffset,
afterState: SetItemAction.emptyStatesWithOffset,
mode: .position(10)
)
}
@Test
func setScrollToStartAction() {
setItemTestCases(
beforeStates: SetItemAction.secondElementStateWithOffset,
afterState: SetItemAction.firstElementStateWithOffset,
mode: .start
)
}
@Test
func setScrollToStartActionWithEmptyState() {
setItemTestCases(
beforeStates: SetItemAction.emptyStatesWithOffset,
afterState: SetItemAction.emptyStatesWithOffset,
mode: .start
)
}
@Test
func setScrollToEndAction() {
setItemTestCases(
beforeStates: SetItemAction.secondElementStateWithOffset,
afterState: SetItemAction.endStateWithOffset,
mode: .end
)
}
@Test
func setScrollToEndActionWithEmptyState() {
setItemTestCases(
beforeStates: SetItemAction.emptyStatesWithOffset,
afterState: SetItemAction.emptyStatesWithOffset,
mode: .end
)
}
private func setItemTestCases(
beforeStates: [ElementState],
afterState: [ElementState],
mode: SetItemAction.Mode
) {
for states in zip(beforeStates, afterState) {
switch states {
case let (before as GalleryViewState, after as GalleryViewState):
setItemTestCase(beforeState: before, afterState: after, mode: mode)
case let (before as PagerViewState, after as PagerViewState):
setItemTestCase(beforeState: before, afterState: after, mode: mode)
case let (before as TabViewState, after as TabViewState):
setItemTestCase(beforeState: before, afterState: after, mode: mode)
case (_, _):
Issue.record("Unsupported element state")
}
}
}
private func setItemTestCase<State: ElementState & Equatable>(
beforeState: State,
afterState: State,
mode: SetItemAction.Mode
) {
blockStateStorage.setState(id: elementId, cardId: cardId, state: beforeState)
actionHandler.handle(
divAction(logId: "test", url: SetItemAction.makeURL(mode: mode)),
path: cardId.path,
source: .tap,
sender: nil
)
#expect(blockStateStorage.getState(elementId, cardId: cardId) == afterState)
}
}
private enum SetItemAction {
enum Mode {
case next(step: Int?, overflow: OverflowMode)
case previous(step: Int?, overflow: OverflowMode)
case current(Int)
case forward(Int, OverflowMode)
case backward(Int, OverflowMode)
case position(Int)
case start
case end
}
static let firstElementState: [ElementState] = [
PagerViewState(numberOfPages: 10, currentPage: 0, animated: true),
GalleryViewState(contentPageIndex: 0, itemsCount: 10, animated: true),
TabViewState(selectedPageIndex: 0, countOfPages: 10),
]
static let secondElementState: [ElementState] = [
PagerViewState(numberOfPages: 10, currentPage: 1, animated: true),
GalleryViewState(contentPageIndex: 1, itemsCount: 10, animated: true),
TabViewState(selectedPageIndex: 1, countOfPages: 10),
]
static let thirdElementState: [ElementState] = [
PagerViewState(numberOfPages: 10, currentPage: 2, animated: true),
GalleryViewState(contentPageIndex: 2, itemsCount: 10, animated: true),
TabViewState(selectedPageIndex: 2, countOfPages: 10),
]
static let lastElementState: [ElementState] = [
PagerViewState(numberOfPages: 10, currentPage: 9, animated: true),
GalleryViewState(contentPageIndex: 9, itemsCount: 10, animated: true),
TabViewState(selectedPageIndex: 9, countOfPages: 10),
]
static let emptyStates: [ElementState] = [
PagerViewState(numberOfPages: 0, currentPage: 0, animated: true),
GalleryViewState(contentPageIndex: 0, itemsCount: 0, animated: true),
TabViewState(selectedPageIndex: 0, countOfPages: 0),
]
static let firstElementStateWithOffset: [ElementState] = [
GalleryViewState(
contentPosition: .offset(0),
itemsCount: 3,
isScrolling: false,
scrollRange: 20,
animated: true
),
]
static let secondElementStateWithOffset: [ElementState] = [
GalleryViewState(
contentPosition: .offset(10),
itemsCount: 3,
isScrolling: false,
scrollRange: 20,
animated: true
),
]
static let endStateWithOffset: [ElementState] = [
GalleryViewState(
contentPosition: .offset(20),
itemsCount: 3,
isScrolling: false,
scrollRange: 20,
animated: true
),
]
static let emptyStatesWithOffset: [ElementState] = [
GalleryViewState(
contentPosition: .offset(0),
itemsCount: 0,
isScrolling: false,
scrollRange: 0,
animated: true
),
GalleryViewState(
contentPosition: .offset(0),
itemsCount: 0,
isScrolling: false,
scrollRange: nil,
animated: true
),
]
static func makeURL(mode: Mode) -> String {
switch mode {
case let .next(step, overflow):
let url = "div-action://set_next_item?id=\(elementId)&overflow=\(overflow)"
if let step {
return url + "&step=\(step)"
}
return url
case let .previous(step, overflow):
let url = "div-action://set_previous_item?id=\(elementId)&overflow=\(overflow)"
if let step {
return url + "&step=\(step)"
}
return url
case let .current(item):
return "div-action://set_current_item?id=\(elementId)&item=\(item)"
case let .forward(step, overflow):
return "div-action://scroll_forward?id=\(elementId)&step=\(Int(step))&overflow=\(overflow)"
case let .backward(step, overflow):
return "div-action://scroll_backward?id=\(elementId)&step=\(Int(step))&overflow=\(overflow)"
case let .position(step):
return "div-action://scroll_to_position?id=\(elementId)&step=\(Int(step))"
case .start:
return "div-action://scroll_to_start?id=\(elementId)"
case .end:
return "div-action://scroll_to_end?id=\(elementId)"
}
}
}
private let cardId: DivCardID = "cardId"
private let elementId: String = "element"