Fixed file path in precommit hook

commit_hash:f7df7bc7f68f49dd4b005691712d8784e092107e
This commit is contained in:
booster
2025-07-17 17:25:25 +03:00
parent cef80c9ae1
commit b489b36d92
60 changed files with 278 additions and 222 deletions
@@ -16,9 +16,15 @@ final class UpdateStructureActionHandler {
}
let updatedValue: DivVariableValue?
if let dict: DivDictionary = context.variablesStorage.getVariableValue(path: context.path, name: divVariableName) {
if let dict: DivDictionary = context.variablesStorage.getVariableValue(
path: context.path,
name: divVariableName
) {
updatedValue = updateDictionary(dict, newValue: newValue, path: pathComponents)
} else if let array: DivArray = context.variablesStorage.getVariableValue(path: context.path, name: divVariableName) {
} else if let array: DivArray = context.variablesStorage.getVariableValue(
path: context.path,
name: divVariableName
) {
updatedValue = updateArray(array, newValue: newValue, path: pathComponents)
} else {
DivKitLogger.error("Action requires array or dictionary variable")
@@ -106,9 +112,11 @@ private func updateElement(
newValue: AnyHashable
) -> AnyHashable? {
if let nestedArray = currentElement as? DivArray {
return updateArray(nestedArray, newValue: newValue, path: path, pathIndex: pathIndex)?.arrayValue
return updateArray(nestedArray, newValue: newValue, path: path, pathIndex: pathIndex)?
.arrayValue
} else if let nestedDict = currentElement as? DivDictionary {
return updateDictionary(nestedDict, newValue: newValue, path: path, pathIndex: pathIndex)?.dictValue
return updateDictionary(nestedDict, newValue: newValue, path: path, pathIndex: pathIndex)?
.dictValue
} else {
DivKitLogger.error(
"Element with path '\(path[0..<pathIndex].joined(separator: "/"))' is not a structure"
@@ -117,8 +125,8 @@ private func updateElement(
}
}
private extension Array {
subscript(insert index: Int) -> Element? {
extension Array {
fileprivate subscript(insert index: Int) -> Element? {
get {
(0..<count).contains(index) ? self[index] : nil
}
@@ -138,16 +146,16 @@ private extension Array {
}
}
private extension DivVariableValue {
var arrayValue: DivArray? {
if case .array(let array) = self {
extension DivVariableValue {
fileprivate var arrayValue: DivArray? {
if case let .array(array) = self {
return array
}
return nil
}
var dictValue: DivDictionary? {
if case .dict(let dict) = self {
fileprivate var dictValue: DivDictionary? {
if case let .dict(dict) = self {
return dict
}
return nil
@@ -155,5 +163,6 @@ private extension DivVariableValue {
}
private func logElementNotFound(path: [String], pathIndex: Int) {
DivKitLogger.error("Element with path '\(path[0...pathIndex].joined(separator: "/"))' is not found")
DivKitLogger
.error("Element with path '\(path[0...pathIndex].joined(separator: "/"))' is not found")
}
+4 -5
View File
@@ -117,7 +117,7 @@ public final class DivBlockStateStorage {
!state.isDifferent(from: existingState) {
shouldUpdatePipe = false
}
_states[key] = state
}
@@ -245,7 +245,6 @@ extension DivBlockStateStorage: ElementStateObserver {
}
private enum StateKey {
case path(UIElementPath)
case id(IdAndCardId)
@@ -261,12 +260,12 @@ private enum StateKey {
var cardID: DivCardID {
id.cardId
}
private var id: IdAndCardId {
switch self {
case .path(let path):
case let .path(path):
IdAndCardId(path: path)
case .id(let id):
case let .id(id):
id
}
}
@@ -90,7 +90,8 @@ private struct AssetsImageProvider: DivImageHolderFactory {
func make(_ url: URL?, _ placeholder: ImagePlaceholder?) -> ImageHolder {
var localImage: ImageHolder?
if url?.scheme == "divkit-asset", let name = url?.host {
//To restrict access to resources, all divkit asset images must start with the 'divkit.' prefix.
// To restrict access to resources, all divkit asset images must start with the 'divkit.'
// prefix.
localImage = Image(named: "divkit.\(name)")
}
return localImage ?? imageHolderFactory.make(url, placeholder)
@@ -50,7 +50,7 @@ struct CalcExpression {
return nil
}
}
func extractDynamicVariableNames(_ context: ExpressionContext) throws -> [String] {
try root.extractDynamicVariableNames(context)
}
@@ -84,7 +84,7 @@ public struct ExpressionLink<T: Sendable>: Sendable {
self.rawValue = rawValue
self.validator = validator
}
func extractDynamicVariableNames(_ context: ExpressionContext) -> [String] {
items.flatMap { item in
switch item {
@@ -134,7 +134,7 @@ public final class ExpressionResolver {
resolveNumeric(expression)
}
func extractDynamicVariables<T>(_ link: ExpressionLink<T>) -> [String] {
func extractDynamicVariables(_ link: ExpressionLink<some Any>) -> [String] {
link.extractDynamicVariableNames(context)
}
@@ -26,9 +26,9 @@ extension [String: Function] {
addFunctions("Url", _getUrl)
addFunctions("OptUrl", _getOptUrl)
addFunction("len", _len)
addFunction("getDictKeys", _getDictKeys)
addFunction("getDictValues", _getDictValues)
}
@@ -45,7 +45,7 @@ extension [String: Function] {
addFunction("isEmpty", _isEmpty)
addFunction("containsKey", _containsKey)
addFunction("getKeys", _getDictKeys)
addFunction("getValues", _getDictValues)
}
@@ -166,7 +166,7 @@ private struct FunctionEvaluator: Function {
}
private struct DynamicVariablesEvaluator: Function {
func invoke(_ args: [Any], context: ExpressionContext) throws -> Any {
func invoke(_ args: [Any], context _: ExpressionContext) throws -> Any {
guard let arg = args.first else {
throw ExpressionError("There is no arguments in getValueFunction")
}
@@ -221,5 +221,5 @@ private let getValueFunctions = [
"getIntegerValue",
"getNumberValue",
"getStringValue",
"getUrlValue"
"getUrlValue",
]
@@ -130,8 +130,8 @@ extension DivBase {
return blockActions
}
func setupContextWithVariablesAndFunctions(
func setupContextWithVariablesAndFunctions(
context: DivBlockModelingContext
) {
context.functionsStorage?.setIfNeeded(
@@ -245,9 +245,17 @@ extension Block {
for extensionHandler in extensionHandlers {
switch order {
case .beforeBaseProperties:
newBlock = extensionHandler.applyBeforeBaseProperties(to: newBlock, div: div, context: context)
newBlock = extensionHandler.applyBeforeBaseProperties(
to: newBlock,
div: div,
context: context
)
case .afterBaseProperties:
newBlock = extensionHandler.applyAfterBaseProperties(to: newBlock, div: div, context: context)
newBlock = extensionHandler.applyAfterBaseProperties(
to: newBlock,
div: div,
context: context
)
}
}
return newBlock
@@ -6,7 +6,7 @@ extension DivCollectionItemBuilder {
context: DivBlockModelingContext,
mappedBy modificator: (Div, Block, DivBlockModelingContext) -> T
) -> [T] {
makeItemDivAndContexts(context: context).compactMap { (div, itemContext) in
makeItemDivAndContexts(context: context).compactMap { div, itemContext in
do {
return try modifyError({
DivBlockModelingError($0.message, path: itemContext.path)
@@ -20,7 +20,7 @@ extension DivCollectionItemBuilder {
}
}
}
func makeItemDivAndContexts(
context: DivBlockModelingContext
) -> [(Div, DivBlockModelingContext)] {
@@ -230,7 +230,7 @@ extension DivContainer.Orientation {
extension DivAlignmentHorizontal {
func alignment(isRTLLayout: Bool) -> Alignment {
switch self {
case .left:
case .left:
isRTLLayout ? .trailing : .leading
case .right:
isRTLLayout ? .leading : .trailing
@@ -34,7 +34,8 @@ extension DivGalleryProtocol {
crossAlignment: (
direction.isHorizontal
? div.value.resolveAlignmentVertical(expressionResolver)?.alignment
: div.value.resolveAlignmentHorizontal(expressionResolver)?.alignment(isRTLLayout: context.layoutDirection == .rightToLeft)
: div.value.resolveAlignmentHorizontal(expressionResolver)?
.alignment(isRTLLayout: context.layoutDirection == .rightToLeft)
) ?? defaultCrossAlignment,
content: block
)
@@ -66,7 +66,8 @@ extension DivGrid: DivBlockModeling {
isRTLLayout: Bool
) -> BlockAlignment2D {
BlockAlignment2D(
horizontal: resolveContentAlignmentHorizontal(expressionResolver).alignment(isRTLLayout: isRTLLayout),
horizontal: resolveContentAlignmentHorizontal(expressionResolver)
.alignment(isRTLLayout: isRTLLayout),
vertical: resolveContentAlignmentVertical(expressionResolver).alignment
)
}
@@ -70,9 +70,9 @@ extension DivTypedValue {
}
return nil
case let .dictValue(value):
if let dictValue = value.resolveValue(expressionResolver) {
return DivDictionary.fromAny(dictValue)
}
if let dictValue = value.resolveValue(expressionResolver) {
return DivDictionary.fromAny(dictValue)
}
return nil
case let .integerValue(value):
if let integerValue = value.resolveValue(expressionResolver) {
@@ -6,7 +6,6 @@ public enum ResourcePreloadFilter {
}
public final class DivDataResourcesPreloader {
private let resourceRequester: URLResourceRequesting
public init(
@@ -25,13 +24,13 @@ public final class DivDataResourcesPreloader {
let validURLs = divData.flatMap(
{
let extensionURLs = $0.makeExtensionPreloadURLs(
extensionHandlers: extensionHandlers,
extensionHandlers: extensionHandlers,
expressionResolver: $1.expressionResolver
)
let imageURLs = $0.makeImageURLs(with: $1.expressionResolver, filter: filter)
let videoURLs = $0.makeVideoURLs(with: $1.expressionResolver, filter: filter)
return extensionURLs + imageURLs + videoURLs
},
},
context: context
)
.flatMap { $0 }
@@ -62,11 +61,11 @@ public final class DivDataResourcesPreloader {
extension DivData {
fileprivate func flatMap<T>(
_ transform: (Div, DivBlockModelingContext) -> T,
_ transform: (Div, DivBlockModelingContext) -> T,
context: DivBlockModelingContext
) -> [T] {
var result: [T] = []
context.functionsStorage?.setIfNeeded(
path: context.path,
functions: functions ?? []
@@ -76,17 +75,17 @@ extension DivData {
path: context.path,
variables: variables?.extractDivVariableValues(context.expressionResolver) ?? [:]
)
func traverse(div: Div, divContext: DivBlockModelingContext) {
div.value.setupContextWithVariablesAndFunctions(context: divContext)
result.append(transform(div, divContext))
if let container = div.value as? DivContainer, let itemBuilder = container.itemBuilder {
itemBuilder.makeItemDivAndContexts(context: divContext).forEach { div, itemContext in
traverse(div: div, divContext: itemContext)
}
}
div.children.forEach {
let childContext = $0.value.modifiedContextParentPath(divContext)
traverse(div: $0, divContext: childContext)
@@ -68,14 +68,14 @@ extension Div {
[]
}
}
func makeExtensionPreloadURLs(
extensionHandlers: [String : DivExtensionHandler],
extensionHandlers: [String: DivExtensionHandler],
expressionResolver: ExpressionResolver
) -> [URL] {
guard !extensionHandlers.isEmpty else { return [] }
return value.extensions?.compactMap {
extensionHandlers[$0.id]?.getPreloadURLs(div: value, expressionResolver: expressionResolver)
guard !extensionHandlers.isEmpty else { return [] }
return value.extensions?.compactMap {
extensionHandlers[$0.id]?.getPreloadURLs(div: value, expressionResolver: expressionResolver)
}.flatMap { $0 } ?? []
}
}
+2 -3
View File
@@ -11,10 +11,10 @@ extension DivArray {
extension DivArray {
func isEqualUnordered(_ other: DivArray?) -> Bool {
guard let other,
self.count == other.count else {
self.count == other.count else {
return false
}
return self.countElements() == other.countElements()
}
}
@@ -26,4 +26,3 @@ extension DivDictionary {
NSDictionary(dictionary: value) as? DivDictionary
}
}
@@ -260,7 +260,10 @@ extension Expression {
fileprivate func getVariablesNames(_ resolver: ExpressionResolver) -> Set<DivVariableName> {
switch self {
case let .link(link):
let dynamicNames = Set(resolver.extractDynamicVariables(link).map(DivVariableName.init(rawValue:)))
let dynamicNames = Set(
resolver.extractDynamicVariables(link)
.map(DivVariableName.init(rawValue:))
)
let staticNames = Set(link.variablesNames.map(DivVariableName.init(rawValue:)))
return staticNames.union(dynamicNames)
case .value:
@@ -173,7 +173,7 @@ final class DivBlockProvider {
self.divData = nil
return
}
if let resourcesPreloader = divKitComponents.resourcesPreloader {
resourcesPreloader.downloadResources(
for: divData,
@@ -1,6 +1,6 @@
import DivKit
import LayoutKitInterface
import Foundation
import LayoutKitInterface
import VGSL
struct LottieExtensionParams {
@@ -34,15 +34,24 @@ struct LottieExtensionParams {
return nil
}
self.repeatCount = (try? paramsDictionary.getOptionalFloat("repeat_count", expressionResolver: expressionResolver)).map(Float.init)
self
.repeatCount = (
try? paramsDictionary
.getOptionalFloat("repeat_count", expressionResolver: expressionResolver)
).map(Float.init)
?? Defaults.defaultRepeatCount
self.repeatMode = (paramsDictionary["repeat_mode"] as? String)
.flatMap(expressionResolver.resolveString)
.flatMap(AnimationRepeatMode.init)
?? Defaults.defaultRepeatMode
.flatMap(expressionResolver.resolveString)
.flatMap(AnimationRepeatMode.init)
?? Defaults.defaultRepeatMode
self.isPlaying = (try? paramsDictionary.getOptionalBool("is_playing", expressionResolver: expressionResolver)) ?? Defaults.defaultIsPlaying
self
.isPlaying = (
try? paramsDictionary
.getOptionalBool("is_playing", expressionResolver: expressionResolver)
) ?? Defaults
.defaultIsPlaying
}
init(source: Source, repeatCount: Float, repeatMode: AnimationRepeatMode, isPlaying: Bool) {
@@ -53,8 +62,8 @@ struct LottieExtensionParams {
}
}
private extension AnimationRepeatMode {
init?(repeatModeString: String) {
extension AnimationRepeatMode {
fileprivate init?(repeatModeString: String) {
switch repeatModeString {
case "reverse":
self = .reverse
@@ -7,7 +7,10 @@ extension Div {
var urls: [URL] = value.background?.compactMap {
$0.resolveImageURL(expressionResolver)
} ?? []
if let url = LottieExtensionHandler.getPreloadURL(div: value, expressionResolver: expressionResolver) {
if let url = LottieExtensionHandler.getPreloadURL(
div: value,
expressionResolver: expressionResolver
) {
urls.append(url)
}
switch self {
@@ -17,7 +17,7 @@ public final class InputPropertiesExtensionHandler: DivExtensionHandler {
}
let params = getExtensionParams(div)
var newBlock = textInputBlock
if let enablesReturnKeyAutomatically = try? params.getOptionalBool(
@@ -16,7 +16,7 @@ extension Dictionary where Key == String {
}
return result
}
func getOptionalFloat(
_ key: Key,
expressionResolver: ExpressionResolver
@@ -1,24 +1,24 @@
@testable import DivKit
@testable import LayoutKit
@testable import DivKitExtensions
import DivKitTestsSupport
@testable import LayoutKit
import VGSL
import XCTest
final class InputExtensionHandlersTests: XCTestCase {
private let variableStorage = DivVariableStorage()
private var context: DivBlockModelingContext!
override func setUp() {
variableStorage.put(name: "input_variable", value: .string("Hello!"))
context = DivBlockModelingContext(variableStorage: variableStorage)
}
func test_ApplyAllExtensionsToTextInput() throws {
let contextWithExtensions = DivBlockModelingContext(
extensionHandlers: [
InputPropertiesExtensionHandler(),
InputAutocorrectionExtensionHandler()
InputAutocorrectionExtensionHandler(),
],
variableStorage: variableStorage
)
@@ -30,15 +30,15 @@ final class InputExtensionHandlersTests: XCTestCase {
id: "input_properties",
params: [
"enables_return_key_automatically": true,
"spell_checking": true
"spell_checking": true,
]
),
DivExtension(
id: "input_autocorrection",
params: [
"enabled": true
"enabled": true,
]
)
),
],
textVariable: "input_variable"
),
@@ -1,7 +1,7 @@
@testable import DivKit
@testable import DivKitExtensions
@testable import LayoutKit
import DivKitTestsSupport
@testable import LayoutKit
import VGSL
import XCTest
@@ -13,7 +13,7 @@ final class LottieExtensionHandlerTests: XCTestCase {
factory: factory,
requester: requester
)
func test_getPreloadURLs() {
let lottieURLString = "https://example.com/DivGif9.json"
let divWithExtension = divContainer(
@@ -24,9 +24,9 @@ final class LottieExtensionHandlerTests: XCTestCase {
params: [
"lottie_url": lottieURLString,
"repeat_count": 2,
"repeat_mode": "reverse"
"repeat_mode": "reverse",
]
)
),
],
items: []
)
@@ -61,9 +61,9 @@ final class LottieExtensionHandlerTests: XCTestCase {
params: [
"lottie_url": "@{lottie_url}",
"repeat_count": "@{repeat_count}",
"repeat_mode": "@{repeat_mode}"
"repeat_mode": "@{repeat_mode}",
]
)
),
],
items: []
),
@@ -85,8 +85,8 @@ final class LottieExtensionHandlerTests: XCTestCase {
private final class MockLottieAnimationFactory: AsyncSourceAnimatableViewFactory {
var returnView = MockAnimatableView(frame: .zero)
func createAsyncSourceAnimatableView(withMode mode: AnimationRepeatMode, repeatCount count: Float)
-> AsyncSourceAnimatableView {
func createAsyncSourceAnimatableView(withMode _: AnimationRepeatMode, repeatCount _: Float)
-> AsyncSourceAnimatableView {
returnView
}
}
@@ -94,13 +94,13 @@ private final class MockLottieAnimationFactory: AsyncSourceAnimatableViewFactory
private final class MockAnimatableView: UIView, AsyncSourceAnimatableView {
var playCallsCount = 0
var receivedSources: [any DivKitExtensions.AnimationSourceType] = []
func play() {
playCallsCount += 1
}
func pause() {}
func setSourceAsync(_ source: any DivKitExtensions.AnimationSourceType) async {
receivedSources.append(source)
}
@@ -84,10 +84,10 @@ private func makeCachingPlayerFactory(requester: URLResourceRequesting) -> Playe
private func makeResourcesPreloader(
requestPerformer: URLRequestPerforming?
) -> DivDataResourcesPreloader? {
guard let requestPerformer = requestPerformer else {
guard let requestPerformer else {
return nil
}
let cacheQueue = OperationQueue.serialQueue(
name: "divkit.resources-preloader.cache-queue",
qos: .utility
@@ -100,7 +100,10 @@ private func makeResourcesPreloader(
reportError: { _ in }
)
let networkRequester = NetworkURLResourceRequester(performer: requestPerformer)
let cachedRequester = CachedURLResourceRequester(cache: diskCache, cachemissRequester: networkRequester)
let cachedRequester = CachedURLResourceRequester(
cache: diskCache,
cachemissRequester: networkRequester
)
return DivDataResourcesPreloader(resourceRequester: cachedRequester)
}
@@ -70,7 +70,7 @@ extension RiveContainerView: AsyncSourceAnimatableView {
func play() {
riveViewModel?.play(loop: loop)
}
func pause() {
riveViewModel?.pause()
}
@@ -8,7 +8,7 @@ open class DivViewController: UIViewController {
private let divKitComponents: DivKitComponents
private let debugParams: DebugParams
private var divView: DivView?
private let scrollView = VisibilityTrackingScrollView()
private var cancellables = Set<AnyCancellable>()
@@ -65,7 +65,7 @@ open class DivViewController: UIViewController {
divView.setParentScrollView(scrollView)
divView.accessibilityIdentifier = identifier
return divView
}
@@ -1,7 +1,7 @@
@testable @_spi(Internal) import DivKit
import DivKitTestsSupport
@testable import LayoutKit
import XCTest
import DivKitTestsSupport
final class CustomFunctionTests: XCTestCase {
private let incrementFunction = DivFunction(
@@ -5,7 +5,7 @@ import XCTest
final class ExpressionResolverAnyTests: XCTestCase {
private var isErrorExpected = false
private var error: String? = nil
private var error: String?
private var variables: DivVariables = [:]
@@ -535,7 +535,8 @@ final class ExpressionResolverTests: XCTestCase {
}
func test_extractDynamicVariables_WithNestedGetValueFunctionsAndUnknownVars() {
let expression = "@{getStringValue('outer_var_' + getStringValue('inner_var_' + string_var, ''), '')}"
let expression =
"@{getStringValue('outer_var_' + getStringValue('inner_var_' + string_var, ''), '')}"
XCTAssertEqual(
expressionResolver.extractDynamicVariables(
@@ -546,7 +547,8 @@ final class ExpressionResolverTests: XCTestCase {
}
func test_extractDynamicVariables_WithGetValueFunctionsWithFallbackValue() {
let expression = "@{getStringValue('outer_var_' + getStringValue('inner_var_' + string_var, 'value'), '')}"
let expression =
"@{getStringValue('outer_var_' + getStringValue('inner_var_' + string_var, 'value'), '')}"
XCTAssertEqual(
expressionResolver.extractDynamicVariables(
@@ -168,12 +168,12 @@ private struct ExpressionTestCase: Decodable {
}
extension ExpressionTestCase: Hashable {
static func == (lhs: ExpressionTestCase, rhs: ExpressionTestCase) -> Bool {
return lhs.expression == rhs.expression &&
lhs.variables == rhs.variables &&
lhs.expected.description == rhs.expected.description
static func ==(lhs: ExpressionTestCase, rhs: ExpressionTestCase) -> Bool {
lhs.expression == rhs.expression &&
lhs.variables == rhs.variables &&
lhs.expected.description == rhs.expected.description
}
func hash(into hasher: inout Hasher) {
hasher.combine(expression)
hasher.combine(variables)
@@ -1,6 +1,6 @@
@testable import DivKit
@testable import LayoutKit
import DivKitTestsSupport
@testable import LayoutKit
import Testing
import UIKit
import VGSL
@@ -1,6 +1,6 @@
@testable import DivKit
@testable import LayoutKit
import DivKitTestsSupport
@testable import LayoutKit
import VGSL
import XCTest
@@ -1,6 +1,6 @@
@testable import DivKit
@testable import LayoutKit
import DivKitTestsSupport
@testable import LayoutKit
import XCTest
final class DivContainerExtensionsTests: XCTestCase {
@@ -1,6 +1,6 @@
@testable import DivKit
@testable import LayoutKit
import DivKitTestsSupport
@testable import LayoutKit
import XCTest
final class DivDataExtensionsTests: XCTestCase {
@@ -1,6 +1,6 @@
@testable import DivKit
@testable import LayoutKit
import DivKitTestsSupport
@testable import LayoutKit
import VGSL
import XCTest
@@ -1,6 +1,6 @@
@testable import DivKit
@testable import LayoutKit
import DivKitTestsSupport
@testable import LayoutKit
import VGSL
import XCTest
@@ -1,6 +1,6 @@
@testable import DivKit
@testable import LayoutKit
import DivKitTestsSupport
@testable import LayoutKit
import XCTest
final class DivSeparatorExtensionsTests: XCTestCase {
@@ -1,6 +1,6 @@
@testable import DivKit
@testable import LayoutKit
import DivKitTestsSupport
@testable import LayoutKit
import XCTest
final class DivStateExtensionsTests: XCTestCase {
@@ -1,6 +1,6 @@
@testable import DivKit
@testable import LayoutKit
import DivKitTestsSupport
@testable import LayoutKit
import VGSL
import XCTest
@@ -1,6 +1,6 @@
@testable import DivKit
@testable import LayoutKit
import DivKitTestsSupport
@testable import LayoutKit
import VGSL
import XCTest
+15 -11
View File
@@ -75,8 +75,12 @@ private func runTest(_ testData: IntegrationTestData) async {
cardId: cardId,
name: DivVariableName(rawValue: name)
)
if case .unorderedArray(let expectedArray) = value {
let variableArray: DivArray? = if case .array(let arr) = variableValue { arr } else { nil }
if case let .unorderedArray(expectedArray) = value {
let variableArray: DivArray? = if case let .array(arr) = variableValue {
arr
} else {
nil
}
XCTAssertTrue(
expectedArray.isEqualUnordered(variableArray),
"Variable name - '\(name)'"
@@ -266,23 +270,23 @@ extension [Expected] {
private func makeDefault(_ value: ExpectedValue) -> DivVariableValue? {
switch value {
case .string, .datetime:
.string("")
.string("")
case .integer:
.integer(0)
.integer(0)
case .double:
.number(0.0)
.number(0.0)
case .bool:
.bool(false)
.bool(false)
case .color:
.color(.black)
.color(.black)
case .url:
.url(URL(string: "empty://")!)
.url(URL(string: "empty://")!)
case .array:
.array([])
.array([])
case .dict:
.dict([:])
.dict([:])
case .unorderedArray:
.array([])
.array([])
case .error:
nil
}
@@ -105,7 +105,7 @@ final class DivDataResourcesPreloaderTests: XCTestCase {
let context = DivBlockModelingContext(
extensionHandlers: [
mockExtensionHandler,
invalidExtensionHandler
invalidExtensionHandler,
]
)
@@ -127,90 +127,95 @@ final class DivDataResourcesPreloaderTests: XCTestCase {
let divData = divData(divWithExtension)
downloadResourcesAndWait(
for: divData,
for: divData,
using: preloader,
context: context
)
XCTAssertEqual(mockRequester.requestedURLs, [mockExtensionHandler.preloadURL])
}
func test_WhenContainerHasNestedItemBuilders_WithAllFilter_PreloadsAllResources() {
let divData = createDivDataWithNestedItemBuilders()
downloadResourcesAndWait(for: divData, using: preloader)
XCTAssertEqual(
Set(mockRequester.requestedURLs.map { $0.absoluteString }),
Set(mockRequester.requestedURLs.map(\.absoluteString)),
Set([requiredImageURL, optionalImageURL, validImageURL])
)
}
func test_WhenItemBuilderUsesVariables_WithRequiredFilter_PreloadsOnlyRequiredResources() {
let mockRequester = MockURLResourceRequester()
mockRequester.shouldSucceed = true
let preloader = DivDataResourcesPreloader(resourceRequester: mockRequester)
let variableStorage = DivVariableStorage()
variableStorage.put(name: "image_url_var", value: .url(URL(string: requiredImageURL)!))
variableStorage.put(name: "preload_required_var", value: .bool(true))
let context = DivBlockModelingContext(variableStorage: variableStorage)
let divData = createDivDataWithVariables()
downloadResourcesAndWait(for: divData, using: preloader, filter: .onlyRequired, context: context)
downloadResourcesAndWait(
for: divData,
using: preloader,
filter: .onlyRequired,
context: context
)
XCTAssertEqual(
Set(mockRequester.requestedURLs.map { $0.absoluteString }),
Set(mockRequester.requestedURLs.map(\.absoluteString)),
Set([requiredImageURL])
)
}
func test_WhenContainerHasItemBuilder_WithRequiredFilter_OnlyDownloadsRequiredResources() {
let divData = createDivDataWithMixedItemBuilderResources()
downloadResourcesAndWait(for: divData, using: preloader, filter: .onlyRequired)
XCTAssertEqual(
Set(mockRequester.requestedURLs.map { $0.absoluteString }),
Set(mockRequester.requestedURLs.map(\.absoluteString)),
Set([requiredImageURL, requiredVideoURL])
)
}
func test_WhenContainerHasItemBuilder_WithAllFilter_DownloadsAllResources() {
let divData = createDivDataWithMixedItemBuilderResources()
downloadResourcesAndWait(for: divData, using: preloader)
XCTAssertEqual(
Set(mockRequester.requestedURLs.map { $0.absoluteString }),
Set(mockRequester.requestedURLs.map(\.absoluteString)),
Set([requiredImageURL, optionalImageURL, requiredVideoURL, optionalVideoURL])
)
}
func test_WhenDivDataHasVariables_WithRequiredFilter_PreloadsCorrectly() {
let divData = createDivDataWithDivVariables()
downloadResourcesAndWait(for: divData, using: preloader, filter: .onlyRequired)
XCTAssertEqual(
Set(mockRequester.requestedURLs.map { $0.absoluteString }),
Set(mockRequester.requestedURLs.map(\.absoluteString)),
Set([requiredImageURL])
)
}
private func createDivDataWithDivVariables() -> DivData {
let imageWithVariable = divImage(
id: "image_with_variable",
imageUrlExpression: "@{image_var}",
preloadRequired: true
)
let container = divContainer(
id: "container_with_variables",
items: [imageWithVariable]
)
return DivData(
functions: nil,
logId: DivBlockModelingContext.testCardId.rawValue,
@@ -219,11 +224,11 @@ final class DivDataResourcesPreloaderTests: XCTestCase {
transitionAnimationSelector: nil,
variableTriggers: nil,
variables: [
.urlVariable(UrlVariable(name: "image_var", value: .value(URL(string: requiredImageURL)!)))
.urlVariable(UrlVariable(name: "image_var", value: .value(URL(string: requiredImageURL)!))),
]
)
}
private func downloadResourcesAndWait(
for divData: DivData,
using preloader: DivDataResourcesPreloader,
@@ -244,19 +249,19 @@ final class DivDataResourcesPreloaderTests: XCTestCase {
}
expectation.fulfill()
}
wait(for: [expectation], timeout: 1.0)
}
private func createDivDataWithNestedItemBuilders() -> DivData {
let outerData: [Any] = [
["type": "container", "items": [
["url": requiredImageURL, "preload_required": true],
["url": optionalImageURL, "preload_required": false]
["url": optionalImageURL, "preload_required": false],
]],
["type": "image", "url": validImageURL, "preload_required": true]
["type": "image", "url": validImageURL, "preload_required": true],
]
let innerImagePrototype = DivCollectionItemBuilder.Prototype(
div: divImage(
id: "inner_image",
@@ -264,7 +269,7 @@ final class DivDataResourcesPreloaderTests: XCTestCase {
preloadRequiredExpression: "@{getBooleanFromDict(item, 'preload_required')}"
)
)
let innerItemBuilder = DivCollectionItemBuilder(
data: .value(outerData.flatMap { item in
guard let dict = item as? [String: Any],
@@ -274,7 +279,7 @@ final class DivDataResourcesPreloaderTests: XCTestCase {
dataElementName: "item",
prototypes: [innerImagePrototype]
)
let containerPrototype = DivCollectionItemBuilder.Prototype(
div: divContainer(
id: "container_from_item_builder",
@@ -282,7 +287,7 @@ final class DivDataResourcesPreloaderTests: XCTestCase {
),
selector: expression("@{getStringFromDict(item, 'type') == 'container'}")
)
let imagePrototype = DivCollectionItemBuilder.Prototype(
div: divImage(
id: "image_from_item_builder",
@@ -291,27 +296,27 @@ final class DivDataResourcesPreloaderTests: XCTestCase {
),
selector: expression("@{getStringFromDict(item, 'type') == 'image'}")
)
let outerItemBuilder = DivCollectionItemBuilder(
data: .value(outerData),
dataElementName: "item",
prototypes: [containerPrototype, imagePrototype]
)
let container = divContainer(
id: "container_with_nested_item_builders",
itemBuilder: outerItemBuilder
)
return divData(container)
}
private func createDivDataWithVariables() -> DivData {
let data: [Any] = [
["type": "image", "use_variable": true],
["type": "image", "url": optionalImageURL, "preload_required": false]
["type": "image", "url": optionalImageURL, "preload_required": false],
]
let variableImagePrototype = DivCollectionItemBuilder.Prototype(
div: divImage(
id: "image_with_variable",
@@ -320,27 +325,27 @@ final class DivDataResourcesPreloaderTests: XCTestCase {
),
selector: expression("@{getStringFromDict(item, 'type') == 'image'}")
)
let itemBuilder = DivCollectionItemBuilder(
data: .value(data),
dataElementName: "item",
prototypes: [variableImagePrototype]
)
let container = divContainer(
id: "container_with_variables",
itemBuilder: itemBuilder
)
return divData(container)
}
private func createDivDataWithMixedItemBuilderResources() -> DivData {
let data: [[String: Any]] = [
["type": "image", "url": requiredImageURL, "preload_required": true],
["type": "image", "url": optionalImageURL, "preload_required": false],
["type": "video", "url": requiredVideoURL, "preload_required": true],
["type": "video", "url": optionalVideoURL, "preload_required": false]
["type": "video", "url": optionalVideoURL, "preload_required": false],
]
let imagePrototype = DivCollectionItemBuilder.Prototype(
@@ -351,7 +356,7 @@ final class DivDataResourcesPreloaderTests: XCTestCase {
),
selector: expression("@{getStringFromDict(it, 'type') == 'image'}")
)
let videoPrototype = DivCollectionItemBuilder.Prototype(
div: divVideo(
id: "video_from_item_builder",
@@ -359,23 +364,23 @@ final class DivDataResourcesPreloaderTests: XCTestCase {
divVideoSource(
mimeType: "video/mp4",
urlExpression: "@{getStringFromDict(it, 'url')}"
)
),
],
preloadRequiredExpression: "@{getBooleanFromDict(it, 'preload_required')}"
),
selector: expression("@{getStringFromDict(it, 'type') == 'video'}")
)
let itemBuilder = DivCollectionItemBuilder(
data: .value(data),
prototypes: [videoPrototype, imagePrototype]
)
let container = divContainer(
id: "container_with_item_builder",
itemBuilder: itemBuilder
)
return divData(container)
}
}
@@ -566,7 +571,7 @@ private func createDivDataWithInvalidURLs() -> DivData {
}
private func testURL(_ path: String, domain: String = "example.com") -> String {
return "https://\(domain)/\(path)"
"https://\(domain)/\(path)"
}
private let requiredImageURL = testURL("required_image.jpg")
@@ -1,6 +1,6 @@
@testable import DivKit
@testable import LayoutKit
import Foundation
@testable import LayoutKit
import VGSL
import XCTest
@@ -101,7 +101,7 @@ public func divImage(
} else {
.value(url(imageUrl))
}
let preloadRequiredValue: Expression<Bool>? = if let preloadRequiredExpression {
expression(preloadRequiredExpression)
} else {
@@ -575,7 +575,6 @@ public func variable(_ name: String, _ value: String) -> DivVariable {
.stringVariable(StringVariable(name: name, value: .value(value)))
}
public func solidBackground(_ color: RGBAColor) -> DivBackground {
.divSolidBackground(DivSolidBackground(color: .value(color)))
}
@@ -591,7 +590,7 @@ public func divVideo(
} else {
preloadRequired.map { .value($0) }
}
return .divVideo(DivVideo(
id: id,
preloadRequired: preloadRequiredValue,
@@ -5,7 +5,7 @@ public final class MockURLResourceRequester: URLResourceRequesting {
public var requestedURLs: [URL] = []
public var shouldSucceed = true
public var customBehavior: ((URL) -> Bool)?
public init() {}
@MainActor
@@ -16,7 +16,7 @@ public final class PhoneMaskFormatter: MaskFormatter {
var stringIndex = rawText.startIndex
var newCursorPosition: CursorPosition?
let mask = findMask(for: rawText)
maskLoop: for element in mask {
if element.isPlaceHolder {
guard stringIndex < rawText.endIndex else {
@@ -28,7 +28,7 @@ public final class PhoneMaskFormatter: MaskFormatter {
break maskLoop
}
}
text.append(rawText[stringIndex])
let textString = String(text)
@@ -42,7 +42,7 @@ public final class PhoneMaskFormatter: MaskFormatter {
text.append(element)
}
}
let textString = String(text)
if let rawCursorPosition {
let cursorIndex = rawCursorPosition.cursorPosition.rawValue
@@ -107,7 +107,7 @@ extension Character {
fileprivate var isPlaceHolder: Bool {
self == "0"
}
fileprivate var isHyphen: Bool {
self == "-"
}
@@ -79,7 +79,7 @@ extension DetachableAnimationBlock: ElementStateUpdating {
if updatedChild === child, animationIn == nil {
return self
}
return Self(
child: updatedChild,
id: id,
@@ -27,7 +27,7 @@ public final class EmptyBlock: BlockWithTraits {
public var isEmpty: Bool {
self == EmptyBlock.zeroSized
}
public func equals(_ other: Block) -> Bool {
guard let other = other as? EmptyBlock else {
return false
@@ -7,7 +7,7 @@ public struct MarksConfigurationModel: Equatable {
let activeMark: RoundedRectangle
let inactiveMark: RoundedRectangle
let layoutDirection: UserInterfaceLayoutDirection
public init(
minValue: CGFloat,
maxValue: CGFloat,
@@ -21,7 +21,7 @@ public struct MarksConfigurationModel: Equatable {
self.inactiveMark = inactiveMark
self.layoutDirection = layoutDirection
}
public static let empty = Self(
minValue: 0,
maxValue: 0,
@@ -4,19 +4,19 @@ final class MarksLayer: CALayer {
var firstThumbProgress: CGFloat = 0
var secondThumbProgress: CGFloat = 0
var configuration = MarksConfiguration.empty
private var maxValue: CGFloat {
configuration.modelConfiguration.maxValue
}
private var minValue: CGFloat {
configuration.modelConfiguration.minValue
}
private var activeMark: MarksConfigurationModel.RoundedRectangle {
configuration.modelConfiguration.activeMark
}
private var inactiveMark: MarksConfigurationModel.RoundedRectangle {
configuration.modelConfiguration.inactiveMark
}
@@ -99,7 +99,7 @@ public struct SliderModel: Equatable {
public let layoutDirection: UserInterfaceLayoutDirection
public let path: UIElementPath?
public let isEnabled: Bool
public var marksConfiguration: MarksConfiguration {
MarksConfiguration(
modelConfiguration: marksModelConfiguration,
@@ -110,7 +110,7 @@ public struct SliderModel: Equatable {
public var valueRange: Int {
maxValue - minValue
}
private let marksModelConfiguration: MarksConfigurationModel
public var sliderHeight: CGFloat {
@@ -196,8 +196,9 @@ public class DefaultTooltipManager: TooltipManager {
guard tooltipWindowManager == nil else { return }
let scenes = UIApplication.shared.connectedScenes
guard let windowScene = scenes.first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene,
let keyWindow = windowScene.windows.first(where: { $0.isKeyWindow }) else {
guard let windowScene = scenes
.first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene,
let keyWindow = windowScene.windows.first(where: { $0.isKeyWindow }) else {
return
}
@@ -123,7 +123,7 @@ private class GradientContainerView: UIView {
private let model: TextBlock.GradientModel
private let rangedTextWithColorTextBlockView: TextBlockView
let gradientView: UIView
init?(model: TextBlock.GradientModel?, mask: UIView) {
guard let model else {
return nil
@@ -131,27 +131,29 @@ private class GradientContainerView: UIView {
self.model = model
gradientView = model.gradient.uiView
gradientView.mask = mask
rangedTextWithColorTextBlockView = TextBlockView()
super.init(frame: .zero)
gradientView.addSubview(rangedTextWithColorTextBlockView)
addSubview(gradientView)
}
@available(*, unavailable)
required init(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
gradientView.frame = bounds
rangedTextWithColorTextBlockView.frame = bounds
}
func configureRangedTextColor(textBlockViewModel: TextBlockView.Model) {
rangedTextWithColorTextBlockView.model = textBlockViewModel.updated(with: model.rangedTextWithColor)
rangedTextWithColorTextBlockView.model = textBlockViewModel
.updated(with: model.rangedTextWithColor)
}
}
@@ -358,7 +358,7 @@ private final class TextInputBlockView: BlockView, VisibleBoundsTrackingLeaf {
setSmartInsertDeleteType(.default)
}
}
func setSmartInsertDeleteType(_ type: UITextSmartInsertDeleteType) {
singleLineInput.smartInsertDeleteType = type
multiLineInput.smartInsertDeleteType = type
@@ -909,7 +909,7 @@ extension UITextField {
guard let selectedRange = self.selectedTextRange else {
return nil
}
let beginning = self.beginningOfDocument
let startPosition = self.offset(from: beginning, to: selectedRange.start)
let endPosition = self.offset(from: beginning, to: selectedRange.end)
@@ -939,12 +939,16 @@ private class PatchedUITextField: UITextField {
guard let string = UIPasteboard.general.string else {
return
}
guard let range = self.selectedNSRange else {
return
}
if (self.delegate as? TextInputBlockView)?.textField(self, shouldChangeCharactersIn: range, replacementString: string) == true {
if (self.delegate as? TextInputBlockView)?.textField(
self,
shouldChangeCharactersIn: range,
replacementString: string
) == true {
super.paste(sender)
}
}
@@ -962,8 +966,12 @@ private class PatchedUITextView: UITextView {
guard let string = UIPasteboard.general.string else {
return
}
if (self.delegate as? TextInputBlockView)?.textView(self, shouldChangeTextIn: self.selectedRange, replacementText: string) == true {
if (self.delegate as? TextInputBlockView)?.textView(
self,
shouldChangeTextIn: self.selectedRange,
replacementText: string
) == true {
isPasting = true
super.paste(sender)
}
@@ -178,16 +178,16 @@ final class TabContentsView: BlockView {
relativeContentOffset = offset
updateSelectedPageIndexIfNeeded(relativeContentOffset)
}
func updateSelectedPageIndexIfNeeded(_ idx: CGFloat) {
guard selectedPageIndex.isApproximatelyNotEqualTo(idx) else {
delegate?.tabContentsViewDidEndAnimation()
return
}
selectedPageIndex = idx
}
private func updateContentOffset(animated: Bool = true) {
collectionView.setContentOffset(selectedPageContentOffset, animated: animated)
}
@@ -203,7 +203,7 @@ final class TabContentsView: BlockView {
collectionView.frame = bounds
updateContentOffset(animated: false)
}
let newLayout = TabContentsViewLayout(
pages: model.pages.map(\.block),
footer: model.footer,
@@ -232,7 +232,7 @@ final class TabContentsView: BlockView {
return
}
let index = isIntermediate ? selectedPageIndex : roundedIndex
updateSelectedPageIndexIfNeeded(index)
updatesDelegate?.onSelectedPageIndexChanged(index, inModel: model)
let state = TabViewState(selectedPageIndex: index, countOfPages: countOfPages)
@@ -46,7 +46,7 @@ final class TabListView: UIView {
contentSize: CGSize,
selection: CGFloat
)?
private var animationInfo: AnimationInfo? {
didSet {
setNeedsLayout()
@@ -66,7 +66,7 @@ final class TabListView: UIView {
init() {
collectionView = makeCollectionView(layout: collectionViewLayout)
delegate = TabListViewDelegate(collectionView: collectionView)
super.init(frame: .zero)
clipsToBounds = true
@@ -176,7 +176,7 @@ final class TabListView: UIView {
func endScrollingAnimation() {
animationInfo = nil
}
func setInitialModel(_ model: TabTitlesViewModel) {
guard !setInitialModel else { return }
self.model = model
@@ -26,7 +26,7 @@ public final class TabbedPagesView: BlockView, VisibleBoundsTrackingContainer {
public var effectiveBackgroundColor: UIColor? { tabContentsView.effectiveBackgroundColor }
public var layoutReporter: LayoutReporter?
public func configure(
model: TabViewModel,
state: TabViewState,
@@ -1,5 +1,5 @@
import SwiftUI
import DivKit
import SwiftUI
struct ContentView: View {
@State private var isPresented: Bool = false