Skip to content

Commit 5b505a2

Browse files
committed
refactor: RootFeature BindingAction 적용
1 parent a1efe58 commit 5b505a2

4 files changed

Lines changed: 57 additions & 24 deletions

File tree

Application/DevLogPresentation/Sources/Root/RootFeature.swift

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ struct RootFeature {
2626
var isNetworkConnected = true
2727
var signIn: Bool?
2828
var theme: SystemTheme = .automatic
29+
var selectedMainTab = MainTab.home
2930
var isObservingNetworkConnectivity = false
3031
var isObservingSession = false
3132
var isObservingTheme = false
@@ -37,15 +38,17 @@ struct RootFeature {
3738
var id: String { todoId }
3839
}
3940

40-
enum Action: Equatable {
41+
enum Action: BindableAction, Equatable {
4142
case alert(PresentationAction<Never>)
43+
case binding(BindingAction<State>)
4244
case sheet(PresentationAction<Sheet>)
4345
case view(ViewAction)
4446
case store(StoreAction)
4547

4648
enum ViewAction: Equatable {
4749
case onAppear
4850
case presentTodoDetail(String)
51+
case openWidgetRoute(MainTab)
4952
}
5053

5154
enum Sheet: Equatable {
@@ -66,10 +69,13 @@ struct RootFeature {
6669
@Dependency(\.setApplicationBadgeCount) var setApplicationBadgeCount
6770

6871
var body: some ReducerOf<Self> {
72+
BindingReducer()
6973
Reduce { state, action in
7074
switch action {
7175
case .alert:
7276
break
77+
case .binding:
78+
break
7379
case .sheet(.dismiss), .sheet(.presented(.tapCloseButton)):
7480
state.sheet = nil
7581
case .sheet:
@@ -95,6 +101,9 @@ struct RootFeature {
95101
return effect
96102
case .view(.presentTodoDetail(let todoId)):
97103
state.sheet = .init(todoId: todoId)
104+
case .view(.openWidgetRoute(let mainTab)):
105+
guard state.signIn == true else { break }
106+
state.selectedMainTab = mainTab
98107
case .store(.networkStatusChanged(let isConnected)):
99108
let wasConnected = state.isNetworkConnected
100109
state.isNetworkConnected = isConnected
@@ -105,7 +114,9 @@ struct RootFeature {
105114
state.theme = theme
106115
case .store(.didLogined(let result)):
107116
state.signIn = result
108-
if !result {
117+
if result {
118+
state.selectedMainTab = .home
119+
} else {
109120
return trackLoginScreenEffect()
110121
}
111122
}

Application/DevLogPresentation/Sources/Root/RootView.swift

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import DevLogDomain
1414
public struct RootView: View {
1515
@Environment(\.diContainer) var container: DIContainer
1616
@State private var store: StoreOf<RootFeature>
17-
@State private var selectedMainTab = MainTab.home
1817
private let widgetURLTab: (URL) -> MainTab?
1918
private let windowEvent: TodoEditorWindowEvent
2019
private let pushNotificationTodoIdPublisher: AnyPublisher<String, Never>
@@ -52,7 +51,7 @@ public struct RootView: View {
5251
MainView(
5352
container: container,
5453
windowEvent: windowEvent,
55-
selectedTab: $selectedMainTab
54+
selectedTab: $store.selectedMainTab
5655
)
5756
} else {
5857
LoginView(signInUseCase: container.resolve(SignInUseCase.self))
@@ -61,22 +60,9 @@ public struct RootView: View {
6160
}
6261
.preferredColorScheme(store.theme.colorScheme)
6362
.onAppear { store.send(.view(.onAppear)) }
64-
.onChange(of: store.signIn) { _, value in
65-
guard let value else { return }
66-
if value {
67-
selectedMainTab = .home
68-
}
69-
}
7063
.onOpenURL { url in
7164
guard let mainTab = widgetURLTab(url) else { return }
72-
switch store.signIn {
73-
case .some(false):
74-
break
75-
case .some(true):
76-
selectedMainTab = mainTab
77-
case .none:
78-
break
79-
}
65+
store.send(.view(.openWidgetRoute(mainTab)))
8066
}
8167
.alert($store.scope(state: \.alert, action: \.alert))
8268
.sheet(item: $store.scope(state: \.sheet, action: \.sheet)) { sheetStore in

Application/DevLogPresentation/Tests/Root/RootFeatureTestSupport.swift

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ protocol RootStateDriving {
2424
func didLogined(_ signIn: Bool) async
2525
func presentTodoDetail(_ todoId: String) async
2626
func dismissSheet() async
27+
func selectMainTab(_ tab: MainTab) async
28+
func openWidgetRoute(_ tab: MainTab) async
2729
}
2830

2931
struct RootStateSnapshot: Equatable {
@@ -32,6 +34,7 @@ struct RootStateSnapshot: Equatable {
3234
let isNetworkConnected: Bool
3335
let signIn: Bool?
3436
let theme: SystemTheme
37+
let selectedMainTab: MainTab
3538
}
3639

3740
@MainActor
@@ -44,7 +47,8 @@ struct RootStoreTestAdapter: RootStateDriving {
4447
alertMessage: store.state.alert?.message.map { String(state: $0) },
4548
isNetworkConnected: store.state.isNetworkConnected,
4649
signIn: store.state.signIn,
47-
theme: store.state.theme
50+
theme: store.state.theme,
51+
selectedMainTab: store.state.selectedMainTab
4852
)
4953
}
5054
var sheetTodoId: String? { store.state.sheet?.todoId }
@@ -107,6 +111,14 @@ struct RootStoreTestAdapter: RootStateDriving {
107111
await store.send(.sheet(.dismiss))
108112
}
109113

114+
func selectMainTab(_ tab: MainTab) async {
115+
await store.send(.binding(.set(\.selectedMainTab, tab)))
116+
}
117+
118+
func openWidgetRoute(_ tab: MainTab) async {
119+
await store.send(.view(.openWidgetRoute(tab)))
120+
}
121+
110122
private func drainReceivedActions() async {
111123
for _ in 0..<8 {
112124
await store.skipReceivedActions(strict: false)
@@ -125,7 +137,8 @@ func verifyNetworkDisconnectedAlert(adapter: some RootStateDriving) async {
125137
alertMessage: String(localized: "root_network_disconnected_message"),
126138
isNetworkConnected: false,
127139
signIn: nil,
128-
theme: .automatic
140+
theme: .automatic,
141+
selectedMainTab: .home
129142
)
130143
)
131144
}
@@ -142,7 +155,8 @@ func verifySetAlert(adapter: some RootStateDriving) async {
142155
alertMessage: nil,
143156
isNetworkConnected: false,
144157
signIn: nil,
145-
theme: .automatic
158+
theme: .automatic,
159+
selectedMainTab: .home
146160
)
147161
)
148162
}
@@ -153,6 +167,7 @@ func verifyThemeUpdate(adapter: some RootStateDriving) async {
153167

154168
#expect(adapter.snapshot.theme == .dark)
155169
#expect(adapter.snapshot.alertTitle == nil)
170+
#expect(adapter.snapshot.selectedMainTab == .home)
156171
}
157172

158173
@MainActor
@@ -166,6 +181,7 @@ func verifyDidLoginedFalse(
166181
}
167182

168183
#expect(adapter.snapshot.signIn == false)
184+
#expect(adapter.snapshot.selectedMainTab == .home)
169185
#expect(trackAnalyticsEventUseCaseSpy.screenNames == ["login"])
170186
}
171187

@@ -174,9 +190,11 @@ func verifyDidLoginedTrue(
174190
adapter: some RootStateDriving,
175191
trackAnalyticsEventUseCaseSpy: RootTrackAnalyticsEventUseCaseSpy
176192
) async {
193+
await adapter.selectMainTab(.today)
177194
await adapter.didLogined(true)
178195

179196
#expect(adapter.snapshot.signIn == true)
197+
#expect(adapter.snapshot.selectedMainTab == .home)
180198
#expect(trackAnalyticsEventUseCaseSpy.screenNames.isEmpty)
181199
}
182200

@@ -198,7 +216,8 @@ func verifyObservedInitialValues(adapter: some RootStateDriving) async {
198216
alertMessage: String(localized: "root_network_disconnected_message"),
199217
isNetworkConnected: false,
200218
signIn: false,
201-
theme: .dark
219+
theme: .dark,
220+
selectedMainTab: .home
202221
)
203222
)
204223
}
@@ -212,6 +231,16 @@ func verifyTodoDetailSheetPresentation(adapter: some RootStateDriving) async {
212231
#expect(adapter.sheetTodoId == nil)
213232
}
214233

234+
@MainActor
235+
func verifyWidgetRouteOpensWhenSignedIn(adapter: some RootStateDriving) async {
236+
await adapter.openWidgetRoute(.today)
237+
#expect(adapter.snapshot.selectedMainTab == .home)
238+
239+
await adapter.didLogined(true)
240+
await adapter.openWidgetRoute(.today)
241+
#expect(adapter.snapshot.selectedMainTab == .today)
242+
}
243+
215244
final class ObserveAuthSessionUseCaseSpy: ObserveAuthSessionUseCase {
216245
let subject: CurrentValueSubject<Bool, Never>
217246
private(set) var observeCallCount = 0

Application/DevLogPresentation/Tests/Root/RootFeatureTests.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ struct RootFeatureTests {
3939
await verifyDidLoginedFalse(adapter: adapter, trackAnalyticsEventUseCaseSpy: trackSpy)
4040
}
4141

42-
@Test("RootFeature didLogined(true)는 기존 Root 상태관리처럼 signIn 상태만 true로 갱신한다")
43-
func RootFeature_didLogined_true는_기존_Root_상태관리처럼_signIn_상태만_true로_갱신한다() async {
42+
@Test("RootFeature didLogined(true)는 기존 Root 상태관리처럼 signIn 상태를 true로 갱신하고 selectedMainTab을 home으로 되돌린다")
43+
func RootFeature_didLogined_true는_기존_Root_상태관리처럼_signIn_상태를_true로_갱신하고_selectedMainTab을_home으로_되돌린다() async {
4444
let trackSpy = RootTrackAnalyticsEventUseCaseSpy()
4545
let adapter = RootStoreTestAdapter(trackAnalyticsEventUseCase: trackSpy)
4646

@@ -97,4 +97,11 @@ struct RootFeatureTests {
9797

9898
await verifyTodoDetailSheetPresentation(adapter: adapter)
9999
}
100+
101+
@Test("RootFeature는 로그인된 경우에만 widget route로 selectedMainTab을 변경한다")
102+
func RootFeature는_로그인된_경우에만_widget_route로_selectedMainTab을_변경한다() async {
103+
let adapter = RootStoreTestAdapter()
104+
105+
await verifyWidgetRouteOpensWhenSignedIn(adapter: adapter)
106+
}
100107
}

0 commit comments

Comments
 (0)