mirror of
https://github.com/fxm90/GradientLoadingBar.git
synced 2026-06-16 12:24:31 +00:00
Merge branch 'feature/add-support-for-swiftui' into feature/adapt-uikit-animation-to-swiftui-variant
# Conflicts: # Example/ExampleTests/ViewModel/GradientActivityIndicatorViewModelTestCase.swift # GradientLoadingBar/Feature/GradientActivityIndicatorView/GradientActivityIndicatorViewModel.swift
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
//
|
||||
// GradientActivityIndicatorViewModelTestCase.swift
|
||||
// ExampleTests
|
||||
//
|
||||
// Created by Felix Mau on 26.08.19.
|
||||
// Copyright © 2019 Felix Mau. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import LightweightObservable
|
||||
|
||||
@testable import GradientLoadingBar
|
||||
|
||||
// swiftlint:disable:next type_name
|
||||
final class GradientActivityIndicatorViewModelTestCase: XCTestCase {
|
||||
// MARK: - Private properties
|
||||
|
||||
private var viewModel: GradientActivityIndicatorViewModel!
|
||||
|
||||
// MARK: - Public methods
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
|
||||
viewModel = GradientActivityIndicatorViewModel()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
viewModel = nil
|
||||
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
// MARK: - Test property `isHidden`
|
||||
|
||||
func test_settingIsHidden_toTrue_shouldUpdateIsAnimatingSubject_withFalse() throws {
|
||||
// Given
|
||||
var receivedIsAnimating: Bool?
|
||||
let disposable = viewModel.isAnimating.subscribe { isAnimating, _ in
|
||||
receivedIsAnimating = isAnimating
|
||||
}
|
||||
|
||||
let isHidden = true
|
||||
|
||||
// When
|
||||
withExtendedLifetime(disposable) {
|
||||
viewModel.isHidden = isHidden
|
||||
}
|
||||
|
||||
// Then
|
||||
let expectedIsAnimating = !isHidden
|
||||
XCTAssertEqual(receivedIsAnimating, expectedIsAnimating)
|
||||
}
|
||||
|
||||
func test_settingIsHidden_toFalse_shouldUpdateIsAnimatingSubject_withTrue() throws {
|
||||
// Given
|
||||
var receivedIsAnimating: Bool?
|
||||
let disposable = viewModel.isAnimating.subscribe { isAnimating, _ in
|
||||
receivedIsAnimating = isAnimating
|
||||
}
|
||||
|
||||
let isHidden = false
|
||||
|
||||
// When
|
||||
withExtendedLifetime(disposable) {
|
||||
viewModel.isHidden = isHidden
|
||||
}
|
||||
|
||||
// Then
|
||||
let expectedIsAnimating = !isHidden
|
||||
XCTAssertEqual(receivedIsAnimating, expectedIsAnimating)
|
||||
}
|
||||
|
||||
// MARK: - Test property `bounds`
|
||||
|
||||
func test_settingBounds_shouldUpdateGradientLayerSizeUpdate() {
|
||||
// Given
|
||||
var receivedSizeUpdate: GradientActivityIndicatorViewModel.SizeUpdate?
|
||||
let disposable = viewModel.gradientLayerSizeUpdate.subscribe { sizeUpdate, _ in
|
||||
receivedSizeUpdate = sizeUpdate
|
||||
}
|
||||
|
||||
let size = CGSize(width: .random(in: 1 ... 100), height: .random(in: 1 ... 100))
|
||||
let bounds = CGRect(origin: .zero, size: size)
|
||||
|
||||
// When
|
||||
withExtendedLifetime(disposable) {
|
||||
viewModel.bounds = bounds
|
||||
}
|
||||
|
||||
// Then
|
||||
let expectedSizeUpdate =
|
||||
GradientActivityIndicatorViewModel.SizeUpdate(frame: CGRect(x: 0, y: 0, width: size.width * 3, height: size.height),
|
||||
fromValue: size.width * -2)
|
||||
|
||||
XCTAssertEqual(receivedSizeUpdate, expectedSizeUpdate)
|
||||
}
|
||||
|
||||
func test_settingBounds_shouldRestartAnimation() {
|
||||
// Given
|
||||
var receivedIsAnimating = [Bool]()
|
||||
let disposable = viewModel.isAnimating.subscribe { isAnimating, _ in
|
||||
receivedIsAnimating.append(isAnimating)
|
||||
}
|
||||
receivedIsAnimating.removeAll()
|
||||
|
||||
// When
|
||||
withExtendedLifetime(disposable) {
|
||||
viewModel.bounds = CGRect(origin: .zero, size: .zero)
|
||||
}
|
||||
|
||||
// Then
|
||||
let expectedIsAnimating = [false, true]
|
||||
XCTAssertEqual(receivedIsAnimating, expectedIsAnimating)
|
||||
}
|
||||
|
||||
func test_settingBounds_shouldNotRestartAnimation_dueToViewIsHidden() {
|
||||
// Given
|
||||
var receivedIsAnimating = [Bool]()
|
||||
let disposable = viewModel.isAnimating.subscribe { isAnimating, _ in
|
||||
receivedIsAnimating.append(isAnimating)
|
||||
}
|
||||
|
||||
viewModel.isHidden = true
|
||||
receivedIsAnimating.removeAll()
|
||||
|
||||
// When
|
||||
withExtendedLifetime(disposable) {
|
||||
viewModel.bounds = CGRect(origin: .zero, size: .zero)
|
||||
}
|
||||
|
||||
// Then
|
||||
XCTAssertTrue(receivedIsAnimating.isEmpty)
|
||||
}
|
||||
|
||||
// MARK: - Test property `gradientColors`
|
||||
|
||||
func test_settingGradientColors_shouldUpdateGradientLayerColors() {
|
||||
// Given
|
||||
var receivedGradientLayerColors: [CGColor]?
|
||||
let disposable = viewModel.gradientLayerColors.subscribe { gradientLayerColors, _ in
|
||||
receivedGradientLayerColors = gradientLayerColors
|
||||
}
|
||||
|
||||
let gradientColors: [UIColor] = [.red, .yellow, .green]
|
||||
|
||||
// When
|
||||
withExtendedLifetime(disposable) {
|
||||
viewModel.gradientColors = gradientColors
|
||||
}
|
||||
|
||||
// Then
|
||||
let expectedGradientLayerColors = [UIColor.red, .yellow, .green, .yellow, .red, .yellow, .green].map { $0.cgColor }
|
||||
XCTAssertEqual(receivedGradientLayerColors, expectedGradientLayerColors)
|
||||
}
|
||||
|
||||
// MARK: - Test property `progressAnimationDuration`
|
||||
|
||||
func test_settingProgressAnimationDuration_shouldUpdategGradientLayerAnimationDuration() {
|
||||
// Given
|
||||
var receivedGradientLayerAnimationDuration: TimeInterval?
|
||||
let disposable = viewModel.gradientLayerAnimationDuration.subscribe { gradientLayerAnimationDuration, _ in
|
||||
receivedGradientLayerAnimationDuration = gradientLayerAnimationDuration
|
||||
}
|
||||
|
||||
let progressAnimationDuration: TimeInterval = .random(in: 0 ... 100)
|
||||
|
||||
// When
|
||||
withExtendedLifetime(disposable) {
|
||||
viewModel.progressAnimationDuration = progressAnimationDuration
|
||||
}
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(receivedGradientLayerAnimationDuration, progressAnimationDuration)
|
||||
}
|
||||
|
||||
func test_settingProgressAnimationDuration_shouldRestartAnimation() {
|
||||
// Given
|
||||
var receivedIsAnimating = [Bool]()
|
||||
let disposable = viewModel.isAnimating.subscribe { isAnimating, _ in
|
||||
receivedIsAnimating.append(isAnimating)
|
||||
}
|
||||
receivedIsAnimating.removeAll()
|
||||
|
||||
// When
|
||||
withExtendedLifetime(disposable) {
|
||||
viewModel.progressAnimationDuration = .random(in: 0 ... 100)
|
||||
}
|
||||
|
||||
// Then
|
||||
let expectedIsAnimating = [false, true]
|
||||
XCTAssertEqual(receivedIsAnimating, expectedIsAnimating)
|
||||
}
|
||||
|
||||
func test_settingProgressAnimationDuration_shouldNotRestartAnimation_dueToViewIsHidden() {
|
||||
// Given
|
||||
var receivedIsAnimating = [Bool]()
|
||||
let disposable = viewModel.isAnimating.subscribe { isAnimating, _ in
|
||||
receivedIsAnimating.append(isAnimating)
|
||||
}
|
||||
|
||||
viewModel.isHidden = true
|
||||
receivedIsAnimating.removeAll()
|
||||
|
||||
// When
|
||||
withExtendedLifetime(disposable) {
|
||||
viewModel.progressAnimationDuration = .random(in: 0 ... 100)
|
||||
}
|
||||
|
||||
// Then
|
||||
XCTAssertTrue(receivedIsAnimating.isEmpty)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
//
|
||||
// GradientLoadingBarView+ViewModelTestCase.swift
|
||||
// ExampleTests
|
||||
//
|
||||
// Created by Felix Mau on 22.03.22.
|
||||
// Copyright © 2022 Felix Mau. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import SwiftUI
|
||||
|
||||
@testable import GradientLoadingBar
|
||||
|
||||
// swiftlint:disable:next type_name
|
||||
final class GradientLoadingBarView_ViewModelTestCase: XCTestCase {
|
||||
// MARK: - Test property `gradientColors`
|
||||
|
||||
func test_initialGradientColors_shouldIncludeReversedGradientColors() {
|
||||
// Given
|
||||
let gradientColors: [Color] = [.red, .yellow, .green]
|
||||
|
||||
// When
|
||||
let viewModel = GradientLoadingBarView.ViewModel(gradientColors: gradientColors, progressDuration: 1)
|
||||
|
||||
// Then
|
||||
let expectedGradientColors: [Color] = [.red, .yellow, .green, .yellow, .red, .yellow, .green]
|
||||
XCTAssertEqual(viewModel.gradientColors, expectedGradientColors)
|
||||
}
|
||||
|
||||
// MARK: - Test property `horizontalOffset`
|
||||
|
||||
func test_initialHorizontalOffset_shouldBeZero() {
|
||||
// When
|
||||
let viewModel = GradientLoadingBarView.ViewModel(gradientColors: [], progressDuration: 1)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(viewModel.horizontalOffset, 0)
|
||||
}
|
||||
|
||||
func test_settingSize_shouldUpdateHorizontalOffset() {
|
||||
// Given
|
||||
let viewModel = GradientLoadingBarView.ViewModel(gradientColors: [], progressDuration: 1)
|
||||
let size = CGSize(width: .random(in: 1 ... 100_000), height: .random(in: 1 ... 100_000))
|
||||
|
||||
var receivedValues = [CGFloat]()
|
||||
let subscription = viewModel.$horizontalOffset.sink {
|
||||
receivedValues.append($0)
|
||||
}
|
||||
|
||||
withExtendedLifetime(subscription) {
|
||||
// When
|
||||
viewModel.size = size
|
||||
}
|
||||
|
||||
// Then
|
||||
let expectedValues = [0, -size.width, size.width]
|
||||
XCTAssertEqual(receivedValues, expectedValues)
|
||||
}
|
||||
|
||||
// MARK: - Test property `gradientWidth`
|
||||
|
||||
func test_initialGradientWidth_shouldBeZero() {
|
||||
// When
|
||||
let viewModel = GradientLoadingBarView.ViewModel(gradientColors: [], progressDuration: 1)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(viewModel.gradientWidth, 0)
|
||||
}
|
||||
|
||||
func test_settingSize_shouldUpdateGradientWidth() {
|
||||
// Given
|
||||
let viewModel = GradientLoadingBarView.ViewModel(gradientColors: [], progressDuration: 1)
|
||||
let size = CGSize(width: .random(in: 1 ... 100_000), height: .random(in: 1 ... 100_000))
|
||||
|
||||
// When
|
||||
viewModel.size = size
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(viewModel.gradientWidth, size.width * 3)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
//
|
||||
// GradientLoadingBarViewModelTestCase.swift
|
||||
// ExampleTests
|
||||
//
|
||||
// Created by Felix Mau on 26.12.17.
|
||||
// Copyright © 2017 Felix Mau. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import LightweightObservable
|
||||
|
||||
@testable import GradientLoadingBar
|
||||
|
||||
final class GradientLoadingBarViewModelTestCase: XCTestCase {
|
||||
// MARK: - Private properties
|
||||
|
||||
private var sharedApplicationMock: SharedApplicationMock!
|
||||
private var notificationCenter: NotificationCenter!
|
||||
|
||||
// MARK: - Public methods
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
|
||||
sharedApplicationMock = SharedApplicationMock()
|
||||
notificationCenter = NotificationCenter()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
notificationCenter = nil
|
||||
sharedApplicationMock = nil
|
||||
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
// MARK: - Test observable `superview`
|
||||
|
||||
func test_initializer_shouldSetupSuperviewObservable_withNil() throws {
|
||||
// When
|
||||
let viewModel = GradientLoadingBarViewModel(sharedApplication: sharedApplicationMock,
|
||||
notificationCenter: notificationCenter)
|
||||
|
||||
// Then
|
||||
let variable = try XCTUnwrap(viewModel.superview as? Variable, "Cast `Observable` instance to `Variable` in order to validate the initial value.")
|
||||
XCTAssertNil(variable.value)
|
||||
}
|
||||
|
||||
func test_initializer_shouldSetupSuperviewObservable_withKeyWindow() {
|
||||
// Given
|
||||
let keyWindow = UIWindow()
|
||||
sharedApplicationMock.keyWindowInConnectedScenes = keyWindow
|
||||
|
||||
// When
|
||||
let viewModel = GradientLoadingBarViewModel(sharedApplication: sharedApplicationMock,
|
||||
notificationCenter: notificationCenter)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(viewModel.superview.value, keyWindow)
|
||||
}
|
||||
|
||||
func test_initializer_shouldSetupSuperviewObservable_afterUIWindowDidBecomeKeyNotification() {
|
||||
// Given
|
||||
sharedApplicationMock.keyWindowInConnectedScenes = nil
|
||||
|
||||
let viewModel = GradientLoadingBarViewModel(sharedApplication: sharedApplicationMock,
|
||||
notificationCenter: notificationCenter)
|
||||
|
||||
// When
|
||||
let keyWindow = UIWindow()
|
||||
sharedApplicationMock.keyWindowInConnectedScenes = keyWindow
|
||||
|
||||
notificationCenter.post(name: UIWindow.didBecomeKeyNotification,
|
||||
object: nil)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(viewModel.superview.value, keyWindow)
|
||||
}
|
||||
|
||||
func test_deinit_shouldResetSuperviewObservable_withNil() {
|
||||
// Given
|
||||
let keyWindow = UIWindow()
|
||||
sharedApplicationMock.keyWindowInConnectedScenes = keyWindow
|
||||
|
||||
var viewModel: GradientLoadingBarViewModel? = GradientLoadingBarViewModel(sharedApplication: sharedApplicationMock,
|
||||
notificationCenter: notificationCenter)
|
||||
|
||||
let expectation = expectation(description: "Expected observer to be informed to reset superview to nil.")
|
||||
var disposeBag = DisposeBag()
|
||||
|
||||
// As we've just initialized the view model it has to exist at this point, and therefore we can "safely" use force-unwrapping here.
|
||||
// swiftlint:disable:next force_unwrapping
|
||||
viewModel!.superview.subscribe { newSuperview, _ in
|
||||
guard newSuperview == nil else {
|
||||
// Skip initial call to observer.
|
||||
return
|
||||
}
|
||||
|
||||
expectation.fulfill()
|
||||
}.disposed(by: &disposeBag)
|
||||
|
||||
// When
|
||||
viewModel = nil
|
||||
|
||||
// Then
|
||||
wait(for: [expectation], timeout: 0.1)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Mocks
|
||||
|
||||
private final class SharedApplicationMock: UIApplicationProtocol {
|
||||
var keyWindowInConnectedScenes: UIWindow?
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
//
|
||||
// NotchGradientLoadingBarViewModelTestCase.swift
|
||||
// ExampleTests
|
||||
//
|
||||
// Created by Felix Mau on 26.12.17.
|
||||
// Copyright © 2017 Felix Mau. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
@testable import GradientLoadingBar
|
||||
|
||||
final class NotchGradientLoadingBarViewModelTestCase: XCTestCase {
|
||||
func test_initializer_shouldSetSafeAreaDevice_toIPhoneX() {
|
||||
// Given
|
||||
let deviceIdentifiers = ["iPhone10,3", "iPhone10,6", "iPhone11,2", "iPhone11,4", "iPhone11,6"]
|
||||
deviceIdentifiers.forEach { deviceIdentifier in
|
||||
|
||||
// When
|
||||
let viewModel = NotchGradientLoadingBarViewModel(deviceIdentifier: deviceIdentifier)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(viewModel.safeAreaDevice, .iPhoneX)
|
||||
}
|
||||
}
|
||||
|
||||
func test_initializer_shouldSetSafeAreaDevice_toIPhoneXR() {
|
||||
// Given
|
||||
let deviceIdentifiers = ["iPhone11,8"]
|
||||
deviceIdentifiers.forEach { deviceIdentifier in
|
||||
|
||||
// When
|
||||
let viewModel = NotchGradientLoadingBarViewModel(deviceIdentifier: deviceIdentifier)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(viewModel.safeAreaDevice, .iPhoneXR)
|
||||
}
|
||||
}
|
||||
|
||||
func test_initializer_shouldSetSafeAreaDevice_toIPhone11() {
|
||||
// Given
|
||||
let deviceIdentifiers = ["iPhone12,1", "iPhone12,3", "iPhone12,5"]
|
||||
deviceIdentifiers.forEach { deviceIdentifier in
|
||||
|
||||
// When
|
||||
let viewModel = NotchGradientLoadingBarViewModel(deviceIdentifier: deviceIdentifier)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(viewModel.safeAreaDevice, .iPhone11)
|
||||
}
|
||||
}
|
||||
|
||||
func test_initializer_shouldSetSafeAreaDevice_toIPhone12() {
|
||||
// Given
|
||||
let deviceIdentifiers = ["iPhone13,1", "iPhone13,2", "iPhone13,3", "iPhone13,4"]
|
||||
deviceIdentifiers.forEach { deviceIdentifier in
|
||||
|
||||
// When
|
||||
let viewModel = NotchGradientLoadingBarViewModel(deviceIdentifier: deviceIdentifier)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(viewModel.safeAreaDevice, .iPhone12)
|
||||
}
|
||||
}
|
||||
|
||||
func test_initializer_shouldSetSafeAreaDevice_toIPhone13() {
|
||||
// Given
|
||||
let deviceIdentifiers = ["iPhone14,4", "iPhone14,5", "iPhone14,2", "iPhone14,3"]
|
||||
deviceIdentifiers.forEach { deviceIdentifier in
|
||||
|
||||
// When
|
||||
let viewModel = NotchGradientLoadingBarViewModel(deviceIdentifier: deviceIdentifier)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(viewModel.safeAreaDevice, .iPhone13)
|
||||
}
|
||||
}
|
||||
|
||||
func test_initializer_shouldSetSafeAreaDevice_toUnknown() {
|
||||
// Given
|
||||
let deviceIdentifier = "Foo-Bar-🤡"
|
||||
|
||||
// When
|
||||
let viewModel = NotchGradientLoadingBarViewModel(deviceIdentifier: deviceIdentifier)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(viewModel.safeAreaDevice, .unknown)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user