Initial pass with diffing fixtures

This commit is contained in:
Adlai Holler
2017-06-30 11:33:18 -07:00
committed by Adlai Holler
parent 58641a85ab
commit 3aee5adc7d
21 changed files with 1191 additions and 2 deletions
@@ -0,0 +1,41 @@
//
// ASDKDiffingFixtures.h
// ASDKDiffing
//
// Created by Adlai Holler on 6/20/17.
//
//
#import <Foundation/Foundation.h>
#import <IGListKit/IGListDiffKit.h>
#import <AsyncDisplayKit/AsyncDisplayKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface ASTViewModel : NSObject <IGListDiffable>
@property (nonatomic, readonly) NSInteger identifier; // Globally unique.
@property (nonatomic, readonly) NSInteger contents; // Globally unique. Auto-increments.
@property (nonatomic, nullable, copy) NSString *debugName; // Helpful. Copied when updating.
// Returns a new view model with the next `contents` value.
- (instancetype)viewModelByUpdating;
+ (void)reset;
@end
@interface ASTItem : ASTViewModel
@end
@interface ASTSection : ASTViewModel
@end
@interface ASTSectionCtrl : ASDiffingSectionController
@end
@interface ASTItemNode : ASCellNode
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,136 @@
//
// ASDKDiffingFixtures.m
// ASDKDiffing
//
// Created by Adlai Holler on 6/20/17.
//
//
#import "ASDKDiffingFixtures.h"
#import <AsyncDisplayKit/AsyncDisplayKit.h>
#import <stdatomic.h>
atomic_uint g_identifier;
atomic_uint g_val;
void ASTReset() {
atomic_store(&g_identifier, 0);
atomic_store(&g_val, 0);
}
NSInteger ASTNextIdentifier() {
return atomic_fetch_add(&g_identifier, 1);
}
NSInteger ASTNextContents() {
return atomic_fetch_add(&g_val, 1);
}
id ASTWithDebugNamesTable(id (NS_NOESCAPE ^block)(NSMapTable<NSNumber *, NSString *> *table)) {
static NSMapTable<NSNumber *, NSString *> *table;
static NSLock *lock;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
table = [NSMapTable strongToStrongObjectsMapTable];
lock = [NSLock new];
});
[lock lock];
id result = block(table);
[lock unlock];
return result;
}
@implementation ASTViewModel
- (instancetype)init
{
if (self = [super init]) {
_identifier = ASTNextIdentifier();
_contents = ASTNextContents();
}
return self;
}
- (instancetype)viewModelByUpdating
{
ASTViewModel *vm = [[self class] alloc]; // No init. We control the values manually.
vm->_identifier = _identifier;
vm->_contents = ASTNextContents();
return vm;
}
- (NSString *)debugDescription
{
return ASObjectDescriptionMake(self, [self propertiesForDebugDescription]);
}
- (void)setDebugName:(NSString *)debugName
{
ASTWithDebugNamesTable(^id(NSMapTable<NSNumber *,NSString *> *table) {
[table setObject:[debugName copy] forKey:@(self.identifier)];
return nil;
});
}
- (NSString *)debugName
{
return ASTWithDebugNamesTable(^id(NSMapTable<NSNumber *,NSString *> *table) {
return [table objectForKey:@(self.identifier)];
});
}
- (NSMutableArray<NSDictionary *> *)propertiesForDebugDescription
{
__auto_type array = [NSMutableArray array];
NSString *name = self.debugName;
if (name.length > 0) {
[array addObject:@{ @"name": name }];
} else {
[array addObject:@{ @"id": @(self.identifier) }];
}
[array addObject:@{ @"contents": @(self.contents) }];
return array;
}
+ (void)reset
{
ASTReset();
}
#pragma mark - IGListDiffable
- (BOOL)isEqualToDiffableObject:(id<IGListDiffable>)object
{
ASTViewModel *vm = (ASTViewModel *)object;
NSParameterAssert([(id)object class] == [self class]);
NSParameterAssert(vm.identifier == self.identifier);
return vm.contents == self.contents;
}
- (id<NSObject>)diffIdentifier
{
return @(self.identifier);
}
@end
@implementation ASTSection
@end
@implementation ASTItem
@end
@implementation ASTSectionCtrl
- (ASCellNodeBlock)nodeBlockForItemAtIndex:(NSInteger)index
{
return ^{
return [ASTItemNode new];
};
}
@end
@implementation ASTItemNode
@end
@@ -0,0 +1,106 @@
//
// ASDKDiffingTests.m
// ASDKDiffingTests
//
// Created by Adlai Holler on 6/20/17.
//
//
#import <XCTest/XCTest.h>
#import "ASTestCase.h"
#import "ASDKDiffingFixtures.h"
#import <AsyncDisplayKit/AsyncDisplayKit.h>
#import <OCMock/OCMock.h>
@interface ASDKDiffingTests : ASTestCase
@end
@implementation ASDKDiffingTests {
ASCollectionNode *collectionNode;
UIWindow *window;
id mockDataSource;
NSMutableArray<ASTSection *> *sections;
}
- (void)setUp {
[super setUp];
[ASTViewModel reset];
sections = [NSMutableArray array];
collectionNode = [[ASCollectionNode alloc] initWithCollectionViewLayout:[UICollectionViewFlowLayout new]];
mockDataSource = OCMStrictProtocolMock(@protocol(ASCollectionModernDataSource));
collectionNode.modernDataSource = mockDataSource;
window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
UIViewController *vc = [[UIViewController alloc] init];
collectionNode.view.frame = vc.view.bounds;
collectionNode.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[vc.view addSubnode:collectionNode];
window.rootViewController = vc;
[window makeKeyAndVisible];
}
- (void)testThatFixturesWork
{
ASTSection *s1 = [ASTSection new];
ASTSection *s1_2 = [s1 viewModelByUpdating];
s1.debugName = @"Hello";
ASTSection *s2 = [ASTSection new];
ASTItem *i1 = [ASTItem new];
// -setDebugName works
XCTAssertEqualObjects(s1.debugName, @"Hello");
// debugName shared between versions even if set after
XCTAssertEqualObjects(s1_2.debugName, s1.debugName);
// identifier starts at 0
XCTAssertEqual(s1.identifier, 0);
// identifier incs each new object
XCTAssertEqual(s2.identifier, 1);
// identifier global between classes
XCTAssertEqual(i1.identifier, 2);
// identifier shared between versions
XCTAssertEqual(s1_2.identifier, s1.identifier);
// contents inc each new object
XCTAssertEqual(s1_2.contents, s1.contents + 1);
// contents global between classes
XCTAssertEqual(i1.contents, 3);
}
- (void)testInitialDataLoad
{
NSMapTable<ASTSection *, id> *sectionToCtrl = [NSMapTable weakToWeakObjectsMapTable];
// It gathers section view models.
OCMExpect([mockDataSource sectionViewModelsForCollectionNode:collectionNode])
.andReturn(sections);
// It gathers section controllers for each model.
__auto_type sectionControllers = [NSMutableArray array];
for (ASTSection *sectionModel in sections) {
ASTSectionCtrl *ctrl = OCMPartialMock([ASTSectionCtrl new]);
[sectionControllers addObject:ctrl];
[sectionToCtrl setObject:ctrl forKey:sectionModel];
OCMExpect([mockDataSource collectionNode:collectionNode controllerForSection:sectionModel])
.andReturn(ctrl);
}
// For each section model
for (ASTSection *sectionModel in sectionToCtrl) {
id mockCtrl = [sectionToCtrl objectForKey:sectionModel];
// It sets the new view model to the section controller
OCMExpect([mockCtrl setViewModel:sectionModel])
.andForwardToRealObject();
// It fetches a new array of item view models
OCMExpect([mockCtrl generateItemViewModels])
.andForwardToRealObject();
}
[window layoutIfNeeded];
}
@end
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>