995 lines
43 KiB
Ruby
995 lines
43 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
#
|
|
# 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__) + '/../api_spec_helper')
|
|
|
|
describe TabsController, type: :request do
|
|
describe 'index' do
|
|
it "requires read permissions on the context" do
|
|
course_factory(active_all: true)
|
|
user_factory(active_all: true)
|
|
api_call(:get, "/api/v1/courses/#{@course.id}/tabs",
|
|
{ :controller => 'tabs', :action => 'index', :course_id => @course.to_param, :format => 'json' },
|
|
{ :include => ['external'] },
|
|
{},
|
|
{ :expected_status => 401 })
|
|
end
|
|
|
|
it 'lists navigation tabs for a course' do
|
|
course_with_teacher(:active_all => true)
|
|
json = api_call(:get, "/api/v1/courses/#{@course.id}/tabs",
|
|
{ :controller => 'tabs', :action => 'index', :course_id => @course.to_param, :format => 'json' },
|
|
{ :include => ['external'] })
|
|
expect(json).to eq [
|
|
{
|
|
"id" => "home",
|
|
"html_url" => "/courses/#{@course.id}",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@course)}/courses/#{@course.id}",
|
|
"position" => 1,
|
|
"visibility" => "public",
|
|
"label" => "Home",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "announcements",
|
|
"html_url" => "/courses/#{@course.id}/announcements",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@course)}/courses/#{@course.id}/announcements",
|
|
"position" => 2,
|
|
"unused" => true,
|
|
"visibility" => "admins",
|
|
"label" => "Announcements",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "assignments",
|
|
"html_url" => "/courses/#{@course.id}/assignments",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@course)}/courses/#{@course.id}/assignments",
|
|
"position" => 3,
|
|
"unused" => true,
|
|
"visibility" => "admins",
|
|
"label" => "Assignments",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "discussions",
|
|
"html_url" => "/courses/#{@course.id}/discussion_topics",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@course)}/courses/#{@course.id}/discussion_topics",
|
|
"position" => 4,
|
|
"visibility" => "public",
|
|
"label" => "Discussions",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "grades",
|
|
"html_url" => "/courses/#{@course.id}/grades",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@course)}/courses/#{@course.id}/grades",
|
|
"position" => 5,
|
|
"visibility" => "public",
|
|
"label" => "Grades",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "people",
|
|
"html_url" => "/courses/#{@course.id}/users",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@course)}/courses/#{@course.id}/users",
|
|
"position" => 6,
|
|
"visibility" => "public",
|
|
"label" => "People",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "pages",
|
|
"html_url" => "/courses/#{@course.id}/wiki",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@course)}/courses/#{@course.id}/wiki",
|
|
"position" => 7,
|
|
"unused" => true,
|
|
"visibility" => "admins",
|
|
"label" => "Pages",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "files",
|
|
"html_url" => "/courses/#{@course.id}/files",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@course)}/courses/#{@course.id}/files",
|
|
"position" => 8,
|
|
"unused" => true,
|
|
"visibility" => "admins",
|
|
"label" => "Files",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "syllabus",
|
|
"html_url" => "/courses/#{@course.id}/assignments/syllabus",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@course)}/courses/#{@course.id}/assignments/syllabus",
|
|
"position" => 9,
|
|
"visibility" => "public",
|
|
"label" => "Syllabus",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "outcomes",
|
|
"html_url" => "/courses/#{@course.id}/outcomes",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@course)}/courses/#{@course.id}/outcomes",
|
|
"position" => 10,
|
|
"unused" => true,
|
|
"visibility" => "admins",
|
|
"label" => "Outcomes",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "rubrics",
|
|
"html_url" => "/courses/#{@course.id}/rubrics",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@course)}/courses/#{@course.id}/rubrics",
|
|
"position" => 11,
|
|
"visibility" => "admins",
|
|
"label" => "Rubrics",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "quizzes",
|
|
"html_url" => "/courses/#{@course.id}/quizzes",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@course)}/courses/#{@course.id}/quizzes",
|
|
"position" => 12,
|
|
"unused" => true,
|
|
"visibility" => "admins",
|
|
"label" => "Quizzes",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "modules",
|
|
"html_url" => "/courses/#{@course.id}/modules",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@course)}/courses/#{@course.id}/modules",
|
|
"position" => 13,
|
|
"unused" => true,
|
|
"visibility" => "admins",
|
|
"label" => "Modules",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "settings",
|
|
"html_url" => "/courses/#{@course.id}/settings",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@course)}/courses/#{@course.id}/settings",
|
|
"position" => 14,
|
|
"visibility" => "admins",
|
|
"label" => "Settings",
|
|
"type" => "internal"
|
|
}
|
|
]
|
|
end
|
|
|
|
it 'includes tabs for institution-visible courses' do
|
|
course_factory(:active_all => true)
|
|
@course.update_attribute(:is_public_to_auth_users, true)
|
|
user_with_pseudonym
|
|
json = api_call(:get, "/api/v1/courses/#{@course.id}/tabs",
|
|
{ :controller => 'tabs', :action => 'index', :course_id => @course.to_param, :format => 'json' },
|
|
{}, {}, { :expected_status => 200 })
|
|
expect(json.map { |tab| tab['id'] }).to include 'home'
|
|
end
|
|
|
|
it 'includes external tools' do
|
|
course_with_teacher(:active_all => true)
|
|
@tool = @course.context_external_tools.new({
|
|
:name => 'Example',
|
|
:url => 'http://www.example.com',
|
|
:consumer_key => 'key',
|
|
:shared_secret => 'secret',
|
|
})
|
|
@tool.settings.merge!({
|
|
:course_navigation => {
|
|
:enabled => 'true',
|
|
:url => 'http://www.example.com',
|
|
},
|
|
})
|
|
@tool.save!
|
|
|
|
json = api_call(:get, "/api/v1/courses/#{@course.id}/tabs",
|
|
{ :controller => 'tabs', :action => 'index', :course_id => @course.to_param, :format => 'json' },
|
|
{ :include => ['external'] })
|
|
|
|
external_tabs = json.select { |tab| tab['type'] == 'external' }
|
|
expect(external_tabs.length).to eq 1
|
|
external_tabs.each do |tab|
|
|
expect(tab).to include('url')
|
|
uri = URI(tab['url'])
|
|
expect(uri.path).to eq "/api/v1/courses/#{@course.id}/external_tools/sessionless_launch"
|
|
expect(uri.query).to include('id=')
|
|
expect(uri.query).to include('launch_type=course_navigation')
|
|
end
|
|
end
|
|
|
|
it 'launches account navigation external tools with launch_type=account_navigation' do
|
|
account_admin_user(:active_all => true)
|
|
@account = @user.account
|
|
@tool = @account.context_external_tools.new(name: "Ex", url: "http://example.com", consumer_key: "k", shared_secret: "s")
|
|
@tool.settings.merge!(account_navigation: { enabled: 'true', url: 'http://example.com' })
|
|
@tool.save!
|
|
json = api_call(:get, "/api/v1/accounts/#{@account.id}/tabs",
|
|
controller: 'tabs', action: 'index', account_id: @account.to_param, format: 'json')
|
|
external_tabs = json.select { |tab| tab['type'] == 'external' }
|
|
expect(external_tabs.length).to eq 1
|
|
expect(external_tabs.first['url']).to match(
|
|
%r{/api/v1/accounts/#{@account.id}/external_tools/sessionless_launch\?.*launch_type=account_navigation}
|
|
)
|
|
end
|
|
|
|
it "includes collaboration tab if configured" do
|
|
course_with_teacher :active_all => true
|
|
@course.enable_feature! 'new_collaborations'
|
|
json = api_call(:get, "/api/v1/courses/#{@course.id}/tabs",
|
|
{ :controller => 'tabs', :action => 'index', :course_id => @course.to_param, :format => 'json' },
|
|
{ :include => ['external'] })
|
|
expect(json.map { |el| el['id'] }).to include 'collaborations'
|
|
end
|
|
|
|
it "includes webconferences tab if configured" do
|
|
course_with_teacher :active_all => true
|
|
allow_any_instance_of(ApplicationController).to receive(:feature_enabled?).with(:web_conferences).and_return(true)
|
|
json = api_call(:get, "/api/v1/courses/#{@course.id}/tabs",
|
|
{ :controller => 'tabs', :action => 'index', :course_id => @course.to_param, :format => 'json' },
|
|
{ :include => ['external'] })
|
|
expect(json.map { |el| el['id'] }).to include 'conferences'
|
|
end
|
|
|
|
it 'lists navigation tabs for a group' do
|
|
group_with_user(:active_all => true)
|
|
json = api_call(:get, "/api/v1/groups/#{@group.id}/tabs",
|
|
{ :controller => 'tabs', :action => 'index', :group_id => @group.to_param, :format => 'json' })
|
|
expect(json).to eq [
|
|
{
|
|
"id" => "home",
|
|
"html_url" => "/groups/#{@group.id}",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@group)}/groups/#{@group.id}",
|
|
"type" => "internal",
|
|
"label" => "Home",
|
|
"position" => 1,
|
|
"visibility" => "public"
|
|
},
|
|
{
|
|
"id" => "announcements",
|
|
"label" => "Announcements",
|
|
"html_url" => "/groups/#{@group.id}/announcements",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@group)}/groups/#{@group.id}/announcements",
|
|
"position" => 2,
|
|
"visibility" => "public",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "pages",
|
|
"html_url" => "/groups/#{@group.id}/wiki",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@group)}/groups/#{@group.id}/wiki",
|
|
"label" => "Pages",
|
|
"position" => 3,
|
|
"visibility" => "public",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "people",
|
|
"html_url" => "/groups/#{@group.id}/users",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@group)}/groups/#{@group.id}/users",
|
|
"label" => "People",
|
|
"position" => 4,
|
|
"visibility" => "public",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "discussions",
|
|
"html_url" => "/groups/#{@group.id}/discussion_topics",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@group)}/groups/#{@group.id}/discussion_topics",
|
|
"label" => "Discussions",
|
|
"position" => 5,
|
|
"visibility" => "public",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "files",
|
|
"html_url" => "/groups/#{@group.id}/files",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@group)}/groups/#{@group.id}/files",
|
|
"label" => "Files",
|
|
"position" => 6,
|
|
"visibility" => "public",
|
|
"type" => "internal"
|
|
}
|
|
]
|
|
end
|
|
|
|
it 'lists navigation tabs for an account' do
|
|
account_admin_user(:active_all => true)
|
|
@account = @user.account
|
|
json = api_call(:get, "/api/v1/accounts/#{@account.id}/tabs",
|
|
{ :controller => 'tabs', :action => 'index', :account_id => @account.to_param, :format => 'json' })
|
|
expect(json).to eq [
|
|
{
|
|
"id" => "courses",
|
|
"html_url" => "/accounts/#{@account.id}",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@account)}/accounts/#{@account.id}",
|
|
"type" => "internal",
|
|
"label" => "Courses",
|
|
"position" => 1,
|
|
"visibility" => "public"
|
|
},
|
|
{
|
|
"id" => "users",
|
|
"label" => "People",
|
|
"html_url" => "/accounts/#{@account.id}/users",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@account)}/accounts/#{@account.id}/users",
|
|
"position" => 2,
|
|
"visibility" => "public",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "statistics",
|
|
"html_url" => "/accounts/#{@account.id}/statistics",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@account)}/accounts/#{@account.id}/statistics",
|
|
"label" => "Statistics",
|
|
"position" => 3,
|
|
"visibility" => "public",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "permissions",
|
|
"html_url" => "/accounts/#{@account.id}/permissions",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@account)}/accounts/#{@account.id}/permissions",
|
|
"label" => "Permissions",
|
|
"position" => 4,
|
|
"visibility" => "public",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "outcomes",
|
|
"html_url" => "/accounts/#{@account.id}/outcomes",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@account)}/accounts/#{@account.id}/outcomes",
|
|
"label" => "Outcomes",
|
|
"position" => 5,
|
|
"visibility" => "public",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "rubrics",
|
|
"html_url" => "/accounts/#{@account.id}/rubrics",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@account)}/accounts/#{@account.id}/rubrics",
|
|
"label" => "Rubrics",
|
|
"position" => 6,
|
|
"visibility" => "public",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "grading_standards",
|
|
"html_url" => "/accounts/#{@account.id}/grading_standards",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@account)}/accounts/#{@account.id}/grading_standards",
|
|
"label" => "Grading",
|
|
"position" => 7,
|
|
"visibility" => "public",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "question_banks",
|
|
"html_url" => "/accounts/#{@account.id}/question_banks",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@account)}/accounts/#{@account.id}/question_banks",
|
|
"label" => "Question Banks",
|
|
"position" => 8,
|
|
"visibility" => "public",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "sub_accounts",
|
|
"html_url" => "/accounts/#{@account.id}/sub_accounts",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@account)}/accounts/#{@account.id}/sub_accounts",
|
|
"label" => "Sub-Accounts",
|
|
"position" => 9,
|
|
"visibility" => "public",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "terms",
|
|
"html_url" => "/accounts/#{@account.id}/terms",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@account)}/accounts/#{@account.id}/terms",
|
|
"label" => "Terms",
|
|
"position" => 10,
|
|
"visibility" => "public",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "authentication",
|
|
"html_url" => "/accounts/#{@account.id}/authentication_providers",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@account)}/accounts/#{@account.id}/authentication_providers",
|
|
"label" => "Authentication",
|
|
"position" => 11,
|
|
"visibility" => "public",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "sis_import",
|
|
"html_url" => "/accounts/#{@account.id}/sis_import",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@account)}/accounts/#{@account.id}/sis_import",
|
|
"label" => "SIS Import",
|
|
"position" => 12,
|
|
"visibility" => "public",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "brand_configs",
|
|
"html_url" => "/accounts/#{@account.id}/brand_configs",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@account)}/accounts/#{@account.id}/brand_configs",
|
|
"label" => "Themes",
|
|
"position" => 13,
|
|
"visibility" => "public",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "developer_keys",
|
|
"html_url" => "/accounts/#{@account.id}/developer_keys",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@account)}/accounts/#{@account.id}/developer_keys",
|
|
"label" => "Developer Keys",
|
|
"position" => 14,
|
|
"visibility" => "public",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "admin_tools",
|
|
"html_url" => "/accounts/#{@account.id}/admin_tools",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@account)}/accounts/#{@account.id}/admin_tools",
|
|
"label" => "Admin Tools",
|
|
"position" => 15,
|
|
"visibility" => "public",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "eportfolio_moderation",
|
|
"html_url" => "/accounts/#{@account.id}/eportfolio_moderation",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@account)}/accounts/#{@account.id}/eportfolio_moderation",
|
|
"label" => "ePortfolio Moderation",
|
|
"position" => 16,
|
|
"visibility" => "public",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "settings",
|
|
"html_url" => "/accounts/#{@account.id}/settings",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@account)}/accounts/#{@account.id}/settings",
|
|
"label" => "Settings",
|
|
"position" => 17,
|
|
"visibility" => "admins",
|
|
"type" => "internal"
|
|
}
|
|
]
|
|
end
|
|
|
|
it "doesn't include hidden tabs for student" do
|
|
course_with_student(active_all: true)
|
|
tab_ids = [
|
|
Course::TAB_HOME,
|
|
Course::TAB_SYLLABUS,
|
|
Course::TAB_ASSIGNMENTS,
|
|
Course::TAB_DISCUSSIONS,
|
|
Course::TAB_GRADES,
|
|
Course::TAB_PEOPLE,
|
|
Course::TAB_ANNOUNCEMENTS,
|
|
Course::TAB_PAGES,
|
|
Course::TAB_FILES,
|
|
Course::TAB_OUTCOMES,
|
|
Course::TAB_QUIZZES,
|
|
Course::TAB_MODULES,
|
|
Course::TAB_OUTCOMES
|
|
]
|
|
hidden_tabs = [Course::TAB_ASSIGNMENTS, Course::TAB_DISCUSSIONS, Course::TAB_GRADES]
|
|
|
|
@course.tab_configuration = tab_ids.map do |n|
|
|
hash = { 'id' => n }
|
|
hash['hidden'] = true if hidden_tabs.include?(n)
|
|
hash
|
|
end
|
|
@course.save
|
|
json = api_call(:get, "/api/v1/courses/#{@course.id}/tabs", { :controller => 'tabs', :action => 'index',
|
|
:course_id => @course.to_param, :format => 'json' })
|
|
expect(json).to match_array([
|
|
a_hash_including({ "id" => "home" }),
|
|
a_hash_including({ "id" => "syllabus" }),
|
|
a_hash_including({ "id" => "people" }),
|
|
])
|
|
end
|
|
|
|
describe 'canvas for elementary' do
|
|
before(:once) do
|
|
course_with_teacher(:active_all => true)
|
|
@course.account.enable_as_k5_account!
|
|
end
|
|
|
|
it 'lists a select subset of tabs if it is an elementary course and has the include[]=course_subject_tabs param' do
|
|
json = api_call(:get, "/api/v1/courses/#{@course.id}/tabs",
|
|
{ :controller => 'tabs', :action => 'index', :course_id => @course.to_param, :format => 'json' },
|
|
{ :include => ['course_subject_tabs'] })
|
|
expect(json).to eq [
|
|
{
|
|
"id" => "home",
|
|
"html_url" => "/courses/#{@course.id}",
|
|
"full_url" => "http://localhost/courses/#{@course.id}",
|
|
"position" => 1,
|
|
"visibility" => "public",
|
|
"label" => "Home",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "schedule",
|
|
"html_url" => "/courses/#{@course.id}",
|
|
"full_url" => "http://localhost/courses/#{@course.id}",
|
|
"position" => 2,
|
|
"visibility" => "public",
|
|
"label" => "Schedule",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "modules",
|
|
"html_url" => "/courses/#{@course.id}/modules",
|
|
"full_url" => "http://localhost/courses/#{@course.id}/modules",
|
|
"position" => 3,
|
|
"visibility" => "public",
|
|
"label" => "Modules",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "grades",
|
|
"html_url" => "/courses/#{@course.id}/grades",
|
|
"full_url" => "http://localhost/courses/#{@course.id}/grades",
|
|
"position" => 4,
|
|
"visibility" => "public",
|
|
"label" => "Grades",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "groups",
|
|
"html_url" => "/courses/#{@course.id}/groups",
|
|
"full_url" => "http://localhost/courses/#{@course.id}/groups",
|
|
"position" => 5,
|
|
"visibility" => "public",
|
|
"label" => "Groups",
|
|
"type" => "internal"
|
|
}
|
|
]
|
|
end
|
|
|
|
it 'lists navigation tabs without home for an elementary course' do
|
|
json = api_call(:get, "/api/v1/courses/#{@course.id}/tabs",
|
|
{ :controller => 'tabs', :action => 'index', :course_id => @course.to_param, :format => 'json' })
|
|
expect(json).to eq [
|
|
{
|
|
"id" => "announcements",
|
|
"html_url" => "/courses/#{@course.id}/announcements",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@course)}/courses/#{@course.id}/announcements",
|
|
"position" => 1,
|
|
"unused" => true,
|
|
"visibility" => "admins",
|
|
"label" => "Announcements",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "assignments",
|
|
"html_url" => "/courses/#{@course.id}/assignments",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@course)}/courses/#{@course.id}/assignments",
|
|
"position" => 2,
|
|
"unused" => true,
|
|
"visibility" => "admins",
|
|
"label" => "Assignments",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "discussions",
|
|
"html_url" => "/courses/#{@course.id}/discussion_topics",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@course)}/courses/#{@course.id}/discussion_topics",
|
|
"position" => 3,
|
|
"visibility" => "public",
|
|
"label" => "Discussions",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "grades",
|
|
"html_url" => "/courses/#{@course.id}/grades",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@course)}/courses/#{@course.id}/grades",
|
|
"position" => 4,
|
|
"visibility" => "public",
|
|
"label" => "Grades",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "people",
|
|
"html_url" => "/courses/#{@course.id}/users",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@course)}/courses/#{@course.id}/users",
|
|
"position" => 5,
|
|
"visibility" => "public",
|
|
"label" => "People",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "pages",
|
|
"html_url" => "/courses/#{@course.id}/wiki",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@course)}/courses/#{@course.id}/wiki",
|
|
"position" => 6,
|
|
"unused" => true,
|
|
"visibility" => "admins",
|
|
"label" => "Pages",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "files",
|
|
"html_url" => "/courses/#{@course.id}/files",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@course)}/courses/#{@course.id}/files",
|
|
"position" => 7,
|
|
"unused" => true,
|
|
"visibility" => "admins",
|
|
"label" => "Files",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "syllabus",
|
|
"html_url" => "/courses/#{@course.id}/assignments/syllabus",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@course)}/courses/#{@course.id}/assignments/syllabus",
|
|
"position" => 8,
|
|
"visibility" => "public",
|
|
"label" => "Important Info",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "outcomes",
|
|
"html_url" => "/courses/#{@course.id}/outcomes",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@course)}/courses/#{@course.id}/outcomes",
|
|
"position" => 9,
|
|
"unused" => true,
|
|
"visibility" => "admins",
|
|
"label" => "Outcomes",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "rubrics",
|
|
"html_url" => "/courses/#{@course.id}/rubrics",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@course)}/courses/#{@course.id}/rubrics",
|
|
"position" => 10,
|
|
"visibility" => "admins",
|
|
"label" => "Rubrics",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "quizzes",
|
|
"html_url" => "/courses/#{@course.id}/quizzes",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@course)}/courses/#{@course.id}/quizzes",
|
|
"position" => 11,
|
|
"unused" => true,
|
|
"visibility" => "admins",
|
|
"label" => "Quizzes",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "modules",
|
|
"html_url" => "/courses/#{@course.id}/modules",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@course)}/courses/#{@course.id}/modules",
|
|
"position" => 12,
|
|
"unused" => true,
|
|
"visibility" => "admins",
|
|
"label" => "Modules",
|
|
"type" => "internal"
|
|
},
|
|
{
|
|
"id" => "settings",
|
|
"html_url" => "/courses/#{@course.id}/settings",
|
|
"full_url" => "#{HostUrl.protocol}://#{HostUrl.context_host(@course)}/courses/#{@course.id}/settings",
|
|
"position" => 13,
|
|
"visibility" => "admins",
|
|
"label" => "Settings",
|
|
"type" => "internal"
|
|
}
|
|
]
|
|
end
|
|
end
|
|
|
|
describe "teacher in a course" do
|
|
before :once do
|
|
course_with_teacher(active_all: true)
|
|
@tab_ids = [0, 1, 3, 8, 5, 6, 14, 2, 11, 15, 18, 4, 10, 13]
|
|
@tab_lookup = {}.with_indifferent_access
|
|
@course.tabs_available(@teacher, :api => true).each do |t|
|
|
t = t.with_indifferent_access
|
|
@tab_lookup[t['css_class']] = t['id']
|
|
end
|
|
end
|
|
|
|
it 'has the correct position' do
|
|
tab_order = [0, 1, 3, 8, 5, 6, 14, 2, 11, 15, 18, 4, 10, 13]
|
|
@course.tab_configuration = tab_order.map { |n| { 'id' => n } }
|
|
@course.save
|
|
json = api_call(:get, "/api/v1/courses/#{@course.id}/tabs", { :controller => 'tabs', :action => 'index',
|
|
:course_id => @course.to_param, :format => 'json' })
|
|
json.each { |t| expect(t['position']).to eq tab_order.find_index(@tab_lookup[t['id']]) + 1 }
|
|
end
|
|
|
|
it 'correctlies label navigation items as unused' do
|
|
unused_tabs = %w{announcements assignments pages files outcomes quizzes modules}
|
|
json = api_call(:get, "/api/v1/courses/#{@course.id}/tabs", { :controller => 'tabs', :action => 'index',
|
|
:course_id => @course.to_param, :format => 'json' })
|
|
json.each do |t|
|
|
if unused_tabs.include? t['id']
|
|
expect(t['unused']).to be_truthy
|
|
else
|
|
expect(t['unused']).to be_falsey
|
|
end
|
|
end
|
|
end
|
|
|
|
it 'labels hidden items correctly' do
|
|
hidden_tabs = [3, 8, 5]
|
|
@course.tab_configuration = @tab_ids.map do |n|
|
|
hash = { 'id' => n }
|
|
hash['hidden'] = true if hidden_tabs.include?(n)
|
|
hash
|
|
end
|
|
@course.save
|
|
json = api_call(:get, "/api/v1/courses/#{@course.id}/tabs", { :controller => 'tabs', :action => 'index',
|
|
:course_id => @course.to_param, :format => 'json' })
|
|
json.each do |t|
|
|
if hidden_tabs.include? @tab_lookup[t['id']]
|
|
expect(t['hidden']).to be_truthy
|
|
else
|
|
expect(t['hidden']).to be_falsey
|
|
end
|
|
end
|
|
end
|
|
|
|
it 'correctly sets visibility' do
|
|
hidden_tabs = [3, 8, 5]
|
|
public_visibility = %w{home people syllabus}
|
|
admins_visibility = %w{announcements assignments pages files outcomes rubrics quizzes modules settings discussions grades}
|
|
@course.tab_configuration = @tab_ids.map do |n|
|
|
hash = { 'id' => n }
|
|
hash['hidden'] = true if hidden_tabs.include?(n)
|
|
hash
|
|
end
|
|
@course.save
|
|
json = api_call(:get, "/api/v1/courses/#{@course.id}/tabs", { :controller => 'tabs', :action => 'index',
|
|
:course_id => @course.to_param, :format => 'json' })
|
|
json.each do |t|
|
|
if t['visibility'] == 'public'
|
|
expect(public_visibility).to include(t['id'])
|
|
elsif t['visibility'] == 'admins'
|
|
expect(admins_visibility).to include(t['id'])
|
|
else
|
|
expect(true).to be_falsey
|
|
end
|
|
end
|
|
end
|
|
|
|
it 'sorts tabs correctly' do
|
|
course_with_teacher(active_all: true)
|
|
tab_order = [0, 1, 3, 8, 5, 6, 14, 2, 11, 15, 4, 10, 13]
|
|
@course.tab_configuration = tab_order.map { |n| { 'id' => n } }
|
|
@course.save
|
|
json = api_call(:get, "/api/v1/courses/#{@course.id}/tabs", { :controller => 'tabs', :action => 'index',
|
|
:course_id => @course.to_param, :format => 'json' })
|
|
json.each_with_index { |t, i| expect(t['position']).to eq i + 1 }
|
|
end
|
|
end
|
|
|
|
describe "user profile" do
|
|
before(:each) { user_model }
|
|
|
|
let(:tool) {
|
|
Account.default.context_external_tools.new({
|
|
:name => 'Example',
|
|
:url => 'http://www.example.com',
|
|
:consumer_key => 'key',
|
|
:shared_secret => 'secret',
|
|
})
|
|
}
|
|
|
|
it 'includes external tools' do
|
|
tool.settings[:user_navigation] = {
|
|
:enabled => 'true',
|
|
:url => 'http://www.example.com',
|
|
}
|
|
tool.save!
|
|
|
|
json = api_call(:get, "/api/v1/users/#{@user.id}/tabs",
|
|
{ :controller => 'tabs', :action => 'index', :user_id => @user.to_param, :format => 'json' })
|
|
|
|
expect(json).to include(include('type' => 'external', 'label' => 'Example'))
|
|
end
|
|
|
|
it "handles external tools with windowTarget: _blank" do
|
|
tool.settings[:user_navigation] = {
|
|
enable: true,
|
|
url: 'http://www.example.com/foo',
|
|
windowTarget: '_blank'
|
|
}
|
|
tool.save!
|
|
|
|
json = api_call(:get, "/api/v1/users/#{@user.id}/tabs",
|
|
{ :controller => 'tabs', :action => 'index', :user_id => @user.to_param, :format => 'json' })
|
|
|
|
tab = json.find { |j| j['type'] == 'external' }
|
|
expect(tab['html_url']).to match(%r{^/users/[0-9]+/external_tools/[0-9]+\?display=borderless$})
|
|
expect(tab['full_url']).to match(%r{^http.*users/[0-9]+/external_tools/[0-9]+\?display=borderless$})
|
|
end
|
|
|
|
it "handles LTI 2 tools" do
|
|
course_model
|
|
|
|
expect(Lti::MessageHandler).to receive(:lti_apps_tabs).and_return([
|
|
{
|
|
:id => "dontcare",
|
|
:label => "dontcare",
|
|
:css_class => "dontcare",
|
|
:href => :course_basic_lti_launch_request_path,
|
|
:visibility => nil,
|
|
:external => true,
|
|
:hidden => false,
|
|
:args => { message_handler_id: 123, resource_link_fragment: "nav", course_id: @course.id }
|
|
}
|
|
])
|
|
json = api_call(:get, "/api/v1/courses/#{@course.id}/tabs",
|
|
{ :controller => 'tabs', :action => 'index', :course_id => @course.id, :format => 'json' })
|
|
expect(json.to_json).not_to include('internal_server_error')
|
|
tab = json.find { |j| j['id'] == 'dontcare' }
|
|
expect(tab['html_url']).to eql("/courses/#{@course.id}/lti/basic_lti_launch_request/123?resource_link_fragment=nav")
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'update' do
|
|
it 'sets the people tab to hidden' do
|
|
tab_id = 'people'
|
|
course_with_teacher(active_all: true)
|
|
json = api_call(:put, "/api/v1/courses/#{@course.id}/tabs/#{tab_id}", { :controller => 'tabs', :action => 'update',
|
|
:course_id => @course.to_param, :tab_id => tab_id,
|
|
:format => 'json', :hidden => true })
|
|
expect(json['hidden']).to eq true
|
|
expect(@course.reload.tab_configuration[json['position'] - 1]['hidden']).to eq true
|
|
end
|
|
|
|
it 'only unhides one tab and not all when first updating' do
|
|
course_with_teacher(:active_all => true)
|
|
tools = []
|
|
|
|
3.times do |i|
|
|
tool = @course.context_external_tools.new({
|
|
:name => "Example #{i}",
|
|
:url => 'http://www.example.com',
|
|
:consumer_key => 'key',
|
|
:shared_secret => 'secret'
|
|
})
|
|
tool.settings.merge!({
|
|
:course_navigation => {
|
|
:default => 'disabled',
|
|
:url => 'http://www.example.com',
|
|
},
|
|
})
|
|
tool.save!
|
|
tools << tool.reload
|
|
end
|
|
|
|
tab_id = "context_external_tool_#{tools.first.id}"
|
|
json = api_call(:put, "/api/v1/courses/#{@course.id}/tabs/#{tab_id}", { :controller => 'tabs', :action => 'update',
|
|
:course_id => @course.to_param, :tab_id => tab_id,
|
|
:format => 'json', :hidden => false })
|
|
expect(json['hidden']).to be_nil
|
|
expect(@course.reload.tab_configuration[json['position'] - 1]['hidden']).to be_nil
|
|
expect(@course.reload.tab_configuration.select { |t| t['hidden'] }.count).to eql(tools.count - 1)
|
|
end
|
|
|
|
it 'allows updating new tabs not in the configuration yet' do
|
|
course_with_teacher(:active_all => true)
|
|
tab_ids = [0, 1, 3, 8, 5, 6, 14, 2, 11, 15, 4, 10, 13]
|
|
@course.tab_configuration = tab_ids.map { |id| hash = { 'id' => id } }
|
|
@course.save!
|
|
|
|
@tool = @course.context_external_tools.new({
|
|
:name => 'Example',
|
|
:url => 'http://www.example.com',
|
|
:consumer_key => 'key',
|
|
:shared_secret => 'secret',
|
|
})
|
|
@tool.settings.merge!({
|
|
:course_navigation => {
|
|
:enabled => 'true',
|
|
:url => 'http://www.example.com',
|
|
},
|
|
})
|
|
@tool.save!
|
|
tab_id = "context_external_tool_#{@tool.id}"
|
|
|
|
json = api_call(:put, "/api/v1/courses/#{@course.id}/tabs/#{tab_id}", { :controller => 'tabs', :action => 'update',
|
|
:course_id => @course.to_param, :tab_id => tab_id,
|
|
:format => 'json', :hidden => true })
|
|
expect(json['hidden']).to eq true
|
|
expect(@course.reload.tab_configuration[json['position'] - 1]['hidden']).to eq true
|
|
end
|
|
|
|
it 'changes the position of the people tab to 2' do
|
|
tab_id = 'people'
|
|
course_with_teacher(active_all: true)
|
|
json = api_call(:put, "/api/v1/courses/#{@course.id}/tabs/#{tab_id}", { :controller => 'tabs', :action => 'update',
|
|
:course_id => @course.to_param, :tab_id => tab_id,
|
|
:format => 'json', :position => 2 })
|
|
expect(json['position']).to eq 2
|
|
expect(@course.reload.tab_configuration[1]['id']).to eq @course.class::TAB_PEOPLE
|
|
end
|
|
|
|
it "won't allow you to hide the home tab" do
|
|
tab_id = 'home'
|
|
course_with_teacher(active_all: true)
|
|
result = raw_api_call(:put, "/api/v1/courses/#{@course.id}/tabs/#{tab_id}", { :controller => 'tabs', :action => 'update',
|
|
:course_id => @course.to_param, :tab_id => tab_id,
|
|
:format => 'json', :hidden => true })
|
|
expect(result).to eq 400
|
|
end
|
|
|
|
it "won't allow you to move a tab to the first position" do
|
|
tab_id = 'people'
|
|
course_with_teacher(active_all: true)
|
|
result = raw_api_call(:put, "/api/v1/courses/#{@course.id}/tabs/#{tab_id}", { :controller => 'tabs', :action => 'update',
|
|
:course_id => @course.to_param, :tab_id => tab_id,
|
|
:format => 'json', :position => 1 })
|
|
expect(result).to eq 400
|
|
end
|
|
|
|
it "won't allow you to move a tab to an invalid position" do
|
|
tab_id = 'people'
|
|
course_with_teacher(active_all: true)
|
|
result = raw_api_call(:put, "/api/v1/courses/#{@course.id}/tabs/#{tab_id}", { :controller => 'tabs', :action => 'update',
|
|
:course_id => @course.to_param, :tab_id => tab_id,
|
|
:format => 'json', :position => 400 })
|
|
expect(result).to eq 400
|
|
end
|
|
|
|
it "doesn't allow a student to modify a tab" do
|
|
course_with_student(active_all: true)
|
|
tab_id = 'people'
|
|
result = raw_api_call(:put, "/api/v1/courses/#{@course.id}/tabs/#{tab_id}", { :controller => 'tabs', :action => 'update',
|
|
:course_id => @course.to_param, :tab_id => tab_id,
|
|
:format => 'json', :position => 4 })
|
|
expect(result).to eq 401
|
|
end
|
|
|
|
it 'allows updating tabs to a new LTI position when the penultimate tab is hidden' do
|
|
course_with_teacher(:active_all => true)
|
|
tab_ids = [0, 1, 3, 8, 5, 6, 14, 2, 11, 15, 4, 10, 13]
|
|
@course.tab_configuration = tab_ids.each_with_index.map do |id, i|
|
|
{ 'id' => id, 'hidden' => (i == tab_ids.count - 2) }
|
|
end
|
|
@course.save!
|
|
|
|
@tool = @course.context_external_tools.new({
|
|
:name => 'Example',
|
|
:url => 'http://www.example.com',
|
|
:consumer_key => 'key',
|
|
:shared_secret => 'secret',
|
|
:course_navigation => {
|
|
:enabled => 'true',
|
|
:url => 'http://www.example.com',
|
|
}
|
|
})
|
|
@tool.save!
|
|
tab_id = 'rubrics'
|
|
position = 14
|
|
|
|
json = api_call(:put, "/api/v1/courses/#{@course.id}/tabs/#{tab_id}", { :controller => 'tabs', :action => 'update',
|
|
:position => position, :course_id => @course.to_param, :tab_id => tab_id,
|
|
:format => 'json' })
|
|
expect(json['position']).to eq position
|
|
end
|
|
end
|
|
end
|