Files
hapi/ios/Packages/HapiKit/Tests/HapiClientTests/Chat/SlashCommandSuggestionsTests.swift
T
weishuandClaude Opus 5.5 f7722fcd65 feat(ios): suggest slash commands in the composer
Typing / in the iOS composer offered nothing; web and Android show the
session's commands. While the input is a lone /token, a list above the
card shows matching commands. A tap inserts "/name " so the user can
add arguments.

As on Android, the sources are metadata.slashCommands plus the
/slash-commands RPC. The RPC is fetched once, when the input first
enters slash mode, and its entries override bare names. A failed fetch
retries on the next /. There is no copy of the shared built-in table,
so the CLI stays the source of truth. Ranking follows web: exact,
prefix, contains, then near-miss typos. The list is hidden in
Scratchlist mode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 18:00:00 +08:00

40 lines
1.9 KiB
Swift

import Foundation
@testable import HapiClient
import HapiProtocol
import Testing
/// Composer `/` autocomplete: the Android `SlashCommandsTest` cases plus the
/// web Levenshtein tail (`useSlashCommands`).
@Suite("Slash command suggestions")
struct SlashCommandSuggestionsTests {
@Test func onlyALoneLeadingTokenOpensTheMenu() {
#expect(SlashCommandSuggestions.query(of: "/") == "")
#expect(SlashCommandSuggestions.query(of: "/co") == "co")
#expect(SlashCommandSuggestions.query(of: "/foo:bar") == "foo:bar")
#expect(SlashCommandSuggestions.query(of: "/compact now") == nil)
#expect(SlashCommandSuggestions.query(of: "hi /co") == nil)
#expect(SlashCommandSuggestions.query(of: "") == nil)
}
@Test func rpcEntriesOverrideMetadataNamesAndTakeTheLaterPosition() {
let merged = SlashCommandSuggestions.merge(
metadataNames: ["/deploy", "Compact", " "],
fetched: [SlashCommand(name: "compact", description: "Summarize", source: "builtin")]
)
#expect(merged.map(\.name) == ["deploy", "compact"])
#expect(merged.last?.description == "Summarize")
#expect(merged.first?.source == "session")
}
@Test func rankingIsExactPrefixContainsThenCloseTypos() {
let commands = ["review", "compact", "context", "co", "status", "plan"].map {
SlashCommand(name: $0, source: "builtin")
}
#expect(SlashCommandSuggestions.filter(commands, query: "co").map(\.name) == ["co", "compact", "context"])
#expect(SlashCommandSuggestions.filter(commands, query: "CONT").map(\.name) == ["context", "co"])
#expect(SlashCommandSuggestions.filter(commands, query: "stauts").map(\.name) == ["status"])
#expect(SlashCommandSuggestions.filter(commands, query: "").count == 6)
#expect(SlashCommandSuggestions.levenshtein("kitten", "sitting") == 3)
}
}