Skip to content

Commit eafe76f

Browse files
committed
feat: 낙관적 업데이트 이후 실패 시 롤백 로직 구현
1 parent d392f03 commit eafe76f

3 files changed

Lines changed: 46 additions & 4 deletions

File tree

Application/DevLogPresentation/Sources/PushNotification/PushNotificationListFeature.swift

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ struct PushNotificationListFeature {
7878
case setHasMore(Bool)
7979
case syncNotifications([PushNotificationItem], nextCursor: PushNotificationCursor?, hasMore: Bool)
8080
case setNotificationHidden(String, Bool)
81+
case setNotificationRead(String, Bool)
8182
case observeNotifications(PushNotificationQuery, Int)
8283
}
8384
}
@@ -156,8 +157,9 @@ private extension PushNotificationListFeature {
156157
guard let index = state.notifications.firstIndex(where: { $0.id == item.id }) else {
157158
return .none
158159
}
159-
state.notifications[index].isRead.toggle()
160-
return toggleReadEffect(item.todoId)
160+
let isRead = !state.notifications[index].isRead
161+
state.notifications[index].isRead = isRead
162+
return toggleReadEffect(notificationId: item.id, todoId: item.todoId, rollbackRead: !isRead)
161163
case .undoDelete:
162164
guard let undoNotificationId = state.undoNotificationId else { return .none }
163165
Self.setNotificationHidden(&state, notificationId: undoNotificationId, isHidden: false)
@@ -190,6 +192,10 @@ private extension PushNotificationListFeature {
190192
state.hasMore = hasMore
191193
case .store(.setNotificationHidden(let notificationId, let isHidden)):
192194
Self.setNotificationHidden(&state, notificationId: notificationId, isHidden: isHidden)
195+
case .store(.setNotificationRead(let notificationId, let isRead)):
196+
if let index = state.notifications.firstIndex(where: { $0.id == notificationId }) {
197+
state.notifications[index].isRead = isRead
198+
}
193199
case .toggleSortOption:
194200
state.query.sortOrder = state.query.sortOrder == .latest ? .oldest : .latest
195201
state.nextCursor = nil
@@ -216,7 +222,7 @@ private extension PushNotificationListFeature {
216222
state.selectedTodoId = TodoIdItem(id: item.todoId)
217223
guard !item.isRead else { return .none }
218224
state.notifications[index].isRead = true
219-
return toggleReadEffect(item.todoId)
225+
return toggleReadEffect(notificationId: item.id, todoId: item.todoId, rollbackRead: false)
220226
case .syncSheetPresentation(let isCompactLayout):
221227
if let todoId = state.selectedTodoId?.id, isCompactLayout {
222228
state.sheet = .init(todoId: todoId)
@@ -320,13 +326,18 @@ private extension PushNotificationListFeature {
320326
}
321327
}
322328

323-
func toggleReadEffect(_ todoId: String) -> Effect<Action> {
329+
func toggleReadEffect(
330+
notificationId: String,
331+
todoId: String,
332+
rollbackRead: Bool
333+
) -> Effect<Action> {
324334
.run { [togglePushNotificationReadUseCase] send in
325335
await send(.loading(.begin(target: .default, mode: .delayed)))
326336
do {
327337
try await togglePushNotificationReadUseCase.execute(todoId)
328338
await send(.loading(.end(target: .default, mode: .delayed)))
329339
} catch {
340+
await send(.store(.setNotificationRead(notificationId, rollbackRead)))
330341
await send(.loading(.end(target: .default, mode: .delayed)))
331342
await send(.store(.setAlert))
332343
}

Application/DevLogPresentation/Tests/PushNotification/PushNotificationListFeatureTests.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,33 @@ struct PushNotificationListFeatureTests {
100100
)
101101
}
102102

103+
@Test("toggleRead 실패 시 읽음 상태를 원래 값으로 롤백한다")
104+
func toggleRead_실패_시_읽음_상태를_원래_값으로_롤백한다() async throws {
105+
struct DummyError: Error {}
106+
107+
let fetchSpy = PushNotificationListFetchUseCaseSpy(pages: [
108+
PushNotificationPage(
109+
items: [
110+
makePushNotification(id: "notification-1", number: 1, isRead: true)
111+
],
112+
nextCursor: nil
113+
)
114+
])
115+
let toggleSpy = TogglePushNotificationReadUseCaseSpy()
116+
toggleSpy.error = DummyError()
117+
let adapter = PushNotificationListStoreTestAdapter(
118+
fetchUseCase: fetchSpy,
119+
toggleReadUseCase: toggleSpy
120+
)
121+
122+
await adapter.fetchNotifications()
123+
let item = try #require(adapter.notifications.first)
124+
125+
await adapter.toggleRead(item)
126+
127+
#expect(adapter.notifications.first?.isRead == true)
128+
}
129+
103130
@Test("syncSheetPresentation은 layout에 따라 시트 상태를 동기화한다")
104131
func syncSheetPresentation은_layout에_따라_시트_상태를_동기화한다() async throws {
105132
let fetchSpy = PushNotificationListFetchUseCaseSpy(pages: [

Application/DevLogPresentation/Tests/Support/TestSupport.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,13 @@ final class UndoDeletePushNotificationUseCaseSpy: UndoDeletePushNotificationUseC
107107

108108
final class TogglePushNotificationReadUseCaseSpy: TogglePushNotificationReadUseCase {
109109
private(set) var calledTodoIds: [String] = []
110+
var error: Error?
110111

111112
func execute(_ todoId: String) async throws {
112113
calledTodoIds.append(todoId)
114+
if let error {
115+
throw error
116+
}
113117
}
114118
}
115119

0 commit comments

Comments
 (0)