# frozen_string_literal: true describe ESM::Command::Argument do let!(:command_class) { ESM::Command::Test::ArgumentDescriptions } let!(:command) { command_class.new } subject(:argument) { new_argument } def transform_and_validate!(content) argument.transform_and_validate!(content, command) end def new_argument(name = :with_locale, type = nil, **opts) described_class.new(name, type, command_class: command_class, **opts) end describe "DEFAULT_TEMPLATE" do it "is always provided as a base template for arguments" do described_class::DEFAULT_TEMPLATE.each do |key, value| expect(argument.public_send(key)).to eq(value) end end end describe "TEMPLATES" do context "when the argument name is in TEMPLATES" do subject(:argument) { new_argument(:community_id) } it "merged into the base template" do expect(argument.checked_against).to eq(described_class::TEMPLATES[:community_id][:checked_against]) end end context "when the argument :template option is in TEMPLATES" do subject(:argument) { new_argument(template: :community_id) } it "merged into the base template" do expect(argument.checked_against).to eq(described_class::TEMPLATES[:community_id][:checked_against]) end end end context "when opts are provided that are already set by TEMPLATE and DEFAULT_TEMPLATE" do subject(:argument) { new_argument(:community_id, checked_against: "HELLO GORDON!") } it "overwrites them" do expect(argument.checked_against).to eq("HELLO GORDON!") end end context "when the type is not provided" do it "defaults to string" do expect(argument.type).to eq(:string) end end context "when the type is provided" do subject(:argument) { new_argument(:with_locale, :integer) } it "uses the type" do expect(argument.type).to eq(:integer) end end context "when :required is not provided" do it "defaults to false" do expect(argument.required?).to be(false) expect(argument.required_by_bot?).to be(false) expect(argument.required_by_discord?).to be(false) end end context "when :required is false" do subject(:argument) { new_argument(required: false, optional_text: "") } it "is optional" do expect(argument.optional?).to be(true) expect(argument.required?).to be(false) expect(argument.required_by_bot?).to be(false) expect(argument.required_by_discord?).to be(false) end it "defaults optional text" do expect(argument.optional_text).to eq("This argument is optional.") end end context "when :required is true" do subject(:argument) { new_argument(required: true) } it "is required" do expect(argument.required?).to be(true) expect(argument.required_by_bot?).to be(true) expect(argument.required_by_discord?).to be(true) expect(argument.optional?).to be(false) end it "defaults optional_text to an empty string" do expect(argument.optional_text).to be_blank end end context "when :required is a Hash" do subject(:argument) { new_argument(required: {discord: false, bot: true}) } it "is expected to be required by the bot but not discord" do expect(argument.required?).to be(true) expect(argument.required_by_bot?).to be(true) expect(argument.required_by_discord?).to be(false) expect(argument.optional?).to be(false) end end context "when :template is provided" do subject(:argument) { new_argument(template: :community_id) } it "pulls the defaults from the template and uses them" do expect(argument.name).to eq(:with_locale) expect(argument.checked_against).to eq(ESM::Regex::COMMUNITY_ID) end end context "when :description is not provided" do context "and there is a locale entry defined" do it "uses the description from the locales" do expect(argument.description).to eq("An argument description") end end context "and there is no locale entry defined" do subject(:argument) { new_argument(:test_name) } it "defaults to an empty string and promptly fails validation" do expect { argument }.to raise_error( ArgumentError, "#{command_class}:argument.test_name - description must be at least 1 character long" ) end end end context "when :description is provided" do context "and the description is a locale path" do subject(:argument) do new_argument( :with_locale, description: "commands.argument_descriptions.arguments.with_locale.description_extra" ) end it "looks up and uses the defined locale" do expect(argument.description).to eq("An argument description extra") end end context "and the description is not a locale path" do subject(:argument) { new_argument(description: "This is a testing description") } it "uses the provided text" do expect(argument.description).to eq("This is a testing description") end end context "and is over 100 characters in length" do subject(:argument) { new_argument(:test_name, description: "1" * 101) } it "raises an exception" do expect { argument }.to raise_error( ArgumentError, "#{command_class}:argument.test_name - description cannot be longer than 100 characters" ) end end context "and is not 1 character in length" do subject(:argument) { new_argument(:test_name, description: "") } it "raises an exception" do expect { argument }.to raise_error( ArgumentError, "#{command_class}:argument.test_name - description must be at least 1 character long" ) end end end context "when :description_extra is provided" do context "and the extra description is a locale path" do subject(:argument) do new_argument( :with_locale, description_extra: "commands.argument_descriptions.arguments.with_locale.optional_text" ) end it "looks up and uses the defined locale" do expect(argument.description_extra).to eq("An argument optional text") end end context "and the extra description is not a locale path" do subject(:argument) { new_argument(description_extra: "This is description extra") } it "uses the provided text" do expect(argument.description_extra).to eq("This is description extra") end end end context "when :optional_text is provided" do context "and the optional text is a locale path" do subject(:argument) do new_argument( :with_locale, optional_text: "commands.argument_descriptions.arguments.with_locale.description" ) end it "looks up and uses the defined locale" do expect(argument.optional_text).to eq("An argument description") end end context "and the optional text is not a locale path" do subject(:argument) { new_argument(optional_text: "This is optional text") } it "uses the provided text" do expect(argument.optional_text).to eq("This is optional text") end end end context "when :optional_text is not provided" do subject(:argument) { new_argument(optional_text: "") } it "defaults the text" do expect(argument.optional_text).to eq("This argument is optional.") end end context "when :display_name is provided" do subject(:argument) { new_argument(display_name: :different_name) } it "uses the display name instead of the given name" do expect(argument.name).to eq(:with_locale) expect(argument.display_name).to eq(:different_name) expect(argument.to_s).to eq("different_name") end end context "when :display_name is not provided" do it "uses the given name" do expect(argument.name).to eq(:with_locale) expect(argument.display_name).to eq(:with_locale) expect(argument.to_s).to eq("with_locale") end end context "when :default is provided" do subject(:argument) { new_argument(default: "foobar") } context "and the input is blank" do it "#default_value is expected to be set" do expect(argument.default_value).to be("foobar") end it "#default_value? is expected to be true" do expect(argument.default_value?).to be(true) end it "uses the default" do expect(transform_and_validate!(nil)).to eq("foobar") expect(transform_and_validate!("")).to eq("foobar") end end context "and the input is present" do it "uses the input" do expect(transform_and_validate!("testing")).to eq("testing") end end end context "when :default is not provided" do it "#default_value is expected to be nil" do expect(argument.default_value).to be(nil) end it "#default_value? is expected to be false" do expect(argument.default_value?).to be(false) end it "defaults to nil" do expect(transform_and_validate!(nil)).to eq(nil) expect(transform_and_validate!("a")).to eq("a") end end context "when :preserve_case is true" do subject(:argument) { new_argument(preserve_case: true) } it "#preserve_case? is expected to be true" do expect(argument.preserve_case?).to be(true) end it "does not convert string input to lowercase" do expect(transform_and_validate!("Hello World!")).to eq("Hello World!") end end context "when :preserve_case is false" do it "#preserve_case? is expected to be false" do expect(argument.preserve_case?).to be(false) end it "converts string input to lowercase" do expect(transform_and_validate!("Hello World!")).to eq("hello world!") end end context "when :modifier is provided" do subject(:argument) do new_argument( :with_locale, modifier: ->(content) { content + "1" } ) end it "#modifier? is expected to be true" do expect(argument.modifier?).to be(true) end it "runs the input through the modifier and stores the return as the new input" do expect(transform_and_validate!("testing")).to eq("testing1") end end context "when :modifier is not provided" do it "#modifier? is expected to be false" do expect(argument.modifier?).to be(false) end it "skips running the input through the modifier" do expect(transform_and_validate!("testing")).to eq("testing") end end context "when :choices is provided" do subject(:argument) { new_argument(choices: {value_1: "Display 1", value_2: "Display 2"}) } it "swaps the key and value for Discord and stores it in options" do choices = argument.options[:choices] expect(choices).to be_kind_of(Hash) expect(choices).to include("Display 1" => "value_1", "Display 2" => "value_2") end end context "when :min_value is provided" do context "and type is :integer" do subject(:argument) { new_argument(:with_locale, :integer, min_value: 2) } it "stores it in options" do expect(argument.options[:min_value]).to eq(2) end end context "and type is not :integer" do subject(:argument) { new_argument(min_value: 2) } it "raises an exception" do expect { argument }.to raise_error( ArgumentError, "#{command_class}:argument.with_locale - min/max values can only be used with integer or number types" ) end end end context "when :max_value is provided" do context "and type is :integer" do subject(:argument) { new_argument(:with_locale, :integer, max_value: 2) } it "stores it in options" do expect(argument.options[:max_value]).to eq(2) end end context "and type is not :integer" do subject(:argument) { new_argument(max_value: 2) } it "raises an exception" do expect { argument }.to raise_error( ArgumentError, "#{command_class}:argument.with_locale - min/max values can only be used with integer or number types" ) end end end context "when :checked_against is not a Hash" do context "and the argument is required and the input is provided" do subject(:argument) { new_argument(required: true, checked_against: "foob") } it "passes validation" do transform_and_validate!("foobar") end it "fails validation" do expect { transform_and_validate!("testing") }.to raise_error(ESM::Exception::InvalidArgument) end end context "and the argument is optional and the input is blank" do subject(:argument) { new_argument(checked_against: "foob") } it "skips validation" do transform_and_validate!("") end end end context "when :checked_against is provided a String" do subject(:argument) { new_argument(checked_against: "test") } context "and the input matches" do it "passes validation" do transform_and_validate!("testing") end end context "and the input does not match" do it "fails and raises an exception" do expect { transform_and_validate!("foobar") }.to raise_error(ESM::Exception::InvalidArgument) end end end context "when :checked_against is provided a Regexp" do subject(:argument) { new_argument(checked_against: /\d+/) } context "and the input matches" do it "passes validation" do transform_and_validate!("12345") end end context "and the input does not match" do it "fails and raises an exception" do expect { transform_and_validate!("a") }.to raise_error(ESM::Exception::InvalidArgument) end end end context "when :checked_against is provided a Proc/Lambda" do subject(:argument) { new_argument(checked_against: ->(content) { content == "54321" }) } context "and the result of the block is truthy" do it "passes validation" do transform_and_validate!("54321") end end context "and the result of the block is falsey" do it "fails and raises an exception" do expect { transform_and_validate!("a") }.to raise_error(ESM::Exception::InvalidArgument) end end end context "when :checked_against is provided an Array" do subject(:argument) { new_argument(checked_against: ["foo", "bar", "baz"]) } context "and the input is in the array" do it "passes validation" do transform_and_validate!("baz") end end context "and the input is not in the array" do it "fails and raises an exception" do expect { transform_and_validate!("testing") }.to raise_error(ESM::Exception::InvalidArgument) end end end context "when :checked_against_if is provided" do subject(:argument) do new_argument( :with_locale, checked_against: ->(content) { content.nil? }, # Set up to fail checked_against_if: ->(arg, content) { content.match?("test") } ) end context "when :checked_against_if returns a truthy value" do it "continues to validation" do expect { transform_and_validate!("test") }.to raise_error(ESM::Exception::InvalidArgument) end end context "when :checked_against_if returns a falsey value" do it "skips validation" do transform_and_validate!("foobar") end end end context "when :checked_against_if is nil" do subject(:argument) { new_argument(checked_against: "test", checked_against_if: nil) } it "skips validation" do transform_and_validate!("foobar") end end end