Skip to content
This repository was archived by the owner on Dec 6, 2018. It is now read-only.
Merged
8 changes: 8 additions & 0 deletions FRP.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
5E730B0E1815F3E4003FCB43 /* FRPGalleryViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E730B0D1815F3E4003FCB43 /* FRPGalleryViewModel.m */; };
5E730B101815F78B003FCB43 /* FRPGalleryViewModelTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E730B0F1815F78B003FCB43 /* FRPGalleryViewModelTests.m */; };
5E730B141815FE97003FCB43 /* FRPFullSizePhotoViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E730B131815FE97003FCB43 /* FRPFullSizePhotoViewModel.m */; };
5E93AD9A186C781000795C9E /* FRPFullSizePhotoViewModelTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E93AD99186C781000795C9E /* FRPFullSizePhotoViewModelTests.m */; };
5E93AD9C186C80DE00795C9E /* FRPPhotoViewModelTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E93AD9B186C80DE00795C9E /* FRPPhotoViewModelTests.m */; };
5EAD1F5818173A3200C67860 /* FRPPhotoViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 5EAD1F5718173A3200C67860 /* FRPPhotoViewModel.m */; };
5EAD1F5C18173F1500C67860 /* FRPLoginViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 5EAD1F5B18173F1500C67860 /* FRPLoginViewModel.m */; };
5EAD1F601817418800C67860 /* FRPPhotoDetailViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 5EAD1F5F1817418800C67860 /* FRPPhotoDetailViewModel.m */; };
Expand Down Expand Up @@ -63,6 +65,8 @@
5E730B0F1815F78B003FCB43 /* FRPGalleryViewModelTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FRPGalleryViewModelTests.m; sourceTree = "<group>"; };
5E730B121815FE97003FCB43 /* FRPFullSizePhotoViewModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FRPFullSizePhotoViewModel.h; sourceTree = "<group>"; };
5E730B131815FE97003FCB43 /* FRPFullSizePhotoViewModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FRPFullSizePhotoViewModel.m; sourceTree = "<group>"; };
5E93AD99186C781000795C9E /* FRPFullSizePhotoViewModelTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FRPFullSizePhotoViewModelTests.m; sourceTree = "<group>"; };
5E93AD9B186C80DE00795C9E /* FRPPhotoViewModelTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FRPPhotoViewModelTests.m; sourceTree = "<group>"; };
5EAD1F5618173A3200C67860 /* FRPPhotoViewModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FRPPhotoViewModel.h; sourceTree = "<group>"; };
5EAD1F5718173A3200C67860 /* FRPPhotoViewModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FRPPhotoViewModel.m; sourceTree = "<group>"; };
5EAD1F5A18173F1500C67860 /* FRPLoginViewModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FRPLoginViewModel.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -282,6 +286,8 @@
isa = PBXGroup;
children = (
5E730B0F1815F78B003FCB43 /* FRPGalleryViewModelTests.m */,
5E93AD99186C781000795C9E /* FRPFullSizePhotoViewModelTests.m */,
5E93AD9B186C80DE00795C9E /* FRPPhotoViewModelTests.m */,
5EBE2B1A180B07D0007B6BF3 /* Supporting Files */,
);
path = FRPTests;
Expand Down Expand Up @@ -482,7 +488,9 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
5E93AD9A186C781000795C9E /* FRPFullSizePhotoViewModelTests.m in Sources */,
5E730B101815F78B003FCB43 /* FRPGalleryViewModelTests.m in Sources */,
5E93AD9C186C80DE00795C9E /* FRPPhotoViewModelTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
2 changes: 1 addition & 1 deletion FRP/FRPFullSizePhotoViewModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@
@interface FRPFullSizePhotoViewModel : RVMViewModel

-(instancetype)initWithPhotoArray:(NSArray *)photoArray initialPhotoIndex:(NSInteger)initialPhotoIndex;
-(FRPPhotoModel *)photoModelAtIndex:(NSInteger)index;

@property (nonatomic, readonly, strong) NSArray *model;
@property (nonatomic, readonly) NSInteger initialPhotoIndex;

@property (nonatomic, readonly) NSString *initialPhotoName;
-(FRPPhotoModel *)photoModelAtIndex:(NSInteger)index;

@end
13 changes: 12 additions & 1 deletion FRP/FRPFullSizePhotoViewModel.m
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,14 @@ -(instancetype)initWithPhotoArray:(NSArray *)photoArray initialPhotoIndex:(NSInt
}

-(NSString *)initialPhotoName {
return [self.model[self.initialPhotoIndex] photoName];
FRPPhotoModel *photoModel = [self initialPhotoModel];
return [photoModel photoName];
}

//-(NSString *)initialPhotoName {
// return [self.model[self.initialPhotoIndex] photoName];
//}

-(FRPPhotoModel *)photoModelAtIndex:(NSInteger)index {
if (index < 0 || index > self.model.count - 1) {
// Index was out of bounds, return nil
Expand All @@ -42,4 +47,10 @@ -(FRPPhotoModel *)photoModelAtIndex:(NSInteger)index {
}
}

#pragma mark - Private Methods

-(FRPPhotoModel *)initialPhotoModel {
return [self photoModelAtIndex:self.initialPhotoIndex];
}

@end
8 changes: 6 additions & 2 deletions FRP/FRPGalleryViewModel.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//
//
// FRPGalleryViewModel.m
// FRP
//
Expand All @@ -21,9 +21,13 @@ -(instancetype)init {
self = [super init];
if (!self) return nil;

RAC(self, model) = [[[FRPPhotoImporter importPhotos] logError] catchTo:[RACSignal empty]];
RAC(self, model) = [self importPhotosSignal];

return self;
}

-(RACSignal *)importPhotosSignal {
return [[[FRPPhotoImporter importPhotos] logError] catchTo:[RACSignal empty]];
}

@end
6 changes: 3 additions & 3 deletions FRP/FRPPhotoImporter.m
Original file line number Diff line number Diff line change
Expand Up @@ -107,17 +107,17 @@ +(void)configurePhotoModel:(FRPPhotoModel *)photoModel withDictionary:(NSDiction
}
}

+(NSString *)urlForImageSize:(NSInteger)size inDictionary:(NSDictionary *)dictionary {
+(NSString *)urlForImageSize:(NSInteger)size inDictionary:(NSArray *)array {
/*
images = (
(
{
size = 3;
url = "http://ppcdn.500px.org/49204370/b125a49d0863e0ba05d8196072b055876159f33e/3.jpg";
}
);
*/

return [[[[[dictionary rac_sequence] filter:^BOOL(NSDictionary *value) {
return [[[[[array rac_sequence] filter:^BOOL(NSDictionary *value) {
return [value[@"size"] integerValue] == size;
}] map:^id(id value) {
return value[@"url"];
Expand Down
19 changes: 11 additions & 8 deletions FRP/FRPPhotoViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -38,27 +38,30 @@ -(instancetype)initWithViewModel:(FRPPhotoViewModel *)viewModel index:(NSInteger
return self;
}

-(void)viewDidLoad
{
-(void)viewDidLoad {
[super viewDidLoad];

// Configure self's view
self.view.backgroundColor = [UIColor blackColor];

// Configure subviews
UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
RAC(imageView, image) = self.viewModel.photoImageSignal;
RAC(imageView, image) = RACObserve(self.viewModel, photoImage);
imageView.contentMode = UIViewContentModeScaleAspectFit;
[self.view addSubview:imageView];
self.imageView = imageView;

[self.viewModel.didBecomeActiveSignal subscribeNext:^(id x) {
[SVProgressHUD dismiss];

[RACObserve(self.viewModel, loading) subscribeNext:^(NSNumber *loading){
if (loading.boolValue) {
[SVProgressHUD show];
} else {
[SVProgressHUD dismiss];
}
}];
}

-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];

self.viewModel.active = YES;
}
Expand Down
3 changes: 2 additions & 1 deletion FRP/FRPPhotoViewModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@

@property (nonatomic, readonly) FRPPhotoModel *model;

@property (nonatomic, readonly) RACSignal *photoImageSignal;
@property (nonatomic, readonly) UIImage *photoImage;
@property (nonatomic, readonly, getter = isLoading) BOOL loading;

-(NSString *)photoName;

Expand Down
28 changes: 21 additions & 7 deletions FRP/FRPPhotoViewModel.m
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@

@interface FRPPhotoViewModel ()

@property (nonatomic, strong) RACSignal *photoImageSignal;
@property (nonatomic, strong) UIImage *photoImage;
@property (nonatomic, assign, getter = isLoading) BOOL loading;

@end

Expand All @@ -27,22 +28,35 @@ -(instancetype)initWithModel:(FRPPhotoModel *)photoModel {
@weakify(self);
[self.didBecomeActiveSignal subscribeNext:^(id x) {
@strongify(self);
[[FRPPhotoImporter fetchPhotoDetails:self.model] subscribeError:^(NSError *error) {
NSLog(@"Could not fetch photo details: %@", error);
} completed:^{
NSLog(@"Fetched photo details.");
}];
[self downloadPhotoModelDetails];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's probably a bit much for your book, but this operation can be written without any state or pesky strongifying:

RAC(self, loading) = [[[RACObserve(self, model)
    sample:self.didBecomeActiveSignal]
    map:^(FRPPhotoModel *model) {
        return [[[[[[FRPPhotoImporter
            fetchPhotoDetails:model]
            catch:^(NSError *error) {
                NSLog(@"Could not fetch photo details: %@", error);
                return [RACSignal empty];
            }]
            doCompleted:^{
                NSLog(@"Fetched photo details.");
            }]
            ignoreValues]
            startWith:@NO]
            concat:[RACSignal return:@YES]];
    }]
    concat];

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a great idea!

}];

self.photoImageSignal = [RACObserve(self.model, fullsizedData) map:^id(id value) {
RAC(self, photoImage) = [RACObserve(self.model, fullsizedData) map:^id(id value) {
return [UIImage imageWithData:value];
}];

return self;
}

#pragma mark - Public Methods

-(NSString *)photoName {
return self.model.photoName;
}

#pragma mark - Private Methods

-(void)downloadPhotoModelDetails {
self.loading = YES;

@weakify(self);
[[FRPPhotoImporter fetchPhotoDetails:self.model] subscribeError:^(NSError *error) {
NSLog(@"Could not fetch photo details: %@", error);
} completed:^{
@strongify(self);
self.loading = NO;
NSLog(@"Fetched photo details.");
}];
}

@end
77 changes: 77 additions & 0 deletions FRPTests/FRPFullSizePhotoViewModelTests.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//
// FRPFullSizePhotoViewModelTests.m
// FRP
//
// Created by Ash Furrow on 12/26/2013.
// Copyright (c) 2013 Ash Furrow. All rights reserved.
//

#import <Specta/Specta.h>
#define EXP_SHORTHAND
#import <Expecta/Expecta.h>
#import <OCMock/OCMock.h>

#import "FRPFullSizePhotoViewModel.h"
#import "FRPPhotoModel.h"

@interface FRPFullSizePhotoViewModel ()

-(FRPPhotoModel *)initialPhotoModel;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would declare this in a private header instead, so you're not duplicating it and potentially opening the door to confusing errors in your tests.


@end

SpecBegin(FRPFullSizePhotoViewModel)

describe(@"FRPFullSizePhotomodel", ^{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This outer describe isn't strictly necessary, though it doesn't harm anything by being here.

it (@"should assign correct attributes when initialized", ^{
NSArray *model = @[];
NSInteger initialPhotoIndex = 1337;

FRPFullSizePhotoViewModel *viewModel = [[FRPFullSizePhotoViewModel alloc] initWithPhotoArray:model initialPhotoIndex:initialPhotoIndex];

expect(model).to.equal(viewModel.model);
expect(initialPhotoIndex).to.equal(viewModel.initialPhotoIndex);
});

it (@"should return nil for an out-of-bounds photo index", ^{
NSArray *model = @[[NSObject new]];
NSInteger initialPhotoIndex = 0;

FRPFullSizePhotoViewModel *viewModel = [[FRPFullSizePhotoViewModel alloc] initWithPhotoArray:model initialPhotoIndex:initialPhotoIndex];

id subzeroModel = [viewModel photoModelAtIndex:-1];
expect(subzeroModel).to.beNil();

id aboveBoundsModel = [viewModel photoModelAtIndex:model.count];
expect(aboveBoundsModel).to.beNil();
});

it (@"should return the correct model for photoModelAtIndex:", ^{
id photoModel = [NSObject new];
NSArray *model = @[photoModel];
NSInteger initialPhotoIndex = 0;

FRPFullSizePhotoViewModel *viewModel = [[FRPFullSizePhotoViewModel alloc] initWithPhotoArray:model initialPhotoIndex:initialPhotoIndex];

id returnedModel = [viewModel photoModelAtIndex:0];
expect(returnedModel).to.equal(photoModel);
});

it (@"should return the correct initial photo model", ^{
NSArray *model = @[[NSObject new]];
NSInteger initialPhotoIndex = 0;

FRPFullSizePhotoViewModel *viewModel = [[FRPFullSizePhotoViewModel alloc] initWithPhotoArray:model initialPhotoIndex:initialPhotoIndex];
id mockViewModel = [OCMockObject partialMockForObject:viewModel];

[[[mockViewModel expect] andReturn:model[0]] photoModelAtIndex:initialPhotoIndex];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This kinda comes down to personal testing style, but I detest mocking. I don't think it's representative of your API's behavior to consumers, and smells of poor decomposition if it's really necessary.

In this specific case, couldn't you just verify that initialPhotoModel returns the same thing as "photo model at the initial index?"


id returnedObject = [mockViewModel initialPhotoModel];

expect(returnedObject).to.equal(model[0]);

[mockViewModel verify];
});
});

SpecEnd
24 changes: 13 additions & 11 deletions FRPTests/FRPGalleryViewModelTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,24 @@
#import <OCMock/OCMock.h>

#import "FRPGalleryViewModel.h"
#import "FRPPhotoImporter.h"

@interface FRPGalleryViewModel ()

-(RACSignal *)importPhotosSignal;

@end

SpecBegin(FRPGalleryViewModel)

describe(@"FRPGalleryViewModel", ^{
beforeAll(^{
// This is run once and only once before all of the examples
// in this group and before any beforeEach blocks.
});

beforeEach(^{
// This is run before each example.
});

it(@"should be initialized and call importPhotos", ^{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this test valuable? In other words, what result are you actually trying to test here? I don't think you care about the actual method call as much as the effect.

STAssertTrue(false, @"Test not implemented.");
id mockObject = [OCMockObject mockForClass:[FRPGalleryViewModel class]];
[[[mockObject expect] andReturn:[RACSignal empty]] importPhotosSignal];

mockObject = [mockObject init];

[mockObject verify];
[mockObject stopMocking];
});
});

Expand Down
Loading