describe Hashr do describe 'initialization' do it 'takes nil' do expect { Hashr.new(nil) }.to_not raise_error end it 'does not explode on a numerical key' do expect { Hashr.new(1 => 2) }.to_not raise_error end it 'does not explode on a true key' do expect { Hashr.new(true => 'on') }.to_not raise_error end it 'raises an ArgumentError when given a string' do expect { Hashr.new('foo') }.to raise_error(ArgumentError) end it 'passing a block allows to define methods on the singleton class' do hashr = Hashr.new(count: 5) do def count @data.count end end expect(hashr.count).to eq(5) end end describe 'defined?' do it 'returns true when a key is defined' do hashr = Hashr.new(foo: 'foo') expect(hashr.defined?(:foo)).to eq(true) end it 'returns false when a key is not defined' do hashr = Hashr.new(foo: 'foo') expect(hashr.defined?(:bar)).to eq(false) end it 'works with a numerical key' do hashr = Hashr.new(1 => 'foo') expect(hashr.defined?(1)).to eq(true) end it 'works with a true key' do hashr = Hashr.new(true => 'foo') expect(hashr.defined?(true)).to eq(true) end it 'is indifferent about symbols/strings (string given, symbol used)' do hashr = Hashr.new('foo' => 'bar') expect(hashr.defined?(:foo)).to eq(true) end it 'is indifferent about symbols/strings (symbol given, string used)' do hashr = Hashr.new(foo: :bar) expect(hashr.defined?('foo')).to eq(true) end end describe 'hash access' do it 'is indifferent about symbols/strings (string given, symbol used)' do hashr = Hashr.new('foo' => { 'bar' => 'baz' }) expect(hashr[:foo][:bar]).to eq('baz') end it 'is indifferent about symbols/strings (symbol given, string used)' do hashr = Hashr.new(foo: { bar: 'baz' }) expect(hashr['foo']['bar']).to eq('baz') end it 'allows accessing keys with Hash core method names (count)' do expect(Hashr.new(count: 2).count).to eq(2) end it 'allows accessing keys with Hash core method names (key)' do expect(Hashr.new(key: 'key').key).to eq('key') end end describe 'hash assignment' do let(:hashr) { Hashr.new } it 'works with a string key' do hashr['foo'] = 'foo' expect(hashr.foo).to eq('foo') end it 'works with a symbol key' do hashr[:foo] = 'foo' expect(hashr.foo).to eq('foo') end end describe 'method access' do describe 'on an existing key' do it 'returns the value' do expect(Hashr.new(foo: 'foo').foo).to eq('foo') end it 'returns a nested hash' do expect(Hashr.new(foo: { bar: 'bar' }).foo).to eq(bar: 'bar') end it 'returns a nested array' do expect(Hashr.new(foo: ['bar', 'buz']).foo).to eq(['bar', 'buz']) end end describe 'on an existing nested key' do it 'returns the value' do expect(Hashr.new(foo: { bar: 'bar' }).foo.bar).to eq('bar') end it 'returns a nested array' do expect(Hashr.new(foo: { bar: ['bar', 'buz'] }).foo.bar).to eq(['bar', 'buz']) end end describe 'on an non-existing key' do it 'it returns nil' do expect(Hashr.new(foo: 'foo').bar).to eq(nil) end it 'it returns nil (nested key)' do expect(Hashr.new(foo: { bar: 'bar' }).foo.baz).to eq(nil) end end describe 'predicate methods' do it 'returns true if the key has a value' do expect(Hashr.new(foo: { bar: 'bar' }).foo.bar?).to eq(true) end it 'returns false if the key does not have a value' do expect(Hashr.new(foo: { bar: 'bar' }).foo.baz?).to eq(false) end end describe 'respond_to?' do it 'returns true for existing keys' do expect(Hashr.new(foo: 'bar').respond_to?(:foo)).to eq(true) end it 'returns false for missing keys' do expect(Hashr.new.respond_to?(:foo)).to eq(true) end end end describe 'method assignment' do let(:hashr) { Hashr.new } it 'assigns a string' do hashr.foo = 'foo' expect(hashr.foo).to eq('foo') end it 'converts a hash into a Hashr instance' do hashr.foo = { bar: { baz: 'baz' } } expect(hashr.foo.bar.baz).to eq('baz') end end describe 'values_at' do let(:hashr) { Hashr.new(foo: 'foo', bar: 'bar') } it 'returns values for the given keys' do expect(hashr.values_at(:foo, :bar)).to eq(['foo', 'bar']) end end describe 'is_a?' do let(:klass) { Class.new(Class.new(Hashr)) { default foo: 'foo' } } it 'returns true if the object has the given superclass' do expect(klass.new.is_a?(Hashr)).to eq(true) end it 'returns false if the object does not have the given superclass' do expect(klass.new.is_a?(Hash)).to eq(false) end end describe 'conversion' do let(:hash) { Hashr.new(foo: { 'bar' => 'baz' }).to_h } it 'converts the Hashr instance to a hash' do expect(hash.class).to eq(Hash) end it 'converts nested instances to hashes' do expect(hash[:foo].class).to eq(Hash) end it 'populates the hash with the symbolized keys' do expect(hash[:foo][:bar]).to eq('baz') end end describe 'defaults' do describe 'using a symbolized hash' do let(:klass) { Class.new(Hashr) { default foo: 'foo' } } it 'defines the default' do expect(klass.new['foo']).to eq('foo') end end describe 'using a stringified hash' do let(:klass) { Class.new(Hashr) { default 'foo' => 'foo' } } it 'defines the default' do expect(klass.new[:foo]).to eq('foo') end end describe 'with a nested hash' do let(:klass) { Class.new(Hashr) { default foo: { bar: { baz: 'baz' } } } } it 'defines the default' do expect(klass.new['foo'][:bar]['baz']).to eq('baz') end end describe 'with a nested array' do let(:klass) { Class.new(Hashr) { default foo: ['bar'] } } it 'defines the default' do expect(klass.new.foo.first).to eq('bar') end end describe 'keeps existing defaults' do let(:klass) { Class.new(Hashr) { default foo: 'foo'; default bar: 'bar' } } it 'defines the existing default' do expect(klass.new.foo).to eq('foo') end it 'defines the new default' do expect(klass.new.bar).to eq('bar') end end describe 'deep_merges inherited defaults' do let(:klass) { Class.new(Class.new(Hashr) { default foo: 'foo' }) { default bar: 'bar' } } it 'defines the inherited default' do expect(klass.new.foo).to eq('foo') end it 'defines the default' do expect(klass.new.bar).to eq('bar') end end end describe 'constant lookup' do let(:klass) { Class.new(Hashr) { def env; ENV; end } } it 'finds global consts' do expect(klass.new.env).to eq(ENV) end end end