Merge branch 'develop' into olkol/copyable_label
This commit is contained in:
Коммит
bf3c406120
|
@ -2,7 +2,7 @@ Pod::Spec.new do |s|
|
|||
s.platform = :ios
|
||||
s.ios.deployment_target = '9.0'
|
||||
s.name = 'EmbeddedSocial'
|
||||
s.version = '0.7.5'
|
||||
s.version = '0.7.10'
|
||||
s.summary = 'SDK for interacting with the Microsoft Embedded Social service from inside your iOS app.'
|
||||
s.description = 'This is an SDK that works with the Microsoft Embedded Social service to provide social networking functionality inside your iOS application.'
|
||||
s.homepage = 'https://github.com/Microsoft/EmbeddedSocial-iOS-SDK'
|
||||
|
@ -54,5 +54,6 @@ Pod::Spec.new do |s|
|
|||
s.dependency 'FBSDKCoreKit', '~> 4.24.0'
|
||||
s.dependency 'FBSDKLoginKit', '~> 4.24.0'
|
||||
s.dependency 'FBSDKShareKit', '~> 4.24.0'
|
||||
s.dependency 'BMACollectionBatchUpdates', '~> 1.1'
|
||||
|
||||
end
|
||||
|
|
|
@ -883,6 +883,7 @@
|
|||
88C6826E1F7A6E64004BD291 /* PaginatedListProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88C6826D1F7A6E64004BD291 /* PaginatedListProcessor.swift */; };
|
||||
88C682711F7A71EE004BD291 /* FollowRequestsAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88C682701F7A71EE004BD291 /* FollowRequestsAPI.swift */; };
|
||||
88CEDD861FA76C110015B122 /* SettingsInteractorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88CEDD851FA76C110015B122 /* SettingsInteractorTests.swift */; };
|
||||
88CF3A831FAB432500F607F8 /* MockSearchPeopleModuleOutput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88CF3A821FAB432500F607F8 /* MockSearchPeopleModuleOutput.swift */; };
|
||||
88D1230C1F73AA9F001523D1 /* OutgoingCommandsUploadStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88D1230B1F73AA9F001523D1 /* OutgoingCommandsUploadStrategy.swift */; };
|
||||
88D1230E1F73AB6F001523D1 /* FetchOutgoingCommandsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88D1230D1F73AB6F001523D1 /* FetchOutgoingCommandsOperation.swift */; };
|
||||
88D123131F73F566001523D1 /* OutgoingCommandsRelatedHandleUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88D123121F73F566001523D1 /* OutgoingCommandsRelatedHandleUpdater.swift */; };
|
||||
|
@ -2166,6 +2167,7 @@
|
|||
88C6826D1F7A6E64004BD291 /* PaginatedListProcessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaginatedListProcessor.swift; sourceTree = "<group>"; };
|
||||
88C682701F7A71EE004BD291 /* FollowRequestsAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FollowRequestsAPI.swift; sourceTree = "<group>"; };
|
||||
88CEDD851FA76C110015B122 /* SettingsInteractorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsInteractorTests.swift; sourceTree = "<group>"; };
|
||||
88CF3A821FAB432500F607F8 /* MockSearchPeopleModuleOutput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSearchPeopleModuleOutput.swift; sourceTree = "<group>"; };
|
||||
88D1230B1F73AA9F001523D1 /* OutgoingCommandsUploadStrategy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutgoingCommandsUploadStrategy.swift; sourceTree = "<group>"; };
|
||||
88D1230D1F73AB6F001523D1 /* FetchOutgoingCommandsOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchOutgoingCommandsOperation.swift; sourceTree = "<group>"; };
|
||||
88D123121F73F566001523D1 /* OutgoingCommandsRelatedHandleUpdater.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutgoingCommandsRelatedHandleUpdater.swift; sourceTree = "<group>"; };
|
||||
|
@ -4778,6 +4780,7 @@
|
|||
children = (
|
||||
889119B21F4AE771005B3B32 /* MockSearchPeopleInteractor.swift */,
|
||||
889119B41F4AE7F5005B3B32 /* MockSearchPeopleView.swift */,
|
||||
88CF3A821FAB432500F607F8 /* MockSearchPeopleModuleOutput.swift */,
|
||||
);
|
||||
path = Mocks;
|
||||
sourceTree = "<group>";
|
||||
|
@ -8033,6 +8036,7 @@
|
|||
9C30243C1F55AB2100675FE9 /* FeedModuleRouterMock.swift in Sources */,
|
||||
88C682411F794D6B004BD291 /* MockTopicServicePredicateBuilder.swift in Sources */,
|
||||
88F7A7861F2788DE005FEC5F /* LoginInteractorTests.swift in Sources */,
|
||||
88CF3A831FAB432500F607F8 /* MockSearchPeopleModuleOutput.swift in Sources */,
|
||||
639A94401F5D2FE800EB0253 /* MockCommentCellInteractor.swift in Sources */,
|
||||
886826111F6A948700F54731 /* OutgoingCommandTests.swift in Sources */,
|
||||
889119C31F4AFBB1005B3B32 /* MockSearchView.swift in Sources */,
|
||||
|
|
|
@ -5,9 +5,22 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
class DateFormatterTool {
|
||||
protocol DateFormatterProtocol {
|
||||
func timeAgo(since: Date) -> String?
|
||||
}
|
||||
|
||||
class DateFormatterTool: DateFormatterProtocol {
|
||||
|
||||
lazy var shortStyle: DateComponentsFormatter = {
|
||||
static let shared: DateFormatterTool = DateFormatterTool()
|
||||
|
||||
func timeAgo(since then: Date) -> String? {
|
||||
let now = Date()
|
||||
let interval: TimeInterval = now.timeIntervalSince(then)
|
||||
return DateFormatterTool.short.string(from: interval)
|
||||
}
|
||||
|
||||
static let short: DateComponentsFormatter = {
|
||||
|
||||
let formatter = DateComponentsFormatter()
|
||||
|
||||
formatter.unitsStyle = .abbreviated
|
||||
|
@ -19,21 +32,4 @@ class DateFormatterTool {
|
|||
return formatter
|
||||
}()
|
||||
|
||||
static func timeAgo(since: Date) -> String? {
|
||||
return short.string(from: since, to: Date())
|
||||
}
|
||||
|
||||
static var short:DateComponentsFormatter {
|
||||
|
||||
let formatter = DateComponentsFormatter()
|
||||
|
||||
formatter.unitsStyle = .abbreviated
|
||||
formatter.includesApproximationPhrase = false
|
||||
formatter.zeroFormattingBehavior = .dropAll
|
||||
formatter.maximumUnitCount = 1
|
||||
formatter.allowsFractionalUnits = false
|
||||
|
||||
return formatter
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,12 +7,19 @@ import UIKit
|
|||
|
||||
class OfflineView: UILabel {
|
||||
|
||||
private let statusBarHeight = UIApplication.shared.statusBarFrame.size.height
|
||||
private let oldOSVersion = 10
|
||||
private let labelHeight: CGFloat = 30
|
||||
private let fontSize: CGFloat = 13
|
||||
|
||||
func show(in controller: UIViewController) {
|
||||
|
||||
if self.superview == nil {
|
||||
self.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: 30)
|
||||
let offsetY = ProcessInfo().operatingSystemVersion.majorVersion > oldOSVersion ? (controller.navigationController?.navigationBar.frame.height ?? 0) + statusBarHeight : 0
|
||||
self.frame = CGRect(x: 0, y: offsetY, width: UIScreen.main.bounds.size.width, height: labelHeight)
|
||||
self.textAlignment = .center
|
||||
self.text = L10n.Error.noInternetConnection
|
||||
self.font = UIFont.systemFont(ofSize: 13)
|
||||
self.font = UIFont.systemFont(ofSize: fontSize)
|
||||
self.backgroundColor = UIColor(red: 34/255 , green: 139/255, blue: 34/255, alpha: 1)
|
||||
self.textColor = .white
|
||||
controller.view.addSubview(self)
|
||||
|
|
|
@ -103,16 +103,6 @@ class SocialService: BaseService, SocialServiceType {
|
|||
outgoingActionsExecutor.execute(command: command, builder: builder, completion: completion)
|
||||
}
|
||||
|
||||
private func processResponse(_ data: Object?, _ error: Error?, _ completion: @escaping (Result<Void>) -> Void) {
|
||||
DispatchQueue.main.async {
|
||||
if error == nil {
|
||||
completion(.success())
|
||||
} else {
|
||||
self.errorHandler.handle(error: error, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getMyFollowing(cursor: String?, limit: Int, completion: @escaping (Result<UsersListResponse>) -> Void) {
|
||||
let builder = SocialAPI.myFollowingGetFollowingUsersWithRequestBuilder(
|
||||
authorization: authorization,
|
||||
|
|
|
@ -43,7 +43,7 @@ struct PostViewModel {
|
|||
cellType: String,
|
||||
actionHandler: ActionHandler? = nil) {
|
||||
|
||||
let formatter = DateFormatterTool()
|
||||
let formatter = DateFormatterTool.shared
|
||||
self.isTrimmed = isTrimmed
|
||||
topicHandle = post.topicHandle
|
||||
userName = User.fullName(firstName: post.firstName, lastName: post.lastName)
|
||||
|
@ -71,7 +71,7 @@ struct PostViewModel {
|
|||
totalCommentsShort = "\(post.totalComments)"
|
||||
|
||||
if let createdTime = post.createdTime {
|
||||
timeCreated = formatter.shortStyle.string(from: createdTime, to: Date()) ?? ""
|
||||
timeCreated = formatter.timeAgo(since: createdTime) ?? ""
|
||||
} else {
|
||||
timeCreated = ""
|
||||
}
|
||||
|
|
|
@ -166,7 +166,7 @@ enum ActivityError: Int, Error {
|
|||
extension ActivityView {
|
||||
func createdTimeAgo() -> String? {
|
||||
guard let date = self.createdTime else { return nil }
|
||||
return DateFormatterTool.timeAgo(since: date)
|
||||
return DateFormatterTool.shared.timeAgo(since: date)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -65,7 +65,7 @@ class CommentCell: UICollectionViewCell, CommentCellViewInput {
|
|||
likesCountButton.setTitle(L10n.Post.likesCount(Int(comment.totalLikes)), for: .normal)
|
||||
repliesCountButton.setTitle(L10n.Post.repliesCount(Int(comment.totalReplies)), for: .normal)
|
||||
|
||||
postedTimeLabel.text = comment.createdTime == nil ? "" : formatter.shortStyle.string(from: comment.createdTime!, to: Date())
|
||||
postedTimeLabel.text = comment.createdTime == nil ? "" : formatter.timeAgo(since: comment.createdTime!)
|
||||
|
||||
if comment.user?.photo?.url == nil {
|
||||
userPhoto.image = userImagePlaceholder
|
||||
|
|
|
@ -39,7 +39,7 @@ extension EditProfileViewController: EditProfileViewInput {
|
|||
}
|
||||
|
||||
func showError(_ error: Error) {
|
||||
showErrorAlert(error)
|
||||
showErrorAlert(error, ignoreNoConnectionErrors: false)
|
||||
}
|
||||
|
||||
func setSaveButtonEnabled(_ isEnabled: Bool) {
|
||||
|
|
|
@ -178,7 +178,7 @@ class FeedModulePresenter: FeedModuleInput, FeedModuleViewOutput, FeedModuleInte
|
|||
fileprivate let limit: Int32 = Int32(Constants.Feed.pageSize)
|
||||
var currentItems: [Post] = [Post]() {
|
||||
didSet {
|
||||
Logger.log("\(oldValue.count) -> \(currentItems.count)", event: .development)
|
||||
//Logger.log("\(oldValue.count) -> \(currentItems.count)", event: .development)
|
||||
}
|
||||
}
|
||||
fileprivate var fetchRequestsInProgress: Set<String> = Set()
|
||||
|
@ -522,8 +522,7 @@ class FeedModulePresenter: FeedModuleInput, FeedModuleViewOutput, FeedModuleInte
|
|||
newModel: [newSection],
|
||||
sectionsPriorityOrder: nil,
|
||||
eliminatesDuplicates: true) { (sections, updates) in
|
||||
|
||||
Logger.log(sections.first?.items, updates, event: .development)
|
||||
|
||||
self.view.performBatches(updates: updates, withSections: sections)
|
||||
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ class ReplyCell: UICollectionViewCell, ReplyCellViewInput {
|
|||
replyLabel.text = reply.text ?? ""
|
||||
totalLikesButton.setTitle(L10n.Post.likesCount(Int(reply.totalLikes)), for: .normal)
|
||||
|
||||
postTimeLabel.text = reply.createdTime == nil ? "" : formatter.shortStyle.string(from: reply.createdTime!, to: Date())
|
||||
postTimeLabel.text = reply.createdTime == nil ? "" : formatter.timeAgo(since: reply.createdTime!)
|
||||
|
||||
if reply.user?.photo?.url == nil {
|
||||
userPhoto.image = UIImage(asset: AppConfiguration.shared.theme.assets.userPhotoPlaceholder)
|
||||
|
|
|
@ -74,6 +74,14 @@ extension SearchPresenter: SearchPeopleModuleOutput, SearchTopicsModuleOutput {
|
|||
func didSelectHashtag(_ hashtag: Hashtag) {
|
||||
view.search(hashtag: hashtag)
|
||||
}
|
||||
|
||||
func didStartLoadingSearchTopicsQuery() {
|
||||
view.setTopicsLayoutFlipEnabled(false)
|
||||
}
|
||||
|
||||
func didLoadSearchTopicsQuery() {
|
||||
view.setTopicsLayoutFlipEnabled(true)
|
||||
}
|
||||
}
|
||||
|
||||
extension SearchPresenter: SearchModuleInput {
|
||||
|
|
|
@ -138,6 +138,10 @@ extension SearchViewController: SearchViewInput {
|
|||
func search(hashtag: Hashtag) {
|
||||
searchSelectedText(hashtag)
|
||||
}
|
||||
|
||||
func setTopicsLayoutFlipEnabled(_ isEnabled: Bool) {
|
||||
feedLayoutButton.isEnabled = isEnabled
|
||||
}
|
||||
}
|
||||
|
||||
extension SearchViewController: UISearchBarDelegate {
|
||||
|
|
|
@ -16,4 +16,6 @@ protocol SearchViewInput: class {
|
|||
func setLayoutAsset(_ asset: Asset)
|
||||
|
||||
func search(hashtag: Hashtag)
|
||||
|
||||
func setTopicsLayoutFlipEnabled(_ isEnabled: Bool)
|
||||
}
|
||||
|
|
|
@ -60,6 +60,7 @@ extension SearchPeoplePresenter: UserListModuleOutput {
|
|||
moduleOutput?.didFailToLoadSuggestedUsers(error)
|
||||
} else if listView == usersListModule.listView {
|
||||
moduleOutput?.didFailToLoadSearchQuery(error)
|
||||
view.setIsEmpty(usersListModule.isListEmpty)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,4 +9,8 @@ protocol SearchTopicsModuleOutput: class {
|
|||
func didFailToLoadSearchQuery(_ error: Error)
|
||||
|
||||
func didSelectHashtag(_ hashtag: Hashtag)
|
||||
|
||||
func didStartLoadingSearchTopicsQuery()
|
||||
|
||||
func didLoadSearchTopicsQuery()
|
||||
}
|
||||
|
|
|
@ -63,8 +63,13 @@ extension SearchTopicsPresenter: FeedModuleOutput {
|
|||
return true
|
||||
}
|
||||
|
||||
func didUpdateFeed() {
|
||||
func didStartRefreshingData() {
|
||||
moduleOutput?.didStartLoadingSearchTopicsQuery()
|
||||
}
|
||||
|
||||
func didFinishRefreshingData(_ error: Error?) {
|
||||
view.setIsEmpty(feedModule.isEmpty)
|
||||
moduleOutput?.didLoadSearchTopicsQuery()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,4 +15,6 @@ protocol UserListModuleInput: class {
|
|||
func setListHeaderView(_ view: UIView?)
|
||||
|
||||
func removeUser(_ user: User)
|
||||
|
||||
var isListEmpty: Bool { get }
|
||||
}
|
||||
|
|
|
@ -123,6 +123,10 @@ extension UserListPresenter: UserListModuleInput {
|
|||
return view
|
||||
}
|
||||
|
||||
var isListEmpty: Bool {
|
||||
return !view.anyItemsShown
|
||||
}
|
||||
|
||||
func setupInitialState() {
|
||||
view.setupInitialState()
|
||||
view.setNoDataText(noDataText)
|
||||
|
|
|
@ -12,11 +12,19 @@ protocol APIErrorHandler {
|
|||
}
|
||||
|
||||
extension APIErrorHandler {
|
||||
func handle<T>(error: ErrorResponse?, completion: @escaping (Result<T>) -> Void) {
|
||||
if canHandle(error) {
|
||||
handle(error)
|
||||
} else {
|
||||
completion(.failure(APIError(error: error)))
|
||||
}
|
||||
}
|
||||
|
||||
func handle<T>(error: Error?, completion: @escaping (Result<T>) -> Void) {
|
||||
if canHandle(error) {
|
||||
handle(error)
|
||||
} else {
|
||||
completion(.failure(APIError(error: error as? ErrorResponse)))
|
||||
completion(.failure(error ?? APIError.unknown))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,8 +13,7 @@ class DateFormatterTests: XCTestCase {
|
|||
override func setUp() {
|
||||
super.setUp()
|
||||
|
||||
sut = DateFormatterTool()
|
||||
|
||||
sut = DateFormatterTool.shared
|
||||
}
|
||||
|
||||
func testThatFormattingIsCorrect() {
|
||||
|
@ -24,11 +23,12 @@ class DateFormatterTests: XCTestCase {
|
|||
var comps = DateComponents()
|
||||
comps.calendar = cal
|
||||
comps.day = -14
|
||||
let to = Date()
|
||||
let from = cal.date(byAdding: comps, to: to)
|
||||
|
||||
|
||||
let now: Date = Date()
|
||||
let then: Date = cal.date(byAdding: comps, to: now)!
|
||||
|
||||
// when
|
||||
let result = sut.shortStyle.string(from: from!, to: to)
|
||||
let result = sut.timeAgo(since: then)
|
||||
|
||||
// then
|
||||
XCTAssert(result == "2w")
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
class MockUserListModuleInput: UserListModuleInput {
|
||||
var listView = UIView()
|
||||
|
||||
var isListEmpty = false
|
||||
|
||||
//MARK: - setupInitialState
|
||||
|
||||
var setupInitialStateCalled = false
|
||||
|
@ -45,4 +47,5 @@ class MockUserListModuleInput: UserListModuleInput {
|
|||
removeUserCalled = true
|
||||
removeUserReceivedUser = user
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -47,14 +47,24 @@ class MockSearchView: SearchViewInput {
|
|||
setLayoutAssetReceivedAsset = asset
|
||||
}
|
||||
|
||||
// MARK: - search
|
||||
//MARK: - search
|
||||
|
||||
var searchHashtagCalled = false
|
||||
var searchHashtagInputHashtag: Hashtag?
|
||||
|
||||
var searchHashtagReceivedHashtag: Hashtag?
|
||||
|
||||
func search(hashtag: Hashtag) {
|
||||
searchHashtagCalled = true
|
||||
searchHashtagInputHashtag = hashtag
|
||||
searchHashtagReceivedHashtag = hashtag
|
||||
}
|
||||
|
||||
//MARK: - setTopicsLayoutFlipEnabled
|
||||
|
||||
var setTopicsLayoutFlipEnabledCalled = false
|
||||
var setTopicsLayoutFlipEnabledReceivedIsEnabled: Bool?
|
||||
|
||||
func setTopicsLayoutFlipEnabled(_ isEnabled: Bool) {
|
||||
setTopicsLayoutFlipEnabledCalled = true
|
||||
setTopicsLayoutFlipEnabledReceivedIsEnabled = isEnabled
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -153,7 +153,7 @@ class SearchPresenterTests: XCTestCase {
|
|||
let hashtag = UUID().uuidString
|
||||
sut.didSelectHashtag(hashtag)
|
||||
XCTAssertTrue(view.searchHashtagCalled)
|
||||
XCTAssertEqual(view.searchHashtagInputHashtag, hashtag)
|
||||
XCTAssertEqual(view.searchHashtagReceivedHashtag, hashtag)
|
||||
}
|
||||
|
||||
private func makePeopleTab() -> SearchTabInfo {
|
||||
|
@ -180,7 +180,7 @@ class SearchPresenterTests: XCTestCase {
|
|||
|
||||
XCTAssertEqual(view.setupInitialStateReceivedTab, topicsTab)
|
||||
XCTAssertTrue(view.searchHashtagCalled)
|
||||
XCTAssertEqual(view.searchHashtagInputHashtag, "1")
|
||||
XCTAssertEqual(view.searchHashtagReceivedHashtag, "1")
|
||||
}
|
||||
|
||||
func testSearchHashtagAfterViewIsReadyAndPeopleTabSelected() {
|
||||
|
@ -200,6 +200,18 @@ class SearchPresenterTests: XCTestCase {
|
|||
XCTAssertEqual(view.switchTabsToFromReceivedArguments?.tab, topicsTab)
|
||||
|
||||
XCTAssertTrue(view.searchHashtagCalled)
|
||||
XCTAssertEqual(view.searchHashtagInputHashtag, "1")
|
||||
XCTAssertEqual(view.searchHashtagReceivedHashtag, "1")
|
||||
}
|
||||
|
||||
func testStartLoadingTopicsQuery() {
|
||||
sut.didStartLoadingSearchTopicsQuery()
|
||||
XCTAssertTrue(view.setTopicsLayoutFlipEnabledCalled)
|
||||
XCTAssertEqual(view.setTopicsLayoutFlipEnabledReceivedIsEnabled, false)
|
||||
}
|
||||
|
||||
func testFinishLoadingTopicsQuery() {
|
||||
sut.didLoadSearchTopicsQuery()
|
||||
XCTAssertTrue(view.setTopicsLayoutFlipEnabledCalled)
|
||||
XCTAssertEqual(view.setTopicsLayoutFlipEnabledReceivedIsEnabled, true)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See LICENSE in the project root for license information.
|
||||
//
|
||||
|
||||
@testable import EmbeddedSocial
|
||||
|
||||
class MockSearchPeopleModuleOutput: SearchPeopleModuleOutput {
|
||||
|
||||
//MARK: - didFailToLoadSuggestedUsers
|
||||
|
||||
var didFailToLoadSuggestedUsersCalled = false
|
||||
var didFailToLoadSuggestedUsersReceivedError: Error?
|
||||
|
||||
func didFailToLoadSuggestedUsers(_ error: Error) {
|
||||
didFailToLoadSuggestedUsersCalled = true
|
||||
didFailToLoadSuggestedUsersReceivedError = error
|
||||
}
|
||||
|
||||
//MARK: - didFailToLoadSearchQuery
|
||||
|
||||
var didFailToLoadSearchQueryCalled = false
|
||||
var didFailToLoadSearchQueryReceivedError: Error?
|
||||
|
||||
func didFailToLoadSearchQuery(_ error: Error) {
|
||||
didFailToLoadSearchQueryCalled = true
|
||||
didFailToLoadSearchQueryReceivedError = error
|
||||
}
|
||||
|
||||
}
|
|
@ -12,6 +12,7 @@ class SearchPeoplePresenterTests: XCTestCase {
|
|||
var usersListModule: MockUserListModuleInput!
|
||||
var backgroundUsersListModule: MockUserListModuleInput!
|
||||
var sut: SearchPeoplePresenter!
|
||||
var moduleOutput: MockSearchPeopleModuleOutput!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
|
@ -19,12 +20,14 @@ class SearchPeoplePresenterTests: XCTestCase {
|
|||
interactor = MockSearchPeopleInteractor()
|
||||
usersListModule = MockUserListModuleInput()
|
||||
backgroundUsersListModule = MockUserListModuleInput()
|
||||
moduleOutput = MockSearchPeopleModuleOutput()
|
||||
sut = SearchPeoplePresenter()
|
||||
|
||||
sut.view = view
|
||||
sut.interactor = interactor
|
||||
sut.usersListModule = usersListModule
|
||||
sut.backgroundUsersListModule = backgroundUsersListModule
|
||||
sut.moduleOutput = moduleOutput
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
|
@ -34,6 +37,50 @@ class SearchPeoplePresenterTests: XCTestCase {
|
|||
usersListModule = nil
|
||||
backgroundUsersListModule = nil
|
||||
sut = nil
|
||||
moduleOutput = nil
|
||||
}
|
||||
|
||||
func testSearchErrorHandlingWhenListIsEmpty() {
|
||||
testSearchErrorHandling(listIsEmpty: true)
|
||||
|
||||
resetModuleOutput()
|
||||
resetView()
|
||||
|
||||
testSearchErrorHandling(listIsEmpty: false)
|
||||
}
|
||||
|
||||
func testSearchErrorHandling(listIsEmpty: Bool) {
|
||||
usersListModule.isListEmpty = listIsEmpty
|
||||
|
||||
sut.didFailToLoadList(listView: usersListModule.listView, error: APIError.unknown)
|
||||
|
||||
XCTAssertTrue(moduleOutput.didFailToLoadSearchQueryCalled)
|
||||
guard let e = moduleOutput.didFailToLoadSearchQueryReceivedError as? APIError, case .unknown = e else {
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertTrue(view.setIsEmptyCalled)
|
||||
XCTAssertEqual(view.setIsEmptyInputIsEmpty, listIsEmpty)
|
||||
}
|
||||
|
||||
func resetView() {
|
||||
view = MockSearchPeopleView()
|
||||
sut.view = view
|
||||
}
|
||||
|
||||
func resetModuleOutput() {
|
||||
moduleOutput = MockSearchPeopleModuleOutput()
|
||||
sut.moduleOutput = moduleOutput
|
||||
}
|
||||
|
||||
func testBackgroundListErrorHandling() {
|
||||
sut.didFailToLoadList(listView: backgroundUsersListModule.listView, error: APIError.unknown)
|
||||
XCTAssertTrue(moduleOutput.didFailToLoadSuggestedUsersCalled)
|
||||
guard let e = moduleOutput.didFailToLoadSuggestedUsersReceivedError as? APIError, case .unknown = e else {
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,4 +26,20 @@ class MockSearchTopicsModuleOutput: SearchTopicsModuleOutput {
|
|||
didSelectHashtagCalled = true
|
||||
didSelectHashtagReceivedHashtag = hashtag
|
||||
}
|
||||
|
||||
//MARK: - didStartLoadingSearchTopicsQuery
|
||||
|
||||
var didStartLoadingSearchTopicsQueryCalled = false
|
||||
|
||||
func didStartLoadingSearchTopicsQuery() {
|
||||
didStartLoadingSearchTopicsQueryCalled = true
|
||||
}
|
||||
|
||||
//MARK: - didLoadSearchTopicsQuery
|
||||
|
||||
var didLoadSearchTopicsQueryCalled = false
|
||||
|
||||
func didLoadSearchTopicsQuery() {
|
||||
didLoadSearchTopicsQueryCalled = true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ class SearchTopicsPresenterTests: XCTestCase {
|
|||
XCTAssertEqual(view.setupInitialStateWithReceivedFeedViewController, feedViewController)
|
||||
}
|
||||
|
||||
func testThatItSetsViewIsEmptyWhenFeedIsUpdated() {
|
||||
func testThatItSetsViewIsEmptyWhenFeedFinishesRefreshing() {
|
||||
testThatItSetsViewIsEmptyWhenFeedIsUpdated(feedIsEmpty: true, isViewExpectedToBeEmpty: true)
|
||||
|
||||
testThatItSetsViewIsEmptyWhenFeedIsUpdated(feedIsEmpty: false, isViewExpectedToBeEmpty: false)
|
||||
|
@ -77,7 +77,7 @@ class SearchTopicsPresenterTests: XCTestCase {
|
|||
feedModule.isEmpty = feedIsEmpty
|
||||
|
||||
// when
|
||||
sut.didUpdateFeed()
|
||||
sut.didFinishRefreshingData(APIError.unknown)
|
||||
|
||||
// then
|
||||
XCTAssertTrue(view.setIsEmptyCalled)
|
||||
|
|
Загрузка…
Ссылка в новой задаче