Add UIVariation, UIVariationApplying and unit tests

This commit is contained in:
Wojciech Nagrodzki 2021-06-03 11:28:53 +02:00
parent 7886f972fe
commit 008ae67343
Signed by: wnagrodzki
GPG key ID: E9D0EB0302264569
3 changed files with 232 additions and 0 deletions

View file

@ -0,0 +1,90 @@
import UIKit
public enum SizeClassDimension {
case horizontal, vertical
}
public class UIVariation<Object: AnyObject, Value>: NSObject {
public weak var traitEnvironment: UITraitEnvironment?
public let object: Object
public let property: ReferenceWritableKeyPath<Object, Value>
public var value: Value {
didSet { applyIfMatchesTraitEnvironment() }
}
public let horizontalSizeClass: UIUserInterfaceSizeClass?
public let verticalSizeClass: UIUserInterfaceSizeClass?
public static func make(for object: Object,
property: ReferenceWritableKeyPath<Object, Value>,
sizeClassDimension: SizeClassDimension,
whenCompact: Value,
whenRegular: Value) -> [UIVariation<Object, Value>] {
switch sizeClassDimension {
case .horizontal:
return [
UIVariation<Object, Value>(object: object, keyPath: property, value: whenCompact, horizontalSizeClass: .compact, verticalSizeClass: nil),
UIVariation<Object, Value>(object: object, keyPath: property, value: whenRegular, horizontalSizeClass: .regular, verticalSizeClass: nil),
]
case .vertical:
return [
UIVariation<Object, Value>(object: object, keyPath: property, value: whenCompact, horizontalSizeClass: nil, verticalSizeClass: .compact),
UIVariation<Object, Value>(object: object, keyPath: property, value: whenRegular, horizontalSizeClass: nil, verticalSizeClass: .regular),
]
}
}
public init(object: Object,
keyPath: ReferenceWritableKeyPath<Object, Value>,
value: Value,
horizontalSizeClass: UIUserInterfaceSizeClass? = nil,
verticalSizeClass: UIUserInterfaceSizeClass? = nil) {
self.object = object
self.property = keyPath
self.value = value
self.horizontalSizeClass = horizontalSizeClass
self.verticalSizeClass = verticalSizeClass
}
public override var description: String {
"""
<Variation<\(Object.self), \(Value.self): \(Unmanaged.passUnretained(self).toOpaque()))> {
object: \(object)
keyPath: \(property._kvcKeyPathString ?? String(describing: property))
value: \(value)
horizontalSizeClass: \(horizontalSizeClass?.name ?? "nil")
verticalSizeClass: \(verticalSizeClass?.name ?? "nil")
}
"""
}
}
extension UIVariation: UIVariationApplying {
func applyIfMatchesTraitEnvironment() {
guard let traitCollection = traitEnvironment?.traitCollection else {
print("Missing traitEnvironment when trying to apply \(self)")
return
}
if let horizontalSizeClass = horizontalSizeClass {
if traitCollection.horizontalSizeClass != horizontalSizeClass { return }
}
if let verticalSizeClass = verticalSizeClass {
if traitCollection.verticalSizeClass != verticalSizeClass { return }
}
object[keyPath: property] = value
}
}
extension UIUserInterfaceSizeClass {
fileprivate var name: String {
switch self {
case .unspecified: return "unspecified"
case .compact: return "compact"
case .regular: return "regular"
@unknown default: return "unknown"
}
}
}

View file

@ -0,0 +1,6 @@
import Foundation
protocol UIVariationApplying: NSObject {
func applyIfMatchesTraitEnvironment()
}

View file

@ -0,0 +1,136 @@
import XCTest
@testable import UserInterfaceVariations
final class UIVariationTests: XCTestCase {
let label = UILabel()
let traitEnvironment = TraitEnvironmentStub()
let valueWhenCompact = "value when compact"
let valueWhenRegular = "value when regular"
override func setUp() {
label.text = nil
traitEnvironment.traitCollection = UITraitCollection()
}
func test_When_horizontalSizeClass_matches_traitEnvironment_Then_variation_is_applied() {
let sut = UIVariation(object: label,
keyPath: \.text,
value: valueWhenCompact,
horizontalSizeClass: .compact,
verticalSizeClass: nil)
traitEnvironment.traitCollection = UITraitCollection(horizontalSizeClass: .compact)
sut.traitEnvironment = traitEnvironment
sut.applyIfMatchesTraitEnvironment()
XCTAssertEqual(label.text, sut.value)
}
func test_When_verticalSizeClass_matches_traitEnvironment_Then_variation_is_applied() {
let sut = UIVariation(object: label,
keyPath: \.text,
value: valueWhenCompact,
horizontalSizeClass: nil,
verticalSizeClass: .compact)
traitEnvironment.traitCollection = UITraitCollection(verticalSizeClass: .compact)
sut.traitEnvironment = traitEnvironment
sut.applyIfMatchesTraitEnvironment()
XCTAssertEqual(label.text, sut.value)
}
func test_When_horizontalSizeClass_do_not_match_traitEnvironment_Then_variation_is_NOT_applied() {
let sut = UIVariation(object: label,
keyPath: \.text,
value: valueWhenCompact,
horizontalSizeClass: .compact,
verticalSizeClass: nil)
traitEnvironment.traitCollection = UITraitCollection(horizontalSizeClass: .regular)
sut.traitEnvironment = traitEnvironment
sut.applyIfMatchesTraitEnvironment()
XCTAssertNotEqual(label.text, sut.value)
}
func test_When_verticalSizeClass_do_not_match_traitEnvironment_Then_variation_is_NOT_applied() {
let sut = UIVariation(object: label,
keyPath: \.text,
value: valueWhenCompact,
horizontalSizeClass: nil,
verticalSizeClass: .compact)
traitEnvironment.traitCollection = UITraitCollection(verticalSizeClass: .regular)
sut.traitEnvironment = traitEnvironment
sut.applyIfMatchesTraitEnvironment()
XCTAssertNotEqual(label.text, sut.value)
}
func test_When_horizontalSizeClass_matches_traitEnvironment_and_value_is_modified_Then_variation_is_applied() {
let newValue = "value when compact after modification"
let sut = UIVariation(object: label,
keyPath: \.text,
value: "value when compact before modification",
horizontalSizeClass: .compact,
verticalSizeClass: nil)
traitEnvironment.traitCollection = UITraitCollection(horizontalSizeClass: .compact)
sut.traitEnvironment = traitEnvironment
sut.value = newValue
XCTAssertEqual(label.text, newValue)
}
func test_When_traitEnvironment_is_nil_Then_variation_is_NOT_applied() {
let sut = UIVariation(object: label,
keyPath: \.text,
value: valueWhenCompact,
horizontalSizeClass: .compact,
verticalSizeClass: nil)
traitEnvironment.traitCollection = UITraitCollection(horizontalSizeClass: .compact)
sut.applyIfMatchesTraitEnvironment()
XCTAssertNotEqual(label.text, sut.value)
}
func test_factory_method_for_sizeClassDimension_horizontal() {
let variations = UIVariation.make(for: label,
property: \.text,
sizeClassDimension: .horizontal,
whenCompact: valueWhenCompact,
whenRegular: valueWhenRegular)
XCTAssertTrue(variations[0].object === label)
XCTAssertTrue(variations[0].property == \.text)
XCTAssertTrue(variations[0].value == valueWhenCompact)
XCTAssertTrue(variations[0].horizontalSizeClass == .compact)
XCTAssertTrue(variations[0].verticalSizeClass == nil)
XCTAssertTrue(variations[1].object === label)
XCTAssertTrue(variations[1].property == \.text)
XCTAssertTrue(variations[1].value == valueWhenRegular)
XCTAssertTrue(variations[1].horizontalSizeClass == .regular)
XCTAssertTrue(variations[1].verticalSizeClass == nil)
}
func test_factory_method_for_sizeClassDimension_vertical() {
let variations = UIVariation.make(for: label,
property: \.text,
sizeClassDimension: .vertical,
whenCompact: valueWhenCompact,
whenRegular: valueWhenRegular)
XCTAssertTrue(variations[0].object === label)
XCTAssertTrue(variations[0].property == \.text)
XCTAssertTrue(variations[0].value == valueWhenCompact)
XCTAssertTrue(variations[0].horizontalSizeClass == nil)
XCTAssertTrue(variations[0].verticalSizeClass == .compact)
XCTAssertTrue(variations[1].object === label)
XCTAssertTrue(variations[1].property == \.text)
XCTAssertTrue(variations[1].value == valueWhenRegular)
XCTAssertTrue(variations[1].horizontalSizeClass == nil)
XCTAssertTrue(variations[1].verticalSizeClass == .regular)
}
}
class TraitEnvironmentStub: NSObject, UITraitEnvironment {
var traitCollection = UITraitCollection()
func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
}
}