//
//  RKFetchedResultsTableControllerTest.m
//  RestKit
//
//  Created by Jeff Arena on 8/12/11.
//  Copyright (c) 2009-2012 RestKit. All rights reserved.
//

#import "RKTestEnvironment.h"
#import "RKFetchedResultsTableController.h"
#import "RKManagedObjectStore.h"
#import "RKEntityMapping.h"
#import "RKHuman.h"
#import "RKEvent.h"
#import "RKAbstractTableController_Internals.h"
#import "RKManagedObjectCaching.h"
#import "RKTableControllerTestDelegate.h"

// Expose the object loader delegate for testing purposes...
@interface RKFetchedResultsTableController () <RKObjectLoaderDelegate>

- (BOOL)isHeaderSection:(NSUInteger)section;
- (BOOL)isHeaderRow:(NSUInteger)row;
- (BOOL)isFooterSection:(NSUInteger)section;
- (BOOL)isFooterRow:(NSUInteger)row;
- (BOOL)isEmptySection:(NSUInteger)section;
- (BOOL)isEmptyRow:(NSUInteger)row;
- (BOOL)isHeaderIndexPath:(NSIndexPath *)indexPath;
- (BOOL)isFooterIndexPath:(NSIndexPath *)indexPath;
- (BOOL)isEmptyItemIndexPath:(NSIndexPath *)indexPath;
- (NSIndexPath *)fetchedResultsIndexPathForIndexPath:(NSIndexPath *)indexPath;

@end

@interface RKFetchedResultsTableControllerSpecViewController : UITableViewController
@end

@implementation RKFetchedResultsTableControllerSpecViewController
@end

@interface RKFetchedResultsTableControllerTest : RKTestCase
@property (nonatomic, readonly) NSManagedObjectContext *managedObjectContext;
@end

@implementation RKFetchedResultsTableControllerTest

- (void)setUp
{
    [RKTestFactory setUp];

    [[[[UIApplication sharedApplication] windows] objectAtIndex:0] setRootViewController:nil];
}

- (void)tearDown
{
    [RKTestFactory tearDown];
}

- (void)bootstrapStoreAndCache
{
    RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore];
    RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"RKHuman"inManagedObjectStore:managedObjectStore];
    [humanMapping mapKeyPath:@"id" toAttribute:@"railsID"];
    [humanMapping mapAttributes:@"name", nil];
    humanMapping.primaryKeyAttribute = @"railsID";

    assertThatInt([managedObjectStore.primaryManagedObjectContext countForEntityForName:@"RKHuman"  predicate:nil error:nil], is(equalToInt(0)));
    RKHuman *blake = [managedObjectStore.primaryManagedObjectContext insertNewObjectForEntityForName:@"RKHuman"];
    blake.railsID = [NSNumber numberWithInt:1234];
    blake.name = @"blake";
    RKHuman *other = [managedObjectStore.primaryManagedObjectContext insertNewObjectForEntityForName:@"RKHuman"];
    other.railsID = [NSNumber numberWithInt:5678];
    other.name = @"other";
    NSError *error = nil;
    [managedObjectStore.primaryManagedObjectContext save:&error];
    assertThat(error, is(nilValue()));
    assertThatInt([managedObjectStore.primaryManagedObjectContext countForEntityForName:@"RKHuman"  predicate:nil error:nil], is(equalToInt(2)));

    RKObjectManager *objectManager = [RKTestFactory objectManager];
    [objectManager.mappingProvider setMapping:humanMapping forKeyPath:@"human"];
    objectManager.managedObjectStore = managedObjectStore;

    [objectManager.mappingProvider setObjectMapping:humanMapping forResourcePathPattern:@"/JSON/humans/all\\.json" withFetchRequestBlock:^NSFetchRequest *(NSString *resourcePath) {
        NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"RKHuman"];
        fetchRequest.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES]];
        return fetchRequest;
    }];

    [objectManager.mappingProvider setObjectMapping:humanMapping forResourcePathPattern:@"/JSON/humans/empty\\.json" withFetchRequestBlock:^NSFetchRequest *(NSString *resourcePath) {
        NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"RKHuman"];
        fetchRequest.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES]];
        return fetchRequest;
    }];
}

- (NSManagedObjectContext *)managedObjectContext
{
    return [[RKObjectManager sharedManager].managedObjectStore mainQueueManagedObjectContext];
}

- (void)bootstrapNakedObjectStoreAndCache
{
    RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore];
    RKEntityMapping *eventMapping = [RKEntityMapping mappingForEntityForName:@"RKEvent"inManagedObjectStore:managedObjectStore];
    [eventMapping mapKeyPath:@"event_id" toAttribute:@"eventID"];
    [eventMapping mapKeyPath:@"type" toAttribute:@"eventType"];
    [eventMapping mapAttributes:@"location", @"summary", nil];
    eventMapping.primaryKeyAttribute = @"eventID";

    assertThatInt([managedObjectStore.primaryManagedObjectContext countForEntityForName:@"RKEvent"  predicate:nil error:nil], is(equalToInt(0)));
    RKEvent *nakedEvent = [managedObjectStore.primaryManagedObjectContext insertNewObjectForEntityForName:@"RKEvent"];
    nakedEvent.eventID = @"RK4424";
    nakedEvent.eventType = @"Concert";
    nakedEvent.location = @"Performance Hall";
    nakedEvent.summary = @"Shindig";
    NSError *error = nil;
    [managedObjectStore.primaryManagedObjectContext save:&error];
    assertThat(error, is(nilValue()));
    assertThatInt([managedObjectStore.primaryManagedObjectContext countForEntityForName:@"RKEvent"  predicate:nil error:nil], is(equalToInt(1)));

    RKObjectManager *objectManager = [RKTestFactory objectManager];
    [objectManager.mappingProvider addObjectMapping:eventMapping];
    objectManager.managedObjectStore = managedObjectStore;

    id mockMappingProvider = [OCMockObject partialMockForObject:objectManager.mappingProvider];

    NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"RKEvent"];
    fetchRequest.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"eventType" ascending:YES]];
    [[[mockMappingProvider stub] andReturn:fetchRequest] fetchRequestForResourcePath:@"/JSON/NakedEvents.json"];
}

- (void)bootstrapEmptyStoreAndCache
{
    RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore];
    RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"RKHuman"inManagedObjectStore:managedObjectStore];
    [humanMapping mapKeyPath:@"id" toAttribute:@"railsID"];
    [humanMapping mapAttributes:@"name", nil];
    humanMapping.primaryKeyAttribute = @"railsID";

    assertThatInt([managedObjectStore.primaryManagedObjectContext countForEntityForName:@"RKHuman"  predicate:nil error:nil], is(equalToInt(0)));

    RKObjectManager *objectManager = [RKTestFactory objectManager];
    [objectManager.mappingProvider setMapping:humanMapping forKeyPath:@"human"];
    objectManager.managedObjectStore = managedObjectStore;

    id mockMappingProvider = [OCMockObject partialMockForObject:objectManager.mappingProvider];
    NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"RKHuman"];
    fetchRequest.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES]];

    [[[mockMappingProvider stub] andReturn:fetchRequest] fetchRequestForResourcePath:@"/JSON/humans/all.json"];
    [[[mockMappingProvider stub] andReturn:fetchRequest] fetchRequestForResourcePath:@"/empty/array"];
}

- (void)stubObjectManagerToOnline
{
    RKObjectManager *objectManager = [RKObjectManager sharedManager];
    id mockManager = [OCMockObject partialMockForObject:objectManager];
    [mockManager setExpectationOrderMatters:YES];
    RKObjectManagerNetworkStatus networkStatus = RKObjectManagerNetworkStatusOnline;
    [[[mockManager stub] andReturnValue:OCMOCK_VALUE(networkStatus)] networkStatus];
    BOOL online = YES; // Initial online state for table
    [[[mockManager stub] andReturnValue:OCMOCK_VALUE(online)] isOnline];
}

- (void)testLoadWithATableViewControllerAndResourcePath
{
    [self bootstrapStoreAndCache];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController = [RKFetchedResultsTableController tableControllerForTableViewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    [tableController loadTable];

    assertThat(tableController.viewController, is(equalTo(viewController)));
    assertThat(tableController.tableView, is(equalTo(viewController.tableView)));
    assertThat(tableController.resourcePath, is(equalTo(@"/JSON/humans/all.json")));
}

- (void)testLoadWithATableViewControllerAndResourcePathFromNakedObjects
{
    [self bootstrapNakedObjectStoreAndCache];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController = [RKFetchedResultsTableController tableControllerForTableViewController:viewController];
    tableController.resourcePath = @"/JSON/NakedEvents.json";
    tableController.managedObjectContext = self.managedObjectContext;
    [tableController setObjectMappingForClass:[RKEvent class]];
    [tableController loadTable];

    assertThat(tableController.viewController, is(equalTo(viewController)));
    assertThat(tableController.tableView, is(equalTo(viewController.tableView)));
    assertThat(tableController.resourcePath, is(equalTo(@"/JSON/NakedEvents.json")));

    RKTableViewCellMapping *cellMapping = [RKTableViewCellMapping mappingForClass:[UITableViewCell class]];
    [cellMapping mapKeyPath:@"summary" toAttribute:@"textLabel.text"];
    RKTableViewCellMappings *mappings = [RKTableViewCellMappings new];
    [mappings setCellMapping:cellMapping forClass:[RKEvent class]];
    tableController.cellMappings = mappings;

    UITableViewCell *cell = [tableController tableView:tableController.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
    assertThat(cell.textLabel.text, is(equalTo(@"Shindig")));
}


- (void)testLoadWithATableViewControllerAndResourcePathAndPredicateAndSortDescriptors
{
    [self bootstrapStoreAndCache];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    NSPredicate *predicate = [NSPredicate predicateWithValue:TRUE];
    NSArray *sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"name"
                                                                                      ascending:YES]];
    RKFetchedResultsTableController *tableController = [RKFetchedResultsTableController tableControllerForTableViewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    tableController.predicate = predicate;
    tableController.sortDescriptors = sortDescriptors;
    [tableController loadTable];

    assertThat(tableController.viewController, is(equalTo(viewController)));
    assertThat(tableController.resourcePath, is(equalTo(@"/JSON/humans/all.json")));
    assertThat(tableController.fetchRequest, is(notNilValue()));
    assertThat([tableController.fetchRequest predicate], is(equalTo(predicate)));
    assertThat([tableController.fetchRequest sortDescriptors], is(equalTo(sortDescriptors)));
}

- (void)testLoadWithATableViewControllerAndResourcePathAndSectionNameAndCacheName
{
    [self bootstrapStoreAndCache];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController = [RKFetchedResultsTableController tableControllerForTableViewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    tableController.sectionNameKeyPath = @"name";
    tableController.cacheName = @"allHumansCache";
    [tableController loadTable];

    assertThat(tableController.viewController, is(equalTo(viewController)));
    assertThat(tableController.resourcePath, is(equalTo(@"/JSON/humans/all.json")));
    assertThat(tableController.fetchRequest, is(notNilValue()));
    assertThat(tableController.fetchedResultsController.sectionNameKeyPath, is(equalTo(@"name")));
    assertThat(tableController.fetchedResultsController.cacheName, is(equalTo(@"allHumansCache")));
}

- (void)testLoadWithAllParams
{
    [self bootstrapStoreAndCache];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    NSPredicate *predicate = [NSPredicate predicateWithValue:TRUE];
    NSArray *sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"name"
                                                                                      ascending:YES]];
    RKFetchedResultsTableController *tableController = [RKFetchedResultsTableController tableControllerForTableViewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    tableController.predicate = predicate;
    tableController.sortDescriptors = sortDescriptors;
    tableController.sectionNameKeyPath = @"name";
    tableController.cacheName = @"allHumansCache";
    [tableController loadTable];

    assertThat(tableController.viewController, is(equalTo(viewController)));
    assertThat(tableController.resourcePath, is(equalTo(@"/JSON/humans/all.json")));
    assertThat(tableController.fetchRequest, is(notNilValue()));
    assertThat([tableController.fetchRequest predicate], is(equalTo(predicate)));
    assertThat([tableController.fetchRequest sortDescriptors], is(equalTo(sortDescriptors)));
    assertThat(tableController.fetchedResultsController.sectionNameKeyPath, is(equalTo(@"name")));
    assertThat(tableController.fetchedResultsController.cacheName, is(equalTo(@"allHumansCache")));
}

- (void)testAlwaysHaveAtLeastOneSection
{
    [self bootstrapStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController = [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                                                                   viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    [tableController loadTable];

    assertThatInt(tableController.sectionCount, is(equalToInt(1)));
}

#pragma mark - Section Management

- (void)testProperlyCountSections
{
    [self bootstrapStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController = [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                                                                   viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    tableController.sectionNameKeyPath = @"name";
    [tableController loadTable];
    assertThatInt(tableController.sectionCount, is(equalToInt(2)));
}

- (void)testProperlyCountRows
{
    [self bootstrapStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController = [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                                                                   viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    [tableController loadTable];
    assertThatInt([tableController rowCount], is(equalToInt(2)));
}

- (void)testProperlyCountRowsWithHeaderItems
{
    [self bootstrapStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController =
    [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    [tableController addHeaderRowForItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Header";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    [tableController loadTable];
    assertThatInt([tableController rowCount], is(equalToInt(3)));
}

- (void)testProperlyCountRowsWithEmptyItemWhenEmpty
{
    [self bootstrapEmptyStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController =
    [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    [tableController setEmptyItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Empty";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    [tableController loadTable];
    assertThatInt([tableController rowCount], is(equalToInt(1)));
}

- (void)testProperlyCountRowsWithEmptyItemWhenFull
{
    [self bootstrapStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController =
    [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    [tableController setEmptyItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Empty";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    [tableController loadTable];
    assertThatInt([tableController rowCount], is(equalToInt(2)));
}

- (void)testProperlyCountRowsWithHeaderAndEmptyItemsWhenEmptyDontShowHeaders
{
    [self bootstrapEmptyStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController =
    [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    [tableController addHeaderRowForItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Header";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    tableController.showsHeaderRowsWhenEmpty = NO;
    [tableController setEmptyItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Empty";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    [tableController loadTable];
    assertThatInt([tableController rowCount], is(equalToInt(1)));
}

- (void)testProperlyCountRowsWithHeaderAndEmptyItemsWhenEmptyShowHeaders
{
    [self bootstrapEmptyStoreAndCache];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController = [RKFetchedResultsTableController tableControllerForTableViewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    [tableController addHeaderRowForItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Header";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    tableController.showsHeaderRowsWhenEmpty = YES;
    [tableController setEmptyItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Empty";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    [tableController loadTable];
    assertThatInt([tableController rowCount], is(equalToInt(2)));
}

- (void)testProperlyCountRowsWithHeaderAndEmptyItemsWhenFull
{
    [self bootstrapStoreAndCache];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController = [RKFetchedResultsTableController tableControllerForTableViewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    [tableController addHeaderRowForItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Header";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    [tableController setEmptyItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Empty";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    [tableController loadTable];
    assertThatInt([tableController rowCount], is(equalToInt(3)));
}

#pragma mark - UITableViewDataSource specs

- (void)testRaiseAnExceptionIfSentAMessageWithATableViewItIsNotBoundTo
{
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController = [RKFetchedResultsTableController tableControllerWithTableView:tableView forViewController:viewController];
    NSException *exception = nil;
    @try {
        [tableController numberOfSectionsInTableView:[UITableView new]];
    }
    @catch (NSException *e) {
        exception = e;
    }
    @finally {
        assertThat(exception, is(notNilValue()));
    }
}

- (void)testReturnTheNumberOfSectionsInTableView
{
    [self bootstrapStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController = [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                                                                   viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    tableController.sectionNameKeyPath = @"name";
    [tableController loadTable];

    assertThatInt([tableController numberOfSectionsInTableView:tableView], is(equalToInt(2)));
}

- (void)testReturnTheNumberOfRowsInSectionInTableView
{
    [self bootstrapStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController = [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                                                                   viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    [tableController loadTable];

    assertThatInt([tableController tableView:tableView numberOfRowsInSection:0], is(equalToInt(2)));
}

- (void)testReturnTheHeaderTitleForSection
{
    [self bootstrapStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController = [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                                                                   viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    tableController.sectionNameKeyPath = @"name";
    [tableController loadTable];

    assertThat([tableController tableView:tableView titleForHeaderInSection:1], is(equalTo(@"other")));
}

- (void)testReturnTheTableViewCellForRowAtIndexPath
{
    [self bootstrapStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController = [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                                                                   viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    [tableController loadTable];

    RKTableViewCellMapping *cellMapping = [RKTableViewCellMapping mappingForClass:[UITableViewCell class]];
    [cellMapping mapKeyPath:@"name" toAttribute:@"textLabel.text"];
    RKTableViewCellMappings *mappings = [RKTableViewCellMappings new];
    [mappings setCellMapping:cellMapping forClass:[RKHuman class]];
    tableController.cellMappings = mappings;

    UITableViewCell *cell = [tableController tableView:tableController.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
    assertThat(cell.textLabel.text, is(equalTo(@"blake")));
}

#pragma mark - Table Cell Mapping

- (void)testReturnTheObjectForARowAtIndexPath
{
    [self bootstrapStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController = [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                                                                   viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    [tableController loadTable];

    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
    RKHuman *blake = [self fetchObjectWithEntityForName:@"RKHuman" withAttribute:@"name" equalTo:@"blake"];
    assertThatBool(blake == [tableController objectForRowAtIndexPath:indexPath], is(equalToBool(YES)));
    [tableController release];
}

#pragma mark - Editing

- (id)fetchObjectWithEntityForName:(NSString *)entityName withAttribute:(NSString *)attribute equalTo:(id)value
{
    NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:entityName];
    fetchRequest.predicate = [NSPredicate predicateWithFormat:@"%K = %@", attribute, value];
    fetchRequest.fetchLimit = 1;

    NSError *error;
    NSArray *results = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
    if ([results count] == 1) {
        return [results objectAtIndex:0];
    }

    return nil;
}

- (void)testFireADeleteRequestWhenTheCanEditRowsPropertyIsSet
{
    [self bootstrapStoreAndCache];
    [self stubObjectManagerToOnline];
    [[RKObjectManager sharedManager].router.routeSet addRoute:[RKRoute routeWithClass:[RKHuman class]
                                                                  resourcePathPattern:@"/humans/:railsID"
                                                                               method:RKRequestMethodDELETE]];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController = [RKFetchedResultsTableController tableControllerForTableViewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    tableController.canEditRows = YES;
    RKTableViewCellMapping *cellMapping = [RKTableViewCellMapping cellMapping];
    [cellMapping mapKeyPath:@"name" toAttribute:@"textLabel.text"];
    [tableController mapObjectsWithClass:[RKHuman class] toTableCellsWithMapping:cellMapping];
    [tableController loadTable];

    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:1 inSection:0];
    NSIndexPath *deleteIndexPath = [NSIndexPath indexPathForRow:0 inSection:0];

    RKHuman *blake = [self fetchObjectWithEntityForName:@"RKHuman" withAttribute:@"name" equalTo:@"blake"];
    RKHuman *other = [self fetchObjectWithEntityForName:@"RKHuman" withAttribute:@"name" equalTo:@"other"];

    assertThatInt([tableController rowCount], is(equalToInt(2)));
    assertThat([tableController objectForRowAtIndexPath:indexPath], is(equalTo(other)));
    assertThat([tableController objectForRowAtIndexPath:deleteIndexPath], is(equalTo(blake)));
    BOOL delegateCanEdit = [tableController tableView:tableController.tableView
                                canEditRowAtIndexPath:deleteIndexPath];
    assertThatBool(delegateCanEdit, is(equalToBool(YES)));

    [RKTestNotificationObserver waitForNotificationWithName:RKRequestDidLoadResponseNotification usingBlock:^{
        [tableController tableView:tableController.tableView
                commitEditingStyle:UITableViewCellEditingStyleDelete
                 forRowAtIndexPath:deleteIndexPath];
    }];

    assertThatInt([tableController rowCount], is(equalToInt(1)));
    assertThat([tableController objectForRowAtIndexPath:deleteIndexPath], is(equalTo(other)));
    assertThatBool([blake isDeleted], is(equalToBool(YES)));
}

- (void)testLocallyCommitADeleteWhenTheCanEditRowsPropertyIsSet
{
    [self bootstrapStoreAndCache];
    [self stubObjectManagerToOnline];

    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController = [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                                                                   viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    tableController.canEditRows = YES;
    [tableController loadTable];

    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
    NSIndexPath *deleteIndexPath = [NSIndexPath indexPathForRow:1 inSection:0];
    RKHuman *blake = [self fetchObjectWithEntityForName:@"RKHuman" withAttribute:@"name" equalTo:@"blake"];
    RKHuman *other = [self fetchObjectWithEntityForName:@"RKHuman" withAttribute:@"name" equalTo:@"other"];
    blake.railsID = nil;
    other.railsID = nil;

    NSError *error = nil;
    [blake.managedObjectContext save:&error];
    assertThat(error, is(nilValue()));

    assertThatInt([tableController rowCount], is(equalToInt(2)));
    assertThat([tableController objectForRowAtIndexPath:indexPath], is(equalTo(blake)));
    assertThat([tableController objectForRowAtIndexPath:deleteIndexPath], is(equalTo(other)));
    BOOL delegateCanEdit = [tableController tableView:tableController.tableView
                                canEditRowAtIndexPath:deleteIndexPath];
    assertThatBool(delegateCanEdit, is(equalToBool(YES)));
    [tableController tableView:tableController.tableView
            commitEditingStyle:UITableViewCellEditingStyleDelete
             forRowAtIndexPath:deleteIndexPath];
    assertThatInt([tableController rowCount], is(equalToInt(1)));
    assertThat([tableController objectForRowAtIndexPath:indexPath], is(equalTo(blake)));
}

- (void)testNotCommitADeletionWhenTheCanEditRowsPropertyIsNotSet
{
    [self bootstrapStoreAndCache];
    [self stubObjectManagerToOnline];

    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController = [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                                                                   viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    [tableController loadTable];

    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
    RKHuman *blake = [self fetchObjectWithEntityForName:@"RKHuman" withAttribute:@"name" equalTo:@"blake"];
    RKHuman *other = [self fetchObjectWithEntityForName:@"RKHuman" withAttribute:@"name" equalTo:@"other"];

    assertThatInt([tableController rowCount], is(equalToInt(2)));
    BOOL delegateCanEdit = [tableController tableView:tableController.tableView
                                canEditRowAtIndexPath:indexPath];
    assertThatBool(delegateCanEdit, is(equalToBool(NO)));
    [tableController tableView:tableController.tableView
            commitEditingStyle:UITableViewCellEditingStyleDelete
             forRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:0]];
    assertThatInt([tableController rowCount], is(equalToInt(2)));
    assertThat([tableController objectForRowAtIndexPath:indexPath], is(equalTo(blake)));
    assertThat([tableController objectForRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:0]],
               is(equalTo(other)));
}

- (void)testDoNothingToCommitAnInsertionWhenTheCanEditRowsPropertyIsSet
{
    [self bootstrapStoreAndCache];
    [self stubObjectManagerToOnline];

    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController =
    [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    tableController.canEditRows = YES;
    [tableController loadTable];

    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
    RKHuman *blake = [self fetchObjectWithEntityForName:@"RKHuman" withAttribute:@"name" equalTo:@"blake"];
    RKHuman *other = [self fetchObjectWithEntityForName:@"RKHuman" withAttribute:@"name" equalTo:@"other"];

    assertThatInt([tableController rowCount], is(equalToInt(2)));
    BOOL delegateCanEdit = [tableController tableView:tableController.tableView
                                canEditRowAtIndexPath:indexPath];
    assertThatBool(delegateCanEdit, is(equalToBool(YES)));
    [tableController tableView:tableController.tableView
            commitEditingStyle:UITableViewCellEditingStyleInsert
             forRowAtIndexPath:[NSIndexPath indexPathForRow:2 inSection:0]];
    assertThatInt([tableController rowCount], is(equalToInt(2)));
    assertThat([tableController objectForRowAtIndexPath:indexPath], is(equalTo(blake)));
    assertThat([tableController objectForRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:0]],
               is(equalTo(other)));
}

- (void)testNotMoveARowWhenTheCanMoveRowsPropertyIsSet
{
    [self bootstrapStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController = [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                                                                   viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    tableController.canMoveRows = YES;
    [tableController loadTable];

    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
    RKHuman *blake = [self fetchObjectWithEntityForName:@"RKHuman" withAttribute:@"name" equalTo:@"blake"];
    RKHuman *other = [self fetchObjectWithEntityForName:@"RKHuman" withAttribute:@"name" equalTo:@"other"];

    assertThatInt([tableController rowCount], is(equalToInt(2)));
    BOOL delegateCanMove = [tableController tableView:tableController.tableView
                                canMoveRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
    assertThatBool(delegateCanMove, is(equalToBool(YES)));
    [tableController tableView:tableController.tableView
            moveRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]
                   toIndexPath:[NSIndexPath indexPathForRow:1 inSection:0]];
    assertThatInt([tableController rowCount], is(equalToInt(2)));
    assertThat([tableController objectForRowAtIndexPath:indexPath], is(equalTo(blake)));
    assertThat([tableController objectForRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:0]],
               is(equalTo(other)));
}

#pragma mark - Header, Footer, and Empty Rows

- (void)testDetermineIfASectionIndexIsAHeaderSection
{
    [self bootstrapEmptyStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController =
    [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    [tableController loadTable];
    assertThatBool([tableController isHeaderSection:0], is(equalToBool(YES)));
    assertThatBool([tableController isHeaderSection:1], is(equalToBool(NO)));
    assertThatBool([tableController isHeaderSection:2], is(equalToBool(NO)));
}

- (void)testDetermineIfARowIndexIsAHeaderRow
{
    [self bootstrapStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController =
    [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    [tableController addHeaderRowForItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Header";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    [tableController loadTable];
    assertThatBool([tableController isHeaderRow:0], is(equalToBool(YES)));
    assertThatBool([tableController isHeaderRow:1], is(equalToBool(NO)));
    assertThatBool([tableController isHeaderRow:2], is(equalToBool(NO)));
}

- (void)testDetermineIfASectionIndexIsAFooterSectionSingleSection
{
    [self bootstrapStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController =
    [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    [tableController addFooterRowForItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Footer";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    [tableController loadTable];
    assertThatBool([tableController isFooterSection:0], is(equalToBool(YES)));
    assertThatBool([tableController isFooterSection:1], is(equalToBool(NO)));
    assertThatBool([tableController isFooterSection:2], is(equalToBool(NO)));
}

- (void)testDetermineIfASectionIndexIsAFooterSectionMultipleSections
{
    [self bootstrapStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController =
    [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    tableController.sectionNameKeyPath = @"name";
    [tableController addFooterRowForItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Footer";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    [tableController loadTable];
    assertThatBool([tableController isFooterSection:0], is(equalToBool(NO)));
    assertThatBool([tableController isFooterSection:1], is(equalToBool(YES)));
    assertThatBool([tableController isFooterSection:2], is(equalToBool(NO)));
}

- (void)testDetermineIfARowIndexIsAFooterRow
{
    [self bootstrapStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController =
    [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    [tableController addFooterRowForItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Footer";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    [tableController loadTable];
    assertThatBool([tableController isFooterRow:0], is(equalToBool(NO)));
    assertThatBool([tableController isFooterRow:1], is(equalToBool(NO)));
    assertThatBool([tableController isFooterRow:2], is(equalToBool(YES)));
}

- (void)testIsFooterRowIsAccurateWhenThereIsAFooterRowWithoutAHeaderRowInASingleSection
{
    RKLogConfigureByName("RestKit/UI", RKLogLevelTrace);
    [self bootstrapStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController =
    [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/empty.json";
    [tableController addFooterRowForItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Footer";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    tableController.showsFooterRowsWhenEmpty = YES;
    tableController.predicate = [NSPredicate predicateWithValue:NO];
    [tableController loadTable];
    
    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
    assertThatBool([tableController isFooterIndexPath:indexPath], is(equalToBool(YES)));
}

- (void)testIsFooterRowIsAccurateWhenThereIsAHeaderAndAFooterRowInASingleSection
{
    [self bootstrapStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController =
    [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/empty.json";
    [tableController addHeaderRowForItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Header";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    [tableController addFooterRowForItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Footer";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    tableController.showsFooterRowsWhenEmpty = YES;
    tableController.predicate = [NSPredicate predicateWithValue:NO];
    [tableController loadTable];
    
    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
    assertThatBool([tableController isFooterIndexPath:indexPath], is(equalToBool(NO)));
    indexPath = [NSIndexPath indexPathForRow:1 inSection:0];
    assertThatBool([tableController isFooterIndexPath:indexPath], is(equalToBool(YES)));
}

- (void)testIsFooterRowIsAccurateWhenThereIsAreContentRowsAndAHeaderAndAFooterRowInASingleSection
{    
    [self bootstrapStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController =
    [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    [tableController addHeaderRowForItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Header";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    [tableController addFooterRowForItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Footer";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    [tableController loadTable];
    
    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
    assertThatBool([tableController isHeaderIndexPath:indexPath], is(equalToBool(YES)));
    assertThatBool([tableController isFooterIndexPath:indexPath], is(equalToBool(NO)));
    
    // Check the content rows -- there are 2 of them in the JSON
    indexPath = [NSIndexPath indexPathForRow:1 inSection:0];
    assertThatBool([tableController isHeaderIndexPath:indexPath], is(equalToBool(NO)));
    assertThatBool([tableController isFooterIndexPath:indexPath], is(equalToBool(NO)));
    indexPath = [NSIndexPath indexPathForRow:2 inSection:0];
    assertThatBool([tableController isHeaderIndexPath:indexPath], is(equalToBool(NO)));
    assertThatBool([tableController isFooterIndexPath:indexPath], is(equalToBool(NO)));
    
    indexPath = [NSIndexPath indexPathForRow:3 inSection:0];
    assertThatBool([tableController isHeaderIndexPath:indexPath], is(equalToBool(NO)));
    assertThatBool([tableController isFooterIndexPath:indexPath], is(equalToBool(YES)));
}

- (void)testDetermineIfASectionIndexIsAnEmptySection
{
    [self bootstrapEmptyStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController =
    [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    [tableController loadTable];
    assertThatBool([tableController isEmptySection:0], is(equalToBool(YES)));
    assertThatBool([tableController isEmptySection:1], is(equalToBool(NO)));
    assertThatBool([tableController isEmptySection:2], is(equalToBool(NO)));
}

- (void)testDetermineIfARowIndexIsAnEmptyRow
{
    [self bootstrapEmptyStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController =
    [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    [tableController loadTable];
    assertThatBool([tableController isEmptyRow:0], is(equalToBool(YES)));
    assertThatBool([tableController isEmptyRow:1], is(equalToBool(NO)));
    assertThatBool([tableController isEmptyRow:2], is(equalToBool(NO)));
}

- (void)testDetermineIfAnIndexPathIsAHeaderIndexPath
{
    [self bootstrapStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController =
    [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    [tableController addHeaderRowForItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Header";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    [tableController loadTable];
    assertThatBool([tableController isHeaderIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]], is(equalToBool(YES)));
    assertThatBool([tableController isHeaderIndexPath:[NSIndexPath indexPathForRow:1 inSection:0]], is(equalToBool(NO)));
    assertThatBool([tableController isHeaderIndexPath:[NSIndexPath indexPathForRow:2 inSection:0]], is(equalToBool(NO)));
    assertThatBool([tableController isHeaderIndexPath:[NSIndexPath indexPathForRow:0 inSection:1]], is(equalToBool(NO)));
    assertThatBool([tableController isHeaderIndexPath:[NSIndexPath indexPathForRow:1 inSection:1]], is(equalToBool(NO)));
    assertThatBool([tableController isHeaderIndexPath:[NSIndexPath indexPathForRow:2 inSection:1]], is(equalToBool(NO)));
}

- (void)testDetermineIfAnIndexPathIsAFooterIndexPath
{
    [self bootstrapStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController =
    [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    [tableController addFooterRowForItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Footer";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    [tableController loadTable];
    assertThatBool([tableController isFooterIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]], is(equalToBool(NO)));
    assertThatBool([tableController isFooterIndexPath:[NSIndexPath indexPathForRow:1 inSection:0]], is(equalToBool(NO)));
    assertThatBool([tableController isFooterIndexPath:[NSIndexPath indexPathForRow:2 inSection:0]], is(equalToBool(YES)));
    assertThatBool([tableController isFooterIndexPath:[NSIndexPath indexPathForRow:0 inSection:1]], is(equalToBool(NO)));
    assertThatBool([tableController isFooterIndexPath:[NSIndexPath indexPathForRow:1 inSection:1]], is(equalToBool(NO)));
    assertThatBool([tableController isFooterIndexPath:[NSIndexPath indexPathForRow:2 inSection:1]], is(equalToBool(NO)));
}

- (void)testDetermineIfAnIndexPathIsAnEmptyIndexPathSingleSectionEmptyItemOnly
{
    [self bootstrapEmptyStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController =
    [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    [tableController setEmptyItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Empty";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    [tableController loadTable];
    assertThatBool([tableController isEmptyItemIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]], is(equalToBool(YES)));
    assertThatBool([tableController isEmptyItemIndexPath:[NSIndexPath indexPathForRow:1 inSection:0]], is(equalToBool(NO)));
    assertThatBool([tableController isEmptyItemIndexPath:[NSIndexPath indexPathForRow:2 inSection:0]], is(equalToBool(NO)));
    assertThatBool([tableController isEmptyItemIndexPath:[NSIndexPath indexPathForRow:0 inSection:1]], is(equalToBool(NO)));
    assertThatBool([tableController isEmptyItemIndexPath:[NSIndexPath indexPathForRow:1 inSection:1]], is(equalToBool(NO)));
    assertThatBool([tableController isEmptyItemIndexPath:[NSIndexPath indexPathForRow:2 inSection:1]], is(equalToBool(NO)));
}

- (void)testConvertAnIndexPathForHeaderRows
{
    [self bootstrapStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController =
    [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    [tableController addHeaderRowForItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Header";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    [tableController loadTable];
    assertThat([tableController fetchedResultsIndexPathForIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]], is(equalTo([NSIndexPath indexPathForRow:0 inSection:0])));
    assertThat([tableController fetchedResultsIndexPathForIndexPath:[NSIndexPath indexPathForRow:1 inSection:0]], is(equalTo([NSIndexPath indexPathForRow:0 inSection:0])));
    assertThat([tableController fetchedResultsIndexPathForIndexPath:[NSIndexPath indexPathForRow:2 inSection:0]], is(equalTo([NSIndexPath indexPathForRow:1 inSection:0])));
    assertThat([tableController fetchedResultsIndexPathForIndexPath:[NSIndexPath indexPathForRow:3 inSection:0]], is(equalTo([NSIndexPath indexPathForRow:2 inSection:0])));
}

- (void)testConvertAnIndexPathForFooterRowsSingleSection
{
    [self bootstrapStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController =
    [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    [tableController addFooterRowForItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Footer";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    [tableController loadTable];
    assertThat([tableController fetchedResultsIndexPathForIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]], is(equalTo([NSIndexPath indexPathForRow:0 inSection:0])));
    assertThat([tableController fetchedResultsIndexPathForIndexPath:[NSIndexPath indexPathForRow:1 inSection:0]], is(equalTo([NSIndexPath indexPathForRow:1 inSection:0])));
    assertThat([tableController fetchedResultsIndexPathForIndexPath:[NSIndexPath indexPathForRow:2 inSection:0]], is(equalTo([NSIndexPath indexPathForRow:2 inSection:0])));
}

- (void)testConvertAnIndexPathForFooterRowsMultipleSections
{
    [self bootstrapStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController =
    [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    tableController.sectionNameKeyPath = @"name";
    [tableController addFooterRowForItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Footer";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    [tableController loadTable];
    assertThat([tableController fetchedResultsIndexPathForIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]], is(equalTo([NSIndexPath indexPathForRow:0 inSection:0])));
    assertThat([tableController fetchedResultsIndexPathForIndexPath:[NSIndexPath indexPathForRow:1 inSection:0]], is(equalTo([NSIndexPath indexPathForRow:1 inSection:0])));
    assertThat([tableController fetchedResultsIndexPathForIndexPath:[NSIndexPath indexPathForRow:0 inSection:1]], is(equalTo([NSIndexPath indexPathForRow:0 inSection:1])));
    assertThat([tableController fetchedResultsIndexPathForIndexPath:[NSIndexPath indexPathForRow:1 inSection:1]], is(equalTo([NSIndexPath indexPathForRow:1 inSection:1])));
}

- (void)testConvertAnIndexPathForEmptyRow
{
    [self bootstrapStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController =
    [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    [tableController setEmptyItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Empty";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    [tableController loadTable];
    assertThat([tableController fetchedResultsIndexPathForIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]], is(equalTo([NSIndexPath indexPathForRow:0 inSection:0])));
    assertThat([tableController fetchedResultsIndexPathForIndexPath:[NSIndexPath indexPathForRow:1 inSection:0]], is(equalTo([NSIndexPath indexPathForRow:1 inSection:0])));
    assertThat([tableController fetchedResultsIndexPathForIndexPath:[NSIndexPath indexPathForRow:2 inSection:0]], is(equalTo([NSIndexPath indexPathForRow:2 inSection:0])));
    assertThat([tableController fetchedResultsIndexPathForIndexPath:[NSIndexPath indexPathForRow:3 inSection:0]], is(equalTo([NSIndexPath indexPathForRow:3 inSection:0])));
}

- (void)testConvertAnIndexPathForHeaderFooterRowsSingleSection
{
    [self bootstrapStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController =
    [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    [tableController addHeaderRowForItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Header";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    [tableController addFooterRowForItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Footer";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    [tableController loadTable];
    assertThat([tableController fetchedResultsIndexPathForIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]], is(equalTo([NSIndexPath indexPathForRow:0 inSection:0])));
    assertThat([tableController fetchedResultsIndexPathForIndexPath:[NSIndexPath indexPathForRow:1 inSection:0]], is(equalTo([NSIndexPath indexPathForRow:0 inSection:0])));
    assertThat([tableController fetchedResultsIndexPathForIndexPath:[NSIndexPath indexPathForRow:2 inSection:0]], is(equalTo([NSIndexPath indexPathForRow:1 inSection:0])));
    assertThat([tableController fetchedResultsIndexPathForIndexPath:[NSIndexPath indexPathForRow:3 inSection:0]], is(equalTo([NSIndexPath indexPathForRow:2 inSection:0])));
}

- (void)testConvertAnIndexPathForHeaderFooterRowsMultipleSections
{
    [self bootstrapStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController =
    [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    tableController.sectionNameKeyPath = @"name";
    [tableController addHeaderRowForItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Header";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    [tableController addFooterRowForItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Footer";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    [tableController loadTable];
    assertThat([tableController fetchedResultsIndexPathForIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]], is(equalTo([NSIndexPath indexPathForRow:0 inSection:0])));
    assertThat([tableController fetchedResultsIndexPathForIndexPath:[NSIndexPath indexPathForRow:1 inSection:0]], is(equalTo([NSIndexPath indexPathForRow:0 inSection:0])));
    assertThat([tableController fetchedResultsIndexPathForIndexPath:[NSIndexPath indexPathForRow:0 inSection:1]], is(equalTo([NSIndexPath indexPathForRow:0 inSection:1])));
    assertThat([tableController fetchedResultsIndexPathForIndexPath:[NSIndexPath indexPathForRow:1 inSection:1]], is(equalTo([NSIndexPath indexPathForRow:1 inSection:1])));
    assertThat([tableController fetchedResultsIndexPathForIndexPath:[NSIndexPath indexPathForRow:2 inSection:1]], is(equalTo([NSIndexPath indexPathForRow:2 inSection:1])));
}

- (void)testConvertAnIndexPathForHeaderFooterEmptyRowsSingleSection
{
    [self bootstrapStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController =
    [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    [tableController addHeaderRowForItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Header";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    [tableController addFooterRowForItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Footer";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    [tableController setEmptyItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Empty";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    [tableController loadTable];
    assertThat([tableController fetchedResultsIndexPathForIndexPath:[NSIndexPath indexPathForRow:1 inSection:0]], is(equalTo([NSIndexPath indexPathForRow:0 inSection:0])));
    assertThat([tableController fetchedResultsIndexPathForIndexPath:[NSIndexPath indexPathForRow:2 inSection:0]], is(equalTo([NSIndexPath indexPathForRow:1 inSection:0])));
    assertThat([tableController fetchedResultsIndexPathForIndexPath:[NSIndexPath indexPathForRow:3 inSection:0]], is(equalTo([NSIndexPath indexPathForRow:2 inSection:0])));
    assertThat([tableController fetchedResultsIndexPathForIndexPath:[NSIndexPath indexPathForRow:4 inSection:0]], is(equalTo([NSIndexPath indexPathForRow:3 inSection:0])));
    assertThat([tableController fetchedResultsIndexPathForIndexPath:[NSIndexPath indexPathForRow:5 inSection:0]], is(equalTo([NSIndexPath indexPathForRow:4 inSection:0])));
}

- (void)testConvertAnIndexPathForHeaderFooterEmptyRowsMultipleSections
{
    [self bootstrapStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController =
    [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    tableController.sectionNameKeyPath = @"name";
    [tableController addHeaderRowForItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Header";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    [tableController addFooterRowForItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Footer";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    [tableController setEmptyItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Empty";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    [tableController loadTable];
    assertThat([tableController fetchedResultsIndexPathForIndexPath:[NSIndexPath indexPathForRow:1 inSection:0]], is(equalTo([NSIndexPath indexPathForRow:0 inSection:0])));
    assertThat([tableController fetchedResultsIndexPathForIndexPath:[NSIndexPath indexPathForRow:0 inSection:1]], is(equalTo([NSIndexPath indexPathForRow:0 inSection:1])));
    assertThat([tableController fetchedResultsIndexPathForIndexPath:[NSIndexPath indexPathForRow:1 inSection:1]], is(equalTo([NSIndexPath indexPathForRow:1 inSection:1])));
    assertThat([tableController fetchedResultsIndexPathForIndexPath:[NSIndexPath indexPathForRow:2 inSection:1]], is(equalTo([NSIndexPath indexPathForRow:2 inSection:1])));
}

- (void)testConvertAnIndexPathForHeaderEmptyRows
{
    [self bootstrapStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController =
    [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    [tableController addHeaderRowForItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Header";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    [tableController setEmptyItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Empty";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    [tableController loadTable];
    assertThat([tableController fetchedResultsIndexPathForIndexPath:[NSIndexPath indexPathForRow:1 inSection:0]], is(equalTo([NSIndexPath indexPathForRow:0 inSection:0])));
    assertThat([tableController fetchedResultsIndexPathForIndexPath:[NSIndexPath indexPathForRow:2 inSection:0]], is(equalTo([NSIndexPath indexPathForRow:1 inSection:0])));
    assertThat([tableController fetchedResultsIndexPathForIndexPath:[NSIndexPath indexPathForRow:3 inSection:0]], is(equalTo([NSIndexPath indexPathForRow:2 inSection:0])));
}

- (void)testShowHeaderRows
{
    [self bootstrapStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController =
    [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    RKTableItem *headerRow = [RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Header";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }];
    [tableController addHeaderRowForItem:headerRow];
    tableController.showsHeaderRowsWhenEmpty = NO;
    tableController.showsFooterRowsWhenEmpty = NO;
    [tableController loadTable];

    RKHuman *blake = [self fetchObjectWithEntityForName:@"RKHuman" withAttribute:@"name" equalTo:@"blake"];
    RKHuman *other = [self fetchObjectWithEntityForName:@"RKHuman" withAttribute:@"name" equalTo:@"other"];

    assertThatInt([tableController rowCount], is(equalToInt(3)));
    assertThat([tableController objectForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]], is(equalTo(headerRow)));
    assertThat([tableController objectForRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:0]], is(equalTo(blake)));
    assertThat([tableController objectForRowAtIndexPath:[NSIndexPath indexPathForRow:2 inSection:0]],
               is(equalTo(other)));
}

- (void)testShowFooterRows
{
    [self bootstrapStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController =
    [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    RKTableItem *footerRow = [RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Footer";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }];
    [tableController addFooterRowForItem:footerRow];
    tableController.showsHeaderRowsWhenEmpty = NO;
    tableController.showsFooterRowsWhenEmpty = NO;
    [tableController loadTable];

    RKHuman *blake = [self fetchObjectWithEntityForName:@"RKHuman" withAttribute:@"name" equalTo:@"blake"];
    RKHuman *other = [self fetchObjectWithEntityForName:@"RKHuman" withAttribute:@"name" equalTo:@"other"];

    assertThatInt([tableController rowCount], is(equalToInt(3)));
    assertThat([tableController objectForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]], is(equalTo(blake)));
    assertThat([tableController objectForRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:0]], is(equalTo(other)));
    assertThat([tableController objectForRowAtIndexPath:[NSIndexPath indexPathForRow:2 inSection:0]],
               is(equalTo(footerRow)));
}

- (void)testHideHeaderRowsWhenEmptyWhenPropertyIsNotSet
{
    [self bootstrapEmptyStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController =
    [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    [tableController addHeaderRowForItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Header";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    tableController.showsHeaderRowsWhenEmpty = NO;
    tableController.showsFooterRowsWhenEmpty = NO;
    [tableController loadTable];

    assertThatBool(tableController.isLoaded, is(equalToBool(YES)));
    assertThatInt([tableController rowCount], is(equalToInt(0)));
    assertThatBool(tableController.isEmpty, is(equalToBool(YES)));
}

- (void)testHideFooterRowsWhenEmptyWhenPropertyIsNotSet
{
    [self bootstrapEmptyStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController =
    [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    [tableController addFooterRowForItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Footer";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    tableController.showsHeaderRowsWhenEmpty = NO;
    tableController.showsFooterRowsWhenEmpty = NO;
    [tableController loadTable];

    assertThatBool(tableController.isLoaded, is(equalToBool(YES)));
    assertThatInt([tableController rowCount], is(equalToInt(0)));
    assertThatBool(tableController.isEmpty, is(equalToBool(YES)));
}

- (void)testRemoveHeaderAndFooterCountsWhenDeterminingIsEmpty
{
    [self bootstrapEmptyStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController =
    [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    [tableController addHeaderRowForItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Header";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    [tableController addFooterRowForItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Footer";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    [tableController setEmptyItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Empty";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    tableController.showsHeaderRowsWhenEmpty = NO;
    tableController.showsFooterRowsWhenEmpty = NO;
    [tableController loadTable];

    assertThatBool(tableController.isLoaded, is(equalToBool(YES)));
    assertThatInt([tableController rowCount], is(equalToInt(1)));
    assertThatBool(tableController.isEmpty, is(equalToBool(YES)));
}

- (void)testNotShowTheEmptyItemWhenTheTableIsNotEmpty
{
    [self bootstrapStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController =
    [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";

    RKTableItem *headerRow = [RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Header";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }];
    [tableController addHeaderRowForItem:headerRow];

    RKTableItem *footerRow = [RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Footer";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }];
    [tableController addFooterRowForItem:footerRow];

    RKTableItem *emptyItem = [RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Empty";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }];
    [tableController setEmptyItem:emptyItem];
    tableController.showsHeaderRowsWhenEmpty = NO;
    tableController.showsFooterRowsWhenEmpty = NO;
    [tableController loadTable];

    RKHuman *blake = [self fetchObjectWithEntityForName:@"RKHuman" withAttribute:@"name" equalTo:@"blake"];
    RKHuman *other = [self fetchObjectWithEntityForName:@"RKHuman" withAttribute:@"name" equalTo:@"other"];

    assertThatInt([tableController rowCount], is(equalToInt(4)));
    assertThat([tableController objectForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]], is(equalTo(headerRow)));
    assertThat([tableController objectForRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:0]], is(equalTo(blake)));
    assertThat([tableController objectForRowAtIndexPath:[NSIndexPath indexPathForRow:2 inSection:0]], is(equalTo(other)));
    assertThat([tableController objectForRowAtIndexPath:[NSIndexPath indexPathForRow:3 inSection:0]],
               is(equalTo(footerRow)));
}

- (void)testShowTheEmptyItemWhenTheTableIsEmpty
{
    [self bootstrapEmptyStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController =
    [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    [tableController addHeaderRowForItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Header";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    [tableController addFooterRowForItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Footer";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    [tableController setEmptyItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Empty";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    tableController.showsHeaderRowsWhenEmpty = NO;
    tableController.showsFooterRowsWhenEmpty = NO;
    [tableController loadTable];

    assertThatBool(tableController.isLoaded, is(equalToBool(YES)));
    assertThatInt([tableController rowCount], is(equalToInt(1)));
    assertThatBool(tableController.isEmpty, is(equalToBool(YES)));
}

- (void)testShowTheEmptyItemPlusHeadersAndFootersWhenTheTableIsEmpty
{
    [self bootstrapEmptyStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController =
    [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    [tableController addHeaderRowForItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Header";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    [tableController addFooterRowForItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Footer";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    [tableController setEmptyItem:[RKTableItem tableItemUsingBlock:^(RKTableItem *tableItem) {
        tableItem.text = @"Empty";
        tableItem.cellMapping = [RKTableViewCellMapping cellMappingUsingBlock:^(RKTableViewCellMapping *cellMapping) {
            [cellMapping addDefaultMappings];
        }];
    }]];
    tableController.showsHeaderRowsWhenEmpty = YES;
    tableController.showsFooterRowsWhenEmpty = YES;
    [tableController loadTable];

    assertThatBool(tableController.isLoaded, is(equalToBool(YES)));
    assertThatInt([tableController rowCount], is(equalToInt(3)));
    assertThatBool(tableController.isEmpty, is(equalToBool(YES)));
}

- (void)testShowTheEmptyImageAfterLoadingAnEmptyCollectionIntoAnEmptyFetch
{
    [self bootstrapEmptyStoreAndCache];
    [self stubObjectManagerToOnline];

    UITableView *tableView = [UITableView new];

    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController = [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                                                                   viewController:viewController];

    UIImage *image = [RKTestFixture imageWithContentsOfFixture:@"blake.png"];

    tableController.imageForEmpty = image;
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/empty/array";
    tableController.autoRefreshFromNetwork = YES;
    [tableController.cache invalidateAll];

    [RKTestNotificationObserver waitForNotificationWithName:RKTableControllerDidFinishLoadNotification usingBlock:^{
        [tableController loadTable];
    }];
    assertThatBool(tableController.isLoaded, is(equalToBool(YES)));
    assertThatInt([tableController rowCount], is(equalToInt(0)));
    assertThatBool(tableController.isEmpty, is(equalToBool(YES)));
    assertThat(tableController.stateOverlayImageView.image, is(notNilValue()));
}

- (void)testPostANotificationWhenObjectsAreLoaded
{
    [self bootstrapNakedObjectStoreAndCache];
    UITableView *tableView = [UITableView new];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController =
    [[RKFetchedResultsTableController alloc] initWithTableView:tableView
                                                viewController:viewController];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/NakedEvents.json";
    [tableController setObjectMappingForClass:[RKEvent class]];

    id observerMock = [OCMockObject observerMock];
    [[NSNotificationCenter defaultCenter] addMockObserver:observerMock name:RKTableControllerDidLoadObjectsNotification object:tableController];
    [[observerMock expect] notificationWithName:RKTableControllerDidLoadObjectsNotification object:tableController];
    [tableController loadTable];
    [observerMock verify];
}

#pragma mark - Delegate Methods

- (void)testDelegateIsInformedOnInsertSection
{
    [self bootstrapStoreAndCache];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController =
    [[RKFetchedResultsTableController alloc] initWithTableView:viewController.tableView viewController:viewController];
    RKTableViewCellMapping *cellMapping = [RKTableViewCellMapping cellMapping];
    [cellMapping mapKeyPath:@"name" toAttribute:@"textLabel.text"];
    [tableController mapObjectsWithClass:[RKHuman class] toTableCellsWithMapping:cellMapping];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    tableController.cacheName = @"allHumansCache";

    RKFetchedResultsTableControllerTestDelegate *delegate = [RKFetchedResultsTableControllerTestDelegate tableControllerDelegate];
    id mockDelegate = [OCMockObject partialMockForObject:delegate];
    [[mockDelegate expect] tableController:tableController didInsertSectionAtIndex:0];
    tableController.delegate = mockDelegate;
    [[[[UIApplication sharedApplication] windows] objectAtIndex:0] setRootViewController:viewController];
    [tableController loadTable];
    assertThatInt([tableController rowCount], is(equalToInt(2)));
    assertThatInt([tableController sectionCount], is(equalToInt(1)));
    [mockDelegate verify];
}

- (void)testDelegateIsInformedOfDidStartLoad
{
    [self bootstrapStoreAndCache];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController =
    [[RKFetchedResultsTableController alloc] initWithTableView:viewController.tableView viewController:viewController];
    RKTableViewCellMapping *cellMapping = [RKTableViewCellMapping cellMapping];
    [cellMapping mapKeyPath:@"name" toAttribute:@"textLabel.text"];
    [tableController mapObjectsWithClass:[RKHuman class] toTableCellsWithMapping:cellMapping];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    tableController.cacheName = @"allHumansCache";

    id mockDelegate = [OCMockObject niceMockForProtocol:@protocol(RKFetchedResultsTableControllerDelegate)];
    [[mockDelegate expect] tableControllerDidStartLoad:tableController];
    tableController.delegate = mockDelegate;
    [[[[UIApplication sharedApplication] windows] objectAtIndex:0] setRootViewController:viewController];
    [tableController loadTable];
    [mockDelegate verify];
}

- (void)testDelegateIsInformedOfDidFinishLoad
{
    [self bootstrapStoreAndCache];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController =
    [[RKFetchedResultsTableController alloc] initWithTableView:viewController.tableView viewController:viewController];
    RKTableViewCellMapping *cellMapping = [RKTableViewCellMapping cellMapping];
    [cellMapping mapKeyPath:@"name" toAttribute:@"textLabel.text"];
    [tableController mapObjectsWithClass:[RKHuman class] toTableCellsWithMapping:cellMapping];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    tableController.cacheName = @"allHumansCache";

    id mockDelegate = [OCMockObject niceMockForProtocol:@protocol(RKFetchedResultsTableControllerDelegate)];
    [[mockDelegate expect] tableControllerDidFinishLoad:tableController];
    tableController.delegate = mockDelegate;
    [[[[UIApplication sharedApplication] windows] objectAtIndex:0] setRootViewController:viewController];
    [tableController loadTable];
    [mockDelegate verify];
}

- (void)testDelegateIsInformedOfDidInsertObjectAtIndexPath
{
    [self bootstrapStoreAndCache];
    RKFetchedResultsTableControllerSpecViewController *viewController = [RKFetchedResultsTableControllerSpecViewController new];
    RKFetchedResultsTableController *tableController =
    [[RKFetchedResultsTableController alloc] initWithTableView:viewController.tableView viewController:viewController];
    RKTableViewCellMapping *cellMapping = [RKTableViewCellMapping cellMapping];
    [cellMapping mapKeyPath:@"name" toAttribute:@"textLabel.text"];
    [tableController mapObjectsWithClass:[RKHuman class] toTableCellsWithMapping:cellMapping];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";
    tableController.cacheName = @"allHumansCache";

    id mockDelegate = [OCMockObject niceMockForProtocol:@protocol(RKFetchedResultsTableControllerDelegate)];
    [[mockDelegate expect] tableController:tableController didInsertObject:OCMOCK_ANY atIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
    [[mockDelegate expect] tableController:tableController didInsertObject:OCMOCK_ANY atIndexPath:[NSIndexPath indexPathForRow:1 inSection:0]];
    tableController.delegate = mockDelegate;
    [[[[UIApplication sharedApplication] windows] objectAtIndex:0] setRootViewController:viewController];
    [tableController loadTable];
    [mockDelegate verify];
}

- (void)testRetrievalOfManagedObjectIndexPath
{
    [self bootstrapStoreAndCache];
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Storyboard" bundle:nil];
    UITableViewController *tableViewController = [storyboard instantiateInitialViewController];
    RKFetchedResultsTableController *tableController;
    tableController = [RKFetchedResultsTableController tableControllerForTableViewController:tableViewController];
    RKTableViewCellMapping *cellMapping = [RKTableViewCellMapping cellMapping];
    [cellMapping mapKeyPath:@"name" toAttribute:@"textLabel.text"];
    [tableController mapObjectsWithClass:[RKHuman class] toTableCellsWithMapping:cellMapping];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";

    RKTableItem *headerItem = [RKTableItem tableItemWithText:@"Header"];
    headerItem.cellMapping.reuseIdentifier = @"HeaderCell";
    [headerItem.cellMapping addDefaultMappings];
    [tableController addHeaderRowForItem:headerItem];

    RKTableItem *footerItem = [RKTableItem tableItemWithText:@"Footer"];
    footerItem.cellMapping.reuseIdentifier = @"FooterCell";
    [footerItem.cellMapping addDefaultMappings];
    [tableController addFooterRowForItem:footerItem];

    [[[[UIApplication sharedApplication] windows] objectAtIndex:0] setRootViewController:tableViewController];

    [RKTestNotificationObserver waitForNotificationWithName:RKTableControllerDidFinishLoadNotification usingBlock:^{
        [tableController loadTable];
    }];

    // Let the table update
    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5]];

    assertThatInt([tableController rowCount], is(equalToInteger(4)));

    NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"RKHuman"];
    fetchRequest.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES]];
    NSArray *objects = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];
    RKHuman *human = [objects objectAtIndex:0];
    NSIndexPath *indexPath = [tableController indexPathForObject:human];
    assertThatInteger(indexPath.section, is(equalToInteger(0)));
    assertThatInteger(indexPath.row, is(equalToInteger(1)));
}

- (void)testRetrievalOfHeaderItemIndexPath
{
    [self bootstrapStoreAndCache];
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Storyboard" bundle:nil];
    UITableViewController *tableViewController = [storyboard instantiateInitialViewController];
    RKFetchedResultsTableController *tableController;
    tableController = [RKFetchedResultsTableController tableControllerForTableViewController:tableViewController];
    RKTableViewCellMapping *cellMapping = [RKTableViewCellMapping cellMapping];
    [cellMapping mapKeyPath:@"name" toAttribute:@"textLabel.text"];
    [tableController mapObjectsWithClass:[RKHuman class] toTableCellsWithMapping:cellMapping];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";

    RKTableItem *headerItem = [RKTableItem tableItemWithText:@"Header"];
    headerItem.cellMapping.reuseIdentifier = @"HeaderCell";
    [headerItem.cellMapping addDefaultMappings];
    [tableController addHeaderRowForItem:headerItem];

    RKTableItem *footerItem = [RKTableItem tableItemWithText:@"Footer"];
    footerItem.cellMapping.reuseIdentifier = @"FooterCell";
    [footerItem.cellMapping addDefaultMappings];
    [tableController addFooterRowForItem:footerItem];

    [[[[UIApplication sharedApplication] windows] objectAtIndex:0] setRootViewController:tableViewController];

    [RKTestNotificationObserver waitForNotificationWithName:RKTableControllerDidFinishLoadNotification usingBlock:^{
        [tableController loadTable];
    }];

    // Let the table update
    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];

    assertThatInt([tableController rowCount], is(equalToInteger(4)));
    NSIndexPath *indexPath = [tableController indexPathForObject:headerItem];
    assertThatInteger(indexPath.section, is(equalToInteger(0)));
    assertThatInteger(indexPath.row, is(equalToInteger(0)));
}

- (void)testRetrievalOfFooterItemIndexPath
{
    [self bootstrapStoreAndCache];
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Storyboard" bundle:nil];
    UITableViewController *tableViewController = [storyboard instantiateInitialViewController];
    RKFetchedResultsTableController *tableController;
    tableController = [RKFetchedResultsTableController tableControllerForTableViewController:tableViewController];
    RKTableViewCellMapping *cellMapping = [RKTableViewCellMapping cellMapping];
    [cellMapping mapKeyPath:@"name" toAttribute:@"textLabel.text"];
    [tableController mapObjectsWithClass:[RKHuman class] toTableCellsWithMapping:cellMapping];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";

    RKTableItem *headerItem = [RKTableItem tableItemWithText:@"Header"];
    headerItem.cellMapping.reuseIdentifier = @"HeaderCell";
    [headerItem.cellMapping addDefaultMappings];
    [tableController addHeaderRowForItem:headerItem];

    RKTableItem *footerItem = [RKTableItem tableItemWithText:@"Footer"];
    footerItem.cellMapping.reuseIdentifier = @"FooterCell";
    [footerItem.cellMapping addDefaultMappings];
    [tableController addFooterRowForItem:footerItem];

    [[[[UIApplication sharedApplication] windows] objectAtIndex:0] setRootViewController:tableViewController];

    [RKTestNotificationObserver waitForNotificationWithName:RKTableControllerDidFinishLoadNotification usingBlock:^{
        [tableController loadTable];
    }];

    // Let the table update
    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];

    assertThatInt([tableController rowCount], is(equalToInteger(4)));
    NSIndexPath *indexPath = [tableController indexPathForObject:footerItem];
    assertThatInteger(indexPath.section, is(equalToInteger(0)));
    assertThatInteger(indexPath.row, is(equalToInteger(3)));
}

- (void)testRetrievalOfEmptyItemReturnsNilIndexPathWhenNotEmpty
{
    [self bootstrapStoreAndCache];
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Storyboard" bundle:nil];
    UITableViewController *tableViewController = [storyboard instantiateInitialViewController];
    RKFetchedResultsTableController *tableController;
    tableController = [RKFetchedResultsTableController tableControllerForTableViewController:tableViewController];
    RKTableViewCellMapping *cellMapping = [RKTableViewCellMapping cellMapping];
    [cellMapping mapKeyPath:@"name" toAttribute:@"textLabel.text"];
    [tableController mapObjectsWithClass:[RKHuman class] toTableCellsWithMapping:cellMapping];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/all.json";

    RKTableItem *emptyItem = [RKTableItem tableItemWithText:@"Empty!"];
    emptyItem.cellMapping.reuseIdentifier = @"HeaderCell";
    [emptyItem.cellMapping addDefaultMappings];
    tableController.emptyItem = emptyItem;

    RKTableItem *headerItem = [RKTableItem tableItemWithText:@"Header"];
    headerItem.cellMapping.reuseIdentifier = @"HeaderCell";
    [headerItem.cellMapping addDefaultMappings];
    [tableController addHeaderRowForItem:headerItem];

    RKTableItem *footerItem = [RKTableItem tableItemWithText:@"Footer"];
    footerItem.cellMapping.reuseIdentifier = @"FooterCell";
    [footerItem.cellMapping addDefaultMappings];
    [tableController addFooterRowForItem:footerItem];

    [[[[UIApplication sharedApplication] windows] objectAtIndex:0] setRootViewController:tableViewController];

    [RKTestNotificationObserver waitForNotificationWithName:RKTableControllerDidFinishLoadNotification usingBlock:^{
        [tableController loadTable];
    }];

    // Let the table update
    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];

    assertThatInt([tableController rowCount], is(equalToInteger(4)));
    NSIndexPath *indexPath = [tableController indexPathForObject:emptyItem];
    assertThat(indexPath, is(nilValue()));
}

- (void)testRetrievalOfEmptyItemReturnsIndexPathWhenEmpty
{
    [self bootstrapStoreAndCache];
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Storyboard" bundle:nil];
    UITableViewController *tableViewController = [storyboard instantiateInitialViewController];
    RKFetchedResultsTableController *tableController;
    tableController = [[RKFetchedResultsTableController tableControllerForTableViewController:tableViewController] retain];
    RKTableViewCellMapping *cellMapping = [RKTableViewCellMapping cellMapping];
    [cellMapping mapKeyPath:@"name" toAttribute:@"textLabel.text"];
    [tableController mapObjectsWithClass:[RKHuman class] toTableCellsWithMapping:cellMapping];
    tableController.managedObjectContext = self.managedObjectContext;
    tableController.resourcePath = @"/JSON/humans/empty.json";
    tableController.showsFooterRowsWhenEmpty = NO;
    tableController.showsHeaderRowsWhenEmpty = NO;

    RKTableItem *emptyItem = [RKTableItem tableItemWithText:@"Empty!"];
    emptyItem.cellMapping.reuseIdentifier = @"HeaderCell";
    [emptyItem.cellMapping addDefaultMappings];
    tableController.emptyItem = emptyItem;

    RKTableItem *headerItem = [RKTableItem tableItemWithText:@"Header"];
    headerItem.cellMapping.reuseIdentifier = @"HeaderCell";
    [headerItem.cellMapping addDefaultMappings];
    [tableController addHeaderRowForItem:headerItem];

    RKTableItem *footerItem = [RKTableItem tableItemWithText:@"Footer"];
    footerItem.cellMapping.reuseIdentifier = @"FooterCell";
    [footerItem.cellMapping addDefaultMappings];
    [tableController addFooterRowForItem:footerItem];

    [[[[UIApplication sharedApplication] windows] objectAtIndex:0] setRootViewController:tableViewController];

    [RKTestNotificationObserver waitForNotificationWithName:RKTableControllerDidFinishLoadNotification usingBlock:^{
        [tableController loadTable];
    }];

    // Let the table update
    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];

    assertThatInt([tableController rowCount], is(equalToInteger(1)));
    NSIndexPath *indexPath = [tableController indexPathForObject:emptyItem];
    assertThatInteger(indexPath.section, is(equalToInteger(0)));
    assertThatInteger(indexPath.row, is(equalToInteger(0)));
}

@end
