diff --git a/CalenTask/Services/TrashService.swift b/CalenTask/Services/TrashService.swift index a6bdda1..9507c4e 100644 --- a/CalenTask/Services/TrashService.swift +++ b/CalenTask/Services/TrashService.swift @@ -27,4 +27,35 @@ enum TrashService { context.saveOrLog() return purgeable.count } + + /// Attività nel cestino (radici dei sottoalberi eliminati), le più recenti prima. + @MainActor + static func trashedRoots(in context: ModelContext) -> [TodoTask] { + let predicate = #Predicate { $0.deletedAt != nil } + return context.fetchOrLog(FetchDescriptor(predicate: predicate)) + .filter { $0.parentTask?.deletedAt == nil } + .sorted { ($0.deletedAt ?? .distantPast) > ($1.deletedAt ?? .distantPast) } + } + + /// Ripristina una task e tutto il suo sottoalbero eliminato. + @MainActor + static func restore(_ task: TodoTask) { + for child in task.subtasks where child.deletedAt != nil { + restore(child) + } + guard task.deletedAt != nil else { return } + task.deletedAt = nil + task.touch() + } + + /// Elimina definitivamente tutto il cestino. Ritorna quante task ha rimosso. + @MainActor + @discardableResult + static func emptyTrash(in context: ModelContext) -> Int { + let predicate = #Predicate { $0.deletedAt != nil } + let all = context.fetchOrLog(FetchDescriptor(predicate: predicate)) + all.forEach { context.delete($0) } + context.saveOrLog() + return all.count + } } diff --git a/CalenTaskTests/TrashServiceTests.swift b/CalenTaskTests/TrashServiceTests.swift index 3f3a536..a351f97 100644 --- a/CalenTaskTests/TrashServiceTests.swift +++ b/CalenTaskTests/TrashServiceTests.swift @@ -35,4 +35,30 @@ struct TrashServiceTests { let titles = try context.fetch(FetchDescriptor()).map(\.title).sorted() #expect(titles == ["Recente", "Viva"]) } + + @Test func restoreBringsBackSubtree() throws { + let context = try makeContext() + let author = UUID(), ws = UUID() + let parent = TodoTask(workspaceID: ws, title: "Padre", createdByID: author) + let child = TodoTask(workspaceID: ws, title: "Figlio", createdByID: author) + context.insert(parent); context.insert(child) + child.parentTask = parent + parent.softDeleteSubtree() + #expect(TrashService.trashedRoots(in: context).map(\.title) == ["Padre"]) + + TrashService.restore(parent) + #expect(parent.deletedAt == nil && child.deletedAt == nil) + #expect(TrashService.trashedRoots(in: context).isEmpty) + } + + @Test func emptyTrashRemovesOnlyDeleted() throws { + let context = try makeContext() + let author = UUID(), ws = UUID() + let gone = TodoTask(workspaceID: ws, title: "Via", createdByID: author) + let live = TodoTask(workspaceID: ws, title: "Viva", createdByID: author) + context.insert(gone); context.insert(live) + gone.deletedAt = .now + #expect(TrashService.emptyTrash(in: context) == 1) + #expect(try context.fetch(FetchDescriptor()).map(\.title) == ["Viva"]) + } }