Skip to content

Commit c10fbe7

Browse files
authored
Merge pull request #246 from datlechin/fix/import-tests-and-plugin-loading
Fix import tests and improve plugin loading
2 parents 2eac70e + 171aa23 commit c10fbe7

9 files changed

Lines changed: 67 additions & 94 deletions

File tree

TablePro/AppDelegate.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -612,8 +612,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
612612
// Enable native macOS window tabbing (Finder/Safari-style tabs)
613613
NSWindow.allowsAutomaticWindowTabbing = true
614614

615-
// Load plugins (driver plugins, etc.) before any connections are created
616-
PluginManager.shared.loadAllPlugins()
615+
// Discover and load plugins (discovery is synchronous, bundle loading is deferred)
616+
PluginManager.shared.loadPlugins()
617617

618618
// Start license periodic validation
619619
Task { @MainActor in

TablePro/Core/Plugins/PluginManager.swift

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,16 @@ final class PluginManager {
4545

4646
// MARK: - Loading
4747

48-
func loadAllPlugins() {
48+
/// Discover and load all plugins. Discovery is synchronous (reads Info.plist),
49+
/// then bundle loading is deferred to the next run loop iteration so it doesn't block app launch.
50+
func loadPlugins() {
51+
discoverAllPlugins()
52+
Task { @MainActor in
53+
self.loadPendingPlugins()
54+
}
55+
}
56+
57+
private func discoverAllPlugins() {
4958
let fm = FileManager.default
5059
if !fm.fileExists(atPath: userPluginsDir.path) {
5160
do {
@@ -65,7 +74,7 @@ final class PluginManager {
6574
}
6675

6776
/// Load all discovered but not-yet-loaded plugin bundles.
68-
/// Called on first driver request or when the plugins settings screen opens.
77+
/// Safety fallback for code paths that need plugins before the deferred Task completes.
6978
func loadPendingPlugins() {
7079
guard !pendingPluginURLs.isEmpty else { return }
7180
let pending = pendingPluginURLs
@@ -285,6 +294,7 @@ final class PluginManager {
285294
// MARK: - Driver Availability
286295

287296
func isDriverAvailable(for databaseType: DatabaseType) -> Bool {
297+
// Safety fallback: loads pending plugins if the deferred startup Task hasn't completed yet
288298
loadPendingPlugins()
289299
return driverPlugins[databaseType.pluginTypeId] != nil
290300
}

TablePro/Resources/Localizable.xcstrings

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3749,6 +3749,16 @@
37493749
}
37503750
}
37513751
},
3752+
"Connection Test Failed" : {
3753+
"localizations" : {
3754+
"vi" : {
3755+
"stringUnit" : {
3756+
"state" : "translated",
3757+
"value" : "Kiểm tra kết nối thất bại"
3758+
}
3759+
}
3760+
}
3761+
},
37523762
"Connection URL" : {
37533763
"localizations" : {
37543764
"vi" : {

TablePro/Views/Connection/ConnectionFormView.swift

Lines changed: 26 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,11 @@ struct ConnectionFormView: View {
8585
@State private var startupCommands: String = ""
8686

8787
@State private var isTesting: Bool = false
88-
@State private var testResult: TestResult?
88+
@State private var testSucceeded: Bool = false
8989

9090
@State private var isInstallingPlugin = false
9191
@State private var pluginInstallProgress: Double = 0
92-
@State private var showPluginInstallError: String?
92+
9393
@State private var pluginInstallConnection: DatabaseConnection?
9494

9595
// Tab selection
@@ -100,11 +100,6 @@ struct ConnectionFormView: View {
100100

101101
// MARK: - Enums
102102

103-
enum TestResult {
104-
case success
105-
case failure(String)
106-
}
107-
108103
private enum FormTab: String, CaseIterable {
109104
case general = "General"
110105
case ssh = "SSH Tunnel"
@@ -139,7 +134,6 @@ struct ConnectionFormView: View {
139134
isNew ? String(localized: "New Connection") : String(localized: "Edit Connection")
140135
)
141136
.onAppear {
142-
PluginManager.shared.loadPendingPlugins()
143137
loadConnectionData()
144138
loadSSHConfig()
145139
}
@@ -680,32 +674,6 @@ struct ConnectionFormView: View {
680674
.padding(.top, 8)
681675
}
682676

683-
if case .failure(let message) = testResult {
684-
HStack(alignment: .top, spacing: 6) {
685-
Image(systemName: "exclamationmark.triangle.fill")
686-
.foregroundStyle(.red)
687-
Text(message)
688-
.font(.caption)
689-
.foregroundStyle(.red)
690-
.fixedSize(horizontal: false, vertical: true)
691-
}
692-
.padding(.horizontal, 16)
693-
.padding(.top, 8)
694-
}
695-
696-
if let pluginError = showPluginInstallError {
697-
HStack(alignment: .top, spacing: 6) {
698-
Image(systemName: "exclamationmark.triangle.fill")
699-
.foregroundStyle(.red)
700-
Text(pluginError)
701-
.font(.caption)
702-
.foregroundStyle(.red)
703-
.fixedSize(horizontal: false, vertical: true)
704-
}
705-
.padding(.horizontal, 16)
706-
.padding(.top, 8)
707-
}
708-
709677
HStack {
710678
// Test connection
711679
Button(action: testConnection) {
@@ -714,8 +682,8 @@ struct ConnectionFormView: View {
714682
ProgressView()
715683
.controlSize(.small)
716684
} else {
717-
Image(systemName: testResultIcon)
718-
.foregroundStyle(testResultColor)
685+
Image(systemName: testSucceeded ? "checkmark.circle.fill" : "bolt.horizontal")
686+
.foregroundStyle(testSucceeded ? .green : .secondary)
719687
}
720688
Text("Test Connection")
721689
}
@@ -783,26 +751,10 @@ struct ConnectionFormView: View {
783751
return basicValid
784752
}
785753

786-
private var testResultIcon: String {
787-
switch testResult {
788-
case .success: return "checkmark.circle.fill"
789-
case .failure: return "xmark.circle.fill"
790-
case .none: return "bolt.horizontal"
791-
}
792-
}
793-
794-
private var testResultColor: Color {
795-
switch testResult {
796-
case .success: return .green
797-
case .failure: return .red
798-
case .none: return .secondary
799-
}
800-
}
801-
802754
private func installPluginForType(_ databaseType: DatabaseType) {
803755
isInstallingPlugin = true
804756
pluginInstallProgress = 0
805-
showPluginInstallError = nil
757+
let window = NSApp.keyWindow
806758

807759
Task {
808760
do {
@@ -812,7 +764,11 @@ struct ConnectionFormView: View {
812764
isInstallingPlugin = false
813765
} catch {
814766
isInstallingPlugin = false
815-
showPluginInstallError = error.localizedDescription
767+
AlertHelper.showErrorSheet(
768+
title: String(localized: "Plugin Installation Failed"),
769+
message: error.localizedDescription,
770+
window: window
771+
)
816772
}
817773
}
818774
}
@@ -1012,7 +968,8 @@ struct ConnectionFormView: View {
1012968

1013969
func testConnection() {
1014970
isTesting = true
1015-
testResult = nil
971+
testSucceeded = false
972+
let window = NSApp.keyWindow
1016973

1017974
// Build SSH config
1018975
let sshConfig = SSHConfiguration(
@@ -1080,13 +1037,24 @@ struct ConnectionFormView: View {
10801037
testConn, sshPassword: sshPassword)
10811038
await MainActor.run {
10821039
isTesting = false
1083-
testResult =
1084-
success ? .success : .failure(String(localized: "Connection test failed"))
1040+
if success {
1041+
testSucceeded = true
1042+
} else {
1043+
AlertHelper.showErrorSheet(
1044+
title: String(localized: "Connection Test Failed"),
1045+
message: String(localized: "Connection test failed"),
1046+
window: window
1047+
)
1048+
}
10851049
}
10861050
} catch {
10871051
await MainActor.run {
10881052
isTesting = false
1089-
testResult = .failure(error.localizedDescription)
1053+
AlertHelper.showErrorSheet(
1054+
title: String(localized: "Connection Test Failed"),
1055+
message: error.localizedDescription,
1056+
window: window
1057+
)
10901058
}
10911059
}
10921060
}

TablePro/Views/Export/ExportDialog.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ struct ExportDialog: View {
6363
.frame(width: dialogWidth)
6464
.background(Color(nsColor: .windowBackgroundColor))
6565
.onAppear {
66-
PluginManager.shared.loadPendingPlugins()
6766
let available = availableFormats
6867
if !available.contains(where: { type(of: $0).formatId == config.formatId }) {
6968
if let first = available.first {

TablePro/Views/Import/ImportDialog.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ struct ImportDialog: View {
6767
}
6868
.background(Color(nsColor: .windowBackgroundColor))
6969
.onAppear {
70-
PluginManager.shared.loadPendingPlugins()
7170
let available = availableFormats
7271
if !available.contains(where: { type(of: $0).formatId == selectedFormatId }) {
7372
if let first = available.first {

TablePro/Views/Settings/Plugins/InstalledPluginsView.swift

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,6 @@ struct InstalledPluginsView: View {
8181
}
8282
return true
8383
}
84-
.onAppear {
85-
pluginManager.loadPendingPlugins()
86-
}
8784
.alert(errorAlertTitle, isPresented: $showErrorAlert) {
8885
Button("OK") {}
8986
} message: {

TableProTests/Core/Services/ImportStateTests.swift

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,8 @@ struct ImportStateTests {
1616
let state = ImportState()
1717
#expect(state.isImporting == false)
1818
#expect(state.progress == 0.0)
19-
#expect(state.currentStatement == "")
20-
#expect(state.currentStatementIndex == 0)
21-
#expect(state.totalStatements == 0)
19+
#expect(state.processedStatements == 0)
20+
#expect(state.estimatedTotalStatements == 0)
2221
#expect(state.statusMessage == "")
2322
#expect(state.errorMessage == nil)
2423
}
@@ -46,7 +45,7 @@ struct ImportStateTests {
4645

4746
#expect(state.isImporting == true)
4847
#expect(state.progress == 0.0)
49-
#expect(state.currentStatement == "")
48+
#expect(state.processedStatements == 0)
5049
#expect(state.errorMessage == nil)
5150
}
5251

@@ -60,14 +59,11 @@ struct ImportStateTests {
6059
state.progress = 0.75
6160
#expect(state.progress == 0.75)
6261

63-
state.currentStatement = "CREATE TABLE test"
64-
#expect(state.currentStatement == "CREATE TABLE test")
62+
state.processedStatements = 5
63+
#expect(state.processedStatements == 5)
6564

66-
state.currentStatementIndex = 5
67-
#expect(state.currentStatementIndex == 5)
68-
69-
state.totalStatements = 20
70-
#expect(state.totalStatements == 20)
65+
state.estimatedTotalStatements = 20
66+
#expect(state.estimatedTotalStatements == 20)
7167

7268
state.statusMessage = "Importing..."
7369
#expect(state.statusMessage == "Importing...")

TableProTests/Views/Import/ImportServiceStateTests.swift

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,8 @@ struct ImportServiceStateTests {
2020

2121
#expect(state.service == nil)
2222
#expect(state.isImporting == false)
23-
#expect(state.currentStatement == "")
24-
#expect(state.currentStatementIndex == 0)
25-
#expect(state.totalStatements == 0)
23+
#expect(state.processedStatements == 0)
24+
#expect(state.estimatedTotalStatements == 0)
2625
#expect(state.statusMessage == "")
2726
}
2827

@@ -36,18 +35,16 @@ struct ImportServiceStateTests {
3635

3736
service.state = ImportState(
3837
isImporting: true,
39-
currentStatement: "CREATE TABLE users",
40-
currentStatementIndex: 3,
41-
totalStatements: 10,
38+
processedStatements: 3,
39+
estimatedTotalStatements: 10,
4240
statusMessage: "Importing..."
4341
)
4442

4543
state.setService(service)
4644

4745
#expect(state.isImporting == true)
48-
#expect(state.currentStatement == "CREATE TABLE users")
49-
#expect(state.currentStatementIndex == 3)
50-
#expect(state.totalStatements == 10)
46+
#expect(state.processedStatements == 3)
47+
#expect(state.estimatedTotalStatements == 10)
5148
#expect(state.statusMessage == "Importing...")
5249
}
5350

@@ -62,18 +59,15 @@ struct ImportServiceStateTests {
6259
state.setService(service)
6360

6461
#expect(state.isImporting == false)
65-
#expect(state.currentStatement == "")
6662

6763
service.state.isImporting = true
68-
service.state.currentStatement = "INSERT INTO orders"
69-
service.state.currentStatementIndex = 7
70-
service.state.totalStatements = 20
64+
service.state.processedStatements = 7
65+
service.state.estimatedTotalStatements = 20
7166
service.state.statusMessage = "Processing statements..."
7267

7368
#expect(state.isImporting == true)
74-
#expect(state.currentStatement == "INSERT INTO orders")
75-
#expect(state.currentStatementIndex == 7)
76-
#expect(state.totalStatements == 20)
69+
#expect(state.processedStatements == 7)
70+
#expect(state.estimatedTotalStatements == 20)
7771
#expect(state.statusMessage == "Processing statements...")
7872
}
7973
}

0 commit comments

Comments
 (0)