Fix dish deletion list flicker with stable identity/order and add regression test
This commit is contained in:
@@ -23,3 +23,14 @@ final class Dish {
|
|||||||
self.createdAt = createdAt
|
self.createdAt = createdAt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Dish {
|
||||||
|
static func stableSortedForDisplay(_ dishes: [Dish]) -> [Dish] {
|
||||||
|
dishes.sorted { lhs, rhs in
|
||||||
|
if lhs.createdAt != rhs.createdAt {
|
||||||
|
return lhs.createdAt > rhs.createdAt
|
||||||
|
}
|
||||||
|
return lhs.id.uuidString < rhs.id.uuidString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ struct DishListView: View {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
List {
|
List {
|
||||||
ForEach(dishes) { dish in
|
ForEach(dishes, id: \.id) { dish in
|
||||||
let dishTags = tags.filter { dish.tagIds.contains($0.id) }
|
let dishTags = tags.filter { dish.tagIds.contains($0.id) }
|
||||||
Button {
|
Button {
|
||||||
editingDish = dish
|
editingDish = dish
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ struct DishDrawerView: View {
|
|||||||
|
|
||||||
private var filteredDishes: [Dish] {
|
private var filteredDishes: [Dish] {
|
||||||
let term = searchText.trimmingCharacters(in: .whitespacesAndNewlines)
|
let term = searchText.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
return dishes.filter { dish in
|
return Dish.stableSortedForDisplay(dishes).filter { dish in
|
||||||
let matchesSearch = term.isEmpty || dish.name.localizedCaseInsensitiveContains(term)
|
let matchesSearch = term.isEmpty || dish.name.localizedCaseInsensitiveContains(term)
|
||||||
let matchesUsedFilter = !hideUsedThisWeek || !usedDishIds.contains(dish.id)
|
let matchesUsedFilter = !hideUsedThisWeek || !usedDishIds.contains(dish.id)
|
||||||
return matchesSearch && matchesUsedFilter
|
return matchesSearch && matchesUsedFilter
|
||||||
@@ -95,7 +95,7 @@ struct DishDrawerView: View {
|
|||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
|
|
||||||
LazyVStack(spacing: 8) {
|
LazyVStack(spacing: 8) {
|
||||||
ForEach(filteredDishes) { dish in
|
ForEach(filteredDishes, id: \.id) { dish in
|
||||||
DishCardView(
|
DishCardView(
|
||||||
dish: dish,
|
dish: dish,
|
||||||
tags: tags,
|
tags: tags,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ struct HomeView: View {
|
|||||||
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||||
@Environment(\.modelContext) private var context
|
@Environment(\.modelContext) private var context
|
||||||
@Environment(\.openURL) private var openURL
|
@Environment(\.openURL) private var openURL
|
||||||
@Query private var dishes: [Dish]
|
@Query(sort: \Dish.createdAt, order: .reverse) private var dishes: [Dish]
|
||||||
@Query private var tags: [Tag]
|
@Query private var tags: [Tag]
|
||||||
@Query(sort: \WeekPlan.weekStartDate, order: .forward) private var weekPlans: [WeekPlan]
|
@Query(sort: \WeekPlan.weekStartDate, order: .forward) private var weekPlans: [WeekPlan]
|
||||||
@Query private var allSettings: [AppSettings]
|
@Query private var allSettings: [AppSettings]
|
||||||
@@ -452,7 +452,7 @@ struct HomeView: View {
|
|||||||
.padding(24)
|
.padding(24)
|
||||||
} else {
|
} else {
|
||||||
List {
|
List {
|
||||||
ForEach(filteredDishes) { dish in
|
ForEach(filteredDishes, id: \.id) { dish in
|
||||||
Button {
|
Button {
|
||||||
onPickDish(dish)
|
onPickDish(dish)
|
||||||
dismiss()
|
dismiss()
|
||||||
|
|||||||
@@ -59,5 +59,17 @@ final class AutocompleteEngineTests: XCTestCase {
|
|||||||
XCTAssertEqual(result.unfilledCount, 1)
|
XCTAssertEqual(result.unfilledCount, 1)
|
||||||
XCTAssertNil(tuesdayDinner.dishId)
|
XCTAssertNil(tuesdayDinner.dishId)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
func testDishStableSortRemainsConsistentAfterDeletion() {
|
||||||
|
let base = Date()
|
||||||
|
let oldest = Dish(id: UUID(uuidString: "00000000-0000-0000-0000-000000000001")!, name: "Old", createdAt: base.addingTimeInterval(-60))
|
||||||
|
let newest = Dish(id: UUID(uuidString: "00000000-0000-0000-0000-000000000002")!, name: "New", createdAt: base)
|
||||||
|
let middle = Dish(id: UUID(uuidString: "00000000-0000-0000-0000-000000000003")!, name: "Middle", createdAt: base.addingTimeInterval(-30))
|
||||||
|
|
||||||
|
let initial = Dish.stableSortedForDisplay([oldest, newest, middle]).map(\.id)
|
||||||
|
XCTAssertEqual(initial, [newest.id, middle.id, oldest.id])
|
||||||
|
|
||||||
|
let afterDeletion = Dish.stableSortedForDisplay([newest, oldest]).map(\.id)
|
||||||
|
XCTAssertEqual(afterDeletion, [newest.id, oldest.id])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user