diff --git a/Stefan.xcodeproj/project.pbxproj b/Stefan.xcodeproj/project.pbxproj index 0305f7e..c1bf2fa 100644 --- a/Stefan.xcodeproj/project.pbxproj +++ b/Stefan.xcodeproj/project.pbxproj @@ -7,7 +7,11 @@ objects = { /* Begin PBXBuildFile section */ + AD06B8EE202758BF000A1736 /* ItemsLoadableStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD06B8ED202758BF000A1736 /* ItemsLoadableStateTests.swift */; }; AD0C13962012B6A800CEA0D0 /* ReloadingType.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD0C13952012B6A800CEA0D0 /* ReloadingType.swift */; }; + AD127C35202719FE00486A0A /* StefanDifferTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD127C34202719FE00486A0A /* StefanDifferTests.swift */; }; + AD127C3720271A4100486A0A /* Fruit.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD127C3620271A4100486A0A /* Fruit.swift */; }; + AD127C3920271A5700486A0A /* FruitStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD127C3820271A5700486A0A /* FruitStorage.swift */; }; AD46DDF3200E015A00D0A5B8 /* Stefan_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AD46DDE9200E015A00D0A5B8 /* Stefan_iOS.framework */; }; AD46DDF8200E015A00D0A5B8 /* StefanTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD46DDF7200E015A00D0A5B8 /* StefanTests.swift */; }; AD46DDFA200E015A00D0A5B8 /* Stefan.h in Headers */ = {isa = PBXBuildFile; fileRef = AD46DDEC200E015A00D0A5B8 /* Stefan.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -41,6 +45,7 @@ AD46DE4B200E06D500D0A5B8 /* FruitStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD46DE4A200E06D500D0A5B8 /* FruitStorage.swift */; }; AD46DE4E200E06F700D0A5B8 /* Fruit.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD46DE4D200E06F700D0A5B8 /* Fruit.swift */; }; AD46DE53200E143100D0A5B8 /* FruitTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD46DE52200E143100D0A5B8 /* FruitTableViewCell.swift */; }; + AD5E9D55202B0AE300D0AD2B /* StefanTestController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD5E9D54202B0AE300D0AD2B /* StefanTestController.swift */; }; AD8ACAF020109B1B00CBDD0A /* Differ.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AD8ACAEF20109B1B00CBDD0A /* Differ.framework */; }; ADB93944202466D80085590A /* SectionatedStefanDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADB93943202466D80085590A /* SectionatedStefanDelegate.swift */; }; ADFB2E05202534440002A1FF /* UIView + LoadableStatePlaceholderPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADFB2E04202534440002A1FF /* UIView + LoadableStatePlaceholderPresentable.swift */; }; @@ -65,7 +70,11 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + AD06B8ED202758BF000A1736 /* ItemsLoadableStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemsLoadableStateTests.swift; sourceTree = ""; }; AD0C13952012B6A800CEA0D0 /* ReloadingType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReloadingType.swift; sourceTree = ""; }; + AD127C34202719FE00486A0A /* StefanDifferTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StefanDifferTests.swift; sourceTree = ""; }; + AD127C3620271A4100486A0A /* Fruit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Fruit.swift; sourceTree = ""; }; + AD127C3820271A5700486A0A /* FruitStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FruitStorage.swift; sourceTree = ""; }; AD46DDE9200E015A00D0A5B8 /* Stefan_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Stefan_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; AD46DDEC200E015A00D0A5B8 /* Stefan.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Stefan.h; sourceTree = ""; }; AD46DDED200E015A00D0A5B8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -104,6 +113,7 @@ AD46DE4A200E06D500D0A5B8 /* FruitStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FruitStorage.swift; sourceTree = ""; }; AD46DE4D200E06F700D0A5B8 /* Fruit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Fruit.swift; sourceTree = ""; }; AD46DE52200E143100D0A5B8 /* FruitTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FruitTableViewCell.swift; sourceTree = ""; }; + AD5E9D54202B0AE300D0AD2B /* StefanTestController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StefanTestController.swift; sourceTree = ""; }; AD8ACAEF20109B1B00CBDD0A /* Differ.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Differ.framework; path = Carthage/Build/iOS/Differ.framework; sourceTree = ""; }; ADB93943202466D80085590A /* SectionatedStefanDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionatedStefanDelegate.swift; sourceTree = ""; }; ADFB2E04202534440002A1FF /* UIView + LoadableStatePlaceholderPresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView + LoadableStatePlaceholderPresentable.swift"; sourceTree = ""; }; @@ -177,8 +187,13 @@ AD46DDF6200E015A00D0A5B8 /* StefanTests */ = { isa = PBXGroup; children = ( + AD127C3620271A4100486A0A /* Fruit.swift */, + AD127C3820271A5700486A0A /* FruitStorage.swift */, AD46DDF7200E015A00D0A5B8 /* StefanTests.swift */, AD46DDF9200E015A00D0A5B8 /* Info.plist */, + AD127C34202719FE00486A0A /* StefanDifferTests.swift */, + AD06B8ED202758BF000A1736 /* ItemsLoadableStateTests.swift */, + AD5E9D54202B0AE300D0AD2B /* StefanTestController.swift */, ); path = StefanTests; sourceTree = ""; @@ -460,6 +475,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + AD5E9D55202B0AE300D0AD2B /* StefanTestController.swift in Sources */, + AD06B8EE202758BF000A1736 /* ItemsLoadableStateTests.swift in Sources */, + AD127C3920271A5700486A0A /* FruitStorage.swift in Sources */, + AD127C3720271A4100486A0A /* Fruit.swift in Sources */, + AD127C35202719FE00486A0A /* StefanDifferTests.swift in Sources */, AD46DDF8200E015A00D0A5B8 /* StefanTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Stefan.xcodeproj/xcshareddata/xcschemes/Stefan-iOS.xcscheme b/Stefan.xcodeproj/xcshareddata/xcschemes/Stefan-iOS.xcscheme index 1f1a8e9..427acc3 100644 --- a/Stefan.xcodeproj/xcshareddata/xcschemes/Stefan-iOS.xcscheme +++ b/Stefan.xcodeproj/xcshareddata/xcschemes/Stefan-iOS.xcscheme @@ -27,7 +27,8 @@ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" language = "" - shouldUseLaunchSchemeArgsEnv = "YES"> + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> diff --git a/Stefan/Enums/ItemReloadingResult.swift b/Stefan/Enums/ItemReloadingResult.swift index 6dfe408..d7fc829 100644 --- a/Stefan/Enums/ItemReloadingResult.swift +++ b/Stefan/Enums/ItemReloadingResult.swift @@ -26,3 +26,26 @@ public enum ItemReloadingResult { case itemsAndPlaceholder(oldItems: [ItemType], newItems: [ItemType]) } + +extension ItemReloadingResult: Equatable { + + public static func == (lhs: ItemReloadingResult, rhs: ItemReloadingResult) -> Bool { + + switch(lhs, rhs) { + + case (.none, .none), (.placeholder, .placeholder): + return true + case (.items(let lOldItems, let lNewItems), .items(let rOldItems, let rNewItems)): + return lOldItems == rOldItems && lNewItems == rNewItems + case (.placeholderAndItems(let lOldItems, let lNewItems), .placeholderAndItems(let rOldItems, let rNewItems)): + return lOldItems == rOldItems && lNewItems == rNewItems + case (.itemsAndPlaceholder(let lOldItems, let lNewItems), .itemsAndPlaceholder(let rOldItems, let rNewItems)): + return lOldItems == rOldItems && lNewItems == rNewItems + default: + return false + } + } + + + +} diff --git a/StefanTests/Fruit.swift b/StefanTests/Fruit.swift new file mode 100644 index 0000000..9173bc4 --- /dev/null +++ b/StefanTests/Fruit.swift @@ -0,0 +1,37 @@ +// +// Fruit.swift +// StefanTests +// +// Created by Szymon Mrozek on 04.02.2018. +// Copyright © 2018 AppUnite. All rights reserved. +// + +import Foundation + +enum FruitSize { + + case small + case medium + case big +} + +struct Fruit { + + let name: String + let size: FruitSize + + init(name: String, size: FruitSize) { + self.name = name + self.size = size + } + +} + +extension Fruit: Equatable { + + static func == (lhs: Fruit, rhs: Fruit) -> Bool { + return lhs.name == rhs.name && + lhs.size == rhs.size + } + +} diff --git a/StefanTests/FruitStorage.swift b/StefanTests/FruitStorage.swift new file mode 100644 index 0000000..d84d889 --- /dev/null +++ b/StefanTests/FruitStorage.swift @@ -0,0 +1,55 @@ +// +// FruitStorage.swift +// StefanTests +// +// Created by Szymon Mrozek on 04.02.2018. +// Copyright © 2018 AppUnite. All rights reserved. +// + +import Foundation + +struct FruitStorage { + + static var smallFruits: [Fruit] { + let fruitSize: FruitSize = .small + + return [ + Fruit(name: "Blackberry", size: fruitSize), + Fruit(name: "Grape", size: fruitSize), + Fruit(name: "Blackcurrant", size: fruitSize), + Fruit(name: "Gooseberry", size: fruitSize), + Fruit(name: "Raspberry", size: fruitSize), + Fruit(name: "Strawberry", size: fruitSize), + Fruit(name: "Cherry", size: fruitSize), + Fruit(name: "Plum", size: fruitSize) + ] + } + + static var mediumFruits: [Fruit] { + let fruitSize: FruitSize = .medium + + return [ + Fruit(name: "Apple", size: fruitSize), + Fruit(name: "Peach", size: fruitSize), + Fruit(name: "Pear", size: fruitSize), + Fruit(name: "Kiwi", size: fruitSize), + Fruit(name: "Maracuja", size: fruitSize), + Fruit(name: "Guava", size: fruitSize) + ] + } + + static var bigFruits: [Fruit] { + let fruitSize: FruitSize = .big + + return [ + Fruit(name: "Banana", size: fruitSize), + Fruit(name: "Mango", size: fruitSize), + Fruit(name: "Pineapple", size: fruitSize), + Fruit(name: "Grapefruit", size: fruitSize), + Fruit(name: "Watermelon", size: fruitSize), + Fruit(name: "Coconut", size: fruitSize), + Fruit(name: "Papaya", size: fruitSize) + ] + } + +} diff --git a/StefanTests/ItemsLoadableStateTests.swift b/StefanTests/ItemsLoadableStateTests.swift new file mode 100644 index 0000000..d4eb325 --- /dev/null +++ b/StefanTests/ItemsLoadableStateTests.swift @@ -0,0 +1,197 @@ +// +// ItemsLoadableStateTests.swift +// StefanTests +// +// Created by Szymon Mrozek on 04.02.2018. +// Copyright © 2018 AppUnite. All rights reserved. +// + +import XCTest + +@testable import Stefan_iOS + +class ItemsLoadableStateTests: XCTestCase { + + enum TestError: Error { + case someError + } + + override func setUp() { + super.setUp() + } + + public func testItemsCount() { + + let items = FruitStorage.smallFruits + + var state = ItemsLoadableState.loaded(items: items) + + let notEmptyItemsCount = items.count + + XCTAssertEqual(state.itemsCount, notEmptyItemsCount) + + state = .noContent + + XCTAssertEqual(state.itemsCount, 0) + } + + public func testGrabbingItemsInLoadedStateCorrectly() { + + let state = ItemsLoadableState.loaded(items: FruitStorage.bigFruits) + + do { + _ = try state.items() + } catch { + XCTFail("Should not throw error") + } + } + + public func testGrabbingItemsInLoadedStateWith0Items() { + + let state = ItemsLoadableState.loaded(items: []) + + do { + _ = try state.items() + XCTFail("Should not return items without error") + } catch let error { + guard let stateError = error as? ItemsLoadableState.ItemsLoadableStateError, stateError == ItemsLoadableState.ItemsLoadableStateError.zeroItemsInLoadedState else { + XCTFail("Wrong error thrown") + return + } + } + } + + public func testGrabbingItemsInRefreshingStateCorrectly() { + + let state = ItemsLoadableState.refreshing(silent: true, items: FruitStorage.mediumFruits) + + do { + _ = try state.items() + } catch { + XCTFail("Should not throw error") + } + } + + public func testGrabbingItemsInSilentRefreshState() { + + let state = ItemsLoadableState.refreshing(silent: false, items: FruitStorage.bigFruits) + + do { + _ = try state.items() + XCTFail("Should not return items without error") + } catch let error { + guard let stateError = error as? ItemsLoadableState.ItemsLoadableStateError, stateError == ItemsLoadableState.ItemsLoadableStateError.wrongStateForReadingItems else { + XCTFail("Wrong error thrown") + return + } + } + } + + public func testGrabbingItemsInWrongStates() { + + let idleState = ItemsLoadableState.idle + let noContentState = ItemsLoadableState.noContent + let loadingState = ItemsLoadableState.loading + + do { + _ = try idleState.items() + _ = try noContentState.items() + _ = try loadingState.items() + XCTFail("Should not return items without error") + } catch let error { + guard let stateError = error as? ItemsLoadableState.ItemsLoadableStateError, stateError == ItemsLoadableState.ItemsLoadableStateError.wrongStateForReadingItems else { + XCTFail("Wrong error thrown") + return + } + } + } + + public func testInitWithItemsExplicity() { + + let items = FruitStorage.mediumFruits + + let state = ItemsLoadableState(items) + + XCTAssertEqual(state, ItemsLoadableState.loaded(items: items)) + } + + public func testInitWithNoItems() { + + let state = ItemsLoadableState([]) + XCTAssertEqual(state, ItemsLoadableState.noContent) + } + + public func testStatesComparingNoItems() { + + var lState = ItemsLoadableState.idle + var rState = ItemsLoadableState.idle + XCTAssertEqual(lState, rState) + + + lState = ItemsLoadableState.loading + rState = ItemsLoadableState.loading + XCTAssertEqual(lState, rState) + + + lState = ItemsLoadableState.noContent + rState = ItemsLoadableState.noContent + XCTAssertEqual(lState, rState) + + + lState = ItemsLoadableState.error(TestError.someError) + rState = ItemsLoadableState.error(TestError.someError) + XCTAssertEqual(lState, rState) + + + lState = ItemsLoadableState.idle + rState = ItemsLoadableState.loading + XCTAssertNotEqual(lState, rState) + + } + + public func testStatesComparingRefreshing() { + + let items = FruitStorage.smallFruits + + var lState = ItemsLoadableState.refreshing(silent: true, items: items) + var rState = ItemsLoadableState.refreshing(silent: true, items: items) + XCTAssertEqual(lState, rState) + + + lState = ItemsLoadableState.refreshing(silent: false, items: items) + rState = ItemsLoadableState.refreshing(silent: false, items: items) + XCTAssertEqual(lState, rState) + + + lState = ItemsLoadableState.refreshing(silent: false, items: items) + rState = ItemsLoadableState.refreshing(silent: true, items: items) + XCTAssertNotEqual(lState, rState) + + + lState = ItemsLoadableState.refreshing(silent: true, items: []) + rState = ItemsLoadableState.refreshing(silent: true, items: items) + XCTAssertNotEqual(lState, rState) + + } + + public func testStatesComparingLoaded() { + + let items = FruitStorage.smallFruits + + var lState = ItemsLoadableState.loaded(items: items) + var rState = ItemsLoadableState.loaded(items: items) + XCTAssertEqual(lState, rState) + + + lState = ItemsLoadableState.loaded(items: []) + rState = ItemsLoadableState.loaded(items: items) + XCTAssertNotEqual(lState, rState) + + + lState = ItemsLoadableState.loaded(items: items) + rState = ItemsLoadableState.loaded(items: items + FruitStorage.mediumFruits) + XCTAssertNotEqual(lState, rState) + + } + +} diff --git a/StefanTests/StefanDifferTests.swift b/StefanTests/StefanDifferTests.swift new file mode 100644 index 0000000..e2c0eab --- /dev/null +++ b/StefanTests/StefanDifferTests.swift @@ -0,0 +1,248 @@ +// +// StefanDifferTests.swift +// StefanTests +// +// Created by Szymon Mrozek on 04.02.2018. +// Copyright © 2018 AppUnite. All rights reserved. +// + +import XCTest + +@testable import Stefan_iOS + +class StefanDifferTests: XCTestCase, ItemsLoadableStateDiffer { + + enum StefanDifferTestsError: Error { + case someError + } + + var old: ItemsLoadableState! + var new: ItemsLoadableState! + + override func setUp() { + super.setUp() + } + + func testFromIdleToLoaded() { + old = .idle + new = .loaded(items: FruitStorage.bigFruits) + + let diffResult = self.load(newState: new, withOld: old) + let expectedResult = ItemReloadingResult.placeholderAndItems(oldItems: [], newItems: FruitStorage.bigFruits) + + XCTAssertEqual(expectedResult, diffResult) + + } + + func testFromIdleToOther() { + old = .idle + new = .loading + + var diffResult = self.load(newState: new, withOld: old) + var expectedResult = ItemReloadingResult.placeholder + + XCTAssertEqual(expectedResult, diffResult) + + old = .idle + new = .noContent + + diffResult = self.load(newState: new, withOld: old) + expectedResult = ItemReloadingResult.placeholder + + XCTAssertEqual(expectedResult, diffResult) + } + + + func testFromIdleToIdle() { + old = .idle + new = .idle + + let diffResult = self.load(newState: new, withOld: old) + let expectedResult = ItemReloadingResult.none + + XCTAssertEqual(expectedResult, diffResult) + } + + func testFromLoadingToLoading() { + old = .loading + new = .loading + + let diffResult = self.load(newState: new, withOld: old) + let expectedResult = ItemReloadingResult.none + + XCTAssertEqual(expectedResult, diffResult) + } + + func testFromNoContentToNoContent() { + old = .noContent + new = .noContent + + let diffResult = self.load(newState: new, withOld: old) + let expectedResult = ItemReloadingResult.none + + XCTAssertEqual(expectedResult, diffResult) + } + + func testFromNoContentToLoading() { + old = .noContent + new = .loading + + let diffResult = self.load(newState: new, withOld: old) + let expectedResult = ItemReloadingResult.placeholder + + XCTAssertEqual(expectedResult, diffResult) + } + + func testFromLoadingToNoContent() { + old = .loading + new = .noContent + + let diffResult = self.load(newState: new, withOld: old) + let expectedResult = ItemReloadingResult.placeholder + + XCTAssertEqual(expectedResult, diffResult) + } + + func testFromErrorToNoContent() { + old = .loading + new = .noContent + + let diffResult = self.load(newState: new, withOld: old) + let expectedResult = ItemReloadingResult.placeholder + + XCTAssertEqual(expectedResult, diffResult) + } + + func testFromLoadingToLoaded() { + old = .loading + new = .loaded(items: FruitStorage.bigFruits) + + let diffResult = self.load(newState: new, withOld: old) + let expectedResult = ItemReloadingResult.placeholderAndItems(oldItems: [], newItems: FruitStorage.bigFruits) + + XCTAssertEqual(expectedResult, diffResult) + } + + func testFromNoContentToLoaded() { + old = .noContent + new = .loaded(items: FruitStorage.bigFruits) + + let diffResult = self.load(newState: new, withOld: old) + let expectedResult = ItemReloadingResult.placeholderAndItems(oldItems: [], newItems: FruitStorage.bigFruits) + + XCTAssertEqual(expectedResult, diffResult) + } + + func testFromErrorToLoaded() { + old = .error(StefanDifferTestsError.someError) + new = .loaded(items: FruitStorage.bigFruits) + + let diffResult = self.load(newState: new, withOld: old) + let expectedResult = ItemReloadingResult.placeholderAndItems(oldItems: [], newItems: FruitStorage.bigFruits) + + XCTAssertEqual(expectedResult, diffResult) + } + + func testFromLoadedToLoaded() { + old = .loaded(items: FruitStorage.mediumFruits) + new = .loaded(items: FruitStorage.bigFruits) + + let diffResult = self.load(newState: new, withOld: old) + let expectedResult = ItemReloadingResult.items(oldItems: FruitStorage.mediumFruits, newItems: FruitStorage.bigFruits) + + XCTAssertEqual(expectedResult, diffResult) + } + + func testFromRefreshingToLoaded() { + old = .refreshing(silent: false, items: FruitStorage.mediumFruits) + new = .loaded(items: FruitStorage.bigFruits) + + let diffResult = self.load(newState: new, withOld: old) + let expectedResult = ItemReloadingResult.placeholderAndItems(oldItems: FruitStorage.mediumFruits, newItems: FruitStorage.bigFruits) + + XCTAssertEqual(expectedResult, diffResult) + } + + func testFromNoContentToSilentRefresh() { + old = .noContent + new = .refreshing(silent: true, items: FruitStorage.mediumFruits) + + let diffResult = self.load(newState: new, withOld: old) + let expectedResult = ItemReloadingResult.none + + XCTAssertEqual(expectedResult, diffResult) + } + + func testFromNoContentToNonSilentRefresh() { + old = .noContent + new = .refreshing(silent: false, items: FruitStorage.mediumFruits) + + let diffResult = self.load(newState: new, withOld: old) + let expectedResult = ItemReloadingResult.placeholder + + XCTAssertEqual(expectedResult, diffResult) + } + + func testFromLoadedToSilentRefresh() { + old = .loaded(items: FruitStorage.bigFruits) + new = .refreshing(silent: true, items: FruitStorage.bigFruits) + + let diffResult = self.load(newState: new, withOld: old) + let expectedResult = ItemReloadingResult.none + + XCTAssertEqual(expectedResult, diffResult) + } + + func testFromLoadedToNonSilentRefresh() { + old = .loaded(items: FruitStorage.bigFruits) + new = .refreshing(silent: false, items: FruitStorage.bigFruits) + + let diffResult = self.load(newState: new, withOld: old) + let expectedResult = ItemReloadingResult.itemsAndPlaceholder(oldItems: FruitStorage.bigFruits, newItems: []) + + XCTAssertEqual(expectedResult, diffResult) + } + + func testFromLoadedToNoContent() { + old = .loaded(items: FruitStorage.bigFruits) + new = .noContent + + let diffResult = self.load(newState: new, withOld: old) + let expectedResult = ItemReloadingResult.itemsAndPlaceholder(oldItems: FruitStorage.bigFruits, newItems: []) + + XCTAssertEqual(expectedResult, diffResult) + } + + func testFromSilentRefreshToNoContent() { + old = .refreshing(silent: true, items: FruitStorage.bigFruits) + new = .noContent + + let diffResult = self.load(newState: new, withOld: old) + let expectedResult = ItemReloadingResult.itemsAndPlaceholder(oldItems: FruitStorage.bigFruits, newItems: []) + + XCTAssertEqual(expectedResult, diffResult) + } + + func testFromNonSilentRefreshToNoContent() { + old = .refreshing(silent: false, items: FruitStorage.bigFruits) + new = .noContent + + let diffResult = self.load(newState: new, withOld: old) + let expectedResult = ItemReloadingResult.placeholder + + XCTAssertEqual(expectedResult, diffResult) + } + + func testDifferentResults() { + // because of coverage + old = .idle + new = .noContent + + let diffResult = self.load(newState: new, withOld: old) + let wrongResult = ItemReloadingResult.none + + XCTAssertNotEqual(wrongResult, diffResult) + } + +} + diff --git a/StefanTests/StefanTestController.swift b/StefanTests/StefanTestController.swift new file mode 100644 index 0000000..83212eb --- /dev/null +++ b/StefanTests/StefanTestController.swift @@ -0,0 +1,19 @@ +// +// StefanTestController.swift +// StefanTests +// +// Created by Szymon Mrozek on 07.02.2018. +// Copyright © 2018 AppUnite. All rights reserved. +// + +import UIKit +import Stefan_iOS + +class StefanTestController: UITableViewController, LoadableStatePlaceholderPresentable { + + weak var placeholderView: LoadableStatePlaceholderView? + + override func viewDidLoad() { + super.viewDidLoad() + } +} diff --git a/StefanTests/StefanTests.swift b/StefanTests/StefanTests.swift index 121d61a..1ec22e8 100644 --- a/StefanTests/StefanTests.swift +++ b/StefanTests/StefanTests.swift @@ -7,24 +7,166 @@ // import XCTest -@testable import Stefan +@testable import Stefan_iOS class StefanTests: XCTestCase { + var stefan = Stefan() + + var firstState: ItemsLoadableState! + var secondState: ItemsLoadableState! + + let testController = StefanTestController() + override func setUp() { super.setUp() + stefan = Stefan() + stefan.placeholderPresenter = testController + stefan.reloadableView = testController.tableView } override func tearDown() { super.tearDown() } - func testExample() { + func testLoadStateNoneResult() { + + // Placeholder on screen + + firstState = .noContent + secondState = .noContent + + stefan.load(newState: firstState) + XCTAssertEqual(firstState, stefan.state) + stefan.load(newState: secondState) + XCTAssertEqual(secondState, stefan.state) + + // Removing placeholder if needed + + firstState = .loaded(items: FruitStorage.mediumFruits) + secondState = .loaded(items: FruitStorage.mediumFruits) + + stefan.load(newState: firstState) + XCTAssertEqual(firstState, stefan.state) + stefan.load(newState: secondState) + XCTAssertEqual(secondState, stefan.state) + } + + func testLoadStatePlaceholderResult() { + + firstState = .noContent + secondState = .loading + + stefan.load(newState: firstState) + XCTAssertEqual(firstState, stefan.state) + stefan.load(newState: secondState) + XCTAssertEqual(secondState, stefan.state) + } + + func testLoadStateItemsResult() { + firstState = .loaded(items: FruitStorage.mediumFruits) + secondState = .loaded(items: FruitStorage.bigFruits) + + stefan.load(newState: firstState) + XCTAssertEqual(firstState, stefan.state) + stefan.load(newState: secondState) + XCTAssertEqual(secondState, stefan.state) + } + + func testLoadStatePlaceholderAndItemsResult() { + // Remove placeholder if needed + firstState = .noContent + secondState = .loaded(items: FruitStorage.bigFruits) + + stefan.load(newState: firstState) + XCTAssertEqual(firstState, stefan.state) + stefan.load(newState: secondState) + XCTAssertEqual(secondState, stefan.state) + + // Test with adding placeholder if needed + stefan.shouldDisplayPlaceholder = { _ in return true } + firstState = .noContent + secondState = .loaded(items: FruitStorage.bigFruits) + + stefan.load(newState: firstState) + XCTAssertEqual(firstState, stefan.state) + stefan.load(newState: secondState) + XCTAssertEqual(secondState, stefan.state) } - func testPerformanceExample() { + func testLoadStateItemsAndPlaceholderResult() { + // Add placeholder if needed + firstState = .loaded(items: FruitStorage.mediumFruits) + secondState = .noContent + + stefan.load(newState: firstState) + XCTAssertEqual(firstState, stefan.state) + stefan.load(newState: secondState) + XCTAssertEqual(secondState, stefan.state) + + // Test with removing placeholder if needed + stefan.shouldDisplayPlaceholder = { _ in return false } + firstState = .loaded(items: FruitStorage.mediumFruits) + secondState = .noContent + + stefan.load(newState: firstState) + XCTAssertEqual(firstState, stefan.state) + stefan.load(newState: secondState) + XCTAssertEqual(secondState, stefan.state) + } + + func testShouldReloadClosure() { + + let shouldReloadAsked = expectation(description: "Closure shouldReload did call") + + firstState = .loaded(items: FruitStorage.mediumFruits) + stefan.load(newState: firstState) + + stefan.shouldReload = { _ in + shouldReloadAsked.fulfill() + return true + } + // TEST + secondState = .noContent + stefan.load(newState: secondState) + + wait(for: [shouldReloadAsked], timeout: 1.0) + } + + func testDidChangeStateClosure() { + + let didChangeStateCalled = expectation(description: "Did change state closure called") + + firstState = .loaded(items: FruitStorage.mediumFruits) + stefan.load(newState: firstState) + + stefan.didChangeState = { _ in + didChangeStateCalled.fulfill() + } + + // TEST + secondState = .noContent + stefan.load(newState: secondState) + wait(for: [didChangeStateCalled], timeout: 1.0) + } + + func testShouldDisplayPlaceholderClosure() { + + let shouldDisplayPlaceholderCalled = expectation(description: "Should display placeholder closure called") + + firstState = .loaded(items: FruitStorage.mediumFruits) + stefan.load(newState: firstState) + + stefan.shouldDisplayPlaceholder = { _ in + shouldDisplayPlaceholderCalled.fulfill() + return true + } + // TEST + secondState = .noContent + stefan.load(newState: secondState) + wait(for: [shouldDisplayPlaceholderCalled], timeout: 1.0) } }