# spec/sleeping_king_studios/shinkansen/models/attributes/builder_spec.rb

require 'rails_helper'

RSpec.describe SleepingKingStudios::Shinkansen::Models::Attributes::Builder do
  include Spec::Examples::Models::AttributesExamples

  mock_class Spec::Models, :AdaptableExample do |klass|
    klass.send :include, SleepingKingStudios::Shinkansen::Models::Adaptable::Model

    hsh = attributes.dup

    klass.adapter = Class.new(SleepingKingStudios::Shinkansen::Models::Adapters::BaseAdapter) do
      define_method :read_attribute, ->(_, attr_name) { hsh[attr_name] }

      define_method :write_attribute, ->(_, attr_name, attr_val) { hsh[attr_name] = attr_val }
    end # adapter
  end # mock_class

  let(:attributes)  { { :string_field => 'string value' } }
  let(:model_class) { Spec::Models::AdaptableExample }
  let(:model)       { model_class.__new__ }
  let(:instance)    { described_class.new model_class }

  describe '::new' do
    it { expect(described_class).to be_constructible.with(1).argument }
  end # describe

  describe '#build_attribute' do
    class << self
      def attribute_name
        :string_field
      end # class method attribute_name

      def attribute_type
        :string
      end # class method attribute_type

      def attribute_options
        {}
      end # class method attribute_options
    end # class

    let(:attribute_name)    { self.class.attribute_name }
    let(:attribute_type)    { self.class.attribute_type }
    let(:attribute_options) { self.class.attribute_options }

    before(:example) do
      allow(instance).to receive(:valid_types).and_return([:string, :text, :integer])
    end # before example

    it { expect(instance).to respond_to(:build_attribute).with(3..4).arguments }

    it { expect(instance).to alias_method(:build_attribute).as(:build) }

    it 'should return the metadata' do
      metadata_class = SleepingKingStudios::Shinkansen::Models::Attributes::Metadata
      metadata       = instance.build_attribute attribute_name, attribute_type, attribute_options

      expect(metadata).to be_a metadata_class
      expect(metadata.name).to    be == attribute_name
      expect(metadata.type).to    be == attribute_type
      expect(metadata.options).to be == attribute_options
    end # it

    describe 'should validate the attribute name' do
      def call_method attribute_name
        instance.send :build_attribute, attribute_name, :text, {}
      end # method call_method

      include_examples 'should validate the attribute name'
    end # describe

    describe 'should validate the attribute type' do
      def call_method attribute_type
        instance.send :build_attribute, :string_field, attribute_type, {}
      end # method call_method

      include_examples 'should validate the attribute type'
    end # describe

    describe 'should validate the attribute options' do
      def call_method attribute_options
        instance.send :build_attribute, :string_field, :text, attribute_options
      end # method call_method

      include_examples 'should validate the attribute options'
    end # describe

    describe 'should define the attribute methods' do
      let(:builder_options) { {} }

      before(:example) do
        instance.build_attribute attribute_name, attribute_type, attribute_options, builder_options
      end # before example

      include_examples 'should define attribute', attribute_name, attribute_type, attribute_options

      describe 'with :reader => false' do
        let(:builder_options) { super().merge :reader => false }
        let(:reader_name)     { attribute_name }

        it { expect(model).not_to respond_to(reader_name) }

        include_examples 'should define attribute', attribute_name, attribute_type, attribute_options, :reader => false
      end # describe

      describe 'with :writer => false' do
        let(:builder_options) { super().merge :writer => false }
        let(:writer_name)     { :"#{attribute_name}=" }

        it { expect(model).not_to respond_to(writer_name) }

        include_examples 'should define attribute', attribute_name, attribute_type, attribute_options, :writer => false
      end # describe
    end # describe
  end # describe

  describe '#model_class' do
    include_examples 'should have reader', :model_class, ->() { be == model_class }
  end # describe

  describe '#valid_options' do
    it { expect(instance).to have_reader(:valid_options).with_value([]) }
  end # describe

  describe '#valid_types' do
    it { expect(instance).to have_reader(:valid_types).with_value([]) }
  end # describe
end # describe
