Try to fix SRGB spec flakiness

Try to fix SRGB spec flakiness

Change-Id: I2a3b13f6a499955e2a1c1803cf1123b2cb10dc7a
Reviewed-on: https://gerrit.instructure.com/138128
Tested-by: Jenkins
Reviewed-by: Clay Diffrient <9dff2e5c98626d20d2848250c411e8009465bb87@instructure.com>
Product-Review: Ryan Shaw <ea3cd978650417470535f3a4725b6b5042a6ab59@instructure.com>
QA-Review: Ryan Shaw <ea3cd978650417470535f3a4725b6b5042a6ab59@instructure.com>

--- app/coffeescripts/ember/screenreader_gradebook/tests/controllers/screenreader_gradebook.spec.coffee
+++ app/coffeescripts/ember/screenreader_gradebook/tests/controllers/screenreader_gradebook.spec.coffee
@@ -15,876 +15,872 @@
 # 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/>.

-define [
-  'jquery'
-  'underscore'
-  'ic-ajax'
-  '../start_app'
-  'ember'
-  '../shared_ajax_fixtures'
-  'spec/jsx/gradebook/GradeCalculatorSpecHelper'
-  '../../controllers/screenreader_gradebook_controller'
-  '../../../../userSettings'
-  'jsx/gradebook/CourseGradeCalculator'
-  'vendor/jquery.ba-tinypubsub'
-], (
-  $, _, ajax, startApp, Ember, fixtures, GradeCalculatorSpecHelper, SRGBController, userSettings,
-  CourseGradeCalculator
-) ->
-
-  qunitTimeout = QUnit.config.testTimeout
-
-  App = null
-  originalIsDraft = null
-
-  clone = (obj) ->
-    Ember.copy obj, true
-
-  createExampleGrades = GradeCalculatorSpecHelper.createCourseGradesWithGradingPeriods
-
-  createExampleGradingPeriodSet = ->
+
+import $ from 'jquery'
+import _ from 'underscore'
+import ajax from 'ic-ajax'
+import startApp from '../start_app'
+import Ember from 'ember'
+import fixtures from '../shared_ajax_fixtures'
+import GradeCalculatorSpecHelper from 'spec/jsx/gradebook/GradeCalculatorSpecHelper'
+import SRGBController from '../../controllers/screenreader_gradebook_controller'
+import userSettings from '../../../../userSettings'
+import CourseGradeCalculator from 'jsx/gradebook/CourseGradeCalculator'
+import 'vendor/jquery.ba-tinypubsub'
+
+qunitTimeout = QUnit.config.testTimeout
+
+App = null
+originalIsDraft = null
+
+clone = (obj) ->
+  Ember.copy obj, true
+
+createExampleGrades = GradeCalculatorSpecHelper.createCourseGradesWithGradingPeriods
+
+createExampleGradingPeriodSet = ->
+  id: '1501'
+  gradingPeriods: [{ id: '701', weight: 50 }, { id: '702', weight: 50 }]
+  weighted: true
+
+setFixtures = (isDraftState=false, sortOrder='assignment_group') ->
+  fixtures.create()
+  @contextGetStub = sinon.stub(userSettings, 'contextGet')
+  @contextSetStub = sinon.stub(userSettings, 'contextSet')
+  @contextGetStub.withArgs('sort_grade_columns_by').returns({sortType: sortOrder})
+  @contextSetStub.returns({sortType: sortOrder})
+
+initializeApp = ->
+  App = startApp()
+  Ember.run =>
+    @srgb = SRGBController.create()
+    @srgb.set('model', {
+      enrollments: Ember.ArrayProxy.create(content: clone fixtures.students)
+      assignment_groups: Ember.ArrayProxy.create(content: [])
+      submissions: Ember.ArrayProxy.create(content: [])
+      sections: Ember.ArrayProxy.create(content: clone fixtures.sections)
+      outcomes: Ember.ArrayProxy.create(content: clone fixtures.outcomes)
+      outcome_rollups: Ember.ArrayProxy.create(content: clone fixtures.outcome_rollups)
+    })
+
+setup = (isDraftState=false, sortOrder='assignment_group') ->
+  setFixtures.call(this, isDraftState, sortOrder)
+  initializeApp.call(this)
+  QUnit.config.testTimeout = 2000
+
+teardown = ->
+  QUnit.config.testTimeout = qunitTimeout
+  @contextGetStub.restore()
+  @contextSetStub.restore()
+  Ember.run App, 'destroy'
+
+QUnit.module 'screenreader_gradebook_controller',
+  setup: ->
+    setup.call this
+  teardown: ->
+    teardown.call this
+
+test 'calculates students properly', ->
+  equal @srgb.get('students.length'), 10
+  equal @srgb.get('students.firstObject').name, fixtures.students[0].user.name
+
+test 'calculates assignments properly', ->
+  ajax.request().then =>
+    equal @srgb.get('assignments.length'), 7
+    ok !@srgb.get('assignments').findBy('name', 'Not Graded')
+    equal @srgb.get('assignments.firstObject').name, fixtures.assignment_groups[0].assignments[0].name
+
+test 'calculates outcomes properly', ->
+  equal @srgb.get('outcomes.length'), 2
+  equal @srgb.get('outcomes.firstObject').title, fixtures.outcomes[0].title
+
+test 'studentsHash returns the expected hash', ->
+  _.each @srgb.studentsHash(), (obj) =>
+    strictEqual @srgb.get('students').findBy('id', obj.id), obj
+
+test 'assignmentGroupsHash retuns the expected hash', (assert) ->
+  start = assert.async()
+  ajax.request().then =>
+    _.each @srgb.assignmentGroupsHash(), (obj) =>
+      strictEqual @srgb.get('assignment_groups').findBy('id', obj.id), obj
+    start()
+
+test 'student objects have isLoaded flag set to true once submissions are loaded', (assert) ->
+  start = assert.async()
+  ajax.request().then =>
+    @srgb.get('students').forEach (s) ->
+      equal Ember.get(s, 'isLoaded'), true
+    start()
+
+test 'displayName is hiddenName when hideStudentNames is true', ->
+  @srgb.set('hideStudentNames', true)
+  equal @srgb.get('displayName'), 'hiddenName'
+  @srgb.set('hideStudentNames', false)
+  equal @srgb.get('displayName'), 'name'
+
+test 'updateSubmission attaches the submission to the student', ->
+  student = clone fixtures.students[0].user
+  submission = clone fixtures.submissions[student.id].submissions[0]
+
+  @srgb.updateSubmission submission, student
+  strictEqual student["assignment_#{submission.assignment_id}"], submission
+
+test 'studentsInSelectedSection is the same as students when selectedSection is null', ->
+  ok (!@srgb.get('selectedSection'))
+  deepEqual @srgb.get('students'), @srgb.get('studentsInSelectedSection')
+
+test 'selectedSubmissionLate is true for a late submission', ->
+  @srgb.set('selectedSubmission', {points_deducted: 1})
+  ok (@srgb.get('selectedSubmissionLate'))
+
+test 'selectedSubmissionLate is false for an on time submission', ->
+  @srgb.set('selectedSubmission', {points_deducted: 0})
+  ok (!@srgb.get('selectedSubmissionLate'))
+
+test 'selecting a section filters students properly', ->
+  Ember.run =>
+    @srgb.set('selectedSection', @srgb.get('sections.lastObject'))
+  equal @srgb.get('studentsInSelectedSection.length'), 6
+  equal @srgb.get('studentsInSelectedSection.firstObject').name, 'Buffy'
+
+test 'sorting assignments by position', (assert) ->
+  start = assert.async()
+  ajax.request().then =>
+    Ember.run =>
+      @srgb.set('assignmentSort', @srgb.get('assignmentSortOptions').findBy('value', 'assignment_group'))
+    equal @srgb.get('assignments.firstObject.name'), 'Z Eats Soup'
+    equal @srgb.get('assignments.lastObject.name'), 'Da Fish and Chips!'
+    start()
+
+test 'updates assignment_visibility on an assignment', (assert) ->
+  start = assert.async()
+  ajax.request().then =>
+    assignments = @srgb.get('assignments')
+    assgn = assignments.objectAt(2)
+    @srgb.updateAssignmentVisibilities(assgn, '3')
+    ok !assgn.assignment_visibility.contains('3')
+    start()
+
+test 'studentsThatCanSeeAssignment doesnt return all students', (assert) ->
+  start = assert.async()
+  ajax.request().then =>
+    assgn = @srgb.get('assignments.firstObject')
+    students = @srgb.studentsThatCanSeeAssignment(assgn)
+    ids = Object.keys(students)
+    equal ids.length, 1
+    equal ids[0], '1'
+    start()
+
+test 'sorting assignments alphabetically', (assert) ->
+  start = assert.async()
+  ajax.request().then =>
+    Ember.run =>
+      @srgb.set('assignmentSort', @srgb.get('assignmentSortOptions').findBy('value', 'alpha'))
+    equal @srgb.get('assignments.firstObject.name'), 'Apples are good'
+    equal @srgb.get('assignments.lastObject.name'), 'Z Eats Soup'
+    start()
+
+test 'sorting assignments by due date', (assert) ->
+  start = assert.async()
+  ajax.request().then =>
+    Ember.run =>
+      @srgb.set('assignmentSort', @srgb.get('assignmentSortOptions').findBy('value', 'due_date'))
+    equal @srgb.get('assignments.firstObject.name'), 'Can You Eat Just One?'
+    equal @srgb.get('assignments.lastObject.name'), 'Drink Water'
+    start()
+
+QUnit.module 'screenreader_gradebook_controller Loading Submissions',
+  setup: ->
+    setFixtures.call(this)
+    ajax.defineFixture window.ENV.GRADEBOOK_OPTIONS.submissions_url,
+      response: [
+        {
+          submissions: [{
+            assignment_id: '1',
+            assignment_visible: true,
+            cached_due_date: '2015-03-01T12:00:00Z',
+            score: 10,
+            user_id: '1'
+          }, {
+            assignment_id: '2',
+            assignment_visible: true,
+            cached_due_date: '2015-05-02T12:00:00Z',
+            score: 9,
+            user_id: '1'
+          }],
+          user_id: '01'
+        }, {
+          submissions: [{
+            assignment_id: '1',
+            assignment_visible: true,
+            cached_due_date: '2015-07-03T12:00:00Z',
+            score: 8,
+            user_id: '2'
+          }],
+          user_id: '2'
+        }
+      ]
+      jqXHR: { getResponseHeader: -> {} }
+      textStatus: 'success'
+
+    ENV.GRADEBOOK_OPTIONS.grading_period_set =
+      id: '1501'
+      grading_periods: [
+        {
+          id: '1403'
+          close_date: '2015-07-08T12:00:00Z'
+          end_date: '2015-07-01T12:00:00Z'
+          is_closed: false
+          start_date: '2015-05-01T12:00:00Z'
+        },
+        {
+          id: '1401'
+          close_date: '2015-03-08T12:00:00Z'
+          end_date: '2015-03-01T12:00:00Z'
+          is_closed: true
+          start_date: '2015-01-01T12:00:00Z'
+        },
+        {
+          id: '1402'
+          close_date: '2015-05-08T12:00:00Z'
+          end_date: '2015-05-01T12:00:00Z'
+          is_closed: false
+          start_date: '2015-03-01T12:00:00Z'
+        }
+      ]
+      weighted: true
+
+    initializeApp.call(this)
+
+  teardown: ->
+    teardown.call(this)
+
+test 'updates effective due dates', ->
+  ajax.request('/api/v1/submissions').then =>
+    effectiveDueDates = @srgb.get('effectiveDueDates.content')
+    deepEqual(Object.keys(effectiveDueDates), ['1', '2'])
+    deepEqual(Object.keys(effectiveDueDates[1]), ['1', '2'])
+    deepEqual(Object.keys(effectiveDueDates[2]), ['1'])
+
+test 'updates effective due dates on related assignments', ->
+  ajax.request('/api/v1/submissions').then =>
+    deepEqual(Object.keys(@srgb.get('assignments').findBy('id', '1').effectiveDueDates), ['1', '2'])
+    deepEqual(Object.keys(@srgb.get('assignments').findBy('id', '2').effectiveDueDates), ['1'])
+
+test 'updates inClosedGradingPeriod on related assignments', ->
+  ajax.request('/api/v1/submissions').then =>
+    strictEqual(@srgb.get('assignments').findBy('id', '1').inClosedGradingPeriod, true)
+    strictEqual(@srgb.get('assignments').findBy('id', '2').inClosedGradingPeriod, false)
+
+QUnit.module 'screenreader_gradebook_controller#gradesAreWeighted',
+  setup: ->
+    setup.call this
+  teardown: ->
+    teardown.call this
+
+test 'is true when the grading period set is weighted', ->
+  gradingPeriodSet = createExampleGradingPeriodSet()
+  gradingPeriodSet.weighted = true
+  @stub(@srgb, 'getGradingPeriodSet').returns(gradingPeriodSet)
+  Ember.run =>
+    @srgb.set('groupsAreWeighted', false)
+    equal @srgb.get('gradesAreWeighted'), true
+
+test 'is true when groupsAreWeighted is true', ->
+  gradingPeriodSet = createExampleGradingPeriodSet()
+  gradingPeriodSet.weighted = false
+  @stub(@srgb, 'getGradingPeriodSet').returns(gradingPeriodSet)
+  Ember.run =>
+    @srgb.set('groupsAreWeighted', true)
+    equal @srgb.get('gradesAreWeighted'), true
+
+test 'is false when assignment groups are not weighted and the grading period set is not weighted', ->
+  gradingPeriodSet = createExampleGradingPeriodSet()
+  gradingPeriodSet.weighted = false
+  @stub(@srgb, 'getGradingPeriodSet').returns(gradingPeriodSet)
+  Ember.run =>
+    @srgb.set('groupsAreWeighted', false)
+    equal @srgb.get('gradesAreWeighted'), false
+
+test 'is false when assignment groups are not weighted and the grading period set is not defined', ->
+  @stub(@srgb, 'getGradingPeriodSet').returns(null)
+  Ember.run =>
+    @srgb.set('groupsAreWeighted', false)
+    equal @srgb.get('gradesAreWeighted'), false
+
+QUnit.module 'screenreader_gradebook_controller#hidePointsPossibleForFinalGrade',
+  setup: ->
+    setup.call this
+  teardown: ->
+    teardown.call this
+
+test 'is true when groupsAreWeighted is true', ->
+  Ember.run =>
+    @srgb.set('groupsAreWeighted', true)
+    equal @srgb.get('hidePointsPossibleForFinalGrade'), true
+
+test 'is true when subtotalByGradingPeriod is true', ->
+  @stub(@srgb, 'subtotalByGradingPeriod').returns(true)
+  Ember.run =>
+    equal @srgb.get('hidePointsPossibleForFinalGrade'), true
+
+test 'is false when groupsAreWeighted is false and subtotalByGradingPeriod is false', ->
+  @stub(@srgb, 'subtotalByGradingPeriod').returns(false)
+  Ember.run =>
+    @srgb.set('groupsAreWeighted', false)
+    equal @srgb.get('hidePointsPossibleForFinalGrade'), false
+
+QUnit.module '#getGradingPeriodSet',
+  setup: ->
+    setup.call this
+
+  teardown: ->
+    teardown.call this
+
+test 'normalizes the grading period set from the env', ->
+  ENV.GRADEBOOK_OPTIONS.grading_period_set =
     id: '1501'
-    gradingPeriods: [{ id: '701', weight: 50 }, { id: '702', weight: 50 }]
+    grading_periods: [{ id: '701', weight: 50 }, { id: '702', weight: 50 }]
     weighted: true
-
-  setFixtures = (isDraftState=false, sortOrder='assignment_group') ->
-    fixtures.create()
-    @contextGetStub = sinon.stub(userSettings, 'contextGet')
-    @contextSetStub = sinon.stub(userSettings, 'contextSet')
-    @contextGetStub.withArgs('sort_grade_columns_by').returns({sortType: sortOrder})
-    @contextSetStub.returns({sortType: sortOrder})
-
-  initializeApp = ->
-    App = startApp()
-    Ember.run =>
-      @srgb = SRGBController.create()
-      @srgb.set('model', {
-        enrollments: Ember.ArrayProxy.create(content: clone fixtures.students)
-        assignment_groups: Ember.ArrayProxy.create(content: [])
-        submissions: Ember.ArrayProxy.create(content: [])
-        sections: Ember.ArrayProxy.create(content: clone fixtures.sections)
-        outcomes: Ember.ArrayProxy.create(content: clone fixtures.outcomes)
-        outcome_rollups: Ember.ArrayProxy.create(content: clone fixtures.outcome_rollups)
-      })
-
-  setup = (isDraftState=false, sortOrder='assignment_group') ->
-    setFixtures.call(this, isDraftState, sortOrder)
-    initializeApp.call(this)
-    QUnit.config.testTimeout = 1000
-
-  teardown = ->
-    QUnit.config.testTimeout = qunitTimeout
+  gradingPeriodSet = @srgb.getGradingPeriodSet()
+  deepEqual(gradingPeriodSet.id, '1501')
+  equal(gradingPeriodSet.gradingPeriods.length, 2)
+  deepEqual(_.map(gradingPeriodSet.gradingPeriods, 'id'), ['701', '702'])
+
+test 'sets grading period set to null when not defined in the env', ->
+  gradingPeriodSet = @srgb.getGradingPeriodSet()
+  deepEqual(gradingPeriodSet, null)
+
+QUnit.module '#submissionsForStudent',
+  setupThis: (options = {}) ->
+    effectiveDueDates = Ember.ObjectProxy.create(
+      content: {
+        1: { 1: { grading_period_id: '1' } },
+        2: { 1: { grading_period_id: '2' } }
+      }
+    )
+
+    defaults = {
+      has_grading_periods: false,
+      'selectedGradingPeriod.id': null,
+      effectiveDueDates
+    }
+    self = _.defaults options, defaults
+    self.get = (attribute) -> self[attribute]
+    self
+
+  setup: ->
+    @student =
+      id: '1'
+      assignment_1: { assignment_id: '1', user_id: '1', name: 'yolo' }
+      assignment_2: { assignment_id: '2', user_id: '1', name: 'froyo' }
+
+    setup.call this
+
+  teardown: ->
+    teardown.call this
+
+test 'returns all submissions for the student when there are no grading periods', ->
+  self = @setupThis()
+  submissions = @srgb.submissionsForStudent.call(self, @student)
+  propEqual _.pluck(submissions, 'assignment_id'), ['1', '2']
+
+test 'returns all submissions if "All Grading Periods" is selected', ->
+  self = @setupThis(
+    has_grading_periods: true,
+    'selectedGradingPeriod.id': '0'
+  )
+  submissions = @srgb.submissionsForStudent.call(self, @student)
+  propEqual _.pluck(submissions, 'assignment_id'), ['1', '2']
+
+test 'only returns submissions due for the student in the selected grading period', ->
+  self = @setupThis(
+    has_grading_periods: true,
+    'selectedGradingPeriod.id': '2'
+  )
+  submissions = @srgb.submissionsForStudent.call(self, @student)
+  propEqual _.pluck(submissions, 'assignment_id'), ['2']
+
+QUnit.module 'screenreader_gradebook_controller: with selected student',
+  setup: ->
+    setup.call this
+    @stub(@srgb, 'calculateStudentGrade')
+    @stub(@srgb, 'subtotalByGradingPeriod')
+    @completeSetup = =>
+      ajax.request().then =>
+        Ember.run =>
+          @srgb.set('selectedGradingPeriod', { id: '3' })
+          @srgb.set('assignment_groups', Ember.ArrayProxy.create(content: clone fixtures.assignment_groups))
+          @srgb.set('assignment_groups.isLoaded', true)
+          student = @srgb.get('students.firstObject')
+          @srgb.set('selectedStudent', student)
+  teardown: ->
+    teardown.call this
+
+test 'selectedSubmission should be null when just selectedStudent is set', (assert) ->
+  start = assert.async()
+  @completeSetup().then =>
+    strictEqual @srgb.get('selectedSubmission'), null
+    start()
+
+QUnit.module 'screenreader_gradebook_controller: with selected student, assignment, and outcome',
+  setup: ->
+    setup.call this
+    @completeSetup = =>
+      Ember.run =>
+        ajax.request().then =>
+          @student = @srgb.get('students.firstObject')
+          @assignment = @srgb.get('assignments.firstObject')
+          @outcome = @srgb.get('outcomes.firstObject')
+          @srgb.set('selectedStudent', @student)
+          @srgb.set('selectedAssignment', @assignment)
+          @srgb.set('selectedOutcome', @outcome)
+
+  teardown: ->
+    teardown.call this
+
+test 'assignmentDetails is computed properly', (assert) ->
+  start = assert.async()
+  @completeSetup().then =>
+    ad = @srgb.get('assignmentDetails')
+    selectedAssignment = @srgb.get('selectedAssignment')
+    strictEqual ad.assignment, selectedAssignment
+    strictEqual ad.cnt, '1'
+    start()
+
+test 'outcomeDetails is computed properly', (assert) ->
+  start = assert.async()
+  @completeSetup().then =>
+    od = @srgb.get('outcomeDetails')
+    selectedOutcome = @srgb.get('selectedOutcome')
+    strictEqual od.cnt, 1
+    start()
+
+test 'selectedSubmission is computed properly', (assert) ->
+  start = assert.async()
+  @completeSetup().then =>
+    selectedSubmission = @srgb.get('selectedSubmission')
+    sub = _.find(fixtures.submissions, (s) => s.user_id == @student.id)
+    submission = _.find(sub.submissions, (s) => s.assignment_id == @assignment.id)
+    _.each submission, (val, key) =>
+      equal selectedSubmission[key], val, "#{key} is the expected value on selectedSubmission"
+    start()
+
+test 'selectedSubmission sets gradeLocked', (assert) ->
+  start = assert.async()
+  @completeSetup().then =>
+    selectedSubmission = @srgb.get('selectedSubmission')
+    equal selectedSubmission.gradeLocked, false
+    start()
+
+test 'selectedSubmission sets gradeLocked for unassigned students', (assert) ->
+  start = assert.async()
+  @completeSetup().then =>
+    @student = @srgb.get('students')[1]
+    Ember.run =>
+      @srgb.set('selectedStudent', @student)
+      selectedSubmission = @srgb.get('selectedSubmission')
+      equal selectedSubmission.gradeLocked, true
+      start()
+
+QUnit.module 'screenreader_gradebook_controller: with selected assignment',
+  setup: ->
+    setup.call this
+    @completeSetup = =>
+      ajax.request().then =>
+        @assignment = @srgb.get('assignments.firstObject')
+        Ember.run =>
+          @srgb.set('selectedAssignment', @assignment)
+
+  teardown: ->
     @contextGetStub.restore()
     @contextSetStub.restore()
     Ember.run App, 'destroy'

-  QUnit.module 'screenreader_gradebook_controller',
-    setup: ->
-      setup.call this
-    teardown: ->
-      teardown.call this
-
-  test 'calculates students properly', ->
-    equal @srgb.get('students.length'), 10
-    equal @srgb.get('students.firstObject').name, fixtures.students[0].user.name
-
-  test 'calculates assignments properly', ->
-    ajax.request().then =>
-      equal @srgb.get('assignments.length'), 7
-      ok !@srgb.get('assignments').findBy('name', 'Not Graded')
-      equal @srgb.get('assignments.firstObject').name, fixtures.assignment_groups[0].assignments[0].name
-
-  test 'calculates outcomes properly', ->
-    equal @srgb.get('outcomes.length'), 2
-    equal @srgb.get('outcomes.firstObject').title, fixtures.outcomes[0].title
-
-  test 'studentsHash returns the expected hash', ->
-    _.each @srgb.studentsHash(), (obj) =>
-      strictEqual @srgb.get('students').findBy('id', obj.id), obj
-
-  test 'assignmentGroupsHash retuns the expected hash', (assert) ->
-    start = assert.async()
-    ajax.request().then =>
-      _.each @srgb.assignmentGroupsHash(), (obj) =>
-        strictEqual @srgb.get('assignment_groups').findBy('id', obj.id), obj
-      start()
-
-  test 'student objects have isLoaded flag set to true once submissions are loaded', (assert) ->
-    start = assert.async()
-    ajax.request().then =>
-      @srgb.get('students').forEach (s) ->
-        equal Ember.get(s, 'isLoaded'), true
-      start()
-
-  test 'displayName is hiddenName when hideStudentNames is true', ->
-    @srgb.set('hideStudentNames', true)
-    equal @srgb.get('displayName'), 'hiddenName'
-    @srgb.set('hideStudentNames', false)
-    equal @srgb.get('displayName'), 'name'
-
-  test 'updateSubmission attaches the submission to the student', ->
-    student = clone fixtures.students[0].user
-    submission = clone fixtures.submissions[student.id].submissions[0]
-
-    @srgb.updateSubmission submission, student
-    strictEqual student["assignment_#{submission.assignment_id}"], submission
-
-  test 'studentsInSelectedSection is the same as students when selectedSection is null', ->
-    ok (!@srgb.get('selectedSection'))
-    deepEqual @srgb.get('students'), @srgb.get('studentsInSelectedSection')
-
-  test 'selectedSubmissionLate is true for a late submission', ->
-    @srgb.set('selectedSubmission', {points_deducted: 1})
-    ok (@srgb.get('selectedSubmissionLate'))
-
-  test 'selectedSubmissionLate is false for an on time submission', ->
-    @srgb.set('selectedSubmission', {points_deducted: 0})
-    ok (!@srgb.get('selectedSubmissionLate'))
-
-  test 'selecting a section filters students properly', ->
-    Ember.run =>
-      @srgb.set('selectedSection', @srgb.get('sections.lastObject'))
-    equal @srgb.get('studentsInSelectedSection.length'), 6
-    equal @srgb.get('studentsInSelectedSection.firstObject').name, 'Buffy'
-
-  test 'sorting assignments by position', (assert) ->
-    start = assert.async()
-    ajax.request().then =>
-      Ember.run =>
-        @srgb.set('assignmentSort', @srgb.get('assignmentSortOptions').findBy('value', 'assignment_group'))
-      equal @srgb.get('assignments.firstObject.name'), 'Z Eats Soup'
-      equal @srgb.get('assignments.lastObject.name'), 'Da Fish and Chips!'
-      start()
-
-  test 'updates assignment_visibility on an assignment', (assert) ->
-    start = assert.async()
-    ajax.request().then =>
+test 'gets the submission types', (assert) ->
+  start = assert.async()
+  @completeSetup().then =>
+    equal @srgb.get('assignmentSubmissionTypes'), 'None'
+    Ember.run =>
       assignments = @srgb.get('assignments')
-      assgn = assignments.objectAt(2)
-      @srgb.updateAssignmentVisibilities(assgn, '3')
-      ok !assgn.assignment_visibility.contains('3')
-      start()
-
-  test 'studentsThatCanSeeAssignment doesnt return all students', (assert) ->
-    start = assert.async()
-    ajax.request().then =>
-      assgn = @srgb.get('assignments.firstObject')
-      students = @srgb.studentsThatCanSeeAssignment(assgn)
-      ids = Object.keys(students)
-      equal ids.length, 1
-      equal ids[0], '1'
-      start()
-
-  test 'sorting assignments alphabetically', (assert) ->
-    start = assert.async()
-    ajax.request().then =>
-      Ember.run =>
-        @srgb.set('assignmentSort', @srgb.get('assignmentSortOptions').findBy('value', 'alpha'))
-      equal @srgb.get('assignments.firstObject.name'), 'Apples are good'
-      equal @srgb.get('assignments.lastObject.name'), 'Z Eats Soup'
-      start()
-
-  test 'sorting assignments by due date', (assert) ->
-    start = assert.async()
-    ajax.request().then =>
-      Ember.run =>
-        @srgb.set('assignmentSort', @srgb.get('assignmentSortOptions').findBy('value', 'due_date'))
-      equal @srgb.get('assignments.firstObject.name'), 'Can You Eat Just One?'
-      equal @srgb.get('assignments.lastObject.name'), 'Drink Water'
-      start()
-
-  QUnit.module 'screenreader_gradebook_controller Loading Submissions',
-    setup: ->
-      setFixtures.call(this)
-      ajax.defineFixture window.ENV.GRADEBOOK_OPTIONS.submissions_url,
-        response: [
-          {
-            submissions: [{
-              assignment_id: '1',
-              assignment_visible: true,
-              cached_due_date: '2015-03-01T12:00:00Z',
-              score: 10,
-              user_id: '1'
-            }, {
-              assignment_id: '2',
-              assignment_visible: true,
-              cached_due_date: '2015-05-02T12:00:00Z',
-              score: 9,
-              user_id: '1'
-            }],
-            user_id: '01'
-          }, {
-            submissions: [{
-              assignment_id: '1',
-              assignment_visible: true,
-              cached_due_date: '2015-07-03T12:00:00Z',
-              score: 8,
-              user_id: '2'
-            }],
-            user_id: '2'
-          }
-        ]
-        jqXHR: { getResponseHeader: -> {} }
-        textStatus: 'success'
-
-      ENV.GRADEBOOK_OPTIONS.grading_period_set =
-        id: '1501'
-        grading_periods: [
-          {
-            id: '1403'
-            close_date: '2015-07-08T12:00:00Z'
-            end_date: '2015-07-01T12:00:00Z'
-            is_closed: false
-            start_date: '2015-05-01T12:00:00Z'
-          },
-          {
-            id: '1401'
-            close_date: '2015-03-08T12:00:00Z'
-            end_date: '2015-03-01T12:00:00Z'
-            is_closed: true
-            start_date: '2015-01-01T12:00:00Z'
-          },
-          {
-            id: '1402'
-            close_date: '2015-05-08T12:00:00Z'
-            end_date: '2015-05-01T12:00:00Z'
-            is_closed: false
-            start_date: '2015-03-01T12:00:00Z'
-          }
-        ]
-        weighted: true
-
-      initializeApp.call(this)
-
-    teardown: ->
-      teardown.call(this)
-
-  test 'updates effective due dates', ->
-    ajax.request('/api/v1/submissions').then =>
-      effectiveDueDates = @srgb.get('effectiveDueDates.content')
-      deepEqual(Object.keys(effectiveDueDates), ['1', '2'])
-      deepEqual(Object.keys(effectiveDueDates[1]), ['1', '2'])
-      deepEqual(Object.keys(effectiveDueDates[2]), ['1'])
-
-  test 'updates effective due dates on related assignments', ->
-    ajax.request('/api/v1/submissions').then =>
-      deepEqual(Object.keys(@srgb.get('assignments').findBy('id', '1').effectiveDueDates), ['1', '2'])
-      deepEqual(Object.keys(@srgb.get('assignments').findBy('id', '2').effectiveDueDates), ['1'])
-
-  test 'updates inClosedGradingPeriod on related assignments', ->
-    ajax.request('/api/v1/submissions').then =>
-      strictEqual(@srgb.get('assignments').findBy('id', '1').inClosedGradingPeriod, true)
-      strictEqual(@srgb.get('assignments').findBy('id', '2').inClosedGradingPeriod, false)
-
-  QUnit.module 'screenreader_gradebook_controller#gradesAreWeighted',
-    setup: ->
-      setup.call this
-    teardown: ->
-      teardown.call this
-
-  test 'is true when the grading period set is weighted', ->
-    gradingPeriodSet = createExampleGradingPeriodSet()
-    gradingPeriodSet.weighted = true
-    @stub(@srgb, 'getGradingPeriodSet').returns(gradingPeriodSet)
-    Ember.run =>
-      @srgb.set('groupsAreWeighted', false)
-      equal @srgb.get('gradesAreWeighted'), true
-
-  test 'is true when groupsAreWeighted is true', ->
-    gradingPeriodSet = createExampleGradingPeriodSet()
-    gradingPeriodSet.weighted = false
-    @stub(@srgb, 'getGradingPeriodSet').returns(gradingPeriodSet)
-    Ember.run =>
-      @srgb.set('groupsAreWeighted', true)
-      equal @srgb.get('gradesAreWeighted'), true
-
-  test 'is false when assignment groups are not weighted and the grading period set is not weighted', ->
-    gradingPeriodSet = createExampleGradingPeriodSet()
-    gradingPeriodSet.weighted = false
-    @stub(@srgb, 'getGradingPeriodSet').returns(gradingPeriodSet)
-    Ember.run =>
-      @srgb.set('groupsAreWeighted', false)
-      equal @srgb.get('gradesAreWeighted'), false
-
-  test 'is false when assignment groups are not weighted and the grading period set is not defined', ->
-    @stub(@srgb, 'getGradingPeriodSet').returns(null)
-    Ember.run =>
-      @srgb.set('groupsAreWeighted', false)
-      equal @srgb.get('gradesAreWeighted'), false
-
-  QUnit.module 'screenreader_gradebook_controller#hidePointsPossibleForFinalGrade',
-    setup: ->
-      setup.call this
-    teardown: ->
-      teardown.call this
-
-  test 'is true when groupsAreWeighted is true', ->
-    Ember.run =>
-      @srgb.set('groupsAreWeighted', true)
-      equal @srgb.get('hidePointsPossibleForFinalGrade'), true
-
-  test 'is true when subtotalByGradingPeriod is true', ->
-    @stub(@srgb, 'subtotalByGradingPeriod').returns(true)
-    Ember.run =>
-      equal @srgb.get('hidePointsPossibleForFinalGrade'), true
-
-  test 'is false when groupsAreWeighted is false and subtotalByGradingPeriod is false', ->
-    @stub(@srgb, 'subtotalByGradingPeriod').returns(false)
-    Ember.run =>
-      @srgb.set('groupsAreWeighted', false)
-      equal @srgb.get('hidePointsPossibleForFinalGrade'), false
-
-  QUnit.module '#getGradingPeriodSet',
-    setup: ->
-      setup.call this
-
-    teardown: ->
-      teardown.call this
-
-  test 'normalizes the grading period set from the env', ->
-    ENV.GRADEBOOK_OPTIONS.grading_period_set =
+      @srgb.set('selectedAssignment', assignments.objectAt(1))
+    equal @srgb.get('assignmentSubmissionTypes'), 'Online URL, Online text entry'
+    start()
+
+test 'assignmentInClosedGradingPeriod returns false when the selected assignment does not have a due date in a closed grading period', (assert) ->
+  start = assert.async()
+  @completeSetup().then =>
+    Ember.run =>
+      assignment = @srgb.get('assignments.lastObject')
+      assignment.inClosedGradingPeriod = false
+      @srgb.set('selectedAssignment', assignment)
+    equal @srgb.get('assignmentInClosedGradingPeriod'), false
+    start()
+
+test 'assignmentInClosedGradingPeriod returns true when the selected assignment has a due date in a closed grading period', (assert) ->
+  start = assert.async()
+  @completeSetup().then =>
+    Ember.run =>
+      assignment = @srgb.get('assignments.lastObject')
+      assignment.inClosedGradingPeriod = true
+      @srgb.set('selectedAssignment', assignment)
+    equal @srgb.get('assignmentInClosedGradingPeriod'), true
+    start()
+
+QUnit.module 'screenreader_gradebook_controller:draftState',
+  setup: ->
+    setup.call this, true
+    @completeSetup = =>
+      ajax.request().then =>
+        Ember.run =>
+          @srgb.get('assignment_groups').pushObject
+            id: '100'
+            name: 'Silent Assignments'
+            position: 2
+            assignments: [
+              {
+                id: '21'
+                name: 'Unpublished Assignment'
+                points_possible: 10
+                grading_type: 'percent'
+                submission_types: ['none']
+                due_at: null
+                position: 6
+                assignment_group_id:'4'
+                published: false
+              }
+            ]
+
+  teardown: ->
+    teardown.call this
+
+test 'calculates assignments properly', (assert) ->
+  start = assert.async()
+  @completeSetup().then =>
+    equal @srgb.get('assignments.length'), 7
+    ok !@srgb.get('assignments').findBy('name', 'Unpublished Assignment')
+    start()
+
+
+calc_stub = {
+  assignmentGroups: {}
+  final:
+    possible: 100
+    score: 90
+  current:
+    possible: 88
+    score: 70
+}
+
+
+calc_stub_with_0_possible = {
+  assignmentGroups: {}
+  final:
+    possible: 0
+    score: 0
+  current:
+    possible: 0
+    score: 0
+}
+
+calculationSetup = (calculationStub = calc_stub) ->
+  App = startApp()
+  Ember.run =>
+    @srgb = SRGBController.create()
+    @srgb.reopen
+      calculate: ->
+        calculationStub
+
+    @srgb.set('model', {
+      enrollments: Ember.ArrayProxy.create(content: clone fixtures.students)
+      assignment_groups: Ember.ArrayProxy.create(content: clone fixtures.assignment_groups)
+      submissions: Ember.ArrayProxy.create(content: [])
+      sections: Ember.ArrayProxy.create(content: clone fixtures.sections)
+    })
+
+QUnit.module 'screenreader_gradebook_controller: grade calc',
+  setup: ->
+    calculationSetup.call this
+
+test 'calculates final grade', (assert) ->
+  start = assert.async()
+  ajax.request().then =>
+    equal @srgb.get('students.firstObject.total_percent'), 79.55
+    start()
+
+QUnit.module 'grade calc with 0s',
+  setup: ->
+    calculationSetup.call this, calc_stub_with_0_possible
+
+test 'calculates final grade', (assert) ->
+  start = assert.async()
+  ajax.request().then =>
+    equal @srgb.get('students.firstObject.total_percent'), 0
+    start()
+
+QUnit.module 'screenreader_gradebook_controller: calculate',
+  setupThis:(options = {}) ->
+    assignments = [{ id: 201, points_possible: 10, omit_from_final_grade: false }]
+    submissions = [{ assignment_id: 201, score: 10 }]
+    assignmentGroupsHash = { 301: { id: 301, group_weight: 60, rules: {}, assignments } }
+    gradingPeriodSet =
       id: '1501'
-      grading_periods: [{ id: '701', weight: 50 }, { id: '702', weight: 50 }]
+      gradingPeriods: [{ id: '701', weight: 50 }, { id: '702', weight: 50 }]
       weighted: true
-    gradingPeriodSet = @srgb.getGradingPeriodSet()
-    deepEqual(gradingPeriodSet.id, '1501')
-    equal(gradingPeriodSet.gradingPeriods.length, 2)
-    deepEqual(_.map(gradingPeriodSet.gradingPeriods, 'id'), ['701', '702'])
-
-  test 'sets grading period set to null when not defined in the env', ->
-    gradingPeriodSet = @srgb.getGradingPeriodSet()
-    deepEqual(gradingPeriodSet, null)
-
-  QUnit.module '#submissionsForStudent',
-    setupThis: (options = {}) ->
-      effectiveDueDates = Ember.ObjectProxy.create(
-        content: {
-          1: { 1: { grading_period_id: '1' } },
-          2: { 1: { grading_period_id: '2' } }
-        }
-      )
-
-      defaults = {
-        has_grading_periods: false,
-        'selectedGradingPeriod.id': null,
-        effectiveDueDates
-      }
-      self = _.defaults options, defaults
-      self.get = (attribute) -> self[attribute]
-      self
-
-    setup: ->
-      @student =
-        id: '1'
-        assignment_1: { assignment_id: '1', user_id: '1', name: 'yolo' }
-        assignment_2: { assignment_id: '2', user_id: '1', name: 'froyo' }
-
-      setup.call this
-
-    teardown: ->
-      teardown.call this
-
-  test 'returns all submissions for the student when there are no grading periods', ->
-    self = @setupThis()
-    submissions = @srgb.submissionsForStudent.call(self, @student)
-    propEqual _.pluck(submissions, 'assignment_id'), ['1', '2']
-
-  test 'returns all submissions if "All Grading Periods" is selected', ->
-    self = @setupThis(
-      has_grading_periods: true,
+    props = _.defaults options,
+      weightingScheme: 'points'
+      getGradingPeriodSet: () -> gradingPeriodSet
+      'effectiveDueDates.content': { 201: { 101: { grading_period_id: '701' } } }
+    _.extend {}, props,
+      get: (attr) -> props[attr]
+      submissionsForStudent: () -> submissions
+      assignmentGroupsHash: () -> assignmentGroupsHash
+
+  setup: ->
+    @calculate = SRGBController.prototype.calculate
+
+test 'calculates grades using properties from the gradebook', ->
+  self = @setupThis()
+  @stub(CourseGradeCalculator, 'calculate').returns('expected')
+  grades = @calculate.call(self, id: '101', loaded: true)
+  equal(grades, 'expected')
+  args = CourseGradeCalculator.calculate.getCall(0).args
+  equal(args[0], self.submissionsForStudent())
+  equal(args[1], self.assignmentGroupsHash())
+  equal(args[2], self.get('weightingScheme'))
+  equal(args[3], self.getGradingPeriodSet())
+
+test 'scopes effective due dates to the user', ->
+  self = @setupThis()
+  @stub(CourseGradeCalculator, 'calculate')
+  @calculate.call(self, id: '101', loaded: true)
+  dueDates = CourseGradeCalculator.calculate.getCall(0).args[4]
+  deepEqual(dueDates, 201: { grading_period_id: '701' })
+
+test 'calculates grades without grading period data when grading period set is null', ->
+  self = @setupThis(getGradingPeriodSet: -> null)
+  @stub(CourseGradeCalculator, 'calculate')
+  @calculate.call(self, id: '101', loaded: true)
+  args = CourseGradeCalculator.calculate.getCall(0).args
+  equal(args[0], self.submissionsForStudent())
+  equal(args[1], self.assignmentGroupsHash())
+  equal(args[2], self.get('weightingScheme'))
+  equal(typeof args[3], 'undefined')
+  equal(typeof args[4], 'undefined')
+
+test 'calculates grades without grading period data when effective due dates are not defined', ->
+  self = @setupThis('effectiveDueDates.content': null)
+  @stub(CourseGradeCalculator, 'calculate')
+  @calculate.call(self, id: '101', loaded: true)
+  args = CourseGradeCalculator.calculate.getCall(0).args
+  equal(args[0], self.submissionsForStudent())
+  equal(args[1], self.assignmentGroupsHash())
+  equal(args[2], self.get('weightingScheme'))
+  equal(typeof args[3], 'undefined')
+  equal(typeof args[4], 'undefined')
+
+QUnit.module 'screenreader_gradebook_controller: calculateStudentGrade',
+  setupThis:(options = {}) ->
+    assignments = [{ id: 201, points_possible: 10, omit_from_final_grade: false }]
+    submissions = [{ assignment_id: 201, score: 10 }]
+    assignmentGroupsHash = { 301: { id: 301, group_weight: 60, rules: {}, assignments } }
+    gradingPeriodSet =
+      id: '1501'
+      gradingPeriods: [{ id: '701', weight: 50 }, { id: '702', weight: 50 }]
+      weighted: true
+    props = _.defaults options,
+      weightingScheme: 'points'
+      getGradingPeriodSet: () -> gradingPeriodSet
+      calculate: () -> CourseGradeCalculator.calculate()
+      'effectiveDueDates.content': { 201: { 101: { grading_period_id: '701' } } }
       'selectedGradingPeriod.id': '0'
-    )
-    submissions = @srgb.submissionsForStudent.call(self, @student)
-    propEqual _.pluck(submissions, 'assignment_id'), ['1', '2']
-
-  test 'only returns submissions due for the student in the selected grading period', ->
-    self = @setupThis(
-      has_grading_periods: true,
-      'selectedGradingPeriod.id': '2'
-    )
-    submissions = @srgb.submissionsForStudent.call(self, @student)
-    propEqual _.pluck(submissions, 'assignment_id'), ['2']
-
-  QUnit.module 'screenreader_gradebook_controller: with selected student',
-    setup: ->
-      setup.call this
-      @stub(@srgb, 'calculateStudentGrade')
-      @stub(@srgb, 'subtotalByGradingPeriod')
-      @completeSetup = =>
-        ajax.request().then =>
-          Ember.run =>
-            @srgb.set('selectedGradingPeriod', { id: '3' })
-            @srgb.set('assignment_groups', Ember.ArrayProxy.create(content: clone fixtures.assignment_groups))
-            @srgb.set('assignment_groups.isLoaded', true)
-            student = @srgb.get('students.firstObject')
-            @srgb.set('selectedStudent', student)
-    teardown: ->
-      teardown.call this
-
-  test 'selectedSubmission should be null when just selectedStudent is set', (assert) ->
-    start = assert.async()
-    @completeSetup().then =>
-      strictEqual @srgb.get('selectedSubmission'), null
-      start()
-
-  QUnit.module 'screenreader_gradebook_controller: with selected student, assignment, and outcome',
-    setup: ->
-      setup.call this
-      @completeSetup = =>
-        Ember.run =>
-          ajax.request().then =>
-            @student = @srgb.get('students.firstObject')
-            @assignment = @srgb.get('assignments.firstObject')
-            @outcome = @srgb.get('outcomes.firstObject')
-            @srgb.set('selectedStudent', @student)
-            @srgb.set('selectedAssignment', @assignment)
-            @srgb.set('selectedOutcome', @outcome)
-
-    teardown: ->
-      teardown.call this
-
-  test 'assignmentDetails is computed properly', (assert) ->
-    start = assert.async()
-    @completeSetup().then =>
-      ad = @srgb.get('assignmentDetails')
-      selectedAssignment = @srgb.get('selectedAssignment')
-      strictEqual ad.assignment, selectedAssignment
-      strictEqual ad.cnt, '1'
-      start()
-
-  test 'outcomeDetails is computed properly', (assert) ->
-    start = assert.async()
-    @completeSetup().then =>
-      od = @srgb.get('outcomeDetails')
-      selectedOutcome = @srgb.get('selectedOutcome')
-      strictEqual od.cnt, 1
-      start()
-
-  test 'selectedSubmission is computed properly', (assert) ->
-    start = assert.async()
-    @completeSetup().then =>
-      selectedSubmission = @srgb.get('selectedSubmission')
-      sub = _.find(fixtures.submissions, (s) => s.user_id == @student.id)
-      submission = _.find(sub.submissions, (s) => s.assignment_id == @assignment.id)
-      _.each submission, (val, key) =>
-        equal selectedSubmission[key], val, "#{key} is the expected value on selectedSubmission"
-      start()
-
-  test 'selectedSubmission sets gradeLocked', (assert) ->
-    start = assert.async()
-    @completeSetup().then =>
-      selectedSubmission = @srgb.get('selectedSubmission')
-      equal selectedSubmission.gradeLocked, false
-      start()
-
-  test 'selectedSubmission sets gradeLocked for unassigned students', (assert) ->
-    start = assert.async()
-    @completeSetup().then =>
-      @student = @srgb.get('students')[1]
-      Ember.run =>
-        @srgb.set('selectedStudent', @student)
-        selectedSubmission = @srgb.get('selectedSubmission')
-        equal selectedSubmission.gradeLocked, true
-        start()
-
-  QUnit.module 'screenreader_gradebook_controller: with selected assignment',
-    setup: ->
-      setup.call this
-      @completeSetup = =>
-        ajax.request().then =>
-          @assignment = @srgb.get('assignments.firstObject')
-          Ember.run =>
-            @srgb.set('selectedAssignment', @assignment)
-
-    teardown: ->
-      @contextGetStub.restore()
-      @contextSetStub.restore()
-      Ember.run App, 'destroy'
-
-  test 'gets the submission types', (assert) ->
-    start = assert.async()
-    @completeSetup().then =>
-      equal @srgb.get('assignmentSubmissionTypes'), 'None'
-      Ember.run =>
-        assignments = @srgb.get('assignments')
-        @srgb.set('selectedAssignment', assignments.objectAt(1))
-      equal @srgb.get('assignmentSubmissionTypes'), 'Online URL, Online text entry'
-      start()
-
-  test 'assignmentInClosedGradingPeriod returns false when the selected assignment does not have a due date in a closed grading period', (assert) ->
-    start = assert.async()
-    @completeSetup().then =>
-      Ember.run =>
-        assignment = @srgb.get('assignments.lastObject')
-        assignment.inClosedGradingPeriod = false
-        @srgb.set('selectedAssignment', assignment)
-      equal @srgb.get('assignmentInClosedGradingPeriod'), false
-      start()
-
-  test 'assignmentInClosedGradingPeriod returns true when the selected assignment has a due date in a closed grading period', (assert) ->
-    start = assert.async()
-    @completeSetup().then =>
-      Ember.run =>
-        assignment = @srgb.get('assignments.lastObject')
-        assignment.inClosedGradingPeriod = true
-        @srgb.set('selectedAssignment', assignment)
-      equal @srgb.get('assignmentInClosedGradingPeriod'), true
-      start()
-
-  QUnit.module 'screenreader_gradebook_controller:draftState',
-    setup: ->
-      setup.call this, true
-      @completeSetup = =>
-        ajax.request().then =>
-          Ember.run =>
-            @srgb.get('assignment_groups').pushObject
-              id: '100'
-              name: 'Silent Assignments'
-              position: 2
-              assignments: [
-                {
-                  id: '21'
-                  name: 'Unpublished Assignment'
-                  points_possible: 10
-                  grading_type: 'percent'
-                  submission_types: ['none']
-                  due_at: null
-                  position: 6
-                  assignment_group_id:'4'
-                  published: false
-                }
-              ]
-
-    teardown: ->
-      teardown.call this
-
-  test 'calculates assignments properly', (assert) ->
-    start = assert.async()
-    @completeSetup().then =>
-      equal @srgb.get('assignments.length'), 7
-      ok !@srgb.get('assignments').findBy('name', 'Unpublished Assignment')
-      start()
-
-
-  calc_stub = {
-    assignmentGroups: {}
-    final:
-      possible: 100
-      score: 90
-    current:
-      possible: 88
-      score: 70
-  }
-
-
-  calc_stub_with_0_possible = {
-    assignmentGroups: {}
-    final:
-      possible: 0
-      score: 0
-    current:
-      possible: 0
-      score: 0
-  }
-
-  calculationSetup = (calculationStub = calc_stub) ->
-    App = startApp()
-    Ember.run =>
-      @srgb = SRGBController.create()
+    _.extend {}, props,
+      get: (attr) -> props[attr]
+      submissionsForStudent: () -> submissions
+      assignmentGroupsHash: () -> assignmentGroupsHash
+      subtotalByGradingPeriod: () -> false
+      calculateSingleGrade: ->
+
+  setup: ->
+    @calculateStudentGrade = SRGBController.prototype.calculateStudentGrade
+
+test 'stores the current grade on the student when not including ungraded assignments', ->
+  exampleGrades = createExampleGrades()
+  self = @setupThis(includeUngradedAssignments: false)
+  @stub(CourseGradeCalculator, 'calculate').returns(exampleGrades)
+  student = Ember.Object.create(id: '101', loaded: true)
+  student.set('isLoaded', true)
+  @calculateStudentGrade.call(self, student)
+  equal(student.total_grade, exampleGrades.current)
+
+test 'stores the final grade on the student when including ungraded assignments', ->
+  exampleGrades = createExampleGrades()
+  self = @setupThis(includeUngradedAssignments: true)
+  @stub(CourseGradeCalculator, 'calculate').returns(exampleGrades)
+  student = Ember.Object.create(id: '101', loaded: true)
+  student.set('isLoaded', true)
+  @calculateStudentGrade.call(self, student)
+  equal(student.total_grade, exampleGrades.final)
+
+test 'stores the current grade from the selected grading period when not including ungraded assignments', ->
+  exampleGrades = createExampleGrades()
+  self = @setupThis('selectedGradingPeriod.id': 701, includeUngradedAssignments: false)
+  @stub(CourseGradeCalculator, 'calculate').returns(exampleGrades)
+  student = Ember.Object.create(id: '101', loaded: true)
+  student.set('isLoaded', true)
+  @calculateStudentGrade.call(self, student)
+  equal(student.total_grade, exampleGrades.gradingPeriods[701].current)
+
+test 'stores the final grade from the selected grading period when including ungraded assignments', ->
+  exampleGrades = createExampleGrades()
+  self = @setupThis('selectedGradingPeriod.id': 701, includeUngradedAssignments: true)
+  @stub(CourseGradeCalculator, 'calculate').returns(exampleGrades)
+  student = Ember.Object.create(id: '101', loaded: true)
+  student.set('isLoaded', true)
+  @calculateStudentGrade.call(self, student)
+  equal(student.total_grade, exampleGrades.gradingPeriods[701].final)
+
+QUnit.module 'screenreader_gradebook_controller: notes computed props',
+  setup: ->
+    setup.call this
+    window.ENV.GRADEBOOK_OPTIONS.custom_column_url = '/here/is/an/:id'
+    window.ENV.GRADEBOOK_OPTIONS.teacher_notes = id:'42'
+    @server = sinon.fakeServer.create()
+    Ember.run =>
+      #@srgb.set('custom_columns', [{teacher_notes: true, id: '42'}])
       @srgb.reopen
-        calculate: ->
-          calculationStub
-
-      @srgb.set('model', {
-        enrollments: Ember.ArrayProxy.create(content: clone fixtures.students)
-        assignment_groups: Ember.ArrayProxy.create(content: clone fixtures.assignment_groups)
-        submissions: Ember.ArrayProxy.create(content: [])
-        sections: Ember.ArrayProxy.create(content: clone fixtures.sections)
-      })
-
-  QUnit.module 'screenreader_gradebook_controller: grade calc',
-    setup: ->
-      calculationSetup.call this
-
-  test 'calculates final grade', (assert) ->
-    start = assert.async()
-    ajax.request().then =>
-      equal @srgb.get('students.firstObject.total_percent'), 79.55
-      start()
-
-  QUnit.module 'grade calc with 0s',
-    setup: ->
-      calculationSetup.call this, calc_stub_with_0_possible
-
-  test 'calculates final grade', (assert) ->
-    start = assert.async()
-    ajax.request().then =>
-      equal @srgb.get('students.firstObject.total_percent'), 0
-      start()
-
-  QUnit.module 'screenreader_gradebook_controller: calculate',
-    setupThis:(options = {}) ->
-      assignments = [{ id: 201, points_possible: 10, omit_from_final_grade: false }]
-      submissions = [{ assignment_id: 201, score: 10 }]
-      assignmentGroupsHash = { 301: { id: 301, group_weight: 60, rules: {}, assignments } }
-      gradingPeriodSet =
-        id: '1501'
-        gradingPeriods: [{ id: '701', weight: 50 }, { id: '702', weight: 50 }]
-        weighted: true
-      props = _.defaults options,
-        weightingScheme: 'points'
-        getGradingPeriodSet: () -> gradingPeriodSet
-        'effectiveDueDates.content': { 201: { 101: { grading_period_id: '701' } } }
-      _.extend {}, props,
-        get: (attr) -> props[attr]
-        submissionsForStudent: () -> submissions
-        assignmentGroupsHash: () -> assignmentGroupsHash
-
-    setup: ->
-      @calculate = SRGBController.prototype.calculate
-
-  test 'calculates grades using properties from the gradebook', ->
-    self = @setupThis()
-    @stub(CourseGradeCalculator, 'calculate').returns('expected')
-    grades = @calculate.call(self, id: '101', loaded: true)
-    equal(grades, 'expected')
-    args = CourseGradeCalculator.calculate.getCall(0).args
-    equal(args[0], self.submissionsForStudent())
-    equal(args[1], self.assignmentGroupsHash())
-    equal(args[2], self.get('weightingScheme'))
-    equal(args[3], self.getGradingPeriodSet())
-
-  test 'scopes effective due dates to the user', ->
-    self = @setupThis()
-    @stub(CourseGradeCalculator, 'calculate')
-    @calculate.call(self, id: '101', loaded: true)
-    dueDates = CourseGradeCalculator.calculate.getCall(0).args[4]
-    deepEqual(dueDates, 201: { grading_period_id: '701' })
-
-  test 'calculates grades without grading period data when grading period set is null', ->
-    self = @setupThis(getGradingPeriodSet: -> null)
-    @stub(CourseGradeCalculator, 'calculate')
-    @calculate.call(self, id: '101', loaded: true)
-    args = CourseGradeCalculator.calculate.getCall(0).args
-    equal(args[0], self.submissionsForStudent())
-    equal(args[1], self.assignmentGroupsHash())
-    equal(args[2], self.get('weightingScheme'))
-    equal(typeof args[3], 'undefined')
-    equal(typeof args[4], 'undefined')
-
-  test 'calculates grades without grading period data when effective due dates are not defined', ->
-    self = @setupThis('effectiveDueDates.content': null)
-    @stub(CourseGradeCalculator, 'calculate')
-    @calculate.call(self, id: '101', loaded: true)
-    args = CourseGradeCalculator.calculate.getCall(0).args
-    equal(args[0], self.submissionsForStudent())
-    equal(args[1], self.assignmentGroupsHash())
-    equal(args[2], self.get('weightingScheme'))
-    equal(typeof args[3], 'undefined')
-    equal(typeof args[4], 'undefined')
-
-  QUnit.module 'screenreader_gradebook_controller: calculateStudentGrade',
-    setupThis:(options = {}) ->
-      assignments = [{ id: 201, points_possible: 10, omit_from_final_grade: false }]
-      submissions = [{ assignment_id: 201, score: 10 }]
-      assignmentGroupsHash = { 301: { id: 301, group_weight: 60, rules: {}, assignments } }
-      gradingPeriodSet =
-        id: '1501'
-        gradingPeriods: [{ id: '701', weight: 50 }, { id: '702', weight: 50 }]
-        weighted: true
-      props = _.defaults options,
-        weightingScheme: 'points'
-        getGradingPeriodSet: () -> gradingPeriodSet
-        calculate: () -> CourseGradeCalculator.calculate()
-        'effectiveDueDates.content': { 201: { 101: { grading_period_id: '701' } } }
-        'selectedGradingPeriod.id': '0'
-      _.extend {}, props,
-        get: (attr) -> props[attr]
-        submissionsForStudent: () -> submissions
-        assignmentGroupsHash: () -> assignmentGroupsHash
-        subtotalByGradingPeriod: () -> false
-        calculateSingleGrade: ->
-
-    setup: ->
-      @calculateStudentGrade = SRGBController.prototype.calculateStudentGrade
-
-  test 'stores the current grade on the student when not including ungraded assignments', ->
-    exampleGrades = createExampleGrades()
-    self = @setupThis(includeUngradedAssignments: false)
-    @stub(CourseGradeCalculator, 'calculate').returns(exampleGrades)
-    student = Ember.Object.create(id: '101', loaded: true)
-    student.set('isLoaded', true)
-    @calculateStudentGrade.call(self, student)
-    equal(student.total_grade, exampleGrades.current)
-
-  test 'stores the final grade on the student when including ungraded assignments', ->
-    exampleGrades = createExampleGrades()
-    self = @setupThis(includeUngradedAssignments: true)
-    @stub(CourseGradeCalculator, 'calculate').returns(exampleGrades)
-    student = Ember.Object.create(id: '101', loaded: true)
-    student.set('isLoaded', true)
-    @calculateStudentGrade.call(self, student)
-    equal(student.total_grade, exampleGrades.final)
-
-  test 'stores the current grade from the selected grading period when not including ungraded assignments', ->
-    exampleGrades = createExampleGrades()
-    self = @setupThis('selectedGradingPeriod.id': 701, includeUngradedAssignments: false)
-    @stub(CourseGradeCalculator, 'calculate').returns(exampleGrades)
-    student = Ember.Object.create(id: '101', loaded: true)
-    student.set('isLoaded', true)
-    @calculateStudentGrade.call(self, student)
-    equal(student.total_grade, exampleGrades.gradingPeriods[701].current)
-
-  test 'stores the final grade from the selected grading period when including ungraded assignments', ->
-    exampleGrades = createExampleGrades()
-    self = @setupThis('selectedGradingPeriod.id': 701, includeUngradedAssignments: true)
-    @stub(CourseGradeCalculator, 'calculate').returns(exampleGrades)
-    student = Ember.Object.create(id: '101', loaded: true)
-    student.set('isLoaded', true)
-    @calculateStudentGrade.call(self, student)
-    equal(student.total_grade, exampleGrades.gradingPeriods[701].final)
-
-  QUnit.module 'screenreader_gradebook_controller: notes computed props',
-    setup: ->
-      setup.call this
-      window.ENV.GRADEBOOK_OPTIONS.custom_column_url = '/here/is/an/:id'
-      window.ENV.GRADEBOOK_OPTIONS.teacher_notes = id:'42'
-      @server = sinon.fakeServer.create()
-      Ember.run =>
-        #@srgb.set('custom_columns', [{teacher_notes: true, id: '42'}])
-        @srgb.reopen
-          updateOrCreateNotesColumn: ->
-    teardown: ->
-      window.ENV.GRADEBOOK_OPTIONS.custom_column_url = null
-      window.ENV.GRADEBOOK_OPTIONS.teacher_notes = null
-      @server.restore()
-      teardown.call this
-
-  test 'computes showNotesColumn correctly', ->
-    window.ENV.GRADEBOOK_OPTIONS.teacher_notes =
-      hidden: false
-    equal @srgb.get('showNotesColumn'), true
-
-    window.ENV.GRADEBOOK_OPTIONS.teacher_notes =
-      hidden: true
-    equal @srgb.get('showNotesColumn'), false
-
+        updateOrCreateNotesColumn: ->
+  teardown: ->
+    window.ENV.GRADEBOOK_OPTIONS.custom_column_url = null
     window.ENV.GRADEBOOK_OPTIONS.teacher_notes = null
-    equal @srgb.get('showNotesColumn'), false
-
-  test 'shouldCreateNotes, no notes in ENV', ->
-    window.ENV.GRADEBOOK_OPTIONS.teacher_notes = null
-    Ember.run =>
-      @srgb.set('showNotesColumn', true)
-    equal @srgb.get('shouldCreateNotes'), true, 'true if no teacher_notes and showNotesColumns is true'
-
-  test 'shouldCreateNotes, notes in ENV, hidden', ->
-    window.ENV.GRADEBOOK_OPTIONS.teacher_notes =
-      hidden: true
-    Ember.run =>
-      @srgb.set('showNotesColumn', true)
-    actual = @srgb.get('shouldCreateNotes')
-    equal actual, false, 'does not create if there is a teacher_notes object in the ENV'
-
-  test 'shouldCreateNotes, notes in ENV, shown', ->
-    window.ENV.GRADEBOOK_OPTIONS.teacher_notes =
-      hidden: false
-    Ember.run =>
-      @srgb.set('showNotesColumn', true)
-    equal @srgb.get('shouldCreateNotes'), false, 'does not create if there is a teacher_notes object in the ENV'
-
-  test 'notesURL, no notes object in ENV', ->
-    Ember.run =>
-      @srgb.set('shouldCreateNotes', true)
-    equal @srgb.get('notesURL'), ENV.GRADEBOOK_OPTIONS.custom_columns_url, 'computes properly when creating'
-    Ember.run =>
-      @srgb.set('shouldCreateNotes', false)
-    equal @srgb.get('notesURL'), '/here/is/an/42', 'computes properly when showing'
-
-  test 'notesParams', ->
-    Ember.run =>
-      @srgb.set('showNotesColumn', true)
-      @srgb.set('shouldCreateNotes', false)
-    deepEqual @srgb.get('notesParams'), 'column[hidden]': false
-
-    Ember.run =>
-      @srgb.set('showNotesColumn', false)
-      @srgb.set('shouldCreateNotes', false)
-    deepEqual @srgb.get('notesParams'), 'column[hidden]': true
-
-    Ember.run =>
-      @srgb.set('showNotesColumn', true)
-      @srgb.set('shouldCreateNotes', true)
-    deepEqual @srgb.get('notesParams'),
-        'column[title]': 'Notes'
-        'column[position]': 1
-        'column[teacher_notes]': true
-
-  test 'notesVerb', ->
-    Ember.run =>
-      @srgb.set('shouldCreateNotes', true)
-    equal @srgb.get('notesVerb'), 'POST'
-
-    Ember.run =>
-      @srgb.set('shouldCreateNotes', false)
-    equal @srgb.get('notesVerb'), 'PUT'
-
-  QUnit.module 'screenreader_gradebook_controller:invalidGroups',
-    setup: ->
-      setup.call this, true
-      Ember.run =>
-        @srgb.set('assignment_groups',Ember.ArrayProxy.create(content: clone fixtures.assignment_groups))
-    teardown: ->
-      teardown.call this
-
-  test 'calculates invalidGroupsWarningPhrases properly', ->
-    equal @srgb.get('invalidGroupsWarningPhrases'),
-      'Note: Score does not include assignments from the group Invalid AG because it has no points possible.'
-
-  test 'sets showInvalidGroupWarning to false if groups are not weighted', ->
-    Ember.run =>
-      @srgb.set('weightingScheme', 'equal')
-      equal @srgb.get('showInvalidGroupWarning'), false
-      @srgb.set('weightingScheme', 'percent')
-      equal @srgb.get('showInvalidGroupWarning'), true
-
-
-  QUnit.module 'screenreader_gradebook_controller: differentiated assignments',
-    setup: ->
-      setup.call this, true
-    teardown: ->
-      teardown.call this
-
-  test 'selectedSubmissionHidden is false when students have visibility', ->
-    student = @srgb.get('students.firstObject')
+    @server.restore()
+    teardown.call this
+
+test 'computes showNotesColumn correctly', ->
+  window.ENV.GRADEBOOK_OPTIONS.teacher_notes =
+    hidden: false
+  equal @srgb.get('showNotesColumn'), true
+
+  window.ENV.GRADEBOOK_OPTIONS.teacher_notes =
+    hidden: true
+  equal @srgb.get('showNotesColumn'), false
+
+  window.ENV.GRADEBOOK_OPTIONS.teacher_notes = null
+  equal @srgb.get('showNotesColumn'), false
+
+test 'shouldCreateNotes, no notes in ENV', ->
+  window.ENV.GRADEBOOK_OPTIONS.teacher_notes = null
+  Ember.run =>
+    @srgb.set('showNotesColumn', true)
+  equal @srgb.get('shouldCreateNotes'), true, 'true if no teacher_notes and showNotesColumns is true'
+
+test 'shouldCreateNotes, notes in ENV, hidden', ->
+  window.ENV.GRADEBOOK_OPTIONS.teacher_notes =
+    hidden: true
+  Ember.run =>
+    @srgb.set('showNotesColumn', true)
+  actual = @srgb.get('shouldCreateNotes')
+  equal actual, false, 'does not create if there is a teacher_notes object in the ENV'
+
+test 'shouldCreateNotes, notes in ENV, shown', ->
+  window.ENV.GRADEBOOK_OPTIONS.teacher_notes =
+    hidden: false
+  Ember.run =>
+    @srgb.set('showNotesColumn', true)
+  equal @srgb.get('shouldCreateNotes'), false, 'does not create if there is a teacher_notes object in the ENV'
+
+test 'notesURL, no notes object in ENV', ->
+  Ember.run =>
+    @srgb.set('shouldCreateNotes', true)
+  equal @srgb.get('notesURL'), ENV.GRADEBOOK_OPTIONS.custom_columns_url, 'computes properly when creating'
+  Ember.run =>
+    @srgb.set('shouldCreateNotes', false)
+  equal @srgb.get('notesURL'), '/here/is/an/42', 'computes properly when showing'
+
+test 'notesParams', ->
+  Ember.run =>
+    @srgb.set('showNotesColumn', true)
+    @srgb.set('shouldCreateNotes', false)
+  deepEqual @srgb.get('notesParams'), 'column[hidden]': false
+
+  Ember.run =>
+    @srgb.set('showNotesColumn', false)
+    @srgb.set('shouldCreateNotes', false)
+  deepEqual @srgb.get('notesParams'), 'column[hidden]': true
+
+  Ember.run =>
+    @srgb.set('showNotesColumn', true)
+    @srgb.set('shouldCreateNotes', true)
+  deepEqual @srgb.get('notesParams'),
+      'column[title]': 'Notes'
+      'column[position]': 1
+      'column[teacher_notes]': true
+
+test 'notesVerb', ->
+  Ember.run =>
+    @srgb.set('shouldCreateNotes', true)
+  equal @srgb.get('notesVerb'), 'POST'
+
+  Ember.run =>
+    @srgb.set('shouldCreateNotes', false)
+  equal @srgb.get('notesVerb'), 'PUT'
+
+QUnit.module 'screenreader_gradebook_controller:invalidGroups',
+  setup: ->
+    setup.call this, true
+    Ember.run =>
+      @srgb.set('assignment_groups',Ember.ArrayProxy.create(content: clone fixtures.assignment_groups))
+  teardown: ->
+    teardown.call this
+
+test 'calculates invalidGroupsWarningPhrases properly', ->
+  equal @srgb.get('invalidGroupsWarningPhrases'),
+    'Note: Score does not include assignments from the group Invalid AG because it has no points possible.'
+
+test 'sets showInvalidGroupWarning to false if groups are not weighted', ->
+  Ember.run =>
+    @srgb.set('weightingScheme', 'equal')
+    equal @srgb.get('showInvalidGroupWarning'), false
+    @srgb.set('weightingScheme', 'percent')
+    equal @srgb.get('showInvalidGroupWarning'), true
+
+
+QUnit.module 'screenreader_gradebook_controller: differentiated assignments',
+  setup: ->
+    setup.call this, true
+  teardown: ->
+    teardown.call this
+
+test 'selectedSubmissionHidden is false when students have visibility', ->
+  student = @srgb.get('students.firstObject')
+  assignment = @srgb.get('assignments.firstObject')
+
+  Ember.run =>
+    @srgb.set('selectedAssignment', assignment)
+    @srgb.set('selectedStudent', student)
+    equal @srgb.get('selectedSubmissionHidden'), false
+
+test 'selectedSubmissionHidden is true when students dont have visibility', (assert) ->
+  start = assert.async()
+  ajax.request().then =>
+    student = @srgb.get('students').objectAt(2)
     assignment = @srgb.get('assignments.firstObject')

     Ember.run =>
       @srgb.set('selectedAssignment', assignment)
       @srgb.set('selectedStudent', student)
-      equal @srgb.get('selectedSubmissionHidden'), false
-
-  test 'selectedSubmissionHidden is true when students dont have visibility', (assert) ->
-    start = assert.async()
-    ajax.request().then =>
-      student = @srgb.get('students').objectAt(2)
-      assignment = @srgb.get('assignments.firstObject')
-
-      Ember.run =>
-        @srgb.set('selectedAssignment', assignment)
-        @srgb.set('selectedStudent', student)
-        equal @srgb.get('selectedSubmissionHidden'), true
-        start()
-
-  QUnit.module 'screenreader_gradebook_controller: selectedOutcomeResult',
-    setup: -> setup.call @
-    teardown: -> teardown.call @
-
-  test 'should return object including mastery_points if result is found', ->
-    student = @srgb.get('students.firstObject')
-    outcome = @srgb.get('outcomes.firstObject')
-
-    Ember.run =>
-      @srgb.set('selectedOutcome', outcome)
-      @srgb.set('selectedStudent', student)
-      equal @srgb.get('selectedOutcomeResult').mastery_points, outcome.mastery_points
+      equal @srgb.get('selectedSubmissionHidden'), true
+      start()
+
+QUnit.module 'screenreader_gradebook_controller: selectedOutcomeResult',
+  setup: -> setup.call @
+  teardown: -> teardown.call @
+
+test 'should return object including mastery_points if result is found', ->
+  student = @srgb.get('students.firstObject')
+  outcome = @srgb.get('outcomes.firstObject')
+
+  Ember.run =>
+    @srgb.set('selectedOutcome', outcome)
+    @srgb.set('selectedStudent', student)
+    equal @srgb.get('selectedOutcomeResult').mastery_points, outcome.mastery_points


Social: defensive check

Social: defensive check

--- workers/social/lib/social/cache/socialapi.coffee
+++ workers/social/lib/social/cache/socialapi.coffee
@@ -12,7 +12,7 @@

   fetchPopularPosts = (cb)->
     opt =
-      channelName : params.section
+      channelName : params?.section or "Public"
       limit       : 25

     SocialChannel.fetchPopularPosts client, opt, cb


no more weeks

no more weeks

--- views/application.js.coffee
+++ views/application.js.coffee
@@ -73,12 +73,11 @@
     countdown = $('#date .about')
     tick = ->
       diff = (ms - new Date().getTime()) / 1000
-      weeks = Math.floor diff / 604800
       days = Math.floor diff % 604800 / 86400
       hours = Math.floor diff % 86400 / 3600
       minutes = Math.floor diff % 3600 / 60
       secs = Math.floor diff % 60
-      countdown.html weeks + ' week ' + days + ' days ' + hours + ' hours ' + minutes + ' minutes ' + secs + ' seconds'
+      countdown.html days + ' days ' + hours + ' hours ' + minutes + ' minutes ' + secs + ' seconds'
       setTimeout tick, 1000
     tick()


convert to named function

convert to named function

The component was lacking a displayName

--- shared/src/components/buttons/close-button.cjsx
+++ shared/src/components/buttons/close-button.cjsx
@@ -3,7 +3,12 @@

 OXLink = require '../../factories/link'

-module.exports = React.createClass
+CloseButton = (props) ->
   render: ->
-    classNames = classnames 'openstax-close-x', 'close', @props.className
-    <button {...OXLink.filterProps(@props)} className={classNames} aria-role='close'></button>
+    classNames = classnames 'openstax-close-x', 'close', props.className
+    <button
+      {...OXLink.filterProps(props)}
+      className={classNames} aria-role='close'
+    ></button>
+
+module.exports = CloseButton


Changed wording

Changed wording

--- client/components/SubmitForm.coffee
+++ client/components/SubmitForm.coffee
@@ -120,7 +120,7 @@
             {
             if @state.submit?.result
                 <Alert bsStyle="success">
-                    Решение успешно отправлено, оно появится в таблице ниже в течение пары минут.
+                    Решение успешно отправлено, оно появится ниже в течение пары минут.
                     Если не появляется, напишите мне.
                 </Alert>
             }


Let the scroll event triggers page change for all popular browsers yay

--- client/landing/introapp/main.coffee
+++ client/landing/introapp/main.coffee
@@ -55,23 +55,29 @@

   constructor:(options={}, data)->
     options.cssClass = 'intro-view'
-  # options.bind     = 'scroll mousewheel wheel'
+    options.bind     = 'scroll mousewheel wheel'
     super options, data

-  # FIXME ~ GG
-  #   for e in ['mousewheel', 'wheel']
-  #     @on e, _.throttle @bound 'scroll'
-
-  # scroll:(e)->
-
-  #   oevent = e.originalEvent
-  #   if oevent?
-  #     deltaY = oevent.wheelDeltaY or oevent.deltaY
-  #     deltaX = oevent.wheelDeltaX or oevent.deltaX
-  #     if deltaY >  30 then @slider.previousPage()
-  #     if deltaY < -30 then @slider.nextPage()
-
-  #   KD.utils.stopDOMEvent e
+  setCurrentPage: (direction)->
+    unless @_lock
+      @_lock = yes
+      @slider[direction]()
+      @utils.wait 1200, => delete @_lock
+
+  mouseWheel:(e)->
+    oevent = e.originalEvent
+
+    if oevent? and not @_lock
+
+      deltaY = oevent.wheelDeltaY or -oevent.deltaY
+      deltaX = oevent.wheelDeltaX or -oevent.deltaX
+
+      if deltaY < -15 then @setCurrentPage 'nextPage'
+      if deltaY >  15 then @setCurrentPage 'previousPage'
+      if deltaX >  15 then @setCurrentPage 'previousSubPage'
+      if deltaX < -15 then @setCurrentPage 'nextSubPage'
+
+    KD.utils.stopDOMEvent e

   destroyIntro:->
     @setClass 'out'


Fixes #43; Game selection is now split into quests

--- web/scripts/codeland.coffee
+++ web/scripts/codeland.coffee
@@ -67,14 +67,20 @@

     gameSequence = root.getGameSequence()
     sel = new gameSelector(mapDiv, false)
+    tmp1 = document.getElementById("gameSelection")
+
     count = 0
     addGameToMap = (game) ->
         # console.log "Game: #{game}"
         count = count + 1
         sel.buildDiv(count, game, descriptions[game], player.games[game], root.canPlay(game), codeland)
-    addGameToMap game for game in gameSequence
-    tmp1 = document.getElementById("gameSelection")
-
+#    addGameToMap game for game in gameSequence
+    qcount = 0
+    for quest in root.quests
+        $("
Quest #{++qcount}:#{quest.title}
").appendTo tmp1
+        for gameKey in quest.games
+            addGameToMap gameKey
+
     $('Choose your Java Game
').prependTo tmp1
     $('').prependTo tmp1

@@ -92,7 +98,8 @@
             root.currentQuest = root.quests[index]
             break
     root.currentGame.finishGame() if root.currentGame
-
+    root.currentGame = null
+
     gamediv = $(root.UIcont)
     tmp1 = document.getElementById("gameSelection")
     if tmp1 != null
@@ -243,12 +250,13 @@
         root.quests = []
         root.visualMasters = {}
         root.beanshellPreload = data.beanshellPreload
-        questIndex = 0
+        questIndex = -1
         for quest in data.quests
             root.readJSON "config/#{quest}", (questData) ->
-                if questData == undefined
+                if questData == undefined or questData.key == undefined
                     configFail = true
-                root.quests[questIndex++] = questData
+                ++questIndex
+                root.quests[questIndex] = questData
                 for game in questData.games
                     root.readJSON "config/#{game}.json", (gameData) ->
                         if gameData == undefined


allowImages (seal)

allowImages (seal)

--- modules/seal.coffee
+++ modules/seal.coffee
@@ -4,6 +4,7 @@
 class SealModule extends BotModule
   init: =>
     @registerCommand 'seal', { allowDM: true }, (msg, args)->
+      return unless d.data.allowImages
       chance = new Chance()
       if chance.integer({ min: 0, max: 100 }) > 2
         # Get a seal from randomse.al


make bold consistent with other dashboard styling

--- app/assets/javascripts/hanuman/conditional-logic.js.coffee
+++ app/assets/javascripts/hanuman/conditional-logic.js.coffee
@@ -15,7 +15,7 @@
         condition = this
         #console.log condition
         questionId = condition.question_id
-        $triggerContainer = $ruleElement.closest('.panel').find("[data-question-id=" + questionId + "]")
+        $triggerContainer = $ruleElement.closest('form').find("[data-question-id=" + questionId + "]")
         # getting element on edit and new page
         $triggerElement = $triggerContainer.find(".form-control")
         # getting element on show page


Add a seperate function to fetch region for all vms, fixes the issue with Teamwork

--- client/landing/app/MainApp/VirtualizationController.coffee
+++ client/landing/app/MainApp/VirtualizationController.coffee
@@ -112,12 +112,12 @@
     if region = @vmRegions[vmName]
       return @utils.defer -> callback region

-    KD.remote.api.JVM.fetchVmInfo vmName, (err, info)=>
-      if err
-        warn err
-        callback 'aws' # This by default 'aws' please change it if needed! ~ GG
+    KD.remote.api.JVM.fetchVmRegion vmName, (err, region)=>
+      if err or not region
+        warn err  if err
+        callback 'sj' # This by default 'aws' please change it if needed! ~ GG
       else
-        @vmRegions[vmName] = info.region
+        @vmRegions[vmName] = region
         callback @vmRegions[vmName]

   fetchVmName: (options, callback) ->


landing: added some additional text to legal doc.

--- client/landing/site.landing/coffee/legal/userpolicy.coffee
+++ client/landing/site.landing/coffee/legal/userpolicy.coffee
@@ -245,6 +245,9 @@

       Koding reserves the right to suspend or terminate accounts found in violation of these items with or without prior notice given and without liability.
+

+      In addition, Koding reserves the right to terminate unused VMs after a period of inactivity (Usually 45-60 days). Inactive accounts will
+      be notified before any such action is taken.

         (This document is an adaptation of the Google App Engine Program Policies


Removed crappy crap test.

Removed crappy crap test.

--- core/spec/javascripts/backbone/views/fact_view_spec.coffee
+++ core/spec/javascripts/backbone/views/fact_view_spec.coffee
@@ -1,19 +0,0 @@
-describe "FactView", ->
-  it "should call render", ->
-    model = new Fact(displaystring: "test")
-    spyOn model, "getFactWheel"
-    spyOn(window, "Wheel").andReturn set: ->
-
-    spyOn(window, "InteractiveWheelView").andReturn render: ->
-
-    spyOn(window, "FactTabsView").andReturn render: ->
-
-    render = spyOn(Backbone.Marionette.Renderer, "render")
-    view = new FactView(model: model)
-    view.render()
-    expect(Backbone.Marionette.Renderer.render).toHaveBeenCalledWith "facts/_fact",
-      displaystring: "test"
-    ,
-      fact_base: `undefined`
-      fact_wheel: `undefined`
-      interacting_users: `undefined`


Update config.cson comments

Update config.cson comments

--- lib/config.cson
+++ lib/config.cson
@@ -9,19 +9,21 @@
 # NOTE 2: Strings need to be quoted.
 #

-# root directory of your blog (leave empty to source the current project path)
-siteLocalDir: ""
-
+# static engine of your blog
+# e.g. one of: general, html, jekyll, octopress, hexo
+siteEngine: "general"
 # website URL of your blog
 siteUrl: ""
-# static engine of your blog
-# one of these: general, html, jekyll, octopress, hexo
-siteEngine: "general"
+
+# root directory of your blog
+# e.g. leave empty to source the current project path (recommended)
+siteLocalDir: ""
 # directory to drafts from siteLocalDir
 siteDraftsDir: "_drafts/"
 # directory to posts from siteLocalDir
 sitePostsDir: "_posts/{year}/"
 # directory to images from siteLocalDir
+# e.g. to use the current filename directory, can use {directory}
 siteImagesDir: "images/{year}/{month}/"

 # URLs to tags/posts/categories JSON files


A few extra cases which should not be treated as html.

--- services/web/test/UnitTests/coffee/FileStore/FileStoreControllerTests.coffee
+++ services/web/test/UnitTests/coffee/FileStore/FileStoreControllerTests.coffee
@@ -108,7 +108,7 @@
 						@controller.getFile @req, @res

 		# None of these should trigger the iOS/html logic
-		['x.html-is-rad', 'html.pdf', 'bare'].forEach (filename) ->
+		['x.html-is-rad', 'html.pdf', '.html-is-good-for-hidden-files', 'somefile'].forEach (filename) ->
 			describe "with filename as '#{filename}'", ->

 				beforeEach ->


add blank lines

add blank lines

--- newebe/client/app/views/note_selector.coffee
+++ newebe/client/app/views/note_selector.coffee
@@ -18,6 +18,7 @@
     constructor: (@model) ->
         super()

+
 class NoteSelectorList extends ViewCollection
     id: "note-selector-list"
     collection: new NoteCollection()
@@ -27,6 +28,7 @@
     constructor: ->
         super()
         @$el = $("##{@id}")
+

 class NoteSelectorWidget extends View
     id: "note-selector-widget"
@@ -74,6 +76,7 @@

 module.exports = NoteSelector = class NoteSelector

+
 NoteSelector.getDialog = ->
     unless @dialog?
         @dialog = new NoteSelectorWidget()


修复一些demo模板BUG

修复一些demo模板BUG

--- exam_sample/app/assets/javascripts/mockup/exercise_base.coffee
+++ exam_sample/app/assets/javascripts/mockup/exercise_base.coffee
@@ -24,7 +24,8 @@

   getRightPercentParams: ->
     if @props.question_index > 0
-      percent: ( 100.0 * @props.question_right / (@props.question_index)).toFixed(0)
+      percent = ( 100.0 * @props.question_right / (@props.question_index)).toFixed(0)
+      percent: percent
       str: "#{percent}%"
     else
       percent: 0
@@ -180,7 +181,7 @@
     RightPercent: React.createClass
       render: ->

-
正确率
,
+
正确率

{@props.str}


Allow setting of url and cache

Allow setting of url and cache

--- apps/channel/routes.coffee
+++ apps/channel/routes.coffee
@@ -10,7 +10,7 @@
 Q = require 'q'
 _ = require 'underscore'

-fetchChannel = (slug, authToken = false) ->
+fetchChannel = ({slug, authToken = null, url = null, cache = false}) ->
   dfd = Q.defer()
   channel = new Channel
     channel_slug: slug
@@ -18,7 +18,11 @@
   blocks = new ChannelBlocks null,
     channel_slug: slug

+  if url
+    channel.url = url
+
   channel.fetch
+    cache: cache
     data:
       auth_token: authToken
     success: ->
@@ -31,7 +35,7 @@
   slug = req.params.channel_slug
   authToken = req.user?.get('authentication_token')

-  fetchChannel(slug, authToken)
+  fetchChannel({ slug, authToken })
     .then ({ channel, blocks }) ->
       return res.redirect 301, "/#{channel.get('slug')}" if channel.get('class') is 'User'
       blocks.add channel.get 'contents'
@@ -50,8 +54,10 @@

 @embed = (req, res, next) ->
   slug = req.params.channel_slug
+  channel = new Channel channel_slug: slug
+  url = "#{channel.urlRoot()}?per=7&direction=desc"

-  fetchChannel(slug)
+  fetchChannel({ slug, url, cache: true } )
     .then ({ channel, blocks }) ->
       blocks.add channel.get 'contents'

@@ -63,6 +69,7 @@
         channel: channel
         blocks: blocks.models
         author: author
+        isEmbedded: true

     .catch next


Fix slow classrooms progress calculation

--- app/lib/coursesHelper.coffee
+++ app/lib/coursesHelper.coffee
@@ -4,35 +4,37 @@
 module.exports =
   # Result: Each course instance gains a property, numCompleted, that is the
   #   number of students in that course instance who have completed ALL of
-  #   the levels in thate course
-  # TODO: simplify, classroom.sessions only includes sessions for assigned courses now
+  #   the levels in that course
   calculateDots: (classrooms, courses, courseInstances) ->
+    userLevelsCompleted = {}
+    sessions = _.flatten (classroom.sessions.models for classroom in classrooms.models)
+    for session in sessions
+      user = session.get 'creator'
+      userLevelsCompleted[user] ?= {}
+      level = session.get('level').original
+      userLevelsCompleted[user][level] ||= session.completed()
     for classroom in classrooms.models
-      # map [user, level] => session so we don't have to do find TODO
       for course, courseIndex in courses.models
         instance = courseInstances.findWhere({ courseID: course.id, classroomID: classroom.id })
         continue if not instance
+        unless classroom.sessions?.loaded
+          instance.sessionsLoaded = false
+          continue
+        instance.sessionsLoaded = true
         instance.numCompleted = 0
         instance.started = false
-        levels = classroom.getLevels({courseID: course.id})
-        levels.remove(levels.filter((level) => level.get('practice')))
+        levelsInVersionedCourse = new Set (level.get('original') for level in classroom.getLevels({courseID: course.id}).models when not level.get('practice'))
         for userID in instance.get('members')
-          unless classroom.sessions?.loaded
-            instance.sessionsLoaded = false
-            continue
-          instance.sessionsLoaded = true
-          instance.started ||= _.any levels.models, (level) ->
-            session = _.find classroom.sessions.models, (session) ->
-              session.get('creator') is userID and session.get('level').original is level.get('original')
-            session?
-          levelCompletes = _.map levels.models, (level) ->
-            #TODO: Hella slow! Do the mapping first!
-            sessions = _.filter classroom.sessions.models, (session) ->
-              session.get('creator') is userID and session.get('level').original is level.get('original')
-            # sessionMap[userID][level].completed()
-            _.find(sessions, (s) -> s.completed())
-          if _.every levelCompletes
-            instance.numCompleted += 1
+          userStarted = false
+          allComplete = true
+          for level, complete of userLevelsCompleted[userID] when levelsInVersionedCourse.has level
+            userStarted = true
+            if not complete
+              allComplete = false
+              break
+          allComplete = false unless userStarted
+          instance.started ||= userStarted
+          ++instance.numCompleted if allComplete

   calculateEarliestIncomplete: (classroom, courses, courseInstances, students) ->
     # Loop through all the combinations of things, return the first one that somebody hasn't finished


build out 10 frames immediately

build out 10 frames immediately

--- app/models/turn-order.coffee
+++ app/models/turn-order.coffee
@@ -17,8 +17,9 @@
         @_generateInternalData()
         @_sortBySpeed()
         @_calculateMultipliers()
-        @_buildNextFrame()
-        debugger
+
+        @_buildNextFrame() for [1..10]
+

     _generateInternalData: ->
         @_participants = ( {pokemon} for pokemon in @get 'participants' )


Load the images using https.

Load the images using https.

--- assets/js/_networks.coffee
+++ assets/js/_networks.coffee
@@ -2,29 +2,29 @@
   supportedNetworks = new App.Collections.SocialNetworks([
     {
       name: 'mt-facebook'
-      imagePath: 'http://mojotech-static.s3.amazonaws.com/facebook@2x.png'
+      imagePath: 'https://mojotech-static.s3.amazonaws.com/facebook@2x.png'
       shareUrl: (url) -> "https://www.facebook.com/sharer/sharer.php?u=#{url}"
       selected: true
     },
     {
       name: 'mt-twitter'
-      imagePath: 'http://mojotech-static.s3.amazonaws.com/twitter@2x.png'
+      imagePath: 'https://mojotech-static.s3.amazonaws.com/twitter@2x.png'
       shareUrl: (url, text) -> "https://twitter.com/intent/tweet?text=#{text}&url=#{url}"
       selected: true
     },
     {
       name: 'mt-linkedin'
-      imagePath: 'http://mojotech-static.s3.amazonaws.com/linkedin@2x.png'
+      imagePath: 'https://mojotech-static.s3.amazonaws.com/linkedin@2x.png'
       shareUrl: (url, text) -> "https://www.linkedin.com/shareArticle?mini=true&url=#{url}&summary=#{text}"
     },
     {
       name: 'mt-google'
-      imagePath: 'http://mojotech-static.s3.amazonaws.com/google@2x.png'
+      imagePath: 'https://mojotech-static.s3.amazonaws.com/google@2x.png'
       shareUrl: (url) -> "https://plus.google.com/share?url=#{url}"
     },
     {
       name: 'mt-pinterest'
-      imagePath: 'http://mojotech-static.s3.amazonaws.com/pinterest@2x.png'
+      imagePath: 'https://mojotech-static.s3.amazonaws.com/pinterest@2x.png'
       shareUrl: (url, text, mediaUrl) -> "https://www.pinterest.com/pin/create/button/?url=#{url}&media=#{mediaUrl}&guid=1234&description=#{text}"
     }
   ])


Fixing vendors merging routine (reading vendors list from the right place).

--- src/toaster/filetype/coffee/optimizer/vendors.coffee
+++ src/toaster/filetype/coffee/optimizer/vendors.coffee
@@ -1,3 +1,6 @@
+path = require 'path'
+
+fs = require 'fs'
 path = require 'path'

 fsu = require 'fs-util'
@@ -10,9 +13,10 @@

   # merge all vendors into one and return as string
   merge_to_str:->
+
     buffer = []
-    for vname, vpath of @config.vendors
-      buffer.push (fs.readFileSync vpath)
+    for vname, vpath of @config.browser.vendors
+      buffer.push (fs.readFileSync vpath, 'utf-8')

     buffer.join '\n'


fixture user is not admin

fixture user is not admin

--- test/scripts/controllers/userDetailController.coffee
+++ test/scripts/controllers/userDetailController.coffee
@@ -31,4 +31,4 @@

       # force check of admin
       spyOn(@userDetailController, 'isMe').and.returnValue(false)
-      expect(@userDetailController.isMeOrAdmin()).toBeTruthy()
+      expect(@userDetailController.isMeOrAdmin()).not.toBeTruthy()


update instance_manipulation_test

update instance_manipulation_test

--- tests/batman/model/instance_manipulation_test.coffee
+++ tests/batman/model/instance_manipulation_test.coffee
@@ -232,13 +232,16 @@
     QUnit.start()
   ok result instanceof @Product

-asyncTest "string ids are coerced into integers when possible", ->
+asyncTest "string ids aren't coerced into integers -- they won't find ", ->
   product = new @Product
   product.save (err) =>
     throw err if err
     id = product.get('id')
     @Product.find ""+id, (err, foundProduct) ->
-      equal foundProduct, product
+      throw err if err
+      notEqual foundProduct, product
+      equal Batman.typeOf(foundProduct.get("id")), "String"
+      equal Batman.typeOf(product.get("id")), "Number"
       QUnit.start()

 asyncTest "save calls in an accessor will have no sources", ->


Made products sort in descending order

Made products sort in descending order

--- server/routes/get.coffee
+++ server/routes/get.coffee
@@ -11,6 +11,7 @@
     # Do the product query
     Product.find(displayDay: $gte: startOfDay)
       .populate('posterUser')
+      .sort(downvotes: -1)
       .exec (err, products) ->
         console.log products
         res.render 'index',


Better audio buffer overflow protection.

--- emulator/core/units/apu.coffee
+++ emulator/core/units/apu.coffee
@@ -5,7 +5,8 @@
 DMCChannel      = require "../channels/dmc-channel"
 logger          = require("../utils/logger").get()

-FRAME_COUNTER_MAX = 14915
+CPU_FREQUENCY_NTSC = 1789773 # Hz
+CPU_FREQUENCY_PAL = CPU_FREQUENCY_NTSC * 5 / 6  # Actually 1662607 Hz, but we need to adjust it according to screen refresh rate (50 Hz vs 60 Hz)

 ###########################################################
 # Audio processing unit
@@ -23,6 +24,7 @@
         @noiseChannel = new NoiseChannel
         @dmcChannel = new DMCChannel cpu, cpuMemory
         @channelEnabled = (true for [0..4]) # Enable flag for each channel
+        @setNTSCMode true
         @stopRecording()

     ###########################################################
@@ -31,7 +33,6 @@

     powerUp: ->
         logger.info "Reseting APU"
-        @setNTSCMode true
         @clearFrameIRQ()
         @pulseChannel1.powerUp()
         @pulseChannel2.powerUp()
@@ -43,7 +44,7 @@
     setNTSCMode: (ntscMode) ->
         @frameCounterMax4 = if ntscMode then [ 7457, 7456, 7458, 7457,    1, 1 ] else [ 8313, 8314, 8312, 8313,    1, 1 ] # 4-step frame counter
         @frameCounterMax5 = if ntscMode then [ 7457, 7456, 7458, 7458, 7452, 1 ] else [ 8313, 8314, 8312, 8314, 8312, 1 ] # 5-step frame counter
-        @cpuFrequency = if ntscMode then 1789773 else 1662607
+        @cpuFrequency = if ntscMode then CPU_FREQUENCY_NTSC else CPU_FREQUENCY_PAL
         @noiseChannel.setNTSCMode ntscMode
         @dmcChannel.setNTSCMode ntscMode

@@ -261,9 +262,8 @@
         @recordCycle = 0                            # CPU cycle counter
         @outputBuffer = new Float32Array bufferSize # Cached audio samples, ready for output to sound card
         @outputBufferFull = false                   # True when output buffer is full and ready
+        @lastPosition = bufferSize - 1              # Last position in the output/record buffer
         @sampleRate = sampleRate                    # How often are samples taken (samples per second)
-        @bufferUnderflowLimit = bufferSize  / 3     # When we need to increase sample rate (buffer underflow protection)
-        @bufferOverflowLimit = bufferSize * 2 / 3   # When we need to decrease sample rate (buffer overflow protection)

     startRecording: ->
         throw "Cannot start audio recording without initialization" unless @recordBuffer
@@ -276,13 +276,14 @@
         position = ~~(@recordCycle++ * @sampleRate / @cpuFrequency)
         if position > @recordPosition
             @$fillRecordBuffer position
-            @$adjustSampleRate()

     fillRecordBuffer: (position) ->
         outputValue = @getOutputValue()
+        if not position? or position > @lastPosition
+            position = @lastPosition
         while @recordPosition < position
             @recordBuffer[++@recordPosition] = outputValue
-        if @recordPosition >= @recordBuffer.length - 1 and not @outputBufferFull
+        if @recordPosition >= @lastPosition and not @outputBufferFull
             @swapOutputBuffer()

     swapOutputBuffer: ->
@@ -291,18 +292,14 @@
         @recordPosition = -1
         @recordCycle = 0

-    adjustSampleRate: ->
-        if @outputBufferFull and @recordPosition > @bufferOverflowLimit
-            @sampleRate -= 0.1 if @sampleRate > 1             # Buffer overflow protection
-        else if not @outputBufferFull or @recordPosition < @bufferUnderflowLimit
-            @sampleRate += 0.1 if @sampleRate < @cpuFrequency # Buffer underflow protection
-
     readOutputBuffer: ->
-        @completeOutputBuffer() unless @outputBufferFull # Buffer overflow
+        @fillRecordBuffer() unless @outputBufferFull # Buffer underflow
+        @adjustSampleRate()
         @outputBufferFull = false
         @outputBuffer

-    completeOutputBuffer: ->
-        @fillRecordBuffer @recordBuffer.length - 1
+    adjustSampleRate: ->
+        # Our goal is to have about 50% of data in cache (record buffer) now
+        @sampleRate -= 100 * (@recordPosition / @lastPosition - 0.5) # TODO use better (propably non-linear) transformation

 module.exports = APU


change function of inclusion resource

change function of inclusion resource

--- app/specs/router_spec.coffee
+++ app/specs/router_spec.coffee
@@ -265,22 +265,42 @@
     .toss()

+content = '''
+日本ではカモ類の多くは渡り鳥ですが、カルガモは留鳥で、年中観察することができます。
+マガモは渡りを行いますが、日本で繁殖する場合もあります。
+滋賀県米原市にある三島池はマガモの繁殖日として有名です。
+
+琵琶湖では、コガモ、オナガガモ、キンクロハジロ、ホシハジロ、スズガモなどのカモ類が多く見られます。
+これらのうち、コガモ、オナガガモ、キンクロハジロ、ホシハジロは狩猟鳥です。
+コガモは狩猟者から「べ」と呼ばれます。
+'''
+
 frisby
     .create 'GET inclusion success'
-    .get APIurl 'inclusion?href=https://gist.github.com/KamataRyo/9b2c4aca61f2f18b301b198c2987514e'
-    .expectStatus 200
-    .expectHeaderContains 'Content-Type', 'application/json'
-    .expectHeaderContains 'Content-Type', 'charset=UTF-8'
-    .expectHeaderContains 'Access-Control-Allow-Origin', '*'
-    .expectJSONTypes '',
-            histogram: Object
-    .toss()
-
-frisby
-    .create 'GET inclusion failes'
-    .get APIurl 'inclusion?href=invalid-url'
-    .expectStatus 404
-    .expectHeaderContains 'Content-Type', 'application/json'
-    .expectHeaderContains 'Content-Type', 'charset=UTF-8'
-    .expectHeaderContains 'Access-Control-Allow-Origin', '*'
-    .toss()
+    .get APIurl "inclusion?content=#{content}"
+    .expectStatus 200
+    .expectHeaderContains 'Content-Type', 'application/json'
+    .expectHeaderContains 'Content-Type', 'charset=UTF-8'
+    .expectHeaderContains 'Access-Control-Allow-Origin', '*'
+    .expectJSON '',
+            histogram:
+                'カルガモ': 1
+                'マガモ' : 2
+                'コガモ' : 3
+                'オナガガモ' : 2
+                'キンクロハジロ' : 2
+                'ホシハジロ' : 2
+                'スズガモ' : 1
+    .toss()
+
+
+frisby
+    .create 'GET inclusion with nocontent'
+    .get APIurl 'inclusion'
+    .expectStatus 200
+    .expectHeaderContains 'Content-Type', 'application/json'
+    .expectHeaderContains 'Content-Type', 'charset=UTF-8'
+    .expectHeaderContains 'Access-Control-Allow-Origin', '*'
+    .expectJSON '',
+            histogram: {}
+    .toss()


Set User Agent to rocketchatbot

Set User Agent to rocketchatbot

In doing so, If we want to share a single page application, we can include requests from `rocketchatbot` in our server rules to redirect to something like prerender.io version of the page or our own pre-rendering server.

Example prerender.io config:

```
    <IfModule mod_proxy_http.c>
        RewriteCond %{HTTP_USER_AGENT} rocketchatbot|baiduspider|facebookexternalhit|twitterbot|rogerbot|linkedinbot|embedly|quora\ link\ preview|showyoubot|outbrain|pinterest|slackbot|vkShare|W3C_Validator [NC,OR]
        RewriteCond %{QUERY_STRING} _escaped_fragment_

        # Only proxy the request to Prerender if it's a request for HTML
        RewriteRule ^(?!.*?(\.js|\.css|\.xml|\.less|\.png|\.jpg|\.jpeg|\.gif|\.pdf|\.doc|\.txt|\.ico|\.rss|\.zip|\.mp3|\.rar|\.exe|\.wmv|\.doc|\.avi|\.ppt|\.mpg|\.mpeg|\.tif|\.wav|\.mov|\.psd|\.ai|\.xls|\.mp4|\.m4a|\.swf|\.dat|\.dmg|\.iso|\.flv|\.m4v|\.torrent|\.ttf|\.woff))(.*) http://service.prerender.io/http://example.com/$2 [P,L]
    </IfModule>
```

--- packages/rocketchat-oembed/server/server.coffee
+++ packages/rocketchat-oembed/server/server.coffee
@@ -70,7 +70,7 @@
 		gzip: true
 		maxRedirects: redirectCount
 		headers:
-			'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36'
+			'User-Agent': 'rocketchatbot'

 	headers = null
 	statusCode = null


Specific selectors when article or post elements have been found

--- js-library/app/js/jail_iframe/classes/paragraph_buttons.coffee
+++ js-library/app/js/jail_iframe/classes/paragraph_buttons.coffee
@@ -24,13 +24,34 @@
     elementsLeft = elements[10..]
     setTimeout (=> @_addParagraphButtonsBatch(elementsLeft)), 200

+  _defaultSelector: ->
+    "p, h2, h3, h4, h5, h6, li, dd, dt"
+
+  _articleContainerSelector: ->
+    selectors = [
+      'article', 'div.article', 'div.story', 'div.single-post', 'div.post'
+      'div#bodyContent', 'div#content', 'div.content', 'div#main', 'div.main'
+      'div#page', 'div.page', 'div#site', 'div.site'
+    ]
+
+    for s in selectors when $(s).length == 1 # Only match if selector is unique
+      return "#{s} p, #{s} h2, #{s} h3, #{s} h4, #{s} h5, #{s} h6, #{s} li, #{s} dd, #{s} dt"
+
+    null
+
+  _paragraphElements: ->
+    selector = @_articleContainerSelector() || @_defaultSelector()
+    console.info 'FactlinkJailRoot: Paragraph selector:', selector
+
+    $(selector).filter(':visible')
+
   addParagraphButtons: ->
     return unless FactlinkJailRoot.can_haz.paragraph_icons

     for paragraphButton in @_paragraphButtons
       paragraphButton.destroy()

-    @_addParagraphButtonsBatch $('p, h2, h3, h4, h5, h6, li')
+    @_addParagraphButtonsBatch @_paragraphElements()

 FactlinkJailRoot.core_loaded_promise.then ->
   paragraphButtons = new ParagraphButtons


fixed a description of the method get()

fixed a description of the method get()

--- _src/lib/node_cache.coffee
+++ _src/lib/node_cache.coffee
@@ -41,7 +41,7 @@
 	#
 	# **Parameters:**
 	#
-	# * `key` ( String | String[] ): cache key or an array of keys
+	# * `key` ( String ): cache key
 	# * `[cb]` ( Function ): Callback function
 	#
 	# **Example:**


#update #23

#update #23

--- lib/main.coffee
+++ lib/main.coffee
@@ -32,8 +32,8 @@
 console.log "use ppapi-flash-version: #{flashVersion}"

 # app.commandLine.appendSwitch 'register-pepper-plugins', ';application/x-shockwave-flash'
-# app.commandLine.appendSwitch 'ppapi-flash-path', flashPath
-# app.commandLine.appendSwitch 'ppapi-flash-version', flashVersion
+app.commandLine.appendSwitch 'ppapi-flash-path', flashPath
+app.commandLine.appendSwitch 'ppapi-flash-version', flashVersion

 # Quit when all windows are closed.
 app.on 'window-all-closed', ->


revert name from editorStates to vimStates

--- lib/main.coffee
+++ lib/main.coffee
@@ -27,20 +27,21 @@
   activate: (state) ->
     @subscriptions = new CompositeDisposable
     @statusBarManager = new StatusBarManager
+    @vimStates = new Map
+
     @registerViewProviders()
-    @editorStates = new Map
+    @subscriptions.add Base.init(@provideVimModePlus())
+    @registerCommands()
+
     @subscriptions.add atom.workspace.observeTextEditors (editor) =>
-      return if editor.isMini() or @editorStates.has(editor)
+      return if editor.isMini() or @vimStates.has(editor)
       vimState = new VimState(editor, @statusBarManager)
-      @editorStates.set(editor, vimState)
+      @vimStates.set(editor, vimState)
       vimState.onDidDestroy =>
-        @editorStates.delete(editor)
+        @vimStates.delete(editor)

     @subscriptions.add new Disposable =>
-      @editorStates.forEach (vimState) -> vimState.destroy()
-
-    @subscriptions.add Base.init(@provideVimModePlus())
-    @registerCommands()
+      @vimStates.forEach (vimState) -> vimState.destroy()

   registerCommands: ->
     for kind in [TextObject, Misc, InsertMode, Motion, Operator, Scroll, VisualBlockwise]
@@ -80,7 +81,7 @@
     globalState

   getEditorState: (editor) ->
-    @editorStates.get(editor)
+    @vimStates.get(editor)

   consumeStatusBar: (statusBar) ->
     @statusBarManager.initialize(statusBar)