require 'spec_helper' describe Riak::Crdt::TypedCollection do let(:parent){ double 'parent' } let(:operation){ double 'operation' } describe 'initialization' do it "accepts a type, parent, and hash of values" do expect{ described_class.new Riak::Crdt::Counter, parent, {} }.to_not raise_error end end describe 'containing' do describe 'registers' do let(:register_class){ Riak::Crdt::InnerRegister } subject do described_class.new register_class, parent, existing: 'existing' end it 'exposes them as frozen strings that are really Registers' do expect(subject[:existing]).to eq 'existing' expect(subject['existing']).to eq 'existing' expect(subject[:existing]).to be_an_instance_of register_class expect(subject['existing'].frozen?).to be expect{subject['existing'].gsub!('e', 'a')}.to raise_error end describe 'creating and updating' do let(:new_value){ 'the new value' } it 'asks the register class for an operation' do expect(register_class).to receive(:update). with(new_value). and_return(operation) expect(operation). to receive(:name=). with('existing') expect(parent). to receive(:operate). with(operation) subject['existing'] = new_value end end describe 'removing' do it 'asks the register class for a remove operation' do expect(register_class). to receive(:delete). and_return(operation) expect(operation). to receive(:name=). with('existing') expect(parent). to receive(:operate). with(operation) subject.delete 'existing' end end end describe 'flags' do let(:flag_class){ Riak::Crdt::InnerFlag } subject do described_class.new flag_class, parent, truthy: true, falsey: false end it 'exposes them as booleans' do expect(subject[:truthy]).to eq true expect(subject['falsey']).to eq false end it 'updates them' do expect(flag_class). to receive(:update). with(true). and_return(operation) expect(operation). to receive(:name=). with('become_truthy') expect(parent). to receive(:operate). with(operation) subject['become_truthy'] = true end it 'deletes them' do expect(flag_class). to receive(:delete). and_return(operation) expect(operation). to receive(:name=). with('become_deleted') expect(parent). to receive(:operate). with(operation) subject.delete 'become_deleted' end end describe 'counters' do let(:counter_class){ Riak::Crdt::InnerCounter } subject{ described_class.new counter_class, parent, zero: 0, one: 1 } it 'exposes existing ones as Counter instances' do expect(subject['zero']).to be_an_instance_of counter_class expect(subject['zero'].to_i).to eq 0 expect(subject['one'].to_i).to eq 1 end it 'exposes new ones as Counter instances' do expect(subject['new_zero']).to be_an_instance_of counter_class expect(subject['new_zero'].to_i).to eq 0 end it 'allows incrementing and decrementing' do counter_name = 'counter' expect(parent).to receive(:operate) do |op| expect(op.name).to eq counter_name expect(op.type).to eq :counter expect(op.value).to eq 1 end subject[counter_name].increment expect(parent).to receive(:operate) do |op| expect(op.name).to eq counter_name expect(op.type).to eq :counter expect(op.value).to eq -5 end subject[counter_name].decrement 5 end end describe 'sets' do let(:set_class){ Riak::Crdt::InnerSet } subject{ described_class.new set_class, parent, brewers: %w{aeropress clever v60}} it 'exposes existing ones as Set instances' do expect(subject['brewers']).to be_an_instance_of set_class expect(subject['brewers']).to include 'aeropress' end it 'exposes new ones as empty Set instances' do expect(subject['filters']).to be_an_instance_of set_class expect(subject['filters']).to be_empty end it 'allows adding and removing' do set_name = 'brewers' expect(parent).to receive(:operate) do |op| expect(op.name).to eq set_name expect(op.type).to eq :set expect(op.value).to eq add: 'frenchpress' end subject[set_name].add 'frenchpress' expect(parent).to receive(:operate) do |op| expect(op.name).to eq set_name expect(op.type).to eq :set expect(op.value).to eq remove: 'aeropress' end allow(parent).to receive(:context?).and_return(true) subject[set_name].remove 'aeropress' end end describe 'maps' do let(:map_class){ Riak::Crdt::InnerMap } let(:contents) do {a: { counters: {}, flags: {}, maps: {}, registers: {'hello' => 'world'}, sets: {} }} end let(:inner_map_name){ 'inner map' } subject do described_class.new map_class, parent, contents end it 'exposes existing ones as populated Map instances' do expect(subject['a']).to be_an_instance_of map_class expect(subject['a'].registers['hello']).to eq 'world' end it 'exposes new ones as empty Map instances' do expect(subject['b']).to be_an_instance_of map_class expect(subject['b'].registers['hello']).to be_nil end it 'cascades operations to a parent map' do expect(operation). to receive(:name=). with(inner_map_name) expect(parent). to receive(:operate). with(operation) subject.operate inner_map_name, operation end end end end