diff --git a/app/services/commands/alias/add.rb b/app/services/commands/alias/add.rb new file mode 100644 index 00000000..a5145e48 --- /dev/null +++ b/app/services/commands/alias/add.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module Commands + module Alias + class Add < Base + argument shortcut: 0 + argument command: (1..) + + # Determine if the argument matches this command. + # + # @return [Boolean] + def self.match?(arguments) + arguments.first == I18n.t("commands.lookup.alias.arguments.add") + end + + private + + # Returns the command, ensuring a forward slash is present. + # + # @return [String] + def command + value = parameters[:command] + + unless value.start_with?("/") + value = "/#{value}" + end + + value + end + + # Return the handler for a successful command execution. + # + # @return [Success] + def success + Success.new(character: character, command: command, shortcut: parameters[:shortcut]) + end + + # Validate if the command is valid. + # + # @return [Boolean] If the alias exists or not. + def validate_command + parsed = Command::Parser.call(command) + + if parsed == Commands::Unknown + InvalidCommand.new(command: command) + end + end + end + end +end diff --git a/app/services/commands/alias/add/invalid_command.rb b/app/services/commands/alias/add/invalid_command.rb new file mode 100644 index 00000000..687625c2 --- /dev/null +++ b/app/services/commands/alias/add/invalid_command.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Commands + module Alias + class Add < Base + class InvalidCommand < Result + locals :command + + # Initialize an alias add invalid command result. + # + # @return [void] + def initialize(command:) + @command = command + end + end + end + end +end diff --git a/app/services/commands/alias/add/success.rb b/app/services/commands/alias/add/success.rb new file mode 100644 index 00000000..64813c38 --- /dev/null +++ b/app/services/commands/alias/add/success.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Commands + module Alias + class Add < Base + class Success < Result + locals :account, :command, :shortcut + + # Initialize an alias add success result. + # + # @return [void] + def initialize(character:, command:, shortcut:) + @character = character + @command = command + @shortcut = shortcut + end + + # Add the alias to the character's account. + # + # @return [void] + def call + account.update!(aliases: account.aliases.merge(shortcut => command)) + end + + private + + attr_reader :character, :command, :shortcut + + delegate :account, to: :character + end + end + end +end diff --git a/app/views/commands/alias/add/_invalid_command.html.erb b/app/views/commands/alias/add/_invalid_command.html.erb new file mode 100644 index 00000000..a07109d7 --- /dev/null +++ b/app/views/commands/alias/add/_invalid_command.html.erb @@ -0,0 +1,7 @@ +<%# locals: (command:) %> + + + + <%= t(".message", command: command) %> + + diff --git a/app/views/commands/alias/add/_invalid_command.turbo_stream.erb b/app/views/commands/alias/add/_invalid_command.turbo_stream.erb new file mode 100644 index 00000000..6c717a0c --- /dev/null +++ b/app/views/commands/alias/add/_invalid_command.turbo_stream.erb @@ -0,0 +1,2 @@ +<%# locals: (command:) %> +<%= turbo_stream.append("messages", partial: "commands/alias/add/invalid_command", locals: { command: command }) %> diff --git a/app/views/commands/alias/add/_success.html.erb b/app/views/commands/alias/add/_success.html.erb new file mode 100644 index 00000000..1ec6427b --- /dev/null +++ b/app/views/commands/alias/add/_success.html.erb @@ -0,0 +1,9 @@ +<%# locals: (account:, command:, shortcut:) %> + + + + <%= t(".message", command: command, shortcut: shortcut) %> + + + + diff --git a/app/views/commands/alias/add/_success.turbo_stream.erb b/app/views/commands/alias/add/_success.turbo_stream.erb new file mode 100644 index 00000000..b930d7e4 --- /dev/null +++ b/app/views/commands/alias/add/_success.turbo_stream.erb @@ -0,0 +1,2 @@ +<%# locals: (account:, command:, shortcut:) %> +<%= turbo_stream.append("messages", partial: "commands/alias/add/success", locals: { account: account, command: command, shortcut: shortcut }) %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 5f17b3ab..b7f40543 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -75,6 +75,11 @@ en: commands: alias: + add: + invalid_command: + message: "There is no \"%{command}\" command." + success: + message: "The alias \"%{shortcut}\" has been added for the \"%{command}\" command." list: success: header: @@ -147,6 +152,7 @@ en: alias: name: "alias" arguments: + add: "add" list: "list" remove: "remove" show: "show" diff --git a/spec/features/commands/alias/add_spec.rb b/spec/features/commands/alias/add_spec.rb new file mode 100644 index 00000000..b5e2016a --- /dev/null +++ b/spec/features/commands/alias/add_spec.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +require "rails_helper" + +describe "Sending the alias add command", :js do + let(:character) { create(:character) } + let(:command) { "/alias add #{shortcut} #{name}" } + let(:name) { "/emote" } + let(:shortcut) { "/e" } + + before do + sign_in_as_character character + end + + it "displays the alias add command message to the sender" do + send_text(command) + + expect(page).to have_alias_add_command_message(command: name, shortcut: shortcut) + end + + it "does not broadcast the message to the room" do + using_session(:nearby_character) do + sign_in_as_character create(:character, room: character.room) + end + + send_text(command) + + wait_for(have_alias_add_command_message(command: name, shortcut: shortcut)) do + using_session(:nearby_character) do + expect(page).not_to have_alias_add_command_message(command: name, shortcut: shortcut) + end + end + end + + context "with an invalid command" do + let(:command) { "/alias add /f #{name}" } + let(:name) { "/fake" } + + it "displays invalid command message to the sender" do + send_text(command) + + expect(page).to have_invalid_command_message(command: name) + end + + it "does not broadcast the message to the room" do + using_session(:nearby_character) do + sign_in_as_character create(:character, room: character.room) + end + + send_text(command) + + wait_for(have_invalid_command_message(command: name)) do + using_session(:nearby_character) do + expect(page).not_to have_invalid_command_message(command: name) + end + end + end + end + + protected + + def have_alias_add_command_message(command:, shortcut:) + have_css( + "#messages .message-alias-add", + text: t("commands.alias.add.success.message", command: command, shortcut: shortcut) + ) + end + + def have_invalid_command_message(command:) + have_css( + "#messages .message-alias-add-invalid-command", + text: t("commands.alias.add.invalid_command.message", command: command) + ) + end +end diff --git a/spec/services/commands/alias/add/invalid_command_spec.rb b/spec/services/commands/alias/add/invalid_command_spec.rb new file mode 100644 index 00000000..3d50fa5d --- /dev/null +++ b/spec/services/commands/alias/add/invalid_command_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require "rails_helper" + +describe Commands::Alias::Add::InvalidCommand, type: :service do + describe "class" do + it "inherits from command result" do + expect(described_class.superclass).to eq(Commands::Result) + end + end + + describe "#locals" do + subject { instance.locals } + + let(:command) { double } + let(:instance) { described_class.new(command: command) } + + it { is_expected.to eq(command: command) } + end +end diff --git a/spec/services/commands/alias/add/success_spec.rb b/spec/services/commands/alias/add/success_spec.rb new file mode 100644 index 00000000..9a38bd5e --- /dev/null +++ b/spec/services/commands/alias/add/success_spec.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require "rails_helper" + +describe Commands::Alias::Add::Success, type: :service do + describe "class" do + it "inherits from command result" do + expect(described_class.superclass).to eq(Commands::Result) + end + end + + describe "#call" do + subject(:call) { instance.call } + + let(:account) { character.account } + let(:character) { create(:character) } + let(:command) { "/emote" } + let(:instance) { described_class.new(character: character, command: command, shortcut: shortcut) } + let(:shortcut) { "/e" } + + it "adds the alias to the character's account" do + call + + expect(account.aliases).to have_key(shortcut) + end + end + + describe "#locals" do + subject { instance.locals } + + let(:character) { build_stubbed(:character) } + let(:command) { double } + let(:instance) { described_class.new(character: character, command: command, shortcut: shortcut) } + let(:shortcut) { double } + + it { is_expected.to eq(account: character.account, command: command, shortcut: shortcut) } + end +end diff --git a/spec/services/commands/alias/add_spec.rb b/spec/services/commands/alias/add_spec.rb new file mode 100644 index 00000000..1c5a8834 --- /dev/null +++ b/spec/services/commands/alias/add_spec.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +require "rails_helper" + +describe Commands::Alias::Add, type: :service do + describe ".match?" do + subject { described_class.match?(arguments) } + + context "with no arguments" do + let(:arguments) { [] } + + it { is_expected.to be(false) } + end + + context "with add argument" do + let(:arguments) { [t("commands.lookup.alias.arguments.add")] } + + it { is_expected.to be(true) } + end + + context "with other arguments" do + let(:arguments) { %w(fake list) } + + it { is_expected.to be(false) } + end + end + + describe "#call" do + subject(:call) { instance.call } + + let(:character) { build_stubbed(:character) } + let(:instance) { described_class.new("/alias add #{shortcut} #{command}", character: character) } + + context "with a valid shortcut and command" do + let(:command) { "/emote" } + let(:result) { instance_double(described_class::Success) } + let(:shortcut) { "/e" } + + before do + allow(result).to receive(:call) + allow(described_class::Success).to receive(:new) + .with(character: character, command: command, shortcut: shortcut) + .and_return(result) + end + + it "delegates to success handler" do + call + + expect(result).to have_received(:call).with(no_args) + end + end + + context "with a valid shortcut and command, without slashes" do + let(:command) { "emote" } + let(:result) { instance_double(described_class::Success) } + let(:shortcut) { "e" } + + before do + allow(result).to receive(:call) + allow(described_class::Success).to receive(:new) + .with(character: character, command: "/#{command}", shortcut: shortcut) + .and_return(result) + end + + it "delegates to success handler" do + call + + expect(result).to have_received(:call).with(no_args) + end + end + + context "with an invalid command" do + let(:command) { "/fake" } + let(:result) { instance_double(described_class::InvalidCommand) } + let(:shortcut) { "/f" } + + before do + allow(described_class::InvalidCommand).to receive(:new) + .with(command: command) + .and_return(result) + end + + it "delegates to invalid command handler" do + allow(result).to receive(:call) + + call + + expect(result).to have_received(:call).with(no_args) + end + end + end +end diff --git a/spec/views/commands/alias/add/_invalid_command.html.erb_spec.rb b/spec/views/commands/alias/add/_invalid_command.html.erb_spec.rb new file mode 100644 index 00000000..c9c83de3 --- /dev/null +++ b/spec/views/commands/alias/add/_invalid_command.html.erb_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require "rails_helper" + +describe "commands/alias/add/_invalid_command.html.erb" do + subject(:html) do + render partial: "commands/alias/add/invalid_command", + locals: { command: command } + + rendered + end + + let(:command) { "/unknown" } + + it "renders the unknown alias message" do + expect(html).to have_message_row( + "td:nth-child(2)", + text: t("commands.alias.add.invalid_command.message", command: command) + ) + end +end diff --git a/spec/views/commands/alias/add/_invalid_command.turbo_stream.erb_spec.rb b/spec/views/commands/alias/add/_invalid_command.turbo_stream.erb_spec.rb new file mode 100644 index 00000000..5d309cda --- /dev/null +++ b/spec/views/commands/alias/add/_invalid_command.turbo_stream.erb_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require "rails_helper" + +describe "commands/alias/add/_invalid_command.turbo_stream.erb" do + subject(:html) do + render partial: "commands/alias/add/invalid_command", + formats: :turbo_stream, + locals: { command: "/fake" } + + rendered + end + + before do + stub_template("commands/alias/add/_invalid_command.html.erb" => "INVALID_COMMAND_TEMPLATE") + end + + it "appends to the messages element" do + expect(html).to have_turbo_stream_element( + action: "append", + target: "messages" + ) + end + + it "renders the HTML template" do + expect(html).to include("INVALID_COMMAND_TEMPLATE") + end +end diff --git a/spec/views/commands/alias/add/_success.html.erb_spec.rb b/spec/views/commands/alias/add/_success.html.erb_spec.rb new file mode 100644 index 00000000..ad0e96e3 --- /dev/null +++ b/spec/views/commands/alias/add/_success.html.erb_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require "rails_helper" + +describe "commands/alias/add/success.html.erb" do + subject(:html) do + render partial: "commands/alias/add/success", locals: { + account: account, + command: command, + shortcut: shortcut + } + + rendered + end + + let(:account) { build_stubbed(:account) } + let(:command) { "/emote" } + let(:shortcut) { "/e" } + + it "renders the message" do + expect(html).to have_css( + "td:nth-child(2)", + text: t("commands.alias.add.success.message", command: command, shortcut: shortcut) + ) + end + + it "overwrites the local alias cache" do + expect(html).to include( + "" + ) + end +end diff --git a/spec/views/commands/alias/add/_success.turbo_stream.erb_spec.rb b/spec/views/commands/alias/add/_success.turbo_stream.erb_spec.rb new file mode 100644 index 00000000..7cdbd13b --- /dev/null +++ b/spec/views/commands/alias/add/_success.turbo_stream.erb_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require "rails_helper" + +describe "commands/alias/add/_success.turbo_stream.erb" do + subject(:html) do + render partial: "commands/alias/add/success", + formats: :turbo_stream, + locals: { account: account, command: "/emote", shortcut: "/e" } + + rendered + end + + let(:account) { build_stubbed(:account) } + + before do + stub_template("commands/alias/add/_success.html.erb" => "SUCCESS_TEMPLATE") + end + + it "appends to the messages element" do + expect(html).to have_turbo_stream_element( + action: "append", + target: "messages" + ) + end + + it "renders the HTML template" do + expect(html).to include("SUCCESS_TEMPLATE") + end +end