require "rails_helper"

RSpec.describe StringAttributeCleaner, type: :lib do
  describe ".nullify_blanks_for" do
    with_model :TestClass do
      table do |t|
        t.string :string_attribute
      end

      model do
        # rubocop:disable RSpec/DescribedClass
        include StringAttributeCleaner.nullify_blanks_for(:string_attribute)
        # rubocop:enable RSpec/DescribedClass
      end
    end

    with_model :AlternateTestClass do
      table do |t|
        t.string :string_attribute
      end

      model do
        # rubocop:disable RSpec/DescribedClass
        include StringAttributeCleaner.nullify_blanks_for(:string_attribute, on: :before_save)
        # rubocop:enable RSpec/DescribedClass
      end
    end

    context "when specifying an :on callback" do
      it "registers that callback on the model", :aggregate_failures do
        before_validation_cbs = AlternateTestClass._validation_callbacks.filter_map do |cb|
          cb.filter if cb.kind == :before
        end

        before_save_cbs = AlternateTestClass._save_callbacks.filter_map do |cb|
          cb.filter if cb.kind == :before
        end

        expect(before_validation_cbs).to eq([])
        expect(before_save_cbs).to eq([:nullify_blank_attributes])
        expect(TestClass.new).to respond_to(:nullify_blank_attributes)
      end
    end

    it "adds a before_validation callback to the including model", :aggregate_failures do
      before_validation_cbs = TestClass._validation_callbacks.filter_map do |cb|
        cb.filter if cb.kind == :before
      end

      expect(before_validation_cbs).to eq([:nullify_blank_attributes])
      expect(TestClass.new).to respond_to(:nullify_blank_attributes)
    end

    it "replaces empty strings with nil" do
      test_instance = TestClass.new(string_attribute: "")

      expect { test_instance.validate }
        .to change(test_instance, :string_attribute).from("").to(nil)
    end

    it "replaces blank strings with nil" do
      test_instance = TestClass.new(string_attribute: " ")

      expect { test_instance.validate }
        .to change(test_instance, :string_attribute).from(" ").to(nil)
    end

    it "leaves non-blank attributes unchanged" do
      test_instance = TestClass.new(string_attribute: "Test")

      expect { test_instance.validate }.not_to change(test_instance, :string_attribute)
    end

    it "ignores obsolete attributes" do
      TestClass.include(described_class.nullify_blanks_for(:non_existing))

      expect { TestClass.new.validate }.not_to raise_error
    end

    it "works with non-AR classes" do
      klass = Struct.new(:test, keyword_init: true)
      klass.include(described_class.nullify_blanks_for(:test))
      test_instance = klass.new(test: " ")

      expect { test_instance.nullify_blank_attributes }
        .to change(test_instance, :test).from(" ").to(nil)
    end
  end
end
