diff --git a/Examples/CaseStudies/Internal/CaseStudy.swift b/Examples/CaseStudies/Internal/CaseStudy.swift index 3759a7ca6..f913c2071 100644 --- a/Examples/CaseStudies/Internal/CaseStudy.swift +++ b/Examples/CaseStudies/Internal/CaseStudy.swift @@ -1,7 +1,6 @@ import SwiftUI import UIKitNavigation -@MainActor protocol CaseStudy { var readMe: String { get } var caseStudyTitle: String { get } @@ -17,7 +16,6 @@ extension CaseStudy { } @resultBuilder -@MainActor enum CaseStudyViewBuilder { @ViewBuilder static func buildBlock() -> some View {} diff --git a/Examples/CaseStudies/ObservableModelDemo.swift b/Examples/CaseStudies/ObservableModelDemo.swift index 1735fbc88..108e562a2 100644 --- a/Examples/CaseStudies/ObservableModelDemo.swift +++ b/Examples/CaseStudies/ObservableModelDemo.swift @@ -43,7 +43,6 @@ struct ObservableModelDemo: SwiftUICaseStudy { } @Observable -@MainActor private class Model { @ObservationIgnored @FetchAll(Fact.order { $0.id.desc() }, animation: .default) diff --git a/Examples/Examples.xcodeproj/project.pbxproj b/Examples/Examples.xcodeproj/project.pbxproj index 2c6f2d6c8..48dbaef12 100644 --- a/Examples/Examples.xcodeproj/project.pbxproj +++ b/Examples/Examples.xcodeproj/project.pbxproj @@ -3,10 +3,12 @@ archiveVersion = 1; classes = { }; - objectVersion = 77; + objectVersion = 100; objects = { /* Begin PBXBuildFile section */ + CA0923FC3037ED040053D2D7 /* DebugSnapshots in Frameworks */ = {isa = PBXBuildFile; productRef = CA0923FB3037ED040053D2D7 /* DebugSnapshots */; }; + CA0923FE3037ED040053D2D7 /* DebugSnapshots in Frameworks */ = {isa = PBXBuildFile; productRef = CA0923FD3037ED040053D2D7 /* DebugSnapshots */; }; CA14DBC92DA884C400E36852 /* CasePaths in Frameworks */ = {isa = PBXBuildFile; productRef = CA14DBC82DA884C400E36852 /* CasePaths */; }; CA2908C92D4AF70E003F165F /* UIKitNavigation in Frameworks */ = {isa = PBXBuildFile; productRef = CA2908C82D4AF70E003F165F /* UIKitNavigation */; }; CA2BDE2A2E71C469000974D3 /* SQLiteData in Frameworks */ = {isa = PBXBuildFile; productRef = CA2BDE292E71C469000974D3 /* SQLiteData */; }; @@ -197,6 +199,7 @@ CAF836D52D4735AB0047AEB5 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; files = ( + CA0923FC3037ED040053D2D7 /* DebugSnapshots in Frameworks */, DCB8AD5030363F2600ACF09E /* SQLiteData in Frameworks */, CA14DBC92DA884C400E36852 /* CasePaths in Frameworks */, CA5E46912DEBB8570069E0F8 /* SwiftUINavigation in Frameworks */, @@ -216,6 +219,7 @@ DC5FA7482D4C63D60082743E /* DependenciesMacros in Frameworks */, DCB8AD5230363F2A00ACF09E /* SQLiteData in Frameworks */, DCBE8A142D4842BF0071F499 /* CasePaths in Frameworks */, + CA0923FE3037ED040053D2D7 /* DebugSnapshots in Frameworks */, ); }; /* End PBXFrameworksBuildPhase section */ @@ -397,6 +401,7 @@ CA14DBC82DA884C400E36852 /* CasePaths */, CA5E46902DEBB8570069E0F8 /* SwiftUINavigation */, DCB8AD4F30363F2600ACF09E /* SQLiteData */, + CA0923FB3037ED040053D2D7 /* DebugSnapshots */, ); productName = Reminders; productReference = CAF836D82D4735AB0047AEB5 /* Reminders.app */; @@ -443,6 +448,7 @@ DCF267382D48437300B680BE /* SwiftUINavigation */, DC5FA7472D4C63D60082743E /* DependenciesMacros */, DCB8AD5130363F2A00ACF09E /* SQLiteData */, + CA0923FD3037ED040053D2D7 /* DebugSnapshots */, ); productName = SyncUps; productReference = DCBE89CC2D483FB90071F499 /* SyncUps.app */; @@ -502,6 +508,7 @@ DC5FA7462D4C63D60082743E /* XCRemoteSwiftPackageReference "swift-dependencies" */, CA5E47052DECEF0F0069E0F8 /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */, DCB8AD4830363D3800ACF09E /* XCLocalSwiftPackageReference "../../sqlite-data" */, + CA0923FA3037ED040053D2D7 /* XCRemoteSwiftPackageReference "swift-debug-snapshots" */, ); preferredProjectObjectVersion = 77; productRefGroup = CAF836992D4735620047AEB5 /* Products */; @@ -1243,6 +1250,16 @@ /* End XCLocalSwiftPackageReference section */ /* Begin XCRemoteSwiftPackageReference section */ + CA0923FA3037ED040053D2D7 /* XCRemoteSwiftPackageReference "swift-debug-snapshots" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/pointfreeco/swift-debug-snapshots.git"; + requirement = { + branch = "even-better-inference"; + kind = branch; + }; + traits = ( + ); + }; CA5E47052DECEF0F0069E0F8 /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/pointfreeco/swift-snapshot-testing.git"; @@ -1278,6 +1295,16 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + CA0923FB3037ED040053D2D7 /* DebugSnapshots */ = { + isa = XCSwiftPackageProductDependency; + package = CA0923FA3037ED040053D2D7 /* XCRemoteSwiftPackageReference "swift-debug-snapshots" */; + productName = DebugSnapshots; + }; + CA0923FD3037ED040053D2D7 /* DebugSnapshots */ = { + isa = XCSwiftPackageProductDependency; + package = CA0923FA3037ED040053D2D7 /* XCRemoteSwiftPackageReference "swift-debug-snapshots" */; + productName = DebugSnapshots; + }; CA14DBC82DA884C400E36852 /* CasePaths */ = { isa = XCSwiftPackageProductDependency; package = DCBE8A122D4842BF0071F499 /* XCRemoteSwiftPackageReference "swift-case-paths" */; diff --git a/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 8bdb0fcc3..2dda67974 100644 --- a/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "c56e7b70de4fe8bcc798354797a8405c0eeb2e9bc68bc0f202c14a8b11a0a97f", + "originHash" : "ad41d862cbf4ae7f82f17f353075ecb723d246b512a0877f555e27ec8185459a", "pins" : [ { "identity" : "combine-schedulers", @@ -64,6 +64,15 @@ "version" : "1.6.0" } }, + { + "identity" : "swift-debug-snapshots", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-debug-snapshots.git", + "state" : { + "branch" : "even-better-inference", + "revision" : "a359f879671f0a733b087cd5ad4a5dd2bb601f10" + } + }, { "identity" : "swift-dependencies", "kind" : "remoteSourceControl", @@ -141,8 +150,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", "state" : { - "revision" : "cb281f343fd953280336dcbd3822cdf47c182f5b", - "version" : "1.10.0" + "revision" : "8f6abcf4c8950e2679d5b2fee4ca284fd7c34886", + "version" : "1.11.0" } } ], diff --git a/Examples/Examples.xcodeproj/xcshareddata/xcschemes/Integration.xcscheme b/Examples/Examples.xcodeproj/xcshareddata/xcschemes/Integration.xcscheme index 45608be57..02cde499a 100644 --- a/Examples/Examples.xcodeproj/xcshareddata/xcschemes/Integration.xcscheme +++ b/Examples/Examples.xcodeproj/xcshareddata/xcschemes/Integration.xcscheme @@ -17,6 +17,7 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "DC1584A530363595009DD95C" BuildableName = "Integration.app" + BlueprintName = "Integration" ReferencedContainer = "container:Examples.xcodeproj"> @@ -46,6 +47,7 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "DC1584A530363595009DD95C" BuildableName = "Integration.app" + BlueprintName = "Integration" ReferencedContainer = "container:Examples.xcodeproj"> @@ -62,6 +64,7 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "DC1584A530363595009DD95C" BuildableName = "Integration.app" + BlueprintName = "Integration" ReferencedContainer = "container:Examples.xcodeproj"> diff --git a/Examples/Reminders/Helpers.swift b/Examples/Reminders/Helpers.swift index aa889bf87..6e3774369 100644 --- a/Examples/Reminders/Helpers.swift +++ b/Examples/Reminders/Helpers.swift @@ -23,10 +23,10 @@ extension Color { public var hexValue: Int64? { guard let components = UIColor(queryOutput).cgColor.components else { return nil } - let r = Int64(components[0] * 0xFF) << 24 - let g = Int64(components[1] * 0xFF) << 16 - let b = Int64(components[2] * 0xFF) << 8 - let a = Int64((components.indices.contains(3) ? components[3] : 1) * 0xFF) + let r = Int64((components[0] * 0xFF).rounded()) << 24 + let g = Int64((components[1] * 0xFF).rounded()) << 16 + let b = Int64((components[2] * 0xFF).rounded()) << 8 + let a = Int64(((components.indices.contains(3) ? components[3] : 1) * 0xFF).rounded()) return r | g | b | a } diff --git a/Examples/Reminders/RemindersDetail.swift b/Examples/Reminders/RemindersDetail.swift index a9cd88407..1e1d978af 100644 --- a/Examples/Reminders/RemindersDetail.swift +++ b/Examples/Reminders/RemindersDetail.swift @@ -1,11 +1,12 @@ import CasePaths import CloudKit +import DebugSnapshots import SQLiteData import Sharing import SwiftUI import SwiftUINavigation -@MainActor +@DebugSnapshot @Observable class RemindersDetailModel: HashableObject { @ObservationIgnored @FetchAll var reminderRows: [Row] @@ -165,11 +166,11 @@ class RemindersDetailModel: HashableObject { @Selection struct Row: Identifiable { var id: Reminder.ID { reminder.id } - let reminder: Reminder - let remindersList: RemindersList - let isPastDue: Bool - let notes: String - let tags: String + var reminder: Reminder + var remindersList: RemindersList + var isPastDue: Bool + var notes: String + var tags: String } } diff --git a/Examples/Reminders/RemindersLists.swift b/Examples/Reminders/RemindersLists.swift index 11080ec35..68dcd641d 100644 --- a/Examples/Reminders/RemindersLists.swift +++ b/Examples/Reminders/RemindersLists.swift @@ -1,10 +1,11 @@ import CloudKit +import DebugSnapshots import SQLiteData import SwiftUI import SwiftUINavigation import TipKit -@MainActor +@DebugSnapshot @Observable class RemindersListsModel { @ObservationIgnored @@ -49,8 +50,8 @@ class RemindersListsModel { ) var stats = Stats() - var destination: Destination? - var searchRemindersModel = SearchRemindersModel() + @DebugSnapshotConvertible var destination: Destination? + @DebugSnapshotConvertible var searchRemindersModel = SearchRemindersModel() var seedDatabaseTip: SeedDatabaseTip? @ObservationIgnored @@ -131,8 +132,8 @@ class RemindersListsModel { let (first, rest) = (ids.first!, ids.dropFirst()) $0.position = rest - .reduce(Case($0.id).when(first.element, then: first.offset)) { cases, id in - cases.when(id.element, then: id.offset) + .reduce(Case($0.id).when(first.element, then: first.offset + 1)) { cases, id in + cases.when(id.element, then: id.offset + 1) } .else($0.position) } @@ -150,7 +151,9 @@ class RemindersListsModel { #endif @CasePathable + @DebugSnapshot enum Destination { + @DebugSnapshotConvertible case detail(RemindersDetailModel) case reminderForm(Reminder.Draft, remindersList: RemindersList) case remindersListForm(RemindersList.Draft) diff --git a/Examples/Reminders/Schema.swift b/Examples/Reminders/Schema.swift index 54dc8f4fc..e63f667a9 100644 --- a/Examples/Reminders/Schema.swift +++ b/Examples/Reminders/Schema.swift @@ -14,12 +14,18 @@ nonisolated struct RemindersList: Hashable, Identifiable { var position = 0 var title = "" - static var defaultColor: Color { Color(red: 0x4a / 255, green: 0x99 / 255, blue: 0xef / 255) } + static var defaultColor: Color { .personal } static var defaultTitle: String { "Personal" } } extension RemindersList.Draft: Identifiable {} +nonisolated extension Color { + static let personal = Color(red: 0x4a / 255, green: 0x99 / 255, blue: 0xef / 255) + static let family = Color(red: 0xed / 255, green: 0x89 / 255, blue: 0x35 / 255) + static let business = Color(red: 0xb2 / 255, green: 0x5d / 255, blue: 0xd3 / 255) +} + @Table nonisolated struct RemindersListAsset: Hashable, Identifiable { @Column(primaryKey: true) @@ -424,17 +430,17 @@ nonisolated private let logger = Logger(subsystem: "Reminders", category: "Datab try db.seed { RemindersList( id: remindersListIDs[0], - color: Color(red: 0x4a / 255, green: 0x99 / 255, blue: 0xef / 255), + color: .personal, title: "Personal" ) RemindersList( id: remindersListIDs[1], - color: Color(red: 0xed / 255, green: 0x89 / 255, blue: 0x35 / 255), + color: .family, title: "Family" ) RemindersList( id: remindersListIDs[2], - color: Color(red: 0xb2 / 255, green: 0x5d / 255, blue: 0xd3 / 255), + color: .business, title: "Business" ) Reminder( diff --git a/Examples/Reminders/SearchReminders.swift b/Examples/Reminders/SearchReminders.swift index 9c33dfcae..586019c6f 100644 --- a/Examples/Reminders/SearchReminders.swift +++ b/Examples/Reminders/SearchReminders.swift @@ -1,8 +1,9 @@ +import DebugSnapshots import IssueReporting import SQLiteData import SwiftUI -@MainActor +@DebugSnapshot @Observable class SearchRemindersModel { var showCompletedInSearchResults = false { @@ -40,6 +41,7 @@ class SearchRemindersModel { !searchText.isEmpty || !searchTokens.isEmpty } + @DebugSnapshotIgnored var searchTask: Task? { willSet { searchTask?.cancel() @@ -115,12 +117,12 @@ class SearchRemindersModel { @Selection struct Row: Identifiable { var id: Reminder.ID { reminder.id } - let isPastDue: Bool - let notes: String - let reminder: Reminders.Reminder - let remindersList: RemindersList - let tags: String - let title: String + var isPastDue: Bool + var notes: String + var reminder: Reminders.Reminder + var remindersList: RemindersList + var tags: String + var title: String } struct SearchRequest: FetchKeyRequest { diff --git a/Examples/RemindersTests/Internal.swift b/Examples/RemindersTests/Internal.swift index 6d763a8f9..93030018e 100644 --- a/Examples/RemindersTests/Internal.swift +++ b/Examples/RemindersTests/Internal.swift @@ -11,9 +11,9 @@ import Testing .dependency(\.date.now, Date(timeIntervalSince1970: 1_234_567_890)), .dependency(\.uuid, .incrementing), .dependencies { - try $0.bootstrapDatabase() + try await $0.bootstrapDatabase() try await $0.defaultSyncEngine.start() - try $0.defaultDatabase.seedSampleData() + try await $0.defaultDatabase.seedTestData() try await $0.defaultSyncEngine.sendChanges() }, .snapshots(record: .missing) diff --git a/Examples/RemindersTests/RemindersDetailsTests.swift b/Examples/RemindersTests/RemindersDetailsTests.swift index 18ea8fb6c..cf96ea812 100644 --- a/Examples/RemindersTests/RemindersDetailsTests.swift +++ b/Examples/RemindersTests/RemindersDetailsTests.swift @@ -1,94 +1,70 @@ +import DebugSnapshots import Dependencies import DependenciesTestSupport +import Foundation import InlineSnapshotTesting +import SQLiteData import SnapshotTestingCustomDump import Testing @testable import Reminders extension BaseTestSuite { - @MainActor struct RemindersDetailsTests { @Dependency(\.defaultDatabase) var database @Test func basics() async throws { let remindersList = try await database.read { try RemindersList.fetchOne($0)! } let model = RemindersDetailModel(detailType: .remindersList(remindersList)) - try await model.$reminderRows.load() - assertInlineSnapshot(of: model.reminderRows, as: .customDump) { - #""" - [ - [0]: RemindersDetailModel.Row( + try await expect(model) { + try await model.$reminderRows.load() + } changes: { + $0.reminderRows = [ + RemindersDetailModel.Row( reminder: Reminder( - id: UUID(00000000-0000-0000-0000-000000000004), - dueDate: Date(2009-02-11T23:31:30.000Z), + id: UUID(4), + dueDate: Date(timeIntervalSince1970: 1_234_395_090), isFlagged: true, - notes: "", position: 2, - priority: nil, - remindersListID: UUID(00000000-0000-0000-0000-000000000000), - status: .incomplete, + remindersListID: UUID(0), title: "Haircut" ), - remindersList: RemindersList( - id: UUID(00000000-0000-0000-0000-000000000000), - color: 1218047999, - position: 1, - title: "Personal" - ), + remindersList: remindersList, isPastDue: true, notes: "", tags: "#someday #optional" ), - [1]: RemindersDetailModel.Row( + RemindersDetailModel.Row( reminder: Reminder( - id: UUID(00000000-0000-0000-0000-000000000005), - dueDate: Date(2009-02-13T23:31:30.000Z), - isFlagged: false, + id: UUID(5), + dueDate: Date(timeIntervalSince1970: 1_234_567_890), notes: "Ask about diet", position: 3, priority: .high, - remindersListID: UUID(00000000-0000-0000-0000-000000000000), - status: .incomplete, + remindersListID: UUID(0), title: "Doctor appointment" ), - remindersList: RemindersList( - id: UUID(00000000-0000-0000-0000-000000000000), - color: 1218047999, - position: 1, - title: "Personal" - ), + remindersList: remindersList, isPastDue: false, notes: "Ask about diet", tags: "#adulting" ), - [2]: RemindersDetailModel.Row( + RemindersDetailModel.Row( reminder: Reminder( - id: UUID(00000000-0000-0000-0000-000000000007), - dueDate: Date(2009-02-13T23:31:30.000Z), - isFlagged: false, - notes: "", + id: UUID(7), + dueDate: Date(timeIntervalSince1970: 1_234_567_890), position: 5, - priority: nil, - remindersListID: UUID(00000000-0000-0000-0000-000000000000), - status: .incomplete, + remindersListID: UUID(0), title: "Buy concert tickets" ), - remindersList: RemindersList( - id: UUID(00000000-0000-0000-0000-000000000000), - color: 1218047999, - position: 1, - title: "Personal" - ), + remindersList: remindersList, isPastDue: false, notes: "", tags: "#social #night" ), - [3]: RemindersDetailModel.Row( + RemindersDetailModel.Row( reminder: Reminder( - id: UUID(00000000-0000-0000-0000-000000000003), - dueDate: nil, - isFlagged: false, + id: UUID(3), notes: """ Milk Eggs @@ -97,23 +73,15 @@ extension BaseTestSuite { Spinach """, position: 1, - priority: nil, - remindersListID: UUID(00000000-0000-0000-0000-000000000000), - status: .incomplete, + remindersListID: UUID(0), title: "Groceries" ), - remindersList: RemindersList( - id: UUID(00000000-0000-0000-0000-000000000000), - color: 1218047999, - position: 1, - title: "Personal" - ), + remindersList: remindersList, isPastDue: false, notes: "Milk Eggs Apples Oatmeal Spinach", tags: "#someday #optional #adulting" - ) + ), ] - """# } } @@ -122,44 +90,32 @@ extension BaseTestSuite { let model = RemindersDetailModel(detailType: .remindersList(remindersList)) try await model.$reminderRows.load() - #expect(model.ordering == .dueDate) - assertInlineSnapshot(of: model.reminderRows.map(\.reminder.title), as: .customDump) { - """ - [ - [0]: "Haircut", - [1]: "Doctor appointment", - [2]: "Buy concert tickets", - [3]: "Groceries" - ] - """ + expect(model) { + $0.ordering = .dueDate + $0.reminderRows[0].reminder.title = "Haircut" + $0.reminderRows[1].reminder.title = "Doctor appointment" + $0.reminderRows[2].reminder.title = "Buy concert tickets" + $0.reminderRows[3].reminder.title = "Groceries" } await model.orderingButtonTapped(.priority) try await model.$reminderRows.load() - #expect(model.ordering == .priority) - assertInlineSnapshot(of: model.reminderRows.map(\.reminder.title), as: .customDump) { - """ - [ - [0]: "Doctor appointment", - [1]: "Haircut", - [2]: "Groceries", - [3]: "Buy concert tickets" - ] - """ + expect(model) { + $0.ordering = .priority + $0.reminderRows[0].reminder.title = "Doctor appointment" + $0.reminderRows[1].reminder.title = "Haircut" + $0.reminderRows[2].reminder.title = "Groceries" + $0.reminderRows[3].reminder.title = "Buy concert tickets" } await model.orderingButtonTapped(.title) try await model.$reminderRows.load() - #expect(model.ordering == .title) - assertInlineSnapshot(of: model.reminderRows.map(\.reminder.title), as: .customDump) { - """ - [ - [0]: "Buy concert tickets", - [1]: "Doctor appointment", - [2]: "Groceries", - [3]: "Haircut" - ] - """ + expect(model) { + $0.ordering = .title + $0.reminderRows[0].reminder.title = "Buy concert tickets" + $0.reminderRows[1].reminder.title = "Doctor appointment" + $0.reminderRows[2].reminder.title = "Groceries" + $0.reminderRows[3].reminder.title = "Haircut" } } @@ -168,45 +124,33 @@ extension BaseTestSuite { let model = RemindersDetailModel(detailType: .remindersList(remindersList)) try await model.$reminderRows.load() - #expect(model.showCompleted == false) - assertInlineSnapshot(of: model.reminderRows.map(\.reminder.title), as: .customDump) { - """ - [ - [0]: "Haircut", - [1]: "Doctor appointment", - [2]: "Buy concert tickets", - [3]: "Groceries" - ] - """ + expect(model) { + $0.showCompleted = false + $0.reminderRows[0].reminder.title = "Haircut" + $0.reminderRows[1].reminder.title = "Doctor appointment" + $0.reminderRows[2].reminder.title = "Buy concert tickets" + $0.reminderRows[3].reminder.title = "Groceries" } await model.showCompletedButtonTapped() try await model.$reminderRows.load() - #expect(model.showCompleted == true) - assertInlineSnapshot(of: model.reminderRows.map(\.reminder.title), as: .customDump) { - """ - [ - [0]: "Haircut", - [1]: "Doctor appointment", - [2]: "Buy concert tickets", - [3]: "Groceries", - [4]: "Take a walk" - ] - """ + expect(model) { + $0.showCompleted = true + $0.reminderRows[0].reminder.title = "Haircut" + $0.reminderRows[1].reminder.title = "Doctor appointment" + $0.reminderRows[2].reminder.title = "Buy concert tickets" + $0.reminderRows[3].reminder.title = "Groceries" + $0.reminderRows[4].reminder.title = "Take a walk" } await model.showCompletedButtonTapped() try await model.$reminderRows.load() - #expect(model.showCompleted == false) - assertInlineSnapshot(of: model.reminderRows.map(\.reminder.title), as: .customDump) { - """ - [ - [0]: "Haircut", - [1]: "Doctor appointment", - [2]: "Buy concert tickets", - [3]: "Groceries" - ] - """ + expect(model) { + $0.showCompleted = false + $0.reminderRows[0].reminder.title = "Haircut" + $0.reminderRows[1].reminder.title = "Doctor appointment" + $0.reminderRows[2].reminder.title = "Buy concert tickets" + $0.reminderRows[3].reminder.title = "Groceries" } } @@ -215,106 +159,78 @@ extension BaseTestSuite { let model = RemindersDetailModel(detailType: .remindersList(remindersList)) try await model.$reminderRows.load() - assertInlineSnapshot(of: model.reminderRows.map(\.reminder.title), as: .customDump) { - """ - [ - [0]: "Haircut", - [1]: "Doctor appointment", - [2]: "Buy concert tickets", - [3]: "Groceries" - ] - """ + expect(model) { + $0.reminderRows[0].reminder.title = "Haircut" + $0.reminderRows[1].reminder.title = "Doctor appointment" + $0.reminderRows[2].reminder.title = "Buy concert tickets" + $0.reminderRows[3].reminder.title = "Groceries" } await model.move(from: [2], to: 0) try await model.$reminderRows.load() - assertInlineSnapshot(of: model.reminderRows.map(\.reminder.title), as: .customDump) { - """ - [ - [0]: "Buy concert tickets", - [1]: "Haircut", - [2]: "Doctor appointment", - [3]: "Groceries" - ] - """ + expect(model) { + $0.ordering = .manual + $0.reminderRows[0].reminder.title = "Buy concert tickets" + $0.reminderRows[1].reminder.title = "Haircut" + $0.reminderRows[2].reminder.title = "Doctor appointment" + $0.reminderRows[3].reminder.title = "Groceries" } - #expect(model.ordering == .manual) } @Test func all() async throws { let model = RemindersDetailModel(detailType: .all) try await model.$reminderRows.load() - assertInlineSnapshot(of: model.reminderRows.map(\.reminder.title), as: .customDump) { - """ - [ - [0]: "Haircut", - [1]: "Doctor appointment", - [2]: "Buy concert tickets", - [3]: "Pick up kids from school", - [4]: "Call accountant", - [5]: "Prepare for WWDC", - [6]: "Take out trash", - [7]: "Groceries" - ] - """ + expect(model) { + $0.reminderRows[0].reminder.title = "Haircut" + $0.reminderRows[1].reminder.title = "Doctor appointment" + $0.reminderRows[2].reminder.title = "Buy concert tickets" + $0.reminderRows[3].reminder.title = "Pick up kids from school" + $0.reminderRows[4].reminder.title = "Call accountant" + $0.reminderRows[5].reminder.title = "Prepare for WWDC" + $0.reminderRows[6].reminder.title = "Take out trash" + $0.reminderRows[7].reminder.title = "Groceries" } } @Test func completed() async throws { let model = RemindersDetailModel(detailType: .completed) try await model.$reminderRows.load() - assertInlineSnapshot(of: model.reminderRows.map(\.reminder.title), as: .customDump) { - """ - [ - [0]: "Take a walk", - [1]: "Get laundry", - [2]: "Send weekly emails" - ] - """ + expect(model) { + $0.reminderRows[0].reminder.title = "Take a walk" + $0.reminderRows[1].reminder.title = "Get laundry" + $0.reminderRows[2].reminder.title = "Send weekly emails" } } @Test func flagged() async throws { let model = RemindersDetailModel(detailType: .flagged) try await model.$reminderRows.load() - assertInlineSnapshot(of: model.reminderRows.map(\.reminder.title), as: .customDump) { - """ - [ - [0]: "Haircut", - [1]: "Pick up kids from school" - ] - """ + expect(model) { + $0.reminderRows[0].reminder.title = "Haircut" + $0.reminderRows[1].reminder.title = "Pick up kids from school" } } @Test func scheduled() async throws { let model = RemindersDetailModel(detailType: .scheduled) try await model.$reminderRows.load() - assertInlineSnapshot(of: model.reminderRows.map(\.reminder.title), as: .customDump) { - """ - [ - [0]: "Haircut", - [1]: "Doctor appointment", - [2]: "Buy concert tickets", - [3]: "Pick up kids from school", - [4]: "Call accountant", - [5]: "Prepare for WWDC", - [6]: "Take out trash" - ] - """ + expect(model) { + $0.reminderRows[0].reminder.title = "Haircut" + $0.reminderRows[1].reminder.title = "Doctor appointment" + $0.reminderRows[2].reminder.title = "Buy concert tickets" + $0.reminderRows[3].reminder.title = "Pick up kids from school" + $0.reminderRows[4].reminder.title = "Call accountant" + $0.reminderRows[5].reminder.title = "Prepare for WWDC" + $0.reminderRows[6].reminder.title = "Take out trash" } } @Test func today() async throws { let model = RemindersDetailModel(detailType: .today) try await model.$reminderRows.load() - assertInlineSnapshot(of: model.reminderRows.map(\.reminder.title), as: .customDump) { - """ - [ - [0]: "Doctor appointment", - [1]: "Buy concert tickets" - ] - """ + expect(model) { + $0.reminderRows[0].reminder.title = "Doctor appointment" + $0.reminderRows[1].reminder.title = "Buy concert tickets" } } @@ -322,13 +238,9 @@ extension BaseTestSuite { let tag = try await database.read { try Tag.find($0, key: "someday") } let model = RemindersDetailModel(detailType: .tags([tag])) try await model.$reminderRows.load() - assertInlineSnapshot(of: model.reminderRows.map(\.reminder.title), as: .customDump) { - """ - [ - [0]: "Haircut", - [1]: "Groceries" - ] - """ + expect(model) { + $0.reminderRows[0].reminder.title = "Haircut" + $0.reminderRows[1].reminder.title = "Groceries" } } } diff --git a/Examples/RemindersTests/RemindersListsTests.swift b/Examples/RemindersTests/RemindersListsTests.swift index 97d01089c..0e90d9032 100644 --- a/Examples/RemindersTests/RemindersListsTests.swift +++ b/Examples/RemindersTests/RemindersListsTests.swift @@ -1,108 +1,96 @@ +import CloudKit +import DebugSnapshots import Dependencies import DependenciesTestSupport import Foundation import InlineSnapshotTesting +import SQLiteData import SnapshotTestingCustomDump import Testing @testable import Reminders extension BaseTestSuite { - @MainActor struct RemindersListsTests { @Dependency(\.defaultDatabase) var database @Dependency(\.defaultSyncEngine) var syncEngine @Test func basics() async throws { let model = RemindersListsModel() - try await model.$remindersLists.load() - try await model.$stats.load() - try await model.$tags.load() - - assertInlineSnapshot(of: model.remindersLists, as: .customDump) { - """ - [ - [0]: RemindersListsModel.ReminderListState( + try await expect(model) { + try await model.$remindersLists.load() + try await model.$stats.load() + try await model.$tags.load() + } changes: { + $0.remindersLists = [ + RemindersListsModel.ReminderListState( remindersCount: 4, remindersList: RemindersList( - id: UUID(00000000-0000-0000-0000-000000000000), - color: 1218047999, + id: UUID(0), + color: .personal, position: 1, title: "Personal" ), share: nil ), - [1]: RemindersListsModel.ReminderListState( + RemindersListsModel.ReminderListState( remindersCount: 2, remindersList: RemindersList( - id: UUID(00000000-0000-0000-0000-000000000001), - color: 3985191935, + id: UUID(1), + color: .family, position: 2, title: "Family" ), share: nil ), - [2]: RemindersListsModel.ReminderListState( + RemindersListsModel.ReminderListState( remindersCount: 2, remindersList: RemindersList( - id: UUID(00000000-0000-0000-0000-000000000002), - color: 2992493567, + id: UUID(2), + color: .business, position: 3, title: "Business" ), share: nil - ) + ), ] - """ - } - assertInlineSnapshot(of: model.stats, as: .customDump) { - """ - RemindersListsModel.Stats( + $0.stats = RemindersListsModel.Stats( allCount: 8, flaggedCount: 2, scheduledCount: 7, todayCount: 2 ) - """ - } - assertInlineSnapshot(of: model.tags, as: .customDump) { - """ - [ - [0]: Tag(title: "adulting"), - [1]: Tag(title: "car"), - [2]: Tag(title: "kids"), - [3]: Tag(title: "night"), - [4]: Tag(title: "optional"), - [5]: Tag(title: "social"), - [6]: Tag(title: "someday") + $0.tags = [ + Tag(title: "adulting"), + Tag(title: "car"), + Tag(title: "kids"), + Tag(title: "night"), + Tag(title: "optional"), + Tag(title: "social"), + Tag(title: "someday"), ] - """ } } @Test func move() async throws { let model = RemindersListsModel() - try await model.$remindersLists.load() - assertInlineSnapshot(of: model.remindersLists.map(\.remindersList.title), as: .customDump) { - """ - [ - [0]: "Personal", - [1]: "Family", - [2]: "Business" - ] - """ + + try await expect(model) { + try await model.$remindersLists.load() + } changes: { + $0.remindersLists[0].remindersList.title = "Personal" + $0.remindersLists[1].remindersList.title = "Family" + $0.remindersLists[2].remindersList.title = "Business" } - model.move(from: [2], to: 0) - try await model.$remindersLists.load() - assertInlineSnapshot(of: model.remindersLists.map(\.remindersList.title), as: .customDump) { - """ - [ - [0]: "Business", - [1]: "Personal", - [2]: "Family" - ] - """ + try await expect(model) { + model.move(from: [2], to: 0) + try await model.$remindersLists.load() + } changes: { + $0.remindersLists.move(fromOffsets: [2], toOffset: 0) + $0.remindersLists[0].remindersList.position = 1 + $0.remindersLists[1].remindersList.position = 2 + $0.remindersLists[2].remindersList.position = 3 } } @@ -114,44 +102,18 @@ extension BaseTestSuite { try RemindersList.find(UUID(0)).fetchOne(db) } ) - let _ = try await syncEngine.share(record: personalRemindersList, configure: { _ in }) + let share = try await syncEngine.share( + record: personalRemindersList, + configure: { _ in } + ) + .share - try await model.$remindersLists.load() - assertInlineSnapshot(of: model.remindersLists, as: .customDump) { - """ - [ - [0]: RemindersListsModel.ReminderListState( - remindersCount: 4, - remindersList: RemindersList( - id: UUID(00000000-0000-0000-0000-000000000000), - color: 1218047999, - position: 1, - title: "Personal" - ), - share: CKShare() - ), - [1]: RemindersListsModel.ReminderListState( - remindersCount: 2, - remindersList: RemindersList( - id: UUID(00000000-0000-0000-0000-000000000001), - color: 3985191935, - position: 2, - title: "Family" - ), - share: nil - ), - [2]: RemindersListsModel.ReminderListState( - remindersCount: 2, - remindersList: RemindersList( - id: UUID(00000000-0000-0000-0000-000000000002), - color: 2992493567, - position: 3, - title: "Business" - ), - share: nil - ) - ] - """ + try await expect(model) { + try await model.$remindersLists.load() + } changes: { + $0.remindersLists[0].remindersList.title = "Personal" + $0.remindersLists[0].share = model.remindersLists[0].share + #expect($0.remindersLists[0].share?.recordID == share.recordID) } } } diff --git a/Examples/RemindersTests/SearchRemindersTests.swift b/Examples/RemindersTests/SearchRemindersTests.swift index aab566ef2..5f657aeb9 100644 --- a/Examples/RemindersTests/SearchRemindersTests.swift +++ b/Examples/RemindersTests/SearchRemindersTests.swift @@ -1,53 +1,42 @@ +import DebugSnapshots import Dependencies import DependenciesTestSupport +import Foundation import InlineSnapshotTesting +import SQLiteData import SnapshotTestingCustomDump import Testing @testable import Reminders extension BaseTestSuite { - @MainActor - @Suite( - .snapshots(record: .missing) - ) struct SearchRemindersTests { @Dependency(\.defaultDatabase) var database @Test func basics() async throws { let model = SearchRemindersModel() - try await model.$searchResults.load() - #expect(model.searchResults.completedCount == 0) - assertInlineSnapshot(of: model.searchResults.rows, as: .customDump) { - """ - [] - """ - } - - model.searchText = "Take" - try await model.searchTask?.value - #expect(model.searchResults.completedCount == 1) - assertInlineSnapshot(of: model.searchResults.rows, as: .customDump) { - """ - [ - [0]: SearchRemindersModel.Row( + try await expect(model) { + model.searchText = "Take" + try await model.searchTask?.value + } changes: { + $0.searchText = "Take" + $0.searchResults.completedCount = 1 + $0.searchResults.rows = [ + SearchRemindersModel.Row( isPastDue: false, notes: "", reminder: Reminder( - id: UUID(00000000-0000-0000-0000-00000000000A), - dueDate: Date(2009-02-17T23:31:30.000Z), - isFlagged: false, - notes: "", + id: UUID(10), + dueDate: Date(timeIntervalSince1970: 1_234_913_490), position: 8, priority: .high, - remindersListID: UUID(00000000-0000-0000-0000-000000000001), - status: .incomplete, + remindersListID: UUID(1), title: "Take out trash" ), remindersList: RemindersList( - id: UUID(00000000-0000-0000-0000-000000000001), - color: 3985191935, + id: UUID(1), + color: .family, position: 2, title: "Family" ), @@ -55,99 +44,94 @@ extension BaseTestSuite { title: "**Take** out trash" ) ] - """ } } @Test func showCompleted() async throws { let model = SearchRemindersModel() - model.searchText = "Take" - try await model.showCompletedButtonTapped() - try await model.searchTask?.value - try await model.$searchResults.load() - assertInlineSnapshot(of: model.searchResults.rows, as: .customDump) { - """ - [ - [0]: SearchRemindersModel.Row( + try await expect(model) { + model.searchText = "Take" + try await model.showCompletedButtonTapped() + try await model.searchTask?.value + try await model.$searchResults.load() + } changes: { + $0.searchText = "Take" + $0.showCompletedInSearchResults = true + $0.searchResults.completedCount = 1 + $0.searchResults.rows = [ + SearchRemindersModel.Row( isPastDue: false, notes: "", reminder: Reminder( - id: UUID(00000000-0000-0000-0000-00000000000A), - dueDate: Date(2009-02-17T23:31:30.000Z), - isFlagged: false, - notes: "", + id: UUID(10), + dueDate: Date(timeIntervalSince1970: 1_234_913_490), position: 8, priority: .high, - remindersListID: UUID(00000000-0000-0000-0000-000000000001), - status: .incomplete, + remindersListID: UUID(1), title: "Take out trash" ), remindersList: RemindersList( - id: UUID(00000000-0000-0000-0000-000000000001), - color: 3985191935, + id: UUID(1), + color: .family, position: 2, title: "Family" ), tags: "", title: "**Take** out trash" ), - [1]: SearchRemindersModel.Row( + SearchRemindersModel.Row( isPastDue: false, notes: "", reminder: Reminder( - id: UUID(00000000-0000-0000-0000-000000000006), - dueDate: Date(2008-08-07T23:31:30.000Z), - isFlagged: false, - notes: "", + id: UUID(6), + dueDate: Date(timeIntervalSince1970: 1_218_151_890), position: 4, - priority: nil, - remindersListID: UUID(00000000-0000-0000-0000-000000000000), + remindersListID: UUID(0), status: .completed, title: "Take a walk" ), remindersList: RemindersList( - id: UUID(00000000-0000-0000-0000-000000000000), - color: 1218047999, + id: UUID(0), + color: .personal, position: 1, title: "Personal" ), tags: "#car #kids #social", title: "**Take** a walk" - ) + ), ] - """ } } @Test func deleteCompleted() async throws { let model = SearchRemindersModel() - model.searchText = "Take" - try await model.showCompletedButtonTapped() - model.deleteCompletedReminders() - try await model.searchTask?.value - try await model.$searchResults.load() - #expect(model.searchResults.completedCount == 0) - assertInlineSnapshot(of: model.searchResults.rows, as: .customDump) { - """ - [ - [0]: SearchRemindersModel.Row( + + try await expect(model) { + model.searchText = "Take" + try await model.showCompletedButtonTapped() + model.deleteCompletedReminders() + try await model.searchTask?.value + try await model.$searchResults.load() + } changes: { + $0.searchText = "Take" + $0.showCompletedInSearchResults = true + $0.searchResults.completedCount = 0 + $0.searchResults.rows = [ + SearchRemindersModel.Row( isPastDue: false, notes: "", reminder: Reminder( - id: UUID(00000000-0000-0000-0000-00000000000A), - dueDate: Date(2009-02-17T23:31:30.000Z), - isFlagged: false, - notes: "", + id: UUID(10), + dueDate: Date(timeIntervalSince1970: 1_234_913_490), position: 8, priority: .high, - remindersListID: UUID(00000000-0000-0000-0000-000000000001), - status: .incomplete, + remindersListID: UUID(1), title: "Take out trash" ), remindersList: RemindersList( - id: UUID(00000000-0000-0000-0000-000000000001), - color: 3985191935, + id: UUID(1), + color: .family, position: 2, title: "Family" ), @@ -155,7 +139,6 @@ extension BaseTestSuite { title: "**Take** out trash" ) ] - """ } } } diff --git a/Examples/RemindersTests/SeedTestData.swift b/Examples/RemindersTests/SeedTestData.swift new file mode 100644 index 000000000..eba800d06 --- /dev/null +++ b/Examples/RemindersTests/SeedTestData.swift @@ -0,0 +1,140 @@ +import Dependencies +import Foundation +import SQLiteData +import SwiftUI + +@testable import Reminders + +extension DatabaseWriter { + func seedTestData() throws { + @Dependency(\.date.now) var now + @Dependency(\.uuid) var uuid + try write { db in + var remindersListIDs: [UUID] = [] + for _ in 0...2 { + remindersListIDs.append(uuid()) + } + var reminderIDs: [UUID] = [] + for _ in 0...10 { + reminderIDs.append(uuid()) + } + try db.seed { + RemindersList( + id: remindersListIDs[0], + color: .personal, + title: "Personal" + ) + RemindersList( + id: remindersListIDs[1], + color: .family, + title: "Family" + ) + RemindersList( + id: remindersListIDs[2], + color: .business, + title: "Business" + ) + Reminder( + id: reminderIDs[0], + notes: "Milk\nEggs\nApples\nOatmeal\nSpinach", + remindersListID: remindersListIDs[0], + title: "Groceries" + ) + Reminder( + id: reminderIDs[1], + dueDate: now.addingTimeInterval(-60 * 60 * 24 * 2), + isFlagged: true, + remindersListID: remindersListIDs[0], + title: "Haircut" + ) + Reminder( + id: reminderIDs[2], + dueDate: now, + notes: "Ask about diet", + priority: .high, + remindersListID: remindersListIDs[0], + title: "Doctor appointment" + ) + Reminder( + id: reminderIDs[3], + dueDate: now.addingTimeInterval(-60 * 60 * 24 * 190), + remindersListID: remindersListIDs[0], + status: .completed, + title: "Take a walk" + ) + Reminder( + id: reminderIDs[4], + dueDate: now, + remindersListID: remindersListIDs[0], + title: "Buy concert tickets" + ) + Reminder( + id: reminderIDs[5], + dueDate: now.addingTimeInterval(60 * 60 * 24 * 2), + isFlagged: true, + priority: .high, + remindersListID: remindersListIDs[1], + title: "Pick up kids from school" + ) + Reminder( + id: reminderIDs[6], + dueDate: now.addingTimeInterval(-60 * 60 * 24 * 2), + priority: .low, + remindersListID: remindersListIDs[1], + status: .completed, + title: "Get laundry" + ) + Reminder( + id: reminderIDs[7], + dueDate: now.addingTimeInterval(60 * 60 * 24 * 4), + priority: .high, + remindersListID: remindersListIDs[1], + status: .incomplete, + title: "Take out trash" + ) + Reminder( + id: reminderIDs[8], + dueDate: now.addingTimeInterval(60 * 60 * 24 * 2), + notes: """ + Status of tax return + Expenses for next year + Changing payroll company + """, + remindersListID: remindersListIDs[2], + title: "Call accountant" + ) + Reminder( + id: reminderIDs[9], + dueDate: now.addingTimeInterval(-60 * 60 * 24 * 2), + priority: .medium, + remindersListID: remindersListIDs[2], + status: .completed, + title: "Send weekly emails" + ) + Reminder( + id: reminderIDs[10], + dueDate: now.addingTimeInterval(60 * 60 * 24 * 2), + remindersListID: remindersListIDs[2], + status: .incomplete, + title: "Prepare for WWDC" + ) + let tagIDs = ["car", "kids", "someday", "optional", "social", "night", "adulting"] + for tagID in tagIDs { + Tag(title: tagID) + } + ReminderTag.Draft(reminderID: reminderIDs[0], tagID: tagIDs[2]) + ReminderTag.Draft(reminderID: reminderIDs[0], tagID: tagIDs[3]) + ReminderTag.Draft(reminderID: reminderIDs[0], tagID: tagIDs[6]) + ReminderTag.Draft(reminderID: reminderIDs[1], tagID: tagIDs[2]) + ReminderTag.Draft(reminderID: reminderIDs[1], tagID: tagIDs[3]) + ReminderTag.Draft(reminderID: reminderIDs[2], tagID: tagIDs[6]) + ReminderTag.Draft(reminderID: reminderIDs[3], tagID: tagIDs[0]) + ReminderTag.Draft(reminderID: reminderIDs[3], tagID: tagIDs[1]) + ReminderTag.Draft(reminderID: reminderIDs[4], tagID: tagIDs[4]) + ReminderTag.Draft(reminderID: reminderIDs[3], tagID: tagIDs[4]) + ReminderTag.Draft(reminderID: reminderIDs[10], tagID: tagIDs[4]) + ReminderTag.Draft(reminderID: reminderIDs[4], tagID: tagIDs[5]) + } + } + } +} diff --git a/Examples/SyncUpTests/SyncUpFormTests.swift b/Examples/SyncUpTests/SyncUpFormTests.swift index c0f60e96f..562a596dc 100644 --- a/Examples/SyncUpTests/SyncUpFormTests.swift +++ b/Examples/SyncUpTests/SyncUpFormTests.swift @@ -1,12 +1,13 @@ +import DebugSnapshots import Dependencies import DependenciesTestSupport import Foundation +import SQLiteData import StructuredQueries import Testing @testable import SyncUps -@MainActor @Suite( .dependencies { try $0.bootstrapDatabase() @@ -21,11 +22,34 @@ struct SyncUpFormTests { @Test func saveNew() async throws { let draft = SyncUp.Draft(title: "Morning Sync") let model = SyncUpFormModel(syncUp: draft) - model.addAttendeeButtonTapped() - model.addAttendeeButtonTapped() + expect(model) { + $0.attendees = [SyncUpFormModel.AttendeeDraft(id: UUID(0))] + $0.focus = .title + $0.isDismissed = false + $0.syncUp = SyncUp.Draft(title: "Morning Sync") + } + + expect(model) { + model.addAttendeeButtonTapped() + } changes: { + $0.attendees.append(SyncUpFormModel.AttendeeDraft(id: UUID(1))) + $0.focus = .attendee(UUID(1)) + } + expect(model) { + model.addAttendeeButtonTapped() + } changes: { + $0.attendees.append(SyncUpFormModel.AttendeeDraft(id: UUID(2))) + $0.focus = .attendee(UUID(2)) + } + model.attendees[0].name = "Blob" model.attendees[1].name = "Blob Jr." - model.saveButtonTapped() + expect(model) { + model.saveButtonTapped() + } changes: { + $0.attendees.removeLast() + $0.isDismissed = true + } let syncUp = try await database.read { db in try #require(try SyncUp.order { $0.id.desc() }.fetchOne(db)) @@ -43,11 +67,44 @@ struct SyncUpFormTests { } let draft = SyncUp.Draft(existingSyncUp) let model = SyncUpFormModel(syncUp: draft) + expect(model) { + $0.attendees = [ + SyncUpFormModel.AttendeeDraft(id: UUID(0), name: "Blob"), + SyncUpFormModel.AttendeeDraft(id: UUID(1), name: "Blob Jr"), + SyncUpFormModel.AttendeeDraft(id: UUID(2), name: "Blob Sr"), + SyncUpFormModel.AttendeeDraft(id: UUID(3), name: "Blob Esq"), + SyncUpFormModel.AttendeeDraft(id: UUID(4), name: "Blob III"), + SyncUpFormModel.AttendeeDraft(id: UUID(5), name: "Blob I"), + ] + $0.focus = .title + $0.isDismissed = false + $0.syncUp = SyncUp.Draft( + id: UUID(1), + seconds: 60, + theme: .appOrange, + title: "Design" + ) + } + model.syncUp.title = "Evening Sync" - model.deleteAttendees(atOffsets: [1, 2, 3, 4, 5]) - model.addAttendeeButtonTapped() + expect(model) { + model.deleteAttendees(atOffsets: [1, 2, 3, 4, 5]) + } changes: { + $0.attendees.removeSubrange(1...5) + $0.focus = .attendee(UUID(0)) + } + expect(model) { + model.addAttendeeButtonTapped() + } changes: { + $0.attendees.append(SyncUpFormModel.AttendeeDraft(id: UUID(6))) + $0.focus = .attendee(UUID(6)) + } model.attendees[model.attendees.count - 1].name = "Blobby McBlob" - model.saveButtonTapped() + expect(model) { + model.saveButtonTapped() + } changes: { + $0.isDismissed = true + } let syncUp = try await database.read { db in try #require(try SyncUp.where { $0.id.eq(existingSyncUp.id) }.fetchOne(db)) diff --git a/Examples/SyncUps/App.swift b/Examples/SyncUps/App.swift index dffcd2bbd..648986c10 100644 --- a/Examples/SyncUps/App.swift +++ b/Examples/SyncUps/App.swift @@ -2,7 +2,6 @@ import CasePaths import SQLiteData import SwiftUI -@MainActor @Observable class AppModel { var path: [Path] { diff --git a/Examples/SyncUps/RecordMeeting.swift b/Examples/SyncUps/RecordMeeting.swift index 6b6620739..7ba8e2dae 100644 --- a/Examples/SyncUps/RecordMeeting.swift +++ b/Examples/SyncUps/RecordMeeting.swift @@ -4,7 +4,6 @@ import Speech import SwiftUI import SwiftUINavigation -@MainActor @Observable final class RecordMeetingModel: HashableObject { var alert: AlertState? diff --git a/Examples/SyncUps/Schema.swift b/Examples/SyncUps/Schema.swift index b806af404..6aa43db48 100644 --- a/Examples/SyncUps/Schema.swift +++ b/Examples/SyncUps/Schema.swift @@ -11,7 +11,7 @@ nonisolated struct SyncUp: Hashable, Identifiable { } @Table -nonisolated struct Attendee: Hashable, Identifiable { +nonisolated struct Attendee: Codable, Hashable, Identifiable { let id: UUID var name = "" var syncUpID: SyncUp.ID diff --git a/Examples/SyncUps/SyncUpDetail.swift b/Examples/SyncUps/SyncUpDetail.swift index 212f88cf0..b9b5aa439 100644 --- a/Examples/SyncUps/SyncUpDetail.swift +++ b/Examples/SyncUps/SyncUpDetail.swift @@ -2,7 +2,6 @@ import SQLiteData import SwiftUI import SwiftUINavigation -@MainActor @Observable final class SyncUpDetailModel: HashableObject { var destination: Destination? diff --git a/Examples/SyncUps/SyncUpForm.swift b/Examples/SyncUps/SyncUpForm.swift index 3cbfaf76d..325b55b9c 100644 --- a/Examples/SyncUps/SyncUpForm.swift +++ b/Examples/SyncUps/SyncUpForm.swift @@ -1,7 +1,9 @@ +import DebugSnapshots import SQLiteData import SwiftUI import SwiftUINavigation +@DebugSnapshot @Observable final class SyncUpFormModel: Identifiable { var attendees: [AttendeeDraft] = [] diff --git a/Examples/SyncUps/SyncUpsList.swift b/Examples/SyncUps/SyncUpsList.swift index 7630c7925..018515579 100644 --- a/Examples/SyncUps/SyncUpsList.swift +++ b/Examples/SyncUps/SyncUpsList.swift @@ -3,7 +3,6 @@ import SwiftUI import SwiftUINavigation import TipKit -@MainActor @Observable final class SyncUpsListModel { var addSyncUp: SyncUpFormModel?