diff --git a/README.md b/README.md index 15d6a0b..7ac1240 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ Vox is a Swift JSONAPI standard implementation. [![Build Status](https://travis-ci.org/aronbalog/Vox.svg?branch=master)](https://travis-ci.org/aronbalog/Vox) [![codecov](https://codecov.io/gh/aronbalog/Vox/branch/master/graph/badge.svg)](https://codecov.io/gh/aronbalog/Vox) +[![Platform](https://img.shields.io/cocoapods/p/Vox.svg?style=flat)](https://github.com/aronbalog/Vox) +[![CocoaPods Compatible](https://img.shields.io/cocoapods/v/Vox.svg)](https://img.shields.io/cocoapods/v/Vox.svg) - 🎩 [The magic behind](#the-magic-behind) - 💻 [Installation](#motivation-) @@ -623,16 +625,36 @@ dataSource - [x] DataSource with path and client when creating resource client receives correct data for execution - [x] DataSource with path and client when fetching single resource invokes execute request on client - [x] DataSource with path and client when fetching single resource client receives correct data for execution -- [x] DataSource with path and client when fetching resource collection invokes execute request on client -- [x] DataSource with path and client when fetching resource collection client receives correct data for execution +- [x] DataSource with path and client when fetching resource collection with custom pagination invokes execute request on client +- [x] DataSource with path and client when fetching resource collection with custom pagination client receives correct data for execution +- [x] DataSource with path and client when fetching resource collection with page based pagination invokes execute request on client +- [x] DataSource with path and client when fetching resource collection with page based pagination client receives correct data for execution +- [x] DataSource with path and client when fetching resource collection with offset based pagination invokes execute request on client +- [x] DataSource with path and client when fetching resource collection with offset based pagination client receives correct data for execution +- [x] DataSource with path and client when fetching resource collection with cursor based pagination invokes execute request on client +- [x] DataSource with path and client when fetching resource collection with cursor based pagination client receives correct data for execution - [x] DataSource with path and client when updating resource invokes execute request on client - [x] DataSource with path and client when updating resource client receives correct data for execution - [x] DataSource with path and client when deleting resource invokes execute request on client - [x] DataSource with path and client when deleting resource client receives correct data for execution - [x] Deserializer when deserializing resource collection maps correctly -- [x] Deserializer when deserializing single resource and error data provided maps to errors object +- [x] Deserializer when deserializing single resource and error data provided with source object included in errors maps to errors object +- [x] Deserializer when deserializing single resource and error data provided with source object included in errors maps to errors object 2 - [x] Deserializer when deserializing document with polymorphic objects in relationships maps correctly - [x] Deserializer when deserializing single resource maps correctly +- [x] Paginated DataSource when fetching first page returns first page document +- [x] Paginated DataSource when fetching first page when fetching next page returns next page document +- [x] Paginated DataSource when fetching first page returns first page document 2 +- [x] Paginated DataSource when fetching first page when fetching first page of document returns first page document +- [x] Paginated DataSource when fetching first page returns first page document 3 +- [x] Paginated DataSource when fetching first page when appending next page document is appended +- [x] Paginated DataSource when fetching first page when appending next page included is appended +- [x] Paginated DataSource when fetching first page returns first page document 4 +- [x] Paginated DataSource when fetching first page when reloading current page receives page +- [x] Paginated DataSource when fetching first page returns first page document 5 +- [x] Paginated DataSource when fetching first page when fetching previous page receives page +- [x] Paginated DataSource when fetching first page returns first page document 6 +- [x] Paginated DataSource when fetching first page when fetching last page returns last page document - [x] Serializer when serializing resource collection maps correctly - [x] Serializer when serializing resource collection returns document data - [x] Serializer when serializing resource collection returns document dictionary diff --git a/Vox.playground/Contents.swift b/Vox.playground/Contents.swift index 2648b66..4562eae 100644 --- a/Vox.playground/Contents.swift +++ b/Vox.playground/Contents.swift @@ -6,9 +6,9 @@ class Cellphone: Resource { } } -class Weed: Resource { +class Wallet: Resource { override class var resourceType: String { - return "Weed" + return "Wallet" } } @@ -24,7 +24,7 @@ class Person: Resource { @objc dynamic var goodFriends: [Person]? @objc dynamic var favoriteWords: [String]? @objc dynamic var items: [Resource]? - @objc dynamic var favoriteWeed: Resource? + @objc dynamic var favoriteItem: Resource? } Resource.load() @@ -46,13 +46,13 @@ anotherFriend.id = "ANOTHER-FRIEND-ID" person.bestFriend = bestFriend person.goodFriends = [bestFriend, anotherFriend] -let weed = Weed() -weed.id = "weed" +let wallet = Wallet() +wallet.id = "wallet" let cellphone = Cellphone() cellphone.id = "cellphone" -person.items = [weed, cellphone] +person.items = [wallet, cellphone] -person.favoriteWeed = weed +person.favoriteItem = wallet let documentDictionary = try! person.documentDictionary() diff --git a/Vox.podspec b/Vox.podspec index 509b62a..43c54f9 100644 --- a/Vox.podspec +++ b/Vox.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'Vox' - spec.version = '1.0.3' + spec.version = '1.0.4' spec.license = 'MIT' spec.summary = 'A Swift JSONAPI framework' spec.author = 'Aron Balog' diff --git a/Vox/Class/BaseResource.m b/Vox/Class/BaseResource.m index be80dc0..63aeaa2 100644 --- a/Vox/Class/BaseResource.m +++ b/Vox/Class/BaseResource.m @@ -20,19 +20,14 @@ + (SEL)getterForPropertyWithName:(NSString*)name { + (SEL)setterForPropertyWithName:(NSString*)name { const char* propertyName = [name cStringUsingEncoding:NSASCIIStringEncoding]; - objc_property_t prop = class_getProperty(self, propertyName); - char *selectorName = property_copyAttributeValue(prop, "S"); NSString* selectorString; - if (selectorName == NULL) { - char firstChar = (char)toupper(propertyName[0]); - NSString* capitalLetter = [NSString stringWithFormat:@"%c", firstChar]; - NSString* reminder = [NSString stringWithCString: propertyName+1 - encoding: NSASCIIStringEncoding]; - selectorString = [@[@"set", capitalLetter, reminder, @":"] componentsJoinedByString:@""]; - } else { - selectorString = [NSString stringWithCString:selectorName encoding:NSASCIIStringEncoding]; - } + + char firstChar = (char)toupper(propertyName[0]); + NSString* capitalLetter = [NSString stringWithFormat:@"%c", firstChar]; + NSString* reminder = [NSString stringWithCString: propertyName+1 + encoding: NSASCIIStringEncoding]; + selectorString = [@[@"set", capitalLetter, reminder, @":"] componentsJoinedByString:@""]; return NSSelectorFromString(selectorString); } diff --git a/Vox/Class/Deserializer_Collection.swift b/Vox/Class/Deserializer_Collection.swift index 4cd9468..6edea3b 100644 --- a/Vox/Class/Deserializer_Collection.swift +++ b/Vox/Class/Deserializer_Collection.swift @@ -2,6 +2,8 @@ import Foundation extension Deserializer { public class Collection { + public init() {} + public func deserialize(data: Data) throws -> Document<[ResourceType]> { return try JSONAPIDecoder.decode(data: data) } diff --git a/VoxTests/DataSource/DataSourceSpec.swift b/VoxTests/DataSource/DataSourceSpec.swift index 4706c6d..e3bfd5e 100644 --- a/VoxTests/DataSource/DataSourceSpec.swift +++ b/VoxTests/DataSource/DataSourceSpec.swift @@ -344,7 +344,7 @@ class DataSourceSpec: QuickSpec { context("when fetching single resource", { let client = MockClient() let filterStrategy = MockFilterStrategy() - let sut = DataSource(strategy: .path(immutablePath), client: client) + let sut = DataSource(strategy: .path("path//"), client: client) try! sut .fetch(id: "mock") .fields([ @@ -368,7 +368,7 @@ class DataSourceSpec: QuickSpec { }) it("client receives correct data for execution", closure: { - expect(client.executeRequestInspector.path).to(equal("path/mock-resource")) + expect(client.executeRequestInspector.path).to(equal("path/mock-resource/mock")) let queryItems = client.executeRequestInspector.queryItems @@ -527,8 +527,9 @@ class DataSourceSpec: QuickSpec { context("when updating resource", { let client = MockClient() - let sut = DataSource(strategy: .path(immutablePath), client: client) + let sut = DataSource(strategy: .path("path//"), client: client) let resource = MockResource() + resource.id = "mock" try! sut.update(resource).result({ (document) in @@ -541,7 +542,7 @@ class DataSourceSpec: QuickSpec { }) it("client receives correct data for execution", closure: { - expect(client.executeRequestInspector.path).to(equal("path/mock-resource")) + expect(client.executeRequestInspector.path).to(equal("path/mock-resource/mock")) }) }) diff --git a/VoxTests/DataSource/PaginationSpec.swift b/VoxTests/DataSource/PaginationSpec.swift index 8ae33da..abd95ae 100644 --- a/VoxTests/DataSource/PaginationSpec.swift +++ b/VoxTests/DataSource/PaginationSpec.swift @@ -111,6 +111,8 @@ class PaginationSpec: QuickSpec { it("returns first page document", closure: { expect(error).toEventually(beNil()) expect(document).toEventuallyNot(beNil()) + expect(error).toEventually(beNil()) + expect(client.firstPath).toEventually(equal(router.fetch(type: Article3.self))) }) context("when fetching next page", { @@ -128,6 +130,44 @@ class PaginationSpec: QuickSpec { }) } + describe("Paginated DataSource") { + let router = MockRouter() + let client = MockClient() + let dataSource: DataSource = DataSource(strategy: .router(router), client: client) + + var document: Document<[Article3]>? + var firstDocument: Document<[Article3]>? + var error: Error? + + context("when fetching first page", { + try! dataSource.fetch().result({ (_document) in + document = _document + }, { (_error) in + error = _error + }) + + it("returns first page document", closure: { + expect(error).toEventually(beNil()) + expect(document).toEventuallyNot(beNil()) + expect(error).toEventually(beNil()) + expect(client.firstPath).toEventually(equal(router.fetch(type: Article3.self))) + }) + + context("when fetching first page of document", { + try! document?.first?.result({ (_firstDocument) in + firstDocument = _firstDocument + }, { (_error) in + error = _error + }) + + it("returns first page document", closure: { + expect(error).toEventually(beNil()) + expect(firstDocument).toEventuallyNot(beNil()) + }) + }) + }) + } + describe("Paginated DataSource") { let router = MockRouter() let client = MockClient() @@ -159,7 +199,7 @@ class PaginationSpec: QuickSpec { error = _error }) - it("receives page", closure: { + it("receives pagination", closure: { expect(error).toEventually(beNil()) expect(pagination).toEventuallyNot(beNil()) expect(pagination?.new).toEventually(haveCount(1)) @@ -183,6 +223,122 @@ class PaginationSpec: QuickSpec { }) }) } + + describe("Paginated DataSource") { + let router = MockRouter() + let client = MockClient() + let dataSource: DataSource = DataSource(strategy: .router(router), client: client) + + var document: Document<[Article3]>? + var reloadedDocument: Document<[Article3]>? + var error: Error? + + context("when fetching first page", { + try! dataSource.fetch().result({ (_document) in + document = _document + }, { (_error) in + error = _error + }) + + it("returns first page document", closure: { + expect(document).toEventuallyNot(beNil()) + expect(document?.links).toEventuallyNot(beNil()) + expect(error).toEventually(beNil()) + expect(client.firstPath).toEventually(equal(router.fetch(type: Article3.self))) + }) + + + context("when reloading current page", { + try! document?.reload?.result({ (document) in + reloadedDocument = document + }, { (_error) in + error = _error + }) + + it("receives page", closure: { + expect(error).toEventually(beNil()) + expect(reloadedDocument).toEventuallyNot(beNil()) + }) + }) + }) + } + + describe("Paginated DataSource") { + let router = MockRouter() + let client = MockClient() + let dataSource: DataSource = DataSource(strategy: .router(router), client: client) + + var document: Document<[Article3]>? + var previousDocument: Document<[Article3]>? + var error: Error? + + context("when fetching first page", { + try! dataSource.fetch().result({ (_document) in + document = _document + }, { (_error) in + error = _error + }) + + it("returns first page document", closure: { + expect(document).toEventuallyNot(beNil()) + expect(document?.links).toEventuallyNot(beNil()) + expect(error).toEventually(beNil()) + expect(client.firstPath).toEventually(equal(router.fetch(type: Article3.self))) + }) + + + context("when fetching previous page", { + try! document?.previous?.result({ (document) in + previousDocument = document + }, { (_error) in + error = _error + }) + + it("receives page", closure: { + expect(error).toEventually(beNil()) + expect(previousDocument).toEventuallyNot(beNil()) + }) + }) + }) + } + + describe("Paginated DataSource") { + let router = MockRouter() + let client = MockClient() + let dataSource: DataSource = DataSource(strategy: .router(router), client: client) + + var document: Document<[Article3]>? + var lastDocument: Document<[Article3]>? + var error: Error? + + context("when fetching first page", { + try! dataSource.fetch().result({ (_document) in + document = _document + }, { (_error) in + error = _error + }) + + it("returns first page document", closure: { + expect(error).toEventually(beNil()) + expect(document).toEventuallyNot(beNil()) + expect(error).toEventually(beNil()) + expect(client.firstPath).toEventually(equal(router.fetch(type: Article3.self))) + }) + + context("when fetching last page", { + try! document?.last?.result({ (_lastDocument) in + lastDocument = _lastDocument + }, { (_error) in + error = _error + }) + + it("returns last page document", closure: { + expect(error).toEventually(beNil()) + expect(lastDocument).toEventuallyNot(beNil()) + }) + }) + }) + } } } diff --git a/VoxTests/PropertyAccessorSpec.swift b/VoxTests/PropertyAccessorSpec.swift index 981626e..6ab42b4 100644 --- a/VoxTests/PropertyAccessorSpec.swift +++ b/VoxTests/PropertyAccessorSpec.swift @@ -24,9 +24,9 @@ fileprivate class Cellphone: Resource { } } -fileprivate class Weed: Resource { +fileprivate class Wallet: Resource { override class var resourceType: String { - return "Weed" + return "Wallet" } } @@ -51,12 +51,12 @@ class PropertyAccessorSpec: QuickSpec { person.bestFriend = bestFriend person.goodFriends = [bestFriend, anotherFriend] - let weed = Weed(context: person.context) - weed.id = "weed id" + let wallet = Wallet(context: person.context) + wallet.id = "wallet id" let cellphone = Cellphone() cellphone.id = "cellphone id" - person.items = [weed, cellphone] + person.items = [wallet, cellphone] it("values are accessible", closure: { expect(person.name).to(equal("MOCK")) @@ -79,7 +79,7 @@ class PropertyAccessorSpec: QuickSpec { expect(person.goodFriends?[1] === anotherFriend).to(beTrue()) expect(person.items).to(haveCount(2)) - expect(person.items?[0] === weed).to(beTrue()) + expect(person.items?[0] === wallet).to(beTrue()) expect(person.items?[1] === cellphone).to(beTrue()) })