| 1 | import XCTest |
| 2 | @testable import LChatCore |
| 3 | |
| 4 | // π€ The native slash router β /me, /theme, /themes, /help, and fallthrough. |
| 5 | final class SlashCommandsTests: XCTestCase { |
| 6 | func testMe() { |
| 7 | XCTAssertEqual(SlashCommand.parse("/me waves", meName: "Paul"), .me("Paul waves")) |
| 8 | XCTAssertEqual(SlashCommand.parse("/me", meName: "Paul"), .sendAsMessage) // bare /me |
| 9 | } |
| 10 | func testTheme() { |
| 11 | XCTAssertEqual(SlashCommand.parse("/theme lagoon", meName: "P"), .setTheme("lagoon")) |
| 12 | XCTAssertEqual(SlashCommand.parse("/theme Lit Room", meName: "P"), .setTheme("litroom")) |
| 13 | XCTAssertEqual(SlashCommand.parse("/theme nonsense", meName: "P"), .openThemes) |
| 14 | XCTAssertEqual(SlashCommand.parse("/theme", meName: "P"), .openThemes) |
| 15 | XCTAssertEqual(SlashCommand.parse("/themes", meName: "P"), .openThemes) |
| 16 | } |
| 17 | func testHelpAndFallthrough() { |
| 18 | XCTAssertEqual(SlashCommand.parse("/help", meName: "P"), .help) |
| 19 | XCTAssertEqual(SlashCommand.parse("/dark", meName: "P"), .sendAsMessage) // bot-owned |
| 20 | XCTAssertEqual(SlashCommand.parse("/profile", meName: "P"), .sendAsMessage) |
| 21 | XCTAssertEqual(SlashCommand.parse("hello", meName: "P"), .sendAsMessage) |
| 22 | XCTAssertEqual(SlashCommand.parse("/", meName: "P"), .sendAsMessage) // lone slash |
| 23 | } |
| 24 | func testLoneSlashIsMessage() { |
| 25 | XCTAssertEqual(SlashCommand.parse("/ hey", meName: "P"), .sendAsMessage) |
| 26 | } |
| 27 | } |
| 28 | |
| 29 | // π€ WebPanelCommands β the commands the native shell forwards to its |
| 30 | // per-thread command-host webview (the web command engine) instead of |
| 31 | // handling natively or sending as a message. |
| 32 | final class WebPanelCommandsTests: XCTestCase { |
| 33 | func testOwnsMatchesTheFirstToken() { |
| 34 | XCTAssertTrue(WebPanelCommands.owns("/emoji")) |
| 35 | XCTAssertTrue(WebPanelCommands.owns("/emoji cat")) |
| 36 | XCTAssertTrue(WebPanelCommands.owns(" /EMOJI heart ")) // trimmed + case-insensitive |
| 37 | XCTAssertTrue(WebPanelCommands.owns("/gif office party")) |
| 38 | } |
| 39 | func testOwnsRejectsEverythingElse() { |
| 40 | XCTAssertFalse(WebPanelCommands.owns("/emojis cat")) // whole-token match only |
| 41 | XCTAssertFalse(WebPanelCommands.owns("/me waves")) // native-handled |
| 42 | XCTAssertFalse(WebPanelCommands.owns("/todo buy milk"))// not in the pre-rows fallback floor |
| 43 | XCTAssertFalse(WebPanelCommands.owns("emoji")) // no slash β plain text |
| 44 | XCTAssertFalse(WebPanelCommands.owns("")) |
| 45 | } |
| 46 | func testCatalogFeedsTheTypeahead() { |
| 47 | XCTAssertTrue(WebPanelCommands.catalog.contains { $0.name == "/emoji" && !$0.desc.isEmpty }) |
| 48 | } |
| 49 | } |
| 50 | |
| 51 | // π€ The phase-3 forwarding decision (docs/native-apps.md): with a thread's |
| 52 | // live commandItems() rows, every framework-owned command forwards to the |
| 53 | // command host β the needKey/needAppThread bridge lifted the old "can't route |
| 54 | // to another thread" cap β while bot-answered rows still ride to the |
| 55 | // conversation. Before rows land, the historical /emoji//gif floor stands. |
| 56 | final class CommandRoutingTests: XCTestCase { |
| 57 | private let rows = [ |
| 58 | CommandRow(name: "/todo", desc: "add a to-do", bot: false), |
| 59 | CommandRow(name: "/emoji", desc: "browse emoji", bot: false), |
| 60 | CommandRow(name: "/help", desc: "what I can do", bot: true), |
| 61 | ] |
| 62 | |
| 63 | func testFrameworkCommandsForwardWithRows() { |
| 64 | // The lifted constraint: /todo typed anywhere forwards now. |
| 65 | XCTAssertEqual(CommandRouting.route("/todo buy milk", rows: rows), .host) |
| 66 | XCTAssertEqual(CommandRouting.route("/emoji cat", rows: rows), .host) |
| 67 | XCTAssertEqual(CommandRouting.route(" /TODO x ", rows: rows), .host) // trim + case |
| 68 | } |
| 69 | |
| 70 | func testBotAnsweredCommandsRideToTheConversation() { |
| 71 | XCTAssertEqual(CommandRouting.route("/help", rows: rows), .message) |
| 72 | } |
| 73 | |
| 74 | func testUnknownCommandsForwardForTheHonestNotice() { |
| 75 | // The web engine answers "Unknown command β try /help" privately |
| 76 | // instead of posting the literal into the chat. |
| 77 | XCTAssertEqual(CommandRouting.route("/typo huh", rows: rows), .host) |
| 78 | } |
| 79 | |
| 80 | func testPlainTextIsNeverACommand() { |
| 81 | XCTAssertEqual(CommandRouting.route("hello", rows: rows), .message) |
| 82 | XCTAssertEqual(CommandRouting.route("/", rows: rows), .message) |
| 83 | } |
| 84 | |
| 85 | func testPreRowsFloorIsTheHistoricalWhitelist() { |
| 86 | XCTAssertEqual(CommandRouting.route("/emoji cat", rows: []), .host) |
| 87 | XCTAssertEqual(CommandRouting.route("/gif office", rows: []), .host) |
| 88 | XCTAssertEqual(CommandRouting.route("/todo buy milk", rows: []), .message) |
| 89 | } |
| 90 | } |
| 91 | |