This commit is contained in:
Родитель
cd4b4ff451
Коммит
0d394c2a27
|
@ -1,2 +1,4 @@
|
|||
#! /bin/bash
|
||||
cp -R EmbeddedSocial\ Swift\ File.xctemplate ~/Library/Developer/Xcode/Templates/Custom/
|
||||
destination=$HOME"/Library/Developer/Xcode/Templates/Custom/"
|
||||
mkdir -p $destination
|
||||
cp -R EmbeddedSocial\ Swift\ File.xctemplate $destination
|
||||
|
|
|
@ -786,6 +786,7 @@
|
|||
EB45E0D87E3DCF49B5A22162 /* CommentRepliesInitializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC43B539B5A91FF6D4EAB9B /* CommentRepliesInitializer.swift */; };
|
||||
EC75A126A73CC5F294926613 /* CommentRepliesConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C6D69159F11B9D4D3F7000 /* CommentRepliesConfigurator.swift */; };
|
||||
ED9BC53643CA7A3E7A3106DC /* CommentCellConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE1F824A8069868818F477BD /* CommentCellConfigurator.swift */; };
|
||||
F8ED92AE1F72DF3E0026AC3E /* ActivityPresenterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8ED92AD1F72DF3E0026AC3E /* ActivityPresenterTests.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
|
@ -1631,6 +1632,7 @@
|
|||
E63643937A3B405C1B4E0B49 /* CommentCellInteractorInput.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CommentCellInteractorInput.swift; sourceTree = "<group>"; };
|
||||
EC1ECDB428A92745248FE72C /* CommentCellRouterInput.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CommentCellRouterInput.swift; sourceTree = "<group>"; };
|
||||
F35AC4A39014F4C18D39AA62 /* ActivityRouter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ActivityRouter.swift; sourceTree = "<group>"; };
|
||||
F8ED92AD1F72DF3E0026AC3E /* ActivityPresenterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityPresenterTests.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
@ -4338,6 +4340,7 @@
|
|||
children = (
|
||||
9CEE0EDD1F6FE3F7008B1104 /* ActivityInteractorTests.swift */,
|
||||
9CCE405C1F72827A003A51D9 /* ActivityTests.swift */,
|
||||
F8ED92AD1F72DF3E0026AC3E /* ActivityPresenterTests.swift */,
|
||||
);
|
||||
path = Activity;
|
||||
sourceTree = "<group>";
|
||||
|
@ -5765,6 +5768,7 @@
|
|||
881F7E621F263888003EB37A /* MockKeyValueStorage.swift in Sources */,
|
||||
88B94ED11F62C3FA002392F9 /* MockSearchTopicsInteractor.swift in Sources */,
|
||||
9C985E531F505BDF00514F85 /* FeedCacheTests.swift in Sources */,
|
||||
F8ED92AE1F72DF3E0026AC3E /* ActivityPresenterTests.swift in Sources */,
|
||||
884C9C341F4C3E940004907F /* UserListPresenterTests.swift in Sources */,
|
||||
932898D41F65929F001F3BC2 /* ReportReplyAPITests.swift in Sources */,
|
||||
9C30243C1F55AB2100675FE9 /* FeedModuleRouterMock.swift in Sources */,
|
||||
|
|
|
@ -8,19 +8,23 @@ import Foundation
|
|||
protocol SectionModelType {
|
||||
associatedtype Item
|
||||
|
||||
var items: [Item] { get }
|
||||
var items: [Item] { get set }
|
||||
}
|
||||
|
||||
struct SectionModel<Section, ItemType>: SectionModelType {
|
||||
typealias Item = ItemType
|
||||
|
||||
let model: Section
|
||||
let items: [Item]
|
||||
var model: Section
|
||||
var items: [Item]
|
||||
|
||||
init(model: Section, items: [Item]) {
|
||||
self.model = model
|
||||
self.items = items
|
||||
}
|
||||
|
||||
mutating func erase() {
|
||||
items = []
|
||||
}
|
||||
}
|
||||
|
||||
extension SectionModel: CustomStringConvertible {
|
||||
|
|
|
@ -91,6 +91,12 @@ struct PendingRequestItem {
|
|||
var userHandle: String
|
||||
}
|
||||
|
||||
extension PendingRequestItem {
|
||||
static func mock(seed: Int) -> PendingRequestItem {
|
||||
return PendingRequestItem(userName: "username \(seed)", userHandle: "handle \(seed)")
|
||||
}
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
enum Change<T> {
|
||||
|
@ -166,7 +172,7 @@ class ActivityViewModelBuilder {
|
|||
|
||||
class SectionsConfigurator {
|
||||
|
||||
func build(section: ActivityPresenter.State) -> [ActivityPresenter.Section] {
|
||||
func build(section: ActivityPresenter.State) -> [Section] {
|
||||
|
||||
switch section {
|
||||
case .my:
|
||||
|
@ -174,21 +180,22 @@ class SectionsConfigurator {
|
|||
case .others:
|
||||
return others
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private var my: [ActivityPresenter.Section] {
|
||||
private var my: [Section] {
|
||||
let sectionHeader = SectionHeader(name: "Section 1", identifier: "")
|
||||
let model = PendingRequestItem(userName: "User", userHandle: "User handle")
|
||||
let item = ActivityItem.pendingRequest(model)
|
||||
let section = ActivityPresenter.Section(model: sectionHeader, items: [item])
|
||||
let section = Section(model: sectionHeader, items: [item])
|
||||
return [section]
|
||||
}
|
||||
|
||||
private var others: [ActivityPresenter.Section] {
|
||||
private var others: [Section] {
|
||||
let sectionHeader = SectionHeader(name: "Section 2", identifier: "")
|
||||
let model = ActionItem.mock(seed: 0)
|
||||
let item = ActivityItem.follower(model)
|
||||
let section = ActivityPresenter.Section(model: sectionHeader, items: [item])
|
||||
let section = Section(model: sectionHeader, items: [item])
|
||||
return [section]
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,10 @@ protocol ActivityInteractorOutput: class {
|
|||
|
||||
protocol ActivityInteractorInput {
|
||||
|
||||
func loadAll()
|
||||
func loadNextPageFollowingActivities(completion: ((Result<[ActionItem]>) -> Void)?)
|
||||
func loadNextPagePendigRequestItems(completion: ((Result<[PendingRequestItem]>) -> Void)?)
|
||||
|
||||
}
|
||||
|
||||
protocol ActivityService: class {
|
||||
|
@ -190,37 +194,39 @@ extension ActivityInteractor: ActivityInteractorInput {
|
|||
let pageID = UUID().uuidString
|
||||
loadingPages.insert(pageID)
|
||||
|
||||
service.loadFollowingActivities(cursor: followersList.cursor,
|
||||
limit: followersList.limit) { [weak self] (result: Result<FeedResponseActivityView>) in
|
||||
|
||||
defer {
|
||||
loadingPages.remove(pageID)
|
||||
}
|
||||
|
||||
// exit on released or canceled
|
||||
guard let strongSelf = self, strongSelf.loadingPages.contains(pageID) else {
|
||||
return
|
||||
}
|
||||
|
||||
// must have data
|
||||
guard let response = result.value else {
|
||||
completion?(.failure(ActivityError.noData))
|
||||
return
|
||||
}
|
||||
|
||||
// map data into page
|
||||
guard let page = strongSelf.process(response: response, pageID: pageID) else {
|
||||
completion?(.failure(ActivityError.notParsable))
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.followersList.add(page: page)
|
||||
|
||||
completion?(.success(page.items))
|
||||
service.loadFollowingActivities(
|
||||
cursor: followersList.cursor,
|
||||
limit: followersList.limit) { [weak self] (result: Result<FeedResponseActivityView>) in
|
||||
|
||||
defer {
|
||||
loadingPages.remove(pageID)
|
||||
}
|
||||
|
||||
// exit on released or canceled
|
||||
guard let strongSelf = self, strongSelf.loadingPages.contains(pageID) else {
|
||||
return
|
||||
}
|
||||
|
||||
// must have data
|
||||
guard let response = result.value else {
|
||||
completion?(.failure(ActivityError.noData))
|
||||
return
|
||||
}
|
||||
|
||||
// map data into page
|
||||
guard let page = strongSelf.process(response: response, pageID: pageID) else {
|
||||
completion?(.failure(ActivityError.notParsable))
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.followersList.add(page: page)
|
||||
|
||||
completion?(.success(page.items))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TODO: remake using generics
|
||||
func loadNextPagePendigRequestItems(completion: ((Result<[PendingRequestItem]>) -> Void)? = nil) {
|
||||
|
||||
let pageID = UUID().uuidString
|
||||
|
|
|
@ -7,14 +7,14 @@ protocol ActivityModuleInput: class {
|
|||
|
||||
}
|
||||
|
||||
typealias Section = SectionModel<SectionHeader, ActivityItem>
|
||||
|
||||
class ActivityPresenter {
|
||||
|
||||
typealias Section = SectionModel<SectionHeader, ActivityItem>
|
||||
|
||||
weak var view: ActivityViewInput!
|
||||
var interactor: ActivityInteractorInput!
|
||||
var router: ActivityRouterInput!
|
||||
|
||||
|
||||
enum State: Int {
|
||||
case my
|
||||
case others
|
||||
|
@ -44,9 +44,9 @@ class ActivityPresenter {
|
|||
sections[.my] = sectionsConfigurator.build(section: .my)
|
||||
sections[.others] = sectionsConfigurator.build(section: .others)
|
||||
}
|
||||
|
||||
|
||||
fileprivate func loadNextPage() {
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,13 +59,98 @@ extension ActivityPresenter: ActivityInteractorOutput {
|
|||
|
||||
}
|
||||
|
||||
|
||||
|
||||
protocol DataSourceProtocol {
|
||||
func loadMore()
|
||||
var section: Section { get }
|
||||
}
|
||||
|
||||
class DataSource: DataSourceProtocol {
|
||||
var interactor: ActivityInteractorInput
|
||||
var section: Section
|
||||
var errorHandler: ((Error) -> Void)?
|
||||
|
||||
func loadMore() { }
|
||||
|
||||
init(interactor: ActivityInteractorInput, section: Section, errorHandler: ((Error) -> Void)? = nil) {
|
||||
self.interactor = interactor
|
||||
self.section = section
|
||||
self.errorHandler = errorHandler
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class MyPendingRequests: DataSource {
|
||||
|
||||
override func loadMore() {
|
||||
|
||||
// load pendings
|
||||
interactor.loadNextPagePendigRequestItems { [weak self] (result) in
|
||||
switch result {
|
||||
case let .failure(error):
|
||||
self?.errorHandler?(error)
|
||||
case let .success(models):
|
||||
let items = models.map { ActivityItem.pendingRequest($0) }
|
||||
self?.section.items.append(contentsOf: items)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class MyFollowersActivity: DataSource {
|
||||
|
||||
override func loadMore() {
|
||||
// load activity
|
||||
interactor.loadNextPageFollowingActivities { [weak self] (result) in
|
||||
switch result {
|
||||
case let .failure(error):
|
||||
self?.errorHandler?(error)
|
||||
case let .success(models):
|
||||
let items = models.map { ActivityItem.follower($0) }
|
||||
self?.section.items.append(contentsOf: items)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MyFollowingsActivity: DataSource {
|
||||
|
||||
override func loadMore() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension ActivityPresenter: ActivityViewOutput {
|
||||
|
||||
func load() {
|
||||
|
||||
interactor.loadAll()
|
||||
}
|
||||
|
||||
func loadMore(for section: Int) {
|
||||
func loadMore() {
|
||||
|
||||
interactor.loadNextPageFollowingActivities { (result) in
|
||||
|
||||
switch result {
|
||||
case let .success(items):
|
||||
|
||||
// sections[state]
|
||||
break
|
||||
|
||||
case let .failure(error):
|
||||
|
||||
break
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
interactor.loadNextPageFollowingActivities { (result) in
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -86,7 +171,7 @@ extension ActivityPresenter: ActivityViewOutput {
|
|||
}
|
||||
|
||||
func numberOfSections() -> Int {
|
||||
return sections.count
|
||||
return sections[state]!.count
|
||||
}
|
||||
|
||||
func numberOfItems(in section: Int) -> Int {
|
||||
|
|
|
@ -7,7 +7,8 @@ import UIKit
|
|||
|
||||
protocol ActivityViewInput: class {
|
||||
func setupInitialState()
|
||||
func registerCell(cell: AnyObject.Type, id: String)
|
||||
func registerCell(cell: UITableViewCell.Type, id: String)
|
||||
func showError(_ error: Error)
|
||||
}
|
||||
|
||||
protocol ActivityViewOutput: class {
|
||||
|
@ -44,11 +45,15 @@ class ActivityViewController: UIViewController {
|
|||
|
||||
extension ActivityViewController: ActivityViewInput {
|
||||
|
||||
func showError(_ error: Error) {
|
||||
Logger.log(error, event: .veryImportant)
|
||||
}
|
||||
|
||||
func setupInitialState() {
|
||||
|
||||
}
|
||||
|
||||
func registerCell(cell: AnyObject.Type, id: String) {
|
||||
func registerCell(cell: UITableViewCell.Type, id: String) {
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See LICENSE in the project root for license information.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import EmbeddedSocial
|
||||
|
||||
class ActivityInteractorMock: ActivityInteractorInput {
|
||||
|
||||
var followingActivityItemsResult: Result<[ActionItem]>!
|
||||
var pendingRequestsItemsResult: Result<[PendingRequestItem]>!
|
||||
|
||||
func loadAll() {
|
||||
|
||||
}
|
||||
|
||||
func loadNextPageFollowingActivities(completion: ((Result<[ActionItem]>) -> Void)? = nil) {
|
||||
completion?(followingActivityItemsResult)
|
||||
}
|
||||
|
||||
func loadNextPagePendigRequestItems(completion: ((Result<[PendingRequestItem]>) -> Void)? = nil) {
|
||||
completion?(pendingRequestsItemsResult)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ActivityPresenterTests: XCTestCase {
|
||||
|
||||
var sut: ActivityPresenter!
|
||||
var interactor : ActivityInteractorMock!
|
||||
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
|
||||
sut = ActivityPresenter()
|
||||
sut.interactor = ActivityInteractorMock()
|
||||
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func test() {
|
||||
|
||||
// given
|
||||
|
||||
let mockItems = Array(1..<5).map { PendingRequestItem.mock(seed: $0) }
|
||||
let result: Result<[PendingRequestItem]> = .success(mockItems)
|
||||
|
||||
let header = SectionHeader(name: "", identifier: "")
|
||||
let section = Section(model: header, items: [])
|
||||
let pendingRequestsDataSource = MyPendingRequests(interactor: interactor,
|
||||
section: section)
|
||||
|
||||
interactor.pendingRequestsItemsResult = result
|
||||
|
||||
pendingRequestsDataSource.loadMore()
|
||||
|
||||
pendingRequestsDataSource.section.items.count
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -87,6 +87,25 @@ class ActivityEntitiesTests: XCTestCase {
|
|||
waitForExpectations(timeout: 1, handler: nil)
|
||||
}
|
||||
|
||||
func testThatPaggingWorksCorrectly() {
|
||||
|
||||
// given
|
||||
let service = ActivityServiceMock()
|
||||
let followingActivitiesResponse = buildActivitiResponseMock()
|
||||
service.followingActivitiesResponse = .success(followingActivitiesResponse)
|
||||
|
||||
let interactor = ActivityInteractor()
|
||||
interactor.service = service
|
||||
|
||||
let pendingRequests = expectation(description: #file)
|
||||
|
||||
// when
|
||||
|
||||
// interactor.
|
||||
|
||||
// then
|
||||
}
|
||||
|
||||
func buildActivitiResponseMock() -> FeedResponseActivityView {
|
||||
return FeedResponseActivityView().mockResponse()
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче