Skip to content

Commit ade94fa

Browse files
committed
feat: AlertState 적용
1 parent 9d16c0d commit ade94fa

3 files changed

Lines changed: 63 additions & 73 deletions

File tree

Application/DevLogPresentation/Sources/Login/LoginFeature.swift

Lines changed: 25 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,12 @@ import Foundation
1313
struct LoginFeature {
1414
@ObservableState
1515
struct State: Equatable {
16+
@Presents var alert: AlertState<Never>?
1617
var isLoading = false
17-
var showAlert = false
18-
var alertType: AlertType?
19-
var alertTitle = ""
20-
var alertMessage = ""
2118
}
2219

23-
enum Action: BindableAction {
24-
case binding(BindingAction<State>)
20+
enum Action {
21+
case alert(PresentationAction<Never>)
2522
case tapSignInButton(AuthProvider)
2623
case signInFailed(AlertType)
2724
case signInCancelled
@@ -35,15 +32,10 @@ struct LoginFeature {
3532
@Dependency(SignInUseCaseDependency.self) var signInUseCase
3633

3734
var body: some ReducerOf<Self> {
38-
BindingReducer()
3935
Reduce { state, action in
4036
switch action {
41-
case .binding(\.showAlert):
42-
if !state.showAlert {
43-
setAlert(&state, isPresented: false, alertType: nil)
44-
}
45-
case .binding: // 다른 binding 액션들은 이쪽으로 처리됨
46-
break // 작성 필수
37+
case .alert:
38+
break
4739
case .tapSignInButton(let provider):
4840
state.isLoading = true
4941
return .run { [signInUseCase] send in
@@ -61,10 +53,11 @@ struct LoginFeature {
6153
state.isLoading = false
6254
case .signInFailed(let alertType):
6355
state.isLoading = false
64-
setAlert(&state, isPresented: true, alertType: alertType)
56+
state.alert = alertState(for: alertType)
6557
}
6658
return .none
6759
}
60+
.ifLet(\.$alert, action: \.alert)
6861
}
6962
}
7063

@@ -98,24 +91,28 @@ extension DependencyValues {
9891
}
9992

10093
private extension LoginFeature {
101-
func setAlert(
102-
_ state: inout State,
103-
isPresented: Bool,
104-
alertType: AlertType?
105-
) {
94+
func alertState(for alertType: AlertType) -> AlertState<Never> {
95+
let title: String
96+
let message: String
97+
10698
switch alertType {
10799
case .emailUnavailable:
108-
state.alertTitle = String(localized: "login_alert_email_unavailable_title")
109-
state.alertMessage = String(localized: "login_alert_email_unavailable_message")
100+
title = String(localized: "login_alert_email_unavailable_title")
101+
message = String(localized: "login_alert_email_unavailable_message")
110102
case .error:
111-
state.alertTitle = String(localized: "common_error_title")
112-
state.alertMessage = String(localized: "common_error_message")
113-
case .none:
114-
state.alertTitle = ""
115-
state.alertMessage = ""
103+
title = String(localized: "common_error_title")
104+
message = String(localized: "common_error_message")
105+
}
106+
107+
return AlertState {
108+
TextState(title)
109+
} actions: {
110+
ButtonState(role: .cancel) {
111+
TextState(String(localized: "common_close"))
112+
}
113+
} message: {
114+
TextState(message)
116115
}
117-
state.showAlert = isPresented
118-
state.alertType = alertType
119116
}
120117

121118
func alertType(for error: Error) -> AlertType {

Application/DevLogPresentation/Sources/Login/LoginView.swift

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,6 @@ struct LoginView: View {
5757
LoadingView()
5858
}
5959
}
60-
.alert(store.alertTitle, isPresented: $store.showAlert) {
61-
Button(String(localized: "common_close"), role: .cancel) { }
62-
} message: {
63-
Text(store.alertMessage)
64-
}
60+
.alert($store.scope(state: \.alert, action: \.alert))
6561
}
6662
}

Application/DevLogPresentation/Tests/Login/LoginFeatureTests.swift

Lines changed: 37 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ struct LoginFeatureTests {
6868
spy.resume()
6969

7070
await waitUntil {
71-
!driver.isLoading && driver.showAlert
71+
!driver.isLoading && driver.hasAlert
7272
}
7373

7474
#expect(!driver.isLoading)
@@ -83,12 +83,13 @@ struct LoginFeatureTests {
8383
driver.tapSignInButton(.google)
8484

8585
await waitUntil {
86-
driver.showAlert
86+
driver.hasAlert
8787
}
8888

89-
#expect(driver.alertKind == .emailUnavailable)
90-
#expect(driver.alertTitle == String(localized: "login_alert_email_unavailable_title"))
91-
#expect(driver.alertMessage == String(localized: "login_alert_email_unavailable_message"))
89+
#expect(driver.alert == expectedAlert(
90+
title: String(localized: "login_alert_email_unavailable_title"),
91+
message: String(localized: "login_alert_email_unavailable_message")
92+
))
9293
}
9394

9495
@Test("일반 로그인 에러가 발생하면 공통 에러 알림을 표시한다")
@@ -100,12 +101,13 @@ struct LoginFeatureTests {
100101
driver.tapSignInButton(.apple)
101102

102103
await waitUntil {
103-
driver.showAlert
104+
driver.hasAlert
104105
}
105106

106-
#expect(driver.alertKind == .error)
107-
#expect(driver.alertTitle == String(localized: "common_error_title"))
108-
#expect(driver.alertMessage == String(localized: "common_error_message"))
107+
#expect(driver.alert == expectedAlert(
108+
title: String(localized: "common_error_title"),
109+
message: String(localized: "common_error_message")
110+
))
109111
}
110112

111113
@Test("소셜 로그인 취소 에러가 발생하면 알림을 표시하지 않는다")
@@ -121,9 +123,7 @@ struct LoginFeatureTests {
121123
}
122124

123125
#expect(!driver.showAlert)
124-
#expect(driver.alertKind == nil)
125-
#expect(driver.alertTitle.isEmpty)
126-
#expect(driver.alertMessage.isEmpty)
126+
#expect(driver.alert == nil)
127127
}
128128

129129
@Test("알림을 닫으면 알림 상태와 문구가 초기화된다")
@@ -135,23 +135,16 @@ struct LoginFeatureTests {
135135
driver.tapSignInButton(.google)
136136

137137
await waitUntil {
138-
driver.showAlert
138+
driver.hasAlert
139139
}
140140

141-
driver.setAlert(false)
141+
driver.dismissAlert()
142142

143143
#expect(!driver.showAlert)
144-
#expect(driver.alertKind == nil)
145-
#expect(driver.alertTitle.isEmpty)
146-
#expect(driver.alertMessage.isEmpty)
144+
#expect(driver.alert == nil)
147145
}
148146
}
149147

150-
private enum LoginAlertKind: Equatable {
151-
case emailUnavailable
152-
case error
153-
}
154-
155148
@MainActor
156149
private struct LoginTestDriver {
157150
private let feature: StoreOf<LoginFeature>
@@ -161,26 +154,15 @@ private struct LoginTestDriver {
161154
}
162155

163156
var showAlert: Bool {
164-
feature.state.showAlert
165-
}
166-
167-
var alertKind: LoginAlertKind? {
168-
switch feature.state.alertType {
169-
case .emailUnavailable:
170-
return .emailUnavailable
171-
case .error:
172-
return .error
173-
case .none:
174-
return nil
175-
}
157+
hasAlert
176158
}
177159

178-
var alertTitle: String {
179-
feature.state.alertTitle
160+
var hasAlert: Bool {
161+
alert != nil
180162
}
181163

182-
var alertMessage: String {
183-
feature.state.alertMessage
164+
var alert: AlertState<Never>? {
165+
feature.state.alert
184166
}
185167

186168
init(useCase: SignInUseCase) {
@@ -197,7 +179,22 @@ private struct LoginTestDriver {
197179
feature.send(.tapSignInButton(provider))
198180
}
199181

200-
func setAlert(_ isPresented: Bool) {
201-
feature.send(.binding(.set(\.showAlert, isPresented)))
182+
func dismissAlert() {
183+
feature.send(.alert(.dismiss))
184+
}
185+
}
186+
187+
private func expectedAlert(
188+
title: String,
189+
message: String
190+
) -> AlertState<Never> {
191+
AlertState {
192+
TextState(title)
193+
} actions: {
194+
ButtonState(role: .cancel) {
195+
TextState(String(localized: "common_close"))
196+
}
197+
} message: {
198+
TextState(message)
202199
}
203200
}

0 commit comments

Comments
 (0)