require 'compo' require 'composite_shared_examples' RSpec.shared_examples 'an array composite' do it_behaves_like 'a composite' # Children let(:c1) { double(:c1) } let(:c2) { double(:c2) } let(:c3) { double(:c3) } before(:each) do allow(c1).to receive(:update_parent) allow(c2).to receive(:update_parent) allow(c3).to receive(:update_parent) end describe '#add' do context 'when the ID is not Numeric' do specify { expect(subject.add(:mr_flibble, c1)).to be_nil } it 'does not add to the list of children' do expect { subject.add(:rimmer, c1) }.to_not change { subject.children } .from({}) subject.add(0, c1) expect { subject.add(:lister, c2) }.to_not change { subject.children } .from(0 => c1) subject.add(1, c2) expect { subject.add(:cat, c3) }.to_not change { subject.children } .from(0 => c1, 1 => c2) end end context 'when the ID is Numeric' do context 'and is equal to the number of children' do it 'returns the child' do expect(subject.add(0, c1)).to eq(c1) expect(subject.add(1, c2)).to eq(c2) expect(subject.add(2, c3)).to eq(c3) end it 'adds to the end of the list of children' do expect(subject.children).to eq({}) expect { subject.add(0, c1) }.to change { subject.children } .from({}) .to(0 => c1) expect { subject.add(1, c2) }.to change { subject.children } .from(0 => c1) .to(0 => c1, 1 => c2) expect { subject.add(2, c3) }.to change { subject.children } .from(0 => c1, 1 => c2) .to(0 => c1, 1 => c2, 2 => c3) end it 'calls #update_parent on the child with itself and an ID proc' do expect(c1).to receive(:update_parent).with( subject, an_object_satisfying { |proc| proc.call == 0 } ) subject.add(0, c1) end end context 'and is greater than the number of children' do it 'returns nil' do expect(subject.add(1, c1)).to be_nil subject.add(0, c1) expect(subject.add(2, c2)).to be_nil subject.add(1, c2) expect(subject.add(3, c3)).to be_nil end it 'does not add to the list of children' do expect { subject.add(1, c1) }.to_not change { subject.children } .from({}) subject.add(0, c1) expect { subject.add(2, c2) }.to_not change { subject.children } .from(0 => c1) subject.add(1, c2) expect { subject.add(3, c2) }.to_not change { subject.children } .from(0 => c1, 1 => c2) end end context 'and is less than the number of children' do it 'returns the child' do subject.add(0, c1) expect(subject.add(0, c2)).to eq(c2) expect(subject.add(1, c3)).to eq(c3) end it 'adds to the list of children at the correct position' do expect(subject.children).to eq({}) expect { subject.add(0, c1) }.to change { subject.children } .from({}) .to(0 => c1) expect { subject.add(0, c2) }.to change { subject.children } .from(0 => c1) .to(0 => c2, 1 => c1) expect { subject.add(1, c3) }.to change { subject.children } .from(0 => c2, 1 => c1) .to(0 => c2, 1 => c3, 2 => c1) end it 'calls #update_parent on the child with itself and an ID proc' do expect(c1).to receive(:update_parent) do |parent, proc| expect(parent).to eq(subject) expect(proc.call).to eq(0) end subject.add(0, c2) subject.add(0, c1) end end end end describe '#remove' do context 'when the child exists in the list' do before(:each) { subject.add(0, c1) } it 'returns the child' do expect(subject.remove(c1)).to eq(c1) end it 'calls #update_parent on the child with a Parentless' do expect(c1).to receive(:update_parent).once do |parent, _| expect(parent).to be_a(Compo::Composites::Parentless) end subject.remove(c1) end it 'calls #update_parent on the child with a nil-returning ID proc' do expect(c1).to receive(:update_parent).once do |_, idp| expect(idp.call).to be_nil end subject.remove(c1) end it 'moves succeeding IDs down by one' do subject.add(1, c2) subject.add(2, c3) expect { subject.remove(c2) }.to change { subject.children } .from(0 => c1, 1 => c2, 2 => c3) .to(0 => c1, 1 => c3) end end context 'when the child does not exist in the list' do specify { expect(subject.remove(c1)).to be_nil } it 'does not change the children' do expect(subject.children).to eq({}) expect { subject.remove(c1) }.to_not change { subject.children } .from({}) subject.add(0, c1) subject.add(1, c2) expect { subject.remove(c3) }.to_not change { subject.children } .from eq(0 => c1, 1 => c2) end end end describe '#remove_id' do context 'when the ID exists' do before(:each) { subject.add(0, c1) } it 'returns the child' do expect(subject.remove_id(0)).to eq(c1) end it 'calls #update_parent on the child with a Parentless' do expect(c1).to receive(:update_parent).once do |parent, _| expect(parent).to be_a(Compo::Composites::Parentless) end subject.remove_id(0) end it 'calls #update_parent on the child with a nil-returning ID proc' do expect(c1).to receive(:update_parent).once do |_, idp| expect(idp.call).to be_nil end subject.remove_id(0) end it 'moves succeeding IDs down by one' do subject.add(1, c2) subject.add(2, c3) expect { subject.remove_id(1) }.to change { subject.children } .from(0 => c1, 1 => c2, 2 => c3) .to(0 => c1, 1 => c3) end end context 'when the ID does not exist' do specify { expect(subject.remove_id(0)).to be_nil } it 'does not change the children' do expect { subject.remove_id(0) }.to_not change { subject.children } .from({}) subject.add(0, c1) subject.add(1, c2) expect { subject.remove_id(2) }.to_not change { subject.children } .from(0 => c1, 1 => c2) end end end describe '#children' do context 'when the list has no children' do it 'returns the empty hash' do expect(subject.children).to eq({}) end end context 'when the list has children' do it 'returns a hash mapping their current indices to themselves' do expect(subject.children).to eq({}) subject.add(0, c1) expect(subject.children).to eq(0 => c1) subject.add(1, c2) expect(subject.children).to eq(0 => c1, 1 => c2) subject.add(2, c3) expect(subject.children).to eq(0 => c1, 1 => c2, 2 => c3) subject.remove(c2) expect(subject.children).to eq(0 => c1, 1 => c3) subject.add(0, c2) expect(subject.children).to eq(0 => c2, 1 => c1, 2 => c3) end end end end