mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-12-12 08:41:46 -05:00
ef0547f72a
In TeacherClassView, when a teacher assigns a paid course to any unenrolled student, the view automatically enrolls those students, rather than requiring the teacher to enroll those students manually first. Update copy throughout. Also add back (smaller) padding to progress dots in TeacherClassView.
314 lines
15 KiB
CoffeeScript
314 lines
15 KiB
CoffeeScript
TeacherClassView = require 'views/courses/TeacherClassView'
|
|
storage = require 'core/storage'
|
|
forms = require 'core/forms'
|
|
factories = require 'test/app/factories'
|
|
Users = require 'collections/Users'
|
|
Courses = require 'collections/Courses'
|
|
Levels = require 'collections/Levels'
|
|
LevelSessions = require 'collections/LevelSessions'
|
|
CourseInstances = require 'collections/CourseInstances'
|
|
Prepaids = require 'collections/Prepaids'
|
|
|
|
describe '/teachers/classes/:handle', ->
|
|
|
|
describe 'TeacherClassView', ->
|
|
|
|
# describe 'when logged out', ->
|
|
# it 'responds with 401 error'
|
|
# it 'shows Log In and Create Account buttons'
|
|
|
|
# describe "when you don't own the class", ->
|
|
# it 'responds with 403 error'
|
|
# it 'shows Log Out button'
|
|
|
|
describe 'when logged in', ->
|
|
beforeEach (done) ->
|
|
me = factories.makeUser({})
|
|
|
|
@courses = new Courses([
|
|
factories.makeCourse({name: 'First Course'}),
|
|
factories.makeCourse({name: 'Second Course'}),
|
|
factories.makeCourse({name: 'Beta Course', releasePhase: 'beta'}),
|
|
])
|
|
@releasedCourses = new Courses(@courses.where({ releasePhase: 'released' }))
|
|
@available1 = factories.makePrepaid({maxRedeemers: 1})
|
|
@available2 = factories.makePrepaid({maxRedeemers: 1})
|
|
expired = factories.makePrepaid({endDate: moment().subtract(1, 'day').toISOString()})
|
|
@prepaids = new Prepaids([@available1, @available2, expired])
|
|
@students = new Users([
|
|
factories.makeUser({name: 'Abner'})
|
|
factories.makeUser({name: 'Abigail'})
|
|
factories.makeUser({name: 'Abby'}, {prepaid: @available1})
|
|
factories.makeUser({name: 'Ben'}, {prepaid: @available1})
|
|
factories.makeUser({name: 'Ned'}, {prepaid: expired})
|
|
factories.makeUser({name: 'Ebner'}, {prepaid: expired})
|
|
])
|
|
@levels = new Levels(_.times(2, -> factories.makeLevel({ concepts: ['basic_syntax', 'arguments', 'functions'] })))
|
|
@levels.push(factories.makeLevel({ concepts: ['basic_syntax', 'arguments', 'functions'], primerLanguage: 'javascript' }))
|
|
|
|
_.defer done
|
|
|
|
describe 'when python classroom', ->
|
|
beforeEach (done) ->
|
|
@classroom = factories.makeClassroom({ aceConfig: { language: 'python' }}, { courses: @releasedCourses, members: @students, levels: [@levels, new Levels()] })
|
|
@courseInstances = new CourseInstances([
|
|
factories.makeCourseInstance({}, { course: @releasedCourses.first(), @classroom, members: @students })
|
|
factories.makeCourseInstance({}, { course: @releasedCourses.last(), @classroom, members: @students })
|
|
])
|
|
|
|
sessions = []
|
|
@finishedStudent = @students.first()
|
|
@unfinishedStudent = @students.last()
|
|
for level in @levels.models
|
|
sessions.push(factories.makeLevelSession(
|
|
{state: {complete: true}, playtime: 60},
|
|
{level, creator: @finishedStudent})
|
|
)
|
|
sessions.push(factories.makeLevelSession(
|
|
{state: {complete: true}, playtime: 60},
|
|
{level: @levels.first(), creator: @unfinishedStudent})
|
|
)
|
|
@levelSessions = new LevelSessions(sessions)
|
|
|
|
@view = new TeacherClassView({}, @courseInstances.first().id)
|
|
@view.classroom.fakeRequests[0].respondWith({ status: 200, responseText: @classroom.stringify() })
|
|
@view.courses.fakeRequests[0].respondWith({ status: 200, responseText: @courses.stringify() })
|
|
@view.courseInstances.fakeRequests[0].respondWith({ status: 200, responseText: @courseInstances.stringify() })
|
|
@view.students.fakeRequests[0].respondWith({ status: 200, responseText: @students.stringify() })
|
|
@view.classroom.sessions.fakeRequests[0].respondWith({ status: 200, responseText: @levelSessions.stringify() })
|
|
@view.levels.fakeRequests[0].respondWith({ status: 200, responseText: @levels.stringify() })
|
|
@view.prepaids.fakeRequests[0].respondWith({ status: 200, responseText: @prepaids.stringify() })
|
|
|
|
jasmine.demoEl(@view.$el)
|
|
_.defer done
|
|
|
|
it 'has contents', ->
|
|
expect(@view.$el.children().length).toBeGreaterThan(0)
|
|
|
|
# it "shows the classroom's name and description"
|
|
# it "shows the classroom's join code"
|
|
|
|
describe 'the Students tab', ->
|
|
beforeEach (done) ->
|
|
@view.state.set('activeTab', '#students-tab')
|
|
_.defer(done)
|
|
|
|
# it 'shows all of the students'
|
|
# it 'sorts correctly by Name'
|
|
# it 'sorts correctly by Progress'
|
|
|
|
describe 'bulk-assign controls', ->
|
|
it 'shows alert when assigning but no students are selected', (done) ->
|
|
expect(@view.$('.no-students-selected').hasClass('visible')).toBe(false)
|
|
@view.$('.assign-to-selected-students').click()
|
|
_.defer =>
|
|
expect(@view.$('.no-students-selected').hasClass('visible')).toBe(true)
|
|
done()
|
|
|
|
# describe 'the Course Progress tab', ->
|
|
# it 'shows the correct Course Overview progress'
|
|
#
|
|
# describe 'when viewing another course'
|
|
# it 'still shows the correct Course Overview progress'
|
|
#
|
|
|
|
describe 'the License Status tab', ->
|
|
beforeEach (done) ->
|
|
@view.state.set('activeTab', '#license-status-tab')
|
|
_.defer(done)
|
|
|
|
describe 'Enroll button', ->
|
|
it 'calls enrollStudents with that user when clicked', ->
|
|
spyOn(@view, 'enrollStudents')
|
|
@view.$('.enroll-student-button:first').click()
|
|
expect(@view.enrollStudents).toHaveBeenCalled()
|
|
users = @view.enrollStudents.calls.argsFor(0)[0]
|
|
expect(users.size()).toBe(1)
|
|
expect(users.first().id).toBe(@view.students.first().id)
|
|
|
|
describe 'Export Student Progress (CSV) button', ->
|
|
it 'downloads a CSV file', ->
|
|
spyOn(window, 'open').and.callFake (encodedCSV) =>
|
|
progressData = decodeURI(encodedCSV)
|
|
CSVHeader = 'data:text\/csv;charset=utf-8,'
|
|
expect(progressData).toMatch new RegExp('^' + CSVHeader)
|
|
lines = progressData.slice(CSVHeader.length).split('\n')
|
|
expect(lines.length).toBe(@students.length + 1)
|
|
for line in lines
|
|
simplerLine = line.replace(/"[^"]+"/g, '""')
|
|
# Username,Email,Total Playtime, [CS1-? Playtime], Concepts
|
|
expect(simplerLine.match(/[^,]+/g).length).toBe(3 + @releasedCourses.length + 1)
|
|
if simplerLine.match new RegExp(@finishedStudent.get('email'))
|
|
expect(simplerLine).toMatch /3 minutes,3 minutes,0/
|
|
else if simplerLine.match new RegExp(@unfinishedStudent.get('email'))
|
|
expect(simplerLine).toMatch /a minute,a minute,0/
|
|
else if simplerLine.match /@/
|
|
expect(simplerLine).toMatch /0,0,0/
|
|
return true
|
|
@view.$('.export-student-progress-btn').click()
|
|
expect(window.open).toHaveBeenCalled()
|
|
|
|
describe 'when javascript classroom', ->
|
|
beforeEach (done) ->
|
|
@classroom = factories.makeClassroom({ aceConfig: { language: 'javascript' }}, { courses: @releasedCourses, members: @students, levels: [@levels, new Levels()]})
|
|
@courseInstances = new CourseInstances([
|
|
factories.makeCourseInstance({}, { course: @releasedCourses.first(), @classroom, members: @students })
|
|
factories.makeCourseInstance({}, { course: @releasedCourses.last(), @classroom, members: @students })
|
|
])
|
|
|
|
sessions = []
|
|
@finishedStudent = @students.first()
|
|
@unfinishedStudent = @students.last()
|
|
classLanguage = @classroom.get('aceConfig')?.language
|
|
for level in @levels.models
|
|
continue if classLanguage and classLanguage is level.get('primerLanguage')
|
|
sessions.push(factories.makeLevelSession(
|
|
{state: {complete: true}, playtime: 60},
|
|
{level, creator: @finishedStudent})
|
|
)
|
|
sessions.push(factories.makeLevelSession(
|
|
{state: {complete: true}, playtime: 60},
|
|
{level: @levels.first(), creator: @unfinishedStudent})
|
|
)
|
|
@levelSessions = new LevelSessions(sessions)
|
|
|
|
@view = new TeacherClassView({}, @courseInstances.first().id)
|
|
@view.classroom.fakeRequests[0].respondWith({ status: 200, responseText: @classroom.stringify() })
|
|
@view.courses.fakeRequests[0].respondWith({ status: 200, responseText: @courses.stringify() })
|
|
@view.courseInstances.fakeRequests[0].respondWith({ status: 200, responseText: @courseInstances.stringify() })
|
|
@view.students.fakeRequests[0].respondWith({ status: 200, responseText: @students.stringify() })
|
|
@view.classroom.sessions.fakeRequests[0].respondWith({ status: 200, responseText: @levelSessions.stringify() })
|
|
@view.levels.fakeRequests[0].respondWith({ status: 200, responseText: @levels.stringify() })
|
|
@view.prepaids.fakeRequests[0].respondWith({ status: 200, responseText: @prepaids.stringify() })
|
|
|
|
jasmine.demoEl(@view.$el)
|
|
_.defer done
|
|
|
|
describe 'Export Student Progress (CSV) button', ->
|
|
it 'downloads a CSV file', ->
|
|
spyOn(window, 'open').and.callFake (encodedCSV) =>
|
|
progressData = decodeURI(encodedCSV)
|
|
CSVHeader = 'data:text\/csv;charset=utf-8,'
|
|
expect(progressData).toMatch new RegExp('^' + CSVHeader)
|
|
lines = progressData.slice(CSVHeader.length).split('\n')
|
|
expect(lines.length).toBe(@students.length + 1)
|
|
for line in lines
|
|
simplerLine = line.replace(/"[^"]+"/g, '""')
|
|
# Username,Email,Total Playtime, [CS1-? Playtime], Concepts
|
|
expect(simplerLine.match(/[^,]+/g).length).toBe(3 + @releasedCourses.length + 1)
|
|
if simplerLine.match new RegExp(@finishedStudent.get('email'))
|
|
expect(simplerLine).toMatch /2 minutes,2 minutes,0/
|
|
else if simplerLine.match new RegExp(@unfinishedStudent.get('email'))
|
|
expect(simplerLine).toMatch /a minute,a minute,0/
|
|
else if simplerLine.match /@/
|
|
expect(simplerLine).toMatch /0,0,0/
|
|
return true
|
|
@view.$('.export-student-progress-btn').click()
|
|
expect(window.open).toHaveBeenCalled()
|
|
|
|
|
|
describe '.assignCourse(courseID, members)', ->
|
|
beforeEach (done) ->
|
|
@classroom = factories.makeClassroom({ aceConfig: { language: 'javascript' }}, { courses: @releasedCourses, members: @students, levels: [@levels, new Levels()]})
|
|
@courseInstances = new CourseInstances([
|
|
factories.makeCourseInstance({}, { course: @releasedCourses.first(), @classroom, members: @students })
|
|
factories.makeCourseInstance({}, { course: @releasedCourses.last(), @classroom, members: @students })
|
|
])
|
|
|
|
sessions = []
|
|
@finishedStudent = @students.first()
|
|
@unfinishedStudent = @students.last()
|
|
classLanguage = @classroom.get('aceConfig')?.language
|
|
for level in @levels.models
|
|
continue if classLanguage and classLanguage is level.get('primerLanguage')
|
|
sessions.push(factories.makeLevelSession(
|
|
{state: {complete: true}, playtime: 60},
|
|
{level, creator: @finishedStudent})
|
|
)
|
|
sessions.push(factories.makeLevelSession(
|
|
{state: {complete: true}, playtime: 60},
|
|
{level: @levels.first(), creator: @unfinishedStudent})
|
|
)
|
|
@levelSessions = new LevelSessions(sessions)
|
|
|
|
@view = new TeacherClassView({}, @courseInstances.first().id)
|
|
@view.classroom.fakeRequests[0].respondWith({ status: 200, responseText: @classroom.stringify() })
|
|
@view.courses.fakeRequests[0].respondWith({ status: 200, responseText: @courses.stringify() })
|
|
@view.courseInstances.fakeRequests[0].respondWith({ status: 200, responseText: @courseInstances.stringify() })
|
|
@view.students.fakeRequests[0].respondWith({ status: 200, responseText: @students.stringify() })
|
|
@view.classroom.sessions.fakeRequests[0].respondWith({ status: 200, responseText: @levelSessions.stringify() })
|
|
@view.levels.fakeRequests[0].respondWith({ status: 200, responseText: @levels.stringify() })
|
|
@view.prepaids.fakeRequests[0].respondWith({ status: 200, responseText: @prepaids.stringify() })
|
|
|
|
jasmine.demoEl(@view.$el)
|
|
_.defer done
|
|
|
|
describe 'when no course instance exists for the given course', ->
|
|
beforeEach (done) ->
|
|
@view.courseInstances.reset()
|
|
@view.assignCourse(@courses.first().id, @students.pluck('_id').slice(0, 1))
|
|
@view.courseInstances.wait('add').then(done)
|
|
|
|
it 'creates the missing course instance', ->
|
|
request = jasmine.Ajax.requests.mostRecent()
|
|
expect(request.method).toBe('POST')
|
|
expect(request.url).toBe('/db/course_instance')
|
|
|
|
it 'shows a noty if the course instance request fails', (done) ->
|
|
spyOn(window, 'noty').and.callFake(done)
|
|
request = jasmine.Ajax.requests.mostRecent()
|
|
request.respondWith({
|
|
status: 500,
|
|
responseText: JSON.stringify({ message: "Internal Server Error" })
|
|
})
|
|
|
|
describe 'when the course is not free and some students are not enrolled', ->
|
|
beforeEach (done) ->
|
|
# first two students are unenrolled
|
|
@view.assignCourse(@courses.first().id, @students.pluck('_id').slice(0, 2))
|
|
@view.wait('begin-redeem-for-assign-course').then(done)
|
|
|
|
it 'enrolls all unenrolled students', (done) ->
|
|
numberOfRequests = _(@view.prepaids.models)
|
|
.map((prepaid) -> prepaid.fakeRequests.length)
|
|
.reduce((num, value) -> num + value)
|
|
expect(numberOfRequests).toBe(2)
|
|
done()
|
|
|
|
it 'shows a noty if a redeem request fails', (done) ->
|
|
spyOn(window, 'noty').and.callFake(done)
|
|
request = jasmine.Ajax.requests.mostRecent()
|
|
request.respondWith({
|
|
status: 500,
|
|
responseText: JSON.stringify({ message: "Internal Server Error" })
|
|
})
|
|
|
|
describe 'when there are not enough licenses available', ->
|
|
beforeEach (done) ->
|
|
# first four students are unenrolled, but only two licenses are available
|
|
@view.assignCourse(@courses.first().id, @students.pluck('_id'))
|
|
spyOn(@view, 'openModalView').and.callFake(done)
|
|
|
|
it 'shows CoursesNotAssignedModal', ->
|
|
expect(@view.openModalView).toHaveBeenCalled()
|
|
|
|
|
|
describe 'when there is nothing else to do first', ->
|
|
beforeEach (done) ->
|
|
@courseInstance = @view.courseInstances.first()
|
|
@courseInstance.set('members', [])
|
|
@view.assignCourse(@courseInstance.get('courseID'), @students.pluck('_id').slice(2, 4))
|
|
@view.wait('begin-assign-course').then(done)
|
|
|
|
it 'adds students to the course instances', ->
|
|
request = jasmine.Ajax.requests.mostRecent()
|
|
expect(request.url).toBe("/db/course_instance/#{@courseInstance.id}/members")
|
|
expect(request.method).toBe('POST')
|
|
|
|
it 'shows a noty if POSTing students fails', (done) ->
|
|
spyOn(window, 'noty').and.callFake(done)
|
|
request = jasmine.Ajax.requests.mostRecent()
|
|
request.respondWith({
|
|
status: 500,
|
|
responseText: JSON.stringify({ message: "Internal Server Error" })
|
|
})
|