Skip to content

Commit

Permalink
Merge pull request #677 from adevinta/204-component-slider-develop
Browse files Browse the repository at this point in the history
[Slider#204] Added snapshot tests + slider SwiftUI
  • Loading branch information
LouisBorleeAdevinta authored Jan 10, 2024
2 parents 21c1f51 + 1e9b92a commit 19c5e7d
Show file tree
Hide file tree
Showing 25 changed files with 1,281 additions and 436 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import UIKit

extension UIView {

/// CGColors need to be refreshed on trait changes
func setBorderColor(from colorToken: any ColorToken) {
self.layer.borderColor = colorToken.uiColor.cgColor
}
Expand Down
50 changes: 50 additions & 0 deletions core/Sources/Components/Slider/Handle/View/SliderHandle.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//
// SliderHandle.swift
// SparkCore
//
// Created by louis.borlee on 13/12/2023.
// Copyright © 2023 Adevinta. All rights reserved.
//

import SwiftUI

struct SliderHandle: View {

@ObservedObject var viewModel: SliderHandleViewModel

@Binding var isEditing: Bool

init(viewModel: SliderHandleViewModel,
isEditing: Binding<Bool>) {
self.viewModel = viewModel
_isEditing = isEditing
}

var body: some View {
ZStack(alignment: .center) {
if self.isEditing {
self.activeIndicatorStroke()
self.activeIndicatorHalo()
}
Circle()
.fill()
.foregroundColor(self.viewModel.color.color)
.frame(width: SliderConstants.handleSize.width, height: SliderConstants.handleSize.height)
}
.frame(width: SliderConstants.handleSize.width, height: SliderConstants.handleSize.height)
}

@ViewBuilder
private func activeIndicatorStroke() -> some View {
Circle()
.strokeBorder(self.viewModel.color.color, lineWidth: 1.0)
.frame(width: SliderConstants.activeIndicatorSize.width + 1, height: SliderConstants.activeIndicatorSize.height + 1)
}

@ViewBuilder
private func activeIndicatorHalo() -> some View {
Circle()
.fill(self.viewModel.activeIndicatorColor)
.frame(width: SliderConstants.activeIndicatorSize.width, height: SliderConstants.activeIndicatorSize.height)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,12 @@ final class SliderHandleUIControl: UIControl {
supercontrol.endTracking(touch, with: event)
}
}

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
// CGColors need to be refreshed on trait changes
if self.traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) {
self.activeIndicatorView.setBorderColor(from: self.viewModel.color)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
//

import Foundation
import Combine

final class SliderHandleViewModel {
final class SliderHandleViewModel: ObservableObject {

@Published var color: any ColorToken
@Published var activeIndicatorColor: any ColorToken
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// SliderGetClosestValueUseCase.swift
// SparkCore
//
// Created by louis.borlee on 19/12/2023.
// Copyright © 2023 Adevinta. All rights reserved.
//

import Foundation

protocol SliderGetClosestValueUseCasable {
func execute<V>(value: V, in values: [V]) -> V where V: BinaryFloatingPoint
}

final class SliderGetClosestValueUseCase: SliderGetClosestValueUseCasable {
func execute<V>(value: V, in values: [V]) -> V where V : BinaryFloatingPoint {
guard let closestValue = values.min(by: {
return abs($0 - value) <= abs($1 - value)
}) else { return value }
return closestValue
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// File.swift
// SparkCoreUnitTests
//
// Created by louis.borlee on 02/01/2024.
// Copyright © 2024 Adevinta. All rights reserved.
//

import Foundation
@testable import SparkCore

final class SliderGetClosestValueUseCasableMock<U>: SparkCore.SliderGetClosestValueUseCasable where U: BinaryFloatingPoint {


// MARK: - Initialization

init() {}

// MARK: - execute<V>


var executeWithValueAndValuesCallsCount = 0
var executeWithValueAndValuesCalled: Bool {
return executeWithValueAndValuesCallsCount > 0
}
var executeWithValueAndValuesReceivedArguments: (value: U, values: [U])?
var executeWithValueAndValuesReceivedInvocations: [(value: U, values: [U])] = []
var executeWithValueAndValuesReturnValue: U!
var _executeWithValueAndValues: ((U, [U]) -> U?)?

func execute<V>(value: V, in values: [V]) -> V where V: BinaryFloatingPoint {
guard let castedValue = value as? U,
let castedValues = values as? [U] else {
fatalError("\(U.self) is not equal to \(V.self)")
}
executeWithValueAndValuesCallsCount += 1
executeWithValueAndValuesReceivedArguments = (value: castedValue, values: castedValues)
executeWithValueAndValuesReceivedInvocations.append((value: castedValue, values: castedValues))
return (_executeWithValueAndValues.map({ $0(castedValue, castedValues) }) ?? executeWithValueAndValuesReturnValue) as! V
}

// MARK: Reset

func reset() {
executeWithValueAndValuesCallsCount = 0
executeWithValueAndValuesReceivedArguments = nil
executeWithValueAndValuesReceivedInvocations = []
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SliderGetClosestValueUseCaseTests.swift
// SparkCoreUnitTests
//
// Created by louis.borlee on 23/11/2023.
// Created by louis.borlee on 19/12/2023.
// Copyright © 2023 Adevinta. All rights reserved.
//

Expand All @@ -11,129 +11,73 @@ import XCTest

final class SliderGetClosestValueUseCaseTests: XCTestCase {

func test_execute_createValuesFromStep_receivedValues() throws {
func test_execute_under_minimum_value() {
// GIVEN
let createValuesFromStepUseCaseMock = SliderCreateValuesFromStepsUseCasableGeneratedMock()
createValuesFromStepUseCaseMock._executeWithFromAndToAndSteps = { _, _ , _ throws -> [Float] in
return [1, 2, 3]
}
let sut = SliderGetClosestValueUseCase(createValuesFromStepsUseCase: createValuesFromStepUseCaseMock)
let useCase = SliderGetClosestValueUseCase()
let values: [CGFloat] = [10, 20, 30]
let value: CGFloat = 0
let expectedClosestValue: CGFloat = 10

// WHEN
_ = sut.execute(from: 2, to: 20, withSteps: 1, fromValue: 15)
let closestValue = useCase.execute(value: value, in: values)

// THEN
XCTAssertEqual(createValuesFromStepUseCaseMock.executeWithFromAndToAndStepsCallsCount,
1,
"executeWithFromAndToAndStepsCallsCount should be called once")
let receivedArguments = try XCTUnwrap(createValuesFromStepUseCaseMock.executeWithFromAndToAndStepsReceivedArguments)
XCTAssertEqual(receivedArguments.from, 2, "Wrong receivedArguments.from")
XCTAssertEqual(receivedArguments.to, 20, "Wrong receivedArguments.to")
XCTAssertEqual(receivedArguments.steps, 1, "Wrong receivedArguments.steps")
XCTAssertEqual(closestValue, expectedClosestValue)
}

func test_execute_createValuesFromStep_throws_invalidRange() {
func test_execute_over_maximum_value() {
// GIVEN
let createValuesFromStepUseCaseMock = SliderCreateValuesFromStepsUseCasableGeneratedMock()
createValuesFromStepUseCaseMock._executeWithFromAndToAndSteps = { _, _ , _ throws -> [Float] in
throw SliderCreateValuesFromStepsUseCasableError.invalidRange
}
let sut = SliderGetClosestValueUseCase(createValuesFromStepsUseCase: createValuesFromStepUseCaseMock)
let useCase = SliderGetClosestValueUseCase()
let values: [Double] = [0.1, 0.2, 0.3]
let value: Double = 0.4
let expectedClosestValue: Double = 0.3

// WHEN
let value = sut.execute(from: 0, to: 100, withSteps: 10, fromValue: 40)
let closestValue = useCase.execute(value: value, in: values)

// THEN
XCTAssertEqual(value, 40)
XCTAssertEqual(closestValue, expectedClosestValue)
}

func test_execute_createValuesFromStep_throws_invalid() {
func test_execute_lower_rounding() {
// GIVEN
let createValuesFromStepUseCaseMock = SliderCreateValuesFromStepsUseCasableGeneratedMock()
createValuesFromStepUseCaseMock._executeWithFromAndToAndSteps = { _, _ , _ throws -> [Float] in
throw SliderCreateValuesFromStepsUseCasableError.invalidStep
}
let sut = SliderGetClosestValueUseCase(createValuesFromStepsUseCase: createValuesFromStepUseCaseMock)
let useCase = SliderGetClosestValueUseCase()
let values: [Float] = [0.0, 0.50, 1.0, 1.50]
let value: Float = 0.74
let expectedClosestValue: Float = 0.50

// WHEN
let value = sut.execute(from: 0, to: 100, withSteps: 10, fromValue: 30)
let closestValue = useCase.execute(value: value, in: values)

// THEN
XCTAssertEqual(value, 30)
XCTAssertEqual(closestValue, expectedClosestValue)
}

func test_execute_createValuesFromStep_empty_values() {
func test_execute_upper_rounding() {
// GIVEN
let createValuesFromStepUseCaseMock = SliderCreateValuesFromStepsUseCasableGeneratedMock()
createValuesFromStepUseCaseMock._executeWithFromAndToAndSteps = { _, _ , _ throws -> [Float] in
return []
}
let sut = SliderGetClosestValueUseCase(createValuesFromStepsUseCase: createValuesFromStepUseCaseMock)
let useCase = SliderGetClosestValueUseCase()
let values: [CGFloat] = [0.0, 0.50, 1.0, 1.50]
let value: CGFloat = 0.76
let expectedClosestValue: CGFloat = 1.0

// WHEN
let value = sut.execute(from: 0, to: 100, withSteps: 10, fromValue: 60)
let closestValue = useCase.execute(value: value, in: values)

// THEN
XCTAssertEqual(value, 60)
XCTAssertEqual(closestValue, expectedClosestValue)
}

func test_execute_inbetween() {
// GIVEN
let createValuesFromStepUseCaseMock = SliderCreateValuesFromStepsUseCasableGeneratedMock()
createValuesFromStepUseCaseMock._executeWithFromAndToAndSteps = { _, _ , _ throws -> [Float] in
return [
0,
10,
20,
30,
40,
50
]
}
let sut = SliderGetClosestValueUseCase(createValuesFromStepsUseCase: createValuesFromStepUseCaseMock)
let useCase = SliderGetClosestValueUseCase()
let values: [CGFloat] = [0.0, 0.50, 1.0, 1.50]
let value: CGFloat = 0.75
let expectedClosestValue: CGFloat = 1.0

// WHEN
let value = sut.execute(from: 0, to: 50, withSteps: 0.1, fromValue: 25)
let closestValue = useCase.execute(value: value, in: values)

// THEN
XCTAssertEqual(value, 30)
}

func test_execute_lower() {
// GIVEN
let createValuesFromStepUseCaseMock = SliderCreateValuesFromStepsUseCasableGeneratedMock()
createValuesFromStepUseCaseMock._executeWithFromAndToAndSteps = { _, _ , _ throws -> [Float] in
return [
0.0,
0.5
]
}
let sut = SliderGetClosestValueUseCase(createValuesFromStepsUseCase: createValuesFromStepUseCaseMock)

// WHEN
let value = sut.execute(from: 0, to: 0.5, withSteps: 0.1, fromValue: 0.249)

// THEN
XCTAssertEqual(value, 0.0)
}

func test_execute_upper() {
// GIVEN
let createValuesFromStepUseCaseMock = SliderCreateValuesFromStepsUseCasableGeneratedMock()
createValuesFromStepUseCaseMock._executeWithFromAndToAndSteps = { _, _ , _ throws -> [Float] in
return [
0.0,
0.1,
0.4,
0.5
]
}
let sut = SliderGetClosestValueUseCase(createValuesFromStepsUseCase: createValuesFromStepUseCaseMock)

// WHEN
let value = sut.execute(from: 0, to: 0.5, withSteps: 0.1, fromValue: 0.251)

// THEN
XCTAssertEqual(value, 0.4)
XCTAssertEqual(closestValue, expectedClosestValue)
}
}
Loading

0 comments on commit 19c5e7d

Please sign in to comment.