import XCTest @testable import LChatCore // 🤖 The native slash router — /me, /theme, /themes, /help, and fallthrough. final class SlashCommandsTests: XCTestCase { func testMe() { XCTAssertEqual(SlashCommand.parse("/me waves", meName: "Paul"), .me("Paul waves")) XCTAssertEqual(SlashCommand.parse("/me", meName: "Paul"), .sendAsMessage) // bare /me } func testTheme() { XCTAssertEqual(SlashCommand.parse("/theme lagoon", meName: "P"), .setTheme("lagoon")) XCTAssertEqual(SlashCommand.parse("/theme Lit Room", meName: "P"), .setTheme("litroom")) XCTAssertEqual(SlashCommand.parse("/theme nonsense", meName: "P"), .openThemes) XCTAssertEqual(SlashCommand.parse("/theme", meName: "P"), .openThemes) XCTAssertEqual(SlashCommand.parse("/themes", meName: "P"), .openThemes) } func testHelpAndFallthrough() { XCTAssertEqual(SlashCommand.parse("/help", meName: "P"), .help) XCTAssertEqual(SlashCommand.parse("/dark", meName: "P"), .sendAsMessage) // bot-owned XCTAssertEqual(SlashCommand.parse("/profile", meName: "P"), .sendAsMessage) XCTAssertEqual(SlashCommand.parse("hello", meName: "P"), .sendAsMessage) XCTAssertEqual(SlashCommand.parse("/", meName: "P"), .sendAsMessage) // lone slash } func testLoneSlashIsMessage() { XCTAssertEqual(SlashCommand.parse("/ hey", meName: "P"), .sendAsMessage) } } // 🤖 WebPanelCommands — the commands the native shell forwards to its // per-thread command-host webview (the web command engine) instead of // handling natively or sending as a message. final class WebPanelCommandsTests: XCTestCase { func testOwnsMatchesTheFirstToken() { XCTAssertTrue(WebPanelCommands.owns("/emoji")) XCTAssertTrue(WebPanelCommands.owns("/emoji cat")) XCTAssertTrue(WebPanelCommands.owns(" /EMOJI heart ")) // trimmed + case-insensitive XCTAssertTrue(WebPanelCommands.owns("/gif office party")) } func testOwnsRejectsEverythingElse() { XCTAssertFalse(WebPanelCommands.owns("/emojis cat")) // whole-token match only XCTAssertFalse(WebPanelCommands.owns("/me waves")) // native-handled XCTAssertFalse(WebPanelCommands.owns("/todo buy milk"))// not in the pre-rows fallback floor XCTAssertFalse(WebPanelCommands.owns("emoji")) // no slash → plain text XCTAssertFalse(WebPanelCommands.owns("")) } func testCatalogFeedsTheTypeahead() { XCTAssertTrue(WebPanelCommands.catalog.contains { $0.name == "/emoji" && !$0.desc.isEmpty }) } } // 🤖 The phase-3 forwarding decision (docs/native-apps.md): with a thread's // live commandItems() rows, every framework-owned command forwards to the // command host — the needKey/needAppThread bridge lifted the old "can't route // to another thread" cap — while bot-answered rows still ride to the // conversation. Before rows land, the historical /emoji//gif floor stands. final class CommandRoutingTests: XCTestCase { private let rows = [ CommandRow(name: "/todo", desc: "add a to-do", bot: false), CommandRow(name: "/emoji", desc: "browse emoji", bot: false), CommandRow(name: "/help", desc: "what I can do", bot: true), ] func testFrameworkCommandsForwardWithRows() { // The lifted constraint: /todo typed anywhere forwards now. XCTAssertEqual(CommandRouting.route("/todo buy milk", rows: rows), .host) XCTAssertEqual(CommandRouting.route("/emoji cat", rows: rows), .host) XCTAssertEqual(CommandRouting.route(" /TODO x ", rows: rows), .host) // trim + case } func testBotAnsweredCommandsRideToTheConversation() { XCTAssertEqual(CommandRouting.route("/help", rows: rows), .message) } func testUnknownCommandsForwardForTheHonestNotice() { // The web engine answers "Unknown command — try /help" privately // instead of posting the literal into the chat. XCTAssertEqual(CommandRouting.route("/typo huh", rows: rows), .host) } func testPlainTextIsNeverACommand() { XCTAssertEqual(CommandRouting.route("hello", rows: rows), .message) XCTAssertEqual(CommandRouting.route("/", rows: rows), .message) } func testPreRowsFloorIsTheHistoricalWhitelist() { XCTAssertEqual(CommandRouting.route("/emoji cat", rows: []), .host) XCTAssertEqual(CommandRouting.route("/gif office", rows: []), .host) XCTAssertEqual(CommandRouting.route("/todo buy milk", rows: []), .message) } }