canvas-lms/spec/lib/bookmarked_collection_spec.rb

361 lines
13 KiB
Ruby

#
# Copyright (C) 2012 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper.rb')
describe "BookmarkedCollection" do
class IDBookmarker
def self.bookmark_for(object)
object.id
end
def self.validate(bookmark)
# can't actually validate because sometimes it'll be a mock
true
end
def self.restrict_scope(scope, pager)
if bookmark = pager.current_bookmark
comparison = (pager.include_bookmark ? 'id >= ?' : 'id > ?')
scope = scope.where(comparison, bookmark)
end
scope.order("id ASC")
end
end
class NameBookmarker
def self.bookmark_for(course)
course.name
end
def self.validate(bookmark)
bookmark.is_a?(String)
end
def self.restrict_scope(scope, pager)
if bookmark = pager.current_bookmark
comparison = (pager.include_bookmark ? 'name >= ?' : 'name > ?')
scope = scope.where(comparison, bookmark)
end
scope.order("name ASC")
end
end
describe ".wrap" do
before :each do
@scope = Course
3.times{ @scope.create! }
end
it "should return a WrapProxy" do
BookmarkedCollection.wrap(IDBookmarker, @scope).should be_a(PaginatedCollection::Proxy)
end
it "should use the provided scope when executing pagination" do
collection = BookmarkedCollection.wrap(IDBookmarker, @scope)
collection.paginate(:per_page => 1).should == [@scope.first]
end
it "should use the bookmarker's bookmark generator to produce bookmarks" do
bookmark = stub
IDBookmarker.stubs(:bookmark_for).returns(bookmark)
collection = BookmarkedCollection.wrap(IDBookmarker, @scope)
collection.paginate(:per_page => 1).next_bookmark.should == bookmark
end
it "should use the bookmarker's bookmark applicator to restrict by bookmark" do
bookmark = @scope.order("courses.id").first.id
bookmarked_scope = @scope.order("courses.id").where("courses.id>?", bookmark)
IDBookmarker.stubs(:restrict_scope).returns(bookmarked_scope)
collection = BookmarkedCollection.wrap(IDBookmarker, @scope)
collection.paginate(:per_page => 1).should == [bookmarked_scope.first]
end
it "should apply any restriction block given to the scope" do
course = @scope.order("courses.id").last
course.update_attributes(:name => 'Matching Name')
collection = BookmarkedCollection.wrap(IDBookmarker, @scope) do |scope|
scope.where(:name => course.name)
end
collection.paginate(:per_page => 1).should == [course]
end
end
describe ".merge" do
before :each do
@created_scope = Course.where(:workflow_state => 'created')
@deleted_scope = Course.where(:workflow_state => 'deleted')
Course.delete_all
@created_course1 = @created_scope.create!
@deleted_course1 = @deleted_scope.create!
@created_course2 = @created_scope.create!
@deleted_course2 = @deleted_scope.create!
@created_collection = BookmarkedCollection.wrap(IDBookmarker, @created_scope)
@deleted_collection = BookmarkedCollection.wrap(IDBookmarker, @deleted_scope)
@collection = BookmarkedCollection.merge(
['created', @created_collection],
['deleted', @deleted_collection]
)
end
it "should return a merge proxy" do
@collection.should be_a(BookmarkedCollection::MergeProxy)
end
it "should merge the given collections" do
@collection.paginate(:per_page => 2).should == [@created_course1, @deleted_course1]
end
it "should have a next page when there's a non-exhausted collection" do
@collection.paginate(:per_page => 3).next_page.should_not be_nil
end
it "should not have a next page when all collections are exhausted" do
@collection.paginate(:per_page => 4).next_page.should be_nil
end
it "should pick up in the middle of a collection" do
page = @collection.paginate(:per_page => 1)
page.should == [@created_course1]
page.next_bookmark.should_not be_nil
@collection.paginate(:page => page.next_page, :per_page => 2).should == [@deleted_course1, @created_course2]
end
context "with a merge proc" do
before :each do
# the name bookmarker will generate the same bookmark for both of the
# courses.
Course.delete_all
@created_course = @created_scope.create!(:name => "Same Name")
@deleted_course = @deleted_scope.create!(:name => "Same Name")
@created_collection = BookmarkedCollection.wrap(NameBookmarker, @created_scope)
@deleted_collection = BookmarkedCollection.wrap(NameBookmarker, @deleted_scope)
@collection = BookmarkedCollection.merge(
['created', @created_collection],
['deleted', @deleted_collection]
) do; end
end
it "should collapse duplicates" do
@collection.paginate(:per_page => 2).should == [@created_course]
end
end
context "with ties across collections" do
before :each do
# the name bookmarker will generate the same bookmark for both of the
# courses.
Course.delete_all
@created_course = @created_scope.create!(:name => "Same Name")
@deleted_course = @deleted_scope.create!(:name => "Same Name")
@created_collection = BookmarkedCollection.wrap(NameBookmarker, @created_scope)
@deleted_collection = BookmarkedCollection.wrap(NameBookmarker, @deleted_scope)
@collection = BookmarkedCollection.merge(
['created', @created_collection],
['deleted', @deleted_collection]
)
end
it "should sort the ties by collection" do
@collection.paginate(:per_page => 2).should == [@created_course, @deleted_course]
end
it "should pick up at the right place when a page break splits the tie" do
page = @collection.paginate(:per_page => 1)
page.should == [@created_course]
page.next_bookmark.should_not be_nil
page = @collection.paginate(:page => page.next_page, :per_page => 1)
page.should == [@deleted_course]
page.next_bookmark.should be_nil
end
end
end
describe ".concat" do
before :each do
@created_scope = Course.where(:workflow_state => 'created')
@deleted_scope = Course.where(:workflow_state => 'deleted')
Course.delete_all
@created_course1 = @created_scope.create!
@deleted_course1 = @deleted_scope.create!
@created_course2 = @created_scope.create!
@deleted_course2 = @deleted_scope.create!
@created_collection = BookmarkedCollection.wrap(IDBookmarker, @created_scope)
@deleted_collection = BookmarkedCollection.wrap(IDBookmarker, @deleted_scope)
@collection = BookmarkedCollection.concat(
['created', @created_collection],
['deleted', @deleted_collection]
)
end
it "should return a concat proxy" do
@collection.should be_a(BookmarkedCollection::ConcatProxy)
end
it "should concatenate the given collections" do
@collection.paginate(:per_page => 3).should == [@created_course1, @created_course2, @deleted_course1]
end
it "should have a next page when there's a non-exhausted collection" do
@collection.paginate(:per_page => 3).next_page.should_not be_nil
end
it "should have a next page on the border between an exhausted collection and a non-exhausted collection" do
@collection.paginate(:per_page => 2).next_page.should_not be_nil
end
it "should not have a next page when all collections are exhausted" do
@collection.paginate(:per_page => 4).next_page.should be_nil
end
it "should pick up in the middle of a collection" do
page = @collection.paginate(:per_page => 1)
page.should == [@created_course1]
page.next_bookmark.should_not be_nil
@collection.paginate(:page => page.next_page, :per_page => 2).should == [@created_course2, @deleted_course1]
end
it "should pick up from a break between collections" do
page = @collection.paginate(:per_page => 2)
page.should == [@created_course1, @created_course2]
page.next_bookmark.should_not be_nil
@collection.paginate(:page => page.next_page, :per_page => 2).should == [@deleted_course1, @deleted_course2]
end
end
describe "nested compositions" do
before :each do
@user_scope = User
@created_scope = Course.where(:workflow_state => 'created')
@deleted_scope = Course.where(:workflow_state => 'deleted')
# user's names are so it sorts Created X < Creighton < Deanne < Deleted
# X when using NameBookmarks
Course.delete_all
User.delete_all
@user1 = @user_scope.create!(:name => "Creighton")
@user2 = @user_scope.create!(:name => "Deanne")
@created_course1 = @created_scope.create!(:name => "Created 1")
@deleted_course1 = @deleted_scope.create!(:name => "Deleted 1")
@created_course2 = @created_scope.create!(:name => "Created 2")
@deleted_course2 = @deleted_scope.create!(:name => "Deleted 2")
end
it "should handle concat(A, merge(B, C))" do
@user_collection = BookmarkedCollection.wrap(IDBookmarker, @user_scope)
@created_collection = BookmarkedCollection.wrap(IDBookmarker, @created_scope)
@deleted_collection = BookmarkedCollection.wrap(IDBookmarker, @deleted_scope)
@course_collection = BookmarkedCollection.merge(
['created', @created_collection],
['deleted', @deleted_collection]
)
@collection = BookmarkedCollection.concat(
['users', @user_collection],
['courses', @course_collection]
)
page = @collection.paginate(:per_page => 4)
page.should == [@user1, @user2, @created_course1, @deleted_course1]
page.next_page.should_not be_nil
page = @collection.paginate(:page => page.next_page, :per_page => 2)
page.should == [@created_course2, @deleted_course2]
page.next_page.should be_nil
end
it "should handle merge(A, merge(B, C))" do
# use NameBookmarker to make user/course merge interesting
@user_collection = BookmarkedCollection.wrap(NameBookmarker, @user_scope)
@created_collection = BookmarkedCollection.wrap(NameBookmarker, @created_scope)
@deleted_collection = BookmarkedCollection.wrap(NameBookmarker, @deleted_scope)
@course_collection = BookmarkedCollection.merge(
['created', @created_collection],
['deleted', @deleted_collection]
)
@collection = BookmarkedCollection.merge(
['users', @user_collection],
['courses', @course_collection]
)
page = @collection.paginate(:per_page => 3)
page.should == [@created_course1, @created_course2, @user1]
page.next_page.should_not be_nil
page = @collection.paginate(:page => page.next_page, :per_page => 3)
page.should == [@user2, @deleted_course1, @deleted_course2]
page.next_page.should be_nil
end
it "should handle concat(A, concat(B, C))" do
@user_collection = BookmarkedCollection.wrap(IDBookmarker, @user_scope)
@created_collection = BookmarkedCollection.wrap(IDBookmarker, @created_scope)
@deleted_collection = BookmarkedCollection.wrap(IDBookmarker, @deleted_scope)
@course_collection = BookmarkedCollection.concat(
['created', @created_collection],
['deleted', @deleted_collection])
@collection = BookmarkedCollection.concat(
['users', @user_collection],
['courses', @course_collection])
page = @collection.paginate(:per_page => 3)
page.should == [@user1, @user2, @created_course1]
page.next_page.should_not be_nil
page = @collection.paginate(:page => page.next_page, :per_page => 3)
page.should == [@created_course2, @deleted_course1, @deleted_course2]
page.next_page.should be_nil
end
it "should not allow merge(A, concat(B, C))" do
@user_collection = BookmarkedCollection.wrap(NameBookmarker, @user_scope)
@created_collection = BookmarkedCollection.wrap(NameBookmarker, @created_scope)
@deleted_collection = BookmarkedCollection.wrap(NameBookmarker, @deleted_scope)
@course_collection = BookmarkedCollection.concat(
['created', @created_collection],
['deleted', @deleted_collection])
expect{
@collection = BookmarkedCollection.merge(
['users', @user_collection],
['courses', @course_collection])
}.to raise_exception ArgumentError
end
end
end