579 lines
26 KiB
Swift
579 lines
26 KiB
Swift
// SPDX-FileCopyrightText: 2026 Kaede Akino
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
import AgentKit
|
|
import Foundation
|
|
import TermindApplication
|
|
import TermindDomain
|
|
import TermindInfrastructure
|
|
import TermindPresentation
|
|
import TermindRemoteInfrastructure
|
|
import Testing
|
|
|
|
import struct TermindDomain.Host
|
|
|
|
@testable import Termind
|
|
|
|
@Suite
|
|
@MainActor
|
|
struct QuickOpenTests {
|
|
@Test
|
|
func resumableConnectActionsUseWithPrefix() {
|
|
let locale = Locale(identifier: "en")
|
|
#expect(AppLocalization.resolve(HostConnectAction.mosh.title, locale: locale) == "with Mosh")
|
|
#expect(
|
|
AppLocalization.resolve(HostConnectAction.eternalTerminal.title, locale: locale)
|
|
== "with Eternal Terminal")
|
|
}
|
|
|
|
@Test
|
|
func nativeHostMenuCanDuplicateWithoutChangingTheOriginalHost() async throws {
|
|
let host = Host(name: "Production", host: "server.invalid", port: 2222)
|
|
let app = AppModel(hosts: HostLibraryModel(hosts: [host]))
|
|
defer { app.shutdownForTermination() }
|
|
let entries = HostMenuEntries.entries(
|
|
for: host, app: app, window: WindowModel(), includesDuplicate: true)
|
|
let duplicate = try #require(entries.first { $0.symbol == MenuSymbol.duplicate })
|
|
#expect(duplicate.isEnabled)
|
|
|
|
duplicate.action()
|
|
for _ in 0..<200 where app.hostLibrary.hosts.count == 1 {
|
|
try await Task.sleep(for: .milliseconds(5))
|
|
}
|
|
|
|
#expect(app.hostLibrary.hosts.count == 2)
|
|
let copy = try #require(app.hostLibrary.hosts.first { $0.id != host.id })
|
|
#expect(copy.name == String(localized: AppStrings.Vault.duplicatedHostName(host.name)))
|
|
#expect(copy.host == host.host)
|
|
#expect(copy.port == host.port)
|
|
#expect(app.hostLibrary.host(host.id)?.name == host.name)
|
|
}
|
|
|
|
@Test
|
|
func telnetUsesItsOwnSymbolInQuickOpenAndNativeHostMenus() throws {
|
|
let host = Host(
|
|
name: "Legacy Console", host: "console.example.invalid",
|
|
telnetEnabled: true)
|
|
let store = HostLibraryModel(hosts: [host])
|
|
let app = AppModel(hosts: store)
|
|
let window = WindowModel()
|
|
|
|
let commands = app.commands.commands(app: app, window: window)
|
|
let ssh = try #require(commands.first { $0.target == .connectHost(host.id, .sshSession) })
|
|
let telnet = try #require(commands.first { $0.target == .connectHost(host.id, .telnetSession) })
|
|
#expect(ssh.symbol == MenuSymbol.ssh)
|
|
#expect(telnet.symbol == MenuSymbol.telnet)
|
|
#expect(ssh.symbol != telnet.symbol)
|
|
|
|
let entries = HostMenuEntries.entries(for: host, app: app, window: window)
|
|
let connect = try #require(entries.first { $0.title == String(localized: "Connect") })
|
|
let telnetEntry = try #require(
|
|
connect.children.first { $0.title == String(localized: "with Telnet") })
|
|
#expect(telnetEntry.symbol == MenuSymbol.telnet)
|
|
#expect(
|
|
entries.map(\.title) == [
|
|
String(localized: "Quick Connect"),
|
|
String(localized: "Connect"),
|
|
String(localized: "Copy SSH Command"),
|
|
"",
|
|
String(localized: "Edit"),
|
|
])
|
|
|
|
let historyEntries = HostMenuEntries.entries(
|
|
for: host, app: app, window: window, includesCopySSHCommand: false)
|
|
#expect(!historyEntries.contains { $0.title == String(localized: "Copy SSH Command") })
|
|
}
|
|
|
|
@Test(arguments: [false, true], [false, true])
|
|
func nativeWakeSharesTheSectionBeforeTailscale(
|
|
isTailscaleManaged: Bool, includesCopySSHCommand: Bool
|
|
) throws {
|
|
var host = Host(name: "Remote", host: "remote.example.invalid")
|
|
host.wakeOnLAN.macAddress = "02:11:22:33:44:55"
|
|
if isTailscaleManaged {
|
|
host.tailscale = .init(groupID: UUID(), stableNodeID: "wake-test")
|
|
}
|
|
let app = AppModel(hosts: HostLibraryModel(hosts: [host]))
|
|
defer { app.shutdownForTermination() }
|
|
let entries = HostMenuEntries.entries(
|
|
for: host, app: app, window: WindowModel(), includesCopySSHCommand: includesCopySSHCommand)
|
|
let sections = entries.split(whereSeparator: \.isSeparator)
|
|
#expect(sections.count == 3)
|
|
let networkSection = try #require(sections.dropFirst().first)
|
|
#expect(
|
|
networkSection.map(\.title) == [
|
|
String(localized: AppStrings.LocalNetwork.wake)
|
|
] + (isTailscaleManaged ? [String(localized: AppStrings.Tailscale.title)] : []))
|
|
#expect(networkSection.first?.symbol == "power")
|
|
#expect(sections.last?.first?.symbol == MenuSymbol.edit)
|
|
}
|
|
|
|
@Test(arguments: ["", " ", "invalid", "FF:FF:FF:FF:FF:FF"], [false, true])
|
|
func nativeWakeIsHiddenWithoutAValidMAC(macAddress: String, isTailscaleManaged: Bool) {
|
|
var host = Host(name: "Remote", host: "remote.example.invalid")
|
|
host.wakeOnLAN.macAddress = macAddress
|
|
if isTailscaleManaged {
|
|
host.tailscale = .init(groupID: UUID(), stableNodeID: "wake-test")
|
|
}
|
|
let app = AppModel(hosts: HostLibraryModel(hosts: [host]))
|
|
defer { app.shutdownForTermination() }
|
|
let entries = HostMenuEntries.entries(for: host, app: app, window: WindowModel())
|
|
#expect(!entries.contains { $0.title == String(localized: AppStrings.LocalNetwork.wake) })
|
|
#expect(entries.filter(\.isSeparator).count == (isTailscaleManaged ? 2 : 1))
|
|
#expect(
|
|
entries.contains { $0.title == String(localized: AppStrings.Tailscale.title) } == isTailscaleManaged)
|
|
}
|
|
|
|
@Test
|
|
func hostConnectMenusUseTheProtocolUnionAndFilterEachActionTargets() throws {
|
|
let ssh = Host(
|
|
name: "SSH", host: "ssh.example.invalid",
|
|
sshEnabled: true, telnetEnabled: false)
|
|
let telnet = Host(
|
|
name: "Telnet", host: "telnet.example.invalid",
|
|
sshEnabled: false, telnetEnabled: true)
|
|
let both = Host(
|
|
name: "Both", host: "both.example.invalid",
|
|
sshEnabled: true, telnetEnabled: true)
|
|
let neither = Host(
|
|
name: "Neither", host: "neither.example.invalid",
|
|
sshEnabled: false, telnetEnabled: false)
|
|
|
|
#expect(HostConnectAction.available(for: [ssh]) == [.ssh, .sftp])
|
|
#expect(HostConnectAction.available(for: [telnet]) == [.telnet])
|
|
#expect(HostConnectAction.available(for: [both]) == [.ssh, .telnet, .sftp])
|
|
#expect(HostConnectAction.available(for: [neither]).isEmpty)
|
|
#expect(HostConnectAction.available(for: [ssh, telnet]) == [.ssh, .telnet, .sftp])
|
|
#expect(HostConnectAction.ssh.matchingHosts(in: [ssh, telnet, both]).map(\.id) == [ssh.id, both.id])
|
|
#expect(HostConnectAction.telnet.matchingHosts(in: [ssh, telnet, both]).map(\.id) == [telnet.id, both.id])
|
|
#expect(HostConnectAction.sftp.matchingHosts(in: [ssh, telnet, both]).map(\.id) == [ssh.id, both.id])
|
|
|
|
let app = AppModel(hosts: HostLibraryModel(hosts: [ssh, telnet, both, neither]))
|
|
let window = WindowModel()
|
|
let sshEntries = HostMenuEntries.entries(for: ssh, app: app, window: window)
|
|
let sshConnect = try #require(sshEntries.first { $0.title == String(localized: "Connect") })
|
|
#expect(sshConnect.children.map(\.title) == [String(localized: "with SSH"), String(localized: "with SFTP")])
|
|
#expect(!sshConnect.children.contains { !$0.isEnabled })
|
|
|
|
let telnetEntries = HostMenuEntries.entries(for: telnet, app: app, window: window)
|
|
let telnetConnect = try #require(telnetEntries.first { $0.title == String(localized: "Connect") })
|
|
#expect(telnetConnect.children.map(\.title) == [String(localized: "with Telnet")])
|
|
#expect(!telnetConnect.children.contains { !$0.isEnabled })
|
|
#expect(
|
|
!HostMenuEntries.entries(for: neither, app: app, window: window)
|
|
.contains { $0.title == String(localized: "Connect") }
|
|
)
|
|
}
|
|
|
|
@Test
|
|
func multiHostQuickConnectUsesOneWorkspace() throws {
|
|
let ssh = Host(name: "SSH", host: "ssh.example.invalid")
|
|
let telnet = Host(
|
|
name: "Telnet", host: "telnet.example.invalid",
|
|
sshEnabled: false, telnetEnabled: true)
|
|
let app = AppModel(hosts: HostLibraryModel(hosts: [ssh, telnet]))
|
|
let window = WindowModel()
|
|
|
|
let layout = HostMenuPolicy.quickConnectLayout(for: [ssh, telnet])
|
|
#expect(layout == .oneWorkspace)
|
|
#expect(HostMenuPolicy.quickConnectLayout(for: [ssh]) == .separateWorkspaces)
|
|
|
|
app.open([ssh, telnet], as: .preferredTerminal, layout: layout, in: window)
|
|
|
|
#expect(window.workspaces.count == 1)
|
|
#expect(window.workspaces[0].panes.map(\.hostID) == [ssh.id, telnet.id])
|
|
}
|
|
|
|
@Test(arguments: [BatchLayout.separateWorkspaces, .oneWorkspace])
|
|
func defaultOpeningUsesEachHostsTransportAndSkipsUnavailableHosts(layout: BatchLayout) throws {
|
|
let transports = HostTerminalProtocol.allCases
|
|
let hosts = transports.map { transport in
|
|
var host = Host(name: transport.rawValue, host: "server.invalid", telnetEnabled: true)
|
|
host.sshEnabled = transport != .telnet
|
|
host.terminalTransports.moshEnabled = true
|
|
host.terminalTransports.eternalTerminalEnabled = true
|
|
if transport != .telnet { host.terminalTransports.defaultTransport = transport }
|
|
return host
|
|
}
|
|
let disabled = Host(name: "Disabled", host: "disabled.invalid", sshEnabled: false)
|
|
let app = AppModel(hosts: HostLibraryModel(hosts: hosts + [disabled]))
|
|
let window = WindowModel()
|
|
defer {
|
|
window.closeAll()
|
|
app.shutdownForTermination()
|
|
}
|
|
|
|
for (host, transport) in zip(hosts, transports) {
|
|
let workspace = try #require(app.open(host, as: .preferredTerminal, in: window))
|
|
if transport == .telnet {
|
|
#expect(workspace.focusedPane.telnetSession != nil)
|
|
} else {
|
|
#expect(workspace.focusedPane.sshSession?.transport == transport)
|
|
}
|
|
}
|
|
window.closeAll()
|
|
#expect(app.open(disabled, as: .preferredTerminal, in: window) == nil)
|
|
app.open([disabled] + hosts, as: .preferredTerminal, layout: layout, in: window)
|
|
|
|
let panes = window.workspaces.flatMap(\.panes)
|
|
#expect(panes.map(\.hostID) == hosts.map(\.id))
|
|
#expect(panes.compactMap { $0.sshSession?.transport } == [.ssh, .mosh, .eternalTerminal])
|
|
#expect(panes.last?.telnetSession != nil)
|
|
#expect(window.workspaces.count == (layout == .oneWorkspace ? 1 : hosts.count))
|
|
#expect(window.selectedWorkspaceID == window.workspaces.first?.id)
|
|
#expect(app.hostLibrary.status(of: disabled.id) == .disconnected)
|
|
}
|
|
|
|
@Test(arguments: [HostTerminalProtocol.ssh, .mosh, .eternalTerminal])
|
|
func quickOpenPrioritizesAndOpensTheDefaultTransport(transport: HostTerminalProtocol) throws {
|
|
var host = Host(name: "Production", host: "server.invalid")
|
|
host.terminalTransports.moshEnabled = true
|
|
host.terminalTransports.eternalTerminalEnabled = true
|
|
host.terminalTransports.defaultTransport = transport
|
|
let app = AppModel(hosts: HostLibraryModel(hosts: [host]))
|
|
defer { app.shutdownForTermination() }
|
|
let window = WindowModel()
|
|
let results = QuickOpenResults(commands: app.commands.commands(app: app, window: window), query: host.name)
|
|
let first = try #require(results.commands.first)
|
|
|
|
#expect(first.symbol == transport.symbol)
|
|
app.commands.perform(first.target, app: app, window: window)
|
|
let session = try #require(window.selectedWorkspace?.focusedPane.sshSession)
|
|
#expect(session.hostID == host.id)
|
|
#expect(session.transport == transport)
|
|
|
|
for (target, expected) in [
|
|
(HostOpenTarget.sshSession, HostTerminalProtocol.ssh),
|
|
(.moshSession, .mosh),
|
|
(.eternalTerminalSession, .eternalTerminal),
|
|
] {
|
|
let command = try #require(results.commands.first { $0.target == .connectHost(host.id, target) })
|
|
app.commands.perform(command.target, app: app, window: window)
|
|
#expect(window.selectedWorkspace?.focusedPane.sshSession?.transport == expected)
|
|
}
|
|
}
|
|
|
|
@Test(arguments: [false, true], [false, true])
|
|
func quickOpenListsOnlyEnabledResumableTransports(moshEnabled: Bool, eternalTerminalEnabled: Bool) throws {
|
|
var host = Host(name: "Production", host: "server.invalid", port: 2222, tags: ["critical"])
|
|
host.terminalTransports.moshEnabled = moshEnabled
|
|
host.terminalTransports.eternalTerminalEnabled = eternalTerminalEnabled
|
|
host.terminalTransports.eternalTerminalPort = 3022
|
|
let app = AppModel(hosts: HostLibraryModel(hosts: [host]))
|
|
defer { app.shutdownForTermination() }
|
|
let commands = app.commands.commands(app: app, window: WindowModel()).filter { $0.section == .hosts }
|
|
|
|
#expect(commands.contains { $0.target == .connectHost(host.id, .moshSession) } == moshEnabled)
|
|
#expect(
|
|
commands.contains { $0.target == .connectHost(host.id, .eternalTerminalSession) } == eternalTerminalEnabled)
|
|
#expect(commands.contains { $0.target == .connectHost(host.id, .sftpChannel) })
|
|
#expect(Set(commands.map(\.id)).count == commands.count)
|
|
#expect(commands.allSatisfy { $0.matches("critical") })
|
|
if moshEnabled {
|
|
let mosh = try #require(commands.first { $0.target == .connectHost(host.id, .moshSession) })
|
|
#expect(mosh.symbol == MenuSymbol.mosh)
|
|
#expect(mosh.matches("Mosh"))
|
|
#expect(mosh.subtitle == String(localized: AppStrings.Connections.connectWithMosh("server.invalid:2222")))
|
|
}
|
|
if eternalTerminalEnabled {
|
|
let et = try #require(commands.first { $0.target == .connectHost(host.id, .eternalTerminalSession) })
|
|
#expect(et.symbol == MenuSymbol.eternalTerminal)
|
|
#expect(et.matches("Eternal Terminal"))
|
|
#expect(
|
|
et.subtitle
|
|
== String(localized: AppStrings.Connections.connectWithEternalTerminal("server.invalid:3022")))
|
|
}
|
|
}
|
|
|
|
@Test
|
|
func quickOpenFallsBackWhenTheDefaultTransportIsUnavailable() {
|
|
func targets(for host: Host) -> [AppCommandTarget] {
|
|
let app = AppModel(hosts: HostLibraryModel(hosts: [host]))
|
|
defer { app.shutdownForTermination() }
|
|
return app.commands.commands(app: app, window: WindowModel())
|
|
.filter { $0.section == .hosts }.map(\.target)
|
|
}
|
|
var host = Host(name: "Production", host: "server.invalid")
|
|
host.terminalTransports.defaultTransport = .mosh
|
|
|
|
#expect(targets(for: host) == [.connectHost(host.id, .sshSession), .connectHost(host.id, .sftpChannel)])
|
|
|
|
host.terminalTransports.moshEnabled = true
|
|
host.terminalTransports.eternalTerminalEnabled = true
|
|
host.tailscale = .init(groupID: UUID(), stableNodeID: "quick-open-test")
|
|
#expect(
|
|
targets(for: host) == [
|
|
.connectHost(host.id, .sshSession), .connectHost(host.id, .eternalTerminalSession),
|
|
.connectHost(host.id, .sftpChannel),
|
|
])
|
|
|
|
host.sshEnabled = false
|
|
host.telnetEnabled = true
|
|
#expect(targets(for: host) == [.connectHost(host.id, .telnetSession)])
|
|
|
|
host.telnetEnabled = false
|
|
#expect(targets(for: host).isEmpty)
|
|
}
|
|
|
|
@Test
|
|
func registryCombinesManagementWorkspacesHostsAndActions() {
|
|
let host = Host(name: "Production", host: "prod.example", tags: ["critical"])
|
|
let template = WorkspaceTemplate(
|
|
name: "Production Layout",
|
|
layout: .pane(.ssh(hostID: host.id))
|
|
)
|
|
let store = HostLibraryModel(hosts: [host], workspaceTemplates: [template])
|
|
let app = AppModel(hosts: store)
|
|
let workspace = Workspace.sshSession(host: host)
|
|
let window = WindowModel(workspaces: [workspace])
|
|
|
|
let commands = app.commands.commands(app: app, window: window)
|
|
|
|
#expect(commands.contains { $0.target == .vaultPage(.hosts) })
|
|
#expect(commands.contains { $0.target == .vault })
|
|
#expect(commands.contains { $0.target == .assistant })
|
|
#expect(commands.contains { $0.target == .settings })
|
|
#expect(commands.contains { $0.target == .workspace(workspace.id) })
|
|
#expect(commands.contains { $0.target == .workspaceTemplate(template.id) })
|
|
#expect(commands.contains { $0.target == .connectHost(host.id, .sshSession) })
|
|
#expect(commands.contains { $0.target == .newChat })
|
|
#expect(commands.contains { $0.target == .showAssistant })
|
|
#expect(commands.filter { $0.matches("critical") }.count == 2)
|
|
|
|
#expect(
|
|
commands.filter { $0.section == .navigation }.allSatisfy {
|
|
switch $0.target {
|
|
case .vaultPage, .vault, .assistant, .settings: true
|
|
default: false
|
|
}
|
|
})
|
|
#expect(
|
|
commands.filter { $0.section == .workspaces }.allSatisfy {
|
|
switch $0.target {
|
|
case .workspace, .workspaceTemplate: true
|
|
default: false
|
|
}
|
|
})
|
|
#expect(commands.filter { $0.section == .conversations }.isEmpty)
|
|
#expect(
|
|
commands.filter { $0.section == .hosts }.allSatisfy {
|
|
if case .connectHost = $0.target { true } else { false }
|
|
})
|
|
#expect(
|
|
commands.filter { $0.section == .actions }.allSatisfy {
|
|
switch $0.target {
|
|
case .newChat, .newHost, .showAssistant, .newWindow, .moveWorkspaceToNewWindow: true
|
|
default: false
|
|
}
|
|
})
|
|
}
|
|
|
|
@Test
|
|
func registryListsSearchesAndOpensWorkspaceTemplates() throws {
|
|
let production = Host(name: "Production API", host: "prod.example")
|
|
let staging = Host(name: "Staging Database", host: "staging.example")
|
|
let fleetID = UUID(uuidString: "00000000-0000-0000-0000-000000000002")!
|
|
let adminID = UUID(uuidString: "00000000-0000-0000-0000-000000000001")!
|
|
let fleet = WorkspaceTemplate(
|
|
id: fleetID,
|
|
name: "Fleet",
|
|
layout: .split(
|
|
axis: .horizontal,
|
|
fraction: 0.5,
|
|
first: .pane(.ssh(hostID: production.id)),
|
|
second: .pane(.sftp(hostID: staging.id))
|
|
)
|
|
)
|
|
let admin = WorkspaceTemplate(
|
|
id: adminID,
|
|
name: "Admin",
|
|
layout: .pane(.empty)
|
|
)
|
|
let store = HostLibraryModel(
|
|
hosts: [production, staging], workspaceTemplates: [fleet, admin]
|
|
)
|
|
let app = AppModel(hosts: store)
|
|
let window = WindowModel()
|
|
window.hostStore = store
|
|
|
|
let templateCommands = app.commands.commands(app: app, window: window).filter {
|
|
if case .workspaceTemplate = $0.target { true } else { false }
|
|
}
|
|
|
|
#expect(templateCommands.map(\.title) == ["Admin", "Fleet"])
|
|
#expect(
|
|
templateCommands.map(\.id) == [
|
|
"workspace-template.\(adminID.uuidString)",
|
|
"workspace-template.\(fleetID.uuidString)",
|
|
])
|
|
#expect(Set(templateCommands.map(\.id)).count == templateCommands.count)
|
|
#expect(templateCommands.allSatisfy { $0.subtitle == "Workspace Template" })
|
|
#expect(templateCommands.allSatisfy { $0.symbol == "rectangle.split.2x1" })
|
|
#expect(
|
|
QuickOpenResults(commands: templateCommands, query: "staging database")
|
|
.commands.map(\.target) == [.workspaceTemplate(fleet.id)]
|
|
)
|
|
|
|
app.commands.perform(.workspaceTemplate(fleet.id), app: app, window: window)
|
|
|
|
let opened = try #require(window.selectedWorkspace)
|
|
#expect(opened.sourceTemplateID == fleet.id)
|
|
#expect(opened.panes.map(\.kind) == [.sshSession, .sftpChannel])
|
|
}
|
|
|
|
@Test
|
|
func registryIncludesActiveConversationsInLibraryOrderAndSearchesVisibleContent() throws {
|
|
let pinned = ConversationModel(
|
|
messages: [
|
|
ChatMessage(role: .assistant, text: "The nginx upstream is healthy.")
|
|
], sortIndex: 20)
|
|
pinned.customTitle = "Pinned deployment"
|
|
pinned.isPinned = true
|
|
|
|
let firstChat = ConversationModel(
|
|
messages: [
|
|
ChatMessage(role: .user, text: "Inspect the database vacuum schedule")
|
|
], sortIndex: 1)
|
|
firstChat.customTitle = "Database maintenance"
|
|
|
|
let secondChat = ConversationModel(
|
|
messages: [
|
|
ChatMessage(
|
|
role: .assistant, text: "The proxy is ready.",
|
|
reasoning: [AgentReasoningBlock(text: "hidden-token")]
|
|
)
|
|
], sortIndex: 2)
|
|
secondChat.customTitle = "NGINX rollout"
|
|
|
|
let archived = ConversationModel(
|
|
messages: [
|
|
ChatMessage(role: .user, text: "Archived nginx notes")
|
|
], sortIndex: 0)
|
|
archived.customTitle = "Archived nginx chat"
|
|
archived.archivedAt = .now
|
|
|
|
let library = AssistantLibraryModel(
|
|
store: AssistantConversationStore(
|
|
conversations: [secondChat, archived, pinned, firstChat]
|
|
))
|
|
let app = AppModel(hosts: HostLibraryModel())
|
|
let window = WindowModel(assistantLibrary: library)
|
|
|
|
let conversationCommands = app.commands.commands(app: app, window: window).filter {
|
|
$0.section == .conversations
|
|
}
|
|
|
|
#expect(
|
|
conversationCommands.map(\.title) == [
|
|
"Pinned deployment", "Database maintenance", "NGINX rollout",
|
|
])
|
|
#expect(Set(conversationCommands.map(\.id)).count == conversationCommands.count)
|
|
#expect(
|
|
conversationCommands.map(\.target) == [
|
|
.assistantConversation(pinned.id),
|
|
.assistantConversation(firstChat.id),
|
|
.assistantConversation(secondChat.id),
|
|
])
|
|
#expect(
|
|
!conversationCommands.contains { command in
|
|
command.target == .assistantConversation(archived.id)
|
|
})
|
|
|
|
let nginx = QuickOpenResults(commands: conversationCommands, query: "nginx")
|
|
#expect(
|
|
nginx.commands.map(\.target) == [
|
|
.assistantConversation(pinned.id),
|
|
.assistantConversation(secondChat.id),
|
|
])
|
|
#expect(QuickOpenResults(commands: conversationCommands, query: "NGINX").commands == nginx.commands)
|
|
#expect(QuickOpenResults(commands: conversationCommands, query: "hidden-token").commands.isEmpty)
|
|
}
|
|
|
|
@Test
|
|
func resultsGroupMatchesAndKeyboardSelectionSkipsHeaders() throws {
|
|
let host = Host(name: "Production", host: "prod.example", tags: ["critical"])
|
|
let store = HostLibraryModel(hosts: [host])
|
|
let app = AppModel(hosts: store)
|
|
let workspace = Workspace.sshSession(host: host)
|
|
let window = WindowModel(workspaces: [workspace])
|
|
let commands = app.commands.commands(app: app, window: window)
|
|
|
|
let results = QuickOpenResults(commands: commands, query: "")
|
|
#expect(results.sections == [.navigation, .workspaces, .hosts, .actions])
|
|
#expect(sectionItems(in: results.listItems(selectedID: nil)) == results.sections)
|
|
|
|
let first = try #require(results.commands.first)
|
|
let second = try #require(results.commands.dropFirst().first)
|
|
let last = try #require(results.commands.last)
|
|
#expect(results.selection(movingFrom: nil, by: 1) == first.id)
|
|
#expect(results.selection(movingFrom: first.id, by: -1) == first.id)
|
|
#expect(results.selection(movingFrom: first.id, by: 1) == second.id)
|
|
#expect(results.selection(movingFrom: last.id, by: 1) == last.id)
|
|
|
|
let filtered = QuickOpenResults(commands: commands, query: "critical")
|
|
#expect(filtered.sections == [.hosts])
|
|
#expect(filtered.commands.count == 2)
|
|
#expect(
|
|
sectionItems(in: filtered.listItems(selectedID: filtered.commands.first?.id)) == [
|
|
.hosts
|
|
])
|
|
#expect(filtered.listItems(selectedID: filtered.commands.first?.id).filter(\.isSelected).count == 1)
|
|
}
|
|
|
|
@Test
|
|
func registryRoutesWithoutDuplicatingNavigationLogic() {
|
|
let store = HostLibraryModel()
|
|
let app = AppModel(hosts: store)
|
|
let window = WindowModel()
|
|
|
|
#expect(
|
|
app.commands.commands(app: app, window: window).contains {
|
|
$0.target == .showAssistant
|
|
}
|
|
)
|
|
|
|
app.commands.perform(.settings, app: app, window: window)
|
|
#expect(window.selectedDestination == .settings)
|
|
#expect(window.settingsRoute == .index)
|
|
#expect(window.workspaces.isEmpty)
|
|
|
|
app.commands.perform(.showAssistant, app: app, window: window)
|
|
#expect(window.selectedDestination == .assistant)
|
|
|
|
app.commands.perform(.newChat, app: app, window: window)
|
|
#expect(window.selectedDestination == .assistant)
|
|
#expect(window.workspaces.isEmpty)
|
|
#expect(window.assistantLibrary.conversations.isEmpty)
|
|
#expect(window.assistantLibrary.selectedConversationID == nil)
|
|
}
|
|
|
|
@Test
|
|
func registryOpensConversationInMainAssistantAndLeavesArchive() {
|
|
let conversation = ConversationModel(messages: [
|
|
ChatMessage(role: .user, text: "Review deployment")
|
|
])
|
|
let library = AssistantLibraryModel(
|
|
store: AssistantConversationStore(
|
|
conversations: [conversation]
|
|
))
|
|
let app = AppModel(hosts: HostLibraryModel())
|
|
let window = WindowModel(assistantLibrary: library)
|
|
window.showAssistantArchive()
|
|
|
|
app.commands.perform(
|
|
.assistantConversation(conversation.id), app: app, window: window
|
|
)
|
|
|
|
#expect(window.selectedDestination == .assistant)
|
|
#expect(window.assistantLibrary.selectedConversationID == conversation.id)
|
|
#expect(!window.isAssistantArchivePresented)
|
|
}
|
|
|
|
private func sectionItems(in items: [QuickOpenListItem]) -> [AppCommandSection] {
|
|
items.compactMap { item in
|
|
guard case .section(let section) = item else { return nil }
|
|
return section
|
|
}
|
|
}
|
|
}
|