├── .gitignore
├── .travis.yml
├── LICENSE
├── LayoutTest.podspec
├── LayoutTest.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── xcshareddata
│ └── xcschemes
│ ├── LayoutTest.xcscheme
│ └── LayoutTestBase.xcscheme
├── LayoutTest
├── Info.plist
├── LayoutTest-Bridging-Header.h
├── LayoutTest.h
├── Swift
│ └── LayoutTestCase.swift
└── TestCase
│ ├── LYTLayoutFailingTestSnapshotRecorder.h
│ ├── LYTLayoutFailingTestSnapshotRecorder.m
│ ├── LYTLayoutTestCase.h
│ └── LYTLayoutTestCase.m
├── LayoutTestBase.podspec
├── LayoutTestBase
├── Autolayout
│ ├── LYTAutolayoutFailureIntercepter.h
│ └── LYTAutolayoutFailureIntercepter.m
├── Catalog
│ ├── LYTCatalogCollectionViewController.h
│ ├── LYTCatalogCollectionViewController.m
│ ├── LYTCatalogTableViewController.h
│ ├── LYTCatalogTableViewController.m
│ └── LYTViewCatalogProvider.h
├── Config
│ ├── LYTConfig.h
│ └── LYTConfig.m
├── Core
│ ├── Generators
│ │ ├── LYTBoolValues.h
│ │ ├── LYTBoolValues.m
│ │ ├── LYTDataValues.h
│ │ ├── LYTDataValues.m
│ │ ├── LYTFloatValues.h
│ │ ├── LYTFloatValues.m
│ │ ├── LYTImageValues.h
│ │ ├── LYTImageValues.m
│ │ ├── LYTIntegerValues.h
│ │ ├── LYTIntegerValues.m
│ │ ├── LYTStringValues.h
│ │ ├── LYTStringValues.m
│ │ └── Private
│ │ │ ├── LYTValuesIterator.h
│ │ │ └── LYTValuesIterator.m
│ ├── Helpers
│ │ ├── LYTMutableCopier.h
│ │ └── LYTMutableCopier.m
│ ├── LYTViewProvider.h
│ ├── Testers
│ │ ├── LYTLayoutPropertyTester.h
│ │ └── LYTLayoutPropertyTester.m
│ └── ViewSizing
│ │ ├── LYTDeviceConstants.h
│ │ ├── LYTDeviceConstants.m
│ │ ├── LYTViewSize.h
│ │ ├── LYTViewSize.m
│ │ ├── UIView+LYTViewSize.h
│ │ └── UIView+LYTViewSize.m
├── Info.plist
├── LayoutTestBase.h
├── Swift
│ ├── LYTDataValues+Swift.swift
│ └── LYTViewSize+Swift.swift
└── UIViewHelpers
│ ├── UIView+LYTFrameComparison.h
│ ├── UIView+LYTFrameComparison.m
│ ├── UIView+LYTHelpers.h
│ ├── UIView+LYTHelpers.m
│ ├── UIView+LYTTestHelpers.h
│ └── UIView+LYTTestHelpers.m
├── LayoutTestTests
├── AutolayoutFailureInterceptorTests.m
├── ConfigTests.m
├── DataValuesTests.m
├── FrameComparisonTests.m
├── Helpers
│ ├── UIViewWithLabel.h
│ ├── UIViewWithLabel.m
│ ├── UnitTestViews.h
│ └── UnitTestViews.m
├── Info.plist
├── IteratorTests.m
├── LayoutTestCaseTests
│ ├── LYTLayoutFailingTestSnapshotRecorderTests.m
│ ├── LayoutReportSnapshotLocationsTests.m
│ ├── LayoutTestCaseAccessibilityControlTests.m
│ ├── LayoutTestCaseAmbiguousTests.m
│ ├── LayoutTestCaseAutolayoutFailureTests.m
│ ├── LayoutTestCaseMissingLabelTests.m
│ ├── LayoutTestCaseMultipleDataOverlapTests.m
│ ├── LayoutTestCaseNestedAccessibilityTests.m
│ ├── LayoutTestCaseOverlapTests.m
│ ├── LayoutTestCaseViewMaxNumberOfCombinationsConfigTests.m
│ ├── LayoutTestCaseViewSizesConfigTests.m
│ ├── LayoutTestCaseViewSizesTests.m
│ ├── LayoutTestCaseWithinSuperviewTests.m
│ └── SwiftLYTLayoutFailingTestSnapshotRecorderTests.swift
├── NullIteratorTests.m
├── SwiftLayoutTestCaseTests.swift
├── TestHelpersTests.m
└── en.lproj
│ └── InfoPlist.strings
├── NOTICE
├── README.md
├── SampleApp
├── CatalogSample
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ ├── Contents.json
│ │ └── LinkedInLogo.imageset
│ │ │ ├── Contents.json
│ │ │ └── LinkedInLogo.png
│ ├── Base.lproj
│ │ └── LaunchScreen.storyboard
│ └── Info.plist
├── Podfile
├── Podfile.lock
├── SampleApp.xcodeproj
│ ├── project.pbxproj
│ └── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── SampleApp.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── SampleApp
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ ├── Contents.json
│ │ └── LinkedInLogo.imageset
│ │ │ ├── Contents.json
│ │ │ └── LinkedInLogo.png
│ ├── Base.lproj
│ │ └── LaunchScreen.storyboard
│ ├── HomeViewController.swift
│ ├── HomeViewController.xib
│ ├── Info.plist
│ ├── SampleFailingView.swift
│ ├── SampleFailingView.xib
│ ├── SampleTableViewCell.swift
│ └── SampleTableViewCell.xib
└── SampleAppTests
│ ├── Info.plist
│ ├── SampleFailingLayoutTestsWithSnapshots.swift
│ └── SampleTableViewCellLayoutTests.swift
├── TestProjects
├── README
└── SampleAppiOS7
│ ├── Podfile
│ ├── Podfile.lock
│ ├── README
│ ├── SampleAppiOS7.xcodeproj
│ └── project.pbxproj
│ ├── SampleAppiOS7
│ ├── AppDelegate.h
│ ├── AppDelegate.m
│ ├── Assets.xcassets
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ ├── Info.plist
│ ├── ViewController.h
│ ├── ViewController.m
│ └── main.m
│ └── SampleAppiOS7Tests
│ ├── Info.plist
│ └── SampleAppiOS7Tests.m
├── build.sh
└── docs
├── Makefile
├── README
├── bin
└── update_theme.sh
├── conf.py
├── images
└── catalog.png
├── index.rst
├── make.bat
└── pages
├── 000_gettingStarted.rst
├── 010_overview.rst
├── 020_writingTest.rst
├── 030_viewSizes.rst
├── 040_includedTests.rst
├── 050_propertyTesting.rst
├── 060_catalog.rst
├── 070_apis.rst
├── 080_config.rst
├── 090_testingOtherObjects.rst
├── 100_performance.rst
├── 110_projectStructure.rst
├── 120_FAQ.rst
└── 130_future.rst
/.gitignore:
--------------------------------------------------------------------------------
1 | # XCode
2 | LayoutTest.xcodeproj/xcuserdata/*
3 | LayoutTest.xcworkspace/xcuserdata/*
4 | LayoutTest.xcodeproj/project.xcworkspace/xcuserdata
5 |
6 | # Cocoapods
7 | Pods
8 |
9 | # Sample App
10 | xcuserdata
11 |
12 | # Docs
13 | _build
14 | .buildinfo
15 |
16 | # macos
17 | .DS_Store
18 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: objective-c
2 | osx_image: xcode10.2
3 | script: sh build.sh
4 | after_success:
5 | - bash <(curl -s https://codecov.io/bash)
6 |
--------------------------------------------------------------------------------
/LayoutTest.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |spec|
2 | spec.name = 'LayoutTest'
3 | spec.version = '6.1.0'
4 | spec.license = { :type => 'Apache License, Version 2.0' }
5 | spec.homepage = 'https://linkedin.github.io/LayoutTest-iOS'
6 | spec.authors = 'LinkedIn'
7 | spec.summary = 'LayoutTest enables you to write unit tests which test the layout of a view in multiple configurations.'
8 | spec.source = { :git => 'https://github.com/linkedin/LayoutTest-iOS.git', :tag => spec.version }
9 | spec.platform = :ios, '12.0'
10 | spec.default_subspecs = 'TestCase'
11 | spec.swift_version = '5.0'
12 |
13 | spec.subspec 'Swift' do |sp|
14 | sp.dependency 'LayoutTest/SwiftSubspec'
15 | end
16 |
17 | spec.subspec 'TestCase' do |sp|
18 | sp.source_files = 'LayoutTest/TestCase'
19 | sp.dependency 'LayoutTestBase/Autolayout'
20 | sp.dependency 'LayoutTestBase/Catalog'
21 | sp.dependency 'LayoutTestBase/Config'
22 | sp.dependency 'LayoutTestBase/Core'
23 | sp.dependency 'LayoutTestBase/UIViewHelpers'
24 | sp.framework = 'XCTest'
25 | end
26 |
27 | spec.subspec 'SwiftSubspec' do |sp|
28 | sp.source_files = 'LayoutTest/Swift', 'LayoutTest/LayoutTest.h'
29 | sp.dependency 'LayoutTest/TestCase'
30 | sp.dependency 'LayoutTestBase/Swift'
31 | end
32 | end
33 |
34 |
--------------------------------------------------------------------------------
/LayoutTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/LayoutTest.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/LayoutTest.xcodeproj/xcshareddata/xcschemes/LayoutTest.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
38 |
39 |
40 |
41 |
43 |
49 |
50 |
51 |
52 |
53 |
63 |
64 |
70 |
71 |
72 |
73 |
79 |
80 |
86 |
87 |
88 |
89 |
91 |
92 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/LayoutTest.xcodeproj/xcshareddata/xcschemes/LayoutTestBase.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
44 |
50 |
51 |
52 |
53 |
59 |
60 |
66 |
67 |
68 |
69 |
71 |
72 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/LayoutTest/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/LayoutTest/LayoutTest-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | //
2 | // Use this file to import your target's public headers that you would like to expose to Swift.
3 | //
4 |
5 | #import "LYTLayoutFailingTestSnapshotRecorder.h"
--------------------------------------------------------------------------------
/LayoutTest/LayoutTest.h:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | /**
11 | This file is included in cocoapods because without it, Xcode refuses to build the project. It shouldn't cause any harm.
12 | */
13 |
14 | #import
15 | #import
16 | #import
17 |
18 | //! Project version number for LayoutTest.
19 | FOUNDATION_EXPORT double LayoutTestBaseVersionNumber;
20 |
21 | //! Project version string for LayoutTest.
22 | FOUNDATION_EXPORT const unsigned char LayoutTestBaseVersionString[];
23 |
24 | #pragma mark - Main Classes
25 |
26 | #import "LYTLayoutTestCase.h"
27 |
--------------------------------------------------------------------------------
/LayoutTest/TestCase/LYTLayoutFailingTestSnapshotRecorder.h:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import
11 | #import
12 | #import
13 |
14 | NS_SWIFT_NAME(LayoutFailingTestSnapshotRecorder)
15 | @interface LYTLayoutFailingTestSnapshotRecorder : NSObject
16 |
17 | /**
18 | Singleton accessor.
19 | */
20 | + (instancetype)sharedInstance;
21 |
22 | /**
23 | Creates the new folder structure and starts index.html file for the given class. If a folder already exists for the class it will be deleted first to clear any images previously saved for failing tests in that class.
24 |
25 | Folder structure for images follows the pattern: {DERIVED_DATA}/LayoutTestImages/{CLASS_NAME}/index.html
26 | Index.html file includes tables with failure description, image and the data that the image failed with.
27 |
28 | \param invocationClass The class to start a new log for, class name is used in the directory structure to save the snapshots.
29 | */
30 | - (void)startNewLogForClass:(Class)invocationClass;
31 |
32 | /**
33 | Finishes the index.html log for the current invocation class by adding closing html tags.
34 | Can be called safely without calling startNewLogForClass: as no index.html file for the class with exists so nothing will happen.
35 |
36 | Then file path to the index.html file for the current class is saved after all of the tests in the test class finish running. Once all test classes in have finished all saved failing test index.html file paths are logged to the console.
37 | */
38 | - (void)finishLog;
39 |
40 | /**
41 | Renders the view passed as a png and saves it. The failure description, image and data are added to the index.html file for the current test class.
42 | The invocation is used to create a folder for the invocations method name and the failing views for that invocation are saved within. Folder structure for invocation follows the pattern: {DERIVED_DATA}/LayoutTestImages/{CLASS_NAME}/{METHOD_NAME}/
43 | Images sare saved with the file name format: Width-{VIEW_WIDTH}_Height-{VIEW_HEIGHT}_Data-{DATA_DESCRIPTION_HASH}.png
44 |
45 | \param view The view to save a image of.
46 | \param data The data that was used to poulate the view
47 | \param invocation The invocation for the current test. When used from a XCTestCase subclass the invocation will include the name of the test method currently being run.
48 | \param failureDescription The description of why the view that is being saved failed its layout tests.
49 | */
50 | - (void)saveImageOfView:(UIView *)view withData:(NSDictionary *)data fromInvocation:(NSInvocation *)invocation failureDescription:(NSString *)failureDescription;
51 |
52 | @end
53 |
--------------------------------------------------------------------------------
/LayoutTestBase.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |spec|
2 | spec.name = 'LayoutTestBase'
3 | spec.version = '6.1.0'
4 | spec.license = { :type => 'Apache License, Version 2.0' }
5 | spec.homepage = 'https://linkedin.github.io/LayoutTest-iOS'
6 | spec.authors = 'LinkedIn'
7 | spec.summary = 'Base library for LayoutTest without XCTest dependency. LayoutTest enables you to write unit tests which test the layout of views.'
8 | spec.source = { :git => 'https://github.com/linkedin/LayoutTest-iOS.git', :tag => spec.version }
9 | spec.platform = :ios, '12.0'
10 | spec.frameworks = 'Foundation', 'UIKit'
11 | spec.default_subspecs = 'Core', 'Autolayout', 'Catalog', 'Config', 'UIViewHelpers'
12 | spec.swift_version = '5.0'
13 |
14 | spec.subspec 'Swift' do |sp|
15 | sp.source_files = 'LayoutTestBase/Swift/**/*', 'LayoutTestBase/LayoutTestBase.h'
16 | sp.dependency 'LayoutTestBase/Core'
17 | sp.dependency 'LayoutTestBase/Autolayout'
18 | sp.dependency 'LayoutTestBase/Catalog'
19 | sp.dependency 'LayoutTestBase/Config'
20 | sp.dependency 'LayoutTestBase/UIViewHelpers'
21 | end
22 |
23 | spec.subspec 'Core' do |sp|
24 | sp.source_files = 'LayoutTestBase/Core/**/*'
25 | sp.dependency 'LayoutTestBase/Config'
26 | end
27 |
28 | spec.subspec 'Autolayout' do |sp|
29 | sp.source_files = 'LayoutTestBase/Autolayout'
30 | end
31 |
32 | spec.subspec 'Catalog' do |sp|
33 | sp.source_files = 'LayoutTestBase/Catalog'
34 | sp.dependency 'LayoutTestBase/Core'
35 | end
36 |
37 | spec.subspec 'Config' do |sp|
38 | sp.source_files = 'LayoutTestBase/Config'
39 | end
40 |
41 | spec.subspec 'UIViewHelpers' do |sp|
42 | sp.source_files = 'LayoutTestBase/UIViewHelpers'
43 | sp.dependency 'LayoutTestBase/Config'
44 | end
45 | end
46 |
47 |
--------------------------------------------------------------------------------
/LayoutTestBase/Autolayout/LYTAutolayoutFailureIntercepter.h:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import
11 |
12 | NS_ASSUME_NONNULL_BEGIN
13 |
14 | /**
15 | This class intercepts autolayout failures. It's useful for testing to ensure that we don't break any constraints.
16 |
17 | It should NOT ever be used in prod. This is because it relies on Apple's private methods which is brittle. Your app will also possibly be rejected
18 | from the App Store because of using private APIs.
19 | It swizzles a private method on a private class (NSISEngine) and runs a block whenever this method is run. This method always gets run when there
20 | is a layout failure (handleUnsatisfiableRowWithHead:body:usingInfeasibilityHandlingBehavior:mutuallyExclusiveConstraints:).
21 |
22 | If Apple updates their implementation, this class will need to be changed.
23 | */
24 | NS_SWIFT_NAME(AutolayoutFailureIntercepter)
25 | @interface LYTAutolayoutFailureIntercepter : NSObject
26 |
27 | /**
28 | Start intecerpting autolayout failures and run a block whenever you find one. This block is strongly retained, so be careful of retain cycles.
29 |
30 | Calling this method multiple times will cause the block to be reset.
31 | */
32 | + (void)interceptAutolayoutFailuresWithBlock:(void(^)(void))block;
33 |
34 | /**
35 | Stops intercepting autolayout failures and nils out the current block (releasing it from memory).
36 | */
37 | + (void)stopInterceptingAutolayoutFailures;
38 |
39 | @end
40 |
41 | NS_ASSUME_NONNULL_END
42 |
--------------------------------------------------------------------------------
/LayoutTestBase/Autolayout/LYTAutolayoutFailureIntercepter.m:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import "LYTAutolayoutFailureIntercepter.h"
11 | #import
12 |
13 |
14 | #define NSStringViewClassName @"UIView"
15 | #define CStringViewClassName "UIView"
16 |
17 | @implementation LYTAutolayoutFailureIntercepter
18 |
19 | + (void)interceptAutolayoutFailuresWithBlock:(void(^)(void))block {
20 | Class class = NSClassFromString(NSStringViewClassName);
21 | [self validateAutolayoutInterceptionForClass:class];
22 | [class interceptAutolayoutFailuresWithBlock:block];
23 | }
24 |
25 | + (void)stopInterceptingAutolayoutFailures {
26 | Class class = NSClassFromString(NSStringViewClassName);
27 | [self validateAutolayoutInterceptionForClass:class];
28 | [class stopInterceptingAutolayoutFailures];
29 | }
30 |
31 | + (void)validateAutolayoutInterceptionForClass:(Class)class {
32 | #pragma clang diagnostic push
33 | #pragma clang diagnostic ignored "-Wundeclared-selector"
34 | if (class && [class instancesRespondToSelector:@selector(engine:willBreakConstraint:dueToMutuallyExclusiveConstraints:)]) {
35 | return;
36 | }
37 | #pragma clang diagnostic pop
38 | NSAssert(false, @"This class no longer exists or it no longer implements the method we swizzle. This means that Apple has changed their implementation of Auto Layout and this code needs to be updated. Please file a bug with this information.");
39 | }
40 |
41 | @end
42 |
43 | static void(^savedBlock)(void);
44 | static BOOL swizzledAutolayout = false;
45 |
46 | @interface NSObject (Private)
47 | @end
48 |
49 | @implementation NSObject (Private)
50 |
51 | /**
52 | This method will run a block whenever there are autolayout failures. Running it multiple times will remove the old block and add a new block.
53 | */
54 | + (void)interceptAutolayoutFailuresWithBlock:(void(^)(void))block {
55 | savedBlock = block;
56 |
57 | if (!swizzledAutolayout) {
58 | swizzledAutolayout = YES;
59 |
60 | [self swizzleAutolayoutMethod];
61 | }
62 | }
63 |
64 | + (void)stopInterceptingAutolayoutFailures {
65 | savedBlock = nil;
66 |
67 | if (swizzledAutolayout) {
68 | swizzledAutolayout = NO;
69 |
70 | [self swizzleAutolayoutMethod];
71 | }
72 | }
73 |
74 | + (void)swizzleAutolayoutMethod {
75 | // Here we switch the implementations of our method and the actual method
76 | // This will either turn on or turn off the feature
77 | #pragma clang diagnostic push
78 | #pragma clang diagnostic ignored "-Wundeclared-selector"
79 | Method original = class_getInstanceMethod(objc_getClass(CStringViewClassName), @selector(engine:willBreakConstraint:dueToMutuallyExclusiveConstraints:));
80 | #pragma clang diagnostic pop
81 |
82 | Method swizzled = class_getInstanceMethod(self, @selector(swizzle_engine:willBreakConstraint:dueToMutuallyExclusiveConstraints:));
83 | method_exchangeImplementations(original, swizzled);
84 | }
85 |
86 | - (id)swizzle_engine:(void *)engine willBreakConstraint:(void *)constraints dueToMutuallyExclusiveConstraints:(void *)mutuallyExclusiveConstraints {
87 | savedBlock();
88 |
89 | // After running our block, we call the original implementation
90 | // This looks like an infinite loop, but it isn't because we switched the implementations. So now, this goes to the original method.
91 | return [self swizzle_engine:engine willBreakConstraint:constraints dueToMutuallyExclusiveConstraints:mutuallyExclusiveConstraints];
92 | }
93 |
94 | @end
95 |
--------------------------------------------------------------------------------
/LayoutTestBase/Catalog/LYTCatalogCollectionViewController.h:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import
11 |
12 |
13 | NS_ASSUME_NONNULL_BEGIN
14 |
15 | @protocol LYTViewCatalogProvider;
16 |
17 | NS_SWIFT_NAME(CatalogCollectionViewController)
18 | @interface LYTCatalogCollectionViewController : UICollectionViewController
19 |
20 | @property (nonatomic, nullable) Class ViewProviderClass;
21 |
22 | @end
23 |
24 | NS_ASSUME_NONNULL_END
25 |
--------------------------------------------------------------------------------
/LayoutTestBase/Catalog/LYTCatalogCollectionViewController.m:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import "LYTCatalogCollectionViewController.h"
11 | // View Provider Protocol
12 | #import "LYTViewProvider.h"
13 | #import "LYTViewCatalogProvider.h"
14 | // Test runner
15 | #import "LYTLayoutPropertyTester.h"
16 |
17 |
18 | @interface LYTCatalogCollectionViewController ()
19 |
20 | @property (nonatomic, strong) NSArray *dataArray;
21 |
22 | @end
23 |
24 | @implementation LYTCatalogCollectionViewController
25 |
26 | - (instancetype)init {
27 | UICollectionViewFlowLayout* flowLayout = [[UICollectionViewFlowLayout alloc] init];
28 | [flowLayout setScrollDirection:UICollectionViewScrollDirectionVertical];
29 |
30 | self = [super initWithCollectionViewLayout:flowLayout];
31 | return self;
32 | }
33 |
34 | - (void)viewDidLoad {
35 | [super viewDidLoad];
36 |
37 | Class viewProviderClass = self.ViewProviderClass;
38 | if (!viewProviderClass) {
39 | NSAssert(NO, @"No ViewProviderClass set");
40 | return;
41 | }
42 |
43 | [viewProviderClass registerClassOnCollectionView:self.collectionView];
44 |
45 | NSMutableArray *dataArray = [NSMutableArray array];
46 |
47 | LYTTesterLimitResults limitResults = LYTTesterLimitResultsNoSizes;
48 | if ([viewProviderClass respondsToSelector:@selector(limitResultsForCatalogByAssumingDataValueIndependence)]) {
49 | if ([viewProviderClass limitResultsForCatalogByAssumingDataValueIndependence]) {
50 | limitResults &= LYTTesterLimitResultsLimitDataCombinations;
51 | }
52 | }
53 | [LYTLayoutPropertyTester runPropertyTestsWithViewProvider:viewProviderClass
54 | limitResults:limitResults
55 | validation:^(__unused UIView *view, NSDictionary *data, __unused id context) {
56 | [dataArray addObject:data];
57 | }];
58 | self.dataArray = dataArray;
59 | }
60 |
61 | #pragma mark - Table view data source
62 |
63 | - (CGSize)collectionView:(__unused UICollectionView *)collectionView
64 | layout:(__unused UICollectionViewLayout *)collectionViewLayout
65 | sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
66 | return [self.ViewProviderClass sizeForCollectionViewCellForCatalogFromData:self.dataArray[(NSUInteger)indexPath.row] viewWidth:self.view.frame.size.width];
67 | }
68 |
69 | - (NSInteger)collectionView:(__unused UICollectionView *)collectionView
70 | numberOfItemsInSection:(__unused NSInteger)section {
71 | return (NSInteger)self.dataArray.count;
72 | }
73 |
74 | - (UICollectionViewCell *)collectionView:(__unused UICollectionView *)collectionView
75 | cellForItemAtIndexPath:(NSIndexPath *)indexPath {
76 | UICollectionViewCell *cell = [self.collectionView dequeueReusableCellWithReuseIdentifier:[self.ViewProviderClass reuseIdentifier] forIndexPath:indexPath];
77 |
78 | id context = nil;
79 | NSError *error;
80 | cell = (UICollectionViewCell *)[self.ViewProviderClass viewForData:self.dataArray[(NSUInteger)indexPath.row]
81 | reuseView:cell
82 | size:nil
83 | context:&context
84 | error:&error];
85 | NSAssert(error == nil, error.description);
86 |
87 | return cell;
88 | }
89 |
90 | @end
91 |
--------------------------------------------------------------------------------
/LayoutTestBase/Catalog/LYTCatalogTableViewController.h:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import
11 |
12 |
13 | NS_ASSUME_NONNULL_BEGIN
14 |
15 | @protocol LYTViewCatalogProvider;
16 |
17 | NS_SWIFT_NAME(CatalogTableViewController)
18 | @interface LYTCatalogTableViewController : UITableViewController
19 |
20 | @property (nonatomic, nullable) Class ViewProviderClass;
21 |
22 | @end
23 |
24 | NS_ASSUME_NONNULL_END
25 |
--------------------------------------------------------------------------------
/LayoutTestBase/Catalog/LYTCatalogTableViewController.m:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import "LYTCatalogTableViewController.h"
11 | // View Provider Protocol
12 | #import "LYTViewProvider.h"
13 | #import "LYTViewCatalogProvider.h"
14 | // Test runner
15 | #import "LYTLayoutPropertyTester.h"
16 |
17 |
18 | @interface LYTCatalogTableViewController ()
19 |
20 | @property (nonatomic, strong) NSArray *dataArray;
21 |
22 | @end
23 |
24 | @implementation LYTCatalogTableViewController
25 |
26 | - (void)viewDidLoad {
27 | [super viewDidLoad];
28 |
29 | Class viewProviderClass = self.ViewProviderClass;
30 | if (!viewProviderClass) {
31 | NSAssert(NO, @"No ViewProviderClass set");
32 | return;
33 | }
34 |
35 | [self.ViewProviderClass registerClassOnTableView:self.tableView];
36 |
37 | NSMutableArray *dataArray = [NSMutableArray array];
38 |
39 | LYTTesterLimitResults limitResults = LYTTesterLimitResultsNoSizes;
40 | if ([viewProviderClass respondsToSelector:@selector(limitResultsForCatalogByAssumingDataValueIndependence)]) {
41 | if ([viewProviderClass limitResultsForCatalogByAssumingDataValueIndependence]) {
42 | limitResults &= LYTTesterLimitResultsLimitDataCombinations;
43 | }
44 | }
45 | [LYTLayoutPropertyTester runPropertyTestsWithViewProvider:viewProviderClass
46 | limitResults:limitResults
47 | validation:^(__unused UIView *view, NSDictionary *data, __unused id context) {
48 | [dataArray addObject:data];
49 | }];
50 | self.dataArray = dataArray;
51 |
52 | self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
53 | }
54 |
55 | #pragma mark - Table view data source
56 |
57 | - (CGFloat)tableView:(__unused UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
58 | return [self.ViewProviderClass heightForTableViewCellForCatalogFromData:self.dataArray[(NSUInteger)indexPath.row]];
59 | }
60 |
61 | - (NSInteger)tableView:(__unused UITableView *)tableView numberOfRowsInSection:(__unused NSInteger)section {
62 | return (NSInteger)self.dataArray.count;
63 | }
64 |
65 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
66 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:[self.ViewProviderClass reuseIdentifier] forIndexPath:indexPath];
67 |
68 | if ([(id)self.ViewProviderClass respondsToSelector:@selector(tableViewCellForCatalogFromData:reuseCell:)]) {
69 | cell = [self.ViewProviderClass tableViewCellForCatalogFromData:self.dataArray[(NSUInteger)indexPath.row] reuseCell:cell];
70 | } else {
71 | NSError *error;
72 | cell = (UITableViewCell *)[self.ViewProviderClass viewForData:self.dataArray[(NSUInteger)indexPath.row]
73 | reuseView:cell
74 | size:nil
75 | context:nil
76 | error:&error];
77 | NSAssert(error == nil, error.description);
78 | NSAssert([cell isKindOfClass:[UITableViewCell class]], @"If your view is not a UITableViewCell, you must implement cellForCatalogFromData:reuseCell:");
79 | }
80 |
81 | cell.selectionStyle = UITableViewCellSelectionStyleNone;
82 |
83 | return cell;
84 | }
85 |
86 | @end
87 |
--------------------------------------------------------------------------------
/LayoutTestBase/Catalog/LYTViewCatalogProvider.h:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import
11 | #import "LYTViewProvider.h"
12 |
13 |
14 | NS_ASSUME_NONNULL_BEGIN
15 |
16 | NS_SWIFT_NAME(ViewCatalogProvider)
17 | @protocol LYTViewCatalogProvider
18 |
19 | /**
20 | This method is only needed for creating a LYTCatalog for your views and is only required if viewForData:reuseView: does not return a UITableViewCell.
21 | You should add your view to a UITableViewCell and return it.
22 |
23 | \param data Data for the view.
24 | \param reuseCell Reuse cell. May be nil.
25 | \returns UITableViewCell with the view inside.
26 | */
27 | @optional
28 | + (UITableViewCell *)tableViewCellForCatalogFromData:(NSDictionary *)data reuseCell:(UITableViewCell * _Nullable)reuseCell;
29 |
30 | /**
31 | Specifies the height of the cells given data. You only need this if you are using the UITableViewCell catalog.
32 |
33 | \param data Data for the cell.
34 | \returns Height for the UITableViewCell.
35 | */
36 | @optional
37 | + (CGFloat)heightForTableViewCellForCatalogFromData:(NSDictionary *)data;
38 |
39 | /**
40 | Specifies the height of the cells given data. You only need this if you are using the UICollectionViewCell catalog.
41 |
42 | \param data Data for the cell.
43 | \param viewWidth The current width of the view presenting the data.
44 | \returns Size for the UICollectionViewCell.
45 | */
46 | @optional
47 | + (CGSize)sizeForCollectionViewCellForCatalogFromData:(NSDictionary *)data viewWidth:(CGFloat)viewWidth;
48 |
49 | /**
50 | Needed for creating a collection view catalog. This method should register either your nib or class with the collection view.
51 |
52 | \param collectionView The collection view you should register with.
53 | */
54 | @optional
55 | + (void)registerClassOnCollectionView:(UICollectionView *)collectionView;
56 |
57 | /**
58 | Needed for creating a table view catalog. This method should register either your nib or class with the table view.
59 |
60 | \param tableView The table view you should register with.
61 | */
62 | @optional
63 | + (void)registerClassOnTableView:(UITableView *)tableView;
64 |
65 | /**
66 | Reuse identifier to use on the cell.
67 | */
68 | @optional
69 | + (NSString *)reuseIdentifier;
70 |
71 | /**
72 | The property tests test a lot of different combinations. Often this is too many to contain in a catalog. Usually, given a LYTDataValues subclass
73 | that outputs 1, 2 and another which outputs a, b, c you'd get the following pairs of data:
74 |
75 | (1, a), (2, a), (1, b), (2, b), (1, c), (2, c)
76 |
77 | If this method returns true, it will not do all combinations, but keep all other fields static while iterating through one field. So you'll get:
78 |
79 | (1, a), (2, a), (1, b), (1, c)
80 |
81 | In this way, we're assuming that the parameters are independent so you don't need to test every combination. In most cases, this provides wide coverage.
82 |
83 | \returns Whether we should limit the results for the LYTCatalog
84 | */
85 | @optional
86 | + (BOOL)limitResultsForCatalogByAssumingDataValueIndependence;
87 |
88 | @end
89 |
90 | NS_ASSUME_NONNULL_END
91 |
--------------------------------------------------------------------------------
/LayoutTestBase/Config/LYTConfig.m:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import
11 | #import "LYTConfig.h"
12 |
13 | @implementation LYTConfig
14 |
15 | NSInteger const LYTSaveUnlimitedSnapshotsPerMethod = -1;
16 |
17 | + (instancetype)sharedInstance {
18 | static LYTConfig *sharedInstance = nil;
19 | static dispatch_once_t onceToken;
20 | dispatch_once(&onceToken, ^{
21 | sharedInstance = [[LYTConfig alloc] init];
22 | });
23 | return sharedInstance;
24 | }
25 |
26 | - (instancetype)init {
27 | self = [super init];
28 | [self resetDefaults];
29 | return self;
30 | }
31 |
32 | - (void)resetDefaults {
33 | self.maxNumberOfCombinations = nil;
34 | self.viewSizesToTest = nil;
35 | self.viewOverlapTestsEnabled = YES;
36 | self.viewWithinSuperviewTestsEnabled = YES;
37 | self.ambiguousAutolayoutTestsEnabled = YES;
38 | self.interceptsAutolayoutErrors = YES;
39 | self.accessibilityTestsEnabled = YES;
40 | self.failingTestSnapshotsEnabled = YES;
41 | /*
42 | UISwitch - This is a known class which has internal overlapping subviews.
43 | UITextView - If you use attributed text, UIKit may add UIImage views which can overlap with the internal text containers.
44 | UIButton - If you set a background image, then this will overlap with other internal views.
45 | */
46 | self.viewClassesAllowingSubviewErrors = [NSSet setWithObjects:[UISwitch class], [UITextView class], [UIButton class], nil];
47 | /*
48 | UIControl - Subclasses of UIControl should all have accessibility labels.
49 | */
50 | self.viewClassesRequiringAccessibilityLabels = [NSSet setWithObjects:[UIControl class], nil];
51 | self.cgFloatEpsilon = 1e-5f;
52 | self.snapshotsToSavePerMethod = LYTSaveUnlimitedSnapshotsPerMethod;
53 | }
54 |
55 | @end
56 |
57 | BOOL LYTEpsilonEqual(CGFloat x, CGFloat y) {
58 | CGFloat epsilon = [LYTConfig sharedInstance].cgFloatEpsilon;
59 | return fabs(x - y) < epsilon;
60 | }
61 |
--------------------------------------------------------------------------------
/LayoutTestBase/Core/Generators/LYTBoolValues.h:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import "LYTDataValues.h"
11 |
12 |
13 | NS_ASSUME_NONNULL_BEGIN
14 |
15 | NS_SWIFT_NAME(BoolValues)
16 | @interface LYTBoolValues : LYTDataValues
17 |
18 | @end
19 |
20 | NS_ASSUME_NONNULL_END
21 |
--------------------------------------------------------------------------------
/LayoutTestBase/Core/Generators/LYTBoolValues.m:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import "LYTBoolValues.h"
11 |
12 | @implementation LYTBoolValues
13 |
14 | - (NSArray *)values {
15 | return @[
16 | @(YES),
17 | @(NO)
18 | ];
19 | }
20 |
21 | @end
22 |
--------------------------------------------------------------------------------
/LayoutTestBase/Core/Generators/LYTDataValues.h:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import
11 |
12 |
13 | NS_ASSUME_NONNULL_BEGIN
14 |
15 | /**
16 | This is the base class for all LYTDataValues subclasses used by the testing library. You can use these LYTDataValues subclasses as values in the
17 | dictionary returned by (NSDictionary *)dataSpecForTest and it will be converted into many different specific values by the library. There are three
18 | main ways you can use this class:
19 |
20 | - Use one of the built in LYTDataValues subclasses (LYTStringValues, LYTFloatValues, etc.)
21 | - Create a one time use object with - (id)initWithValues:(NSArray *)values;
22 | - Create your own subclasses
23 |
24 | To create your own subclass, the only method you need to override is the - (NSArray *)values. Simple return all the values you want the LYTDataValues
25 | subclass to return.
26 |
27 | NSNull
28 |
29 | If you put NSNull into the values array, it will be converted to nil (as in, remove the key from the final JSON).
30 | */
31 | NS_SWIFT_NAME(DataValues)
32 | @interface LYTDataValues : NSObject
33 |
34 | /**
35 | Returns all the possible replacement values for this LYTDataValues class. This is the only method you should override when subclassing.
36 |
37 | \warning You should not use this to access the values that the generator represents. You should only use this for subclassing.
38 | */
39 | @property (nonatomic, readonly) NSArray *values;
40 |
41 | /**
42 | Returns LYTDataValues with all values.
43 | */
44 | - (instancetype)init;
45 |
46 | /**
47 | Returns LYTDataValues with all values except nil. Use this for required fields.
48 | */
49 | - (instancetype)initWithRequired:(BOOL)required;
50 |
51 | /**
52 | Returns LYTDataValues with these specific values and overrides any subclass's behavior.
53 | */
54 | - (instancetype)initWithValues:(NSArray *)values NS_REFINED_FOR_SWIFT;
55 |
56 | /**
57 | Returns LYTDataValues with all the values that return true from the filter block.
58 | */
59 | - (instancetype)initWithFilter:(BOOL(^ _Nullable)(id value))filter;
60 |
61 | /**
62 | Returns the number of values this object contains.
63 | */
64 | - (NSUInteger)numberOfValues;
65 |
66 | /**
67 | Returns a value for a certain index. If out of range, it will throw and exception.
68 | */
69 | - (id)valueAtIndex:(NSUInteger)index;
70 |
71 | @end
72 |
73 | NS_ASSUME_NONNULL_END
74 |
--------------------------------------------------------------------------------
/LayoutTestBase/Core/Generators/LYTDataValues.m:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import "LYTDataValues.h"
11 |
12 | @interface LYTDataValues ()
13 |
14 | @property (nonatomic, strong) NSArray *filteredValues;
15 |
16 | @end
17 |
18 | @implementation LYTDataValues
19 |
20 | - (id)init {
21 | return [self initWithFilter:nil];
22 | }
23 |
24 | - (id)initWithRequired:(BOOL)required {
25 | return [self initWithFilter:^BOOL(id value) {
26 | return (value != nil) || !required;
27 | }];
28 | }
29 |
30 | - (id)initWithValues:(NSArray *)values {
31 | self = [super init];
32 | if (self) {
33 | self.filteredValues = values;
34 | }
35 | return self;
36 | }
37 |
38 | - (id)initWithFilter:(BOOL(^)(id value))filter {
39 | self = [super init];
40 | if (self) {
41 | if (filter) {
42 | NSMutableArray *filteredValues = [NSMutableArray array];
43 | for (id object in self.values) {
44 | id reportedObject = object;
45 | if ([object isKindOfClass:[NSNull class]]) {
46 | reportedObject = nil;
47 | }
48 | if (filter(reportedObject)) {
49 | [filteredValues addObject:object];
50 | }
51 | }
52 | self.filteredValues = filteredValues;
53 | } else {
54 | self.filteredValues = self.values;
55 | }
56 |
57 | NSAssert([self.filteredValues count] > 0, @"After filtering the values, there must be at least one value for the LYTDataValues subclass.");
58 | }
59 |
60 | return self;
61 | }
62 |
63 | - (NSUInteger)numberOfValues {
64 | return [self.filteredValues count];
65 | }
66 |
67 | - (id)valueAtIndex:(NSUInteger)index {
68 | return self.filteredValues[index];
69 | }
70 |
71 | @end
72 |
--------------------------------------------------------------------------------
/LayoutTestBase/Core/Generators/LYTFloatValues.h:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import "LYTDataValues.h"
11 |
12 |
13 | NS_ASSUME_NONNULL_BEGIN
14 |
15 | NS_SWIFT_NAME(FloatValues)
16 | @interface LYTFloatValues : LYTDataValues
17 |
18 | @end
19 |
20 | NS_ASSUME_NONNULL_END
21 |
--------------------------------------------------------------------------------
/LayoutTestBase/Core/Generators/LYTFloatValues.m:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import "LYTFloatValues.h"
11 |
12 | @implementation LYTFloatValues
13 |
14 | - (NSArray *)values {
15 | return @[
16 | @(4.5324),
17 | @(0),
18 | [NSNull null],
19 | @(-M_PI),
20 | @(10),
21 | @(MAXFLOAT),
22 | @(-MAXFLOAT),
23 | @(0.000000000001)
24 | ];
25 | }
26 |
27 | @end
28 |
--------------------------------------------------------------------------------
/LayoutTestBase/Core/Generators/LYTImageValues.h:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import "LYTDataValues.h"
11 |
12 |
13 | NS_ASSUME_NONNULL_BEGIN
14 |
15 | NS_SWIFT_NAME(ImageValues)
16 | @interface LYTImageValues : LYTDataValues
17 |
18 | @end
19 |
20 | NS_ASSUME_NONNULL_END
21 |
22 |
--------------------------------------------------------------------------------
/LayoutTestBase/Core/Generators/LYTImageValues.m:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import "LYTImageValues.h"
11 |
12 | @implementation LYTImageValues
13 |
14 | - (NSArray *)values {
15 | return @[
16 | [self generateImageWithWidth:1000 height:1000],
17 | [self generateImageWithWidth:10 height:10],
18 | [self generateImageWithWidth:10 height:1000],
19 | [self generateImageWithWidth:1000 height:10],
20 | ];
21 | }
22 |
23 | - (UIImage *)generateImageWithWidth:(CGFloat)width height:(CGFloat)height {
24 | CGSize size = CGSizeMake(width, height);
25 | UIGraphicsBeginImageContextWithOptions(size, YES, 0);
26 | [[UIColor redColor] setFill];
27 | UIRectFill(CGRectMake(0, 0, size.width, size.height));
28 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
29 | UIGraphicsEndImageContext();
30 | return image;
31 | }
32 |
33 | @end
34 |
35 |
--------------------------------------------------------------------------------
/LayoutTestBase/Core/Generators/LYTIntegerValues.h:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import "LYTDataValues.h"
11 |
12 |
13 | NS_ASSUME_NONNULL_BEGIN
14 |
15 | NS_SWIFT_NAME(IntegerValues)
16 | @interface LYTIntegerValues : LYTDataValues
17 |
18 | @end
19 |
20 | NS_ASSUME_NONNULL_END
21 |
--------------------------------------------------------------------------------
/LayoutTestBase/Core/Generators/LYTIntegerValues.m:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import "LYTIntegerValues.h"
11 |
12 | @implementation LYTIntegerValues
13 |
14 | - (NSArray *)values {
15 | return @[
16 | @(42),
17 | @(0),
18 | [NSNull null],
19 | @(-34),
20 | @(NSIntegerMax),
21 | @(NSIntegerMin)
22 | ];
23 | }
24 |
25 | @end
26 |
--------------------------------------------------------------------------------
/LayoutTestBase/Core/Generators/LYTStringValues.h:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import "LYTDataValues.h"
11 |
12 |
13 | NS_ASSUME_NONNULL_BEGIN
14 |
15 | NS_SWIFT_NAME(StringValues)
16 | @interface LYTStringValues : LYTDataValues
17 |
18 | @end
19 |
20 | NS_ASSUME_NONNULL_END
21 |
--------------------------------------------------------------------------------
/LayoutTestBase/Core/Generators/LYTStringValues.m:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import "LYTStringValues.h"
11 |
12 | @implementation LYTStringValues
13 |
14 | - (NSArray *)values {
15 | return @[
16 | @"Normal length string",
17 | @"",
18 | [NSNull null],
19 | @"Very long string. This string is so long that it's longer than I want it to be. Just a really really long string. In fact, it's just long as long can be. I'm just lengthening the longest string by making it longer with more characters. Do you think this is long enough yet? I think so, so I'm going to stop making this long string longer by adding more characters.",
20 | @"漢語 ♔ 🚂 ☎ ‰ 🚀 Here are some more special characters ˆ™£‡‹·Ú‹›`å∑œ´®∆ƒ∆√˜Ω≥µ˜ƒª•"
21 | ];
22 | }
23 |
24 | @end
25 |
--------------------------------------------------------------------------------
/LayoutTestBase/Core/Generators/Private/LYTValuesIterator.h:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import
11 |
12 |
13 | NS_ASSUME_NONNULL_BEGIN
14 |
15 | @class LYTDataValues;
16 |
17 | NS_SWIFT_NAME(ValuesIterator)
18 | @interface LYTValuesIterator : NSObject
19 |
20 | @property (nonatomic, strong, nullable) LYTDataValues *dataValues;
21 | @property (nonatomic) NSUInteger index;
22 |
23 | @end
24 |
25 | NS_ASSUME_NONNULL_END
26 |
--------------------------------------------------------------------------------
/LayoutTestBase/Core/Generators/Private/LYTValuesIterator.m:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import "LYTValuesIterator.h"
11 |
12 | @implementation LYTValuesIterator
13 |
14 | @end
15 |
--------------------------------------------------------------------------------
/LayoutTestBase/Core/Helpers/LYTMutableCopier.h:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import
11 |
12 |
13 | NS_ASSUME_NONNULL_BEGIN
14 |
15 | @interface NSDictionary (LYTMutableCopier)
16 |
17 | - (NSDictionary *)lyt_mutableDeepCopyWithReplaceBlock:(id(^)(id object))replacementBlock;
18 |
19 | @end
20 |
21 | @interface NSArray (LYTMutableCopier)
22 |
23 | - (NSArray *)lyt_mutableDeepCopyWithReplaceBlock:(id(^)(id object))replacementBlock;
24 |
25 | @end
26 |
27 | NS_ASSUME_NONNULL_END
28 |
--------------------------------------------------------------------------------
/LayoutTestBase/Core/Helpers/LYTMutableCopier.m:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import "LYTMutableCopier.h"
11 |
12 | @implementation NSDictionary (LYTMutableCopier)
13 |
14 | - (NSDictionary *)lyt_mutableDeepCopyWithReplaceBlock:(id(^)(id object))replacementBlock {
15 | NSMutableDictionary *newDictionary = [NSMutableDictionary dictionary];
16 | for (id key in [self allKeys]) {
17 | id object = self[key];
18 | // Default is just to set it to the same value
19 | id newObject = object;
20 | if ([object isKindOfClass:[NSArray class]]) {
21 | newObject = [(NSArray *)object lyt_mutableDeepCopyWithReplaceBlock:replacementBlock];
22 | } else if ([object isKindOfClass:[NSDictionary class]]) {
23 | newObject = [(NSDictionary *)object lyt_mutableDeepCopyWithReplaceBlock:replacementBlock];
24 | } else if (replacementBlock) {
25 | newObject = replacementBlock(object);
26 | }
27 | if (newObject) {
28 | // New object can be nil. If so, we'll just leave it out.
29 | [newDictionary setObject:newObject forKey:key];
30 | }
31 | }
32 | return newDictionary;
33 | }
34 |
35 | @end
36 |
37 | @implementation NSArray (LYTMutableCopier)
38 |
39 | - (NSArray *)lyt_mutableDeepCopyWithReplaceBlock:(id(^)(id object))replacementBlock {
40 | NSMutableArray *newArray = [NSMutableArray array];
41 | for (id object in self) {
42 | // Default is just to set it to the same value
43 | id newObject = object;
44 | if ([object isKindOfClass:[NSArray class]]) {
45 | newObject = [(NSArray *)object lyt_mutableDeepCopyWithReplaceBlock:replacementBlock];
46 | } else if ([object isKindOfClass:[NSDictionary class]]) {
47 | newObject = [(NSDictionary *)object lyt_mutableDeepCopyWithReplaceBlock:replacementBlock];
48 | } else if (replacementBlock) {
49 | newObject = replacementBlock(object);
50 | }
51 | if (newObject) {
52 | // New object can be nil. If so, we'll just leave it out.
53 | [newArray addObject:newObject];
54 | }
55 | }
56 | return newArray;
57 | }
58 |
59 | @end
60 |
--------------------------------------------------------------------------------
/LayoutTestBase/Core/LYTViewProvider.h:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import
11 |
12 |
13 | @class LYTViewSize;
14 |
15 | NS_ASSUME_NONNULL_BEGIN
16 |
17 | NS_SWIFT_NAME(ViewProvider)
18 | @protocol LYTViewProvider
19 |
20 | #pragma mark - Main Methods
21 |
22 | /**
23 | This is the template for the dictionary that will be passed to the viewForData:reuseView: method. You can insert LYTDataValues subclasses as values in this dictionary for generating different combinations of data. For example, the dictionary:
24 |
25 | @{ @"key": [[LYTStringValues alloc] init] }
26 |
27 | Will produce a bunch of dictionaries:
28 |
29 | @{ @"key": @"Normal length string" },
30 | @{ @"key": @"" },
31 | @{ }, etc.
32 |
33 | \returns Template for the data to test on.
34 | */
35 | @required
36 | + (nullable NSDictionary *)dataSpecForTestWithError:(NSError * _Nullable *)error;
37 |
38 | /**
39 | This method should return a view to run your tests on. You should always use the reuse view if you can, and if not, should recreate a view with the same size as the reuseView. You should then inflate the view with your data and return it.
40 |
41 | You may optionally set some context. This can be anything and will be passed back to you in the completion block of the test. You should first check that context is not nil before trying to set it.
42 |
43 | \param data Data to inflate int the view. This data will not contain any LYTDataValues subclasses. It is nonoptional and will never be nil.
44 | \param view Reuse view to reinflate with new data. It may be nil.
45 | \param context You can set this to anything and it will be returned to you in the completion block of the test. Could be nil.
46 | \returns View inflated with data
47 | */
48 | @required
49 | + (nullable UIView *)viewForData:(NSDictionary *)data
50 | reuseView:(nullable UIView *)reuseView
51 | size:(nullable LYTViewSize *)size
52 | context:(id _Nullable * _Nullable)context
53 | error:(NSError * _Nullable *)error;
54 |
55 | /**
56 | Returns an array of NSValue objects which wrap a CGSize struct. These are all the sizes that we should test on. The way this works is a little tricky though. It will return a reuse view of this specific size to viewForData:reuseView:. So if that method resizes the view or recreates the view, then this data will be lost.
57 |
58 | The data will first be tested on the 'default size' of the view or whatever is returned by viewForData:, then iterate through each of these sizes. First it will resize the view, then pass it to viewForData:reuseView:
59 |
60 | \returns NSArray of NSValues which encapsulate an CGSize
61 | */
62 | @optional
63 | + (NSArray *)sizesForView;
64 |
65 | /**
66 | This is called after viewForData:reuseView:size:context: and after the view has been resized using the sizes provided. This gives you the opportunity to make any final changes to the view based on the size of the view before any assertions are made.
67 |
68 | For example, if you use multiple different widths to test your view, you may want to adjust the height in this method.
69 |
70 | \param view The view which we are about to test. This is already setup with data.
71 | \param data The data used to layout the view.
72 | \param size This size used to resize the view. This size has already been applied.
73 | \param context If a context was set in viewForData:reuseView:size:context:, it will be passed back to you here.
74 | */
75 | @optional
76 | + (void)adjustViewSize:(UIView *)view
77 | data:(NSDictionary *)data
78 | size:(nullable LYTViewSize *)size
79 | context:(nullable id)context
80 | error:(NSError * _Nullable *)error
81 | __attribute__((swift_error(nonnull_error)));
82 |
83 | @end
84 |
85 | NS_ASSUME_NONNULL_END
86 |
--------------------------------------------------------------------------------
/LayoutTestBase/Core/Testers/LYTLayoutPropertyTester.h:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import
11 |
12 |
13 | NS_ASSUME_NONNULL_BEGIN
14 |
15 | /**
16 | This enum is a bit mask (aka NS_OPTIONS/OptionSet) which you can use to limit results.
17 |
18 | LYTTesterLimitResultsNone - Default value. No options are set.
19 | LYTTesterLimitResultsLimitDataCombinations - Usually, we do all possible combinations of the data. However, if you set this flag, we will do a polynomial
20 | set of options instead of exponential. Every value in the data set will be run at least once, but not all possible combinations will be run.
21 | LYTTesterLimitResultsNoSizes - This will ignore any view sizes that are set in the LYTViewProvider or the LYTConfig.
22 | */
23 | NS_SWIFT_NAME(TesterLimitResults)
24 | typedef NS_OPTIONS(NSInteger, LYTTesterLimitResults) {
25 | LYTTesterLimitResultsNone = 0,
26 | LYTTesterLimitResultsLimitDataCombinations = 1 << 0,
27 | LYTTesterLimitResultsNoSizes = 1 << 1
28 | };
29 |
30 | NS_SWIFT_NAME(LayoutPropertyTester)
31 | @interface LYTLayoutPropertyTester : NSObject
32 |
33 | /**
34 | Runs a suite of tests on a given viewProvider. This is the main method to be used for your unit tests.
35 |
36 | Instead of calling this directly, it's more common to subclass the more powerful LYTLayoutTestCase.
37 |
38 | \param viewProvider Class to test. Must conform to LYTViewProvider.
39 | \param validation Block to validate the view given the data. The data here will not contain any LYTDataValues subclasses and both the view and data
40 | will never be nil. Here you should assert on the properties of the view. If you set a context in your viewForData: method, it will be passed back here.
41 | */
42 | + (void)runPropertyTestsWithViewProvider:(Class)viewProvider validation:(NS_NOESCAPE void(^)(id view, NSDictionary *data, _Nullable id context))validation;
43 |
44 | /**
45 | Runs a suite of tests on a given viewProvider. This is the main method to be used for your unit tests.
46 |
47 | Instead of calling this directly, it's more common to subclass the more powerful LYTLayoutTestCase.
48 |
49 | \param viewProvider Class to test. Must conform to LYTViewProvider.
50 | \param validation Block to validate the view given the data. The data here will not contain any LYTDataValues subclasses and both the view and data
51 | will never be nil. Here you should assert on the properties of the view. If you set a context in your viewForData: method, it will be passed back here.
52 | \param limitResults Use this parameter to run less combinations. This is useful if you're running into performance problems. See LYTTesterLimitResults
53 | docs for more info.
54 | */
55 | + (void)runPropertyTestsWithViewProvider:(Class)viewProvider limitResults:(LYTTesterLimitResults)limitResults validation:(NS_NOESCAPE void(^)(id view, NSDictionary *data, id _Nullable context))validation;
56 |
57 | @end
58 |
59 | NS_ASSUME_NONNULL_END
60 |
--------------------------------------------------------------------------------
/LayoutTestBase/Core/ViewSizing/LYTDeviceConstants.h:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import
11 |
12 | /**
13 | These constants define Apple device dimensions in points. They are useful when defining a LYTViewSize.
14 |
15 | All the values are in points (not pixels). Width is by convention always the shorter side.
16 | */
17 |
18 | extern CGFloat const LYTiPhone4Width;
19 | extern CGFloat const LYTiPhone4Height;
20 | extern CGFloat const LYTiPhone5Width;
21 | extern CGFloat const LYTiPhone5Height;
22 | extern CGFloat const LYTiPhone6Width;
23 | extern CGFloat const LYTiPhone6Height;
24 | extern CGFloat const LYTiPhone6PlusWidth;
25 | extern CGFloat const LYTiPhone6PlusHeight;
26 | extern CGFloat const LYTiPadWidth;
27 | extern CGFloat const LYTiPadHeight;
28 | extern CGFloat const LYTiPadProWidth;
29 | extern CGFloat const LYTiPadProHeight;
30 | extern CGFloat const LYTAppleWatch38Width;
31 | extern CGFloat const LYTAppleWatch38Height;
32 | extern CGFloat const LYTAppleWatch42Width;
33 | extern CGFloat const LYTAppleWatch42Height;
34 |
--------------------------------------------------------------------------------
/LayoutTestBase/Core/ViewSizing/LYTDeviceConstants.m:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import "LYTDeviceConstants.h"
11 |
12 |
13 | CGFloat const LYTiPhone4Width = 320;
14 | CGFloat const LYTiPhone4Height = 480;
15 | CGFloat const LYTiPhone5Width = 320;
16 | CGFloat const LYTiPhone5Height = 568;
17 | CGFloat const LYTiPhone6Width = 375;
18 | CGFloat const LYTiPhone6Height = 667;
19 | CGFloat const LYTiPhone6PlusWidth = 414;
20 | CGFloat const LYTiPhone6PlusHeight = 736;
21 | CGFloat const LYTiPhone15Width = 390;
22 | CGFloat const LYTiPhone15Height = 844;
23 | CGFloat const LYTiPhone15PlusWidth = 430;
24 | CGFloat const LYTiPhone15PlusHeight = 932;
25 | CGFloat const LYTiPhone15ProWidth = 393;
26 | CGFloat const LYTiPhone15ProHeight = 852;
27 | CGFloat const LYTiPhone15ProMaxWidth = 430;
28 | CGFloat const LYTiPhone15ProMaxHeight = 932;
29 | CGFloat const LYTiPadWidth = 768;
30 | CGFloat const LYTiPadHeight = 1024;
31 | CGFloat const LYTiPadProWidth = 1024;
32 | CGFloat const LYTiPadProHeight = 1366;
33 | CGFloat const LYTAppleWatch38Width = 272;
34 | CGFloat const LYTAppleWatch38Height = 340;
35 | CGFloat const LYTAppleWatch42Width = 312;
36 | CGFloat const LYTAppleWatch42Height = 390;
37 |
--------------------------------------------------------------------------------
/LayoutTestBase/Core/ViewSizing/LYTViewSize.h:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import
11 |
12 | NS_ASSUME_NONNULL_BEGIN
13 |
14 | /**
15 | When providing sizes to test your view on, you may want to test the view on different widths, heights or both. This object let's you specify sizes
16 | like this. Both width and height are optional. So, if you specify a width and no height, then the library will resize your view's width, but not edit the height.
17 |
18 | For example, let's say you're testing a UITableViewCell subclass with a dynamic height. You want to test it on different devices, so you return
19 |
20 | @code
21 | @[ [[LYTViewSize alloc] initWithWidth:@(LYTDeviceConstants.LYTiPhone4Width)], [[LYTViewSize alloc] initWithWidth:@(LYTDeviceConstants.LYTiPhone6PlusWidth)] ]
22 | @endcode
23 |
24 | This will test the view on multiple different widths, but not change the height. If you need to manually set the height based on the data being set,
25 | then you should implement adjustViewSize:data:size:context:.
26 |
27 |
28 | You can utitilize predefined constants or enter a custom value.
29 | */
30 | NS_SWIFT_NAME(ViewSize)
31 | @interface LYTViewSize : NSObject
32 |
33 | /**
34 | The width for the view. If nil, do not edit the width.
35 | */
36 | @property (nonatomic, strong, readonly, nullable) NSNumber *width NS_REFINED_FOR_SWIFT;
37 | /**
38 | The height for the view. If nil, do not edit the height.
39 | */
40 | @property (nonatomic, strong, readonly, nullable) NSNumber *height NS_REFINED_FOR_SWIFT;
41 |
42 | /**
43 | Creates a view size which will change both the width and the height of the view.
44 | */
45 | - (instancetype)initWithWidth:(nullable NSNumber *)width height:(nullable NSNumber *)height NS_REFINED_FOR_SWIFT;
46 |
47 | /**
48 | Creates a view size which will only change the width of the view.
49 | */
50 | - (instancetype)initWithWidth:(NSNumber *)width NS_SWIFT_UNAVAILABLE("init(width:height:) takes optional arguments instead");
51 |
52 | /**
53 | Creates a view size which will only change the height of the view.
54 | */
55 |
56 | - (instancetype)initWithHeight:(NSNumber *)height NS_SWIFT_UNAVAILABLE("init(width:height:) takes optional arguments instead");
57 |
58 | @end
59 |
60 | NS_ASSUME_NONNULL_END
61 |
--------------------------------------------------------------------------------
/LayoutTestBase/Core/ViewSizing/LYTViewSize.m:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import "LYTViewSize.h"
11 |
12 |
13 | @interface LYTViewSize ()
14 |
15 | @property (nonatomic, strong, nullable) NSNumber *width;
16 | @property (nonatomic, strong, nullable) NSNumber *height;
17 |
18 | @end
19 |
20 | @implementation LYTViewSize
21 |
22 | - (id)initWithWidth:(NSNumber *)width height:(NSNumber *)height {
23 | self = [super init];
24 | self.width = width;
25 | self.height = height;
26 | return self;
27 | }
28 |
29 | - (instancetype)initWithWidth:(NSNumber *)width {
30 | return [self initWithWidth:width height:nil];
31 | }
32 |
33 | - (instancetype)initWithHeight:(NSNumber *)height {
34 | return [self initWithWidth:nil height:height];
35 | }
36 |
37 | @end
38 |
--------------------------------------------------------------------------------
/LayoutTestBase/Core/ViewSizing/UIView+LYTViewSize.h:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import
11 | #import "LYTViewSize.h"
12 |
13 | NS_ASSUME_NONNULL_BEGIN
14 |
15 | @interface UIView (LYTViewSize)
16 |
17 | - (void)lyt_setSize:(nullable LYTViewSize *)size NS_SWIFT_NAME(setSize(_:));
18 |
19 | @end
20 |
21 | NS_ASSUME_NONNULL_END
22 |
--------------------------------------------------------------------------------
/LayoutTestBase/Core/ViewSizing/UIView+LYTViewSize.m:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import "UIView+LYTViewSize.h"
11 |
12 |
13 | NS_ASSUME_NONNULL_BEGIN
14 |
15 | @implementation UIView (LYTViewSize)
16 |
17 | - (void)lyt_setSize:(nullable LYTViewSize *)size {
18 | CGFloat height = self.bounds.size.height;
19 | if (size.height) {
20 | height = size.height.floatValue;
21 | }
22 | CGFloat width = self.bounds.size.width;
23 | if (size.width) {
24 | width = size.width.floatValue;
25 | }
26 |
27 | self.bounds = CGRectMake(self.bounds.origin.x, self.bounds.origin.y, width, height);
28 | }
29 |
30 | @end
31 |
32 | NS_ASSUME_NONNULL_END
33 |
--------------------------------------------------------------------------------
/LayoutTestBase/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/LayoutTestBase/LayoutTestBase.h:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | /**
11 | This file is included in cocoapods because without it, Xcode refuses to build the project. It shouldn't cause any harm.
12 | */
13 |
14 | #import
15 | #import
16 |
17 | //! Project version number for LayoutTest.
18 | FOUNDATION_EXPORT double LayoutTestBaseVersionNumber;
19 |
20 | //! Project version string for LayoutTest.
21 | FOUNDATION_EXPORT const unsigned char LayoutTestBaseVersionString[];
22 |
23 | #pragma mark Main Classes
24 |
25 | #import "LYTViewProvider.h"
26 | #import "LYTViewCatalogProvider.h"
27 | #import "LYTConfig.h"
28 | #import "LYTLayoutPropertyTester.h"
29 |
30 | #pragma mark View Sizing
31 |
32 | #import "LYTViewSize.h"
33 | #import "UIView+LYTViewSize.h"
34 | #import "LYTDeviceConstants.h"
35 |
36 | #pragma mark Auto Layout
37 |
38 | #import "LYTAutolayoutFailureIntercepter.h"
39 |
40 | #pragma mark Data Values
41 |
42 | #import "LYTDataValues.h"
43 | #import "LYTBoolValues.h"
44 | #import "LYTFloatValues.h"
45 | #import "LYTIntegerValues.h"
46 | #import "LYTStringValues.h"
47 |
48 | #pragma mark Test Helpers
49 |
50 | #import "UIView+LYTHelpers.h"
51 | #import "UIView+LYTTestHelpers.h"
52 | #import "UIView+LYTFrameComparison.h"
53 |
54 | #pragma mark Catalog
55 |
56 | #import "LYTCatalogCollectionViewController.h"
57 | #import "LYTCatalogTableViewController.h"
58 |
--------------------------------------------------------------------------------
/LayoutTestBase/Swift/LYTDataValues+Swift.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LYTDataValues+Swift.swift
3 | // LayoutTest
4 | //
5 | // Created by Peter Livesey on 9/30/16.
6 | // Copyright © 2016 LinkedIn. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension DataValues {
12 | /**
13 | Returns LYTDataValues with these specific values and overrides any subclass's behavior.
14 | This function converts all nil values to NSNull which is what the library expects.
15 | These will be represented as empty entries in the dictionary.
16 | */
17 | public convenience init(values: [Any?]) {
18 | let array: [Any] = values.map { value in
19 | if let value = value {
20 | return value
21 | } else {
22 | return NSNull()
23 | }
24 | }
25 | self.init(__values: array)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/LayoutTestBase/Swift/LYTViewSize+Swift.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LYTViewSize+Swift.swift
3 | // LayoutTest
4 | //
5 | // Created by Peter Livesey on 9/30/16.
6 | // Copyright © 2016 LinkedIn. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension ViewSize {
12 |
13 | /**
14 | The width for the view. If nil, do not edit the width.
15 | */
16 | public var width: CGFloat? {
17 | get {
18 | return __width.flatMap { CGFloat( $0.floatValue ) }
19 | }
20 | }
21 |
22 | /**
23 | The height for the view. If nil, do not edit the height.
24 | */
25 | public var height: CGFloat? {
26 | get {
27 | return __height.flatMap { CGFloat( $0.floatValue ) }
28 | }
29 | }
30 |
31 | /**
32 | Creates a view size which will either the width, height, or both.
33 | If width or height is set to nil, that dimension will not be changed.
34 | */
35 | public convenience init(width: CGFloat? = nil, height: CGFloat? = nil) {
36 | self.init(__width: width.flatMap { NSNumber(value: Double($0)) }, height: height.flatMap { NSNumber(value: Double($0)) })
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/LayoutTestBase/UIViewHelpers/UIView+LYTFrameComparison.m:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import "UIView+LYTFrameComparison.h"
11 | #import "UIView+LYTHelpers.h"
12 | #import "LYTConfig.h"
13 |
14 |
15 | @implementation UIView (LYTFrameComparison)
16 |
17 | #pragma mark - Frames
18 |
19 | - (BOOL)lyt_before:(UIView *)otherView {
20 | return [self lyt_before:otherView fromCenter:NO];
21 | }
22 |
23 | - (BOOL)lyt_before:(UIView *)otherView fromCenter:(BOOL)fromCenter {
24 | CGRect otherViewBounds = [self convertRect:otherView.bounds fromView:otherView];
25 | CGFloat epsilon = [LYTConfig sharedInstance].cgFloatEpsilon;
26 |
27 | if ([self lyt_leftToRight]) {
28 | if (fromCenter) {
29 | return self.center.x <= otherViewBounds.origin.x + epsilon;
30 | } else {
31 | return self.bounds.origin.x + self.bounds.size.width <= otherViewBounds.origin.x + epsilon;
32 | }
33 | } else {
34 | if (fromCenter) {
35 | return self.bounds.origin.x + epsilon >= (otherViewBounds.origin.x + otherViewBounds.size.width/2);
36 | } else {
37 | return self.bounds.origin.x + epsilon >= otherViewBounds.origin.x + otherViewBounds.size.width;
38 | }
39 | }
40 | }
41 |
42 | - (BOOL)lyt_after:(UIView *)otherView {
43 | return [otherView lyt_before:self];
44 | }
45 |
46 | - (BOOL)lyt_after:(UIView *)otherView fromCenter:(BOOL)fromCenter {
47 | return [otherView lyt_before:self fromCenter:fromCenter];
48 | }
49 |
50 | - (BOOL)lyt_above:(UIView *)otherView {
51 | CGRect otherViewBounds = [self convertRect:otherView.bounds fromView:otherView];
52 | CGFloat epsilon = [LYTConfig sharedInstance].cgFloatEpsilon;
53 |
54 | return self.bounds.origin.y + self.bounds.size.height <= otherViewBounds.origin.y + epsilon;
55 | }
56 |
57 | - (BOOL)lyt_below:(UIView *)otherView {
58 | return [otherView lyt_above:self];
59 | }
60 |
61 | #pragma mark - Alignment
62 |
63 | - (BOOL)lyt_leadingAligned:(UIView *)otherView {
64 | CGRect otherViewBounds = [self convertRect:otherView.bounds fromView:otherView];
65 | if ([self lyt_leftToRight]) {
66 | return LYTEpsilonEqual(self.bounds.origin.x, otherViewBounds.origin.x);
67 | } else {
68 | return LYTEpsilonEqual(self.bounds.origin.x + self.bounds.size.width, otherViewBounds.origin.x + otherViewBounds.size.width);
69 | }
70 | }
71 |
72 | - (BOOL)lyt_trailingAligned:(UIView *)otherView {
73 | CGRect otherViewBounds = [self convertRect:otherView.bounds fromView:otherView];
74 | if ([self lyt_leftToRight]) {
75 | return LYTEpsilonEqual(self.bounds.origin.x + self.bounds.size.width, otherViewBounds.origin.x + otherViewBounds.size.width);
76 | } else {
77 | return LYTEpsilonEqual(self.bounds.origin.x, otherViewBounds.origin.x);
78 | }
79 | }
80 |
81 | - (BOOL)lyt_topAligned:(UIView *)otherView {
82 | CGRect otherViewBounds = [self convertRect:otherView.bounds fromView:otherView];
83 | return LYTEpsilonEqual(self.bounds.origin.y, otherViewBounds.origin.y);
84 | }
85 |
86 | - (BOOL)lyt_bottomAligned:(UIView *)otherView {
87 | CGRect otherViewBounds = [self convertRect:otherView.bounds fromView:otherView];
88 | return LYTEpsilonEqual(self.bounds.origin.y + self.bounds.size.height, otherViewBounds.origin.y + otherViewBounds.size.height);
89 | }
90 |
91 | #pragma mark - Private Helpers
92 |
93 | - (BOOL)lyt_leftToRight {
94 | switch ([UIApplication sharedApplication].userInterfaceLayoutDirection) {
95 | case UIUserInterfaceLayoutDirectionLeftToRight:
96 | return YES;
97 | case UIUserInterfaceLayoutDirectionRightToLeft:
98 | return NO;
99 | }
100 | }
101 |
102 | @end
103 |
--------------------------------------------------------------------------------
/LayoutTestBase/UIViewHelpers/UIView+LYTHelpers.h:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import
11 |
12 | NS_ASSUME_NONNULL_BEGIN
13 |
14 | /**
15 | This file exposes some useful helpers for testing views.
16 | */
17 | @interface UIView (LYTHelpers)
18 |
19 | /**
20 | Returns .frame.origin.y
21 |
22 | When setting, it will keep the height and width constant and change frame.origin.y
23 | */
24 | @property (nonatomic) CGFloat lyt_top;
25 |
26 | /**
27 | Returns .frame.origin.x
28 |
29 | When setting, it will keep the height and width constant and change frame.origin.x
30 | */
31 | @property (nonatomic) CGFloat lyt_left;
32 |
33 | /**
34 | Returns .frame.origin.x + .frame.size.width
35 |
36 | When setting, it will keep the height and width constant and change frame.origin.x
37 | */
38 | @property (nonatomic) CGFloat lyt_right;
39 |
40 | /**
41 | Returns .frame.origin.y + .frame.size.height
42 |
43 | When setting, it will keep the height and width constant and change frame.origin.y
44 | */
45 | @property (nonatomic) CGFloat lyt_bottom;
46 |
47 | /**
48 | Returns the width of the view
49 | */
50 | @property (nonatomic) CGFloat lyt_width;
51 |
52 | /**
53 | Returns the height of the view
54 | */
55 | @property (nonatomic) CGFloat lyt_height;
56 |
57 | @end
58 |
59 | NS_ASSUME_NONNULL_END
60 |
--------------------------------------------------------------------------------
/LayoutTestBase/UIViewHelpers/UIView+LYTHelpers.m:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import "UIView+LYTHelpers.h"
11 |
12 | @implementation UIView (LYTHelpers)
13 |
14 | - (CGFloat)lyt_left {
15 | return self.frame.origin.x;
16 | }
17 |
18 | - (void)setLyt_left:(CGFloat)x {
19 | CGRect frame = self.frame;
20 | frame.origin.x = x;
21 | self.frame = frame;
22 | }
23 |
24 | - (CGFloat)lyt_top {
25 | return self.frame.origin.y;
26 | }
27 |
28 | - (void)setLyt_top:(CGFloat)y {
29 | CGRect frame = self.frame;
30 | frame.origin.y = y;
31 | self.frame = frame;
32 | }
33 |
34 | - (CGFloat)lyt_right {
35 | return self.frame.origin.x + self.frame.size.width;
36 | }
37 |
38 | - (void)setLyt_right:(CGFloat)right {
39 | self.lyt_left = right - self.lyt_width;
40 | }
41 |
42 | - (CGFloat)lyt_bottom {
43 | return self.frame.origin.y + self.frame.size.height;
44 | }
45 |
46 | - (void)setLyt_bottom:(CGFloat)bottom {
47 | self.lyt_top = bottom - self.lyt_height;
48 | }
49 |
50 | - (CGFloat)lyt_width {
51 | return self.frame.size.width;
52 | }
53 |
54 | - (void)setLyt_width:(CGFloat)width {
55 | CGRect frame = self.frame;
56 | frame.size.width = width;
57 | self.frame = frame;
58 | }
59 |
60 | - (CGFloat)lyt_height {
61 | return self.frame.size.height;
62 | }
63 |
64 | - (void)setLyt_height:(CGFloat)height {
65 | CGRect frame = self.frame;
66 | frame.size.height = height;
67 | self.frame = frame;
68 | }
69 |
70 | @end
71 |
--------------------------------------------------------------------------------
/LayoutTestBase/UIViewHelpers/UIView+LYTTestHelpers.h:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import
11 |
12 | NS_ASSUME_NONNULL_BEGIN
13 |
14 | @interface UIView (LYTTestHelpers)
15 |
16 | /**
17 | Useful helper for writing tests. Ensures that all the subviews of the view are within the bounds of their superviews. If this condition breaks, it
18 | calls the error block. This is not recursive.
19 |
20 | When comparing CGFloats, (x > y) if and only if (x > y + epsilon). This epsilon value is defined in LYTConfig.
21 |
22 | @discussion When calling this on UITableViewCells, you should probably call it on
23 | cell.contentView because it sometimes fails for the contentView in it's
24 | superview due to some UIKit weirdness.
25 | */
26 | - (void)lyt_assertViewWithinSuperViewBounds:(void(^)(NSString *error, UIView *view))errorBlock NS_SWIFT_NAME(lyt_assertViewWithinSuperViewBounds(_:));
27 |
28 | /**
29 | Useful helper for writing tests. Ensures recursively that all the subviews of the view are within the bounds of their superviews. If this condition breaks,
30 | it calls the error block.
31 |
32 | When comparing CGFloats, (x > y) if and only if (x > y + epsilon). This epsilon value is defined in LYTConfig.
33 |
34 | @discussion When calling this on UITableViewCells, you should probably call it
35 | on cell.contentView because it sometimes fails for the contentView in it's
36 | superview due to some UIKit weirdness.
37 | */
38 | - (void)lyt_recursivelyAssertViewWithinSuperViewBounds:(NS_NOESCAPE void(^)(NSString *error, UIView *view))errorBlock NS_SWIFT_NAME(lyt_recursivelyAssertViewWithinSuperViewBounds(_:));
39 |
40 | /**
41 | Useful helper for writing tests. Ensures that none of the subviews of this view overlap.
42 |
43 | When comparing CGFloats, (x > y) if and only if (x > y + epsilon). This epsilon value is defined in LYTConfig.
44 | */
45 | - (void)lyt_assertNoSubviewsOverlap:(NS_NOESCAPE void(^)(NSString *error, UIView *view1, UIView *view2))errorBlock NS_SWIFT_NAME(lyt_assertNoSubviewsOverlap(_:));
46 |
47 | /**
48 | Useful helper for writing tests. Ensures that none of the subviews of this view overlap. It calls this recursively on subviews, but does not test all
49 | combinations of subviews (ie. one subview from view tree A and a subview from view tree B). It only tests all combinations of immediate subviews.
50 | However, if you combine this with lyt_recursivelyAssertViewWithinSuperViewBounds: then you can be sure that no subviews from different trees will overlap.
51 |
52 | When comparing CGFloats, (x > y) if and only if (x > y + epsilon). This epsilon value is defined in LYTConfig.
53 | */
54 | - (void)lyt_recursivelyAssertNoSubviewsOverlap:(NS_NOESCAPE void(^)(NSString *error, UIView *view1, UIView *view2))errorBlock NS_SWIFT_NAME(lyt_recursivelyAssertNoSubviewsOverlap(_:));
55 |
56 | /**
57 | This method first returns the current view, then traverses the view hierarchy.
58 | */
59 | - (void)lyt_recursivelyTraverseViewHierarchy:(NS_NOESCAPE void(^)(UIView *subview))subviewBlock NS_SWIFT_NAME(lyt_recursivelyTraverseViewHierarchy(_:));
60 |
61 | /**
62 | This method first returns the current view, then traverses the view hierarchy.
63 |
64 | It also provides a stop parameter. If you set this to true, then the method will stop recursing on this branch only.
65 | */
66 | - (void)lyt_recursivelyTraverseViewHierarchyWithStop:(NS_NOESCAPE void(^)(UIView *subview, BOOL *stopBranch))subviewBlock NS_SWIFT_NAME(lyt_recursivelyTraverseViewHierarchyWithStop(_:));
67 |
68 | @end
69 |
70 | NS_ASSUME_NONNULL_END
71 |
--------------------------------------------------------------------------------
/LayoutTestTests/AutolayoutFailureInterceptorTests.m:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import
11 | #import "LYTAutolayoutFailureIntercepter.h"
12 | // Helpers
13 | #import "UnitTestViews.h"
14 |
15 |
16 | @interface AutolayoutFailureInterceptorTests : XCTestCase
17 |
18 | @end
19 |
20 | @implementation AutolayoutFailureInterceptorTests
21 |
22 | - (void)tearDown {
23 | [LYTAutolayoutFailureIntercepter stopInterceptingAutolayoutFailures];
24 | [super tearDown];
25 | }
26 |
27 | - (void)testAutolayoutFail {
28 | __block BOOL autolayoutFailed = NO;
29 | [LYTAutolayoutFailureIntercepter interceptAutolayoutFailuresWithBlock:^{
30 | autolayoutFailed = YES;
31 | }];
32 |
33 | UIView *view = [UnitTestViews viewWithIncorrectAutolayout];
34 | [view layoutIfNeeded];
35 |
36 | XCTAssertTrue(autolayoutFailed);
37 | }
38 |
39 | - (void)testAutolayoutSuccess {
40 | __block BOOL autolayoutFailed = NO;
41 | [LYTAutolayoutFailureIntercepter interceptAutolayoutFailuresWithBlock:^{
42 | autolayoutFailed = YES;
43 | }];
44 |
45 | UIView *view = [UnitTestViews viewWithNoProblems];
46 | [view layoutIfNeeded];
47 |
48 | XCTAssertFalse(autolayoutFailed);
49 | }
50 |
51 | - (void)testTurningOffIntercepter {
52 | __block BOOL autolayoutFailed = NO;
53 | [LYTAutolayoutFailureIntercepter interceptAutolayoutFailuresWithBlock:^{
54 | autolayoutFailed = YES;
55 | }];
56 |
57 | [LYTAutolayoutFailureIntercepter stopInterceptingAutolayoutFailures];
58 |
59 | UIView *view = [UnitTestViews viewWithIncorrectAutolayout];
60 | [view layoutIfNeeded];
61 |
62 | XCTAssertFalse(autolayoutFailed);
63 | }
64 |
65 | - (void)testTurningOnIntercepterTwice {
66 | __block BOOL autolayoutFailed = NO;
67 | [LYTAutolayoutFailureIntercepter interceptAutolayoutFailuresWithBlock:^{
68 | autolayoutFailed = YES;
69 | }];
70 | [LYTAutolayoutFailureIntercepter interceptAutolayoutFailuresWithBlock:^{
71 | autolayoutFailed = YES;
72 | }];
73 |
74 | UIView *view = [UnitTestViews viewWithIncorrectAutolayout];
75 | [view layoutIfNeeded];
76 |
77 | XCTAssertTrue(autolayoutFailed);
78 | }
79 |
80 | - (void)testTurningOffIntercepterTwice {
81 | __block BOOL autolayoutFailed = NO;
82 | [LYTAutolayoutFailureIntercepter interceptAutolayoutFailuresWithBlock:^{
83 | autolayoutFailed = YES;
84 | }];
85 |
86 | [LYTAutolayoutFailureIntercepter stopInterceptingAutolayoutFailures];
87 | [LYTAutolayoutFailureIntercepter stopInterceptingAutolayoutFailures];
88 |
89 | UIView *view = [UnitTestViews viewWithIncorrectAutolayout];
90 | [view layoutIfNeeded];
91 |
92 | XCTAssertFalse(autolayoutFailed);
93 | }
94 |
95 | @end
96 |
--------------------------------------------------------------------------------
/LayoutTestTests/DataValuesTests.m:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import
11 | #import
12 | #import "LYTStringValues.h"
13 |
14 |
15 | @interface DataValuesTests : XCTestCase
16 |
17 | @end
18 |
19 | @implementation DataValuesTests
20 |
21 | - (void)testRequiredGeneratorTrue {
22 | LYTDataValues *generator = [[LYTStringValues alloc] initWithRequired:YES];
23 | for (NSUInteger i = 0; i<[generator numberOfValues]; i++) {
24 | XCTAssertNotEqualObjects([NSNull null], [generator valueAtIndex:i]);
25 | }
26 | }
27 |
28 | - (void)testRequiredGeneratorFalse {
29 | LYTDataValues *generator = [[LYTStringValues alloc] initWithRequired:NO];
30 | BOOL foundNull = false;
31 | for (NSUInteger i = 0; i<[generator numberOfValues]; i++) {
32 | foundNull = foundNull || [[generator valueAtIndex:i] isEqual:[NSNull null]];
33 | }
34 | XCTAssertTrue(foundNull);
35 | }
36 |
37 | @end
38 |
--------------------------------------------------------------------------------
/LayoutTestTests/Helpers/UIViewWithLabel.h:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import
11 |
12 | @interface UIViewWithLabel : UIView
13 |
14 | @property (nonatomic) UILabel *label;
15 |
16 | @end
17 |
--------------------------------------------------------------------------------
/LayoutTestTests/Helpers/UIViewWithLabel.m:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import "UIViewWithLabel.h"
11 |
12 | @implementation UIViewWithLabel
13 |
14 | @end
15 |
--------------------------------------------------------------------------------
/LayoutTestTests/Helpers/UnitTestViews.h:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import
11 | #import
12 |
13 | @class UIViewWithLabel;
14 |
15 | @interface UnitTestViews : NSObject
16 |
17 | + (UIView *)viewWithNoProblems;
18 |
19 | + (UIView *)viewWithIncorrectAutolayout;
20 |
21 | + (UIView *)viewWithOverlappingViews;
22 |
23 | + (UIView *)viewWithUISwitchSubview;
24 |
25 | + (UIView *)viewWithSubviewOutOfSuperview;
26 |
27 | + (UIView *)viewWithAmbiguousLayout;
28 |
29 | + (UIView *)viewWithButtonAndAccessibility;
30 |
31 | + (UIView *)viewWithButtonAndNoAccessibility;
32 |
33 | + (UIView *)viewWithNestedAccessibility;
34 |
35 | + (UIView *)viewWithAccessibilityIDButNoLabel;
36 |
37 | + (UIViewWithLabel *)viewWithLongStringOverlappingLabel;
38 |
39 | + (UIButton *)buttonWithBackgroundImage;
40 |
41 | @end
42 |
--------------------------------------------------------------------------------
/LayoutTestTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/LayoutTestTests/LayoutTestCaseTests/LayoutReportSnapshotLocationsTests.m:
--------------------------------------------------------------------------------
1 | //
2 | // LayoutReportSnapshotLocations.m
3 | // LayoutTest
4 | //
5 | // Created by Jock Findlay on 09/02/2016.
6 | // Copyright © 2016 LinkedIn. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "LYTLayoutTestCase.h"
11 | #import "LYTLayoutFailingTestSnapshotRecorder.h"
12 |
13 | @interface LYTLayoutFailingTestSnapshotRecorder ()
14 | @property (nonatomic) NSMutableSet *failingTestsSnapshotFolders;
15 | @end
16 |
17 | @interface LayoutReportSnapshotLocationsTests : LYTLayoutTestCase
18 | @end
19 |
20 | @implementation LayoutReportSnapshotLocationsTests
21 |
22 | - (void)testAllLocationsOfReportsAreLoggedToConsoleAtTheEnd {
23 | XCTIssue *issue = [[XCTIssue alloc] initWithType:XCTIssueTypeAssertionFailure compactDescription:@""];
24 | [[LYTLayoutFailingTestSnapshotRecorder sharedInstance] testCase:self didRecordIssue:issue];
25 | NSString *expectedPathContaining = @"LayoutTestImages/LayoutReportSnapshotLocationsTests/index.html";
26 | NSString *actualPath = [LYTLayoutFailingTestSnapshotRecorder sharedInstance].failingTestsSnapshotFolders.anyObject;
27 | XCTAssertTrue([actualPath containsString:expectedPathContaining]);
28 | }
29 |
30 | @end
31 |
--------------------------------------------------------------------------------
/LayoutTestTests/LayoutTestCaseTests/LayoutTestCaseAccessibilityControlTests.m:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import "LayoutTest.h"
11 | #import "LayoutTestBase.h"
12 | #import "UnitTestViews.h"
13 |
14 |
15 | @interface LayoutTestCaseAccessibilityControlTests : LYTLayoutTestCase
16 |
17 | @property (nonatomic) NSInteger testFailures;
18 |
19 | @end
20 |
21 | @implementation LayoutTestCaseAccessibilityControlTests
22 |
23 | - (void)testFails {
24 | __block NSInteger timesCalled = 0;
25 | [self runLayoutTestsWithViewProvider:[self class] validation:^(__unused UIView * view, __unused NSDictionary * data, __unused id context) {
26 | timesCalled++;
27 | }];
28 |
29 | XCTAssertEqual(timesCalled, 2);
30 | XCTAssertEqual(self.testFailures, 1);
31 | }
32 |
33 | - (void)testNoFailWithAllowErrors {
34 | __block NSInteger timesCalled = 0;
35 | [self runLayoutTestsWithViewProvider:[self class] validation:^(UIView *view, __unused NSDictionary * data, __unused id context) {
36 | timesCalled++;
37 |
38 | [self.viewsAllowingAccessibilityErrors addObject:view.subviews[0]];
39 | }];
40 |
41 | XCTAssertEqual(timesCalled, 2);
42 | XCTAssertEqual(self.testFailures, 0);
43 |
44 | self.interceptsAutolayoutErrors = YES;
45 | }
46 |
47 | - (void)testNoFailWithDifferentClasses {
48 | // NOTE: Now UIButton is not in this set
49 | self.viewClassesRequiringAccessibilityLabels = [NSSet setWithObjects:[UISwitch class], nil];
50 |
51 | __block NSInteger timesCalled = 0;
52 | [self runLayoutTestsWithViewProvider:[self class] validation:^(__unused UIView * view, __unused NSDictionary * data, __unused id context) {
53 | timesCalled++;
54 | }];
55 |
56 | XCTAssertEqual(timesCalled, 2);
57 | XCTAssertEqual(self.testFailures, 0);
58 |
59 | self.viewClassesRequiringAccessibilityLabels = [LYTConfig sharedInstance].viewClassesRequiringAccessibilityLabels;
60 | }
61 |
62 | - (void)testNoFailWithAccessibilityOff {
63 | self.accessibilityTestsEnabled = NO;
64 |
65 | __block NSInteger timesCalled = 0;
66 | [self runLayoutTestsWithViewProvider:[self class] validation:^(__unused UIView * view, __unused NSDictionary * data, __unused id context) {
67 | timesCalled++;
68 | }];
69 |
70 | XCTAssertEqual(timesCalled, 2);
71 | XCTAssertEqual(self.testFailures, 0);
72 |
73 | self.accessibilityTestsEnabled = YES;
74 | }
75 |
76 | #pragma mark - Override
77 |
78 | - (void)failTest:(__unused NSString *)errorMessage view:(__unused UIView *)view {
79 | self.testFailures++;
80 | }
81 |
82 | #pragma mark - LYTViewProvider
83 |
84 | + (NSDictionary *)dataSpecForTestWithError:(__unused NSError * _Nullable __autoreleasing *)error {
85 | // Return 2 views to test. One with a button with accessibility, the other a button without accessibility.
86 | return @{
87 | @"view": [[LYTDataValues alloc] initWithValues:@[
88 | [UnitTestViews viewWithButtonAndAccessibility],
89 | [UnitTestViews viewWithButtonAndNoAccessibility]
90 | ]]
91 | };
92 | }
93 |
94 | + (UIView *)viewForData:(NSDictionary *)data
95 | reuseView:(__unused UIView *)view
96 | size:(__unused LYTViewSize *)size
97 | context:(__unused id __autoreleasing *)context
98 | error:(__unused NSError * _Nullable __autoreleasing *)error {
99 | return (UIView *)data[@"view"];
100 | }
101 |
102 | @end
103 |
--------------------------------------------------------------------------------
/LayoutTestTests/LayoutTestCaseTests/LayoutTestCaseAmbiguousTests.m:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import "LayoutTest.h"
11 | #import "LayoutTestBase.h"
12 | #import "UnitTestViews.h"
13 |
14 |
15 | @interface LayoutTestCaseAmbiguousTests : LYTLayoutTestCase
16 |
17 | @property (nonatomic) NSInteger testFailures;
18 |
19 | @end
20 |
21 | @implementation LayoutTestCaseAmbiguousTests
22 |
23 | - (void)testFails {
24 | __block NSInteger timesCalled = 0;
25 | [self runLayoutTestsWithViewProvider:[self class] validation:^(__unused UIView * view, __unused NSDictionary * data, __unused id context) {
26 | timesCalled++;
27 | }];
28 |
29 | XCTAssertEqual(timesCalled, 2);
30 | XCTAssertEqual(self.testFailures, 1);
31 | }
32 |
33 | - (void)testNoFailWithAllowsOverlap {
34 | self.ambiguousAutolayoutTestsEnabled = NO;
35 |
36 | __block NSInteger timesCalled = 0;
37 | [self runLayoutTestsWithViewProvider:[self class] validation:^(__unused UIView *view, __unused NSDictionary * data, __unused id context) {
38 | timesCalled++;
39 | }];
40 |
41 | XCTAssertEqual(timesCalled, 2);
42 | XCTAssertEqual(self.testFailures, 0);
43 |
44 | self.ambiguousAutolayoutTestsEnabled = YES;
45 | }
46 |
47 | #pragma mark - Override
48 |
49 | - (void)failTest:(__unused NSString *)errorMessage view:(__unused UIView *)view {
50 | self.testFailures++;
51 | }
52 |
53 | #pragma mark - LYTViewProvider
54 |
55 | + (nullable NSDictionary *)dataSpecForTestWithError:(__unused NSError * _Nullable __autoreleasing *)error {
56 | // Return 3 views to test. One correct view, on view with overlapping subviews and one view with a switch subview.
57 | return @{
58 | @"view": [[LYTDataValues alloc] initWithValues:@[
59 | [UnitTestViews viewWithNoProblems],
60 | [UnitTestViews viewWithAmbiguousLayout]
61 | ]]
62 | };
63 | }
64 |
65 | + (UIView *)viewForData:(NSDictionary *)data
66 | reuseView:(__unused UIView *)view
67 | size:(__unused LYTViewSize *)size
68 | context:(__unused id __autoreleasing *)context
69 | error:(__unused NSError * _Nullable __autoreleasing * _Nullable)error {
70 | return (UIView *)data[@"view"];
71 | }
72 |
73 | @end
74 |
--------------------------------------------------------------------------------
/LayoutTestTests/LayoutTestCaseTests/LayoutTestCaseAutolayoutFailureTests.m:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import "LayoutTest.h"
11 | #import "LayoutTestBase.h"
12 | #import "UnitTestViews.h"
13 |
14 |
15 | @interface LayoutTestCaseAutolayoutFailureTests : LYTLayoutTestCase
16 |
17 | @property (nonatomic) NSInteger testFailures;
18 |
19 | @end
20 |
21 | @implementation LayoutTestCaseAutolayoutFailureTests
22 |
23 | - (void)testFails {
24 | __block NSInteger timesCalled = 0;
25 | [self runLayoutTestsWithViewProvider:[self class] validation:^(__unused UIView * view, __unused NSDictionary * data, __unused id context) {
26 | timesCalled++;
27 | }];
28 |
29 | XCTAssertEqual(timesCalled, 2);
30 | XCTAssertEqual(self.testFailures, 1);
31 | }
32 |
33 | - (void)testNoFailWithAllowsOverlap {
34 | self.interceptsAutolayoutErrors = NO;
35 |
36 | __block NSInteger timesCalled = 0;
37 | [self runLayoutTestsWithViewProvider:[self class] validation:^(__unused UIView *view, __unused NSDictionary * data, __unused id context) {
38 | timesCalled++;
39 | }];
40 |
41 | XCTAssertEqual(timesCalled, 2);
42 | XCTAssertEqual(self.testFailures, 0);
43 |
44 | self.interceptsAutolayoutErrors = YES;
45 | }
46 |
47 | #pragma mark - Override
48 |
49 | - (void)failTest:(__unused NSString *)errorMessage view:(__unused UIView *)view {
50 | self.testFailures++;
51 | }
52 |
53 | #pragma mark - LYTViewProvider
54 |
55 | + (nullable NSDictionary *)dataSpecForTestWithError:(__unused NSError * _Nullable __autoreleasing *)error {
56 | // Return 3 views to test. One correct view, on view with overlapping subviews and one view with a switch subview.
57 | return @{
58 | @"view": [[LYTDataValues alloc] initWithValues:@[
59 | [UnitTestViews viewWithNoProblems],
60 | [UnitTestViews viewWithIncorrectAutolayout]
61 | ]]
62 | };
63 | }
64 |
65 | + (UIView *)viewForData:(NSDictionary *)data
66 | reuseView:(__unused UIView *)view
67 | size:(__unused LYTViewSize *)size
68 | context:(__unused id __autoreleasing *)context
69 | error:(__unused NSError * _Nullable __autoreleasing * _Nullable)error {
70 | return (UIView *)data[@"view"];
71 | }
72 |
73 | @end
74 |
--------------------------------------------------------------------------------
/LayoutTestTests/LayoutTestCaseTests/LayoutTestCaseMissingLabelTests.m:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import "LayoutTest.h"
11 | #import "LayoutTestBase.h"
12 | #import "UnitTestViews.h"
13 |
14 |
15 | @interface LayoutTestCaseMissingLabelTests : LYTLayoutTestCase
16 |
17 | @property (nonatomic) NSInteger testFailures;
18 |
19 | @end
20 |
21 | @implementation LayoutTestCaseMissingLabelTests
22 |
23 | - (void)testFails {
24 | __block NSInteger timesCalled = 0;
25 | [self runLayoutTestsWithViewProvider:[self class] validation:^(__unused UIView * view, __unused NSDictionary * data, __unused id context) {
26 | timesCalled++;
27 | }];
28 |
29 | XCTAssertEqual(timesCalled, 2);
30 | XCTAssertEqual(self.testFailures, 1);
31 | }
32 |
33 | - (void)testNoFailWithAllowErrors {
34 | __block NSInteger timesCalled = 0;
35 | [self runLayoutTestsWithViewProvider:[self class] validation:^(UIView *view, __unused NSDictionary * data, __unused id context) {
36 | timesCalled++;
37 |
38 | [self.viewsAllowingAccessibilityErrors addObject:view.subviews[0]];
39 | }];
40 |
41 | XCTAssertEqual(timesCalled, 2);
42 | XCTAssertEqual(self.testFailures, 0);
43 |
44 | self.interceptsAutolayoutErrors = YES;
45 | }
46 |
47 | - (void)testNoFailWithAccessibilityOff {
48 | self.accessibilityTestsEnabled = NO;
49 |
50 | __block NSInteger timesCalled = 0;
51 | [self runLayoutTestsWithViewProvider:[self class] validation:^(__unused UIView * view, __unused NSDictionary * data, __unused id context) {
52 | timesCalled++;
53 | }];
54 |
55 | XCTAssertEqual(timesCalled, 2);
56 | XCTAssertEqual(self.testFailures, 0);
57 |
58 | self.accessibilityTestsEnabled = YES;
59 | }
60 |
61 | #pragma mark - Override
62 |
63 | - (void)failTest:(__unused NSString *)errorMessage view:(__unused UIView *)view {
64 | self.testFailures++;
65 | }
66 |
67 | #pragma mark - LYTViewProvider
68 |
69 | + (nullable NSDictionary *)dataSpecForTestWithError:(__unused NSError * _Nullable __autoreleasing *)error {
70 | // Return 3 views to test. One correct view, on view with overlapping subviews and one view with a switch subview.
71 | return @{
72 | @"view": [[LYTDataValues alloc] initWithValues:@[
73 | [UnitTestViews viewWithNoProblems],
74 | [UnitTestViews viewWithAccessibilityIDButNoLabel]
75 | ]]
76 | };
77 | }
78 |
79 | + (UIView *)viewForData:(NSDictionary *)data
80 | reuseView:(__unused UIView *)view
81 | size:(__unused LYTViewSize *)size
82 | context:(__unused id __autoreleasing *)context
83 | error:(__unused NSError * _Nullable __autoreleasing * _Nullable)error {
84 | return (UIView *)data[@"view"];
85 | }
86 |
87 | @end
88 |
--------------------------------------------------------------------------------
/LayoutTestTests/LayoutTestCaseTests/LayoutTestCaseMultipleDataOverlapTests.m:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import "LayoutTest.h"
11 | #import "LayoutTestBase.h"
12 | #import "UnitTestViews.h"
13 | #import "UIViewWithLabel.h"
14 |
15 | @interface LayoutTestCaseMultipleDataOverlapTests : LYTLayoutTestCase
16 |
17 | @property (nonatomic) NSInteger testFailures;
18 |
19 | @end
20 |
21 | @implementation LayoutTestCaseMultipleDataOverlapTests
22 |
23 | - (void)testPassWithoutOverlappingTextAndFailWithOverlappingText {
24 | __block NSInteger timesCalled = 0;
25 |
26 | [self runLayoutTestsWithViewProvider:[self class] validation:^(__unused UIView * view, __unused NSDictionary * data, __unused id context) {
27 | timesCalled++;
28 | }];
29 |
30 | XCTAssertEqual(timesCalled, 2);
31 | XCTAssertEqual(self.testFailures, 1);
32 | }
33 |
34 | #pragma mark - Override
35 |
36 | - (void)failTest:(__unused NSString *)errorMessage view:(__unused UIView *)view {
37 | self.testFailures++;
38 | }
39 |
40 | #pragma mark - LYTViewProvider
41 |
42 | + (nullable NSDictionary *)dataSpecForTestWithError:(__unused NSError * _Nullable __autoreleasing *)error {
43 | return @{
44 | @"view": [UnitTestViews viewWithLongStringOverlappingLabel],
45 | @"text": [[LYTStringValues alloc] initWithValues:@[@"X", @"A long string that will cause overlap"]]
46 | };
47 | }
48 |
49 | + (UIView *)viewForData:(NSDictionary *)data
50 | reuseView:(UIView *)view
51 | size:(__unused LYTViewSize *)size
52 | context:(__unused id __autoreleasing *)context
53 | error:(__unused NSError * _Nullable __autoreleasing * _Nullable)error {
54 | UIViewWithLabel *reuseView = (UIViewWithLabel *)(view ? view : data[@"view"]);
55 | reuseView.label.text = data[@"text"];
56 | return reuseView;
57 | }
58 |
59 | @end
60 |
--------------------------------------------------------------------------------
/LayoutTestTests/LayoutTestCaseTests/LayoutTestCaseNestedAccessibilityTests.m:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import "LayoutTest.h"
11 | #import "LayoutTestBase.h"
12 | #import "UnitTestViews.h"
13 |
14 |
15 | @interface LayoutTestCaseNestedAccessibilityTests : LYTLayoutTestCase
16 |
17 | @property (nonatomic) NSInteger testFailures;
18 |
19 | @end
20 |
21 | @implementation LayoutTestCaseNestedAccessibilityTests
22 |
23 | - (void)testFails {
24 | __block NSInteger timesCalled = 0;
25 | [self runLayoutTestsWithViewProvider:[self class] validation:^(__unused UIView * view, __unused NSDictionary * data, __unused id context) {
26 | timesCalled++;
27 | }];
28 |
29 | XCTAssertEqual(timesCalled, 2);
30 | XCTAssertEqual(self.testFailures, 1);
31 | }
32 |
33 | - (void)testNoFailWithAllowErrors {
34 | __block NSInteger timesCalled = 0;
35 | [self runLayoutTestsWithViewProvider:[self class] validation:^(__unused UIView *view, __unused NSDictionary * data, __unused id context) {
36 | timesCalled++;
37 |
38 | [self.viewsAllowingAccessibilityErrors addObject:view.subviews[0]];
39 | }];
40 |
41 | XCTAssertEqual(timesCalled, 2);
42 | XCTAssertEqual(self.testFailures, 0);
43 |
44 | self.interceptsAutolayoutErrors = YES;
45 | }
46 |
47 | - (void)testNoFailWithAccessibilityOff {
48 | self.accessibilityTestsEnabled = NO;
49 |
50 | __block NSInteger timesCalled = 0;
51 | [self runLayoutTestsWithViewProvider:[self class] validation:^(__unused UIView * view, __unused NSDictionary * data, __unused id context) {
52 | timesCalled++;
53 | }];
54 |
55 | XCTAssertEqual(timesCalled, 2);
56 | XCTAssertEqual(self.testFailures, 0);
57 |
58 | self.accessibilityTestsEnabled = YES;
59 | }
60 |
61 | #pragma mark - Override
62 |
63 | - (void)failTest:(__unused NSString *)errorMessage view:(__unused UIView *)view {
64 | self.testFailures++;
65 | }
66 |
67 | #pragma mark - LYTViewProvider
68 |
69 | + (nullable NSDictionary *)dataSpecForTestWithError:(__unused NSError * _Nullable __autoreleasing *)error {
70 | // Return 3 views to test. One correct view, on view with overlapping subviews and one view with a switch subview.
71 | return @{
72 | @"view": [[LYTDataValues alloc] initWithValues:@[
73 | [UnitTestViews viewWithNoProblems],
74 | [UnitTestViews viewWithNestedAccessibility]
75 | ]]
76 | };
77 | }
78 |
79 | + (UIView *)viewForData:(NSDictionary *)data
80 | reuseView:(__unused UIView *)view
81 | size:(__unused LYTViewSize *)size
82 | context:(__unused id __autoreleasing *)context
83 | error:(__unused NSError * _Nullable __autoreleasing *)error {
84 | return (UIView *)data[@"view"];
85 | }
86 |
87 | @end
88 |
--------------------------------------------------------------------------------
/LayoutTestTests/LayoutTestCaseTests/LayoutTestCaseOverlapTests.m:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import "LayoutTest.h"
11 | #import "LayoutTestBase.h"
12 | #import "UnitTestViews.h"
13 |
14 |
15 | @interface LayoutTestCaseOverlapTests : LYTLayoutTestCase
16 |
17 | @property (nonatomic) NSInteger testFailures;
18 |
19 | @end
20 |
21 | @implementation LayoutTestCaseOverlapTests
22 |
23 | - (void)testFails {
24 | __block NSInteger timesCalled = 0;
25 | [self runLayoutTestsWithViewProvider:[self class] validation:^(__unused UIView * view, __unused NSDictionary * data, __unused id context) {
26 | timesCalled++;
27 | }];
28 |
29 | XCTAssertEqual(timesCalled, 4);
30 | // We should have failed once for the view that overlaps
31 | XCTAssertEqual(self.testFailures, 1);
32 | }
33 |
34 | - (void)testPassesWhenHidden {
35 | __block NSInteger timesCalled = 0;
36 | [self runLayoutTestsWithViewProvider:[self class] validation:^(UIView * view, __unused NSDictionary * data, __unused id context) {
37 | timesCalled++;
38 | // Let's hide one of the subviews. Now, nothing should be wrong.
39 | view.subviews[0].hidden = YES;
40 | }];
41 |
42 | XCTAssertEqual(timesCalled, 4);
43 | XCTAssertEqual(self.testFailures, 0);
44 | }
45 |
46 | - (void)testNoFailWithAllowsOverlap {
47 | __block NSInteger timesCalled = 0;
48 | [self runLayoutTestsWithViewProvider:[self class] validation:^(UIView *view, __unused NSDictionary * data, __unused id context) {
49 | timesCalled++;
50 |
51 | [self.viewsAllowingOverlap addObject:view.subviews[0]];
52 | }];
53 |
54 | XCTAssertEqual(timesCalled, 4);
55 | XCTAssertEqual(self.testFailures, 0);
56 | }
57 |
58 | - (void)testviewClassesAllowingSubviewErrorsOff {
59 | // Remove the UISwitch class from classes allowing overlap
60 | self.viewClassesAllowingSubviewErrors = [NSSet set];
61 |
62 | __block NSInteger timesCalled = 0;
63 | [self runLayoutTestsWithViewProvider:[self class] validation:^(UIView *view, __unused NSDictionary * data, __unused id context) {
64 | timesCalled++;
65 |
66 | UIView *subview = view.subviews[0];
67 | if (![subview isKindOfClass:[UISwitch class]]) {
68 | [self.viewsAllowingOverlap addObject:subview];
69 | }
70 | }];
71 |
72 | XCTAssertEqual(timesCalled, 4);
73 | // This will fail multiple times for a UISwitch because it has many overlapping subviews
74 | XCTAssertTrue(self.testFailures > 0);
75 |
76 | // Reset this now
77 | self.viewClassesAllowingSubviewErrors = [LYTConfig sharedInstance].viewClassesAllowingSubviewErrors;
78 | }
79 |
80 | #pragma mark - Override
81 |
82 | - (void)failTest:(__unused NSString *)errorMessage view:(__unused UIView *)view {
83 | self.testFailures++;
84 | }
85 |
86 | #pragma mark - LYTViewProvider
87 |
88 | + (nullable NSDictionary *)dataSpecForTestWithError:(__unused NSError * _Nullable __autoreleasing *)error {
89 | // Return 4 views to test.
90 | // - One correct view
91 | // - One view with overlapping subviews
92 | // - One view with a switch subview.
93 | // - One view with a button with overlapping subviews
94 | return @{
95 | @"view": [[LYTDataValues alloc] initWithValues:@[
96 | [UnitTestViews viewWithNoProblems],
97 | [UnitTestViews viewWithOverlappingViews],
98 | [UnitTestViews viewWithUISwitchSubview],
99 | [UnitTestViews buttonWithBackgroundImage]
100 | ]]
101 | };
102 | }
103 |
104 | + (UIView *)viewForData:(NSDictionary *)data
105 | reuseView:(__unused UIView *)view
106 | size:(__unused LYTViewSize *)size
107 | context:(__unused id __autoreleasing *)context
108 | error:(__unused NSError * _Nullable __autoreleasing * _Nullable)error {
109 | return (UIView *)data[@"view"];
110 | }
111 |
112 | @end
113 |
--------------------------------------------------------------------------------
/LayoutTestTests/LayoutTestCaseTests/LayoutTestCaseViewSizesConfigTests.m:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import "LayoutTest.h"
11 | #import "LayoutTestBase.h"
12 | #import "UnitTestViews.h"
13 |
14 |
15 | @interface LayoutTestCaseViewSizesConfigTests : LYTLayoutTestCase
16 |
17 | @end
18 |
19 | @implementation LayoutTestCaseViewSizesConfigTests
20 |
21 | - (void)setUp {
22 | [super setUp];
23 |
24 | [LYTConfig sharedInstance].viewSizesToTest = @[
25 | [[LYTViewSize alloc] initWithWidth:@(100) height:@(100)],
26 | [[LYTViewSize alloc] initWithHeight:@(200)],
27 | [[LYTViewSize alloc] initWithWidth:@(200)]
28 | ];
29 | }
30 |
31 | - (void)tearDown {
32 | [[LYTConfig sharedInstance] resetDefaults];
33 |
34 | [super tearDown];
35 | }
36 |
37 | - (void)testNoOptions {
38 | __block NSInteger timesCalled = 0;
39 | [self runLayoutTestsWithViewProvider:[self class] validation:^(UIView * view, __unused NSDictionary * data, __unused id context) {
40 | switch (timesCalled) {
41 | case 0:
42 | XCTAssertEqual(view.lyt_width, 100);
43 | XCTAssertEqual(view.lyt_height, 100);
44 | break;
45 | case 1:
46 | XCTAssertEqual(view.lyt_width, 300);
47 | XCTAssertEqual(view.lyt_height, 200);
48 | break;
49 | case 2:
50 | XCTAssertEqual(view.lyt_width, 200);
51 | XCTAssertEqual(view.lyt_height, 300);
52 | break;
53 | default:
54 | XCTFail();
55 | }
56 | timesCalled++;
57 | }];
58 |
59 | XCTAssertEqual(timesCalled, 3);
60 | }
61 |
62 | - (void)testNoViewSizes {
63 | __block NSInteger timesCalled = 0;
64 | [self runLayoutTestsWithViewProvider:[self class] limitResults:LYTTesterLimitResultsNoSizes validation:^(UIView * view,
65 | __unused NSDictionary * data,
66 | __unused id context) {
67 | XCTAssertEqual(view.lyt_width, 300);
68 | XCTAssertEqual(view.lyt_height, 300);
69 | timesCalled++;
70 | }];
71 |
72 | XCTAssertEqual(timesCalled, 1);
73 | }
74 |
75 | #pragma mark - LYTViewProvider
76 |
77 | + (nullable NSDictionary *)dataSpecForTestWithError:(__unused NSError * _Nullable __autoreleasing *)error {
78 | return @{
79 | // Empty data
80 | };
81 | }
82 |
83 | + (UIView *)viewForData:(__unused NSDictionary *)data
84 | reuseView:(UIView *)view
85 | size:(__unused LYTViewSize *)size
86 | context:(__unused id __autoreleasing *)context
87 | error:(__unused NSError * _Nullable __autoreleasing * _Nullable)error {
88 | return view ?: [UnitTestViews viewWithNoProblems];
89 | }
90 |
91 | + (void)adjustViewSize:(UIView *)view
92 | data:(__unused NSDictionary *)data
93 | size:(LYTViewSize *)size
94 | context:(__unused id)context
95 | error:(__unused NSError * _Nullable __autoreleasing * _Nullable)error {
96 | if (!size.width) {
97 | view.lyt_width = 300;
98 | }
99 | if (!size.height) {
100 | view.lyt_height = 300;
101 | }
102 | }
103 |
104 | @end
105 |
--------------------------------------------------------------------------------
/LayoutTestTests/LayoutTestCaseTests/LayoutTestCaseViewSizesTests.m:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import "LayoutTest.h"
11 | #import "LayoutTestBase.h"
12 | #import "UnitTestViews.h"
13 |
14 |
15 | /**
16 | These tests assume we are testing on:
17 | (w: 100, h: 100), (w: 300, h:200), (w: 200, h: 300)
18 | using both the LYTViewSizes and the adjust view methods.
19 | */
20 | @interface LayoutTestCaseViewSizesTests : LYTLayoutTestCase
21 |
22 | @end
23 |
24 | @implementation LayoutTestCaseViewSizesTests
25 |
26 | - (void)testNoOptions {
27 | __block NSInteger timesCalled = 0;
28 | [self runLayoutTestsWithViewProvider:[self class] validation:^(UIView * view, __unused NSDictionary * data, __unused id context) {
29 | switch (timesCalled) {
30 | case 0:
31 | XCTAssertEqual(view.lyt_width, 100);
32 | XCTAssertEqual(view.lyt_height, 100);
33 | break;
34 | case 1:
35 | XCTAssertEqual(view.lyt_width, 300);
36 | XCTAssertEqual(view.lyt_height, 200);
37 | break;
38 | case 2:
39 | XCTAssertEqual(view.lyt_width, 200);
40 | XCTAssertEqual(view.lyt_height, 300);
41 | break;
42 | default:
43 | XCTFail();
44 | }
45 | timesCalled++;
46 | }];
47 |
48 | XCTAssertEqual(timesCalled, 3);
49 | }
50 |
51 | - (void)testNoViewSizes {
52 | __block NSInteger timesCalled = 0;
53 | [self runLayoutTestsWithViewProvider:[self class] limitResults:LYTTesterLimitResultsNoSizes validation:^(UIView * view,
54 | __unused NSDictionary * data,
55 | __unused id context) {
56 | XCTAssertEqual(view.lyt_width, 300);
57 | XCTAssertEqual(view.lyt_height, 300);
58 | timesCalled++;
59 | }];
60 |
61 | XCTAssertEqual(timesCalled, 1);
62 | }
63 |
64 | #pragma mark - LYTViewProvider
65 |
66 | + (nullable NSDictionary *)dataSpecForTestWithError:(__unused NSError * _Nullable __autoreleasing *)error {
67 | return @{
68 | // Empty data
69 | };
70 | }
71 |
72 | + (UIView *)viewForData:(__unused NSDictionary *)data
73 | reuseView:(UIView *)view
74 | size:(__unused LYTViewSize *)size
75 | context:(__unused id __autoreleasing *)context
76 | error:(__unused NSError * _Nullable __autoreleasing * _Nullable)error {
77 | return view ?: [UnitTestViews viewWithNoProblems];
78 | }
79 |
80 | + (NSArray *)sizesForView {
81 | return @[
82 | [[LYTViewSize alloc] initWithWidth:@(100) height:@(100)],
83 | [[LYTViewSize alloc] initWithHeight:@(200)],
84 | [[LYTViewSize alloc] initWithWidth:@(200)] ];
85 | }
86 |
87 | + (void)adjustViewSize:(UIView *)view
88 | data:(__unused NSDictionary *)data
89 | size:(LYTViewSize *)size
90 | context:(__unused id)context
91 | error:(__unused NSError * _Nullable __autoreleasing * _Nullable)error {
92 | if (!size.width) {
93 | view.lyt_width = 300;
94 | }
95 | if (!size.height) {
96 | view.lyt_height = 300;
97 | }
98 | }
99 |
100 | @end
101 |
--------------------------------------------------------------------------------
/LayoutTestTests/LayoutTestCaseTests/LayoutTestCaseWithinSuperviewTests.m:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import "LayoutTest.h"
11 | #import "LayoutTestBase.h"
12 | #import "UnitTestViews.h"
13 |
14 |
15 | @interface LayoutTestCaseWithinSuperviewTests : LYTLayoutTestCase
16 |
17 | @property (nonatomic) NSInteger testFailures;
18 |
19 | @end
20 |
21 | @implementation LayoutTestCaseWithinSuperviewTests
22 |
23 | - (void)testFails {
24 | __block NSInteger timesCalled = 0;
25 | [self runLayoutTestsWithViewProvider:[self class] validation:^(__unused UIView *view, __unused NSDictionary*data, __unused id context) {
26 | timesCalled++;
27 | }];
28 |
29 | XCTAssertEqual(timesCalled, 2);
30 | // We should have failed once for the view that is out of the superview
31 | XCTAssertEqual(self.testFailures, 1);
32 | }
33 |
34 | - (void)testPassesWhenHidden {
35 | __block NSInteger timesCalled = 0;
36 | [self runLayoutTestsWithViewProvider:[self class] validation:^(UIView * view, __unused NSDictionary * data, __unused id context) {
37 | timesCalled++;
38 |
39 | view.subviews[0].hidden = YES;
40 | }];
41 |
42 | XCTAssertEqual(timesCalled, 2);
43 | XCTAssertEqual(self.testFailures, 0);
44 | }
45 |
46 | - (void)testNoFailWithAllowsOverlap {
47 | __block NSInteger timesCalled = 0;
48 | [self runLayoutTestsWithViewProvider:[self class] validation:^(UIView *view, __unused NSDictionary * data, __unused id context) {
49 | timesCalled++;
50 |
51 | [self.viewsAllowingOverlap addObject:view.subviews[0]];
52 | }];
53 |
54 | XCTAssertEqual(timesCalled, 2);
55 | XCTAssertEqual(self.testFailures, 0);
56 | }
57 |
58 | #pragma mark - Override
59 |
60 | - (void)failTest:(__unused NSString *)errorMessage view:(__unused UIView *)view {
61 | self.testFailures++;
62 | }
63 |
64 | #pragma mark - LYTViewProvider
65 |
66 | + (nullable NSDictionary *)dataSpecForTestWithError:(__unused NSError * _Nullable __autoreleasing *)error {
67 | // Return 3 views to test. One correct view, on view with overlapping subviews and one view with a switch subview.
68 | return @{
69 | @"view": [[LYTDataValues alloc] initWithValues:@[
70 | [UnitTestViews viewWithNoProblems],
71 | [UnitTestViews viewWithSubviewOutOfSuperview]
72 | ]]
73 | };
74 | }
75 |
76 | + (UIView *)viewForData:(NSDictionary *)data
77 | reuseView:(__unused UIView *)view
78 | size:(__unused LYTViewSize *)size
79 | context:(__unused id __autoreleasing *)context
80 | error:(__unused NSError * _Nullable __autoreleasing *)error {
81 | return (UIView *)data[@"view"];
82 | }
83 |
84 | @end
85 |
--------------------------------------------------------------------------------
/LayoutTestTests/LayoutTestCaseTests/SwiftLYTLayoutFailingTestSnapshotRecorderTests.swift:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | import XCTest
11 | import LayoutTest
12 |
13 | class SwiftLYTLayoutFailingTestSnapshotRecorderTests: XCTestCase {
14 |
15 | func testStartNewLogDeletesExisitingClassSnapshotDirectory() {
16 | let fileManager = FileManager.default
17 | let currentDirectory = Bundle(for: type(of: self)).bundlePath
18 | let classDirectory = currentDirectory + "/LayoutTestImages/SwiftLYTLayoutFailingTestSnapshotRecorderTests"
19 | let testFilePath = classDirectory + "/testFile.html"
20 | do {
21 | try fileManager.createDirectory(atPath: classDirectory, withIntermediateDirectories: true, attributes: nil)
22 | } catch{}
23 | fileManager.createFile(atPath: testFilePath, contents: nil, attributes: nil)
24 |
25 | let recorder = LayoutFailingTestSnapshotRecorder()
26 | recorder.startNewLog(for: type(of: self))
27 |
28 | XCTAssertFalse(fileManager.fileExists(atPath: testFilePath))
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/LayoutTestTests/NullIteratorTests.m:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | #import
11 | // Test Class
12 | #import "LYTLayoutPropertyTester.h"
13 | // Helpers
14 | #import "LYTDataValues.h"
15 | #import "LYTViewProvider.h"
16 |
17 |
18 | @interface NullIteratorTests : XCTestCase
19 |
20 | @end
21 |
22 | @interface NullDataValues: LYTDataValues
23 |
24 | @end
25 |
26 | @implementation NullIteratorTests
27 |
28 | static NSDictionary *testData = nil;
29 |
30 | - (void)setUp {
31 | [super setUp];
32 | testData = nil;
33 | }
34 |
35 | - (void)tearDown {
36 | [super tearDown];
37 | testData = nil;
38 | }
39 |
40 | - (void)testNullInDictionary
41 | {
42 | testData = @{
43 | @"null": [[NullDataValues alloc] init]
44 | };
45 | __block NSUInteger numberTimesCalled = 0;
46 | [LYTLayoutPropertyTester runPropertyTestsWithViewProvider:[self class]
47 | validation:^(__unused UIView *view, NSDictionary *data, __unused id context) {
48 | id object = data[@"null"];
49 | if (numberTimesCalled < 2) {
50 | XCTAssertEqual([object unsignedIntegerValue], numberTimesCalled, @"Should be equal");
51 | } else {
52 | XCTAssertNil(object, @"Object should be nil");
53 | }
54 | numberTimesCalled++;
55 | }];
56 | XCTAssertEqual(numberTimesCalled, 3, @"Should have been called three times");
57 | }
58 |
59 | - (void)testNullInArray
60 | {
61 | testData = @{
62 | @"array": @[@"0", [[NullDataValues alloc] init], @(2)]
63 | };
64 | __block NSUInteger numberTimesCalled = 0;
65 | [LYTLayoutPropertyTester runPropertyTestsWithViewProvider:[self class]
66 | validation:^(__unused UIView *view, NSDictionary *data, __unused id context) {
67 | id object = data[@"array"][1];
68 | // if it's null, it should be @(2)
69 | XCTAssertEqual([object unsignedIntegerValue], numberTimesCalled, @"Should be equal");
70 | numberTimesCalled++;
71 | }];
72 | XCTAssertEqual(numberTimesCalled, 3, @"Should have been called three times");
73 | }
74 |
75 | // Helpers
76 |
77 | - (void)validateGenerators:(NSArray *)values context:(NSMutableSet *)context {
78 | for (NSNumber *number in values) {
79 | XCTAssert([number integerValue] >= 0, @"Number must be greater than 0");
80 | XCTAssert([number integerValue] < 4, @"Number must be less than 4");
81 | }
82 | if ([context containsObject:values]) {
83 | XCTFail(@"We've got a duplicate set of values.\nDuplication: %@\nContext: %@", values, context);
84 | }
85 | [context addObject:values];
86 | }
87 |
88 | // View Provider Protocol
89 |
90 | + (nullable NSDictionary *)dataSpecForTestWithError:(__unused NSError * _Nullable __autoreleasing *)error {
91 | return testData;
92 | }
93 |
94 | + (UIView *)viewForData:(__unused NSDictionary *)data
95 | reuseView:(__unused UIView *)view
96 | size:(__unused LYTViewSize *)size
97 | context:(__unused id __autoreleasing *)context
98 | error:(__unused NSError * _Nullable __autoreleasing * _Nullable)error {
99 | return nil;
100 | }
101 |
102 | @end
103 |
104 | @implementation NullDataValues
105 |
106 | - (NSArray *)values {
107 | return @[@(0), @(1), [NSNull null]];
108 | }
109 |
110 | @end
111 |
--------------------------------------------------------------------------------
/LayoutTestTests/en.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /* Localized versions of Info.plist keys */
2 |
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | © 2015 LinkedIn Corp. All rights reserved.
2 | Licensed under the Apache License, Version 2.0 (the "License");
3 | you may not use this file except in compliance with the License.
4 | You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 |
6 | Unless required by applicable law or agreed to in writing, software
7 | distributed under the License is distributed on an "AS IS" BASIS,
8 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Overview
2 |
3 | This library enables you to write unit tests which test the layout of a view in multiple configurations. It tests the view with different data combinations and different view sizes. The library works in both Objective-C and Swift.
4 |
5 | To learn how to use this library, there is a LinkedIn Learning course you can access completely for free authored by [Kyle Sherman](http://github.com/drumnkyle/). You can access it [here](https://www.linkedin.com/learning/learning-layouttest-for-ios-development). It will teach you how to use LayoutTest and all of its features by video tutorial.
6 |
7 | ## Motivation
8 |
9 | When creating views, apps often have conditional logic which depends on the data used to setup the view. LayoutTest provides an easy way to define a data spec (a dictionary) which is then used to generate many different combinations of data. The library then uses this data to layout your view multiple times. For example, this is a small portion of the tests ran in our sample app:
10 |
11 | 
12 |
13 | In just one test, your view will be laid out multiple times with different data. You can then run test assertions on these views to verify that the layout and view content is correct. Also, the library will run a few tests automatically such as checking for Autolayout errors, missing accessibility, and overlapping views.
14 | Finally, the library makes it easy to test each view with different sizes so you can verify the view will work on different devices.
15 |
16 | ## Docs
17 |
18 | To get started, you should take a look at the docs:
19 |
20 | https://linkedin.github.io/LayoutTest-iOS
21 |
22 | ## Installation
23 |
24 | Add to your unit test target:
25 |
26 | ```
27 | pod 'LayoutTest'
28 | ```
29 |
30 | or
31 |
32 | ```
33 | pod 'LayoutTest/Swift'
34 | ```
35 |
36 | ## Example
37 |
38 | A simple test would look something like this. Check the docs for more detailed information and examples.
39 |
40 | Objective-C:
41 | ```objective-c
42 | @interface SampleTableViewCellLayoutTests : LYTLayoutTestCase
43 | @end
44 |
45 | @implementation SampleTableViewCellLayoutTests
46 | - (void)testSampleTableViewCellLayout {
47 | [self runLayoutTestsWithViewProvider:[SampleTableViewCell class]
48 | validation:^(UIView * view, NSDictionary * data, id context) {
49 | // Add your custom tests here.
50 | }];
51 | }
52 | @end
53 |
54 | @implementation SampleTableViewCell (LayoutTesting)
55 | + (NSDictionary *)dataSpecForTest {
56 | return @{
57 | @"text": [[LYTStringValues alloc] init],
58 | @"showButton": [[LYTBoolValues alloc] init]
59 | }
60 | }
61 | + (UIView *)viewForData:(NSDictionary *)data
62 | reuseView:(nullable UIView *)reuseView
63 | size:(nullable LYTViewSize *)size
64 | context:(id _Nullable * _Nullable)context {
65 | SampleTableViewCell *view = (SampleTableViewCell *)reuseView ?: [SampleTableViewCell viewFromNib];
66 | [view setupWithJSON:data];
67 | return view;
68 | }
69 | @end
70 | ```
71 | Swift:
72 |
73 | ```swift
74 | class SampleTableViewCellLayoutTests {
75 | func testSampleTableViewCell() {
76 | runLayoutTests() { (view: SampleTableViewCell, data: [NSObject: AnyObject], context: Any?) in
77 | // Add your custom tests here.
78 | }
79 | }
80 | }
81 |
82 | extension SampleTableViewCell: LYTViewProvider {
83 | class func dataSpecForTest() -> [NSObject: AnyObject] {
84 | return [
85 | "text": LYTStringValues(),
86 | "showButton": LYTBoolValues()
87 | ]
88 | }
89 | class func viewForData(data: [NSObject: AnyObject],
90 | reuseView: UIView?,
91 | size: LYTViewSize?,
92 | context: AutoreleasingUnsafeMutablePointer) -> UIView {
93 | let cell = reuseView as? SampleTableViewCell ?? SampleTableViewCell.loadFromNib()
94 | cell.setupWithDictionary(data)
95 | return cell
96 | }
97 | }
98 | ```
99 |
--------------------------------------------------------------------------------
/SampleApp/CatalogSample/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | import UIKit
11 | import LayoutTestBase
12 |
13 | @UIApplicationMain
14 | class AppDelegate: UIResponder, UIApplicationDelegate {
15 |
16 | var window: UIWindow?
17 |
18 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
19 |
20 | window = UIWindow(frame: UIScreen.main.bounds)
21 | let rootViewController = CatalogTableViewController()
22 | rootViewController.viewProviderClass = SampleTableViewCell.self
23 | window?.rootViewController = UINavigationController(rootViewController: rootViewController)
24 | window?.makeKeyAndVisible()
25 |
26 | return true
27 | }
28 | }
29 |
30 | extension SampleTableViewCell: ViewCatalogProvider {
31 | public static func dataSpecForTest() throws -> [AnyHashable: Any] {
32 | return [
33 | "text": StringValues(),
34 | "buttonText": DataValues(values: ["Share", "Like", nil]),
35 | "buttonEnabled": BoolValues(),
36 | "imageType": DataValues(values: ["linkedin", nil])
37 | ]
38 | }
39 |
40 | public static func view(forData data: [AnyHashable: Any],
41 | reuse reuseView: UIView?,
42 | size: ViewSize?,
43 | context: AutoreleasingUnsafeMutablePointer?) throws -> UIView {
44 | let view = (reuseView as? SampleTableViewCell) ?? loadFromBundle()
45 | view.setup(data)
46 | return view
47 | }
48 |
49 | public static func registerClass(on tableView: UITableView) {
50 | SampleTableViewCell.registerForTableView(tableView)
51 | tableView.estimatedRowHeight = 100
52 | }
53 |
54 | public static func heightForTableViewCellForCatalog(fromData data: [AnyHashable: Any]) -> CGFloat {
55 | return UITableView.automaticDimension
56 | }
57 |
58 | public static func reuseIdentifier() -> String {
59 | return "SampleTableViewCell"
60 | }
61 |
62 | // MARK: - Privat methods
63 |
64 | private static func loadFromBundle() -> SampleTableViewCell {
65 | let cellFromBundle = Bundle.main.loadNibNamed("SampleTableViewCell", owner: nil, options: nil)?[0]
66 |
67 | guard let sampleTableViewCell = cellFromBundle as? SampleTableViewCell else {
68 | fatalError("Could not load SampleTableViewCell from bundle")
69 | }
70 | return sampleTableViewCell
71 | }
72 | }
73 |
74 |
--------------------------------------------------------------------------------
/SampleApp/CatalogSample/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | }
33 | ],
34 | "info" : {
35 | "version" : 1,
36 | "author" : "xcode"
37 | }
38 | }
--------------------------------------------------------------------------------
/SampleApp/CatalogSample/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/SampleApp/CatalogSample/Assets.xcassets/LinkedInLogo.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LinkedInLogo.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/SampleApp/CatalogSample/Assets.xcassets/LinkedInLogo.imageset/LinkedInLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linkedin/LayoutTest-iOS/0796ec35ddadc58181019a706dcd6b1492d40837/SampleApp/CatalogSample/Assets.xcassets/LinkedInLogo.imageset/LinkedInLogo.png
--------------------------------------------------------------------------------
/SampleApp/CatalogSample/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/SampleApp/CatalogSample/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/SampleApp/Podfile:
--------------------------------------------------------------------------------
1 | use_frameworks!
2 |
3 | platform :ios, '12.0'
4 |
5 | pod 'LayoutTestBase/Swift', :path => '..'
6 |
7 | target :SampleApp do
8 | end
9 |
10 | target :CatalogSample do
11 | end
12 |
13 | target :SampleAppTests do
14 | pod 'LayoutTest/Swift', :path => '..'
15 | end
16 |
--------------------------------------------------------------------------------
/SampleApp/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - LayoutTest/Swift (6.1.0):
3 | - LayoutTest/SwiftSubspec
4 | - LayoutTest/SwiftSubspec (6.1.0):
5 | - LayoutTest/TestCase
6 | - LayoutTestBase/Swift
7 | - LayoutTest/TestCase (6.1.0):
8 | - LayoutTestBase/Autolayout
9 | - LayoutTestBase/Catalog
10 | - LayoutTestBase/Config
11 | - LayoutTestBase/Core
12 | - LayoutTestBase/UIViewHelpers
13 | - LayoutTestBase/Autolayout (6.1.0)
14 | - LayoutTestBase/Catalog (6.1.0):
15 | - LayoutTestBase/Core
16 | - LayoutTestBase/Config (6.1.0)
17 | - LayoutTestBase/Core (6.1.0):
18 | - LayoutTestBase/Config
19 | - LayoutTestBase/Swift (6.1.0):
20 | - LayoutTestBase/Autolayout
21 | - LayoutTestBase/Catalog
22 | - LayoutTestBase/Config
23 | - LayoutTestBase/Core
24 | - LayoutTestBase/UIViewHelpers
25 | - LayoutTestBase/UIViewHelpers (6.1.0):
26 | - LayoutTestBase/Config
27 |
28 | DEPENDENCIES:
29 | - LayoutTest/Swift (from `..`)
30 | - LayoutTestBase/Swift (from `..`)
31 |
32 | EXTERNAL SOURCES:
33 | LayoutTest:
34 | :path: ".."
35 | LayoutTestBase:
36 | :path: ".."
37 |
38 | SPEC CHECKSUMS:
39 | LayoutTest: b437c8db637fc36895f9802004be09c75c733183
40 | LayoutTestBase: 9f511b9d5cd506b15db04e8948ef0979e454c6c9
41 |
42 | PODFILE CHECKSUM: 5246816d918cdf1a74e375caab9c5c7d07c813b4
43 |
44 | COCOAPODS: 1.15.2
45 |
--------------------------------------------------------------------------------
/SampleApp/SampleApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/SampleApp/SampleApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/SampleApp/SampleApp.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/SampleApp/SampleApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/SampleApp/SampleApp/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | import UIKit
11 |
12 | @UIApplicationMain
13 | class AppDelegate: UIResponder, UIApplicationDelegate {
14 |
15 | var window: UIWindow?
16 |
17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
18 |
19 | window = UIWindow(frame: UIScreen.main.bounds)
20 | window?.rootViewController = UINavigationController(rootViewController: HomeViewController())
21 | window?.makeKeyAndVisible()
22 |
23 | return true
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/SampleApp/SampleApp/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "ipad",
35 | "size" : "29x29",
36 | "scale" : "1x"
37 | },
38 | {
39 | "idiom" : "ipad",
40 | "size" : "29x29",
41 | "scale" : "2x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "40x40",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "40x40",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "76x76",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "76x76",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "83.5x83.5",
66 | "scale" : "2x"
67 | }
68 | ],
69 | "info" : {
70 | "version" : 1,
71 | "author" : "xcode"
72 | }
73 | }
--------------------------------------------------------------------------------
/SampleApp/SampleApp/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/SampleApp/SampleApp/Assets.xcassets/LinkedInLogo.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LinkedInLogo.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/SampleApp/SampleApp/Assets.xcassets/LinkedInLogo.imageset/LinkedInLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linkedin/LayoutTest-iOS/0796ec35ddadc58181019a706dcd6b1492d40837/SampleApp/SampleApp/Assets.xcassets/LinkedInLogo.imageset/LinkedInLogo.png
--------------------------------------------------------------------------------
/SampleApp/SampleApp/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/SampleApp/SampleApp/HomeViewController.swift:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | import UIKit
11 |
12 | class HomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
13 |
14 | @IBOutlet var tableView: UITableView!
15 |
16 | let data = [
17 | [
18 | "imageType":"linkedin",
19 | "text": "Short piece of text",
20 | "buttonText": "Share",
21 | "buttonEnabled": true
22 | ],
23 | [
24 | "imageType":"linkedin",
25 | "text": "A long, long piece of text which spans multiple lines. This is an example of what happens when the text gets long.",
26 | "buttonText": "Share",
27 | "buttonEnabled": true
28 | ],
29 | [
30 | "text": "Another short piece of text",
31 | "buttonText": "Share",
32 | "buttonEnabled": true
33 | ]
34 | ]
35 |
36 | init() {
37 | super.init(nibName: "HomeViewController", bundle: nil)
38 | }
39 |
40 | required init?(coder aDecoder: NSCoder) {
41 | fatalError("init(coder:) has not been implemented")
42 | }
43 |
44 | override func viewDidLoad() {
45 | super.viewDidLoad()
46 |
47 | tableView.estimatedRowHeight = 100
48 |
49 | SampleTableViewCell.registerForTableView(tableView)
50 | }
51 |
52 | // MARK: TableView
53 |
54 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
55 | let cell = SampleTableViewCell.dequeueForTableView(tableView, indexPath: indexPath)
56 | cell.setup(data[(indexPath as NSIndexPath).row])
57 | return cell
58 | }
59 |
60 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
61 | return data.count
62 | }
63 |
64 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
65 | return UITableView.automaticDimension
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/SampleApp/SampleApp/HomeViewController.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/SampleApp/SampleApp/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/SampleApp/SampleApp/SampleFailingView.swift:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | import UIKit
11 |
12 | enum SampleFailingViewError: Error {
13 | case invalidNib
14 | }
15 |
16 | open class SampleFailingView: UIView {
17 |
18 | private static let nibName = "SampleFailingView"
19 |
20 | @IBOutlet weak var label: UILabel!
21 | @IBOutlet weak var button: UIButton!
22 |
23 | class func loadFromNib() throws -> SampleFailingView {
24 | guard let viewInBundle = Bundle.main.loadNibNamed(
25 | SampleFailingView.nibName,
26 | owner: nil,
27 | options: nil),
28 | let sampleFailingView = viewInBundle.first as? SampleFailingView else {
29 | throw SampleFailingViewError.invalidNib
30 | }
31 |
32 | return sampleFailingView
33 | }
34 |
35 | func setup(_ json: [AnyHashable: Any]) {
36 | label.text = json["text"] as? String
37 | button.setTitle(json["buttonText"] as? String, for: .normal)
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/SampleApp/SampleApp/SampleFailingView.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
21 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/SampleApp/SampleApp/SampleTableViewCell.swift:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | import UIKit
11 |
12 | enum SampleTableViewCellError: Error {
13 | case invalidNib
14 | }
15 |
16 | open class SampleTableViewCell: UITableViewCell {
17 |
18 | private static let leftMaxEdge: CGFloat = 38
19 | private static let leftMinEdge: CGFloat = 0
20 | private static let buttonWidth: CGFloat = 80
21 |
22 | private static let nibName = "SampleTableViewCell"
23 |
24 | @IBOutlet var mainImageView: UIImageView!
25 | @IBOutlet var titleLabel: UILabel!
26 | @IBOutlet var rightButton: UIButton!
27 |
28 | @IBOutlet private var labelLeftEdge: NSLayoutConstraint!
29 | @IBOutlet private var buttonWidth: NSLayoutConstraint!
30 |
31 | open class func registerForTableView(_ tableView: UITableView) {
32 | tableView.register(UINib(nibName: nibName, bundle: nil), forCellReuseIdentifier: nibName)
33 | }
34 |
35 | open class func dequeueForTableView(_ tableView: UITableView, indexPath: IndexPath) -> SampleTableViewCell {
36 | return tableView.dequeueReusableCell(withIdentifier: nibName, for: indexPath) as! SampleTableViewCell
37 | }
38 |
39 | class func loadFromNib() throws -> SampleTableViewCell {
40 | guard let viewInBundle = Bundle.main.loadNibNamed(
41 | SampleTableViewCell.nibName,
42 | owner: nil,
43 | options: nil),
44 | let sampleFailingView = viewInBundle.first as? SampleTableViewCell else {
45 | throw SampleTableViewCellError.invalidNib
46 | }
47 |
48 | return sampleFailingView
49 | }
50 |
51 | func setup(_ json: [AnyHashable: Any]) {
52 | if let imageType = json["imageType"] as? String {
53 | switch imageType {
54 | case "linkedin":
55 | mainImageView.image = UIImage(named: "LinkedInLogo")
56 | mainImageView.isHidden = false
57 | labelLeftEdge.constant = SampleTableViewCell.leftMaxEdge
58 | default:
59 | mainImageView.image = nil
60 | mainImageView.isHidden = true
61 | labelLeftEdge.constant = 0
62 | }
63 | } else {
64 | mainImageView.image = nil
65 | mainImageView.isHidden = true
66 | labelLeftEdge.constant = 0
67 | }
68 |
69 | if let buttonText = json["buttonText"] as? String {
70 | rightButton.isHidden = false
71 | rightButton.setTitle(buttonText, for: .normal)
72 | rightButton.accessibilityLabel = "Double tap to " + buttonText
73 | buttonWidth.constant = SampleTableViewCell.buttonWidth
74 | } else {
75 | rightButton.isHidden = true
76 | buttonWidth.constant = 0
77 | }
78 |
79 | rightButton.isEnabled = json["buttonEnabled"] as? Bool ?? false
80 |
81 | titleLabel.text = json["text"] as? String
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/SampleApp/SampleAppTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/SampleApp/SampleAppTests/SampleFailingLayoutTestsWithSnapshots.swift:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | import XCTest
11 | @testable import SampleApp
12 | import LayoutTest
13 | import LayoutTestBase
14 |
15 | class SampleFailingLayoutTestsWithSnapshots : LayoutTestCase {
16 |
17 | func testSampleFailingViewWithSnapshots() {
18 | XCTExpectFailure("Expecting one of the auto-layout tests to fail when the really long string causes the label to overlap with the button") {
19 | runLayoutTests() { (view: SampleFailingView, data: [AnyHashable: Any], context: Any?) in
20 | }
21 | }
22 | }
23 | }
24 |
25 | extension SampleFailingView : ViewProvider {
26 | public class func dataSpecForTest() throws -> [AnyHashable: Any] {
27 | return [
28 | "text": StringValues(),
29 | "buttonText": StringValues()
30 | ]
31 | }
32 |
33 | public class func view(forData data: [AnyHashable: Any],
34 | reuse reuseView: UIView?,
35 | size: ViewSize?,
36 | context: AutoreleasingUnsafeMutablePointer?) throws -> UIView {
37 | let view: SampleFailingView
38 | if let reuseView = reuseView as? SampleFailingView {
39 | view = reuseView
40 | } else {
41 | view = try SampleFailingView.loadFromNib()
42 | }
43 |
44 | view.setup(data)
45 | return view
46 | }
47 |
48 | public class func sizesForView() -> [ViewSize] {
49 | return [
50 | ViewSize(width: 300),
51 | ]
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/SampleApp/SampleAppTests/SampleTableViewCellLayoutTests.swift:
--------------------------------------------------------------------------------
1 | // © 2016 LinkedIn Corp. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5 | //
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 |
10 | import XCTest
11 | @testable import SampleApp
12 | import LayoutTest
13 | import LayoutTestBase
14 |
15 | // If you are writing in Objective-C, you should use LYTLayoutTestCase instead
16 |
17 | class SampleTableViewCellLayoutTests : LayoutTestCase {
18 |
19 | let titleLabelLeftPadding: CGFloat = 8
20 |
21 | func testSampleTableViewCell() {
22 | runLayoutTests() { (view: SampleTableViewCell, data: [AnyHashable: Any], context: Any?) in
23 |
24 | // Verify that the label and image view are top aligned
25 | XCTAssertTrue(view.titleLabel.lyt_topAligned(view.mainImageView))
26 |
27 | // Verify that the text gets set correctly
28 | XCTAssertEqual(view.titleLabel.text, data["text"] as? String)
29 |
30 | if view.mainImageView.isHidden {
31 | // If the image view is hidden, then the title label should be 8 from the left edge (image should be squashed)
32 | XCTAssertEqual(view.titleLabel.lyt_left, self.titleLabelLeftPadding)
33 | } else {
34 | // If it is not hidden, then it should be 8 away from the right of the image view
35 | XCTAssertEqual(view.titleLabel.lyt_left, view.mainImageView.lyt_right + 8)
36 | // The image view should be before the title label
37 | XCTAssertTrue(view.mainImageView.lyt_before(view.titleLabel))
38 | }
39 |
40 | if view.rightButton.isHidden {
41 | // If the right button is hidden, then the text label should be 8 from the right edge
42 | XCTAssertEqual(view.titleLabel.lyt_right, view.lyt_width - self.titleLabelLeftPadding)
43 | } else {
44 | // Otherwise, the text label should be right up against the button
45 | XCTAssertEqual(view.titleLabel.lyt_right, view.rightButton.lyt_left)
46 | // Here, I verify that the button's title is being set correctly
47 | XCTAssertEqual(view.rightButton.title(for: .normal), data["buttonText"] as? String)
48 | }
49 |
50 | // Verify that the right label is enabled iff the data specifies that it is enabled
51 | XCTAssertEqual(view.rightButton.isEnabled, data["buttonEnabled"] as? Bool ?? false)
52 | }
53 | }
54 | }
55 |
56 | extension SampleTableViewCell : ViewProvider {
57 | public class func dataSpecForTest() throws -> [AnyHashable: Any] {
58 | return [
59 | "text": StringValues(),
60 | "buttonText": StringValues(),
61 | "buttonEnabled": BoolValues(),
62 | "imageType": DataValues(values: ["linkedin", "garbageString", NSNull()])
63 | ]
64 | }
65 |
66 | public class func view(forData data: [AnyHashable: Any],
67 | reuse reuseView: UIView?,
68 | size: ViewSize?,
69 | context: AutoreleasingUnsafeMutablePointer?) throws -> UIView {
70 | let view: SampleTableViewCell
71 | if let reuseView = reuseView as? SampleTableViewCell {
72 | view = reuseView
73 | } else {
74 | view = try SampleTableViewCell.loadFromNib()
75 | }
76 |
77 | view.setup(data)
78 | return view
79 | }
80 |
81 | // This is an optional method and can also be specified globally in the config.
82 | public class func sizesForView() -> [ViewSize] {
83 | return [
84 | // Test the view with a specific width
85 | ViewSize(width: 300),
86 | // Test the view with the same width as an iPhone 4
87 | ViewSize(width: LYTiPhone4Width),
88 | // Test the view by setting the width to the iPad width
89 | ViewSize(width: LYTiPadWidth),
90 | // Test the view by setting the width to the iPad height
91 | ViewSize(width: LYTiPadHeight)
92 | ]
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/TestProjects/README:
--------------------------------------------------------------------------------
1 | This folder is for test applications which may differ from the sample app. We expect all these projects to compile whenever we release.
2 |
3 |
--------------------------------------------------------------------------------
/TestProjects/SampleAppiOS7/Podfile:
--------------------------------------------------------------------------------
1 | target :SampleAppiOS7Tests do
2 | pod 'LayoutTestBase', :path => '../..'
3 | pod 'LayoutTest', :path => '../..'
4 | end
5 |
6 |
--------------------------------------------------------------------------------
/TestProjects/SampleAppiOS7/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - LayoutTest (1.1.1):
3 | - LayoutTest/TestCase (= 1.1.1)
4 | - LayoutTestBase (= 1.1.1)
5 | - LayoutTest/ModuleHeader (1.1.1):
6 | - LayoutTestBase (= 1.1.1)
7 | - LayoutTest/TestCase (1.1.1):
8 | - LayoutTest/ModuleHeader
9 | - LayoutTestBase (= 1.1.1)
10 | - LayoutTestBase/Config
11 | - LayoutTestBase/Core
12 | - LayoutTestBase (1.1.1):
13 | - LayoutTestBase/Autolayout (= 1.1.1)
14 | - LayoutTestBase/Catalog (= 1.1.1)
15 | - LayoutTestBase/Config (= 1.1.1)
16 | - LayoutTestBase/Core (= 1.1.1)
17 | - LayoutTestBase/UIViewHelpers (= 1.1.1)
18 | - LayoutTestBase/Autolayout (1.1.1)
19 | - LayoutTestBase/Catalog (1.1.1):
20 | - LayoutTestBase/Core
21 | - LayoutTestBase/Config (1.1.1)
22 | - LayoutTestBase/Core (1.1.1):
23 | - LayoutTestBase/Config
24 | - LayoutTestBase/UIViewHelpers (1.1.1):
25 | - LayoutTestBase/Config
26 |
27 | DEPENDENCIES:
28 | - LayoutTest (from `../..`)
29 | - LayoutTestBase (from `../..`)
30 |
31 | EXTERNAL SOURCES:
32 | LayoutTest:
33 | :path: ../..
34 | LayoutTestBase:
35 | :path: ../..
36 |
37 | SPEC CHECKSUMS:
38 | LayoutTest: 04b0a8894b3f9fe093a6ff5881e9b44c64cef066
39 | LayoutTestBase: 93d8f19820743852760491874d75480d573c60a8
40 |
41 | COCOAPODS: 0.39.0
42 |
--------------------------------------------------------------------------------
/TestProjects/SampleAppiOS7/README:
--------------------------------------------------------------------------------
1 | This project takes an iOS 7 project which does not use 'use_frameworks!' in the Podfile. This was a bug at one point (see issue #8), so we should ensure that this project builds whenever we release.
2 | Tests should build and pass.
3 |
4 |
--------------------------------------------------------------------------------
/TestProjects/SampleAppiOS7/SampleAppiOS7/AppDelegate.h:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.h
3 | // SampleAppiOS7
4 | //
5 | // Created by Peter Livesey on 2/18/16.
6 | // Copyright © 2016 LinkedIn. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface AppDelegate : UIResponder
12 |
13 | @property (strong, nonatomic) UIWindow *window;
14 |
15 |
16 | @end
17 |
18 |
--------------------------------------------------------------------------------
/TestProjects/SampleAppiOS7/SampleAppiOS7/AppDelegate.m:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.m
3 | // SampleAppiOS7
4 | //
5 | // Created by Peter Livesey on 2/18/16.
6 | // Copyright © 2016 LinkedIn. All rights reserved.
7 | //
8 |
9 | #import "AppDelegate.h"
10 |
11 | @interface AppDelegate ()
12 |
13 | @end
14 |
15 | @implementation AppDelegate
16 |
17 | @end
18 |
--------------------------------------------------------------------------------
/TestProjects/SampleAppiOS7/SampleAppiOS7/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | }
33 | ],
34 | "info" : {
35 | "version" : 1,
36 | "author" : "xcode"
37 | }
38 | }
--------------------------------------------------------------------------------
/TestProjects/SampleAppiOS7/SampleAppiOS7/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/TestProjects/SampleAppiOS7/SampleAppiOS7/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/TestProjects/SampleAppiOS7/SampleAppiOS7/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UIRequiredDeviceCapabilities
30 |
31 | armv7
32 |
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 | UIInterfaceOrientationLandscapeLeft
37 | UIInterfaceOrientationLandscapeRight
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/TestProjects/SampleAppiOS7/SampleAppiOS7/ViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.h
3 | // SampleAppiOS7
4 | //
5 | // Created by Peter Livesey on 2/18/16.
6 | // Copyright © 2016 LinkedIn. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface ViewController : UIViewController
12 |
13 |
14 | @end
15 |
16 |
--------------------------------------------------------------------------------
/TestProjects/SampleAppiOS7/SampleAppiOS7/ViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.m
3 | // SampleAppiOS7
4 | //
5 | // Created by Peter Livesey on 2/18/16.
6 | // Copyright © 2016 LinkedIn. All rights reserved.
7 | //
8 |
9 | #import "ViewController.h"
10 |
11 | @interface ViewController ()
12 |
13 | @end
14 |
15 | @implementation ViewController
16 |
17 | @end
18 |
--------------------------------------------------------------------------------
/TestProjects/SampleAppiOS7/SampleAppiOS7/main.m:
--------------------------------------------------------------------------------
1 | //
2 | // main.m
3 | // SampleAppiOS7
4 | //
5 | // Created by Peter Livesey on 2/18/16.
6 | // Copyright © 2016 LinkedIn. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "AppDelegate.h"
11 |
12 | int main(int argc, char * argv[]) {
13 | @autoreleasepool {
14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/TestProjects/SampleAppiOS7/SampleAppiOS7Tests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/TestProjects/SampleAppiOS7/SampleAppiOS7Tests/SampleAppiOS7Tests.m:
--------------------------------------------------------------------------------
1 | //
2 | // SampleAppiOS7Tests.m
3 | // SampleAppiOS7Tests
4 | //
5 | // Created by Peter Livesey on 2/18/16.
6 | // Copyright © 2016 LinkedIn. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "ViewController.h"
11 | #import "LayoutTest.h"
12 | #import "LYTViewProvider.h"
13 |
14 |
15 | @interface ViewController ()
16 |
17 | @end
18 |
19 | @implementation ViewController
20 |
21 | + (NSDictionary *)dataSpecForTest {
22 | return @{};
23 | }
24 |
25 | + (UIView *)viewForData:(NSDictionary *)data reuseView:(nullable UIView *)reuseView size:(nullable LYTViewSize *)size context:(id _Nullable * _Nullable)context {
26 | ViewController *controller = [[ViewController alloc] init];
27 | *context = controller;
28 | return controller.view;
29 | }
30 |
31 | @end
32 |
33 | @interface SampleAppiOS7Tests : LYTLayoutTestCase
34 |
35 | @end
36 |
37 | @implementation SampleAppiOS7Tests
38 |
39 | - (void)testExample {
40 | [self runLayoutTestsWithViewProvider:[ViewController class] validation:^(id view, NSDictionary *data, id context) {
41 | // Run no extra tests
42 | }];
43 | }
44 |
45 | @end
46 |
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -o pipefail &&
4 | time xcodebuild clean test \
5 | -project LayoutTest.xcodeproj \
6 | -scheme LayoutTest \
7 | -sdk iphonesimulator12.2 \
8 | -destination 'platform=iOS Simulator,name=iPhone SE,OS=12.2' \
9 | | xcpretty
10 |
--------------------------------------------------------------------------------
/docs/README:
--------------------------------------------------------------------------------
1 | How to Build Docs
2 |
3 | - Install Sphinx: $ sudo easy_install sphinx
4 | - Install the theme: $ ./bin/update_theme.sh
5 | - Build the docs: $ make clean html
6 | - Open the docs locally: $ open _build/html/index.html
7 |
8 |
--------------------------------------------------------------------------------
/docs/bin/update_theme.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | cd `dirname $0`/../
4 | rm -rf source/sphinx_rtd_theme
5 | git clone https://github.com/snide/sphinx_rtd_theme.git temp
6 | mv temp/sphinx_rtd_theme sphinx_rtd_theme
7 | rm -rf temp
8 | echo "Theme updated"
9 |
10 |
--------------------------------------------------------------------------------
/docs/images/catalog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linkedin/LayoutTest-iOS/0796ec35ddadc58181019a706dcd6b1492d40837/docs/images/catalog.png
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | .. Layout Testing Library documentation master file, created by
2 | sphinx-quickstart on Wed Jan 21 10:16:33 2015.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | Welcome to Layout Testing Library's documentation!
7 | ==================================================
8 |
9 | Contents:
10 |
11 | .. toctree::
12 | :maxdepth: 1
13 | :glob:
14 |
15 | pages/*
16 |
17 | This library enables you to write unit tests which test the layout of a view in multiple configurations. It tests the view with different data combinations and different view sizes. The library works in both Objective-C and Swift.
18 |
19 | You can view the source code on GitHub: https://github.com/linkedin/LayoutTest-iOS
20 |
21 | The syntax is also light allowing you to add these tests easily. This test automatically verifies no views are overlapping, verifies Autolayout doesn't throw and runs some sanity accessibility tests.
22 |
23 | .. code-block:: objective-c
24 |
25 | @interface SampleTableViewCellLayoutTests : LYTLayoutTestCase
26 | @end
27 |
28 | @implementation LayoutTestCaseMissingLabelTests
29 |
30 | - (void)testSampleTableViewCellLayout {
31 | [self runLayoutTestsWithViewProvider:[SampleTableViewCell class]
32 | validation:^(UIView * view, NSDictionary * data, id context) {
33 | // Add your custom tests here.
34 | }];
35 | }
36 |
37 | @end
38 |
39 | @implementation SampleTableViewCell (LayoutTesting)
40 | + (nullable NSDictionary *)dataSpecForTestWithError:(__unused NSError * _Nullable __autoreleasing *)error {
41 | return @{
42 | @"text": [[LYTStringValues alloc] init],
43 | @"showButton": [[LYTBoolValues alloc] init]
44 | }
45 | }
46 | + (nullable UIView *)viewForData:(NSDictionary *)data
47 | reuseView:(nullable UIView *)reuseView
48 | size:(nullable LYTViewSize *)size
49 | context:(id _Nullable * _Nullable)context
50 | error:(__unused NSError * _Nullable __autoreleasing *)error {
51 | SampleTableViewCell *view = (SampleTableViewCell *)reuseView ?: [SampleTableViewCell viewFromNib];
52 | [view setupWithJSON:data];
53 | return view;
54 | }
55 | @end
56 |
57 | These docs do not try to give API level documentation. Instead, they try to outline at a higher level the purpose and motivation for this library. These docs should give you a good overview of what the library does, how to set it up, an overview of the API, etc. To get the API level documentation, you should checkout the headers in the code.
58 |
--------------------------------------------------------------------------------
/docs/pages/000_gettingStarted.rst:
--------------------------------------------------------------------------------
1 | Getting Started
2 | ---------------
3 |
4 | The library is distributed using CocoaPods. You can install by adding the following to your Podfile.
5 |
6 | Objective-C
7 | ===========
8 |
9 | .. code-block:: ruby
10 |
11 | target :MyAppNameTests do
12 | pod 'LayoutTest'
13 | end
14 |
15 | Swift
16 | =====
17 |
18 | .. code-block:: ruby
19 |
20 | target :MyAppNameTests do
21 | pod 'LayoutTest/Swift'
22 | end
23 |
24 | Adding To Non-UnitTest Targets
25 | ==============================
26 |
27 | .. NOTE::
28 | Since LayoutTestCase depends on XCTest, you should only add LayoutTest to your unit tests target. If you want to use some of the functionality in an app (ie. catalog functionality or helpers), then you can include the LayoutTestBase pod in those targets.
29 |
30 | For example:
31 |
32 | .. code-block:: ruby
33 |
34 | target :MyAppName do
35 | pod 'LayoutTestBase'
36 | end
37 |
38 | target :MyAppNameTests do
39 | pod 'LayoutTest'
40 | end
41 |
42 | Writing Tests
43 | =============
44 |
45 | See :doc:`020_writingTest`
46 |
--------------------------------------------------------------------------------
/docs/pages/010_overview.rst:
--------------------------------------------------------------------------------
1 | Overview
2 | --------
3 |
4 | This library helps you quickly write unit tests which test the layout of your views. These tests are run as part of your unit test suite. It is a property-based testing library, so it will run each test multiple times with different inputs.
5 |
6 | Why Use This Library?
7 | =====================
8 |
9 | 1. It's easy to write layout tests for views in just a few lines of code.
10 | 2. It's much faster to write than a UI automation test.
11 | 3. It automatically tests for all kinds of edge cases you may have not considered when writing your view.
12 | 4. The tests run fast (they're just unit tests).
13 | 5. The tests are stable and deterministic.
14 | 6. The library helps stop regressions in your layout logic.
15 |
16 | What this Library doesn't do
17 | ============================
18 |
19 | 1. This library doesn't test flows. You can't test tap targets or view transitions. For this, you'll need to use UI automation tests.
20 | 2. This library doesn't take screenshots of views and compare them. Instead, you are checking properties of the UIView object for correctness.
21 |
22 | General Flow
23 | ============
24 |
25 | 1. You define a spec for the data that should inflate this view. Effectively, this is mock data.
26 | 2. You define how the view gets built. This is usually a couple of lines of code (init and inflate).
27 | 3. You write your tests by asserting properties on the view that should always be true. For example, you may check that one view's width is the same as another. You should check any layout logic which you've added to your view class.
28 | 4. The testing framework creates many combinations of the data and inflates the view multiple times running the tests each time. In this way, you can write one test which tests hundreds of edge cases with no extra work. These edge cases include
29 |
--------------------------------------------------------------------------------
/docs/pages/030_viewSizes.rst:
--------------------------------------------------------------------------------
1 | Testing Different View Sizes
2 | ----------------------------
3 |
4 | Often, it's useful to test your view on different sizes. For instance, when testing a UITableViewCell subclass, you may want to test on iPhone 5, iPhone 6, iPad, etc.
5 |
6 | First, you need to implement sizesForView:
7 |
8 | .. code-block:: objective-c
9 |
10 | // Objective-C
11 | + (NSArray *)sizesForView {
12 | return @[
13 | [[LYTViewSize alloc] initWithWidth:@(LYTiPhone4Width)],
14 | [[LYTViewSize alloc] initWithWidth:@(LYTiPadWidth)]
15 | ]
16 | }
17 |
18 | .. code-block:: objective-c
19 |
20 | // Swift
21 | class func sizesForView() -> [LYTViewSize] {
22 | return [
23 | LYTViewSize(width: LYTiPhone4Width),
24 | LYTViewSize(width: LYTiPadWidth)
25 | ]
26 | }
27 |
28 | This will now run your tests twice - once on either size. Note that the tests will only edit the width of the view, not the height. This is likely what you want, but you can choose to edit the height instead or as well as the width.
29 |
30 | If you need to dynamically set the height of the view, you can also implement ``+ (void)adjustViewSize:data:size:context:`` which gives you one last chance to change the height of the view before the test is run. See the in code docs for more information.
31 |
32 | .. NOTE::
33 | These tests don't actually run on these devices. They will always just run the tests on the target device. These are just constants which define a certain width and it will resize the view to this width while running tests.
34 |
35 | These sizes can also be set in the config so they are globally set for all tests.
36 |
--------------------------------------------------------------------------------
/docs/pages/040_includedTests.rst:
--------------------------------------------------------------------------------
1 | Automatic Tests
2 | ---------------
3 |
4 | When you subclass LYTLayoutTestCase or LayoutTestCase, a few tests are run automatically. These are tests that most views should pass. You can turn these off in the config or individually on each test.
5 |
6 | No subviews overlap
7 | ===================
8 |
9 | The test verifies that no sibling subviews are overlapping. This can often happen when something is calculated incorrectly. If this is intentional, you can add certain views to an exception set.
10 |
11 | If there is just one subview you want to allow overlaps for, you can add it to viewsAllowingOverlap in the validation block of your test. If there is a certain class which internally has overlapping subviews (such as a custom button), you can add it to viewClassesAllowingSubviewErrors.
12 |
13 | All subviews contained within superview
14 | =======================================
15 |
16 | The test verifies that no subview is out of bounds of it's superview. Again, you can add exceptions if this is intentional.
17 |
18 | If there is just one subview you want to allow overlaps for, you can add it to viewsAllowingOverlap in the validation block of your test.
19 |
20 | Autolayout is not ambiguous and doesn't throw errors
21 | ====================================================
22 |
23 | The test verifies that Autolayout is not ambiguous and also checks that there are no errors from having too many constraints.
24 |
25 | Accessibility sanity check
26 | ==========================
27 |
28 | The test runs some basic accessibility sanity checks. These are not comprehensive, but will catch some common errors. These include:
29 |
30 | - All UIControl subclasses have accessibility labels set. This way, accessibility users will be able to interact with them. You can add to this list of classes by adding to viewClassesRequiringAccessibilityLabels.
31 | - All elements with accessibility identifiers also have accessibility labels. Otherwise, the identifiers will be read out.
32 | - No elements with accessibility labels are nested inside other elements with accessibility labels. Accessibility doesn't support nested elements, and nested elements are not reachable by accessibility users.
33 |
34 | You can add views that allow accessibility errors to viewsAllowingAccessibilityErrors in the validation block.
35 |
--------------------------------------------------------------------------------
/docs/pages/050_propertyTesting.rst:
--------------------------------------------------------------------------------
1 | Property Testing
2 | ----------------
3 |
4 | This library is based on the idea of property testing. Property testing is basically when you create a bunch of inputs to a function and assert properties on the output. It gained popularity in Haskell with the QuickCheck library and in Scala with ScalaCheck.
5 |
6 | For an overview of property based testing, see:
7 |
8 | http://blog.jessitron.com/2013/04/property-based-testing-what-is-it.html
9 |
10 | Differences
11 | ===========
12 |
13 | The main difference between this library and traditional property testing is that traditional property testing uses truly random input. For now, this library is deterministic, but takes the idea of running many combinations from property based testing.
14 |
15 | Also, QuickCheck and ScalaTest attempt to provide the smallest possible input for a failure. So if it fails on an array of 100 elements, it will try it with some of the elements removed, and when it presents a failure will try to give the smallest input it failed on. This library does not attempt anything like that, but it's a good idea for the future.
16 |
--------------------------------------------------------------------------------
/docs/pages/060_catalog.rst:
--------------------------------------------------------------------------------
1 | UI Catalog
2 | ----------
3 |
4 | Since you've already written code to inflate your view with many combinations of data, you can leverage this to create a UICatalog. Basically, you can create a UITableView or UICollectionView of all of your views and inspect them visually. This can help you iterate on APIs fast with fake data and also catch edge cases. It can also be useful to visualize all the edge cases for a view to other members of your team.
5 |
6 | .. IMPORTANT::
7 | Since LayoutTestBase contains references to private APIs (for Autolayout failure detection), you not use UICatalog in your production application. Instead, you should probably create a separate target for the catalog.
8 |
9 | For more info, see LYTCatalogTableViewController, LYTCatalogCollectionViewController and LYTViewCatalogProvider. If you run the catalog target in the sample app, you can see an example of what the catalog will provide. It looks something like this:
10 |
11 | .. image:: ../images/catalog.png
12 | :width: 50%
13 | :align: center
14 |
15 | .. NOTE::
16 | This part of the library is currently less used and hence less mature and tested than other parts of the library. Please give feedback on the Github page if you find any issues.
17 |
--------------------------------------------------------------------------------
/docs/pages/070_apis.rst:
--------------------------------------------------------------------------------
1 | Other APIs
2 | ----------
3 |
4 | There are some other helper APIs to help you write tests quickly. As always, the code is the best place for up to date documentation.
5 |
6 | Autolayout
7 | ==========
8 |
9 | LYTAutolayoutFailureIntercepter - Globally detects when throws an error when laying out a view with Autolayout. This programmatically catches the errors you see in the console when you have too many constraints.
10 |
11 | UIView Helpers
12 | ==============
13 |
14 | UIView (LYTHelpers) - provides quick lookups for getting x, y, width, height etc.
15 |
16 | UIView (LYTFrameComparison) - provides helpers for verifying relative frames of two views. This includes verifying the top is aligned or that one view is above another. It includes support for right to left languages.
17 |
18 | UIView (LYTTestHelpers) - provides some common tests which most views should pass. These are run automatically when you subclass LYTLayoutTestCase or LayoutTestCase.
19 |
--------------------------------------------------------------------------------
/docs/pages/080_config.rst:
--------------------------------------------------------------------------------
1 | Config
2 | ------
3 |
4 | The LYTConfig class allows you to set global config for the library. Most of these properties can also be set individually on tests. If they are set in both places, the test's methods are used.
5 |
--------------------------------------------------------------------------------
/docs/pages/090_testingOtherObjects.rst:
--------------------------------------------------------------------------------
1 | Testing Other Objects
2 | ---------------------
3 |
4 | Usually, you want to test a UIView subclass, but sometimes, you need to track other objects as well. For instance, you may want to test the view of a UIViewController subclass. To test this, you need to have a reference to the UIViewController in your tests (to access IBOutlets and other things). To do this, you can use the context parameter. This can be used for many other things but testing UIViewControllers serves as a good example.
5 |
6 | Sample Code
7 | ===========
8 |
9 | .. code-block:: objective-c
10 |
11 | // Objective-C
12 | // LYTViewProvider
13 | + (nullable UIView *)viewForData:(NSDictionary *)data
14 | reuseView:(nullable UIView *)reuseView
15 | size:(nullable LYTViewSize *)size
16 | context:(id _Nullable * _Nullable)context
17 | error:(__unused NSError * _Nullable __autoreleasing *)error {
18 | // Ignoring reuse because it doesn't make sense for testing UIViewController subclasses
19 | SampleViewController *controller = [[SampleViewController alloc] init];
20 | [controller setupWithDictionary:data];
21 | *context = controller;
22 | return controller.view;
23 | }
24 |
25 | // LYTLayoutTest
26 | - (void)testViewController {
27 | [self runLayoutTestsWithViewProvider:[SampleViewController class]
28 | validation:^(UIView * view, NSDictionary * data, id context) {
29 | SampleViewController *controller = context;
30 | // Rest of your test goes here
31 | }
32 | }
33 |
34 | .. code-block:: objective-c
35 |
36 | // Swift
37 | // LYTViewProvider
38 | class func viewForData(data: [NSObject: AnyObject],
39 | reuseView: UIView?,
40 | size: LYTViewSize?,
41 | context: AutoreleasingUnsafeMutablePointer) throws -> UIView {
42 | // Ignoring reuse because it doesn't make sense for testing UIViewController subclasses
43 | let controller = SampleViewController()
44 | controller.setupWithData(data)
45 | context.memory = controller
46 | return controller.view
47 | }
48 |
49 | // LayoutTest
50 | func testViewController() {
51 | runLayoutTestsWithViewProvider(SampleViewController.self) { (view: UIView, data: [NSObject: AnyObject], context: Any?) in
52 | let controller = context as! SampleViewController
53 | // Rest of your test goes here
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/docs/pages/100_performance.rst:
--------------------------------------------------------------------------------
1 | Performance
2 | -----------
3 |
4 | Since a simple test can run hundreds or thousands of times, sometimes these tests can start to run slowly. There are multiple ways to make the tests run faster.
5 |
6 | How many times will it run?
7 | ===========================
8 |
9 | Currently, the number of test runs will be:
10 |
11 | ::
12 |
13 | number_of_combinations_from_data_spec * number_of_sizes_to_test
14 |
15 | You can limit the number of test runs by decreasing either of these values.
16 |
17 | Limit LYTDataValues
18 | ===================
19 |
20 | The more LYTDataValues subclasses you use in the data spec, the more tests will get run. You should only use LYTDataValues subclasses for things you actually want to test. For opaque values which don't really matter or don't affect layout (eg. id fields, tracking tokens), you may want to consider hard coding a single value.
21 |
22 | Limit Results API
23 | =================
24 |
25 | When running tests, you can also pass in a limitResults flag. See LYTTesterLimitResults in LYTLayoutPropertyTester.h for full documentation.
26 |
27 | Currently, results can be limited in two different ways.
28 |
29 | =======================
30 | Limit Data Combinations
31 | =======================
32 |
33 | With this flag on, the library will not choose every combination of data from the data spec. Instead, it will choose each single value at least once, but not all combinations of values. This makes the number of combinations O(m*n) instead of O(m^n) where m is the number of values in each LYTDataValues subclass and n is the number of LYTDataValues subclasses in the data spec.
34 |
35 | Effectively, with this flag on, we assume independence of the data values. So, we assume that only single value changes will cause test failures and not combinations of values. Of course, this isn't strictly true, but it can help you run tests with larger data specs easily without losing too many tests.
36 |
37 | =============
38 | No View Sizes
39 | =============
40 |
41 | This ignores the view sizes passed in and runs the test on one size (the original size of the view).
42 |
--------------------------------------------------------------------------------
/docs/pages/110_projectStructure.rst:
--------------------------------------------------------------------------------
1 | Project Structure
2 | -----------------
3 |
4 | The project is currently split into two CocoaPods: LayoutTestBase and LayoutTest.
5 |
6 | LayoutTestBase contains most of the library (which is then split into subspecs), but doesn't contain anything with a dependency on XCTest. This allows applications to use LayoutTestBase in a runnable application. This is useful for using the catalog view controllers or just using some of the helpers.
7 |
8 | LayoutTest depends on LayoutTestBase and contains all the files which require XCTest.
9 |
10 | This is not the easiest setup to use because it requires you to import both frameworks when you use the library, but currently, we have not found a better way to setup the project.
11 |
12 | Swift
13 | =====
14 |
15 | All the swift code is kept in a subspec. This subspec is NOT included automatically in the main import. This is intentional since it seems most apps wanting to use this library are written in Objective-C and don't want to deal with the pain of swift dependencies.
16 |
17 | Subspecs
18 | ========
19 |
20 | We considered using subspecs for the base code and XCTest dependent code, but couldn't because of this ticket: https://github.com/CocoaPods/CocoaPods/issues/4629
21 |
--------------------------------------------------------------------------------
/docs/pages/120_FAQ.rst:
--------------------------------------------------------------------------------
1 | FAQ
2 | ---
3 |
4 | No questions yet, but reserving this page for questions once we get them.
5 |
--------------------------------------------------------------------------------
/docs/pages/130_future.rst:
--------------------------------------------------------------------------------
1 | Future Plans
2 | ------------
3 |
4 | Over time, we hope to add more features to the library and improve the API. Here's some of the big issues we are thinking about adding in the future:
5 |
6 | 1. Allow for an option of random generators.
7 | 2. Implement the 'smaller' protocol like Haskell and ScalaCheck to find the smallest possible failing case (instead of hundreds of failing cases).
8 |
--------------------------------------------------------------------------------