mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 21:45:19 +00:00
Remove unnecessary files
This commit is contained in:
parent
44d9bab2b0
commit
6c2e5ea779
1
.gitignore
vendored
1
.gitignore
vendored
@ -31,7 +31,6 @@ Project.xcodeproj/*
|
||||
Watch/Watch.xcodeproj/*
|
||||
AppBundle.xcworkspace/*
|
||||
*.xcworkspace
|
||||
tools/buck
|
||||
*.xcodeproj
|
||||
!*_Xcode.xcodeproj
|
||||
.idea
|
||||
|
@ -298,6 +298,7 @@ def watch_extension_info_plist_substitutions():
|
||||
"CURRENT_PROJECT_VERSION": "1",
|
||||
"BUILD_NUMBER": get_build_number(),
|
||||
"PRODUCT_BUNDLE_SHORT_VERSION": get_short_version(),
|
||||
"MinimumOSVersion": "5.0",
|
||||
}
|
||||
return substitutions
|
||||
|
||||
@ -312,5 +313,6 @@ def watch_info_plist_substitutions():
|
||||
"CURRENT_PROJECT_VERSION": "1",
|
||||
"BUILD_NUMBER": get_build_number(),
|
||||
"PRODUCT_BUNDLE_SHORT_VERSION": get_short_version(),
|
||||
"MinimumOSVersion": "5.0",
|
||||
}
|
||||
return substitutions
|
||||
|
@ -6,7 +6,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface NotificationServiceImpl : NSObject
|
||||
|
||||
- (instancetype)initWithCountIncomingMessage:(void (^)(NSString *, int64_t, DeviceSpecificEncryptionParameters *, int64_t, int32_t))countIncomingMessage isLocked:(bool (^)(NSString *))isLocked lockedMessageText:(NSString *(^)(NSString *))lockedMessageText;
|
||||
- (instancetype)initWithSerialDispatch:(void (^)(dispatch_block_t))serialDispatch countIncomingMessage:(void (^)(NSString *, int64_t, DeviceSpecificEncryptionParameters *, int64_t, int32_t))countIncomingMessage isLocked:(bool (^)(NSString *))isLocked lockedMessageText:(NSString *(^)(NSString *))lockedMessageText;
|
||||
|
||||
- (void)updateUnreadCount:(int32_t)unreadCount;
|
||||
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler;
|
||||
|
@ -51,6 +51,7 @@ static void reportMemory() {
|
||||
#endif
|
||||
|
||||
@interface NotificationServiceImpl () {
|
||||
void (^_serialDispatch)(dispatch_block_t);
|
||||
void (^_countIncomingMessage)(NSString *, int64_t, DeviceSpecificEncryptionParameters *, int64_t, int32_t);
|
||||
|
||||
NSString * _Nullable _rootPath;
|
||||
@ -70,13 +71,14 @@ static void reportMemory() {
|
||||
|
||||
@implementation NotificationServiceImpl
|
||||
|
||||
- (instancetype)initWithCountIncomingMessage:(void (^)(NSString *, int64_t, DeviceSpecificEncryptionParameters *, int64_t, int32_t))countIncomingMessage isLocked:(nonnull bool (^)(NSString * _Nonnull))isLocked lockedMessageText:(NSString *(^)(NSString *))lockedMessageText {
|
||||
- (instancetype)initWithSerialDispatch:(void (^)(dispatch_block_t))serialDispatch countIncomingMessage:(void (^)(NSString *, int64_t, DeviceSpecificEncryptionParameters *, int64_t, int32_t))countIncomingMessage isLocked:(nonnull bool (^)(NSString * _Nonnull))isLocked lockedMessageText:(NSString *(^)(NSString *))lockedMessageText {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
#if DEBUG
|
||||
reportMemory();
|
||||
#endif
|
||||
|
||||
_serialDispatch = [serialDispatch copy];
|
||||
_countIncomingMessage = [countIncomingMessage copy];
|
||||
|
||||
NSString *appBundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
|
||||
@ -127,27 +129,32 @@ static void reportMemory() {
|
||||
reportMemory();
|
||||
#endif
|
||||
|
||||
#ifdef __IPHONE_13_0
|
||||
if (_baseAppBundleId != nil) {
|
||||
BGAppRefreshTaskRequest *request = [[BGAppRefreshTaskRequest alloc] initWithIdentifier:[_baseAppBundleId stringByAppendingString:@".refresh"]];
|
||||
request.earliestBeginDate = nil;
|
||||
NSError *error = nil;
|
||||
[[BGTaskScheduler sharedScheduler] submitTaskRequest:request error:&error];
|
||||
if (error != nil) {
|
||||
NSLog(@"Error: %@", error);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
NSString *baseAppBundleId = _baseAppBundleId;
|
||||
void (^contentHandler)(UNNotificationContent *) = [_contentHandler copy];
|
||||
UNMutableNotificationContent *bestAttemptContent = _bestAttemptContent;
|
||||
NSNumber *updatedUnreadCount = updatedUnreadCount;
|
||||
|
||||
if (_bestAttemptContent && _contentHandler) {
|
||||
if (_updatedUnreadCount != nil) {
|
||||
int32_t unreadCount = (int32_t)[_updatedUnreadCount intValue];
|
||||
if (unreadCount > 0) {
|
||||
_bestAttemptContent.badge = @(unreadCount);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
#ifdef __IPHONE_13_0
|
||||
if (baseAppBundleId != nil && false) {
|
||||
BGAppRefreshTaskRequest *request = [[BGAppRefreshTaskRequest alloc] initWithIdentifier:[baseAppBundleId stringByAppendingString:@".refresh"]];
|
||||
request.earliestBeginDate = nil;
|
||||
NSError *error = nil;
|
||||
[[BGTaskScheduler sharedScheduler] submitTaskRequest:request error:&error];
|
||||
if (error != nil) {
|
||||
NSLog(@"Error: %@", error);
|
||||
}
|
||||
}
|
||||
_contentHandler(_bestAttemptContent);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (updatedUnreadCount != nil) {
|
||||
int32_t unreadCount = (int32_t)[updatedUnreadCount intValue];
|
||||
if (unreadCount > 0) {
|
||||
bestAttemptContent.badge = @(unreadCount);
|
||||
}
|
||||
}
|
||||
contentHandler(bestAttemptContent);
|
||||
});
|
||||
}
|
||||
|
||||
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
|
||||
@ -321,7 +328,7 @@ static void reportMemory() {
|
||||
if (_lockedMessageTextValue != nil) {
|
||||
_bestAttemptContent.body = _lockedMessageTextValue;
|
||||
} else {
|
||||
_bestAttemptContent.body = @"You have a new message";
|
||||
_bestAttemptContent.body = @"^You have a new message";
|
||||
}
|
||||
} else {
|
||||
_bestAttemptContent.subtitle = subtitle;
|
||||
@ -334,7 +341,7 @@ static void reportMemory() {
|
||||
if (_lockedMessageTextValue != nil) {
|
||||
_bestAttemptContent.body = _lockedMessageTextValue;
|
||||
} else {
|
||||
_bestAttemptContent.body = @"You have a new message";
|
||||
_bestAttemptContent.body = @"^You have a new message";
|
||||
}
|
||||
} else {
|
||||
_bestAttemptContent.body = alert;
|
||||
@ -402,9 +409,11 @@ static void reportMemory() {
|
||||
} else {
|
||||
BuildConfig *buildConfig = [[BuildConfig alloc] initWithBaseAppBundleId:_baseAppBundleId];
|
||||
|
||||
void (^serialDispatch)(dispatch_block_t) = _serialDispatch;
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
_cancelFetch = fetchImage(buildConfig, accountInfos.proxy, account, inputFileLocation, fileDatacenterId, ^(NSData * _Nullable data) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
serialDispatch(^{
|
||||
__strong typeof(weakSelf) strongSelf = weakSelf;
|
||||
if (strongSelf == nil) {
|
||||
return;
|
||||
|
@ -1,35 +1,52 @@
|
||||
import Foundation
|
||||
import UserNotifications
|
||||
import SwiftSignalKit
|
||||
|
||||
private let queue = Queue()
|
||||
|
||||
@available(iOSApplicationExtension 10.0, *)
|
||||
@objc(NotificationService)
|
||||
final class NotificationService: UNNotificationServiceExtension {
|
||||
private let impl: NotificationServiceImpl
|
||||
private let impl: QueueLocalObject<NotificationServiceImpl>
|
||||
|
||||
override init() {
|
||||
var completion: ((Int32) -> Void)?
|
||||
self.impl = NotificationServiceImpl(countIncomingMessage: { rootPath, accountId, encryptionParameters, peerId, messageId in
|
||||
SyncProviderImpl.addIncomingMessage(withRootPath: rootPath, accountId: accountId, encryptionParameters: encryptionParameters, peerId: peerId, messageId: messageId, completion: { count in
|
||||
completion?(count)
|
||||
self.impl = QueueLocalObject(queue: queue, generate: {
|
||||
var completion: ((Int32) -> Void)?
|
||||
let impl = NotificationServiceImpl(serialDispatch: { f in
|
||||
queue.async {
|
||||
f()
|
||||
}
|
||||
}, countIncomingMessage: { rootPath, accountId, encryptionParameters, peerId, messageId in
|
||||
SyncProviderImpl.addIncomingMessage(queue: queue, withRootPath: rootPath, accountId: accountId, encryptionParameters: encryptionParameters, peerId: peerId, messageId: messageId, completion: { count in
|
||||
completion?(count)
|
||||
})
|
||||
}, isLocked: { rootPath in
|
||||
return SyncProviderImpl.isLocked(withRootPath: rootPath)
|
||||
}, lockedMessageText: { rootPath in
|
||||
return SyncProviderImpl.lockedMessageText(withRootPath: rootPath)
|
||||
})
|
||||
}, isLocked: { rootPath in
|
||||
return SyncProviderImpl.isLocked(withRootPath: rootPath)
|
||||
}, lockedMessageText: { rootPath in
|
||||
return SyncProviderImpl.lockedMessageText(withRootPath: rootPath)
|
||||
|
||||
completion = { [weak impl] count in
|
||||
queue.async {
|
||||
impl?.updateUnreadCount(count)
|
||||
}
|
||||
}
|
||||
|
||||
return impl
|
||||
})
|
||||
|
||||
super.init()
|
||||
|
||||
completion = { [weak self] count in
|
||||
self?.impl.updateUnreadCount(count)
|
||||
}
|
||||
}
|
||||
|
||||
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
|
||||
self.impl.didReceive(request, withContentHandler: contentHandler)
|
||||
self.impl.with { impl in
|
||||
impl.didReceive(request, withContentHandler: contentHandler)
|
||||
}
|
||||
}
|
||||
|
||||
override func serviceExtensionTimeWillExpire() {
|
||||
self.impl.serviceExtensionTimeWillExpire()
|
||||
self.impl.with { impl in
|
||||
impl.serviceExtensionTimeWillExpire()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,17 +42,17 @@ enum SyncProviderImpl {
|
||||
}
|
||||
}
|
||||
|
||||
static func addIncomingMessage(withRootPath rootPath: String, accountId: Int64, encryptionParameters: DeviceSpecificEncryptionParameters, peerId: Int64, messageId: Int32, completion: @escaping (Int32) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
static func addIncomingMessage(queue: Queue, withRootPath rootPath: String, accountId: Int64, encryptionParameters: DeviceSpecificEncryptionParameters, peerId: Int64, messageId: Int32, completion: @escaping (Int32) -> Void) {
|
||||
queue.async {
|
||||
let _ = registeredTypes
|
||||
|
||||
let sharedBasePath = rootPath + "/accounts-metadata"
|
||||
let basePath = rootPath + "/" + accountRecordIdPathName(accountId) + "/postbox"
|
||||
|
||||
let sharedValueBox = SqliteValueBox(basePath: sharedBasePath + "/db", queue: Queue.mainQueue(), logger: ValueBoxLoggerImpl(), encryptionParameters: nil, disableCache: true, upgradeProgress: { _ in
|
||||
let sharedValueBox = SqliteValueBox(basePath: sharedBasePath + "/db", queue: queue, logger: ValueBoxLoggerImpl(), encryptionParameters: nil, disableCache: true, upgradeProgress: { _ in
|
||||
})
|
||||
|
||||
let valueBox = SqliteValueBox(basePath: basePath + "/db", queue: Queue.mainQueue(), logger: ValueBoxLoggerImpl(), encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: encryptionParameters.key)!, salt: ValueBoxEncryptionParameters.Salt(data: encryptionParameters.salt)!), disableCache: true, upgradeProgress: { _ in
|
||||
let valueBox = SqliteValueBox(basePath: basePath + "/db", queue: queue, logger: ValueBoxLoggerImpl(), encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: encryptionParameters.key)!, salt: ValueBoxEncryptionParameters.Salt(data: encryptionParameters.salt)!), disableCache: true, upgradeProgress: { _ in
|
||||
})
|
||||
|
||||
let metadataTable = MessageHistoryMetadataTable(valueBox: valueBox, table: MessageHistoryMetadataTable.tableSpec(10))
|
||||
|
@ -1,4 +1,6 @@
|
||||
# TON Wallet iOS Source Code Compilation Guide
|
||||
# Test Gram Wallet (iOS)
|
||||
|
||||
This is the source code and build instructions for a TON Testnet Wallet implementation for iOS.
|
||||
|
||||
1. Install Xcode 11.1
|
||||
```
|
||||
@ -31,12 +33,31 @@ sh ./prepare_buck_source.sh $HOME/buck_source
|
||||
5. Now you can build Wallet application (IPA)
|
||||
|
||||
Note:
|
||||
It is recommended to use an artifact cache to optimize build speed. Prepend any of the following commands with BUCK_DIR_CACHE="path/to/existing/directory"
|
||||
It is recommended to use an artifact cache to optimize build speed. Prepend any of the following commands with
|
||||
```
|
||||
BUCK_DIR_CACHE="path/to/existing/directory"
|
||||
```
|
||||
|
||||
```BUCK="$HOME/buck_source/buck/buck-out/gen/programs/buck.pex" DISTRIBUTION_CODE_SIGN_IDENTITY="iPhone Distribution: XXXXXXX (XXXXXXXXXX)" DEVELOPMENT_TEAM="XXXXXXXXXX" WALLET_BUNDLE_ID="wallet.bundle.id" BUILD_NUMBER=30 WALLET_DISTRIBUTION_PROVISIONING_PROFILE_APP="wallet provisioning profile name" CODESIGNING_SOURCE_DATA_PATH="$HOME/wallet_codesigning" sh Wallet/example_wallet_env.sh make -f Wallet.makefile wallet_app
|
||||
```
|
||||
BUCK="$HOME/buck_source/buck/buck-out/gen/programs/buck.pex" \
|
||||
BUILD_NUMBER=30 \
|
||||
DISTRIBUTION_CODE_SIGN_IDENTITY="iPhone Distribution: XXXXXXX (XXXXXXXXXX)" \
|
||||
DEVELOPMENT_TEAM="XXXXXXXXXX" WALLET_BUNDLE_ID="wallet.bundle.id" \
|
||||
WALLET_DISTRIBUTION_PROVISIONING_PROFILE_APP="wallet distribution provisioning profile name" \
|
||||
CODESIGNING_SOURCE_DATA_PATH="$HOME/wallet_codesigning" \
|
||||
sh Wallet/example_wallet_env.sh make -f Wallet.makefile wallet_app
|
||||
```
|
||||
|
||||
6. If needed, generate Xcode project
|
||||
```
|
||||
BUCK="$HOME/buck_source/buck/buck-out/gen/programs/buck.pex" DISTRIBUTION_CODE_SIGN_IDENTITY="iPhone Distribution: XXXXXXX (XXXXXXXXXX)" DEVELOPMENT_TEAM="XXXXXXXXXX" WALLET_BUNDLE_ID="wallet.bundle.id" BUILD_NUMBER=30 WALLET_DISTRIBUTION_PROVISIONING_PROFILE_APP="wallet provisioning profile name" CODESIGNING_SOURCE_DATA_PATH="$HOME/wallet_codesigning" sh Wallet/example_wallet_env.sh make -f Wallet.makefile wallet_project
|
||||
BUCK="$HOME/buck_source/buck/buck-out/gen/programs/buck.pex" \
|
||||
BUILD_NUMBER=30 \
|
||||
DEVELOPMENT_CODE_SIGN_IDENTITY="iPhone Developer: XXXXXXX (XXXXXXXXXX)" \
|
||||
DISTRIBUTION_CODE_SIGN_IDENTITY="iPhone Distribution: XXXXXXX (XXXXXXXXXX)" \
|
||||
DEVELOPMENT_TEAM="XXXXXXXXXX" WALLET_BUNDLE_ID="wallet.bundle.id" \
|
||||
WALLET_DEVELOPMENT_PROVISIONING_PROFILE_APP="wallet development provisioning profile name" \
|
||||
WALLET_DISTRIBUTION_PROVISIONING_PROFILE_APP="wallet distribution provisioning profile name" \
|
||||
CODESIGNING_SOURCE_DATA_PATH="$HOME/wallet_codesigning" \
|
||||
sh Wallet/example_wallet_env.sh make -f Wallet.makefile wallet_project
|
||||
```
|
||||
|
||||
|
@ -23,7 +23,9 @@ export APP_SPECIFIC_URL_SCHEME=""
|
||||
export API_ID="0"
|
||||
export API_HASH=""
|
||||
|
||||
export DEVELOPMENT_CODE_SIGN_IDENTITY="iPhone Developer: AAAAA AAAAA (XXXXXXXXXX)"
|
||||
if [ -z "$DEVELOPMENT_CODE_SIGN_IDENTITY" ]; then
|
||||
export DEVELOPMENT_CODE_SIGN_IDENTITY="iPhone Developer: AAAAA AAAAA (XXXXXXXXXX)"
|
||||
fi
|
||||
if [ -z "$DISTRIBUTION_CODE_SIGN_IDENTITY" ]; then
|
||||
export DISTRIBUTION_CODE_SIGN_IDENTITY="iPhone Distribution: AAAAA AAAAA (XXXXXXXXXX)"
|
||||
fi
|
||||
@ -41,7 +43,9 @@ if [ -z "$BUILD_NUMBER" ]; then
|
||||
fi
|
||||
|
||||
export WALLET_ENTITLEMENTS_APP="Wallet.entitlements"
|
||||
export WALLET_DEVELOPMENT_PROVISIONING_PROFILE_APP="development profile name"
|
||||
if [ -z "$WALLET_DEVELOPMENT_PROVISIONING_PROFILE_APP" ]; then
|
||||
export WALLET_DEVELOPMENT_PROVISIONING_PROFILE_APP="development profile name"
|
||||
fi
|
||||
if [ -z "$WALLET_DISTRIBUTION_PROVISIONING_PROFILE_APP" ]; then
|
||||
export WALLET_DISTRIBUTION_PROVISIONING_PROFILE_APP="distribution profile name"
|
||||
fi
|
||||
@ -55,10 +59,9 @@ if [ -z "$CODESIGNING_SOURCE_DATA_PATH" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$CODESIGNING_SOURCE_DATA_PATH/profiles" ] || [ ! -d "$CODESIGNING_SOURCE_DATA_PATH/certs" ]; then
|
||||
if [ ! -d "$CODESIGNING_SOURCE_DATA_PATH/profiles" ]; then
|
||||
echo "Expected codesigning directory layout:"
|
||||
echo "$CODESIGNING_SOURCE_DATA_PATH/profiles/appstore/*.mobileprovision"
|
||||
echo "$CODESIGNING_SOURCE_DATA_PATH/certs/distribution/*.{cer,p12}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
@ -76,6 +76,7 @@ shutil.copytree('Config', destination + '/' + 'Config')
|
||||
shutil.copytree('tools/buck', destination + '/' + 'tools/buck')
|
||||
|
||||
shutil.copy('Wallet/README.md', destination + '/' + 'README.md')
|
||||
os.remove(destination + '/Wallet/' + 'README.md')
|
||||
|
||||
copy_files = [
|
||||
'.buckconfig',
|
||||
|
22
submodules/AsyncDisplayKit/.buckconfig
vendored
22
submodules/AsyncDisplayKit/.buckconfig
vendored
@ -1,22 +0,0 @@
|
||||
[cxx]
|
||||
default_platform = iphonesimulator-x86_64
|
||||
combined_preprocess_and_compile = true
|
||||
|
||||
[apple]
|
||||
iphonesimulator_target_sdk_version = 8.0
|
||||
iphoneos_target_sdk_version = 8.0
|
||||
xctool_default_destination_specifier = platform=iOS Simulator, name=iPhone 6, OS=10.2
|
||||
|
||||
[alias]
|
||||
lib = //:AsyncDisplayKit
|
||||
tests = //:Tests
|
||||
|
||||
[httpserver]
|
||||
port = 8080
|
||||
|
||||
[project]
|
||||
ide = xcode
|
||||
ignore = .buckd, \
|
||||
.hg, \
|
||||
.git, \
|
||||
buck-out, \
|
1
submodules/AsyncDisplayKit/.buckversion
vendored
1
submodules/AsyncDisplayKit/.buckversion
vendored
@ -1 +0,0 @@
|
||||
f399f484bf13b47bcc2bf0f2e092ab5d8de9f6e6
|
19
submodules/AsyncDisplayKit/.editorconfig
vendored
19
submodules/AsyncDisplayKit/.editorconfig
vendored
@ -1,19 +0,0 @@
|
||||
# http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[**.{h,cc,mm,m}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.{md,markdown}]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
# Makefiles always use tabs for indentation
|
||||
[Makefile]
|
||||
indent_style = tab
|
@ -1,7 +0,0 @@
|
||||
// If you're looking for help, please consider joining our slack channel:
|
||||
// http://asyncdisplaykit.org/slack (we'll be updating the name to Texture soon)
|
||||
|
||||
// The more information you include, the faster we can help you out!
|
||||
// Please include: a sample project or screenshots, code snippets
|
||||
// Texture version, and/or backtraces for any crashes (> bt all).
|
||||
// Please delete these lines before posting. Thanks!
|
@ -1,3 +0,0 @@
|
||||
issues=false
|
||||
since-tag=2.8
|
||||
future-release=2.9
|
5
submodules/AsyncDisplayKit/.slather.yml
vendored
5
submodules/AsyncDisplayKit/.slather.yml
vendored
@ -1,5 +0,0 @@
|
||||
ci_service: travis_ci
|
||||
coverage_service: coveralls
|
||||
xcodeproj: AsyncDisplayKit.xcodeproj
|
||||
source_directory: AsyncDisplayKit
|
||||
|
34
submodules/AsyncDisplayKit/.travis.yml
vendored
34
submodules/AsyncDisplayKit/.travis.yml
vendored
@ -1,34 +0,0 @@
|
||||
language: objective-c
|
||||
cache:
|
||||
- bundler
|
||||
- cocoapods
|
||||
osx_image: xcode8.1
|
||||
git:
|
||||
depth: 10
|
||||
before_install:
|
||||
- brew update
|
||||
- brew outdated xctool || brew upgrade xctool
|
||||
- brew outdated carthage || brew upgrade carthage
|
||||
- gem install cocoapods -v 1.0.1
|
||||
- gem install xcpretty -v 0.2.2
|
||||
- gem install xcpretty-travis-formatter
|
||||
# - gem install slather
|
||||
- xcrun simctl list
|
||||
install: echo "<3"
|
||||
env:
|
||||
- MODE=tests
|
||||
- MODE=tests_listkit
|
||||
- MODE=examples-pt1
|
||||
- MODE=examples-pt2
|
||||
- MODE=examples-pt3
|
||||
- MODE=life-without-cocoapods
|
||||
- MODE=framework
|
||||
script: ./build.sh $MODE
|
||||
|
||||
#after_success:
|
||||
# - slather
|
||||
|
||||
# whitelist
|
||||
branches:
|
||||
only:
|
||||
- master
|
@ -1,4 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
./build.sh all
|
@ -1,6 +0,0 @@
|
||||
[
|
||||
"^plans/",
|
||||
"^docs/",
|
||||
"^CI/exclude-from-build.json$",
|
||||
"^**/*.md$"
|
||||
]
|
@ -1,70 +0,0 @@
|
||||
//
|
||||
// ASAbsoluteLayoutSpecSnapshotTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASLayoutSpecSnapshotTestsHelper.h"
|
||||
|
||||
#import <AsyncDisplayKit/ASAbsoluteLayoutSpec.h>
|
||||
#import <AsyncDisplayKit/ASBackgroundLayoutSpec.h>
|
||||
|
||||
@interface ASAbsoluteLayoutSpecSnapshotTests : ASLayoutSpecSnapshotTestCase
|
||||
@end
|
||||
|
||||
@implementation ASAbsoluteLayoutSpecSnapshotTests
|
||||
|
||||
- (void)testSizingBehaviour
|
||||
{
|
||||
[self testWithSizeRange:ASSizeRangeMake(CGSizeMake(150, 200), CGSizeMake(INFINITY, INFINITY))
|
||||
identifier:@"underflowChildren"];
|
||||
[self testWithSizeRange:ASSizeRangeMake(CGSizeZero, CGSizeMake(50, 100))
|
||||
identifier:@"overflowChildren"];
|
||||
// Expect the spec to wrap its content because children sizes are between constrained size
|
||||
[self testWithSizeRange:ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY / 2, INFINITY / 2))
|
||||
identifier:@"wrappedChildren"];
|
||||
}
|
||||
|
||||
- (void)testChildrenMeasuredWithAutoMaxSize
|
||||
{
|
||||
ASDisplayNode *firstChild = ASDisplayNodeWithBackgroundColor([UIColor redColor], (CGSize){50, 50});
|
||||
firstChild.style.layoutPosition = CGPointMake(0, 0);
|
||||
|
||||
ASDisplayNode *secondChild = ASDisplayNodeWithBackgroundColor([UIColor blueColor], (CGSize){100, 100});
|
||||
secondChild.style.layoutPosition = CGPointMake(10, 60);
|
||||
|
||||
ASSizeRange sizeRange = ASSizeRangeMake(CGSizeMake(10, 10), CGSizeMake(110, 160));
|
||||
[self testWithChildren:@[firstChild, secondChild] sizeRange:sizeRange identifier:nil];
|
||||
}
|
||||
|
||||
- (void)testWithSizeRange:(ASSizeRange)sizeRange identifier:(NSString *)identifier
|
||||
{
|
||||
ASDisplayNode *firstChild = ASDisplayNodeWithBackgroundColor([UIColor redColor], (CGSize){50, 50});
|
||||
firstChild.style.layoutPosition = CGPointMake(0, 0);
|
||||
|
||||
ASDisplayNode *secondChild = ASDisplayNodeWithBackgroundColor([UIColor blueColor], (CGSize){100, 100});
|
||||
secondChild.style.layoutPosition = CGPointMake(0, 50);
|
||||
|
||||
[self testWithChildren:@[firstChild, secondChild] sizeRange:sizeRange identifier:identifier];
|
||||
}
|
||||
|
||||
- (void)testWithChildren:(NSArray *)children sizeRange:(ASSizeRange)sizeRange identifier:(NSString *)identifier
|
||||
{
|
||||
ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor whiteColor]);
|
||||
|
||||
NSMutableArray *subnodes = [NSMutableArray arrayWithArray:children];
|
||||
[subnodes insertObject:backgroundNode atIndex:0];
|
||||
|
||||
ASLayoutSpec *layoutSpec =
|
||||
[ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:
|
||||
[ASAbsoluteLayoutSpec
|
||||
absoluteLayoutSpecWithChildren:children]
|
||||
background:backgroundNode];
|
||||
|
||||
[self testLayoutSpec:layoutSpec sizeRange:sizeRange subnodes:subnodes identifier:identifier];
|
||||
}
|
||||
|
||||
@end
|
@ -1,40 +0,0 @@
|
||||
//
|
||||
// ASBackgroundLayoutSpecSnapshotTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASLayoutSpecSnapshotTestsHelper.h"
|
||||
|
||||
#import <AsyncDisplayKit/ASBackgroundLayoutSpec.h>
|
||||
#import <AsyncDisplayKit/ASCenterLayoutSpec.h>
|
||||
|
||||
static const ASSizeRange kSize = {{320, 320}, {320, 320}};
|
||||
|
||||
@interface ASBackgroundLayoutSpecSnapshotTests : ASLayoutSpecSnapshotTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASBackgroundLayoutSpecSnapshotTests
|
||||
|
||||
- (void)testBackground
|
||||
{
|
||||
ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor blueColor]);
|
||||
ASDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor blackColor], {20, 20});
|
||||
|
||||
ASLayoutSpec *layoutSpec =
|
||||
[ASBackgroundLayoutSpec
|
||||
backgroundLayoutSpecWithChild:
|
||||
[ASCenterLayoutSpec
|
||||
centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringXY
|
||||
sizingOptions:{}
|
||||
child:foregroundNode]
|
||||
background:backgroundNode];
|
||||
|
||||
[self testLayoutSpec:layoutSpec sizeRange:kSize subnodes:@[backgroundNode, foregroundNode] identifier: nil];
|
||||
}
|
||||
|
||||
@end
|
@ -1,75 +0,0 @@
|
||||
//
|
||||
// ASBasicImageDownloaderContextTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASBasicImageDownloader.h>
|
||||
#import <AsyncDisplayKit/ASBasicImageDownloaderInternal.h>
|
||||
|
||||
#import <OCMock/OCMock.h>
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
|
||||
@interface ASBasicImageDownloaderContextTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASBasicImageDownloaderContextTests
|
||||
|
||||
- (NSURL *)randomURL
|
||||
{
|
||||
// random URL for each test, doesn't matter that this is not really a URL
|
||||
return [NSURL URLWithString:[NSUUID UUID].UUIDString];
|
||||
}
|
||||
|
||||
- (void)testContextCreation
|
||||
{
|
||||
NSURL *url = [self randomURL];
|
||||
ASBasicImageDownloaderContext *c1 = [ASBasicImageDownloaderContext contextForURL:url];
|
||||
ASBasicImageDownloaderContext *c2 = [ASBasicImageDownloaderContext contextForURL:url];
|
||||
XCTAssert(c1 == c2, @"Context objects are not the same");
|
||||
}
|
||||
|
||||
- (void)testContextInvalidation
|
||||
{
|
||||
NSURL *url = [self randomURL];
|
||||
ASBasicImageDownloaderContext *context = [ASBasicImageDownloaderContext contextForURL:url];
|
||||
[context cancel];
|
||||
XCTAssert([context isCancelled], @"Context should be cancelled");
|
||||
}
|
||||
|
||||
/* This test is currently unreliable. See https://github.com/facebook/AsyncDisplayKit/issues/459
|
||||
- (void)testAsyncContextInvalidation
|
||||
{
|
||||
NSURL *url = [self randomURL];
|
||||
ASBasicImageDownloaderContext *context = [ASBasicImageDownloaderContext contextForURL:url];
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"Context invalidation"];
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
[expectation fulfill];
|
||||
XCTAssert([context isCancelled], @"Context should be cancelled");
|
||||
});
|
||||
|
||||
[context cancel];
|
||||
[self waitForExpectationsWithTimeout:30.0 handler:nil];
|
||||
}
|
||||
*/
|
||||
|
||||
- (void)testContextSessionCanceled
|
||||
{
|
||||
NSURL *url = [self randomURL];
|
||||
id task = [OCMockObject mockForClass:[NSURLSessionTask class]];
|
||||
ASBasicImageDownloaderContext *context = [ASBasicImageDownloaderContext contextForURL:url];
|
||||
context.sessionTask = task;
|
||||
|
||||
[[task expect] cancel];
|
||||
|
||||
[context cancel];
|
||||
}
|
||||
|
||||
@end
|
@ -1,45 +0,0 @@
|
||||
//
|
||||
// ASBasicImageDownloaderTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASBasicImageDownloader.h>
|
||||
|
||||
@interface ASBasicImageDownloaderTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASBasicImageDownloaderTests
|
||||
|
||||
- (void)testAsynchronouslyDownloadTheSameURLTwice
|
||||
{
|
||||
XCTestExpectation *firstExpectation = [self expectationWithDescription:@"First ASBasicImageDownloader completion handler should be called within 3 seconds"];
|
||||
XCTestExpectation *secondExpectation = [self expectationWithDescription:@"Second ASBasicImageDownloader completion handler should be called within 3 seconds"];
|
||||
|
||||
ASBasicImageDownloader *downloader = [ASBasicImageDownloader sharedImageDownloader];
|
||||
NSURL *URL = [NSURL URLWithString:@"http://wrongPath/wrongResource.png"];
|
||||
|
||||
[downloader downloadImageWithURL:URL
|
||||
callbackQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
|
||||
downloadProgress:nil
|
||||
completion:^(id<ASImageContainerProtocol> _Nullable image, NSError * _Nullable error, id _Nullable downloadIdentifier, id _Nullable userInfo) {
|
||||
[firstExpectation fulfill];
|
||||
}];
|
||||
|
||||
[downloader downloadImageWithURL:URL
|
||||
callbackQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
|
||||
downloadProgress:nil
|
||||
completion:^(id<ASImageContainerProtocol> _Nullable image, NSError * _Nullable error, id _Nullable downloadIdentifier, id _Nullable userInfo) {
|
||||
[secondExpectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectationsWithTimeout:30 handler:nil];
|
||||
}
|
||||
|
||||
@end
|
@ -1,120 +0,0 @@
|
||||
//
|
||||
// ASBatchFetchingTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
#import <AsyncDisplayKit/ASBatchFetching.h>
|
||||
|
||||
@interface ASBatchFetchingTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASBatchFetchingTests
|
||||
|
||||
#define PASSING_RECT CGRectMake(0,0,1,1)
|
||||
#define PASSING_SIZE CGSizeMake(1,1)
|
||||
#define PASSING_POINT CGPointMake(1,1)
|
||||
#define VERTICAL_RECT(h) CGRectMake(0,0,1,h)
|
||||
#define VERTICAL_SIZE(h) CGSizeMake(0,h)
|
||||
#define VERTICAL_OFFSET(y) CGPointMake(0,y)
|
||||
#define HORIZONTAL_RECT(w) CGRectMake(0,0,w,1)
|
||||
#define HORIZONTAL_SIZE(w) CGSizeMake(w,0)
|
||||
#define HORIZONTAL_OFFSET(x) CGPointMake(x,0)
|
||||
|
||||
- (void)testBatchNullState {
|
||||
ASBatchContext *context = [[ASBatchContext alloc] init];
|
||||
BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, CGRectZero, CGSizeZero, CGPointZero, 0.0, YES, CGPointZero, nil);
|
||||
XCTAssert(shouldFetch == NO, @"Should not fetch in the null state");
|
||||
}
|
||||
|
||||
- (void)testBatchAlreadyFetching {
|
||||
ASBatchContext *context = [[ASBatchContext alloc] init];
|
||||
[context beginBatchFetching];
|
||||
BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0, YES, CGPointZero, nil);
|
||||
XCTAssert(shouldFetch == NO, @"Should not fetch when context is already fetching");
|
||||
}
|
||||
|
||||
- (void)testUnsupportedScrollDirections {
|
||||
ASBatchContext *context = [[ASBatchContext alloc] init];
|
||||
BOOL fetchRight = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionRight, ASScrollDirectionHorizontalDirections, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0, YES, CGPointZero, nil);
|
||||
XCTAssert(fetchRight == YES, @"Should fetch for scrolling right");
|
||||
BOOL fetchDown = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0, YES, CGPointZero, nil);
|
||||
XCTAssert(fetchDown == YES, @"Should fetch for scrolling down");
|
||||
BOOL fetchUp = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionUp, ASScrollDirectionVerticalDirections, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0, YES, CGPointZero, nil);
|
||||
XCTAssert(fetchUp == NO, @"Should not fetch for scrolling up");
|
||||
BOOL fetchLeft = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionLeft, ASScrollDirectionHorizontalDirections, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0, YES, CGPointZero, nil);
|
||||
XCTAssert(fetchLeft == NO, @"Should not fetch for scrolling left");
|
||||
}
|
||||
|
||||
- (void)testVerticalScrollToExactLeading {
|
||||
CGFloat screen = 1.0;
|
||||
ASBatchContext *context = [[ASBatchContext alloc] init];
|
||||
// scroll to 1-screen top offset, height is 1 screen, so bottom is 1 screen away from end of content
|
||||
BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 3.0), VERTICAL_OFFSET(screen * 1.0), 1.0, YES, CGPointZero, nil);
|
||||
XCTAssert(shouldFetch == YES, @"Fetch should begin when vertically scrolling to exactly 1 leading screen away");
|
||||
}
|
||||
|
||||
- (void)testVerticalScrollToLessThanLeading {
|
||||
CGFloat screen = 1.0;
|
||||
ASBatchContext *context = [[ASBatchContext alloc] init];
|
||||
// 3 screens of content, scroll only 1/2 of one screen
|
||||
BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 3.0), VERTICAL_OFFSET(screen * 0.5), 1.0, YES, CGPointZero, nil);
|
||||
XCTAssert(shouldFetch == NO, @"Fetch should not begin when vertically scrolling less than the leading distance away");
|
||||
}
|
||||
|
||||
- (void)testVerticalScrollingPastContentSize {
|
||||
CGFloat screen = 1.0;
|
||||
ASBatchContext *context = [[ASBatchContext alloc] init];
|
||||
// 3 screens of content, top offset to 3-screens, height 1 screen, so its 1 screen past the leading
|
||||
BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 3.0), VERTICAL_OFFSET(screen * 3.0), 1.0, YES, CGPointZero, nil);
|
||||
XCTAssert(shouldFetch == YES, @"Fetch should begin when vertically scrolling past the content size");
|
||||
}
|
||||
|
||||
- (void)testHorizontalScrollToExactLeading {
|
||||
CGFloat screen = 1.0;
|
||||
ASBatchContext *context = [[ASBatchContext alloc] init];
|
||||
// scroll to 1-screen left offset, width is 1 screen, so right is 1 screen away from end of content
|
||||
BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionRight, ASScrollDirectionVerticalDirections, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 3.0), HORIZONTAL_OFFSET(screen * 1.0), 1.0, YES, CGPointZero, nil);
|
||||
XCTAssert(shouldFetch == YES, @"Fetch should begin when horizontally scrolling to exactly 1 leading screen away");
|
||||
}
|
||||
|
||||
- (void)testHorizontalScrollToLessThanLeading {
|
||||
CGFloat screen = 1.0;
|
||||
ASBatchContext *context = [[ASBatchContext alloc] init];
|
||||
// 3 screens of content, scroll only 1/2 of one screen
|
||||
BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionLeft, ASScrollDirectionHorizontalDirections, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 3.0), HORIZONTAL_OFFSET(screen * 0.5), 1.0, YES, CGPointZero, nil);
|
||||
XCTAssert(shouldFetch == NO, @"Fetch should not begin when horizontally scrolling less than the leading distance away");
|
||||
}
|
||||
|
||||
- (void)testHorizontalScrollingPastContentSize {
|
||||
CGFloat screen = 1.0;
|
||||
ASBatchContext *context = [[ASBatchContext alloc] init];
|
||||
// 3 screens of content, left offset to 3-screens, width 1 screen, so its 1 screen past the leading
|
||||
BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionHorizontalDirections, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 3.0), HORIZONTAL_OFFSET(screen * 3.0), 1.0, YES, CGPointZero, nil);
|
||||
XCTAssert(shouldFetch == YES, @"Fetch should begin when vertically scrolling past the content size");
|
||||
}
|
||||
|
||||
- (void)testVerticalScrollingSmallContentSize {
|
||||
CGFloat screen = 1.0;
|
||||
ASBatchContext *context = [[ASBatchContext alloc] init];
|
||||
// when the content size is < screen size, the target offset will always be 0
|
||||
BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 0.5), VERTICAL_OFFSET(0.0), 1.0, YES, CGPointZero, nil);
|
||||
XCTAssert(shouldFetch == YES, @"Fetch should begin when the target is 0 and the content size is smaller than the scree");
|
||||
}
|
||||
|
||||
- (void)testHorizontalScrollingSmallContentSize {
|
||||
CGFloat screen = 1.0;
|
||||
ASBatchContext *context = [[ASBatchContext alloc] init];
|
||||
// when the content size is < screen size, the target offset will always be 0
|
||||
BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionRight, ASScrollDirectionHorizontalDirections, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 0.5), HORIZONTAL_OFFSET(0.0), 1.0, YES, CGPointZero, nil);
|
||||
XCTAssert(shouldFetch == YES, @"Fetch should begin when the target is 0 and the content size is smaller than the scree");
|
||||
}
|
||||
|
||||
@end
|
@ -1,231 +0,0 @@
|
||||
//
|
||||
// ASBridgedPropertiesTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import <AsyncDisplayKit/ASPendingStateController.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode.h>
|
||||
#import <AsyncDisplayKit/ASThread.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
|
||||
#import <AsyncDisplayKit/_ASPendingState.h>
|
||||
#import <AsyncDisplayKit/ASCellNode.h>
|
||||
|
||||
@interface ASPendingStateController (Testing)
|
||||
- (BOOL)test_isFlushScheduled;
|
||||
@end
|
||||
|
||||
@interface ASBridgedPropertiesTestView : UIView
|
||||
@property (nonatomic, readonly) BOOL receivedSetNeedsLayout;
|
||||
@end
|
||||
|
||||
@implementation ASBridgedPropertiesTestView
|
||||
|
||||
- (void)setNeedsLayout
|
||||
{
|
||||
_receivedSetNeedsLayout = YES;
|
||||
[super setNeedsLayout];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface ASBridgedPropertiesTestNode : ASDisplayNode
|
||||
@property (nullable, nonatomic, copy) dispatch_block_t onDealloc;
|
||||
@end
|
||||
|
||||
@implementation ASBridgedPropertiesTestNode
|
||||
|
||||
- (void)dealloc {
|
||||
_onDealloc();
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface ASBridgedPropertiesTests : XCTestCase
|
||||
@end
|
||||
|
||||
/// Dispatches the given block synchronously onto a different thread.
|
||||
/// This is useful for testing non-main-thread behavior because `dispatch_sync`
|
||||
/// will often use the current thread.
|
||||
static inline void ASDispatchSyncOnOtherThread(dispatch_block_t block) {
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
|
||||
dispatch_group_enter(group);
|
||||
dispatch_async(q, ^{
|
||||
ASDisplayNodeCAssertNotMainThread();
|
||||
block();
|
||||
dispatch_group_leave(group);
|
||||
});
|
||||
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
|
||||
}
|
||||
|
||||
@implementation ASBridgedPropertiesTests
|
||||
|
||||
- (void)testTheresASharedInstance
|
||||
{
|
||||
XCTAssertNotNil([ASPendingStateController sharedInstance]);
|
||||
}
|
||||
|
||||
/// FIXME: This test is unreliable for an as-yet unknown reason
|
||||
/// but that being intermittent, and this test being so strict, it's
|
||||
/// reasonable to assume for now the failures don't reflect a framework bug.
|
||||
/// See https://github.com/facebook/AsyncDisplayKit/pull/1048
|
||||
- (void)DISABLED_testThatDirtyNodesAreNotRetained
|
||||
{
|
||||
ASPendingStateController *ctrl = [ASPendingStateController sharedInstance];
|
||||
__block BOOL didDealloc = NO;
|
||||
@autoreleasepool {
|
||||
__attribute__((objc_precise_lifetime)) ASBridgedPropertiesTestNode *node = [ASBridgedPropertiesTestNode new];
|
||||
node.onDealloc = ^{
|
||||
didDealloc = YES;
|
||||
};
|
||||
[node view];
|
||||
XCTAssertEqual(node.alpha, 1);
|
||||
ASDispatchSyncOnOtherThread(^{
|
||||
node.alpha = 0;
|
||||
});
|
||||
XCTAssertEqual(node.alpha, 1);
|
||||
XCTAssert(ctrl.test_isFlushScheduled);
|
||||
}
|
||||
XCTAssertTrue(didDealloc);
|
||||
}
|
||||
|
||||
- (void)testThatSettingABridgedViewPropertyInBackgroundGetsFlushedOnNextRunLoop
|
||||
{
|
||||
ASDisplayNode *node = [ASDisplayNode new];
|
||||
[node view];
|
||||
XCTAssertEqual(node.alpha, 1);
|
||||
ASDispatchSyncOnOtherThread(^{
|
||||
node.alpha = 0;
|
||||
});
|
||||
XCTAssertEqual(node.alpha, 1);
|
||||
[self waitForMainDispatchQueueToFlush];
|
||||
XCTAssertEqual(node.alpha, 0);
|
||||
}
|
||||
|
||||
- (void)testThatSettingABridgedLayerPropertyInBackgroundGetsFlushedOnNextRunLoop
|
||||
{
|
||||
ASDisplayNode *node = [ASDisplayNode new];
|
||||
[node view];
|
||||
XCTAssertEqual(node.shadowOpacity, 0);
|
||||
ASDispatchSyncOnOtherThread(^{
|
||||
node.shadowOpacity = 1;
|
||||
});
|
||||
XCTAssertEqual(node.shadowOpacity, 0);
|
||||
[self waitForMainDispatchQueueToFlush];
|
||||
XCTAssertEqual(node.shadowOpacity, 1);
|
||||
}
|
||||
|
||||
- (void)testThatReadingABridgedViewPropertyInBackgroundThrowsAnException
|
||||
{
|
||||
ASDisplayNode *node = [ASDisplayNode new];
|
||||
[node view];
|
||||
ASDispatchSyncOnOtherThread(^{
|
||||
XCTAssertThrows(node.alpha);
|
||||
});
|
||||
}
|
||||
|
||||
- (void)testThatReadingABridgedLayerPropertyInBackgroundThrowsAnException
|
||||
{
|
||||
ASDisplayNode *node = [ASDisplayNode new];
|
||||
[node view];
|
||||
ASDispatchSyncOnOtherThread(^{
|
||||
XCTAssertThrows(node.contentsScale);
|
||||
});
|
||||
}
|
||||
|
||||
- (void)testThatManuallyFlushingTheSyncControllerImmediatelyAppliesChanges
|
||||
{
|
||||
ASPendingStateController *ctrl = [ASPendingStateController sharedInstance];
|
||||
ASDisplayNode *node = [ASDisplayNode new];
|
||||
[node view];
|
||||
XCTAssertEqual(node.alpha, 1);
|
||||
ASDispatchSyncOnOtherThread(^{
|
||||
node.alpha = 0;
|
||||
});
|
||||
XCTAssertEqual(node.alpha, 1);
|
||||
[ctrl flush];
|
||||
XCTAssertEqual(node.alpha, 0);
|
||||
XCTAssertFalse(ctrl.test_isFlushScheduled);
|
||||
}
|
||||
|
||||
- (void)testThatFlushingTheControllerInBackgroundThrows
|
||||
{
|
||||
ASPendingStateController *ctrl = [ASPendingStateController sharedInstance];
|
||||
ASDisplayNode *node = [ASDisplayNode new];
|
||||
[node view];
|
||||
XCTAssertEqual(node.alpha, 1);
|
||||
ASDispatchSyncOnOtherThread(^{
|
||||
node.alpha = 0;
|
||||
XCTAssertThrows([ctrl flush]);
|
||||
});
|
||||
}
|
||||
|
||||
- (void)testThatSettingABridgedPropertyOnMainThreadPassesDirectlyToView
|
||||
{
|
||||
ASPendingStateController *ctrl = [ASPendingStateController sharedInstance];
|
||||
ASDisplayNode *node = [ASDisplayNode new];
|
||||
XCTAssertFalse(ASDisplayNodeGetPendingState(node).hasChanges);
|
||||
[node view];
|
||||
XCTAssertEqual(node.alpha, 1);
|
||||
node.alpha = 0;
|
||||
XCTAssertEqual(node.view.alpha, 0);
|
||||
XCTAssertEqual(node.alpha, 0);
|
||||
XCTAssertFalse(ASDisplayNodeGetPendingState(node).hasChanges);
|
||||
XCTAssertFalse(ctrl.test_isFlushScheduled);
|
||||
}
|
||||
|
||||
- (void)testThatCallingSetNeedsLayoutFromBackgroundCausesItToHappenLater
|
||||
{
|
||||
ASDisplayNode *node = [[ASDisplayNode alloc] initWithViewClass:ASBridgedPropertiesTestView.class];
|
||||
ASBridgedPropertiesTestView *view = (ASBridgedPropertiesTestView *)node.view;
|
||||
XCTAssertFalse(view.receivedSetNeedsLayout);
|
||||
ASDispatchSyncOnOtherThread(^{
|
||||
XCTAssertNoThrow([node setNeedsLayout]);
|
||||
});
|
||||
XCTAssertFalse(view.receivedSetNeedsLayout);
|
||||
[self waitForMainDispatchQueueToFlush];
|
||||
XCTAssertTrue(view.receivedSetNeedsLayout);
|
||||
}
|
||||
|
||||
- (void)testThatCallingSetNeedsLayoutOnACellNodeFromBackgroundIsSafe
|
||||
{
|
||||
ASCellNode *node = [ASCellNode new];
|
||||
[node view];
|
||||
ASDispatchSyncOnOtherThread(^{
|
||||
XCTAssertNoThrow([node setNeedsLayout]);
|
||||
});
|
||||
}
|
||||
|
||||
- (void)testThatCallingSetNeedsDisplayFromBackgroundCausesItToHappenLater
|
||||
{
|
||||
ASDisplayNode *node = [ASDisplayNode new];
|
||||
[node.layer displayIfNeeded];
|
||||
XCTAssertFalse(node.layer.needsDisplay);
|
||||
ASDispatchSyncOnOtherThread(^{
|
||||
XCTAssertNoThrow([node setNeedsDisplay]);
|
||||
});
|
||||
XCTAssertFalse(node.layer.needsDisplay);
|
||||
[self waitForMainDispatchQueueToFlush];
|
||||
XCTAssertTrue(node.layer.needsDisplay);
|
||||
}
|
||||
|
||||
/// [XCTExpectation expectationWithPredicate:] should handle this
|
||||
/// but under Xcode 7.2.1 its polling interval is 1 second
|
||||
/// which makes the tests really slow and I'm impatient.
|
||||
- (void)waitForMainDispatchQueueToFlush
|
||||
{
|
||||
__block BOOL done = NO;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
done = YES;
|
||||
});
|
||||
while (!done) {
|
||||
[NSRunLoop.mainRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
@ -1,54 +0,0 @@
|
||||
//
|
||||
// ASButtonNodeTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASButtonNode.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
|
||||
|
||||
@interface ASButtonNodeTests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation ASButtonNodeTests
|
||||
|
||||
- (void)testAccessibility
|
||||
{
|
||||
// Setup a button with some title.
|
||||
ASButtonNode *buttonNode = nil;
|
||||
buttonNode = [[ASButtonNode alloc] init];
|
||||
NSString *title = @"foo";
|
||||
[buttonNode setTitle:title withFont:nil withColor:nil forState:UIControlStateNormal];
|
||||
|
||||
// Verify accessibility properties.
|
||||
XCTAssertTrue(buttonNode.accessibilityTraits == UIAccessibilityTraitButton,
|
||||
@"Should have button accessibility trait, instead has %llu",
|
||||
buttonNode.accessibilityTraits);
|
||||
XCTAssertTrue(buttonNode.defaultAccessibilityTraits == UIAccessibilityTraitButton,
|
||||
@"Default accessibility traits should return button accessibility trait, instead "
|
||||
@"returns %llu",
|
||||
buttonNode.defaultAccessibilityTraits);
|
||||
XCTAssertTrue([buttonNode.accessibilityLabel isEqualToString:title],
|
||||
@"Accessibility label is incorrectly set to \n%@\n when it should be \n%@\n",
|
||||
buttonNode.accessibilityLabel, title);
|
||||
XCTAssertTrue([buttonNode.defaultAccessibilityLabel isEqualToString:title],
|
||||
@"Default accessibility label incorrectly returns \n%@\n when it should be \n%@\n",
|
||||
buttonNode.defaultAccessibilityLabel, title);
|
||||
|
||||
// Disable the button and verify that accessibility traits has been updated correctly.
|
||||
buttonNode.enabled = NO;
|
||||
UIAccessibilityTraits disabledButtonTrait = UIAccessibilityTraitButton | UIAccessibilityTraitNotEnabled;
|
||||
XCTAssertTrue(buttonNode.accessibilityTraits == disabledButtonTrait,
|
||||
@"Should have disabled button accessibility trait, instead has %llu",
|
||||
buttonNode.accessibilityTraits);
|
||||
XCTAssertTrue(buttonNode.defaultAccessibilityTraits == disabledButtonTrait,
|
||||
@"Default accessibility traits should return disabled button accessibility trait, "
|
||||
@"instead returns %llu",
|
||||
buttonNode.defaultAccessibilityTraits);
|
||||
}
|
||||
|
||||
@end
|
@ -1,107 +0,0 @@
|
||||
//
|
||||
// ASCALayerTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <OCMock/OCMock.h>
|
||||
|
||||
/**
|
||||
* Tests that confirm what we know about Core Animation behavior.
|
||||
*
|
||||
* These tests are not run during the normal test action. You can run them yourself
|
||||
* to investigate and confirm CA behavior.
|
||||
*/
|
||||
@interface ASCALayerTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
#define DeclareLayerAndSublayer() \
|
||||
CALayer *realSublayer = [CALayer layer]; \
|
||||
id layer = [OCMockObject partialMockForObject:[CALayer layer]]; \
|
||||
id sublayer = [OCMockObject partialMockForObject:realSublayer]; \
|
||||
[layer addSublayer:realSublayer];
|
||||
|
||||
@implementation ASCALayerTests
|
||||
|
||||
- (void)testThatLayerBeginsWithCleanLayout
|
||||
{
|
||||
XCTAssertFalse([CALayer layer].needsLayout);
|
||||
}
|
||||
|
||||
- (void)testThatAddingSublayersDirtysLayout
|
||||
{
|
||||
CALayer *layer = [CALayer layer];
|
||||
[layer addSublayer:[CALayer layer]];
|
||||
XCTAssertTrue([layer needsLayout]);
|
||||
}
|
||||
|
||||
- (void)testThatRemovingSublayersDirtysLayout
|
||||
{
|
||||
DeclareLayerAndSublayer();
|
||||
[layer layoutIfNeeded];
|
||||
XCTAssertFalse([layer needsLayout]);
|
||||
[sublayer removeFromSuperlayer];
|
||||
XCTAssertTrue([layer needsLayout]);
|
||||
}
|
||||
|
||||
- (void)testDirtySublayerLayoutDoesntDirtySuperlayer
|
||||
{
|
||||
DeclareLayerAndSublayer();
|
||||
[layer layoutIfNeeded];
|
||||
|
||||
// Dirtying sublayer doesn't dirty superlayer.
|
||||
[sublayer setNeedsLayout];
|
||||
XCTAssertTrue([sublayer needsLayout]);
|
||||
XCTAssertFalse([layer needsLayout]);
|
||||
[[[sublayer expect] andForwardToRealObject] layoutSublayers];
|
||||
// NOTE: We specifically don't expect layer to get -layoutSublayers
|
||||
[sublayer layoutIfNeeded];
|
||||
[sublayer verify];
|
||||
[layer verify];
|
||||
}
|
||||
|
||||
- (void)testDirtySuperlayerLayoutDoesntDirtySublayerLayout
|
||||
{
|
||||
DeclareLayerAndSublayer();
|
||||
[layer layoutIfNeeded];
|
||||
|
||||
// Dirtying superlayer doesn't dirty sublayer.
|
||||
[layer setNeedsLayout];
|
||||
XCTAssertTrue([layer needsLayout]);
|
||||
XCTAssertFalse([sublayer needsLayout]);
|
||||
[[[layer expect] andForwardToRealObject] layoutSublayers];
|
||||
// NOTE: We specifically don't expect sublayer to get -layoutSublayers
|
||||
[layer layoutIfNeeded];
|
||||
[sublayer verify];
|
||||
[layer verify];
|
||||
}
|
||||
|
||||
- (void)testDirtyHierarchyIsLaidOutTopDown
|
||||
{
|
||||
DeclareLayerAndSublayer();
|
||||
[sublayer setNeedsLayout];
|
||||
|
||||
XCTAssertTrue([layer needsLayout]);
|
||||
XCTAssertTrue([sublayer needsLayout]);
|
||||
|
||||
__block BOOL superlayerLaidOut = NO;
|
||||
[[[[layer expect] andDo:^(NSInvocation *i) {
|
||||
superlayerLaidOut = YES;
|
||||
}] andForwardToRealObject] layoutSublayers];
|
||||
|
||||
[[[[sublayer expect] andDo:^(NSInvocation *i) {
|
||||
XCTAssertTrue(superlayerLaidOut);
|
||||
}] andForwardToRealObject] layoutSublayers];
|
||||
|
||||
[layer layoutIfNeeded];
|
||||
[sublayer verify];
|
||||
[layer verify];
|
||||
}
|
||||
|
||||
@end
|
@ -1,112 +0,0 @@
|
||||
//
|
||||
// ASCenterLayoutSpecSnapshotTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASLayoutSpecSnapshotTestsHelper.h"
|
||||
|
||||
#import <AsyncDisplayKit/ASBackgroundLayoutSpec.h>
|
||||
#import <AsyncDisplayKit/ASCenterLayoutSpec.h>
|
||||
#import <AsyncDisplayKit/ASStackLayoutSpec.h>
|
||||
|
||||
static const ASSizeRange kSize = {{100, 120}, {320, 160}};
|
||||
|
||||
@interface ASCenterLayoutSpecSnapshotTests : ASLayoutSpecSnapshotTestCase
|
||||
@end
|
||||
|
||||
@implementation ASCenterLayoutSpecSnapshotTests
|
||||
|
||||
- (void)testWithOptions
|
||||
{
|
||||
[self testWithCenteringOptions:ASCenterLayoutSpecCenteringNone sizingOptions:{}];
|
||||
[self testWithCenteringOptions:ASCenterLayoutSpecCenteringXY sizingOptions:{}];
|
||||
[self testWithCenteringOptions:ASCenterLayoutSpecCenteringX sizingOptions:{}];
|
||||
[self testWithCenteringOptions:ASCenterLayoutSpecCenteringY sizingOptions:{}];
|
||||
}
|
||||
|
||||
- (void)testWithSizingOptions
|
||||
{
|
||||
[self testWithCenteringOptions:ASCenterLayoutSpecCenteringNone
|
||||
sizingOptions:ASCenterLayoutSpecSizingOptionDefault];
|
||||
[self testWithCenteringOptions:ASCenterLayoutSpecCenteringNone
|
||||
sizingOptions:ASCenterLayoutSpecSizingOptionMinimumX];
|
||||
[self testWithCenteringOptions:ASCenterLayoutSpecCenteringNone
|
||||
sizingOptions:ASCenterLayoutSpecSizingOptionMinimumY];
|
||||
[self testWithCenteringOptions:ASCenterLayoutSpecCenteringNone
|
||||
sizingOptions:ASCenterLayoutSpecSizingOptionMinimumXY];
|
||||
}
|
||||
|
||||
- (void)testWithCenteringOptions:(ASCenterLayoutSpecCenteringOptions)options
|
||||
sizingOptions:(ASCenterLayoutSpecSizingOptions)sizingOptions
|
||||
{
|
||||
ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor redColor]);
|
||||
ASDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor greenColor], CGSizeMake(70, 100));
|
||||
|
||||
ASLayoutSpec *layoutSpec =
|
||||
[ASBackgroundLayoutSpec
|
||||
backgroundLayoutSpecWithChild:
|
||||
[ASCenterLayoutSpec
|
||||
centerLayoutSpecWithCenteringOptions:options
|
||||
sizingOptions:sizingOptions
|
||||
child:foregroundNode]
|
||||
background:backgroundNode];
|
||||
|
||||
[self testLayoutSpec:layoutSpec
|
||||
sizeRange:kSize
|
||||
subnodes:@[backgroundNode, foregroundNode]
|
||||
identifier:suffixForCenteringOptions(options, sizingOptions)];
|
||||
}
|
||||
|
||||
static NSString *suffixForCenteringOptions(ASCenterLayoutSpecCenteringOptions centeringOptions,
|
||||
ASCenterLayoutSpecSizingOptions sizingOptinos)
|
||||
{
|
||||
NSMutableString *suffix = [NSMutableString string];
|
||||
|
||||
if ((centeringOptions & ASCenterLayoutSpecCenteringX) != 0) {
|
||||
[suffix appendString:@"CenteringX"];
|
||||
}
|
||||
|
||||
if ((centeringOptions & ASCenterLayoutSpecCenteringY) != 0) {
|
||||
[suffix appendString:@"CenteringY"];
|
||||
}
|
||||
|
||||
if ((sizingOptinos & ASCenterLayoutSpecSizingOptionMinimumX) != 0) {
|
||||
[suffix appendString:@"SizingMinimumX"];
|
||||
}
|
||||
|
||||
if ((sizingOptinos & ASCenterLayoutSpecSizingOptionMinimumY) != 0) {
|
||||
[suffix appendString:@"SizingMinimumY"];
|
||||
}
|
||||
|
||||
return suffix;
|
||||
}
|
||||
|
||||
- (void)testMinimumSizeRangeIsGivenToChildWhenNotCentering
|
||||
{
|
||||
ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor redColor]);
|
||||
ASDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor redColor], CGSizeMake(10, 10));
|
||||
foregroundNode.style.flexGrow = 1;
|
||||
|
||||
ASCenterLayoutSpec *layoutSpec =
|
||||
[ASCenterLayoutSpec
|
||||
centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringNone
|
||||
sizingOptions:{}
|
||||
child:
|
||||
[ASBackgroundLayoutSpec
|
||||
backgroundLayoutSpecWithChild:
|
||||
[ASStackLayoutSpec
|
||||
stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical
|
||||
spacing:0
|
||||
justifyContent:ASStackLayoutJustifyContentStart
|
||||
alignItems:ASStackLayoutAlignItemsStart
|
||||
children:@[foregroundNode]]
|
||||
background:backgroundNode]];
|
||||
|
||||
[self testLayoutSpec:layoutSpec sizeRange:kSize subnodes:@[backgroundNode, foregroundNode] identifier:nil];
|
||||
}
|
||||
|
||||
@end
|
@ -1,363 +0,0 @@
|
||||
//
|
||||
// ASCollectionModernDataSourceTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import <OCMock/OCMock.h>
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
#import <AsyncDisplayKit/NSIndexSet+ASHelpers.h>
|
||||
#import "OCMockObject+ASAdditions.h"
|
||||
#import "ASTestCase.h"
|
||||
|
||||
@interface ASCollectionModernDataSourceTests : ASTestCase
|
||||
@end
|
||||
|
||||
@interface ASTestCellNode : ASCellNode
|
||||
@end
|
||||
|
||||
@interface ASTestSection : NSObject <ASSectionContext>
|
||||
@property (nonatomic, readonly) NSMutableArray *nodeModels;
|
||||
@end
|
||||
|
||||
@implementation ASCollectionModernDataSourceTests {
|
||||
@private
|
||||
id mockDataSource;
|
||||
UIWindow *window;
|
||||
UIViewController *viewController;
|
||||
ASCollectionNode *collectionNode;
|
||||
NSMutableArray<ASTestSection *> *sections;
|
||||
}
|
||||
|
||||
- (void)setUp {
|
||||
[super setUp];
|
||||
// Default is 2 sections: 2 items in first, 1 item in second.
|
||||
sections = [NSMutableArray array];
|
||||
[sections addObject:[ASTestSection new]];
|
||||
[sections[0].nodeModels addObject:[NSObject new]];
|
||||
[sections[0].nodeModels addObject:[NSObject new]];
|
||||
[sections addObject:[ASTestSection new]];
|
||||
[sections[1].nodeModels addObject:[NSObject new]];
|
||||
window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
|
||||
viewController = [[UIViewController alloc] init];
|
||||
|
||||
window.rootViewController = viewController;
|
||||
[window makeKeyAndVisible];
|
||||
collectionNode = [[ASCollectionNode alloc] initWithCollectionViewLayout:[UICollectionViewFlowLayout new]];
|
||||
collectionNode.frame = viewController.view.bounds;
|
||||
collectionNode.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
[viewController.view addSubnode:collectionNode];
|
||||
|
||||
mockDataSource = OCMStrictProtocolMock(@protocol(ASCollectionDataSource));
|
||||
[mockDataSource addImplementedOptionalProtocolMethods:
|
||||
@selector(numberOfSectionsInCollectionNode:),
|
||||
@selector(collectionNode:numberOfItemsInSection:),
|
||||
@selector(collectionNode:nodeBlockForItemAtIndexPath:),
|
||||
@selector(collectionNode:nodeModelForItemAtIndexPath:),
|
||||
@selector(collectionNode:contextForSection:),
|
||||
nil];
|
||||
[mockDataSource setExpectationOrderMatters:YES];
|
||||
|
||||
// NOTE: Adding optionally-implemented methods after this point won't work due to ASCollectionNode selector caching.
|
||||
collectionNode.dataSource = mockDataSource;
|
||||
}
|
||||
|
||||
- (void)tearDown
|
||||
{
|
||||
[collectionNode waitUntilAllUpdatesAreProcessed];
|
||||
[super tearDown];
|
||||
}
|
||||
|
||||
#pragma mark - Test Methods
|
||||
|
||||
- (void)testInitialDataLoading
|
||||
{
|
||||
[self loadInitialData];
|
||||
}
|
||||
|
||||
- (void)testReloadingAnItem
|
||||
{
|
||||
[self loadInitialData];
|
||||
|
||||
// Reload at (0, 0)
|
||||
NSIndexPath *reloadedPath = [NSIndexPath indexPathForItem:0 inSection:0];
|
||||
|
||||
[self performUpdateReloadingSections:nil
|
||||
reloadingItems:@{ reloadedPath: [NSObject new] }
|
||||
reloadMappings:@{ reloadedPath: reloadedPath }
|
||||
insertingItems:nil
|
||||
deletingItems:nil
|
||||
skippedReloadIndexPaths:nil];
|
||||
}
|
||||
|
||||
- (void)testInsertingAnItem
|
||||
{
|
||||
[self loadInitialData];
|
||||
|
||||
// Insert at (1, 0)
|
||||
NSIndexPath *insertedPath = [NSIndexPath indexPathForItem:0 inSection:1];
|
||||
|
||||
[self performUpdateReloadingSections:nil
|
||||
reloadingItems:nil
|
||||
reloadMappings:nil
|
||||
insertingItems:@{ insertedPath: [NSObject new] }
|
||||
deletingItems:nil
|
||||
skippedReloadIndexPaths:nil];
|
||||
}
|
||||
|
||||
- (void)testReloadingAnItemWithACompatibleNodeModel
|
||||
{
|
||||
[self loadInitialData];
|
||||
|
||||
// Reload and delete together, for good measure.
|
||||
NSIndexPath *reloadedPath = [NSIndexPath indexPathForItem:1 inSection:0];
|
||||
NSIndexPath *deletedPath = [NSIndexPath indexPathForItem:0 inSection:0];
|
||||
|
||||
id nodeModel = [NSObject new];
|
||||
|
||||
// Cell node should get -canUpdateToNodeModel:
|
||||
id mockCellNode = [collectionNode nodeForItemAtIndexPath:reloadedPath];
|
||||
OCMExpect([mockCellNode canUpdateToNodeModel:nodeModel])
|
||||
.andReturn(YES);
|
||||
|
||||
[self performUpdateReloadingSections:nil
|
||||
reloadingItems:@{ reloadedPath: nodeModel }
|
||||
reloadMappings:@{ reloadedPath: [NSIndexPath indexPathForItem:0 inSection:0] }
|
||||
insertingItems:nil
|
||||
deletingItems:@[ deletedPath ]
|
||||
skippedReloadIndexPaths:@[ reloadedPath ]];
|
||||
}
|
||||
|
||||
- (void)testReloadingASection
|
||||
{
|
||||
[self loadInitialData];
|
||||
|
||||
[self performUpdateReloadingSections:@{ @0: [ASTestSection new] }
|
||||
reloadingItems:nil
|
||||
reloadMappings:nil
|
||||
insertingItems:nil
|
||||
deletingItems:nil
|
||||
skippedReloadIndexPaths:nil];
|
||||
}
|
||||
|
||||
#pragma mark - Helpers
|
||||
|
||||
- (void)loadInitialData
|
||||
{
|
||||
// Count methods are called twice in a row for first data load.
|
||||
// Since -reloadData is routed through our batch update system,
|
||||
// the batch update latches the "old data source counts" if needed at -beginUpdates time
|
||||
// and then verifies them against the "new data source counts" after the updates.
|
||||
// This isn't ideal, but the cost is very small and the system works well.
|
||||
for (int i = 0; i < 2; i++) {
|
||||
// It reads all the counts
|
||||
[self expectDataSourceCountMethods];
|
||||
}
|
||||
|
||||
// It reads each section object.
|
||||
for (NSInteger section = 0; section < sections.count; section++) {
|
||||
[self expectContextMethodForSection:section];
|
||||
}
|
||||
|
||||
// It reads the contents for each item.
|
||||
for (NSInteger section = 0; section < sections.count; section++) {
|
||||
NSArray *nodeModels = sections[section].nodeModels;
|
||||
|
||||
// For each item:
|
||||
for (NSInteger i = 0; i < nodeModels.count; i++) {
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:section];
|
||||
[self expectNodeModelMethodForItemAtIndexPath:indexPath nodeModel:nodeModels[i]];
|
||||
[self expectNodeBlockMethodForItemAtIndexPath:indexPath];
|
||||
}
|
||||
}
|
||||
|
||||
[window layoutIfNeeded];
|
||||
|
||||
// Assert item counts & content:
|
||||
[self assertCollectionNodeContent];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds expectations for the sequence:
|
||||
*
|
||||
* numberOfSectionsInCollectionNode:
|
||||
* for section in countsArray
|
||||
* numberOfItemsInSection:
|
||||
*/
|
||||
- (void)expectDataSourceCountMethods
|
||||
{
|
||||
// -numberOfSectionsInCollectionNode
|
||||
OCMExpect([mockDataSource numberOfSectionsInCollectionNode:collectionNode])
|
||||
.andReturn(sections.count);
|
||||
|
||||
// For each section:
|
||||
// Note: Skip fast enumeration for readability.
|
||||
for (NSInteger section = 0; section < sections.count; section++) {
|
||||
OCMExpect([mockDataSource collectionNode:collectionNode numberOfItemsInSection:section])
|
||||
.andReturn(sections[section].nodeModels.count);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)expectNodeModelMethodForItemAtIndexPath:(NSIndexPath *)indexPath nodeModel:(id)nodeModel
|
||||
{
|
||||
OCMExpect([mockDataSource collectionNode:collectionNode nodeModelForItemAtIndexPath:indexPath])
|
||||
.andReturn(nodeModel);
|
||||
}
|
||||
|
||||
- (void)expectContextMethodForSection:(NSInteger)section
|
||||
{
|
||||
OCMExpect([mockDataSource collectionNode:collectionNode contextForSection:section])
|
||||
.andReturn(sections[section]);
|
||||
}
|
||||
|
||||
- (void)expectNodeBlockMethodForItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
OCMExpect([mockDataSource collectionNode:collectionNode nodeBlockForItemAtIndexPath:indexPath])
|
||||
.andReturn((ASCellNodeBlock)^{
|
||||
ASCellNode *node = [ASTestCellNode new];
|
||||
// Generating multiple partial mocks of the same class is not thread-safe.
|
||||
id mockNode;
|
||||
@synchronized (NSNull.null) {
|
||||
mockNode = OCMPartialMock(node);
|
||||
}
|
||||
[mockNode setExpectationOrderMatters:YES];
|
||||
return mockNode;
|
||||
});
|
||||
}
|
||||
|
||||
/// Asserts that counts match and all view-models are up-to-date between us and collectionNode.
|
||||
- (void)assertCollectionNodeContent
|
||||
{
|
||||
// Assert section count
|
||||
XCTAssertEqual(collectionNode.numberOfSections, sections.count);
|
||||
|
||||
for (NSInteger section = 0; section < sections.count; section++) {
|
||||
ASTestSection *sectionObject = sections[section];
|
||||
NSArray *nodeModels = sectionObject.nodeModels;
|
||||
|
||||
// Assert section object
|
||||
XCTAssertEqualObjects([collectionNode contextForSection:section], sectionObject);
|
||||
|
||||
// Assert item count
|
||||
XCTAssertEqual([collectionNode numberOfItemsInSection:section], nodeModels.count);
|
||||
for (NSInteger item = 0; item < nodeModels.count; item++) {
|
||||
// Assert node model
|
||||
// Could use pointer equality but the error message is less readable.
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:item inSection:section];
|
||||
id nodeModel = nodeModels[indexPath.item];
|
||||
XCTAssertEqualObjects(nodeModel, [collectionNode nodeModelForItemAtIndexPath:indexPath]);
|
||||
ASCellNode *node = [collectionNode nodeForItemAtIndexPath:indexPath];
|
||||
XCTAssertEqualObjects(node.nodeModel, nodeModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the collection node, with expectations and assertions about the call-order and the correctness of the
|
||||
* new data. You should update the data source _before_ calling this method.
|
||||
*
|
||||
* skippedReloadIndexPaths are the old index paths for nodes that should use -canUpdateToNodeModel: instead of being refetched.
|
||||
*/
|
||||
- (void)performUpdateReloadingSections:(NSDictionary<NSNumber *, id> *)reloadedSections
|
||||
reloadingItems:(NSDictionary<NSIndexPath *, id> *)reloadedItems
|
||||
reloadMappings:(NSDictionary<NSIndexPath *, NSIndexPath *> *)reloadMappings
|
||||
insertingItems:(NSDictionary<NSIndexPath *, id> *)insertedItems
|
||||
deletingItems:(NSArray<NSIndexPath *> *)deletedItems
|
||||
skippedReloadIndexPaths:(NSArray<NSIndexPath *> *)skippedReloadIndexPaths
|
||||
{
|
||||
[collectionNode performBatchUpdates:^{
|
||||
// First update our data source.
|
||||
[reloadedItems enumerateKeysAndObjectsUsingBlock:^(NSIndexPath * _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
|
||||
sections[key.section].nodeModels[key.item] = obj;
|
||||
}];
|
||||
[reloadedSections enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
|
||||
sections[key.integerValue] = obj;
|
||||
}];
|
||||
|
||||
// Deletion paths, sorted descending
|
||||
for (NSIndexPath *indexPath in [deletedItems sortedArrayUsingSelector:@selector(compare:)].reverseObjectEnumerator) {
|
||||
[sections[indexPath.section].nodeModels removeObjectAtIndex:indexPath.item];
|
||||
}
|
||||
|
||||
// Insertion paths, sorted ascending.
|
||||
NSArray *insertionsSortedAcending = [insertedItems.allKeys sortedArrayUsingSelector:@selector(compare:)];
|
||||
for (NSIndexPath *indexPath in insertionsSortedAcending) {
|
||||
[sections[indexPath.section].nodeModels insertObject:insertedItems[indexPath] atIndex:indexPath.item];
|
||||
}
|
||||
|
||||
// Then update the collection node.
|
||||
NSMutableIndexSet *reloadedSectionIndexes = [NSMutableIndexSet indexSet];
|
||||
for (NSNumber *i in reloadedSections) {
|
||||
[reloadedSectionIndexes addIndex:i.integerValue];
|
||||
}
|
||||
[collectionNode reloadSections:reloadedSectionIndexes];
|
||||
[collectionNode reloadItemsAtIndexPaths:reloadedItems.allKeys];
|
||||
[collectionNode deleteItemsAtIndexPaths:deletedItems];
|
||||
[collectionNode insertItemsAtIndexPaths:insertedItems.allKeys];
|
||||
|
||||
// Before the commit, lay out our expectations.
|
||||
|
||||
// Expect it to load the new counts.
|
||||
[self expectDataSourceCountMethods];
|
||||
|
||||
// Combine reloads + inserts and expect them to load content for all of them, in ascending order.
|
||||
NSMutableDictionary<NSIndexPath *, id> *insertsPlusReloads = [[NSMutableDictionary alloc] initWithDictionary:insertedItems];
|
||||
|
||||
// Go through reloaded sections and add all their items into `insertsPlusReloads`
|
||||
[reloadedSectionIndexes enumerateIndexesUsingBlock:^(NSUInteger section, BOOL * _Nonnull stop) {
|
||||
[self expectContextMethodForSection:section];
|
||||
NSArray *nodeModels = sections[section].nodeModels;
|
||||
for (NSInteger i = 0; i < nodeModels.count; i++) {
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:section];
|
||||
insertsPlusReloads[indexPath] = nodeModels[i];
|
||||
}
|
||||
}];
|
||||
|
||||
[reloadedItems enumerateKeysAndObjectsUsingBlock:^(NSIndexPath * _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
|
||||
insertsPlusReloads[reloadMappings[key]] = obj;
|
||||
}];
|
||||
|
||||
for (NSIndexPath *indexPath in [insertsPlusReloads.allKeys sortedArrayUsingSelector:@selector(compare:)]) {
|
||||
[self expectNodeModelMethodForItemAtIndexPath:indexPath nodeModel:insertsPlusReloads[indexPath]];
|
||||
NSIndexPath *oldIndexPath = [reloadMappings allKeysForObject:indexPath].firstObject;
|
||||
BOOL isSkippedReload = oldIndexPath && [skippedReloadIndexPaths containsObject:oldIndexPath];
|
||||
if (!isSkippedReload) {
|
||||
[self expectNodeBlockMethodForItemAtIndexPath:indexPath];
|
||||
}
|
||||
}
|
||||
} completion:nil];
|
||||
|
||||
// Assert that the counts and node models are all correct now.
|
||||
[self assertCollectionNodeContent];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Other Objects
|
||||
|
||||
@implementation ASTestCellNode
|
||||
|
||||
- (BOOL)canUpdateToNodeModel:(id)nodeModel
|
||||
{
|
||||
// Our tests default to NO for migrating node models. We use OCMExpect to return YES when we specifically want to.
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASTestSection
|
||||
@synthesize collectionView;
|
||||
@synthesize sectionName;
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_nodeModels = [NSMutableArray array];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
@ -1,428 +0,0 @@
|
||||
//
|
||||
// ASCollectionViewFlowLayoutInspectorTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <OCMock/OCMock.h>
|
||||
#import "ASXCTExtensions.h"
|
||||
|
||||
#import <AsyncDisplayKit/ASCollectionView.h>
|
||||
#import <AsyncDisplayKit/ASCollectionNode.h>
|
||||
#import <AsyncDisplayKit/ASCollectionViewFlowLayoutInspector.h>
|
||||
#import <AsyncDisplayKit/ASCellNode.h>
|
||||
#import <AsyncDisplayKit/ASCollectionView+Undeprecated.h>
|
||||
|
||||
@interface ASCollectionView (Private)
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* Test Data Source
|
||||
*/
|
||||
@interface InspectorTestDataSource : NSObject <ASCollectionDataSource>
|
||||
@end
|
||||
|
||||
@implementation InspectorTestDataSource
|
||||
|
||||
- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
return [[ASCellNode alloc] init];
|
||||
}
|
||||
|
||||
- (ASCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
return ^{ return [[ASCellNode alloc] init]; };
|
||||
}
|
||||
|
||||
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@protocol InspectorTestDataSourceDelegateProtocol <ASCollectionDataSource, ASCollectionDelegate>
|
||||
|
||||
@end
|
||||
|
||||
@interface InspectorTestDataSourceDelegateWithoutNodeConstrainedSize : NSObject <InspectorTestDataSourceDelegateProtocol>
|
||||
@end
|
||||
|
||||
@implementation InspectorTestDataSourceDelegateWithoutNodeConstrainedSize
|
||||
|
||||
- (ASCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
return ^{ return [[ASCellNode alloc] init]; };
|
||||
}
|
||||
|
||||
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface ASCollectionViewFlowLayoutInspectorTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* Test Delegate for Header Reference Size Implementation
|
||||
*/
|
||||
@interface HeaderReferenceSizeTestDelegate : NSObject <ASCollectionDelegateFlowLayout>
|
||||
|
||||
@end
|
||||
|
||||
@implementation HeaderReferenceSizeTestDelegate
|
||||
|
||||
- (CGSize)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section
|
||||
{
|
||||
return CGSizeMake(125.0, 125.0);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* Test Delegate for Footer Reference Size Implementation
|
||||
*/
|
||||
@interface FooterReferenceSizeTestDelegate : NSObject <ASCollectionDelegateFlowLayout>
|
||||
|
||||
@end
|
||||
|
||||
@implementation FooterReferenceSizeTestDelegate
|
||||
|
||||
- (CGSize)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section
|
||||
{
|
||||
return CGSizeMake(125.0, 125.0);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASCollectionViewFlowLayoutInspectorTests
|
||||
|
||||
- (void)setUp {
|
||||
[super setUp];
|
||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
- (void)tearDown {
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
[super tearDown];
|
||||
}
|
||||
|
||||
#pragma mark - #collectionView:constrainedSizeForSupplementaryNodeOfKind:atIndexPath:
|
||||
|
||||
// Vertical
|
||||
|
||||
// Delegate implementation
|
||||
|
||||
- (void)testThatItReturnsAVerticalConstrainedSizeFromTheHeaderDelegateImplementation
|
||||
{
|
||||
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
|
||||
HeaderReferenceSizeTestDelegate *delegate = [[HeaderReferenceSizeTestDelegate alloc] init];
|
||||
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
layout.scrollDirection = UICollectionViewScrollDirectionVertical;
|
||||
|
||||
CGRect rect = CGRectMake(0, 0, 100.0, 100.0);
|
||||
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout];
|
||||
collectionView.asyncDataSource = dataSource;
|
||||
collectionView.asyncDelegate = delegate;
|
||||
|
||||
ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector);
|
||||
ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
|
||||
ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeMake(collectionView.bounds.size.width, 125.0));
|
||||
|
||||
ASXCTAssertEqualSizeRanges(size, sizeCompare, @"should have a size constrained by the values returned in the delegate implementation");
|
||||
|
||||
collectionView.asyncDataSource = nil;
|
||||
collectionView.asyncDelegate = nil;
|
||||
}
|
||||
|
||||
- (void)testThatItReturnsAVerticalConstrainedSizeFromTheFooterDelegateImplementation
|
||||
{
|
||||
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
|
||||
FooterReferenceSizeTestDelegate *delegate = [[FooterReferenceSizeTestDelegate alloc] init];
|
||||
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
layout.scrollDirection = UICollectionViewScrollDirectionVertical;
|
||||
|
||||
CGRect rect = CGRectMake(0, 0, 100.0, 100.0);
|
||||
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout];
|
||||
collectionView.asyncDataSource = dataSource;
|
||||
collectionView.asyncDelegate = delegate;
|
||||
|
||||
ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector);
|
||||
ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
|
||||
ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeMake(collectionView.bounds.size.width, 125.0));
|
||||
ASXCTAssertEqualSizeRanges(size, sizeCompare, @"should have a size constrained by the values returned in the delegate implementation");
|
||||
|
||||
collectionView.asyncDataSource = nil;
|
||||
collectionView.asyncDelegate = nil;
|
||||
}
|
||||
|
||||
// Size implementation
|
||||
|
||||
- (void)testThatItReturnsAVerticalConstrainedSizeFromTheHeaderProperty
|
||||
{
|
||||
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
|
||||
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
layout.scrollDirection = UICollectionViewScrollDirectionVertical;
|
||||
layout.headerReferenceSize = CGSizeMake(125.0, 125.0);
|
||||
|
||||
CGRect rect = CGRectMake(0, 0, 100.0, 100.0);
|
||||
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout];
|
||||
collectionView.asyncDataSource = dataSource;
|
||||
|
||||
ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector);
|
||||
ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
|
||||
ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeMake(collectionView.bounds.size.width, 125.0));
|
||||
ASXCTAssertEqualSizeRanges(size, sizeCompare, @"should have a size constrained by the size set on the layout");
|
||||
|
||||
collectionView.asyncDataSource = nil;
|
||||
collectionView.asyncDelegate = nil;
|
||||
}
|
||||
|
||||
- (void)testThatItReturnsAVerticalConstrainedSizeFromTheFooterProperty
|
||||
{
|
||||
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
|
||||
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
layout.scrollDirection = UICollectionViewScrollDirectionVertical;
|
||||
layout.footerReferenceSize = CGSizeMake(125.0, 125.0);
|
||||
|
||||
CGRect rect = CGRectMake(0, 0, 100.0, 100.0);
|
||||
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout];
|
||||
collectionView.asyncDataSource = dataSource;
|
||||
|
||||
ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector);
|
||||
ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
|
||||
ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeMake(collectionView.bounds.size.width, 125.0));
|
||||
ASXCTAssertEqualSizeRanges(size, sizeCompare, @"should have a size constrained by the size set on the layout");
|
||||
|
||||
collectionView.asyncDataSource = nil;
|
||||
collectionView.asyncDelegate = nil;
|
||||
}
|
||||
|
||||
// Horizontal
|
||||
|
||||
- (void)testThatItReturnsAHorizontalConstrainedSizeFromTheHeaderDelegateImplementation
|
||||
{
|
||||
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
|
||||
HeaderReferenceSizeTestDelegate *delegate = [[HeaderReferenceSizeTestDelegate alloc] init];
|
||||
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
|
||||
|
||||
CGRect rect = CGRectMake(0, 0, 100.0, 100.0);
|
||||
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout];
|
||||
collectionView.asyncDataSource = dataSource;
|
||||
collectionView.asyncDelegate = delegate;
|
||||
|
||||
ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector);
|
||||
ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
|
||||
ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeMake(125.0, collectionView.bounds.size.height));
|
||||
ASXCTAssertEqualSizeRanges(size, sizeCompare, @"should have a size constrained by the values returned in the delegate implementation");
|
||||
|
||||
collectionView.asyncDataSource = nil;
|
||||
collectionView.asyncDelegate = nil;
|
||||
}
|
||||
|
||||
- (void)testThatItReturnsAHorizontalConstrainedSizeFromTheFooterDelegateImplementation
|
||||
{
|
||||
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
|
||||
FooterReferenceSizeTestDelegate *delegate = [[FooterReferenceSizeTestDelegate alloc] init];
|
||||
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
|
||||
|
||||
CGRect rect = CGRectMake(0, 0, 100.0, 100.0);
|
||||
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout];
|
||||
collectionView.asyncDataSource = dataSource;
|
||||
collectionView.asyncDelegate = delegate;
|
||||
|
||||
ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector);
|
||||
ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
|
||||
ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeMake(125.0, collectionView.bounds.size.height));
|
||||
ASXCTAssertEqualSizeRanges(size, sizeCompare, @"should have a size constrained by the values returned in the delegate implementation");
|
||||
|
||||
collectionView.asyncDataSource = nil;
|
||||
collectionView.asyncDelegate = nil;
|
||||
}
|
||||
|
||||
// Size implementation
|
||||
|
||||
- (void)testThatItReturnsAHorizontalConstrainedSizeFromTheHeaderProperty
|
||||
{
|
||||
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
|
||||
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
|
||||
layout.headerReferenceSize = CGSizeMake(125.0, 125.0);
|
||||
|
||||
CGRect rect = CGRectMake(0, 0, 100.0, 100.0);
|
||||
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout];
|
||||
collectionView.asyncDataSource = dataSource;
|
||||
|
||||
ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector);
|
||||
ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
|
||||
ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeMake(125.0, collectionView.bounds.size.width));
|
||||
ASXCTAssertEqualSizeRanges(size, sizeCompare, @"should have a size constrained by the size set on the layout");
|
||||
|
||||
collectionView.asyncDataSource = nil;
|
||||
collectionView.asyncDelegate = nil;
|
||||
}
|
||||
|
||||
- (void)testThatItReturnsAHorizontalConstrainedSizeFromTheFooterProperty
|
||||
{
|
||||
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
|
||||
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
|
||||
layout.footerReferenceSize = CGSizeMake(125.0, 125.0);
|
||||
|
||||
CGRect rect = CGRectMake(0, 0, 100.0, 100.0);
|
||||
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout];
|
||||
collectionView.asyncDataSource = dataSource;
|
||||
|
||||
ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector);
|
||||
ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
|
||||
ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeMake(125.0, collectionView.bounds.size.height));
|
||||
ASXCTAssertEqualSizeRanges(size, sizeCompare, @"should have a size constrained by the size set on the layout");
|
||||
|
||||
collectionView.asyncDataSource = nil;
|
||||
collectionView.asyncDelegate = nil;
|
||||
}
|
||||
|
||||
- (void)testThatItReturnsZeroSizeWhenNoReferenceSizeIsImplemented
|
||||
{
|
||||
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
|
||||
HeaderReferenceSizeTestDelegate *delegate = [[HeaderReferenceSizeTestDelegate alloc] init];
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
|
||||
collectionView.asyncDataSource = dataSource;
|
||||
collectionView.asyncDelegate = delegate;
|
||||
ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector);
|
||||
ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
|
||||
ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeZero, CGSizeZero);
|
||||
XCTAssert(CGSizeEqualToSize(size.min, sizeCompare.min) && CGSizeEqualToSize(size.max, sizeCompare.max), @"should have a zero size");
|
||||
|
||||
collectionView.asyncDataSource = nil;
|
||||
collectionView.asyncDelegate = nil;
|
||||
}
|
||||
|
||||
#pragma mark - #collectionView:supplementaryNodesOfKind:inSection:
|
||||
|
||||
- (void)testThatItReturnsOneWhenAValidSizeIsImplementedOnTheDelegate
|
||||
{
|
||||
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
|
||||
HeaderReferenceSizeTestDelegate *delegate = [[HeaderReferenceSizeTestDelegate alloc] init];
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
|
||||
collectionView.asyncDataSource = dataSource;
|
||||
collectionView.asyncDelegate = delegate;
|
||||
ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector);
|
||||
NSUInteger count = [inspector collectionView:collectionView supplementaryNodesOfKind:UICollectionElementKindSectionHeader inSection:0];
|
||||
XCTAssert(count == 1, @"should have a header supplementary view");
|
||||
|
||||
collectionView.asyncDataSource = nil;
|
||||
collectionView.asyncDelegate = nil;
|
||||
}
|
||||
|
||||
- (void)testThatItReturnsOneWhenAValidSizeIsImplementedOnTheLayout
|
||||
{
|
||||
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
|
||||
HeaderReferenceSizeTestDelegate *delegate = [[HeaderReferenceSizeTestDelegate alloc] init];
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
layout.footerReferenceSize = CGSizeMake(125.0, 125.0);
|
||||
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
|
||||
collectionView.asyncDataSource = dataSource;
|
||||
collectionView.asyncDelegate = delegate;
|
||||
ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector);
|
||||
NSUInteger count = [inspector collectionView:collectionView supplementaryNodesOfKind:UICollectionElementKindSectionFooter inSection:0];
|
||||
XCTAssert(count == 1, @"should have a footer supplementary view");
|
||||
|
||||
collectionView.asyncDataSource = nil;
|
||||
collectionView.asyncDelegate = nil;
|
||||
}
|
||||
|
||||
- (void)testThatItReturnsNoneWhenNoReferenceSizeIsImplemented
|
||||
{
|
||||
InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init];
|
||||
HeaderReferenceSizeTestDelegate *delegate = [[HeaderReferenceSizeTestDelegate alloc] init];
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
|
||||
collectionView.asyncDataSource = dataSource;
|
||||
collectionView.asyncDelegate = delegate;
|
||||
ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector);
|
||||
NSUInteger count = [inspector collectionView:collectionView supplementaryNodesOfKind:UICollectionElementKindSectionFooter inSection:0];
|
||||
XCTAssert(count == 0, @"should not have a footer supplementary view");
|
||||
|
||||
collectionView.asyncDataSource = nil;
|
||||
collectionView.asyncDelegate = nil;
|
||||
}
|
||||
|
||||
- (void)testThatItThrowsIfNodeConstrainedSizeIsImplementedOnDataSourceButNotOnDelegateLayoutInspector
|
||||
{
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
ASCollectionNode *node = [[ASCollectionNode alloc] initWithCollectionViewLayout:layout];
|
||||
ASCollectionView *collectionView = node.view;
|
||||
|
||||
id dataSourceAndDelegate = [OCMockObject mockForProtocol:@protocol(InspectorTestDataSourceDelegateProtocol)];
|
||||
ASSizeRange constrainedSize = ASSizeRangeMake(CGSizeZero, CGSizeZero);
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0];
|
||||
NSValue *value = [NSValue value:&constrainedSize withObjCType:@encode(ASSizeRange)];
|
||||
[[[dataSourceAndDelegate stub] andReturnValue:value] collectionNode:node constrainedSizeForItemAtIndexPath:indexPath];
|
||||
node.dataSource = dataSourceAndDelegate;
|
||||
|
||||
id delegate = [InspectorTestDataSourceDelegateWithoutNodeConstrainedSize new];
|
||||
node.delegate = delegate;
|
||||
|
||||
ASCollectionViewLayoutInspector *inspector = [[ASCollectionViewLayoutInspector alloc] init];
|
||||
|
||||
collectionView.layoutInspector = inspector;
|
||||
XCTAssertThrows([inspector collectionView:collectionView constrainedSizeForNodeAtIndexPath:indexPath]);
|
||||
|
||||
node.delegate = dataSourceAndDelegate;
|
||||
XCTAssertNoThrow([inspector collectionView:collectionView constrainedSizeForNodeAtIndexPath:indexPath]);
|
||||
}
|
||||
|
||||
- (void)testThatItThrowsIfNodeConstrainedSizeIsImplementedOnDataSourceButNotOnDelegateFlowLayoutInspector
|
||||
{
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
|
||||
ASCollectionNode *node = [[ASCollectionNode alloc] initWithCollectionViewLayout:layout];
|
||||
ASCollectionView *collectionView = node.view;
|
||||
id dataSourceAndDelegate = [OCMockObject mockForProtocol:@protocol(InspectorTestDataSourceDelegateProtocol)];
|
||||
ASSizeRange constrainedSize = ASSizeRangeMake(CGSizeZero, CGSizeZero);
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0];
|
||||
NSValue *value = [NSValue value:&constrainedSize withObjCType:@encode(ASSizeRange)];
|
||||
|
||||
[[[dataSourceAndDelegate stub] andReturnValue:value] collectionNode:node constrainedSizeForItemAtIndexPath:indexPath];
|
||||
node.dataSource = dataSourceAndDelegate;
|
||||
id delegate = [InspectorTestDataSourceDelegateWithoutNodeConstrainedSize new];
|
||||
|
||||
node.delegate = delegate;
|
||||
ASCollectionViewFlowLayoutInspector *inspector = collectionView.layoutInspector;
|
||||
|
||||
XCTAssertThrows([inspector collectionView:collectionView constrainedSizeForNodeAtIndexPath:indexPath]);
|
||||
|
||||
node.delegate = dataSourceAndDelegate;
|
||||
XCTAssertNoThrow([inspector collectionView:collectionView constrainedSizeForNodeAtIndexPath:indexPath]);
|
||||
}
|
||||
|
||||
@end
|
File diff suppressed because it is too large
Load Diff
@ -1,217 +0,0 @@
|
||||
//
|
||||
// ASCollectionViewThrashTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
#import <AsyncDisplayKit/ASCollectionView.h>
|
||||
#import <stdatomic.h>
|
||||
|
||||
#import "ASThrashUtility.h"
|
||||
|
||||
@interface ASCollectionViewThrashTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASCollectionViewThrashTests
|
||||
{
|
||||
// The current update, which will be logged in case of a failure.
|
||||
ASThrashUpdate *_update;
|
||||
BOOL _failed;
|
||||
}
|
||||
|
||||
- (void)tearDown
|
||||
{
|
||||
if (_failed && _update != nil) {
|
||||
NSLog(@"Failed update %@: %@", _update, _update.logFriendlyBase64Representation);
|
||||
}
|
||||
_failed = NO;
|
||||
_update = nil;
|
||||
}
|
||||
|
||||
// NOTE: Despite the documentation, this is not always called if an exception is caught.
|
||||
- (void)recordFailureWithDescription:(NSString *)description inFile:(NSString *)filePath atLine:(NSUInteger)lineNumber expected:(BOOL)expected
|
||||
{
|
||||
_failed = YES;
|
||||
[super recordFailureWithDescription:description inFile:filePath atLine:lineNumber expected:expected];
|
||||
}
|
||||
|
||||
- (void)verifyDataSource:(ASThrashDataSource *)ds
|
||||
{
|
||||
CollectionView *collectionView = ds.collectionView;
|
||||
NSArray <ASThrashTestSection *> *data = [ds data];
|
||||
for (NSInteger i = 0; i < collectionView.numberOfSections; i++) {
|
||||
XCTAssertEqual([collectionView numberOfItemsInSection:i], data[i].items.count);
|
||||
|
||||
for (NSInteger j = 0; j < [collectionView numberOfItemsInSection:i]; j++) {
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:j inSection:i];
|
||||
ASThrashTestItem *item = data[i].items[j];
|
||||
ASThrashTestNode *node = (ASThrashTestNode *)[collectionView nodeForItemAtIndexPath:indexPath];
|
||||
XCTAssertEqualObjects(node.item, item, @"Wrong node at index path %@", indexPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark Test Methods
|
||||
|
||||
- (void)testInitialDataRead
|
||||
{
|
||||
ASThrashDataSource *ds = [[ASThrashDataSource alloc] initCollectionViewDataSourceWithData:[ASThrashTestSection sectionsWithCount:kInitialSectionCount]];
|
||||
[self verifyDataSource:ds];
|
||||
}
|
||||
|
||||
/// Replays the Base64 representation of an ASThrashUpdate from "ASThrashTestRecordedCase" file
|
||||
- (void)testRecordedThrashCase
|
||||
{
|
||||
NSURL *caseURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"ASThrashTestRecordedCase" withExtension:nil subdirectory:@"TestResources"];
|
||||
NSString *base64 = [NSString stringWithContentsOfURL:caseURL encoding:NSUTF8StringEncoding error:NULL];
|
||||
|
||||
_update = [ASThrashUpdate thrashUpdateWithBase64String:base64];
|
||||
if (_update == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
ASThrashDataSource *ds = [[ASThrashDataSource alloc] initCollectionViewDataSourceWithData:_update.oldData];
|
||||
[self applyUpdateUsingBatchUpdates:_update
|
||||
toDataSource:ds
|
||||
animated:NO
|
||||
useXCTestWait:YES];
|
||||
[self verifyDataSource:ds];
|
||||
}
|
||||
|
||||
- (void)testThrashingWildly
|
||||
{
|
||||
for (NSInteger i = 0; i < kThrashingIterationCount; i++) {
|
||||
[self setUp];
|
||||
@autoreleasepool {
|
||||
NSArray *sections = [ASThrashTestSection sectionsWithCount:kInitialSectionCount];
|
||||
_update = [[ASThrashUpdate alloc] initWithData:sections];
|
||||
ASThrashDataSource *ds = [[ASThrashDataSource alloc] initCollectionViewDataSourceWithData:sections];
|
||||
|
||||
[self applyUpdateUsingBatchUpdates:_update
|
||||
toDataSource:ds
|
||||
animated:NO
|
||||
useXCTestWait:NO];
|
||||
[self verifyDataSource:ds];
|
||||
[self expectationForPredicate:[ds predicateForDeallocatedHierarchy] evaluatedWithObject:(id)kCFNull handler:nil];
|
||||
}
|
||||
[self waitForExpectationsWithTimeout:3 handler:nil];
|
||||
|
||||
[self tearDown];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testThrashingWildlyOnSameCollectionView
|
||||
{
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"last test ran"];
|
||||
ASThrashDataSource *ds = [[ASThrashDataSource alloc] initCollectionViewDataSourceWithData:nil];
|
||||
for (NSInteger i = 0; i < 1000; i++) {
|
||||
[self setUp];
|
||||
@autoreleasepool {
|
||||
NSArray *sections = [ASThrashTestSection sectionsWithCount:kInitialSectionCount];
|
||||
_update = [[ASThrashUpdate alloc] initWithData:sections];
|
||||
[ds setData:sections];
|
||||
[ds.collectionView reloadData];
|
||||
|
||||
[self applyUpdateUsingBatchUpdates:_update
|
||||
toDataSource:ds
|
||||
animated:NO
|
||||
useXCTestWait:NO];
|
||||
[self verifyDataSource:ds];
|
||||
if (i == 999) {
|
||||
[expectation fulfill];
|
||||
}
|
||||
}
|
||||
|
||||
[self tearDown];
|
||||
}
|
||||
[self waitForExpectationsWithTimeout:3 handler:nil];
|
||||
}
|
||||
|
||||
- (void)testThrashingWildlyDispatchWildly
|
||||
{
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"last test ran"];
|
||||
for (NSInteger i = 0; i < kThrashingIterationCount; i++) {
|
||||
[self setUp];
|
||||
@autoreleasepool {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSArray *sections = [ASThrashTestSection sectionsWithCount:kInitialSectionCount];
|
||||
_update = [[ASThrashUpdate alloc] initWithData:sections];
|
||||
ASThrashDataSource *ds = [[ASThrashDataSource alloc] initCollectionViewDataSourceWithData:sections];
|
||||
|
||||
[self applyUpdateUsingBatchUpdates:_update
|
||||
toDataSource:ds
|
||||
animated:NO
|
||||
useXCTestWait:NO];
|
||||
[self verifyDataSource:ds];
|
||||
if (i == kThrashingIterationCount-1) {
|
||||
[expectation fulfill];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[self tearDown];
|
||||
}
|
||||
|
||||
[self waitForExpectationsWithTimeout:100 handler:nil];
|
||||
}
|
||||
|
||||
#pragma mark Helpers
|
||||
|
||||
- (void)applyUpdateUsingBatchUpdates:(ASThrashUpdate *)update
|
||||
toDataSource:(ASThrashDataSource *)dataSource animated:(BOOL)animated
|
||||
useXCTestWait:(BOOL)wait
|
||||
{
|
||||
CollectionView *collectionView = dataSource.collectionView;
|
||||
|
||||
XCTestExpectation *expectation;
|
||||
if (wait) {
|
||||
expectation = [self expectationWithDescription:@"Wait for collection view to update"];
|
||||
}
|
||||
|
||||
void (^updateBlock)() = ^ void (){
|
||||
dataSource.data = update.data;
|
||||
|
||||
[collectionView insertSections:update.insertedSectionIndexes];
|
||||
[collectionView deleteSections:update.deletedSectionIndexes];
|
||||
[collectionView reloadSections:update.replacedSectionIndexes];
|
||||
|
||||
[update.insertedItemIndexes enumerateObjectsUsingBlock:^(NSMutableIndexSet * _Nonnull indexes, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
NSArray *indexPaths = [indexes indexPathsInSection:idx];
|
||||
[collectionView insertItemsAtIndexPaths:indexPaths];
|
||||
}];
|
||||
|
||||
[update.deletedItemIndexes enumerateObjectsUsingBlock:^(NSMutableIndexSet * _Nonnull indexes, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
NSArray *indexPaths = [indexes indexPathsInSection:idx];
|
||||
[collectionView deleteItemsAtIndexPaths:indexPaths];
|
||||
}];
|
||||
|
||||
[update.replacedItemIndexes enumerateObjectsUsingBlock:^(NSMutableIndexSet * _Nonnull indexes, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
NSArray *indexPaths = [indexes indexPathsInSection:idx];
|
||||
[collectionView reloadItemsAtIndexPaths:indexPaths];
|
||||
}];
|
||||
};
|
||||
|
||||
@try {
|
||||
[collectionView performBatchAnimated:animated
|
||||
updates:updateBlock
|
||||
completion:^(BOOL finished) {
|
||||
[expectation fulfill];
|
||||
}];
|
||||
} @catch (NSException *exception) {
|
||||
_failed = YES;
|
||||
XCTFail("TEST FAILED");
|
||||
@throw exception;
|
||||
}
|
||||
|
||||
if (wait) {
|
||||
[self waitForExpectationsWithTimeout:1 handler:nil];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
@ -1,55 +0,0 @@
|
||||
//
|
||||
// ASCollectionsTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import <AsyncDisplayKit/ASCollections.h>
|
||||
|
||||
@interface ASCollectionsTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASCollectionsTests
|
||||
|
||||
- (void)testTransferArray {
|
||||
id objs[2];
|
||||
objs[0] = [NSObject new];
|
||||
id o0 = objs[0];
|
||||
objs[1] = [NSObject new];
|
||||
__weak id w0 = objs[0];
|
||||
__weak id w1 = objs[1];
|
||||
CFTypeRef cf0 = (__bridge CFTypeRef)objs[0];
|
||||
CFTypeRef cf1 = (__bridge CFTypeRef)objs[1];
|
||||
XCTAssertEqual(CFGetRetainCount(cf0), 2);
|
||||
XCTAssertEqual(CFGetRetainCount(cf1), 1);
|
||||
NSArray *arr = [NSArray arrayByTransferring:objs count:2];
|
||||
XCTAssertNil(objs[0]);
|
||||
XCTAssertNil(objs[1]);
|
||||
XCTAssertEqual(CFGetRetainCount(cf0), 2);
|
||||
XCTAssertEqual(CFGetRetainCount(cf1), 1);
|
||||
NSArray *immutableCopy = [arr copy];
|
||||
XCTAssertEqual(immutableCopy, arr);
|
||||
XCTAssertEqual(CFGetRetainCount(cf0), 2);
|
||||
XCTAssertEqual(CFGetRetainCount(cf1), 1);
|
||||
NSMutableArray *mc = [arr mutableCopy];
|
||||
XCTAssertEqual(CFGetRetainCount(cf0), 3);
|
||||
XCTAssertEqual(CFGetRetainCount(cf1), 2);
|
||||
arr = nil;
|
||||
immutableCopy = nil;
|
||||
XCTAssertEqual(CFGetRetainCount(cf0), 2);
|
||||
XCTAssertEqual(CFGetRetainCount(cf1), 1);
|
||||
[mc removeObjectAtIndex:0];
|
||||
XCTAssertEqual(CFGetRetainCount(cf0), 1);
|
||||
XCTAssertEqual(CFGetRetainCount(cf1), 1);
|
||||
[mc removeObjectAtIndex:0];
|
||||
XCTAssertEqual(CFGetRetainCount(cf0), 1);
|
||||
XCTAssertNil(w1);
|
||||
o0 = nil;
|
||||
XCTAssertNil(w0);
|
||||
}
|
||||
|
||||
@end
|
@ -1,139 +0,0 @@
|
||||
//
|
||||
// ASConfigurationTests.m
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASAvailability.h>
|
||||
#import <AsyncDisplayKit/ASConfiguration.h>
|
||||
#import <AsyncDisplayKit/ASConfigurationDelegate.h>
|
||||
#import <AsyncDisplayKit/ASConfigurationInternal.h>
|
||||
|
||||
#import "ASTestCase.h"
|
||||
|
||||
static ASExperimentalFeatures features[] = {
|
||||
ASExperimentalGraphicsContexts,
|
||||
#if AS_ENABLE_TEXTNODE
|
||||
ASExperimentalTextNode,
|
||||
#endif
|
||||
ASExperimentalInterfaceStateCoalescing,
|
||||
ASExperimentalUnfairLock,
|
||||
ASExperimentalLayerDefaults,
|
||||
ASExperimentalCollectionTeardown,
|
||||
ASExperimentalFramesetterCache,
|
||||
ASExperimentalSkipClearData,
|
||||
ASExperimentalDidEnterPreloadSkipASMLayout,
|
||||
ASExperimentalDisableAccessibilityCache,
|
||||
ASExperimentalDispatchApply,
|
||||
ASExperimentalImageDownloaderPriority,
|
||||
ASExperimentalTextDrawing
|
||||
};
|
||||
|
||||
@interface ASConfigurationTests : ASTestCase <ASConfigurationDelegate>
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASConfigurationTests {
|
||||
void (^onActivate)(ASConfigurationTests *self, ASExperimentalFeatures feature);
|
||||
}
|
||||
|
||||
+ (NSArray *)names {
|
||||
return @[
|
||||
@"exp_graphics_contexts",
|
||||
@"exp_text_node",
|
||||
@"exp_interface_state_coalesce",
|
||||
@"exp_unfair_lock",
|
||||
@"exp_infer_layer_defaults",
|
||||
@"exp_collection_teardown",
|
||||
@"exp_framesetter_cache",
|
||||
@"exp_skip_clear_data",
|
||||
@"exp_did_enter_preload_skip_asm_layout",
|
||||
@"exp_disable_a11y_cache",
|
||||
@"exp_dispatch_apply",
|
||||
@"exp_image_downloader_priority",
|
||||
@"exp_text_drawing"
|
||||
];
|
||||
}
|
||||
|
||||
- (ASExperimentalFeatures)allFeatures {
|
||||
ASExperimentalFeatures allFeatures = 0;
|
||||
for (int i = 0; i < sizeof(features)/sizeof(ASExperimentalFeatures); i++) {
|
||||
allFeatures |= features[i];
|
||||
}
|
||||
return allFeatures;
|
||||
}
|
||||
|
||||
#if AS_ENABLE_TEXTNODE
|
||||
|
||||
- (void)testExperimentalFeatureConfig
|
||||
{
|
||||
// Set the config
|
||||
ASConfiguration *config = [[ASConfiguration alloc] initWithDictionary:nil];
|
||||
config.experimentalFeatures = ASExperimentalGraphicsContexts;
|
||||
config.delegate = self;
|
||||
[ASConfigurationManager test_resetWithConfiguration:config];
|
||||
|
||||
// Set an expectation for a callback, and assert we only get one.
|
||||
XCTestExpectation *e = [self expectationWithDescription:@"Callbacks done."];
|
||||
e.expectedFulfillmentCount = 2;
|
||||
e.assertForOverFulfill = YES;
|
||||
onActivate = ^(ASConfigurationTests *self, ASExperimentalFeatures feature) {
|
||||
[e fulfill];
|
||||
};
|
||||
|
||||
// Now activate the graphics experiment and expect it works.
|
||||
XCTAssertTrue(ASActivateExperimentalFeature(ASExperimentalGraphicsContexts));
|
||||
// We should get a callback here
|
||||
// Now activate text node and expect it fails.
|
||||
XCTAssertFalse(ASActivateExperimentalFeature(ASExperimentalTextNode));
|
||||
// But we should get another callback.
|
||||
[self waitForExpectationsWithTimeout:3 handler:nil];
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
- (void)textureDidActivateExperimentalFeatures:(ASExperimentalFeatures)feature
|
||||
{
|
||||
if (onActivate) {
|
||||
onActivate(self, feature);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testMappingNamesToFlags
|
||||
{
|
||||
// Throw in a bad bit.
|
||||
ASExperimentalFeatures allFeatures = [self allFeatures];
|
||||
ASExperimentalFeatures featuresWithBadBit = allFeatures | (1 << 22);
|
||||
NSArray *expectedNames = [ASConfigurationTests names];
|
||||
XCTAssertEqualObjects(expectedNames, ASExperimentalFeaturesGetNames(featuresWithBadBit));
|
||||
}
|
||||
|
||||
- (void)testMappingFlagsFromNames
|
||||
{
|
||||
// Throw in a bad name.
|
||||
NSMutableArray *allNames = [[NSMutableArray alloc] initWithArray:[ASConfigurationTests names]];
|
||||
[allNames addObject:@"__invalid_name"];
|
||||
ASExperimentalFeatures expected = [self allFeatures];
|
||||
XCTAssertEqual(expected, ASExperimentalFeaturesFromArray(allNames));
|
||||
}
|
||||
|
||||
- (void)testFlagMatchName
|
||||
{
|
||||
NSArray *names = [ASConfigurationTests names];
|
||||
for (NSInteger i = 0; i < names.count; i++) {
|
||||
XCTAssertEqual(features[i], ASExperimentalFeaturesFromArray(@[names[i]]));
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testNameMatchFlag {
|
||||
NSArray *names = [ASConfigurationTests names];
|
||||
for (NSInteger i = 0; i < names.count; i++) {
|
||||
XCTAssertEqualObjects(@[names[i]], ASExperimentalFeaturesGetNames(features[i]));
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
@ -1,225 +0,0 @@
|
||||
//
|
||||
// ASControlNodeTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASControlNode.h>
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import <OCMock/OCMock.h>
|
||||
|
||||
#define ACTION @selector(action)
|
||||
#define ACTION_SENDER @selector(action:)
|
||||
#define ACTION_SENDER_EVENT @selector(action:event:)
|
||||
#define EVENT ASControlNodeEventTouchUpInside
|
||||
|
||||
@interface ReceiverController : UIViewController
|
||||
@property (nonatomic) NSInteger hits;
|
||||
@end
|
||||
@implementation ReceiverController
|
||||
@end
|
||||
|
||||
@interface ASActionController : ReceiverController
|
||||
@end
|
||||
@implementation ASActionController
|
||||
- (void)action { self.hits++; }
|
||||
- (void)firstAction { }
|
||||
- (void)secondAction { }
|
||||
- (void)thirdAction { }
|
||||
@end
|
||||
|
||||
@interface ASActionSenderController : ReceiverController
|
||||
@end
|
||||
@implementation ASActionSenderController
|
||||
- (void)action:(id)sender { self.hits++; }
|
||||
@end
|
||||
|
||||
@interface ASActionSenderEventController : ReceiverController
|
||||
@end
|
||||
@implementation ASActionSenderEventController
|
||||
- (void)action:(id)sender event:(UIEvent *)event { self.hits++; }
|
||||
@end
|
||||
|
||||
@interface ASGestureController : ReceiverController
|
||||
@end
|
||||
@implementation ASGestureController
|
||||
- (void)onGesture:(UIGestureRecognizer *)recognizer { self.hits++; }
|
||||
- (void)action:(id)sender { self.hits++; }
|
||||
@end
|
||||
|
||||
@interface ASControlNodeTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASControlNodeTests
|
||||
|
||||
- (void)testActionWithoutParameters {
|
||||
ASActionController *controller = [[ASActionController alloc] init];
|
||||
ASControlNode *node = [[ASControlNode alloc] init];
|
||||
[node addTarget:controller action:ACTION forControlEvents:EVENT];
|
||||
[controller.view addSubview:node.view];
|
||||
[node sendActionsForControlEvents:EVENT withEvent:nil];
|
||||
XCTAssert(controller.hits == 1, @"Controller did not receive the action event");
|
||||
}
|
||||
|
||||
- (void)testActionAndSender {
|
||||
ASActionSenderController *controller = [[ASActionSenderController alloc] init];
|
||||
ASControlNode *node = [[ASControlNode alloc] init];
|
||||
[node addTarget:controller action:ACTION_SENDER forControlEvents:EVENT];
|
||||
[controller.view addSubview:node.view];
|
||||
[node sendActionsForControlEvents:EVENT withEvent:nil];
|
||||
XCTAssert(controller.hits == 1, @"Controller did not receive the action event");
|
||||
}
|
||||
|
||||
- (void)testActionAndSenderAndEvent {
|
||||
ASActionSenderEventController *controller = [[ASActionSenderEventController alloc] init];
|
||||
ASControlNode *node = [[ASControlNode alloc] init];
|
||||
[node addTarget:controller action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[controller.view addSubview:node.view];
|
||||
[node sendActionsForControlEvents:EVENT withEvent:nil];
|
||||
XCTAssert(controller.hits == 1, @"Controller did not receive the action event");
|
||||
}
|
||||
|
||||
- (void)testActionWithoutTarget {
|
||||
ASActionController *controller = [[ASActionController alloc] init];
|
||||
ASControlNode *node = [[ASControlNode alloc] init];
|
||||
[node addTarget:nil action:ACTION forControlEvents:EVENT];
|
||||
[controller.view addSubview:node.view];
|
||||
[node sendActionsForControlEvents:EVENT withEvent:nil];
|
||||
XCTAssert(controller.hits == 1, @"Controller did not receive the action event");
|
||||
}
|
||||
|
||||
- (void)testActionAndSenderWithoutTarget {
|
||||
ASActionSenderController *controller = [[ASActionSenderController alloc] init];
|
||||
ASControlNode *node = [[ASControlNode alloc] init];
|
||||
[node addTarget:nil action:ACTION_SENDER forControlEvents:EVENT];
|
||||
[controller.view addSubview:node.view];
|
||||
[node sendActionsForControlEvents:EVENT withEvent:nil];
|
||||
XCTAssert(controller.hits == 1, @"Controller did not receive the action event");
|
||||
}
|
||||
|
||||
- (void)testActionAndSenderAndEventWithoutTarget {
|
||||
ASActionSenderEventController *controller = [[ASActionSenderEventController alloc] init];
|
||||
ASControlNode *node = [[ASControlNode alloc] init];
|
||||
[node addTarget:nil action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[controller.view addSubview:node.view];
|
||||
[node sendActionsForControlEvents:EVENT withEvent:nil];
|
||||
XCTAssert(controller.hits == 1, @"Controller did not receive the action event");
|
||||
}
|
||||
|
||||
- (void)testRemoveWithoutTargetRemovesTargetlessAction {
|
||||
ASActionSenderEventController *controller = [[ASActionSenderEventController alloc] init];
|
||||
ASControlNode *node = [[ASControlNode alloc] init];
|
||||
[node addTarget:nil action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[node removeTarget:nil action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[controller.view addSubview:node.view];
|
||||
[node sendActionsForControlEvents:EVENT withEvent:nil];
|
||||
XCTAssertEqual(controller.hits, 0, @"Controller did not receive exactly zero action events");
|
||||
}
|
||||
|
||||
- (void)testRemoveWithTarget {
|
||||
ASActionSenderEventController *controller = [[ASActionSenderEventController alloc] init];
|
||||
ASControlNode *node = [[ASControlNode alloc] init];
|
||||
[node addTarget:controller action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[node removeTarget:controller action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[controller.view addSubview:node.view];
|
||||
[node sendActionsForControlEvents:EVENT withEvent:nil];
|
||||
XCTAssertEqual(controller.hits, 0, @"Controller did not receive exactly zero action events");
|
||||
}
|
||||
|
||||
- (void)testRemoveWithTargetRemovesAction {
|
||||
ASActionSenderEventController *controller = [[ASActionSenderEventController alloc] init];
|
||||
ASControlNode *node = [[ASControlNode alloc] init];
|
||||
[node addTarget:controller action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[node removeTarget:controller action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[controller.view addSubview:node.view];
|
||||
[node sendActionsForControlEvents:EVENT withEvent:nil];
|
||||
XCTAssertEqual(controller.hits, 0, @"Controller did not receive exactly zero action events");
|
||||
}
|
||||
|
||||
- (void)testRemoveWithoutTargetRemovesTargetedAction {
|
||||
ASActionSenderEventController *controller = [[ASActionSenderEventController alloc] init];
|
||||
ASControlNode *node = [[ASControlNode alloc] init];
|
||||
[node addTarget:controller action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[node removeTarget:nil action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[controller.view addSubview:node.view];
|
||||
[node sendActionsForControlEvents:EVENT withEvent:nil];
|
||||
XCTAssertEqual(controller.hits, 0, @"Controller did not receive exactly zero action events");
|
||||
}
|
||||
|
||||
- (void)testDuplicateEntriesWithoutTarget {
|
||||
ASActionSenderEventController *controller = [[ASActionSenderEventController alloc] init];
|
||||
ASControlNode *node = [[ASControlNode alloc] init];
|
||||
[node addTarget:nil action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[node addTarget:nil action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[controller.view addSubview:node.view];
|
||||
[node sendActionsForControlEvents:EVENT withEvent:nil];
|
||||
XCTAssertEqual(controller.hits, 1, @"Controller did not receive exactly one action event");
|
||||
}
|
||||
|
||||
- (void)testDuplicateEntriesWithTarget {
|
||||
ASActionSenderEventController *controller = [[ASActionSenderEventController alloc] init];
|
||||
ASControlNode *node = [[ASControlNode alloc] init];
|
||||
[node addTarget:controller action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[node addTarget:controller action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[controller.view addSubview:node.view];
|
||||
[node sendActionsForControlEvents:EVENT withEvent:nil];
|
||||
XCTAssertEqual(controller.hits, 1, @"Controller did not receive exactly one action event");
|
||||
}
|
||||
|
||||
- (void)testDuplicateEntriesWithAndWithoutTarget {
|
||||
ASActionSenderEventController *controller = [[ASActionSenderEventController alloc] init];
|
||||
ASControlNode *node = [[ASControlNode alloc] init];
|
||||
[node addTarget:controller action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[node addTarget:nil action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[controller.view addSubview:node.view];
|
||||
[node sendActionsForControlEvents:EVENT withEvent:nil];
|
||||
XCTAssertEqual(controller.hits, 2, @"Controller did not receive exactly two action events");
|
||||
}
|
||||
|
||||
- (void)testDeeperHierarchyWithoutTarget {
|
||||
ASActionController *controller = [[ASActionController alloc] init];
|
||||
UIView *view = [[UIView alloc] init];
|
||||
ASControlNode *node = [[ASControlNode alloc] init];
|
||||
[node addTarget:nil action:ACTION forControlEvents:EVENT];
|
||||
[view addSubview:node.view];
|
||||
[controller.view addSubview:view];
|
||||
[node sendActionsForControlEvents:EVENT withEvent:nil];
|
||||
XCTAssert(controller.hits == 1, @"Controller did not receive the action event");
|
||||
}
|
||||
|
||||
- (void)testTouchesWorkWithGestures {
|
||||
ASGestureController *controller = [[ASGestureController alloc] init];
|
||||
ASControlNode *node = [[ASControlNode alloc] init];
|
||||
[node addTarget:controller action:@selector(action:) forControlEvents:ASControlNodeEventTouchUpInside];
|
||||
[node.view addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:controller action:@selector(onGesture:)]];
|
||||
[controller.view addSubnode:node];
|
||||
|
||||
[node sendActionsForControlEvents:EVENT withEvent:nil];
|
||||
XCTAssert(controller.hits == 1, @"Controller did not receive the tap event");
|
||||
}
|
||||
|
||||
- (void)testActionsAreCalledInTheSameOrderAsTheyWereAdded {
|
||||
ASActionController *controller = [[ASActionController alloc] init];
|
||||
ASControlNode *node = [[ASControlNode alloc] init];
|
||||
[node addTarget:controller action:@selector(firstAction) forControlEvents:ASControlNodeEventTouchUpInside];
|
||||
[node addTarget:controller action:@selector(secondAction) forControlEvents:ASControlNodeEventTouchUpInside];
|
||||
[node addTarget:controller action:@selector(thirdAction) forControlEvents:ASControlNodeEventTouchUpInside];
|
||||
[controller.view addSubnode:node];
|
||||
|
||||
id controllerMock = [OCMockObject partialMockForObject:controller];
|
||||
[controllerMock setExpectationOrderMatters:YES];
|
||||
[[controllerMock expect] firstAction];
|
||||
[[controllerMock expect] secondAction];
|
||||
[[controllerMock expect] thirdAction];
|
||||
|
||||
[node sendActionsForControlEvents:ASControlNodeEventTouchUpInside withEvent:nil];
|
||||
|
||||
[controllerMock verify];
|
||||
}
|
||||
|
||||
@end
|
@ -1,215 +0,0 @@
|
||||
//
|
||||
// ASCornerLayoutSpecSnapshotTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASLayoutSpecSnapshotTestsHelper.h"
|
||||
#import <AsyncDisplayKit/ASCornerLayoutSpec.h>
|
||||
#import <AsyncDisplayKit/ASBackgroundLayoutSpec.h>
|
||||
|
||||
typedef NS_ENUM(NSInteger, ASCornerLayoutSpecSnapshotTestsOffsetOption) {
|
||||
ASCornerLayoutSpecSnapshotTestsOffsetOptionCenter,
|
||||
ASCornerLayoutSpecSnapshotTestsOffsetOptionInner,
|
||||
ASCornerLayoutSpecSnapshotTestsOffsetOptionOuter,
|
||||
};
|
||||
|
||||
|
||||
@interface ASCornerLayoutSpecSnapshotTests : ASLayoutSpecSnapshotTestCase
|
||||
|
||||
@property (nonatomic, copy) UIColor *boxColor;
|
||||
@property (nonatomic, copy) UIColor *baseColor;
|
||||
@property (nonatomic, copy) UIColor *cornerColor;
|
||||
@property (nonatomic, copy) UIColor *contextColor;
|
||||
|
||||
@property (nonatomic) CGSize baseSize;
|
||||
@property (nonatomic) CGSize cornerSize;
|
||||
@property (nonatomic) CGSize contextSize;
|
||||
|
||||
@property (nonatomic) ASSizeRange contextSizeRange;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ASCornerLayoutSpecSnapshotTests
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
[super setUp];
|
||||
|
||||
self.recordMode = NO;
|
||||
|
||||
_boxColor = [UIColor greenColor];
|
||||
_baseColor = [UIColor blueColor];
|
||||
_cornerColor = [UIColor orangeColor];
|
||||
_contextColor = [UIColor lightGrayColor];
|
||||
|
||||
_baseSize = CGSizeMake(60, 60);
|
||||
_cornerSize = CGSizeMake(20, 20);
|
||||
_contextSize = CGSizeMake(120, 120);
|
||||
|
||||
_contextSizeRange = ASSizeRangeMake(CGSizeZero, _contextSize);
|
||||
}
|
||||
|
||||
- (void)testCornerSpecForAllLocations
|
||||
{
|
||||
ASCornerLayoutSpecSnapshotTestsOffsetOption center = ASCornerLayoutSpecSnapshotTestsOffsetOptionCenter;
|
||||
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationTopLeft offsetOption:center wrapsCorner:NO];
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationTopLeft offsetOption:center wrapsCorner:YES];
|
||||
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationTopRight offsetOption:center wrapsCorner:NO];
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationTopRight offsetOption:center wrapsCorner:YES];
|
||||
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationBottomLeft offsetOption:center wrapsCorner:NO];
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationBottomLeft offsetOption:center wrapsCorner:YES];
|
||||
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationBottomRight offsetOption:center wrapsCorner:NO];
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationBottomRight offsetOption:center wrapsCorner:YES];
|
||||
}
|
||||
|
||||
- (void)testCornerSpecForAllLocationsWithInnerOffset
|
||||
{
|
||||
ASCornerLayoutSpecSnapshotTestsOffsetOption inner = ASCornerLayoutSpecSnapshotTestsOffsetOptionInner;
|
||||
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationTopLeft offsetOption:inner wrapsCorner:NO];
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationTopLeft offsetOption:inner wrapsCorner:YES];
|
||||
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationTopRight offsetOption:inner wrapsCorner:NO];
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationTopRight offsetOption:inner wrapsCorner:YES];
|
||||
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationBottomLeft offsetOption:inner wrapsCorner:NO];
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationBottomLeft offsetOption:inner wrapsCorner:YES];
|
||||
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationBottomRight offsetOption:inner wrapsCorner:NO];
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationBottomRight offsetOption:inner wrapsCorner:YES];
|
||||
}
|
||||
|
||||
- (void)testCornerSpecForAllLocationsWithOuterOffset
|
||||
{
|
||||
ASCornerLayoutSpecSnapshotTestsOffsetOption outer = ASCornerLayoutSpecSnapshotTestsOffsetOptionOuter;
|
||||
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationTopLeft offsetOption:outer wrapsCorner:NO];
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationTopLeft offsetOption:outer wrapsCorner:YES];
|
||||
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationTopRight offsetOption:outer wrapsCorner:NO];
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationTopRight offsetOption:outer wrapsCorner:YES];
|
||||
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationBottomLeft offsetOption:outer wrapsCorner:NO];
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationBottomLeft offsetOption:outer wrapsCorner:YES];
|
||||
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationBottomRight offsetOption:outer wrapsCorner:NO];
|
||||
[self testCornerSpecWithLocation:ASCornerLayoutLocationBottomRight offsetOption:outer wrapsCorner:YES];
|
||||
}
|
||||
|
||||
- (void)testCornerSpecWithLocation:(ASCornerLayoutLocation)location
|
||||
offsetOption:(ASCornerLayoutSpecSnapshotTestsOffsetOption)offsetOption
|
||||
wrapsCorner:(BOOL)wrapsCorner
|
||||
{
|
||||
ASDisplayNode *baseNode = ASDisplayNodeWithBackgroundColor(_baseColor, _baseSize);
|
||||
ASDisplayNode *cornerNode = ASDisplayNodeWithBackgroundColor(_cornerColor, _cornerSize);
|
||||
ASDisplayNode *debugBoxNode = ASDisplayNodeWithBackgroundColor(_boxColor);
|
||||
|
||||
baseNode.style.layoutPosition = CGPointMake((_contextSize.width - _baseSize.width) / 2,
|
||||
(_contextSize.height - _baseSize.height) / 2);
|
||||
|
||||
ASCornerLayoutSpec *cornerSpec = [ASCornerLayoutSpec cornerLayoutSpecWithChild:baseNode
|
||||
corner:cornerNode
|
||||
location:location];
|
||||
|
||||
CGPoint delta = (CGPoint){ _cornerSize.width / 2, _cornerSize.height / 2 };
|
||||
cornerSpec.offset = [self offsetForOption:offsetOption location:location delta:delta];
|
||||
cornerSpec.wrapsCorner = wrapsCorner;
|
||||
|
||||
ASBackgroundLayoutSpec *backgroundSpec = [ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:cornerSpec
|
||||
background:debugBoxNode];
|
||||
|
||||
[self testLayoutSpec:backgroundSpec
|
||||
sizeRange:_contextSizeRange
|
||||
subnodes:@[debugBoxNode, baseNode, cornerNode]
|
||||
identifier:[self suffixWithLocation:location option:offsetOption wrapsCorner:wrapsCorner]];
|
||||
}
|
||||
|
||||
- (CGPoint)offsetForOption:(ASCornerLayoutSpecSnapshotTestsOffsetOption)option
|
||||
location:(ASCornerLayoutLocation)location
|
||||
delta:(CGPoint)delta
|
||||
{
|
||||
CGFloat x = delta.x;
|
||||
CGFloat y = delta.y;
|
||||
|
||||
switch (option) {
|
||||
|
||||
case ASCornerLayoutSpecSnapshotTestsOffsetOptionCenter:
|
||||
return CGPointZero;
|
||||
|
||||
case ASCornerLayoutSpecSnapshotTestsOffsetOptionInner:
|
||||
|
||||
switch (location) {
|
||||
case ASCornerLayoutLocationTopLeft: return (CGPoint){ x, y };
|
||||
case ASCornerLayoutLocationTopRight: return (CGPoint){ -x, y };
|
||||
case ASCornerLayoutLocationBottomLeft: return (CGPoint){ x, -y };
|
||||
case ASCornerLayoutLocationBottomRight: return (CGPoint){ -x, -y };
|
||||
}
|
||||
|
||||
case ASCornerLayoutSpecSnapshotTestsOffsetOptionOuter:
|
||||
|
||||
switch (location) {
|
||||
case ASCornerLayoutLocationTopLeft: return (CGPoint){ -x, -y };
|
||||
case ASCornerLayoutLocationTopRight: return (CGPoint){ x, -y };
|
||||
case ASCornerLayoutLocationBottomLeft: return (CGPoint){ -x, y };
|
||||
case ASCornerLayoutLocationBottomRight: return (CGPoint){ x, y };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
- (NSString *)suffixWithLocation:(ASCornerLayoutLocation)location
|
||||
option:(ASCornerLayoutSpecSnapshotTestsOffsetOption)option
|
||||
wrapsCorner:(BOOL)wrapsCorner
|
||||
{
|
||||
NSMutableString *desc = [NSMutableString string];
|
||||
|
||||
switch (location) {
|
||||
case ASCornerLayoutLocationTopLeft:
|
||||
[desc appendString:@"topLeft"];
|
||||
break;
|
||||
case ASCornerLayoutLocationTopRight:
|
||||
[desc appendString:@"topRight"];
|
||||
break;
|
||||
case ASCornerLayoutLocationBottomLeft:
|
||||
[desc appendString:@"bottomLeft"];
|
||||
break;
|
||||
case ASCornerLayoutLocationBottomRight:
|
||||
[desc appendString:@"bottomRight"];
|
||||
break;
|
||||
}
|
||||
|
||||
[desc appendString:@"_"];
|
||||
|
||||
switch (option) {
|
||||
case ASCornerLayoutSpecSnapshotTestsOffsetOptionCenter:
|
||||
[desc appendString:@"center"];
|
||||
break;
|
||||
case ASCornerLayoutSpecSnapshotTestsOffsetOptionInner:
|
||||
[desc appendString:@"inner"];
|
||||
break;
|
||||
case ASCornerLayoutSpecSnapshotTestsOffsetOptionOuter:
|
||||
[desc appendString:@"outer"];
|
||||
break;
|
||||
}
|
||||
|
||||
[desc appendString:@"_"];
|
||||
|
||||
if (wrapsCorner) {
|
||||
[desc appendString:@"fullSize"];
|
||||
} else {
|
||||
[desc appendString:@"childSize"];
|
||||
}
|
||||
|
||||
return desc.copy;
|
||||
}
|
||||
|
||||
@end
|
@ -1,106 +0,0 @@
|
||||
//
|
||||
// ASDimensionTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import <AsyncDisplayKit/ASDimension.h>
|
||||
|
||||
#import "ASXCTExtensions.h"
|
||||
|
||||
|
||||
@interface ASDimensionTests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation ASDimensionTests
|
||||
|
||||
- (void)testCreatingDimensionUnitAutos
|
||||
{
|
||||
XCTAssertNoThrow(ASDimensionMake(ASDimensionUnitAuto, 0));
|
||||
XCTAssertThrows(ASDimensionMake(ASDimensionUnitAuto, 100));
|
||||
ASXCTAssertEqualDimensions(ASDimensionAuto, ASDimensionMake(@""));
|
||||
ASXCTAssertEqualDimensions(ASDimensionAuto, ASDimensionMake(@"auto"));
|
||||
}
|
||||
|
||||
- (void)testCreatingDimensionUnitFraction
|
||||
{
|
||||
XCTAssertNoThrow(ASDimensionMake(ASDimensionUnitFraction, 0.5));
|
||||
ASXCTAssertEqualDimensions(ASDimensionMake(ASDimensionUnitFraction, 0.5), ASDimensionMake(@"50%"));
|
||||
}
|
||||
|
||||
- (void)testCreatingDimensionUnitPoints
|
||||
{
|
||||
XCTAssertNoThrow(ASDimensionMake(ASDimensionUnitPoints, 100));
|
||||
ASXCTAssertEqualDimensions(ASDimensionMake(ASDimensionUnitPoints, 100), ASDimensionMake(@"100pt"));
|
||||
}
|
||||
|
||||
- (void)testIntersectingOverlappingSizeRangesReturnsTheirIntersection
|
||||
{
|
||||
// range: |---------|
|
||||
// other: |----------|
|
||||
// result: |----|
|
||||
|
||||
ASSizeRange range = {{0,0}, {10,10}};
|
||||
ASSizeRange other = {{7,7}, {15,15}};
|
||||
ASSizeRange result = ASSizeRangeIntersect(range, other);
|
||||
ASSizeRange expected = {{7,7}, {10,10}};
|
||||
XCTAssertTrue(ASSizeRangeEqualToSizeRange(result, expected), @"Expected %@ but got %@", NSStringFromASSizeRange(expected), NSStringFromASSizeRange(result));
|
||||
}
|
||||
|
||||
- (void)testIntersectingSizeRangeWithRangeThatContainsItReturnsSameRange
|
||||
{
|
||||
// range: |-----|
|
||||
// other: |---------|
|
||||
// result: |-----|
|
||||
|
||||
ASSizeRange range = {{2,2}, {8,8}};
|
||||
ASSizeRange other = {{0,0}, {10,10}};
|
||||
ASSizeRange result = ASSizeRangeIntersect(range, other);
|
||||
ASSizeRange expected = {{2,2}, {8,8}};
|
||||
XCTAssertTrue(ASSizeRangeEqualToSizeRange(result, expected), @"Expected %@ but got %@", NSStringFromASSizeRange(expected), NSStringFromASSizeRange(result));
|
||||
}
|
||||
|
||||
- (void)testIntersectingSizeRangeWithRangeContainedWithinItReturnsContainedRange
|
||||
{
|
||||
// range: |---------|
|
||||
// other: |-----|
|
||||
// result: |-----|
|
||||
|
||||
ASSizeRange range = {{0,0}, {10,10}};
|
||||
ASSizeRange other = {{2,2}, {8,8}};
|
||||
ASSizeRange result = ASSizeRangeIntersect(range, other);
|
||||
ASSizeRange expected = {{2,2}, {8,8}};
|
||||
XCTAssertTrue(ASSizeRangeEqualToSizeRange(result, expected), @"Expected %@ but got %@", NSStringFromASSizeRange(expected), NSStringFromASSizeRange(result));
|
||||
}
|
||||
|
||||
- (void)testIntersectingSizeRangeWithNonOverlappingRangeToRightReturnsSinglePointNearestOtherRange
|
||||
{
|
||||
// range: |-----|
|
||||
// other: |---|
|
||||
// result: *
|
||||
|
||||
ASSizeRange range = {{0,0}, {5,5}};
|
||||
ASSizeRange other = {{10,10}, {15,15}};
|
||||
ASSizeRange result = ASSizeRangeIntersect(range, other);
|
||||
ASSizeRange expected = {{5,5}, {5,5}};
|
||||
XCTAssertTrue(ASSizeRangeEqualToSizeRange(result, expected), @"Expected %@ but got %@", NSStringFromASSizeRange(expected), NSStringFromASSizeRange(result));
|
||||
}
|
||||
|
||||
- (void)testIntersectingSizeRangeWithNonOverlappingRangeToLeftReturnsSinglePointNearestOtherRange
|
||||
{
|
||||
// range: |---|
|
||||
// other: |-----|
|
||||
// result: *
|
||||
|
||||
ASSizeRange range = {{10,10}, {15,15}};
|
||||
ASSizeRange other = {{0,0}, {5,5}};
|
||||
ASSizeRange result = ASSizeRangeIntersect(range, other);
|
||||
ASSizeRange expected = {{10,10}, {10,10}};
|
||||
XCTAssertTrue(ASSizeRangeEqualToSizeRange(result, expected), @"Expected %@ but got %@", NSStringFromASSizeRange(expected), NSStringFromASSizeRange(result));
|
||||
}
|
||||
|
||||
@end
|
@ -1,64 +0,0 @@
|
||||
//
|
||||
// ASDispatchTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import <AsyncDisplayKit/ASDispatch.h>
|
||||
|
||||
@interface ASDispatchTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASDispatchTests
|
||||
|
||||
- (void)testDispatchApply
|
||||
{
|
||||
dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
|
||||
NSInteger expectedThreadCount = [NSProcessInfo processInfo].activeProcessorCount * 2;
|
||||
NSLock *lock = [NSLock new];
|
||||
NSMutableSet *threads = [NSMutableSet set];
|
||||
NSMutableIndexSet *indices = [NSMutableIndexSet indexSet];
|
||||
|
||||
size_t const iterations = 1E5;
|
||||
ASDispatchApply(iterations, q, 0, ^(size_t i) {
|
||||
[lock lock];
|
||||
[threads addObject:[NSThread currentThread]];
|
||||
XCTAssertFalse([indices containsIndex:i]);
|
||||
[indices addIndex:i];
|
||||
[lock unlock];
|
||||
});
|
||||
XCTAssertLessThanOrEqual(threads.count, expectedThreadCount);
|
||||
XCTAssertEqualObjects(indices, [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, iterations)]);
|
||||
}
|
||||
|
||||
- (void)testDispatchAsync
|
||||
{
|
||||
dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
|
||||
NSInteger expectedThreadCount = [NSProcessInfo processInfo].activeProcessorCount * 2;
|
||||
NSLock *lock = [NSLock new];
|
||||
NSMutableSet *threads = [NSMutableSet set];
|
||||
NSMutableIndexSet *indices = [NSMutableIndexSet indexSet];
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"Executed all blocks"];
|
||||
|
||||
size_t const iterations = 1E5;
|
||||
ASDispatchAsync(iterations, q, 0, ^(size_t i) {
|
||||
[lock lock];
|
||||
[threads addObject:[NSThread currentThread]];
|
||||
XCTAssertFalse([indices containsIndex:i]);
|
||||
[indices addIndex:i];
|
||||
if (indices.count == iterations) {
|
||||
[expectation fulfill];
|
||||
}
|
||||
[lock unlock];
|
||||
});
|
||||
[self waitForExpectationsWithTimeout:10 handler:nil];
|
||||
XCTAssertLessThanOrEqual(threads.count, expectedThreadCount);
|
||||
XCTAssertEqualObjects(indices, [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, iterations)]);
|
||||
}
|
||||
|
||||
@end
|
@ -1,598 +0,0 @@
|
||||
//
|
||||
// ASDisplayLayerTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <objc/runtime.h>
|
||||
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
|
||||
#import "ASDisplayNodeTestsHelper.h"
|
||||
|
||||
static UIImage *bogusImage() {
|
||||
static UIImage *bogusImage = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
|
||||
UIGraphicsBeginImageContext(CGSizeMake(10, 10));
|
||||
|
||||
bogusImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
});
|
||||
|
||||
return bogusImage;
|
||||
}
|
||||
|
||||
@interface _ASDisplayLayerTestContainerLayer : CALayer
|
||||
@property (nonatomic, readonly) NSUInteger didCompleteTransactionCount;
|
||||
@end
|
||||
|
||||
@implementation _ASDisplayLayerTestContainerLayer
|
||||
|
||||
- (void)asyncdisplaykit_asyncTransactionContainerDidCompleteTransaction:(_ASAsyncTransaction *)transaction
|
||||
{
|
||||
_didCompleteTransactionCount++;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface ASDisplayNode (HackForTests)
|
||||
- (id)initWithViewClass:(Class)viewClass;
|
||||
- (id)initWithLayerClass:(Class)layerClass;
|
||||
@end
|
||||
|
||||
|
||||
@interface _ASDisplayLayerTestLayer : _ASDisplayLayer
|
||||
{
|
||||
BOOL _isInCancelAsyncDisplay;
|
||||
BOOL _isInDisplay;
|
||||
}
|
||||
@property (nonatomic, readonly) NSUInteger displayCount;
|
||||
@property (nonatomic, readonly) NSUInteger drawInContextCount;
|
||||
@property (nonatomic, readonly) NSUInteger setContentsAsyncCount;
|
||||
@property (nonatomic, readonly) NSUInteger setContentsSyncCount;
|
||||
@property (nonatomic, copy, readonly) NSString *setContentsCounts;
|
||||
- (BOOL)checkSetContentsCountsWithSyncCount:(NSUInteger)syncCount asyncCount:(NSUInteger)asyncCount;
|
||||
@end
|
||||
|
||||
@implementation _ASDisplayLayerTestLayer
|
||||
|
||||
- (NSString *)setContentsCounts
|
||||
{
|
||||
return [NSString stringWithFormat:@"syncCount:%tu, asyncCount:%tu", _setContentsSyncCount, _setContentsAsyncCount];
|
||||
}
|
||||
|
||||
- (BOOL)checkSetContentsCountsWithSyncCount:(NSUInteger)syncCount asyncCount:(NSUInteger)asyncCount
|
||||
{
|
||||
return ((syncCount == _setContentsSyncCount) &&
|
||||
(asyncCount == _setContentsAsyncCount));
|
||||
}
|
||||
|
||||
- (void)setContents:(id)contents
|
||||
{
|
||||
[super setContents:contents];
|
||||
|
||||
if (self.displaysAsynchronously) {
|
||||
if (_isInDisplay) {
|
||||
[NSException raise:NSInvalidArgumentException format:@"There is no placeholder logic in _ASDisplayLayer, unknown caller for setContents:"];
|
||||
} else if (!_isInCancelAsyncDisplay) {
|
||||
_setContentsAsyncCount++;
|
||||
}
|
||||
} else {
|
||||
_setContentsSyncCount++;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)display
|
||||
{
|
||||
_isInDisplay = YES;
|
||||
[super display];
|
||||
_isInDisplay = NO;
|
||||
_displayCount++;
|
||||
}
|
||||
|
||||
- (void)cancelAsyncDisplay
|
||||
{
|
||||
_isInCancelAsyncDisplay = YES;
|
||||
[super cancelAsyncDisplay];
|
||||
_isInCancelAsyncDisplay = NO;
|
||||
}
|
||||
|
||||
// This should never get called. This just records if it is.
|
||||
- (void)drawInContext:(CGContextRef)context
|
||||
{
|
||||
[super drawInContext:context];
|
||||
_drawInContextCount++;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
typedef NS_ENUM(NSUInteger, _ASDisplayLayerTestDelegateMode)
|
||||
{
|
||||
_ASDisplayLayerTestDelegateModeNone = 0,
|
||||
_ASDisplayLayerTestDelegateModeDrawParameters = 1 << 0,
|
||||
_ASDisplayLayerTestDelegateModeWillDisplay = 1 << 1,
|
||||
_ASDisplayLayerTestDelegateModeDidDisplay = 1 << 2,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSUInteger, _ASDisplayLayerTestDelegateClassModes) {
|
||||
_ASDisplayLayerTestDelegateClassModeNone = 0,
|
||||
_ASDisplayLayerTestDelegateClassModeDisplay = 1 << 0,
|
||||
_ASDisplayLayerTestDelegateClassModeDrawInContext = 1 << 1,
|
||||
};
|
||||
|
||||
@interface _ASDisplayLayerTestDelegate : ASDisplayNode <_ASDisplayLayerDelegate>
|
||||
|
||||
@property (nonatomic) NSUInteger didDisplayCount;
|
||||
@property (nonatomic) NSUInteger drawParametersCount;
|
||||
@property (nonatomic) NSUInteger willDisplayCount;
|
||||
|
||||
// for _ASDisplayLayerTestDelegateModeClassDisplay
|
||||
@property (nonatomic) NSUInteger displayCount;
|
||||
@property (nonatomic) UIImage *(^displayLayerBlock)(void);
|
||||
|
||||
// for _ASDisplayLayerTestDelegateModeClassDrawInContext
|
||||
@property (nonatomic) NSUInteger drawRectCount;
|
||||
|
||||
@end
|
||||
|
||||
@implementation _ASDisplayLayerTestDelegate {
|
||||
_ASDisplayLayerTestDelegateMode _modes;
|
||||
}
|
||||
|
||||
static _ASDisplayLayerTestDelegateClassModes _class_modes;
|
||||
|
||||
+ (void)setClassModes:(_ASDisplayLayerTestDelegateClassModes)classModes
|
||||
{
|
||||
_class_modes = classModes;
|
||||
}
|
||||
|
||||
- (id)initWithModes:(_ASDisplayLayerTestDelegateMode)modes
|
||||
{
|
||||
_modes = modes;
|
||||
|
||||
if (!(self = [super initWithLayerClass:[_ASDisplayLayerTestLayer class]]))
|
||||
return nil;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)didDisplayAsyncLayer:(_ASDisplayLayer *)layer
|
||||
{
|
||||
_didDisplayCount++;
|
||||
}
|
||||
|
||||
- (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer
|
||||
{
|
||||
_drawParametersCount++;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)willDisplayAsyncLayer:(_ASDisplayLayer *)layer
|
||||
{
|
||||
_willDisplayCount++;
|
||||
}
|
||||
|
||||
- (BOOL)respondsToSelector:(SEL)selector
|
||||
{
|
||||
if (sel_isEqual(selector, @selector(didDisplayAsyncLayer:))) {
|
||||
return (_modes & _ASDisplayLayerTestDelegateModeDidDisplay);
|
||||
} else if (sel_isEqual(selector, @selector(drawParametersForAsyncLayer:))) {
|
||||
return (_modes & _ASDisplayLayerTestDelegateModeDrawParameters);
|
||||
} else if (sel_isEqual(selector, @selector(willDisplayAsyncLayer:))) {
|
||||
return (_modes & _ASDisplayLayerTestDelegateModeWillDisplay);
|
||||
} else {
|
||||
return [super respondsToSelector:selector];
|
||||
}
|
||||
}
|
||||
|
||||
+ (BOOL)respondsToSelector:(SEL)selector
|
||||
{
|
||||
if (sel_isEqual(selector, @selector(displayWithParameters:isCancelled:))) {
|
||||
return _class_modes & _ASDisplayLayerTestDelegateClassModeDisplay;
|
||||
} else if (sel_isEqual(selector, @selector(drawRect:withParameters:isCancelled:isRasterizing:))) {
|
||||
return _class_modes & _ASDisplayLayerTestDelegateClassModeDrawInContext;
|
||||
} else {
|
||||
return [super respondsToSelector:selector];
|
||||
}
|
||||
}
|
||||
|
||||
// DANGER: Don't use the delegate as the parameters in real code; this is not thread-safe and just for accounting in unit tests!
|
||||
+ (UIImage *)displayWithParameters:(_ASDisplayLayerTestDelegate *)delegate isCancelled:(NS_NOESCAPE asdisplaynode_iscancelled_block_t)sentinelBlock
|
||||
{
|
||||
UIImage *contents = bogusImage();
|
||||
if (delegate->_displayLayerBlock != NULL) {
|
||||
contents = delegate->_displayLayerBlock();
|
||||
}
|
||||
delegate->_displayCount++;
|
||||
return contents;
|
||||
}
|
||||
|
||||
// DANGER: Don't use the delegate as the parameters in real code; this is not thread-safe and just for accounting in unit tests!
|
||||
+ (void)drawRect:(CGRect)bounds withParameters:(_ASDisplayLayerTestDelegate *)delegate isCancelled:(NS_NOESCAPE asdisplaynode_iscancelled_block_t)sentinelBlock isRasterizing:(BOOL)isRasterizing
|
||||
{
|
||||
__atomic_add_fetch(&delegate->_drawRectCount, 1, __ATOMIC_SEQ_CST);
|
||||
}
|
||||
|
||||
- (NSUInteger)drawRectCount
|
||||
{
|
||||
return(__atomic_load_n(&_drawRectCount, __ATOMIC_SEQ_CST));
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface _ASDisplayLayerTests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation _ASDisplayLayerTests
|
||||
|
||||
- (void)setUp {
|
||||
[super setUp];
|
||||
// Force bogusImage() to create+cache its image. This impacts any time-sensitive tests which call the method from
|
||||
// within the timed portion of the test. It seems that, in rare cases, this image creation can take a bit too long,
|
||||
// causing a test failure.
|
||||
bogusImage();
|
||||
}
|
||||
|
||||
// since we're not running in an application, we need to force this display on layer the hierarchy
|
||||
- (void)displayLayerRecursively:(CALayer *)layer
|
||||
{
|
||||
if (layer.needsDisplay) {
|
||||
[layer displayIfNeeded];
|
||||
}
|
||||
for (CALayer *sublayer in layer.sublayers) {
|
||||
[self displayLayerRecursively:sublayer];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)waitForDisplayQueue
|
||||
{
|
||||
// make sure we don't lock up the tests indefinitely; fail after 1 sec by using an async barrier
|
||||
__block BOOL didHitBarrier = NO;
|
||||
dispatch_barrier_async([_ASDisplayLayer displayQueue], ^{
|
||||
__atomic_store_n(&didHitBarrier, YES, __ATOMIC_SEQ_CST);
|
||||
});
|
||||
XCTAssertTrue(ASDisplayNodeRunRunLoopUntilBlockIsTrue(^BOOL{ return __atomic_load_n(&didHitBarrier, __ATOMIC_SEQ_CST); }));
|
||||
}
|
||||
|
||||
- (void)waitForLayer:(_ASDisplayLayerTestLayer *)layer asyncDisplayCount:(NSUInteger)count
|
||||
{
|
||||
// make sure we don't lock up the tests indefinitely; fail after 1 sec of waiting for the setContents async count to increment
|
||||
// NOTE: the layer sets its contents async back on the main queue, so we need to wait for main
|
||||
XCTAssertTrue(ASDisplayNodeRunRunLoopUntilBlockIsTrue(^BOOL{
|
||||
return (layer.setContentsAsyncCount == count);
|
||||
}));
|
||||
}
|
||||
|
||||
- (void)waitForAsyncDelegate:(_ASDisplayLayerTestDelegate *)asyncDelegate
|
||||
{
|
||||
XCTAssertTrue(ASDisplayNodeRunRunLoopUntilBlockIsTrue(^BOOL{
|
||||
return (asyncDelegate.didDisplayCount == 1);
|
||||
}));
|
||||
}
|
||||
|
||||
- (void)checkDelegateDisplay:(BOOL)displaysAsynchronously
|
||||
{
|
||||
[_ASDisplayLayerTestDelegate setClassModes:_ASDisplayLayerTestDelegateClassModeDisplay];
|
||||
_ASDisplayLayerTestDelegate *asyncDelegate = [[_ASDisplayLayerTestDelegate alloc] initWithModes:_ASDisplayLayerTestDelegateMode(_ASDisplayLayerTestDelegateModeDidDisplay | _ASDisplayLayerTestDelegateModeDrawParameters)];
|
||||
|
||||
_ASDisplayLayerTestLayer *layer = (_ASDisplayLayerTestLayer *)asyncDelegate.layer;
|
||||
layer.displaysAsynchronously = displaysAsynchronously;
|
||||
|
||||
if (displaysAsynchronously) {
|
||||
dispatch_suspend([_ASDisplayLayer displayQueue]);
|
||||
}
|
||||
layer.frame = CGRectMake(0.0, 0.0, 100.0, 100.0);
|
||||
[layer setNeedsDisplay];
|
||||
[layer displayIfNeeded];
|
||||
|
||||
if (displaysAsynchronously) {
|
||||
XCTAssertTrue([layer checkSetContentsCountsWithSyncCount:0 asyncCount:0], @"%@", layer.setContentsCounts);
|
||||
XCTAssertEqual(layer.displayCount, 1u);
|
||||
XCTAssertEqual(layer.drawInContextCount, 0u);
|
||||
dispatch_resume([_ASDisplayLayer displayQueue]);
|
||||
[self waitForDisplayQueue];
|
||||
[self waitForAsyncDelegate:asyncDelegate];
|
||||
XCTAssertTrue([layer checkSetContentsCountsWithSyncCount:0 asyncCount:1], @"%@", layer.setContentsCounts);
|
||||
} else {
|
||||
XCTAssertTrue([layer checkSetContentsCountsWithSyncCount:1 asyncCount:0], @"%@", layer.setContentsCounts);
|
||||
}
|
||||
|
||||
XCTAssertFalse(layer.needsDisplay);
|
||||
XCTAssertEqual(layer.displayCount, 1u);
|
||||
XCTAssertEqual(layer.drawInContextCount, 0u);
|
||||
XCTAssertEqual(asyncDelegate.didDisplayCount, 1u);
|
||||
XCTAssertEqual(asyncDelegate.displayCount, 1u);
|
||||
}
|
||||
|
||||
- (void)testDelegateDisplaySync
|
||||
{
|
||||
[self checkDelegateDisplay:NO];
|
||||
}
|
||||
|
||||
- (void)testDelegateDisplayAsync
|
||||
{
|
||||
[self checkDelegateDisplay:YES];
|
||||
}
|
||||
|
||||
- (void)checkDelegateDrawInContext:(BOOL)displaysAsynchronously
|
||||
{
|
||||
[_ASDisplayLayerTestDelegate setClassModes:_ASDisplayLayerTestDelegateClassModeDrawInContext];
|
||||
_ASDisplayLayerTestDelegate *asyncDelegate = [[_ASDisplayLayerTestDelegate alloc] initWithModes:_ASDisplayLayerTestDelegateMode(_ASDisplayLayerTestDelegateModeDidDisplay | _ASDisplayLayerTestDelegateModeDrawParameters)];
|
||||
|
||||
_ASDisplayLayerTestLayer *layer = (_ASDisplayLayerTestLayer *)asyncDelegate.layer;
|
||||
layer.displaysAsynchronously = displaysAsynchronously;
|
||||
|
||||
if (displaysAsynchronously) {
|
||||
dispatch_suspend([_ASDisplayLayer displayQueue]);
|
||||
}
|
||||
layer.frame = CGRectMake(0.0, 0.0, 100.0, 100.0);
|
||||
[layer setNeedsDisplay];
|
||||
[layer displayIfNeeded];
|
||||
|
||||
if (displaysAsynchronously) {
|
||||
XCTAssertTrue([layer checkSetContentsCountsWithSyncCount:0 asyncCount:0], @"%@", layer.setContentsCounts);
|
||||
XCTAssertEqual(layer.displayCount, 1u);
|
||||
XCTAssertEqual(layer.drawInContextCount, 0u);
|
||||
XCTAssertEqual(asyncDelegate.drawRectCount, 0u);
|
||||
dispatch_resume([_ASDisplayLayer displayQueue]);
|
||||
[self waitForLayer:layer asyncDisplayCount:1];
|
||||
[self waitForAsyncDelegate:asyncDelegate];
|
||||
XCTAssertTrue([layer checkSetContentsCountsWithSyncCount:0 asyncCount:1], @"%@", layer.setContentsCounts);
|
||||
} else {
|
||||
XCTAssertTrue([layer checkSetContentsCountsWithSyncCount:1 asyncCount:0], @"%@", layer.setContentsCounts);
|
||||
}
|
||||
|
||||
XCTAssertFalse(layer.needsDisplay);
|
||||
XCTAssertEqual(layer.displayCount, 1u);
|
||||
XCTAssertEqual(layer.drawInContextCount, 0u);
|
||||
XCTAssertEqual(asyncDelegate.didDisplayCount, 1u);
|
||||
XCTAssertEqual(asyncDelegate.displayCount, 0u);
|
||||
XCTAssertEqual(asyncDelegate.drawParametersCount, 1u);
|
||||
XCTAssertEqual(asyncDelegate.drawRectCount, 1u);
|
||||
}
|
||||
|
||||
- (void)testDelegateDrawInContextSync
|
||||
{
|
||||
[self checkDelegateDrawInContext:NO];
|
||||
}
|
||||
|
||||
- (void)testDelegateDrawInContextAsync
|
||||
{
|
||||
[self checkDelegateDrawInContext:YES];
|
||||
}
|
||||
|
||||
- (void)checkDelegateDisplayAndDrawInContext:(BOOL)displaysAsynchronously
|
||||
{
|
||||
[_ASDisplayLayerTestDelegate setClassModes:_ASDisplayLayerTestDelegateClassModes(_ASDisplayLayerTestDelegateClassModeDisplay | _ASDisplayLayerTestDelegateClassModeDrawInContext)];
|
||||
_ASDisplayLayerTestDelegate *asyncDelegate = [[_ASDisplayLayerTestDelegate alloc] initWithModes:_ASDisplayLayerTestDelegateMode(_ASDisplayLayerTestDelegateModeDidDisplay | _ASDisplayLayerTestDelegateModeDrawParameters)];
|
||||
|
||||
_ASDisplayLayerTestLayer *layer = (_ASDisplayLayerTestLayer *)asyncDelegate.layer;
|
||||
layer.displaysAsynchronously = displaysAsynchronously;
|
||||
|
||||
if (displaysAsynchronously) {
|
||||
dispatch_suspend([_ASDisplayLayer displayQueue]);
|
||||
}
|
||||
layer.frame = CGRectMake(0.0, 0.0, 100.0, 100.0);
|
||||
[layer setNeedsDisplay];
|
||||
[layer displayIfNeeded];
|
||||
|
||||
if (displaysAsynchronously) {
|
||||
XCTAssertTrue([layer checkSetContentsCountsWithSyncCount:0 asyncCount:0], @"%@", layer.setContentsCounts);
|
||||
XCTAssertEqual(layer.displayCount, 1u);
|
||||
XCTAssertEqual(asyncDelegate.drawParametersCount, 1u);
|
||||
XCTAssertEqual(asyncDelegate.drawRectCount, 0u);
|
||||
dispatch_resume([_ASDisplayLayer displayQueue]);
|
||||
[self waitForDisplayQueue];
|
||||
[self waitForAsyncDelegate:asyncDelegate];
|
||||
XCTAssertTrue([layer checkSetContentsCountsWithSyncCount:0 asyncCount:1], @"%@", layer.setContentsCounts);
|
||||
} else {
|
||||
XCTAssertTrue([layer checkSetContentsCountsWithSyncCount:1 asyncCount:0], @"%@", layer.setContentsCounts);
|
||||
}
|
||||
|
||||
XCTAssertFalse(layer.needsDisplay);
|
||||
XCTAssertEqual(layer.displayCount, 1u);
|
||||
XCTAssertEqual(layer.drawInContextCount, 0u);
|
||||
XCTAssertEqual(asyncDelegate.didDisplayCount, 1u);
|
||||
XCTAssertEqual(asyncDelegate.displayCount, 1u);
|
||||
XCTAssertEqual(asyncDelegate.drawParametersCount, 1u);
|
||||
XCTAssertEqual(asyncDelegate.drawRectCount, 0u);
|
||||
}
|
||||
|
||||
- (void)testDelegateDisplayAndDrawInContextSync
|
||||
{
|
||||
[self checkDelegateDisplayAndDrawInContext:NO];
|
||||
}
|
||||
|
||||
- (void)testDelegateDisplayAndDrawInContextAsync
|
||||
{
|
||||
[self checkDelegateDisplayAndDrawInContext:YES];
|
||||
}
|
||||
|
||||
- (void)testCancelAsyncDisplay
|
||||
{
|
||||
[_ASDisplayLayerTestDelegate setClassModes:_ASDisplayLayerTestDelegateClassModeDisplay];
|
||||
_ASDisplayLayerTestDelegate *asyncDelegate = [[_ASDisplayLayerTestDelegate alloc] initWithModes:_ASDisplayLayerTestDelegateModeDidDisplay];
|
||||
_ASDisplayLayerTestLayer *layer = (_ASDisplayLayerTestLayer *)asyncDelegate.layer;
|
||||
|
||||
dispatch_suspend([_ASDisplayLayer displayQueue]);
|
||||
layer.frame = CGRectMake(0.0, 0.0, 100.0, 100.0);
|
||||
[layer setNeedsDisplay];
|
||||
XCTAssertTrue(layer.needsDisplay);
|
||||
[layer displayIfNeeded];
|
||||
|
||||
XCTAssertTrue([layer checkSetContentsCountsWithSyncCount:0 asyncCount:0], @"%@", layer.setContentsCounts);
|
||||
XCTAssertFalse(layer.needsDisplay);
|
||||
XCTAssertEqual(layer.displayCount, 1u);
|
||||
XCTAssertEqual(layer.drawInContextCount, 0u);
|
||||
|
||||
[layer cancelAsyncDisplay];
|
||||
|
||||
dispatch_resume([_ASDisplayLayer displayQueue]);
|
||||
[self waitForDisplayQueue];
|
||||
XCTAssertTrue([layer checkSetContentsCountsWithSyncCount:0 asyncCount:0], @"%@", layer.setContentsCounts);
|
||||
XCTAssertEqual(layer.displayCount, 1u);
|
||||
XCTAssertEqual(layer.drawInContextCount, 0u);
|
||||
XCTAssertEqual(asyncDelegate.didDisplayCount, 0u);
|
||||
XCTAssertEqual(asyncDelegate.displayCount, 0u);
|
||||
XCTAssertEqual(asyncDelegate.drawParametersCount, 0u);
|
||||
}
|
||||
|
||||
- (void)DISABLED_testTransaction
|
||||
{
|
||||
auto delegateModes = _ASDisplayLayerTestDelegateMode(_ASDisplayLayerTestDelegateModeDidDisplay | _ASDisplayLayerTestDelegateModeDrawParameters);
|
||||
[_ASDisplayLayerTestDelegate setClassModes:_ASDisplayLayerTestDelegateClassModeDisplay];
|
||||
|
||||
// Setup
|
||||
_ASDisplayLayerTestContainerLayer *containerLayer = [[_ASDisplayLayerTestContainerLayer alloc] init];
|
||||
containerLayer.asyncdisplaykit_asyncTransactionContainer = YES;
|
||||
containerLayer.frame = CGRectMake(0.0, 0.0, 100.0, 100.0);
|
||||
|
||||
_ASDisplayLayerTestDelegate *layer1Delegate = [[_ASDisplayLayerTestDelegate alloc] initWithModes:delegateModes];
|
||||
_ASDisplayLayerTestLayer *layer1 = (_ASDisplayLayerTestLayer *)layer1Delegate.layer;
|
||||
layer1.displaysAsynchronously = YES;
|
||||
|
||||
dispatch_semaphore_t displayAsyncLayer1Sema = dispatch_semaphore_create(0);
|
||||
layer1Delegate.displayLayerBlock = ^UIImage *{
|
||||
dispatch_semaphore_wait(displayAsyncLayer1Sema, DISPATCH_TIME_FOREVER);
|
||||
return bogusImage();
|
||||
};
|
||||
layer1.backgroundColor = [UIColor blackColor].CGColor;
|
||||
layer1.frame = CGRectMake(0.0, 0.0, 333.0, 123.0);
|
||||
[containerLayer addSublayer:layer1];
|
||||
|
||||
_ASDisplayLayerTestDelegate *layer2Delegate = [[_ASDisplayLayerTestDelegate alloc] initWithModes:delegateModes];
|
||||
_ASDisplayLayerTestLayer *layer2 = (_ASDisplayLayerTestLayer *)layer2Delegate.layer;
|
||||
layer2.displaysAsynchronously = YES;
|
||||
layer2.backgroundColor = [UIColor blackColor].CGColor;
|
||||
layer2.frame = CGRectMake(0.0, 50.0, 97.0, 50.0);
|
||||
[containerLayer addSublayer:layer2];
|
||||
|
||||
dispatch_suspend([_ASDisplayLayer displayQueue]);
|
||||
|
||||
// display below if needed
|
||||
[layer1 setNeedsDisplay];
|
||||
[layer2 setNeedsDisplay];
|
||||
[containerLayer setNeedsDisplay];
|
||||
[self displayLayerRecursively:containerLayer];
|
||||
|
||||
// check state before running displayQueue
|
||||
XCTAssertTrue([layer1 checkSetContentsCountsWithSyncCount:0 asyncCount:0], @"%@", layer1.setContentsCounts);
|
||||
XCTAssertEqual(layer1.displayCount, 1u);
|
||||
XCTAssertEqual(layer1Delegate.displayCount, 0u);
|
||||
XCTAssertTrue([layer2 checkSetContentsCountsWithSyncCount:0 asyncCount:0], @"%@", layer2.setContentsCounts);
|
||||
XCTAssertEqual(layer2.displayCount, 1u);
|
||||
XCTAssertEqual(layer1Delegate.displayCount, 0u);
|
||||
XCTAssertEqual(containerLayer.didCompleteTransactionCount, 0u);
|
||||
|
||||
// run displayQueue until async display for layer2 has been run
|
||||
dispatch_resume([_ASDisplayLayer displayQueue]);
|
||||
XCTAssertTrue(ASDisplayNodeRunRunLoopUntilBlockIsTrue(^BOOL{
|
||||
return (layer2Delegate.displayCount == 1);
|
||||
}));
|
||||
|
||||
// check layer1 has not had async display run
|
||||
XCTAssertTrue([layer1 checkSetContentsCountsWithSyncCount:0 asyncCount:0], @"%@", layer1.setContentsCounts);
|
||||
XCTAssertEqual(layer1.displayCount, 1u);
|
||||
XCTAssertEqual(layer1Delegate.displayCount, 0u);
|
||||
// check layer2 has had async display run
|
||||
XCTAssertTrue([layer2 checkSetContentsCountsWithSyncCount:0 asyncCount:0], @"%@", layer2.setContentsCounts);
|
||||
XCTAssertEqual(layer2.displayCount, 1u);
|
||||
XCTAssertEqual(layer2Delegate.displayCount, 1u);
|
||||
XCTAssertEqual(containerLayer.didCompleteTransactionCount, 0u);
|
||||
|
||||
|
||||
// allow layer1 to complete display
|
||||
dispatch_semaphore_signal(displayAsyncLayer1Sema);
|
||||
[self waitForLayer:layer1 asyncDisplayCount:1];
|
||||
|
||||
// check that both layers have completed display
|
||||
XCTAssertTrue([layer1 checkSetContentsCountsWithSyncCount:0 asyncCount:1], @"%@", layer1.setContentsCounts);
|
||||
XCTAssertEqual(layer1.displayCount, 1u);
|
||||
XCTAssertEqual(layer1Delegate.displayCount, 1u);
|
||||
XCTAssertTrue([layer2 checkSetContentsCountsWithSyncCount:0 asyncCount:1], @"%@", layer2.setContentsCounts);
|
||||
XCTAssertEqual(layer2.displayCount, 1u);
|
||||
XCTAssertEqual(layer2Delegate.displayCount, 1u);
|
||||
|
||||
XCTAssertTrue(ASDisplayNodeRunRunLoopUntilBlockIsTrue(^BOOL{
|
||||
return (containerLayer.didCompleteTransactionCount == 1);
|
||||
}));
|
||||
}
|
||||
|
||||
- (void)checkSuspendResume:(BOOL)displaysAsynchronously
|
||||
{
|
||||
[_ASDisplayLayerTestDelegate setClassModes:_ASDisplayLayerTestDelegateClassModeDrawInContext];
|
||||
_ASDisplayLayerTestDelegate *asyncDelegate = [[_ASDisplayLayerTestDelegate alloc] initWithModes:_ASDisplayLayerTestDelegateMode(_ASDisplayLayerTestDelegateModeDidDisplay | _ASDisplayLayerTestDelegateModeDrawParameters)];
|
||||
|
||||
_ASDisplayLayerTestLayer *layer = (_ASDisplayLayerTestLayer *)asyncDelegate.layer;
|
||||
layer.displaysAsynchronously = displaysAsynchronously;
|
||||
layer.frame = CGRectMake(0.0, 0.0, 100.0, 100.0);
|
||||
|
||||
if (displaysAsynchronously) {
|
||||
dispatch_suspend([_ASDisplayLayer displayQueue]);
|
||||
}
|
||||
|
||||
// Layer shouldn't display because display is suspended
|
||||
layer.displaySuspended = YES;
|
||||
[layer setNeedsDisplay];
|
||||
[layer displayIfNeeded];
|
||||
XCTAssertEqual(layer.displayCount, 0u, @"Should not have displayed because display is suspended, thus -setNeedsDisplay is a no-op");
|
||||
XCTAssertFalse(layer.needsDisplay, @"Should not need display");
|
||||
if (displaysAsynchronously) {
|
||||
XCTAssertTrue([layer checkSetContentsCountsWithSyncCount:0 asyncCount:0], @"%@", layer.setContentsCounts);
|
||||
dispatch_resume([_ASDisplayLayer displayQueue]);
|
||||
[self waitForDisplayQueue];
|
||||
XCTAssertTrue([layer checkSetContentsCountsWithSyncCount:0 asyncCount:0], @"%@", layer.setContentsCounts);
|
||||
} else {
|
||||
XCTAssertTrue([layer checkSetContentsCountsWithSyncCount:0 asyncCount:0], @"%@", layer.setContentsCounts);
|
||||
}
|
||||
XCTAssertFalse(layer.needsDisplay);
|
||||
XCTAssertEqual(layer.drawInContextCount, 0u);
|
||||
XCTAssertEqual(asyncDelegate.drawRectCount, 0u);
|
||||
|
||||
// Layer should display because display is resumed
|
||||
if (displaysAsynchronously) {
|
||||
dispatch_suspend([_ASDisplayLayer displayQueue]);
|
||||
}
|
||||
layer.displaySuspended = NO;
|
||||
XCTAssertTrue(layer.needsDisplay);
|
||||
[layer displayIfNeeded];
|
||||
XCTAssertEqual(layer.displayCount, 1u);
|
||||
if (displaysAsynchronously) {
|
||||
XCTAssertTrue([layer checkSetContentsCountsWithSyncCount:0 asyncCount:0], @"%@", layer.setContentsCounts);
|
||||
XCTAssertEqual(layer.drawInContextCount, 0u);
|
||||
XCTAssertEqual(asyncDelegate.drawRectCount, 0u);
|
||||
dispatch_resume([_ASDisplayLayer displayQueue]);
|
||||
[self waitForLayer:layer asyncDisplayCount:1];
|
||||
XCTAssertTrue([layer checkSetContentsCountsWithSyncCount:0 asyncCount:1], @"%@", layer.setContentsCounts);
|
||||
} else {
|
||||
XCTAssertTrue([layer checkSetContentsCountsWithSyncCount:1 asyncCount:0], @"%@", layer.setContentsCounts);
|
||||
}
|
||||
XCTAssertEqual(layer.drawInContextCount, 0u);
|
||||
XCTAssertEqual(asyncDelegate.drawParametersCount, 1u);
|
||||
XCTAssertEqual(asyncDelegate.drawRectCount, 1u);
|
||||
XCTAssertFalse(layer.needsDisplay);
|
||||
}
|
||||
|
||||
- (void)testSuspendResumeAsync
|
||||
{
|
||||
[self checkSuspendResume:YES];
|
||||
}
|
||||
|
||||
- (void)testSuspendResumeSync
|
||||
{
|
||||
[self checkSuspendResume:NO];
|
||||
}
|
||||
|
||||
@end
|
@ -1,417 +0,0 @@
|
||||
//
|
||||
// ASDisplayNodeAppearanceTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import <objc/runtime.h>
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <AsyncDisplayKit/_ASDisplayView.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
|
||||
#import <AsyncDisplayKit/UIView+ASConvenience.h>
|
||||
|
||||
// helper functions
|
||||
IMP class_replaceMethodWithBlock(Class theClass, SEL originalSelector, id block);
|
||||
IMP class_replaceMethodWithBlock(Class theClass, SEL originalSelector, id block)
|
||||
{
|
||||
IMP newImplementation = imp_implementationWithBlock(block);
|
||||
Method method = class_getInstanceMethod(theClass, originalSelector);
|
||||
return class_replaceMethod(theClass, originalSelector, newImplementation, method_getTypeEncoding(method));
|
||||
}
|
||||
|
||||
static dispatch_block_t modifyMethodByAddingPrologueBlockAndReturnCleanupBlock(Class theClass, SEL originalSelector, void (^block)(id))
|
||||
{
|
||||
__block IMP originalImp = NULL;
|
||||
void (^blockActualSwizzle)(id) = ^(id swizzedSelf){
|
||||
block(swizzedSelf);
|
||||
((void(*)(id, SEL))originalImp)(swizzedSelf, originalSelector);
|
||||
};
|
||||
originalImp = class_replaceMethodWithBlock(theClass, originalSelector, blockActualSwizzle);
|
||||
void (^cleanupBlock)(void) = ^{
|
||||
// restore original method
|
||||
Method method = class_getInstanceMethod(theClass, originalSelector);
|
||||
class_replaceMethod(theClass, originalSelector, originalImp, method_getTypeEncoding(method));
|
||||
};
|
||||
return cleanupBlock;
|
||||
};
|
||||
|
||||
@interface ASDisplayNode (PrivateStuffSoWeDontPullInCPPInternalH)
|
||||
- (BOOL)__visibilityNotificationsDisabled;
|
||||
- (BOOL)__selfOrParentHasVisibilityNotificationsDisabled;
|
||||
- (id)initWithViewClass:(Class)viewClass;
|
||||
- (id)initWithLayerClass:(Class)layerClass;
|
||||
@end
|
||||
|
||||
@interface ASDisplayNodeAppearanceTests : XCTestCase
|
||||
@end
|
||||
|
||||
// Conveniences for making nodes named a certain way
|
||||
#define DeclareNodeNamed(n) ASDisplayNode *n = [[ASDisplayNode alloc] init]; n.debugName = @#n
|
||||
#define DeclareViewNamed(v) \
|
||||
ASDisplayNode *node_##v = [[ASDisplayNode alloc] init]; \
|
||||
node_##v.debugName = @#v; \
|
||||
UIView *v = node_##v.view;
|
||||
|
||||
@implementation ASDisplayNodeAppearanceTests
|
||||
{
|
||||
_ASDisplayView *_view;
|
||||
|
||||
NSMutableArray *_swizzleCleanupBlocks;
|
||||
|
||||
NSCountedSet *_willEnterHierarchyCounts;
|
||||
NSCountedSet *_didExitHierarchyCounts;
|
||||
|
||||
}
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
[super setUp];
|
||||
|
||||
_swizzleCleanupBlocks = [[NSMutableArray alloc] init];
|
||||
|
||||
// Using this instead of mocks. Count # of times method called
|
||||
_willEnterHierarchyCounts = [[NSCountedSet alloc] init];
|
||||
_didExitHierarchyCounts = [[NSCountedSet alloc] init];
|
||||
|
||||
dispatch_block_t cleanupBlock = modifyMethodByAddingPrologueBlockAndReturnCleanupBlock([ASDisplayNode class], @selector(willEnterHierarchy), ^(id blockSelf){
|
||||
[_willEnterHierarchyCounts addObject:blockSelf];
|
||||
});
|
||||
[_swizzleCleanupBlocks addObject:cleanupBlock];
|
||||
cleanupBlock = modifyMethodByAddingPrologueBlockAndReturnCleanupBlock([ASDisplayNode class], @selector(didExitHierarchy), ^(id blockSelf){
|
||||
[_didExitHierarchyCounts addObject:blockSelf];
|
||||
});
|
||||
[_swizzleCleanupBlocks addObject:cleanupBlock];
|
||||
}
|
||||
|
||||
- (void)tearDown
|
||||
{
|
||||
[super tearDown];
|
||||
|
||||
for(dispatch_block_t cleanupBlock in _swizzleCleanupBlocks) {
|
||||
cleanupBlock();
|
||||
}
|
||||
_swizzleCleanupBlocks = nil;
|
||||
_willEnterHierarchyCounts = nil;
|
||||
_didExitHierarchyCounts = nil;
|
||||
}
|
||||
|
||||
- (void)testAppearanceMethodsCalledWithRootNodeInWindowLayer
|
||||
{
|
||||
[self checkAppearanceMethodsCalledWithRootNodeInWindowLayerBacked:YES];
|
||||
}
|
||||
|
||||
- (void)testAppearanceMethodsCalledWithRootNodeInWindowView
|
||||
{
|
||||
[self checkAppearanceMethodsCalledWithRootNodeInWindowLayerBacked:NO];
|
||||
}
|
||||
|
||||
- (void)checkAppearanceMethodsCalledWithRootNodeInWindowLayerBacked:(BOOL)isLayerBacked
|
||||
{
|
||||
// ASDisplayNode visibility does not change if modifying a hierarchy that is not in a window. So create one and add the superview to it.
|
||||
UIWindow *window = [[UIWindow alloc] initWithFrame:CGRectZero];
|
||||
|
||||
DeclareNodeNamed(n);
|
||||
DeclareViewNamed(superview);
|
||||
|
||||
n.layerBacked = isLayerBacked;
|
||||
|
||||
if (isLayerBacked) {
|
||||
[superview.layer addSublayer:n.layer];
|
||||
} else {
|
||||
[superview addSubview:n.view];
|
||||
}
|
||||
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:n], 0u, @"willEnterHierarchy erroneously called");
|
||||
XCTAssertEqual([_didExitHierarchyCounts countForObject:n], 0u, @"didExitHierarchy erroneously called");
|
||||
|
||||
[window addSubview:superview];
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:n], 1u, @"willEnterHierarchy not called when node's view added to hierarchy");
|
||||
XCTAssertEqual([_didExitHierarchyCounts countForObject:n], 0u, @"didExitHierarchy erroneously called");
|
||||
|
||||
XCTAssertTrue(n.inHierarchy, @"Node should be visible");
|
||||
|
||||
if (isLayerBacked) {
|
||||
[n.layer removeFromSuperlayer];
|
||||
} else {
|
||||
[n.view removeFromSuperview];
|
||||
}
|
||||
|
||||
XCTAssertFalse(n.inHierarchy, @"Node should be not visible");
|
||||
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:n], 1u, @"willEnterHierarchy not called when node's view added to hierarchy");
|
||||
XCTAssertEqual([_didExitHierarchyCounts countForObject:n], 1u, @"didExitHierarchy erroneously called");
|
||||
}
|
||||
|
||||
- (void)checkManualAppearanceViewLoaded:(BOOL)isViewLoaded layerBacked:(BOOL)isLayerBacked
|
||||
{
|
||||
// ASDisplayNode visibility does not change if modifying a hierarchy that is not in a window. So create one and add the superview to it.
|
||||
UIWindow *window = [[UIWindow alloc] initWithFrame:CGRectZero];
|
||||
|
||||
DeclareNodeNamed(parent);
|
||||
DeclareNodeNamed(a);
|
||||
DeclareNodeNamed(b);
|
||||
DeclareNodeNamed(aa);
|
||||
DeclareNodeNamed(ab);
|
||||
|
||||
for (ASDisplayNode *n in @[parent, a, b, aa, ab]) {
|
||||
n.layerBacked = isLayerBacked;
|
||||
if (isViewLoaded)
|
||||
[n layer];
|
||||
}
|
||||
|
||||
[parent addSubnode:a];
|
||||
|
||||
XCTAssertFalse(parent.inHierarchy, @"Nothing should be visible");
|
||||
XCTAssertFalse(a.inHierarchy, @"Nothing should be visible");
|
||||
XCTAssertFalse(b.inHierarchy, @"Nothing should be visible");
|
||||
XCTAssertFalse(aa.inHierarchy, @"Nothing should be visible");
|
||||
XCTAssertFalse(ab.inHierarchy, @"Nothing should be visible");
|
||||
|
||||
if (isLayerBacked) {
|
||||
[window.layer addSublayer:parent.layer];
|
||||
} else {
|
||||
[window addSubview:parent.view];
|
||||
}
|
||||
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:parent], 1u, @"Should have -willEnterHierarchy called once");
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:a], 1u, @"Should have -willEnterHierarchy called once");
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:b], 0u, @"Should not have appeared yet");
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:aa], 0u, @"Should not have appeared yet");
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:ab], 0u, @"Should not have appeared yet");
|
||||
|
||||
XCTAssertTrue(parent.inHierarchy, @"Should be visible");
|
||||
XCTAssertTrue(a.inHierarchy, @"Should be visible");
|
||||
XCTAssertFalse(b.inHierarchy, @"Nothing should be visible");
|
||||
XCTAssertFalse(aa.inHierarchy, @"Nothing should be visible");
|
||||
XCTAssertFalse(ab.inHierarchy, @"Nothing should be visible");
|
||||
|
||||
// Add to an already-visible node should make the node visible
|
||||
[parent addSubnode:b];
|
||||
[a insertSubnode:aa atIndex:0];
|
||||
[a insertSubnode:ab aboveSubnode:aa];
|
||||
|
||||
XCTAssertTrue(parent.inHierarchy, @"Should be visible");
|
||||
XCTAssertTrue(a.inHierarchy, @"Should be visible");
|
||||
XCTAssertTrue(b.inHierarchy, @"Should be visible after adding to visible parent");
|
||||
XCTAssertTrue(aa.inHierarchy, @"Nothing should be visible");
|
||||
XCTAssertTrue(ab.inHierarchy, @"Nothing should be visible");
|
||||
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:parent], 1u, @"Should have -willEnterHierarchy called once");
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:a], 1u, @"Should have -willEnterHierarchy called once");
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:b], 1u, @"Should have -willEnterHierarchy called once");
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:aa], 1u, @"Should have -willEnterHierarchy called once");
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:ab], 1u, @"Should have -willEnterHierarchy called once");
|
||||
|
||||
if (isLayerBacked) {
|
||||
[parent.layer removeFromSuperlayer];
|
||||
} else {
|
||||
[parent.view removeFromSuperview];
|
||||
}
|
||||
|
||||
XCTAssertFalse(parent.inHierarchy, @"Nothing should be visible");
|
||||
XCTAssertFalse(a.inHierarchy, @"Nothing should be visible");
|
||||
XCTAssertFalse(b.inHierarchy, @"Nothing should be visible");
|
||||
XCTAssertFalse(aa.inHierarchy, @"Nothing should be visible");
|
||||
XCTAssertFalse(ab.inHierarchy, @"Nothing should be visible");
|
||||
}
|
||||
|
||||
- (void)testAppearanceMethodsNoLayer
|
||||
{
|
||||
[self checkManualAppearanceViewLoaded:NO layerBacked:YES];
|
||||
}
|
||||
|
||||
- (void)testAppearanceMethodsNoView
|
||||
{
|
||||
[self checkManualAppearanceViewLoaded:NO layerBacked:NO];
|
||||
}
|
||||
|
||||
- (void)testAppearanceMethodsLayer
|
||||
{
|
||||
[self checkManualAppearanceViewLoaded:YES layerBacked:YES];
|
||||
}
|
||||
|
||||
- (void)testAppearanceMethodsView
|
||||
{
|
||||
[self checkManualAppearanceViewLoaded:YES layerBacked:NO];
|
||||
}
|
||||
|
||||
- (void)testSynchronousIntermediaryView
|
||||
{
|
||||
// Parent is a wrapper node for a scrollview
|
||||
ASDisplayNode *parentSynchronousNode = [[ASDisplayNode alloc] initWithViewClass:[UIScrollView class]];
|
||||
DeclareNodeNamed(layerBackedNode);
|
||||
DeclareNodeNamed(viewBackedNode);
|
||||
|
||||
layerBackedNode.layerBacked = YES;
|
||||
|
||||
UIWindow *window = [[UIWindow alloc] initWithFrame:CGRectZero];
|
||||
[parentSynchronousNode addSubnode:layerBackedNode];
|
||||
[parentSynchronousNode addSubnode:viewBackedNode];
|
||||
|
||||
XCTAssertFalse(parentSynchronousNode.inHierarchy, @"Should not yet be visible");
|
||||
XCTAssertFalse(layerBackedNode.inHierarchy, @"Should not yet be visible");
|
||||
XCTAssertFalse(viewBackedNode.inHierarchy, @"Should not yet be visible");
|
||||
|
||||
[window addSubview:parentSynchronousNode.view];
|
||||
|
||||
// This is a known case that isn't supported
|
||||
XCTAssertFalse(parentSynchronousNode.inHierarchy, @"Synchronous views are not currently marked visible");
|
||||
|
||||
XCTAssertTrue(layerBackedNode.inHierarchy, @"Synchronous views' subviews should get marked visible");
|
||||
XCTAssertTrue(viewBackedNode.inHierarchy, @"Synchronous views' subviews should get marked visible");
|
||||
|
||||
// Try moving a node to/from a synchronous node in the window with the node API
|
||||
// Setup
|
||||
[layerBackedNode removeFromSupernode];
|
||||
[viewBackedNode removeFromSupernode];
|
||||
XCTAssertFalse(layerBackedNode.inHierarchy, @"aoeu");
|
||||
XCTAssertFalse(viewBackedNode.inHierarchy, @"aoeu");
|
||||
|
||||
// now move to synchronous node
|
||||
[parentSynchronousNode addSubnode:layerBackedNode];
|
||||
[parentSynchronousNode insertSubnode:viewBackedNode aboveSubnode:layerBackedNode];
|
||||
XCTAssertTrue(layerBackedNode.inHierarchy, @"Synchronous views' subviews should get marked visible");
|
||||
XCTAssertTrue(viewBackedNode.inHierarchy, @"Synchronous views' subviews should get marked visible");
|
||||
|
||||
[parentSynchronousNode.view removeFromSuperview];
|
||||
|
||||
XCTAssertFalse(parentSynchronousNode.inHierarchy, @"Should not have changed");
|
||||
XCTAssertFalse(layerBackedNode.inHierarchy, @"Should have been marked invisible when synchronous superview was removed from the window");
|
||||
XCTAssertFalse(viewBackedNode.inHierarchy, @"Should have been marked invisible when synchronous superview was removed from the window");
|
||||
}
|
||||
|
||||
- (void)checkMoveAcrossHierarchyLayerBacked:(BOOL)isLayerBacked useManualCalls:(BOOL)useManualDisable useNodeAPI:(BOOL)useNodeAPI
|
||||
{
|
||||
UIWindow *window = [[UIWindow alloc] initWithFrame:CGRectZero];
|
||||
|
||||
DeclareNodeNamed(parentA);
|
||||
DeclareNodeNamed(parentB);
|
||||
DeclareNodeNamed(child);
|
||||
DeclareNodeNamed(childSubnode);
|
||||
|
||||
for (ASDisplayNode *n in @[parentA, parentB, child, childSubnode]) {
|
||||
n.layerBacked = isLayerBacked;
|
||||
}
|
||||
|
||||
[parentA addSubnode:child];
|
||||
[child addSubnode:childSubnode];
|
||||
|
||||
XCTAssertFalse(parentA.inHierarchy, @"Should not yet be visible");
|
||||
XCTAssertFalse(parentB.inHierarchy, @"Should not yet be visible");
|
||||
XCTAssertFalse(child.inHierarchy, @"Should not yet be visible");
|
||||
XCTAssertFalse(childSubnode.inHierarchy, @"Should not yet be visible");
|
||||
XCTAssertFalse(childSubnode.inHierarchy, @"Should not yet be visible");
|
||||
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:child], 0u, @"Should not have -willEnterHierarchy called");
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:childSubnode], 0u, @"Should not have -willEnterHierarchy called");
|
||||
|
||||
if (isLayerBacked) {
|
||||
[window.layer addSublayer:parentA.layer];
|
||||
[window.layer addSublayer:parentB.layer];
|
||||
} else {
|
||||
[window addSubview:parentA.view];
|
||||
[window addSubview:parentB.view];
|
||||
}
|
||||
|
||||
XCTAssertTrue(parentA.inHierarchy, @"Should be visible after added to window");
|
||||
XCTAssertTrue(parentB.inHierarchy, @"Should be visible after added to window");
|
||||
XCTAssertTrue(child.inHierarchy, @"Should be visible after parent added to window");
|
||||
XCTAssertTrue(childSubnode.inHierarchy, @"Should be visible after parent added to window");
|
||||
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:child], 1u, @"Should have -willEnterHierarchy called once");
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:childSubnode], 1u, @"Should have -willEnterHierarchy called once");
|
||||
|
||||
// Move subnode from A to B
|
||||
if (useManualDisable) {
|
||||
ASDisplayNodeDisableHierarchyNotifications(child);
|
||||
}
|
||||
if (!useNodeAPI) {
|
||||
[child removeFromSupernode];
|
||||
[parentB addSubnode:child];
|
||||
} else {
|
||||
[parentB addSubnode:child];
|
||||
}
|
||||
if (useManualDisable) {
|
||||
XCTAssertTrue([child __visibilityNotificationsDisabled], @"Should not have re-enabled yet");
|
||||
XCTAssertTrue([child __selfOrParentHasVisibilityNotificationsDisabled], @"Should not have re-enabled yet");
|
||||
ASDisplayNodeEnableHierarchyNotifications(child);
|
||||
}
|
||||
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:child], 1u, @"Should not have -willEnterHierarchy called when moving child around in hierarchy");
|
||||
|
||||
// Move subnode back to A
|
||||
if (useManualDisable) {
|
||||
ASDisplayNodeDisableHierarchyNotifications(child);
|
||||
}
|
||||
if (!useNodeAPI) {
|
||||
[child removeFromSupernode];
|
||||
[parentA insertSubnode:child atIndex:0];
|
||||
} else {
|
||||
[parentA insertSubnode:child atIndex:0];
|
||||
}
|
||||
if (useManualDisable) {
|
||||
XCTAssertTrue([child __visibilityNotificationsDisabled], @"Should not have re-enabled yet");
|
||||
XCTAssertTrue([child __selfOrParentHasVisibilityNotificationsDisabled], @"Should not have re-enabled yet");
|
||||
ASDisplayNodeEnableHierarchyNotifications(child);
|
||||
}
|
||||
|
||||
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:child], 1u, @"Should not have -willEnterHierarchy called when moving child around in hierarchy");
|
||||
|
||||
// Finally, remove subnode
|
||||
[child removeFromSupernode];
|
||||
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:child], 1u, @"Should appear and disappear just once");
|
||||
|
||||
// Make sure that we don't leave these unbalanced
|
||||
XCTAssertFalse([child __visibilityNotificationsDisabled], @"Unbalanced visibility notifications calls");
|
||||
XCTAssertFalse([child __selfOrParentHasVisibilityNotificationsDisabled], @"Should not have re-enabled yet");
|
||||
}
|
||||
|
||||
- (void)testMoveAcrossHierarchyLayer
|
||||
{
|
||||
[self checkMoveAcrossHierarchyLayerBacked:YES useManualCalls:NO useNodeAPI:YES];
|
||||
}
|
||||
|
||||
- (void)testMoveAcrossHierarchyView
|
||||
{
|
||||
[self checkMoveAcrossHierarchyLayerBacked:NO useManualCalls:NO useNodeAPI:YES];
|
||||
}
|
||||
|
||||
- (void)testMoveAcrossHierarchyManualLayer
|
||||
{
|
||||
[self checkMoveAcrossHierarchyLayerBacked:YES useManualCalls:YES useNodeAPI:NO];
|
||||
}
|
||||
|
||||
- (void)testMoveAcrossHierarchyManualView
|
||||
{
|
||||
[self checkMoveAcrossHierarchyLayerBacked:NO useManualCalls:YES useNodeAPI:NO];
|
||||
}
|
||||
|
||||
- (void)testDisableWithNodeAPILayer
|
||||
{
|
||||
[self checkMoveAcrossHierarchyLayerBacked:YES useManualCalls:YES useNodeAPI:YES];
|
||||
}
|
||||
|
||||
- (void)testDisableWithNodeAPIView
|
||||
{
|
||||
[self checkMoveAcrossHierarchyLayerBacked:NO useManualCalls:YES useNodeAPI:YES];
|
||||
}
|
||||
|
||||
- (void)testPreventManualAppearanceMethods
|
||||
{
|
||||
DeclareNodeNamed(n);
|
||||
|
||||
XCTAssertThrows([n willEnterHierarchy], @"Should not allow manually calling appearance methods.");
|
||||
XCTAssertThrows([n didExitHierarchy], @"Should not allow manually calling appearance methods.");
|
||||
}
|
||||
|
||||
@end
|
@ -1,76 +0,0 @@
|
||||
//
|
||||
// ASDisplayNodeExtrasTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
|
||||
|
||||
@interface ASDisplayNodeExtrasTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@interface TestDisplayNode : ASDisplayNode
|
||||
@end
|
||||
|
||||
@implementation TestDisplayNode
|
||||
@end
|
||||
|
||||
@implementation ASDisplayNodeExtrasTests
|
||||
|
||||
- (void)testShallowFindSubnodesOfSubclass {
|
||||
ASDisplayNode *supernode = [[ASDisplayNode alloc] initWithLayerBlock:^CALayer * _Nonnull{
|
||||
return [CALayer layer];
|
||||
}];
|
||||
NSUInteger count = 10;
|
||||
NSMutableArray *expected = [[NSMutableArray alloc] initWithCapacity:count];
|
||||
for (NSUInteger nodeIndex = 0; nodeIndex < count; nodeIndex++) {
|
||||
TestDisplayNode *node = [[TestDisplayNode alloc] initWithLayerBlock:^CALayer * _Nonnull{
|
||||
return [CALayer layer];
|
||||
}];
|
||||
[supernode addSubnode:node];
|
||||
[expected addObject:node];
|
||||
}
|
||||
NSArray *found = ASDisplayNodeFindAllSubnodesOfClass(supernode, [TestDisplayNode class]);
|
||||
XCTAssertEqualObjects(found, expected, @"Expecting %lu %@ nodes, found %lu", (unsigned long)count, [TestDisplayNode class], (unsigned long)found.count);
|
||||
}
|
||||
|
||||
- (void)testDeepFindSubnodesOfSubclass {
|
||||
ASDisplayNode *supernode = [[ASDisplayNode alloc] initWithLayerBlock:^CALayer * _Nonnull{
|
||||
return [CALayer layer];
|
||||
}];
|
||||
|
||||
const NSUInteger count = 2;
|
||||
const NSUInteger levels = 2;
|
||||
const NSUInteger capacity = [[self class] capacityForCount:count levels:levels];
|
||||
NSMutableArray *expected = [[NSMutableArray alloc] initWithCapacity:capacity];
|
||||
|
||||
[[self class] addSubnodesToNode:supernode number:count remainingLevels:levels accumulated:expected];
|
||||
|
||||
NSArray *found = ASDisplayNodeFindAllSubnodesOfClass(supernode, [TestDisplayNode class]);
|
||||
XCTAssertEqualObjects(found, expected, @"Expecting %lu %@ nodes, found %lu", (unsigned long)count, [TestDisplayNode class], (unsigned long)found.count);
|
||||
}
|
||||
|
||||
+ (void)addSubnodesToNode:(ASDisplayNode *)supernode number:(NSUInteger)number remainingLevels:(NSUInteger)level accumulated:(inout NSMutableArray *)expected {
|
||||
if (level == 0) return;
|
||||
for (NSUInteger nodeIndex = 0; nodeIndex < number; nodeIndex++) {
|
||||
TestDisplayNode *node = [[TestDisplayNode alloc] initWithLayerBlock:^CALayer * _Nonnull{
|
||||
return [CALayer layer];
|
||||
}];
|
||||
[supernode addSubnode:node];
|
||||
[expected addObject:node];
|
||||
[self addSubnodesToNode:node number:number remainingLevels:(level - 1) accumulated:expected];
|
||||
}
|
||||
}
|
||||
|
||||
// Graph theory is failing me atm.
|
||||
+ (NSUInteger)capacityForCount:(NSUInteger)count levels:(NSUInteger)level {
|
||||
if (level == 0) return 0;
|
||||
return pow(count, level) + [self capacityForCount:count levels:(level - 1)];
|
||||
}
|
||||
|
||||
@end
|
@ -1,329 +0,0 @@
|
||||
//
|
||||
// ASDisplayNodeImplicitHierarchyTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
|
||||
|
||||
#import "ASDisplayNodeTestsHelper.h"
|
||||
|
||||
@interface ASSpecTestDisplayNode : ASDisplayNode
|
||||
|
||||
/**
|
||||
Simple state identifier to allow control of current spec inside of the layoutSpecBlock
|
||||
*/
|
||||
@property (nonatomic) NSNumber *layoutState;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASSpecTestDisplayNode
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_layoutState = @1;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface ASDisplayNodeImplicitHierarchyTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASDisplayNodeImplicitHierarchyTests
|
||||
|
||||
- (void)testFeatureFlag
|
||||
{
|
||||
ASDisplayNode *node = [[ASDisplayNode alloc] init];
|
||||
XCTAssertFalse(node.automaticallyManagesSubnodes);
|
||||
|
||||
node.automaticallyManagesSubnodes = YES;
|
||||
XCTAssertTrue(node.automaticallyManagesSubnodes);
|
||||
}
|
||||
|
||||
- (void)testInitialNodeInsertionWithOrdering
|
||||
{
|
||||
static CGSize kSize = {100, 100};
|
||||
|
||||
ASDisplayNode *node1 = [[ASDisplayNode alloc] init];
|
||||
ASDisplayNode *node2 = [[ASDisplayNode alloc] init];
|
||||
ASDisplayNode *node3 = [[ASDisplayNode alloc] init];
|
||||
ASDisplayNode *node4 = [[ASDisplayNode alloc] init];
|
||||
ASDisplayNode *node5 = [[ASDisplayNode alloc] init];
|
||||
|
||||
|
||||
// As we will involve a stack spec we have to give the nodes an intrinsic content size
|
||||
node1.style.preferredSize = kSize;
|
||||
node2.style.preferredSize = kSize;
|
||||
node3.style.preferredSize = kSize;
|
||||
node4.style.preferredSize = kSize;
|
||||
node5.style.preferredSize = kSize;
|
||||
|
||||
ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init];
|
||||
node.automaticallyManagesSubnodes = YES;
|
||||
node.layoutSpecBlock = ^(ASDisplayNode *weakNode, ASSizeRange constrainedSize) {
|
||||
ASAbsoluteLayoutSpec *absoluteLayout = [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[node4]];
|
||||
|
||||
ASStackLayoutSpec *stack1 = [[ASStackLayoutSpec alloc] init];
|
||||
[stack1 setChildren:@[node1, node2]];
|
||||
|
||||
ASStackLayoutSpec *stack2 = [[ASStackLayoutSpec alloc] init];
|
||||
[stack2 setChildren:@[node3, absoluteLayout]];
|
||||
|
||||
return [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[stack1, stack2, node5]];
|
||||
};
|
||||
|
||||
ASDisplayNodeSizeToFitSizeRange(node, ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)));
|
||||
[node.view layoutIfNeeded];
|
||||
|
||||
XCTAssertEqual(node.subnodes[0], node1);
|
||||
XCTAssertEqual(node.subnodes[1], node2);
|
||||
XCTAssertEqual(node.subnodes[2], node3);
|
||||
XCTAssertEqual(node.subnodes[3], node4);
|
||||
XCTAssertEqual(node.subnodes[4], node5);
|
||||
}
|
||||
|
||||
- (void)testInitialNodeInsertionWhenEnterPreloadState
|
||||
{
|
||||
static CGSize kSize = {100, 100};
|
||||
|
||||
static NSInteger subnodeCount = 5;
|
||||
NSMutableArray<ASDisplayNode *> *subnodes = [NSMutableArray arrayWithCapacity:subnodeCount];
|
||||
for (NSInteger i = 0; i < subnodeCount; i++) {
|
||||
ASDisplayNode *subnode = [[ASDisplayNode alloc] init];
|
||||
// As we will involve a stack spec we have to give the nodes an intrinsic content size
|
||||
subnode.style.preferredSize = kSize;
|
||||
[subnodes addObject:subnode];
|
||||
}
|
||||
|
||||
ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init];
|
||||
[node setHierarchyState:ASHierarchyStateRangeManaged];
|
||||
node.automaticallyManagesSubnodes = YES;
|
||||
node.layoutSpecBlock = ^(ASDisplayNode *weakNode, ASSizeRange constrainedSize) {
|
||||
ASAbsoluteLayoutSpec *absoluteLayout = [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[subnodes[3]]];
|
||||
|
||||
ASStackLayoutSpec *stack1 = [[ASStackLayoutSpec alloc] init];
|
||||
[stack1 setChildren:@[subnodes[0], subnodes[1]]];
|
||||
|
||||
ASStackLayoutSpec *stack2 = [[ASStackLayoutSpec alloc] init];
|
||||
[stack2 setChildren:@[subnodes[2], absoluteLayout]];
|
||||
|
||||
return [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[stack1, stack2, subnodes[4]]];
|
||||
};
|
||||
|
||||
ASDisplayNodeSizeToFitSizeRange(node, ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)));
|
||||
[node recursivelySetInterfaceState:ASInterfaceStatePreload];
|
||||
|
||||
ASCATransactionQueueWait(nil);
|
||||
// No premature view allocation
|
||||
XCTAssertFalse(node.isNodeLoaded);
|
||||
// Subnodes should be inserted, laid out and entered preload state
|
||||
XCTAssertTrue([subnodes isEqualToArray:node.subnodes]);
|
||||
for (NSInteger i = 0; i < subnodeCount; i++) {
|
||||
ASDisplayNode *subnode = subnodes[i];
|
||||
XCTAssertTrue(CGSizeEqualToSize(kSize, subnode.bounds.size));
|
||||
XCTAssertTrue(ASInterfaceStateIncludesPreload(subnode.interfaceState));
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testCalculatedLayoutHierarchyTransitions
|
||||
{
|
||||
static CGSize kSize = {100, 100};
|
||||
|
||||
ASDisplayNode *node1 = [[ASDisplayNode alloc] init];
|
||||
ASDisplayNode *node2 = [[ASDisplayNode alloc] init];
|
||||
ASDisplayNode *node3 = [[ASDisplayNode alloc] init];
|
||||
|
||||
node1.debugName = @"a";
|
||||
node2.debugName = @"b";
|
||||
node3.debugName = @"c";
|
||||
|
||||
// As we will involve a stack spec we have to give the nodes an intrinsic content size
|
||||
node1.style.preferredSize = kSize;
|
||||
node2.style.preferredSize = kSize;
|
||||
node3.style.preferredSize = kSize;
|
||||
|
||||
ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init];
|
||||
node.automaticallyManagesSubnodes = YES;
|
||||
node.layoutSpecBlock = ^(ASDisplayNode *weakNode, ASSizeRange constrainedSize){
|
||||
ASSpecTestDisplayNode *strongNode = (ASSpecTestDisplayNode *)weakNode;
|
||||
if ([strongNode.layoutState isEqualToNumber:@1]) {
|
||||
return [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[node1, node2]];
|
||||
} else {
|
||||
ASStackLayoutSpec *stackLayout = [[ASStackLayoutSpec alloc] init];
|
||||
[stackLayout setChildren:@[node3, node2]];
|
||||
return [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[node1, stackLayout]];
|
||||
}
|
||||
};
|
||||
|
||||
ASDisplayNodeSizeToFitSizeRange(node, ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)));
|
||||
[node.view layoutIfNeeded];
|
||||
XCTAssertEqual(node.subnodes[0], node1);
|
||||
XCTAssertEqual(node.subnodes[1], node2);
|
||||
|
||||
node.layoutState = @2;
|
||||
[node setNeedsLayout]; // After a state change the layout needs to be invalidated
|
||||
[node.view layoutIfNeeded]; // A new layout pass will trigger the hiearchy transition
|
||||
|
||||
XCTAssertEqual(node.subnodes[0], node1);
|
||||
XCTAssertEqual(node.subnodes[1], node3);
|
||||
XCTAssertEqual(node.subnodes[2], node2);
|
||||
}
|
||||
|
||||
// Disable test for now as we disabled the assertion
|
||||
//- (void)testLayoutTransitionWillThrowForManualSubnodeManagement
|
||||
//{
|
||||
// ASDisplayNode *node1 = [[ASDisplayNode alloc] init];
|
||||
// node1.name = @"node1";
|
||||
//
|
||||
// ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init];
|
||||
// node.automaticallyManagesSubnodes = YES;
|
||||
// node.layoutSpecBlock = ^ASLayoutSpec *(ASDisplayNode *weakNode, ASSizeRange constrainedSize){
|
||||
// return [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[node1]];
|
||||
// };
|
||||
//
|
||||
// XCTAssertNoThrow([node layoutThatFits:ASSizeRangeMake(CGSizeZero)]);
|
||||
// XCTAssertThrows([node1 removeFromSupernode]);
|
||||
//}
|
||||
|
||||
- (void)testLayoutTransitionMeasurementCompletionBlockIsCalledOnMainThread
|
||||
{
|
||||
const CGSize kSize = CGSizeMake(100, 100);
|
||||
|
||||
ASDisplayNode *displayNode = [[ASDisplayNode alloc] init];
|
||||
displayNode.style.preferredSize = kSize;
|
||||
|
||||
// Trigger explicit view creation to be able to use the Transition API
|
||||
[displayNode view];
|
||||
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"Call measurement completion block on main"];
|
||||
|
||||
[displayNode transitionLayoutWithSizeRange:ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY)) animated:YES shouldMeasureAsync:YES measurementCompletion:^{
|
||||
XCTAssertTrue(ASDisplayNodeThreadIsMain(), @"Measurement completion block should be called on main thread");
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectationsWithTimeout:2.0 handler:nil];
|
||||
}
|
||||
|
||||
- (void)testMeasurementInBackgroundThreadWithLoadedNode
|
||||
{
|
||||
const CGSize kNodeSize = CGSizeMake(100, 100);
|
||||
ASDisplayNode *node1 = [[ASDisplayNode alloc] init];
|
||||
ASDisplayNode *node2 = [[ASDisplayNode alloc] init];
|
||||
|
||||
ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init];
|
||||
node.style.preferredSize = kNodeSize;
|
||||
node.automaticallyManagesSubnodes = YES;
|
||||
node.layoutSpecBlock = ^(ASDisplayNode *weakNode, ASSizeRange constrainedSize) {
|
||||
ASSpecTestDisplayNode *strongNode = (ASSpecTestDisplayNode *)weakNode;
|
||||
if ([strongNode.layoutState isEqualToNumber:@1]) {
|
||||
return [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[node1]];
|
||||
} else {
|
||||
return [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[node2]];
|
||||
}
|
||||
};
|
||||
|
||||
// Intentionally trigger view creation
|
||||
[node view];
|
||||
[node2 view];
|
||||
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"Fix IHM layout also if one node is already loaded"];
|
||||
|
||||
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
|
||||
// Measurement happens in the background
|
||||
ASDisplayNodeSizeToFitSizeRange(node, ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY)));
|
||||
|
||||
// Dispatch back to the main thread to let the insertion / deletion of subnodes happening
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
|
||||
// Layout on main
|
||||
[node setNeedsLayout];
|
||||
[node.view layoutIfNeeded];
|
||||
XCTAssertEqual(node.subnodes[0], node1);
|
||||
|
||||
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
|
||||
// Change state and measure in the background
|
||||
node.layoutState = @2;
|
||||
[node setNeedsLayout];
|
||||
|
||||
ASDisplayNodeSizeToFitSizeRange(node, ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY)));
|
||||
|
||||
// Dispatch back to the main thread to let the insertion / deletion of subnodes happening
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
|
||||
// Layout on main again
|
||||
[node.view layoutIfNeeded];
|
||||
XCTAssertEqual(node.subnodes[0], node2);
|
||||
|
||||
[expectation fulfill];
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
[self waitForExpectationsWithTimeout:5.0 handler:^(NSError *error) {
|
||||
if (error) {
|
||||
NSLog(@"Timeout Error: %@", error);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)testTransitionLayoutWithAnimationWithLoadedNodes
|
||||
{
|
||||
const CGSize kNodeSize = CGSizeMake(100, 100);
|
||||
ASDisplayNode *node1 = [[ASDisplayNode alloc] init];
|
||||
ASDisplayNode *node2 = [[ASDisplayNode alloc] init];
|
||||
|
||||
ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init];
|
||||
node.automaticallyManagesSubnodes = YES;
|
||||
node.style.preferredSize = kNodeSize;
|
||||
node.layoutSpecBlock = ^(ASDisplayNode *weakNode, ASSizeRange constrainedSize) {
|
||||
ASSpecTestDisplayNode *strongNode = (ASSpecTestDisplayNode *)weakNode;
|
||||
if ([strongNode.layoutState isEqualToNumber:@1]) {
|
||||
return [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[node1]];
|
||||
} else {
|
||||
return [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[node2]];
|
||||
}
|
||||
};
|
||||
|
||||
// Intentionally trigger view creation
|
||||
[node1 view];
|
||||
[node2 view];
|
||||
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"Fix IHM layout transition also if one node is already loaded"];
|
||||
|
||||
ASDisplayNodeSizeToFitSizeRange(node, ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY)));
|
||||
[node.view layoutIfNeeded];
|
||||
XCTAssertEqual(node.subnodes[0], node1);
|
||||
|
||||
node.layoutState = @2;
|
||||
[node invalidateCalculatedLayout];
|
||||
[node transitionLayoutWithAnimation:YES shouldMeasureAsync:YES measurementCompletion:^{
|
||||
// Push this to the next runloop to let async insertion / removing of nodes finished before checking
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
XCTAssertEqual(node.subnodes[0], node2);
|
||||
[expectation fulfill];
|
||||
});
|
||||
}];
|
||||
|
||||
[self waitForExpectationsWithTimeout:5.0 handler:^(NSError *error) {
|
||||
if (error) {
|
||||
NSLog(@"Timeout Error: %@", error);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
@ -1,175 +0,0 @@
|
||||
//
|
||||
// ASDisplayNodeLayoutTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASXCTExtensions.h"
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
|
||||
#import <stdatomic.h>
|
||||
|
||||
#import "ASLayoutSpecSnapshotTestsHelper.h"
|
||||
|
||||
@interface ASDisplayNodeLayoutTests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation ASDisplayNodeLayoutTests
|
||||
|
||||
- (void)testMeasureOnLayoutIfNotHappenedBefore
|
||||
{
|
||||
CGSize nodeSize = CGSizeMake(100, 100);
|
||||
|
||||
ASDisplayNode *displayNode = [[ASDisplayNode alloc] init];
|
||||
displayNode.style.width = ASDimensionMake(100);
|
||||
displayNode.style.height = ASDimensionMake(100);
|
||||
|
||||
// Use a button node in here as ASButtonNode uses layoutSpecThatFits:
|
||||
ASButtonNode *buttonNode = [ASButtonNode new];
|
||||
[displayNode addSubnode:buttonNode];
|
||||
|
||||
displayNode.frame = {.size = nodeSize};
|
||||
buttonNode.frame = {.size = nodeSize};
|
||||
|
||||
ASXCTAssertEqualSizes(displayNode.calculatedSize, CGSizeZero, @"Calculated size before measurement and layout should be 0");
|
||||
ASXCTAssertEqualSizes(buttonNode.calculatedSize, CGSizeZero, @"Calculated size before measurement and layout should be 0");
|
||||
|
||||
// Trigger view creation and layout pass without a manual -layoutThatFits: call before so the automatic measurement
|
||||
// pass will trigger in the layout pass
|
||||
[displayNode.view layoutIfNeeded];
|
||||
|
||||
ASXCTAssertEqualSizes(displayNode.calculatedSize, nodeSize, @"Automatic measurement pass should have happened in layout pass");
|
||||
ASXCTAssertEqualSizes(buttonNode.calculatedSize, nodeSize, @"Automatic measurement pass should have happened in layout pass");
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
- (void)testNotAllowAddingSubnodesInLayoutSpecThatFits
|
||||
{
|
||||
ASDisplayNode *displayNode = [ASDisplayNode new];
|
||||
ASDisplayNode *someOtherNode = [ASDisplayNode new];
|
||||
|
||||
displayNode.layoutSpecBlock = ^(ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) {
|
||||
[node addSubnode:someOtherNode];
|
||||
return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsZero child:someOtherNode];
|
||||
};
|
||||
|
||||
XCTAssertThrows([displayNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(100, 100))], @"Should throw if subnode was added in layoutSpecThatFits:");
|
||||
}
|
||||
|
||||
- (void)testNotAllowModifyingSubnodesInLayoutSpecThatFits
|
||||
{
|
||||
ASDisplayNode *displayNode = [ASDisplayNode new];
|
||||
ASDisplayNode *someOtherNode = [ASDisplayNode new];
|
||||
|
||||
[displayNode addSubnode:someOtherNode];
|
||||
|
||||
displayNode.layoutSpecBlock = ^(ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) {
|
||||
[someOtherNode removeFromSupernode];
|
||||
[node addSubnode:[ASDisplayNode new]];
|
||||
return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsZero child:someOtherNode];
|
||||
};
|
||||
|
||||
XCTAssertThrows([displayNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(100, 100))], @"Should throw if subnodes where modified in layoutSpecThatFits:");
|
||||
}
|
||||
#endif
|
||||
|
||||
- (void)testMeasureOnLayoutIfNotHappenedBeforeNoRemeasureForSameBounds
|
||||
{
|
||||
CGSize nodeSize = CGSizeMake(100, 100);
|
||||
|
||||
ASDisplayNode *displayNode = [ASDisplayNode new];
|
||||
displayNode.style.width = ASDimensionMake(nodeSize.width);
|
||||
displayNode.style.height = ASDimensionMake(nodeSize.height);
|
||||
|
||||
ASButtonNode *buttonNode = [ASButtonNode new];
|
||||
[displayNode addSubnode:buttonNode];
|
||||
|
||||
__block atomic_int numberOfLayoutSpecThatFitsCalls = ATOMIC_VAR_INIT(0);
|
||||
displayNode.layoutSpecBlock = ^(ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) {
|
||||
atomic_fetch_add(&numberOfLayoutSpecThatFitsCalls, 1);
|
||||
return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsZero child:buttonNode];
|
||||
};
|
||||
|
||||
displayNode.frame = {.size = nodeSize};
|
||||
|
||||
// Trigger initial layout pass without a measurement pass before
|
||||
[displayNode.view layoutIfNeeded];
|
||||
XCTAssertEqual(numberOfLayoutSpecThatFitsCalls, 1, @"Should measure during layout if not measured");
|
||||
|
||||
[displayNode layoutThatFits:ASSizeRangeMake(nodeSize, nodeSize)];
|
||||
XCTAssertEqual(numberOfLayoutSpecThatFitsCalls, 1, @"Should not remeasure with same bounds");
|
||||
}
|
||||
|
||||
- (void)testThatLayoutWithInvalidSizeCausesException
|
||||
{
|
||||
ASDisplayNode *displayNode = [[ASDisplayNode alloc] init];
|
||||
ASDisplayNode *node = [[ASDisplayNode alloc] init];
|
||||
node.layoutSpecBlock = ^ASLayoutSpec *(ASDisplayNode *node, ASSizeRange constrainedSize) {
|
||||
return [ASWrapperLayoutSpec wrapperWithLayoutElement:displayNode];
|
||||
};
|
||||
|
||||
XCTAssertThrows([node layoutThatFits:ASSizeRangeMake(CGSizeMake(0, FLT_MAX))]);
|
||||
}
|
||||
|
||||
- (void)testThatLayoutCreatedWithInvalidSizeCausesException
|
||||
{
|
||||
ASDisplayNode *displayNode = [[ASDisplayNode alloc] init];
|
||||
XCTAssertThrows([ASLayout layoutWithLayoutElement:displayNode size:CGSizeMake(FLT_MAX, FLT_MAX)]);
|
||||
XCTAssertThrows([ASLayout layoutWithLayoutElement:displayNode size:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)]);
|
||||
XCTAssertThrows([ASLayout layoutWithLayoutElement:displayNode size:CGSizeMake(INFINITY, INFINITY)]);
|
||||
}
|
||||
|
||||
- (void)testThatLayoutElementCreatedInLayoutSpecThatFitsDoNotGetDeallocated
|
||||
{
|
||||
const CGSize kSize = CGSizeMake(300, 300);
|
||||
|
||||
ASDisplayNode *subNode = [[ASDisplayNode alloc] init];
|
||||
subNode.automaticallyManagesSubnodes = YES;
|
||||
subNode.layoutSpecBlock = ^(ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) {
|
||||
ASTextNode *textNode = [ASTextNode new];
|
||||
textNode.attributedText = [[NSAttributedString alloc] initWithString:@"Test Test Test Test Test Test Test Test"];
|
||||
ASInsetLayoutSpec *insetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsZero child:textNode];
|
||||
return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsZero child:insetSpec];
|
||||
};
|
||||
|
||||
ASDisplayNode *rootNode = [[ASDisplayNode alloc] init];
|
||||
rootNode.automaticallyManagesSubnodes = YES;
|
||||
rootNode.layoutSpecBlock = ^(ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) {
|
||||
ASTextNode *textNode = [ASTextNode new];
|
||||
textNode.attributedText = [[NSAttributedString alloc] initWithString:@"Test Test Test Test Test"];
|
||||
ASInsetLayoutSpec *insetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsZero child:textNode];
|
||||
|
||||
return [ASStackLayoutSpec
|
||||
stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical
|
||||
spacing:0.0
|
||||
justifyContent:ASStackLayoutJustifyContentStart
|
||||
alignItems:ASStackLayoutAlignItemsStretch
|
||||
children:@[insetSpec, subNode]];
|
||||
};
|
||||
|
||||
rootNode.frame = CGRectMake(0, 0, kSize.width, kSize.height);
|
||||
[rootNode view];
|
||||
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"Execute measure and layout pass"];
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
|
||||
[rootNode layoutThatFits:ASSizeRangeMake(kSize)];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
XCTAssertNoThrow([rootNode.view layoutIfNeeded]);
|
||||
[expectation fulfill];
|
||||
});
|
||||
});
|
||||
|
||||
[self waitForExpectationsWithTimeout:5.0 handler:^(NSError *error) {
|
||||
if (error) {
|
||||
XCTFail(@"Expectation failed: %@", error);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
@ -1,36 +0,0 @@
|
||||
//
|
||||
// ASDisplayNodeSnapshotTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASSnapshotTestCase.h"
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
|
||||
@interface ASDisplayNodeSnapshotTests : ASSnapshotTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASDisplayNodeSnapshotTests
|
||||
|
||||
- (void)testBasicHierarchySnapshotTesting
|
||||
{
|
||||
ASDisplayNode *node = [[ASDisplayNode alloc] init];
|
||||
node.backgroundColor = [UIColor blueColor];
|
||||
|
||||
ASTextNode *subnode = [[ASTextNode alloc] init];
|
||||
subnode.backgroundColor = [UIColor whiteColor];
|
||||
|
||||
subnode.attributedText = [[NSAttributedString alloc] initWithString:@"Hello"];
|
||||
node.automaticallyManagesSubnodes = YES;
|
||||
node.layoutSpecBlock = ^(ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) {
|
||||
return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(5, 5, 5, 5) child:subnode];
|
||||
};
|
||||
|
||||
ASDisplayNodeSizeToFitSizeRange(node, ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY)));
|
||||
ASSnapshotVerifyNode(node, nil);
|
||||
}
|
||||
|
||||
@end
|
File diff suppressed because it is too large
Load Diff
@ -1,21 +0,0 @@
|
||||
//
|
||||
// ASDisplayNodeTestsHelper.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <AsyncDisplayKit/ASDimension.h>
|
||||
|
||||
@class ASCATransactionQueue, ASDisplayNode;
|
||||
|
||||
typedef BOOL (^as_condition_block_t)(void);
|
||||
|
||||
AS_EXTERN BOOL ASDisplayNodeRunRunLoopUntilBlockIsTrue(as_condition_block_t block);
|
||||
|
||||
AS_EXTERN void ASDisplayNodeSizeToFitSize(ASDisplayNode *node, CGSize size);
|
||||
AS_EXTERN void ASDisplayNodeSizeToFitSizeRange(ASDisplayNode *node, ASSizeRange sizeRange);
|
||||
AS_EXTERN void ASCATransactionQueueWait(ASCATransactionQueue *q); // nil means shared queue
|
@ -1,69 +0,0 @@
|
||||
//
|
||||
// ASDisplayNodeTestsHelper.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASDisplayNodeTestsHelper.h"
|
||||
#import <AsyncDisplayKit/ASDisplayNode.h>
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
#import <AsyncDisplayKit/ASRunLoopQueue.h>
|
||||
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
|
||||
#import <libkern/OSAtomic.h>
|
||||
|
||||
// Poll the condition 1000 times a second.
|
||||
static CFTimeInterval kSingleRunLoopTimeout = 0.001;
|
||||
|
||||
// Time out after 30 seconds.
|
||||
static CFTimeInterval kTimeoutInterval = 30.0f;
|
||||
|
||||
BOOL ASDisplayNodeRunRunLoopUntilBlockIsTrue(as_condition_block_t block)
|
||||
{
|
||||
CFTimeInterval timeoutDate = CACurrentMediaTime() + kTimeoutInterval;
|
||||
BOOL passed = NO;
|
||||
while (true) {
|
||||
OSMemoryBarrier();
|
||||
passed = block();
|
||||
OSMemoryBarrier();
|
||||
if (passed) {
|
||||
break;
|
||||
}
|
||||
CFTimeInterval now = CACurrentMediaTime();
|
||||
if (now > timeoutDate) {
|
||||
break;
|
||||
}
|
||||
// Run until the poll timeout or until timeoutDate, whichever is first.
|
||||
CFTimeInterval runLoopTimeout = MIN(kSingleRunLoopTimeout, timeoutDate - now);
|
||||
CFRunLoopRunInMode(kCFRunLoopDefaultMode, runLoopTimeout, true);
|
||||
}
|
||||
return passed;
|
||||
}
|
||||
|
||||
void ASDisplayNodeSizeToFitSize(ASDisplayNode *node, CGSize size)
|
||||
{
|
||||
CGSize sizeThatFits = [node layoutThatFits:ASSizeRangeMake(size)].size;
|
||||
node.bounds = (CGRect){.origin = CGPointZero, .size = sizeThatFits};
|
||||
}
|
||||
|
||||
void ASDisplayNodeSizeToFitSizeRange(ASDisplayNode *node, ASSizeRange sizeRange)
|
||||
{
|
||||
CGSize sizeThatFits = [node layoutThatFits:sizeRange].size;
|
||||
node.bounds = (CGRect){.origin = CGPointZero, .size = sizeThatFits};
|
||||
}
|
||||
|
||||
void ASCATransactionQueueWait(ASCATransactionQueue *q)
|
||||
{
|
||||
if (!q) { q = ASCATransactionQueueGet(); }
|
||||
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:1];
|
||||
BOOL whileResult = YES;
|
||||
while ([date timeIntervalSinceNow] > 0 &&
|
||||
(whileResult = ![q isEmpty])) {
|
||||
[[NSRunLoop currentRunLoop] runUntilDate:
|
||||
[NSDate dateWithTimeIntervalSinceNow:0.01]];
|
||||
}
|
||||
}
|
@ -1,303 +0,0 @@
|
||||
//
|
||||
// ASDisplayViewAccessibilityTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASDisplayNode.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
|
||||
#import <AsyncDisplayKit/ASTextNode.h>
|
||||
#import <AsyncDisplayKit/ASConfiguration.h>
|
||||
#import <AsyncDisplayKit/ASConfigurationInternal.h>
|
||||
#import <OCMock/OCMock.h>
|
||||
|
||||
@interface ASDisplayViewAccessibilityTests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation ASDisplayViewAccessibilityTests
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
ASConfiguration *config = [[ASConfiguration alloc] initWithDictionary:nil];
|
||||
config.experimentalFeatures = ASExperimentalDisableAccessibilityCache;
|
||||
[ASConfigurationManager test_resetWithConfiguration:config];
|
||||
}
|
||||
|
||||
- (void)testAccessibilityElementsAccessors
|
||||
{
|
||||
// Setup nodes with accessibility info
|
||||
ASDisplayNode *node = nil;
|
||||
ASDisplayNode *subnode = nil;
|
||||
node = [[ASDisplayNode alloc] init];
|
||||
subnode = [[ASDisplayNode alloc] init];
|
||||
NSString *label = @"foo";
|
||||
subnode.isAccessibilityElement = YES;
|
||||
subnode.accessibilityLabel = label;
|
||||
[node addSubnode:subnode];
|
||||
XCTAssertEqualObjects([node.view.accessibilityElements.firstObject accessibilityLabel], label);
|
||||
// NOTE: The following tests will fail unless accessibility is enabled, e.g. by turning the
|
||||
// accessibility inspector on. See https://github.com/TextureGroup/Texture/pull/1069 for details.
|
||||
/*XCTAssertEqualObjects([[node.view accessibilityElementAtIndex:0] accessibilityLabel], label);
|
||||
XCTAssertEqual(node.view.accessibilityElementCount, 1);
|
||||
XCTAssertEqual([node.view indexOfAccessibilityElement:node.view.accessibilityElements.firstObject], 0);*/
|
||||
}
|
||||
|
||||
- (void)testThatSubnodeAccessibilityLabelAggregationWorks
|
||||
{
|
||||
// Setup nodes
|
||||
ASDisplayNode *node = nil;
|
||||
ASDisplayNode *innerNode1 = nil;
|
||||
ASDisplayNode *innerNode2 = nil;
|
||||
node = [[ASDisplayNode alloc] init];
|
||||
innerNode1 = [[ASDisplayNode alloc] init];
|
||||
innerNode2 = [[ASDisplayNode alloc] init];
|
||||
|
||||
// Initialize nodes with relevant accessibility data
|
||||
node.isAccessibilityContainer = YES;
|
||||
innerNode1.accessibilityLabel = @"hello";
|
||||
innerNode2.accessibilityLabel = @"world";
|
||||
|
||||
// Attach the subnodes to the parent node, then ensure their accessibility labels have been'
|
||||
// aggregated to the parent's accessibility label
|
||||
[node addSubnode:innerNode1];
|
||||
[node addSubnode:innerNode2];
|
||||
XCTAssertEqualObjects([node.view.accessibilityElements.firstObject accessibilityLabel],
|
||||
@"hello, world", @"Subnode accessibility label aggregation broken %@",
|
||||
[node.view.accessibilityElements.firstObject accessibilityLabel]);
|
||||
}
|
||||
|
||||
- (void)testThatContainerAccessibilityLabelOverrideStopsAggregation
|
||||
{
|
||||
// Setup nodes
|
||||
ASDisplayNode *node = nil;
|
||||
ASDisplayNode *innerNode = nil;
|
||||
node = [[ASDisplayNode alloc] init];
|
||||
innerNode = [[ASDisplayNode alloc] init];
|
||||
|
||||
// Initialize nodes with relevant accessibility data
|
||||
node.isAccessibilityContainer = YES;
|
||||
node.accessibilityLabel = @"hello";
|
||||
innerNode.accessibilityLabel = @"world";
|
||||
|
||||
// Attach the subnode to the parent node, then ensure the parent's accessibility label does not
|
||||
// get aggregated with the subnode's label
|
||||
[node addSubnode:innerNode];
|
||||
XCTAssertEqualObjects([node.view.accessibilityElements.firstObject accessibilityLabel], @"hello",
|
||||
@"Container accessibility label override broken %@",
|
||||
[node.view.accessibilityElements.firstObject accessibilityLabel]);
|
||||
}
|
||||
|
||||
- (void)testAccessibilityLayerbackedNodesOperationInContainer {
|
||||
ASDisplayNode *contianer = [[ASDisplayNode alloc] init];
|
||||
contianer.frame = CGRectMake(50, 50, 200, 400);
|
||||
contianer.backgroundColor = [UIColor grayColor];
|
||||
contianer.isAccessibilityContainer = YES;
|
||||
// Do any additional setup after loading the view, typically from a nib.
|
||||
ASTextNode *text1 = [[ASTextNode alloc] init];
|
||||
text1.layerBacked = YES;
|
||||
text1.attributedText = [[NSAttributedString alloc] initWithString:@"hello"];
|
||||
text1.frame = CGRectMake(50, 100, 200, 200);
|
||||
[contianer addSubnode:text1];
|
||||
[contianer layoutIfNeeded];
|
||||
[contianer.layer displayIfNeeded];
|
||||
NSArray<UIAccessibilityElement *> *elements = contianer.view.accessibilityElements;
|
||||
XCTAssertTrue(elements.count == 1);
|
||||
XCTAssertTrue([[elements.firstObject accessibilityLabel] isEqualToString:@"hello"]);
|
||||
ASTextNode *text2 = [[ASTextNode alloc] init];
|
||||
text2.layerBacked = YES;
|
||||
text2.attributedText = [[NSAttributedString alloc] initWithString:@"world"];
|
||||
text2.frame = CGRectMake(50, 300, 200, 200);
|
||||
[contianer addSubnode:text2];
|
||||
[contianer layoutIfNeeded];
|
||||
[contianer.layer displayIfNeeded];
|
||||
NSArray<UIAccessibilityElement *> *updatedElements = contianer.view.accessibilityElements;
|
||||
XCTAssertTrue(updatedElements.count == 1);
|
||||
XCTAssertTrue([[updatedElements.firstObject accessibilityLabel] isEqualToString:@"hello, world"]);
|
||||
ASTextNode *text3 = [[ASTextNode alloc] init];
|
||||
text3.attributedText = [[NSAttributedString alloc] initWithString:@"!!!!"];
|
||||
text3.frame = CGRectMake(50, 400, 200, 100);
|
||||
text3.layerBacked = YES;
|
||||
[text2 addSubnode:text3];
|
||||
[contianer layoutIfNeeded];
|
||||
[contianer.layer displayIfNeeded];
|
||||
NSArray<UIAccessibilityElement *> *updatedElements2 = contianer.view.accessibilityElements;
|
||||
XCTAssertTrue([[updatedElements2.firstObject accessibilityLabel] isEqualToString:@"hello, world, !!!!"]);
|
||||
}
|
||||
|
||||
- (void)testAccessibilityNonLayerbackedNodesOperationInContainer
|
||||
{
|
||||
ASDisplayNode *contianer = [[ASDisplayNode alloc] init];
|
||||
contianer.frame = CGRectMake(50, 50, 200, 600);
|
||||
contianer.backgroundColor = [UIColor grayColor];
|
||||
contianer.isAccessibilityContainer = YES;
|
||||
// Do any additional setup after loading the view, typically from a nib.
|
||||
ASTextNode *text1 = [[ASTextNode alloc] init];
|
||||
text1.attributedText = [[NSAttributedString alloc] initWithString:@"hello"];
|
||||
text1.frame = CGRectMake(50, 100, 200, 200);
|
||||
[contianer addSubnode:text1];
|
||||
[contianer layoutIfNeeded];
|
||||
[contianer.layer displayIfNeeded];
|
||||
NSArray<UIAccessibilityElement *> *elements = contianer.view.accessibilityElements;
|
||||
XCTAssertTrue(elements.count == 1);
|
||||
XCTAssertTrue([[elements.firstObject accessibilityLabel] isEqualToString:@"hello"]);
|
||||
ASTextNode *text2 = [[ASTextNode alloc] init];
|
||||
text2.attributedText = [[NSAttributedString alloc] initWithString:@"world"];
|
||||
text2.frame = CGRectMake(50, 300, 200, 200);
|
||||
[contianer addSubnode:text2];
|
||||
[contianer layoutIfNeeded];
|
||||
[contianer.layer displayIfNeeded];
|
||||
NSArray<UIAccessibilityElement *> *updatedElements = contianer.view.accessibilityElements;
|
||||
XCTAssertTrue(updatedElements.count == 1);
|
||||
XCTAssertTrue([[updatedElements.firstObject accessibilityLabel] isEqualToString:@"hello, world"]);
|
||||
ASTextNode *text3 = [[ASTextNode alloc] init];
|
||||
text3.attributedText = [[NSAttributedString alloc] initWithString:@"!!!!"];
|
||||
text3.frame = CGRectMake(50, 400, 200, 100);
|
||||
[text2 addSubnode:text3];
|
||||
[contianer layoutIfNeeded];
|
||||
[contianer.layer displayIfNeeded];
|
||||
NSArray<UIAccessibilityElement *> *updatedElements2 = contianer.view.accessibilityElements;
|
||||
XCTAssertTrue([[updatedElements2.firstObject accessibilityLabel] isEqualToString:@"hello, world, !!!!"]);
|
||||
}
|
||||
|
||||
- (void)testAccessibilityNonLayerbackedNodesOperationInNonContainer
|
||||
{
|
||||
ASDisplayNode *contianer = [[ASDisplayNode alloc] init];
|
||||
contianer.frame = CGRectMake(50, 50, 200, 600);
|
||||
contianer.backgroundColor = [UIColor grayColor];
|
||||
// Do any additional setup after loading the view, typically from a nib.
|
||||
ASTextNode *text1 = [[ASTextNode alloc] init];
|
||||
text1.attributedText = [[NSAttributedString alloc] initWithString:@"hello"];
|
||||
text1.frame = CGRectMake(50, 100, 200, 200);
|
||||
[contianer addSubnode:text1];
|
||||
[contianer layoutIfNeeded];
|
||||
[contianer.layer displayIfNeeded];
|
||||
NSArray<UIAccessibilityElement *> *elements = contianer.view.accessibilityElements;
|
||||
XCTAssertTrue(elements.count == 1);
|
||||
XCTAssertTrue([[elements.firstObject accessibilityLabel] isEqualToString:@"hello"]);
|
||||
ASTextNode *text2 = [[ASTextNode alloc] init];
|
||||
text2.attributedText = [[NSAttributedString alloc] initWithString:@"world"];
|
||||
text2.frame = CGRectMake(50, 300, 200, 200);
|
||||
[contianer addSubnode:text2];
|
||||
[contianer layoutIfNeeded];
|
||||
[contianer.layer displayIfNeeded];
|
||||
NSArray<UIAccessibilityElement *> *updatedElements = contianer.view.accessibilityElements;
|
||||
XCTAssertTrue(updatedElements.count == 2);
|
||||
XCTAssertTrue([[updatedElements.firstObject accessibilityLabel] isEqualToString:@"hello"]);
|
||||
XCTAssertTrue([[updatedElements.lastObject accessibilityLabel] isEqualToString:@"world"]);
|
||||
ASTextNode *text3 = [[ASTextNode alloc] init];
|
||||
text3.attributedText = [[NSAttributedString alloc] initWithString:@"!!!!"];
|
||||
text3.frame = CGRectMake(50, 400, 200, 100);
|
||||
[text2 addSubnode:text3];
|
||||
[contianer layoutIfNeeded];
|
||||
[contianer.layer displayIfNeeded];
|
||||
NSArray<UIAccessibilityElement *> *updatedElements2 = contianer.view.accessibilityElements;
|
||||
//text3 won't be read out cause it's overshadowed by text2
|
||||
XCTAssertTrue(updatedElements2.count == 2);
|
||||
XCTAssertTrue([[updatedElements2.firstObject accessibilityLabel] isEqualToString:@"hello"]);
|
||||
XCTAssertTrue([[updatedElements2.lastObject accessibilityLabel] isEqualToString:@"world"]);
|
||||
}
|
||||
- (void)testAccessibilityLayerbackedNodesOperationInNonContainer
|
||||
{
|
||||
ASDisplayNode *contianer = [[ASDisplayNode alloc] init];
|
||||
contianer.frame = CGRectMake(50, 50, 200, 600);
|
||||
contianer.backgroundColor = [UIColor grayColor];
|
||||
// Do any additional setup after loading the view, typically from a nib.
|
||||
ASTextNode *text1 = [[ASTextNode alloc] init];
|
||||
text1.layerBacked = YES;
|
||||
text1.attributedText = [[NSAttributedString alloc] initWithString:@"hello"];
|
||||
text1.frame = CGRectMake(50, 0, 100, 100);
|
||||
[contianer addSubnode:text1];
|
||||
[contianer layoutIfNeeded];
|
||||
[contianer.layer displayIfNeeded];
|
||||
NSArray<UIAccessibilityElement *> *elements = contianer.view.accessibilityElements;
|
||||
XCTAssertTrue(elements.count == 1);
|
||||
XCTAssertTrue([[elements.firstObject accessibilityLabel] isEqualToString:@"hello"]);
|
||||
ASTextNode *text2 = [[ASTextNode alloc] init];
|
||||
text2.layerBacked = YES;
|
||||
text2.attributedText = [[NSAttributedString alloc] initWithString:@"world"];
|
||||
text2.frame = CGRectMake(50, 100, 100, 100);
|
||||
[contianer addSubnode:text2];
|
||||
[contianer layoutIfNeeded];
|
||||
[contianer.layer displayIfNeeded];
|
||||
NSArray<UIAccessibilityElement *> *updatedElements = contianer.view.accessibilityElements;
|
||||
XCTAssertTrue(updatedElements.count == 2);
|
||||
XCTAssertTrue([[updatedElements.firstObject accessibilityLabel] isEqualToString:@"hello"]);
|
||||
XCTAssertTrue([[updatedElements.lastObject accessibilityLabel] isEqualToString:@"world"]);
|
||||
ASTextNode *text3 = [[ASTextNode alloc] init];
|
||||
text3.layerBacked = YES;
|
||||
text3.attributedText = [[NSAttributedString alloc] initWithString:@"!!!!"];
|
||||
text3.frame = CGRectMake(50, 200, 100, 100);
|
||||
[text2 addSubnode:text3];
|
||||
[contianer layoutIfNeeded];
|
||||
[contianer.layer displayIfNeeded];
|
||||
NSArray<UIAccessibilityElement *> *updatedElements2 = contianer.view.accessibilityElements;
|
||||
//text3 won't be read out cause it's overshadowed by text2
|
||||
XCTAssertTrue(updatedElements2.count == 2);
|
||||
XCTAssertTrue([[updatedElements2.firstObject accessibilityLabel] isEqualToString:@"hello"]);
|
||||
XCTAssertTrue([[updatedElements2.lastObject accessibilityLabel] isEqualToString:@"world"]);
|
||||
}
|
||||
|
||||
- (void)testAccessibilityUpdatesWithElementsChanges
|
||||
{
|
||||
ASDisplayNode *contianer = [[ASDisplayNode alloc] init];
|
||||
contianer.frame = CGRectMake(50, 50, 200, 600);
|
||||
contianer.backgroundColor = [UIColor grayColor];
|
||||
contianer.isAccessibilityContainer = YES;
|
||||
// Do any additional setup after loading the view, typically from a nib.
|
||||
ASTextNode *text1 = [[ASTextNode alloc] init];
|
||||
text1.layerBacked = YES;
|
||||
text1.attributedText = [[NSAttributedString alloc] initWithString:@"hello"];
|
||||
text1.frame = CGRectMake(50, 0, 100, 100);
|
||||
[contianer addSubnode:text1];
|
||||
[contianer layoutIfNeeded];
|
||||
[contianer.layer displayIfNeeded];
|
||||
NSArray<UIAccessibilityElement *> *elements = contianer.view.accessibilityElements;
|
||||
XCTAssertTrue(elements.count == 1);
|
||||
XCTAssertTrue([[elements.firstObject accessibilityLabel] isEqualToString:@"hello"]);
|
||||
text1.attributedText = [[NSAttributedString alloc] initWithString:@"greeting"];
|
||||
[contianer layoutIfNeeded];
|
||||
[contianer.layer displayIfNeeded];
|
||||
NSArray<UIAccessibilityElement *> *elements2 = contianer.view.accessibilityElements;
|
||||
XCTAssertTrue(elements2.count == 1);
|
||||
XCTAssertTrue([[elements2.firstObject accessibilityLabel] isEqualToString:@"greeting"]);
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark UIAccessibilityAction Forwarding
|
||||
|
||||
- (void)testActionForwarding {
|
||||
ASDisplayNode *node = [ASDisplayNode new];
|
||||
UIView *view = node.view;
|
||||
|
||||
id mockNode = OCMPartialMock(node);
|
||||
|
||||
OCMExpect([mockNode accessibilityActivate]);
|
||||
[view accessibilityActivate];
|
||||
|
||||
OCMExpect([mockNode accessibilityIncrement]);
|
||||
[view accessibilityIncrement];
|
||||
|
||||
OCMExpect([mockNode accessibilityDecrement]);
|
||||
[view accessibilityDecrement];
|
||||
|
||||
OCMExpect([mockNode accessibilityScroll:UIAccessibilityScrollDirectionDown]);
|
||||
[view accessibilityScroll:UIAccessibilityScrollDirectionDown];
|
||||
|
||||
OCMExpect([mockNode accessibilityPerformEscape]);
|
||||
[view accessibilityPerformEscape];
|
||||
|
||||
OCMExpect([mockNode accessibilityPerformMagicTap]);
|
||||
[view accessibilityPerformMagicTap];
|
||||
|
||||
OCMVerifyAll(mockNode);
|
||||
}
|
||||
|
||||
@end
|
@ -1,171 +0,0 @@
|
||||
//
|
||||
// ASEditableTextNodeTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
#import <AsyncDisplayKit/ASEditableTextNode.h>
|
||||
|
||||
static BOOL CGSizeEqualToSizeWithIn(CGSize size1, CGSize size2, CGFloat delta)
|
||||
{
|
||||
return fabs(size1.width - size2.width) < delta && fabs(size1.height - size2.height) < delta;
|
||||
}
|
||||
|
||||
@interface ASEditableTextNodeTests : XCTestCase
|
||||
@property (nonatomic) ASEditableTextNode *editableTextNode;
|
||||
@property (nonatomic, copy) NSAttributedString *attributedText;
|
||||
@end
|
||||
|
||||
@implementation ASEditableTextNodeTests
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
[super setUp];
|
||||
|
||||
_editableTextNode = [[ASEditableTextNode alloc] init];
|
||||
|
||||
NSMutableAttributedString *mas = [[NSMutableAttributedString alloc] initWithString:@"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."];
|
||||
NSMutableParagraphStyle *para = [NSMutableParagraphStyle new];
|
||||
para.alignment = NSTextAlignmentCenter;
|
||||
para.lineSpacing = 1.0;
|
||||
[mas addAttribute:NSParagraphStyleAttributeName value:para
|
||||
range:NSMakeRange(0, mas.length - 1)];
|
||||
|
||||
// Vary the linespacing on the last line
|
||||
NSMutableParagraphStyle *lastLinePara = [NSMutableParagraphStyle new];
|
||||
lastLinePara.alignment = para.alignment;
|
||||
lastLinePara.lineSpacing = 5.0;
|
||||
[mas addAttribute:NSParagraphStyleAttributeName value:lastLinePara
|
||||
range:NSMakeRange(mas.length - 1, 1)];
|
||||
|
||||
_attributedText = mas;
|
||||
_editableTextNode.attributedText = _attributedText;
|
||||
}
|
||||
|
||||
#pragma mark - ASEditableTextNode
|
||||
|
||||
- (void)testAllocASEditableTextNode
|
||||
{
|
||||
ASEditableTextNode *node = [[ASEditableTextNode alloc] init];
|
||||
XCTAssertTrue([[node class] isSubclassOfClass:[ASEditableTextNode class]], @"ASEditableTextNode alloc should return an instance of ASEditableTextNode, instead returned %@", [node class]);
|
||||
}
|
||||
|
||||
#pragma mark - ASEditableTextNode Tests
|
||||
|
||||
- (void)testUITextInputTraitDefaults
|
||||
{
|
||||
ASEditableTextNode *editableTextNode = [[ASEditableTextNode alloc] init];
|
||||
|
||||
XCTAssertTrue(editableTextNode.autocapitalizationType == UITextAutocapitalizationTypeSentences, @"_ASTextInputTraitsPendingState's autocapitalizationType default should be UITextAutocapitalizationTypeSentences.");
|
||||
XCTAssertTrue(editableTextNode.autocorrectionType == UITextAutocorrectionTypeDefault, @"_ASTextInputTraitsPendingState's autocorrectionType default should be UITextAutocorrectionTypeDefault.");
|
||||
XCTAssertTrue(editableTextNode.spellCheckingType == UITextSpellCheckingTypeDefault, @"_ASTextInputTraitsPendingState's spellCheckingType default should be UITextSpellCheckingTypeDefault.");
|
||||
XCTAssertTrue(editableTextNode.keyboardType == UIKeyboardTypeDefault, @"_ASTextInputTraitsPendingState's keyboardType default should be UIKeyboardTypeDefault.");
|
||||
XCTAssertTrue(editableTextNode.keyboardAppearance == UIKeyboardAppearanceDefault, @"_ASTextInputTraitsPendingState's keyboardAppearance default should be UIKeyboardAppearanceDefault.");
|
||||
XCTAssertTrue(editableTextNode.returnKeyType == UIReturnKeyDefault, @"_ASTextInputTraitsPendingState's returnKeyType default should be UIReturnKeyDefault.");
|
||||
XCTAssertTrue(editableTextNode.enablesReturnKeyAutomatically == NO, @"_ASTextInputTraitsPendingState's enablesReturnKeyAutomatically default should be NO.");
|
||||
XCTAssertTrue(editableTextNode.isSecureTextEntry == NO, @"_ASTextInputTraitsPendingState's isSecureTextEntry default should be NO.");
|
||||
|
||||
XCTAssertTrue(editableTextNode.textView.autocapitalizationType == UITextAutocapitalizationTypeSentences, @"textView's autocapitalizationType default should be UITextAutocapitalizationTypeSentences.");
|
||||
XCTAssertTrue(editableTextNode.textView.autocorrectionType == UITextAutocorrectionTypeDefault, @"textView's autocorrectionType default should be UITextAutocorrectionTypeDefault.");
|
||||
XCTAssertTrue(editableTextNode.textView.spellCheckingType == UITextSpellCheckingTypeDefault, @"textView's spellCheckingType default should be UITextSpellCheckingTypeDefault.");
|
||||
XCTAssertTrue(editableTextNode.textView.keyboardType == UIKeyboardTypeDefault, @"textView's keyboardType default should be UIKeyboardTypeDefault.");
|
||||
XCTAssertTrue(editableTextNode.textView.keyboardAppearance == UIKeyboardAppearanceDefault, @"textView's keyboardAppearance default should be UIKeyboardAppearanceDefault.");
|
||||
XCTAssertTrue(editableTextNode.textView.returnKeyType == UIReturnKeyDefault, @"textView's returnKeyType default should be UIReturnKeyDefault.");
|
||||
XCTAssertTrue(editableTextNode.textView.enablesReturnKeyAutomatically == NO, @"textView's enablesReturnKeyAutomatically default should be NO.");
|
||||
XCTAssertTrue(editableTextNode.textView.isSecureTextEntry == NO, @"textView's isSecureTextEntry default should be NO.");
|
||||
}
|
||||
|
||||
- (void)testUITextInputTraitsSetTraitsBeforeViewLoaded
|
||||
{
|
||||
// UITextView ignores any values set on the first 3 properties below if secureTextEntry is enabled.
|
||||
// Because of this UIKit behavior, we'll test secure entry seperately
|
||||
ASEditableTextNode *editableTextNode = [[ASEditableTextNode alloc] init];
|
||||
|
||||
editableTextNode.autocapitalizationType = UITextAutocapitalizationTypeWords;
|
||||
editableTextNode.autocorrectionType = UITextAutocorrectionTypeYes;
|
||||
editableTextNode.spellCheckingType = UITextSpellCheckingTypeYes;
|
||||
editableTextNode.keyboardType = UIKeyboardTypeTwitter;
|
||||
editableTextNode.keyboardAppearance = UIKeyboardAppearanceDark;
|
||||
editableTextNode.returnKeyType = UIReturnKeyGo;
|
||||
editableTextNode.enablesReturnKeyAutomatically = YES;
|
||||
|
||||
XCTAssertTrue(editableTextNode.textView.autocapitalizationType == UITextAutocapitalizationTypeWords, @"textView's autocapitalizationType should be UITextAutocapitalizationTypeAllCharacters.");
|
||||
XCTAssertTrue(editableTextNode.textView.autocorrectionType == UITextAutocorrectionTypeYes, @"textView's autocorrectionType should be UITextAutocorrectionTypeYes.");
|
||||
XCTAssertTrue(editableTextNode.textView.spellCheckingType == UITextSpellCheckingTypeYes, @"textView's spellCheckingType should be UITextSpellCheckingTypeYes.");
|
||||
XCTAssertTrue(editableTextNode.textView.keyboardType == UIKeyboardTypeTwitter, @"textView's keyboardType should be UIKeyboardTypeTwitter.");
|
||||
XCTAssertTrue(editableTextNode.textView.keyboardAppearance == UIKeyboardAppearanceDark, @"textView's keyboardAppearance should be UIKeyboardAppearanceDark.");
|
||||
XCTAssertTrue(editableTextNode.textView.returnKeyType == UIReturnKeyGo, @"textView's returnKeyType should be UIReturnKeyGo.");
|
||||
XCTAssertTrue(editableTextNode.textView.enablesReturnKeyAutomatically == YES, @"textView's enablesReturnKeyAutomatically should be YES.");
|
||||
|
||||
ASEditableTextNode *secureEditableTextNode = [[ASEditableTextNode alloc] init];
|
||||
secureEditableTextNode.secureTextEntry = YES;
|
||||
|
||||
XCTAssertTrue(secureEditableTextNode.textView.secureTextEntry == YES, @"textView's isSecureTextEntry should be YES.");
|
||||
}
|
||||
|
||||
- (void)testUITextInputTraitsChangeTraitAfterViewLoaded
|
||||
{
|
||||
// UITextView ignores any values set on the first 3 properties below if secureTextEntry is enabled.
|
||||
// Because of this UIKit behavior, we'll test secure entry seperately
|
||||
ASEditableTextNode *editableTextNode = [[ASEditableTextNode alloc] init];
|
||||
|
||||
editableTextNode.textView.autocapitalizationType = UITextAutocapitalizationTypeWords;
|
||||
editableTextNode.textView.autocorrectionType = UITextAutocorrectionTypeYes;
|
||||
editableTextNode.textView.spellCheckingType = UITextSpellCheckingTypeYes;
|
||||
editableTextNode.textView.keyboardType = UIKeyboardTypeTwitter;
|
||||
editableTextNode.textView.keyboardAppearance = UIKeyboardAppearanceDark;
|
||||
editableTextNode.textView.returnKeyType = UIReturnKeyGo;
|
||||
editableTextNode.textView.enablesReturnKeyAutomatically = YES;
|
||||
|
||||
XCTAssertTrue(editableTextNode.textView.autocapitalizationType == UITextAutocapitalizationTypeWords, @"textView's autocapitalizationType should be UITextAutocapitalizationTypeAllCharacters.");
|
||||
XCTAssertTrue(editableTextNode.textView.autocorrectionType == UITextAutocorrectionTypeYes, @"textView's autocorrectionType should be UITextAutocorrectionTypeYes.");
|
||||
XCTAssertTrue(editableTextNode.textView.spellCheckingType == UITextSpellCheckingTypeYes, @"textView's spellCheckingType should be UITextSpellCheckingTypeYes.");
|
||||
XCTAssertTrue(editableTextNode.textView.keyboardType == UIKeyboardTypeTwitter, @"textView's keyboardType should be UIKeyboardTypeTwitter.");
|
||||
XCTAssertTrue(editableTextNode.textView.keyboardAppearance == UIKeyboardAppearanceDark, @"textView's keyboardAppearance should be UIKeyboardAppearanceDark.");
|
||||
XCTAssertTrue(editableTextNode.textView.returnKeyType == UIReturnKeyGo, @"textView's returnKeyType should be UIReturnKeyGo.");
|
||||
XCTAssertTrue(editableTextNode.textView.enablesReturnKeyAutomatically == YES, @"textView's enablesReturnKeyAutomatically should be YES.");
|
||||
|
||||
ASEditableTextNode *secureEditableTextNode = [[ASEditableTextNode alloc] init];
|
||||
secureEditableTextNode.textView.secureTextEntry = YES;
|
||||
|
||||
XCTAssertTrue(secureEditableTextNode.textView.secureTextEntry == YES, @"textView's isSecureTextEntry should be YES.");
|
||||
}
|
||||
|
||||
- (void)testCalculatedSizeIsGreaterThanOrEqualToConstrainedSize
|
||||
{
|
||||
for (NSInteger i = 10; i < 500; i += 50) {
|
||||
CGSize constrainedSize = CGSizeMake(i, i);
|
||||
CGSize calculatedSize = [_editableTextNode layoutThatFits:ASSizeRangeMake(CGSizeZero, constrainedSize)].size;
|
||||
XCTAssertTrue(calculatedSize.width <= constrainedSize.width, @"Calculated width (%f) should be less than or equal to constrained width (%f)", calculatedSize.width, constrainedSize.width);
|
||||
XCTAssertTrue(calculatedSize.height <= constrainedSize.height, @"Calculated height (%f) should be less than or equal to constrained height (%f)", calculatedSize.height, constrainedSize.height);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testRecalculationOfSizeIsSameAsOriginallyCalculatedSize
|
||||
{
|
||||
for (NSInteger i = 10; i < 500; i += 50) {
|
||||
CGSize constrainedSize = CGSizeMake(i, i);
|
||||
CGSize calculatedSize = [_editableTextNode layoutThatFits:ASSizeRangeMake(CGSizeZero, constrainedSize)].size;
|
||||
CGSize recalculatedSize = [_editableTextNode layoutThatFits:ASSizeRangeMake(CGSizeZero, constrainedSize)].size;
|
||||
|
||||
XCTAssertTrue(CGSizeEqualToSizeWithIn(calculatedSize, recalculatedSize, 4.0), @"Recalculated size %@ should be same as original size %@", NSStringFromCGSize(recalculatedSize), NSStringFromCGSize(calculatedSize));
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testRecalculationOfSizeIsSameAsOriginallyCalculatedFloatingPointSize
|
||||
{
|
||||
for (CGFloat i = 10; i < 500; i *= 1.3) {
|
||||
CGSize constrainedSize = CGSizeMake(i, i);
|
||||
CGSize calculatedSize = [_editableTextNode layoutThatFits:ASSizeRangeMake(CGSizeZero, constrainedSize)].size;
|
||||
CGSize recalculatedSize = [_editableTextNode layoutThatFits:ASSizeRangeMake(CGSizeZero, constrainedSize)].size;
|
||||
|
||||
XCTAssertTrue(CGSizeEqualToSizeWithIn(calculatedSize, recalculatedSize, 11.0), @"Recalculated size %@ should be same as original size %@", NSStringFromCGSize(recalculatedSize), NSStringFromCGSize(calculatedSize));
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
@ -1,94 +0,0 @@
|
||||
//
|
||||
// ASImageNodeSnapshotTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASSnapshotTestCase.h"
|
||||
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
|
||||
@interface ASImageNodeSnapshotTests : ASSnapshotTestCase
|
||||
@end
|
||||
|
||||
@implementation ASImageNodeSnapshotTests
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
[super setUp];
|
||||
|
||||
self.recordMode = NO;
|
||||
}
|
||||
|
||||
- (UIImage *)testImage
|
||||
{
|
||||
NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"logo-square"
|
||||
ofType:@"png"
|
||||
inDirectory:@"TestResources"];
|
||||
return [UIImage imageWithContentsOfFile:path];
|
||||
}
|
||||
|
||||
- (void)testRenderLogoSquare
|
||||
{
|
||||
// trivial test case to ensure ASSnapshotTestCase works
|
||||
ASImageNode *imageNode = [[ASImageNode alloc] init];
|
||||
imageNode.image = [self testImage];
|
||||
ASDisplayNodeSizeToFitSize(imageNode, CGSizeMake(100, 100));
|
||||
|
||||
ASSnapshotVerifyNode(imageNode, nil);
|
||||
}
|
||||
|
||||
- (void)testForcedScaling
|
||||
{
|
||||
CGSize forcedImageSize = CGSizeMake(100, 100);
|
||||
|
||||
ASImageNode *imageNode = [[ASImageNode alloc] init];
|
||||
imageNode.forcedSize = forcedImageSize;
|
||||
imageNode.image = [self testImage];
|
||||
|
||||
// Snapshot testing requires that node is formally laid out.
|
||||
imageNode.style.width = ASDimensionMake(forcedImageSize.width);
|
||||
imageNode.style.height = ASDimensionMake(forcedImageSize.height);
|
||||
ASDisplayNodeSizeToFitSize(imageNode, forcedImageSize);
|
||||
ASSnapshotVerifyNode(imageNode, @"first");
|
||||
|
||||
imageNode.style.width = ASDimensionMake(200);
|
||||
imageNode.style.height = ASDimensionMake(200);
|
||||
ASDisplayNodeSizeToFitSize(imageNode, CGSizeMake(200, 200));
|
||||
ASSnapshotVerifyNode(imageNode, @"second");
|
||||
|
||||
XCTAssert(CGImageGetWidth((CGImageRef)imageNode.contents) == forcedImageSize.width * imageNode.contentsScale &&
|
||||
CGImageGetHeight((CGImageRef)imageNode.contents) == forcedImageSize.height * imageNode.contentsScale,
|
||||
@"Contents should be 100 x 100 by contents scale.");
|
||||
}
|
||||
|
||||
- (void)testTintColorBlock
|
||||
{
|
||||
UIImage *test = [self testImage];
|
||||
UIImage *tinted = ASImageNodeTintColorModificationBlock([UIColor redColor])(test);
|
||||
ASImageNode *node = [[ASImageNode alloc] init];
|
||||
node.image = tinted;
|
||||
ASDisplayNodeSizeToFitSize(node, test.size);
|
||||
|
||||
ASSnapshotVerifyNode(node, nil);
|
||||
}
|
||||
|
||||
- (void)testRoundedCornerBlock
|
||||
{
|
||||
UIGraphicsBeginImageContext(CGSizeMake(100, 100));
|
||||
[[UIColor blueColor] setFill];
|
||||
UIRectFill(CGRectMake(0, 0, 100, 100));
|
||||
UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
UIImage *rounded = ASImageNodeRoundBorderModificationBlock(2, [UIColor redColor])(result);
|
||||
ASImageNode *node = [[ASImageNode alloc] init];
|
||||
node.image = rounded;
|
||||
ASDisplayNodeSizeToFitSize(node, rounded.size);
|
||||
|
||||
ASSnapshotVerifyNode(node, nil);
|
||||
}
|
||||
|
||||
@end
|
@ -1,117 +0,0 @@
|
||||
//
|
||||
// ASInsetLayoutSpecSnapshotTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASLayoutSpecSnapshotTestsHelper.h"
|
||||
|
||||
#import <AsyncDisplayKit/ASBackgroundLayoutSpec.h>
|
||||
#import <AsyncDisplayKit/ASInsetLayoutSpec.h>
|
||||
|
||||
typedef NS_OPTIONS(NSUInteger, ASInsetLayoutSpecTestEdge) {
|
||||
ASInsetLayoutSpecTestEdgeTop = 1 << 0,
|
||||
ASInsetLayoutSpecTestEdgeLeft = 1 << 1,
|
||||
ASInsetLayoutSpecTestEdgeBottom = 1 << 2,
|
||||
ASInsetLayoutSpecTestEdgeRight = 1 << 3,
|
||||
};
|
||||
|
||||
static CGFloat insetForEdge(NSUInteger combination, ASInsetLayoutSpecTestEdge edge, CGFloat insetValue)
|
||||
{
|
||||
return combination & edge ? INFINITY : insetValue;
|
||||
}
|
||||
|
||||
static UIEdgeInsets insetsForCombination(NSUInteger combination, CGFloat insetValue)
|
||||
{
|
||||
return {
|
||||
.top = insetForEdge(combination, ASInsetLayoutSpecTestEdgeTop, insetValue),
|
||||
.left = insetForEdge(combination, ASInsetLayoutSpecTestEdgeLeft, insetValue),
|
||||
.bottom = insetForEdge(combination, ASInsetLayoutSpecTestEdgeBottom, insetValue),
|
||||
.right = insetForEdge(combination, ASInsetLayoutSpecTestEdgeRight, insetValue),
|
||||
};
|
||||
}
|
||||
|
||||
static NSString *nameForInsets(UIEdgeInsets insets)
|
||||
{
|
||||
return [NSString stringWithFormat:@"%.f-%.f-%.f-%.f", insets.top, insets.left, insets.bottom, insets.right];
|
||||
}
|
||||
|
||||
@interface ASInsetLayoutSpecSnapshotTests : ASLayoutSpecSnapshotTestCase
|
||||
@end
|
||||
|
||||
@implementation ASInsetLayoutSpecSnapshotTests
|
||||
|
||||
- (void)testInsetsWithVariableSize
|
||||
{
|
||||
for (NSUInteger combination = 0; combination < 16; combination++) {
|
||||
UIEdgeInsets insets = insetsForCombination(combination, 10);
|
||||
ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor grayColor]);
|
||||
ASDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor greenColor], {10, 10});
|
||||
|
||||
ASLayoutSpec *layoutSpec =
|
||||
[ASBackgroundLayoutSpec
|
||||
backgroundLayoutSpecWithChild:
|
||||
[ASInsetLayoutSpec
|
||||
insetLayoutSpecWithInsets:insets
|
||||
child:foregroundNode]
|
||||
background:backgroundNode];
|
||||
|
||||
static ASSizeRange kVariableSize = {{0, 0}, {300, 300}};
|
||||
[self testLayoutSpec:layoutSpec
|
||||
sizeRange:kVariableSize
|
||||
subnodes:@[backgroundNode, foregroundNode]
|
||||
identifier:nameForInsets(insets)];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testInsetsWithFixedSize
|
||||
{
|
||||
for (NSUInteger combination = 0; combination < 16; combination++) {
|
||||
UIEdgeInsets insets = insetsForCombination(combination, 10);
|
||||
ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor grayColor]);
|
||||
ASDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor greenColor], {10, 10});
|
||||
|
||||
ASLayoutSpec *layoutSpec =
|
||||
[ASBackgroundLayoutSpec
|
||||
backgroundLayoutSpecWithChild:
|
||||
[ASInsetLayoutSpec
|
||||
insetLayoutSpecWithInsets:insets
|
||||
child:foregroundNode]
|
||||
background:backgroundNode];
|
||||
|
||||
static ASSizeRange kFixedSize = {{300, 300}, {300, 300}};
|
||||
[self testLayoutSpec:layoutSpec
|
||||
sizeRange:kFixedSize
|
||||
subnodes:@[backgroundNode, foregroundNode]
|
||||
identifier:nameForInsets(insets)];
|
||||
}
|
||||
}
|
||||
|
||||
/** Regression test, there was a bug mixing insets with infinite and zero sizes */
|
||||
- (void)testInsetsWithInfinityAndZeroInsetValue
|
||||
{
|
||||
for (NSUInteger combination = 0; combination < 16; combination++) {
|
||||
UIEdgeInsets insets = insetsForCombination(combination, 0);
|
||||
ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor grayColor]);
|
||||
ASDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor greenColor], {10, 10});
|
||||
|
||||
ASLayoutSpec *layoutSpec =
|
||||
[ASBackgroundLayoutSpec
|
||||
backgroundLayoutSpecWithChild:
|
||||
[ASInsetLayoutSpec
|
||||
insetLayoutSpecWithInsets:insets
|
||||
child:foregroundNode]
|
||||
background:backgroundNode];
|
||||
|
||||
static ASSizeRange kFixedSize = {{300, 300}, {300, 300}};
|
||||
[self testLayoutSpec:layoutSpec
|
||||
sizeRange:kFixedSize
|
||||
subnodes:@[backgroundNode, foregroundNode]
|
||||
identifier:nameForInsets(insets)];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
@ -1,113 +0,0 @@
|
||||
//
|
||||
// ASIntegerMapTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASTestCase.h"
|
||||
#import "ASIntegerMap.h"
|
||||
|
||||
@interface ASIntegerMapTests : ASTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASIntegerMapTests
|
||||
|
||||
- (void)testIsEqual
|
||||
{
|
||||
ASIntegerMap *map = [[ASIntegerMap alloc] init];
|
||||
[map setInteger:1 forKey:0];
|
||||
ASIntegerMap *alsoMap = [[ASIntegerMap alloc] init];
|
||||
[alsoMap setInteger:1 forKey:0];
|
||||
ASIntegerMap *notMap = [[ASIntegerMap alloc] init];
|
||||
[notMap setInteger:2 forKey:0];
|
||||
XCTAssertEqualObjects(map, alsoMap);
|
||||
XCTAssertNotEqualObjects(map, notMap);
|
||||
}
|
||||
|
||||
#pragma mark - Changeset mapping
|
||||
|
||||
/// 1 item, no changes -> identity map
|
||||
- (void)testEmptyChange
|
||||
{
|
||||
ASIntegerMap *map = [ASIntegerMap mapForUpdateWithOldCount:1 deleted:nil inserted:nil];
|
||||
XCTAssertEqual(map, ASIntegerMap.identityMap);
|
||||
}
|
||||
|
||||
/// 0 items -> empty map
|
||||
- (void)testChangeOnNoData
|
||||
{
|
||||
ASIntegerMap *map = [ASIntegerMap mapForUpdateWithOldCount:0 deleted:nil inserted:nil];
|
||||
XCTAssertEqual(map, ASIntegerMap.emptyMap);
|
||||
}
|
||||
|
||||
/// 2 items, delete 0
|
||||
- (void)testBasicChange1
|
||||
{
|
||||
ASIntegerMap *map = [ASIntegerMap mapForUpdateWithOldCount:2 deleted:[NSIndexSet indexSetWithIndex:0] inserted:nil];
|
||||
XCTAssertEqual([map integerForKey:0], NSNotFound);
|
||||
XCTAssertEqual([map integerForKey:1], 0);
|
||||
XCTAssertEqual([map integerForKey:2], NSNotFound);
|
||||
}
|
||||
|
||||
/// 2 items, insert 0
|
||||
- (void)testBasicChange2
|
||||
{
|
||||
ASIntegerMap *map = [ASIntegerMap mapForUpdateWithOldCount:2 deleted:nil inserted:[NSIndexSet indexSetWithIndex:0]];
|
||||
XCTAssertEqual([map integerForKey:0], 1);
|
||||
XCTAssertEqual([map integerForKey:1], 2);
|
||||
XCTAssertEqual([map integerForKey:2], NSNotFound);
|
||||
}
|
||||
|
||||
/// 2 items, insert 0, delete 0
|
||||
- (void)testChange1
|
||||
{
|
||||
ASIntegerMap *map = [ASIntegerMap mapForUpdateWithOldCount:2 deleted:[NSIndexSet indexSetWithIndex:0] inserted:[NSIndexSet indexSetWithIndex:0]];
|
||||
XCTAssertEqual([map integerForKey:0], NSNotFound);
|
||||
XCTAssertEqual([map integerForKey:1], 1);
|
||||
XCTAssertEqual([map integerForKey:2], NSNotFound);
|
||||
}
|
||||
|
||||
/// 4 items, insert {0-1, 3}
|
||||
- (void)testChange2
|
||||
{
|
||||
NSMutableIndexSet *inserts = [NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)];
|
||||
[inserts addIndex:3];
|
||||
ASIntegerMap *map = [ASIntegerMap mapForUpdateWithOldCount:4 deleted:nil inserted:inserts];
|
||||
XCTAssertEqual([map integerForKey:0], 2);
|
||||
XCTAssertEqual([map integerForKey:1], 4);
|
||||
XCTAssertEqual([map integerForKey:2], 5);
|
||||
XCTAssertEqual([map integerForKey:3], 6);
|
||||
}
|
||||
|
||||
/// 4 items, delete {0-1, 3}
|
||||
- (void)testChange3
|
||||
{
|
||||
NSMutableIndexSet *deletes = [NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)];
|
||||
[deletes addIndex:3];
|
||||
ASIntegerMap *map = [ASIntegerMap mapForUpdateWithOldCount:4 deleted:deletes inserted:nil];
|
||||
XCTAssertEqual([map integerForKey:0], NSNotFound);
|
||||
XCTAssertEqual([map integerForKey:1], NSNotFound);
|
||||
XCTAssertEqual([map integerForKey:2], 0);
|
||||
XCTAssertEqual([map integerForKey:3], NSNotFound);
|
||||
}
|
||||
|
||||
/// 5 items, delete {0-1, 3} insert {1-2, 4}
|
||||
- (void)testChange4
|
||||
{
|
||||
NSMutableIndexSet *deletes = [NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)];
|
||||
[deletes addIndex:3];
|
||||
NSMutableIndexSet *inserts = [NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(1, 2)];
|
||||
[inserts addIndex:4];
|
||||
ASIntegerMap *map = [ASIntegerMap mapForUpdateWithOldCount:5 deleted:deletes inserted:inserts];
|
||||
XCTAssertEqual([map integerForKey:0], NSNotFound);
|
||||
XCTAssertEqual([map integerForKey:1], NSNotFound);
|
||||
XCTAssertEqual([map integerForKey:2], 0);
|
||||
XCTAssertEqual([map integerForKey:3], NSNotFound);
|
||||
XCTAssertEqual([map integerForKey:4], 3);
|
||||
XCTAssertEqual([map integerForKey:5], NSNotFound);
|
||||
}
|
||||
|
||||
@end
|
@ -1,127 +0,0 @@
|
||||
//
|
||||
// ASLayoutElementStyleTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import "ASXCTExtensions.h"
|
||||
#import <AsyncDisplayKit/ASLayoutElement.h>
|
||||
|
||||
#pragma mark - ASLayoutElementStyleTestsDelegate
|
||||
|
||||
@interface ASLayoutElementStyleTestsDelegate : NSObject<ASLayoutElementStyleDelegate>
|
||||
@property (copy, nonatomic) NSString *propertyNameChanged;
|
||||
@end
|
||||
|
||||
@implementation ASLayoutElementStyleTestsDelegate
|
||||
|
||||
- (void)style:(id)style propertyDidChange:(NSString *)propertyName
|
||||
{
|
||||
self.propertyNameChanged = propertyName;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - ASLayoutElementStyleTests
|
||||
|
||||
@interface ASLayoutElementStyleTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASLayoutElementStyleTests
|
||||
|
||||
- (void)testSettingSize
|
||||
{
|
||||
ASLayoutElementStyle *style = [ASLayoutElementStyle new];
|
||||
|
||||
style.width = ASDimensionMake(100);
|
||||
style.height = ASDimensionMake(100);
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.width, ASDimensionMake(100)));
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.height, ASDimensionMake(100)));
|
||||
|
||||
style.minWidth = ASDimensionMake(100);
|
||||
style.minHeight = ASDimensionMake(100);
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.width, ASDimensionMake(100)));
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.height, ASDimensionMake(100)));
|
||||
|
||||
style.maxWidth = ASDimensionMake(100);
|
||||
style.maxHeight = ASDimensionMake(100);
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.width, ASDimensionMake(100)));
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.height, ASDimensionMake(100)));
|
||||
}
|
||||
|
||||
- (void)testSettingSizeViaCGSize
|
||||
{
|
||||
ASLayoutElementStyle *style = [ASLayoutElementStyle new];
|
||||
|
||||
ASXCTAssertEqualSizes(style.preferredSize, CGSizeZero);
|
||||
|
||||
CGSize size = CGSizeMake(100, 100);
|
||||
|
||||
style.preferredSize = size;
|
||||
ASXCTAssertEqualSizes(style.preferredSize, size);
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.width, ASDimensionMakeWithPoints(size.width)));
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.height, ASDimensionMakeWithPoints(size.height)));
|
||||
|
||||
style.minSize = size;
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.minWidth, ASDimensionMakeWithPoints(size.width)));
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.minHeight, ASDimensionMakeWithPoints(size.height)));
|
||||
|
||||
style.maxSize = size;
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.maxWidth, ASDimensionMakeWithPoints(size.width)));
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.maxHeight, ASDimensionMakeWithPoints(size.height)));
|
||||
}
|
||||
|
||||
- (void)testReadingInvalidSizeForPreferredSize
|
||||
{
|
||||
ASLayoutElementStyle *style = [ASLayoutElementStyle new];
|
||||
|
||||
XCTAssertNoThrow(style.preferredSize);
|
||||
|
||||
style.width = ASDimensionMake(ASDimensionUnitFraction, 0.5);
|
||||
XCTAssertThrows(style.preferredSize);
|
||||
|
||||
style.preferredSize = CGSizeMake(100, 100);
|
||||
XCTAssertNoThrow(style.preferredSize);
|
||||
}
|
||||
|
||||
- (void)testSettingSizeViaLayoutSize
|
||||
{
|
||||
ASLayoutElementStyle *style = [ASLayoutElementStyle new];
|
||||
|
||||
ASLayoutSize layoutSize = ASLayoutSizeMake(ASDimensionMake(100), ASDimensionMake(100));
|
||||
|
||||
style.preferredLayoutSize = layoutSize;
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.width, layoutSize.width));
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.height, layoutSize.height));
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.preferredLayoutSize.width, layoutSize.width));
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.preferredLayoutSize.height, layoutSize.height));
|
||||
|
||||
style.minLayoutSize = layoutSize;
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.minWidth, layoutSize.width));
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.minHeight, layoutSize.height));
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.minLayoutSize.width, layoutSize.width));
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.minLayoutSize.height, layoutSize.height));
|
||||
|
||||
style.maxLayoutSize = layoutSize;
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.maxWidth, layoutSize.width));
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.maxHeight, layoutSize.height));
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.maxLayoutSize.width, layoutSize.width));
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.maxLayoutSize.height, layoutSize.height));
|
||||
}
|
||||
|
||||
- (void)testSettingPropertiesWillCallDelegate
|
||||
{
|
||||
ASLayoutElementStyleTestsDelegate *delegate = [ASLayoutElementStyleTestsDelegate new];
|
||||
ASLayoutElementStyle *style = [[ASLayoutElementStyle alloc] initWithDelegate:delegate];
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.width, ASDimensionAuto));
|
||||
style.width = ASDimensionMake(100);
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(style.width, ASDimensionMake(100)));
|
||||
XCTAssertTrue([delegate.propertyNameChanged isEqualToString:ASLayoutElementStyleWidthProperty]);
|
||||
}
|
||||
|
||||
@end
|
@ -1,591 +0,0 @@
|
||||
//
|
||||
// ASLayoutEngineTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASTestCase.h"
|
||||
#import "ASLayoutTestNode.h"
|
||||
#import "ASXCTExtensions.h"
|
||||
#import "ASTLayoutFixture.h"
|
||||
|
||||
@interface ASLayoutEngineTests : ASTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASLayoutEngineTests {
|
||||
ASLayoutTestNode *nodeA;
|
||||
ASLayoutTestNode *nodeB;
|
||||
ASLayoutTestNode *nodeC;
|
||||
ASLayoutTestNode *nodeD;
|
||||
ASLayoutTestNode *nodeE;
|
||||
ASTLayoutFixture *fixture1;
|
||||
ASTLayoutFixture *fixture2;
|
||||
ASTLayoutFixture *fixture3;
|
||||
ASTLayoutFixture *fixture4;
|
||||
ASTLayoutFixture *fixture5;
|
||||
|
||||
// fixtures 1, 3 and 5 share the same exact node A layout spec block.
|
||||
// we don't want the infra to call -setNeedsLayout when we switch fixtures
|
||||
// so we need to use the same exact block.
|
||||
ASLayoutSpecBlock fixture1and3and5NodeALayoutSpecBlock;
|
||||
|
||||
UIWindow *window;
|
||||
UIViewController *vc;
|
||||
NSArray<ASLayoutTestNode *> *allNodes;
|
||||
NSTimeInterval verifyDelay;
|
||||
// See -stubCalculatedLayoutDidChange.
|
||||
BOOL stubbedCalculatedLayoutDidChange;
|
||||
}
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
[super setUp];
|
||||
verifyDelay = 3;
|
||||
window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 10, 1)];
|
||||
vc = [[UIViewController alloc] init];
|
||||
nodeA = [ASLayoutTestNode new];
|
||||
nodeA.backgroundColor = [UIColor redColor];
|
||||
|
||||
// NOTE: nodeB has flexShrink, the others don't
|
||||
nodeB = [ASLayoutTestNode new];
|
||||
nodeB.style.flexShrink = 1;
|
||||
nodeB.backgroundColor = [UIColor orangeColor];
|
||||
|
||||
nodeC = [ASLayoutTestNode new];
|
||||
nodeC.backgroundColor = [UIColor yellowColor];
|
||||
nodeD = [ASLayoutTestNode new];
|
||||
nodeD.backgroundColor = [UIColor greenColor];
|
||||
nodeE = [ASLayoutTestNode new];
|
||||
nodeE.backgroundColor = [UIColor blueColor];
|
||||
allNodes = @[ nodeA, nodeB, nodeC, nodeD, nodeE ];
|
||||
ASSetDebugNames(nodeA, nodeB, nodeC, nodeD, nodeE);
|
||||
ASLayoutSpecBlock b = ^ASLayoutSpec * _Nonnull(__kindof ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) {
|
||||
return [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal spacing:0 justifyContent:ASStackLayoutJustifyContentSpaceBetween alignItems:ASStackLayoutAlignItemsStart children:@[ nodeB, nodeC, nodeD ]];
|
||||
};
|
||||
fixture1and3and5NodeALayoutSpecBlock = b;
|
||||
fixture1 = [self createFixture1];
|
||||
fixture2 = [self createFixture2];
|
||||
fixture3 = [self createFixture3];
|
||||
fixture4 = [self createFixture4];
|
||||
fixture5 = [self createFixture5];
|
||||
|
||||
nodeA.frame = vc.view.bounds;
|
||||
nodeA.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
[vc.view addSubnode:nodeA];
|
||||
|
||||
window.rootViewController = vc;
|
||||
[window makeKeyAndVisible];
|
||||
}
|
||||
|
||||
- (void)tearDown
|
||||
{
|
||||
nodeA.layoutSpecBlock = nil;
|
||||
for (ASLayoutTestNode *node in allNodes) {
|
||||
OCMVerifyAllWithDelay(node.mock, verifyDelay);
|
||||
}
|
||||
[super tearDown];
|
||||
}
|
||||
|
||||
- (void)testFirstLayoutPassWhenInWindow
|
||||
{
|
||||
[self runFirstLayoutPassWithFixture:fixture1];
|
||||
}
|
||||
|
||||
- (void)testSetNeedsLayoutAndNormalLayoutPass
|
||||
{
|
||||
[self runFirstLayoutPassWithFixture:fixture1];
|
||||
|
||||
[fixture2 apply];
|
||||
|
||||
// skip nodeB because its layout doesn't change.
|
||||
for (ASLayoutTestNode *node in @[ nodeA, nodeC, nodeE ]) {
|
||||
[fixture2 withSizeRangesForNode:node block:^(ASSizeRange sizeRange) {
|
||||
OCMExpect([node.mock calculateLayoutThatFits:sizeRange]).onMainThread();
|
||||
}];
|
||||
OCMExpect([node.mock calculatedLayoutDidChange]).onMainThread();
|
||||
}
|
||||
|
||||
[window layoutIfNeeded];
|
||||
[self verifyFixture:fixture2];
|
||||
}
|
||||
|
||||
/**
|
||||
* Transition from fixture1 to Fixture2 on node A.
|
||||
*
|
||||
* Expect A and D to calculate once off main, and
|
||||
* to receive calculatedLayoutDidChange on main,
|
||||
* then to get the measurement completion call on main,
|
||||
* then to get animateLayoutTransition: and didCompleteLayoutTransition: on main.
|
||||
*/
|
||||
- (void)testLayoutTransitionWithAsyncMeasurement
|
||||
{
|
||||
[self stubCalculatedLayoutDidChange];
|
||||
[self runFirstLayoutPassWithFixture:fixture1];
|
||||
|
||||
[fixture2 apply];
|
||||
|
||||
// Expect A, C, E to calculate new layouts off-main
|
||||
// dispatch_once onto main to run our injectedMainThread work while the transition calculates.
|
||||
__block dispatch_block_t injectedMainThreadWork = nil;
|
||||
for (ASLayoutTestNode *node in @[ nodeA, nodeC, nodeE ]) {
|
||||
[fixture2 withSizeRangesForNode:node block:^(ASSizeRange sizeRange) {
|
||||
OCMExpect([node.mock calculateLayoutThatFits:sizeRange])
|
||||
.offMainThread()
|
||||
.andDo(^(NSInvocation *inv) {
|
||||
// On first calculateLayoutThatFits, schedule our injected main thread work.
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
injectedMainThreadWork();
|
||||
});
|
||||
});
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
// The code in this section is designed to move in time order, all on the main thread:
|
||||
|
||||
OCMExpect([nodeA.mock animateLayoutTransition:OCMOCK_ANY]).onMainThread();
|
||||
OCMExpect([nodeA.mock didCompleteLayoutTransition:OCMOCK_ANY]).onMainThread();
|
||||
|
||||
// Trigger the layout transition.
|
||||
__block dispatch_block_t measurementCompletionBlock = nil;
|
||||
[nodeA transitionLayoutWithAnimation:NO shouldMeasureAsync:YES measurementCompletion:^{
|
||||
measurementCompletionBlock();
|
||||
}];
|
||||
|
||||
// This block will get run after bg layout calculate starts, but before measurementCompletion
|
||||
__block BOOL injectedMainThreadWorkDone = NO;
|
||||
injectedMainThreadWork = ^{
|
||||
injectedMainThreadWorkDone = YES;
|
||||
|
||||
[window layoutIfNeeded];
|
||||
|
||||
// Ensure we're still on the old layout. We should stay on this until the transition completes.
|
||||
[self verifyFixture:fixture1];
|
||||
};
|
||||
|
||||
measurementCompletionBlock = ^{
|
||||
XCTAssert(injectedMainThreadWorkDone, @"We hoped to get onto the main thread before the measurementCompletion callback ran.");
|
||||
};
|
||||
|
||||
for (ASLayoutTestNode *node in allNodes) {
|
||||
OCMVerifyAllWithDelay(node.mock, verifyDelay);
|
||||
}
|
||||
|
||||
[self verifyFixture:fixture2];
|
||||
}
|
||||
|
||||
/**
|
||||
* Transition from fixture1 to Fixture2 on node A.
|
||||
*
|
||||
* Expect A and D to calculate once on main, and
|
||||
* to receive calculatedLayoutDidChange on main,
|
||||
* then to get animateLayoutTransition: and didCompleteLayoutTransition: on main.
|
||||
*/
|
||||
- (void)testLayoutTransitionWithSyncMeasurement
|
||||
{
|
||||
[self stubCalculatedLayoutDidChange];
|
||||
|
||||
// Precondition
|
||||
XCTAssertFalse(CGSizeEqualToSize(fixture5.layout.size, fixture1.layout.size));
|
||||
|
||||
// First, apply fixture 5 and run a measurement pass, but don't run a layout pass
|
||||
// After this step, nodes will have pending layouts that are not yet applied
|
||||
[fixture5 apply];
|
||||
[fixture5 withSizeRangesForAllNodesUsingBlock:^(ASLayoutTestNode * _Nonnull node, ASSizeRange sizeRange) {
|
||||
OCMExpect([node.mock calculateLayoutThatFits:sizeRange])
|
||||
.onMainThread();
|
||||
}];
|
||||
|
||||
[nodeA layoutThatFits:ASSizeRangeMake(fixture5.layout.size)];
|
||||
|
||||
// Assert that node A has layout size and size range from fixture 5
|
||||
XCTAssertTrue(CGSizeEqualToSize(fixture5.layout.size, nodeA.calculatedSize));
|
||||
XCTAssertTrue(ASSizeRangeEqualToSizeRange([fixture5 firstSizeRangeForNode:nodeA], nodeA.constrainedSizeForCalculatedLayout));
|
||||
|
||||
// Then switch to fixture 1 and kick off a synchronous layout transition
|
||||
// Unapplied pending layouts from the previous measurement pass will be outdated
|
||||
[fixture1 apply];
|
||||
[fixture1 withSizeRangesForAllNodesUsingBlock:^(ASLayoutTestNode * _Nonnull node, ASSizeRange sizeRange) {
|
||||
OCMExpect([node.mock calculateLayoutThatFits:sizeRange])
|
||||
.onMainThread();
|
||||
}];
|
||||
|
||||
OCMExpect([nodeA.mock animateLayoutTransition:OCMOCK_ANY]).onMainThread();
|
||||
OCMExpect([nodeA.mock didCompleteLayoutTransition:OCMOCK_ANY]).onMainThread();
|
||||
|
||||
[nodeA transitionLayoutWithAnimation:NO shouldMeasureAsync:NO measurementCompletion:nil];
|
||||
|
||||
// Assert that node A picks up new layout size and size range from fixture 1
|
||||
XCTAssertTrue(CGSizeEqualToSize(fixture1.layout.size, nodeA.calculatedSize));
|
||||
XCTAssertTrue(ASSizeRangeEqualToSizeRange([fixture1 firstSizeRangeForNode:nodeA], nodeA.constrainedSizeForCalculatedLayout));
|
||||
|
||||
[window layoutIfNeeded];
|
||||
[self verifyFixture:fixture1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Start at fixture 1.
|
||||
* Trigger an async transition to fixture 2.
|
||||
* While it's measuring, on main switch to fixture 4 (setNeedsLayout A, D) and run a CA layout pass.
|
||||
*
|
||||
* Correct behavior, we end up at fixture 4 since it's newer.
|
||||
* Current incorrect behavior, we end up at fixture 2 and we remeasure surviving node C.
|
||||
* Note: incorrect behavior likely introduced by the early check in __layout added in
|
||||
* https://github.com/facebookarchive/AsyncDisplayKit/pull/2657
|
||||
*/
|
||||
- (void)DISABLE_testASetNeedsLayoutInterferingWithTheCurrentTransition
|
||||
{
|
||||
static BOOL enforceCorrectBehavior = NO;
|
||||
|
||||
[self stubCalculatedLayoutDidChange];
|
||||
[self runFirstLayoutPassWithFixture:fixture1];
|
||||
|
||||
[fixture2 apply];
|
||||
|
||||
// Expect A, C, E to calculate new layouts off-main
|
||||
// dispatch_once onto main to run our injectedMainThread work while the transition calculates.
|
||||
__block dispatch_block_t injectedMainThreadWork = nil;
|
||||
for (ASLayoutTestNode *node in @[ nodeA, nodeC, nodeE ]) {
|
||||
[fixture2 withSizeRangesForNode:node block:^(ASSizeRange sizeRange) {
|
||||
OCMExpect([node.mock calculateLayoutThatFits:sizeRange])
|
||||
.offMainThread()
|
||||
.andDo(^(NSInvocation *inv) {
|
||||
// On first calculateLayoutThatFits, schedule our injected main thread work.
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
injectedMainThreadWork();
|
||||
});
|
||||
});
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
// The code in this section is designed to move in time order, all on the main thread:
|
||||
|
||||
// With the current behavior, the transition will continue and complete.
|
||||
if (!enforceCorrectBehavior) {
|
||||
OCMExpect([nodeA.mock animateLayoutTransition:OCMOCK_ANY]).onMainThread();
|
||||
OCMExpect([nodeA.mock didCompleteLayoutTransition:OCMOCK_ANY]).onMainThread();
|
||||
}
|
||||
|
||||
// Trigger the layout transition.
|
||||
__block dispatch_block_t measurementCompletionBlock = nil;
|
||||
[nodeA transitionLayoutWithAnimation:NO shouldMeasureAsync:YES measurementCompletion:^{
|
||||
measurementCompletionBlock();
|
||||
}];
|
||||
|
||||
// Injected block will get run on main after bg layout calculate starts, but before measurementCompletion
|
||||
__block BOOL injectedMainThreadWorkDone = NO;
|
||||
injectedMainThreadWork = ^{
|
||||
as_log_verbose(OS_LOG_DEFAULT, "Begin injectedMainThreadWork");
|
||||
injectedMainThreadWorkDone = YES;
|
||||
|
||||
[fixture4 apply];
|
||||
as_log_verbose(OS_LOG_DEFAULT, "Did apply new fixture");
|
||||
|
||||
if (enforceCorrectBehavior) {
|
||||
// Correct measurement behavior here is unclear, may depend on whether the layouts which
|
||||
// are common to both fixture2 and fixture4 are available from the cache.
|
||||
} else {
|
||||
// Incorrect behavior: nodeC will get measured against its new bounds on main.
|
||||
const auto cPendingSize = [fixture2 layoutForNode:nodeC].size;
|
||||
OCMExpect([nodeC.mock calculateLayoutThatFits:ASSizeRangeMake(cPendingSize)]).onMainThread();
|
||||
}
|
||||
[window layoutIfNeeded];
|
||||
as_log_verbose(OS_LOG_DEFAULT, "End injectedMainThreadWork");
|
||||
};
|
||||
|
||||
measurementCompletionBlock = ^{
|
||||
XCTAssert(injectedMainThreadWorkDone, @"We hoped to get onto the main thread before the measurementCompletion callback ran.");
|
||||
};
|
||||
|
||||
for (ASLayoutTestNode *node in allNodes) {
|
||||
OCMVerifyAllWithDelay(node.mock, verifyDelay);
|
||||
}
|
||||
|
||||
// Incorrect behavior: The transition will "win" even though its transitioning to stale data.
|
||||
if (enforceCorrectBehavior) {
|
||||
[self verifyFixture:fixture4];
|
||||
} else {
|
||||
[self verifyFixture:fixture2];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start on fixture 3 where nodeB is force-shrunk via multipass layout.
|
||||
* Apply fixture 1, which just changes nodeB's size and calls -setNeedsLayout on it.
|
||||
*
|
||||
* This behavior is currently broken. See implementation for correct behavior and incorrect behavior.
|
||||
*/
|
||||
- (void)testCallingSetNeedsLayoutOnANodeThatWasSubjectToMultipassLayout
|
||||
{
|
||||
static BOOL const enforceCorrectBehavior = NO;
|
||||
[self stubCalculatedLayoutDidChange];
|
||||
[self runFirstLayoutPassWithFixture:fixture3];
|
||||
|
||||
// Switch to fixture 1, updating nodeB's desired size and calling -setNeedsLayout
|
||||
// Now nodeB will fit happily into the stack.
|
||||
[fixture1 apply];
|
||||
|
||||
if (enforceCorrectBehavior) {
|
||||
/*
|
||||
* Correct behavior: nodeB is remeasured against the first (unconstrained) size
|
||||
* and when it's discovered that now nodeB fits, nodeA will re-layout and we'll
|
||||
* end up correctly at fixture1.
|
||||
*/
|
||||
OCMExpect([nodeB.mock calculateLayoutThatFits:[fixture3 firstSizeRangeForNode:nodeB]]);
|
||||
|
||||
[fixture1 withSizeRangesForNode:nodeA block:^(ASSizeRange sizeRange) {
|
||||
OCMExpect([nodeA.mock calculateLayoutThatFits:sizeRange]);
|
||||
}];
|
||||
|
||||
[window layoutIfNeeded];
|
||||
[self verifyFixture:fixture1];
|
||||
} else {
|
||||
/*
|
||||
* Incorrect behavior: nodeB is remeasured against the second (fixed-width) constraint.
|
||||
* The returned value (8) is clamped to the fixed with (7), and then compared to the previous
|
||||
* width (7) and we decide not to propagate up the invalidation, and we stay stuck on the old
|
||||
* layout (fixture3).
|
||||
*/
|
||||
OCMExpect([nodeB.mock calculateLayoutThatFits:nodeB.constrainedSizeForCalculatedLayout]);
|
||||
[window layoutIfNeeded];
|
||||
[self verifyFixture:fixture3];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Helpers
|
||||
|
||||
- (void)verifyFixture:(ASTLayoutFixture *)fixture
|
||||
{
|
||||
const auto expected = fixture.layout;
|
||||
|
||||
// Ensure expected == frames
|
||||
const auto frames = [fixture.rootNode currentLayoutBasedOnFrames];
|
||||
if (![expected isEqual:frames]) {
|
||||
XCTFail(@"\n*** Layout verification failed – frames don't match expected. ***\nGot:\n%@\nExpected:\n%@", [frames recursiveDescription], [expected recursiveDescription]);
|
||||
}
|
||||
|
||||
// Ensure expected == calculatedLayout
|
||||
const auto calculated = fixture.rootNode.calculatedLayout;
|
||||
if (![expected isEqual:calculated]) {
|
||||
XCTFail(@"\n*** Layout verification failed – calculated layout doesn't match expected. ***\nGot:\n%@\nExpected:\n%@", [calculated recursiveDescription], [expected recursiveDescription]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stubs calculatedLayoutDidChange for all nodes.
|
||||
*
|
||||
* It's not really a core layout engine method, and it's also
|
||||
* currently bugged and gets called a lot so for most
|
||||
* tests its better not to have expectations about it littered around.
|
||||
* https://github.com/TextureGroup/Texture/issues/422
|
||||
*/
|
||||
- (void)stubCalculatedLayoutDidChange
|
||||
{
|
||||
stubbedCalculatedLayoutDidChange = YES;
|
||||
for (ASLayoutTestNode *node in allNodes) {
|
||||
OCMStub([node.mock calculatedLayoutDidChange]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixture 1: A basic horizontal stack, all single-pass.
|
||||
*
|
||||
* [A: HorizStack([B, C, D])]. A is (10x1), B is (1x1), C is (2x1), D is (1x1)
|
||||
*/
|
||||
- (ASTLayoutFixture *)createFixture1
|
||||
{
|
||||
const auto fixture = [[ASTLayoutFixture alloc] init];
|
||||
|
||||
// nodeB
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeB];
|
||||
const auto layoutB = [ASLayout layoutWithLayoutElement:nodeB size:{1,1} position:{0,0} sublayouts:nil];
|
||||
|
||||
// nodeC
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeC];
|
||||
const auto layoutC = [ASLayout layoutWithLayoutElement:nodeC size:{2,1} position:{4,0} sublayouts:nil];
|
||||
|
||||
// nodeD
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeD];
|
||||
const auto layoutD = [ASLayout layoutWithLayoutElement:nodeD size:{1,1} position:{9,0} sublayouts:nil];
|
||||
|
||||
[fixture addSizeRange:{{10, 1}, {10, 1}} forNode:nodeA];
|
||||
const auto layoutA = [ASLayout layoutWithLayoutElement:nodeA size:{10,1} position:ASPointNull sublayouts:@[ layoutB, layoutC, layoutD ]];
|
||||
fixture.layout = layoutA;
|
||||
|
||||
[fixture.layoutSpecBlocks setObject:fixture1and3and5NodeALayoutSpecBlock forKey:nodeA];
|
||||
return fixture;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixture 2: A simple transition away from fixture 1.
|
||||
*
|
||||
* [A: HorizStack([B, C, E])]. A is (10x1), B is (1x1), C is (4x1), E is (1x1)
|
||||
*
|
||||
* From fixture 1:
|
||||
* B survives with same layout
|
||||
* C survives with new layout
|
||||
* D is removed
|
||||
* E joins with first layout
|
||||
*/
|
||||
- (ASTLayoutFixture *)createFixture2
|
||||
{
|
||||
const auto fixture = [[ASTLayoutFixture alloc] init];
|
||||
|
||||
// nodeB
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeB];
|
||||
const auto layoutB = [ASLayout layoutWithLayoutElement:nodeB size:{1,1} position:{0,0} sublayouts:nil];
|
||||
|
||||
// nodeC
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeC];
|
||||
const auto layoutC = [ASLayout layoutWithLayoutElement:nodeC size:{4,1} position:{3,0} sublayouts:nil];
|
||||
|
||||
// nodeE
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeE];
|
||||
const auto layoutE = [ASLayout layoutWithLayoutElement:nodeE size:{1,1} position:{9,0} sublayouts:nil];
|
||||
|
||||
[fixture addSizeRange:{{10, 1}, {10, 1}} forNode:nodeA];
|
||||
const auto layoutA = [ASLayout layoutWithLayoutElement:nodeA size:{10,1} position:ASPointNull sublayouts:@[ layoutB, layoutC, layoutE ]];
|
||||
fixture.layout = layoutA;
|
||||
|
||||
ASLayoutSpecBlock specBlockA = ^ASLayoutSpec * _Nonnull(__kindof ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) {
|
||||
return [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal spacing:0 justifyContent:ASStackLayoutJustifyContentSpaceBetween alignItems:ASStackLayoutAlignItemsStart children:@[ nodeB, nodeC, nodeE ]];
|
||||
};
|
||||
[fixture.layoutSpecBlocks setObject:specBlockA forKey:nodeA];
|
||||
return fixture;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixture 3: Multipass stack layout
|
||||
*
|
||||
* [A: HorizStack([B, C, D])]. A is (10x1), B is (7x1), C is (2x1), D is (1x1)
|
||||
*
|
||||
* nodeB (which has flexShrink=1) will return 8x1 for its size during the first
|
||||
* stack pass, and it'll be subject to a second pass where it returns 7x1.
|
||||
*
|
||||
*/
|
||||
- (ASTLayoutFixture *)createFixture3
|
||||
{
|
||||
const auto fixture = [[ASTLayoutFixture alloc] init];
|
||||
|
||||
// nodeB wants 8,1 but it will settle for 7,1
|
||||
[fixture setReturnedSize:{8,1} forNode:nodeB];
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeB];
|
||||
[fixture addSizeRange:{{7, 0}, {7, 1}} forNode:nodeB];
|
||||
const auto layoutB = [ASLayout layoutWithLayoutElement:nodeB size:{7,1} position:{0,0} sublayouts:nil];
|
||||
|
||||
// nodeC
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeC];
|
||||
const auto layoutC = [ASLayout layoutWithLayoutElement:nodeC size:{2,1} position:{7,0} sublayouts:nil];
|
||||
|
||||
// nodeD
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeD];
|
||||
const auto layoutD = [ASLayout layoutWithLayoutElement:nodeD size:{1,1} position:{9,0} sublayouts:nil];
|
||||
|
||||
[fixture addSizeRange:{{10, 1}, {10, 1}} forNode:nodeA];
|
||||
const auto layoutA = [ASLayout layoutWithLayoutElement:nodeA size:{10,1} position:ASPointNull sublayouts:@[ layoutB, layoutC, layoutD ]];
|
||||
fixture.layout = layoutA;
|
||||
|
||||
[fixture.layoutSpecBlocks setObject:fixture1and3and5NodeALayoutSpecBlock forKey:nodeA];
|
||||
return fixture;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixture 4: A different simple transition away from fixture 1.
|
||||
*
|
||||
* [A: HorizStack([B, D, E])]. A is (10x1), B is (1x1), D is (2x1), E is (1x1)
|
||||
*
|
||||
* From fixture 1:
|
||||
* B survives with same layout
|
||||
* C is removed
|
||||
* D survives with new layout
|
||||
* E joins with first layout
|
||||
*
|
||||
* From fixture 2:
|
||||
* B survives with same layout
|
||||
* C is removed
|
||||
* D joins with first layout
|
||||
* E survives with same layout
|
||||
*/
|
||||
- (ASTLayoutFixture *)createFixture4
|
||||
{
|
||||
const auto fixture = [[ASTLayoutFixture alloc] init];
|
||||
|
||||
// nodeB
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeB];
|
||||
const auto layoutB = [ASLayout layoutWithLayoutElement:nodeB size:{1,1} position:{0,0} sublayouts:nil];
|
||||
|
||||
// nodeD
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeD];
|
||||
const auto layoutD = [ASLayout layoutWithLayoutElement:nodeD size:{2,1} position:{4,0} sublayouts:nil];
|
||||
|
||||
// nodeE
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeE];
|
||||
const auto layoutE = [ASLayout layoutWithLayoutElement:nodeE size:{1,1} position:{9,0} sublayouts:nil];
|
||||
|
||||
[fixture addSizeRange:{{10, 1}, {10, 1}} forNode:nodeA];
|
||||
const auto layoutA = [ASLayout layoutWithLayoutElement:nodeA size:{10,1} position:ASPointNull sublayouts:@[ layoutB, layoutD, layoutE ]];
|
||||
fixture.layout = layoutA;
|
||||
|
||||
ASLayoutSpecBlock specBlockA = ^ASLayoutSpec * _Nonnull(__kindof ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) {
|
||||
return [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal spacing:0 justifyContent:ASStackLayoutJustifyContentSpaceBetween alignItems:ASStackLayoutAlignItemsStart children:@[ nodeB, nodeD, nodeE ]];
|
||||
};
|
||||
[fixture.layoutSpecBlocks setObject:specBlockA forKey:nodeA];
|
||||
return fixture;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixture 5: Same as fixture 1, but with a bigger root node (node A).
|
||||
*
|
||||
* [A: HorizStack([B, C, D])]. A is (15x1), B is (1x1), C is (2x1), D is (1x1)
|
||||
*/
|
||||
- (ASTLayoutFixture *)createFixture5
|
||||
{
|
||||
const auto fixture = [[ASTLayoutFixture alloc] init];
|
||||
|
||||
// nodeB
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeB];
|
||||
const auto layoutB = [ASLayout layoutWithLayoutElement:nodeB size:{1,1} position:{0,0} sublayouts:nil];
|
||||
|
||||
// nodeC
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeC];
|
||||
const auto layoutC = [ASLayout layoutWithLayoutElement:nodeC size:{2,1} position:{4,0} sublayouts:nil];
|
||||
|
||||
// nodeD
|
||||
[fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeD];
|
||||
const auto layoutD = [ASLayout layoutWithLayoutElement:nodeD size:{1,1} position:{9,0} sublayouts:nil];
|
||||
|
||||
[fixture addSizeRange:{{15, 1}, {15, 1}} forNode:nodeA];
|
||||
const auto layoutA = [ASLayout layoutWithLayoutElement:nodeA size:{15,1} position:ASPointNull sublayouts:@[ layoutB, layoutC, layoutD ]];
|
||||
fixture.layout = layoutA;
|
||||
|
||||
[fixture.layoutSpecBlocks setObject:fixture1and3and5NodeALayoutSpecBlock forKey:nodeA];
|
||||
return fixture;
|
||||
}
|
||||
|
||||
- (void)runFirstLayoutPassWithFixture:(ASTLayoutFixture *)fixture
|
||||
{
|
||||
[fixture apply];
|
||||
[fixture withSizeRangesForAllNodesUsingBlock:^(ASLayoutTestNode * _Nonnull node, ASSizeRange sizeRange) {
|
||||
OCMExpect([node.mock calculateLayoutThatFits:sizeRange]).onMainThread();
|
||||
|
||||
if (!stubbedCalculatedLayoutDidChange) {
|
||||
OCMExpect([node.mock calculatedLayoutDidChange]).onMainThread();
|
||||
}
|
||||
}];
|
||||
|
||||
// Trigger CA layout pass.
|
||||
[window layoutIfNeeded];
|
||||
|
||||
// Make sure it went through.
|
||||
[self verifyFixture:fixture];
|
||||
}
|
||||
|
||||
@end
|
@ -1,206 +0,0 @@
|
||||
//
|
||||
// ASLayoutFlatteningTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode.h>
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
#import <AsyncDisplayKit/ASLayoutSpec.h>
|
||||
|
||||
@interface ASLayoutFlatteningTests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation ASLayoutFlatteningTests
|
||||
|
||||
static ASLayout *layoutWithCustomPosition(CGPoint position, id<ASLayoutElement> element, NSArray<ASLayout *> *sublayouts)
|
||||
{
|
||||
return [ASLayout layoutWithLayoutElement:element
|
||||
size:CGSizeMake(100, 100)
|
||||
position:position
|
||||
sublayouts:sublayouts];
|
||||
}
|
||||
|
||||
static ASLayout *layout(id<ASLayoutElement> element, NSArray<ASLayout *> *sublayouts)
|
||||
{
|
||||
return layoutWithCustomPosition(CGPointZero, element, sublayouts);
|
||||
}
|
||||
|
||||
- (void)testThatFlattenedLayoutContainsOnlyDirectSubnodesInValidOrder
|
||||
{
|
||||
ASLayout *flattenedLayout;
|
||||
|
||||
@autoreleasepool {
|
||||
NSMutableArray<ASDisplayNode *> *subnodes = [NSMutableArray array];
|
||||
NSMutableArray<ASLayoutSpec *> *layoutSpecs = [NSMutableArray array];
|
||||
NSMutableArray<ASDisplayNode *> *indirectSubnodes = [NSMutableArray array];
|
||||
|
||||
ASDisplayNode *(^subnode)(void) = ^ASDisplayNode *() { [subnodes addObject:[[ASDisplayNode alloc] init]]; return [subnodes lastObject]; };
|
||||
ASLayoutSpec *(^layoutSpec)(void) = ^ASLayoutSpec *() { [layoutSpecs addObject:[[ASLayoutSpec alloc] init]]; return [layoutSpecs lastObject]; };
|
||||
ASDisplayNode *(^indirectSubnode)(void) = ^ASDisplayNode *() { [indirectSubnodes addObject:[[ASDisplayNode alloc] init]]; return [indirectSubnodes lastObject]; };
|
||||
|
||||
NSArray<ASLayout *> *sublayouts = @[
|
||||
layout(subnode(), @[
|
||||
layout(indirectSubnode(), @[]),
|
||||
]),
|
||||
layout(layoutSpec(), @[
|
||||
layout(subnode(), @[]),
|
||||
layout(layoutSpec(), @[
|
||||
layout(layoutSpec(), @[]),
|
||||
layout(subnode(), @[]),
|
||||
]),
|
||||
layout(layoutSpec(), @[]),
|
||||
]),
|
||||
layout(layoutSpec(), @[
|
||||
layout(subnode(), @[
|
||||
layout(indirectSubnode(), @[]),
|
||||
layout(indirectSubnode(), @[
|
||||
layout(indirectSubnode(), @[])
|
||||
]),
|
||||
])
|
||||
]),
|
||||
layout(subnode(), @[]),
|
||||
];
|
||||
|
||||
ASDisplayNode *rootNode = [[ASDisplayNode alloc] init];
|
||||
ASLayout *originalLayout = [ASLayout layoutWithLayoutElement:rootNode
|
||||
size:CGSizeMake(1000, 1000)
|
||||
sublayouts:sublayouts];
|
||||
flattenedLayout = [originalLayout filteredNodeLayoutTree];
|
||||
NSArray<ASLayout *> *flattenedSublayouts = flattenedLayout.sublayouts;
|
||||
NSUInteger sublayoutsCount = flattenedSublayouts.count;
|
||||
|
||||
XCTAssertEqualObjects(originalLayout.layoutElement, flattenedLayout.layoutElement, @"The root node should be reserved");
|
||||
XCTAssertTrue(ASPointIsNull(flattenedLayout.position), @"Position of the root layout should be null");
|
||||
XCTAssertEqual(subnodes.count, sublayoutsCount, @"Flattened layout should only contain direct subnodes");
|
||||
for (int i = 0; i < sublayoutsCount; i++) {
|
||||
XCTAssertEqualObjects(subnodes[i], flattenedSublayouts[i].layoutElement, @"Sublayouts should be in correct order (flattened in DFS fashion)");
|
||||
}
|
||||
}
|
||||
|
||||
for (ASLayout *sublayout in flattenedLayout.sublayouts) {
|
||||
XCTAssertNotNil(sublayout.layoutElement, @"Sublayout elements should be retained");
|
||||
XCTAssertEqual(0, sublayout.sublayouts.count, @"Sublayouts should not have their own sublayouts");
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Test reusing ASLayouts while flattening
|
||||
|
||||
- (void)testThatLayoutWithNonNullPositionIsNotReused
|
||||
{
|
||||
ASDisplayNode *rootNode = [[ASDisplayNode alloc] init];
|
||||
ASLayout *originalLayout = layoutWithCustomPosition(CGPointMake(10, 10), rootNode, @[]);
|
||||
ASLayout *flattenedLayout = [originalLayout filteredNodeLayoutTree];
|
||||
XCTAssertNotEqualObjects(originalLayout, flattenedLayout, "@Layout should be reused");
|
||||
XCTAssertTrue(ASPointIsNull(flattenedLayout.position), @"Position of a root layout should be null");
|
||||
}
|
||||
|
||||
- (void)testThatLayoutWithNullPositionAndNoSublayoutIsReused
|
||||
{
|
||||
ASDisplayNode *rootNode = [[ASDisplayNode alloc] init];
|
||||
ASLayout *originalLayout = layoutWithCustomPosition(ASPointNull, rootNode, @[]);
|
||||
ASLayout *flattenedLayout = [originalLayout filteredNodeLayoutTree];
|
||||
XCTAssertEqualObjects(originalLayout, flattenedLayout, "@Layout should be reused");
|
||||
XCTAssertTrue(ASPointIsNull(flattenedLayout.position), @"Position of a root layout should be null");
|
||||
}
|
||||
|
||||
- (void)testThatLayoutWithNullPositionAndFlattenedNodeSublayoutsIsReused
|
||||
{
|
||||
ASLayout *flattenedLayout;
|
||||
|
||||
@autoreleasepool {
|
||||
ASDisplayNode *rootNode = [[ASDisplayNode alloc] init];
|
||||
NSMutableArray<ASDisplayNode *> *subnodes = [NSMutableArray array];
|
||||
ASDisplayNode *(^subnode)(void) = ^ASDisplayNode *() { [subnodes addObject:[[ASDisplayNode alloc] init]]; return [subnodes lastObject]; };
|
||||
ASLayout *originalLayout = layoutWithCustomPosition(ASPointNull,
|
||||
rootNode,
|
||||
@[
|
||||
layoutWithCustomPosition(CGPointMake(10, 10), subnode(), @[]),
|
||||
layoutWithCustomPosition(CGPointMake(20, 20), subnode(), @[]),
|
||||
layoutWithCustomPosition(CGPointMake(30, 30), subnode(), @[]),
|
||||
]);
|
||||
flattenedLayout = [originalLayout filteredNodeLayoutTree];
|
||||
XCTAssertEqualObjects(originalLayout, flattenedLayout, "@Layout should be reused");
|
||||
XCTAssertTrue(ASPointIsNull(flattenedLayout.position), @"Position of the root layout should be null");
|
||||
}
|
||||
|
||||
for (ASLayout *sublayout in flattenedLayout.sublayouts) {
|
||||
XCTAssertNotNil(sublayout.layoutElement, @"Sublayout elements should be retained");
|
||||
XCTAssertEqual(0, sublayout.sublayouts.count, @"Sublayouts should not have their own sublayouts");
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testThatLayoutWithNullPositionAndUnflattenedSublayoutsIsNotReused
|
||||
{
|
||||
ASLayout *flattenedLayout;
|
||||
|
||||
@autoreleasepool {
|
||||
ASDisplayNode *rootNode = [[ASDisplayNode alloc] init];
|
||||
NSMutableArray<ASDisplayNode *> *subnodes = [NSMutableArray array];
|
||||
NSMutableArray<ASLayoutSpec *> *layoutSpecs = [NSMutableArray array];
|
||||
NSMutableArray<ASDisplayNode *> *indirectSubnodes = [NSMutableArray array];
|
||||
NSMutableArray<ASLayout *> *reusedLayouts = [NSMutableArray array];
|
||||
|
||||
ASDisplayNode *(^subnode)(void) = ^ASDisplayNode *() { [subnodes addObject:[[ASDisplayNode alloc] init]]; return [subnodes lastObject]; };
|
||||
ASLayoutSpec *(^layoutSpec)(void) = ^ASLayoutSpec *() { [layoutSpecs addObject:[[ASLayoutSpec alloc] init]]; return [layoutSpecs lastObject]; };
|
||||
ASDisplayNode *(^indirectSubnode)(void) = ^ASDisplayNode *() { [indirectSubnodes addObject:[[ASDisplayNode alloc] init]]; return [indirectSubnodes lastObject]; };
|
||||
ASLayout *(^reusedLayout)(ASDisplayNode *) = ^ASLayout *(ASDisplayNode *subnode) { [reusedLayouts addObject:layout(subnode, @[])]; return [reusedLayouts lastObject]; };
|
||||
|
||||
/*
|
||||
* Layouts with sublayouts of both nodes and layout specs should not be reused.
|
||||
* However, all flattened node sublayouts with valid position should be reused.
|
||||
*/
|
||||
ASLayout *originalLayout = layoutWithCustomPosition(ASPointNull,
|
||||
rootNode,
|
||||
@[
|
||||
reusedLayout(subnode()),
|
||||
// The 2 node sublayouts below should be reused although they are in a layout spec sublayout.
|
||||
// That is because each of them have an absolute position of zero.
|
||||
// This case can happen, for example, as the result of a background/overlay layout spec.
|
||||
layout(layoutSpec(), @[
|
||||
reusedLayout(subnode()),
|
||||
reusedLayout(subnode())
|
||||
]),
|
||||
layout(subnode(), @[
|
||||
layout(layoutSpec(), @[])
|
||||
]),
|
||||
layout(subnode(), @[
|
||||
layout(indirectSubnode(), @[])
|
||||
]),
|
||||
layoutWithCustomPosition(CGPointMake(10, 10), subnode(), @[]),
|
||||
// The 2 node sublayouts below shouldn't be reused because they have non-zero absolute positions.
|
||||
layoutWithCustomPosition(CGPointMake(20, 20), layoutSpec(), @[
|
||||
layout(subnode(), @[]),
|
||||
layout(subnode(), @[])
|
||||
]),
|
||||
]);
|
||||
flattenedLayout = [originalLayout filteredNodeLayoutTree];
|
||||
NSArray<ASLayout *> *flattenedSublayouts = flattenedLayout.sublayouts;
|
||||
NSUInteger sublayoutsCount = flattenedSublayouts.count;
|
||||
|
||||
XCTAssertNotEqualObjects(originalLayout, flattenedLayout, @"Original layout should not be reused");
|
||||
XCTAssertEqualObjects(originalLayout.layoutElement, flattenedLayout.layoutElement, @"The root node should be reserved");
|
||||
XCTAssertTrue(ASPointIsNull(flattenedLayout.position), @"Position of the root layout should be null");
|
||||
XCTAssertTrue(reusedLayouts.count <= sublayoutsCount, @"Some sublayouts can't be reused");
|
||||
XCTAssertEqual(subnodes.count, sublayoutsCount, @"Flattened layout should only contain direct subnodes");
|
||||
int numOfActualReusedLayouts = 0;
|
||||
for (int i = 0; i < sublayoutsCount; i++) {
|
||||
ASLayout *sublayout = flattenedSublayouts[i];
|
||||
XCTAssertEqualObjects(subnodes[i], sublayout.layoutElement, @"Sublayouts should be in correct order (flattened in DFS fashion)");
|
||||
if ([reusedLayouts containsObject:sublayout]) {
|
||||
numOfActualReusedLayouts++;
|
||||
}
|
||||
}
|
||||
XCTAssertEqual(numOfActualReusedLayouts, reusedLayouts.count, @"Should reuse all layouts that can be reused");
|
||||
}
|
||||
|
||||
for (ASLayout *sublayout in flattenedLayout.sublayouts) {
|
||||
XCTAssertNotNil(sublayout.layoutElement, @"Sublayout elements should be retained");
|
||||
XCTAssertEqual(0, sublayout.sublayouts.count, @"Sublayouts should not have their own sublayouts");
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
@ -1,45 +0,0 @@
|
||||
//
|
||||
// ASLayoutSpecSnapshotTestsHelper.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASSnapshotTestCase.h"
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
|
||||
|
||||
@class ASLayoutSpec;
|
||||
|
||||
@interface ASLayoutSpecSnapshotTestCase: ASSnapshotTestCase
|
||||
/**
|
||||
Test the layout spec or records a snapshot if recordMode is YES.
|
||||
@param layoutSpec The layout spec under test or to snapshot
|
||||
@param sizeRange The size range used to calculate layout of the given layout spec.
|
||||
@param subnodes An array of ASDisplayNodes used within the layout spec.
|
||||
@param identifier An optional identifier, used to identify this snapshot test.
|
||||
|
||||
@discussion In order to make the layout spec visible, it is embeded to a ASDisplayNode host.
|
||||
Any subnodes used within the layout spec must be provided.
|
||||
They will be added to the host in the same order as the array.
|
||||
*/
|
||||
- (void)testLayoutSpec:(ASLayoutSpec *)layoutSpec
|
||||
sizeRange:(ASSizeRange)sizeRange
|
||||
subnodes:(NSArray *)subnodes
|
||||
identifier:(NSString *)identifier;
|
||||
@end
|
||||
|
||||
__attribute__((overloadable)) static inline ASDisplayNode *ASDisplayNodeWithBackgroundColor(UIColor *backgroundColor, CGSize size) {
|
||||
ASDisplayNode *node = [[ASDisplayNode alloc] init];
|
||||
node.layerBacked = YES;
|
||||
node.backgroundColor = backgroundColor;
|
||||
node.style.width = ASDimensionMakeWithPoints(size.width);
|
||||
node.style.height = ASDimensionMakeWithPoints(size.height);
|
||||
return node;
|
||||
}
|
||||
|
||||
__attribute__((overloadable)) static inline ASDisplayNode *ASDisplayNodeWithBackgroundColor(UIColor *backgroundColor)
|
||||
{
|
||||
return ASDisplayNodeWithBackgroundColor(backgroundColor, CGSizeZero);
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
//
|
||||
// ASLayoutSpecSnapshotTestsHelper.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASLayoutSpecSnapshotTestsHelper.h"
|
||||
|
||||
#import <AsyncDisplayKit/ASDisplayNode.h>
|
||||
#import <AsyncDisplayKit/ASLayoutSpec.h>
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
|
||||
|
||||
@interface ASTestNode : ASDisplayNode
|
||||
@property (nonatomic, nullable) ASLayoutSpec *layoutSpecUnderTest;
|
||||
@end
|
||||
|
||||
@implementation ASLayoutSpecSnapshotTestCase
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
[super setUp];
|
||||
self.recordMode = NO;
|
||||
}
|
||||
|
||||
- (void)testLayoutSpec:(ASLayoutSpec *)layoutSpec
|
||||
sizeRange:(ASSizeRange)sizeRange
|
||||
subnodes:(NSArray *)subnodes
|
||||
identifier:(NSString *)identifier
|
||||
{
|
||||
ASTestNode *node = [[ASTestNode alloc] init];
|
||||
|
||||
for (ASDisplayNode *subnode in subnodes) {
|
||||
[node addSubnode:subnode];
|
||||
}
|
||||
|
||||
node.layoutSpecUnderTest = layoutSpec;
|
||||
|
||||
ASDisplayNodeSizeToFitSizeRange(node, sizeRange);
|
||||
ASSnapshotVerifyNode(node, identifier);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASTestNode
|
||||
- (instancetype)init
|
||||
{
|
||||
if (self = [super init]) {
|
||||
self.layerBacked = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
|
||||
{
|
||||
return _layoutSpecUnderTest;
|
||||
}
|
||||
|
||||
@end
|
@ -1,112 +0,0 @@
|
||||
//
|
||||
// ASLayoutSpecTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
#import <AsyncDisplayKit/ASLayoutElementExtensibility.h>
|
||||
|
||||
#pragma mark - ASDKExtendedLayoutSpec
|
||||
|
||||
/*
|
||||
* Extend the ASDKExtendedLayoutElement
|
||||
* It adds a
|
||||
* - primitive / CGFloat (extendedWidth)
|
||||
* - struct / ASDimension (extendedDimension)
|
||||
* - primitive / ASStackLayoutDirection (extendedDirection)
|
||||
*/
|
||||
@protocol ASDKExtendedLayoutElement <NSObject>
|
||||
@property (nonatomic) CGFloat extendedWidth;
|
||||
@property (nonatomic) ASDimension extendedDimension;
|
||||
@property (copy, nonatomic) NSString *extendedName;
|
||||
@end
|
||||
|
||||
/*
|
||||
* Let the ASLayoutElementStyle conform to the ASDKExtendedLayoutElement protocol and add properties implementation
|
||||
*/
|
||||
@interface ASLayoutElementStyle (ASDKExtendedLayoutElement) <ASDKExtendedLayoutElement>
|
||||
@end
|
||||
|
||||
@implementation ASLayoutElementStyle (ASDKExtendedLayoutElement)
|
||||
ASDK_STYLE_PROP_PRIM(CGFloat, extendedWidth, setExtendedWidth, 0);
|
||||
ASDK_STYLE_PROP_STR(ASDimension, extendedDimension, setExtendedDimension, ASDimensionMake(ASDimensionUnitAuto, 0));
|
||||
ASDK_STYLE_PROP_OBJ(NSString *, extendedName, setExtendedName);
|
||||
@end
|
||||
|
||||
/*
|
||||
* As the ASLayoutElementStyle conforms to the ASDKExtendedLayoutElement protocol now, ASDKExtendedLayoutElement properties
|
||||
* can be accessed in ASDKExtendedLayoutSpec
|
||||
*/
|
||||
@interface ASDKExtendedLayoutSpec : ASLayoutSpec
|
||||
@end
|
||||
|
||||
@implementation ASDKExtendedLayoutSpec
|
||||
|
||||
- (void)doSetSomeStyleValuesToChildren
|
||||
{
|
||||
for (id<ASLayoutElement> child in self.children) {
|
||||
child.style.extendedWidth = 100;
|
||||
child.style.extendedDimension = ASDimensionMake(100);
|
||||
child.style.extendedName = @"ASDK";
|
||||
}
|
||||
}
|
||||
|
||||
- (void)doUseSomeStyleValuesFromChildren
|
||||
{
|
||||
for (id<ASLayoutElement> child in self.children) {
|
||||
__unused CGFloat extendedWidth = child.style.extendedWidth;
|
||||
__unused ASDimension extendedDimension = child.style.extendedDimension;
|
||||
__unused NSString *extendedName = child.style.extendedName;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#pragma mark - ASLayoutSpecTests
|
||||
|
||||
@interface ASLayoutSpecTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASLayoutSpecTests
|
||||
|
||||
- (void)testSetPrimitiveToExtendedStyle
|
||||
{
|
||||
ASDisplayNode *node = [[ASDisplayNode alloc] init];
|
||||
node.style.extendedWidth = 100;
|
||||
XCTAssert(node.style.extendedWidth == 100, @"Primitive value should be set on extended style");
|
||||
}
|
||||
|
||||
- (void)testSetStructToExtendedStyle
|
||||
{
|
||||
ASDisplayNode *node = [[ASDisplayNode alloc] init];
|
||||
node.style.extendedDimension = ASDimensionMake(100);
|
||||
XCTAssertTrue(ASDimensionEqualToDimension(node.style.extendedDimension, ASDimensionMake(100)), @"Struct should be set on extended style");
|
||||
}
|
||||
|
||||
- (void)testSetObjectToExtendedStyle
|
||||
{
|
||||
NSString *extendedName = @"ASDK";
|
||||
|
||||
ASDisplayNode *node = [[ASDisplayNode alloc] init];
|
||||
node.style.extendedName = extendedName;
|
||||
XCTAssertEqualObjects(node.style.extendedName, extendedName, @"Object should be set on extended style");
|
||||
}
|
||||
|
||||
|
||||
- (void)testUseOfExtendedStyleProperties
|
||||
{
|
||||
ASDKExtendedLayoutSpec *extendedLayoutSpec = [ASDKExtendedLayoutSpec new];
|
||||
extendedLayoutSpec.children = @[[[ASDisplayNode alloc] init], [[ASDisplayNode alloc] init]];
|
||||
XCTAssertNoThrow([extendedLayoutSpec doSetSomeStyleValuesToChildren]);
|
||||
XCTAssertNoThrow([extendedLayoutSpec doUseSomeStyleValuesFromChildren]);
|
||||
}
|
||||
|
||||
@end
|
@ -1,38 +0,0 @@
|
||||
//
|
||||
// ASLayoutTestNode.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
#import <OCMock/OCMock.h>
|
||||
|
||||
@interface ASLayoutTestNode : ASDisplayNode
|
||||
|
||||
/**
|
||||
* Mocking ASDisplayNodes directly isn't very safe because when you pump mock objects
|
||||
* into the guts of the framework, bad things happen e.g. direct-ivar-access on mock
|
||||
* objects will return garbage data.
|
||||
*
|
||||
* Instead we create a strict mock for each node, and forward a selected set of calls to it.
|
||||
*/
|
||||
@property (nonatomic, readonly) id mock;
|
||||
|
||||
/**
|
||||
* The size that this node will return in calculateLayoutThatFits (if it doesn't have a layoutSpecBlock).
|
||||
*
|
||||
* Changing this value will call -setNeedsLayout on the node.
|
||||
*/
|
||||
@property (nonatomic) CGSize testSize;
|
||||
|
||||
/**
|
||||
* Generate a layout based on the frame of this node and its subtree.
|
||||
*
|
||||
* The root layout will be unpositioned. This is so that the returned layout can be directly
|
||||
* compared to `calculatedLayout`
|
||||
*/
|
||||
- (ASLayout *)currentLayoutBasedOnFrames;
|
||||
|
||||
@end
|
@ -1,88 +0,0 @@
|
||||
//
|
||||
// ASLayoutTestNode.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASLayoutTestNode.h"
|
||||
#import <OCMock/OCMock.h>
|
||||
#import "OCMockObject+ASAdditions.h"
|
||||
|
||||
@implementation ASLayoutTestNode
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_mock = OCMStrictClassMock([ASDisplayNode class]);
|
||||
|
||||
// If errors occur (e.g. unexpected method) we need to quickly figure out
|
||||
// which node is at fault, so we inject the node name into the mock instance
|
||||
// description.
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
[_mock setModifyDescriptionBlock:^(id mock, NSString *baseDescription){
|
||||
return [NSString stringWithFormat:@"Mock(%@)", weakSelf.description];
|
||||
}];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (ASLayout *)currentLayoutBasedOnFrames
|
||||
{
|
||||
return [self _currentLayoutBasedOnFramesForRootNode:YES];
|
||||
}
|
||||
|
||||
- (ASLayout *)_currentLayoutBasedOnFramesForRootNode:(BOOL)isRootNode
|
||||
{
|
||||
const auto sublayouts = [[NSMutableArray<ASLayout *> alloc] init];
|
||||
for (ASLayoutTestNode *subnode in self.subnodes) {
|
||||
[sublayouts addObject:[subnode _currentLayoutBasedOnFramesForRootNode:NO]];
|
||||
}
|
||||
CGPoint rootPosition = isRootNode ? ASPointNull : self.frame.origin;
|
||||
return [ASLayout layoutWithLayoutElement:self size:self.frame.size position:rootPosition sublayouts:sublayouts];
|
||||
}
|
||||
|
||||
- (void)setTestSize:(CGSize)testSize
|
||||
{
|
||||
if (!CGSizeEqualToSize(testSize, _testSize)) {
|
||||
_testSize = testSize;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
}
|
||||
|
||||
- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize
|
||||
{
|
||||
[_mock calculateLayoutThatFits:constrainedSize];
|
||||
|
||||
// If we have a layout spec block, or no test size, return super.
|
||||
if (self.layoutSpecBlock || CGSizeEqualToSize(self.testSize, CGSizeZero)) {
|
||||
return [super calculateLayoutThatFits:constrainedSize];
|
||||
} else {
|
||||
// Interestingly, the infra will auto-clamp sizes from calculateSizeThatFits, but not from calculateLayoutThatFits.
|
||||
const auto size = ASSizeRangeClamp(constrainedSize, self.testSize);
|
||||
return [ASLayout layoutWithLayoutElement:self size:size];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Forwarding to mock
|
||||
|
||||
- (void)calculatedLayoutDidChange
|
||||
{
|
||||
[_mock calculatedLayoutDidChange];
|
||||
[super calculatedLayoutDidChange];
|
||||
}
|
||||
|
||||
- (void)didCompleteLayoutTransition:(id<ASContextTransitioning>)context
|
||||
{
|
||||
[_mock didCompleteLayoutTransition:context];
|
||||
[super didCompleteLayoutTransition:context];
|
||||
}
|
||||
|
||||
- (void)animateLayoutTransition:(id<ASContextTransitioning>)context
|
||||
{
|
||||
[_mock animateLayoutTransition:context];
|
||||
[super animateLayoutTransition:context];
|
||||
}
|
||||
|
||||
@end
|
@ -1,265 +0,0 @@
|
||||
//
|
||||
// ASMultiplexImageNodeTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <OCMock/OCMock.h>
|
||||
#import "NSInvocation+ASTestHelpers.h"
|
||||
|
||||
#import <AsyncDisplayKit/ASImageProtocols.h>
|
||||
#import <AsyncDisplayKit/ASMultiplexImageNode.h>
|
||||
#import <AsyncDisplayKit/ASImageContainerProtocolCategories.h>
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
@interface ASMultiplexImageNodeTests : XCTestCase
|
||||
{
|
||||
@private
|
||||
id mockCache;
|
||||
id mockDownloader;
|
||||
id mockDataSource;
|
||||
id mockDelegate;
|
||||
ASMultiplexImageNode *imageNode;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASMultiplexImageNodeTests
|
||||
|
||||
#pragma mark - Helpers.
|
||||
|
||||
- (NSURL *)_testImageURL
|
||||
{
|
||||
return [[NSBundle bundleForClass:[self class]] URLForResource:@"logo-square"
|
||||
withExtension:@"png"
|
||||
subdirectory:@"TestResources"];
|
||||
}
|
||||
|
||||
- (UIImage *)_testImage
|
||||
{
|
||||
return [UIImage imageWithContentsOfFile:[self _testImageURL].path];
|
||||
}
|
||||
|
||||
#pragma mark - Unit tests.
|
||||
|
||||
// TODO: add tests for delegate display notifications
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
[super setUp];
|
||||
|
||||
mockCache = OCMStrictProtocolMock(@protocol(ASImageCacheProtocol));
|
||||
[mockCache setExpectationOrderMatters:YES];
|
||||
mockDownloader = OCMStrictProtocolMock(@protocol(ASImageDownloaderProtocol));
|
||||
[mockDownloader setExpectationOrderMatters:YES];
|
||||
imageNode = [[ASMultiplexImageNode alloc] initWithCache:mockCache downloader:mockDownloader];
|
||||
|
||||
mockDataSource = OCMStrictProtocolMock(@protocol(ASMultiplexImageNodeDataSource));
|
||||
[mockDataSource setExpectationOrderMatters:YES];
|
||||
imageNode.dataSource = mockDataSource;
|
||||
|
||||
mockDelegate = OCMProtocolMock(@protocol(ASMultiplexImageNodeDelegate));
|
||||
[mockDelegate setExpectationOrderMatters:YES];
|
||||
imageNode.delegate = mockDelegate;
|
||||
}
|
||||
|
||||
- (void)tearDown
|
||||
{
|
||||
OCMVerifyAll(mockDelegate);
|
||||
OCMVerifyAll(mockDataSource);
|
||||
OCMVerifyAll(mockDownloader);
|
||||
OCMVerifyAll(mockCache);
|
||||
[super tearDown];
|
||||
}
|
||||
|
||||
- (void)testDataSourceImageMethod
|
||||
{
|
||||
NSNumber *imageIdentifier = @1;
|
||||
|
||||
OCMExpect([mockDataSource multiplexImageNode:imageNode imageForImageIdentifier:imageIdentifier])
|
||||
.andReturn([self _testImage]);
|
||||
|
||||
imageNode.imageIdentifiers = @[imageIdentifier];
|
||||
[imageNode reloadImageIdentifierSources];
|
||||
|
||||
// Also expect it to be loaded immediately.
|
||||
XCTAssertEqualObjects(imageNode.loadedImageIdentifier, imageIdentifier, @"imageIdentifier was not loaded");
|
||||
// And for the image to be equivalent to the image we provided.
|
||||
XCTAssertEqualObjects(UIImagePNGRepresentation(imageNode.image),
|
||||
UIImagePNGRepresentation([self _testImage]),
|
||||
@"Loaded image isn't the one we provided");
|
||||
}
|
||||
|
||||
- (void)testDataSourceURLMethod
|
||||
{
|
||||
NSNumber *imageIdentifier = @1;
|
||||
|
||||
// First expect to be hit for the image directly, and fail to return it.
|
||||
OCMExpect([mockDataSource multiplexImageNode:imageNode imageForImageIdentifier:imageIdentifier])
|
||||
.andReturn((id)nil);
|
||||
// BUG: -imageForImageIdentifier is called twice in this case (where we return nil).
|
||||
OCMExpect([mockDataSource multiplexImageNode:imageNode imageForImageIdentifier:imageIdentifier])
|
||||
.andReturn((id)nil);
|
||||
// Then expect to be hit for the URL, which we'll return.
|
||||
OCMExpect([mockDataSource multiplexImageNode:imageNode URLForImageIdentifier:imageIdentifier])
|
||||
.andReturn([self _testImageURL]);
|
||||
|
||||
// Mock the cache to do a cache-hit for the test image URL.
|
||||
OCMExpect([mockCache cachedImageWithURL:[self _testImageURL] callbackQueue:OCMOCK_ANY completion:[OCMArg isNotNil]])
|
||||
.andDo(^(NSInvocation *inv) {
|
||||
ASImageCacherCompletion completionBlock = [inv as_argumentAtIndexAsObject:4];
|
||||
completionBlock([self _testImage]);
|
||||
});
|
||||
|
||||
imageNode.imageIdentifiers = @[imageIdentifier];
|
||||
// Kick off loading.
|
||||
[imageNode reloadImageIdentifierSources];
|
||||
|
||||
// Also expect it to be loaded immediately.
|
||||
XCTAssertEqualObjects(imageNode.loadedImageIdentifier, imageIdentifier, @"imageIdentifier was not loaded");
|
||||
// And for the image to be equivalent to the image we provided.
|
||||
XCTAssertEqualObjects(UIImagePNGRepresentation(imageNode.image),
|
||||
UIImagePNGRepresentation([self _testImage]),
|
||||
@"Loaded image isn't the one we provided");
|
||||
}
|
||||
|
||||
- (void)testAddLowerQualityImageIdentifier
|
||||
{
|
||||
// Adding a lower quality image identifier should not cause any loading.
|
||||
NSNumber *highResIdentifier = @2, *lowResIdentifier = @1;
|
||||
|
||||
OCMExpect([mockDataSource multiplexImageNode:imageNode imageForImageIdentifier:highResIdentifier])
|
||||
.andReturn([self _testImage]);
|
||||
imageNode.imageIdentifiers = @[highResIdentifier];
|
||||
[imageNode reloadImageIdentifierSources];
|
||||
|
||||
// At this point, we should have the high-res identifier loaded and the DS should have been hit once.
|
||||
XCTAssertEqualObjects(imageNode.loadedImageIdentifier, highResIdentifier, @"High res identifier should be loaded.");
|
||||
|
||||
// BUG: We should not get another -imageForImageIdentifier:highResIdentifier.
|
||||
OCMExpect([mockDataSource multiplexImageNode:imageNode imageForImageIdentifier:highResIdentifier])
|
||||
.andReturn([self _testImage]);
|
||||
|
||||
imageNode.imageIdentifiers = @[highResIdentifier, lowResIdentifier];
|
||||
[imageNode reloadImageIdentifierSources];
|
||||
|
||||
// At this point the high-res should still be loaded, and the data source should not have been hit again (see BUG above).
|
||||
XCTAssertEqualObjects(imageNode.loadedImageIdentifier, highResIdentifier, @"High res identifier should be loaded.");
|
||||
}
|
||||
|
||||
- (void)testAddHigherQualityImageIdentifier
|
||||
{
|
||||
NSNumber *lowResIdentifier = @1, *highResIdentifier = @2;
|
||||
|
||||
OCMExpect([mockDataSource multiplexImageNode:imageNode imageForImageIdentifier:lowResIdentifier])
|
||||
.andReturn([self _testImage]);
|
||||
|
||||
imageNode.imageIdentifiers = @[lowResIdentifier];
|
||||
[imageNode reloadImageIdentifierSources];
|
||||
|
||||
// At this point, we should have the low-res identifier loaded and the DS should have been hit once.
|
||||
XCTAssertEqualObjects(imageNode.loadedImageIdentifier, lowResIdentifier, @"Low res identifier should be loaded.");
|
||||
|
||||
OCMExpect([mockDataSource multiplexImageNode:imageNode imageForImageIdentifier:highResIdentifier])
|
||||
.andReturn([self _testImage]);
|
||||
|
||||
imageNode.imageIdentifiers = @[highResIdentifier, lowResIdentifier];
|
||||
[imageNode reloadImageIdentifierSources];
|
||||
|
||||
// At this point the high-res should be loaded, and the data source should been hit twice.
|
||||
XCTAssertEqualObjects(imageNode.loadedImageIdentifier, highResIdentifier, @"High res identifier should be loaded.");
|
||||
}
|
||||
|
||||
- (void)testIntermediateImageDownloading
|
||||
{
|
||||
imageNode.downloadsIntermediateImages = YES;
|
||||
|
||||
// Let them call URLForImageIdentifier all they want.
|
||||
OCMStub([mockDataSource multiplexImageNode:imageNode URLForImageIdentifier:[OCMArg isNotNil]]);
|
||||
|
||||
// Set up a few identifiers to load.
|
||||
NSInteger identifierCount = 5;
|
||||
NSMutableArray *imageIdentifiers = [NSMutableArray array];
|
||||
for (NSInteger identifier = identifierCount; identifier > 0; identifier--) {
|
||||
[imageIdentifiers addObject:@(identifier)];
|
||||
}
|
||||
|
||||
// Create the array of IDs in the order we expect them to get -imageForImageIdentifier:
|
||||
// BUG: The second to last ID (the last one that returns nil) will get -imageForImageIdentifier: called
|
||||
// again after the last ID (the one that returns non-nil).
|
||||
id secondToLastID = imageIdentifiers[identifierCount - 2];
|
||||
NSArray *imageIdentifiersThatWillBeCalled = [imageIdentifiers arrayByAddingObject:secondToLastID];
|
||||
|
||||
for (id imageID in imageIdentifiersThatWillBeCalled) {
|
||||
// Return nil for everything except the worst ID.
|
||||
OCMExpect([mockDataSource multiplexImageNode:imageNode imageForImageIdentifier:imageID])
|
||||
.andDo(^(NSInvocation *inv){
|
||||
id imageID = [inv as_argumentAtIndexAsObject:3];
|
||||
if ([imageID isEqual:imageIdentifiers.lastObject]) {
|
||||
[inv as_setReturnValueWithObject:[self _testImage]];
|
||||
} else {
|
||||
[inv as_setReturnValueWithObject:nil];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
imageNode.imageIdentifiers = imageIdentifiers;
|
||||
[imageNode reloadImageIdentifierSources];
|
||||
}
|
||||
|
||||
- (void)testUncachedDownload
|
||||
{
|
||||
// Mock a cache miss.
|
||||
OCMExpect([mockCache cachedImageWithURL:[self _testImageURL] callbackQueue:OCMOCK_ANY completion:[OCMArg isNotNil]])
|
||||
.andDo(^(NSInvocation *inv){
|
||||
ASImageCacherCompletion completion = [inv as_argumentAtIndexAsObject:4];
|
||||
completion(nil);
|
||||
});
|
||||
|
||||
// Mock a 50%-progress URL download.
|
||||
const CGFloat mockedProgress = 0.5;
|
||||
OCMExpect([mockDownloader downloadImageWithURL:[self _testImageURL] callbackQueue:OCMOCK_ANY downloadProgress:[OCMArg isNotNil] completion:[OCMArg isNotNil]])
|
||||
.andDo(^(NSInvocation *inv){
|
||||
// Simulate progress.
|
||||
ASImageDownloaderProgress progressBlock = [inv as_argumentAtIndexAsObject:4];
|
||||
progressBlock(mockedProgress);
|
||||
|
||||
// Simulate completion.
|
||||
ASImageDownloaderCompletion completionBlock = [inv as_argumentAtIndexAsObject:5];
|
||||
completionBlock([self _testImage], nil, nil, nil);
|
||||
});
|
||||
|
||||
NSNumber *imageIdentifier = @1;
|
||||
|
||||
// Mock the data source to return nil image, and our test URL.
|
||||
OCMExpect([mockDataSource multiplexImageNode:imageNode imageForImageIdentifier:imageIdentifier]);
|
||||
// BUG: Multiplex image node will call imageForImageIdentifier twice if we return nil.
|
||||
OCMExpect([mockDataSource multiplexImageNode:imageNode imageForImageIdentifier:imageIdentifier]);
|
||||
OCMExpect([mockDataSource multiplexImageNode:imageNode URLForImageIdentifier:imageIdentifier])
|
||||
.andReturn([self _testImageURL]);
|
||||
|
||||
// Mock the delegate to expect start, 50% progress, and completion invocations.
|
||||
OCMExpect([mockDelegate multiplexImageNode:imageNode didStartDownloadOfImageWithIdentifier:imageIdentifier]);
|
||||
OCMExpect([mockDelegate multiplexImageNode:imageNode didUpdateDownloadProgress:mockedProgress forImageWithIdentifier:imageIdentifier]);
|
||||
OCMExpect([mockDelegate multiplexImageNode:imageNode didUpdateImage:[OCMArg isNotNil] withIdentifier:imageIdentifier fromImage:[OCMArg isNil] withIdentifier:[OCMArg isNil]]);
|
||||
OCMExpect([mockDelegate multiplexImageNode:imageNode didFinishDownloadingImageWithIdentifier:imageIdentifier error:[OCMArg isNil]]);
|
||||
|
||||
imageNode.imageIdentifiers = @[imageIdentifier];
|
||||
// Kick off loading.
|
||||
[imageNode reloadImageIdentifierSources];
|
||||
|
||||
// Wait until the image is loaded.
|
||||
[self expectationForPredicate:[NSPredicate predicateWithFormat:@"loadedImageIdentifier = %@", imageIdentifier] evaluatedWithObject:imageNode handler:nil];
|
||||
[self waitForExpectationsWithTimeout:30 handler:nil];
|
||||
}
|
||||
|
||||
- (void)testThatSettingAnImageExternallyWillThrow
|
||||
{
|
||||
XCTAssertThrows(imageNode.image = [UIImage imageNamed:@""]);
|
||||
}
|
||||
|
||||
@end
|
@ -1,78 +0,0 @@
|
||||
//
|
||||
// ASMutableAttributedStringBuilderTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASMutableAttributedStringBuilder.h>
|
||||
|
||||
@interface ASMutableAttributedStringBuilderTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASMutableAttributedStringBuilderTests
|
||||
|
||||
- (NSString *)_string
|
||||
{
|
||||
return @"Normcore PBR hella, viral slow-carb mustache chillwave church-key cornhole messenger bag swag vinyl biodiesel ethnic. Fashion axe messenger bag raw denim street art. Flannel Wes Anderson normcore church-key 8-bit. Master cleanse four loko try-hard Carles stumptown ennui, twee literally wayfarers kitsch tofu PBR. Cliche organic post-ironic Wes Anderson kale chips fashion axe. Narwhal Blue Bottle sustainable, Odd Future Godard sriracha banjo disrupt Marfa irony pug Wes Anderson YOLO yr church-key. Mlkshk Intelligentsia semiotics quinoa, butcher meggings wolf Bushwick keffiyeh ethnic pour-over Pinterest letterpress.";
|
||||
}
|
||||
|
||||
- (ASMutableAttributedStringBuilder *)_builder
|
||||
{
|
||||
return [[ASMutableAttributedStringBuilder alloc] initWithString:[self _string]];
|
||||
}
|
||||
|
||||
- (NSRange)_randomizedRangeForStringBuilder:(ASMutableAttributedStringBuilder *)builder
|
||||
{
|
||||
NSUInteger loc = arc4random() % (builder.length - 1);
|
||||
NSUInteger len = arc4random() % (builder.length - loc);
|
||||
len = ((len > 0) ? len : 1);
|
||||
return NSMakeRange(loc, len);
|
||||
}
|
||||
|
||||
- (void)testSimpleAttributions
|
||||
{
|
||||
// Add a attributes, and verify that they get set on the correct locations.
|
||||
for (int i = 0; i < 100; i++) {
|
||||
ASMutableAttributedStringBuilder *builder = [self _builder];
|
||||
NSRange range = [self _randomizedRangeForStringBuilder:builder];
|
||||
NSString *keyValue = [NSString stringWithFormat:@"%d", i];
|
||||
[builder addAttribute:keyValue value:keyValue range:range];
|
||||
NSAttributedString *attrStr = [builder composedAttributedString];
|
||||
XCTAssertEqual(builder.length, attrStr.length, @"out string should have same length as builder");
|
||||
__block BOOL found = NO;
|
||||
[attrStr enumerateAttributesInRange:NSMakeRange(0, attrStr.length) options:0 usingBlock:^(NSDictionary *attrs, NSRange r, BOOL *stop) {
|
||||
if ([attrs[keyValue] isEqualToString:keyValue]) {
|
||||
XCTAssertTrue(NSEqualRanges(range, r), @"enumerated range %@ should be equal to the set range %@", NSStringFromRange(r), NSStringFromRange(range));
|
||||
found = YES;
|
||||
}
|
||||
}];
|
||||
XCTAssertTrue(found, @"enumeration should have found the attribute we set");
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testSetOverAdd
|
||||
{
|
||||
ASMutableAttributedStringBuilder *builder = [self _builder];
|
||||
NSRange addRange = NSMakeRange(0, builder.length);
|
||||
NSRange setRange = NSMakeRange(0, 1);
|
||||
[builder addAttribute:@"attr" value:@"val1" range:addRange];
|
||||
[builder setAttributes:@{@"attr" : @"val2"} range:setRange];
|
||||
NSAttributedString *attrStr = [builder composedAttributedString];
|
||||
NSRange setRangeOut;
|
||||
NSString *setAttr = [attrStr attribute:@"attr" atIndex:0 effectiveRange:&setRangeOut];
|
||||
XCTAssertTrue(NSEqualRanges(setRange, setRangeOut), @"The out set range should equal the range we used originally");
|
||||
XCTAssertEqualObjects(setAttr, @"val2", @"the set value should be val2");
|
||||
|
||||
NSRange addRangeOut;
|
||||
NSString *addAttr = [attrStr attribute:@"attr" atIndex:2 effectiveRange:&addRangeOut];
|
||||
XCTAssertTrue(NSEqualRanges(NSMakeRange(1, builder.length - 1), addRangeOut), @"the add range should only cover beyond the set range");
|
||||
XCTAssertEqualObjects(addAttr, @"val1", @"the added attribute should be present at index 2");
|
||||
}
|
||||
|
||||
@end
|
@ -1,52 +0,0 @@
|
||||
//
|
||||
// ASNavigationControllerTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
|
||||
@interface ASNavigationControllerTests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation ASNavigationControllerTests
|
||||
|
||||
- (void)testSetViewControllers {
|
||||
ASViewController *firstController = [ASViewController new];
|
||||
ASViewController *secondController = [ASViewController new];
|
||||
NSArray *expectedViewControllerStack = @[firstController, secondController];
|
||||
ASNavigationController *navigationController = [ASNavigationController new];
|
||||
[navigationController setViewControllers:@[firstController, secondController]];
|
||||
XCTAssertEqual(navigationController.topViewController, secondController);
|
||||
XCTAssertEqual(navigationController.visibleViewController, secondController);
|
||||
XCTAssertTrue([navigationController.viewControllers isEqualToArray:expectedViewControllerStack]);
|
||||
}
|
||||
|
||||
- (void)testPopViewController {
|
||||
ASViewController *firstController = [ASViewController new];
|
||||
ASViewController *secondController = [ASViewController new];
|
||||
NSArray *expectedViewControllerStack = @[firstController];
|
||||
ASNavigationController *navigationController = [ASNavigationController new];
|
||||
[navigationController setViewControllers:@[firstController, secondController]];
|
||||
[navigationController popViewControllerAnimated:false];
|
||||
XCTAssertEqual(navigationController.topViewController, firstController);
|
||||
XCTAssertEqual(navigationController.visibleViewController, firstController);
|
||||
XCTAssertTrue([navigationController.viewControllers isEqualToArray:expectedViewControllerStack]);
|
||||
}
|
||||
|
||||
- (void)testPushViewController {
|
||||
ASViewController *firstController = [ASViewController new];
|
||||
ASViewController *secondController = [ASViewController new];
|
||||
NSArray *expectedViewControllerStack = @[firstController, secondController];
|
||||
ASNavigationController *navigationController = [[ASNavigationController new] initWithRootViewController:firstController];
|
||||
[navigationController pushViewController:secondController animated:false];
|
||||
XCTAssertEqual(navigationController.topViewController, secondController);
|
||||
XCTAssertEqual(navigationController.visibleViewController, secondController);
|
||||
XCTAssertTrue([navigationController.viewControllers isEqualToArray:expectedViewControllerStack]);
|
||||
}
|
||||
|
||||
@end
|
@ -1,135 +0,0 @@
|
||||
//
|
||||
// ASNetworkImageNodeTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import <OCMock/OCMock.h>
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
|
||||
|
||||
@interface ASNetworkImageNodeTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@interface ASTestImageDownloader : NSObject <ASImageDownloaderProtocol>
|
||||
@end
|
||||
@interface ASTestImageCache : NSObject <ASImageCacheProtocol>
|
||||
@end
|
||||
|
||||
@implementation ASNetworkImageNodeTests {
|
||||
ASNetworkImageNode *node;
|
||||
id downloader;
|
||||
id cache;
|
||||
}
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
[super setUp];
|
||||
cache = [OCMockObject partialMockForObject:[[ASTestImageCache alloc] init]];
|
||||
downloader = [OCMockObject partialMockForObject:[[ASTestImageDownloader alloc] init]];
|
||||
node = [[ASNetworkImageNode alloc] initWithCache:cache downloader:downloader];
|
||||
}
|
||||
|
||||
/// Test is flaky: https://github.com/facebook/AsyncDisplayKit/issues/2898
|
||||
- (void)DISABLED_testThatProgressBlockIsSetAndClearedCorrectlyOnVisibility
|
||||
{
|
||||
node.URL = [NSURL URLWithString:@"http://imageA"];
|
||||
|
||||
// Enter preload range, wait for download start.
|
||||
[[[downloader expect] andForwardToRealObject] downloadImageWithURL:[OCMArg isNotNil] callbackQueue:OCMOCK_ANY downloadProgress:OCMOCK_ANY completion:OCMOCK_ANY];
|
||||
[node enterInterfaceState:ASInterfaceStatePreload];
|
||||
[downloader verifyWithDelay:5];
|
||||
|
||||
// Make the node visible.
|
||||
[[downloader expect] setProgressImageBlock:[OCMArg isNotNil] callbackQueue:OCMOCK_ANY withDownloadIdentifier:@0];
|
||||
[node enterInterfaceState:ASInterfaceStateInHierarchy];
|
||||
[downloader verify];
|
||||
|
||||
// Make the node invisible.
|
||||
[[downloader expect] setProgressImageBlock:[OCMArg isNil] callbackQueue:OCMOCK_ANY withDownloadIdentifier:@0];
|
||||
[node exitInterfaceState:ASInterfaceStateInHierarchy];
|
||||
[downloader verify];
|
||||
}
|
||||
|
||||
- (void)testThatProgressBlockIsSetAndClearedCorrectlyOnChangeURL
|
||||
{
|
||||
[node layer];
|
||||
[node enterInterfaceState:ASInterfaceStateInHierarchy];
|
||||
|
||||
// Set URL while visible, should set progress block
|
||||
[[downloader expect] setProgressImageBlock:[OCMArg isNotNil] callbackQueue:OCMOCK_ANY withDownloadIdentifier:@0];
|
||||
node.URL = [NSURL URLWithString:@"http://imageA"];
|
||||
[downloader verifyWithDelay:5];
|
||||
|
||||
// Change URL while visible, should clear prior block and set new one
|
||||
[[downloader expect] setProgressImageBlock:[OCMArg isNil] callbackQueue:OCMOCK_ANY withDownloadIdentifier:@0];
|
||||
[[downloader expect] cancelImageDownloadForIdentifier:@0];
|
||||
[[downloader expect] setProgressImageBlock:[OCMArg isNotNil] callbackQueue:OCMOCK_ANY withDownloadIdentifier:@1];
|
||||
node.URL = [NSURL URLWithString:@"http://imageB"];
|
||||
[downloader verifyWithDelay:5];
|
||||
}
|
||||
|
||||
- (void)testThatSettingAnImageWillStayForEnteringAndExitingPreloadState
|
||||
{
|
||||
UIImage *image = [[UIImage alloc] init];
|
||||
ASNetworkImageNode *networkImageNode = [[ASNetworkImageNode alloc] init];
|
||||
networkImageNode.image = image;
|
||||
[networkImageNode enterHierarchyState:ASHierarchyStateRangeManaged]; // Ensures didExitPreloadState is called
|
||||
XCTAssertEqualObjects(image, networkImageNode.image);
|
||||
[networkImageNode enterInterfaceState:ASInterfaceStatePreload];
|
||||
XCTAssertEqualObjects(image, networkImageNode.image);
|
||||
[networkImageNode exitInterfaceState:ASInterfaceStatePreload];
|
||||
XCTAssertEqualObjects(image, networkImageNode.image);
|
||||
[networkImageNode exitHierarchyState:ASHierarchyStateRangeManaged];
|
||||
XCTAssertEqualObjects(image, networkImageNode.image);
|
||||
}
|
||||
|
||||
- (void)testThatSettingADefaultImageWillStayForEnteringAndExitingPreloadState
|
||||
{
|
||||
UIImage *image = [[UIImage alloc] init];
|
||||
ASNetworkImageNode *networkImageNode = [[ASNetworkImageNode alloc] init];
|
||||
networkImageNode.defaultImage = image;
|
||||
[networkImageNode enterHierarchyState:ASHierarchyStateRangeManaged]; // Ensures didExitPreloadState is called
|
||||
XCTAssertEqualObjects(image, networkImageNode.defaultImage);
|
||||
[networkImageNode enterInterfaceState:ASInterfaceStatePreload];
|
||||
XCTAssertEqualObjects(image, networkImageNode.defaultImage);
|
||||
[networkImageNode exitInterfaceState:ASInterfaceStatePreload];
|
||||
XCTAssertEqualObjects(image, networkImageNode.defaultImage);
|
||||
[networkImageNode exitHierarchyState:ASHierarchyStateRangeManaged];
|
||||
XCTAssertEqualObjects(image, networkImageNode.defaultImage);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASTestImageCache
|
||||
|
||||
- (void)cachedImageWithURL:(NSURL *)URL callbackQueue:(dispatch_queue_t)callbackQueue completion:(ASImageCacherCompletion)completion
|
||||
{
|
||||
completion(nil);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASTestImageDownloader {
|
||||
NSInteger _currentDownloadID;
|
||||
}
|
||||
|
||||
- (void)cancelImageDownloadForIdentifier:(id)downloadIdentifier
|
||||
{
|
||||
// nop
|
||||
}
|
||||
|
||||
- (id)downloadImageWithURL:(NSURL *)URL callbackQueue:(dispatch_queue_t)callbackQueue downloadProgress:(ASImageDownloaderProgress)downloadProgress completion:(ASImageDownloaderCompletion)completion
|
||||
{
|
||||
return @(_currentDownloadID++);
|
||||
}
|
||||
|
||||
- (void)setProgressImageBlock:(ASImageDownloaderProgressImage)progressBlock callbackQueue:(dispatch_queue_t)callbackQueue withDownloadIdentifier:(id)downloadIdentifier
|
||||
{
|
||||
// nop
|
||||
}
|
||||
@end
|
@ -1,39 +0,0 @@
|
||||
//
|
||||
// ASOverlayLayoutSpecSnapshotTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASLayoutSpecSnapshotTestsHelper.h"
|
||||
|
||||
#import <AsyncDisplayKit/ASOverlayLayoutSpec.h>
|
||||
#import <AsyncDisplayKit/ASCenterLayoutSpec.h>
|
||||
|
||||
static const ASSizeRange kSize = {{320, 320}, {320, 320}};
|
||||
|
||||
@interface ASOverlayLayoutSpecSnapshotTests : ASLayoutSpecSnapshotTestCase
|
||||
@end
|
||||
|
||||
@implementation ASOverlayLayoutSpecSnapshotTests
|
||||
|
||||
- (void)testOverlay
|
||||
{
|
||||
ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor blueColor]);
|
||||
ASDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor blackColor], {20, 20});
|
||||
|
||||
ASLayoutSpec *layoutSpec =
|
||||
[ASOverlayLayoutSpec
|
||||
overlayLayoutSpecWithChild:backgroundNode
|
||||
overlay:
|
||||
[ASCenterLayoutSpec
|
||||
centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringXY
|
||||
sizingOptions:{}
|
||||
child:foregroundNode]];
|
||||
|
||||
[self testLayoutSpec:layoutSpec sizeRange:kSize subnodes:@[backgroundNode, foregroundNode] identifier: nil];
|
||||
}
|
||||
|
||||
@end
|
@ -1,179 +0,0 @@
|
||||
//
|
||||
// ASPagerNodeTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
|
||||
@interface ASPagerNodeTestDataSource : NSObject <ASPagerDataSource>
|
||||
@end
|
||||
|
||||
@implementation ASPagerNodeTestDataSource
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (!(self = [super init])) {
|
||||
return nil;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSInteger)numberOfPagesInPagerNode:(ASPagerNode *)pagerNode
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
- (ASCellNode *)pagerNode:(ASPagerNode *)pagerNode nodeAtIndex:(NSInteger)index
|
||||
{
|
||||
return [[ASCellNode alloc] init];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface ASPagerNodeTestController: UIViewController
|
||||
@property (nonatomic) ASPagerNodeTestDataSource *testDataSource;
|
||||
@property (nonatomic) ASPagerNode *pagerNode;
|
||||
@end
|
||||
|
||||
@implementation ASPagerNodeTestController
|
||||
|
||||
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
|
||||
{
|
||||
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
|
||||
if (self) {
|
||||
// Populate these immediately so that they're not unexpectedly nil during tests.
|
||||
self.testDataSource = [[ASPagerNodeTestDataSource alloc] init];
|
||||
|
||||
self.pagerNode = [[ASPagerNode alloc] init];
|
||||
self.pagerNode.dataSource = self.testDataSource;
|
||||
|
||||
[self.view addSubnode:self.pagerNode];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface ASPagerNodeTests : XCTestCase
|
||||
@property (nonatomic) ASPagerNode *pagerNode;
|
||||
|
||||
@property (nonatomic) ASPagerNodeTestDataSource *testDataSource;
|
||||
@end
|
||||
|
||||
@implementation ASPagerNodeTests
|
||||
|
||||
- (void)testPagerReturnsIndexOfPages
|
||||
{
|
||||
ASPagerNodeTestController *testController = [self testController];
|
||||
|
||||
ASCellNode *cellNode = [testController.pagerNode nodeForPageAtIndex:0];
|
||||
|
||||
XCTAssertEqual([testController.pagerNode indexOfPageWithNode:cellNode], 0);
|
||||
}
|
||||
|
||||
- (void)testPagerReturnsNotFoundForCellThatDontExistInPager
|
||||
{
|
||||
ASPagerNodeTestController *testController = [self testController];
|
||||
|
||||
ASCellNode *badNode = [[ASCellNode alloc] init];
|
||||
|
||||
XCTAssertEqual([testController.pagerNode indexOfPageWithNode:badNode], NSNotFound);
|
||||
}
|
||||
|
||||
- (void)testScrollPageToIndex
|
||||
{
|
||||
ASPagerNodeTestController *testController = [self testController];
|
||||
testController.pagerNode.frame = CGRectMake(0, 0, 500, 500);
|
||||
[testController.pagerNode scrollToPageAtIndex:1 animated:false];
|
||||
|
||||
XCTAssertEqual(testController.pagerNode.currentPageIndex, 1);
|
||||
}
|
||||
|
||||
- (ASPagerNodeTestController *)testController
|
||||
{
|
||||
ASPagerNodeTestController *testController = [[ASPagerNodeTestController alloc] initWithNibName:nil bundle:nil];
|
||||
UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
|
||||
[window makeKeyAndVisible];
|
||||
window.rootViewController = testController;
|
||||
|
||||
[testController.pagerNode reloadData];
|
||||
[testController.pagerNode setNeedsLayout];
|
||||
|
||||
return testController;
|
||||
}
|
||||
|
||||
// Disabled due to flakiness https://github.com/facebook/AsyncDisplayKit/issues/2818
|
||||
- (void)DISABLED_testThatRootPagerNodeDoesGetTheRightInsetWhilePoppingBack
|
||||
{
|
||||
UICollectionViewCell *cell = nil;
|
||||
|
||||
UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
||||
ASDisplayNode *node = [[ASDisplayNode alloc] init];
|
||||
node.automaticallyManagesSubnodes = YES;
|
||||
|
||||
ASPagerNodeTestDataSource *dataSource = [[ASPagerNodeTestDataSource alloc] init];
|
||||
ASPagerNode *pagerNode = [[ASPagerNode alloc] init];
|
||||
pagerNode.dataSource = dataSource;
|
||||
node.layoutSpecBlock = ^(ASDisplayNode *node, ASSizeRange constrainedSize){
|
||||
return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsZero child:pagerNode];
|
||||
};
|
||||
ASViewController *vc = [[ASViewController alloc] initWithNode:node];
|
||||
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc];
|
||||
window.rootViewController = nav;
|
||||
[window makeKeyAndVisible];
|
||||
[window layoutIfNeeded];
|
||||
|
||||
// Wait until view controller is visible
|
||||
XCTestExpectation *e = [self expectationWithDescription:@"Transition completed"];
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
|
||||
[e fulfill];
|
||||
});
|
||||
[self waitForExpectationsWithTimeout:2 handler:nil];
|
||||
|
||||
// Test initial values
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
cell = [pagerNode.view cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
|
||||
#pragma clang diagnostic pop
|
||||
XCTAssertEqualObjects(NSStringFromCGRect(window.bounds), NSStringFromCGRect(node.frame));
|
||||
XCTAssertEqualObjects(NSStringFromCGRect(window.bounds), NSStringFromCGRect(cell.frame));
|
||||
XCTAssertEqual(pagerNode.contentOffset.y, 0);
|
||||
XCTAssertEqual(pagerNode.contentInset.top, 0);
|
||||
|
||||
e = [self expectationWithDescription:@"Transition completed"];
|
||||
// Push another view controller
|
||||
UIViewController *vc2 = [[UIViewController alloc] init];
|
||||
vc2.view.frame = nav.view.bounds;
|
||||
vc2.view.backgroundColor = [UIColor blueColor];
|
||||
[nav pushViewController:vc2 animated:YES];
|
||||
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.505 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
|
||||
[e fulfill];
|
||||
});
|
||||
[self waitForExpectationsWithTimeout:2 handler:nil];
|
||||
|
||||
// Pop view controller
|
||||
e = [self expectationWithDescription:@"Transition completed"];
|
||||
[vc2.navigationController popViewControllerAnimated:YES];
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.505 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
|
||||
[e fulfill];
|
||||
});
|
||||
[self waitForExpectationsWithTimeout:2 handler:nil];
|
||||
|
||||
// Test values again after popping the view controller
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
cell = [pagerNode.view cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
|
||||
#pragma clang diagnostic pop
|
||||
XCTAssertEqualObjects(NSStringFromCGRect(window.bounds), NSStringFromCGRect(node.frame));
|
||||
XCTAssertEqualObjects(NSStringFromCGRect(window.bounds), NSStringFromCGRect(cell.frame));
|
||||
XCTAssertEqual(pagerNode.contentOffset.y, 0);
|
||||
XCTAssertEqual(pagerNode.contentInset.top, 0);
|
||||
}
|
||||
|
||||
@end
|
@ -1,44 +0,0 @@
|
||||
//
|
||||
// ASPerformanceTestContext.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <XCTest/XCTestAssertionsImpl.h>
|
||||
#import <AsyncDisplayKit/ASBaseDefines.h>
|
||||
|
||||
#define ASXCTAssertRelativePerformanceInRange(test, caseName, min, max) \
|
||||
_XCTPrimitiveAssertLessThanOrEqual(self, test.results[caseName].relativePerformance, @#caseName, max, @#max);\
|
||||
_XCTPrimitiveAssertGreaterThanOrEqual(self, test.results[caseName].relativePerformance, @#caseName, min, @#min)
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef void (^ASTestPerformanceCaseBlock)(NSUInteger i, dispatch_block_t startMeasuring, dispatch_block_t stopMeasuring);
|
||||
|
||||
@interface ASPerformanceTestResult : NSObject
|
||||
@property (nonatomic, readonly) NSTimeInterval timePer1000;
|
||||
@property (nonatomic, readonly) NSString *caseName;
|
||||
|
||||
@property (nonatomic, readonly, getter=isReferenceCase) BOOL referenceCase;
|
||||
@property (nonatomic, readonly) float relativePerformance;
|
||||
|
||||
@property (nonatomic, readonly) NSMutableDictionary *userInfo;
|
||||
@end
|
||||
|
||||
@interface ASPerformanceTestContext : NSObject
|
||||
|
||||
/**
|
||||
* The first case you add here will be considered the reference case.
|
||||
*/
|
||||
- (void)addCaseWithName:(NSString *)caseName block:(AS_NOESCAPE ASTestPerformanceCaseBlock)block;
|
||||
|
||||
@property (nonatomic, copy, readonly) NSDictionary<NSString *, ASPerformanceTestResult *> *results;
|
||||
|
||||
- (BOOL)areAllUserInfosEqual;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
@ -1,135 +0,0 @@
|
||||
//
|
||||
// ASPerformanceTestContext.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASPerformanceTestContext.h"
|
||||
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <QuartzCore/CABase.h>
|
||||
|
||||
@interface ASPerformanceTestResult ()
|
||||
@property (nonatomic) NSTimeInterval timePer1000;
|
||||
@property (nonatomic) NSString *caseName;
|
||||
|
||||
@property (nonatomic, getter=isReferenceCase) BOOL referenceCase;
|
||||
@property (nonatomic) float relativePerformance;
|
||||
@end
|
||||
|
||||
@implementation ASPerformanceTestResult
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_userInfo = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
NSString *userInfoStr = [_userInfo.description stringByReplacingOccurrencesOfString:@"\n" withString:@" "];
|
||||
return [NSString stringWithFormat:@"<%-20s: time-per-1000=%04.2f rel-perf=%04.2f user-info=%@>", _caseName.UTF8String, _timePer1000, _relativePerformance, userInfoStr];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASPerformanceTestContext {
|
||||
NSMutableDictionary *_results;
|
||||
NSInteger _iterationCount;
|
||||
ASPerformanceTestResult * _Nullable _referenceResult;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_iterationCount = 1E4;
|
||||
_results = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSDictionary<NSString *, ASPerformanceTestResult *> *)results
|
||||
{
|
||||
return _results;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
/**
|
||||
* I know this seems wacky but it's a pain to have to put this in every single test method.
|
||||
*/
|
||||
NSLog(@"%@", self.description);
|
||||
}
|
||||
|
||||
- (BOOL)areAllUserInfosEqual
|
||||
{
|
||||
ASDisplayNodeAssert(_results.count >= 2, nil);
|
||||
NSEnumerator *resultsEnumerator = [_results objectEnumerator];
|
||||
NSDictionary *userInfo = [[resultsEnumerator nextObject] userInfo];
|
||||
for (ASPerformanceTestResult *otherResult in resultsEnumerator) {
|
||||
if ([userInfo isEqualToDictionary:otherResult.userInfo] == NO) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)addCaseWithName:(NSString *)caseName block:(AS_NOESCAPE ASTestPerformanceCaseBlock)block
|
||||
{
|
||||
ASDisplayNodeAssert(_results[caseName] == nil, @"Already have a case named %@", caseName);
|
||||
ASPerformanceTestResult *result = [[ASPerformanceTestResult alloc] init];
|
||||
result.caseName = caseName;
|
||||
result.timePer1000 = [self _testPerformanceForCaseWithBlock:block] / (_iterationCount / 1000);
|
||||
if (_referenceResult == nil) {
|
||||
result.referenceCase = YES;
|
||||
result.relativePerformance = 1.0f;
|
||||
_referenceResult = result;
|
||||
} else {
|
||||
result.relativePerformance = _referenceResult.timePer1000 / result.timePer1000;
|
||||
}
|
||||
_results[caseName] = result;
|
||||
}
|
||||
|
||||
/// Returns total work time
|
||||
- (CFTimeInterval)_testPerformanceForCaseWithBlock:(AS_NOESCAPE ASTestPerformanceCaseBlock)block
|
||||
{
|
||||
__block CFTimeInterval time = 0;
|
||||
for (NSInteger i = 0; i < _iterationCount; i++) {
|
||||
__block CFTimeInterval start = 0;
|
||||
__block BOOL calledStop = NO;
|
||||
@autoreleasepool {
|
||||
block(i, ^{
|
||||
ASDisplayNodeAssert(start == 0, @"Called startMeasuring block twice.");
|
||||
start = CACurrentMediaTime();
|
||||
}, ^{
|
||||
time += (CACurrentMediaTime() - start);
|
||||
ASDisplayNodeAssert(calledStop == NO, @"Called stopMeasuring block twice.");
|
||||
ASDisplayNodeAssert(start != 0, @"Failed to call startMeasuring block");
|
||||
calledStop = YES;
|
||||
});
|
||||
}
|
||||
|
||||
ASDisplayNodeAssert(calledStop, @"Failed to call stopMeasuring block.");
|
||||
}
|
||||
return time;
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
NSMutableString *str = [NSMutableString stringWithString:@"Results:\n"];
|
||||
for (ASPerformanceTestResult *result in [_results objectEnumerator]) {
|
||||
[str appendFormat:@"\t%@\n", result];
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
@end
|
@ -1,65 +0,0 @@
|
||||
//
|
||||
// ASPhotosFrameworkImageRequestTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#if AS_USE_PHOTOS
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import <AsyncDisplayKit/ASPhotosFrameworkImageRequest.h>
|
||||
|
||||
static NSString *const kTestAssetID = @"testAssetID";
|
||||
|
||||
@interface ASPhotosFrameworkImageRequestTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASPhotosFrameworkImageRequestTests
|
||||
|
||||
#pragma mark Example Data
|
||||
|
||||
+ (ASPhotosFrameworkImageRequest *)exampleImageRequest
|
||||
{
|
||||
ASPhotosFrameworkImageRequest *req = [[ASPhotosFrameworkImageRequest alloc] initWithAssetIdentifier:kTestAssetID];
|
||||
req.options.networkAccessAllowed = YES;
|
||||
req.options.normalizedCropRect = CGRectMake(0.2, 0.1, 0.6, 0.8);
|
||||
req.targetSize = CGSizeMake(1024, 1536);
|
||||
req.contentMode = PHImageContentModeAspectFill;
|
||||
req.options.version = PHImageRequestOptionsVersionOriginal;
|
||||
req.options.resizeMode = PHImageRequestOptionsResizeModeFast;
|
||||
return req;
|
||||
}
|
||||
|
||||
+ (NSURL *)urlForExampleImageRequest
|
||||
{
|
||||
NSString *str = [NSString stringWithFormat:@"ph://%@?width=1024&height=1536&version=2&contentmode=1&network=1&resizemode=1&deliverymode=0&crop_x=0.2&crop_y=0.1&crop_w=0.6&crop_h=0.8", kTestAssetID];
|
||||
return [NSURL URLWithString:str];
|
||||
}
|
||||
|
||||
#pragma mark Test cases
|
||||
|
||||
- (void)testThatConvertingToURLWorks
|
||||
{
|
||||
XCTAssertEqualObjects([self.class exampleImageRequest].url, [self.class urlForExampleImageRequest]);
|
||||
}
|
||||
|
||||
- (void)testThatParsingFromURLWorks
|
||||
{
|
||||
NSURL *url = [self.class urlForExampleImageRequest];
|
||||
XCTAssertEqualObjects([ASPhotosFrameworkImageRequest requestWithURL:url], [self.class exampleImageRequest]);
|
||||
}
|
||||
|
||||
- (void)testThatCopyingWorks
|
||||
{
|
||||
ASPhotosFrameworkImageRequest *example = [self.class exampleImageRequest];
|
||||
ASPhotosFrameworkImageRequest *copy = [[self.class exampleImageRequest] copy];
|
||||
XCTAssertEqualObjects(example, copy);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
@ -1,38 +0,0 @@
|
||||
//
|
||||
// ASRatioLayoutSpecSnapshotTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASLayoutSpecSnapshotTestsHelper.h"
|
||||
|
||||
#import <AsyncDisplayKit/ASRatioLayoutSpec.h>
|
||||
|
||||
static const ASSizeRange kFixedSize = {{0, 0}, {100, 100}};
|
||||
|
||||
@interface ASRatioLayoutSpecSnapshotTests : ASLayoutSpecSnapshotTestCase
|
||||
@end
|
||||
|
||||
@implementation ASRatioLayoutSpecSnapshotTests
|
||||
|
||||
- (void)testRatioLayoutSpecWithRatio:(CGFloat)ratio childSize:(CGSize)childSize identifier:(NSString *)identifier
|
||||
{
|
||||
ASDisplayNode *subnode = ASDisplayNodeWithBackgroundColor([UIColor greenColor], childSize);
|
||||
|
||||
ASLayoutSpec *layoutSpec = [ASRatioLayoutSpec ratioLayoutSpecWithRatio:ratio child:subnode];
|
||||
|
||||
[self testLayoutSpec:layoutSpec sizeRange:kFixedSize subnodes:@[subnode] identifier:identifier];
|
||||
}
|
||||
|
||||
- (void)testRatioLayout
|
||||
{
|
||||
[self testRatioLayoutSpecWithRatio:0.5 childSize:CGSizeMake(100, 100) identifier:@"HalfRatio"];
|
||||
[self testRatioLayoutSpecWithRatio:2.0 childSize:CGSizeMake(100, 100) identifier:@"DoubleRatio"];
|
||||
[self testRatioLayoutSpecWithRatio:7.0 childSize:CGSizeMake(100, 100) identifier:@"SevenTimesRatio"];
|
||||
[self testRatioLayoutSpecWithRatio:10.0 childSize:CGSizeMake(20, 200) identifier:@"TenTimesRatioWithItemTooBig"];
|
||||
}
|
||||
|
||||
@end
|
@ -1,184 +0,0 @@
|
||||
//
|
||||
// ASRecursiveUnfairLockTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASTestCase.h"
|
||||
#import <AsyncDisplayKit/ASRecursiveUnfairLock.h>
|
||||
#import <stdatomic.h>
|
||||
#import <os/lock.h>
|
||||
|
||||
@interface ASRecursiveUnfairLockTests : ASTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASRecursiveUnfairLockTests {
|
||||
ASRecursiveUnfairLock lock;
|
||||
}
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
[super setUp];
|
||||
lock = AS_RECURSIVE_UNFAIR_LOCK_INIT;
|
||||
}
|
||||
|
||||
- (void)testTheAtomicIsLockFree
|
||||
{
|
||||
XCTAssertTrue(atomic_is_lock_free(&lock._thread));
|
||||
}
|
||||
|
||||
- (void)testRelockingFromSameThread
|
||||
{
|
||||
ASRecursiveUnfairLockLock(&lock);
|
||||
ASRecursiveUnfairLockLock(&lock);
|
||||
ASRecursiveUnfairLockUnlock(&lock);
|
||||
// Now try locking from another thread.
|
||||
XCTestExpectation *e1 = [self expectationWithDescription:@"Other thread tried lock."];
|
||||
[NSThread detachNewThreadWithBlock:^{
|
||||
XCTAssertFalse(ASRecursiveUnfairLockTryLock(&self->lock));
|
||||
[e1 fulfill];
|
||||
}];
|
||||
[self waitForExpectationsWithTimeout:1 handler:nil];
|
||||
ASRecursiveUnfairLockUnlock(&lock);
|
||||
|
||||
XCTestExpectation *e2 = [self expectationWithDescription:@"Other thread tried lock again"];
|
||||
[NSThread detachNewThreadWithBlock:^{
|
||||
XCTAssertTrue(ASRecursiveUnfairLockTryLock(&self->lock));
|
||||
ASRecursiveUnfairLockUnlock(&self->lock);
|
||||
[e2 fulfill];
|
||||
}];
|
||||
[self waitForExpectationsWithTimeout:1 handler:nil];
|
||||
}
|
||||
|
||||
- (void)testThatUnlockingWithoutHoldingMakesAssertion
|
||||
{
|
||||
#ifdef NS_BLOCK_ASSERTIONS
|
||||
#warning Assertions should be on for `testThatUnlockingWithoutHoldingMakesAssertion`
|
||||
NSLog(@"Passing because assertions are off.");
|
||||
#else
|
||||
ASRecursiveUnfairLockLock(&lock);
|
||||
XCTestExpectation *e1 = [self expectationWithDescription:@"Other thread tried lock."];
|
||||
[NSThread detachNewThreadWithBlock:^{
|
||||
XCTAssertThrows(ASRecursiveUnfairLockUnlock(&lock));
|
||||
[e1 fulfill];
|
||||
}];
|
||||
[self waitForExpectationsWithTimeout:10 handler:nil];
|
||||
ASRecursiveUnfairLockUnlock(&lock);
|
||||
#endif
|
||||
}
|
||||
|
||||
#define CHAOS_TEST_BODY(contested, prefix, infix, postfix) \
|
||||
dispatch_group_t g = dispatch_group_create(); \
|
||||
for (int i = 0; i < (contested ? 16 : 2); i++) {\
|
||||
dispatch_group_enter(g);\
|
||||
[NSThread detachNewThreadWithBlock:^{\
|
||||
for (int i = 0; i < 20000; i++) {\
|
||||
prefix;\
|
||||
value += 150;\
|
||||
infix;\
|
||||
value -= 150;\
|
||||
postfix;\
|
||||
}\
|
||||
dispatch_group_leave(g);\
|
||||
}];\
|
||||
}\
|
||||
dispatch_group_wait(g, DISPATCH_TIME_FOREVER);
|
||||
|
||||
#pragma mark - Correctness Tests
|
||||
|
||||
- (void)testRecursiveUnfairLockContested
|
||||
{
|
||||
__block int value = 0;
|
||||
[self measureBlock:^{
|
||||
CHAOS_TEST_BODY(YES, ASRecursiveUnfairLockLock(&lock), {}, ASRecursiveUnfairLockUnlock(&lock));
|
||||
}];
|
||||
XCTAssertEqual(value, 0);
|
||||
}
|
||||
|
||||
- (void)testRecursiveUnfairLockUncontested
|
||||
{
|
||||
__block int value = 0;
|
||||
[self measureBlock:^{
|
||||
CHAOS_TEST_BODY(NO, ASRecursiveUnfairLockLock(&lock), {}, ASRecursiveUnfairLockUnlock(&lock));
|
||||
}];
|
||||
XCTAssertEqual(value, 0);
|
||||
}
|
||||
|
||||
#pragma mark - Lock performance tests
|
||||
|
||||
#if RUN_LOCK_PERF_TESTS
|
||||
- (void)testNoLockContested
|
||||
{
|
||||
__block int value = 0;
|
||||
[self measureBlock:^{
|
||||
CHAOS_TEST_BODY(YES, {}, {}, {});
|
||||
}];
|
||||
XCTAssertNotEqual(value, 0);
|
||||
}
|
||||
|
||||
- (void)testPlainUnfairLockContested
|
||||
{
|
||||
__block int value = 0;
|
||||
__block os_unfair_lock unfairLock = OS_UNFAIR_LOCK_INIT;
|
||||
[self measureBlock:^{
|
||||
CHAOS_TEST_BODY(YES, os_unfair_lock_lock(&unfairLock), {}, os_unfair_lock_unlock(&unfairLock));
|
||||
}];
|
||||
XCTAssertEqual(value, 0);
|
||||
}
|
||||
|
||||
- (void)testRecursiveMutexContested
|
||||
{
|
||||
__block int value = 0;
|
||||
pthread_mutexattr_t attr;
|
||||
pthread_mutexattr_init (&attr);
|
||||
pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);
|
||||
__block pthread_mutex_t m;
|
||||
pthread_mutex_init (&m, &attr);
|
||||
pthread_mutexattr_destroy (&attr);
|
||||
|
||||
[self measureBlock:^{
|
||||
CHAOS_TEST_BODY(YES, pthread_mutex_lock(&m), {}, pthread_mutex_unlock(&m));
|
||||
}];
|
||||
pthread_mutex_destroy(&m);
|
||||
}
|
||||
|
||||
- (void)testNoLockUncontested
|
||||
{
|
||||
__block int value = 0;
|
||||
[self measureBlock:^{
|
||||
CHAOS_TEST_BODY(NO, {}, {}, {});
|
||||
}];
|
||||
XCTAssertNotEqual(value, 0);
|
||||
}
|
||||
|
||||
- (void)testPlainUnfairLockUncontested
|
||||
{
|
||||
__block int value = 0;
|
||||
__block os_unfair_lock unfairLock = OS_UNFAIR_LOCK_INIT;
|
||||
[self measureBlock:^{
|
||||
CHAOS_TEST_BODY(NO, os_unfair_lock_lock(&unfairLock), {}, os_unfair_lock_unlock(&unfairLock));
|
||||
}];
|
||||
XCTAssertEqual(value, 0);
|
||||
}
|
||||
|
||||
- (void)testRecursiveMutexUncontested
|
||||
{
|
||||
__block int value = 0;
|
||||
pthread_mutexattr_t attr;
|
||||
pthread_mutexattr_init (&attr);
|
||||
pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);
|
||||
__block pthread_mutex_t m;
|
||||
pthread_mutex_init (&m, &attr);
|
||||
pthread_mutexattr_destroy (&attr);
|
||||
|
||||
[self measureBlock:^{
|
||||
CHAOS_TEST_BODY(NO, pthread_mutex_lock(&m), {}, pthread_mutex_unlock(&m));
|
||||
}];
|
||||
pthread_mutex_destroy(&m);
|
||||
}
|
||||
|
||||
#endif // RUN_LOCK_PERF_TESTS
|
||||
@end
|
@ -1,135 +0,0 @@
|
||||
//
|
||||
// ASRelativeLayoutSpecSnapshotTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASLayoutSpecSnapshotTestsHelper.h"
|
||||
|
||||
#import <AsyncDisplayKit/ASBackgroundLayoutSpec.h>
|
||||
#import <AsyncDisplayKit/ASRelativeLayoutSpec.h>
|
||||
#import <AsyncDisplayKit/ASStackLayoutSpec.h>
|
||||
|
||||
static const ASSizeRange kSize = {{100, 120}, {320, 160}};
|
||||
|
||||
@interface ASRelativeLayoutSpecSnapshotTests : ASLayoutSpecSnapshotTestCase
|
||||
@end
|
||||
|
||||
@implementation ASRelativeLayoutSpecSnapshotTests
|
||||
|
||||
#pragma mark - XCTestCase
|
||||
|
||||
- (void)testWithOptions
|
||||
{
|
||||
[self testAllVerticalPositionsForHorizontalPosition:ASRelativeLayoutSpecPositionStart];
|
||||
[self testAllVerticalPositionsForHorizontalPosition:ASRelativeLayoutSpecPositionCenter];
|
||||
[self testAllVerticalPositionsForHorizontalPosition:ASRelativeLayoutSpecPositionEnd];
|
||||
|
||||
}
|
||||
|
||||
- (void)testAllVerticalPositionsForHorizontalPosition:(ASRelativeLayoutSpecPosition)horizontalPosition
|
||||
{
|
||||
[self testWithHorizontalPosition:horizontalPosition verticalPosition:ASRelativeLayoutSpecPositionStart sizingOptions:{}];
|
||||
[self testWithHorizontalPosition:horizontalPosition verticalPosition:ASRelativeLayoutSpecPositionCenter sizingOptions:{}];
|
||||
[self testWithHorizontalPosition:horizontalPosition verticalPosition:ASRelativeLayoutSpecPositionEnd sizingOptions:{}];
|
||||
}
|
||||
|
||||
- (void)testWithSizingOptions
|
||||
{
|
||||
[self testWithHorizontalPosition:ASRelativeLayoutSpecPositionStart
|
||||
verticalPosition:ASRelativeLayoutSpecPositionStart
|
||||
sizingOptions:ASRelativeLayoutSpecSizingOptionDefault];
|
||||
[self testWithHorizontalPosition:ASRelativeLayoutSpecPositionStart
|
||||
verticalPosition:ASRelativeLayoutSpecPositionStart
|
||||
sizingOptions:ASRelativeLayoutSpecSizingOptionMinimumWidth];
|
||||
[self testWithHorizontalPosition:ASRelativeLayoutSpecPositionStart
|
||||
verticalPosition:ASRelativeLayoutSpecPositionStart
|
||||
sizingOptions:ASRelativeLayoutSpecSizingOptionMinimumHeight];
|
||||
[self testWithHorizontalPosition:ASRelativeLayoutSpecPositionStart
|
||||
verticalPosition:ASRelativeLayoutSpecPositionStart
|
||||
sizingOptions:ASRelativeLayoutSpecSizingOptionMinimumSize];
|
||||
}
|
||||
|
||||
- (void)testWithHorizontalPosition:(ASRelativeLayoutSpecPosition)horizontalPosition
|
||||
verticalPosition:(ASRelativeLayoutSpecPosition)verticalPosition
|
||||
sizingOptions:(ASRelativeLayoutSpecSizingOption)sizingOptions
|
||||
{
|
||||
ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor redColor]);
|
||||
ASDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor greenColor], CGSizeMake(70, 100));
|
||||
|
||||
ASLayoutSpec *layoutSpec =
|
||||
[ASBackgroundLayoutSpec
|
||||
backgroundLayoutSpecWithChild:
|
||||
[ASRelativeLayoutSpec
|
||||
relativePositionLayoutSpecWithHorizontalPosition:horizontalPosition
|
||||
verticalPosition:verticalPosition
|
||||
sizingOption:sizingOptions
|
||||
child:foregroundNode]
|
||||
background:backgroundNode];
|
||||
|
||||
[self testLayoutSpec:layoutSpec
|
||||
sizeRange:kSize
|
||||
subnodes:@[backgroundNode, foregroundNode]
|
||||
identifier:suffixForPositionOptions(horizontalPosition, verticalPosition, sizingOptions)];
|
||||
}
|
||||
|
||||
static NSString *suffixForPositionOptions(ASRelativeLayoutSpecPosition horizontalPosition,
|
||||
ASRelativeLayoutSpecPosition verticalPosition,
|
||||
ASRelativeLayoutSpecSizingOption sizingOptions)
|
||||
{
|
||||
NSMutableString *suffix = [NSMutableString string];
|
||||
|
||||
if (horizontalPosition == ASRelativeLayoutSpecPositionCenter) {
|
||||
[suffix appendString:@"CenterX"];
|
||||
} else if (horizontalPosition == ASRelativeLayoutSpecPositionEnd) {
|
||||
[suffix appendString:@"EndX"];
|
||||
}
|
||||
|
||||
if (verticalPosition == ASRelativeLayoutSpecPositionCenter) {
|
||||
[suffix appendString:@"CenterY"];
|
||||
} else if (verticalPosition == ASRelativeLayoutSpecPositionEnd) {
|
||||
[suffix appendString:@"EndY"];
|
||||
}
|
||||
|
||||
if ((sizingOptions & ASRelativeLayoutSpecSizingOptionMinimumWidth) != 0) {
|
||||
[suffix appendString:@"SizingMinimumWidth"];
|
||||
}
|
||||
|
||||
if ((sizingOptions & ASRelativeLayoutSpecSizingOptionMinimumHeight) != 0) {
|
||||
[suffix appendString:@"SizingMinimumHeight"];
|
||||
}
|
||||
|
||||
return suffix;
|
||||
}
|
||||
|
||||
- (void)testMinimumSizeRangeIsGivenToChildWhenNotPositioning
|
||||
{
|
||||
ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor redColor]);
|
||||
ASDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor redColor], CGSizeMake(10, 10));
|
||||
foregroundNode.style.flexGrow = 1;
|
||||
|
||||
ASLayoutSpec *childSpec =
|
||||
[ASBackgroundLayoutSpec
|
||||
backgroundLayoutSpecWithChild:
|
||||
[ASStackLayoutSpec
|
||||
stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical
|
||||
spacing:0
|
||||
justifyContent:ASStackLayoutJustifyContentStart
|
||||
alignItems:ASStackLayoutAlignItemsStart
|
||||
children:@[foregroundNode]]
|
||||
background:backgroundNode];
|
||||
|
||||
ASRelativeLayoutSpec *layoutSpec =
|
||||
[ASRelativeLayoutSpec
|
||||
relativePositionLayoutSpecWithHorizontalPosition:ASRelativeLayoutSpecPositionNone
|
||||
verticalPosition:ASRelativeLayoutSpecPositionNone
|
||||
sizingOption:{}
|
||||
child:childSpec];
|
||||
|
||||
[self testLayoutSpec:layoutSpec sizeRange:kSize subnodes:@[backgroundNode, foregroundNode] identifier:nil];
|
||||
}
|
||||
|
||||
@end
|
@ -1,201 +0,0 @@
|
||||
//
|
||||
// ASRunLoopQueueTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASTestCase.h"
|
||||
|
||||
#import <AsyncDisplayKit/ASRunLoopQueue.h>
|
||||
|
||||
#import "ASDisplayNodeTestsHelper.h"
|
||||
|
||||
static NSTimeInterval const kRunLoopRunTime = 0.001; // Allow the RunLoop to run for one millisecond each time.
|
||||
|
||||
@interface QueueObject : NSObject <ASCATransactionQueueObserving>
|
||||
@property (nonatomic) BOOL queueObjectProcessed;
|
||||
@end
|
||||
|
||||
@implementation QueueObject
|
||||
- (void)prepareForCATransactionCommit
|
||||
{
|
||||
self.queueObjectProcessed = YES;
|
||||
}
|
||||
@end
|
||||
|
||||
@interface ASRunLoopQueueTests : ASTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASRunLoopQueueTests
|
||||
|
||||
#pragma mark enqueue tests
|
||||
|
||||
- (void)testEnqueueNilObjectsToQueue
|
||||
{
|
||||
ASRunLoopQueue *queue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() retainObjects:YES handler:nil];
|
||||
id object = nil;
|
||||
[queue enqueue:object];
|
||||
XCTAssertTrue(queue.isEmpty);
|
||||
}
|
||||
|
||||
- (void)testEnqueueSameObjectTwiceToDefaultQueue
|
||||
{
|
||||
id object = [[NSObject alloc] init];
|
||||
__unsafe_unretained id weakObject = object;
|
||||
__block NSUInteger dequeuedCount = 0;
|
||||
ASRunLoopQueue *queue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() retainObjects:YES handler:^(id _Nonnull dequeuedItem, BOOL isQueueDrained) {
|
||||
if (dequeuedItem == weakObject) {
|
||||
dequeuedCount++;
|
||||
}
|
||||
}];
|
||||
[queue enqueue:object];
|
||||
[queue enqueue:object];
|
||||
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:kRunLoopRunTime]];
|
||||
XCTAssert(dequeuedCount == 1);
|
||||
}
|
||||
|
||||
- (void)testEnqueueSameObjectTwiceToNonExclusiveMembershipQueue
|
||||
{
|
||||
id object = [[NSObject alloc] init];
|
||||
__unsafe_unretained id weakObject = object;
|
||||
__block NSUInteger dequeuedCount = 0;
|
||||
ASRunLoopQueue *queue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() retainObjects:YES handler:^(id _Nonnull dequeuedItem, BOOL isQueueDrained) {
|
||||
if (dequeuedItem == weakObject) {
|
||||
dequeuedCount++;
|
||||
}
|
||||
}];
|
||||
queue.ensureExclusiveMembership = NO;
|
||||
[queue enqueue:object];
|
||||
[queue enqueue:object];
|
||||
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:kRunLoopRunTime]];
|
||||
XCTAssert(dequeuedCount == 2);
|
||||
}
|
||||
|
||||
#pragma mark processQueue tests
|
||||
|
||||
- (void)testDefaultQueueProcessObjectsOneAtATime
|
||||
{
|
||||
ASRunLoopQueue *queue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() retainObjects:YES handler:^(id _Nonnull dequeuedItem, BOOL isQueueDrained) {
|
||||
[NSThread sleepForTimeInterval:kRunLoopRunTime * 2]; // So each element takes more time than the available
|
||||
}];
|
||||
[queue enqueue:[[NSObject alloc] init]];
|
||||
[queue enqueue:[[NSObject alloc] init]];
|
||||
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:kRunLoopRunTime]];
|
||||
XCTAssertFalse(queue.isEmpty);
|
||||
}
|
||||
|
||||
- (void)testQueueProcessObjectsInBatchesOfSpecifiedSize
|
||||
{
|
||||
ASRunLoopQueue *queue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() retainObjects:YES handler:^(id _Nonnull dequeuedItem, BOOL isQueueDrained) {
|
||||
[NSThread sleepForTimeInterval:kRunLoopRunTime * 2]; // So each element takes more time than the available
|
||||
}];
|
||||
queue.batchSize = 2;
|
||||
[queue enqueue:[[NSObject alloc] init]];
|
||||
[queue enqueue:[[NSObject alloc] init]];
|
||||
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:kRunLoopRunTime]];
|
||||
XCTAssertTrue(queue.isEmpty);
|
||||
}
|
||||
|
||||
- (void)testQueueOnlySendsIsDrainedForLastObjectInBatch
|
||||
{
|
||||
id objectA = [[NSObject alloc] init];
|
||||
id objectB = [[NSObject alloc] init];
|
||||
__unsafe_unretained id weakObjectA = objectA;
|
||||
__unsafe_unretained id weakObjectB = objectB;
|
||||
__block BOOL isQueueDrainedWhenProcessingA = NO;
|
||||
__block BOOL isQueueDrainedWhenProcessingB = NO;
|
||||
ASRunLoopQueue *queue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() retainObjects:YES handler:^(id _Nonnull dequeuedItem, BOOL isQueueDrained) {
|
||||
if (dequeuedItem == weakObjectA) {
|
||||
isQueueDrainedWhenProcessingA = isQueueDrained;
|
||||
} else if (dequeuedItem == weakObjectB) {
|
||||
isQueueDrainedWhenProcessingB = isQueueDrained;
|
||||
}
|
||||
}];
|
||||
queue.batchSize = 2;
|
||||
[queue enqueue:objectA];
|
||||
[queue enqueue:objectB];
|
||||
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:kRunLoopRunTime]];
|
||||
XCTAssertFalse(isQueueDrainedWhenProcessingA);
|
||||
XCTAssertTrue(isQueueDrainedWhenProcessingB);
|
||||
}
|
||||
|
||||
#pragma mark strong/weak tests
|
||||
|
||||
- (void)testStrongQueueRetainsObjects
|
||||
{
|
||||
id object = [[NSObject alloc] init];
|
||||
__unsafe_unretained id weakObject = object;
|
||||
__block BOOL didProcessObject = NO;
|
||||
ASRunLoopQueue *queue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() retainObjects:YES handler:^(id _Nonnull dequeuedItem, BOOL isQueueDrained) {
|
||||
if (dequeuedItem == weakObject) {
|
||||
didProcessObject = YES;
|
||||
}
|
||||
}];
|
||||
[queue enqueue:object];
|
||||
object = nil;
|
||||
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:kRunLoopRunTime]];
|
||||
XCTAssertTrue(didProcessObject);
|
||||
}
|
||||
|
||||
- (void)testWeakQueueDoesNotRetainsObjects
|
||||
{
|
||||
id object = [[NSObject alloc] init];
|
||||
__unsafe_unretained id weakObject = object;
|
||||
__block BOOL didProcessObject = NO;
|
||||
ASRunLoopQueue *queue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() retainObjects:NO handler:^(id _Nonnull dequeuedItem, BOOL isQueueDrained) {
|
||||
if (dequeuedItem == weakObject) {
|
||||
didProcessObject = YES;
|
||||
}
|
||||
}];
|
||||
[queue enqueue:object];
|
||||
object = nil;
|
||||
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:kRunLoopRunTime]];
|
||||
XCTAssertFalse(didProcessObject);
|
||||
}
|
||||
|
||||
- (void)testWeakQueueWithAllDeallocatedObjectsIsDrained
|
||||
{
|
||||
ASRunLoopQueue *queue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() retainObjects:NO handler:nil];
|
||||
id object = [[NSObject alloc] init];
|
||||
[queue enqueue:object];
|
||||
object = nil;
|
||||
XCTAssertFalse(queue.isEmpty);
|
||||
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:kRunLoopRunTime]];
|
||||
XCTAssertTrue(queue.isEmpty);
|
||||
}
|
||||
|
||||
- (void)testASCATransactionQueueDisable
|
||||
{
|
||||
// Disable coalescing.
|
||||
ASConfiguration *config = [[ASConfiguration alloc] init];
|
||||
config.experimentalFeatures = kNilOptions;
|
||||
[ASConfigurationManager test_resetWithConfiguration:config];
|
||||
|
||||
ASCATransactionQueue *queue = [[ASCATransactionQueue alloc] init];
|
||||
QueueObject *object = [[QueueObject alloc] init];
|
||||
XCTAssertFalse(object.queueObjectProcessed);
|
||||
[queue enqueue:object];
|
||||
XCTAssertTrue(object.queueObjectProcessed);
|
||||
XCTAssertTrue([queue isEmpty]);
|
||||
XCTAssertFalse(queue.enabled);
|
||||
}
|
||||
|
||||
- (void)testASCATransactionQueueProcess
|
||||
{
|
||||
ASConfiguration *config = [[ASConfiguration alloc] initWithDictionary:nil];
|
||||
config.experimentalFeatures = ASExperimentalInterfaceStateCoalescing;
|
||||
[ASConfigurationManager test_resetWithConfiguration:config];
|
||||
|
||||
ASCATransactionQueue *queue = [[ASCATransactionQueue alloc] init];
|
||||
QueueObject *object = [[QueueObject alloc] init];
|
||||
[queue enqueue:object];
|
||||
XCTAssertFalse(object.queueObjectProcessed);
|
||||
ASCATransactionQueueWait(queue);
|
||||
XCTAssertTrue(object.queueObjectProcessed);
|
||||
XCTAssertTrue(queue.enabled);
|
||||
}
|
||||
|
||||
@end
|
@ -1,168 +0,0 @@
|
||||
//
|
||||
// ASScrollNodeTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
|
||||
#import "ASXCTExtensions.h"
|
||||
|
||||
@interface ASScrollNodeTests : XCTestCase
|
||||
|
||||
@property (nonatomic) ASScrollNode *scrollNode;
|
||||
@property (nonatomic) ASDisplayNode *subnode;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASScrollNodeTests
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
ASDisplayNode *subnode = [[ASDisplayNode alloc] init];
|
||||
self.subnode = subnode;
|
||||
|
||||
self.scrollNode = [[ASScrollNode alloc] init];
|
||||
self.scrollNode.scrollableDirections = ASScrollDirectionVerticalDirections;
|
||||
self.scrollNode.automaticallyManagesContentSize = YES;
|
||||
self.scrollNode.automaticallyManagesSubnodes = YES;
|
||||
self.scrollNode.layoutSpecBlock = ^ASLayoutSpec * _Nonnull(__kindof ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) {
|
||||
return [[ASWrapperLayoutSpec alloc] initWithLayoutElement:subnode];
|
||||
};
|
||||
[self.scrollNode view];
|
||||
}
|
||||
|
||||
- (void)testSubnodeLayoutCalculatedWithUnconstrainedMaxSizeInScrollableDirection
|
||||
{
|
||||
CGSize parentSize = CGSizeMake(100, 100);
|
||||
ASSizeRange sizeRange = ASSizeRangeMake(parentSize);
|
||||
|
||||
[self.scrollNode layoutThatFits:sizeRange parentSize:parentSize];
|
||||
|
||||
ASSizeRange subnodeSizeRange = sizeRange;
|
||||
subnodeSizeRange.max.height = CGFLOAT_MAX;
|
||||
XCTAssertEqual(self.scrollNode.scrollableDirections, ASScrollDirectionVerticalDirections);
|
||||
ASXCTAssertEqualSizeRanges(self.subnode.constrainedSizeForCalculatedLayout, subnodeSizeRange);
|
||||
|
||||
// Same test for horizontal scrollable directions
|
||||
self.scrollNode.scrollableDirections = ASScrollDirectionHorizontalDirections;
|
||||
[self.scrollNode layoutThatFits:sizeRange parentSize:parentSize];
|
||||
|
||||
subnodeSizeRange = sizeRange;
|
||||
subnodeSizeRange.max.width = CGFLOAT_MAX;
|
||||
|
||||
ASXCTAssertEqualSizeRanges(self.subnode.constrainedSizeForCalculatedLayout, subnodeSizeRange);
|
||||
}
|
||||
|
||||
- (void)testAutomaticallyManagesContentSizeUnderflow
|
||||
{
|
||||
CGSize subnodeSize = CGSizeMake(100, 100);
|
||||
CGSize parentSize = CGSizeMake(100, 200);
|
||||
ASSizeRange sizeRange = ASSizeRangeUnconstrained;
|
||||
|
||||
self.subnode.style.preferredSize = subnodeSize;
|
||||
|
||||
[self.scrollNode layoutThatFits:sizeRange parentSize:parentSize];
|
||||
[self.scrollNode layout];
|
||||
|
||||
ASXCTAssertEqualSizes(self.scrollNode.calculatedSize, parentSize);
|
||||
ASXCTAssertEqualSizes(self.scrollNode.view.contentSize, subnodeSize);
|
||||
}
|
||||
|
||||
- (void)testAutomaticallyManagesContentSizeOverflow
|
||||
{
|
||||
CGSize subnodeSize = CGSizeMake(100, 500);
|
||||
CGSize parentSize = CGSizeMake(100, 200);
|
||||
ASSizeRange sizeRange = ASSizeRangeUnconstrained;
|
||||
|
||||
self.subnode.style.preferredSize = subnodeSize;
|
||||
|
||||
[self.scrollNode layoutThatFits:sizeRange parentSize:parentSize];
|
||||
[self.scrollNode layout];
|
||||
|
||||
ASXCTAssertEqualSizes(self.scrollNode.calculatedSize, parentSize);
|
||||
ASXCTAssertEqualSizes(self.scrollNode.view.contentSize, subnodeSize);
|
||||
}
|
||||
|
||||
- (void)testAutomaticallyManagesContentSizeWithSizeRangeSmallerThanParentSize
|
||||
{
|
||||
CGSize subnodeSize = CGSizeMake(100, 100);
|
||||
CGSize parentSize = CGSizeMake(100, 500);
|
||||
ASSizeRange sizeRange = ASSizeRangeMake(CGSizeMake(100, 100), CGSizeMake(100, 200));
|
||||
|
||||
self.subnode.style.preferredSize = subnodeSize;
|
||||
|
||||
[self.scrollNode layoutThatFits:sizeRange parentSize:parentSize];
|
||||
[self.scrollNode layout];
|
||||
|
||||
ASXCTAssertEqualSizes(self.scrollNode.calculatedSize, sizeRange.max);
|
||||
ASXCTAssertEqualSizes(self.scrollNode.view.contentSize, subnodeSize);
|
||||
}
|
||||
|
||||
- (void)testAutomaticallyManagesContentSizeWithSizeRangeBiggerThanParentSize
|
||||
{
|
||||
CGSize subnodeSize = CGSizeMake(100, 200);
|
||||
CGSize parentSize = CGSizeMake(100, 100);
|
||||
ASSizeRange sizeRange = ASSizeRangeMake(CGSizeMake(100, 150));
|
||||
|
||||
self.subnode.style.preferredSize = subnodeSize;
|
||||
|
||||
[self.scrollNode layoutThatFits:sizeRange parentSize:parentSize];
|
||||
[self.scrollNode layout];
|
||||
|
||||
ASXCTAssertEqualSizes(self.scrollNode.calculatedSize, sizeRange.min);
|
||||
ASXCTAssertEqualSizes(self.scrollNode.view.contentSize, subnodeSize);
|
||||
}
|
||||
|
||||
- (void)testAutomaticallyManagesContentSizeWithInvalidCalculatedSizeForLayout
|
||||
{
|
||||
CGSize subnodeSize = CGSizeMake(100, 200);
|
||||
CGSize parentSize = CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX);
|
||||
ASSizeRange sizeRange = ASSizeRangeUnconstrained;
|
||||
|
||||
self.subnode.style.preferredSize = subnodeSize;
|
||||
|
||||
[self.scrollNode layoutThatFits:sizeRange parentSize:parentSize];
|
||||
[self.scrollNode layout];
|
||||
|
||||
ASXCTAssertEqualSizes(self.scrollNode.calculatedSize, subnodeSize);
|
||||
ASXCTAssertEqualSizes(self.scrollNode.view.contentSize, subnodeSize);
|
||||
}
|
||||
|
||||
- (void)testASScrollNodeAccessibility {
|
||||
ASDisplayNode *scrollNode = [[ASDisplayNode alloc] init];
|
||||
ASDisplayNode *node = [[ASDisplayNode alloc] init];
|
||||
node.isAccessibilityContainer = YES;
|
||||
node.accessibilityLabel = @"node";
|
||||
[scrollNode addSubnode:node];
|
||||
node.frame = CGRectMake(0,0,100,100);
|
||||
ASTextNode2 *text = [[ASTextNode2 alloc] init];
|
||||
text.attributedText = [[NSAttributedString alloc] initWithString:@"text"];
|
||||
[node addSubnode:text];
|
||||
|
||||
ASTextNode2 *text2 = [[ASTextNode2 alloc] init];
|
||||
text2.attributedText = [[NSAttributedString alloc] initWithString:@"text2"];
|
||||
[node addSubnode:text2];
|
||||
__unused UIView *view = scrollNode.view;
|
||||
XCTAssertTrue(node.view.accessibilityElements.firstObject, @"node");
|
||||
|
||||
// Following tests will only pass when accessibility is enabled.
|
||||
// More details: https://github.com/TextureGroup/Texture/pull/1188
|
||||
|
||||
// A bunch of a11y containers each of which hold aggregated labels.
|
||||
/* NSArray *a11yElements = [scrollNode.view accessibilityElements];
|
||||
XCTAssertTrue(a11yElements.count > 0, @"accessibilityElements should exist");
|
||||
|
||||
UIAccessibilityElement *container = a11yElements.firstObject;
|
||||
XCTAssertTrue(container.isAccessibilityElement == false && container.accessibilityElements.count > 0);
|
||||
UIAccessibilityElement *ae = container.accessibilityElements.firstObject;
|
||||
XCTAssertTrue([[ae accessibilityLabel] isEqualToString:@"node, text, text2"]);
|
||||
*/
|
||||
}
|
||||
|
||||
@end
|
@ -1,43 +0,0 @@
|
||||
//
|
||||
// ASSnapshotTestCase.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdocumentation"
|
||||
#import <FBSnapshotTestCase/FBSnapshotTestCase.h>
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
#import "ASDisplayNodeTestsHelper.h"
|
||||
|
||||
@class ASDisplayNode;
|
||||
|
||||
NSOrderedSet *ASSnapshotTestCaseDefaultSuffixes(void);
|
||||
|
||||
#define ASSnapshotVerifyNode(node__, identifier__) \
|
||||
{ \
|
||||
[ASSnapshotTestCase hackilySynchronouslyRecursivelyRenderNode:node__]; \
|
||||
FBSnapshotVerifyLayerWithOptions(node__.layer, identifier__, ASSnapshotTestCaseDefaultSuffixes(), 0) \
|
||||
}
|
||||
|
||||
#define ASSnapshotVerifyLayer(layer__, identifier__) \
|
||||
FBSnapshotVerifyLayerWithOptions(layer__, identifier__, ASSnapshotTestCaseDefaultSuffixes(), 0);
|
||||
|
||||
#define ASSnapshotVerifyView(view__, identifier__) \
|
||||
FBSnapshotVerifyViewWithOptions(view__, identifier__, ASSnapshotTestCaseDefaultSuffixes(), 0);
|
||||
|
||||
#define ASSnapshotVerifyViewWithTolerance(view__, identifier__, tolerance__) \
|
||||
FBSnapshotVerifyViewWithOptions(view__, identifier__, ASSnapshotTestCaseDefaultSuffixes(), tolerance__);
|
||||
|
||||
@interface ASSnapshotTestCase : FBSnapshotTestCase
|
||||
|
||||
/**
|
||||
* Hack for testing. ASDisplayNode lacks an explicit -render method, so we manually hit its layout & display codepaths.
|
||||
*/
|
||||
+ (void)hackilySynchronouslyRecursivelyRenderNode:(ASDisplayNode *)node;
|
||||
|
||||
@end
|
@ -1,40 +0,0 @@
|
||||
//
|
||||
// ASSnapshotTestCase.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASSnapshotTestCase.h"
|
||||
#import <AsyncDisplayKit/ASAvailability.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
|
||||
|
||||
NSOrderedSet *ASSnapshotTestCaseDefaultSuffixes(void)
|
||||
{
|
||||
NSMutableOrderedSet *suffixesSet = [[NSMutableOrderedSet alloc] init];
|
||||
// In some rare cases, slightly different rendering may occur on iOS 10 (text rasterization).
|
||||
// If the test folders find any image that exactly matches, they pass;
|
||||
// if an image is not present at all, or it fails, it moves on to check the others.
|
||||
// This means the order doesn't matter besides reducing logging / performance.
|
||||
if (AS_AT_LEAST_IOS10) {
|
||||
[suffixesSet addObject:@"_iOS_10"];
|
||||
}
|
||||
[suffixesSet addObject:@"_64"];
|
||||
return [suffixesSet copy];
|
||||
}
|
||||
|
||||
@implementation ASSnapshotTestCase
|
||||
|
||||
+ (void)hackilySynchronouslyRecursivelyRenderNode:(ASDisplayNode *)node
|
||||
{
|
||||
ASDisplayNodePerformBlockOnEveryNode(nil, node, YES, ^(ASDisplayNode * _Nonnull node) {
|
||||
[node.layer setNeedsDisplay];
|
||||
});
|
||||
[node recursivelyEnsureDisplaySynchronously:YES];
|
||||
}
|
||||
|
||||
@end
|
File diff suppressed because it is too large
Load Diff
@ -1,60 +0,0 @@
|
||||
//
|
||||
// ASTLayoutFixture.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
|
||||
#import "ASTestCase.h"
|
||||
#import "ASLayoutTestNode.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
AS_SUBCLASSING_RESTRICTED
|
||||
@interface ASTLayoutFixture : NSObject
|
||||
|
||||
/// The correct layout. The root should be unpositioned (same as -calculatedLayout).
|
||||
@property (nonatomic, nullable) ASLayout *layout;
|
||||
|
||||
/// The layoutSpecBlocks for non-leaf nodes.
|
||||
@property (nonatomic, readonly) NSMapTable<ASDisplayNode *, ASLayoutSpecBlock> *layoutSpecBlocks;
|
||||
|
||||
@property (nonatomic, readonly) ASLayoutTestNode *rootNode;
|
||||
|
||||
@property (nonatomic, readonly) NSSet<ASLayoutTestNode *> *allNodes;
|
||||
|
||||
/// Get the (correct) layout for the specified node.
|
||||
- (ASLayout *)layoutForNode:(ASLayoutTestNode *)node;
|
||||
|
||||
/// Add this to the list of expected size ranges for the given node.
|
||||
- (void)addSizeRange:(ASSizeRange)sizeRange forNode:(ASLayoutTestNode *)node;
|
||||
|
||||
/// If you have a node that wants a size different than it gets, set it here.
|
||||
/// For any leaf nodes that you don't call this on, the node will return the correct size
|
||||
/// based on the fixture's layout. This is useful for triggering multipass stack layout.
|
||||
- (void)setReturnedSize:(CGSize)size forNode:(ASLayoutTestNode *)node;
|
||||
|
||||
/// Get the first expected size range for the node.
|
||||
- (ASSizeRange)firstSizeRangeForNode:(ASLayoutTestNode *)node;
|
||||
|
||||
/// Enumerate all the size ranges for all the nodes using the provided block.
|
||||
- (void)withSizeRangesForAllNodesUsingBlock:(void (^)(ASLayoutTestNode *node, ASSizeRange sizeRange))block;
|
||||
|
||||
/// Enumerate all the size ranges for the node.
|
||||
- (void)withSizeRangesForNode:(ASLayoutTestNode *)node block:(void (^)(ASSizeRange sizeRange))block;
|
||||
|
||||
/// Configure the nodes for this fixture. Set testSize on leaf nodes, layoutSpecBlock on container nodes.
|
||||
- (void)apply;
|
||||
|
||||
@end
|
||||
|
||||
@interface ASLayout (TestHelpers)
|
||||
|
||||
@property (nonatomic, readonly) NSArray<ASDisplayNode *> *allNodes;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
@ -1,139 +0,0 @@
|
||||
//
|
||||
// ASTLayoutFixture.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASTLayoutFixture.h"
|
||||
|
||||
@interface ASTLayoutFixture ()
|
||||
|
||||
/// The size ranges against which nodes are expected to be measured.
|
||||
@property (nonatomic, readonly) NSMapTable<ASDisplayNode *, NSMutableArray<NSValue *> *> *sizeRanges;
|
||||
|
||||
/// The overridden returned sizes for nodes where you want to trigger multipass layout.
|
||||
@property (nonatomic, readonly) NSMapTable<ASDisplayNode *, NSValue *> *returnedSizes;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASTLayoutFixture
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_sizeRanges = [NSMapTable mapTableWithKeyOptions:NSMapTableObjectPointerPersonality valueOptions:NSMapTableStrongMemory];
|
||||
_layoutSpecBlocks = [NSMapTable mapTableWithKeyOptions:NSMapTableObjectPointerPersonality valueOptions:NSMapTableStrongMemory];
|
||||
_returnedSizes = [NSMapTable mapTableWithKeyOptions:NSMapTableObjectPointerPersonality valueOptions:NSMapTableStrongMemory];
|
||||
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)addSizeRange:(ASSizeRange)sizeRange forNode:(ASLayoutTestNode *)node
|
||||
{
|
||||
auto ranges = [_sizeRanges objectForKey:node];
|
||||
if (ranges == nil) {
|
||||
ranges = [[NSMutableArray alloc] init];
|
||||
[_sizeRanges setObject:ranges forKey:node];
|
||||
}
|
||||
[ranges addObject:[NSValue valueWithBytes:&sizeRange objCType:@encode(ASSizeRange)]];
|
||||
}
|
||||
|
||||
- (void)setReturnedSize:(CGSize)size forNode:(ASLayoutTestNode *)node
|
||||
{
|
||||
[_returnedSizes setObject:[NSValue valueWithCGSize:size] forKey:node];
|
||||
}
|
||||
|
||||
- (ASSizeRange)firstSizeRangeForNode:(ASLayoutTestNode *)node
|
||||
{
|
||||
const auto val = [_sizeRanges objectForKey:node].firstObject;
|
||||
ASSizeRange r;
|
||||
[val getValue:&r];
|
||||
return r;
|
||||
}
|
||||
|
||||
- (void)withSizeRangesForAllNodesUsingBlock:(void (^)(ASLayoutTestNode * _Nonnull, ASSizeRange))block
|
||||
{
|
||||
for (ASLayoutTestNode *node in self.allNodes) {
|
||||
[self withSizeRangesForNode:node block:^(ASSizeRange sizeRange) {
|
||||
block(node, sizeRange);
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)withSizeRangesForNode:(ASLayoutTestNode *)node block:(void (^)(ASSizeRange))block
|
||||
{
|
||||
for (NSValue *value in [_sizeRanges objectForKey:node]) {
|
||||
ASSizeRange r;
|
||||
[value getValue:&r];
|
||||
block(r);
|
||||
}
|
||||
}
|
||||
|
||||
- (ASLayout *)layoutForNode:(ASLayoutTestNode *)node
|
||||
{
|
||||
NSMutableArray *allLayouts = [NSMutableArray array];
|
||||
[ASTLayoutFixture collectAllLayoutsFromLayout:self.layout array:allLayouts];
|
||||
for (ASLayout *layout in allLayouts) {
|
||||
if (layout.layoutElement == node) {
|
||||
return layout;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
/// A very dumb tree iteration approach. NSEnumerator or something would be way better.
|
||||
+ (void)collectAllLayoutsFromLayout:(ASLayout *)layout array:(NSMutableArray<ASLayout *> *)array
|
||||
{
|
||||
[array addObject:layout];
|
||||
for (ASLayout *sublayout in layout.sublayouts) {
|
||||
[self collectAllLayoutsFromLayout:sublayout array:array];
|
||||
}
|
||||
}
|
||||
|
||||
- (ASLayoutTestNode *)rootNode
|
||||
{
|
||||
return (ASLayoutTestNode *)self.layout.layoutElement;
|
||||
}
|
||||
|
||||
- (NSSet<ASLayoutTestNode *> *)allNodes
|
||||
{
|
||||
const auto allLayouts = [NSMutableArray array];
|
||||
[ASTLayoutFixture collectAllLayoutsFromLayout:self.layout array:allLayouts];
|
||||
return [NSSet setWithArray:[allLayouts valueForKey:@"layoutElement"]];
|
||||
}
|
||||
|
||||
- (void)apply
|
||||
{
|
||||
// Update layoutSpecBlock for parent nodes, set automatic subnode management
|
||||
for (ASDisplayNode *node in _layoutSpecBlocks) {
|
||||
const auto block = [_layoutSpecBlocks objectForKey:node];
|
||||
if (node.layoutSpecBlock != block) {
|
||||
node.automaticallyManagesSubnodes = YES;
|
||||
node.layoutSpecBlock = block;
|
||||
[node setNeedsLayout];
|
||||
}
|
||||
}
|
||||
|
||||
[self setTestSizesOfLeafNodesInLayout:self.layout];
|
||||
}
|
||||
|
||||
/// Go through the given layout, and for all the leaf nodes, set their preferredSize
|
||||
/// to the layout size if needed, then call -setNeedsLayout
|
||||
- (void)setTestSizesOfLeafNodesInLayout:(ASLayout *)layout
|
||||
{
|
||||
const auto node = (ASLayoutTestNode *)layout.layoutElement;
|
||||
if (layout.sublayouts.count == 0) {
|
||||
const auto override = [self.returnedSizes objectForKey:node];
|
||||
node.testSize = override ? override.CGSizeValue : layout.size;
|
||||
} else {
|
||||
node.testSize = CGSizeZero;
|
||||
for (ASLayout *sublayout in layout.sublayouts) {
|
||||
[self setTestSizesOfLeafNodesInLayout:sublayout];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
@ -1,41 +0,0 @@
|
||||
//
|
||||
// ASTabBarControllerTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import <AsyncDisplayKit/ASTabBarController.h>
|
||||
#import <AsyncDisplayKit/ASViewController.h>
|
||||
|
||||
@interface ASTabBarControllerTests: XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASTabBarControllerTests
|
||||
|
||||
- (void)testTabBarControllerSelectIndex {
|
||||
ASViewController *firstViewController = [ASViewController new];
|
||||
ASViewController *secondViewController = [ASViewController new];
|
||||
NSArray *viewControllers = @[firstViewController, secondViewController];
|
||||
ASTabBarController *tabBarController = [ASTabBarController new];
|
||||
[tabBarController setViewControllers:viewControllers];
|
||||
[tabBarController setSelectedIndex:1];
|
||||
XCTAssertTrue([tabBarController.viewControllers isEqualToArray:viewControllers]);
|
||||
XCTAssertEqual(tabBarController.selectedViewController, secondViewController);
|
||||
}
|
||||
|
||||
- (void)testTabBarControllerSelectViewController {
|
||||
ASViewController *firstViewController = [ASViewController new];
|
||||
ASViewController *secondViewController = [ASViewController new];
|
||||
NSArray *viewControllers = @[firstViewController, secondViewController];
|
||||
ASTabBarController *tabBarController = [ASTabBarController new];
|
||||
[tabBarController setViewControllers:viewControllers];
|
||||
[tabBarController setSelectedViewController:secondViewController];
|
||||
XCTAssertTrue([tabBarController.viewControllers isEqualToArray:viewControllers]);
|
||||
XCTAssertEqual(tabBarController.selectedViewController, secondViewController);
|
||||
}
|
||||
|
||||
@end
|
@ -1,930 +0,0 @@
|
||||
//
|
||||
// ASTableViewTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdocumentation"
|
||||
#import <JGMethodSwizzler/JGMethodSwizzler.h>
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
#import <AsyncDisplayKit/ASTableView.h>
|
||||
#import <AsyncDisplayKit/ASTableViewInternal.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
|
||||
#import <AsyncDisplayKit/ASCellNode.h>
|
||||
#import <AsyncDisplayKit/ASTableNode.h>
|
||||
#import <AsyncDisplayKit/ASTableView+Undeprecated.h>
|
||||
#import <AsyncDisplayKit/ASInternalHelpers.h>
|
||||
|
||||
#import "ASXCTExtensions.h"
|
||||
|
||||
#define NumberOfSections 10
|
||||
#define NumberOfReloadIterations 50
|
||||
|
||||
@interface ASTestDataController : ASDataController
|
||||
@property (nonatomic) int numberOfAllNodesRelayouts;
|
||||
@end
|
||||
|
||||
@implementation ASTestDataController
|
||||
|
||||
- (void)relayoutAllNodesWithInvalidationBlock:(nullable void (^)())invalidationBlock
|
||||
{
|
||||
_numberOfAllNodesRelayouts++;
|
||||
[super relayoutAllNodesWithInvalidationBlock:invalidationBlock];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface UITableView (Testing)
|
||||
// This will start recording all editing calls to UITableView
|
||||
// into the provided array.
|
||||
// Make sure to call [UITableView deswizzleInstanceMethods] to reset this.
|
||||
+ (void)as_recordEditingCallsIntoArray:(NSMutableArray<NSString *> *)selectors;
|
||||
@end
|
||||
|
||||
@interface ASTestTableView : ASTableView
|
||||
@property (nonatomic) void (^willDeallocBlock)(ASTableView *tableView);
|
||||
@end
|
||||
|
||||
@implementation ASTestTableView
|
||||
|
||||
- (instancetype)__initWithFrame:(CGRect)frame style:(UITableViewStyle)style
|
||||
{
|
||||
|
||||
return [super _initWithFrame:frame style:style dataControllerClass:[ASTestDataController class] owningNode:nil eventLog:nil];
|
||||
}
|
||||
|
||||
- (ASTestDataController *)testDataController
|
||||
{
|
||||
return (ASTestDataController *)self.dataController;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
if (_willDeallocBlock) {
|
||||
_willDeallocBlock(self);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface ASTableViewTestDelegate : NSObject <ASTableDataSource, ASTableDelegate>
|
||||
@property (nonatomic) void (^willDeallocBlock)(ASTableViewTestDelegate *delegate);
|
||||
@property (nonatomic) CGFloat headerHeight;
|
||||
@property (nonatomic) CGFloat footerHeight;
|
||||
@end
|
||||
|
||||
@implementation ASTableViewTestDelegate
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (ASCellNodeBlock)tableView:(ASTableView *)tableView nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
|
||||
{
|
||||
return _footerHeight;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
|
||||
{
|
||||
return _headerHeight;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
if (_willDeallocBlock) {
|
||||
_willDeallocBlock(self);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface ASTestTextCellNode : ASTextCellNode
|
||||
/** Calculated by counting how many times -layoutSpecThatFits: is called on the main thread. */
|
||||
@property (nonatomic) int numberOfLayoutsOnMainThread;
|
||||
@end
|
||||
|
||||
@implementation ASTestTextCellNode
|
||||
|
||||
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
|
||||
{
|
||||
if ([NSThread isMainThread]) {
|
||||
_numberOfLayoutsOnMainThread++;
|
||||
}
|
||||
return [super layoutSpecThatFits:constrainedSize];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface ASTableViewFilledDataSource : NSObject <ASTableDataSource, ASTableDelegate>
|
||||
@property (nonatomic) BOOL usesSectionIndex;
|
||||
@property (nonatomic) NSInteger numberOfSections;
|
||||
@property (nonatomic) NSInteger rowsPerSection;
|
||||
@property (nonatomic, nullable) ASCellNodeBlock(^nodeBlockForItem)(NSIndexPath *);
|
||||
@end
|
||||
|
||||
@implementation ASTableViewFilledDataSource
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_numberOfSections = NumberOfSections;
|
||||
_rowsPerSection = 20;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)respondsToSelector:(SEL)aSelector
|
||||
{
|
||||
if (aSelector == @selector(sectionIndexTitlesForTableView:) || aSelector == @selector(tableView:sectionForSectionIndexTitle:atIndex:)) {
|
||||
return _usesSectionIndex;
|
||||
} else {
|
||||
return [super respondsToSelector:aSelector];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
|
||||
{
|
||||
return _numberOfSections;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
||||
{
|
||||
return _rowsPerSection;
|
||||
}
|
||||
|
||||
- (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
ASTestTextCellNode *textCellNode = [ASTestTextCellNode new];
|
||||
textCellNode.text = indexPath.description;
|
||||
|
||||
return textCellNode;
|
||||
}
|
||||
|
||||
- (ASCellNodeBlock)tableView:(ASTableView *)tableView nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if (_nodeBlockForItem) {
|
||||
return _nodeBlockForItem(indexPath);
|
||||
}
|
||||
|
||||
return ^{
|
||||
ASTestTextCellNode *textCellNode = [ASTestTextCellNode new];
|
||||
textCellNode.text = [NSString stringWithFormat:@"{%d, %d}", (int)indexPath.section, (int)indexPath.row];
|
||||
textCellNode.backgroundColor = [UIColor whiteColor];
|
||||
return textCellNode;
|
||||
};
|
||||
}
|
||||
|
||||
- (nullable NSArray<NSString *> *)sectionIndexTitlesForTableView:(UITableView *)tableView
|
||||
{
|
||||
return @[ @"A", @"B", @"C" ];
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface ASTableViewFilledDelegate : NSObject <ASTableDelegate>
|
||||
@end
|
||||
|
||||
@implementation ASTableViewFilledDelegate
|
||||
|
||||
- (ASSizeRange)tableView:(ASTableView *)tableView constrainedSizeForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
return ASSizeRangeMake(CGSizeMake(10, 42));
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface ASTableViewTests : XCTestCase
|
||||
@property (nonatomic, retain) ASTableView *testTableView;
|
||||
@end
|
||||
|
||||
@implementation ASTableViewTests
|
||||
|
||||
- (void)testDataSourceImplementsNecessaryMethods
|
||||
{
|
||||
ASTestTableView *tableView = [[ASTestTableView alloc] __initWithFrame:CGRectMake(0, 0, 100, 400)
|
||||
style:UITableViewStylePlain];
|
||||
|
||||
|
||||
|
||||
ASTableViewFilledDataSource *dataSource = (ASTableViewFilledDataSource *)[NSObject new];
|
||||
XCTAssertThrows((tableView.asyncDataSource = dataSource));
|
||||
|
||||
dataSource = [ASTableViewFilledDataSource new];
|
||||
XCTAssertNoThrow((tableView.asyncDataSource = dataSource));
|
||||
}
|
||||
|
||||
- (void)testConstrainedSizeForRowAtIndexPath
|
||||
{
|
||||
// Initial width of the table view is non-zero and all nodes are measured with this size.
|
||||
// Any subsequent size change must trigger a relayout.
|
||||
// Width and height are swapped so that a later size change will simulate a rotation
|
||||
ASTestTableView *tableView = [[ASTestTableView alloc] __initWithFrame:CGRectMake(0, 0, 100, 400)
|
||||
style:UITableViewStylePlain];
|
||||
|
||||
ASTableViewFilledDelegate *delegate = [ASTableViewFilledDelegate new];
|
||||
ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new];
|
||||
|
||||
tableView.asyncDelegate = delegate;
|
||||
tableView.asyncDataSource = dataSource;
|
||||
|
||||
[tableView reloadData];
|
||||
[tableView waitUntilAllUpdatesAreCommitted];
|
||||
[tableView setNeedsLayout];
|
||||
[tableView layoutIfNeeded];
|
||||
|
||||
CGFloat separatorHeight = 1.0 / ASScreenScale();
|
||||
for (int section = 0; section < NumberOfSections; section++) {
|
||||
for (int row = 0; row < [tableView numberOfRowsInSection:section]; row++) {
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section];
|
||||
CGRect rect = [tableView rectForRowAtIndexPath:indexPath];
|
||||
XCTAssertEqual(rect.size.width, 100); // specified width should be ignored for table
|
||||
XCTAssertEqual(rect.size.height, 42 + separatorHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Convert this to ARC.
|
||||
- (void)DISABLED_testTableViewDoesNotRetainItselfAndDelegate
|
||||
{
|
||||
ASTestTableView *tableView = [[ASTestTableView alloc] __initWithFrame:CGRectZero style:UITableViewStylePlain];
|
||||
|
||||
__block BOOL tableViewDidDealloc = NO;
|
||||
tableView.willDeallocBlock = ^(ASTableView *v){
|
||||
tableViewDidDealloc = YES;
|
||||
};
|
||||
|
||||
ASTableViewTestDelegate *delegate = [[ASTableViewTestDelegate alloc] init];
|
||||
|
||||
__block BOOL delegateDidDealloc = NO;
|
||||
delegate.willDeallocBlock = ^(ASTableViewTestDelegate *d){
|
||||
delegateDidDealloc = YES;
|
||||
};
|
||||
|
||||
tableView.asyncDataSource = delegate;
|
||||
tableView.asyncDelegate = delegate;
|
||||
|
||||
// [delegate release];
|
||||
XCTAssertTrue(delegateDidDealloc, @"unexpected delegate lifetime:%@", delegate);
|
||||
|
||||
// XCTAssertNoThrow([tableView release], @"unexpected exception when deallocating table view:%@", tableView);
|
||||
XCTAssertTrue(tableViewDidDealloc, @"unexpected table view lifetime:%@", tableView);
|
||||
}
|
||||
|
||||
- (NSIndexSet *)randomIndexSet
|
||||
{
|
||||
NSInteger randA = arc4random_uniform(NumberOfSections - 1);
|
||||
NSInteger randB = arc4random_uniform(NumberOfSections - 1);
|
||||
|
||||
return [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(MIN(randA, randB), MAX(randA, randB) - MIN(randA, randB))];
|
||||
}
|
||||
|
||||
- (NSArray *)randomIndexPathsExisting:(BOOL)existing rowCount:(NSInteger)rowCount
|
||||
{
|
||||
NSMutableArray *indexPaths = [NSMutableArray array];
|
||||
[[self randomIndexSet] enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
|
||||
NSIndexPath *sectionIndex = [[NSIndexPath alloc] initWithIndex:idx];
|
||||
for (NSUInteger i = (existing ? 0 : rowCount); i < (existing ? rowCount : rowCount * 2); i++) {
|
||||
// Maximize evility by sporadically skipping indicies 1/3rd of the time, but only if reloading existing rows
|
||||
if (existing && arc4random_uniform(2) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
NSIndexPath *indexPath = [sectionIndex indexPathByAddingIndex:i];
|
||||
[indexPaths addObject:indexPath];
|
||||
}
|
||||
}];
|
||||
return indexPaths;
|
||||
}
|
||||
|
||||
- (void)DISABLED_testReloadData
|
||||
{
|
||||
// Keep the viewport moderately sized so that new cells are loaded on scrolling
|
||||
ASTableView *tableView = [[ASTestTableView alloc] __initWithFrame:CGRectMake(0, 0, 100, 500)
|
||||
style:UITableViewStylePlain];
|
||||
|
||||
ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new];
|
||||
|
||||
tableView.asyncDelegate = dataSource;
|
||||
tableView.asyncDataSource = dataSource;
|
||||
|
||||
XCTestExpectation *reloadDataExpectation = [self expectationWithDescription:@"reloadData"];
|
||||
|
||||
[tableView reloadDataWithCompletion:^{
|
||||
NSLog(@"*** Reload Complete ***");
|
||||
[reloadDataExpectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectationsWithTimeout:5 handler:^(NSError *error) {
|
||||
if (error) {
|
||||
XCTFail(@"Expectation failed: %@", error);
|
||||
}
|
||||
}];
|
||||
|
||||
for (int i = 0; i < NumberOfReloadIterations; ++i) {
|
||||
UITableViewRowAnimation rowAnimation = (arc4random_uniform(2) == 0 ? UITableViewRowAnimationMiddle : UITableViewRowAnimationNone);
|
||||
BOOL animatedScroll = (arc4random_uniform(2) == 0 ? YES : NO);
|
||||
BOOL reloadRowsInsteadOfSections = (arc4random_uniform(2) == 0 ? YES : NO);
|
||||
NSTimeInterval runLoopDelay = ((arc4random_uniform(2) == 0) ? (1.0 / (1 + arc4random_uniform(500))) : 0);
|
||||
BOOL useBeginEndUpdates = (arc4random_uniform(3) == 0 ? YES : NO);
|
||||
|
||||
// instrument our instrumentation ;)
|
||||
//NSLog(@"Iteration %03d: %@|%@|%@|%@|%g", i, (rowAnimation == UITableViewRowAnimationNone) ? @"NONE " : @"MIDDLE", animatedScroll ? @"ASCR" : @" ", reloadRowsInsteadOfSections ? @"ROWS" : @"SECS", useBeginEndUpdates ? @"BEGEND" : @" ", runLoopDelay);
|
||||
|
||||
if (useBeginEndUpdates) {
|
||||
[tableView beginUpdates];
|
||||
}
|
||||
|
||||
if (reloadRowsInsteadOfSections) {
|
||||
NSArray *indexPaths = [self randomIndexPathsExisting:YES rowCount:dataSource.rowsPerSection];
|
||||
//NSLog(@"reloading rows: %@", indexPaths);
|
||||
[tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:rowAnimation];
|
||||
} else {
|
||||
NSIndexSet *sections = [self randomIndexSet];
|
||||
//NSLog(@"reloading sections: %@", sections);
|
||||
[tableView reloadSections:sections withRowAnimation:rowAnimation];
|
||||
}
|
||||
|
||||
[tableView setContentOffset:CGPointMake(0, arc4random_uniform(tableView.contentSize.height - tableView.bounds.size.height)) animated:animatedScroll];
|
||||
|
||||
if (runLoopDelay > 0) {
|
||||
// Run other stuff on the main queue for between 2ms and 1000ms.
|
||||
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:runLoopDelay]];
|
||||
}
|
||||
|
||||
if (useBeginEndUpdates) {
|
||||
[tableView endUpdates];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testRelayoutAllNodesWithNonZeroSizeInitially
|
||||
{
|
||||
// Initial width of the table view is non-zero and all nodes are measured with this size.
|
||||
// Any subsequence size change must trigger a relayout.
|
||||
CGSize tableViewFinalSize = CGSizeMake(100, 500);
|
||||
// Width and height are swapped so that a later size change will simulate a rotation
|
||||
ASTestTableView *tableView = [[ASTestTableView alloc] __initWithFrame:CGRectMake(0, 0, tableViewFinalSize.height, tableViewFinalSize.width)
|
||||
style:UITableViewStylePlain];
|
||||
|
||||
ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new];
|
||||
|
||||
tableView.asyncDelegate = dataSource;
|
||||
tableView.asyncDataSource = dataSource;
|
||||
|
||||
[tableView layoutIfNeeded];
|
||||
|
||||
XCTAssertEqual(tableView.testDataController.numberOfAllNodesRelayouts, 0);
|
||||
[self triggerSizeChangeAndAssertRelayoutAllNodesForTableView:tableView newSize:tableViewFinalSize];
|
||||
}
|
||||
|
||||
- (void)testRelayoutVisibleRowsWhenEditingModeIsChanged
|
||||
{
|
||||
CGSize tableViewSize = CGSizeMake(100, 500);
|
||||
ASTestTableView *tableView = [[ASTestTableView alloc] __initWithFrame:CGRectMake(0, 0, tableViewSize.width, tableViewSize.height)
|
||||
style:UITableViewStylePlain];
|
||||
ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new];
|
||||
// Currently this test requires that the text in the cell node fills the
|
||||
// visible width, so we use the long description for the index path.
|
||||
dataSource.nodeBlockForItem = ^(NSIndexPath *indexPath) {
|
||||
return (ASCellNodeBlock)^{
|
||||
ASTestTextCellNode *textCellNode = [[ASTestTextCellNode alloc] init];
|
||||
textCellNode.text = indexPath.description;
|
||||
return textCellNode;
|
||||
};
|
||||
};
|
||||
tableView.asyncDelegate = dataSource;
|
||||
tableView.asyncDataSource = dataSource;
|
||||
|
||||
[self triggerFirstLayoutMeasurementForTableView:tableView];
|
||||
|
||||
NSArray *visibleNodes = [tableView visibleNodes];
|
||||
XCTAssertGreaterThan(visibleNodes.count, 0);
|
||||
|
||||
// Cause table view to enter editing mode.
|
||||
// Visibile nodes should be re-measured on main thread with the new (smaller) content view width.
|
||||
// Other nodes are untouched.
|
||||
XCTestExpectation *relayoutAfterEnablingEditingExpectation = [self expectationWithDescription:@"relayoutAfterEnablingEditing"];
|
||||
[tableView beginUpdates];
|
||||
[tableView setEditing:YES];
|
||||
[tableView endUpdatesAnimated:YES completion:^(BOOL completed) {
|
||||
for (int section = 0; section < NumberOfSections; section++) {
|
||||
for (int row = 0; row < dataSource.rowsPerSection; row++) {
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section];
|
||||
ASTestTextCellNode *node = (ASTestTextCellNode *)[tableView nodeForRowAtIndexPath:indexPath];
|
||||
if ([visibleNodes containsObject:node]) {
|
||||
XCTAssertEqual(node.numberOfLayoutsOnMainThread, 1);
|
||||
XCTAssertLessThan(node.constrainedSizeForCalculatedLayout.max.width, tableViewSize.width);
|
||||
} else {
|
||||
XCTAssertEqual(node.numberOfLayoutsOnMainThread, 0);
|
||||
XCTAssertEqual(node.constrainedSizeForCalculatedLayout.max.width, tableViewSize.width);
|
||||
}
|
||||
}
|
||||
}
|
||||
[relayoutAfterEnablingEditingExpectation fulfill];
|
||||
}];
|
||||
[self waitForExpectationsWithTimeout:5 handler:^(NSError *error) {
|
||||
if (error) {
|
||||
XCTFail(@"Expectation failed: %@", error);
|
||||
}
|
||||
}];
|
||||
|
||||
// Cause table view to leave editing mode.
|
||||
// Visibile nodes should be re-measured again.
|
||||
// All nodes should have max constrained width equals to the table view width.
|
||||
XCTestExpectation *relayoutAfterDisablingEditingExpectation = [self expectationWithDescription:@"relayoutAfterDisablingEditing"];
|
||||
[tableView beginUpdates];
|
||||
[tableView setEditing:NO];
|
||||
[tableView endUpdatesAnimated:YES completion:^(BOOL completed) {
|
||||
for (int section = 0; section < NumberOfSections; section++) {
|
||||
for (int row = 0; row < dataSource.rowsPerSection; row++) {
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section];
|
||||
ASTestTextCellNode *node = (ASTestTextCellNode *)[tableView nodeForRowAtIndexPath:indexPath];
|
||||
BOOL visible = [visibleNodes containsObject:node];
|
||||
XCTAssertEqual(node.numberOfLayoutsOnMainThread, visible ? 2: 0);
|
||||
XCTAssertEqual(node.constrainedSizeForCalculatedLayout.max.width, tableViewSize.width);
|
||||
}
|
||||
}
|
||||
[relayoutAfterDisablingEditingExpectation fulfill];
|
||||
}];
|
||||
[self waitForExpectationsWithTimeout:5 handler:^(NSError *error) {
|
||||
if (error) {
|
||||
XCTFail(@"Expectation failed: %@", error);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)DISABLED_testRelayoutRowsAfterEditingModeIsChangedAndTheyBecomeVisible
|
||||
{
|
||||
CGSize tableViewSize = CGSizeMake(100, 500);
|
||||
ASTestTableView *tableView = [[ASTestTableView alloc] __initWithFrame:CGRectMake(0, 0, tableViewSize.width, tableViewSize.height)
|
||||
style:UITableViewStylePlain];
|
||||
ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new];
|
||||
|
||||
tableView.asyncDelegate = dataSource;
|
||||
tableView.asyncDataSource = dataSource;
|
||||
|
||||
[self triggerFirstLayoutMeasurementForTableView:tableView];
|
||||
|
||||
// Cause table view to enter editing mode and then scroll to the bottom.
|
||||
// The last node should be re-measured on main thread with the new (smaller) content view width.
|
||||
NSIndexPath *lastRowIndexPath = [NSIndexPath indexPathForRow:(dataSource.rowsPerSection - 1) inSection:(NumberOfSections - 1)];
|
||||
XCTestExpectation *relayoutExpectation = [self expectationWithDescription:@"relayout"];
|
||||
[tableView beginUpdates];
|
||||
[tableView setEditing:YES];
|
||||
[tableView setContentOffset:CGPointMake(0, CGFLOAT_MAX) animated:YES];
|
||||
[tableView endUpdatesAnimated:YES completion:^(BOOL completed) {
|
||||
ASTestTextCellNode *node = (ASTestTextCellNode *)[tableView nodeForRowAtIndexPath:lastRowIndexPath];
|
||||
XCTAssertEqual(node.numberOfLayoutsOnMainThread, 1);
|
||||
XCTAssertLessThan(node.constrainedSizeForCalculatedLayout.max.width, tableViewSize.width);
|
||||
[relayoutExpectation fulfill];
|
||||
}];
|
||||
[self waitForExpectationsWithTimeout:5 handler:^(NSError *error) {
|
||||
if (error) {
|
||||
XCTFail(@"Expectation failed: %@", error);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)testIndexPathForNode
|
||||
{
|
||||
CGSize tableViewSize = CGSizeMake(100, 500);
|
||||
ASTestTableView *tableView = [[ASTestTableView alloc] initWithFrame:CGRectMake(0, 0, tableViewSize.width, tableViewSize.height)
|
||||
style:UITableViewStylePlain];
|
||||
ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new];
|
||||
|
||||
tableView.asyncDelegate = dataSource;
|
||||
tableView.asyncDataSource = dataSource;
|
||||
|
||||
[tableView reloadDataWithCompletion:^{
|
||||
for (NSUInteger i = 0; i < NumberOfSections; i++) {
|
||||
for (NSUInteger j = 0; j < dataSource.rowsPerSection; j++) {
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:j inSection:i];
|
||||
ASCellNode *cellNode = [tableView nodeForRowAtIndexPath:indexPath];
|
||||
NSIndexPath *reportedIndexPath = [tableView indexPathForNode:cellNode];
|
||||
XCTAssertEqual(indexPath.row, reportedIndexPath.row);
|
||||
}
|
||||
}
|
||||
self.testTableView = nil;
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)triggerFirstLayoutMeasurementForTableView:(ASTableView *)tableView{
|
||||
XCTestExpectation *reloadDataExpectation = [self expectationWithDescription:@"reloadData"];
|
||||
[tableView reloadDataWithCompletion:^{
|
||||
for (int section = 0; section < NumberOfSections; section++) {
|
||||
for (int row = 0; row < [tableView numberOfRowsInSection:section]; row++) {
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section];
|
||||
ASTestTextCellNode *node = (ASTestTextCellNode *)[tableView nodeForRowAtIndexPath:indexPath];
|
||||
XCTAssertEqual(node.numberOfLayoutsOnMainThread, 0);
|
||||
XCTAssertEqual(node.constrainedSizeForCalculatedLayout.max.width, tableView.frame.size.width);
|
||||
}
|
||||
}
|
||||
[reloadDataExpectation fulfill];
|
||||
}];
|
||||
[self waitForExpectationsWithTimeout:5 handler:^(NSError *error) {
|
||||
if (error) {
|
||||
XCTFail(@"Expectation failed: %@", error);
|
||||
}
|
||||
}];
|
||||
[tableView setNeedsLayout];
|
||||
[tableView layoutIfNeeded];
|
||||
[tableView waitUntilAllUpdatesAreCommitted];
|
||||
}
|
||||
|
||||
- (void)triggerSizeChangeAndAssertRelayoutAllNodesForTableView:(ASTestTableView *)tableView newSize:(CGSize)newSize
|
||||
{
|
||||
XCTestExpectation *nodesMeasuredUsingNewConstrainedSizeExpectation = [self expectationWithDescription:@"nodesMeasuredUsingNewConstrainedSize"];
|
||||
|
||||
[tableView beginUpdates];
|
||||
|
||||
CGRect frame = tableView.frame;
|
||||
frame.size = newSize;
|
||||
tableView.frame = frame;
|
||||
[tableView layoutIfNeeded];
|
||||
|
||||
[tableView endUpdatesAnimated:NO completion:^(BOOL completed) {
|
||||
XCTAssertEqual(tableView.testDataController.numberOfAllNodesRelayouts, 1);
|
||||
|
||||
for (int section = 0; section < NumberOfSections; section++) {
|
||||
for (int row = 0; row < [tableView numberOfRowsInSection:section]; row++) {
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section];
|
||||
ASTestTextCellNode *node = (ASTestTextCellNode *)[tableView nodeForRowAtIndexPath:indexPath];
|
||||
XCTAssertLessThanOrEqual(node.numberOfLayoutsOnMainThread, 1);
|
||||
XCTAssertEqual(node.constrainedSizeForCalculatedLayout.max.width, newSize.width);
|
||||
}
|
||||
}
|
||||
[nodesMeasuredUsingNewConstrainedSizeExpectation fulfill];
|
||||
}];
|
||||
[self waitForExpectationsWithTimeout:5 handler:^(NSError *error) {
|
||||
if (error) {
|
||||
XCTFail(@"Expectation failed: %@", error);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
/**
|
||||
* This may seem silly, but we had issues where the runtime sometimes wouldn't correctly report
|
||||
* conformances declared on categories.
|
||||
*/
|
||||
- (void)testThatTableNodeConformsToExpectedProtocols
|
||||
{
|
||||
ASTableNode *node = [[ASTableNode alloc] initWithStyle:UITableViewStylePlain];
|
||||
XCTAssert([node conformsToProtocol:@protocol(ASRangeControllerUpdateRangeProtocol)]);
|
||||
}
|
||||
|
||||
- (void)testThatInitialDataLoadHappensInOneShot
|
||||
{
|
||||
NSMutableArray *selectors = [NSMutableArray array];
|
||||
ASTableNode *node = [[ASTableNode alloc] initWithStyle:UITableViewStylePlain];
|
||||
|
||||
ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new];
|
||||
node.frame = CGRectMake(0, 0, 100, 100);
|
||||
|
||||
node.dataSource = dataSource;
|
||||
node.delegate = dataSource;
|
||||
|
||||
[UITableView as_recordEditingCallsIntoArray:selectors];
|
||||
XCTAssertGreaterThan(node.numberOfSections, 0);
|
||||
[node waitUntilAllUpdatesAreProcessed];
|
||||
XCTAssertGreaterThan(node.view.numberOfSections, 0);
|
||||
|
||||
// The first reloadData call helps prevent UITableView from calling it multiple times while ASDataController is working.
|
||||
// The second reloadData call is the real one.
|
||||
NSArray *expectedSelectors = @[ NSStringFromSelector(@selector(reloadData)),
|
||||
NSStringFromSelector(@selector(reloadData)) ];
|
||||
XCTAssertEqualObjects(selectors, expectedSelectors);
|
||||
|
||||
[UITableView deswizzleAllInstanceMethods];
|
||||
}
|
||||
|
||||
- (void)testThatReloadDataHappensInOneShot
|
||||
{
|
||||
NSMutableArray *selectors = [NSMutableArray array];
|
||||
ASTableNode *node = [[ASTableNode alloc] initWithStyle:UITableViewStylePlain];
|
||||
|
||||
ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new];
|
||||
node.frame = CGRectMake(0, 0, 100, 100);
|
||||
|
||||
node.dataSource = dataSource;
|
||||
node.delegate = dataSource;
|
||||
|
||||
// Load initial data.
|
||||
XCTAssertGreaterThan(node.numberOfSections, 0);
|
||||
[node waitUntilAllUpdatesAreProcessed];
|
||||
XCTAssertGreaterThan(node.view.numberOfSections, 0);
|
||||
|
||||
// Reload data.
|
||||
[UITableView as_recordEditingCallsIntoArray:selectors];
|
||||
[node reloadData];
|
||||
[node waitUntilAllUpdatesAreProcessed];
|
||||
|
||||
// Assert that the beginning of the call pattern is correct.
|
||||
// There is currently noise that comes after that we will allow for this test.
|
||||
NSArray *expectedSelectors = @[ NSStringFromSelector(@selector(reloadData)) ];
|
||||
XCTAssertEqualObjects(selectors, expectedSelectors);
|
||||
|
||||
[UITableView deswizzleAllInstanceMethods];
|
||||
}
|
||||
|
||||
/**
|
||||
* This tests an issue where, if the table is loaded before the first layout pass,
|
||||
* the nodes are first measured with a constrained width of 0 which isn't ideal.
|
||||
*/
|
||||
- (void)testThatNodeConstrainedSizesAreCorrectIfReloadIsPreempted
|
||||
{
|
||||
ASTableNode *node = [[ASTableNode alloc] initWithStyle:UITableViewStylePlain];
|
||||
|
||||
ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new];
|
||||
CGFloat cellWidth = 320;
|
||||
node.frame = CGRectMake(0, 0, cellWidth, 480);
|
||||
|
||||
node.dataSource = dataSource;
|
||||
node.delegate = dataSource;
|
||||
|
||||
// Trigger data load BEFORE first layout pass, to ensure constrained size is correct.
|
||||
XCTAssertGreaterThan(node.numberOfSections, 0);
|
||||
[node waitUntilAllUpdatesAreProcessed];
|
||||
|
||||
ASSizeRange expectedSizeRange = ASSizeRangeMake(CGSizeMake(cellWidth, 0));
|
||||
expectedSizeRange.max.height = CGFLOAT_MAX;
|
||||
|
||||
for (NSInteger i = 0; i < node.numberOfSections; i++) {
|
||||
for (NSInteger j = 0; j < [node numberOfRowsInSection:i]; j++) {
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:j inSection:i];
|
||||
ASTestTextCellNode *cellNode = (id)[node nodeForRowAtIndexPath:indexPath];
|
||||
ASXCTAssertEqualSizeRanges(cellNode.constrainedSizeForCalculatedLayout, expectedSizeRange);
|
||||
XCTAssertEqual(cellNode.numberOfLayoutsOnMainThread, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testSectionIndexHandling
|
||||
{
|
||||
ASTableNode *node = [[ASTableNode alloc] initWithStyle:UITableViewStylePlain];
|
||||
|
||||
ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new];
|
||||
dataSource.usesSectionIndex = YES;
|
||||
node.frame = CGRectMake(0, 0, 320, 480);
|
||||
|
||||
node.dataSource = dataSource;
|
||||
node.delegate = dataSource;
|
||||
|
||||
// Trigger data load
|
||||
XCTAssertGreaterThan(node.numberOfSections, 0);
|
||||
XCTAssertGreaterThan([node numberOfRowsInSection:0], 0);
|
||||
|
||||
// UITableView's section index view is added only after some rows were inserted to the table.
|
||||
// All nodes loaded and measured during the initial reloadData used an outdated constrained width (i.e full width: 320).
|
||||
// So we need to force a new layout pass so that the table will pick up a new constrained size and apply to its node.
|
||||
[node setNeedsLayout];
|
||||
[node.view layoutIfNeeded];
|
||||
[node waitUntilAllUpdatesAreProcessed];
|
||||
|
||||
UITableViewCell *cell = [node.view cellForRowAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
|
||||
XCTAssertNotNil(cell);
|
||||
|
||||
CGFloat cellWidth = cell.contentView.frame.size.width;
|
||||
XCTAssert(cellWidth > 0 && cellWidth < 320, @"Expected cell width to be about 305. Width: %@", @(cellWidth));
|
||||
|
||||
ASSizeRange expectedSizeRange = ASSizeRangeMake(CGSizeMake(cellWidth, 0));
|
||||
expectedSizeRange.max.height = CGFLOAT_MAX;
|
||||
|
||||
for (NSInteger i = 0; i < node.numberOfSections; i++) {
|
||||
for (NSInteger j = 0; j < [node numberOfRowsInSection:i]; j++) {
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:j inSection:i];
|
||||
ASTestTextCellNode *cellNode = (id)[node nodeForRowAtIndexPath:indexPath];
|
||||
ASXCTAssertEqualSizeRanges(cellNode.constrainedSizeForCalculatedLayout, expectedSizeRange);
|
||||
// We will have to accept a relayout on main thread, since the index bar won't show
|
||||
// up until some of the cells are inserted.
|
||||
XCTAssertLessThanOrEqual(cellNode.numberOfLayoutsOnMainThread, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testThatNilBatchUpdatesCanBeSubmitted
|
||||
{
|
||||
ASTableNode *node = [[ASTableNode alloc] initWithStyle:UITableViewStylePlain];
|
||||
|
||||
// Passing nil blocks should not crash
|
||||
[node performBatchUpdates:nil completion:nil];
|
||||
[node performBatchAnimated:NO updates:nil completion:nil];
|
||||
}
|
||||
|
||||
// https://github.com/facebook/AsyncDisplayKit/issues/2252#issuecomment-263689979
|
||||
- (void)testIssue2252
|
||||
{
|
||||
// Hard-code an iPhone 7 screen. There's something particular about this geometry that causes the issue to repro.
|
||||
UIWindow *window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 375, 667)];
|
||||
|
||||
ASTableNode *node = [[ASTableNode alloc] initWithStyle:UITableViewStyleGrouped];
|
||||
node.frame = window.bounds;
|
||||
ASTableViewTestDelegate *del = [[ASTableViewTestDelegate alloc] init];
|
||||
del.headerHeight = 32;
|
||||
del.footerHeight = 0.01;
|
||||
node.delegate = del;
|
||||
ASTableViewFilledDataSource *ds = [[ASTableViewFilledDataSource alloc] init];
|
||||
ds.rowsPerSection = 1;
|
||||
node.dataSource = ds;
|
||||
ASViewController *vc = [[ASViewController alloc] initWithNode:node];
|
||||
UITabBarController *tabCtrl = [[UITabBarController alloc] init];
|
||||
tabCtrl.viewControllers = @[ vc ];
|
||||
tabCtrl.tabBar.translucent = NO;
|
||||
window.rootViewController = tabCtrl;
|
||||
[window makeKeyAndVisible];
|
||||
|
||||
[window layoutIfNeeded];
|
||||
[node waitUntilAllUpdatesAreProcessed];
|
||||
XCTAssertEqual(node.view.numberOfSections, NumberOfSections);
|
||||
ASXCTAssertEqualRects(CGRectMake(0, 32, 375, 44), [node rectForRowAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]], @"This text requires very specific geometry. The rect for the first row should match up.");
|
||||
|
||||
__unused XCTestExpectation *e = [self expectationWithDescription:@"Did a bunch of rounds of updates."];
|
||||
NSInteger totalCount = 20;
|
||||
__block NSInteger count = 0;
|
||||
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
|
||||
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 0.2 * NSEC_PER_SEC, 0.01 * NSEC_PER_SEC);
|
||||
dispatch_source_set_event_handler(timer, ^{
|
||||
[node performBatchUpdates:^{
|
||||
[node reloadSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, NumberOfSections)] withRowAnimation:UITableViewRowAnimationNone];
|
||||
} completion:^(BOOL finished) {
|
||||
if (++count == totalCount) {
|
||||
dispatch_cancel(timer);
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[e fulfill];
|
||||
});
|
||||
}
|
||||
}];
|
||||
});
|
||||
dispatch_resume(timer);
|
||||
[self waitForExpectationsWithTimeout:60 handler:nil];
|
||||
}
|
||||
|
||||
- (void)testThatInvalidUpdateExceptionReasonContainsDataSourceClassName
|
||||
{
|
||||
ASTableNode *node = [[ASTableNode alloc] initWithStyle:UITableViewStyleGrouped];
|
||||
node.bounds = CGRectMake(0, 0, 100, 100);
|
||||
ASTableViewFilledDataSource *ds = [[ASTableViewFilledDataSource alloc] init];
|
||||
node.dataSource = ds;
|
||||
|
||||
// Force node to load initial data.
|
||||
[node.view layoutIfNeeded];
|
||||
|
||||
// Submit an invalid update, ensure exception name matches and that data source is included in the reason.
|
||||
@try {
|
||||
[node deleteSections:[NSIndexSet indexSetWithIndex:1000] withRowAnimation:UITableViewRowAnimationNone];
|
||||
XCTFail(@"Expected validation to fail.");
|
||||
} @catch (NSException *e) {
|
||||
XCTAssertEqual(e.name, ASCollectionInvalidUpdateException);
|
||||
XCTAssert([e.reason containsString:NSStringFromClass([ds class])], @"Expected validation reason to contain the data source class name. Got:\n%@", e.reason);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testAutomaticallyAdjustingContentOffset
|
||||
{
|
||||
ASTableNode *node = [[ASTableNode alloc] initWithStyle:UITableViewStylePlain];
|
||||
node.automaticallyAdjustsContentOffset = YES;
|
||||
node.bounds = CGRectMake(0, 0, 100, 100);
|
||||
ASTableViewFilledDataSource *ds = [[ASTableViewFilledDataSource alloc] init];
|
||||
node.dataSource = ds;
|
||||
|
||||
[node.view layoutIfNeeded];
|
||||
[node waitUntilAllUpdatesAreProcessed];
|
||||
CGFloat rowHeight = [node.view rectForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]].size.height;
|
||||
// Scroll to row (0,1) + 10pt
|
||||
node.contentOffset = CGPointMake(0, rowHeight + 10);
|
||||
|
||||
[node performBatchAnimated:NO updates:^{
|
||||
// Delete row 0 from all sections.
|
||||
// This is silly but it's a consequence of how ASTableViewFilledDataSource is built.
|
||||
ds.rowsPerSection -= 1;
|
||||
for (NSInteger i = 0; i < NumberOfSections; i++) {
|
||||
[node deleteRowsAtIndexPaths:@[ [NSIndexPath indexPathForItem:0 inSection:i]] withRowAnimation:UITableViewRowAnimationAutomatic];
|
||||
}
|
||||
} completion:nil];
|
||||
[node waitUntilAllUpdatesAreProcessed];
|
||||
|
||||
// Now that row (0,0) is deleted, we should have slid up to be at just 10
|
||||
// i.e. we should have subtracted the deleted row height from our content offset.
|
||||
XCTAssertEqual(node.contentOffset.y, 10);
|
||||
}
|
||||
|
||||
- (void)testTableViewReloadDoesReloadIfEditableTextNodeIsFirstResponder
|
||||
{
|
||||
ASEditableTextNode *editableTextNode = [[ASEditableTextNode alloc] init];
|
||||
|
||||
UIWindow *window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 375, 667)];
|
||||
ASTableNode *node = [[ASTableNode alloc] initWithStyle:UITableViewStyleGrouped];
|
||||
node.frame = window.bounds;
|
||||
[window addSubnode:node];
|
||||
|
||||
ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new];
|
||||
dataSource.rowsPerSection = 1;
|
||||
dataSource.numberOfSections = 1;
|
||||
// Currently this test requires that the text in the cell node fills the
|
||||
// visible width, so we use the long description for the index path.
|
||||
dataSource.nodeBlockForItem = ^(NSIndexPath *indexPath) {
|
||||
return (ASCellNodeBlock)^{
|
||||
ASCellNode *cellNode = [[ASCellNode alloc] init];
|
||||
cellNode.automaticallyManagesSubnodes = YES;
|
||||
cellNode.layoutSpecBlock = ^ASLayoutSpec * _Nonnull(__kindof ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) {
|
||||
return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(10, 10, 10, 10) child:editableTextNode];
|
||||
};
|
||||
return cellNode;
|
||||
};
|
||||
};
|
||||
node.delegate = dataSource;
|
||||
node.dataSource = dataSource;
|
||||
|
||||
// Reload the data for the initial load
|
||||
[node reloadData];
|
||||
[node waitUntilAllUpdatesAreProcessed];
|
||||
[node setNeedsLayout];
|
||||
[node layoutIfNeeded];
|
||||
|
||||
// Set the textView as first responder
|
||||
[editableTextNode.textView becomeFirstResponder];
|
||||
|
||||
// Change data source count and try to reload a second time
|
||||
dataSource.rowsPerSection = 2;
|
||||
[node reloadData];
|
||||
[node waitUntilAllUpdatesAreProcessed];
|
||||
|
||||
// Check that numberOfRows in section 0 is 2
|
||||
XCTAssertEqual([node numberOfRowsInSection:0], 2);
|
||||
XCTAssertEqual([node.view numberOfRowsInSection:0], 2);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation UITableView (Testing)
|
||||
|
||||
+ (void)as_recordEditingCallsIntoArray:(NSMutableArray<NSString *> *)selectors
|
||||
{
|
||||
[UITableView swizzleInstanceMethod:@selector(reloadData) withReplacement:JGMethodReplacementProviderBlock {
|
||||
return JGMethodReplacement(void, UITableView *) {
|
||||
JGOriginalImplementation(void);
|
||||
[selectors addObject:NSStringFromSelector(_cmd)];
|
||||
};
|
||||
}];
|
||||
[UITableView swizzleInstanceMethod:@selector(beginUpdates) withReplacement:JGMethodReplacementProviderBlock {
|
||||
return JGMethodReplacement(void, UITableView *) {
|
||||
JGOriginalImplementation(void);
|
||||
[selectors addObject:NSStringFromSelector(_cmd)];
|
||||
};
|
||||
}];
|
||||
[UITableView swizzleInstanceMethod:@selector(endUpdates) withReplacement:JGMethodReplacementProviderBlock {
|
||||
return JGMethodReplacement(void, UITableView *) {
|
||||
JGOriginalImplementation(void);
|
||||
[selectors addObject:NSStringFromSelector(_cmd)];
|
||||
};
|
||||
}];
|
||||
[UITableView swizzleInstanceMethod:@selector(insertRowsAtIndexPaths:withRowAnimation:) withReplacement:JGMethodReplacementProviderBlock {
|
||||
return JGMethodReplacement(void, UITableView *, NSArray *indexPaths, UITableViewRowAnimation anim) {
|
||||
JGOriginalImplementation(void, indexPaths, anim);
|
||||
[selectors addObject:NSStringFromSelector(_cmd)];
|
||||
};
|
||||
}];
|
||||
[UITableView swizzleInstanceMethod:@selector(deleteRowsAtIndexPaths:withRowAnimation:) withReplacement:JGMethodReplacementProviderBlock {
|
||||
return JGMethodReplacement(void, UITableView *, NSArray *indexPaths, UITableViewRowAnimation anim) {
|
||||
JGOriginalImplementation(void, indexPaths, anim);
|
||||
[selectors addObject:NSStringFromSelector(_cmd)];
|
||||
};
|
||||
}];
|
||||
[UITableView swizzleInstanceMethod:@selector(insertSections:withRowAnimation:) withReplacement:JGMethodReplacementProviderBlock {
|
||||
return JGMethodReplacement(void, UITableView *, NSIndexSet *indexes, UITableViewRowAnimation anim) {
|
||||
JGOriginalImplementation(void, indexes, anim);
|
||||
[selectors addObject:NSStringFromSelector(_cmd)];
|
||||
};
|
||||
}];
|
||||
[UITableView swizzleInstanceMethod:@selector(deleteSections:withRowAnimation:) withReplacement:JGMethodReplacementProviderBlock {
|
||||
return JGMethodReplacement(void, UITableView *, NSIndexSet *indexes, UITableViewRowAnimation anim) {
|
||||
JGOriginalImplementation(void, indexes, anim);
|
||||
[selectors addObject:NSStringFromSelector(_cmd)];
|
||||
};
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
@ -1,154 +0,0 @@
|
||||
//
|
||||
// ASTableViewThrashTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
#import <AsyncDisplayKit/ASTableViewInternal.h>
|
||||
#import <AsyncDisplayKit/ASTableView+Undeprecated.h>
|
||||
#import <stdatomic.h>
|
||||
|
||||
#import "ASThrashUtility.h"
|
||||
|
||||
@interface ASTableViewThrashTests: XCTestCase
|
||||
@end
|
||||
|
||||
@implementation ASTableViewThrashTests
|
||||
{
|
||||
// The current update, which will be logged in case of a failure.
|
||||
ASThrashUpdate *_update;
|
||||
BOOL _failed;
|
||||
}
|
||||
|
||||
#pragma mark Overrides
|
||||
|
||||
- (void)tearDown
|
||||
{
|
||||
if (_failed && _update != nil) {
|
||||
NSLog(@"Failed update %@: %@", _update, _update.logFriendlyBase64Representation);
|
||||
}
|
||||
_failed = NO;
|
||||
_update = nil;
|
||||
}
|
||||
|
||||
// NOTE: Despite the documentation, this is not always called if an exception is caught.
|
||||
- (void)recordFailureWithDescription:(NSString *)description inFile:(NSString *)filePath atLine:(NSUInteger)lineNumber expected:(BOOL)expected
|
||||
{
|
||||
_failed = YES;
|
||||
[super recordFailureWithDescription:description inFile:filePath atLine:lineNumber expected:expected];
|
||||
}
|
||||
|
||||
#pragma mark Test Methods
|
||||
|
||||
// Disabled temporarily due to issue where cell nodes are not marked invisible before deallocation.
|
||||
- (void)testInitialDataRead
|
||||
{
|
||||
ASThrashDataSource *ds = [[ASThrashDataSource alloc] initTableViewDataSourceWithData:[ASThrashTestSection sectionsWithCount:kInitialSectionCount]];
|
||||
[self verifyDataSource:ds];
|
||||
}
|
||||
|
||||
/// Replays the Base64 representation of an ASThrashUpdate from "ASThrashTestRecordedCase" file
|
||||
- (void)testRecordedThrashCase
|
||||
{
|
||||
NSURL *caseURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"ASThrashTestRecordedCase" withExtension:nil subdirectory:@"TestResources"];
|
||||
NSString *base64 = [NSString stringWithContentsOfURL:caseURL encoding:NSUTF8StringEncoding error:NULL];
|
||||
|
||||
_update = [ASThrashUpdate thrashUpdateWithBase64String:base64];
|
||||
if (_update == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
ASThrashDataSource *ds = [[ASThrashDataSource alloc] initTableViewDataSourceWithData:_update.oldData];
|
||||
ds.tableView.test_enableSuperUpdateCallLogging = YES;
|
||||
[self applyUpdate:_update toDataSource:ds];
|
||||
[self verifyDataSource:ds];
|
||||
}
|
||||
|
||||
// Disabled temporarily due to issue where cell nodes are not marked invisible before deallocation.
|
||||
- (void)testThrashingWildly
|
||||
{
|
||||
for (NSInteger i = 0; i < kThrashingIterationCount; i++) {
|
||||
[self setUp];
|
||||
@autoreleasepool {
|
||||
NSArray *sections = [ASThrashTestSection sectionsWithCount:kInitialSectionCount];
|
||||
_update = [[ASThrashUpdate alloc] initWithData:sections];
|
||||
ASThrashDataSource *ds = [[ASThrashDataSource alloc] initTableViewDataSourceWithData:sections];
|
||||
|
||||
[self applyUpdate:_update toDataSource:ds];
|
||||
[self verifyDataSource:ds];
|
||||
[self expectationForPredicate:[ds predicateForDeallocatedHierarchy] evaluatedWithObject:(id)kCFNull handler:nil];
|
||||
}
|
||||
[self waitForExpectationsWithTimeout:3 handler:nil];
|
||||
|
||||
[self tearDown];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark Helpers
|
||||
|
||||
- (void)applyUpdate:(ASThrashUpdate *)update toDataSource:(ASThrashDataSource *)dataSource
|
||||
{
|
||||
TableView *tableView = dataSource.tableView;
|
||||
|
||||
[tableView beginUpdates];
|
||||
dataSource.data = update.data;
|
||||
|
||||
[tableView insertSections:update.insertedSectionIndexes withRowAnimation:UITableViewRowAnimationNone];
|
||||
|
||||
[tableView deleteSections:update.deletedSectionIndexes withRowAnimation:UITableViewRowAnimationNone];
|
||||
|
||||
[tableView reloadSections:update.replacedSectionIndexes withRowAnimation:UITableViewRowAnimationNone];
|
||||
|
||||
[update.insertedItemIndexes enumerateObjectsUsingBlock:^(NSMutableIndexSet * _Nonnull indexes, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
NSArray *indexPaths = [indexes indexPathsInSection:idx];
|
||||
[tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
|
||||
}];
|
||||
|
||||
[update.deletedItemIndexes enumerateObjectsUsingBlock:^(NSMutableIndexSet * _Nonnull indexes, NSUInteger sec, BOOL * _Nonnull stop) {
|
||||
NSArray *indexPaths = [indexes indexPathsInSection:sec];
|
||||
[tableView deleteRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
|
||||
}];
|
||||
|
||||
[update.replacedItemIndexes enumerateObjectsUsingBlock:^(NSMutableIndexSet * _Nonnull indexes, NSUInteger sec, BOOL * _Nonnull stop) {
|
||||
NSArray *indexPaths = [indexes indexPathsInSection:sec];
|
||||
[tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
|
||||
}];
|
||||
@try {
|
||||
[tableView endUpdatesAnimated:NO completion:nil];
|
||||
#if !USE_UIKIT_REFERENCE
|
||||
[tableView waitUntilAllUpdatesAreCommitted];
|
||||
#endif
|
||||
} @catch (NSException *exception) {
|
||||
_failed = YES;
|
||||
@throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)verifyDataSource:(ASThrashDataSource *)ds
|
||||
{
|
||||
TableView *tableView = ds.tableView;
|
||||
NSArray <ASThrashTestSection *> *data = [ds data];
|
||||
XCTAssertEqual(data.count, tableView.numberOfSections);
|
||||
for (NSInteger i = 0; i < tableView.numberOfSections; i++) {
|
||||
XCTAssertEqual([tableView numberOfRowsInSection:i], data[i].items.count);
|
||||
XCTAssertEqual([tableView rectForHeaderInSection:i].size.height, data[i].headerHeight);
|
||||
|
||||
for (NSInteger j = 0; j < [tableView numberOfRowsInSection:i]; j++) {
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:j inSection:i];
|
||||
ASThrashTestItem *item = data[i].items[j];
|
||||
#if USE_UIKIT_REFERENCE
|
||||
XCTAssertEqual([tableView rectForRowAtIndexPath:indexPath].size.height, item.rowHeight);
|
||||
#else
|
||||
ASThrashTestNode *node = (ASThrashTestNode *)[tableView nodeForRowAtIndexPath:indexPath];
|
||||
XCTAssertEqualObjects(node.item, item, @"Wrong node at index path %@", indexPath);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
@ -1,74 +0,0 @@
|
||||
//
|
||||
// ASTextKitCoreTextAdditionsTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <CoreText/CoreText.h>
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASTextKitCoreTextAdditions.h>
|
||||
|
||||
#if AS_ENABLE_TEXTNODE
|
||||
|
||||
BOOL floatsCloseEnough(CGFloat float1, CGFloat float2) {
|
||||
CGFloat epsilon = 0.00001;
|
||||
return (fabs(float1 - float2) < epsilon);
|
||||
}
|
||||
|
||||
@interface ASTextKitCoreTextAdditionsTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASTextKitCoreTextAdditionsTests
|
||||
|
||||
- (void)testAttributeCleansing
|
||||
{
|
||||
UIFont *font = [UIFont systemFontOfSize:12.0];
|
||||
NSMutableAttributedString *testString = [[NSMutableAttributedString alloc] initWithString:@"Test" attributes:@{NSFontAttributeName:font}];
|
||||
CFRange cfRange = CFRangeMake(0, testString.length);
|
||||
CGColorRef blueColor = CGColorRetain([UIColor blueColor].CGColor);
|
||||
CFAttributedStringSetAttribute((CFMutableAttributedStringRef)testString,
|
||||
cfRange,
|
||||
kCTForegroundColorAttributeName,
|
||||
blueColor);
|
||||
UIColor *color = [UIColor colorWithCGColor:blueColor];
|
||||
|
||||
NSAttributedString *actualCleansedString = ASCleanseAttributedStringOfCoreTextAttributes(testString);
|
||||
XCTAssertTrue([[actualCleansedString attribute:NSForegroundColorAttributeName atIndex:0 effectiveRange:NULL] isEqual:color], @"Expected the %@ core text attribute to be cleansed from the string %@\n Should match %@", kCTForegroundColorFromContextAttributeName, actualCleansedString, color);
|
||||
CGColorRelease(blueColor);
|
||||
}
|
||||
|
||||
- (void)testNoAttributeCleansing
|
||||
{
|
||||
NSMutableAttributedString *testString = [[NSMutableAttributedString alloc] initWithString:@"Test" attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:12.0],
|
||||
NSForegroundColorAttributeName : [UIColor blueColor]}];
|
||||
|
||||
NSAttributedString *actualCleansedString = ASCleanseAttributedStringOfCoreTextAttributes(testString);
|
||||
XCTAssertTrue([testString isEqualToAttributedString:actualCleansedString], @"Expected the output string %@ to be the same as the input %@ if there are no core text attributes", actualCleansedString, testString);
|
||||
}
|
||||
|
||||
- (void)testNSParagraphStyleNoCleansing
|
||||
{
|
||||
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
|
||||
paragraphStyle.lineSpacing = 10.0;
|
||||
|
||||
//NSUnderlineStyleAttributeName flags the unsupported CT attribute check
|
||||
NSDictionary *attributes = @{NSParagraphStyleAttributeName:paragraphStyle,
|
||||
NSUnderlineStyleAttributeName:@(NSUnderlineStyleSingle)};
|
||||
|
||||
NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:@"Test" attributes:attributes];
|
||||
NSAttributedString *cleansedString = ASCleanseAttributedStringOfCoreTextAttributes(attributedString);
|
||||
|
||||
NSParagraphStyle *cleansedParagraphStyle = [cleansedString attribute:NSParagraphStyleAttributeName atIndex:0 effectiveRange:NULL];
|
||||
|
||||
XCTAssertTrue(floatsCloseEnough(cleansedParagraphStyle.lineSpacing, paragraphStyle.lineSpacing), @"Expected the output line spacing: %f to be equal to the input line spacing: %f", cleansedParagraphStyle.lineSpacing, paragraphStyle.lineSpacing);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
@ -1,51 +0,0 @@
|
||||
//
|
||||
// ASTextKitFontSizeAdjusterTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASTextKitFontSizeAdjuster.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#if AS_ENABLE_TEXTNODE
|
||||
|
||||
@interface ASFontSizeAdjusterTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASFontSizeAdjusterTests
|
||||
|
||||
- (void)testFontSizeAdjusterAttributes
|
||||
{
|
||||
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
|
||||
paragraphStyle.lineHeightMultiple = 2.0;
|
||||
paragraphStyle.lineSpacing = 2.0;
|
||||
paragraphStyle.paragraphSpacing = 4.0;
|
||||
paragraphStyle.firstLineHeadIndent = 6.0;
|
||||
paragraphStyle.headIndent = 8.0;
|
||||
paragraphStyle.tailIndent = 10.0;
|
||||
paragraphStyle.minimumLineHeight = 12.0;
|
||||
paragraphStyle.maximumLineHeight = 14.0;
|
||||
|
||||
NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:@"Lorem ipsum dolor sit amet"
|
||||
attributes:@{ NSParagraphStyleAttributeName: paragraphStyle }];
|
||||
|
||||
[ASTextKitFontSizeAdjuster adjustFontSizeForAttributeString:string withScaleFactor:0.5];
|
||||
|
||||
NSParagraphStyle *adjustedParagraphStyle = [string attribute:NSParagraphStyleAttributeName atIndex:0 effectiveRange:nil];
|
||||
|
||||
XCTAssertEqual(adjustedParagraphStyle.lineHeightMultiple, 2.0);
|
||||
XCTAssertEqual(adjustedParagraphStyle.lineSpacing, 1.0);
|
||||
XCTAssertEqual(adjustedParagraphStyle.paragraphSpacing, 2.0);
|
||||
XCTAssertEqual(adjustedParagraphStyle.firstLineHeadIndent, 3.0);
|
||||
XCTAssertEqual(adjustedParagraphStyle.headIndent, 4.0);
|
||||
XCTAssertEqual(adjustedParagraphStyle.tailIndent, 5.0);
|
||||
XCTAssertEqual(adjustedParagraphStyle.minimumLineHeight, 6.0);
|
||||
XCTAssertEqual(adjustedParagraphStyle.maximumLineHeight, 7.0);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
@ -1,231 +0,0 @@
|
||||
//
|
||||
// ASTextKitTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdocumentation"
|
||||
#import <FBSnapshotTestCase/FBSnapshotTestController.h>
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
#import <AsyncDisplayKit/ASTextKitAttributes.h>
|
||||
|
||||
#if AS_ENABLE_TEXTNODE
|
||||
|
||||
#import <AsyncDisplayKit/ASTextKitComponents.h>
|
||||
#import <AsyncDisplayKit/ASTextKitEntityAttribute.h>
|
||||
#import <AsyncDisplayKit/ASTextKitRenderer.h>
|
||||
#import <AsyncDisplayKit/ASTextKitRenderer+Positioning.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASInternalHelpers.h>
|
||||
|
||||
@interface ASTextKitTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
static UITextView *UITextViewWithAttributes(const ASTextKitAttributes &attributes,
|
||||
const CGSize constrainedSize,
|
||||
NSDictionary *linkTextAttributes)
|
||||
{
|
||||
UITextView *textView = [[UITextView alloc] initWithFrame:{ .size = constrainedSize }];
|
||||
textView.backgroundColor = [UIColor clearColor];
|
||||
textView.textContainer.lineBreakMode = attributes.lineBreakMode;
|
||||
textView.textContainer.lineFragmentPadding = 0.f;
|
||||
textView.textContainer.maximumNumberOfLines = attributes.maximumNumberOfLines;
|
||||
textView.textContainerInset = UIEdgeInsetsZero;
|
||||
textView.layoutManager.usesFontLeading = NO;
|
||||
textView.attributedText = attributes.attributedString;
|
||||
textView.linkTextAttributes = linkTextAttributes;
|
||||
return textView;
|
||||
}
|
||||
|
||||
static UIImage *UITextViewImageWithAttributes(const ASTextKitAttributes &attributes,
|
||||
const CGSize constrainedSize,
|
||||
NSDictionary *linkTextAttributes)
|
||||
{
|
||||
UITextView *textView = UITextViewWithAttributes(attributes, constrainedSize, linkTextAttributes);
|
||||
UIGraphicsBeginImageContextWithOptions(constrainedSize, NO, 0);
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
|
||||
CGContextSaveGState(context);
|
||||
{
|
||||
[textView.layer renderInContext:context];
|
||||
}
|
||||
CGContextRestoreGState(context);
|
||||
|
||||
UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
static UIImage *ASTextKitImageWithAttributes(const ASTextKitAttributes &attributes, const CGSize constrainedSize)
|
||||
{
|
||||
ASTextKitRenderer *renderer = [[ASTextKitRenderer alloc] initWithTextKitAttributes:attributes
|
||||
constrainedSize:constrainedSize];
|
||||
UIGraphicsBeginImageContextWithOptions(constrainedSize, NO, 0);
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
|
||||
CGContextSaveGState(context);
|
||||
{
|
||||
[renderer drawInContext:context bounds:{.size = constrainedSize}];
|
||||
}
|
||||
CGContextRestoreGState(context);
|
||||
|
||||
UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
// linkTextAttributes are only applied to UITextView
|
||||
static BOOL checkAttributes(const ASTextKitAttributes &attributes, const CGSize constrainedSize, NSDictionary *linkTextAttributes)
|
||||
{
|
||||
FBSnapshotTestController *controller = [[FBSnapshotTestController alloc] init];
|
||||
UIImage *labelImage = UITextViewImageWithAttributes(attributes, constrainedSize, linkTextAttributes);
|
||||
UIImage *textKitImage = ASTextKitImageWithAttributes(attributes, constrainedSize);
|
||||
return [controller compareReferenceImage:labelImage toImage:textKitImage tolerance:0.0 error:nil];
|
||||
}
|
||||
|
||||
@implementation ASTextKitTests
|
||||
|
||||
- (void)testSimpleStrings
|
||||
{
|
||||
ASTextKitAttributes attributes {
|
||||
.attributedString = [[NSAttributedString alloc] initWithString:@"hello" attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:12]}]
|
||||
};
|
||||
XCTAssert(checkAttributes(attributes, { 100, 100 }, nil));
|
||||
}
|
||||
|
||||
- (void)testChangingAPropertyChangesHash
|
||||
{
|
||||
NSAttributedString *as = [[NSAttributedString alloc] initWithString:@"hello" attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:12]}];
|
||||
|
||||
ASTextKitAttributes attrib1 {
|
||||
.attributedString = as,
|
||||
.lineBreakMode = NSLineBreakByClipping,
|
||||
};
|
||||
ASTextKitAttributes attrib2 {
|
||||
.attributedString = as,
|
||||
};
|
||||
|
||||
XCTAssertNotEqual(attrib1.hash(), attrib2.hash(), @"Hashes should differ when NSLineBreakByClipping changes.");
|
||||
}
|
||||
|
||||
- (void)testSameStringHashesSame
|
||||
{
|
||||
ASTextKitAttributes attrib1 {
|
||||
.attributedString = [[NSAttributedString alloc] initWithString:@"hello" attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:12]}],
|
||||
};
|
||||
ASTextKitAttributes attrib2 {
|
||||
.attributedString = [[NSAttributedString alloc] initWithString:@"hello" attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:12]}],
|
||||
};
|
||||
|
||||
XCTAssertEqual(attrib1.hash(), attrib2.hash(), @"Hashes should be the same!");
|
||||
}
|
||||
|
||||
|
||||
- (void)testStringsWithVariableAttributes
|
||||
{
|
||||
NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithString:@"hello" attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:12]}];
|
||||
for (int i = 0; i < attrStr.length; i++) {
|
||||
// Color each character something different
|
||||
CGFloat factor = ((CGFloat)i) / ((CGFloat)attrStr.length);
|
||||
[attrStr addAttribute:NSForegroundColorAttributeName
|
||||
value:[UIColor colorWithRed:factor
|
||||
green:1.0 - factor
|
||||
blue:0.0
|
||||
alpha:1.0]
|
||||
range:NSMakeRange(i, 1)];
|
||||
}
|
||||
ASTextKitAttributes attributes {
|
||||
.attributedString = attrStr
|
||||
};
|
||||
XCTAssert(checkAttributes(attributes, { 100, 100 }, nil));
|
||||
}
|
||||
|
||||
- (void)testLinkInTextUsesForegroundColor
|
||||
{
|
||||
NSDictionary *linkTextAttributes = @{ NSForegroundColorAttributeName : [UIColor redColor],
|
||||
// UITextView adds underline by default and we can't get rid of it
|
||||
// so we have to choose a style and color and match it in the text kit version
|
||||
// for this test
|
||||
NSUnderlineStyleAttributeName : @(NSUnderlineStyleSingle),
|
||||
NSUnderlineColorAttributeName: [UIColor blueColor],
|
||||
};
|
||||
NSDictionary *textAttributes = @{NSFontAttributeName : [UIFont systemFontOfSize:12],
|
||||
};
|
||||
|
||||
NSString *prefixString = @"click ";
|
||||
NSString *linkString = @"this link";
|
||||
NSString *textString = [prefixString stringByAppendingString:linkString];
|
||||
|
||||
NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithString:textString attributes:textAttributes];
|
||||
NSURL *linkURL = [NSURL URLWithString:@"https://github.com/facebook/AsyncDisplayKit/issues/967"];
|
||||
NSRange selectedRange = (NSRange){prefixString.length, linkString.length};
|
||||
|
||||
[attrStr addAttribute:NSLinkAttributeName value:linkURL range:selectedRange];
|
||||
|
||||
for (NSString *attributeName in linkTextAttributes.keyEnumerator) {
|
||||
[attrStr addAttribute:attributeName
|
||||
value:linkTextAttributes[attributeName]
|
||||
range:selectedRange];
|
||||
}
|
||||
|
||||
ASTextKitAttributes textKitattributes {
|
||||
.attributedString = attrStr
|
||||
};
|
||||
|
||||
XCTAssert(checkAttributes(textKitattributes, { 100, 100 }, linkTextAttributes));
|
||||
}
|
||||
|
||||
- (void)testRectsForRangeBeyondTruncationSizeReturnsNonZeroNumberOfRects
|
||||
{
|
||||
NSAttributedString *attributedString =
|
||||
[[NSAttributedString alloc]
|
||||
initWithString:@"90's cray photo booth tote bag bespoke Carles. Plaid wayfarers Odd Future master cleanse tattooed four dollar toast small batch kale chips leggings meh photo booth occupy irony. " attributes:@{ASTextKitEntityAttributeName : [[ASTextKitEntityAttribute alloc] initWithEntity:@"entity"]}];
|
||||
ASTextKitRenderer *renderer =
|
||||
[[ASTextKitRenderer alloc]
|
||||
initWithTextKitAttributes:{
|
||||
.attributedString = attributedString,
|
||||
.maximumNumberOfLines = 1,
|
||||
.truncationAttributedString = [[NSAttributedString alloc] initWithString:@"... Continue Reading"]
|
||||
}
|
||||
constrainedSize:{ 100, 100 }];
|
||||
XCTAssert([renderer rectsForTextRange:NSMakeRange(0, attributedString.length) measureOption:ASTextKitRendererMeasureOptionBlock].count > 0);
|
||||
}
|
||||
|
||||
- (void)testTextKitComponentsCanCalculateSizeInBackground
|
||||
{
|
||||
NSAttributedString *attributedString =
|
||||
[[NSAttributedString alloc]
|
||||
initWithString:@"90's cray photo booth tote bag bespoke Carles. Plaid wayfarers Odd Future master cleanse tattooed four dollar toast small batch kale chips leggings meh photo booth occupy irony. " attributes:@{ASTextKitEntityAttributeName : [[ASTextKitEntityAttribute alloc] initWithEntity:@"entity"]}];
|
||||
ASTextKitComponents *components = [ASTextKitComponents componentsWithAttributedSeedString:attributedString textContainerSize:CGSizeZero];
|
||||
components.textView = [[ASTextKitComponentsTextView alloc] initWithFrame:CGRectZero textContainer:components.textContainer];
|
||||
components.textView.frame = CGRectMake(0, 0, 20, 1000);
|
||||
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"Components deallocated in background"];
|
||||
|
||||
ASPerformBlockOnBackgroundThread(^{
|
||||
// Use an autorelease pool here to ensure temporary components are (and can be) released in background
|
||||
@autoreleasepool {
|
||||
[components sizeForConstrainedWidth:100];
|
||||
[components sizeForConstrainedWidth:50 forMaxNumberOfLines:5];
|
||||
}
|
||||
|
||||
[expectation fulfill];
|
||||
});
|
||||
|
||||
[self waitForExpectationsWithTimeout:1 handler:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
@ -1,165 +0,0 @@
|
||||
//
|
||||
// ASTextKitTruncationTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASTextKitContext.h>
|
||||
|
||||
#if AS_ENABLE_TEXTNODE
|
||||
|
||||
#import <AsyncDisplayKit/ASTextKitTailTruncater.h>
|
||||
|
||||
@interface ASTextKitTruncationTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASTextKitTruncationTests
|
||||
|
||||
- (NSString *)_sentenceString
|
||||
{
|
||||
return @"90's cray photo booth tote bag bespoke Carles. Plaid wayfarers Odd Future master cleanse tattooed four dollar toast small batch kale chips leggings meh photo booth occupy irony.";
|
||||
}
|
||||
|
||||
- (NSAttributedString *)_sentenceAttributedString
|
||||
{
|
||||
return [[NSAttributedString alloc] initWithString:[self _sentenceString] attributes:@{}];
|
||||
}
|
||||
|
||||
- (NSAttributedString *)_simpleTruncationAttributedString
|
||||
{
|
||||
return [[NSAttributedString alloc] initWithString:@"..." attributes:@{}];
|
||||
}
|
||||
|
||||
- (void)testEmptyTruncationStringSameAsStraightTextKitTailTruncation
|
||||
{
|
||||
CGSize constrainedSize = CGSizeMake(100, 50);
|
||||
NSAttributedString *attributedString = [self _sentenceAttributedString];
|
||||
ASTextKitContext *context = [[ASTextKitContext alloc] initWithAttributedString:attributedString
|
||||
lineBreakMode:NSLineBreakByWordWrapping
|
||||
maximumNumberOfLines:0
|
||||
exclusionPaths:nil
|
||||
constrainedSize:constrainedSize];
|
||||
__block NSRange textKitVisibleRange;
|
||||
[context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
|
||||
textKitVisibleRange = [layoutManager characterRangeForGlyphRange:[layoutManager glyphRangeForTextContainer:textContainer]
|
||||
actualGlyphRange:NULL];
|
||||
}];
|
||||
ASTextKitTailTruncater *tailTruncater = [[ASTextKitTailTruncater alloc] initWithContext:context
|
||||
truncationAttributedString:nil
|
||||
avoidTailTruncationSet:nil];
|
||||
[tailTruncater truncate];
|
||||
XCTAssert(NSEqualRanges(textKitVisibleRange, tailTruncater.visibleRanges[0]));
|
||||
XCTAssert(NSEqualRanges(textKitVisibleRange, tailTruncater.firstVisibleRange));
|
||||
}
|
||||
|
||||
- (void)testSimpleTailTruncation
|
||||
{
|
||||
CGSize constrainedSize = CGSizeMake(100, 60);
|
||||
NSAttributedString *attributedString = [self _sentenceAttributedString];
|
||||
ASTextKitContext *context = [[ASTextKitContext alloc] initWithAttributedString:attributedString
|
||||
lineBreakMode:NSLineBreakByWordWrapping
|
||||
maximumNumberOfLines:0
|
||||
exclusionPaths:nil
|
||||
constrainedSize:constrainedSize];
|
||||
ASTextKitTailTruncater *tailTruncater = [[ASTextKitTailTruncater alloc] initWithContext:context
|
||||
truncationAttributedString:[self _simpleTruncationAttributedString]
|
||||
avoidTailTruncationSet:[NSCharacterSet characterSetWithCharactersInString:@""]];
|
||||
[tailTruncater truncate];
|
||||
__block NSString *drawnString;
|
||||
[context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
|
||||
drawnString = textStorage.string;
|
||||
}];
|
||||
NSString *expectedString = @"90's cray photo booth tote bag bespoke Carles. Plaid wayfarers...";
|
||||
XCTAssertEqualObjects(expectedString, drawnString);
|
||||
XCTAssert(NSEqualRanges(NSMakeRange(0, 62), tailTruncater.visibleRanges[0]));
|
||||
XCTAssert(NSEqualRanges(NSMakeRange(0, 62), tailTruncater.firstVisibleRange));
|
||||
}
|
||||
|
||||
- (void)testAvoidedCharTailWordBoundaryTruncation
|
||||
{
|
||||
CGSize constrainedSize = CGSizeMake(100, 50);
|
||||
NSAttributedString *attributedString = [self _sentenceAttributedString];
|
||||
ASTextKitContext *context = [[ASTextKitContext alloc] initWithAttributedString:attributedString
|
||||
lineBreakMode:NSLineBreakByWordWrapping
|
||||
maximumNumberOfLines:0
|
||||
exclusionPaths:nil
|
||||
constrainedSize:constrainedSize];
|
||||
ASTextKitTailTruncater *tailTruncater = [[ASTextKitTailTruncater alloc] initWithContext:context
|
||||
truncationAttributedString:[self _simpleTruncationAttributedString]
|
||||
avoidTailTruncationSet:[NSCharacterSet characterSetWithCharactersInString:@"."]];
|
||||
[tailTruncater truncate];
|
||||
__block NSString *drawnString;
|
||||
[context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
|
||||
drawnString = textStorage.string;
|
||||
}];
|
||||
// This should have removed the additional "." in the string right after Carles.
|
||||
NSString *expectedString = @"90's cray photo booth tote bag bespoke Carles...";
|
||||
XCTAssertEqualObjects(expectedString, drawnString);
|
||||
}
|
||||
|
||||
- (void)testAvoidedCharTailCharBoundaryTruncation
|
||||
{
|
||||
CGSize constrainedSize = CGSizeMake(50, 50);
|
||||
NSAttributedString *attributedString = [self _sentenceAttributedString];
|
||||
ASTextKitContext *context = [[ASTextKitContext alloc] initWithAttributedString:attributedString
|
||||
lineBreakMode:NSLineBreakByCharWrapping
|
||||
maximumNumberOfLines:0
|
||||
exclusionPaths:nil
|
||||
constrainedSize:constrainedSize];
|
||||
ASTextKitTailTruncater *tailTruncater = [[ASTextKitTailTruncater alloc] initWithContext:context
|
||||
truncationAttributedString:[self _simpleTruncationAttributedString]
|
||||
avoidTailTruncationSet:[NSCharacterSet characterSetWithCharactersInString:@"."]];
|
||||
[tailTruncater truncate];
|
||||
__block NSString *drawnString;
|
||||
[context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
|
||||
drawnString = textStorage.string;
|
||||
}];
|
||||
// This should have removed the additional "." in the string right after Carles.
|
||||
NSString *expectedString = @"90's cray photo booth t...";
|
||||
XCTAssertEqualObjects(expectedString, drawnString);
|
||||
}
|
||||
|
||||
- (void)testHandleZeroSizeConstrainedSize
|
||||
{
|
||||
CGSize constrainedSize = CGSizeZero;
|
||||
NSAttributedString *attributedString = [self _sentenceAttributedString];
|
||||
|
||||
ASTextKitContext *context = [[ASTextKitContext alloc] initWithAttributedString:attributedString
|
||||
lineBreakMode:NSLineBreakByWordWrapping
|
||||
maximumNumberOfLines:0
|
||||
exclusionPaths:nil
|
||||
constrainedSize:constrainedSize];
|
||||
ASTextKitTailTruncater *tailTruncater = [[ASTextKitTailTruncater alloc] initWithContext:context
|
||||
truncationAttributedString:[self _simpleTruncationAttributedString]
|
||||
avoidTailTruncationSet:nil];
|
||||
XCTAssertNoThrow([tailTruncater truncate]);
|
||||
XCTAssert(tailTruncater.visibleRanges.size() == 0);
|
||||
NSEqualRanges(NSMakeRange(0, 0), tailTruncater.firstVisibleRange);
|
||||
}
|
||||
|
||||
- (void)testHandleZeroHeightConstrainedSize
|
||||
{
|
||||
CGSize constrainedSize = CGSizeMake(50, 0);
|
||||
NSAttributedString *attributedString = [self _sentenceAttributedString];
|
||||
ASTextKitContext *context = [[ASTextKitContext alloc] initWithAttributedString:attributedString
|
||||
lineBreakMode:NSLineBreakByCharWrapping
|
||||
maximumNumberOfLines:0
|
||||
exclusionPaths:nil
|
||||
constrainedSize:constrainedSize];
|
||||
|
||||
ASTextKitTailTruncater *tailTruncater = [[ASTextKitTailTruncater alloc] initWithContext:context
|
||||
truncationAttributedString:[self _simpleTruncationAttributedString]
|
||||
avoidTailTruncationSet:[NSCharacterSet characterSetWithCharactersInString:@"."]];
|
||||
XCTAssertNoThrow([tailTruncater truncate]);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
@ -1,301 +0,0 @@
|
||||
//
|
||||
// ASTextNode2SnapshotTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
|
||||
#import "ASTestCase.h"
|
||||
#import "ASSnapshotTestCase.h"
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
|
||||
@interface ASTextNode2SnapshotTests : ASSnapshotTestCase
|
||||
|
||||
@end
|
||||
|
||||
@interface LineBreakConfig : NSObject
|
||||
|
||||
@property (nonatomic, assign) NSUInteger numberOfLines;
|
||||
@property (nonatomic, assign) NSLineBreakMode lineBreakMode;
|
||||
|
||||
+ (NSArray<LineBreakConfig *> *)configs;
|
||||
|
||||
- (instancetype)initWithNumberOfLines:(NSUInteger)numberOfLines lineBreakMode:(NSLineBreakMode)lineBreakMode;
|
||||
- (NSString *)breakModeDescription;
|
||||
|
||||
@end
|
||||
|
||||
@implementation LineBreakConfig
|
||||
|
||||
+ (NSArray<LineBreakConfig *> *)configs
|
||||
{
|
||||
static dispatch_once_t init_predicate;
|
||||
static NSArray<LineBreakConfig *> *allConfigs = nil;
|
||||
|
||||
dispatch_once(&init_predicate, ^{
|
||||
NSMutableArray *setup = [NSMutableArray new];
|
||||
for (int i = 0; i <= 3; i++) {
|
||||
for (int j = NSLineBreakByWordWrapping; j <= NSLineBreakByTruncatingMiddle; j++) {
|
||||
if (j == NSLineBreakByClipping) continue;
|
||||
[setup addObject:[[LineBreakConfig alloc] initWithNumberOfLines:i lineBreakMode:(NSLineBreakMode) j]];
|
||||
}
|
||||
|
||||
allConfigs = [NSArray arrayWithArray:setup];
|
||||
}
|
||||
});
|
||||
return allConfigs;
|
||||
}
|
||||
|
||||
- (instancetype)initWithNumberOfLines:(NSUInteger)numberOfLines lineBreakMode:(NSLineBreakMode)lineBreakMode
|
||||
{
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_numberOfLines = numberOfLines;
|
||||
_lineBreakMode = lineBreakMode;
|
||||
|
||||
return self;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSString *)breakModeDescription {
|
||||
NSString *lineBreak = nil;
|
||||
switch (self.lineBreakMode) {
|
||||
case NSLineBreakByTruncatingHead:
|
||||
lineBreak = @"NSLineBreakByTruncatingHead";
|
||||
break;
|
||||
case NSLineBreakByCharWrapping:
|
||||
lineBreak = @"NSLineBreakByCharWrapping";
|
||||
break;
|
||||
case NSLineBreakByClipping:
|
||||
lineBreak = @"NSLineBreakByClipping";
|
||||
break;
|
||||
case NSLineBreakByWordWrapping:
|
||||
lineBreak = @"NSLineBreakByWordWrapping";
|
||||
break;
|
||||
case NSLineBreakByTruncatingTail:
|
||||
lineBreak = @"NSLineBreakByTruncatingTail";
|
||||
break;
|
||||
case NSLineBreakByTruncatingMiddle:
|
||||
lineBreak = @"NSLineBreakByTruncatingMiddle";
|
||||
break;
|
||||
default:
|
||||
lineBreak = @"Unknown?";
|
||||
break;
|
||||
}
|
||||
return lineBreak;
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"numberOfLines: %lu\nlineBreakMode: %@", (unsigned long) self.numberOfLines, [self breakModeDescription]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASTextNode2SnapshotTests
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
[super setUp];
|
||||
|
||||
// This will use ASTextNode2 for snapshot tests.
|
||||
// All tests are duplicated from ASTextNodeSnapshotTests.
|
||||
ASConfiguration *config = [[ASConfiguration alloc] initWithDictionary:nil];
|
||||
#if AS_ENABLE_TEXTNODE
|
||||
config.experimentalFeatures = ASExperimentalTextNode;
|
||||
#endif
|
||||
[ASConfigurationManager test_resetWithConfiguration:config];
|
||||
|
||||
self.recordMode = NO;
|
||||
}
|
||||
|
||||
- (void)tearDown
|
||||
{
|
||||
[super tearDown];
|
||||
ASConfiguration *config = [[ASConfiguration alloc] initWithDictionary:nil];
|
||||
config.experimentalFeatures = kNilOptions;
|
||||
[ASConfigurationManager test_resetWithConfiguration:config];
|
||||
}
|
||||
|
||||
- (void)testTextContainerInset_ASTextNode2
|
||||
{
|
||||
// trivial test case to ensure ASSnapshotTestCase works
|
||||
ASTextNode *textNode = [[ASTextNode alloc] init];
|
||||
textNode.attributedText = [[NSAttributedString alloc] initWithString:@"judar"
|
||||
attributes:@{NSFontAttributeName: [UIFont italicSystemFontOfSize:24]}];
|
||||
textNode.textContainerInset = UIEdgeInsetsMake(0, 2, 0, 2);
|
||||
ASDisplayNodeSizeToFitSizeRange(textNode, ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)));
|
||||
|
||||
ASSnapshotVerifyNode(textNode, nil);
|
||||
}
|
||||
|
||||
- (void)testTextTruncationModes_ASTextNode2
|
||||
{
|
||||
UIView *container = [[UIView alloc] initWithFrame:(CGRect) {CGPointZero, (CGSize) {375.0f, 667.0f}}];
|
||||
|
||||
UILabel *textNodeLabel = [[UILabel alloc] init];
|
||||
UILabel *uiLabelLabel = [[UILabel alloc] init];
|
||||
UILabel *description = [[UILabel alloc] init];
|
||||
textNodeLabel.text = @"ASTextNode2:";
|
||||
textNodeLabel.font = [UIFont boldSystemFontOfSize:16.0];
|
||||
textNodeLabel.textColor = [UIColor colorWithRed:0.0 green:0.7 blue:0.0 alpha:1.0];
|
||||
uiLabelLabel.text = @"UILabel:";
|
||||
uiLabelLabel.font = [UIFont boldSystemFontOfSize:16.0];
|
||||
uiLabelLabel.textColor = [UIColor colorWithRed:0.0 green:0.7 blue:0.0 alpha:1.0];
|
||||
|
||||
description.text = @"<Description>";
|
||||
description.font = [UIFont italicSystemFontOfSize:16.0];
|
||||
description.numberOfLines = 0;
|
||||
|
||||
uiLabelLabel.textColor = [UIColor colorWithRed:0.0 green:0.7 blue:0.0 alpha:1.0];
|
||||
|
||||
UILabel *reference = [[UILabel alloc] init];
|
||||
ASTextNode *textNode = [[ASTextNode alloc] init]; // ASTextNode2
|
||||
|
||||
NSMutableAttributedString *refString = [[NSMutableAttributedString alloc] initWithString:@"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
|
||||
attributes:@{ NSFontAttributeName : [UIFont systemFontOfSize:18.0f] }];
|
||||
NSMutableAttributedString *asString = [refString mutableCopy];
|
||||
|
||||
reference.attributedText = refString;
|
||||
textNode.attributedText = asString;
|
||||
|
||||
CGSize size = (CGSize) {container.bounds.size.width, 120.0};
|
||||
CGPoint origin = (CGPoint) {CGRectGetWidth(container.bounds) / 2 - size.width / 2, CGRectGetHeight(container.bounds) / 2 - size.height / 2}; // center
|
||||
|
||||
textNode.frame = (CGRect) {origin, size};
|
||||
reference.frame = CGRectOffset(textNode.frame, 0, -160.0f);
|
||||
|
||||
textNodeLabel.bounds = (CGRect) {CGPointZero, (CGSize) {container.bounds.size.width, textNodeLabel.font.lineHeight}};
|
||||
origin = (CGPoint) {textNode.frame.origin.x, textNode.frame.origin.y - textNodeLabel.bounds.size.height};
|
||||
textNodeLabel.frame = (CGRect) {origin, textNodeLabel.bounds.size};
|
||||
|
||||
uiLabelLabel.bounds = (CGRect) {CGPointZero, (CGSize) {container.bounds.size.width, uiLabelLabel.font.lineHeight}};
|
||||
origin = (CGPoint) {reference.frame.origin.x, reference.frame.origin.y - uiLabelLabel.bounds.size.height};
|
||||
uiLabelLabel.frame = (CGRect) {origin, uiLabelLabel.bounds.size};
|
||||
|
||||
uiLabelLabel.bounds = (CGRect) {CGPointZero, (CGSize) {container.bounds.size.width, uiLabelLabel.font.lineHeight}};
|
||||
origin = (CGPoint) {textNode.frame.origin.x, textNode.frame.origin.y - uiLabelLabel.bounds.size.height};
|
||||
uiLabelLabel.frame = (CGRect) {origin, uiLabelLabel.bounds.size};
|
||||
|
||||
uiLabelLabel.bounds = (CGRect) {CGPointZero, (CGSize) {container.bounds.size.width, uiLabelLabel.font.lineHeight}};
|
||||
origin = (CGPoint) {reference.frame.origin.x, reference.frame.origin.y - uiLabelLabel.bounds.size.height};
|
||||
uiLabelLabel.frame = (CGRect) {origin, uiLabelLabel.bounds.size};
|
||||
|
||||
description.bounds = textNode.bounds;
|
||||
description.frame = (CGRect) {(CGPoint) {0, container.bounds.size.height * 0.8}, description.bounds.size};
|
||||
|
||||
[container addSubview:reference];
|
||||
[container addSubview:textNode.view];
|
||||
[container addSubview:textNodeLabel];
|
||||
[container addSubview:uiLabelLabel];
|
||||
[container addSubview:description];
|
||||
|
||||
NSArray<LineBreakConfig *> *c = [LineBreakConfig configs];
|
||||
for (LineBreakConfig *config in c) {
|
||||
reference.lineBreakMode = textNode.truncationMode = config.lineBreakMode;
|
||||
reference.numberOfLines = textNode.maximumNumberOfLines = config.numberOfLines;
|
||||
description.text = config.description;
|
||||
[container setNeedsLayout];
|
||||
NSString *identifier = [NSString stringWithFormat:@"%@_%luLines", [config breakModeDescription], (unsigned long)config.numberOfLines];
|
||||
[ASSnapshotTestCase hackilySynchronouslyRecursivelyRenderNode:textNode];
|
||||
ASSnapshotVerifyViewWithTolerance(container, identifier, 0.01);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testTextContainerInsetIsIncludedWithSmallerConstrainedSize_ASTextNode2
|
||||
{
|
||||
UIView *backgroundView = [[UIView alloc] initWithFrame:CGRectZero];
|
||||
backgroundView.layer.as_allowsHighlightDrawing = YES;
|
||||
|
||||
ASTextNode *textNode = [[ASTextNode alloc] init];
|
||||
textNode.attributedText = [[NSAttributedString alloc] initWithString:@"judar judar judar judar judar judar"
|
||||
attributes:@{ NSFontAttributeName : [UIFont systemFontOfSize:30] }];
|
||||
|
||||
textNode.textContainerInset = UIEdgeInsetsMake(10, 10, 10, 10);
|
||||
|
||||
ASLayout *layout = [textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(100, 80))];
|
||||
textNode.frame = CGRectMake(50, 50, layout.size.width, layout.size.height);
|
||||
|
||||
[backgroundView addSubview:textNode.view];
|
||||
backgroundView.frame = UIEdgeInsetsInsetRect(textNode.bounds, UIEdgeInsetsMake(-50, -50, -50, -50));
|
||||
|
||||
textNode.highlightRange = NSMakeRange(0, textNode.attributedText.length);
|
||||
|
||||
[ASSnapshotTestCase hackilySynchronouslyRecursivelyRenderNode:textNode];
|
||||
ASSnapshotVerifyLayer(backgroundView.layer, nil);
|
||||
}
|
||||
|
||||
- (void)testTextContainerInsetHighlight_ASTextNode2
|
||||
{
|
||||
UIView *backgroundView = [[UIView alloc] initWithFrame:CGRectZero];
|
||||
backgroundView.layer.as_allowsHighlightDrawing = YES;
|
||||
|
||||
ASTextNode *textNode = [[ASTextNode alloc] init];
|
||||
textNode.attributedText = [[NSAttributedString alloc] initWithString:@"yolo"
|
||||
attributes:@{ NSFontAttributeName : [UIFont systemFontOfSize:30] }];
|
||||
|
||||
textNode.textContainerInset = UIEdgeInsetsMake(5, 10, 10, 5);
|
||||
ASLayout *layout = [textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY))];
|
||||
textNode.frame = CGRectMake(50, 50, layout.size.width, layout.size.height);
|
||||
|
||||
[backgroundView addSubview:textNode.view];
|
||||
backgroundView.frame = UIEdgeInsetsInsetRect(textNode.bounds, UIEdgeInsetsMake(-50, -50, -50, -50));
|
||||
|
||||
textNode.highlightRange = NSMakeRange(0, textNode.attributedText.length);
|
||||
|
||||
[ASSnapshotTestCase hackilySynchronouslyRecursivelyRenderNode:textNode];
|
||||
ASSnapshotVerifyView(backgroundView, nil);
|
||||
}
|
||||
|
||||
// This test is disabled because the fast-path is disabled.
|
||||
- (void)DISABLED_testThatFastPathTruncationWorks_ASTextNode2
|
||||
{
|
||||
ASTextNode *textNode = [[ASTextNode alloc] init];
|
||||
textNode.attributedText = [[NSAttributedString alloc] initWithString:@"Quality is Important" attributes:@{ NSForegroundColorAttributeName: [UIColor blueColor], NSFontAttributeName: [UIFont italicSystemFontOfSize:24] }];
|
||||
[textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(100, 50))];
|
||||
ASSnapshotVerifyNode(textNode, nil);
|
||||
}
|
||||
|
||||
- (void)testThatSlowPathTruncationWorks_ASTextNode2
|
||||
{
|
||||
ASTextNode *textNode = [[ASTextNode alloc] init];
|
||||
textNode.attributedText = [[NSAttributedString alloc] initWithString:@"Quality is Important" attributes:@{ NSForegroundColorAttributeName: [UIColor blueColor], NSFontAttributeName: [UIFont italicSystemFontOfSize:24] }];
|
||||
// Set exclusion paths to trigger slow path
|
||||
textNode.exclusionPaths = @[ [UIBezierPath bezierPath] ];
|
||||
ASDisplayNodeSizeToFitSizeRange(textNode, ASSizeRangeMake(CGSizeZero, CGSizeMake(100, 50)));
|
||||
ASSnapshotVerifyNode(textNode, nil);
|
||||
}
|
||||
|
||||
- (void)testShadowing_ASTextNode2
|
||||
{
|
||||
ASTextNode *textNode = [[ASTextNode alloc] init];
|
||||
textNode.attributedText = [[NSAttributedString alloc] initWithString:@"Quality is Important"];
|
||||
textNode.shadowColor = [UIColor blackColor].CGColor;
|
||||
textNode.shadowOpacity = 0.3;
|
||||
textNode.shadowRadius = 3;
|
||||
textNode.shadowOffset = CGSizeMake(0, 1);
|
||||
ASDisplayNodeSizeToFitSizeRange(textNode, ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY)));
|
||||
ASSnapshotVerifyNode(textNode, nil);
|
||||
}
|
||||
|
||||
/**
|
||||
* https://github.com/TextureGroup/Texture/issues/822
|
||||
*/
|
||||
- (void)DISABLED_testThatTruncationTokenAttributesPrecedeThoseInheritedFromTextWhenTruncateTailMode_ASTextNode2
|
||||
{
|
||||
ASTextNode *textNode = [[ASTextNode alloc] init];
|
||||
textNode.style.maxSize = CGSizeMake(20, 80);
|
||||
NSMutableAttributedString *mas = [[NSMutableAttributedString alloc] initWithString:@"Quality is an important "];
|
||||
[mas appendAttributedString:[[NSAttributedString alloc] initWithString:@"thing" attributes:@{ NSBackgroundColorAttributeName : UIColor.yellowColor}]];
|
||||
textNode.attributedText = mas;
|
||||
textNode.truncationMode = NSLineBreakByTruncatingTail;
|
||||
|
||||
textNode.truncationAttributedText = [[NSAttributedString alloc] initWithString:@"\u2026" attributes:@{ NSBackgroundColorAttributeName: UIColor.greenColor }];
|
||||
ASDisplayNodeSizeToFitSizeRange(textNode, ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY)));
|
||||
ASSnapshotVerifyNode(textNode, nil);
|
||||
}
|
||||
|
||||
@end
|
@ -1,94 +0,0 @@
|
||||
//
|
||||
// ASTextNode2Tests.mm
|
||||
// TextureTests
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <CoreText/CoreText.h>
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
|
||||
#import <AsyncDisplayKit/ASTextNode2.h>
|
||||
#import <AsyncDisplayKit/ASTextNode+Beta.h>
|
||||
|
||||
#import "ASTestCase.h"
|
||||
|
||||
@interface ASTextNode2Tests : XCTestCase
|
||||
|
||||
@property(nonatomic) ASTextNode2 *textNode;
|
||||
@property(nonatomic, copy) NSAttributedString *attributedText;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASTextNode2Tests
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
[super setUp];
|
||||
_textNode = [[ASTextNode2 alloc] init];
|
||||
|
||||
UIFontDescriptor *desc = [UIFontDescriptor fontDescriptorWithName:@"Didot" size:18];
|
||||
NSArray *arr = @[ @{
|
||||
UIFontFeatureTypeIdentifierKey : @(kLetterCaseType),
|
||||
UIFontFeatureSelectorIdentifierKey : @(kSmallCapsSelector)
|
||||
} ];
|
||||
desc = [desc fontDescriptorByAddingAttributes:@{UIFontDescriptorFeatureSettingsAttribute : arr}];
|
||||
UIFont *f = [UIFont fontWithDescriptor:desc size:0];
|
||||
NSDictionary *d = @{NSFontAttributeName : f};
|
||||
NSMutableAttributedString *mas = [[NSMutableAttributedString alloc]
|
||||
initWithString:
|
||||
@"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor "
|
||||
@"incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud "
|
||||
@"exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure "
|
||||
@"dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. "
|
||||
@"Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt "
|
||||
@"mollit anim id est laborum."
|
||||
attributes:d];
|
||||
NSMutableParagraphStyle *para = [NSMutableParagraphStyle new];
|
||||
para.alignment = NSTextAlignmentCenter;
|
||||
para.lineSpacing = 1.0;
|
||||
[mas addAttribute:NSParagraphStyleAttributeName value:para range:NSMakeRange(0, mas.length - 1)];
|
||||
|
||||
// Vary the linespacing on the last line
|
||||
NSMutableParagraphStyle *lastLinePara = [NSMutableParagraphStyle new];
|
||||
lastLinePara.alignment = para.alignment;
|
||||
lastLinePara.lineSpacing = 5.0;
|
||||
[mas addAttribute:NSParagraphStyleAttributeName
|
||||
value:lastLinePara
|
||||
range:NSMakeRange(mas.length - 1, 1)];
|
||||
|
||||
_attributedText = mas;
|
||||
_textNode.attributedText = _attributedText;
|
||||
}
|
||||
|
||||
- (void)testTruncation
|
||||
{
|
||||
XCTAssertTrue([(ASTextNode *)_textNode shouldTruncateForConstrainedSize:ASSizeRangeMake(CGSizeMake(100, 100))], @"Text Node should truncate");
|
||||
|
||||
_textNode.frame = CGRectMake(0, 0, 100, 100);
|
||||
XCTAssertTrue(_textNode.isTruncated, @"Text Node should be truncated");
|
||||
}
|
||||
|
||||
- (void)testAccessibility
|
||||
{
|
||||
XCTAssertTrue(_textNode.isAccessibilityElement, @"Should be an accessibility element");
|
||||
XCTAssertTrue(_textNode.accessibilityTraits == UIAccessibilityTraitStaticText,
|
||||
@"Should have static text accessibility trait, instead has %llu",
|
||||
_textNode.accessibilityTraits);
|
||||
XCTAssertTrue(_textNode.defaultAccessibilityTraits == UIAccessibilityTraitStaticText,
|
||||
@"Default accessibility traits should return static text accessibility trait, "
|
||||
@"instead returns %llu",
|
||||
_textNode.defaultAccessibilityTraits);
|
||||
|
||||
XCTAssertTrue([_textNode.accessibilityLabel isEqualToString:_attributedText.string],
|
||||
@"Accessibility label is incorrectly set to \n%@\n when it should be \n%@\n",
|
||||
_textNode.accessibilityLabel, _attributedText.string);
|
||||
XCTAssertTrue([_textNode.defaultAccessibilityLabel isEqualToString:_attributedText.string],
|
||||
@"Default accessibility label incorrectly returns \n%@\n when it should be \n%@\n",
|
||||
_textNode.defaultAccessibilityLabel, _attributedText.string);
|
||||
}
|
||||
|
||||
@end
|
@ -1,234 +0,0 @@
|
||||
//
|
||||
// ASTextNodePerformanceTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import "ASPerformanceTestContext.h"
|
||||
#import <AsyncDisplayKit/ASTextNode.h>
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
#import <AsyncDisplayKit/ASInternalHelpers.h>
|
||||
#import <AsyncDisplayKit/CoreGraphics+ASConvenience.h>
|
||||
|
||||
#import "ASXCTExtensions.h"
|
||||
|
||||
/**
|
||||
* NOTE: This test case is not run during the "test" action. You have to run it manually (click the little diamond.)
|
||||
*/
|
||||
|
||||
@interface ASTextNodePerformanceTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASTextNodePerformanceTests
|
||||
|
||||
#pragma mark Performance Tests
|
||||
|
||||
static NSString *const kTestCaseUIKit = @"UIKit";
|
||||
static NSString *const kTestCaseASDK = @"ASDK";
|
||||
static NSString *const kTestCaseUIKitPrivateCaching = @"UIKitPrivateCaching";
|
||||
static NSString *const kTestCaseUIKitWithNoContext = @"UIKitNoContext";
|
||||
static NSString *const kTestCaseUIKitWithFreshContext = @"UIKitFreshContext";
|
||||
static NSString *const kTestCaseUIKitWithReusedContext = @"UIKitReusedContext";
|
||||
|
||||
+ (NSArray<NSAttributedString *> *)realisticDataSet
|
||||
{
|
||||
static NSArray *array;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
NSString *file = [[NSBundle bundleForClass:self] pathForResource:@"AttributedStringsFixture0" ofType:@"plist" inDirectory:@"TestResources"];
|
||||
if (file != nil) {
|
||||
array = [NSKeyedUnarchiver unarchiveObjectWithFile:file];
|
||||
}
|
||||
NSAssert([array isKindOfClass:[NSArray class]], nil);
|
||||
NSSet *unique = [NSSet setWithArray:array];
|
||||
NSLog(@"Loaded realistic text data set with %d attributed strings, %d unique.", (int)array.count, (int)unique.count);
|
||||
});
|
||||
return array;
|
||||
}
|
||||
|
||||
- (void)testPerformance_RealisticData
|
||||
{
|
||||
NSArray *data = [self.class realisticDataSet];
|
||||
|
||||
CGSize maxSize = CGSizeMake(355, CGFLOAT_MAX);
|
||||
CGSize __block uiKitSize, __block asdkSize;
|
||||
|
||||
ASPerformanceTestContext *ctx = [[ASPerformanceTestContext alloc] init];
|
||||
[ctx addCaseWithName:kTestCaseUIKit block:^(NSUInteger i, dispatch_block_t _Nonnull startMeasuring, dispatch_block_t _Nonnull stopMeasuring) {
|
||||
NSAttributedString *text = data[i % data.count];
|
||||
startMeasuring();
|
||||
uiKitSize = [text boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingTruncatesLastVisibleLine context:nil].size;
|
||||
stopMeasuring();
|
||||
}];
|
||||
uiKitSize.width = ASCeilPixelValue(uiKitSize.width);
|
||||
uiKitSize.height = ASCeilPixelValue(uiKitSize.height);
|
||||
ctx.results[kTestCaseUIKit].userInfo[@"size"] = NSStringFromCGSize(uiKitSize);
|
||||
|
||||
[ctx addCaseWithName:kTestCaseASDK block:^(NSUInteger i, dispatch_block_t _Nonnull startMeasuring, dispatch_block_t _Nonnull stopMeasuring) {
|
||||
ASTextNode *node = [[ASTextNode alloc] init];
|
||||
NSAttributedString *text = data[i % data.count];
|
||||
startMeasuring();
|
||||
node.attributedText = text;
|
||||
asdkSize = [node layoutThatFits:ASSizeRangeMake(CGSizeZero, maxSize)].size;
|
||||
stopMeasuring();
|
||||
}];
|
||||
ctx.results[kTestCaseASDK].userInfo[@"size"] = NSStringFromCGSize(asdkSize);
|
||||
|
||||
ASXCTAssertEqualSizes(uiKitSize, asdkSize);
|
||||
ASXCTAssertRelativePerformanceInRange(ctx, kTestCaseASDK, 0.2, 0.5);
|
||||
}
|
||||
|
||||
- (void)testPerformance_TwoParagraphLatinNoTruncation
|
||||
{
|
||||
NSAttributedString *text = [ASTextNodePerformanceTests twoParagraphLatinText];
|
||||
|
||||
CGSize maxSize = CGSizeMake(355, CGFLOAT_MAX);
|
||||
CGSize __block uiKitSize, __block asdkSize;
|
||||
|
||||
ASPerformanceTestContext *ctx = [[ASPerformanceTestContext alloc] init];
|
||||
[ctx addCaseWithName:kTestCaseUIKit block:^(NSUInteger i, dispatch_block_t _Nonnull startMeasuring, dispatch_block_t _Nonnull stopMeasuring) {
|
||||
startMeasuring();
|
||||
uiKitSize = [text boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingTruncatesLastVisibleLine context:nil].size;
|
||||
stopMeasuring();
|
||||
}];
|
||||
uiKitSize.width = ASCeilPixelValue(uiKitSize.width);
|
||||
uiKitSize.height = ASCeilPixelValue(uiKitSize.height);
|
||||
ctx.results[kTestCaseUIKit].userInfo[@"size"] = NSStringFromCGSize(uiKitSize);
|
||||
|
||||
[ctx addCaseWithName:kTestCaseASDK block:^(NSUInteger i, dispatch_block_t _Nonnull startMeasuring, dispatch_block_t _Nonnull stopMeasuring) {
|
||||
ASTextNode *node = [[ASTextNode alloc] init];
|
||||
startMeasuring();
|
||||
node.attributedText = text;
|
||||
asdkSize = [node layoutThatFits:ASSizeRangeMake(CGSizeZero, maxSize)].size;
|
||||
stopMeasuring();
|
||||
}];
|
||||
ctx.results[kTestCaseASDK].userInfo[@"size"] = NSStringFromCGSize(asdkSize);
|
||||
|
||||
ASXCTAssertEqualSizes(uiKitSize, asdkSize);
|
||||
ASXCTAssertRelativePerformanceInRange(ctx, kTestCaseASDK, 0.5, 0.9);
|
||||
}
|
||||
|
||||
- (void)testPerformance_OneParagraphLatinWithTruncation
|
||||
{
|
||||
NSAttributedString *text = [ASTextNodePerformanceTests oneParagraphLatinText];
|
||||
|
||||
CGSize maxSize = CGSizeMake(355, 150);
|
||||
CGSize __block uiKitSize, __block asdkSize;
|
||||
|
||||
ASPerformanceTestContext *testCtx = [[ASPerformanceTestContext alloc] init];
|
||||
[testCtx addCaseWithName:kTestCaseUIKit block:^(NSUInteger i, dispatch_block_t _Nonnull startMeasuring, dispatch_block_t _Nonnull stopMeasuring) {
|
||||
startMeasuring();
|
||||
uiKitSize = [text boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingTruncatesLastVisibleLine context:nil].size;
|
||||
stopMeasuring();
|
||||
}];
|
||||
uiKitSize.width = ASCeilPixelValue(uiKitSize.width);
|
||||
uiKitSize.height = ASCeilPixelValue(uiKitSize.height);
|
||||
testCtx.results[kTestCaseUIKit].userInfo[@"size"] = NSStringFromCGSize(uiKitSize);
|
||||
|
||||
[testCtx addCaseWithName:kTestCaseASDK block:^(NSUInteger i, dispatch_block_t _Nonnull startMeasuring, dispatch_block_t _Nonnull stopMeasuring) {
|
||||
ASTextNode *node = [[ASTextNode alloc] init];
|
||||
startMeasuring();
|
||||
node.attributedText = text;
|
||||
asdkSize = [node layoutThatFits:ASSizeRangeMake(CGSizeZero, maxSize)].size;
|
||||
stopMeasuring();
|
||||
}];
|
||||
testCtx.results[kTestCaseASDK].userInfo[@"size"] = NSStringFromCGSize(asdkSize);
|
||||
|
||||
XCTAssert(CGSizeEqualToSizeWithIn(uiKitSize, asdkSize, 5));
|
||||
ASXCTAssertRelativePerformanceInRange(testCtx, kTestCaseASDK, 0.1, 0.3);
|
||||
}
|
||||
|
||||
- (void)testThatNotUsingAStringDrawingContextHasSimilarPerformanceToHavingOne
|
||||
{
|
||||
ASPerformanceTestContext *ctx = [[ASPerformanceTestContext alloc] init];
|
||||
|
||||
NSAttributedString *text = [ASTextNodePerformanceTests oneParagraphLatinText];
|
||||
CGSize maxSize = CGSizeMake(355, 150);
|
||||
NSStringDrawingOptions options = NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingTruncatesLastVisibleLine;
|
||||
__block CGSize size;
|
||||
// nil context
|
||||
[ctx addCaseWithName:kTestCaseUIKitWithNoContext block:^(NSUInteger i, dispatch_block_t _Nonnull startMeasuring, dispatch_block_t _Nonnull stopMeasuring) {
|
||||
startMeasuring();
|
||||
size = [text boundingRectWithSize:maxSize options:options context:nil].size;
|
||||
stopMeasuring();
|
||||
}];
|
||||
ctx.results[kTestCaseUIKitWithNoContext].userInfo[@"size"] = NSStringFromCGSize(size);
|
||||
|
||||
// Fresh context
|
||||
[ctx addCaseWithName:kTestCaseUIKitWithFreshContext block:^(NSUInteger i, dispatch_block_t _Nonnull startMeasuring, dispatch_block_t _Nonnull stopMeasuring) {
|
||||
NSStringDrawingContext *stringDrawingCtx = [[NSStringDrawingContext alloc] init];
|
||||
startMeasuring();
|
||||
size = [text boundingRectWithSize:maxSize options:options context:stringDrawingCtx].size;
|
||||
stopMeasuring();
|
||||
}];
|
||||
ctx.results[kTestCaseUIKitWithFreshContext].userInfo[@"size"] = NSStringFromCGSize(size);
|
||||
|
||||
// Reused context
|
||||
NSStringDrawingContext *stringDrawingCtx = [[NSStringDrawingContext alloc] init];
|
||||
[ctx addCaseWithName:kTestCaseUIKitWithReusedContext block:^(NSUInteger i, dispatch_block_t _Nonnull startMeasuring, dispatch_block_t _Nonnull stopMeasuring) {
|
||||
startMeasuring();
|
||||
size = [text boundingRectWithSize:maxSize options:options context:stringDrawingCtx].size;
|
||||
stopMeasuring();
|
||||
}];
|
||||
ctx.results[kTestCaseUIKitWithReusedContext].userInfo[@"size"] = NSStringFromCGSize(size);
|
||||
|
||||
XCTAssertTrue([ctx areAllUserInfosEqual]);
|
||||
ASXCTAssertRelativePerformanceInRange(ctx, kTestCaseUIKitWithReusedContext, 0.8, 1.2);
|
||||
ASXCTAssertRelativePerformanceInRange(ctx, kTestCaseUIKitWithFreshContext, 0.8, 1.2);
|
||||
}
|
||||
|
||||
- (void)testThatUIKitPrivateLayoutCachingIsAwesome
|
||||
{
|
||||
NSAttributedString *text = [ASTextNodePerformanceTests oneParagraphLatinText];
|
||||
ASPerformanceTestContext *ctx = [[ASPerformanceTestContext alloc] init];
|
||||
CGSize maxSize = CGSizeMake(355, 150);
|
||||
__block CGSize uncachedSize, cachedSize;
|
||||
NSStringDrawingOptions options = NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingTruncatesLastVisibleLine;
|
||||
|
||||
// No caching, reused ctx
|
||||
NSStringDrawingContext *defaultCtx = [[NSStringDrawingContext alloc] init];
|
||||
XCTAssertFalse([[defaultCtx valueForKey:@"cachesLayout"] boolValue]);
|
||||
[ctx addCaseWithName:kTestCaseUIKit block:^(NSUInteger i, dispatch_block_t _Nonnull startMeasuring, dispatch_block_t _Nonnull stopMeasuring) {
|
||||
startMeasuring();
|
||||
uncachedSize = [text boundingRectWithSize:maxSize options:options context:defaultCtx].size;
|
||||
stopMeasuring();
|
||||
}];
|
||||
XCTAssertFalse([[defaultCtx valueForKey:@"cachesLayout"] boolValue]);
|
||||
ctx.results[kTestCaseUIKit].userInfo[@"size"] = NSStringFromCGSize(uncachedSize);
|
||||
|
||||
// Caching
|
||||
NSStringDrawingContext *cachingCtx = [[NSStringDrawingContext alloc] init];
|
||||
[cachingCtx setValue:@YES forKey:@"cachesLayout"];
|
||||
[ctx addCaseWithName:kTestCaseUIKitPrivateCaching block:^(NSUInteger i, dispatch_block_t _Nonnull startMeasuring, dispatch_block_t _Nonnull stopMeasuring) {
|
||||
startMeasuring();
|
||||
cachedSize = [text boundingRectWithSize:maxSize options:options context:cachingCtx].size;
|
||||
stopMeasuring();
|
||||
}];
|
||||
ctx.results[kTestCaseUIKitPrivateCaching].userInfo[@"size"] = NSStringFromCGSize(cachedSize);
|
||||
|
||||
XCTAssertTrue([ctx areAllUserInfosEqual]);
|
||||
ASXCTAssertRelativePerformanceInRange(ctx, kTestCaseUIKitPrivateCaching, 1.2, FLT_MAX);
|
||||
}
|
||||
|
||||
#pragma mark Fixture Data
|
||||
|
||||
+ (NSMutableAttributedString *)oneParagraphLatinText
|
||||
{
|
||||
NSDictionary *attributes = @{
|
||||
NSFontAttributeName: [UIFont systemFontOfSize:14]
|
||||
};
|
||||
return [[NSMutableAttributedString alloc] initWithString:@"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam gravida, metus non tincidunt tincidunt, arcu quam vulputate magna, nec semper libero mi in lorem. Quisque turpis erat, congue sit amet eros at, gravida gravida lacus. Maecenas maximus lectus in efficitur pulvinar. Nam elementum massa eget luctus condimentum. Curabitur egestas mauris urna. Fusce lacus ante, laoreet vitae leo quis, mattis aliquam est. Donec bibendum augue at elit lacinia lobortis. Cras imperdiet ac justo eget sollicitudin. Pellentesque malesuada nec tellus vitae dictum. Proin vestibulum tempus odio in condimentum. Interdum et malesuada fames ac ante ipsum primis in faucibus. Duis vel turpis at velit dignissim rutrum. Nunc lorem felis, molestie eget ornare id, luctus at nunc. Maecenas suscipit nisi sit amet nulla cursus, id eleifend odio laoreet." attributes:attributes];
|
||||
}
|
||||
|
||||
+ (NSMutableAttributedString *)twoParagraphLatinText
|
||||
{
|
||||
NSDictionary *attributes = @{
|
||||
NSFontAttributeName: [UIFont systemFontOfSize:14]
|
||||
};
|
||||
return [[NSMutableAttributedString alloc] initWithString:@"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam gravida, metus non tincidunt tincidunt, arcu quam vulputate magna, nec semper libero mi in lorem. Quisque turpis erat, congue sit amet eros at, gravida gravida lacus. Maecenas maximus lectus in efficitur pulvinar. Nam elementum massa eget luctus condimentum. Curabitur egestas mauris urna. Fusce lacus ante, laoreet vitae leo quis, mattis aliquam est. Donec bibendum augue at elit lacinia lobortis. Cras imperdiet ac justo eget sollicitudin. Pellentesque malesuada nec tellus vitae dictum. Proin vestibulum tempus odio in condimentum. Interdum et malesuada fames ac ante ipsum primis in faucibus. Duis vel turpis at velit dignissim rutrum. Nunc lorem felis, molestie eget ornare id, luctus at nunc. Maecenas suscipit nisi sit amet nulla cursus, id eleifend odio laoreet.\n\nPellentesque auctor pulvinar velit, venenatis elementum ex tempus eu. Vestibulum iaculis hendrerit tortor quis sagittis. Pellentesque quam sem, varius ac orci nec, tincidunt ultricies mauris. Aliquam est nunc, eleifend et posuere sed, vestibulum eu elit. Pellentesque pharetra bibendum finibus. Aliquam interdum metus ac feugiat congue. Donec suscipit neque quis mauris volutpat, at molestie tortor aliquam. Aenean posuere nulla a ex posuere finibus. Integer tincidunt quam urna, et vulputate enim tempor sit amet. Nullam ut tellus ac arcu fringilla cursus." attributes:attributes];
|
||||
}
|
||||
@end
|
@ -1,147 +0,0 @@
|
||||
//
|
||||
// ASTextNodeSnapshotTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASSnapshotTestCase.h"
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
|
||||
@interface ASTextNodeSnapshotTests : ASSnapshotTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASTextNodeSnapshotTests
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
[super setUp];
|
||||
|
||||
self.recordMode = NO;
|
||||
}
|
||||
|
||||
- (void)testTextContainerInset
|
||||
{
|
||||
// trivial test case to ensure ASSnapshotTestCase works
|
||||
ASTextNode *textNode = [[ASTextNode alloc] init];
|
||||
textNode.attributedText = [[NSAttributedString alloc] initWithString:@"judar"
|
||||
attributes:@{NSFontAttributeName : [UIFont italicSystemFontOfSize:24]}];
|
||||
textNode.textContainerInset = UIEdgeInsetsMake(0, 2, 0, 2);
|
||||
ASDisplayNodeSizeToFitSizeRange(textNode, ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)));
|
||||
|
||||
ASSnapshotVerifyNode(textNode, nil);
|
||||
}
|
||||
|
||||
- (void)testTextContainerInsetIsIncludedWithSmallerConstrainedSize
|
||||
{
|
||||
UIView *backgroundView = [[UIView alloc] initWithFrame:CGRectZero];
|
||||
backgroundView.layer.as_allowsHighlightDrawing = YES;
|
||||
|
||||
ASTextNode *textNode = [[ASTextNode alloc] init];
|
||||
textNode.attributedText = [[NSAttributedString alloc] initWithString:@"judar judar judar judar judar judar"
|
||||
attributes:@{ NSFontAttributeName : [UIFont systemFontOfSize:30] }];
|
||||
|
||||
textNode.textContainerInset = UIEdgeInsetsMake(10, 10, 10, 10);
|
||||
|
||||
ASLayout *layout = [textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(100, 80))];
|
||||
textNode.frame = CGRectMake(50, 50, layout.size.width, layout.size.height);
|
||||
|
||||
[backgroundView addSubview:textNode.view];
|
||||
backgroundView.frame = UIEdgeInsetsInsetRect(textNode.bounds, UIEdgeInsetsMake(-50, -50, -50, -50));
|
||||
|
||||
textNode.highlightRange = NSMakeRange(0, textNode.attributedText.length);
|
||||
|
||||
[ASSnapshotTestCase hackilySynchronouslyRecursivelyRenderNode:textNode];
|
||||
ASSnapshotVerifyLayer(backgroundView.layer, nil);
|
||||
}
|
||||
|
||||
- (void)testTextContainerInsetHighlight
|
||||
{
|
||||
UIView *backgroundView = [[UIView alloc] initWithFrame:CGRectZero];
|
||||
backgroundView.layer.as_allowsHighlightDrawing = YES;
|
||||
|
||||
ASTextNode *textNode = [[ASTextNode alloc] init];
|
||||
textNode.attributedText = [[NSAttributedString alloc] initWithString:@"yolo"
|
||||
attributes:@{ NSFontAttributeName : [UIFont systemFontOfSize:30] }];
|
||||
|
||||
textNode.textContainerInset = UIEdgeInsetsMake(5, 10, 10, 5);
|
||||
ASLayout *layout = [textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY))];
|
||||
textNode.frame = CGRectMake(50, 50, layout.size.width, layout.size.height);
|
||||
|
||||
[backgroundView addSubview:textNode.view];
|
||||
backgroundView.frame = UIEdgeInsetsInsetRect(textNode.bounds, UIEdgeInsetsMake(-50, -50, -50, -50));
|
||||
|
||||
textNode.highlightRange = NSMakeRange(0, textNode.attributedText.length);
|
||||
|
||||
[ASSnapshotTestCase hackilySynchronouslyRecursivelyRenderNode:textNode];
|
||||
ASSnapshotVerifyView(backgroundView, nil);
|
||||
}
|
||||
|
||||
// This test is disabled because the fast-path is disabled.
|
||||
- (void)DISABLED_testThatFastPathTruncationWorks
|
||||
{
|
||||
ASTextNode *textNode = [[ASTextNode alloc] init];
|
||||
textNode.attributedText = [[NSAttributedString alloc] initWithString:@"Quality is Important" attributes:@{ NSForegroundColorAttributeName: [UIColor blueColor], NSFontAttributeName: [UIFont italicSystemFontOfSize:24] }];
|
||||
[textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(100, 50))];
|
||||
ASSnapshotVerifyNode(textNode, nil);
|
||||
}
|
||||
|
||||
- (void)testThatSlowPathTruncationWorks
|
||||
{
|
||||
ASTextNode *textNode = [[ASTextNode alloc] init];
|
||||
textNode.attributedText = [[NSAttributedString alloc] initWithString:@"Quality is Important" attributes:@{ NSForegroundColorAttributeName: [UIColor blueColor], NSFontAttributeName: [UIFont italicSystemFontOfSize:24] }];
|
||||
// Set exclusion paths to trigger slow path
|
||||
textNode.exclusionPaths = @[ [UIBezierPath bezierPath] ];
|
||||
ASDisplayNodeSizeToFitSizeRange(textNode, ASSizeRangeMake(CGSizeZero, CGSizeMake(100, 50)));
|
||||
ASSnapshotVerifyNode(textNode, nil);
|
||||
}
|
||||
|
||||
- (void)testShadowing
|
||||
{
|
||||
ASTextNode *textNode = [[ASTextNode alloc] init];
|
||||
textNode.attributedText = [[NSAttributedString alloc] initWithString:@"Quality is Important"];
|
||||
textNode.shadowColor = [UIColor blackColor].CGColor;
|
||||
textNode.shadowOpacity = 0.3;
|
||||
textNode.shadowRadius = 3;
|
||||
textNode.shadowOffset = CGSizeMake(0, 1);
|
||||
ASDisplayNodeSizeToFitSizeRange(textNode, ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY)));
|
||||
ASSnapshotVerifyNode(textNode, nil);
|
||||
}
|
||||
|
||||
/**
|
||||
* https://github.com/TextureGroup/Texture/issues/822
|
||||
*/
|
||||
- (void)DISABLED_testThatTruncationTokenAttributesPrecedeThoseInheritedFromTextWhenTruncateTailMode
|
||||
{
|
||||
ASTextNode *textNode = [[ASTextNode alloc] init];
|
||||
textNode.style.maxSize = CGSizeMake(20, 80);
|
||||
NSMutableAttributedString *mas = [[NSMutableAttributedString alloc] initWithString:@"Quality is an important "];
|
||||
[mas appendAttributedString:[[NSAttributedString alloc] initWithString:@"thing" attributes:@{ NSBackgroundColorAttributeName : UIColor.yellowColor}]];
|
||||
textNode.attributedText = mas;
|
||||
textNode.truncationMode = NSLineBreakByTruncatingTail;
|
||||
|
||||
textNode.truncationAttributedText = [[NSAttributedString alloc] initWithString:@"\u2026" attributes:@{ NSBackgroundColorAttributeName: UIColor.greenColor }];
|
||||
ASDisplayNodeSizeToFitSizeRange(textNode, ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY)));
|
||||
ASSnapshotVerifyNode(textNode, nil);
|
||||
}
|
||||
|
||||
- (void)testFontPointSizeScaling
|
||||
{
|
||||
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
|
||||
paragraphStyle.lineHeightMultiple = 0.5;
|
||||
paragraphStyle.lineSpacing = 2.0;
|
||||
|
||||
ASTextNode *textNode = [[ASTextNode alloc] init];
|
||||
textNode.style.maxSize = CGSizeMake(60, 80);
|
||||
textNode.pointSizeScaleFactors = @[@0.5];
|
||||
textNode.attributedText = [[NSAttributedString alloc] initWithString:@"Quality is an important thing"
|
||||
attributes:@{ NSParagraphStyleAttributeName: paragraphStyle }];
|
||||
|
||||
ASDisplayNodeSizeToFitSizeRange(textNode, ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY)));
|
||||
ASSnapshotVerifyNode(textNode, nil);
|
||||
}
|
||||
|
||||
@end
|
@ -1,324 +0,0 @@
|
||||
//
|
||||
// ASTextNodeTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <CoreText/CoreText.h>
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import <OCMock/OCMock.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASAvailability.h>
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
#import <AsyncDisplayKit/ASTextNode.h>
|
||||
#import <AsyncDisplayKit/ASTextNode+Beta.h>
|
||||
#import <AsyncDisplayKit/CoreGraphics+ASConvenience.h>
|
||||
|
||||
#import "ASTestCase.h"
|
||||
|
||||
|
||||
|
||||
@interface ASTextNodeTestDelegate : NSObject <ASTextNodeDelegate>
|
||||
|
||||
@property (nonatomic, copy, readonly) NSString *tappedLinkAttribute;
|
||||
@property (nonatomic, readonly) id tappedLinkValue;
|
||||
|
||||
@end
|
||||
@interface ASTextNodeSubclass : ASTextNode
|
||||
@end
|
||||
@interface ASTextNodeSecondSubclass : ASTextNodeSubclass
|
||||
@end
|
||||
|
||||
@implementation ASTextNodeTestDelegate
|
||||
|
||||
- (void)textNode:(ASTextNode *)textNode tappedLinkAttribute:(NSString *)attribute value:(id)value atPoint:(CGPoint)point textRange:(NSRange)textRange
|
||||
{
|
||||
_tappedLinkAttribute = attribute;
|
||||
_tappedLinkValue = value;
|
||||
}
|
||||
|
||||
- (BOOL)textNode:(ASTextNode *)textNode shouldHighlightLinkAttribute:(NSString *)attribute value:(id)value atPoint:(CGPoint)point
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface ASTextNodeTests : XCTestCase
|
||||
|
||||
@property (nonatomic) ASTextNode *textNode;
|
||||
@property (nonatomic, copy) NSAttributedString *attributedText;
|
||||
@property (nonatomic) NSMutableArray *textNodeBucket;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASTextNodeTests
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
[super setUp];
|
||||
_textNode = [[ASTextNode alloc] init];
|
||||
_textNodeBucket = [[NSMutableArray alloc] init];
|
||||
|
||||
UIFontDescriptor *desc =
|
||||
[UIFontDescriptor fontDescriptorWithName:@"Didot" size:18];
|
||||
NSArray *arr =
|
||||
@[@{UIFontFeatureTypeIdentifierKey:@(kLetterCaseType),
|
||||
UIFontFeatureSelectorIdentifierKey:@(kSmallCapsSelector)}];
|
||||
desc =
|
||||
[desc fontDescriptorByAddingAttributes:
|
||||
@{UIFontDescriptorFeatureSettingsAttribute:arr}];
|
||||
UIFont *f = [UIFont fontWithDescriptor:desc size:0];
|
||||
NSDictionary *d = @{NSFontAttributeName: f};
|
||||
NSMutableAttributedString *mas =
|
||||
[[NSMutableAttributedString alloc] initWithString:@"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." attributes:d];
|
||||
NSMutableParagraphStyle *para = [NSMutableParagraphStyle new];
|
||||
para.alignment = NSTextAlignmentCenter;
|
||||
para.lineSpacing = 1.0;
|
||||
[mas addAttribute:NSParagraphStyleAttributeName value:para
|
||||
range:NSMakeRange(0, mas.length - 1)];
|
||||
|
||||
// Vary the linespacing on the last line
|
||||
NSMutableParagraphStyle *lastLinePara = [NSMutableParagraphStyle new];
|
||||
lastLinePara.alignment = para.alignment;
|
||||
lastLinePara.lineSpacing = 5.0;
|
||||
[mas addAttribute:NSParagraphStyleAttributeName value:lastLinePara
|
||||
range:NSMakeRange(mas.length - 1, 1)];
|
||||
|
||||
_attributedText = mas;
|
||||
_textNode.attributedText = _attributedText;
|
||||
}
|
||||
|
||||
#pragma mark - ASTextNode
|
||||
|
||||
- (void)testAllocASTextNode
|
||||
{
|
||||
ASTextNode *node = [[ASTextNode alloc] init];
|
||||
XCTAssertTrue([[node class] isSubclassOfClass:[ASTextNode class]], @"ASTextNode alloc should return an instance of ASTextNode, instead returned %@", [node class]);
|
||||
}
|
||||
|
||||
#pragma mark - ASTextNode
|
||||
|
||||
- (void)testTruncation
|
||||
{
|
||||
XCTAssertTrue([_textNode shouldTruncateForConstrainedSize:ASSizeRangeMake(CGSizeMake(100, 100))], @"");
|
||||
|
||||
_textNode.frame = CGRectMake(0, 0, 100, 100);
|
||||
XCTAssertTrue(_textNode.isTruncated, @"Text Node should be truncated");
|
||||
}
|
||||
|
||||
- (void)testSettingTruncationMessage
|
||||
{
|
||||
NSAttributedString *truncation = [[NSAttributedString alloc] initWithString:@"..." attributes:nil];
|
||||
_textNode.truncationAttributedText = truncation;
|
||||
XCTAssertTrue([_textNode.truncationAttributedText isEqualToAttributedString:truncation], @"Failed to set truncation message");
|
||||
}
|
||||
|
||||
- (void)testSettingAdditionalTruncationMessage
|
||||
{
|
||||
NSAttributedString *additionalTruncationMessage = [[NSAttributedString alloc] initWithString:@"read more" attributes:nil];
|
||||
_textNode.additionalTruncationMessage = additionalTruncationMessage;
|
||||
XCTAssertTrue([_textNode.additionalTruncationMessage isEqualToAttributedString:additionalTruncationMessage], @"Failed to set additionalTruncationMessage message");
|
||||
}
|
||||
|
||||
- (void)testCalculatedSizeIsGreaterThanOrEqualToConstrainedSize
|
||||
{
|
||||
for (NSInteger i = 10; i < 500; i += 50) {
|
||||
CGSize constrainedSize = CGSizeMake(i, i);
|
||||
CGSize calculatedSize = [_textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, constrainedSize)].size;
|
||||
XCTAssertTrue(calculatedSize.width <= constrainedSize.width, @"Calculated width (%f) should be less than or equal to constrained width (%f)", calculatedSize.width, constrainedSize.width);
|
||||
XCTAssertTrue(calculatedSize.height <= constrainedSize.height, @"Calculated height (%f) should be less than or equal to constrained height (%f)", calculatedSize.height, constrainedSize.height);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testRecalculationOfSizeIsSameAsOriginallyCalculatedSize
|
||||
{
|
||||
for (NSInteger i = 10; i < 500; i += 50) {
|
||||
CGSize constrainedSize = CGSizeMake(i, i);
|
||||
CGSize calculatedSize = [_textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, constrainedSize)].size;
|
||||
CGSize recalculatedSize = [_textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, constrainedSize)].size;
|
||||
|
||||
XCTAssertTrue(CGSizeEqualToSizeWithIn(calculatedSize, recalculatedSize, 4.0), @"Recalculated size %@ should be same as original size %@", NSStringFromCGSize(recalculatedSize), NSStringFromCGSize(calculatedSize));
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testRecalculationOfSizeIsSameAsOriginallyCalculatedFloatingPointSize
|
||||
{
|
||||
for (CGFloat i = 10; i < 500; i *= 1.3) {
|
||||
CGSize constrainedSize = CGSizeMake(i, i);
|
||||
CGSize calculatedSize = [_textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, constrainedSize)].size;
|
||||
CGSize recalculatedSize = [_textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, constrainedSize)].size;
|
||||
|
||||
XCTAssertTrue(CGSizeEqualToSizeWithIn(calculatedSize, recalculatedSize, 11.0), @"Recalculated size %@ should be same as original size %@", NSStringFromCGSize(recalculatedSize), NSStringFromCGSize(calculatedSize));
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testMeasureWithZeroSizeAndPlaceholder
|
||||
{
|
||||
_textNode.placeholderEnabled = YES;
|
||||
|
||||
XCTAssertNoThrow([_textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeZero)], @"Measure with zero size and placeholder enabled should not throw an exception");
|
||||
XCTAssertNoThrow([_textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(0, 100))], @"Measure with zero width and placeholder enabled should not throw an exception");
|
||||
XCTAssertNoThrow([_textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(100, 0))], @"Measure with zero height and placeholder enabled should not throw an exception");
|
||||
}
|
||||
|
||||
- (void)testAccessibility
|
||||
{
|
||||
_textNode.attributedText = _attributedText;
|
||||
XCTAssertTrue(_textNode.isAccessibilityElement, @"Should be an accessibility element");
|
||||
XCTAssertTrue(_textNode.accessibilityTraits == UIAccessibilityTraitStaticText, @"Should have static text accessibility trait, instead has %llu", _textNode.accessibilityTraits);
|
||||
|
||||
XCTAssertTrue([_textNode.accessibilityLabel isEqualToString:_attributedText.string], @"Accessibility label is incorrectly set to \n%@\n when it should be \n%@\n", _textNode.accessibilityLabel, _attributedText.string);
|
||||
}
|
||||
|
||||
- (void)testLinkAttribute
|
||||
{
|
||||
NSString *linkAttributeName = @"MockLinkAttributeName";
|
||||
NSString *linkAttributeValue = @"MockLinkAttributeValue";
|
||||
NSString *linkString = @"Link";
|
||||
NSRange linkRange = NSMakeRange(0, linkString.length);
|
||||
NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:linkString attributes:@{ linkAttributeName : linkAttributeValue}];
|
||||
_textNode.attributedText = attributedString;
|
||||
_textNode.linkAttributeNames = @[linkAttributeName];
|
||||
|
||||
ASTextNodeTestDelegate *delegate = [ASTextNodeTestDelegate new];
|
||||
_textNode.delegate = delegate;
|
||||
|
||||
ASLayout *layout = [_textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(100, 100))];
|
||||
_textNode.frame = CGRectMake(0, 0, layout.size.width, layout.size.height);
|
||||
|
||||
NSRange returnedLinkRange;
|
||||
NSString *returnedAttributeName;
|
||||
NSString *returnedLinkAttributeValue = [_textNode linkAttributeValueAtPoint:CGPointMake(3, 3) attributeName:&returnedAttributeName range:&returnedLinkRange];
|
||||
XCTAssertTrue([linkAttributeName isEqualToString:returnedAttributeName], @"Expecting a link attribute name of %@, returned %@", linkAttributeName, returnedAttributeName);
|
||||
XCTAssertTrue([linkAttributeValue isEqualToString:returnedLinkAttributeValue], @"Expecting a link attribute value of %@, returned %@", linkAttributeValue, returnedLinkAttributeValue);
|
||||
XCTAssertTrue(NSEqualRanges(linkRange, returnedLinkRange), @"Expected a range of %@, got a link range of %@", NSStringFromRange(linkRange), NSStringFromRange(returnedLinkRange));
|
||||
}
|
||||
|
||||
- (void)testTapNotOnALinkAttribute
|
||||
{
|
||||
NSString *linkAttributeName = @"MockLinkAttributeName";
|
||||
NSString *linkAttributeValue = @"MockLinkAttributeValue";
|
||||
NSString *linkString = @"Link notalink";
|
||||
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:linkString];
|
||||
[attributedString addAttribute:linkAttributeName value:linkAttributeValue range:NSMakeRange(0, 4)];
|
||||
_textNode.attributedText = attributedString;
|
||||
_textNode.linkAttributeNames = @[linkAttributeName];
|
||||
|
||||
ASTextNodeTestDelegate *delegate = [ASTextNodeTestDelegate new];
|
||||
_textNode.delegate = delegate;
|
||||
|
||||
CGSize calculatedSize = [_textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(100, 100))].size;
|
||||
NSRange returnedLinkRange = NSMakeRange(NSNotFound, 0);
|
||||
NSRange expectedRange = NSMakeRange(NSNotFound, 0);
|
||||
NSString *returnedAttributeName;
|
||||
CGPoint pointNearEndOfString = CGPointMake(calculatedSize.width - 3, calculatedSize.height / 2);
|
||||
NSString *returnedLinkAttributeValue = [_textNode linkAttributeValueAtPoint:pointNearEndOfString attributeName:&returnedAttributeName range:&returnedLinkRange];
|
||||
XCTAssertFalse(returnedAttributeName, @"Expecting no link attribute name, returned %@", returnedAttributeName);
|
||||
XCTAssertFalse(returnedLinkAttributeValue, @"Expecting no link attribute value, returned %@", returnedLinkAttributeValue);
|
||||
XCTAssertTrue(NSEqualRanges(expectedRange, returnedLinkRange), @"Expected a range of %@, got a link range of %@", NSStringFromRange(expectedRange), NSStringFromRange(returnedLinkRange));
|
||||
|
||||
XCTAssertFalse(delegate.tappedLinkAttribute, @"Expected the delegate to be told that %@ was tapped, instead it thinks the tapped attribute is %@", linkAttributeName, delegate.tappedLinkAttribute);
|
||||
XCTAssertFalse(delegate.tappedLinkValue, @"Expected the delegate to be told that the value %@ was tapped, instead it thinks the tapped attribute value is %@", linkAttributeValue, delegate.tappedLinkValue);
|
||||
}
|
||||
|
||||
#pragma mark exclusion Paths
|
||||
|
||||
- (void)testSettingExclusionPaths
|
||||
{
|
||||
NSArray *exclusionPaths = @[[UIBezierPath bezierPathWithRect:CGRectMake(10, 20, 30, 40)]];
|
||||
_textNode.exclusionPaths = exclusionPaths;
|
||||
XCTAssertTrue([_textNode.exclusionPaths isEqualToArray:exclusionPaths], @"Failed to set exclusion paths");
|
||||
}
|
||||
|
||||
- (void)testAddingExclusionPathsShouldInvalidateAndIncreaseTheSize
|
||||
{
|
||||
CGSize constrainedSize = CGSizeMake(100, CGFLOAT_MAX);
|
||||
CGSize sizeWithoutExclusionPaths = [_textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, constrainedSize)].size;
|
||||
_textNode.exclusionPaths = @[[UIBezierPath bezierPathWithRect:CGRectMake(50, 20, 30, 40)]];
|
||||
CGSize sizeWithExclusionPaths = [_textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, constrainedSize)].size;
|
||||
|
||||
XCTAssertGreaterThan(sizeWithExclusionPaths.height, sizeWithoutExclusionPaths.height, @"Setting exclusions paths should invalidate the calculated size and return a greater size");
|
||||
}
|
||||
|
||||
#if AS_ENABLE_TEXTNODE
|
||||
- (void)testThatTheExperimentWorksCorrectly
|
||||
{
|
||||
ASConfiguration *config = [ASConfiguration new];
|
||||
config.experimentalFeatures = ASExperimentalTextNode;
|
||||
[ASConfigurationManager test_resetWithConfiguration:config];
|
||||
|
||||
ASTextNode *plainTextNode = [[ASTextNode alloc] init];
|
||||
XCTAssertEqualObjects(plainTextNode.class, [ASTextNode2 class]);
|
||||
|
||||
ASTextNodeSecondSubclass *sc2 = [[ASTextNodeSecondSubclass alloc] init];
|
||||
XCTAssertEqualObjects([ASTextNodeSubclass superclass], [ASTextNode2 class]);
|
||||
XCTAssertEqualObjects(sc2.superclass, [ASTextNodeSubclass class]);
|
||||
}
|
||||
|
||||
- (void)testTextNodeSwitchWorksInMultiThreadEnvironment
|
||||
{
|
||||
ASConfiguration *config = [ASConfiguration new];
|
||||
config.experimentalFeatures = ASExperimentalTextNode;
|
||||
[ASConfigurationManager test_resetWithConfiguration:config];
|
||||
XCTestExpectation *exp = [self expectationWithDescription:@"wait for full bucket"];
|
||||
|
||||
dispatch_queue_t queue = dispatch_queue_create("com.texture.AsyncDisplayKit.ASTextNodeTestsQueue", DISPATCH_QUEUE_CONCURRENT);
|
||||
dispatch_group_t g = dispatch_group_create();
|
||||
for (int i = 0; i < 20; i++) {
|
||||
dispatch_group_async(g, queue, ^{
|
||||
ASTextNode *textNode = [[ASTextNodeSecondSubclass alloc] init];
|
||||
XCTAssert([textNode isKindOfClass:[ASTextNode2 class]]);
|
||||
@synchronized(self.textNodeBucket) {
|
||||
[self.textNodeBucket addObject:textNode];
|
||||
if (self.textNodeBucket.count == 20) {
|
||||
[exp fulfill];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
[self waitForExpectations:@[exp] timeout:3];
|
||||
exp = nil;
|
||||
[self.textNodeBucket removeAllObjects];
|
||||
}
|
||||
|
||||
- (void)testTextNodeSwitchWorksInMultiThreadEnvironment2
|
||||
{
|
||||
ASConfiguration *config = [ASConfiguration new];
|
||||
config.experimentalFeatures = ASExperimentalTextNode;
|
||||
[ASConfigurationManager test_resetWithConfiguration:config];
|
||||
XCTestExpectation *exp = [self expectationWithDescription:@"wait for full bucket"];
|
||||
|
||||
NSLock *lock = [[NSLock alloc] init];
|
||||
NSMutableArray *textNodeBucket = [[NSMutableArray alloc] init];
|
||||
|
||||
dispatch_queue_t queue = dispatch_queue_create("com.texture.AsyncDisplayKit.ASTextNodeTestsQueue", DISPATCH_QUEUE_CONCURRENT);
|
||||
dispatch_group_t g = dispatch_group_create();
|
||||
for (int i = 0; i < 20; i++) {
|
||||
dispatch_group_async(g, queue, ^{
|
||||
ASTextNode *textNode = [[ASTextNodeSecondSubclass alloc] init];
|
||||
XCTAssert([textNode isKindOfClass:[ASTextNode2 class]]);
|
||||
[lock lock];
|
||||
[textNodeBucket addObject:textNode];
|
||||
if (textNodeBucket.count == 20) {
|
||||
[exp fulfill];
|
||||
}
|
||||
[lock unlock];
|
||||
});
|
||||
}
|
||||
[self waitForExpectations:@[exp] timeout:3];
|
||||
exp = nil;
|
||||
[textNodeBucket removeAllObjects];
|
||||
}
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASTextNodeSubclass
|
||||
@end
|
||||
@implementation ASTextNodeSecondSubclass
|
||||
@end
|
@ -1,149 +0,0 @@
|
||||
//
|
||||
// ASTextNodeWordKernerTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASTextKitComponents.h>
|
||||
#import <AsyncDisplayKit/ASTextNodeTypes.h>
|
||||
#import <AsyncDisplayKit/ASTextNodeWordKerner.h>
|
||||
|
||||
#pragma mark - Tests
|
||||
|
||||
@interface ASTextNodeWordKernerTests : XCTestCase
|
||||
|
||||
@property (nonatomic) ASTextNodeWordKerner *layoutManagerDelegate;
|
||||
@property (nonatomic) ASTextKitComponents *components;
|
||||
@property (nonatomic, copy) NSAttributedString *attributedString;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASTextNodeWordKernerTests
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
[super setUp];
|
||||
_layoutManagerDelegate = [[ASTextNodeWordKerner alloc] init];
|
||||
_components.layoutManager.delegate = _layoutManagerDelegate;
|
||||
}
|
||||
|
||||
- (void)setupTextKitComponentsWithoutWordKerning
|
||||
{
|
||||
CGSize size = CGSizeMake(200, 200);
|
||||
NSDictionary *attributes = nil;
|
||||
NSString *seedString = @"Hello world";
|
||||
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:seedString attributes:attributes];
|
||||
_components = [ASTextKitComponents componentsWithAttributedSeedString:attributedString textContainerSize:size];
|
||||
}
|
||||
|
||||
- (void)setupTextKitComponentsWithWordKerning
|
||||
{
|
||||
CGSize size = CGSizeMake(200, 200);
|
||||
NSDictionary *attributes = @{ASTextNodeWordKerningAttributeName: @".5"};
|
||||
NSString *seedString = @"Hello world";
|
||||
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:seedString attributes:attributes];
|
||||
_components = [ASTextKitComponents componentsWithAttributedSeedString:attributedString textContainerSize:size];
|
||||
}
|
||||
|
||||
- (void)setupTextKitComponentsWithWordKerningDifferentFontSizes
|
||||
{
|
||||
CGSize size = CGSizeMake(200, 200);
|
||||
NSDictionary *attributes = @{ASTextNodeWordKerningAttributeName: @".5"};
|
||||
NSString *seedString = @" ";
|
||||
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:seedString attributes:attributes];
|
||||
UIFont *bigFont = [UIFont systemFontOfSize:36];
|
||||
UIFont *normalFont = [UIFont systemFontOfSize:12];
|
||||
[attributedString addAttribute:NSFontAttributeName value:bigFont range:NSMakeRange(0, 1)];
|
||||
[attributedString addAttribute:NSFontAttributeName value:normalFont range:NSMakeRange(1, 1)];
|
||||
_components = [ASTextKitComponents componentsWithAttributedSeedString:attributedString textContainerSize:size];
|
||||
}
|
||||
|
||||
- (void)testSomeGlyphsToChangeIfWordKerning
|
||||
{
|
||||
[self setupTextKitComponentsWithWordKerning];
|
||||
|
||||
NSInteger glyphsToChange = [self _layoutManagerShouldGenerateGlyphs];
|
||||
XCTAssertTrue(glyphsToChange > 0, @"Should have changed the properties on some glyphs");
|
||||
}
|
||||
|
||||
- (void)testSpaceBoundingBoxForNoWordKerning
|
||||
{
|
||||
CGSize size = CGSizeMake(200, 200);
|
||||
UIFont *font = [UIFont systemFontOfSize:12.0];
|
||||
NSDictionary *attributes = @{NSFontAttributeName : font};
|
||||
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:@" " attributes:attributes];
|
||||
_components = [ASTextKitComponents componentsWithAttributedSeedString:attributedString textContainerSize:size];
|
||||
CGFloat expectedWidth = [@" " sizeWithAttributes:@{ NSFontAttributeName : font }].width;
|
||||
|
||||
CGRect boundingBox = [_layoutManagerDelegate layoutManager:_components.layoutManager boundingBoxForControlGlyphAtIndex:0 forTextContainer:_components.textContainer proposedLineFragment:CGRectZero glyphPosition:CGPointZero characterIndex:0];
|
||||
|
||||
XCTAssertEqualWithAccuracy(boundingBox.size.width, expectedWidth, FLT_EPSILON, @"Word kerning shouldn't alter the default width of %f. Encountered space width was %f", expectedWidth, boundingBox.size.width);
|
||||
}
|
||||
|
||||
- (void)testSpaceBoundingBoxForWordKerning
|
||||
{
|
||||
CGSize size = CGSizeMake(200, 200);
|
||||
UIFont *font = [UIFont systemFontOfSize:12];
|
||||
|
||||
CGFloat kernValue = 0.5;
|
||||
NSDictionary *attributes = @{ASTextNodeWordKerningAttributeName: @(kernValue),
|
||||
NSFontAttributeName : font};
|
||||
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:@" " attributes:attributes];
|
||||
_components = [ASTextKitComponents componentsWithAttributedSeedString:attributedString textContainerSize:size];
|
||||
CGFloat expectedWidth = [@" " sizeWithAttributes:@{ NSFontAttributeName : font }].width + kernValue;
|
||||
|
||||
CGRect boundingBox = [_layoutManagerDelegate layoutManager:_components.layoutManager boundingBoxForControlGlyphAtIndex:0 forTextContainer:_components.textContainer proposedLineFragment:CGRectZero glyphPosition:CGPointZero characterIndex:0];
|
||||
XCTAssertEqualWithAccuracy(boundingBox.size.width, expectedWidth, FLT_EPSILON, @"Word kerning shouldn't alter the default width of %f. Encountered space width was %f", expectedWidth, boundingBox.size.width);
|
||||
}
|
||||
|
||||
- (NSInteger)_layoutManagerShouldGenerateGlyphs
|
||||
{
|
||||
NSRange stringRange = NSMakeRange(0, _components.textStorage.length);
|
||||
NSRange glyphRange = [_components.layoutManager glyphRangeForCharacterRange:stringRange actualCharacterRange:NULL];
|
||||
NSInteger glyphCount = glyphRange.length;
|
||||
NSUInteger *characterIndexes = (NSUInteger *)malloc(sizeof(NSUInteger) * glyphCount);
|
||||
for (NSUInteger i=0; i < stringRange.length; i++) {
|
||||
characterIndexes[i] = i;
|
||||
}
|
||||
NSGlyphProperty *glyphProperties = (NSGlyphProperty *)malloc(sizeof(NSGlyphProperty) * glyphCount);
|
||||
CGGlyph *glyphs = (CGGlyph *)malloc(sizeof(CGGlyph) * glyphCount);
|
||||
NSInteger glyphsToChange = [_layoutManagerDelegate layoutManager:_components.layoutManager shouldGenerateGlyphs:glyphs properties:glyphProperties characterIndexes:characterIndexes font:[UIFont systemFontOfSize:12.0] forGlyphRange:stringRange];
|
||||
free(characterIndexes);
|
||||
free(glyphProperties);
|
||||
free(glyphs);
|
||||
return glyphsToChange;
|
||||
}
|
||||
|
||||
- (void)testPerCharacterWordKerning
|
||||
{
|
||||
[self setupTextKitComponentsWithWordKerningDifferentFontSizes];
|
||||
CGPoint glyphPosition = CGPointZero;
|
||||
NSUInteger bigSpaceIndex = 0;
|
||||
NSUInteger normalSpaceIndex = 1;
|
||||
CGRect bigBoundingBox = [_layoutManagerDelegate layoutManager:_components.layoutManager boundingBoxForControlGlyphAtIndex:bigSpaceIndex forTextContainer:_components.textContainer proposedLineFragment:CGRectZero glyphPosition:glyphPosition characterIndex:bigSpaceIndex];
|
||||
CGRect normalBoundingBox = [_layoutManagerDelegate layoutManager:_components.layoutManager boundingBoxForControlGlyphAtIndex:normalSpaceIndex forTextContainer:_components.textContainer proposedLineFragment:CGRectZero glyphPosition:glyphPosition characterIndex:normalSpaceIndex];
|
||||
XCTAssertTrue(bigBoundingBox.size.width > normalBoundingBox.size.width, @"Unbolded and bolded spaces should have different kerning");
|
||||
}
|
||||
|
||||
- (void)testWordKerningDoesNotAlterGlyphOrigin
|
||||
{
|
||||
CGSize size = CGSizeMake(200, 200);
|
||||
NSDictionary *attributes = @{ASTextNodeWordKerningAttributeName: @".5"};
|
||||
NSString *seedString = @" ";
|
||||
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:seedString attributes:attributes];
|
||||
UIFont *normalFont = [UIFont systemFontOfSize:12];
|
||||
[attributedString addAttribute:NSFontAttributeName value:normalFont range:NSMakeRange(0, 1)];
|
||||
_components = [ASTextKitComponents componentsWithAttributedSeedString:attributedString textContainerSize:size];
|
||||
|
||||
CGPoint glyphPosition = CGPointMake(42, 54);
|
||||
|
||||
CGRect boundingBox = [_layoutManagerDelegate layoutManager:_components.layoutManager boundingBoxForControlGlyphAtIndex:0 forTextContainer:_components.textContainer proposedLineFragment:CGRectZero glyphPosition:glyphPosition characterIndex:0];
|
||||
XCTAssertTrue(CGPointEqualToPoint(glyphPosition, boundingBox.origin), @"Word kerning shouldn't alter the origin point of a glyph");
|
||||
}
|
||||
|
||||
@end
|
@ -1,111 +0,0 @@
|
||||
//
|
||||
// Tests/ASThrashUtility.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
#import <stdatomic.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
#define kInitialSectionCount 10
|
||||
#define kInitialItemCount 10
|
||||
#define kMinimumItemCount 5
|
||||
#define kMinimumSectionCount 3
|
||||
#define kFickleness 0.1
|
||||
#define kThrashingIterationCount 10
|
||||
|
||||
// Set to 1 to use UITableView and see if the issue still exists.
|
||||
#define USE_UIKIT_REFERENCE 0
|
||||
|
||||
#if USE_UIKIT_REFERENCE
|
||||
#define TableView UITableView
|
||||
#define CollectionView UICollectionView
|
||||
#define kCellReuseID @"ASThrashTestCellReuseID"
|
||||
#else
|
||||
#define TableView ASTableView
|
||||
#define CollectionView ASCollectionNode
|
||||
#endif
|
||||
|
||||
static NSInteger ASThrashUpdateCurrentSerializationVersion = 1;
|
||||
|
||||
@class ASThrashTestSection;
|
||||
static atomic_uint ASThrashTestItemNextID;
|
||||
@interface ASThrashTestItem: NSObject <NSSecureCoding>
|
||||
@property (nonatomic, readonly) NSInteger itemID;
|
||||
|
||||
+ (NSMutableArray <ASThrashTestItem *> *)itemsWithCount:(NSInteger)count;
|
||||
|
||||
- (CGFloat)rowHeight;
|
||||
@end
|
||||
|
||||
|
||||
@interface ASThrashTestSection: NSObject <NSCopying, NSSecureCoding>
|
||||
@property (nonatomic, readonly) NSMutableArray *items;
|
||||
@property (nonatomic, readonly) NSInteger sectionID;
|
||||
|
||||
+ (NSMutableArray <ASThrashTestSection *> *)sectionsWithCount:(NSInteger)count;
|
||||
|
||||
- (instancetype)initWithCount:(NSInteger)count;
|
||||
- (CGFloat)headerHeight;
|
||||
@end
|
||||
|
||||
@interface ASThrashDataSource: NSObject
|
||||
#if USE_UIKIT_REFERENCE
|
||||
<UITableViewDataSource, UITableViewDelegate, UICollectionViewDataSource, UICollectionViewDelegate>
|
||||
#else
|
||||
<ASTableDataSource, ASTableDelegate, ASCollectionDelegate, ASCollectionDataSource>
|
||||
#endif
|
||||
|
||||
@property (nonatomic, readonly) UIWindow *window;
|
||||
@property (nonatomic, readonly) TableView *tableView;
|
||||
@property (nonatomic, readonly) CollectionView *collectionView;
|
||||
@property (nonatomic) NSArray <ASThrashTestSection *> *data;
|
||||
// Only access on main
|
||||
@property (nonatomic) ASWeakSet *allNodes;
|
||||
|
||||
- (instancetype)initTableViewDataSourceWithData:(NSArray <ASThrashTestSection *> *)data;
|
||||
- (instancetype)initCollectionViewDataSourceWithData:(NSArray <ASThrashTestSection *> * _Nullable)data;
|
||||
- (NSPredicate *)predicateForDeallocatedHierarchy;
|
||||
@end
|
||||
|
||||
@interface NSIndexSet (ASThrashHelpers)
|
||||
- (NSArray <NSIndexPath *> *)indexPathsInSection:(NSInteger)section;
|
||||
/// `insertMode` means that for each index selected, the max goes up by one.
|
||||
+ (NSMutableIndexSet *)randomIndexesLessThan:(NSInteger)max probability:(float)probability insertMode:(BOOL)insertMode;
|
||||
@end
|
||||
|
||||
#if !USE_UIKIT_REFERENCE
|
||||
@interface ASThrashTestNode: ASCellNode
|
||||
@property (nonatomic) ASThrashTestItem *item;
|
||||
@end
|
||||
#endif
|
||||
|
||||
@interface ASThrashUpdate : NSObject <NSSecureCoding>
|
||||
@property (nonatomic, readonly) NSArray<ASThrashTestSection *> *oldData;
|
||||
@property (nonatomic, readonly) NSMutableArray<ASThrashTestSection *> *data;
|
||||
@property (nonatomic, readonly) NSMutableIndexSet *deletedSectionIndexes;
|
||||
@property (nonatomic, readonly) NSMutableIndexSet *replacedSectionIndexes;
|
||||
/// The sections used to replace the replaced sections.
|
||||
@property (nonatomic, readonly) NSMutableArray<ASThrashTestSection *> *replacingSections;
|
||||
@property (nonatomic, readonly) NSMutableIndexSet *insertedSectionIndexes;
|
||||
@property (nonatomic, readonly) NSMutableArray<ASThrashTestSection *> *insertedSections;
|
||||
@property (nonatomic, readonly) NSMutableArray<NSMutableIndexSet *> *deletedItemIndexes;
|
||||
@property (nonatomic, readonly) NSMutableArray<NSMutableIndexSet *> *replacedItemIndexes;
|
||||
/// The items used to replace the replaced items.
|
||||
@property (nonatomic, readonly) NSMutableArray<NSArray <ASThrashTestItem *> *> *replacingItems;
|
||||
@property (nonatomic, readonly) NSMutableArray<NSMutableIndexSet *> *insertedItemIndexes;
|
||||
@property (nonatomic, readonly) NSMutableArray<NSArray <ASThrashTestItem *> *> *insertedItems;
|
||||
|
||||
- (instancetype)initWithData:(NSArray<ASThrashTestSection *> *)data;
|
||||
|
||||
+ (ASThrashUpdate *)thrashUpdateWithBase64String:(NSString *)base64;
|
||||
- (NSString *)base64Representation;
|
||||
- (NSString *)logFriendlyBase64Representation;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
@ -1,467 +0,0 @@
|
||||
//
|
||||
// ASTableViewThrashTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASThrashUtility.h"
|
||||
#import <AsyncDisplayKit/ASTableViewInternal.h>
|
||||
#import <AsyncDisplayKit/ASTableView+Undeprecated.h>
|
||||
|
||||
static NSString *ASThrashArrayDescription(NSArray *array)
|
||||
{
|
||||
NSMutableString *str = [NSMutableString stringWithString:@"(\n"];
|
||||
NSInteger i = 0;
|
||||
for (id obj in array) {
|
||||
[str appendFormat:@"\t[%ld]: \"%@\",\n", (long)i, obj];
|
||||
i += 1;
|
||||
}
|
||||
[str appendString:@")"];
|
||||
return str;
|
||||
}
|
||||
|
||||
@implementation ASThrashTestItem
|
||||
|
||||
+ (BOOL)supportsSecureCoding
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_itemID = atomic_fetch_add(&ASThrashTestItemNextID, 1);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder
|
||||
{
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_itemID = [aDecoder decodeIntegerForKey:@"itemID"];
|
||||
NSAssert(_itemID > 0, @"Failed to decode %@", self);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder
|
||||
{
|
||||
[aCoder encodeInteger:_itemID forKey:@"itemID"];
|
||||
}
|
||||
|
||||
+ (NSMutableArray <ASThrashTestItem *> *)itemsWithCount:(NSInteger)count
|
||||
{
|
||||
NSMutableArray *result = [NSMutableArray arrayWithCapacity:count];
|
||||
for (NSInteger i = 0; i < count; i += 1) {
|
||||
[result addObject:[[ASThrashTestItem alloc] init]];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
- (CGFloat)rowHeight
|
||||
{
|
||||
return (self.itemID % 400) ?: 44;
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<Item %lu>", (unsigned long)_itemID];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static atomic_uint ASThrashTestSectionNextID = 1;
|
||||
@implementation ASThrashTestSection
|
||||
|
||||
/// Create an array of sections with the given count
|
||||
+ (NSMutableArray <ASThrashTestSection *> *)sectionsWithCount:(NSInteger)count
|
||||
{
|
||||
NSMutableArray *result = [NSMutableArray arrayWithCapacity:count];
|
||||
for (NSInteger i = 0; i < count; i += 1) {
|
||||
[result addObject:[[ASThrashTestSection alloc] initWithCount:kInitialItemCount]];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCount:(NSInteger)count
|
||||
{
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_sectionID = atomic_fetch_add(&ASThrashTestSectionNextID, 1);
|
||||
_items = [ASThrashTestItem itemsWithCount:count];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
return [self initWithCount:0];
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder
|
||||
{
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_items = [aDecoder decodeObjectOfClass:[NSArray class] forKey:@"items"];
|
||||
_sectionID = [aDecoder decodeIntegerForKey:@"sectionID"];
|
||||
NSAssert(_sectionID > 0, @"Failed to decode %@", self);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (BOOL)supportsSecureCoding
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder
|
||||
{
|
||||
[aCoder encodeObject:_items forKey:@"items"];
|
||||
[aCoder encodeInteger:_sectionID forKey:@"sectionID"];
|
||||
}
|
||||
|
||||
- (CGFloat)headerHeight
|
||||
{
|
||||
return self.sectionID % 400 ?: 44;
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<Section %lu: itemCount=%lu, items=%@>", (unsigned long)_sectionID, (unsigned long)self.items.count, ASThrashArrayDescription(self.items)];
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
ASThrashTestSection *copy = [[ASThrashTestSection alloc] init];
|
||||
copy->_sectionID = _sectionID;
|
||||
copy->_items = [_items mutableCopy];
|
||||
return copy;
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object
|
||||
{
|
||||
if ([object isKindOfClass:[ASThrashTestSection class]]) {
|
||||
return [(ASThrashTestSection *)object sectionID] == _sectionID;
|
||||
} else {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSIndexSet (ASThrashHelpers)
|
||||
|
||||
- (NSArray <NSIndexPath *> *)indexPathsInSection:(NSInteger)section
|
||||
{
|
||||
NSMutableArray *result = [NSMutableArray arrayWithCapacity:self.count];
|
||||
[self enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
[result addObject:[NSIndexPath indexPathForItem:idx inSection:section]];
|
||||
}];
|
||||
return result;
|
||||
}
|
||||
|
||||
/// `insertMode` means that for each index selected, the max goes up by one.
|
||||
+ (NSMutableIndexSet *)randomIndexesLessThan:(NSInteger)max probability:(float)probability insertMode:(BOOL)insertMode
|
||||
{
|
||||
NSMutableIndexSet *indexes = [[NSMutableIndexSet alloc] init];
|
||||
u_int32_t cutoff = probability * 100;
|
||||
for (NSInteger i = 0; i < max; i++) {
|
||||
if (arc4random_uniform(100) < cutoff) {
|
||||
[indexes addIndex:i];
|
||||
if (insertMode) {
|
||||
max += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return indexes;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASThrashDataSource
|
||||
|
||||
- (instancetype)initTableViewDataSourceWithData:(NSArray <ASThrashTestSection *> *)data
|
||||
{
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_data = [[NSArray alloc] initWithArray:data copyItems:YES];
|
||||
_window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
||||
_tableView = [[TableView alloc] initWithFrame:_window.bounds style:UITableViewStylePlain];
|
||||
_allNodes = [[ASWeakSet alloc] init];
|
||||
[_window addSubview:_tableView];
|
||||
#if USE_UIKIT_REFERENCE
|
||||
_tableView.dataSource = self;
|
||||
_tableView.delegate = self;
|
||||
[_tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:kCellReuseID];
|
||||
#else
|
||||
_tableView.asyncDelegate = self;
|
||||
_tableView.asyncDataSource = self;
|
||||
[_tableView reloadData];
|
||||
[_tableView waitUntilAllUpdatesAreCommitted];
|
||||
#endif
|
||||
[_tableView layoutIfNeeded];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initCollectionViewDataSourceWithData:(NSArray <ASThrashTestSection *> *)data
|
||||
{
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_data = data != nil ? [[NSArray alloc] initWithArray:data copyItems:YES] : [[NSArray alloc] init];
|
||||
_window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
||||
_collectionView = [[CollectionView alloc] initWithCollectionViewLayout:[[UICollectionViewFlowLayout alloc] init]];
|
||||
_allNodes = [[ASWeakSet alloc] init];
|
||||
[_window addSubview:_tableView];
|
||||
_collectionView.delegate = self;
|
||||
_collectionView.dataSource = self;
|
||||
#if USE_UIKIT_REFERENCE
|
||||
[_collectionView registerClass:[UITableViewCell class] forCellReuseIdentifier:kCellReuseID];
|
||||
#else
|
||||
[_collectionView reloadData];
|
||||
[_collectionView waitUntilAllUpdatesAreProcessed];
|
||||
#endif
|
||||
[_collectionView layoutIfNeeded];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setData:(NSArray<ASThrashTestSection *> *)data
|
||||
{
|
||||
_data = data;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
||||
{
|
||||
return self.data[section].items.count;
|
||||
}
|
||||
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
|
||||
{
|
||||
return self.data.count;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
|
||||
{
|
||||
return self.data[section].headerHeight;
|
||||
}
|
||||
|
||||
- (NSInteger)collectionNode:(ASCollectionNode *)collectionNode numberOfItemsInSection:(NSInteger)section
|
||||
{
|
||||
return self.data[section].items.count;
|
||||
}
|
||||
|
||||
|
||||
- (NSInteger)numberOfSectionsInCollectionNode:(ASCollectionNode *)collectionNode
|
||||
{
|
||||
return self.data.count;
|
||||
}
|
||||
|
||||
/// Object passed into predicate is ignored.
|
||||
- (NSPredicate *)predicateForDeallocatedHierarchy
|
||||
{
|
||||
ASWeakSet *allNodes = self.allNodes;
|
||||
__weak UIWindow *window = _window;
|
||||
__weak ASTableView *view = _tableView;
|
||||
return [NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary<NSString *,id> * _Nullable bindings) {
|
||||
return window == nil && view == nil && allNodes.isEmpty;
|
||||
}];
|
||||
}
|
||||
|
||||
#if USE_UIKIT_REFERENCE
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
return [tableView dequeueReusableCellWithIdentifier:kCellReuseID forIndexPath:indexPath];
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
ASThrashTestItem *item = self.data[indexPath.section].items[indexPath.item];
|
||||
return item.rowHeight;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
- (ASCellNode *)collectionNode:(ASCollectionNode *)collectionNode nodeForItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
ASThrashTestNode *node = [[ASThrashTestNode alloc] init];
|
||||
node.item = self.data[indexPath.section].items[indexPath.row];
|
||||
[self.allNodes addObject:node];
|
||||
return node;
|
||||
}
|
||||
|
||||
- (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
ASThrashTestNode *node = [[ASThrashTestNode alloc] init];
|
||||
node.item = self.data[indexPath.section].items[indexPath.item];
|
||||
[self.allNodes addObject:node];
|
||||
return node;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
#if !USE_UIKIT_REFERENCE
|
||||
@implementation ASThrashTestNode
|
||||
|
||||
- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize
|
||||
{
|
||||
ASDisplayNodeAssertFalse(isinf(constrainedSize.width));
|
||||
return CGSizeMake(constrainedSize.width, 44);
|
||||
}
|
||||
|
||||
@end
|
||||
#endif
|
||||
|
||||
@implementation ASThrashUpdate
|
||||
|
||||
- (instancetype)initWithData:(NSArray<ASThrashTestSection *> *)data
|
||||
{
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_data = [[NSMutableArray alloc] initWithArray:data copyItems:YES];
|
||||
_oldData = [[NSArray alloc] initWithArray:data copyItems:YES];
|
||||
|
||||
_deletedItemIndexes = [NSMutableArray array];
|
||||
_replacedItemIndexes = [NSMutableArray array];
|
||||
_insertedItemIndexes = [NSMutableArray array];
|
||||
_replacingItems = [NSMutableArray array];
|
||||
_insertedItems = [NSMutableArray array];
|
||||
|
||||
// Randomly reload some items
|
||||
for (ASThrashTestSection *section in _data) {
|
||||
NSMutableIndexSet *indexes = [NSIndexSet randomIndexesLessThan:section.items.count probability:kFickleness insertMode:NO];
|
||||
NSArray *newItems = [ASThrashTestItem itemsWithCount:indexes.count];
|
||||
[section.items replaceObjectsAtIndexes:indexes withObjects:newItems];
|
||||
[_replacingItems addObject:newItems];
|
||||
[_replacedItemIndexes addObject:indexes];
|
||||
}
|
||||
|
||||
// Randomly replace some sections
|
||||
_replacedSectionIndexes = [NSIndexSet randomIndexesLessThan:_data.count probability:kFickleness insertMode:NO];
|
||||
_replacingSections = [ASThrashTestSection sectionsWithCount:_replacedSectionIndexes.count];
|
||||
[_data replaceObjectsAtIndexes:_replacedSectionIndexes withObjects:_replacingSections];
|
||||
|
||||
// Randomly delete some items
|
||||
[_data enumerateObjectsUsingBlock:^(ASThrashTestSection * _Nonnull section, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
if (section.items.count >= kMinimumItemCount) {
|
||||
NSMutableIndexSet *indexes = [NSIndexSet randomIndexesLessThan:section.items.count probability:kFickleness insertMode:NO];
|
||||
|
||||
/// Cannot reload & delete the same item.
|
||||
[indexes removeIndexes:_replacedItemIndexes[idx]];
|
||||
|
||||
[section.items removeObjectsAtIndexes:indexes];
|
||||
[_deletedItemIndexes addObject:indexes];
|
||||
} else {
|
||||
[_deletedItemIndexes addObject:[NSMutableIndexSet indexSet]];
|
||||
}
|
||||
}];
|
||||
|
||||
// Randomly delete some sections
|
||||
if (_data.count >= kMinimumSectionCount) {
|
||||
_deletedSectionIndexes = [NSIndexSet randomIndexesLessThan:_data.count probability:kFickleness insertMode:NO];
|
||||
} else {
|
||||
_deletedSectionIndexes = [NSMutableIndexSet indexSet];
|
||||
}
|
||||
// Cannot replace & delete the same section.
|
||||
[_deletedSectionIndexes removeIndexes:_replacedSectionIndexes];
|
||||
|
||||
// Cannot delete/replace item in deleted/replaced section
|
||||
[_deletedSectionIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
[_replacedItemIndexes[idx] removeAllIndexes];
|
||||
[_deletedItemIndexes[idx] removeAllIndexes];
|
||||
}];
|
||||
[_replacedSectionIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
[_replacedItemIndexes[idx] removeAllIndexes];
|
||||
[_deletedItemIndexes[idx] removeAllIndexes];
|
||||
}];
|
||||
[_data removeObjectsAtIndexes:_deletedSectionIndexes];
|
||||
|
||||
// Randomly insert some sections
|
||||
_insertedSectionIndexes = [NSIndexSet randomIndexesLessThan:(_data.count + 1) probability:kFickleness insertMode:YES];
|
||||
_insertedSections = [ASThrashTestSection sectionsWithCount:_insertedSectionIndexes.count];
|
||||
[_data insertObjects:_insertedSections atIndexes:_insertedSectionIndexes];
|
||||
|
||||
// Randomly insert some items
|
||||
for (ASThrashTestSection *section in _data) {
|
||||
// Only insert items into the old sections – not replaced/inserted sections.
|
||||
if ([_oldData containsObject:section]) {
|
||||
NSMutableIndexSet *indexes = [NSIndexSet randomIndexesLessThan:(section.items.count + 1) probability:kFickleness insertMode:YES];
|
||||
NSArray *newItems = [ASThrashTestItem itemsWithCount:indexes.count];
|
||||
[section.items insertObjects:newItems atIndexes:indexes];
|
||||
[_insertedItems addObject:newItems];
|
||||
[_insertedItemIndexes addObject:indexes];
|
||||
} else {
|
||||
[_insertedItems addObject:@[]];
|
||||
[_insertedItemIndexes addObject:[NSMutableIndexSet indexSet]];
|
||||
}
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (BOOL)supportsSecureCoding
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (ASThrashUpdate *)thrashUpdateWithBase64String:(NSString *)base64
|
||||
{
|
||||
return [NSKeyedUnarchiver unarchiveObjectWithData:[[NSData alloc] initWithBase64EncodedString:base64 options:kNilOptions]];
|
||||
}
|
||||
|
||||
- (NSString *)base64Representation
|
||||
{
|
||||
return [[NSKeyedArchiver archivedDataWithRootObject:self] base64EncodedStringWithOptions:kNilOptions];
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder
|
||||
{
|
||||
NSDictionary *dict = [self dictionaryWithValuesForKeys:@[
|
||||
@"oldData",
|
||||
@"data",
|
||||
@"deletedSectionIndexes",
|
||||
@"replacedSectionIndexes",
|
||||
@"replacingSections",
|
||||
@"insertedSectionIndexes",
|
||||
@"insertedSections",
|
||||
@"deletedItemIndexes",
|
||||
@"replacedItemIndexes",
|
||||
@"replacingItems",
|
||||
@"insertedItemIndexes",
|
||||
@"insertedItems"
|
||||
]];
|
||||
[aCoder encodeObject:dict forKey:@"_dict"];
|
||||
[aCoder encodeInteger:ASThrashUpdateCurrentSerializationVersion forKey:@"_version"];
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder
|
||||
{
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
NSAssert(ASThrashUpdateCurrentSerializationVersion == [aDecoder decodeIntegerForKey:@"_version"], @"This thrash update was archived from a different version and can't be read. Sorry.");
|
||||
NSDictionary *dict = [aDecoder decodeObjectOfClass:[NSDictionary class] forKey:@"_dict"];
|
||||
[self setValuesForKeysWithDictionary:dict];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<ASThrashUpdate %p:\nOld data: %@\nDeleted items: %@\nDeleted sections: %@\nReplaced items: %@\nReplaced sections: %@\nInserted items: %@\nInserted sections: %@\nNew data: %@>", self, ASThrashArrayDescription(_oldData), ASThrashArrayDescription(_deletedItemIndexes), _deletedSectionIndexes, ASThrashArrayDescription(_replacedItemIndexes), _replacedSectionIndexes, ASThrashArrayDescription(_insertedItemIndexes), _insertedSectionIndexes, ASThrashArrayDescription(_data)];
|
||||
}
|
||||
|
||||
- (NSString *)logFriendlyBase64Representation
|
||||
{
|
||||
return [NSString stringWithFormat:@"\n\n**********\nBase64 Representation:\n**********\n%@\n**********\nEnd Base64 Representation\n**********", self.base64Representation];
|
||||
}
|
||||
|
||||
@end
|
@ -1,141 +0,0 @@
|
||||
//
|
||||
// ASUICollectionViewTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import <OCMock/OCMock.h>
|
||||
#import "NSInvocation+ASTestHelpers.h"
|
||||
|
||||
@interface ASUICollectionViewTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASUICollectionViewTests
|
||||
|
||||
/// Test normal item-affiliated supplementary node
|
||||
- (void)testNormalTwoIndexSupplementaryElement
|
||||
{
|
||||
[self _testSupplementaryNodeAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:1] sectionCount:2 expectException:NO];
|
||||
}
|
||||
|
||||
/// If your supp is indexPathForItem:inSection:, the section index must be in bounds
|
||||
- (void)testThatSupplementariesWithItemIndexesMustBeWithinNormalSections
|
||||
{
|
||||
[self _testSupplementaryNodeAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:3] sectionCount:2 expectException:YES];
|
||||
}
|
||||
|
||||
/// If your supp is indexPathWithIndex:, that's OK even if that section is out of bounds!
|
||||
- (void)testThatSupplementariesWithOneIndexAreOKOutOfSectionBounds
|
||||
{
|
||||
[self _testSupplementaryNodeAtIndexPath:[NSIndexPath indexPathWithIndex:3] sectionCount:2 expectException:NO];
|
||||
}
|
||||
|
||||
- (void)testThatNestedBatchCompletionsAreCalledInOrder
|
||||
{
|
||||
UICollectionViewLayout *layout = [[UICollectionViewLayout alloc] init];
|
||||
id layoutMock = [OCMockObject partialMockForObject:layout];
|
||||
|
||||
UICollectionView *cv = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, 100, 100) collectionViewLayout:layoutMock];
|
||||
id dataSource = [OCMockObject niceMockForProtocol:@protocol(UICollectionViewDataSource)];
|
||||
|
||||
cv.dataSource = dataSource;
|
||||
|
||||
XCTestExpectation *inner0 = [self expectationWithDescription:@"Inner completion 0 is called"];
|
||||
XCTestExpectation *inner1 = [self expectationWithDescription:@"Inner completion 1 is called"];
|
||||
XCTestExpectation *outer = [self expectationWithDescription:@"Outer completion is called"];
|
||||
|
||||
NSMutableArray<XCTestExpectation *> *completions = [NSMutableArray array];
|
||||
|
||||
[cv performBatchUpdates:^{
|
||||
[cv performBatchUpdates:^{
|
||||
|
||||
} completion:^(BOOL finished) {
|
||||
[completions addObject:inner0];
|
||||
[inner0 fulfill];
|
||||
}];
|
||||
[cv performBatchUpdates:^{
|
||||
|
||||
} completion:^(BOOL finished) {
|
||||
[completions addObject:inner1];
|
||||
[inner1 fulfill];
|
||||
}];
|
||||
} completion:^(BOOL finished) {
|
||||
[completions addObject:outer];
|
||||
[outer fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectationsWithTimeout:5 handler:nil];
|
||||
XCTAssertEqualObjects(completions, (@[ outer, inner0, inner1 ]), @"Expected completion order to be correct");
|
||||
}
|
||||
|
||||
- (void)_testSupplementaryNodeAtIndexPath:(NSIndexPath *)indexPath sectionCount:(NSInteger)sectionCount expectException:(BOOL)shouldFail
|
||||
{
|
||||
UICollectionViewLayoutAttributes *attr = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:@"SuppKind" withIndexPath:indexPath];
|
||||
attr.frame = CGRectMake(0, 0, 20, 20);
|
||||
UICollectionViewLayout *layout = [[UICollectionViewLayout alloc] init];
|
||||
id layoutMock = [OCMockObject partialMockForObject:layout];
|
||||
|
||||
[[[[layoutMock expect] ignoringNonObjectArgs] andReturn:@[ attr ]] layoutAttributesForElementsInRect:CGRectZero];
|
||||
UICollectionView *cv = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, 100, 100) collectionViewLayout:layoutMock];
|
||||
[cv registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:@"SuppKind" withReuseIdentifier:@"ReuseID"];
|
||||
|
||||
id dataSource = [OCMockObject niceMockForProtocol:@protocol(UICollectionViewDataSource)];
|
||||
__block id view = nil;
|
||||
[[[dataSource expect] andDo:^(NSInvocation *invocation) {
|
||||
NSIndexPath *indexPath = [invocation as_argumentAtIndexAsObject:4];
|
||||
view = [cv dequeueReusableSupplementaryViewOfKind:@"SuppKind" withReuseIdentifier:@"ReuseID" forIndexPath:indexPath];
|
||||
[invocation setReturnValue:&view];
|
||||
}] collectionView:cv viewForSupplementaryElementOfKind:@"SuppKind" atIndexPath:indexPath];
|
||||
[[[dataSource expect] andReturnValue:[NSNumber numberWithInteger:sectionCount]] numberOfSectionsInCollectionView:cv];
|
||||
|
||||
cv.dataSource = dataSource;
|
||||
if (shouldFail) {
|
||||
XCTAssertThrowsSpecificNamed([cv layoutIfNeeded], NSException, NSInternalInconsistencyException);
|
||||
// Early return because behavior after exception is thrown is undefined.
|
||||
return;
|
||||
}
|
||||
|
||||
[cv layoutIfNeeded];
|
||||
XCTAssertEqualObjects(attr, [cv layoutAttributesForSupplementaryElementOfKind:@"SuppKind" atIndexPath:indexPath]);
|
||||
XCTAssertEqual(view, [cv supplementaryViewForElementKind:@"SuppKind" atIndexPath:indexPath]);
|
||||
[dataSource verify];
|
||||
[layoutMock verify];
|
||||
}
|
||||
|
||||
- (void)testThatIssuingAnUpdateBeforeInitialReloadIsUnacceptable
|
||||
{
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
UICollectionView *cv = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, 100, 100) collectionViewLayout:layout];
|
||||
id dataSource = [OCMockObject niceMockForProtocol:@protocol(UICollectionViewDataSource)];
|
||||
|
||||
// Setup empty data source – 0 sections, 0 items
|
||||
[[[dataSource stub] andDo:^(NSInvocation *invocation) {
|
||||
NSIndexPath *indexPath = [invocation as_argumentAtIndexAsObject:3];
|
||||
__autoreleasing UICollectionViewCell *view = [cv dequeueReusableCellWithReuseIdentifier:@"CellID" forIndexPath:indexPath];
|
||||
[invocation setReturnValue:&view];
|
||||
}] collectionView:cv cellForItemAtIndexPath:OCMOCK_ANY];
|
||||
[[[dataSource stub] andReturnValue:[NSNumber numberWithInteger:0]] numberOfSectionsInCollectionView:cv];
|
||||
[[[dataSource stub] andReturnValue:[NSNumber numberWithInteger:0]] collectionView:cv numberOfItemsInSection:0];
|
||||
[cv registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"CellID"];
|
||||
cv.dataSource = dataSource;
|
||||
|
||||
// Update data source – 1 section, 0 items
|
||||
[[[dataSource stub] andReturnValue:[NSNumber numberWithInteger:1]] numberOfSectionsInCollectionView:cv];
|
||||
|
||||
/**
|
||||
* Inform collection view – insert section 0
|
||||
* Throws exception because collection view never saw the data source have 0 sections.
|
||||
* so it's going to read "oldSectionCount" now and get 1. It will also read
|
||||
* "newSectionCount" and get 1. Then it'll throw because "oldSectionCount(1) + insertedCount(1) != newSectionCount(1)".
|
||||
* To workaround this, you could add `[cv numberOfSections]` before the data source is updated to
|
||||
* trigger the collection view to read oldSectionCount=0.
|
||||
*/
|
||||
XCTAssertThrowsSpecificNamed([cv insertSections:[NSIndexSet indexSetWithIndex:0]], NSException, NSInternalInconsistencyException);
|
||||
}
|
||||
|
||||
@end
|
@ -1,428 +0,0 @@
|
||||
//
|
||||
// ASVideoNodeTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import <OCMock/OCMock.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
|
||||
#import "ASDisplayNodeTestsHelper.h"
|
||||
|
||||
#if AS_USE_VIDEO
|
||||
@interface ASVideoNodeTests : XCTestCase <ASVideoNodeDelegate>
|
||||
{
|
||||
ASVideoNode *_videoNode;
|
||||
AVURLAsset *_firstAsset;
|
||||
AVAsset *_secondAsset;
|
||||
NSURL *_url;
|
||||
NSArray *_requestedKeys;
|
||||
}
|
||||
@end
|
||||
|
||||
@interface ASVideoNode () {
|
||||
ASDisplayNode *_playerNode;
|
||||
AVPlayer *_player;
|
||||
}
|
||||
|
||||
|
||||
@property ASInterfaceState interfaceState;
|
||||
@property (readonly) ASDisplayNode *spinner;
|
||||
@property ASDisplayNode *playerNode;
|
||||
@property AVPlayer *player;
|
||||
@property BOOL shouldBePlaying;
|
||||
|
||||
- (void)setVideoPlaceholderImage:(UIImage *)image;
|
||||
- (void)prepareToPlayAsset:(AVAsset *)asset withKeys:(NSArray *)requestedKeys;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASVideoNodeTests
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
_videoNode = [[ASVideoNode alloc] init];
|
||||
_firstAsset = [AVURLAsset assetWithURL:[NSURL URLWithString:@"firstURL"]];
|
||||
_secondAsset = [AVAsset assetWithURL:[NSURL URLWithString:@"secondURL"]];
|
||||
_url = [NSURL URLWithString:@"testURL"];
|
||||
_requestedKeys = @[ @"playable" ];
|
||||
}
|
||||
|
||||
- (void)testOnPlayIfVideoIsNotReadyInitializeSpinnerAndAddAsSubnode
|
||||
{
|
||||
_videoNode.asset = _firstAsset;
|
||||
[self doOnPlayIfVideoIsNotReadyInitializeSpinnerAndAddAsSubnodeWithUrl];
|
||||
}
|
||||
|
||||
- (void)testOnPlayIfVideoIsNotReadyInitializeSpinnerAndAddAsSubnodeWithUrl
|
||||
{
|
||||
_videoNode.asset = [AVAsset assetWithURL:_url];
|
||||
[self doOnPlayIfVideoIsNotReadyInitializeSpinnerAndAddAsSubnodeWithUrl];
|
||||
}
|
||||
|
||||
- (void)doOnPlayIfVideoIsNotReadyInitializeSpinnerAndAddAsSubnodeWithUrl
|
||||
{
|
||||
_videoNode.interfaceState = ASInterfaceStatePreload;
|
||||
[_videoNode play];
|
||||
}
|
||||
|
||||
|
||||
- (void)testOnPauseSpinnerIsPausedIfPresent
|
||||
{
|
||||
_videoNode.asset = _firstAsset;
|
||||
[self doOnPauseSpinnerIsPausedIfPresentWithURL];
|
||||
}
|
||||
|
||||
- (void)testOnPauseSpinnerIsPausedIfPresentWithURL
|
||||
{
|
||||
_videoNode.asset = [AVAsset assetWithURL:_url];
|
||||
[self doOnPauseSpinnerIsPausedIfPresentWithURL];
|
||||
}
|
||||
|
||||
- (void)doOnPauseSpinnerIsPausedIfPresentWithURL
|
||||
{
|
||||
_videoNode.interfaceState = ASInterfaceStatePreload;
|
||||
|
||||
[_videoNode play];
|
||||
[_videoNode pause];
|
||||
|
||||
}
|
||||
|
||||
|
||||
- (void)testOnVideoReadySpinnerIsStoppedAndRemoved
|
||||
{
|
||||
_videoNode.asset = _firstAsset;
|
||||
[self doOnVideoReadySpinnerIsStoppedAndRemovedWithURL];
|
||||
}
|
||||
|
||||
- (void)testOnVideoReadySpinnerIsStoppedAndRemovedWithURL
|
||||
{
|
||||
_videoNode.asset = [AVAsset assetWithURL:_url];
|
||||
[self doOnVideoReadySpinnerIsStoppedAndRemovedWithURL];
|
||||
}
|
||||
|
||||
- (void)doOnVideoReadySpinnerIsStoppedAndRemovedWithURL
|
||||
{
|
||||
_videoNode.interfaceState = ASInterfaceStatePreload;
|
||||
|
||||
[_videoNode play];
|
||||
[_videoNode observeValueForKeyPath:@"status" ofObject:[_videoNode currentItem] change:@{NSKeyValueChangeNewKey : @(AVPlayerItemStatusReadyToPlay)} context:NULL];
|
||||
}
|
||||
|
||||
|
||||
- (void)testPlayerDefaultsToNil
|
||||
{
|
||||
_videoNode.asset = _firstAsset;
|
||||
XCTAssertNil(_videoNode.player);
|
||||
}
|
||||
|
||||
- (void)testPlayerDefaultsToNilWithURL
|
||||
{
|
||||
_videoNode.asset = [AVAsset assetWithURL:_url];
|
||||
XCTAssertNil(_videoNode.player);
|
||||
}
|
||||
|
||||
- (void)testPlayerIsCreatedAsynchronouslyInPreload
|
||||
{
|
||||
AVAsset *asset = _firstAsset;
|
||||
|
||||
id assetMock = [OCMockObject partialMockForObject:asset];
|
||||
id videoNodeMock = [OCMockObject partialMockForObject:_videoNode];
|
||||
|
||||
[[[assetMock stub] andReturnValue:@YES] isPlayable];
|
||||
[[[videoNodeMock expect] andForwardToRealObject] prepareToPlayAsset:assetMock withKeys:_requestedKeys];
|
||||
|
||||
_videoNode.asset = assetMock;
|
||||
_videoNode.interfaceState = ASInterfaceStatePreload;
|
||||
|
||||
[videoNodeMock verifyWithDelay:1.0f];
|
||||
|
||||
XCTAssertNotNil(_videoNode.player);
|
||||
}
|
||||
|
||||
- (void)testPlayerIsCreatedAsynchronouslyInPreloadWithURL
|
||||
{
|
||||
AVAsset *asset = [AVAsset assetWithURL:_url];
|
||||
|
||||
id assetMock = [OCMockObject partialMockForObject:asset];
|
||||
id videoNodeMock = [OCMockObject partialMockForObject:_videoNode];
|
||||
|
||||
[[[assetMock stub] andReturnValue:@YES] isPlayable];
|
||||
[[[videoNodeMock expect] andForwardToRealObject] prepareToPlayAsset:assetMock withKeys:_requestedKeys];
|
||||
|
||||
_videoNode.asset = assetMock;
|
||||
_videoNode.interfaceState = ASInterfaceStatePreload;
|
||||
|
||||
[videoNodeMock verifyWithDelay:1.0f];
|
||||
|
||||
XCTAssertNotNil(_videoNode.player);
|
||||
}
|
||||
|
||||
- (void)testPlayerLayerNodeIsAddedOnDidLoadIfVisibleAndAutoPlaying
|
||||
{
|
||||
_videoNode.asset = _firstAsset;
|
||||
[self doPlayerLayerNodeIsAddedOnDidLoadIfVisibleAndAutoPlayingWithURL];
|
||||
}
|
||||
|
||||
- (void)testPlayerLayerNodeIsAddedOnDidLoadIfVisibleAndAutoPlayingWithURL
|
||||
{
|
||||
_videoNode.asset = [AVAsset assetWithURL:_url];
|
||||
[self doPlayerLayerNodeIsAddedOnDidLoadIfVisibleAndAutoPlayingWithURL];
|
||||
}
|
||||
|
||||
- (void)doPlayerLayerNodeIsAddedOnDidLoadIfVisibleAndAutoPlayingWithURL
|
||||
{
|
||||
[_videoNode setInterfaceState:ASInterfaceStateNone];
|
||||
[_videoNode didLoad];
|
||||
|
||||
XCTAssert(![_videoNode.subnodes containsObject:_videoNode.playerNode]);
|
||||
}
|
||||
|
||||
|
||||
- (void)testPlayerLayerNodeIsNotAddedIfVisibleButShouldNotBePlaying
|
||||
{
|
||||
_videoNode.asset = _firstAsset;
|
||||
[self doPlayerLayerNodeIsNotAddedIfVisibleButShouldNotBePlaying];
|
||||
}
|
||||
|
||||
- (void)testPlayerLayerNodeIsNotAddedIfVisibleButShouldNotBePlayingWithUrl
|
||||
{
|
||||
_videoNode.asset = [AVAsset assetWithURL:_url];
|
||||
[self doPlayerLayerNodeIsNotAddedIfVisibleButShouldNotBePlaying];
|
||||
}
|
||||
|
||||
- (void)doPlayerLayerNodeIsNotAddedIfVisibleButShouldNotBePlaying
|
||||
{
|
||||
[_videoNode pause];
|
||||
[_videoNode layer];
|
||||
[_videoNode setInterfaceState:ASInterfaceStateVisible | ASInterfaceStateDisplay];
|
||||
|
||||
XCTAssert(![_videoNode.subnodes containsObject:_videoNode.playerNode]);
|
||||
}
|
||||
|
||||
|
||||
- (void)testVideoStartsPlayingOnDidDidBecomeVisibleWhenShouldAutoplay
|
||||
{
|
||||
_videoNode.asset = _firstAsset;
|
||||
[self doVideoStartsPlayingOnDidDidBecomeVisibleWhenShouldAutoplay];
|
||||
}
|
||||
|
||||
- (void)testVideoStartsPlayingOnDidDidBecomeVisibleWhenShouldAutoplayWithURL
|
||||
{
|
||||
_videoNode.asset = [AVAsset assetWithURL:_url];
|
||||
[self doVideoStartsPlayingOnDidDidBecomeVisibleWhenShouldAutoplay];
|
||||
}
|
||||
|
||||
- (void)doVideoStartsPlayingOnDidDidBecomeVisibleWhenShouldAutoplay
|
||||
{
|
||||
_videoNode.shouldAutoplay = YES;
|
||||
_videoNode.playerNode = [[ASDisplayNode alloc] initWithLayerBlock:^CALayer *{
|
||||
AVPlayerLayer *playerLayer = [[AVPlayerLayer alloc] init];
|
||||
return playerLayer;
|
||||
}];
|
||||
_videoNode.playerNode.layer.frame = CGRectZero;
|
||||
|
||||
[_videoNode layer];
|
||||
[_videoNode didEnterVisibleState];
|
||||
|
||||
XCTAssertTrue(_videoNode.shouldBePlaying);
|
||||
}
|
||||
|
||||
- (void)testVideoShouldPauseWhenItLeavesVisibleButShouldKnowPlayingShouldRestartLater
|
||||
{
|
||||
_videoNode.asset = _firstAsset;
|
||||
[self doVideoShouldPauseWhenItLeavesVisibleButShouldKnowPlayingShouldRestartLater];
|
||||
}
|
||||
|
||||
- (void)testVideoShouldPauseWhenItLeavesVisibleButShouldKnowPlayingShouldRestartLaterWithURL
|
||||
{
|
||||
_videoNode.asset = [AVAsset assetWithURL:_url];
|
||||
[self doVideoShouldPauseWhenItLeavesVisibleButShouldKnowPlayingShouldRestartLater];
|
||||
}
|
||||
|
||||
- (void)doVideoShouldPauseWhenItLeavesVisibleButShouldKnowPlayingShouldRestartLater
|
||||
{
|
||||
[_videoNode play];
|
||||
|
||||
[_videoNode interfaceStateDidChange:ASInterfaceStateNone fromState:ASInterfaceStateVisible];
|
||||
|
||||
XCTAssertFalse(_videoNode.isPlaying);
|
||||
XCTAssertTrue(_videoNode.shouldBePlaying);
|
||||
}
|
||||
|
||||
|
||||
- (void)testVideoThatIsPlayingWhenItLeavesVisibleRangeStartsAgainWhenItComesBack
|
||||
{
|
||||
_videoNode.asset = _firstAsset;
|
||||
[self doVideoThatIsPlayingWhenItLeavesVisibleRangeStartsAgainWhenItComesBack];
|
||||
}
|
||||
|
||||
- (void)testVideoThatIsPlayingWhenItLeavesVisibleRangeStartsAgainWhenItComesBackWithURL
|
||||
{
|
||||
_videoNode.asset = [AVAsset assetWithURL:_url];
|
||||
[self doVideoThatIsPlayingWhenItLeavesVisibleRangeStartsAgainWhenItComesBack];
|
||||
}
|
||||
|
||||
- (void)doVideoThatIsPlayingWhenItLeavesVisibleRangeStartsAgainWhenItComesBack
|
||||
{
|
||||
[_videoNode play];
|
||||
|
||||
[_videoNode interfaceStateDidChange:ASInterfaceStateVisible fromState:ASInterfaceStateNone];
|
||||
[_videoNode interfaceStateDidChange:ASInterfaceStateNone fromState:ASInterfaceStateVisible];
|
||||
|
||||
XCTAssertTrue(_videoNode.shouldBePlaying);
|
||||
}
|
||||
|
||||
- (void)testMutingShouldMutePlayer
|
||||
{
|
||||
[_videoNode setPlayer:[[AVPlayer alloc] init]];
|
||||
|
||||
_videoNode.muted = YES;
|
||||
|
||||
XCTAssertTrue(_videoNode.player.muted);
|
||||
}
|
||||
|
||||
- (void)testUnMutingShouldUnMutePlayer
|
||||
{
|
||||
[_videoNode setPlayer:[[AVPlayer alloc] init]];
|
||||
|
||||
_videoNode.muted = YES;
|
||||
_videoNode.muted = NO;
|
||||
|
||||
XCTAssertFalse(_videoNode.player.muted);
|
||||
}
|
||||
|
||||
- (void)testVideoThatDoesNotAutorepeatsShouldPauseOnPlaybackEnd
|
||||
{
|
||||
id assetMock = [OCMockObject partialMockForObject:_firstAsset];
|
||||
[[[assetMock stub] andReturnValue:@YES] isPlayable];
|
||||
|
||||
_videoNode.asset = assetMock;
|
||||
_videoNode.shouldAutorepeat = NO;
|
||||
|
||||
[_videoNode layer];
|
||||
[_videoNode setInterfaceState:ASInterfaceStateVisible | ASInterfaceStateDisplay | ASInterfaceStatePreload];
|
||||
[_videoNode prepareToPlayAsset:assetMock withKeys:_requestedKeys];
|
||||
[_videoNode play];
|
||||
|
||||
XCTAssertTrue(_videoNode.isPlaying);
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:AVPlayerItemDidPlayToEndTimeNotification object:_videoNode.currentItem];
|
||||
|
||||
XCTAssertFalse(_videoNode.isPlaying);
|
||||
XCTAssertEqual(0, CMTimeGetSeconds(_videoNode.player.currentTime));
|
||||
}
|
||||
|
||||
- (void)testVideoThatAutorepeatsShouldRepeatOnPlaybackEnd
|
||||
{
|
||||
id assetMock = [OCMockObject partialMockForObject:_firstAsset];
|
||||
[[[assetMock stub] andReturnValue:@YES] isPlayable];
|
||||
|
||||
_videoNode.asset = assetMock;
|
||||
_videoNode.shouldAutorepeat = YES;
|
||||
|
||||
[_videoNode layer];
|
||||
[_videoNode setInterfaceState:ASInterfaceStateVisible | ASInterfaceStateDisplay | ASInterfaceStatePreload];
|
||||
[_videoNode prepareToPlayAsset:assetMock withKeys:_requestedKeys];
|
||||
[_videoNode play];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:AVPlayerItemDidPlayToEndTimeNotification object:_videoNode.currentItem];
|
||||
|
||||
XCTAssertTrue(_videoNode.isPlaying);
|
||||
}
|
||||
|
||||
- (void)testVideoResumedWhenBufferIsLikelyToKeepUp
|
||||
{
|
||||
id assetMock = [OCMockObject partialMockForObject:_firstAsset];
|
||||
[[[assetMock stub] andReturnValue:@YES] isPlayable];
|
||||
|
||||
_videoNode.asset = assetMock;
|
||||
|
||||
[_videoNode layer];
|
||||
[_videoNode setInterfaceState:ASInterfaceStateVisible | ASInterfaceStateDisplay | ASInterfaceStatePreload];
|
||||
[_videoNode prepareToPlayAsset:assetMock withKeys:_requestedKeys];
|
||||
ASCATransactionQueueWait(nil);
|
||||
[_videoNode pause];
|
||||
_videoNode.shouldBePlaying = YES;
|
||||
XCTAssertFalse(_videoNode.isPlaying);
|
||||
|
||||
[_videoNode observeValueForKeyPath:@"playbackLikelyToKeepUp" ofObject:[_videoNode currentItem] change:@{NSKeyValueChangeNewKey : @YES} context:NULL];
|
||||
|
||||
XCTAssertTrue(_videoNode.isPlaying);
|
||||
}
|
||||
|
||||
- (void)testSettingVideoGravityChangesPlaceholderContentMode
|
||||
{
|
||||
[_videoNode setVideoPlaceholderImage:[[UIImage alloc] init]];
|
||||
XCTAssertEqual(UIViewContentModeScaleAspectFit, _videoNode.contentMode);
|
||||
|
||||
_videoNode.gravity = AVLayerVideoGravityResize;
|
||||
XCTAssertEqual(UIViewContentModeScaleToFill, _videoNode.contentMode);
|
||||
|
||||
_videoNode.gravity = AVLayerVideoGravityResizeAspect;
|
||||
XCTAssertEqual(UIViewContentModeScaleAspectFit, _videoNode.contentMode);
|
||||
|
||||
_videoNode.gravity = AVLayerVideoGravityResizeAspectFill;
|
||||
XCTAssertEqual(UIViewContentModeScaleAspectFill, _videoNode.contentMode);
|
||||
}
|
||||
|
||||
- (void)testChangingAssetsChangesPlaceholderImage
|
||||
{
|
||||
UIImage *firstImage = [[UIImage alloc] init];
|
||||
|
||||
_videoNode.asset = _firstAsset;
|
||||
[_videoNode setVideoPlaceholderImage:firstImage];
|
||||
XCTAssertEqual(firstImage, _videoNode.image);
|
||||
|
||||
_videoNode.asset = _secondAsset;
|
||||
XCTAssertNotEqual(firstImage, _videoNode.image);
|
||||
}
|
||||
|
||||
- (void)testClearingPreloadedContentShouldClearAssetData
|
||||
{
|
||||
AVAsset *asset = _firstAsset;
|
||||
|
||||
id assetMock = [OCMockObject partialMockForObject:asset];
|
||||
id videoNodeMock = [OCMockObject partialMockForObject:_videoNode];
|
||||
|
||||
[[[assetMock stub] andReturnValue:@YES] isPlayable];
|
||||
[[[videoNodeMock expect] andForwardToRealObject] prepareToPlayAsset:assetMock withKeys:_requestedKeys];
|
||||
|
||||
_videoNode.asset = assetMock;
|
||||
[_videoNode didEnterPreloadState];
|
||||
[_videoNode setVideoPlaceholderImage:[[UIImage alloc] init]];
|
||||
|
||||
[videoNodeMock verifyWithDelay:1.0f];
|
||||
|
||||
XCTAssertNotNil(_videoNode.player);
|
||||
XCTAssertNotNil(_videoNode.currentItem);
|
||||
XCTAssertNotNil(_videoNode.image);
|
||||
|
||||
[_videoNode didExitPreloadState];
|
||||
XCTAssertNil(_videoNode.player);
|
||||
XCTAssertNil(_videoNode.currentItem);
|
||||
}
|
||||
|
||||
- (void)testDelegateProperlySetForClassHierarchy
|
||||
{
|
||||
_videoNode.delegate = self;
|
||||
|
||||
XCTAssertTrue([_videoNode.delegate conformsToProtocol:@protocol(ASVideoNodeDelegate)]);
|
||||
XCTAssertTrue([_videoNode.delegate conformsToProtocol:@protocol(ASNetworkImageNodeDelegate)]);
|
||||
XCTAssertTrue([((ASNetworkImageNode*)_videoNode).delegate conformsToProtocol:@protocol(ASNetworkImageNodeDelegate)]);
|
||||
|
||||
XCTAssertEqual(_videoNode.delegate, self);
|
||||
XCTAssertEqual(((ASNetworkImageNode*)_videoNode).delegate, self);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
@ -1,88 +0,0 @@
|
||||
//
|
||||
// ASViewControllerTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <OCMock/OCMock.h>
|
||||
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
|
||||
#import "NSInvocation+ASTestHelpers.h"
|
||||
|
||||
@interface ASViewControllerTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASViewControllerTests
|
||||
|
||||
- (void)testThatAutomaticSubnodeManagementScrollViewInsetsAreApplied
|
||||
{
|
||||
UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
||||
ASDisplayNode *node = [[ASDisplayNode alloc] init];
|
||||
node.automaticallyManagesSubnodes = YES;
|
||||
ASScrollNode *scrollNode = [[ASScrollNode alloc] init];
|
||||
node.layoutSpecBlock = ^(ASDisplayNode *node, ASSizeRange constrainedSize){
|
||||
return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsZero child:scrollNode];
|
||||
};
|
||||
ASViewController *vc = [[ASViewController alloc] initWithNode:node];
|
||||
window.rootViewController = [[UINavigationController alloc] initWithRootViewController:vc];
|
||||
[window makeKeyAndVisible];
|
||||
[window layoutIfNeeded];
|
||||
XCTAssertEqualObjects(NSStringFromCGRect(window.bounds), NSStringFromCGRect(node.frame));
|
||||
XCTAssertNotEqual(scrollNode.view.contentInset.top, 0);
|
||||
}
|
||||
|
||||
- (void)testThatViewControllerFrameIsRightAfterCustomTransitionWithNonextendedEdges
|
||||
{
|
||||
UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
||||
ASDisplayNode *node = [[ASDisplayNode alloc] init];
|
||||
|
||||
ASViewController *vc = [[ASViewController alloc] initWithNode:node];
|
||||
vc.node.backgroundColor = [UIColor greenColor];
|
||||
vc.edgesForExtendedLayout = UIRectEdgeNone;
|
||||
|
||||
UIViewController * oldVC = [[UIViewController alloc] init];
|
||||
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:oldVC];
|
||||
id navDelegate = [OCMockObject niceMockForProtocol:@protocol(UINavigationControllerDelegate)];
|
||||
id animator = [OCMockObject niceMockForProtocol:@protocol(UIViewControllerAnimatedTransitioning)];
|
||||
[[[[navDelegate expect] ignoringNonObjectArgs] andReturn:animator] navigationController:[OCMArg any] animationControllerForOperation:UINavigationControllerOperationPush fromViewController:[OCMArg any] toViewController:[OCMArg any]];
|
||||
[[[animator expect] andReturnValue:@0.3] transitionDuration:[OCMArg any]];
|
||||
XCTestExpectation *e = [self expectationWithDescription:@"Transition completed"];
|
||||
[[[animator expect] andDo:^(NSInvocation *invocation) {
|
||||
id<UIViewControllerContextTransitioning> ctx = [invocation as_argumentAtIndexAsObject:2];
|
||||
UIView *container = [ctx containerView];
|
||||
[container addSubview:vc.view];
|
||||
vc.view.alpha = 0;
|
||||
vc.view.frame = [ctx finalFrameForViewController:vc];
|
||||
[UIView animateWithDuration:0.3 animations:^{
|
||||
vc.view.alpha = 1;
|
||||
oldVC.view.alpha = 0;
|
||||
} completion:^(BOOL finished) {
|
||||
[oldVC.view removeFromSuperview];
|
||||
[ctx completeTransition:finished];
|
||||
[e fulfill];
|
||||
}];
|
||||
}] animateTransition:[OCMArg any]];
|
||||
nav.delegate = navDelegate;
|
||||
window.rootViewController = nav;
|
||||
[window makeKeyAndVisible];
|
||||
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate date]];
|
||||
[nav pushViewController:vc animated:YES];
|
||||
|
||||
[self waitForExpectationsWithTimeout:2 handler:nil];
|
||||
|
||||
CGFloat navHeight = CGRectGetMaxY([nav.navigationBar convertRect:nav.navigationBar.bounds toView:window]);
|
||||
CGRect expectedRect, slice;
|
||||
CGRectDivide(window.bounds, &slice, &expectedRect, navHeight, CGRectMinYEdge);
|
||||
XCTAssertEqualObjects(NSStringFromCGRect(expectedRect), NSStringFromCGRect(node.frame));
|
||||
[navDelegate verify];
|
||||
[animator verify];
|
||||
}
|
||||
|
||||
@end
|
@ -1,54 +0,0 @@
|
||||
//
|
||||
// ASWeakMapTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import <AsyncDisplayKit/ASWeakMap.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ASWeakMapTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASWeakMapTests
|
||||
|
||||
- (void)testKeyAndValueAreReleasedWhenEntryIsReleased
|
||||
{
|
||||
ASWeakMap <NSObject *, NSObject *> *weakMap = [[ASWeakMap alloc] init];
|
||||
|
||||
__weak NSObject *weakKey;
|
||||
__weak NSObject *weakValue;
|
||||
@autoreleasepool {
|
||||
NSObject *key = [[NSObject alloc] init];
|
||||
NSObject *value = [[NSObject alloc] init];
|
||||
ASWeakMapEntry *entry = [weakMap setObject:value forKey:key];
|
||||
XCTAssertEqual([weakMap entryForKey:key], entry);
|
||||
|
||||
weakKey = key;
|
||||
weakValue = value;
|
||||
}
|
||||
XCTAssertNil(weakKey);
|
||||
XCTAssertNil(weakValue);
|
||||
}
|
||||
|
||||
- (void)testKeyEquality
|
||||
{
|
||||
ASWeakMap <NSString *, NSObject *> *weakMap = [[ASWeakMap alloc] init];
|
||||
NSString *keyA = @"key";
|
||||
NSString *keyB = [keyA copy]; // `isEqual` but not pointer equal
|
||||
NSObject *value = [[NSObject alloc] init];
|
||||
|
||||
ASWeakMapEntry *entryA = [weakMap setObject:value forKey:keyA];
|
||||
ASWeakMapEntry *entryB = [weakMap entryForKey:keyB];
|
||||
XCTAssertEqual(entryA, entryB);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
@ -1,135 +0,0 @@
|
||||
//
|
||||
// ASWeakSetTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import <AsyncDisplayKit/ASWeakSet.h>
|
||||
|
||||
@interface ASWeakSetTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASWeakSetTests
|
||||
|
||||
- (void)testAddingACoupleRetainedObjects
|
||||
{
|
||||
ASWeakSet <NSString *> *weakSet = [ASWeakSet new];
|
||||
NSString *hello = @"hello";
|
||||
NSString *world = @"hello";
|
||||
[weakSet addObject:hello];
|
||||
[weakSet addObject:world];
|
||||
XCTAssert([weakSet containsObject:hello]);
|
||||
XCTAssert([weakSet containsObject:world]);
|
||||
XCTAssert(![weakSet containsObject:@"apple"]);
|
||||
}
|
||||
|
||||
- (void)testThatCountIncorporatesDeallocatedObjects
|
||||
{
|
||||
ASWeakSet *weakSet = [ASWeakSet new];
|
||||
XCTAssertEqual(weakSet.count, 0);
|
||||
NSObject *a = [NSObject new];
|
||||
NSObject *b = [NSObject new];
|
||||
[weakSet addObject:a];
|
||||
[weakSet addObject:b];
|
||||
XCTAssertEqual(weakSet.count, 2);
|
||||
|
||||
@autoreleasepool {
|
||||
NSObject *doomedObject = [NSObject new];
|
||||
[weakSet addObject:doomedObject];
|
||||
XCTAssertEqual(weakSet.count, 3);
|
||||
}
|
||||
|
||||
XCTAssertEqual(weakSet.count, 2);
|
||||
}
|
||||
|
||||
- (void)testThatIsEmptyIncorporatesDeallocatedObjects
|
||||
{
|
||||
ASWeakSet *weakSet = [ASWeakSet new];
|
||||
XCTAssertTrue(weakSet.isEmpty);
|
||||
@autoreleasepool {
|
||||
NSObject *doomedObject = [NSObject new];
|
||||
[weakSet addObject:doomedObject];
|
||||
XCTAssertFalse(weakSet.isEmpty);
|
||||
}
|
||||
XCTAssertTrue(weakSet.isEmpty);
|
||||
}
|
||||
|
||||
- (void)testThatContainsObjectWorks
|
||||
{
|
||||
ASWeakSet *weakSet = [ASWeakSet new];
|
||||
NSObject *a = [NSObject new];
|
||||
NSObject *b = [NSObject new];
|
||||
[weakSet addObject:a];
|
||||
XCTAssertTrue([weakSet containsObject:a]);
|
||||
XCTAssertFalse([weakSet containsObject:b]);
|
||||
}
|
||||
|
||||
- (void)testThatRemoveObjectWorks
|
||||
{
|
||||
ASWeakSet *weakSet = [ASWeakSet new];
|
||||
NSObject *a = [NSObject new];
|
||||
NSObject *b = [NSObject new];
|
||||
[weakSet addObject:a];
|
||||
[weakSet addObject:b];
|
||||
XCTAssertTrue([weakSet containsObject:a]);
|
||||
XCTAssertTrue([weakSet containsObject:b]);
|
||||
XCTAssertEqual(weakSet.count, 2);
|
||||
|
||||
[weakSet removeObject:b];
|
||||
XCTAssertTrue([weakSet containsObject:a]);
|
||||
XCTAssertFalse([weakSet containsObject:b]);
|
||||
XCTAssertEqual(weakSet.count, 1);
|
||||
}
|
||||
|
||||
- (void)testThatFastEnumerationWorks
|
||||
{
|
||||
ASWeakSet *weakSet = [ASWeakSet new];
|
||||
NSObject *a = [NSObject new];
|
||||
NSObject *b = [NSObject new];
|
||||
[weakSet addObject:a];
|
||||
[weakSet addObject:b];
|
||||
|
||||
@autoreleasepool {
|
||||
NSObject *doomedObject = [NSObject new];
|
||||
[weakSet addObject:doomedObject];
|
||||
XCTAssertEqual(weakSet.count, 3);
|
||||
}
|
||||
|
||||
NSInteger i = 0;
|
||||
NSMutableSet *awaitingObjects = [NSMutableSet setWithObjects:a, b, nil];
|
||||
for (NSObject *object in weakSet) {
|
||||
XCTAssertTrue([awaitingObjects containsObject:object]);
|
||||
[awaitingObjects removeObject:object];
|
||||
i += 1;
|
||||
}
|
||||
|
||||
XCTAssertEqual(i, 2);
|
||||
}
|
||||
|
||||
- (void)testThatRemoveAllObjectsWorks
|
||||
{
|
||||
ASWeakSet *weakSet = [ASWeakSet new];
|
||||
NSObject *a = [NSObject new];
|
||||
NSObject *b = [NSObject new];
|
||||
[weakSet addObject:a];
|
||||
[weakSet addObject:b];
|
||||
XCTAssertEqual(weakSet.count, 2);
|
||||
|
||||
[weakSet removeAllObjects];
|
||||
|
||||
XCTAssertEqual(weakSet.count, 0);
|
||||
|
||||
NSInteger i = 0;
|
||||
for (__unused NSObject *object in weakSet) {
|
||||
i += 1;
|
||||
}
|
||||
|
||||
XCTAssertEqual(i, 0);
|
||||
}
|
||||
|
||||
@end
|
@ -1,52 +0,0 @@
|
||||
//
|
||||
// ASWrapperSpecSnapshotTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
|
||||
#import "ASLayoutSpecSnapshotTestsHelper.h"
|
||||
#import <AsyncDisplayKit/ASBackgroundLayoutSpec.h>
|
||||
|
||||
@interface ASWrapperSpecSnapshotTests : ASLayoutSpecSnapshotTestCase
|
||||
@end
|
||||
|
||||
@implementation ASWrapperSpecSnapshotTests
|
||||
|
||||
- (void)testWrapperSpecWithOneElementShouldSizeToElement
|
||||
{
|
||||
ASDisplayNode *child = ASDisplayNodeWithBackgroundColor([UIColor redColor], {50, 50});
|
||||
|
||||
ASSizeRange sizeRange = ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY));
|
||||
[self testWithChildren:@[child] sizeRange:sizeRange identifier:nil];
|
||||
}
|
||||
|
||||
- (void)testWrapperSpecWithMultipleElementsShouldSizeToLargestElement
|
||||
{
|
||||
ASDisplayNode *firstChild = ASDisplayNodeWithBackgroundColor([UIColor redColor], {50, 50});
|
||||
ASDisplayNode *secondChild = ASDisplayNodeWithBackgroundColor([UIColor greenColor], {100, 100});
|
||||
|
||||
ASSizeRange sizeRange = ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY));
|
||||
[self testWithChildren:@[secondChild, firstChild] sizeRange:sizeRange identifier:nil];
|
||||
}
|
||||
|
||||
- (void)testWithChildren:(NSArray *)children sizeRange:(ASSizeRange)sizeRange identifier:(NSString *)identifier
|
||||
{
|
||||
ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor whiteColor]);
|
||||
|
||||
NSMutableArray *subnodes = [NSMutableArray arrayWithArray:children];
|
||||
[subnodes insertObject:backgroundNode atIndex:0];
|
||||
|
||||
ASLayoutSpec *layoutSpec =
|
||||
[ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:
|
||||
[ASWrapperLayoutSpec
|
||||
wrapperWithLayoutElements:children]
|
||||
background:backgroundNode];
|
||||
|
||||
[self testLayoutSpec:layoutSpec sizeRange:sizeRange subnodes:subnodes identifier:identifier];
|
||||
}
|
||||
|
||||
@end
|
@ -1,311 +0,0 @@
|
||||
//
|
||||
// ArrayDiffingTests.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <AsyncDisplayKit/NSArray+Diffing.h>
|
||||
|
||||
@interface NSArray (ArrayDiffingTests)
|
||||
- (NSIndexSet *)_asdk_commonIndexesWithArray:(NSArray *)array compareBlock:(BOOL (^)(id lhs, id rhs))comparison;
|
||||
@end
|
||||
|
||||
@interface ArrayDiffingTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ArrayDiffingTests
|
||||
|
||||
- (void)testDiffingCommonIndexes
|
||||
{
|
||||
NSArray<NSArray *> *tests = @[
|
||||
@[
|
||||
@[@"bob", @"alice", @"dave"],
|
||||
@[@"bob", @"alice", @"dave", @"gary"],
|
||||
@[@0, @1, @2]
|
||||
],
|
||||
@[
|
||||
@[@"bob", @"alice", @"dave"],
|
||||
@[@"bob", @"gary", @"dave"],
|
||||
@[@0, @2]
|
||||
],
|
||||
@[
|
||||
@[@"bob", @"alice"],
|
||||
@[@"gary", @"dave"],
|
||||
@[],
|
||||
],
|
||||
@[
|
||||
@[@"bob", @"alice", @"dave"],
|
||||
@[],
|
||||
@[],
|
||||
],
|
||||
@[
|
||||
@[],
|
||||
@[@"bob", @"alice", @"dave"],
|
||||
@[],
|
||||
],
|
||||
];
|
||||
|
||||
for (NSArray *test in tests) {
|
||||
NSIndexSet *indexSet = [test[0] _asdk_commonIndexesWithArray:test[1] compareBlock:^BOOL(id lhs, id rhs) {
|
||||
return [lhs isEqual:rhs];
|
||||
}];
|
||||
NSMutableIndexSet *mutableIndexSet = [indexSet mutableCopy];
|
||||
|
||||
for (NSNumber *index in (NSArray *)test[2]) {
|
||||
XCTAssert([indexSet containsIndex:[index integerValue]]);
|
||||
[mutableIndexSet removeIndex:[index integerValue]];
|
||||
}
|
||||
|
||||
XCTAssert([mutableIndexSet count] == 0, @"Unaccounted deletions: %@", mutableIndexSet);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testDiffingInsertionsAndDeletions {
|
||||
NSArray<NSArray *> *tests = @[
|
||||
@[
|
||||
@[@"bob", @"alice", @"dave"],
|
||||
@[@"bob", @"alice", @"dave", @"gary"],
|
||||
@[@3],
|
||||
@[],
|
||||
],
|
||||
@[
|
||||
@[@"a", @"b", @"c", @"d"],
|
||||
@[@"d", @"c", @"b", @"a"],
|
||||
@[@1, @2, @3],
|
||||
@[@0, @1, @2],
|
||||
],
|
||||
@[
|
||||
@[@"bob", @"alice", @"dave"],
|
||||
@[@"bob", @"gary", @"alice", @"dave"],
|
||||
@[@1],
|
||||
@[],
|
||||
],
|
||||
@[
|
||||
@[@"bob", @"alice", @"dave"],
|
||||
@[@"bob", @"alice"],
|
||||
@[],
|
||||
@[@2],
|
||||
],
|
||||
@[
|
||||
@[@"bob", @"alice", @"dave"],
|
||||
@[],
|
||||
@[],
|
||||
@[@0, @1, @2],
|
||||
],
|
||||
@[
|
||||
@[@"bob", @"alice", @"dave"],
|
||||
@[@"gary", @"alice", @"dave", @"jack"],
|
||||
@[@0, @3],
|
||||
@[@0],
|
||||
],
|
||||
@[
|
||||
@[@"bob", @"alice", @"dave", @"judy", @"lynda", @"tony"],
|
||||
@[@"gary", @"bob", @"suzy", @"tony"],
|
||||
@[@0, @2],
|
||||
@[@1, @2, @3, @4],
|
||||
],
|
||||
@[
|
||||
@[@"bob", @"alice", @"dave", @"judy"],
|
||||
@[@"judy", @"dave", @"alice", @"bob"],
|
||||
@[@1, @2, @3],
|
||||
@[@0, @1, @2],
|
||||
],
|
||||
];
|
||||
|
||||
long n = 0;
|
||||
for (NSArray *test in tests) {
|
||||
NSIndexSet *insertions, *deletions;
|
||||
[test[0] asdk_diffWithArray:test[1] insertions:&insertions deletions:&deletions];
|
||||
NSMutableIndexSet *mutableInsertions = [insertions mutableCopy];
|
||||
NSMutableIndexSet *mutableDeletions = [deletions mutableCopy];
|
||||
|
||||
for (NSNumber *index in (NSArray *)test[2]) {
|
||||
XCTAssert([mutableInsertions containsIndex:[index integerValue]], @"Test #%ld: insertions %@ does not contain %@",
|
||||
n, insertions, index);
|
||||
[mutableInsertions removeIndex:[index integerValue]];
|
||||
}
|
||||
for (NSNumber *index in (NSArray *)test[3]) {
|
||||
XCTAssert([mutableDeletions containsIndex:[index integerValue]], @"Test #%ld: deletions %@ does not contain %@",
|
||||
n, deletions, index
|
||||
);
|
||||
[mutableDeletions removeIndex:[index integerValue]];
|
||||
}
|
||||
|
||||
XCTAssert([mutableInsertions count] == 0, @"Test #%ld: Unaccounted insertions: %@", n, mutableInsertions);
|
||||
XCTAssert([mutableDeletions count] == 0, @"Test #%ld: Unaccounted deletions: %@", n, mutableDeletions);
|
||||
n++;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testDiffingInsertsDeletesAndMoves
|
||||
{
|
||||
NSArray<NSArray *> *tests = @[
|
||||
@[
|
||||
@[@"a", @"b"],
|
||||
@[@"b", @"a"],
|
||||
@[],
|
||||
@[],
|
||||
@[[NSIndexPath indexPathWithIndexes:(NSUInteger[]) {1, 0} length:2],
|
||||
[NSIndexPath indexPathWithIndexes:(NSUInteger[]) {0, 1} length:2]
|
||||
]],
|
||||
@[
|
||||
@[@"bob", @"alice", @"dave"],
|
||||
@[@"bob", @"alice", @"dave", @"gary"],
|
||||
@[@3],
|
||||
@[],
|
||||
@[]],
|
||||
@[
|
||||
@[@"a", @"b", @"c", @"d"],
|
||||
@[@"d", @"c", @"b", @"a"],
|
||||
@[],
|
||||
@[],
|
||||
@[[NSIndexPath indexPathWithIndexes:(NSUInteger[]){3, 0} length:2],
|
||||
[NSIndexPath indexPathWithIndexes:(NSUInteger[]){2, 1} length:2],
|
||||
[NSIndexPath indexPathWithIndexes:(NSUInteger[]){1, 2} length:2],
|
||||
[NSIndexPath indexPathWithIndexes:(NSUInteger[]){0, 3} length:2]
|
||||
]],
|
||||
@[
|
||||
@[@"bob", @"alice", @"dave"],
|
||||
@[@"bob", @"gary", @"dave", @"alice"],
|
||||
@[@1],
|
||||
@[],
|
||||
@[[NSIndexPath indexPathWithIndexes:(NSUInteger[]) {1, 3} length:2]
|
||||
]],
|
||||
@[
|
||||
@[@"bob", @"alice", @"dave"],
|
||||
@[@"bob", @"alice"],
|
||||
@[],
|
||||
@[@2],
|
||||
@[]],
|
||||
@[
|
||||
@[@"bob", @"alice", @"dave"],
|
||||
@[],
|
||||
@[],
|
||||
@[@0, @1, @2],
|
||||
@[]],
|
||||
@[
|
||||
@[@"bob", @"alice", @"dave"],
|
||||
@[@"gary", @"alice", @"dave", @"jack"],
|
||||
@[@0, @3],
|
||||
@[@0],
|
||||
@[]],
|
||||
@[
|
||||
@[@"bob", @"alice", @"dave", @"judy", @"lynda", @"tony"],
|
||||
@[@"gary", @"bob", @"suzy", @"tony"],
|
||||
@[@0, @2],
|
||||
@[@1, @2, @3, @4],
|
||||
@[[NSIndexPath indexPathWithIndexes:(NSUInteger[]){0, 1} length:2],
|
||||
[NSIndexPath indexPathWithIndexes:(NSUInteger[]){5, 3} length:2]
|
||||
]],
|
||||
@[
|
||||
@[@"bob", @"alice", @"dave", @"judy"],
|
||||
@[@"judy", @"dave", @"alice", @"bob"],
|
||||
@[],
|
||||
@[],
|
||||
@[[NSIndexPath indexPathWithIndexes:(NSUInteger[]){3, 0} length:2],
|
||||
[NSIndexPath indexPathWithIndexes:(NSUInteger[]){2, 1} length:2],
|
||||
[NSIndexPath indexPathWithIndexes:(NSUInteger[]){1, 2} length:2],
|
||||
[NSIndexPath indexPathWithIndexes:(NSUInteger[]){0, 3} length:2]
|
||||
]]
|
||||
|
||||
];
|
||||
|
||||
long n = 0;
|
||||
for (NSArray *test in tests) {
|
||||
NSIndexSet *insertions, *deletions;
|
||||
NSArray<NSIndexPath *> *moves;
|
||||
[test[0] asdk_diffWithArray:test[1] insertions:&insertions deletions:&deletions moves:&moves];
|
||||
NSMutableIndexSet *mutableInsertions = [insertions mutableCopy];
|
||||
NSMutableIndexSet *mutableDeletions = [deletions mutableCopy];
|
||||
|
||||
for (NSNumber *index in (NSArray *) test[2]) {
|
||||
XCTAssert([mutableInsertions containsIndex:[index integerValue]], @"Test #%ld, insertions does not contain %ld",
|
||||
n, (long)[index integerValue]);
|
||||
[mutableInsertions removeIndex:(NSUInteger) [index integerValue]];
|
||||
}
|
||||
for (NSNumber *index in (NSArray *) test[3]) {
|
||||
XCTAssert([mutableDeletions containsIndex:[index integerValue]], @"Test #%ld, deletions does not contain %ld",
|
||||
n, (long)[index integerValue]);
|
||||
[mutableDeletions removeIndex:(NSUInteger) [index integerValue]];
|
||||
}
|
||||
|
||||
XCTAssert([mutableInsertions count] == 0, @"Test #%ld, Unaccounted insertions: %@", n, mutableInsertions);
|
||||
XCTAssert([mutableDeletions count] == 0, @"Test #%ld, Unaccounted deletions: %@", n, mutableDeletions);
|
||||
|
||||
XCTAssert([moves isEqual:test[4]], @"Test #%ld, %@ !isEqual: %@", n, moves, test[4]);
|
||||
n++;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testArrayDiffingRebuildingWithRandomElements
|
||||
{
|
||||
NSArray<NSNumber *> *original = @[];
|
||||
NSArray<NSNumber *> *pending = @[];
|
||||
|
||||
NSIndexSet *insertions = nil;
|
||||
NSIndexSet *deletions = nil;
|
||||
NSArray<NSIndexPath *> *moves;
|
||||
|
||||
for (int testNumber = 0; testNumber <= 25; testNumber++) {
|
||||
int len = arc4random_uniform(10);
|
||||
for (int j = 0; j < len; j++) {
|
||||
original = [original arrayByAddingObject:@(arc4random_uniform(25))];
|
||||
}
|
||||
len = arc4random_uniform(10);
|
||||
for (int j = 0; j < len; j++) {
|
||||
pending = [pending arrayByAddingObject:@(arc4random_uniform(25))];
|
||||
}
|
||||
// Some sequences that presented issues in the past:
|
||||
if (testNumber == 0) {
|
||||
original = @[@20, @11, @14, @2, @14, @5, @4, @18, @0];
|
||||
pending = @[@9, @18, @18, @19, @20, @18, @22, @10, @3];
|
||||
}
|
||||
if (testNumber == 1) {
|
||||
original = @[@5, @9, @21, @11, @5, @9, @8];
|
||||
pending = @[@2, @12, @17, @19, @9, @1, @8, @5, @21];
|
||||
}
|
||||
if (testNumber == 2) {
|
||||
original = @[@14, @14, @12, @8, @20, @4, @0, @10];
|
||||
pending = @[@14];
|
||||
}
|
||||
|
||||
[original asdk_diffWithArray:pending insertions:&insertions deletions:&deletions moves:&moves];
|
||||
|
||||
NSMutableArray *deletionsList = [NSMutableArray new];
|
||||
[deletions enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
|
||||
[deletionsList addObject:@(idx)];
|
||||
}];
|
||||
NSMutableArray *insertionsList = [NSMutableArray new];
|
||||
[insertions enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
|
||||
[insertionsList addObject:@(idx)];
|
||||
}];
|
||||
|
||||
NSUInteger i = 0;
|
||||
NSUInteger j = 0;
|
||||
NSMutableArray<NSNumber *> *test = [NSMutableArray new];
|
||||
for (NSUInteger count = 0; count < [pending count]; count++) {
|
||||
if (i < [insertionsList count] && [insertionsList[i] unsignedIntegerValue] == count) {
|
||||
[test addObject:pending[[insertionsList[i] unsignedIntegerValue]]];
|
||||
i++;
|
||||
} else if (j < [moves count] && [moves[j] indexAtPosition:1] == count) {
|
||||
[test addObject:original[[moves[j] indexAtPosition:0]]];
|
||||
j++;
|
||||
} else {
|
||||
[test addObject:original[count]];
|
||||
}
|
||||
}
|
||||
|
||||
XCTAssert([test isEqualToArray:pending], @"Did not mutate to expected new array:\n [%@] -> [%@], actual: [%@]\ninsertions: %@\nmoves: %@\ndeletions: %@",
|
||||
[original componentsJoinedByString:@","], [pending componentsJoinedByString:@","], [test componentsJoinedByString:@","],
|
||||
insertions, moves, deletions);
|
||||
original = @[];
|
||||
pending = @[];
|
||||
}
|
||||
}
|
||||
@end
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user