# frozen_string_literal: true

require_relative '../spec_helper'
require 'recursive_open_struct'

describe RecursiveOpenStruct do
  subject(:ros) { described_class.new(hash) }

  let(:hash) { {} }

  describe 'behavior it inherits from OpenStruct' do
    context 'when not initialized from anything' do
      subject(:ros) { described_class.new }

      it 'can represent arbitrary data objects' do
        ros.blah = 'John Smith'
        expect(ros.blah).to eq 'John Smith'
      end

      it 'returns nil for missing attributes' do
        expect(ros.foo).to be_nil
      end
    end

    context 'when initialized with nil' do
      let(:hash) { nil }

      it 'returns nil for missing attributes' do
        expect(ros.foo).to be_nil
      end
    end

    context 'when initialized with an empty hash' do
      it 'returns nil for missing attributes' do
        expect(ros.foo).to be_nil
      end
    end

    context 'when initialized from a hash' do
      let(:hash) { { asdf: 'John Smith' } }

      context 'when it contains symbol keys' do
        it 'turns those symbol keys into method names' do
          expect(ros.asdf).to eq 'John Smith'
        end
      end

      it 'can modify an existing key' do
        ros.asdf = 'George Washington'
        expect(ros.asdf).to eq 'George Washington'
      end

      context 'when it contains string keys' do
        let(:hash) { { 'asdf' => 'John Smith' } }

        it 'turns those string keys into method names' do
          expect(ros.asdf).to eq 'John Smith'
        end
      end

      context 'when it contains keys that mirror existing private methods' do
        let(:hash) { { test: :foo, rand: 'not a number' } }

        # https://github.com/aetherknight/recursive-open-struct/issues/42
        it 'handles subscript notation without calling the method name first (#42)' do
          expect(ros['test']).to eq :foo
          expect(ros['rand']).to eq 'not a number'

          expect(ros.test).to eq :foo
          expect(ros.rand).to eq 'not a number'
        end
      end

      context 'when it contains keys that mirror existing public methods inherited from Object' do
        let(:hash) { { method: :something } }

        it 'handles subscript notation without calling the existing methods' do
          expect(ros[:method]).to eq :something
          expect(ros['method']).to eq :something
        end
      end

      if [/\A([0-9]+)\.([0-9]+)\.([0-9]+)\z/.match(RUBY_VERSION)].tap do |l|
        m = l[0]
        l[0] = (m[1].to_i >= 2 && m[2].to_i >= 4)
      end.first
        context 'when Ruby 2.4.0 or newer' do
          specify 'new_ostruct_member! is private' do
            expect do
              ros.new_ostruct_member!(:bonsoir)
            end.to raise_error(NoMethodError)
            # OpenStruct.new().new_ostruct_member!(:foo)
          end
        end
      end
    end

    describe 'handling of arbitrary attributes' do
      subject(:ros) { described_class.new }

      before do
        ros.blah = 'John Smith'
      end

      describe '#respond?' do
        it { expect(ros).to respond_to :blah }
        it { expect(ros).to respond_to :blah= }
        it { expect(ros).not_to respond_to :asdf }
        it { expect(ros).not_to respond_to :asdf= }
      end # describe #respond?

      describe '#methods' do
        it { expect(ros.methods.map(&:to_sym)).to include :blah }
        it { expect(ros.methods.map(&:to_sym)).to include :blah= }
        it { expect(ros.methods.map(&:to_sym)).not_to include :asdf }
        it { expect(ros.methods.map(&:to_sym)).not_to include :asdf= }
      end # describe #methods
    end # describe handling of arbitrary attributes

    describe 'handling of freezing' do
      let(:hash) { { asdf: 'John Smith' } }

      before do
        ros.freeze
      end

      it 'can read existing keys' do
        expect(ros.asdf).to eq 'John Smith'
      end

      it 'cannot write new keys' do
        expect { ros.new_key = 'new_value' }.to raise_error FrozenError
      end

      it 'cannot write existing keys' do
        expect { ros.asdf = 'new_value' }.to raise_error FrozenError
      end

      context 'with recursive structure' do
        let(:hash) { { key: { subkey: 42 } } }

        it 'can read existing sub-elements' do
          expect(ros.key.subkey).to eq 42
        end

        it 'can write new sub-elements' do
          expect { ros.key.new_subkey = 43 }.not_to raise_error
        end

        it 'can write existing sub-elements' do
          expect { ros.key.subkey = 43 }.not_to raise_error
        end
      end
    end
  end # describe behavior it inherits from OpenStruct
end
