diff --git a/Brick.xcodeproj/project.pbxproj b/Brick.xcodeproj/project.pbxproj index 98c83e9..2fef840 100644 --- a/Brick.xcodeproj/project.pbxproj +++ b/Brick.xcodeproj/project.pbxproj @@ -16,13 +16,13 @@ BD8070D71D9448830082800B /* Nimble.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BD8070D41D9448830082800B /* Nimble.framework */; }; BD8070D91D94488E0082800B /* Fakery.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BD8070D81D94488E0082800B /* Fakery.framework */; }; BDBA5F221D940F09003A5C6B /* Brick.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BDFD54221D940EDC003CB98B /* Brick.framework */; }; - BDDA5FEB1CBBCF45000FD5A6 /* ViewConfigurable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDDA5FEA1CBBCF45000FD5A6 /* ViewConfigurable.swift */; }; - BDDA5FEC1CBBCF45000FD5A6 /* ViewConfigurable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDDA5FEA1CBBCF45000FD5A6 /* ViewConfigurable.swift */; }; - BDE7D2171D940FC300027CE4 /* ViewModelSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5C629971C3A8BDA007F7B7C /* ViewModelSpec.swift */; }; + BDDA5FEB1CBBCF45000FD5A6 /* ItemConfigurable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDDA5FEA1CBBCF45000FD5A6 /* ItemConfigurable.swift */; }; + BDDA5FEC1CBBCF45000FD5A6 /* ItemConfigurable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDDA5FEA1CBBCF45000FD5A6 /* ItemConfigurable.swift */; }; + BDE7D2171D940FC300027CE4 /* ItemSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5C629971C3A8BDA007F7B7C /* ItemSpec.swift */; }; BDE7D2181D940FC300027CE4 /* ExtensionsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5ACAC9C1CCE449A00567809 /* ExtensionsSpec.swift */; }; BDE7D2191D940FC300027CE4 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57832961CE142C8005ED144 /* Helpers.swift */; }; - BDE7D21A1D940FCD00027CE4 /* ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5C629861C3A89A8007F7B7C /* ViewModel.swift */; }; - BDE7D21B1D940FCD00027CE4 /* ViewConfigurable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDDA5FEA1CBBCF45000FD5A6 /* ViewConfigurable.swift */; }; + BDE7D21A1D940FCD00027CE4 /* Item.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5C629861C3A89A8007F7B7C /* Item.swift */; }; + BDE7D21B1D940FCD00027CE4 /* ItemConfigurable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDDA5FEA1CBBCF45000FD5A6 /* ItemConfigurable.swift */; }; BDE7D21C1D940FCD00027CE4 /* StringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAA38AA41CBEEAD900D48603 /* StringConvertible.swift */; }; BDE7D21D1D940FCD00027CE4 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5ACAC991CCE439F00567809 /* Extensions.swift */; }; D57832991CE142DE005ED144 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57832961CE142C8005ED144 /* Helpers.swift */; }; @@ -37,10 +37,10 @@ D5C629781C3A878E007F7B7C /* Nimble.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D5C629761C3A878E007F7B7C /* Nimble.framework */; }; D5C6297B1C3A879F007F7B7C /* Quick.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D5C629791C3A879F007F7B7C /* Quick.framework */; }; D5C6297C1C3A879F007F7B7C /* Nimble.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D5C6297A1C3A879F007F7B7C /* Nimble.framework */; }; - D5C629871C3A89A8007F7B7C /* ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5C629861C3A89A8007F7B7C /* ViewModel.swift */; }; - D5C629881C3A89A8007F7B7C /* ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5C629861C3A89A8007F7B7C /* ViewModel.swift */; }; - D5C6299C1C3A8BDA007F7B7C /* ViewModelSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5C629971C3A8BDA007F7B7C /* ViewModelSpec.swift */; }; - D5C6299E1C3A8C75007F7B7C /* ViewModelSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5C629971C3A8BDA007F7B7C /* ViewModelSpec.swift */; }; + D5C629871C3A89A8007F7B7C /* Item.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5C629861C3A89A8007F7B7C /* Item.swift */; }; + D5C629881C3A89A8007F7B7C /* Item.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5C629861C3A89A8007F7B7C /* Item.swift */; }; + D5C6299C1C3A8BDA007F7B7C /* ItemSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5C629971C3A8BDA007F7B7C /* ItemSpec.swift */; }; + D5C6299E1C3A8C75007F7B7C /* ItemSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5C629971C3A8BDA007F7B7C /* ItemSpec.swift */; }; DAA38AA51CBEEAD900D48603 /* StringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAA38AA41CBEEAD900D48603 /* StringConvertible.swift */; }; DAA38AA61CBEEAD900D48603 /* StringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAA38AA41CBEEAD900D48603 /* StringConvertible.swift */; }; /* End PBXBuildFile section */ @@ -80,7 +80,7 @@ BD8070D41D9448830082800B /* Nimble.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Nimble.framework; path = Carthage/Build/tvOS/Nimble.framework; sourceTree = ""; }; BD8070D81D94488E0082800B /* Fakery.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Fakery.framework; path = Carthage/Build/tvOS/Fakery.framework; sourceTree = ""; }; BDBA5F1D1D940F09003A5C6B /* Brick-tvOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Brick-tvOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - BDDA5FEA1CBBCF45000FD5A6 /* ViewConfigurable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewConfigurable.swift; sourceTree = ""; }; + BDDA5FEA1CBBCF45000FD5A6 /* ItemConfigurable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemConfigurable.swift; sourceTree = ""; }; BDE7D2151D940F7800027CE4 /* Info-tvOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-tvOS.plist"; sourceTree = ""; }; BDE7D2161D940FAA00027CE4 /* Info-tvOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-tvOS.plist"; sourceTree = ""; }; BDFD54221D940EDC003CB98B /* Brick.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Brick.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -97,12 +97,12 @@ D5C629761C3A878E007F7B7C /* Nimble.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Nimble.framework; path = Carthage/Build/iOS/Nimble.framework; sourceTree = ""; }; D5C629791C3A879F007F7B7C /* Quick.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Quick.framework; path = Carthage/Build/Mac/Quick.framework; sourceTree = ""; }; D5C6297A1C3A879F007F7B7C /* Nimble.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Nimble.framework; path = Carthage/Build/Mac/Nimble.framework; sourceTree = ""; }; - D5C629861C3A89A8007F7B7C /* ViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewModel.swift; sourceTree = ""; }; + D5C629861C3A89A8007F7B7C /* Item.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Item.swift; sourceTree = ""; }; D5C6298B1C3A8BBD007F7B7C /* Info-iOS.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-iOS.plist"; sourceTree = ""; }; D5C6298C1C3A8BBD007F7B7C /* Info-Mac.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-Mac.plist"; sourceTree = ""; }; D5C629901C3A8BDA007F7B7C /* Info-iOS.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-iOS.plist"; sourceTree = ""; }; D5C629911C3A8BDA007F7B7C /* Info-Mac.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-Mac.plist"; sourceTree = ""; }; - D5C629971C3A8BDA007F7B7C /* ViewModelSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewModelSpec.swift; sourceTree = ""; }; + D5C629971C3A8BDA007F7B7C /* ItemSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemSpec.swift; sourceTree = ""; }; DAA38AA41CBEEAD900D48603 /* StringConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringConvertible.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -214,8 +214,8 @@ D5C6296E1C3A809D007F7B7C /* Shared */ = { isa = PBXGroup; children = ( - D5C629861C3A89A8007F7B7C /* ViewModel.swift */, - BDDA5FEA1CBBCF45000FD5A6 /* ViewConfigurable.swift */, + D5C629861C3A89A8007F7B7C /* Item.swift */, + BDDA5FEA1CBBCF45000FD5A6 /* ItemConfigurable.swift */, DAA38AA41CBEEAD900D48603 /* StringConvertible.swift */, D5ACAC991CCE439F00567809 /* Extensions.swift */, ); @@ -252,7 +252,7 @@ D5C629961C3A8BDA007F7B7C /* Shared */ = { isa = PBXGroup; children = ( - D5C629971C3A8BDA007F7B7C /* ViewModelSpec.swift */, + D5C629971C3A8BDA007F7B7C /* ItemSpec.swift */, D5ACAC9C1CCE449A00567809 /* ExtensionsSpec.swift */, D57832961CE142C8005ED144 /* Helpers.swift */, ); @@ -618,7 +618,7 @@ buildActionMask = 2147483647; files = ( BDE7D2181D940FC300027CE4 /* ExtensionsSpec.swift in Sources */, - BDE7D2171D940FC300027CE4 /* ViewModelSpec.swift in Sources */, + BDE7D2171D940FC300027CE4 /* ItemSpec.swift in Sources */, BDE7D2191D940FC300027CE4 /* Helpers.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -627,8 +627,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - BDE7D21A1D940FCD00027CE4 /* ViewModel.swift in Sources */, - BDE7D21B1D940FCD00027CE4 /* ViewConfigurable.swift in Sources */, + BDE7D21A1D940FCD00027CE4 /* Item.swift in Sources */, + BDE7D21B1D940FCD00027CE4 /* ItemConfigurable.swift in Sources */, BDE7D21C1D940FCD00027CE4 /* StringConvertible.swift in Sources */, BDE7D21D1D940FCD00027CE4 /* Extensions.swift in Sources */, ); @@ -638,9 +638,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - D5C629871C3A89A8007F7B7C /* ViewModel.swift in Sources */, + D5C629871C3A89A8007F7B7C /* Item.swift in Sources */, D5ACAC9A1CCE439F00567809 /* Extensions.swift in Sources */, - BDDA5FEB1CBBCF45000FD5A6 /* ViewConfigurable.swift in Sources */, + BDDA5FEB1CBBCF45000FD5A6 /* ItemConfigurable.swift in Sources */, DAA38AA51CBEEAD900D48603 /* StringConvertible.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -651,7 +651,7 @@ files = ( D5ACAC9F1CCE45CE00567809 /* ExtensionsSpec.swift in Sources */, D57832991CE142DE005ED144 /* Helpers.swift in Sources */, - D5C6299E1C3A8C75007F7B7C /* ViewModelSpec.swift in Sources */, + D5C6299E1C3A8C75007F7B7C /* ItemSpec.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -661,8 +661,8 @@ files = ( DAA38AA61CBEEAD900D48603 /* StringConvertible.swift in Sources */, D5ACAC9B1CCE439F00567809 /* Extensions.swift in Sources */, - D5C629881C3A89A8007F7B7C /* ViewModel.swift in Sources */, - BDDA5FEC1CBBCF45000FD5A6 /* ViewConfigurable.swift in Sources */, + D5C629881C3A89A8007F7B7C /* Item.swift in Sources */, + BDDA5FEC1CBBCF45000FD5A6 /* ItemConfigurable.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -672,7 +672,7 @@ files = ( D5ACACA01CCE45CF00567809 /* ExtensionsSpec.swift in Sources */, D578329A1CE142DF005ED144 /* Helpers.swift in Sources */, - D5C6299C1C3A8BDA007F7B7C /* ViewModelSpec.swift in Sources */, + D5C6299C1C3A8BDA007F7B7C /* ItemSpec.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/BrickTests/Shared/ExtensionsSpec.swift b/BrickTests/Shared/ExtensionsSpec.swift index ea9da08..57e7d2c 100644 --- a/BrickTests/Shared/ExtensionsSpec.swift +++ b/BrickTests/Shared/ExtensionsSpec.swift @@ -7,11 +7,11 @@ class ExtensionsSpec: QuickSpec { override func spec() { describe("Array+Brick") { - var items = [ViewModel]() + var items = [Item]() beforeEach { for index in 0..<10 { - var item = ViewModel(title: "Test") + var item = Item(title: "Test") item.index = 9 - index items.append(item) diff --git a/BrickTests/Shared/ItemSpec.swift b/BrickTests/Shared/ItemSpec.swift new file mode 100644 index 0000000..69a09cf --- /dev/null +++ b/BrickTests/Shared/ItemSpec.swift @@ -0,0 +1,203 @@ +@testable import Brick +import Quick +import Nimble +import Fakery + +class ItemSpec: QuickSpec { + + override func spec() { + describe("Item") { + var item: Item! + var data: [String : AnyObject]! + let faker = Faker() + + beforeEach { + data = [ + "title": faker.lorem.paragraph(), + "subtitle": faker.lorem.paragraph(), + "image" : faker.internet.image(), + "kind" : faker.team.name(), + "action" : faker.internet.ipV6Address(), + "meta" : [ + "domain" : faker.internet.domainName() + ] + ] + } + + describe("#mapping") { + beforeEach { + item = Item(data) + } + + it("it creates an instance") { + expect(item).notTo(beNil()) + } + + it("sets values") { + expect(item.title).to(equal(data["title"] as? String)) + expect(item.subtitle).to(equal(data["subtitle"] as? String)) + expect(item.image).to(equal(data["image"] as? String)) + expect(item.kind).to(equal(data["kind"] as? String)) + expect(item.action).to(equal(data["action"] as? String)) + expect(item.meta("domain", "")).to(equal(data["meta"]!["domain"])) + } + } + + describe("#relations") { + it("sets relations") { + data["relations"] = ["Items" : [data, data, data]] + item = Item(data) + + expect(item.relations["Items"]!.count).to(equal(3)) + expect(item.relations["Items"]!.first!.title).to(equal(data["title"] as? String)) + expect(item.relations["Items"]!.first!.subtitle).to(equal(data["subtitle"] as? String)) + expect(item.relations["Items"]!.first!.image).to(equal(data["image"] as? String)) + expect(item.relations["Items"]!.first!.kind).to(equal(data["kind"] as? String)) + expect(item.relations["Items"]!.first!.action).to(equal(data["action"] as? String)) + + expect(item.relations["Items"]!.last!.title).to(equal(data["title"] as? String)) + expect(item.relations["Items"]!.last!.subtitle).to(equal(data["subtitle"] as? String)) + expect(item.relations["Items"]!.last!.image).to(equal(data["image"] as? String)) + expect(item.relations["Items"]!.last!.kind).to(equal(data["kind"] as? String)) + expect(item.relations["Items"]!.last!.action).to(equal(data["action"] as? String)) + + let Item2 = item + expect(item == Item2).to(beTrue()) + + item.relations["Items"]![2].title = "new" + expect(item == Item2).to(beFalse()) + } + } + + describe("#meta") { + it("resolves meta data created from JSON") { + item = Item(data) + expect(item.meta("domain", "")).to(equal(data["meta"]!["domain"])) + } + + it("resolves meta data created from object") { + var data = ["id": 11, "name": "Name"] + + item = Item(meta: Meta(data)) + expect(item.meta("id", 0)).to(equal(data["id"])) + expect(item.meta("name", "")).to(equal(data["name"])) + } + } + + describe("#metaInstance") { + it("resolves meta data created from object") { + var data = ["id": 11, "name": "Name"] + item = Item(meta: Meta(data)) + let result: Meta = item.metaInstance() + + expect(result.id).to(equal(data["id"])) + expect(result.name).to(equal(data["name"])) + } + } + + describe("#equality") { + it("compares two view models that are equal using identifier") { + let left = Item(identifier: "foo".hashValue) + let right = Item(identifier: "foo".hashValue) + + expect(left === right).to(beTrue()) + } + + it("compares two view models that are not equal using identifier") { + let left = Item(identifier: "foo".hashValue) + let right = Item(identifier: "bar".hashValue) + + expect(left === right).to(beFalse()) + } + + it("compares two view models that are equal") { + let left = Item(title: "foo", size: CGSize(width: 40, height: 40)) + let right = Item(title: "foo", size: CGSize(width: 40, height: 40)) + + expect(left === right).to(beTrue()) + } + + it("compares two unequal view model") { + let left = Item(title: "foo", size: CGSize(width: 40, height: 40)) + let right = Item(title: "foo", size: CGSize(width: 60, height: 60)) + + expect(left === right).to(beFalse()) + } + + it("compares a collection of view models that are equal") { + let left = [ + Item(title: "foo", size: CGSize(width: 40, height: 40)), + Item(title: "foo", size: CGSize(width: 40, height: 40)) + ] + let right = [ + Item(title: "foo", size: CGSize(width: 40, height: 40)), + Item(title: "foo", size: CGSize(width: 40, height: 40)) + ] + + expect(left === right).to(beTrue()) + } + + it("compares a collection of view models that are not equal") { + let left = [ + Item(title: "foo", size: CGSize(width: 40, height: 40)), + Item(title: "foo", size: CGSize(width: 60, height: 40)) + ] + let right = [ + Item(title: "foo", size: CGSize(width: 40, height: 40)), + Item(title: "foo", size: CGSize(width: 40, height: 40)) + ] + + expect(left === right).to(beFalse()) + } + } + + describe("#dictionary") { + beforeEach { + data["relations"] = ["Items" : [data, data]] + item = Item(data) + } + + it("returns a dictionary representation of the view model") { + let newItem = Item(item.dictionary) + + expect(newItem == item).to(beTrue()) + expect(newItem.relations["Items"]!.count).to(equal(item.relations["Items"]!.count)) + expect(newItem.relations["Items"]!.first! + == item.relations["Items"]!.first!).to(beTrue()) + expect(newItem.relations["Items"]!.last! + == item.relations["Items"]!.last!).to(beTrue()) + } + } + + describe("#update:kind") { + beforeEach { + item = Item(data) + } + + it("updates kind") { + item.update(kind: "test") + expect(item.kind).to(equal("test")) + } + } + + describe("#compareRelations") { + beforeEach { + data["relations"] = ["Items" : [data, data, data]] + item = Item(data) + } + + it("compare relations properly") { + var Item2 = Item(data) + expect(compareRelations(item, Item2)).to(beTrue()) + + Item2.relations["Items"]![2].title = "new" + expect(compareRelations(item, Item2)).to(beFalse()) + + data["relations"] = ["Items" : [data, data]] + Item2 = Item(data) + expect(compareRelations(item, Item2)).to(beFalse()) + } + } + } + } +} diff --git a/BrickTests/Shared/ViewModelSpec.swift b/BrickTests/Shared/ViewModelSpec.swift deleted file mode 100644 index f91d064..0000000 --- a/BrickTests/Shared/ViewModelSpec.swift +++ /dev/null @@ -1,203 +0,0 @@ -@testable import Brick -import Quick -import Nimble -import Fakery - -class ViewModelSpec: QuickSpec { - - override func spec() { - describe("ViewModel") { - var viewModel: ViewModel! - var data: [String : AnyObject]! - let faker = Faker() - - beforeEach { - data = [ - "title": faker.lorem.paragraph(), - "subtitle": faker.lorem.paragraph(), - "image" : faker.internet.image(), - "kind" : faker.team.name(), - "action" : faker.internet.ipV6Address(), - "meta" : [ - "domain" : faker.internet.domainName() - ] - ] - } - - describe("#mapping") { - beforeEach { - viewModel = ViewModel(data) - } - - it("it creates an instance") { - expect(viewModel).notTo(beNil()) - } - - it("sets values") { - expect(viewModel.title).to(equal(data["title"] as? String)) - expect(viewModel.subtitle).to(equal(data["subtitle"] as? String)) - expect(viewModel.image).to(equal(data["image"] as? String)) - expect(viewModel.kind).to(equal(data["kind"] as? String)) - expect(viewModel.action).to(equal(data["action"] as? String)) - expect(viewModel.meta("domain", "")).to(equal(data["meta"]!["domain"])) - } - } - - describe("#relations") { - it("sets relations") { - data["relations"] = ["viewmodels" : [data, data, data]] - viewModel = ViewModel(data) - - expect(viewModel.relations["viewmodels"]!.count).to(equal(3)) - expect(viewModel.relations["viewmodels"]!.first!.title).to(equal(data["title"] as? String)) - expect(viewModel.relations["viewmodels"]!.first!.subtitle).to(equal(data["subtitle"] as? String)) - expect(viewModel.relations["viewmodels"]!.first!.image).to(equal(data["image"] as? String)) - expect(viewModel.relations["viewmodels"]!.first!.kind).to(equal(data["kind"] as? String)) - expect(viewModel.relations["viewmodels"]!.first!.action).to(equal(data["action"] as? String)) - - expect(viewModel.relations["viewmodels"]!.last!.title).to(equal(data["title"] as? String)) - expect(viewModel.relations["viewmodels"]!.last!.subtitle).to(equal(data["subtitle"] as? String)) - expect(viewModel.relations["viewmodels"]!.last!.image).to(equal(data["image"] as? String)) - expect(viewModel.relations["viewmodels"]!.last!.kind).to(equal(data["kind"] as? String)) - expect(viewModel.relations["viewmodels"]!.last!.action).to(equal(data["action"] as? String)) - - let viewModel2 = viewModel - expect(viewModel == viewModel2).to(beTrue()) - - viewModel.relations["viewmodels"]![2].title = "new" - expect(viewModel == viewModel2).to(beFalse()) - } - } - - describe("#meta") { - it("resolves meta data created from JSON") { - viewModel = ViewModel(data) - expect(viewModel.meta("domain", "")).to(equal(data["meta"]!["domain"])) - } - - it("resolves meta data created from object") { - var data = ["id": 11, "name": "Name"] - - viewModel = ViewModel(meta: Meta(data)) - expect(viewModel.meta("id", 0)).to(equal(data["id"])) - expect(viewModel.meta("name", "")).to(equal(data["name"])) - } - } - - describe("#metaInstance") { - it("resolves meta data created from object") { - var data = ["id": 11, "name": "Name"] - viewModel = ViewModel(meta: Meta(data)) - let result: Meta = viewModel.metaInstance() - - expect(result.id).to(equal(data["id"])) - expect(result.name).to(equal(data["name"])) - } - } - - describe("#equality") { - it("compares two view models that are equal using identifier") { - let left = ViewModel(identifier: "foo".hashValue) - let right = ViewModel(identifier: "foo".hashValue) - - expect(left === right).to(beTrue()) - } - - it("compares two view models that are not equal using identifier") { - let left = ViewModel(identifier: "foo".hashValue) - let right = ViewModel(identifier: "bar".hashValue) - - expect(left === right).to(beFalse()) - } - - it("compares two view models that are equal") { - let left = ViewModel(title: "foo", size: CGSize(width: 40, height: 40)) - let right = ViewModel(title: "foo", size: CGSize(width: 40, height: 40)) - - expect(left === right).to(beTrue()) - } - - it("compares two unequal view model") { - let left = ViewModel(title: "foo", size: CGSize(width: 40, height: 40)) - let right = ViewModel(title: "foo", size: CGSize(width: 60, height: 60)) - - expect(left === right).to(beFalse()) - } - - it("compares a collection of view models that are equal") { - let left = [ - ViewModel(title: "foo", size: CGSize(width: 40, height: 40)), - ViewModel(title: "foo", size: CGSize(width: 40, height: 40)) - ] - let right = [ - ViewModel(title: "foo", size: CGSize(width: 40, height: 40)), - ViewModel(title: "foo", size: CGSize(width: 40, height: 40)) - ] - - expect(left === right).to(beTrue()) - } - - it("compares a collection of view models that are not equal") { - let left = [ - ViewModel(title: "foo", size: CGSize(width: 40, height: 40)), - ViewModel(title: "foo", size: CGSize(width: 60, height: 40)) - ] - let right = [ - ViewModel(title: "foo", size: CGSize(width: 40, height: 40)), - ViewModel(title: "foo", size: CGSize(width: 40, height: 40)) - ] - - expect(left === right).to(beFalse()) - } - } - - describe("#dictionary") { - beforeEach { - data["relations"] = ["viewmodels" : [data, data]] - viewModel = ViewModel(data) - } - - it("returns a dictionary representation of the view model") { - let newViewModel = ViewModel(viewModel.dictionary) - - expect(newViewModel == viewModel).to(beTrue()) - expect(newViewModel.relations["viewmodels"]!.count).to(equal(viewModel.relations["viewmodels"]!.count)) - expect(newViewModel.relations["viewmodels"]!.first! - == viewModel.relations["viewmodels"]!.first!).to(beTrue()) - expect(newViewModel.relations["viewmodels"]!.last! - == viewModel.relations["viewmodels"]!.last!).to(beTrue()) - } - } - - describe("#update:kind") { - beforeEach { - viewModel = ViewModel(data) - } - - it("updates kind") { - viewModel.update(kind: "test") - expect(viewModel.kind).to(equal("test")) - } - } - - describe("#compareRelations") { - beforeEach { - data["relations"] = ["viewmodels" : [data, data, data]] - viewModel = ViewModel(data) - } - - it("compare relations properly") { - var viewModel2 = ViewModel(data) - expect(compareRelations(viewModel, viewModel2)).to(beTrue()) - - viewModel2.relations["viewmodels"]![2].title = "new" - expect(compareRelations(viewModel, viewModel2)).to(beFalse()) - - data["relations"] = ["viewmodels" : [data, data]] - viewModel2 = ViewModel(data) - expect(compareRelations(viewModel, viewModel2)).to(beFalse()) - } - } - } - } -} diff --git a/Playground-Mac.playground/Contents.swift b/Playground-Mac.playground/Contents.swift index 2feb2e3..3d46dab 100644 --- a/Playground-Mac.playground/Contents.swift +++ b/Playground-Mac.playground/Contents.swift @@ -3,7 +3,7 @@ import Cocoa import Brick -let item = ViewModel( +let item = Item( title: "John Hyperseed", subtitle: "Build machine", meta: [ diff --git a/Playground-iOS.playground/Contents.swift b/Playground-iOS.playground/Contents.swift index f9f5b38..cfb7819 100644 --- a/Playground-iOS.playground/Contents.swift +++ b/Playground-iOS.playground/Contents.swift @@ -3,7 +3,7 @@ import UIKit import Brick -let item = ViewModel( +let item = Item( title: "John Hyperseed", subtitle: "Build machine", meta: [ diff --git a/README.md b/README.md index 2a79ec9..03f9f72 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Brick is a generic view model for both basic and complex scenarios. Mapping a basic table view cells is as easy as pie, if you have more properties, you can use the `meta` dictionary to add all additional properties that you might need. It also supports relations so that you can nest view models inside of view models. ```swift -public struct ViewModel: Mappable { +public struct Item: Mappable { public var index = 0 public var title = "" public var subtitle = "" @@ -48,7 +48,7 @@ This is used for extra data that you might need access to inside of your view, i ## Usage ```swift -let item = ViewModel( +let item = Item( title: "John Hyperseed", subtitle: "Build machine", meta: [ diff --git a/Sources/Shared/Extensions.swift b/Sources/Shared/Extensions.swift index 1e03241..100b898 100644 --- a/Sources/Shared/Extensions.swift +++ b/Sources/Shared/Extensions.swift @@ -2,7 +2,7 @@ import Tailor // MARK: - Array -public extension _ArrayType where Generator.Element == ViewModel { +public extension _ArrayType where Generator.Element == Item { mutating func refreshIndexes() { enumerate().forEach { @@ -19,20 +19,22 @@ public extension _ArrayType where Generator.Element == ViewModel { extension Dictionary where Key: StringLiteralConvertible { /** - - Parameter name: The name of the property that you want to map - - Returns: A generic type if casting succeeds, otherwise it returns nil + - parameter name: The name of the property that you want to map + + - returns: A generic type if casting succeeds, otherwise it returns nil */ - func property(name: ViewModel.Key) -> T? { + func property(name: Item.Key) -> T? { return property(name.string) } /** Access the value associated with the given key. - - Parameter key: The key associated with the value you want to get - - Returns: The value associated with the given key + - parameter key: The key associated with the value you want to get + + - returns: The value associated with the given key */ - subscript(key: ViewModel.Key) -> Value? { + subscript(key: Item.Key) -> Value? { set(value) { guard let key = key.string as? Key else { return } self[key] = value @@ -49,7 +51,7 @@ extension Dictionary where Key: StringLiteralConvertible { extension Mappable { /** - - Returns: A key-value dictionary. + - returns: A key-value dictionary. */ var metaProperties: [String : AnyObject] { var properties = [String : AnyObject]() diff --git a/Sources/Shared/ViewModel.swift b/Sources/Shared/Item.swift similarity index 65% rename from Sources/Shared/ViewModel.swift rename to Sources/Shared/Item.swift index 169bac4..ca5024e 100644 --- a/Sources/Shared/ViewModel.swift +++ b/Sources/Shared/Item.swift @@ -9,7 +9,7 @@ import Tailor /** A value type struct, it conforms to the Mappable protocol so that it can be instantiated with JSON */ -public struct ViewModel: Mappable { +public struct Item: Mappable { /** An enum with all the string keys used in the view model @@ -35,19 +35,19 @@ public struct ViewModel: Mappable { } } - /// The index of the ViewModel when appearing in a list, should be computed and continuously updated by the data source + /// The index of the Item when appearing in a list, should be computed and continuously updated by the data source public var index = 0 /// An optional identifier for your data public var identifier: Int? - /// The main representation of the ViewModel + /// The main representation of the Item public var title = "" - /// Supplementary information to the ViewModel + /// Supplementary information to the Item public var subtitle = "" - /// A visual representation of the ViewModel, usually a string URL or image name + /// A visual representation of the Item, usually a string URL or image name public var image = "" - /// Determines what kind of UI should be used to represent the ViewModel + /// Determines what kind of UI should be used to represent the Item public var kind: String = "" - /// A string representation of what should happens when a ViewModel is tapped, usually a URN or URL + /// A string representation of what should happens when a Item is tapped, usually a URN or URL public var action: String? /// The width and height of the view model, usually calculated and updated by the UI component public var size = CGSize(width: 0, height: 0) @@ -56,7 +56,7 @@ public struct ViewModel: Mappable { /// A key-value dictionary for any additional information public var meta = [String : AnyObject]() /// A key-value dictionary for related view models - public var relations = [String : [ViewModel]]() + public var relations = [String : [Item]]() /// A dictionary representation of the view model public var dictionary: [String : AnyObject] { @@ -101,9 +101,9 @@ public struct ViewModel: Mappable { // MARK: - Initialization /** - Initialization a new instance of a ViewModel and map it to a JSON dictionary + Initialization a new instance of a Item and map it to a JSON dictionary - - Parameter map: A JSON dictionary + - parameter map: A JSON dictionary */ public init(_ map: [String : AnyObject]) { index <- map.property(.Index) @@ -116,15 +116,15 @@ public struct ViewModel: Mappable { meta <- map.property(.Meta) children = map[.Children] as? [[String : AnyObject]] ?? [] - if let relation = map[.Relations] as? [String : [ViewModel]] { + if let relation = map[.Relations] as? [String : [Item]] { relations = relation } if let relations = map[.Relations] as? [String : [[String : AnyObject]]] { - var newRelations = [String : [ViewModel]]() + var newRelations = [String : [Item]]() relations.forEach { key, array in - if newRelations[key] == nil { newRelations[key] = [ViewModel]() } - array.forEach { newRelations[key]?.append(ViewModel($0)) } + if newRelations[key] == nil { newRelations[key] = [Item]() } + array.forEach { newRelations[key]?.append(Item($0)) } self.relations = newRelations } @@ -137,13 +137,13 @@ public struct ViewModel: Mappable { } /** - Initialization a new instance of a ViewModel and map it to a JSON dictionary + Initialization a new instance of a Item and map it to a JSON dictionary - - Parameter title: The title string for the view model, defaults to empty string - - Parameter subtitle: The subtitle string for the view model, default to empty string - - Parameter image: Image name or URL as a string, default to empty string + - parameter title: The title string for the view model, defaults to empty string + - parameter subtitle: The subtitle string for the view model, default to empty string + - parameter image: Image name or URL as a string, default to empty string */ - public init(identifier: Int? = nil, title: String = "", subtitle: String = "", image: String = "", kind: StringConvertible = "", action: String? = nil, size: CGSize = CGSize(width: 0, height: 0), meta: [String : AnyObject] = [:], relations: [String : [ViewModel]] = [:]) { + public init(identifier: Int? = nil, title: String = "", subtitle: String = "", image: String = "", kind: StringConvertible = "", action: String? = nil, size: CGSize = CGSize(width: 0, height: 0), meta: [String : AnyObject] = [:], relations: [String : [Item]] = [:]) { self.identifier = identifier self.title = title self.subtitle = subtitle @@ -156,13 +156,13 @@ public struct ViewModel: Mappable { } /** - Initialization a new instance of a ViewModel and map it to a JSON dictionary + Initialization a new instance of a Item and map it to a JSON dictionary - - Parameter title: The title string for the view model, defaults to empty string - - Parameter subtitle: The subtitle string for the view model, default to empty string - - Parameter image: Image name or URL as a string, default to empty string + - parameter title: The title string for the view model, defaults to empty string + - parameter subtitle: The subtitle string for the view model, default to empty string + - parameter image: Image name or URL as a string, default to empty string */ - public init(identifier: Int? = nil, title: String = "", subtitle: String = "", image: String = "", kind: StringConvertible = "", action: String? = nil, size: CGSize = CGSize(width: 0, height: 0), meta: Mappable, relations: [String : [ViewModel]] = [:]) { + public init(identifier: Int? = nil, title: String = "", subtitle: String = "", image: String = "", kind: StringConvertible = "", action: String? = nil, size: CGSize = CGSize(width: 0, height: 0), meta: Mappable, relations: [String : [Item]] = [:]) { self.init(identifier: identifier, title: title, subtitle: subtitle, image: image, kind: kind, action: action, size: size, meta: meta.metaProperties, relations: relations) } @@ -172,9 +172,10 @@ public struct ViewModel: Mappable { /** A generic convenience method for resolving meta attributes - - Parameter key: String - - Parameter defaultValue: A generic value that works as a fallback if the key value object cannot be cast into the generic type - - Returns: A generic value based on `defaultValue`, it falls back to `defaultValue` if type casting fails + - parameter key: String + - parameter defaultValue: A generic value that works as a fallback if the key value object cannot be cast into the generic type + + - returns: A generic value based on `defaultValue`, it falls back to `defaultValue` if type casting fails */ public func meta(key: String, _ defaultValue: T) -> T { return meta[key] as? T ?? defaultValue @@ -183,9 +184,10 @@ public struct ViewModel: Mappable { /** A generic convenience method for resolving meta attributes - - Parameter key: String - - Parameter type: A generic type used for casting the meta property to a specific value or reference type - - Returns: An optional generic value based on `type` + - parameter key: String + - parameter type: A generic type used for casting the meta property to a specific value or reference type + + - returns: An optional generic value based on `type` */ public func meta(key: String, type: T.Type) -> T? { return meta[key] as? T @@ -194,7 +196,7 @@ public struct ViewModel: Mappable { /** A generic convenience method for resolving meta instance - - Returns: A generic meta instance based on `type` + - returns: A generic meta instance based on `type` */ public func metaInstance() -> T { return T(meta) @@ -203,10 +205,10 @@ public struct ViewModel: Mappable { /** A convenience lookup method for resolving view model relations - - Parameter key: String - - Parameter index: The index of the object inside of `self.relations` + - parameter key: String + - parameter index: The index of the object inside of `self.relations` */ - public func relation(key: String, _ index: Int) -> ViewModel? { + public func relation(key: String, _ index: Int) -> Item? { if let items = relations[key] where index < items.count { return items[index] } else { @@ -217,7 +219,7 @@ public struct ViewModel: Mappable { /** A method for mutating the kind of a view model - - Parameter kind: A StringConvertible object + - parameter kind: A StringConvertible object */ public mutating func update(kind kind: StringConvertible) { self.kind = kind.string @@ -225,12 +227,12 @@ public struct ViewModel: Mappable { } /** - A collection of ViewModel Equatable implementation - - Parameter lhs: Left hand collection of ViewModels - - Parameter rhs: Right hand collection of ViewModels - - Returns: A boolean value, true if both ViewModel are equal + A collection of Item Equatable implementation + - parameter lhs: Left hand collection of Items + - parameter rhs: Right hand collection of Items + - returns: A boolean value, true if both Item are equal */ -public func ==(lhs: [ViewModel], rhs: [ViewModel]) -> Bool { +public func ==(lhs: [Item], rhs: [Item]) -> Bool { var equal = lhs.count == rhs.count if !equal { return false } @@ -243,12 +245,12 @@ public func ==(lhs: [ViewModel], rhs: [ViewModel]) -> Bool { } /** - A collection of ViewModel Equatable implementation to see if they are truly equal - - Parameter lhs: Left hand collection of ViewModels - - Parameter rhs: Right hand collection of ViewModels - - Returns: A boolean value, true if both ViewModel are equal + A collection of Item Equatable implementation to see if they are truly equal + - parameter lhs: Left hand collection of Items + - parameter rhs: Right hand collection of Items + - returns: A boolean value, true if both Item are equal */ -public func ===(lhs: [ViewModel], rhs: [ViewModel]) -> Bool { +public func ===(lhs: [Item], rhs: [Item]) -> Bool { var equal = lhs.count == rhs.count if !equal { return false } @@ -264,12 +266,13 @@ public func ===(lhs: [ViewModel], rhs: [ViewModel]) -> Bool { } /** - ViewModel Equatable implementation - - Parameter lhs: Left hand ViewModel - - Parameter rhs: Right hand ViewModel - - Returns: A boolean value, true if both ViewModel are equal + Item Equatable implementation + - parameter lhs: Left hand Item + - parameter rhs: Right hand Item + + - returns: A boolean value, true if both Item are equal */ -public func ==(lhs: ViewModel, rhs: ViewModel) -> Bool { +public func ==(lhs: Item, rhs: Item) -> Bool { return lhs.identifier == rhs.identifier && lhs.title == rhs.title && lhs.subtitle == rhs.subtitle && @@ -281,13 +284,14 @@ public func ==(lhs: ViewModel, rhs: ViewModel) -> Bool { } /** - Check if ViewModel's are truly equal by including size in comparison + Check if Item's are truly equal by including size in comparison - - Parameter lhs: Left hand ViewModel - - Parameter rhs: Right hand ViewModel - - Returns: A boolean value, true if both ViewModel are equal + - parameter lhs: Left hand Item + - parameter rhs: Right hand Item + + - returns: A boolean value, true if both Item are equal */ -public func ===(lhs: ViewModel, rhs: ViewModel) -> Bool { +public func ===(lhs: Item, rhs: Item) -> Bool { let equal = lhs.identifier == rhs.identifier && lhs.title == rhs.title && lhs.subtitle == rhs.subtitle && @@ -302,16 +306,17 @@ public func ===(lhs: ViewModel, rhs: ViewModel) -> Bool { } /** - A reverse Equatable implementation for comparing ViewModel's - - Parameter lhs: Left hand ViewModel - - Parameter rhs: Right hand ViewModel - - Returns: A boolean value, false if both ViewModel are equal + A reverse Equatable implementation for comparing Item's + - parameter lhs: Left hand Item + - parameter rhs: Right hand Item + + - returns: A boolean value, false if both Item are equal */ -public func !=(lhs: ViewModel, rhs: ViewModel) -> Bool { +public func !=(lhs: Item, rhs: Item) -> Bool { return !(lhs == rhs) } -func compareRelations(lhs: ViewModel, _ rhs: ViewModel) -> Bool { +func compareRelations(lhs: Item, _ rhs: Item) -> Bool { guard lhs.relations.count == rhs.relations.count else { return false } diff --git a/Sources/Shared/ItemConfigurable.swift b/Sources/Shared/ItemConfigurable.swift new file mode 100644 index 0000000..e6eea9c --- /dev/null +++ b/Sources/Shared/ItemConfigurable.swift @@ -0,0 +1,12 @@ +/** + A class protocol that requires configure(item: Item), it can be applied to UI components to annotate that they are intended to use Item. + */ +public protocol ItemConfigurable: class { + + /** + A configure method that is used on reference types that can be configured using a view model + + - parameter item: A inout Item so that the ItemConfigurable object can configure the view model width and height based on its UI components + */ + func configure(inout item: Item) +} diff --git a/Sources/Shared/StringConvertible.swift b/Sources/Shared/StringConvertible.swift index 5149609..789171c 100644 --- a/Sources/Shared/StringConvertible.swift +++ b/Sources/Shared/StringConvertible.swift @@ -12,7 +12,8 @@ extension String: StringConvertible { /** The required implementation for String to make it conform to StringConvertible - - Returns: self as string + + - returns: self as string */ public var string: String { return self diff --git a/Sources/Shared/ViewConfigurable.swift b/Sources/Shared/ViewConfigurable.swift deleted file mode 100644 index 96dfce6..0000000 --- a/Sources/Shared/ViewConfigurable.swift +++ /dev/null @@ -1,12 +0,0 @@ -/** - A class protocol that requires configure(item: ViewModel), it can be applied to UI components to annotate that they are intended to use ViewModel. - */ -public protocol ViewConfigurable: class { - - /** - A configure method that is used on reference types that can be configured using a view model - - - Parameter item: A inout ViewModel so that the ViewConfigurable object can configure the view model width and height based on its UI components - */ - func configure(inout item: ViewModel) -}