require 'spec_helper' describe OpenConferenceWare::Schedule do fixtures :all before(:each) do # Sessions @sessions = [] # ...typical session @sessions << @rakudo_session = proposals(:rakudo_session) # ...at same time, but with different duration @sessions << @drizzle_session = proposals(:drizzle_session) @sessions << @postgresql_session = proposals(:postgresql_session) # ..at same time, with same duration @sessions << @cloud_session = proposals(:cloud_session) @sessions << @business_session = proposals(:business_session) # Schedule items @schedule_items = [] # ...without a duration/end_time @schedule_items << @opening_item = schedule_items(:opening) # ...with a duration/end_time @schedule_items << @coffee_break_item = schedule_items(:coffee_break) # ..without a start_time @schedule_items << @unscheduled_item = schedule_items(:unscheduled) # Composites @entries = (@sessions + @schedule_items).sort { rand } @scheduleable_entries = @entries - [@unscheduled_item] @schedule = OpenConferenceWare::Schedule.new(@entries) end it "should not initialize from unknown types" do lambda { OpenConferenceWare::Schedule.new(:omg) }.should raise_error(TypeError) end it "should initialize from an Event" do @schedule.days.should_not be_empty @schedule.sections.should_not be_empty @schedule.slices.should_not be_empty @schedule.blocks.should_not be_empty @schedule.items.should_not be_empty end it "should sort schedule contents by their start time and end time if given" do @schedule.items.should == @schedule.items.sort_by{|t| [t.start_time.to_i, t.end_time.to_i]} end it "should initialize from an array of proposals, skipping unscheduled items" do @schedule.items.sort_by(&:title).should == @scheduleable_entries.sort_by(&:title) end describe "is a container for days and" do before do @days = @schedule.days end it "thus, should have days" do @days.should_not be_blank @days.first.should be_a_kind_of(OpenConferenceWare::ScheduleDay) end describe "days are containers for sections and" do before do @sections = @days.first.sections end it "thus, they should have sections" do @sections.should_not be_blank @sections.first.should be_a_kind_of(OpenConferenceWare::ScheduleSection) end describe "sections are containers for slices and" do before do @slices = @sections.first.slices end it "thus, they should have slices" do @slices.should_not be_blank @slices.first.should be_a_kind_of(OpenConferenceWare::ScheduleSlice) end describe "blocks are containers for blocks and" do before do @blocks = @slices.first.blocks end it "thus, they should have blocks" do @blocks.should_not be_blank @blocks.first.should be_a_kind_of(OpenConferenceWare::ScheduleBlock) end describe "blocks are containers for items and" do before do @items = @blocks.first.items end it "thus, they should have items" do @items.should_not be_blank @items.first.should respond_to(:start_time) end end end end it "thus, they should have access to their children's slices" do @days.first.slices.first.should be_a_kind_of(OpenConferenceWare::ScheduleSlice) end it "thus, they should have access to their children's blocks" do @days.first.blocks.first.should be_a_kind_of(OpenConferenceWare::ScheduleBlock) end end end describe "(given a valid set of items to process)" do describe "when building a schedule" do before(:each) do @days = @schedule.days @sections = @schedule.sections @slices = @schedule.slices @blocks = @schedule.blocks @postgresql_block = @blocks.find{|block| block.items.include?(@postgresql_session)} @drizzle_block = @blocks.find{|block| block.items.include?(@drizzle_session)} @rakudo_block = @blocks.find{|block| block.items.include?(@rakudo_session)} @cloud_block = @blocks.find{|block| block.items.include?(@cloud_session)} @postgresql_slice = @slices.find{|slice| slice.blocks.include?(@postgresql_block)} @drizzle_slice = @slices.find{|slice| slice.blocks.include?(@drizzle_block)} @rakudo_section = @sections.find{|section| section.blocks.include?(@rakudo_block)} @postgresql_section = @sections.find{|section| section.blocks.include?(@postgresql_block)} end it "should create a day for each day represented in the input set" do @days.map(&:date).sort.should == @scheduleable_entries.map{|entry| entry.start_time.to_date}.uniq.sort end it "each item should be contained in one and only one block" do @scheduleable_entries.each do |entry| @blocks.select{|block| block.items.include?(entry)}.size.should == 1 end end it "should set the time boundries for each block equal to those of its items" do @blocks.each do |block| block.items.each do |item| block.start_time.should == item.start_time block.duration.to_i.should == item.duration.to_i end end end describe "should group items that share a start time and duration into a block:" do it "The drizzle session should be alone in its block" do @drizzle_block.items.should == [@drizzle_session] end it "The cloud session should be in the same block as the business session" do @cloud_block.items.size.should == 2 @cloud_block.items.should include(@business_session) end end describe "should group overlapping blocks into sections:" do it "The rakudo block should be alone in its section" do @rakudo_section.blocks.should == [@rakudo_block] end it "The postgresql block should be in the same block as the drizzle block" do @postgresql_section.blocks.size.should == 2 @postgresql_section.blocks.should include(@drizzle_block) end end describe "should break sections into slices to avoid overlaps:" do it "The section containing postgresql should contain a slice for postgresql and a slice for drizzle" do @postgresql_section.slices.size.should == 2 @postgresql_section.slices.should include(@postgresql_slice) @postgresql_section.slices.should include(@drizzle_slice) end it "The rakudo section should contain a single slice" do @rakudo_section.slices.size.should == 1 end end end end describe "(given an invalid set of items to process)" do it "should raise an error when given an item that is not scheduleable" end describe "with conflicts" do before(:each) do @time1 = Time.zone.parse("2009-05-13 18:00") @time2 = Time.zone.parse("2009-05-13 20:45") @time3 = Time.zone.parse("2009-05-13 21:00") @time4 = Time.zone.parse("2009-05-13 21:30") @room1 = stub_model(Room) @room2 = stub_model(Room) @user1 = stub_model(User) @user2 = stub_model(User) # Same time and room @item1 = build(:proposal, room: @room1, start_time: @time1) @item1.stub(end_time: @time2) @item2 = build(:proposal, room: @room1, start_time: @time1) @item2.stub(end_time: @time2) # Same time, different room @item3 = build(:proposal, room: @room2, start_time: @time1) @item3.stub(end_time: @time2) # Different time, same room @item4 = build(:proposal, room: @room1, start_time: @time3) @item4.stub(end_time: @time4) # Same time and speaker @item5 = build(:proposal, room: @room1, start_time: @time1, users: [@user1]) @item5.stub(end_time: @time2) @item6 = build(:proposal, room: @room2, start_time: @time1, users: [@user1]) @item6.stub(end_time: @time2) # Same time, different speaker @item7 = build(:proposal, room: @room2, start_time: @time1, users: [@user2]) @item7.stub(end_time: @time2) # Different time and speaker @item8 = build(:proposal, room: @room2, start_time: @time3, users: [@user2]) @item8.stub(end_time: @time8) # User associations @user1.stub(:proposals).and_return(double(Array, scheduled: double(Array, all: [@item5, @item6]))) @user2.stub(:proposals).and_return(double(Array, scheduled: double(Array, all: [@item7, @item8]))) end describe "for users" do it "should identify conflicting items" do items = [@item5, @item6] schedule = OpenConferenceWare::Schedule.new(items) conflicts = schedule.user_conflicts conflicts.size.should == 1 end it "should not misidentify nonconflicting items" do items = [@item5, @item7] schedule = OpenConferenceWare::Schedule.new(items) conflicts = schedule.user_conflicts conflicts.size.should == 0 end end describe "for rooms" do it "should identify items happening at same time and room" do schedule = OpenConferenceWare::Schedule.new([@item1, @item2]) conflicts = schedule.room_conflicts conflicts.size.should == 1 [ {room: @room1, item: @item1, conflicts_with: @item2}, {room: @room1, item: @item2, conflicts_with: @item1} ].should include(conflicts.first) end it "should not identify items happening at same time in separate rooms" do schedule = OpenConferenceWare::Schedule.new([@item1, @item3]) conflicts = schedule.room_conflicts conflicts.size.should == 0 end it "should not identify items happening at different times in same room" do schedule = OpenConferenceWare::Schedule.new([@item1, @item4]) conflicts = schedule.room_conflicts conflicts.size.should == 0 end end end end class SchedulableThingy include OpenConferenceWare::Schedulable end describe SchedulableThingy do before(:each) do @thingy = SchedulableThingy.new @duration = 45 @time1 = Time.now @time2 = @time1 + @duration.minutes end describe "when start_time is set" do before(:each) do @thingy.start_time = @time1 end it "should set start_time" do @thingy.start_time.should == @time1 end it "should set duration from end_time" do @thingy.end_time = @time2 @thingy.duration.should == @duration end it "should set end_time from duration" do @thingy.duration = @duration @thingy.end_time.should == @time2 end it "should fail if end_time is before the set start_time" do lambda { @thingy.end_time = @time1 - 1.hour }.should raise_error(ArgumentError) end it "should fail if duration is negative" do lambda { @thingy.duration = -42 }.should raise_error(ArgumentError) end end describe "when start_time is not set" do it "should fail to set end_time if start_time isn't set" do lambda { @thingy.end_time = @time2 }.should raise_error(ArgumentError) end it "should fail to set duration if start_time isn't set" do lambda { @thingy.duration = -42 }.should raise_error(ArgumentError) end end end describe OpenConferenceWare::ScheduleDay do it "should compute lowest-common multiplier for day's section slices" do values = [5, 6, 15] day = OpenConferenceWare::ScheduleDay.new([]) day.stub(:sections).and_return(double(Array, map: values)) day.lcm_colspan.should == 30 end end describe OpenConferenceWare::ScheduleSection do it "should compute least-common multiplier for section's slice blocks" do values = [3, 4, 6] section = OpenConferenceWare::ScheduleSection.new([]) section.stub(:slices).and_return(double(Array, map: values)) section.lcm_rowspan.should == 12 end end