init iOS module
This commit is contained in:
38
iOS/Example/Pods/Nimble-Snapshots/CurrentTestCaseTracker.swift
generated
Normal file
38
iOS/Example/Pods/Nimble-Snapshots/CurrentTestCaseTracker.swift
generated
Normal file
@@ -0,0 +1,38 @@
|
||||
import XCTest
|
||||
|
||||
/// Helper class providing access to the currently executing XCTestCase instance, if any
|
||||
@objc public final class CurrentTestCaseTracker: NSObject, XCTestObservation {
|
||||
@objc public static let shared = CurrentTestCaseTracker()
|
||||
|
||||
private(set) var currentTestCase: XCTestCase?
|
||||
|
||||
@objc public func testCaseWillStart(_ testCase: XCTestCase) {
|
||||
currentTestCase = testCase
|
||||
}
|
||||
|
||||
@objc public func testCaseDidFinish(_ testCase: XCTestCase) {
|
||||
currentTestCase = nil
|
||||
}
|
||||
}
|
||||
|
||||
extension XCTestCase {
|
||||
var sanitizedName: String? {
|
||||
let fullName = self.name
|
||||
let characterSet = CharacterSet(charactersIn: "[]+-")
|
||||
#if swift(>=4)
|
||||
let name = fullName.components(separatedBy: characterSet).joined()
|
||||
#else
|
||||
let name = (fullName ?? "").components(separatedBy: characterSet).joined()
|
||||
#endif
|
||||
|
||||
if let quickClass = NSClassFromString("QuickSpec"), self.isKind(of: quickClass) {
|
||||
let className = String(describing: type(of: self))
|
||||
if let range = name.range(of: className), range.lowerBound == name.startIndex {
|
||||
return name.replacingCharacters(in: range, with: "")
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
}
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
}
|
||||
217
iOS/Example/Pods/Nimble-Snapshots/DynamicSize/DynamicSizeSnapshot.swift
generated
Normal file
217
iOS/Example/Pods/Nimble-Snapshots/DynamicSize/DynamicSizeSnapshot.swift
generated
Normal file
@@ -0,0 +1,217 @@
|
||||
import Foundation
|
||||
import Nimble
|
||||
import QuartzCore
|
||||
import UIKit
|
||||
|
||||
public enum ResizeMode {
|
||||
case frame
|
||||
case constrains
|
||||
case block(resizeBlock: (UIView, CGSize) -> Void)
|
||||
case custom(viewResizer: ViewResizer)
|
||||
|
||||
func viewResizer() -> ViewResizer {
|
||||
switch self {
|
||||
case .frame:
|
||||
return FrameViewResizer()
|
||||
|
||||
case .constrains:
|
||||
return ConstraintViewResizer()
|
||||
|
||||
case .block(resizeBlock: let block):
|
||||
return BlockViewResizer(block: block)
|
||||
|
||||
case .custom(viewResizer: let resizer):
|
||||
return resizer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public protocol ViewResizer {
|
||||
func resize(view: UIView, for size: CGSize)
|
||||
}
|
||||
|
||||
struct FrameViewResizer: ViewResizer {
|
||||
func resize(view: UIView, for size: CGSize) {
|
||||
view.frame = CGRect(origin: .zero, size: size)
|
||||
view.layoutIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
struct BlockViewResizer: ViewResizer {
|
||||
|
||||
let resizeBlock: (UIView, CGSize) -> Void
|
||||
|
||||
init(block: @escaping (UIView, CGSize) -> Void) {
|
||||
self.resizeBlock = block
|
||||
}
|
||||
|
||||
func resize(view: UIView, for size: CGSize) {
|
||||
self.resizeBlock(view, size)
|
||||
}
|
||||
}
|
||||
|
||||
class ConstraintViewResizer: ViewResizer {
|
||||
|
||||
typealias SizeConstrainsWrapper = (heightConstrain: NSLayoutConstraint, widthConstrain: NSLayoutConstraint)
|
||||
|
||||
func resize(view: UIView, for size: CGSize) {
|
||||
let sizesConstrains = findConstrains(of: view)
|
||||
|
||||
sizesConstrains.heightConstrain.constant = size.height
|
||||
sizesConstrains.widthConstrain.constant = size.width
|
||||
|
||||
NSLayoutConstraint.activate([sizesConstrains.heightConstrain,
|
||||
sizesConstrains.widthConstrain])
|
||||
|
||||
view.layoutIfNeeded()
|
||||
|
||||
//iOS 9+ BUG: Before the first draw, iOS will not calculate the layout,
|
||||
// it add a _UITemporaryLayoutWidth equals to its bounds and create a conflict.
|
||||
// So to it do all the layout we create a Window and add it as subview
|
||||
if view.bounds.width != size.width || view.bounds.width != size.width {
|
||||
let window = UIWindow(frame: CGRect(origin: .zero, size: size))
|
||||
let viewController = UIViewController()
|
||||
viewController.view = UIView()
|
||||
viewController.view.addSubview(view)
|
||||
window.rootViewController = viewController
|
||||
window.makeKeyAndVisible()
|
||||
view.setNeedsLayout()
|
||||
view.layoutIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
func findConstrains(of view: UIView) -> SizeConstrainsWrapper {
|
||||
var height: NSLayoutConstraint!
|
||||
var width: NSLayoutConstraint!
|
||||
|
||||
let heightLayout = NSLayoutAttribute.height
|
||||
let widthLayout = NSLayoutAttribute.width
|
||||
let equalRelation = NSLayoutRelation.equal
|
||||
|
||||
for constrain in view.constraints {
|
||||
if constrain.firstAttribute == heightLayout &&
|
||||
constrain.relation == equalRelation && constrain.secondItem == nil {
|
||||
height = constrain
|
||||
}
|
||||
|
||||
if constrain.firstAttribute == widthLayout &&
|
||||
constrain.relation == equalRelation && constrain.secondItem == nil {
|
||||
width = constrain
|
||||
}
|
||||
}
|
||||
|
||||
if height == nil {
|
||||
height = NSLayoutConstraint(item: view, attribute: heightLayout, relatedBy: equalRelation, toItem: nil,
|
||||
attribute: heightLayout, multiplier: 1, constant: 0)
|
||||
view.addConstraint(height)
|
||||
}
|
||||
|
||||
if width == nil {
|
||||
width = NSLayoutConstraint(item: view, attribute: widthLayout, relatedBy: equalRelation, toItem: nil,
|
||||
attribute: widthLayout, multiplier: 1, constant: 0)
|
||||
view.addConstraint(width)
|
||||
}
|
||||
|
||||
return (height, width)
|
||||
}
|
||||
}
|
||||
|
||||
public struct DynamicSizeSnapshot {
|
||||
let name: String?
|
||||
let record: Bool
|
||||
let sizes: [String: CGSize]
|
||||
let resizeMode: ResizeMode
|
||||
|
||||
init(name: String?, record: Bool, sizes: [String: CGSize], resizeMode: ResizeMode) {
|
||||
self.name = name
|
||||
self.record = record
|
||||
self.sizes = sizes
|
||||
self.resizeMode = resizeMode
|
||||
}
|
||||
}
|
||||
|
||||
public func snapshot(_ name: String? = nil, sizes: [String: CGSize],
|
||||
resizeMode: ResizeMode = .frame) -> DynamicSizeSnapshot {
|
||||
return DynamicSizeSnapshot(name: name, record: false, sizes: sizes, resizeMode: resizeMode)
|
||||
}
|
||||
|
||||
public func haveValidDynamicSizeSnapshot(named name: String? = nil, sizes: [String: CGSize],
|
||||
isDeviceAgnostic: Bool = false, usesDrawRect: Bool = false,
|
||||
tolerance: CGFloat? = nil,
|
||||
resizeMode: ResizeMode = .frame) -> Predicate<Snapshotable> {
|
||||
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
|
||||
return performDynamicSizeSnapshotTest(name, sizes: sizes, isDeviceAgnostic: isDeviceAgnostic,
|
||||
usesDrawRect: usesDrawRect, actualExpression: actualExpression,
|
||||
failureMessage: failureMessage, tolerance: tolerance,
|
||||
isRecord: false, resizeMode: resizeMode)
|
||||
}
|
||||
}
|
||||
|
||||
// swiftlint:disable:next function_parameter_count
|
||||
func performDynamicSizeSnapshotTest(_ name: String?, sizes: [String: CGSize], isDeviceAgnostic: Bool = false,
|
||||
usesDrawRect: Bool = false, actualExpression: Expression<Snapshotable>,
|
||||
failureMessage: FailureMessage, tolerance: CGFloat? = nil, isRecord: Bool,
|
||||
resizeMode: ResizeMode) -> Bool {
|
||||
// swiftlint:disable:next force_try force_unwrapping
|
||||
let instance = try! actualExpression.evaluate()!
|
||||
let testFileLocation = actualExpression.location.file
|
||||
let referenceImageDirectory = getDefaultReferenceDirectory(testFileLocation)
|
||||
let snapshotName = sanitizedTestName(name)
|
||||
let tolerance = tolerance ?? getTolerance()
|
||||
|
||||
let resizer = resizeMode.viewResizer()
|
||||
|
||||
let result = sizes.map { (sizeName, size) -> Bool in
|
||||
// swiftlint:disable:next force_unwrapping
|
||||
let view = instance.snapshotObject!
|
||||
|
||||
resizer.resize(view: view, for: size)
|
||||
|
||||
return FBSnapshotTest.compareSnapshot(instance, isDeviceAgnostic: isDeviceAgnostic, usesDrawRect: usesDrawRect,
|
||||
snapshot: "\(snapshotName) - \(sizeName)", record: isRecord,
|
||||
referenceDirectory: referenceImageDirectory, tolerance: tolerance,
|
||||
filename: actualExpression.location.file)
|
||||
}
|
||||
|
||||
if isRecord {
|
||||
if result.filter({ !$0 }).isEmpty {
|
||||
let name = name ?? snapshotName
|
||||
failureMessage.actualValue = "snapshot \(name) successfully recorded, replace recordSnapshot with a check"
|
||||
} else {
|
||||
failureMessage.actualValue = "expected to record a snapshot in \(String(describing: name))"
|
||||
}
|
||||
|
||||
return false
|
||||
} else {
|
||||
if !result.filter({ !$0 }).isEmpty {
|
||||
clearFailureMessage(failureMessage)
|
||||
failureMessage.actualValue = "expected a matching snapshot in \(snapshotName)"
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public func recordSnapshot(_ name: String? = nil, sizes: [String: CGSize],
|
||||
resizeMode: ResizeMode = .frame) -> DynamicSizeSnapshot {
|
||||
return DynamicSizeSnapshot(name: name, record: true, sizes: sizes, resizeMode: resizeMode)
|
||||
}
|
||||
|
||||
public func recordDynamicSizeSnapshot(named name: String? = nil, sizes: [String: CGSize],
|
||||
isDeviceAgnostic: Bool = false, usesDrawRect: Bool = false,
|
||||
resizeMode: ResizeMode = .frame) -> Predicate<Snapshotable> {
|
||||
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
|
||||
return performDynamicSizeSnapshotTest(name, sizes: sizes, isDeviceAgnostic: isDeviceAgnostic,
|
||||
usesDrawRect: usesDrawRect, actualExpression: actualExpression,
|
||||
failureMessage: failureMessage, isRecord: true, resizeMode: resizeMode)
|
||||
}
|
||||
}
|
||||
|
||||
public func == (lhs: Expectation<Snapshotable>, rhs: DynamicSizeSnapshot) {
|
||||
if rhs.record {
|
||||
lhs.to(recordDynamicSizeSnapshot(named: rhs.name, sizes: rhs.sizes, resizeMode: rhs.resizeMode))
|
||||
} else {
|
||||
lhs.to(haveValidDynamicSizeSnapshot(named: rhs.name, sizes: rhs.sizes, resizeMode: rhs.resizeMode))
|
||||
}
|
||||
}
|
||||
120
iOS/Example/Pods/Nimble-Snapshots/DynamicType/HaveValidDynamicTypeSnapshot.swift
generated
Normal file
120
iOS/Example/Pods/Nimble-Snapshots/DynamicType/HaveValidDynamicTypeSnapshot.swift
generated
Normal file
@@ -0,0 +1,120 @@
|
||||
import Nimble
|
||||
import UIKit
|
||||
|
||||
public func allContentSizeCategories() -> [UIContentSizeCategory] {
|
||||
return [
|
||||
.extraSmall, .small, .medium,
|
||||
.large, .extraLarge, .extraExtraLarge,
|
||||
.extraExtraExtraLarge, .accessibilityMedium,
|
||||
.accessibilityLarge, .accessibilityExtraLarge,
|
||||
.accessibilityExtraExtraLarge, .accessibilityExtraExtraExtraLarge
|
||||
]
|
||||
}
|
||||
|
||||
func shortCategoryName(_ category: UIContentSizeCategory) -> String {
|
||||
return category.rawValue.replacingOccurrences(of: "UICTContentSizeCategory", with: "")
|
||||
}
|
||||
|
||||
func combinePredicates<T>(_ predicates: [Predicate<T>], ignoreFailures: Bool = false,
|
||||
deferred: (() -> Void)? = nil) -> Predicate<T> {
|
||||
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
|
||||
defer {
|
||||
deferred?()
|
||||
}
|
||||
|
||||
return try predicates.reduce(true) { acc, matcher -> Bool in
|
||||
guard acc || ignoreFailures else {
|
||||
return false
|
||||
}
|
||||
|
||||
let result = try matcher.matches(actualExpression, failureMessage: failureMessage)
|
||||
return result && acc
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func haveValidDynamicTypeSnapshot(named name: String? = nil, usesDrawRect: Bool = false,
|
||||
tolerance: CGFloat? = nil,
|
||||
sizes: [UIContentSizeCategory] = allContentSizeCategories(),
|
||||
isDeviceAgnostic: Bool = false) -> Predicate<Snapshotable> {
|
||||
let mock = NBSMockedApplication()
|
||||
|
||||
let predicates: [Predicate<Snapshotable>] = sizes.map { category in
|
||||
let sanitizedName = sanitizedTestName(name)
|
||||
let nameWithCategory = "\(sanitizedName)_\(shortCategoryName(category))"
|
||||
|
||||
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
|
||||
mock.mockPreferredContentSizeCategory(category)
|
||||
updateTraitCollection(on: actualExpression)
|
||||
|
||||
let predicate: Predicate<Snapshotable>
|
||||
if isDeviceAgnostic {
|
||||
predicate = haveValidDeviceAgnosticSnapshot(named: nameWithCategory,
|
||||
usesDrawRect: usesDrawRect, tolerance: tolerance)
|
||||
} else {
|
||||
predicate = haveValidSnapshot(named: nameWithCategory, usesDrawRect: usesDrawRect, tolerance: tolerance)
|
||||
}
|
||||
|
||||
return try predicate.matches(actualExpression, failureMessage: failureMessage)
|
||||
}
|
||||
}
|
||||
|
||||
return combinePredicates(predicates) {
|
||||
mock.stopMockingPreferredContentSizeCategory()
|
||||
}
|
||||
}
|
||||
|
||||
public func recordDynamicTypeSnapshot(named name: String? = nil, usesDrawRect: Bool = false,
|
||||
sizes: [UIContentSizeCategory] = allContentSizeCategories(),
|
||||
isDeviceAgnostic: Bool = false) -> Predicate<Snapshotable> {
|
||||
let mock = NBSMockedApplication()
|
||||
|
||||
let predicates: [Predicate<Snapshotable>] = sizes.map { category in
|
||||
let sanitizedName = sanitizedTestName(name)
|
||||
let nameWithCategory = "\(sanitizedName)_\(shortCategoryName(category))"
|
||||
|
||||
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
|
||||
mock.mockPreferredContentSizeCategory(category)
|
||||
updateTraitCollection(on: actualExpression)
|
||||
|
||||
let predicate: Predicate<Snapshotable>
|
||||
if isDeviceAgnostic {
|
||||
predicate = recordDeviceAgnosticSnapshot(named: nameWithCategory, usesDrawRect: usesDrawRect)
|
||||
} else {
|
||||
predicate = recordSnapshot(named: nameWithCategory, usesDrawRect: usesDrawRect)
|
||||
}
|
||||
|
||||
return try predicate.matches(actualExpression, failureMessage: failureMessage)
|
||||
}
|
||||
}
|
||||
|
||||
return combinePredicates(predicates, ignoreFailures: true) {
|
||||
mock.stopMockingPreferredContentSizeCategory()
|
||||
}
|
||||
}
|
||||
|
||||
private func updateTraitCollection(on expression: Expression<Snapshotable>) {
|
||||
// swiftlint:disable:next force_try force_unwrapping
|
||||
let instance = try! expression.evaluate()!
|
||||
updateTraitCollection(on: instance)
|
||||
}
|
||||
|
||||
private func updateTraitCollection(on element: Snapshotable) {
|
||||
if let environment = element as? UITraitEnvironment {
|
||||
if let vc = environment as? UIViewController {
|
||||
vc.beginAppearanceTransition(true, animated: false)
|
||||
vc.endAppearanceTransition()
|
||||
}
|
||||
|
||||
environment.traitCollectionDidChange(nil)
|
||||
|
||||
if let view = environment as? UIView {
|
||||
view.subviews.forEach(updateTraitCollection(on:))
|
||||
} else if let vc = environment as? UIViewController {
|
||||
vc.childViewControllers.forEach(updateTraitCollection(on:))
|
||||
if vc.isViewLoaded {
|
||||
updateTraitCollection(on: vc.view)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
13
iOS/Example/Pods/Nimble-Snapshots/DynamicType/NBSMockedApplication.h
generated
Normal file
13
iOS/Example/Pods/Nimble-Snapshots/DynamicType/NBSMockedApplication.h
generated
Normal file
@@ -0,0 +1,13 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface NBSMockedApplication : NSObject
|
||||
|
||||
- (void)mockPreferredContentSizeCategory:(UIContentSizeCategory)category;
|
||||
- (void)stopMockingPreferredContentSizeCategory;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
156
iOS/Example/Pods/Nimble-Snapshots/DynamicType/NBSMockedApplication.m
generated
Normal file
156
iOS/Example/Pods/Nimble-Snapshots/DynamicType/NBSMockedApplication.m
generated
Normal file
@@ -0,0 +1,156 @@
|
||||
#import "NBSMockedApplication.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
@interface NBSMockedApplication ()
|
||||
|
||||
@property (nonatomic) BOOL isSwizzled;
|
||||
|
||||
@end
|
||||
|
||||
@interface UIFont (Swizzling)
|
||||
|
||||
+ (void)nbs_swizzle;
|
||||
|
||||
@end
|
||||
|
||||
@interface UIApplication (Swizzling)
|
||||
|
||||
+ (void)nbs_swizzle;
|
||||
|
||||
@property (nonatomic) UIContentSizeCategory nbs_preferredContentSizeCategory;
|
||||
|
||||
@end
|
||||
|
||||
@interface UITraitCollection (Swizzling)
|
||||
|
||||
+ (void)nbs_swizzle;
|
||||
|
||||
@end
|
||||
|
||||
@implementation NBSMockedApplication
|
||||
|
||||
/* On iOS 9, +[UIFont preferredFontForTextStyle:] uses -[UIApplication preferredContentSizeCategory]
|
||||
to get the content size category. However, this changed on iOS 10. While I haven't found what UIFont uses to get
|
||||
the current category, swizzling preferredFontForTextStyle: to use +[UIFont preferredFontForTextStyle: compatibleWithTraitCollection:]
|
||||
(only available on iOS >= 10), passing an UITraitCollection with the desired contentSizeCategory.
|
||||
*/
|
||||
|
||||
- (void)mockPreferredContentSizeCategory:(UIContentSizeCategory)category {
|
||||
UIApplication.sharedApplication.nbs_preferredContentSizeCategory = category;
|
||||
|
||||
if (!self.isSwizzled) {
|
||||
[UIApplication nbs_swizzle];
|
||||
[UIFont nbs_swizzle];
|
||||
[UITraitCollection nbs_swizzle];
|
||||
self.isSwizzled = YES;
|
||||
}
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:UIContentSizeCategoryDidChangeNotification
|
||||
object:[UIApplication sharedApplication]
|
||||
userInfo:@{UIContentSizeCategoryNewValueKey: category}];
|
||||
}
|
||||
|
||||
- (void)stopMockingPreferredContentSizeCategory {
|
||||
if (self.isSwizzled) {
|
||||
[UIApplication nbs_swizzle];
|
||||
[UIFont nbs_swizzle];
|
||||
[UITraitCollection nbs_swizzle];
|
||||
self.isSwizzled = NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[self stopMockingPreferredContentSizeCategory];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation UIFont (Swizzling)
|
||||
|
||||
+ (UIFont *)nbs_preferredFontForTextStyle:(UIFontTextStyle)style {
|
||||
UIContentSizeCategory category = UIApplication.sharedApplication.preferredContentSizeCategory;
|
||||
UITraitCollection *categoryTrait = [UITraitCollection traitCollectionWithPreferredContentSizeCategory:category];
|
||||
return [UIFont preferredFontForTextStyle:style compatibleWithTraitCollection:categoryTrait];
|
||||
}
|
||||
|
||||
+ (void)nbs_swizzle {
|
||||
if (![UITraitCollection instancesRespondToSelector:@selector(preferredContentSizeCategory)]) {
|
||||
return;
|
||||
}
|
||||
|
||||
SEL selector = @selector(preferredFontForTextStyle:);
|
||||
SEL replacedSelector = @selector(nbs_preferredFontForTextStyle:);
|
||||
|
||||
Method originalMethod = class_getClassMethod(self, selector);
|
||||
Method extendedMethod = class_getClassMethod(self, replacedSelector);
|
||||
method_exchangeImplementations(originalMethod, extendedMethod);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation UIApplication (Swizzling)
|
||||
|
||||
- (UIContentSizeCategory)nbs_preferredContentSizeCategory {
|
||||
return objc_getAssociatedObject(self, @selector(nbs_preferredContentSizeCategory));
|
||||
}
|
||||
|
||||
- (void)setNbs_preferredContentSizeCategory:(UIContentSizeCategory)category {
|
||||
objc_setAssociatedObject(self, @selector(nbs_preferredContentSizeCategory),
|
||||
category, OBJC_ASSOCIATION_COPY_NONATOMIC);
|
||||
}
|
||||
|
||||
+ (void)nbs_swizzle {
|
||||
SEL selector = @selector(preferredContentSizeCategory);
|
||||
SEL replacedSelector = @selector(nbs_preferredContentSizeCategory);
|
||||
|
||||
Method originalMethod = class_getInstanceMethod(self, selector);
|
||||
Method extendedMethod = class_getInstanceMethod(self, replacedSelector);
|
||||
method_exchangeImplementations(originalMethod, extendedMethod);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation UITraitCollection (Swizzling)
|
||||
|
||||
- (UIContentSizeCategory)nbs_preferredContentSizeCategory {
|
||||
return UIApplication.sharedApplication.preferredContentSizeCategory;
|
||||
}
|
||||
|
||||
- (BOOL)nbs__changedContentSizeCategoryFromTraitCollection:(id)arg {
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (void)nbs_swizzle {
|
||||
[self nbs_swizzlePreferredContentSizeCategory];
|
||||
[self nbs_swizzleChangedContentSizeCategoryFromTraitCollection];
|
||||
}
|
||||
|
||||
+ (void)nbs_swizzlePreferredContentSizeCategory {
|
||||
SEL selector = @selector(preferredContentSizeCategory);
|
||||
|
||||
if (![self instancesRespondToSelector:selector]) {
|
||||
return;
|
||||
}
|
||||
|
||||
SEL replacedSelector = @selector(nbs_preferredContentSizeCategory);
|
||||
|
||||
Method originalMethod = class_getInstanceMethod(self, selector);
|
||||
Method extendedMethod = class_getInstanceMethod(self, replacedSelector);
|
||||
method_exchangeImplementations(originalMethod, extendedMethod);
|
||||
}
|
||||
|
||||
+ (void)nbs_swizzleChangedContentSizeCategoryFromTraitCollection {
|
||||
SEL selector = sel_registerName("_changedContentSizeCategoryFromTraitCollection:");
|
||||
|
||||
if (![self instancesRespondToSelector:selector]) {
|
||||
return;
|
||||
}
|
||||
|
||||
SEL replacedSelector = @selector(nbs__changedContentSizeCategoryFromTraitCollection:);
|
||||
|
||||
Method originalMethod = class_getInstanceMethod(self, selector);
|
||||
Method extendedMethod = class_getInstanceMethod(self, replacedSelector);
|
||||
method_exchangeImplementations(originalMethod, extendedMethod);
|
||||
}
|
||||
|
||||
@end
|
||||
45
iOS/Example/Pods/Nimble-Snapshots/DynamicType/PrettyDynamicTypeSyntax.swift
generated
Normal file
45
iOS/Example/Pods/Nimble-Snapshots/DynamicType/PrettyDynamicTypeSyntax.swift
generated
Normal file
@@ -0,0 +1,45 @@
|
||||
import Nimble
|
||||
|
||||
// MARK: - Nicer syntax using == operator
|
||||
|
||||
public struct DynamicTypeSnapshot {
|
||||
let name: String?
|
||||
let record: Bool
|
||||
let sizes: [UIContentSizeCategory]
|
||||
let deviceAgnostic: Bool
|
||||
|
||||
init(name: String?, record: Bool, sizes: [UIContentSizeCategory], deviceAgnostic: Bool) {
|
||||
self.name = name
|
||||
self.record = record
|
||||
self.sizes = sizes
|
||||
self.deviceAgnostic = deviceAgnostic
|
||||
}
|
||||
}
|
||||
|
||||
public func dynamicTypeSnapshot(_ name: String? = nil, sizes: [UIContentSizeCategory] = allContentSizeCategories(),
|
||||
deviceAgnostic: Bool = false) -> DynamicTypeSnapshot {
|
||||
return DynamicTypeSnapshot(name: name, record: false, sizes: sizes, deviceAgnostic: deviceAgnostic)
|
||||
}
|
||||
|
||||
public func recordDynamicTypeSnapshot(_ name: String? = nil,
|
||||
sizes: [UIContentSizeCategory] = allContentSizeCategories(),
|
||||
deviceAgnostic: Bool = false) -> DynamicTypeSnapshot {
|
||||
return DynamicTypeSnapshot(name: name, record: true, sizes: sizes, deviceAgnostic: deviceAgnostic)
|
||||
}
|
||||
|
||||
public func == (lhs: Expectation<Snapshotable>, rhs: DynamicTypeSnapshot) {
|
||||
if let name = rhs.name {
|
||||
if rhs.record {
|
||||
lhs.to(recordDynamicTypeSnapshot(named: name, sizes: rhs.sizes, isDeviceAgnostic: rhs.deviceAgnostic))
|
||||
} else {
|
||||
lhs.to(haveValidDynamicTypeSnapshot(named: name, sizes: rhs.sizes, isDeviceAgnostic: rhs.deviceAgnostic))
|
||||
}
|
||||
|
||||
} else {
|
||||
if rhs.record {
|
||||
lhs.to(recordDynamicTypeSnapshot(sizes: rhs.sizes, isDeviceAgnostic: rhs.deviceAgnostic))
|
||||
} else {
|
||||
lhs.to(haveValidDynamicTypeSnapshot(sizes: rhs.sizes, isDeviceAgnostic: rhs.deviceAgnostic))
|
||||
}
|
||||
}
|
||||
}
|
||||
239
iOS/Example/Pods/Nimble-Snapshots/HaveValidSnapshot.swift
generated
Normal file
239
iOS/Example/Pods/Nimble-Snapshots/HaveValidSnapshot.swift
generated
Normal file
@@ -0,0 +1,239 @@
|
||||
import FBSnapshotTestCase
|
||||
import Foundation
|
||||
import Nimble
|
||||
import QuartzCore
|
||||
import UIKit
|
||||
|
||||
@objc public protocol Snapshotable {
|
||||
var snapshotObject: UIView? { get }
|
||||
}
|
||||
|
||||
extension UIViewController : Snapshotable {
|
||||
public var snapshotObject: UIView? {
|
||||
self.beginAppearanceTransition(true, animated: false)
|
||||
self.endAppearanceTransition()
|
||||
return view
|
||||
}
|
||||
}
|
||||
|
||||
extension UIView : Snapshotable {
|
||||
public var snapshotObject: UIView? {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
@objc public class FBSnapshotTest: NSObject {
|
||||
|
||||
var referenceImagesDirectory: String?
|
||||
var tolerance: CGFloat = 0
|
||||
|
||||
static let sharedInstance = FBSnapshotTest()
|
||||
|
||||
public class func setReferenceImagesDirectory(_ directory: String?) {
|
||||
sharedInstance.referenceImagesDirectory = directory
|
||||
}
|
||||
|
||||
// swiftlint:disable:next function_parameter_count
|
||||
class func compareSnapshot(_ instance: Snapshotable, isDeviceAgnostic: Bool = false,
|
||||
usesDrawRect: Bool = false, snapshot: String, record: Bool,
|
||||
referenceDirectory: String, tolerance: CGFloat,
|
||||
filename: String) -> Bool {
|
||||
|
||||
let testName = parseFilename(filename: filename)
|
||||
let snapshotController: FBSnapshotTestController = FBSnapshotTestController(testName: testName)
|
||||
snapshotController.isDeviceAgnostic = isDeviceAgnostic
|
||||
snapshotController.recordMode = record
|
||||
snapshotController.referenceImagesDirectory = referenceDirectory
|
||||
snapshotController.usesDrawViewHierarchyInRect = usesDrawRect
|
||||
|
||||
let reason = "Missing value for referenceImagesDirectory - " +
|
||||
"Call FBSnapshotTest.setReferenceImagesDirectory(FB_REFERENCE_IMAGE_DIR)"
|
||||
assert(snapshotController.referenceImagesDirectory != nil, reason)
|
||||
|
||||
do {
|
||||
try snapshotController.compareSnapshot(ofViewOrLayer: instance.snapshotObject,
|
||||
selector: Selector(snapshot), identifier: nil, tolerance: tolerance)
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Note that these must be lower case.
|
||||
private var testFolderSuffixes = ["tests", "specs"]
|
||||
|
||||
public func setNimbleTestFolder(_ testFolder: String) {
|
||||
testFolderSuffixes = [testFolder.lowercased()]
|
||||
}
|
||||
|
||||
public func setNimbleTolerance(_ tolerance: CGFloat) {
|
||||
FBSnapshotTest.sharedInstance.tolerance = tolerance
|
||||
}
|
||||
|
||||
func getDefaultReferenceDirectory(_ sourceFileName: String) -> String {
|
||||
if let globalReference = FBSnapshotTest.sharedInstance.referenceImagesDirectory {
|
||||
return globalReference
|
||||
}
|
||||
|
||||
// Search the test file's path to find the first folder with a test suffix,
|
||||
// then append "/ReferenceImages" and use that.
|
||||
|
||||
// Grab the file's path
|
||||
let pathComponents = (sourceFileName as NSString).pathComponents as NSArray
|
||||
|
||||
// Find the directory in the path that ends with a test suffix.
|
||||
let testPath = pathComponents.first { component -> Bool in
|
||||
return !testFolderSuffixes.filter {
|
||||
(component as AnyObject).lowercased.hasSuffix($0)
|
||||
}.isEmpty
|
||||
}
|
||||
|
||||
guard let testDirectory = testPath else {
|
||||
fatalError("Could not infer reference image folder – You should provide a reference dir using " +
|
||||
"FBSnapshotTest.setReferenceImagesDirectory(FB_REFERENCE_IMAGE_DIR)")
|
||||
}
|
||||
|
||||
// Recombine the path components and append our own image directory.
|
||||
let currentIndex = pathComponents.index(of: testDirectory) + 1
|
||||
let folderPathComponents = pathComponents.subarray(with: NSRange(location: 0, length: currentIndex)) as NSArray
|
||||
let folderPath = folderPathComponents.componentsJoined(by: "/")
|
||||
|
||||
return folderPath + "/ReferenceImages"
|
||||
}
|
||||
|
||||
private func parseFilename(filename: String) -> String {
|
||||
let nsName = filename as NSString
|
||||
|
||||
let type = ".\(nsName.pathExtension)"
|
||||
let sanitizedName = nsName.lastPathComponent.replacingOccurrences(of: type, with: "")
|
||||
|
||||
return sanitizedName
|
||||
}
|
||||
|
||||
func sanitizedTestName(_ name: String?) -> String {
|
||||
guard let testName = currentTestName() else {
|
||||
fatalError("Test matchers must be called from inside a test block")
|
||||
}
|
||||
|
||||
var filename = name ?? testName
|
||||
filename = filename.replacingOccurrences(of: "root example group, ", with: "")
|
||||
let characterSet = CharacterSet(charactersIn: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_")
|
||||
let components = filename.components(separatedBy: characterSet.inverted)
|
||||
return components.joined(separator: "_")
|
||||
}
|
||||
|
||||
func getTolerance() -> CGFloat {
|
||||
return FBSnapshotTest.sharedInstance.tolerance
|
||||
}
|
||||
|
||||
func clearFailureMessage(_ failureMessage: FailureMessage) {
|
||||
failureMessage.actualValue = nil
|
||||
failureMessage.expected = ""
|
||||
failureMessage.postfixMessage = ""
|
||||
failureMessage.to = ""
|
||||
}
|
||||
|
||||
private func performSnapshotTest(_ name: String?, isDeviceAgnostic: Bool = false, usesDrawRect: Bool = false,
|
||||
actualExpression: Expression<Snapshotable>, failureMessage: FailureMessage,
|
||||
tolerance: CGFloat?) -> Bool {
|
||||
// swiftlint:disable:next force_try force_unwrapping
|
||||
let instance = try! actualExpression.evaluate()!
|
||||
let testFileLocation = actualExpression.location.file
|
||||
let referenceImageDirectory = getDefaultReferenceDirectory(testFileLocation)
|
||||
let snapshotName = sanitizedTestName(name)
|
||||
let tolerance = tolerance ?? getTolerance()
|
||||
|
||||
let result = FBSnapshotTest.compareSnapshot(instance, isDeviceAgnostic: isDeviceAgnostic,
|
||||
usesDrawRect: usesDrawRect, snapshot: snapshotName, record: false,
|
||||
referenceDirectory: referenceImageDirectory, tolerance: tolerance,
|
||||
filename: actualExpression.location.file)
|
||||
|
||||
if !result {
|
||||
clearFailureMessage(failureMessage)
|
||||
failureMessage.expected = "expected a matching snapshot in \(snapshotName)"
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private func recordSnapshot(_ name: String?, isDeviceAgnostic: Bool = false, usesDrawRect: Bool = false,
|
||||
actualExpression: Expression<Snapshotable>, failureMessage: FailureMessage) -> Bool {
|
||||
// swiftlint:disable:next force_try force_unwrapping
|
||||
let instance = try! actualExpression.evaluate()!
|
||||
let testFileLocation = actualExpression.location.file
|
||||
let referenceImageDirectory = getDefaultReferenceDirectory(testFileLocation)
|
||||
let snapshotName = sanitizedTestName(name)
|
||||
let tolerance = getTolerance()
|
||||
|
||||
clearFailureMessage(failureMessage)
|
||||
|
||||
if FBSnapshotTest.compareSnapshot(instance, isDeviceAgnostic: isDeviceAgnostic, usesDrawRect: usesDrawRect,
|
||||
snapshot: snapshotName, record: true, referenceDirectory: referenceImageDirectory,
|
||||
tolerance: tolerance, filename: actualExpression.location.file) {
|
||||
let name = name ?? snapshotName
|
||||
failureMessage.expected = "snapshot \(name) successfully recorded, replace recordSnapshot with a check"
|
||||
} else {
|
||||
let expectedMessage: String
|
||||
if let name = name {
|
||||
expectedMessage = "expected to record a snapshot in \(name)"
|
||||
} else {
|
||||
expectedMessage = "expected to record a snapshot"
|
||||
}
|
||||
failureMessage.expected = expectedMessage
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private func currentTestName() -> String? {
|
||||
return CurrentTestCaseTracker.shared.currentTestCase?.sanitizedName
|
||||
}
|
||||
|
||||
internal var switchChecksWithRecords = false
|
||||
|
||||
public func haveValidSnapshot(named name: String? = nil, usesDrawRect: Bool = false,
|
||||
tolerance: CGFloat? = nil) -> Predicate<Snapshotable> {
|
||||
|
||||
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
|
||||
if switchChecksWithRecords {
|
||||
return recordSnapshot(name, usesDrawRect: usesDrawRect, actualExpression: actualExpression,
|
||||
failureMessage: failureMessage)
|
||||
}
|
||||
|
||||
return performSnapshotTest(name, usesDrawRect: usesDrawRect, actualExpression: actualExpression,
|
||||
failureMessage: failureMessage, tolerance: tolerance)
|
||||
}
|
||||
}
|
||||
|
||||
public func haveValidDeviceAgnosticSnapshot(named name: String? = nil, usesDrawRect: Bool = false,
|
||||
tolerance: CGFloat? = nil) -> Predicate<Snapshotable> {
|
||||
|
||||
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
|
||||
if switchChecksWithRecords {
|
||||
return recordSnapshot(name, isDeviceAgnostic: true, usesDrawRect: usesDrawRect,
|
||||
actualExpression: actualExpression, failureMessage: failureMessage)
|
||||
}
|
||||
|
||||
return performSnapshotTest(name, isDeviceAgnostic: true, usesDrawRect: usesDrawRect,
|
||||
actualExpression: actualExpression,
|
||||
failureMessage: failureMessage, tolerance: tolerance)
|
||||
}
|
||||
}
|
||||
|
||||
public func recordSnapshot(named name: String? = nil, usesDrawRect: Bool = false) -> Predicate<Snapshotable> {
|
||||
|
||||
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
|
||||
return recordSnapshot(name, usesDrawRect: usesDrawRect,
|
||||
actualExpression: actualExpression, failureMessage: failureMessage)
|
||||
}
|
||||
}
|
||||
|
||||
public func recordDeviceAgnosticSnapshot(named name: String? = nil,
|
||||
usesDrawRect: Bool = false) -> Predicate<Snapshotable> {
|
||||
|
||||
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
|
||||
return recordSnapshot(name, isDeviceAgnostic: true, usesDrawRect: usesDrawRect,
|
||||
actualExpression: actualExpression, failureMessage: failureMessage)
|
||||
}
|
||||
}
|
||||
21
iOS/Example/Pods/Nimble-Snapshots/LICENSE
generated
Normal file
21
iOS/Example/Pods/Nimble-Snapshots/LICENSE
generated
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Artsy, Ash Furrow
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
54
iOS/Example/Pods/Nimble-Snapshots/PrettySyntax.swift
generated
Normal file
54
iOS/Example/Pods/Nimble-Snapshots/PrettySyntax.swift
generated
Normal file
@@ -0,0 +1,54 @@
|
||||
import Nimble
|
||||
|
||||
// MARK: - Nicer syntax using == operator
|
||||
|
||||
public struct Snapshot {
|
||||
let name: String?
|
||||
let record: Bool
|
||||
let usesDrawRect: Bool
|
||||
|
||||
init(name: String?, record: Bool, usesDrawRect: Bool) {
|
||||
self.name = name
|
||||
self.record = record
|
||||
self.usesDrawRect = usesDrawRect
|
||||
}
|
||||
}
|
||||
|
||||
public func snapshot(_ name: String? = nil,
|
||||
usesDrawRect: Bool = false) -> Snapshot {
|
||||
return Snapshot(name: name, record: false, usesDrawRect: usesDrawRect)
|
||||
}
|
||||
|
||||
public func recordSnapshot(_ name: String? = nil,
|
||||
usesDrawRect: Bool = false) -> Snapshot {
|
||||
return Snapshot(name: name, record: true, usesDrawRect: usesDrawRect)
|
||||
}
|
||||
|
||||
public func == (lhs: Expectation<Snapshotable>, rhs: Snapshot) {
|
||||
if let name = rhs.name {
|
||||
if rhs.record {
|
||||
lhs.to(recordSnapshot(named: name, usesDrawRect: rhs.usesDrawRect))
|
||||
} else {
|
||||
lhs.to(haveValidSnapshot(named: name, usesDrawRect: rhs.usesDrawRect))
|
||||
}
|
||||
|
||||
} else {
|
||||
if rhs.record {
|
||||
lhs.to(recordSnapshot(usesDrawRect: rhs.usesDrawRect))
|
||||
} else {
|
||||
lhs.to(haveValidSnapshot(usesDrawRect: rhs.usesDrawRect))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Nicer syntax using emoji
|
||||
|
||||
// swiftlint:disable:next identifier_name
|
||||
public func 📷(_ snapshottable: Snapshotable, file: FileString = #file, line: UInt = #line) {
|
||||
expect(snapshottable, file: file, line: line).to(recordSnapshot())
|
||||
}
|
||||
|
||||
// swiftlint:disable:next identifier_name
|
||||
public func 📷(_ snapshottable: Snapshotable, named name: String, file: FileString = #file, line: UInt = #line) {
|
||||
expect(snapshottable, file: file, line: line).to(recordSnapshot(named: name))
|
||||
}
|
||||
180
iOS/Example/Pods/Nimble-Snapshots/README.md
generated
Normal file
180
iOS/Example/Pods/Nimble-Snapshots/README.md
generated
Normal file
@@ -0,0 +1,180 @@
|
||||
[](https://circleci.com/gh/ashfurrow/Nimble-Snapshots/tree/master)
|
||||
=============================
|
||||
|
||||
[Nimble](https://github.com/Quick/Nimble) matchers for [FBSnapshotTestCase](https://github.com/facebook/ios-snapshot-test-case).
|
||||
Highly derivative of [Expecta Matchers for FBSnapshotTestCase](https://github.com/dblock/ios-snapshot-test-case-expecta).
|
||||
|
||||
<p align="center">
|
||||
<img src="http://gifs.ashfurrow.com/click.gif" />
|
||||
</p>
|
||||
|
||||
Installing
|
||||
----------
|
||||
|
||||
## CocoaPods
|
||||
|
||||
You need to be using CocoaPods 0.36 Beta 1 or higher. Your `Podfile` should look
|
||||
something like the following.
|
||||
|
||||
```rb
|
||||
platform :ios, '8.0'
|
||||
|
||||
source 'https://github.com/CocoaPods/Specs.git'
|
||||
|
||||
# Whichever pods you need for your app go here.
|
||||
|
||||
target 'YOUR_APP_NAME_HERE_Tests', :exclusive => true do
|
||||
pod 'Nimble-Snapshots'
|
||||
pod 'Quick' # if you want to use it with Quick
|
||||
end
|
||||
```
|
||||
|
||||
Then run:
|
||||
```
|
||||
$ pod install
|
||||
```
|
||||
|
||||
## Carthage
|
||||
|
||||
You need to be using Carthage 0.18 or higher. Your `Cartfile` (or `Cartfile.private`) should look
|
||||
something like the following.
|
||||
|
||||
```rb
|
||||
github "Quick/Quick" ~> 1.0
|
||||
github "Quick/Nimble" ~> 7.0
|
||||
github "facebook/ios-snapshot-test-case" "2.1.4"
|
||||
github "ashfurrow/Nimble-Snapshots"
|
||||
```
|
||||
|
||||
Then run:
|
||||
```
|
||||
$ carthage bootstrap --platform iOS --toolchain com.apple.dt.toolchain.Swift_3_0
|
||||
```
|
||||
|
||||
Use
|
||||
---
|
||||
|
||||
Your tests will look something like the following.
|
||||
|
||||
```swift
|
||||
import Quick
|
||||
import Nimble
|
||||
import Nimble_Snapshots
|
||||
import UIKit
|
||||
|
||||
class MySpec: QuickSpec {
|
||||
override func spec() {
|
||||
describe("in some context") {
|
||||
it("has valid snapshot") {
|
||||
let view = ... // some view you want to test
|
||||
expect(view).to( haveValidSnapshot() )
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
There are some options for testing the validity of snapshots. Snapshots can be
|
||||
given a name:
|
||||
|
||||
```swift
|
||||
expect(view).to( haveValidSnapshot(named: "some custom name") )
|
||||
```
|
||||
|
||||
We also have a prettier syntax for custom-named snapshots:
|
||||
|
||||
```swift
|
||||
expect(view) == snapshot("some custom name")
|
||||
```
|
||||
|
||||
To record snapshots, just replace `haveValidSnapshot()` with `recordSnapshot()`
|
||||
and `haveValidSnapshot(named:)` with `recordSnapshot(named:)`. We also have a
|
||||
handy emoji operator.
|
||||
|
||||
```swift
|
||||
📷(view)
|
||||
📷(view, "some custom name")
|
||||
```
|
||||
|
||||
By default, this pod will put the reference images inside a `ReferenceImages`
|
||||
directory; we try to put this in a place that makes sense (inside your unit
|
||||
tests directory). If we can't figure it out, or if you want to use your own
|
||||
directory instead, call `setNimbleTestFolder()` with the name of the directory
|
||||
in your unit test's path that we should use. For example, if the tests are in
|
||||
`App/AppTesting/`, you can call it with `AppTesting`.
|
||||
|
||||
If you have any questions or run into any trouble, feel free to open an issue
|
||||
on this repo.
|
||||
|
||||
## Dynamic Type
|
||||
|
||||
Testing Dynamic Type manually is boring and no one seems to remember doing it
|
||||
when implementing a view/screen, so you can have snapshot tests according to
|
||||
content size categories.
|
||||
|
||||
In order to use Dynamic Type testing, make sure to provide a valid `Host Application` in your testing target.
|
||||
|
||||
Then you can use the `haveValidDynamicTypeSnapshot` and
|
||||
`recordDynamicTypeSnapshot` matchers:
|
||||
|
||||
```swift
|
||||
// expect(view).to(recordDynamicTypeSnapshot()
|
||||
expect(view).to(haveValidDynamicTypeSnapshot())
|
||||
|
||||
// You can also just test some sizes:
|
||||
expect(view).to(haveValidDynamicTypeSnapshot(sizes: [UIContentSizeCategoryExtraLarge]))
|
||||
|
||||
// If you prefer the == syntax, we got you covered too:
|
||||
expect(view) == dynamicTypeSnapshot()
|
||||
expect(view) == dynamicTypeSnapshot(sizes: [UIContentSizeCategoryExtraLarge])
|
||||
```
|
||||
|
||||
Note that this will post an `UIContentSizeCategoryDidChangeNotification`,
|
||||
so your views/view controllers need to observe that and update themselves.
|
||||
|
||||
For more info on usage, check out the
|
||||
[dynamic type tests](Bootstrap/BootstrapTests/DynamicTypeTests.swift).
|
||||
|
||||
|
||||
|
||||
## Dynamic Size
|
||||
|
||||
Testing the same view with many sizes is easy but error prone. It easy to fix one test
|
||||
on change and forget the others. For this we create a easy way to tests all sizes at same time.
|
||||
|
||||
You can use the new `haveValidDynamicSizeSnapshot` and `recordDynamicSizeSnapshot`
|
||||
matchers to test multiple sizes at once:
|
||||
|
||||
```swift
|
||||
let sizes = ["SmallSize": CGSize(width: 44, height: 44),
|
||||
"MediumSize": CGSize(width: 88, height: 88),
|
||||
"LargeSize": CGSize(width: 132, height: 132)]
|
||||
|
||||
// expect(view).to(recordDynamicSizeSnapshot(sizes: sizes))
|
||||
expect(view).to(haveValidDynamicSizeSnapshot(sizes: sizes))
|
||||
|
||||
// You can also just test some sizes:
|
||||
expect(view).to(haveValidDynamicSizeSnapshot(sizes: sizes))
|
||||
|
||||
// If you prefer the == syntax, we got you covered too:
|
||||
expect(view) == dynamicSizeSnapshot(sizes: sizes)
|
||||
expect(view) == dynamicSizeSnapshot(sizes: sizes)
|
||||
```
|
||||
|
||||
By default, the size will be set on the view using the frame property. To change this behavior
|
||||
you can use the `ResizeMode` enum:
|
||||
|
||||
```swift
|
||||
public enum ResizeMode {
|
||||
case frame
|
||||
case constrains
|
||||
case block(resizeBlock: (UIView, CGSize) -> Void)
|
||||
case custom(viewResizer: ViewResizer)
|
||||
}
|
||||
```
|
||||
To use the enum you can `expect(view) == dynamicSizeSnapshot(sizes: sizes, resizeMode: newResizeMode)`.
|
||||
For custom behavior you can use `ResizeMode.block`. The block will be call on every resize. Or you can
|
||||
implement the `ViewResizer` protocol and resize yourself.
|
||||
The custom behavior can be used to record the views too.
|
||||
|
||||
For more info on usage, check the [dynamic sizes tests](Bootstrap/BootstrapTests/DynamicSizeTests.swift).
|
||||
5
iOS/Example/Pods/Nimble-Snapshots/XCTestObservationCenter+CurrentTestCaseTracker.h
generated
Normal file
5
iOS/Example/Pods/Nimble-Snapshots/XCTestObservationCenter+CurrentTestCaseTracker.h
generated
Normal file
@@ -0,0 +1,5 @@
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
@interface XCTestObservationCenter (CurrentTestCaseTracker)
|
||||
|
||||
@end
|
||||
10
iOS/Example/Pods/Nimble-Snapshots/XCTestObservationCenter+CurrentTestCaseTracker.m
generated
Normal file
10
iOS/Example/Pods/Nimble-Snapshots/XCTestObservationCenter+CurrentTestCaseTracker.m
generated
Normal file
@@ -0,0 +1,10 @@
|
||||
#import "XCTestObservationCenter+CurrentTestCaseTracker.h"
|
||||
#import "Nimble_Snapshots/Nimble_Snapshots-Swift.h"
|
||||
|
||||
@implementation XCTestObservationCenter (CurrentTestCaseTracker)
|
||||
|
||||
+ (void)load {
|
||||
[[self sharedTestObservationCenter] addTestObserver:[CurrentTestCaseTracker shared]];
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user