From 63a11a75f4e706d1d73f990017602fcbd10b9f18 Mon Sep 17 00:00:00 2001 From: Scott Erickson Date: Tue, 3 May 2016 10:38:20 -0700 Subject: [PATCH] Refactor client tests to use factories instead of fixtures --- app/collections/CocoCollection.coffee | 4 +- app/models/CocoModel.coffee | 2 + test/app/factories.coffee | 146 ++++++++++++++++++ test/app/lib/CoursesHelper.spec.coffee | 95 +++++++----- ...bView.coffee => LadderTabView.spec.coffee} | 4 +- .../modal/CourseVictoryModal.spec.coffee | 54 ++++--- .../modal/ShareProgressModal.spec.coffee | 1 - .../teachers/TeacherClassView.spec.coffee | 47 ++++-- 8 files changed, 270 insertions(+), 83 deletions(-) create mode 100644 test/app/factories.coffee rename test/app/views/play/ladder/{LadderTabView.coffee => LadderTabView.spec.coffee} (86%) diff --git a/app/collections/CocoCollection.coffee b/app/collections/CocoCollection.coffee index 8d311f378..18d1d9ddd 100644 --- a/app/collections/CocoCollection.coffee +++ b/app/collections/CocoCollection.coffee @@ -31,4 +31,6 @@ module.exports = class CocoCollection extends Backbone.Collection @loading = true @jqxhr - setProjection: (@project) -> \ No newline at end of file + setProjection: (@project) -> + + stringify: -> return JSON.stringify(@toJSON()) \ No newline at end of file diff --git a/app/models/CocoModel.coffee b/app/models/CocoModel.coffee index 009e32554..8c8510857 100644 --- a/app/models/CocoModel.coffee +++ b/app/models/CocoModel.coffee @@ -450,5 +450,7 @@ class CocoModel extends Backbone.Model options.url = @urlRoot + '/' + (@get('original') or @id) + '/patches' patches.fetch(options) return patches + + stringify: -> return JSON.stringify(@toJSON()) module.exports = CocoModel diff --git a/test/app/factories.coffee b/test/app/factories.coffee new file mode 100644 index 000000000..5165af654 --- /dev/null +++ b/test/app/factories.coffee @@ -0,0 +1,146 @@ +Level = require 'models/Level' +Course = require 'models/Course' +Courses = require 'collections/Courses' +User = require 'models/User' +Classroom = require 'models/Classroom' +LevelSession = require 'models/LevelSession' +CourseInstance = require 'models/CourseInstance' +Achievement = require 'models/Achievement' +EarnedAchievement = require 'models/EarnedAchievement' +ThangType = require 'models/ThangType' +Users = require 'collections/Users' + +module.exports = { + + makeCourse: (attrs, sources={}) -> + _id = _.uniqueId('course_') + attrs = _.extend({}, { + _id: _id + name: _.string.humanize(_id) + }, attrs) + + attrs.campaignID ?= sources.campaign?.id or _.uniqueId('campaign_') + return new Course(attrs) + + makeLevel: (attrs) -> + _id = _.uniqueId('level_') + attrs = _.extend({}, { + _id: _id + name: _.string.humanize(_id) + original: _id+'_original' + version: + major: 0 + minor: 0 + isLatestMajor: true + isLatestMinor: true + }, attrs) + return new Level(attrs) + + makeUser: (attrs) -> + _id = _.uniqueId('user_') + attrs = _.extend({ + _id: _id + permissions: [] + email: _id+'@email.com' + anonymous: false + name: _.string.humanize(_id) + }, attrs) + return new User(attrs) + + makeClassroom: (attrs, sources={}) -> + levels = sources.levels or [] # array of Levels collections + courses = sources.courses or new Courses() + members = sources.members or new Users() + + _id = _.uniqueId('classroom_') + attrs = _.extend({}, { + _id: _id, + name: _.string.humanize(_id) + aceConfig: { language: 'python' } + }, attrs) + + # populate courses + if not attrs.courses + courses = sources.courses or new Courses() + attrs.courses = (course.pick('_id') for course in courses.models) + + # populate levels + for [courseAttrs, levels] in _.zip(attrs.courses, levels) + break if not courseAttrs + course ?= @makeCourse() + levels ?= new Levels() + courseAttrs.levels = (level.pick('_id', 'slug', 'name', 'original', 'type') for level in levels.models) + + # populate members + if not attrs.members + members = members or new Users() + attrs.members = (member.id for member in members.models) + + return new Classroom(attrs) + + makeLevelSession: (attrs, sources={}) -> + level = sources.level or @makeLevel() + creator = sources.creator or @makeUser() + attrs = _.extend({}, { + level: + original: level.get('original'), + creator: creator.id, + }, attrs) + return new LevelSession(attrs) + + makeCourseInstance: (attrs, sources={}) -> + _id = _.uniqueId('course_instance_') + course = sources.course or @makeCourse() + classroom = sources.classroom or @makeClassroom() + owner = sources.owner or @makeUser() + members = sources.members or new Users() + attrs = _.extend({}, { + _id + courseID: course.id + classroomID: classroom.id + ownerID: owner.id + members: members.pluck('_id') + }, attrs) + return new CourseInstance(attrs) + + makeLevelCompleteAchievement: (attrs, sources={}) -> + _id = _.uniqueId('achievement_') + level = sources.level or @makeLevel() + attrs = _.extend({}, { + _id + name: _.string.humanize(_id) + query: { + 'state.complete': true, + 'level.original': level.get('original') + } + rewards: { gems: 10 } + worth: 20 + }, attrs) + return new Achievement(attrs) + + makeEarnedAchievement: (attrs, sources={}) -> + _id = _.uniqueId('earned_achievement_') + achievement = sources.achievement or @makeLevelCompleteAchievement() + user = sources.user or @makeUser() + attrs = _.extend({}, { + _id, + "achievement": achievement.id, + "user": user.id, + "earnedRewards": _.clone(achievement.get('rewards')), + "earnedPoints": achievement.get('worth'), + "achievementName": achievement.get('name'), + "notified": true + }, attrs) + return new EarnedAchievement(attrs) + + makeThangType: (attrs) -> + _id = _.uniqueId('thang_type_') + attrs = _.extend({}, { + _id + name: _.string.humanize(_id) + }, attrs) + return new ThangType(attrs) + +} + + diff --git a/test/app/lib/CoursesHelper.spec.coffee b/test/app/lib/CoursesHelper.spec.coffee index e228b9365..243f01920 100644 --- a/test/app/lib/CoursesHelper.spec.coffee +++ b/test/app/lib/CoursesHelper.spec.coffee @@ -4,55 +4,64 @@ Users = require 'collections/Users' Courses = require 'collections/Courses' CourseInstances = require 'collections/CourseInstances' Classrooms = require 'collections/Classrooms' +Levels = require 'collections/Levels' +LevelSessions = require 'collections/LevelSessions' +factories = require 'test/app/factories' -# These got broken by changes to fixtures :( describe 'CoursesHelper', -> describe 'calculateAllProgress', -> beforeEach -> # classrooms, courses, campaigns, courseInstances, students - @classroom = require 'test/app/fixtures/classrooms/active-classroom' + @course = factories.makeCourse() + @courses = new Courses([@course]) + @members = new Users(_.times(2, -> factories.makeUser())) + @levels = new Levels(_.times(2, -> factories.makeLevel())) + + @classroom = factories.makeClassroom({}, { @courses, @members, levels: [@levels] }) @classrooms = new Classrooms([ @classroom ]) - @courses = require 'test/app/fixtures/courses' - @course = @courses.models[0] - @campaigns = require 'test/app/fixtures/campaigns' - @campaign = @campaigns.models[0] - @students = require 'test/app/fixtures/students' + + courseInstance = factories.makeCourseInstance({}, { @course, @classroom, @members }) + @courseInstances = new CourseInstances([courseInstance]) describe 'when all students have completed a course', -> beforeEach -> - @classroom.sessions = require 'test/app/fixtures/level-sessions-completed' - @courseInstances = require 'test/app/fixtures/course-instances' + sessions = [] + for level in @levels.models + for creator in @members.models + sessions.push(factories.makeLevelSession({state: {complete: true}}, { level, creator })) + @classroom.sessions = new LevelSessions(sessions) describe 'progressData.get({classroom, course})', -> it 'returns object with .completed=true and .started=true', -> - progressData = helper.calculateAllProgress(@classrooms, @courses, @courseInstances, @students) + progressData = helper.calculateAllProgress(@classrooms, @courses, @courseInstances, @members) + console.log 'progress data?', progressData progress = progressData.get {@classroom, @course} expect(progress.completed).toBe true expect(progress.started).toBe true describe 'progressData.get({classroom, course, level, user})', -> it 'returns object with .completed=true and .started=true', -> - for student in @students.models - progressData = helper.calculateAllProgress(@classrooms, @courses, @courseInstances, @students) + for student in @members.models + progressData = helper.calculateAllProgress(@classrooms, @courses, @courseInstances, @members) progress = progressData.get {@classroom, @course, user: student} expect(progress.completed).toBe true expect(progress.started).toBe true describe 'progressData.get({classroom, course, level, user})', -> it 'returns object with .completed=true and .started=true', -> - progressData = helper.calculateAllProgress(@classrooms, @courses, @courseInstances, @students) - for level in @campaign.getLevels().models + progressData = helper.calculateAllProgress(@classrooms, @courses, @courseInstances, @members) + for level in @levels.models progress = progressData.get {@classroom, @course, level} expect(progress.completed).toBe true expect(progress.started).toBe true describe 'progressData.get({classroom, course, level, user})', -> it 'returns object with .completed=true and .started=true', -> - progressData = helper.calculateAllProgress(@classrooms, @courses, @courseInstances, @students) - for level in @campaign.getLevels().models - for user in @students.models + progressData = helper.calculateAllProgress(@classrooms, @courses, @courseInstances, @members) + for level in @levels.models + for user in @members.models progress = progressData.get {@classroom, @course, level, user} expect(progress.completed).toBe true expect(progress.started).toBe true @@ -60,60 +69,66 @@ describe 'CoursesHelper', -> describe 'when NOT all students have completed a course', -> beforeEach -> - @classroom.sessions = require 'test/app/fixtures/level-sessions-partially-completed' - @courseInstances = require 'test/app/fixtures/course-instances' + sessions = [] + @finishedMember = @members.first() + @unfinishedMember = @members.last() + for level in @levels.models + sessions.push(factories.makeLevelSession( + {state: {complete: true}}, + {level, creator: @finishedMember}) + ) + sessions.push(factories.makeLevelSession( + {state: {complete: false}}, + {level: @levels.first(), creator: @unfinishedMember}) + ) + @classroom.sessions = new LevelSessions(sessions) it 'progressData.get({classroom, course}) returns object with .completed=false', -> - progressData = helper.calculateAllProgress(@classrooms, @courses, @courseInstances, @students) + progressData = helper.calculateAllProgress(@classrooms, @courses, @courseInstances, @members) progress = progressData.get {@classroom, @course} expect(progress.completed).toBe false describe 'when NOT all students have completed a level', -> it 'progressData.get({classroom, course, level}) returns object with .completed=false and .started=true', -> - progressData = helper.calculateAllProgress(@classrooms, @courses, @courseInstances, @students) - for level in @campaign.getLevels().models + progressData = helper.calculateAllProgress(@classrooms, @courses, @courseInstances, @members) + for level in @levels.models progress = progressData.get {@classroom, @course, level} expect(progress.completed).toBe false describe 'when the student has completed the course', -> it 'progressData.get({classroom, course, user}) returns object with .completed=true and .started=true', -> - progressData = helper.calculateAllProgress(@classrooms, @courses, @courseInstances, @students) - student = @students.get('student0') - progress = progressData.get {@classroom, @course, user: student} + progressData = helper.calculateAllProgress(@classrooms, @courses, @courseInstances, @members) + progress = progressData.get {@classroom, @course, user: @finishedMember} expect(progress.completed).toBe true expect(progress.started).toBe true describe 'when the student has NOT completed the course', -> it 'progressData.get({classroom, course, user}) returns object with .completed=false and .started=true', -> - progressData = helper.calculateAllProgress(@classrooms, @courses, @courseInstances, @students) - student = @students.get('student1') - progress = progressData.get {@classroom, @course, user: student} + progressData = helper.calculateAllProgress(@classrooms, @courses, @courseInstances, @members) + progress = progressData.get {@classroom, @course, user: @unfinishedMember} expect(progress.completed).toBe false expect(progress.started).toBe true describe 'when the student has completed the level', -> it 'progressData.get({classroom, course, level, user}) returns object with .completed=true and .started=true', -> - progressData = helper.calculateAllProgress(@classrooms, @courses, @courseInstances, @students) - student = @students.get('student0') - for level in @campaign.getLevels().models - progress = progressData.get {@classroom, @course, level, user: student} + progressData = helper.calculateAllProgress(@classrooms, @courses, @courseInstances, @members) + for level in @levels.models + progress = progressData.get {@classroom, @course, level, user: @finishedMember} expect(progress.completed).toBe true expect(progress.started).toBe true describe 'when the student has NOT completed the level but has started', -> it 'progressData.get({classroom, course, level, user}) returns object with .completed=true and .started=true', -> - progressData = helper.calculateAllProgress(@classrooms, @courses, @courseInstances, @students) - user = @students.get('student2') - level = @campaign.getLevels().get('level0_0') - progress = progressData.get {@classroom, @course, level, user} + progressData = helper.calculateAllProgress(@classrooms, @courses, @courseInstances, @members) + level = @levels.first() + progress = progressData.get {@classroom, @course, level, user: @unfinishedMember} expect(progress.completed).toBe false expect(progress.started).toBe true describe 'when the student has NOT started the level', -> it 'progressData.get({classroom, course, level, user}) returns object with .completed=false and .started=false', -> - progressData = helper.calculateAllProgress(@classrooms, @courses, @courseInstances, @students) - user = @students.get('student3') - level = @campaign.getLevels().get('level0_0') - progress = progressData.get {@classroom, @course, level, user} + progressData = helper.calculateAllProgress(@classrooms, @courses, @courseInstances, @members) + level = @levels.last() + progress = progressData.get {@classroom, @course, level, user: @unfinishedMember} expect(progress.completed).toBe false expect(progress.started).toBe false diff --git a/test/app/views/play/ladder/LadderTabView.coffee b/test/app/views/play/ladder/LadderTabView.spec.coffee similarity index 86% rename from test/app/views/play/ladder/LadderTabView.coffee rename to test/app/views/play/ladder/LadderTabView.spec.coffee index 0fe946224..67ff6b1b6 100644 --- a/test/app/views/play/ladder/LadderTabView.coffee +++ b/test/app/views/play/ladder/LadderTabView.spec.coffee @@ -1,10 +1,10 @@ LadderTabView = require 'views/ladder/LadderTabView' Level = require 'models/Level' -fixtures = require 'test/app/fixtures/levels' +factories = require 'test/app/factories' describe 'LeaderboardData', -> it 'triggers "sync" when its request is finished', -> - level = new Level(fixtures.LadderLevel) + level = factories.makeLevel() leaderboard = new LadderTabView.LeaderboardData(level, 'humans', null, 4) leaderboard.fetch() diff --git a/test/app/views/play/level/modal/CourseVictoryModal.spec.coffee b/test/app/views/play/level/modal/CourseVictoryModal.spec.coffee index cf5b5ed22..09e5e21f2 100644 --- a/test/app/views/play/level/modal/CourseVictoryModal.spec.coffee +++ b/test/app/views/play/level/modal/CourseVictoryModal.spec.coffee @@ -1,12 +1,11 @@ - Course = require 'models/Course' Level = require 'models/Level' LevelSession = require 'models/LevelSession' Achievements = require 'collections/Achievements' CourseVictoryModal = require 'views/play/level/modal/CourseVictoryModal' -fixtures = require './CourseVictoryModal.fixtures' NewItemView = require 'views/play/level/modal/NewItemView' ProgressView = require 'views/play/level/modal/ProgressView' +factories = require 'test/app/factories' describe 'CourseVictoryModal', -> beforeEach -> @@ -15,34 +14,33 @@ describe 'CourseVictoryModal', -> it 'will eventually be the only victory modal' makeViewOptions = -> + level = factories.makeLevel() + course = factories.makeCourse() + courseInstance = factories.makeCourseInstance() { - course: new Course(fixtures.course) - level: new Level(fixtures.level) - session: new LevelSession(fixtures.session) - achievements: new Achievements(fixtures.achievements) - nextLevel: new Level(fixtures.nextLevel) - courseInstanceID: '56414c3868785b5f152424f1' - courseID: '560f1a9f22961295f9427742' + course: factories.makeCourse() + level: level + session: factories.makeLevelSession({ state: { complete: true } }, { level }) + achievements: new Achievements([factories.makeLevelCompleteAchievement({}, {level: level})]) + nextLevel: factories.makeLevel() + courseInstanceID: courseInstance.id + courseID: course.id } nextLevelRequest = null - handleRequests = -> + handleRequests = (modal) -> requests = jasmine.Ajax.requests.all() thangRequest = _.find(requests, (r) -> _.string.startsWith(r.url, '/db/thang.type')) - thangRequest?.respondWith({status: 200, responseText: JSON.stringify(fixtures.thangType)}) - - earnedAchievementRequests = _.where(requests, {url: '/db/earned_achievement'}) - for [request, response] in _.zip(earnedAchievementRequests, fixtures.earnedAchievements) - request.respondWith({status: 200, responseText: JSON.stringify(response)}) - - sessionsRequest = _.findWhere(requests, {url: '/db/course_instance/56414c3868785b5f152424f1/my-course-level-sessions'}) - sessionsRequest.respondWith({status: 200, responseText: JSON.stringify(fixtures.courseInstanceSessions)}) - - classroomRequest = _.findWhere(requests, {url: '/db/course_instance/56414c3868785b5f152424f1/classroom'}) - classroomRequest.respondWith({status: 200, responseText: JSON.stringify(fixtures.campaign)}) # TODO: Fix this... - - nextLevelRequest = _.findWhere(requests, {url: '/db/course_instance/56414c3868785b5f152424f1/levels/54173c90844506ae0195a0b4/next'}) + thangRequest?.respondWith({status: 200, responseText: factories.makeThangType().stringify()}) + modal.newEarnedAchievements[0].fakeRequests[0].respondWith({ + status: 200, responseText: factories.makeEarnedAchievement().stringify() + }) + modal.levelSessions.fakeRequests[0].respondWith({ status: 200, responseText: '[]' }) + modal.classroom.fakeRequests[0].respondWith({ + status: 200, responseText: factories.makeClassroom().stringify() + }) + nextLevelRequest = modal.nextLevel.fakeRequests[0] describe 'given a course level with a next level and no item or hero rewards', -> modal = null @@ -50,8 +48,8 @@ describe 'CourseVictoryModal', -> beforeEach (done) -> options = makeViewOptions() modal = new CourseVictoryModal(options) - handleRequests() - nextLevelRequest.respondWith({status: 200, responseText: JSON.stringify(fixtures.nextLevel)}) + handleRequests(modal) + nextLevelRequest.respondWith({status: 200, responseText: factories.makeLevel().stringify()}) _.defer done it 'only shows the ProgressView', -> @@ -84,7 +82,7 @@ describe 'CourseVictoryModal', -> level.unset('nextLevel') delete options.nextLevel modal = new CourseVictoryModal(options) - handleRequests() + handleRequests(modal) nextLevelRequest.respondWith({status: 404, responseText: '{}'}) _.defer done @@ -117,8 +115,8 @@ describe 'CourseVictoryModal', -> achievement.set('rewards', rewards) modal = new CourseVictoryModal(options) - handleRequests() - nextLevelRequest.respondWith({status: 200, responseText: JSON.stringify(fixtures.nextLevel)}) + handleRequests(modal) + nextLevelRequest.respondWith({status: 200, responseText: factories.makeLevel().stringify()}) _.defer done it 'includes a NewItemView when the level rewards a new item', -> diff --git a/test/app/views/play/level/modal/ShareProgressModal.spec.coffee b/test/app/views/play/level/modal/ShareProgressModal.spec.coffee index 355482ba5..66defaadc 100644 --- a/test/app/views/play/level/modal/ShareProgressModal.spec.coffee +++ b/test/app/views/play/level/modal/ShareProgressModal.spec.coffee @@ -3,7 +3,6 @@ Course = require 'models/Course' Level = require 'models/Level' LevelSession = require 'models/LevelSession' Achievements = require 'collections/Achievements' -fixtures = require './CourseVictoryModal.fixtures' describe 'ShareProgressModal', -> beforeEach -> diff --git a/test/app/views/teachers/TeacherClassView.spec.coffee b/test/app/views/teachers/TeacherClassView.spec.coffee index d19018819..7c66f5bbd 100644 --- a/test/app/views/teachers/TeacherClassView.spec.coffee +++ b/test/app/views/teachers/TeacherClassView.spec.coffee @@ -1,6 +1,12 @@ 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' describe '/teachers/classes/:handle', -> @@ -16,19 +22,38 @@ describe 'TeacherClassView', -> describe 'when logged in', -> beforeEach (done) -> - me = require 'test/app/fixtures/teacher' - @classroom = require 'test/app/fixtures/classrooms/active-classroom' - @students = require 'test/app/fixtures/students' - @courses = require 'test/app/fixtures/courses' - @courseInstances = require 'test/app/fixtures/course-instances' - @levelSessions = require 'test/app/fixtures/level-sessions-partially-completed' + me = factories.makeUser({}) + + @courses = new Courses([factories.makeCourse({name: 'First Course'}), factories.makeCourse({name: 'Second Course'})]) + @students = new Users(_.times(2, -> factories.makeUser())) + @levels = new Levels(_.times(2, -> factories.makeLevel())) + @classroom = factories.makeClassroom({}, { @courses, members: @students, levels: [@levels, new Levels()] }) + @courseInstances = new CourseInstances([ + factories.makeCourseInstance({}, { course: @courses.first(), @classroom, members: @students }) + factories.makeCourseInstance({}, { course: @courses.last(), @classroom, members: @students }) + ]) + + sessions = [] + @finishedStudent = @students.first() + @unfinishedStudent = @students.last() + for level in @levels.models + sessions.push(factories.makeLevelSession( + {state: {complete: true}}, + {level, creator: @finishedStudent}) + ) + sessions.push(factories.makeLevelSession( + {state: {complete: false}}, + {level: @levels.first(), creator: @unfinishedStudent}) + ) + @levelSessions = new LevelSessions(sessions) @view = new TeacherClassView({}, @courseInstances.first().id) - @view.classroom.fakeRequests.forEach (r, index) => r.respondWith({ status: 200, responseText: JSON.stringify(@classroom) }) - @view.courses.fakeRequests.forEach (r, index) => r.respondWith({ status: 200, responseText: JSON.stringify(@courses) }) - @view.courseInstances.fakeRequests.forEach (r, index) => r.respondWith({ status: 200, responseText: JSON.stringify(@courseInstances) }) - @view.students.fakeRequests.forEach (r, index) => r.respondWith({ status: 200, responseText: JSON.stringify(@students) }) - @view.classroom.sessions.fakeRequests.forEach (r, index) => r.respondWith({ status: 200, responseText: JSON.stringify(@levelSessions) }) + @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() }) jasmine.demoEl(@view.$el) _.defer done