don't let students message the whole class by default, fixes CNVS-3536

adds new "Send messages to the entire class" permission, which defaults
to off for students. controls whether or not the "Select All" checkbox
and the checkboxes next to Everybody/Teachers/Students/etc. are
available in the recipient finder.

test plan:

1. As a teacher, confirm that the Everybody/Teachers/Students/Select All
   checkboxes are available in any courses you teach
2. As a student, confirm that the Everybody/Teachers/Students/Select All
   checkboxes are not available in any courses where you are just a
   student
3. In course permissions, let students "Send messages to the entire class"
4. Confirm that students now see all those checkboxes

Change-Id: I5bca82414dc6e1e2f19abdd2e3257ca935d6f2c4
Reviewed-on: https://gerrit.instructure.com/17519
Tested-by: Jenkins <jenkins@instructure.com>
Reviewed-by: Joe Tanner <joe@instructure.com>
QA-Review: Cam Theriault <cam@instructure.com>
This commit is contained in:
Jon Jensen 2013-02-06 17:40:16 -07:00
parent b290e23d31
commit d1434fffb3
14 changed files with 182 additions and 54 deletions

View File

@ -543,6 +543,8 @@ define [
delete data.avatar_url # since it's the wrong size and possibly a blank image
currentData = @userCache[data.id] ? {}
@userCache[data.id] = $.extend(currentData, data)
canToggle: (data) ->
data.type is 'user' or data.permissions?.send_messages_all
selector:
limiter: (options) =>
if options.level > 0 then -1 else 5
@ -550,25 +552,30 @@ define [
preparer: (postData, data, parent) =>
context = postData.context
if not postData.search and context and data.length > 1
parentData = parent.data('user_data')
if context.match(/^(course|section)_\d+$/)
# i.e. we are listing synthetic contexts under a course or section
data.unshift
id: "#{context}_all"
name: everyoneText
user_count: parent.data('user_data').user_count
user_count: parentData.user_count
type: 'context'
avatar_url: parent.data('user_data').avatar_url
selectAll: true
else if context.match(/^((course|section)_\d+_.*|group_\d+)$/) and not context.match(/^course_\d+_(groups|sections)$/)
avatar_url: parentData.avatar_url
permissions: parentData.permissions
selectAll: parentData.permissions.send_messages_all
else if context.match(/^((course|section)_\d+_.*|group_\d+)$/) and not context.match(/^course_\d+_(groups|sections)$/) and parentData.permissions.send_messages_all
# i.e. we are listing all users in a group or synthetic context
data.unshift
id: context
name: selectAllText
user_count: parent.data('user_data').user_count
user_count: parentData.user_count
type: 'context'
avatar_url: parent.data('user_data').avatar_url
avatar_url: parentData.avatar_url
permissions: parentData.permissions
selectAll: true
noExpand: true # just a magic select-all checkbox, you can't drill into it
baseData:
permissions: ["send_messages_all"]
return if $scope

View File

@ -45,10 +45,13 @@ define [
options = $.extend true, {}, @defaults(), options
@prefixUserIds = options.prefixUserIds
@contexts = options.contexts
@canToggle = options.canToggle if options.canToggle
super $node, options
populator: (selector, $node, data, options={}) =>
data.id = "#{data.id}"
data.type ?= 'user'
if data.avatar_url
$img = $('<img class="avatar" />')
$img.attr('src', data.avatar_url)
@ -61,7 +64,7 @@ define [
$span = $('<span />', class: 'details')
if data.common_courses?
$span.html(@contextList(courses: data.common_courses, groups: data.common_groups))
else if data.type and data.user_count?
else if data.user_count?
$span.text(I18n.t('people_count', 'person', {count: data.user_count}))
else if data.item_count?
if data.id.match(/_groups$/)
@ -82,14 +85,17 @@ define [
$node.data('id', if data.type is 'context' or not @prefixUserIds then data.id else "user_#{data.id}")
data.rootId = options.ancestors[0]
$node.data('user_data', data)
$node.addClass(if data.type then data.type else 'user')
$node.addClass(data.type)
if options.level > 0 and selector.options.showToggles
$node.prepend('<a class="toggle"><i></i></a>')
$node.addClass('toggleable') unless data.item_count # can't toggle certain synthetic contexts, e.g. "Student Groups"
if data.type == 'context' and not data.noExpand
$node.addClass('toggleable') if @canToggle(data)
if data.type is 'context' and not data.noExpand
$node.prepend('<a class="expand"><i></i></a>')
$node.addClass('expandable')
canToggle: (data) ->
not data.item_count # can't toggle certain synthetic contexts, e.g. "Student Groups"
buildContextInfo: (data) =>
match = data.id.match(/^(course|section)_(\d+)$/)
termInfo = @contexts["#{match[1]}s"][match[2]] if match

View File

@ -31,7 +31,6 @@ define [
@fetchListAjaxRequests = []
@queryCache = {}
@$container = $('<div />').addClass('autocomplete_menu')
@$container.addClass('with-toggles') if @options.showToggles
@$menu = $('<div />').append(@$list = @newList())
@$container.append($('<div />').append(@$menu))
@$container.css('top', 0).css('left', 0)
@ -92,7 +91,9 @@ define [
@clear()
@close()
@input.focus()
$list.body = $list.find('ul').last()
$uls = $list.find('ul')
$list.heading = $uls.first()
$list.body = $uls.last()
$list
captureKeyDown: (e) ->
@ -367,6 +368,9 @@ define [
@$list.disableWhileLoading(deferred)
deferred
toggleableItems: (data) ->
return false unless data.length
renderList: (data, options={}, postData={}) ->
@open()
@ -377,10 +381,8 @@ define [
$list.selectAll = null
@selection = null
$uls = $list.find('ul')
$uls.html('')
$heading = $uls.first()
$body = $uls.last()
$list.heading.html('')
$list.body.html('')
if data.length
parent = if @stack.length then @stack[@stack.length - 1][0] else null
ancestors = if @stack.length then (ancestor[0].data('id') for ancestor in @stack) else []
@ -393,19 +395,20 @@ define [
@populateRow($li, row, level: @stack.length, first: (i is 0), last: (i is data.length - 1), parent: parent, ancestors: ancestors)
$list.selectAll = $li if row.selectAll
$li.addClass('on') if $li.hasClass('toggleable') and @input.hasToken($li.data('id'))
$body.append($li)
$list.body.append($li)
$list.body.find('li.toggleable').addClass('on') if $list.selectAll?.hasClass?('on') or @stack.length and @stack[@stack.length - 1][0].hasClass?('on')
else
$message = $('<li class="message first last"></li>')
$message.text(@options.messages?.noResults ? '')
$body.append($message)
$list.body.append($message)
$list.toggleClass('with-toggles', @options.showToggles and $list.body.find('li.toggleable').length > 0)
if @listExpanded()
$li = @stack[@stack.length - 1][0].clone()
$li.addClass('expanded').removeClass('active first last')
$heading.append($li).show()
$list.heading.append($li).show()
else
$heading.hide()
$list.heading.hide()
if options.expand
$list.insertAfter(@$list)

View File

@ -239,7 +239,8 @@ class RoleOverridesController < ApplicationController
# read_reports -- [sTAD ] View usage reports for the course
# read_roster -- [STADo] See the list of users
# read_sis -- [sTa ] Read SIS data
# send_messages -- [STADo] Send messages to course members
# send_messages -- [STADo] Send messages to individual course members
# send_messages_all -- [sTADo] Send messages to the entire class
# view_all_grades -- [ TAd ] View all grades
# view_group_pages -- [sTADo] View the group pages of all student groups
#

View File

@ -55,6 +55,10 @@ class SearchController < ApplicationController
# @argument type ["user"|"context"] Limit the search just to users or contexts (groups/courses).
# @argument user_id [Integer] Search for a specific user id. This ignores the other above parameters, and will never return more than one result.
# @argument from_conversation_id [Integer] When searching by user_id, only users that could be normally messaged by this user will be returned. This parameter allows you to specify a conversation that will be referenced for a shared context -- if both the current user and the searched user are in the conversation, the user will be returned. This is used to start new side conversations.
# @argument permissions[] Array of permission strings to be checked for each
# matched context (e.g. "send_messages"). This argument determines which
# permissions may be returned in the response; it won't prevent contexts
# from being returned if they don't grant the permission(s)
#
# @example_response
# [
@ -77,12 +81,16 @@ class SearchController < ApplicationController
# enrollment types for each course to show what they share with this user
# @response_field common_groups Only set for users. Hash of group ids and
# enrollment types for each group to show what they share with this user
# @response_field permissions[] Only set for contexts. Mapping of requested
# permissions that the context grants the current user, e.g.
# {send_messages: true}
def recipients
# admins may not be able to see the course listed at the top level (since
# they aren't enrolled in it), but if they search within it, we want
# things to work, so we set everything up here
load_all_contexts :context => get_admin_search_context(params[:context])
load_all_contexts :context => get_admin_search_context(params[:context]),
:permissions => params[:permissions]
types = (params[:types] || [] + [params[:type]]).compact
types |= [:course, :section, :group] if types.delete('context')
@ -179,9 +187,8 @@ class SearchController < ApplicationController
end
elsif options[:synthetic_contexts]
if context_name =~ /\Acourse_(\d+)(_(groups|sections))?\z/ && (course = @contexts[:courses][$1.to_i]) && messageable_context_states[course[:state]]
course = Course.find_by_id(course[:id])
sections = @contexts[:sections].values.select{ |section| section[:parent] == {:course => course.id} }
groups = @contexts[:groups].values.select{ |group| group[:parent] == {:course => course.id} }
sections = @contexts[:sections].values.select{ |section| section[:parent] == {:course => course[:id]} }
groups = @contexts[:groups].values.select{ |group| group[:parent] == {:course => course[:id]} }
case context_name
when /\Acourse_\d+\z/
if terms.present? || options[:search_all_contexts] # search all groups and sections (and users)
@ -203,8 +210,7 @@ class SearchController < ApplicationController
if terms.present? # we'll just search the users
result = []
else
section = CourseSection.find_by_id(section[:id])
return synthetic_contexts_for(section.course, context_name)
return synthetic_contexts_for(course_for_section(section), context_name)
end
end
end
@ -227,8 +233,16 @@ class SearchController < ApplicationController
:name => context[:name],
:avatar_url => avatar_url,
:type => :context,
:user_count => user_counts[context[:type]][context[:id]]
:user_count => user_counts[context[:type]][context[:id]],
:permissions => context[:permissions] || {}
}
if context[:type] == :section
# TODO: have load_all_contexts actually return section-level
# permissions. but before we do that, sections will need to grant many
# more permission (possibly inherited from the course, like
# :send_messages_all)
ret[:permissions] = course_for_section(context)[:permissions]
end
ret[:context_name] = context[:context_name] if context[:context_name] && context_name.nil?
ret
}
@ -240,6 +254,10 @@ class SearchController < ApplicationController
options[:limit] ? result[offset, offset + options[:limit]] : result
end
def course_for_section(section)
@contexts[:courses][section[:parent][:course]]
end
def synthetic_contexts_for(course, context)
@skip_users = true
# TODO: move the aggregation entirely into the DB. we only select a little
@ -247,17 +265,18 @@ class SearchController < ApplicationController
users = @current_user.messageable_users(:context => context)
enrollment_counts = {:all => users.size}
users.each do |user|
user.common_courses[course.id].uniq.each do |role|
user.common_courses[course[:id]].uniq.each do |role|
enrollment_counts[role] ||= 0
enrollment_counts[role] += 1
end
end
avatar_url = avatar_url_for_group(blank_fallback)
result = []
result << {:id => "#{context}_teachers", :name => t(:enrollments_teachers, "Teachers"), :user_count => enrollment_counts['TeacherEnrollment'], :avatar_url => avatar_url, :type => :context} if enrollment_counts['TeacherEnrollment'].to_i > 0
result << {:id => "#{context}_tas", :name => t(:enrollments_tas, "Teaching Assistants"), :user_count => enrollment_counts['TaEnrollment'], :avatar_url => avatar_url, :type => :context} if enrollment_counts['TaEnrollment'].to_i > 0
result << {:id => "#{context}_students", :name => t(:enrollments_students, "Students"), :user_count => enrollment_counts['StudentEnrollment'], :avatar_url => avatar_url, :type => :context} if enrollment_counts['StudentEnrollment'].to_i > 0
result << {:id => "#{context}_observers", :name => t(:enrollments_observers, "Observers"), :user_count => enrollment_counts['ObserverEnrollment'], :avatar_url => avatar_url, :type => :context} if enrollment_counts['ObserverEnrollment'].to_i > 0
synthetic_context = {:avatar_url => avatar_url, :type => :context, :permissions => course[:permissions]}
result << synthetic_context.merge({:id => "#{context}_teachers", :name => t(:enrollments_teachers, "Teachers"), :user_count => enrollment_counts['TeacherEnrollment']}) if enrollment_counts['TeacherEnrollment'].to_i > 0
result << synthetic_context.merge({:id => "#{context}_tas", :name => t(:enrollments_tas, "Teaching Assistants"), :user_count => enrollment_counts['TaEnrollment']}) if enrollment_counts['TaEnrollment'].to_i > 0
result << synthetic_context.merge({:id => "#{context}_students", :name => t(:enrollments_students, "Students"), :user_count => enrollment_counts['StudentEnrollment']}) if enrollment_counts['StudentEnrollment'].to_i > 0
result << synthetic_context.merge({:id => "#{context}_observers", :name => t(:enrollments_observers, "Observers"), :user_count => enrollment_counts['ObserverEnrollment']}) if enrollment_counts['ObserverEnrollment'].to_i > 0
result
end

View File

@ -95,7 +95,7 @@ module SearchHelper
permissions = options[:permissions] || []
@contexts.each do |type, contexts|
contexts.each do |id, context|
context[:permissions] ||= {}
context[:permissions] = HashWithIndifferentAccess.new(context[:permissions] || {})
context[:permissions].slice!(*permissions) unless permissions == :all
end
end

View File

@ -319,6 +319,7 @@ class Group < ActiveRecord::Base
can :read and
can :read_roster and
can :send_messages and
can :send_messages_all and
can :follow
# if I am a member of this group and I can moderate_forum in the group's context

View File

@ -168,7 +168,7 @@ class RoleOverride < ActiveRecord::Base
]
},
:send_messages => {
:label => lambda { t('permissions.send_messages', "Send messages to course members") },
:label => lambda { t('permissions.send_messages', "Send messages to individual course members") },
:available_to => [
'StudentEnrollment',
'TaEnrollment',
@ -187,6 +187,25 @@ class RoleOverride < ActiveRecord::Base
'AccountAdmin'
]
},
:send_messages_all => {
:label => lambda { t('permissions.send_messages_all', "Send messages to the entire class") },
:available_to => [
'StudentEnrollment',
'TaEnrollment',
'DesignerEnrollment',
'TeacherEnrollment',
'TeacherlessStudentEnrollment',
'ObserverEnrollment',
'AccountAdmin',
'AccountMembership'
],
:true_for => [
'TaEnrollment',
'DesignerEnrollment',
'TeacherEnrollment',
'AccountAdmin'
]
},
:manage_outcomes => {
:label => lambda { t('permissions.manage_outcomes', "Manage learning outcomes") },
:available_to => [

View File

@ -21,6 +21,7 @@
i
background: transparent url(/images/messages/expand-context.png) no-repeat 10px -23px
a.toggle
display: none
float: left
margin: 0 14px 0 6px
i
@ -87,13 +88,15 @@
i
background-position: 10px -55px
a.toggle
display: none
display: none !important
li.active.expanded
a.expand:hover
cursor: pointer
background: #fff
border-color: #e4ebef
&.with-toggles
div.with-toggles
a.toggle
display: block
li.expanded, li.active.expanded
padding-left: 11px
img.avatar

View File

@ -970,10 +970,10 @@ describe ConversationsController, :type => :integration do
{ :controller => 'search', :action => 'recipients', :format => 'json', :search => 'o' })
json.each { |c| c.delete("avatar_url") }
json.should eql [
{"id" => "course_#{@course.id}", "name" => "the course", "type" => "context", "user_count" => 6},
{"id" => "section_#{@other_section.id}", "name" => "the other section", "type" => "context", "user_count" => 1, "context_name" => "the course"},
{"id" => "section_#{@course.default_section.id}", "name" => "the section", "type" => "context", "user_count" => 5, "context_name" => "the course"},
{"id" => "group_#{@group.id}", "name" => "the group", "type" => "context", "user_count" => 3, "context_name" => "the course"},
{"id" => "course_#{@course.id}", "name" => "the course", "type" => "context", "user_count" => 6, "permissions" => {}},
{"id" => "section_#{@other_section.id}", "name" => "the other section", "type" => "context", "user_count" => 1, "context_name" => "the course", "permissions" => {}},
{"id" => "section_#{@course.default_section.id}", "name" => "the section", "type" => "context", "user_count" => 5, "context_name" => "the course", "permissions" => {}},
{"id" => "group_#{@group.id}", "name" => "the group", "type" => "context", "user_count" => 3, "context_name" => "the course", "permissions" => {}},
{"id" => @bob.id, "name" => "bob", "common_courses" => {@course.id.to_s => ["StudentEnrollment"]}, "common_groups" => {@group.id.to_s => ["Member"]}},
{"id" => @joe.id, "name" => "joe", "common_courses" => {@course.id.to_s => ["StudentEnrollment"]}, "common_groups" => {@group.id.to_s => ["Member"]}},
{"id" => @me.id, "name" => @me.name, "common_courses" => {@course.id.to_s => ["TeacherEnrollment"]}, "common_groups" => {@group.id.to_s => ["Member"]}},

View File

@ -35,10 +35,10 @@ describe SearchController, :type => :integration do
{ :controller => 'search', :action => 'recipients', :format => 'json', :search => 'o' })
json.each { |c| c.delete("avatar_url") }
json.should eql [
{"id" => "course_#{@course.id}", "name" => "the course", "type" => "context", "user_count" => 6},
{"id" => "section_#{@other_section.id}", "name" => "the other section", "type" => "context", "user_count" => 1, "context_name" => "the course"},
{"id" => "section_#{@course.default_section.id}", "name" => "the section", "type" => "context", "user_count" => 5, "context_name" => "the course"},
{"id" => "group_#{@group.id}", "name" => "the group", "type" => "context", "user_count" => 3, "context_name" => "the course"},
{"id" => "course_#{@course.id}", "name" => "the course", "type" => "context", "user_count" => 6, "permissions" => {}},
{"id" => "section_#{@other_section.id}", "name" => "the other section", "type" => "context", "user_count" => 1, "context_name" => "the course", "permissions" => {}},
{"id" => "section_#{@course.default_section.id}", "name" => "the section", "type" => "context", "user_count" => 5, "context_name" => "the course", "permissions" => {}},
{"id" => "group_#{@group.id}", "name" => "the group", "type" => "context", "user_count" => 3, "context_name" => "the course", "permissions" => {}},
{"id" => @bob.id, "name" => "bob", "common_courses" => {@course.id.to_s => ["StudentEnrollment"]}, "common_groups" => {@group.id.to_s => ["Member"]}},
{"id" => @joe.id, "name" => "joe", "common_courses" => {@course.id.to_s => ["StudentEnrollment"]}, "common_groups" => {@group.id.to_s => ["Member"]}},
{"id" => @me.id, "name" => @me.name, "common_courses" => {@course.id.to_s => ["TeacherEnrollment"]}, "common_groups" => {@group.id.to_s => ["Member"]}},
@ -210,8 +210,8 @@ describe SearchController, :type => :integration do
{ :controller => 'search', :action => 'recipients', :format => 'json', :context => "course_#{@course.id}", :synthetic_contexts => "1" })
json.each { |c| c.delete("avatar_url") }
json.should eql [
{"id" => "course_#{@course.id}_teachers", "name" => "Teachers", "type" => "context", "user_count" => 1},
{"id" => "course_#{@course.id}_students", "name" => "Students", "type" => "context", "user_count" => 5},
{"id" => "course_#{@course.id}_teachers", "name" => "Teachers", "type" => "context", "user_count" => 1, "permissions" => {}},
{"id" => "course_#{@course.id}_students", "name" => "Students", "type" => "context", "user_count" => 5, "permissions" => {}},
{"id" => "course_#{@course.id}_sections", "name" => "Course Sections", "type" => "context", "item_count" => 2},
{"id" => "course_#{@course.id}_groups", "name" => "Student Groups", "type" => "context", "item_count" => 1}
]
@ -222,8 +222,8 @@ describe SearchController, :type => :integration do
{ :controller => 'search', :action => 'recipients', :format => 'json', :context => "section_#{@course.default_section.id}", :synthetic_contexts => "1" })
json.each { |c| c.delete("avatar_url") }
json.should eql [
{"id" => "section_#{@course.default_section.id}_teachers", "name" => "Teachers", "type" => "context", "user_count" => 1},
{"id" => "section_#{@course.default_section.id}_students", "name" => "Students", "type" => "context", "user_count" => 4}
{"id" => "section_#{@course.default_section.id}_teachers", "name" => "Teachers", "type" => "context", "user_count" => 1, "permissions" => {}},
{"id" => "section_#{@course.default_section.id}_students", "name" => "Students", "type" => "context", "user_count" => 4, "permissions" => {}}
]
end
@ -232,7 +232,7 @@ describe SearchController, :type => :integration do
{ :controller => 'search', :action => 'recipients', :format => 'json', :context => "course_#{@course.id}_groups", :synthetic_contexts => "1" })
json.each { |c| c.delete("avatar_url") }
json.should eql [
{"id" => "group_#{@group.id}", "name" => "the group", "type" => "context", "user_count" => 3}
{"id" => "group_#{@group.id}", "name" => "the group", "type" => "context", "user_count" => 3, "permissions" => {}}
]
end
@ -241,8 +241,34 @@ describe SearchController, :type => :integration do
{ :controller => 'search', :action => 'recipients', :format => 'json', :context => "course_#{@course.id}_sections", :synthetic_contexts => "1" })
json.each { |c| c.delete("avatar_url") }
json.should eql [
{"id" => "section_#{@other_section.id}", "name" => @other_section.name, "type" => "context", "user_count" => 1},
{"id" => "section_#{@course.default_section.id}", "name" => @course.default_section.name, "type" => "context", "user_count" => 5}
{"id" => "section_#{@other_section.id}", "name" => @other_section.name, "type" => "context", "user_count" => 1, "permissions" => {}},
{"id" => "section_#{@course.default_section.id}", "name" => @course.default_section.name, "type" => "context", "user_count" => 5, "permissions" => {}}
]
end
end
context "permissions" do
it "should return valid permission data" do
json = api_call(:get, "/api/v1/search/recipients.json?search=the%20o&permissions[]=send_messages_all",
{ :controller => 'search', :action => 'recipients', :format => 'json', :search => 'the o', :permissions => ["send_messages_all"] })
json.each { |c| c.delete("avatar_url") }
json.should eql [
{"id" => "course_#{@course.id}", "name" => "the course", "type" => "context", "user_count" => 6, "permissions" => {"send_messages_all" => true}},
{"id" => "section_#{@other_section.id}", "name" => "the other section", "type" => "context", "user_count" => 1, "context_name" => "the course", "permissions" => {"send_messages_all" => true}},
{"id" => "section_#{@course.default_section.id}", "name" => "the section", "type" => "context", "user_count" => 5, "context_name" => "the course", "permissions" => {"send_messages_all" => true}},
{"id" => "group_#{@group.id}", "name" => "the group", "type" => "context", "user_count" => 3, "context_name" => "the course", "permissions" => {"send_messages_all" => true}}
]
end
it "should not return invalid permission data" do
json = api_call(:get, "/api/v1/search/recipients.json?search=the%20o&permissions[]=foo_bar",
{ :controller => 'search', :action => 'recipients', :format => 'json', :search => 'the o', :permissions => ["foo_bar"] })
json.each { |c| c.delete("avatar_url") }
json.should eql [
{"id" => "course_#{@course.id}", "name" => "the course", "type" => "context", "user_count" => 6, "permissions" => {}},
{"id" => "section_#{@other_section.id}", "name" => "the other section", "type" => "context", "user_count" => 1, "context_name" => "the course", "permissions" => {}},
{"id" => "section_#{@course.default_section.id}", "name" => "the section", "type" => "context", "user_count" => 5, "context_name" => "the course", "permissions" => {}},
{"id" => "group_#{@group.id}", "name" => "the group", "type" => "context", "user_count" => 3, "context_name" => "the course", "permissions" => {}}
]
end
end

View File

@ -31,7 +31,7 @@ describe SearchHelper do
@contexts[:courses][@course.id][:permissions].should be_empty
load_all_contexts(:permissions => [:manage_assignments])
@contexts[:courses][@course.id][:permissions].should eql({:manage_assignments => true})
@contexts[:courses][@course.id][:permissions][:manage_assignments].should be_true
end
end
end

View File

@ -26,6 +26,7 @@ describe "conversations recipient finder" do
menu.should == ["the course", "the group"]
browse "the course" do
menu.should == ["Everyone", "Teachers", "Students", "Course Sections", "Student Groups"]
toggleable.should == ["Everyone", "Teachers", "Students"]
browse("Everyone") { menu.should == ["Select All", "nobody@example.com", "student 1", "student 2"] }
browse("Teachers") { menu.should == ["nobody@example.com"] }
browse("Students") { menu.should == ["Select All", "student 1", "student 2"] }
@ -50,6 +51,40 @@ describe "conversations recipient finder" do
browse("the group") { menu.should == ["Select All", "nobody@example.com", "student 1"] }
end
it "should respect permissions" do
# only affects courses/sections, not groups
RoleOverride.create!(:context => Account.default, :permission => 'send_messages_all', :enrollment_type => 'TeacherEnrollment', :enabled => false)
browse_menu
menu.should == ["the course", "the group"]
browse "the course" do
menu.should == ["Everyone", "Teachers", "Students", "Course Sections", "Student Groups"]
toggleable.should == []
browse("Everyone") { menu.should == ["nobody@example.com", "student 1", "student 2"] }
browse("Teachers") { menu.should == ["nobody@example.com"] }
browse("Students") { menu.should == ["student 1", "student 2"] }
browse "Course Sections" do
menu.should == ["the other section", "the section"]
browse "the other section" do
menu.should == ["Students"]
browse("Students") { menu.should == ["student 2"] }
end
browse "the section" do
menu.should == ["Everyone", "Teachers", "Students"]
browse("Everyone") { menu.should == ["nobody@example.com", "student 1"] }
browse("Teachers") { menu.should == ["nobody@example.com"] }
browse("Students") { menu.should == ["student 1"] }
end
end
browse "Student Groups" do
menu.should == ["the group"]
browse("the group") { menu.should == ["Select All", "nobody@example.com", "student 1"] }
end
end
browse("the group") { menu.should == ["Select All", "nobody@example.com", "student 1"] }
end
it "should return recently concluded courses" do
@course.complete!

View File

@ -77,8 +77,16 @@ shared_examples_for "conversations selenium tests" do
elements.map(&:last)
end
def toggleable
with_class("toggleable")
end
def toggled
elements.select { |e| e.first.attribute('class') =~ /(^| )on($| )/ }.map(&:last)
with_class("on")
end
def with_class(klass)
elements.select { |e| e.first.attribute('class') =~ /(\A| )#{klass}(\z| )/ }.map(&:last)
end
def click(name)