Friday, September 20, 2024 1:42:13 AM
> settings

Customize


Authenticate

> argument_spec.rb
# 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
All opinions represented herein are my own
- © 2024 itsthedevman
- build 340fbb8