Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Changes to a theme file made outside TablePro not shown until Settings > Appearance was opened.
- Theme files with a JSON syntax error reported without the line and column.
- Two themes with the same name impossible to tell apart in the theme pickers.
- No warning on a connection in the Welcome window whose driver plugin failed to load or is out of date.

## [0.81.0] - 2026-10-16

Expand Down
9 changes: 2 additions & 7 deletions TablePro/ViewModels/WelcomeViewModel+Rows.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
//
// WelcomeViewModel+Rows.swift
// TablePro
//

import AppKit
import TableProConnectionLibrary

Expand Down Expand Up @@ -38,8 +33,8 @@ extension WelcomeViewModel {
let group = connection.groupId.flatMap { groupsById[$0] }
let groupPath = group.map { groupGraph.pathNames(to: $0.id) } ?? []
let typeId = connection.type.pluginTypeId
let isDriverRejected = services.pluginManager.rejectedPlugins.contains { rejected in
rejected.bundleId == typeId || rejected.registryId == typeId
let isDriverRejected = services.pluginManager.rejectedPlugins.contains {
$0.providedDatabaseTypeIds.contains(typeId)
}
return WelcomeRowPresentation.savedConnection(
connection,
Expand Down
45 changes: 41 additions & 4 deletions TableProTests/ViewModels/WelcomeViewModelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,11 @@ final class WelcomeViewModelTests {

private func makeViewModel(
importableAppDetector: @escaping @MainActor () -> Bool = { false },
isUITestSandbox: Bool = false
isUITestSandbox: Bool = false,
pluginManager: PluginManager = AppServices.live.pluginManager
) -> WelcomeViewModel {
WelcomeViewModel(
services: makeServices(),
services: makeServices(pluginManager: pluginManager),
importableAppDetector: importableAppDetector,
groupExpansionStore: WelcomeGroupExpansionStore(defaults: defaults),
recentConnections: recents,
Expand All @@ -72,15 +73,15 @@ final class WelcomeViewModelTests {
try? FileManager.default.removeItem(at: connectionFileURL.deletingLastPathComponent())
}

private func makeServices() -> AppServices {
private func makeServices(pluginManager: PluginManager) -> AppServices {
let live = AppServices.live
return AppServices(
appEvents: live.appEvents,
appSettings: live.appSettings,
appSettingsStorage: AppSettingsStorage(userDefaults: defaults),
connectionStorage: connectionStorage,
databaseManager: live.databaseManager,
pluginManager: live.pluginManager,
pluginManager: pluginManager,
schemaService: live.schemaService,
schemaRefreshService: live.schemaRefreshService,
schemaProviderRegistry: live.schemaProviderRegistry,
Expand Down Expand Up @@ -776,6 +777,42 @@ final class WelcomeViewModelTests {
#expect(viewModel.libraryErrorMessage != nil, "A refused delete must say so")
}

// MARK: - Driver Plugins

@Test func testARejectedPluginFlagsEveryConnectionOfTheTypesItProvides() {
let pluginManager = PluginManager(
userDefaults: defaults,
builtInPluginsURL: nil,
userPluginsDir: connectionFileURL.deletingLastPathComponent().appendingPathComponent("Plugins")
)
pluginManager.rejectedPlugins = [RejectedPlugin(
url: URL(fileURLWithPath: "/tmp/CassandraDriver.tableplugin"),
bundleId: "com.TablePro.CassandraDriver",
registryId: "com.TablePro.CassandraDriver",
name: "Cassandra Driver",
reason: "Built for an older PluginKit",
isOutdated: true,
providedDatabaseTypeIds: ["Cassandra", "ScyllaDB"]
)]
let welcome = makeViewModel(pluginManager: pluginManager)
let cassandra = DatabaseConnection(name: "Ring", type: .cassandra, sortOrder: 0)
let scylla = DatabaseConnection(name: "Scylla", type: .scylladb, sortOrder: 1)
let mysql = DatabaseConnection(name: "Shop", type: .mysql, sortOrder: 2)
save([cassandra, scylla, mysql])

welcome.loadConnections()

func isDriverRejected(_ connection: DatabaseConnection) -> Bool? {
guard case .connection(let row) = welcome.rowModel(for: .connection(connection.id, section: .connections)) else {
return nil
}
return row.isDriverRejected
}
#expect(isDriverRejected(cassandra) == true)
#expect(isDriverRejected(scylla) == true)
#expect(isDriverRejected(mysql) == false)
}

// MARK: - Shared Connections

@Test func testSharedRowIdsAreDerivedFromThePayloadAndAreStable() {
Expand Down
Loading