Files
Termind/TFMTests/ChatSlashCommandTests.swift
T
Kaede Akino 45e84f28bb test: remove redundant coverage and consolidate duplicated suites
An adversarial audit of the three test targets rejected most pruning
candidates: 110 of 183 proposed findings either cited files that do not
exist, mistook architectural guards for tautologies, or treated tests
that drive distinct production branches as duplicates.

Delete tests that pinned no behavior beyond a fixture literal, an
initializer assignment, or an assertion that could not fail, and drop
the unused pluralForms helper. Merge the remaining duplicates into
parameterized tests so each branch keeps its own assertion and failure
localization.
2026-10-17 20:19:44 +08:00

279 lines
9.6 KiB
Swift

import AgentKit
import Foundation
import TermindApplication
import Testing
@Suite
struct ChatSlashCommandTests {
private let commands = [
ChatSlashCommand(
id: "compact", displayName: "Compact",
summary: "Summarize older turns", icon: .contextRing,
argumentHint: "instructions"
),
ChatSlashCommand(
id: "plan", displayName: "Plan mode",
summary: "Research first", icon: .symbol("sparkles")
),
ChatSlashCommand(
id: "reasoning", displayName: "Reasoning",
summary: "Thinking effort", icon: .symbol("brain")
),
]
private func options(_ command: ChatSlashCommand) -> [ChatSlashCommand.Option] {
guard command.id == "reasoning" else { return [] }
return [
ChatSlashCommand.Option(id: "low", label: "Low"),
ChatSlashCommand.Option(id: "medium", label: "Medium"),
ChatSlashCommand.Option(id: "high", label: "High"),
]
}
// MARK: - Parsing
@Test
func aBareSlashIsACommandBeingNamed() {
#expect(ChatSlashCommandInput.parse("/") == .naming(leadingText: "", partial: ""))
#expect(ChatSlashCommandInput.parse("/comp") == .naming(leadingText: "", partial: "comp"))
}
@Test
func aSpaceSeparatesTheNameFromItsArgument() {
#expect(
ChatSlashCommandInput.parse("/compact keep the nginx findings")
== .argument(leadingText: "", name: "compact", text: "keep the nginx findings")
)
// The trailing space is what advances the palette to the options, so an
// empty argument has to be distinguishable from no argument at all.
#expect(
ChatSlashCommandInput.parse("/reasoning ")
== .argument(leadingText: "", name: "reasoning", text: "")
)
}
@Test
func textWithoutASlashIsNotACommand() {
#expect(ChatSlashCommandInput.parse("what does df say") == nil)
#expect(ChatSlashCommandInput.parse("") == nil)
}
@Test
func theLastSlashCanOpenCommandsAnywhereInTheDraft() {
#expect(
ChatSlashCommandInput.parse("check the service /comp")
== .naming(leadingText: "check the service ", partial: "comp")
)
#expect(
ChatSlashCommandInput.parse("first /plan then /reasoning hi")
== .argument(leadingText: "first /plan then ", name: "reasoning", text: "hi")
)
}
/// A path pasted as the whole draft still parses as a name — it is one
/// token beginning with a slash — but it matches no command, so the palette
/// stays shut and the draft sends as a message.
@Test
func aPastedPathMatchesNoCommandAndSendsAsAMessage() {
let completion = ChatSlashCommandCompletion.rows(
draft: "/etc/nginx/nginx.conf", commands: commands, options: options
)
#expect(completion.isEmpty)
}
@Test
func aCommandFragmentCannotCrossALineButCanStartOnTheLastLine() {
#expect(ChatSlashCommandInput.parse("/compact\nand also check disk") == nil)
#expect(
ChatSlashCommandInput.parse("check disk\nthen /comp")
== .naming(leadingText: "check disk\nthen ", partial: "comp")
)
}
// MARK: - Completion
@Test
func displayNamesDoNotChangeTheTypedCommands() {
#expect(commands[1].name == "/plan")
#expect(
ChatSlashCommandCompletion.accepted(
.command(commands[1]), takesArgument: false, in: "/pl"
) == "/plan"
)
}
@Test
func anEmptyNameOffersEveryCommand() {
let completion = ChatSlashCommandCompletion.rows(
draft: "/", commands: commands, options: options
)
#expect(completion.rows.count == 3)
}
@Test
func typingNarrowsTheListAndThenClosesIt() {
#expect(
ChatSlashCommandCompletion.rows(
draft: "/co", commands: commands, options: options
).rows.count == 1
)
#expect(
ChatSlashCommandCompletion.rows(
draft: "/zzz", commands: commands, options: options
).isEmpty
)
}
@Test
func aCompletedNameOffersItsOptions() {
let completion = ChatSlashCommandCompletion.rows(
draft: "/reasoning ", commands: commands, options: options
)
#expect(completion.rows.count == 3)
#expect(completion.rows.first?.id == "reasoning/low")
guard let first = completion.rows.first,
case .option(let command, _) = first
else {
Issue.record("Expected an option row")
return
}
#expect(command.icon == .symbol("brain"))
}
@Test
func anArgumentFiltersTheOptions() {
let completion = ChatSlashCommandCompletion.rows(
draft: "/reasoning hi", commands: commands, options: options
)
#expect(completion.rows.count == 1)
#expect(completion.rows.first?.id == "reasoning/high")
}
@Test
func anOptionPageCanSelectItsCurrentValue() {
let completion = ChatSlashCommandCompletion.rows(
draft: "/reasoning ", commands: commands, options: options
)
#expect(completion.selectionIndex(commandID: "reasoning", optionID: "medium") == 1)
}
@Test
func preferredOptionMatchingUsesTheStableIDNotTheVisibleLabel() {
let model = ChatSlashCommand(
id: "model", displayName: "Model", summary: "Switch models",
icon: .symbol("sparkles")
)
let providerA = "11111111-1111-1111-1111-111111111111:same-model"
let providerB = "22222222-2222-2222-2222-222222222222:same-model"
let completion = ChatSlashCommandCompletion.rows(
draft: "/model ", commands: [model]
) { _ in
[
ChatSlashCommand.Option(id: providerA, label: "Same model"),
ChatSlashCommand.Option(id: providerB, label: "Same model"),
]
}
#expect(completion.selectionIndex(commandID: "model", optionID: providerB) == 1)
}
@Test
func opaqueArgumentsDoNotIntroduceANewerSlashForPathLikeModelIDs() {
let argument = ChatSlashCommand.argumentID(
scope: UUID().uuidString.lowercased(), value: "vendor/model-preview"
)
#expect(!argument.contains("/"))
#expect(
ChatSlashCommandInput.parse("draft /model \(argument)")?.name == "model"
)
}
@Test
func anUnavailableOrFilteredPreferredOptionFallsBackToTheFirstRow() {
let completion = ChatSlashCommandCompletion.rows(
draft: "/reasoning hi", commands: commands, options: options
)
#expect(completion.selectionIndex(commandID: "reasoning", optionID: "medium") == 0)
#expect(completion.selectionIndex(commandID: "model", optionID: "high") == 0)
}
/// `/compact` takes free text, not a menu. Once the name is complete the
/// palette gets out of the way so the instructions can be typed.
@Test
func aFreeTextCommandClosesThePaletteAfterItsName() {
let completion = ChatSlashCommandCompletion.rows(
draft: "/compact drop the log dumps", commands: commands, options: options
)
#expect(completion.isEmpty)
}
// MARK: - Acceptance
@Test
func acceptingANameThatTakesAnArgumentLeavesATrailingSpace() {
let row = ChatSlashCommandCompletion.Row.command(commands[2])
#expect(ChatSlashCommandCompletion.accepted(row, takesArgument: true, in: "/rea") == "/reasoning ")
#expect(ChatSlashCommandCompletion.accepted(row, takesArgument: false, in: "/rea") == "/reasoning")
}
@Test
func acceptingAnOptionWritesTheWholeCommand() {
let row = ChatSlashCommandCompletion.Row.option(
commands[2], ChatSlashCommand.Option(id: "high", label: "High")
)
#expect(
ChatSlashCommandCompletion.accepted(row, takesArgument: false, in: "/reasoning h")
== "/reasoning high"
)
}
@Test
func acceptingACompletionOnlyReplacesTheTrailingSlashFragment() {
let row = ChatSlashCommandCompletion.Row.option(
commands[2], ChatSlashCommand.Option(id: "high", label: "High")
)
#expect(
ChatSlashCommandCompletion.accepted(
row, takesArgument: false, in: "keep this prompt /reasoning h"
) == "keep this prompt /reasoning high"
)
#expect(ChatSlashCommandInput.parse("keep this prompt /reasoning high")?.leadingText == "keep this prompt ")
}
// MARK: - Named arguments
/// `/skill <name>` arms it, `/skill <name> <text>` arms it and asks in the
/// same keystroke, and the two have to be told apart from one draft.
@Test
func aNamedArgumentSplitsTheNameFromWhatFollowsIt() {
let armed = ChatSlashCommandInput.parse("/skill disk-triage")
#expect(armed?.selection?.name == "disk-triage")
#expect(armed?.selection?.remainder == "")
let asked = ChatSlashCommandInput.parse("/skill disk-triage why is root full?")
#expect(asked?.selection?.name == "disk-triage")
#expect(asked?.selection?.remainder == "why is root full?")
let padded = ChatSlashCommandInput.parse("/skill disk-triage check staging ")
#expect(padded?.selection?.name == "disk-triage")
#expect(padded?.selection?.remainder == "check staging")
}
@Test
func aCommandWithNothingAfterItNamesNothing() {
#expect(ChatSlashCommandInput.parse("/skill")?.selection == nil)
#expect(ChatSlashCommandInput.parse("/skill ")?.selection == nil)
}
}