diff --git a/CHANGELOG.md b/CHANGELOG.md index e01df760..213c30ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Saved connections disappearing after normal app quit (Cmd+Q) while persisting after force quit (#452) - Detail pane showing truncated values for LONGTEXT/MEDIUMTEXT/CLOB columns, preventing correct editing - Redis hash/list/set/zset/stream views showing empty or misaligned rows when values contained binary, null, or integer types diff --git a/TablePro/AppDelegate.swift b/TablePro/AppDelegate.swift index 63240e7c..efb654f2 100644 --- a/TablePro/AppDelegate.swift +++ b/TablePro/AppDelegate.swift @@ -116,6 +116,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { } func applicationWillTerminate(_ notification: Notification) { + UserDefaults.standard.synchronize() SSHTunnelManager.shared.terminateAllProcessesSync() } diff --git a/TablePro/Models/Connection/DatabaseConnection.swift b/TablePro/Models/Connection/DatabaseConnection.swift index f0b17a8b..c3c1564e 100644 --- a/TablePro/Models/Connection/DatabaseConnection.swift +++ b/TablePro/Models/Connection/DatabaseConnection.swift @@ -500,10 +500,10 @@ struct DatabaseConnection: Identifiable, Hashable { } } -// MARK: - Sample Data for Development +// MARK: - Preview Data extension DatabaseConnection { - static let sampleConnections: [DatabaseConnection] = [] + static let preview = DatabaseConnection(name: "Preview Connection") } // MARK: - Codable Conformance diff --git a/TablePro/Views/Connection/ConnectionFormView.swift b/TablePro/Views/Connection/ConnectionFormView.swift index 5263faa2..a6e8afe9 100644 --- a/TablePro/Views/Connection/ConnectionFormView.swift +++ b/TablePro/Views/Connection/ConnectionFormView.swift @@ -1754,5 +1754,5 @@ private struct StartupCommandsEditor: NSViewRepresentable { } #Preview("Edit Connection") { - ConnectionFormView(connectionId: DatabaseConnection.sampleConnections[0].id) + ConnectionFormView(connectionId: DatabaseConnection.preview.id) } diff --git a/TablePro/Views/Connection/WelcomeWindowView.swift b/TablePro/Views/Connection/WelcomeWindowView.swift index c3793e18..bfbbfcb1 100644 --- a/TablePro/Views/Connection/WelcomeWindowView.swift +++ b/TablePro/Views/Connection/WelcomeWindowView.swift @@ -728,13 +728,7 @@ struct WelcomeWindowView: View { // MARK: - Actions private func loadConnections() { - let saved = storage.loadConnections() - if saved.isEmpty { - connections = DatabaseConnection.sampleConnections - storage.saveConnections(connections) - } else { - connections = saved - } + connections = storage.loadConnections() loadGroups() } diff --git a/TablePro/Views/Main/MainContentView.swift b/TablePro/Views/Main/MainContentView.swift index f5ad4fb8..67931508 100644 --- a/TablePro/Views/Main/MainContentView.swift +++ b/TablePro/Views/Main/MainContentView.swift @@ -1067,11 +1067,11 @@ private struct FocusedCommandActionsModifier: ViewModifier { #Preview("With Connection") { let state = SessionStateFactory.create( - connection: DatabaseConnection.sampleConnections[0], + connection: DatabaseConnection.preview, payload: nil ) MainContentView( - connection: DatabaseConnection.sampleConnections[0], + connection: DatabaseConnection.preview, payload: nil, windowTitle: .constant("SQL Query"), tables: .constant([]), diff --git a/TableProTests/Core/Storage/ConnectionStoragePersistenceTests.swift b/TableProTests/Core/Storage/ConnectionStoragePersistenceTests.swift new file mode 100644 index 00000000..619e1c4c --- /dev/null +++ b/TableProTests/Core/Storage/ConnectionStoragePersistenceTests.swift @@ -0,0 +1,56 @@ +// +// ConnectionStoragePersistenceTests.swift +// TableProTests +// + +import Foundation +import Testing +@testable import TablePro + +@Suite("ConnectionStorage Persistence", .serialized) +struct ConnectionStoragePersistenceTests { + private let storage = ConnectionStorage.shared + + @Test("loading empty storage does not write back to UserDefaults") + func loadEmptyDoesNotWrite() { + let original = storage.loadConnections() + defer { storage.saveConnections(original) } + + // Clear all connections + storage.saveConnections([]) + + // Force cache clear by saving then loading + let loaded = storage.loadConnections() + #expect(loaded.isEmpty) + + // Add a connection directly, bypassing cache + let connection = DatabaseConnection(name: "Persistence Test") + storage.addConnection(connection) + defer { storage.deleteConnection(connection) } + + // Loading again should return the connection, not overwrite with empty + let reloaded = storage.loadConnections() + #expect(reloaded.contains { $0.id == connection.id }) + } + + @Test("round-trip save and load preserves connections") + func roundTripSaveLoad() { + let original = storage.loadConnections() + defer { storage.saveConnections(original) } + + let connection = DatabaseConnection( + name: "Round Trip Test", + host: "127.0.0.1", + port: 5432, + type: .postgresql + ) + + storage.saveConnections([connection]) + let loaded = storage.loadConnections() + + #expect(loaded.count == 1) + #expect(loaded.first?.id == connection.id) + #expect(loaded.first?.name == "Round Trip Test") + #expect(loaded.first?.host == "127.0.0.1") + } +}