mirror of
https://github.com/divkit/divkit.git
synced 2026-06-06 20:07:59 +00:00
fixed division by zero in DivActionHandler.
05ec0af3e193df3b57525fb76d3a1a3a322b8477
This commit is contained in:
@@ -9687,6 +9687,7 @@
|
||||
"client/ios/DivKitSnapshotTests/YSFontProvider.swift":"divkit/public/client/ios/DivKitSnapshotTests/YSFontProvider.swift",
|
||||
"client/ios/DivKitTests/Actions/DivActionHandlerTests.swift":"divkit/public/client/ios/DivKitTests/Actions/DivActionHandlerTests.swift",
|
||||
"client/ios/DivKitTests/Actions/DivActionIntentTests.swift":"divkit/public/client/ios/DivKitTests/Actions/DivActionIntentTests.swift",
|
||||
"client/ios/DivKitTests/Actions/DivActionURLHandlerTests.swift":"divkit/public/client/ios/DivKitTests/Actions/DivActionURLHandlerTests.swift",
|
||||
"client/ios/DivKitTests/DivBlockModelingContextTests.swift":"divkit/public/client/ios/DivKitTests/DivBlockModelingContextTests.swift",
|
||||
"client/ios/DivKitTests/DivBlockStateStorageTests.swift":"divkit/public/client/ios/DivKitTests/DivBlockStateStorageTests.swift",
|
||||
"client/ios/DivKitTests/DivDeserializationErrorsTests.swift":"divkit/public/client/ios/DivKitTests/DivDeserializationErrorsTests.swift",
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
0A9A9AA028CB1A9200D1D810 /* Lottie in Frameworks */ = {isa = PBXBuildFile; productRef = 0A9A9A9F28CB1A9200D1D810 /* Lottie */; };
|
||||
0AB4193E28BCF61D00FDAAD5 /* LottieAnimationFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AB4193D28BCF61D00FDAAD5 /* LottieAnimationFactory.swift */; };
|
||||
431276EE2A72B8F2000CC821 /* DivView+SwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431276ED2A72B8F2000CC821 /* DivView+SwiftUI.swift */; };
|
||||
431CF8582B7BC47E005A2E61 /* DivActionURLHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431CF8572B7BC47E005A2E61 /* DivActionURLHandlerTests.swift */; };
|
||||
431DC9C22961EF84007FA268 /* ContainerBlockLayoutTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431DC9C12961EF84007FA268 /* ContainerBlockLayoutTests.swift */; };
|
||||
431DC9C429620AA1007FA268 /* BlockTestsModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431DC9C329620AA1007FA268 /* BlockTestsModels.swift */; };
|
||||
435653DB29742C9300224B9D /* BlockWithFixedWrapContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435653DA29742C9300224B9D /* BlockWithFixedWrapContent.swift */; };
|
||||
@@ -275,6 +276,7 @@
|
||||
/* Begin PBXFileReference section */
|
||||
0AB4193D28BCF61D00FDAAD5 /* LottieAnimationFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LottieAnimationFactory.swift; sourceTree = "<group>"; };
|
||||
431276ED2A72B8F2000CC821 /* DivView+SwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DivView+SwiftUI.swift"; sourceTree = "<group>"; };
|
||||
431CF8572B7BC47E005A2E61 /* DivActionURLHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DivActionURLHandlerTests.swift; sourceTree = "<group>"; };
|
||||
431DC9C12961EF84007FA268 /* ContainerBlockLayoutTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainerBlockLayoutTests.swift; sourceTree = "<group>"; };
|
||||
431DC9C329620AA1007FA268 /* BlockTestsModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockTestsModels.swift; sourceTree = "<group>"; };
|
||||
435653DA29742C9300224B9D /* BlockWithFixedWrapContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockWithFixedWrapContent.swift; sourceTree = "<group>"; };
|
||||
@@ -744,6 +746,7 @@
|
||||
children = (
|
||||
8C7B1B312865C01C0036EF4C /* DivActionIntentTests.swift */,
|
||||
8C40BA012ABB3B5C008405AD /* DivActionHandlerTests.swift */,
|
||||
431CF8572B7BC47E005A2E61 /* DivActionURLHandlerTests.swift */,
|
||||
);
|
||||
path = Actions;
|
||||
sourceTree = "<group>";
|
||||
@@ -1638,6 +1641,7 @@
|
||||
8C7B1CA92865C01E0036EF4C /* DivStateManagerTests.swift in Sources */,
|
||||
8C7B1BFD2865C01C0036EF4C /* DivSeparatorExtensionsTests.swift in Sources */,
|
||||
8C23F41229DDB3810069F3F7 /* EntityTemplate.swift in Sources */,
|
||||
431CF8582B7BC47E005A2E61 /* DivActionURLHandlerTests.swift in Sources */,
|
||||
8C23F43629DDB3810069F3F7 /* EntityWithStringEnumPropertyWithDefaultValueTemplate.swift in Sources */,
|
||||
8C23F41C29DDB3810069F3F7 /* EntityWithArrayWithTransformTemplate.swift in Sources */,
|
||||
8CD1521028CF217B0061128B /* ExpressionResolverTests.swift in Sources */,
|
||||
|
||||
@@ -214,6 +214,9 @@ public final class DivActionURLHandler {
|
||||
count: Int,
|
||||
overflow: OverflowMode
|
||||
) -> Int {
|
||||
guard count != 0 else {
|
||||
return current
|
||||
}
|
||||
switch overflow {
|
||||
case .ring:
|
||||
return (current + 1) % count
|
||||
@@ -270,9 +273,12 @@ public final class DivActionURLHandler {
|
||||
count: Int,
|
||||
overflow: OverflowMode
|
||||
) -> Int {
|
||||
guard count != 0 else {
|
||||
return current
|
||||
}
|
||||
switch overflow {
|
||||
case .ring:
|
||||
return (count + (current - 1)) % count
|
||||
return (current + count - 1) % count
|
||||
case .clamp:
|
||||
return max(0, current - 1)
|
||||
}
|
||||
@@ -284,18 +290,22 @@ public final class DivActionURLHandler {
|
||||
index: Int,
|
||||
itemsCount: Int
|
||||
) {
|
||||
let clampedIndex = clamp(index, min: 0, max: max(0, itemsCount - 1))
|
||||
guard clampedIndex == index else {
|
||||
return
|
||||
}
|
||||
blockStateStorage.setState(
|
||||
id: id,
|
||||
cardId: cardId,
|
||||
state: GalleryViewState(
|
||||
contentPageIndex: CGFloat(max(0, index)),
|
||||
contentPageIndex: CGFloat(clampedIndex),
|
||||
itemsCount: itemsCount
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private func setPagerCurrentItem(id: String, cardId: DivCardID, index: Int, numberOfPages: Int) {
|
||||
let clampedIndex = clamp(index, min: 0, max: numberOfPages - 1)
|
||||
let clampedIndex = clamp(index, min: 0, max: max(0, numberOfPages - 1))
|
||||
guard clampedIndex == index else {
|
||||
return
|
||||
}
|
||||
@@ -311,11 +321,15 @@ public final class DivActionURLHandler {
|
||||
}
|
||||
|
||||
private func setTabsCurrentItem(id: String, cardId: DivCardID, index: Int, countOfPages: Int) {
|
||||
let clampedIndex = clamp(index, min: 0, max: max(0, countOfPages - 1))
|
||||
guard clampedIndex == index else {
|
||||
return
|
||||
}
|
||||
blockStateStorage.setState(
|
||||
id: id,
|
||||
cardId: cardId,
|
||||
state: TabViewState(
|
||||
selectedPageIndex: CGFloat(max(0, index)),
|
||||
selectedPageIndex: CGFloat(clampedIndex),
|
||||
countOfPages: countOfPages
|
||||
)
|
||||
)
|
||||
|
||||
@@ -148,7 +148,7 @@ private final class MockActionLogger: DivActionLogger {
|
||||
}
|
||||
}
|
||||
|
||||
private final class MockPatchProvider: DivPatchProvider {
|
||||
final class MockPatchProvider: DivPatchProvider {
|
||||
func getPatch(url _: URL, completion _: @escaping DivPatchProviderCompletion) {}
|
||||
|
||||
func cancelRequests() {}
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
@testable import DivKit
|
||||
@testable import LayoutKit
|
||||
|
||||
import XCTest
|
||||
|
||||
final class DivActionURLHandlerTests: XCTestCase {
|
||||
private var actionHandler: DivActionURLHandler!
|
||||
private let blockStateStorage = DivBlockStateStorage()
|
||||
|
||||
override func setUp() {
|
||||
actionHandler = DivActionURLHandler(
|
||||
stateUpdater: DefaultDivStateManagement(),
|
||||
blockStateStorage: blockStateStorage,
|
||||
patchProvider: MockPatchProvider(),
|
||||
variableUpdater: DivVariablesStorage(),
|
||||
updateCard: { _ in },
|
||||
showTooltip: nil,
|
||||
tooltipActionPerformer: nil,
|
||||
persistentValuesStorage: DivPersistentValuesStorage()
|
||||
)
|
||||
}
|
||||
|
||||
func test_setNextItemAction() {
|
||||
setItemTestCases(
|
||||
beforeStates: SetItemAction.firstElementState,
|
||||
afterState: SetItemAction.secondElementState,
|
||||
mode: .next(.clamp)
|
||||
)
|
||||
}
|
||||
|
||||
func test_setNextItemActionWithRingOverflow() {
|
||||
setItemTestCases(
|
||||
beforeStates: SetItemAction.lastElementsStates,
|
||||
afterState: SetItemAction.firstElementState,
|
||||
mode: .next(.ring)
|
||||
)
|
||||
}
|
||||
|
||||
func test_setNextItemActionWithEmptyState() {
|
||||
setItemTestCases(
|
||||
beforeStates: SetItemAction.emptyStates,
|
||||
afterState: SetItemAction.emptyStates,
|
||||
mode: .next(.clamp)
|
||||
)
|
||||
}
|
||||
|
||||
func test_setPreviousItemAction() {
|
||||
setItemTestCases(
|
||||
beforeStates: SetItemAction.secondElementState,
|
||||
afterState: SetItemAction.firstElementState,
|
||||
mode: .previous(.clamp)
|
||||
)
|
||||
}
|
||||
|
||||
func test_setPreviousItemActionWithRingOverflow() {
|
||||
setItemTestCases(
|
||||
beforeStates: SetItemAction.firstElementState,
|
||||
afterState: SetItemAction.lastElementsStates,
|
||||
mode: .previous(.ring)
|
||||
)
|
||||
}
|
||||
|
||||
func test_setPreviousItemActionWithEmptyState() {
|
||||
setItemTestCases(
|
||||
beforeStates: SetItemAction.emptyStates,
|
||||
afterState: SetItemAction.emptyStates,
|
||||
mode: .previous(.clamp)
|
||||
)
|
||||
}
|
||||
|
||||
func test_setCurrentItemAction() {
|
||||
setItemTestCases(
|
||||
beforeStates: SetItemAction.firstElementState,
|
||||
afterState: SetItemAction.firstElementState,
|
||||
mode: .current(0)
|
||||
)
|
||||
setItemTestCases(
|
||||
beforeStates: SetItemAction.firstElementState,
|
||||
afterState: SetItemAction.secondElementState,
|
||||
mode: .current(1)
|
||||
)
|
||||
}
|
||||
|
||||
func test_setCurrentItemActionWithOverflow() {
|
||||
setItemTestCases(
|
||||
beforeStates: SetItemAction.firstElementState,
|
||||
afterState: SetItemAction.firstElementState,
|
||||
mode: .current(50)
|
||||
)
|
||||
}
|
||||
|
||||
private func setItemTestCases(
|
||||
beforeStates: [ElementState],
|
||||
afterState: [ElementState],
|
||||
mode: SetItemAction.Mode
|
||||
) {
|
||||
zip(beforeStates, afterState).forEach {
|
||||
switch $0 {
|
||||
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 (_, _):
|
||||
assertionFailure("Unsupported element state")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func setItemTestCase<State: ElementState & Equatable>(
|
||||
beforeState: State,
|
||||
afterState: State,
|
||||
mode: SetItemAction.Mode
|
||||
) {
|
||||
blockStateStorage.setState(id: elementID, cardId: cardID, state: beforeState)
|
||||
let _ = actionHandler.handleURL(SetItemAction.makeURL(mode: mode), cardId: cardID)
|
||||
XCTAssertEqual(blockStateStorage.getState(elementID, cardId: cardID), afterState)
|
||||
}
|
||||
}
|
||||
|
||||
private enum SetItemAction {
|
||||
enum Mode {
|
||||
enum Overflow: String {
|
||||
case clamp
|
||||
case ring
|
||||
}
|
||||
|
||||
case next(Overflow)
|
||||
case previous(Overflow)
|
||||
case current(Int)
|
||||
}
|
||||
|
||||
static let firstElementState: [ElementState] = [
|
||||
PagerViewState(numberOfPages: 10, currentPage: 0),
|
||||
GalleryViewState(contentPageIndex: 0, itemsCount: 10),
|
||||
TabViewState(selectedPageIndex: 0, countOfPages: 10),
|
||||
]
|
||||
|
||||
static let secondElementState: [ElementState] = [
|
||||
PagerViewState(numberOfPages: 10, currentPage: 1),
|
||||
GalleryViewState(contentPageIndex: 1, itemsCount: 10),
|
||||
TabViewState(selectedPageIndex: 1, countOfPages: 10),
|
||||
]
|
||||
|
||||
static let lastElementsStates: [ElementState] = [
|
||||
PagerViewState(numberOfPages: 10, currentPage: 9),
|
||||
GalleryViewState(contentPageIndex: 9, itemsCount: 10),
|
||||
TabViewState(selectedPageIndex: 9, countOfPages: 10),
|
||||
]
|
||||
|
||||
static let emptyStates: [ElementState] = [
|
||||
PagerViewState(numberOfPages: 0, currentPage: 0),
|
||||
GalleryViewState(contentPageIndex: 0, itemsCount: 0),
|
||||
TabViewState(selectedPageIndex: 0, countOfPages: 0),
|
||||
]
|
||||
|
||||
static func makeURL(mode: Mode) -> URL {
|
||||
switch mode {
|
||||
case let .next(overflow):
|
||||
url("div-action://set_next_item?id=\(elementID)&overflow=\(overflow)")
|
||||
case let .previous(overflow):
|
||||
url("div-action://set_previous_item?id=\(elementID)&overflow=\(overflow)")
|
||||
case let .current(item):
|
||||
url("div-action://set_current_item?id=\(elementID)&item=\(item)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let cardID: DivCardID = "cardID"
|
||||
private let elementID: String = "element"
|
||||
Reference in New Issue
Block a user