add unit tests for grading period react components

add unit tests and refactor GradingPeriodCollection.jsx and
GradingPeriod.jsx.

closes CNVS-19912

test plan:

the majority of this commit is adding unit testing around
the grading period components. however, i did do some refactoring
of the grading period codebase, so this commit could potentially
affect any behavior on the grading periods page (courses/:course_id/
grading_standards and accounts/:account_id/grading_standards, with
the multiple grading periods flag turned on).

Change-Id: I53c382f952b71de6a6f0c78d4b5047939ef63d3d
Reviewed-on: https://gerrit.instructure.com/52591
Tested-by: Jenkins
Reviewed-by: Josh Simpson <jsimpson@instructure.com>
QA-Review: Amber Taniuchi <amber@instructure.com>
Product-Review: Spencer Olson <solson@instructure.com>
This commit is contained in:
Spencer Olson 2015-04-15 10:50:18 -05:00
parent 86c2fbe3a2
commit 0c4a819f15
9 changed files with 623 additions and 51 deletions

View File

@ -43,7 +43,7 @@ var b = arr.map( (s) => {
// lexical `this`
var obj = {
multiplier: 3,
multiplyStuff (stuff) {
return stuff.map((x) =>
// no bind!
@ -194,7 +194,7 @@ console.log(`Fifteen is ${a + b} and not ${2 * a + b}.`);
```
[1]:https://github.com/instructure-wfx/RFCs/blob/master/active/canvas-js-structure-build.md
[1]:https://github.com/instructure-wfx/RFCs/blob/master/active/canvas-js-structure-build.md
[arrows]:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
[class]:http://tc39wiki.calculist.org/es6/classes/
[rest]:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters

View File

@ -118,11 +118,11 @@ function(React, $, I18n, _) {
},
checkFormForUpdates: function() {
var shouldUpdateBeDisabled = !this.formIsCompleted() || !this.inputsHaveChanged();
var shouldUpdateBeDisabled = !this.formIsComplete() || !this.inputsHaveChanged();
this.setState({shouldUpdateBeDisabled: shouldUpdateBeDisabled});
},
formIsCompleted: function() {
formIsComplete: function() {
var titleCompleted = (this.state.title).trim().length > 0;
var startDateCompleted = (this.state.startDate).trim().length > 0;
var endDateCompleted = (this.state.endDate).trim().length > 0;
@ -130,10 +130,9 @@ function(React, $, I18n, _) {
},
inputsHaveChanged: function() {
if(this.state.title !== this.props.title) return true;
if(this.state.startDate !== this.parseDateTime(this.props.startDate)) return true;
if(this.state.endDate !== this.parseDateTime(this.props.endDate)) return true;
return false;
return (this.state.title !== this.props.title) ||
(this.state.startDate !== this.parseDateTime(this.props.startDate)) ||
(this.state.endDate !== this.parseDateTime(this.props.endDate))
},
parseDateTime: function(inputDate) {
@ -163,8 +162,8 @@ function(React, $, I18n, _) {
return this.parseDateTime(dateInput) !== "";
},
triggerDeleteGradingPeriod: function(event) {
this.props.onDeleteGradingPeriod(event, this.state.id);
triggerDeleteGradingPeriod: function() {
this.props.onDeleteGradingPeriod(this.state.id);
},
renderSaveUpdateButton: function() {
@ -208,7 +207,7 @@ function(React, $, I18n, _) {
render: function () {
return (
<div className="grading-period pad-box-mini border border-trbl border-round">
<div id={"grading-period-" + this.state.id} className="grading-period pad-box-mini border border-trbl border-round">
<div className="grid-row pad-box-micro">
<div className="col-xs-12 col-sm-6 col-lg-3">
<label htmlFor={"period_title_" + this.state.id}>

View File

@ -33,9 +33,9 @@ function(React, GradingPeriod, $, I18n, _, ConvertCase) {
gotPeriods: function(periods, idToExclude) {
var unsavedPeriods = [];
if (this.state.periods) {
unsavedPeriods = this.state.periods.filter(p => p.id.indexOf('new') > -1 && p.id !== idToExclude);
unsavedPeriods = _.filter(this.state.periods, period => period.id.indexOf('new') > -1 && period.id !== idToExclude);
}
var camelizedPeriods = _.map(periods.grading_periods, function (gradingPeriod) { return ConvertCase.camelize(gradingPeriod) });
var camelizedPeriods = _.map(periods.grading_periods, period => ConvertCase.camelize(period));
this.setState({
periods: camelizedPeriods.concat(unsavedPeriods),
needsToCopy: !this.canManageAtLeastOnePeriod(camelizedPeriods) && camelizedPeriods.length > 0,
@ -51,7 +51,7 @@ function(React, GradingPeriod, $, I18n, _, ConvertCase) {
},
canManageAtLeastOnePeriod: function(periods) {
return _.any(periods, function(period){ return period.permissions.manage });
return _.any(periods, period => period.permissions.manage);
},
copyTemplatePeriods: function(periodsToCopy, idToExclude) {
@ -72,28 +72,16 @@ function(React, GradingPeriod, $, I18n, _, ConvertCase) {
});
},
componentDidUpdate: function(prevProps, prevState) {
if (prevState.periods) {
var removedAGradingPeriod = this.state.periods.length < prevState.periods.length;
if (removedAGradingPeriod) this.refs.addPeriodButton.getDOMNode().focus();
}
},
deleteGradingPeriod: function(event, id) {
var $gradingPeriodElement = $(event.target).parents('.grading-period'),
self = this;
deleteGradingPeriod: function(id) {
if (id.indexOf('new') > -1) {
this.removeDeletedGradingPeriod(id);
return;
}
if (this.state.needsToCopy) {
} else if (this.state.needsToCopy) {
var periodsToCopy = _.reject(this.state.periods, p => p.id === id || isNaN(p.id));
var confirmDelete = confirm("Are you sure you want to remove this grading period?");
var confirmDelete = confirm(I18n.t("Are you sure you want to remove this grading period?"));
if (confirmDelete) this.copyTemplatePeriods(periodsToCopy);
} else {
$gradingPeriodElement.confirmDelete({
var self = this;
$("#grading-period-" + id).confirmDelete({
url: ENV.GRADING_PERIODS_URL + "/" + id,
message: I18n.t("Are you sure you want to delete this grading period?"),
success: function () {
@ -120,17 +108,16 @@ function(React, GradingPeriod, $, I18n, _, ConvertCase) {
},
removeDeletedGradingPeriod: function(id) {
var newPeriods = _.reject(this.state.periods, function(period){ return period.id === id });
if (this.lastRemainingPeriod()) {
this.getPeriods();
} else {
var newPeriods = _.reject(this.state.periods, period => period.id === id);
this.setState({periods: newPeriods});
}
},
getCreateGradingPeriodCSS: function() {
var cssClasses = "center-md new-grading-period pad-box border border-round";
if (!this.state.periods || this.state.periods.length === 0) {
cssClasses += " no-active-grading-periods";
}
@ -139,18 +126,19 @@ function(React, GradingPeriod, $, I18n, _, ConvertCase) {
},
createNewGradingPeriod: function() {
var newPeriod = {title: '', startDate: '', endDate: '', id: _.uniqueId('new'), permissions: { read: true, manage: true }};
var newPeriod = { title: '', startDate: '', endDate: '', id: _.uniqueId('new'),
permissions: { read: true, manage: true } };
var periods = update(this.state.periods, {$push: [newPeriod]});
this.setState({periods: periods});
},
getPeriodById: function(id) {
return _.find(this.state.periods, function(period){ return period.id === id });
return _.find(this.state.periods, period => period.id === id);
},
updateGradingPeriodCollection: function(updatedGradingPeriod, permissions, previousStateId) {
if (this.state.needsToCopy && previousStateId) {
var periodsToCopy = _.reject(this.state.periods, p => p.id === previousStateId || isNaN(p.id));
var periodsToCopy = _.reject(this.state.periods, p => (p.id === previousStateId) || isNaN(p.id));
this.copyTemplatePeriods(periodsToCopy, previousStateId);
} else if (previousStateId) {
this.getPeriods(previousStateId);
@ -167,7 +155,7 @@ function(React, GradingPeriod, $, I18n, _, ConvertCase) {
renderLinkToSettingsPage: function() {
if (this.state.periods && this.state.periods.length <= 1) {
return (
<span id="disable-feature-message">
<span id="disable-feature-message" ref="linkToSettings">
{I18n.t("You can disable this feature ")}
<a href={ENV.CONTEXT_SETTINGS_URL + "#tab-features"} aria-label={I18n.t("Feature Options")}> {I18n.t("here.")} </a>
</span>);
@ -176,19 +164,19 @@ function(React, GradingPeriod, $, I18n, _, ConvertCase) {
renderAdminPeriodsMessage: function() {
if (this.state.periods && this.state.periods.length > 0 && !this.canManageAtLeastOnePeriod(this.state.periods)) {
return <span id="admin-periods-message"> {I18n.t("These grading periods were created for you by an administrator.")} </span>;
return <span id="admin-periods-message" ref="adminPeriodsMessage"> {I18n.t("These grading periods were created for you by an administrator.")} </span>;
}
},
renderGradingPeriods: function() {
if (!this.state.periods) return null;
return this.state.periods.map(function(period){
return _.map(this.state.periods, period => {
return (<GradingPeriod id={period.id} key={period.id} title={period.title} startDate={period.startDate}
endDate={period.endDate} weight={period.weight} permissions={period.permissions}
onDeleteGradingPeriod={this.deleteGradingPeriod} cannotDelete={this.cannotDeleteLastPeriod}
updateGradingPeriodCollection={this.updateGradingPeriodCollection}
disabled={this.state.disabled}/>);
}, this);
});
},
render: function () {

View File

@ -263,8 +263,8 @@ function(React, DataRow, $, I18n, _) {
renderInvalidStandardMessage: function() {
var message = "Invalid grading scheme";
if(!this.rowDataIsValid()) message = "Cannot have overlapping or empty ranges. Fix the ranges and try clicking 'Save' again.";
if(!this.rowNamesAreValid()) message = "Cannot have duplicate or empty row names. Fix the names and try clicking 'Save' again.";
if (!this.rowDataIsValid()) message = "Cannot have overlapping or empty ranges. Fix the ranges and try clicking 'Save' again.";
if (!this.rowNamesAreValid()) message = "Cannot have duplicate or empty row names. Fix the names and try clicking 'Save' again.";
return (
<div id={"invalid_standard_message_" + this.props.uniqueId} className="alert-message" tabIndex="-1" ref="invalidStandardAlert">
{I18n.t("%{message}", { message: message })}

View File

@ -39,6 +39,7 @@ define [
module 'DataRow being edited',
setup: ->
@sandbox = sinon.sandbox.create()
props =
key: 0
uniqueId: 0
@ -54,12 +55,13 @@ define [
teardown: ->
React.unmountComponentAtNode(@dataRow.getDOMNode().parentNode)
$("#fixtures").empty()
@sandbox.restore()
test 'renders in "edit" mode (as opposed to "view" mode)', ->
ok @dataRow.refs.editContainer
test 'does not accept non-numeric input', ->
changeMinScore = sinon.spy(@dataRow.props, 'onRowMinScoreChange')
changeMinScore = @sandbox.spy(@dataRow.props, 'onRowMinScoreChange')
Simulate.change(@dataRow.refs.minScoreInput.getDOMNode(), {target: {value: 'A'}})
deepEqual @dataRow.renderMinScore(), '92.346'
Simulate.change(@dataRow.refs.minScoreInput.getDOMNode(), {target: {value: '*&@%!'}})
@ -69,17 +71,17 @@ define [
ok changeMinScore.notCalled
test 'does not call onRowMinScoreChange if the input is less than 0', ->
changeMinScore = sinon.spy(@dataRow.props, 'onRowMinScoreChange')
changeMinScore = @sandbox.spy(@dataRow.props, 'onRowMinScoreChange')
Simulate.change(@dataRow.refs.minScoreInput.getDOMNode(), {target: {value: '-1'}})
ok changeMinScore.notCalled
test 'does not call onRowMinScoreChange if the input is greater than 100', ->
changeMinScore = sinon.spy(@dataRow.props, 'onRowMinScoreChange')
changeMinScore = @sandbox.spy(@dataRow.props, 'onRowMinScoreChange')
Simulate.change(@dataRow.refs.minScoreInput.getDOMNode(), {target: {value: '101'}})
ok changeMinScore.notCalled
test 'calls onRowMinScoreChange when input is a number between 0 and 100 (with or without a trailing period), or blank', ->
changeMinScore = sinon.spy(@dataRow.props, 'onRowMinScoreChange')
changeMinScore = @sandbox.spy(@dataRow.props, 'onRowMinScoreChange')
Simulate.change(@dataRow.refs.minScoreInput.getDOMNode(), {target: {value: '88.'}})
Simulate.change(@dataRow.refs.minScoreInput.getDOMNode(), {target: {value: ''}})
Simulate.change(@dataRow.refs.minScoreInput.getDOMNode(), {target: {value: '100'}})
@ -89,7 +91,7 @@ define [
deepEqual changeMinScore.callCount, 4
test 'calls onRowNameChange when input changes', ->
changeMinScore = sinon.spy(@dataRow.props, 'onRowNameChange')
changeMinScore = @sandbox.spy(@dataRow.props, 'onRowNameChange')
Simulate.change(@dataRow.refs.nameInput.getDOMNode(), {target: {value: 'F'}})
ok changeMinScore.calledOnce
@ -112,7 +114,7 @@ define [
deepEqual @dataRow.refs.insertRowLink, undefined
test 'calls onDeleteRow when the delete link is clicked', ->
deleteRow = sinon.spy(@dataRow.props, 'onDeleteRow')
deleteRow = @sandbox.spy(@dataRow.props, 'onDeleteRow')
Simulate.click(@dataRow.refs.deleteLink.getDOMNode())
ok deleteRow.calledOnce

View File

@ -0,0 +1,313 @@
define [
'react'
'jquery'
'underscore'
'jsx/grading/gradingPeriodCollection'
'jquery.instructure_misc_plugins'
], (React, $, _, GradingPeriodCollection) ->
TestUtils = React.addons.TestUtils
Simulate = TestUtils.Simulate
module 'GradingPeriodCollection with read and manage permission for all periods',
setup: ->
@fMessage = $.flashMessage
@fError = $.flashError
@wConfirm = window.confirm
$.flashMessage = ->
$.flashError = ->
window.confirm = -> true
@server = sinon.fakeServer.create()
@sandbox = sinon.sandbox.create()
ENV.current_user_roles = ["teacher"]
ENV.GRADING_PERIODS_URL = "/api/v1/courses/1/grading_periods"
@indexData = "grading_periods":[
{
"id":"1", "start_date":"2015-03-01T06:00:00Z", "end_date":"2015-05-31T05:00:00Z",
"weight":null, "title":"Spring", "permissions": { "read":true, "manage":true }
},
{
"id":"2", "start_date":"2015-06-01T05:00:00Z", "end_date":"2015-08-31T05:00:00Z",
"weight":null, "title":"Summer", "permissions": { "read":true, "manage":true }
}
]
@formattedIndexData = "grading_periods":[
{
"id":"1", "startDate":"2015-03-01T06:00:00Z", "endDate":"2015-05-31T05:00:00Z",
"weight":null, "title":"Spring", "permissions": { "read":true, "manage":true }
},
{
"id":"2", "startDate":"2015-06-01T05:00:00Z", "endDate":"2015-08-31T05:00:00Z",
"weight":null, "title":"Summer", "permissions": { "read":true, "manage":true }
}
]
@createdPeriodData = "grading_periods":[
{
"id":"3", "start_date":"2015-04-20T05:00:00Z", "end_date":"2015-04-21T05:00:00Z",
"weight":null, "title":"New Period!", "permissions": { "read":true, "manage":true }
}
]
@server.respondWith "GET", ENV.GRADING_PERIODS_URL, [200, {"Content-Type":"application/json"}, JSON.stringify @indexData]
@server.respondWith "POST", ENV.GRADING_PERIODS_URL, [200, {"Content-Type":"application/json"}, JSON.stringify @createdPeriodData]
@server.respondWith "DELETE", ENV.GRADING_PERIODS_URL + "/1", [204, {}, ""]
@gradingPeriodCollection = TestUtils.renderIntoDocument(GradingPeriodCollection())
@server.respond()
teardown: ->
React.unmountComponentAtNode(@gradingPeriodCollection.getDOMNode().parentNode)
ENV.current_user_roles = null
ENV.GRADING_PERIODS_URL = null
$.flashMessage = @fMessage
$.flashError = @fError
window.confirm = @wConfirm
@server.restore()
@sandbox.restore()
test 'gets the grading periods from the grading periods controller', ->
deepEqual @gradingPeriodCollection.state.periods, @formattedIndexData.grading_periods
test 'canManageAtLeastOnePeriod returns true', ->
periods = @gradingPeriodCollection.state.periods
deepEqual @gradingPeriodCollection.canManageAtLeastOnePeriod(periods), true
test 'cannotDeleteLastPeriod returns false if there is one period left and manage permission is true', ->
onePeriod = [
{
"id":"1", "startDate":"2029-03-01T06:00:00Z", "endDate":"2030-05-31T05:00:00Z",
"weight":null, "title":"A Lonely Grading Period with manage permission",
"permissions": { "read":true, "manage":true }
}
]
@gradingPeriodCollection.setState({periods: onePeriod})
deepEqual @gradingPeriodCollection.cannotDeleteLastPeriod(), false
test 'getPeriods requests the index data from the server', ->
@sandbox.spy($, "ajax")
@gradingPeriodCollection.getPeriods()
ok $.ajax.calledOnce
test 'getPeriods calls gotPeriods once the data is received', ->
@sandbox.stub(@gradingPeriodCollection, 'gotPeriods')
@gradingPeriodCollection.getPeriods()
@server.respond()
ok @gradingPeriodCollection.gotPeriods.calledOnce
test 'gotPeriods sets disabled state to false', ->
@gradingPeriodCollection.setState({disabled: true})
deepEqual @gradingPeriodCollection.state.disabled, true
@gradingPeriodCollection.gotPeriods(@indexData)
deepEqual @gradingPeriodCollection.state.disabled, false
test 'gotPeriods concatenates the index returned from the ajax call with any new, unsaved grading periods, and sets the result to the periods state', ->
unsavedPeriods = [
{
"id":"new1", "startDate":"2029-03-01T06:00:00Z", "endDate":"2030-05-31T05:00:00Z",
"weight":null, "title":"New Period. I'm not saved yet!",
"permissions": { "read":true, "manage":true }
},
{
"id":"new2", "startDate":"2039-03-01T06:00:00Z", "endDate":"2042-05-31T05:00:00Z",
"weight":null, "title":"Another New Period. Also not saved yet!",
"permissions": { "read":true, "manage":true }
}
]
@gradingPeriodCollection.setState({periods: unsavedPeriods})
@gradingPeriodCollection.gotPeriods(@indexData)
newPeriods = @formattedIndexData.grading_periods.concat(unsavedPeriods)
deepEqual @gradingPeriodCollection.state.periods, newPeriods
test 'gotPeriods excludes the grading period with the id matching idToExclude when setting state', ->
idToExclude = 'new2'
unsavedPeriods = [
{
"id":"new1", "startDate":"2029-03-01T06:00:00Z", "endDate":"2030-05-31T05:00:00Z",
"weight":null, "title":"New Period. I'm not saved yet!",
"permissions": { "read":true, "manage":true }
},
{
"id":"new2", "startDate":"2039-03-01T06:00:00Z", "endDate":"2042-05-31T05:00:00Z",
"weight":null, "title":"Another New Period. Also not saved yet! I won't be in the new state.",
"permissions": { "read":true, "manage":true }
}
]
@gradingPeriodCollection.setState({periods: unsavedPeriods})
@gradingPeriodCollection.gotPeriods(@indexData, idToExclude)
newPeriods = @formattedIndexData.grading_periods.concat(unsavedPeriods[0])
deepEqual @gradingPeriodCollection.state.periods, newPeriods
test 'lastRemainingPeriod returns false if there is more than one period left', ->
deepEqual @gradingPeriodCollection.lastRemainingPeriod(), false
test 'lastRemainingPeriod returns true if there is one period left', ->
onePeriod = [
{
"id":"1", "startDate":"2029-03-01T06:00:00Z", "endDate":"2030-05-31T05:00:00Z",
"weight":null, "title":"A Lonely Grading Period",
"permissions": { "read":true, "manage":true }
}
]
@gradingPeriodCollection.setState({periods: onePeriod})
deepEqual @gradingPeriodCollection.lastRemainingPeriod(), true
test 'createNewGradingPeriod adds a new period', ->
deepEqual @gradingPeriodCollection.state.periods.length, 2
@gradingPeriodCollection.createNewGradingPeriod()
deepEqual @gradingPeriodCollection.state.periods.length, 3
test 'createNewGradingPeriod adds the new period with a blank title, start date, and end date', ->
@gradingPeriodCollection.createNewGradingPeriod()
newPeriod = _.find(@gradingPeriodCollection.state.periods, (p) => p.id.indexOf('new') > -1)
deepEqual newPeriod.title, ''
deepEqual newPeriod.startDate, ''
deepEqual newPeriod.endDate, ''
test 'deleteGradingPeriod does not call confirmDelete if the grading period is not saved', ->
unsavedPeriod = [
{
"id":"new1", "startDate":"2029-03-01T06:00:00Z", "endDate":"2030-05-31T05:00:00Z",
"weight":null, "title":"New Period. I'm not saved yet!",
"permissions": { "read":true, "manage":true }
}
]
@gradingPeriodCollection.setState({periods: unsavedPeriod})
confirmDelete = @sandbox.stub($.fn, 'confirmDelete')
@gradingPeriodCollection.deleteGradingPeriod('new1')
ok confirmDelete.notCalled
test 'deleteGradingPeriod calls confirmDelete if the period being deleted is not new (it is saved server side)', ->
confirmDelete = @sandbox.stub($.fn, 'confirmDelete')
@gradingPeriodCollection.deleteGradingPeriod('1')
ok confirmDelete.calledOnce
test 'updateGradingPeriodCollection correctly updates the periods state', ->
updatedPeriodData = {
"id":"1", "startDate":"2069-03-01T06:00:00Z", "endDate":"2070-05-31T05:00:00Z",
"weight":null, "title":"Updating an existing period!"
}
updatedPermissions = { "read":true, "manage":true }
@gradingPeriodCollection.updateGradingPeriodCollection(updatedPeriodData, updatedPermissions)
updatedPeriod = _.find(@gradingPeriodCollection.state.periods, (p) => p.id == "1")
deepEqual updatedPeriod.title, updatedPeriodData.title
test 'updateGradingPeriodCollection calls getPeriods if a previousStateId is passed in', ->
@sandbox.stub(@gradingPeriodCollection, 'getPeriods')
@gradingPeriodCollection.updateGradingPeriodCollection({}, {}, '1')
ok @gradingPeriodCollection.getPeriods.calledOnce
test 'getPeriodById retuns the period with the matching id (if one exists)', ->
period = @gradingPeriodCollection.getPeriodById('1')
deepEqual period.id, '1'
test 'a link to the settings page is displayed if there are 0 or 1 grading periods on the page', ->
deepEqual @gradingPeriodCollection.refs.linkToSettings, undefined
onePeriod = [
{
"id":"1", "startDate":"2029-03-01T06:00:00Z", "endDate":"2030-05-31T05:00:00Z",
"weight":null, "title":"A Lonely Grading Period",
"permissions": { "read":true, "manage":true }
}
]
@gradingPeriodCollection.setState({periods: onePeriod})
ok @gradingPeriodCollection.refs.linkToSettings
@gradingPeriodCollection.setState({periods: []})
ok @gradingPeriodCollection.refs.linkToSettings
test 'an admin created periods message is NOT displayed since the user has manage permission for the periods', ->
deepEqual @gradingPeriodCollection.refs.adminPeriodsMessage, undefined
module 'GradingPeriodCollection without read or manage permissions for any periods',
setup: ->
@fMessage = $.flashMessage
@fError = $.flashError
@wConfirm = window.confirm
$.flashMessage = ->
$.flashError = ->
window.confirm = -> true
@server = sinon.fakeServer.create()
@sandbox = sinon.sandbox.create()
ENV.current_user_roles = ["teacher"]
ENV.GRADING_PERIODS_URL = "/api/v1/courses/1/grading_periods"
@indexData = "grading_periods":[
{
"id":"1", "start_date":"2015-03-01T06:00:00Z", "end_date":"2015-05-31T05:00:00Z",
"weight":null, "title":"Spring", "permissions": { "read":false, "manage":false }
},
{
"id":"2", "start_date":"2015-06-01T05:00:00Z", "end_date":"2015-08-31T05:00:00Z",
"weight":null, "title":"Summer", "permissions": { "read":false, "manage":false }
}
]
@formattedIndexData = "grading_periods":[
{
"id":"1", "startDate":"2015-03-01T06:00:00Z", "endDate":"2015-05-31T05:00:00Z",
"weight":null, "title":"Spring", "permissions": { "read":false, "manage":false }
},
{
"id":"2", "startDate":"2015-06-01T05:00:00Z", "endDate":"2015-08-31T05:00:00Z",
"weight":null, "title":"Summer", "permissions": { "read":false, "manage":false }
}
]
@createdPeriodData = "grading_periods":[
{
"id":"3", "start_date":"2015-04-20T05:00:00Z", "end_date":"2015-04-21T05:00:00Z",
"weight":null, "title":"New Period!", "permissions": { "read":true, "manage":true }
}
]
@server.respondWith "GET", ENV.GRADING_PERIODS_URL, [200, {"Content-Type":"application/json"}, JSON.stringify @indexData]
@server.respondWith "POST", ENV.GRADING_PERIODS_URL, [200, {"Content-Type":"application/json"}, JSON.stringify @createdPeriodData ]
@server.respondWith "DELETE", ENV.GRADING_PERIODS_URL + "/1", [204, {}, ""]
@gradingPeriodCollection = TestUtils.renderIntoDocument(GradingPeriodCollection())
@server.respond()
teardown: ->
React.unmountComponentAtNode(@gradingPeriodCollection.getDOMNode().parentNode)
ENV.current_user_roles = null
ENV.GRADING_PERIODS_URL = null
$.flashMessage = @fMessage
$.flashError = @fError
window.confirm = @wConfirm
@server.restore()
@sandbox.restore()
test 'gets the grading periods from the grading periods controller', ->
deepEqual @gradingPeriodCollection.state.periods, @formattedIndexData.grading_periods
test 'canManageAtLeastOnePeriod returns false', ->
periods = @gradingPeriodCollection.state.periods
deepEqual @gradingPeriodCollection.canManageAtLeastOnePeriod(periods), false
test 'copyTemplatePeriods sets the disabled state to true while periods are being copied', ->
@sandbox.stub(@gradingPeriodCollection, 'getPeriods')
deepEqual @gradingPeriodCollection.state.disabled, false
@gradingPeriodCollection.copyTemplatePeriods(@gradingPeriodCollection.state.periods)
@server.respond()
deepEqual @gradingPeriodCollection.state.disabled, true
test 'copyTemplatePeriods calls getPeriods', ->
@sandbox.stub(@gradingPeriodCollection, 'getPeriods')
@gradingPeriodCollection.copyTemplatePeriods(@gradingPeriodCollection.state.periods)
@server.respond()
ok @gradingPeriodCollection.getPeriods.calledOnce
test 'deleteGradingPeriod calls copyTemplatePeriods if periods need to be copied (cannot manage any periods and there is at least 1)', ->
copyPeriods = @sandbox.stub(@gradingPeriodCollection, 'copyTemplatePeriods')
@gradingPeriodCollection.deleteGradingPeriod('1')
ok copyPeriods.calledOnce
test 'updateGradingPeriodCollection calls copyTemplatePeriods if periods need to be copied (cannot manage any periods and there is at least 1)', ->
copyPeriods = @sandbox.stub(@gradingPeriodCollection, 'copyTemplatePeriods')
@gradingPeriodCollection.updateGradingPeriodCollection({}, {}, '1')
ok copyPeriods.calledOnce
test 'cannotDeleteLastPeriod returns true if there is one period left and manage permission is false', ->
onePeriod = [
{
"id":"1", "startDate":"2029-03-01T06:00:00Z", "endDate":"2030-05-31T05:00:00Z",
"weight":null, "title":"A Lonely Grading Period without manage permission",
"permissions": { "read":true, "manage":false }
}
]
@gradingPeriodCollection.setState({periods: onePeriod})
deepEqual @gradingPeriodCollection.cannotDeleteLastPeriod(), true
test 'an admin created periods message is displayed since the user does not have manage permission for the periods', ->
ok @gradingPeriodCollection.refs.adminPeriodsMessage

View File

@ -0,0 +1,268 @@
define [
'react'
'jquery'
'underscore'
'jsx/grading/gradingPeriod'
'jquery.instructure_misc_plugins'
], (React, $, _, GradingPeriod) ->
TestUtils = React.addons.TestUtils
Simulate = TestUtils.Simulate
module 'GradingPeriod',
setup: ->
@fMessage = $.flashMessage
@fError = $.flashError
@hErrors = $.fn.hideErrors
@eBox = $.fn.errorBox
$.flashMessage = ->
$.flashError = ->
$.fn.hideErrors = ->
$.fn.errorBox = ->
@server = sinon.fakeServer.create()
@sandbox = sinon.sandbox.create()
ENV.GRADING_PERIODS_URL = "api/v1/courses/1/grading_periods"
@createdPeriodData = "grading_periods":[
{
"id":"3", "start_date":"2015-04-20T05:00:00Z", "end_date":"2015-04-21T05:00:00Z",
"weight":null, "title":"New Period!", "permissions": { "read":true, "manage":true }
}
]
@updatedPeriodData = "grading_periods":[
{
"id":"1", "startDate":"2015-03-01T06:00:00Z", "endDate":"2015-05-31T05:00:00Z",
"weight":null, "title":"Updated Grading Period!", "permissions": { "read":true, "manage":true }
}
]
@server.respondWith "POST", ENV.GRADING_PERIODS_URL, [200, {"Content-Type":"application/json"}, JSON.stringify @createdPeriodData]
@server.respondWith "PUT", ENV.GRADING_PERIODS_URL + "/1", [200, {"Content-Type":"application/json"}, JSON.stringify @updatedPeriodData]
@props =
id: "1"
title: "Spring"
startDate: "2015-03-01T00:00:00Z"
endDate: "2015-05-31T00:00:00Z"
weight: null
disabled: false
permissions:
read: true
manage: true
cannotDelete: -> false
onDeleteGradingPeriod: ->
updateGradingPeriodCollection: ->
@gradingPeriod = TestUtils.renderIntoDocument(GradingPeriod(@props))
@server.respond()
teardown: ->
React.unmountComponentAtNode(@gradingPeriod.getDOMNode().parentNode)
ENV.GRADING_PERIODS_URL = null
$.flashMessage = @fMessage
$.flashError = @fError
$.fn.hideErrors = @hErrors
$.fn.errorBox = @eBox
@server.restore()
@sandbox.restore()
test 'sets initial state properly', ->
deepEqual @gradingPeriod.state.id, @props.id
deepEqual @gradingPeriod.state.title, @props.title
deepEqual @gradingPeriod.state.startDate, $.datetime.process(@props.startDate)
deepEqual @gradingPeriod.state.endDate, $.datetime.process(@props.endDate)
deepEqual @gradingPeriod.state.weight, @props.weight
deepEqual @gradingPeriod.state.permissions, @props.permissions
deepEqual @gradingPeriod.state.shouldUpdateBeDisabled, true
test 'handleDateChange changes the state of the respective date passed in', ->
fakeEvent = { target: { name: "startDate", value: "Feb 20, 2015 2:55 am" } }
@gradingPeriod.handleDateChange(fakeEvent)
deepEqual @gradingPeriod.state.startDate, $.datetime.process("Feb 20, 2015 2:55 am")
test 'handleDateChange calls replaceInputWithDate', ->
fakeEvent = { target: { name: "startDate", value: "Feb 20, 2015 2:55 am" } }
replaceInputWithDate = @sandbox.stub(@gradingPeriod, 'replaceInputWithDate')
@gradingPeriod.handleDateChange(fakeEvent)
ok replaceInputWithDate.calledOnce
test 'handleDateChange calls checkFormForUpdates', ->
fakeEvent = { target: { name: "startDate", value: "Feb 20, 2015 2:55 am" } }
checkForm = @sandbox.stub(@gradingPeriod, 'checkFormForUpdates')
@gradingPeriod.handleDateChange(fakeEvent)
ok checkForm.calledOnce
test 'handleDateChange calls updateGradingPeriodCollection', ->
fakeEvent = { target: { name: "startDate", value: "Feb 20, 2015 2:55 am" } }
update = @sandbox.stub(@gradingPeriod.props, 'updateGradingPeriodCollection')
@gradingPeriod.handleDateChange(fakeEvent)
ok update.calledOnce
test 'formatDataForSubmission returns the title, startDate, and endDate with keys snake cased', ->
expectedOutput =
title: @gradingPeriod.state.title
start_date: @gradingPeriod.state.startDate
end_date: @gradingPeriod.state.endDate
deepEqual @gradingPeriod.formatDataForSubmission(), expectedOutput
test 'isStartDateBeforeEndDate returns true if the start date is before the end date', ->
deepEqual @gradingPeriod.isStartDateBeforeEndDate(), true
test 'isStartDateBeforeEndDate returns false if the start date is equal to the end date', ->
@gradingPeriod.setState({startDate: @gradingPeriod.state.endDate})
deepEqual @gradingPeriod.isStartDateBeforeEndDate(), false
test 'isStartDateBeforeEndDate returns false if the start date is after the end date', ->
startDate = $.datetime.process("2015-06-01T00:00:00Z")
@gradingPeriod.setState({startDate: startDate})
deepEqual @gradingPeriod.isStartDateBeforeEndDate(), false
test 'isNewGradingPeriod returns false if the id does not contain "new"', ->
deepEqual @gradingPeriod.isNewGradingPeriod(), false
test 'isNewGradingPeriod returns true if the id contains "new"', ->
@gradingPeriod.setState({id: "new1"})
deepEqual @gradingPeriod.isNewGradingPeriod(), true
test 'checkFormForUpdates sets shouldUpdateBeDisabled to false if the form is complete and inputs have changed', ->
@sandbox.stub(@gradingPeriod, 'formIsComplete', -> true)
@sandbox.stub(@gradingPeriod, 'inputsHaveChanged', -> true)
deepEqual @gradingPeriod.state.shouldUpdateBeDisabled, true
@gradingPeriod.checkFormForUpdates()
deepEqual @gradingPeriod.state.shouldUpdateBeDisabled, false
test 'checkFormForUpdates sets shouldUpdateBeDisabled to true if the form is not complete', ->
@sandbox.stub(@gradingPeriod, 'formIsComplete', -> false)
@sandbox.stub(@gradingPeriod, 'inputsHaveChanged', -> true)
@gradingPeriod.setState({shouldUpdateBeDisabled: false})
@gradingPeriod.checkFormForUpdates()
deepEqual @gradingPeriod.state.shouldUpdateBeDisabled, true
test 'checkFormForUpdates sets shouldUpdateBeDisabled to true if inputs have not changed', ->
@sandbox.stub(@gradingPeriod, 'formIsComplete', -> true)
@sandbox.stub(@gradingPeriod, 'inputsHaveChanged', -> false)
@gradingPeriod.setState({shouldUpdateBeDisabled: false})
@gradingPeriod.checkFormForUpdates()
deepEqual @gradingPeriod.state.shouldUpdateBeDisabled, true
test 'formIsComplete returns true if title, startDate, and endDate are all non-blank', ->
deepEqual @gradingPeriod.formIsComplete(), true
test 'formIsComplete returns false if the title is blank, or only spaces', ->
@gradingPeriod.setState({title: ""})
deepEqual @gradingPeriod.formIsComplete(), false
@gradingPeriod.setState({title: " "})
deepEqual @gradingPeriod.formIsComplete(), false
test 'formIsComplete returns false if the startDate is blank, or only spaces', ->
@gradingPeriod.setState({startDate: ""})
deepEqual @gradingPeriod.formIsComplete(), false
@gradingPeriod.setState({startDate: " "})
deepEqual @gradingPeriod.formIsComplete(), false
test 'formIsComplete returns false if the endDate is blank, or only spaces', ->
@gradingPeriod.setState({endDate: ""})
deepEqual @gradingPeriod.formIsComplete(), false
@gradingPeriod.setState({endDate: " "})
deepEqual @gradingPeriod.formIsComplete(), false
test 'inputsHaveChanged returns false if the current states of title, startDate, and endDate match the props passed in', ->
deepEqual @gradingPeriod.inputsHaveChanged(), false
test 'inputsHaveChanged returns true if the title state differs from the title prop passed in', ->
@gradingPeriod.setState({title: "AirBud 2"})
deepEqual @gradingPeriod.inputsHaveChanged(), true
test 'inputsHaveChanged returns true if the startDate state differs from the startDate prop passed in', ->
startDate = $.datetime.process("2015-01-01T00:00:00Z")
@gradingPeriod.setState({startDate: startDate})
deepEqual @gradingPeriod.inputsHaveChanged(), true
test 'inputsHaveChanged returns true if the endDate state differs from the endDate prop passed in', ->
endDate = $.datetime.process("2015-10-01T00:00:00Z")
@gradingPeriod.setState({endDate: endDate})
deepEqual @gradingPeriod.inputsHaveChanged(), true
test 'handleTitleChange changes the title state', ->
fakeEvent = { target: { name: "title", value: "MXP: Most Xtreme Primate" } }
@gradingPeriod.handleTitleChange(fakeEvent)
deepEqual @gradingPeriod.state.title, "MXP: Most Xtreme Primate"
test 'handleTitleChange calls checkFormForUpdates', ->
fakeEvent = { target: { name: "title", value: "MXP: Most Xtreme Primate" } }
checkForm = @sandbox.stub(@gradingPeriod, 'checkFormForUpdates')
@gradingPeriod.handleTitleChange(fakeEvent)
ok checkForm.calledOnce
test 'handleTitleChange calls updateGradingPeriodCollection', ->
fakeEvent = { target: { name: "title", value: "MXP: Most Xtreme Primate" } }
update = @sandbox.stub(@gradingPeriod.props, 'updateGradingPeriodCollection')
@gradingPeriod.handleTitleChange(fakeEvent)
ok update.calledOnce
test 'isValidDateInput returns true for a valid date', ->
deepEqual @gradingPeriod.isValidDateInput("2015-01-01T00:00:00Z"), true
deepEqual @gradingPeriod.isValidDateInput("Tomorrow"), true
test 'isValidDateInput returns false for an invalid date', ->
deepEqual @gradingPeriod.isValidDateInput("2015-01-01T00:00:00ZOOPS"), false
deepEqual @gradingPeriod.isValidDateInput("Yesteryear"), false
test 'replaceInputWithDate calls formatDateForDisplay if the date in the input is valid', ->
formatDate = @sandbox.stub(@gradingPeriod, 'formatDateForDisplay')
@gradingPeriod.refs.startDate.getDOMNode().value = 'Today'
@gradingPeriod.replaceInputWithDate(@gradingPeriod.refs.startDate)
@gradingPeriod.refs.startDate.getDOMNode().value = 'January 23, 2015 at 5:15 pm'
@gradingPeriod.replaceInputWithDate(@gradingPeriod.refs.startDate)
ok formatDate.calledTwice
test 'replaceInputWithDate calls formatDateForDisplay if the date in the input is valid', ->
formatDate = @sandbox.stub(@gradingPeriod, 'formatDateForDisplay')
@gradingPeriod.refs.startDate.getDOMNode().value = 'wat'
@gradingPeriod.replaceInputWithDate(@gradingPeriod.refs.startDate)
ok formatDate.notCalled
@gradingPeriod.refs.startDate.getDOMNode().value = 'January 32, 2015 at 5:15 pm'
@gradingPeriod.replaceInputWithDate(@gradingPeriod.refs.startDate)
ok formatDate.notCalled
test 'triggerDeleteGradingPeriod calls onDeleteGradingPeriod', ->
deletePeriod = @sandbox.stub(@gradingPeriod.props, 'onDeleteGradingPeriod')
@gradingPeriod.triggerDeleteGradingPeriod()
ok deletePeriod.calledOnce
test 'saveGradingPeriod makes an AJAX call if the start date is before the end date', ->
@sandbox.stub(@gradingPeriod, 'isStartDateBeforeEndDate', -> true)
ajax = @sandbox.spy($, 'ajax')
@gradingPeriod.saveGradingPeriod()
ok ajax.calledOnce
test 'saveGradingPeriod does not make an AJAX call if the start date is not before the end date', ->
@sandbox.stub(@gradingPeriod, 'isStartDateBeforeEndDate', -> false)
ajax = @sandbox.spy($, 'ajax')
@gradingPeriod.saveGradingPeriod()
ok ajax.notCalled
test 'saveGradingPeriod should re-assign the id if the period is new', ->
@sandbox.stub(@gradingPeriod, 'isStartDateBeforeEndDate', -> true)
@gradingPeriod.setState({id: "new1"})
@gradingPeriod.saveGradingPeriod()
@server.respond()
deepEqual @gradingPeriod.state.id, '3'
test 'saveGradingPeriod should not re-assign the id if the period is not new', ->
@sandbox.stub(@gradingPeriod, 'isStartDateBeforeEndDate', -> true)
idBeforeSaving = @gradingPeriod.state.id
@gradingPeriod.saveGradingPeriod()
@server.respond()
deepEqual @gradingPeriod.state.id, idBeforeSaving
test 'saveGradingPeriod calls updateGradingPeriodCollection for new periods', ->
@sandbox.stub(@gradingPeriod, 'isStartDateBeforeEndDate', -> true)
update = @sandbox.stub(@gradingPeriod.props, 'updateGradingPeriodCollection')
@gradingPeriod.setState({id: "new1"})
@gradingPeriod.saveGradingPeriod()
@server.respond()
ok update.calledOnce
test 'saveGradingPeriod calls updateGradingPeriodCollection for existing periods', ->
@sandbox.stub(@gradingPeriod, 'isStartDateBeforeEndDate', -> true)
update = @sandbox.stub(@gradingPeriod.props, 'updateGradingPeriodCollection')
@gradingPeriod.saveGradingPeriod()
@server.respond()
ok update.calledOnce

View File

@ -18,6 +18,7 @@ define [
@wConfirm = window.confirm
window.confirm = ->
@server = sinon.fakeServer.create()
@sandbox = sinon.sandbox.create()
ENV.current_user_roles = ["admin", "teacher"]
ENV.GRADING_STANDARDS_URL = "/courses/1/grading_standards"
ENV.DEFAULT_GRADING_STANDARD_DATA = [
@ -77,10 +78,11 @@ define [
ENV.current_user_roles = null
ENV.GRADING_STANDARDS_URL = null
ENV.DEFAULT_GRADING_STANDARD_DATA = null
@server.restore()
$.flashMessage = @fMessage
$.flashError = @fError
window.confirm = @wConfirm
@server.restore()
@sandbox.restore()
test 'gets the standards data from the grading standards controller, and multiplies data values by 100 (i.e. .20 becomes 20)', ->
deepEqual @gradingStandardCollection.state.standards, @processedIndexData
@ -103,7 +105,7 @@ define [
test 'does not save the new standard on the backend when the add button is clicked', ->
saveGradingStandard = sinon.spy(@gradingStandardCollection, 'saveGradingStandard')
saveGradingStandard = @sandbox.spy(@gradingStandardCollection, 'saveGradingStandard')
Simulate.click(@gradingStandardCollection.refs.addButton.getDOMNode())
ok saveGradingStandard.notCalled
@ -223,7 +225,7 @@ define [
ok @gradingStandardCollection.refs.noSchemesMessage
test 'deleteGradingStandard calls confirmDelete', ->
confirmDelete = sinon.spy($.fn, "confirmDelete")
confirmDelete = @sandbox.spy($.fn, "confirmDelete")
deleteLink = @gradingStandardCollection.refs.gradingStandard1.refs.deleteLink.getDOMNode()
Simulate.click(deleteLink)
ok confirmDelete.calledOnce