Skip to content

Commit

Permalink
Reducer Protocol (#62)
Browse files Browse the repository at this point in the history
* add dependencies and its test

* update effect and reducer

* add reducers, rename anyreducer, delete xct related files

* extract storeconfig

* task result and task cancellable value

* observable

* effect

* fix package swift

* any disposable

* import xctdynamicoverlay

* update effects

* make any disposable public

* fix any reducer compatibility

* first passed test 5.7

* refactor package.swift

* remove anyequatable double

* update store

* wrap in discardable

* [TestStore] - Update to conform changes ReducerProtocol (#65)

* progress update teststore

* progressing update TestStore

* Fixing TestStore & make it success run test

* update indent code TestStore, add comment for send action assert testStore

* change observable

* update observable

* rename conflicted name

* undo

* add flatmap

* add xctest dynamic overlay podspec

* add xctdo podspec

* update exports.swift

* update podspec

* update typo podspec

* fix dispose issue

* restructure dependency module, fix test, update podspec and package.swift

* update store old behaviour, fix store tests

* remove unused code

* fix flatmap, add StoreOf

* update teststore generics order, remove all spi internals

* Rewrite example using Reducer Protocol style (#63)

* update store, rewrite basic and scoping example

* rewrite environment example

* rewrite pullback vc example

* update counterview, rewrite iflet

* rewrite never equal

* update timer example

* update test

* update old scope and store tests

* use OptionalPath

* remove duplicated StoreConfig

* add ACL for TestReducer class & property

* fix default value for useNewScope in StoreConfig

* [Reducer Protocol] - Add New Test Cases (#68)

* fix passing useNewScope to Store from TestStore

* add new test to TestStore

* add new TestStoreTest cases

* update to fix async task cancel

* update StoreTest test case

* add more StoreTests test cases

* update failingWhenNothingChanges to true by default on new TestStore code

* update & add new cases for TestStoreTest

* remove import combine

* separate oldScope & newScope test on Storetest and TestStoreTest

* update testcases based on new default value for  for Store and TestStore

* [Reducer Protocol] - Support Bootstrap MockKit (#70)

Support our Bootstrap MockKit when using ReducerProtocol Dependency Environment

* update pullback vc

* bump xcode versions

* update ci.yml to use xcode 14.1 for example tests

* update ci yml again for xcode 14.1

* refactor ci.yml

* update xcode 14 1 name

* sync with 0.44.1 minus PR 1510 and EffectTask

* fix warning

* Fix Reducer builder inference issue: pointfreeco/swift-composable-architecture#1591 Implement Test Pt1

* adding more test, still failing dependency test on both store and teststore

* pointfreeco/swift-composable-architecture#1570
pointfreeco/swift-composable-architecture#1575

* add usenewscope

* Sync Cancellation

* Readd PR #1510 and add tests, fix tests examples

* add optional reducer, update dependency values tests with binding

* sync up to 0.46.0

* sync up to 0.47.2

* sync up to 0.48.0

* bump xctestdynamicoverlay

* remove assert function on teststore

* add failing when nothing change on scope teststore

* update store send and mainqueue syntax

---------

Co-authored-by: Andhika Setiadi <[email protected]>
Co-authored-by: Jefferson Setiawan <[email protected]>
  • Loading branch information
3 people authored Feb 6, 2023
1 parent 8bdc761 commit c0e0b54
Show file tree
Hide file tree
Showing 127 changed files with 15,120 additions and 2,937 deletions.
31 changes: 22 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,33 +9,46 @@ on:
- '*'

jobs:
build_monterey:

build_library:
strategy:
matrix:
xcode:
- '13.4'
- '14.0'
- '13.4.1'
- '14.1'
runs-on: macos-12
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Select Xcode ${{ matrix.xcode }}
run: sudo xcode-select -s /Applications/Xcode_${{ matrix.xcode }}.app
- name: Run build
run: make build-library
- name: Run benchmark
run: make benchmark
unit_test_monterey:

unit_test_library:
strategy:
matrix:
xcode:
- '13.4'
- '14.0'
- '13.4.1'
- '14.1'
runs-on: macos-12
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Select Xcode ${{ matrix.xcode }}
run: sudo xcode-select -s /Applications/Xcode_${{ matrix.xcode }}.app
- name: Run tests
run: make test-library
- name: Run Example Tests

unit_test_example:
runs-on: macos-12
steps:
- uses: actions/checkout@v3
- name: Selext Xcode ${{ matrix.xcode }}
run: sudo xcode-select -s /Applications/Xcode_14.1.app
- name: Run tests
run: make test-example




7 changes: 7 additions & 0 deletions .swift-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"indentation" : {
"spaces" : 4
},
"tabWidth" : 8,
"version" : 1
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@
"revision": "ce9c0d897db8a840c39de64caaa9b60119cf4be8",
"version": "0.8.1"
}
},
{
"package": "xctest-dynamic-overlay",
"repositoryURL": "https://github.com/pointfreeco/xctest-dynamic-overlay",
"state": {
"branch": null,
"revision": "16e6409ee82e1b81390bdffbf217b9c08ab32784",
"version": "0.5.0"
}
}
]
},
Expand Down
46 changes: 24 additions & 22 deletions Examples/Examples/1-BasicUsage/BasicUsage+Reducer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,31 @@

import RxComposableArchitecture

struct BasicState: Equatable {
var number: Int
var errorMessage: String?
}

enum BasicAction: Equatable {
case didTapPlus
case didTapMinus
}

let basicUsageReducer = Reducer<BasicState, BasicAction, Void> { state, action, _ in
switch action {
case .didTapMinus:
guard state.number > 0 else {
state.errorMessage = "Can't below 0"
struct Basic: ReducerProtocol {
struct State: Equatable {
var number: Int
var errorMessage: String?
}

enum Action: Equatable {
case didTapPlus
case didTapMinus
}

func reduce(into state: inout State, action: Action) -> Effect<Action> {
switch action {
case .didTapMinus:
guard state.number > 0 else {
state.errorMessage = "Can't below 0"
return .none
}
state.number -= 1
state.errorMessage = nil
return .none
case .didTapPlus:
state.number += 1
state.errorMessage = nil
return .none
}
state.number -= 1
state.errorMessage = nil
return .none
case .didTapPlus:
state.number += 1
state.errorMessage = nil
return .none
}
}
4 changes: 2 additions & 2 deletions Examples/Examples/1-BasicUsage/BasicUsageVC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ class BasicUsageVC: UIScrollVC {
return label
}()

private let store: Store<BasicState, BasicAction>
private let store: StoreOf<Basic>

init(store: Store<BasicState, BasicAction>) {
init(store: StoreOf<Basic>) {
self.store = store
super.init()
title = "Basic Usage of State, Action & Reducer"
Expand Down
122 changes: 78 additions & 44 deletions Examples/Examples/2-Environment/EnvironmentDemoVC+Reducer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,54 +9,34 @@ import CasePaths
import Foundation
import RxComposableArchitecture
import RxSwift
import XCTestDynamicOverlay

struct CustomError: Error, Equatable {
var message: String
}

struct EnvironmentState: Equatable {
var text: String = "First Load"
var isLoading = false
var alertMessage: String?
var uuidString: String = "NONE"
var currentDate: Date?
}

enum EnvironmentAction: Equatable {
case didLoad
case receiveData(Result<Int, CustomError>)
case refresh
case getCurrentDate
case generateUUID
case dismissAlert
}

struct AnalyticsEvent: Equatable {
var name: String
var category: String
}

class AnalyticManager {
private init() {}
static func track(_ event: AnalyticsEvent) {
print("<<< Track event of \(event)")
struct Environment: ReducerProtocol {
struct State: Equatable {
var text: String = "First Load"
var isLoading = false
var alertMessage: String?
var uuidString: String = "NONE"
var currentDate: Date?
}
}

struct EnvironmentVCEnvironment {
var loadData: () -> Effect<Result<Int, CustomError>>
var trackEvent: (AnalyticsEvent) -> Void
var date: () -> Date
var uuid: () -> UUID
}

let environmentReducer: Reducer<EnvironmentState, EnvironmentAction, EnvironmentVCEnvironment> = {
Reducer<EnvironmentState, EnvironmentAction, EnvironmentVCEnvironment> { state, action, env in

enum Action: Equatable {
case didLoad
case receiveData(Result<Int, CustomError>)
case refresh
case getCurrentDate
case generateUUID
case dismissAlert
}

@Dependency(\.envVCEnvironment) var env

func reduce(into state: inout State, action: Action) -> Effect<Action> {
switch action {
case .didLoad:
state.isLoading = true
return env.loadData()
.map(EnvironmentAction.receiveData)
.map(Action.receiveData)
case let .receiveData(response):
state.isLoading = false
switch response {
Expand All @@ -71,7 +51,7 @@ let environmentReducer: Reducer<EnvironmentState, EnvironmentAction, Environment
state.isLoading = true
return .merge(
env.loadData()
.map(EnvironmentAction.receiveData)
.map(Action.receiveData)
.eraseToEffect(),
.fireAndForget {
env.trackEvent(AnalyticsEvent(name: "refresh", category: "DUMMY"))
Expand All @@ -92,4 +72,58 @@ let environmentReducer: Reducer<EnvironmentState, EnvironmentAction, Environment
return .none
}
}
}()
}

struct AnalyticsEvent: Equatable {
var name: String
var category: String
}

struct CustomError: Error, Equatable {
var message: String
}

class AnalyticsManager {
private init() {}
static func track(_ event: AnalyticsEvent) {
print(">> Tracked: \(event)")
}
}

struct EnvironmentVCEnvironment {
var loadData: () -> Effect<Result<Int, CustomError>>
var trackEvent: (AnalyticsEvent) -> Void
var date: () -> Date
var uuid: () -> UUID
}

extension EnvironmentVCEnvironment: DependencyKey {
static var liveValue: EnvironmentVCEnvironment {
EnvironmentVCEnvironment(
loadData: {
Observable.just(Result.success(Int.random(in: 0 ... 10000)))
.delay(.milliseconds(500), scheduler: MainScheduler.instance)
.eraseToEffect()
},
trackEvent: AnalyticsManager.track(_:),
date: Date.init,
uuid: UUID.init
)
}

static var testValue: EnvironmentVCEnvironment {
EnvironmentVCEnvironment(
loadData: unimplemented("\(Self.self).loadData", placeholder: .just(.failure(CustomError(message: "Error"))).eraseToEffect()),
trackEvent: unimplemented("\(Self.self).trackEvent"),
date: unimplemented("\(Self.self).date", placeholder: Date()),
uuid: unimplemented("\(Self.self).uuid", placeholder: UUID())
)
}
}

extension DependencyValues {
var envVCEnvironment: EnvironmentVCEnvironment {
get { self[EnvironmentVCEnvironment.self] }
set { self[EnvironmentVCEnvironment.self] = newValue }
}
}
4 changes: 2 additions & 2 deletions Examples/Examples/2-Environment/EnvironmentDemoVC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ class EnvironmentDemoVC: UIScrollVC {
private let getDateButton = UIButton.template(title: "Get new Date")
private let getUUIDButton = UIButton.template(title: "Get new UUID")

private let store: Store<EnvironmentState, EnvironmentAction>
private let store: StoreOf<Environment>

init(store: Store<EnvironmentState, EnvironmentAction>) {
init(store: StoreOf<Environment>) {
self.store = store
super.init()
}
Expand Down
48 changes: 28 additions & 20 deletions Examples/Examples/2-Environment/EnvironmentRoute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,32 +29,40 @@ class EnvironmentRouteVC: UITableViewController {
let selectedRoute = routes[indexPath.row]
switch selectedRoute {
case .live:
let viewController = EnvironmentDemoVC(store: Store(
initialState: EnvironmentState(),
reducer: environmentReducer,
environment: EnvironmentVCEnvironment.live
))
let viewController = EnvironmentDemoVC(
store: Store(
initialState: Environment.State(),
reducer: Environment()
.dependency(\.envVCEnvironment, .live)
)
)
navigationController?.pushViewController(viewController, animated: true)
case .mockSuccess:
let viewController = EnvironmentDemoVC(store: Store(
initialState: EnvironmentState(),
reducer: environmentReducer,
environment: EnvironmentVCEnvironment.mockSuccess
))
let viewController = EnvironmentDemoVC(
store: Store(
initialState: Environment.State(),
reducer: Environment()
.dependency(\.envVCEnvironment, .mockSuccess)
)
)
navigationController?.pushViewController(viewController, animated: true)
case .mockFailed:
let viewController = EnvironmentDemoVC(store: Store(
initialState: EnvironmentState(),
reducer: environmentReducer,
environment: EnvironmentVCEnvironment.mockFailed
))
let viewController = EnvironmentDemoVC(
store: Store(
initialState: Environment.State(),
reducer: Environment()
.dependency(\.envVCEnvironment, .mockFailed)
)
)
navigationController?.pushViewController(viewController, animated: true)
case .mockRandom:
let viewController = EnvironmentDemoVC(store: Store(
initialState: EnvironmentState(),
reducer: environmentReducer,
environment: EnvironmentVCEnvironment.mockRandom
))
let viewController = EnvironmentDemoVC(
store: Store(
initialState: Environment.State(),
reducer: Environment()
.dependency(\.envVCEnvironment, .mockRandom)
)
)
navigationController?.pushViewController(viewController, animated: true)
}
}
Expand Down
2 changes: 1 addition & 1 deletion Examples/Examples/2-Environment/EnvironmentVC+Mock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ extension EnvironmentVCEnvironment {
.delay(.milliseconds(500), scheduler: MainScheduler.instance)
.eraseToEffect()
},
trackEvent: AnalyticManager.track,
trackEvent: AnalyticsManager.track,
date: Date.init,
uuid: UUID.init
)
Expand Down
Loading

0 comments on commit c0e0b54

Please sign in to comment.