multiple grading periods enrollment term dropdown
adds the enrollment term dropdown to the account grading periods page. selecting an enrollment term from the dropdown will filter the grading period sets to only show those that contain the selected enrollment term. closes CNVS-27108 test plan: - enable multiple grading periods for an account - create at least three grading period sets - create at least three grading periods per grading period set - create at least four enrollment terms belonging to the account. make sure three of them belong to one of the sets above, and one does not belong to any set. ensure a) one of the enrollment terms has a name and a start_at b) one of the enrollment terms is missing a name but has a start_at c) one of the enrollment terms is missing a name and a start_at - go to the account grading periods page at /accounts/:id/grading_standards - verify the enrollment terms are populated in the dropdown. the displayed name should be the name, or "Term starting **date here**" if no name exists, or "Term created **date here**" if no name exists and no start_at exists. verify the enrollment term that does not belong to a set is not displayed. - verify the enrollment terms are ordered from top to bottom by start_at descending (if start_at exists, with all terms having a start_at displayed above all terms without a start_at), and then by created_at descending for terms without a start_at. - verify selecting an enrollment term from the dropdown filters the grading period sets to only inlcude those that are associated with the selected term. - verify the enrollment term names show below the grading period set name (with the correct display name as defined three steps before this one). Change-Id: I920152f1c7a13720ab24e2fae0f4ec811bde5a93 Reviewed-on: https://gerrit.instructure.com/81420 Reviewed-by: Derek Bender <djbender@instructure.com> Tested-by: Jenkins Reviewed-by: Keith T. Garner <kgarner@instructure.com> QA-Review: KC Naegle <knaegle@instructure.com> Product-Review: Keith T. Garner <kgarner@instructure.com>
This commit is contained in:
parent
33c669bc8e
commit
585511c860
|
@ -6,14 +6,15 @@ require [
|
|||
mgpEnabled = ENV.MULTIPLE_GRADING_PERIODS
|
||||
readOnly = ENV.GRADING_PERIODS_READ_ONLY
|
||||
urls =
|
||||
gradingPeriodSetsURL: ENV.GRADING_PERIOD_SETS_URL,
|
||||
gradingPeriodSetsURL: ENV.GRADING_PERIOD_SETS_URL
|
||||
gradingPeriodsUpdateURL: ENV.GRADING_PERIODS_UPDATE_URL
|
||||
enrollmentTermsURL: ENV.ENROLLMENT_TERMS_URL
|
||||
|
||||
React.render(
|
||||
TabContainerFactory(
|
||||
multipleGradingPeriodsEnabled: mgpEnabled
|
||||
readOnly: readOnly
|
||||
URLs: urls
|
||||
urls: urls
|
||||
),
|
||||
document.getElementById("react_grading_tabs")
|
||||
)
|
||||
|
|
|
@ -26,6 +26,7 @@ class GradingStandardsController < ApplicationController
|
|||
client_env = {
|
||||
:GRADING_STANDARDS_URL => context_url(@context, :context_grading_standards_url),
|
||||
:GRADING_PERIOD_SETS_URL => api_v1_account_grading_period_sets_url(@context),
|
||||
:ENROLLMENT_TERMS_URL => api_v1_enrollment_terms_url(@context),
|
||||
:MULTIPLE_GRADING_PERIODS => multiple_grading_periods?,
|
||||
:DEFAULT_GRADING_STANDARD_DATA => GradingStandard.default_grading_standard,
|
||||
:CONTEXT_SETTINGS_URL => context_url(@context, :context_settings_url)
|
||||
|
|
|
@ -13,10 +13,14 @@ define([
|
|||
return object;
|
||||
},
|
||||
|
||||
formatDateForDisplay: function(date) {
|
||||
formatDatetimeForDisplay: function(date) {
|
||||
return $.datetimeString(date, { format: 'medium', timezone: ENV.CONTEXT_TIMEZONE });
|
||||
},
|
||||
|
||||
formatDateForDisplay: function(date) {
|
||||
return $.dateString(date, { format: 'medium', timezone: ENV.CONTEXT_TIMEZONE });
|
||||
},
|
||||
|
||||
isMidnight: function(date) {
|
||||
return tz.isMidnight(date, { timezone: ENV.CONTEXT_TIMEZONE });
|
||||
}
|
||||
|
|
|
@ -24,10 +24,10 @@ define([
|
|||
<span tabIndex="0">{this.props.period.title}</span>
|
||||
</div>
|
||||
<div className="GradingPeriodList__period__attribute col-xs-12 col-md-8 col-lg-4">
|
||||
<span tabIndex="0" ref="startDate">{I18n.t("Start Date:")} {DatesHelper.formatDateForDisplay(this.props.period.startDate)}</span>
|
||||
<span tabIndex="0" ref="startDate">{I18n.t("Start Date:")} {DatesHelper.formatDatetimeForDisplay(this.props.period.startDate)}</span>
|
||||
</div>
|
||||
<div className="GradingPeriodList__period__attribute col-xs-12 col-md-8 col-lg-4">
|
||||
<span tabIndex="0" ref="endDate">{I18n.t("End Date:")} {DatesHelper.formatDateForDisplay(this.props.period.endDate)}</span>
|
||||
<span tabIndex="0" ref="endDate">{I18n.t("End Date:")} {DatesHelper.formatDatetimeForDisplay(this.props.period.endDate)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="GradingPeriodList__period__actions">
|
||||
|
|
|
@ -11,9 +11,10 @@ define([
|
|||
propTypes: {
|
||||
multipleGradingPeriodsEnabled: types.bool.isRequired,
|
||||
readOnly: types.bool.isRequired,
|
||||
URLs: types.shape({
|
||||
urls: types.shape({
|
||||
gradingPeriodSetsURL: types.string.isRequired,
|
||||
gradingPeriodsUpdateURL: types.string.isRequired
|
||||
gradingPeriodsUpdateURL: types.string.isRequired,
|
||||
enrollmentTermsURL: types.string.isRequired
|
||||
}).isRequired
|
||||
},
|
||||
|
||||
|
@ -31,7 +32,7 @@ define([
|
|||
<li><a href="#grading-standards-tab" className="grading_standards_tab"> {I18n.t('Grading Schemes')}</a></li>
|
||||
</ul>
|
||||
<div ref="gradingPeriods" id="grading-periods-tab">
|
||||
<GradingPeriodSetCollection URLs={this.props.URLs}
|
||||
<GradingPeriodSetCollection urls={this.props.urls}
|
||||
readOnly={this.props.readOnly} />
|
||||
</div>
|
||||
<div ref="gradingStandards" id="grading-standards-tab">
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
define([
|
||||
'react',
|
||||
'underscore',
|
||||
'i18n!grading_periods',
|
||||
], function(React, _, I18n) {
|
||||
|
||||
let EnrollmentTermsDropdown = React.createClass({
|
||||
propTypes: {
|
||||
terms: React.PropTypes.array.isRequired,
|
||||
changeSelectedEnrollmentTerm: React.PropTypes.func.isRequired
|
||||
},
|
||||
|
||||
termsBelongingToSets(terms) {
|
||||
return _.select(terms, term => term.gradingPeriodGroupId);
|
||||
},
|
||||
|
||||
sortedTerms(terms) {
|
||||
const dated = _.select(terms, term => term.startAt);
|
||||
const datedTermsSortedByStart = _.sortBy(dated, term => term.startAt).reverse();
|
||||
|
||||
const undated = _.select(terms, term => !term.startAt);
|
||||
const undatedTermsSortedByCreate = _.sortBy(undated, term => term.createdAt).reverse();
|
||||
return datedTermsSortedByStart.concat(undatedTermsSortedByCreate);
|
||||
},
|
||||
|
||||
termOptions(terms) {
|
||||
const allTermsOption = (<option key={0} value={0}>{I18n.t("All Terms")}</option>);
|
||||
const termsWithSets = this.termsBelongingToSets(terms);
|
||||
let options = _.map(this.sortedTerms(termsWithSets), function(term) {
|
||||
return (<option key={term.id} value={term.id}>{term.displayName}</option>);
|
||||
});
|
||||
|
||||
options.unshift(allTermsOption);
|
||||
return options;
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<select
|
||||
className="EnrollmentTerms__dropdown"
|
||||
name="enrollment_term"
|
||||
data-view="termSelect"
|
||||
aria-label="Enrollment Term"
|
||||
ref="termsDropdown"
|
||||
onChange={this.props.changeSelectedEnrollmentTerm} >
|
||||
{this.termOptions(this.props.terms)}
|
||||
</select>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return EnrollmentTermsDropdown;
|
||||
});
|
|
@ -65,6 +65,7 @@ define([
|
|||
title: types.string
|
||||
}).isRequired,
|
||||
gradingPeriods: types.array.isRequired,
|
||||
terms: types.array.isRequired,
|
||||
urls: types.shape({
|
||||
batchUpdateUrl: types.string.isRequired
|
||||
}).isRequired,
|
||||
|
@ -104,6 +105,17 @@ define([
|
|||
e.stopPropagation();
|
||||
},
|
||||
|
||||
setTerms() {
|
||||
return _.filter(this.props.terms, (term) => {
|
||||
return term.gradingPeriodGroupId === parseInt(this.props.set.id);
|
||||
});
|
||||
},
|
||||
|
||||
termNames() {
|
||||
const names = _.pluck(this.setTerms(), "displayName");
|
||||
return I18n.t("Terms: ") + names.join(", ");
|
||||
},
|
||||
|
||||
editSet(e) {
|
||||
e.stopPropagation();
|
||||
},
|
||||
|
@ -130,6 +142,8 @@ define([
|
|||
},
|
||||
|
||||
renderSetBody() {
|
||||
if(!this.state.expanded) return null;
|
||||
|
||||
return (
|
||||
<div ref="setBody" className="ig-body">
|
||||
<div className="GradingPeriodList" ref="gradingPeriodList">
|
||||
|
@ -156,20 +170,25 @@ define([
|
|||
<div className="ItemGroup__header"
|
||||
ref="toggleSetBody"
|
||||
onClick={this.toggleSetBody}>
|
||||
<div className="ItemGroup__header__title">
|
||||
<button className={"Button Button--icon-action GradingPeriodSet__toggle"}
|
||||
aria-expanded={this.state.expanded}
|
||||
aria-label="Toggle grading period visibility">
|
||||
<i className={"icon-mini-arrow-" + arrow}/>
|
||||
</button>
|
||||
<span className="screenreader-only">{I18n.t("Grading period title")}</span>
|
||||
<h2 tabIndex="0" className="GradingPeriodSet__title">
|
||||
{this.props.set.title}
|
||||
</h2>
|
||||
<div>
|
||||
<div className="ItemGroup__header__title">
|
||||
<button className={"Button Button--icon-action GradingPeriodSet__toggle"}
|
||||
aria-expanded={this.state.expanded}
|
||||
aria-label="Toggle grading period visibility">
|
||||
<i className={"icon-mini-arrow-" + arrow}/>
|
||||
</button>
|
||||
<span className="screenreader-only">{I18n.t("Grading period title")}</span>
|
||||
<h2 tabIndex="0" className="GradingPeriodSet__title">
|
||||
{this.props.set.title}
|
||||
</h2>
|
||||
</div>
|
||||
{this.renderEditAndDeleteIcons()}
|
||||
</div>
|
||||
<div className="EnrollmentTerms__list" tabIndex="0">
|
||||
{this.termNames()}
|
||||
</div>
|
||||
{this.renderEditAndDeleteIcons()}
|
||||
</div>
|
||||
{this.state.expanded && this.renderSetBody()}
|
||||
{this.renderSetBody()}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -7,8 +7,11 @@ define([
|
|||
'convert_case',
|
||||
'jsx/grading/GradingPeriodSet',
|
||||
'jsx/grading/SearchGradingPeriodsField',
|
||||
'jsx/shared/helpers/searchHelpers'
|
||||
], function(React, _, $, axios, I18n, ConvertCase, GradingPeriodSet, SearchGradingPeriodsField, SearchHelpers) {
|
||||
'jsx/grading/EnrollmentTermsDropdown',
|
||||
'jsx/shared/helpers/searchHelpers',
|
||||
'jsx/gradebook/grid/helpers/datesHelper'
|
||||
], function(React, _, $, axios, I18n, ConvertCase, GradingPeriodSet, SearchGradingPeriodsField, EnrollmentTermsDropdown, SearchHelpers, DatesHelper) {
|
||||
|
||||
const deserializeSets = function(sets) {
|
||||
return _.map(sets, function(set) {
|
||||
let newSet = ConvertCase.camelize(set);
|
||||
|
@ -28,31 +31,55 @@ define([
|
|||
});
|
||||
};
|
||||
|
||||
const deserializeEnrollmentTerms = function(enrollmentTerms) {
|
||||
return _.map(enrollmentTerms, term => {
|
||||
let newTerm = ConvertCase.camelize(term);
|
||||
if(term.start_at) newTerm.startAt = new Date(term.start_at);
|
||||
if(term.end_at) newTerm.endAt = new Date(term.end_at);
|
||||
if(term.created_at) newTerm.createdAt = new Date(term.created_at);
|
||||
|
||||
if(newTerm.name) {
|
||||
newTerm.displayName = newTerm.name;
|
||||
} else if(_.isDate(newTerm.startAt)) {
|
||||
let started = DatesHelper.formatDateForDisplay(newTerm.startAt);
|
||||
newTerm.displayName = I18n.t("Term starting ") + started;
|
||||
} else {
|
||||
let created = DatesHelper.formatDateForDisplay(newTerm.createdAt);
|
||||
newTerm.displayName = I18n.t("Term created ") + created;
|
||||
}
|
||||
|
||||
return newTerm;
|
||||
});
|
||||
};
|
||||
|
||||
const types = React.PropTypes;
|
||||
|
||||
let GradingPeriodSetCollection = React.createClass({
|
||||
propTypes: {
|
||||
readOnly: types.bool.isRequired,
|
||||
URLs: types.shape({
|
||||
urls: types.shape({
|
||||
gradingPeriodSetsURL: types.string.isRequired,
|
||||
gradingPeriodsUpdateURL: types.string.isRequired
|
||||
gradingPeriodsUpdateURL: types.string.isRequired,
|
||||
enrollmentTermsURL: types.string.isRequired
|
||||
}).isRequired
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
getInitialState() {
|
||||
return {
|
||||
enrollmentTerms: [],
|
||||
sets: [],
|
||||
showNewSetForm: false,
|
||||
searchText: ""
|
||||
searchText: "",
|
||||
selectedTermID: 0
|
||||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
componentWillMount() {
|
||||
this.getTerms();
|
||||
this.getSets();
|
||||
},
|
||||
|
||||
getSets: function() {
|
||||
axios.get(this.props.URLs.gradingPeriodSetsURL)
|
||||
getSets() {
|
||||
axios.get(this.props.urls.gradingPeriodSetsURL)
|
||||
.then((response) => {
|
||||
this.setState({
|
||||
sets: deserializeSets(response.data.grading_period_sets)
|
||||
|
@ -63,17 +90,16 @@ define([
|
|||
});
|
||||
},
|
||||
|
||||
renderNewGradingPeriodSetForm: function() {
|
||||
if(!this.state.showNewSetForm) return null;
|
||||
|
||||
return (
|
||||
<NewGradingPeriodSetForm
|
||||
ref="newSetForm"
|
||||
closeForm={this.closeNewSetForm}
|
||||
URLs={this.props.URLs}
|
||||
/>
|
||||
);
|
||||
},
|
||||
getTerms() {
|
||||
axios.get(this.props.urls.enrollmentTermsURL)
|
||||
.then((response) => {
|
||||
const enrollmentTerms = deserializeEnrollmentTerms(response.data.enrollment_terms);
|
||||
this.setState({ enrollmentTerms: enrollmentTerms });
|
||||
})
|
||||
.catch(function (response) {
|
||||
$.flashError(I18n.t("An error occured while fetching enrollment terms."));
|
||||
});
|
||||
},
|
||||
|
||||
setAndGradingPeriodTitles(set) {
|
||||
let titles = _.pluck(set.gradingPeriods, 'title');
|
||||
|
@ -87,24 +113,42 @@ define([
|
|||
});
|
||||
},
|
||||
|
||||
filterSetsBySearchText: function() {
|
||||
if (this.state.searchText === "") return this.state.sets;
|
||||
filterSetsBySearchText(sets, searchText) {
|
||||
if (searchText === "") return sets;
|
||||
|
||||
return _.filter(this.state.sets, (set) => {
|
||||
return _.filter(sets, (set) => {
|
||||
let titles = this.setAndGradingPeriodTitles(set);
|
||||
return this.searchTextMatchesTitles(titles);
|
||||
});
|
||||
},
|
||||
|
||||
changeSearchText: function(searchText) {
|
||||
changeSearchText(searchText) {
|
||||
if (searchText !== this.state.searchText) {
|
||||
this.setState({ searchText: searchText });
|
||||
}
|
||||
},
|
||||
|
||||
renderSets: function() {
|
||||
let urls = { batchUpdateUrl: this.props.URLs.gradingPeriodsUpdateURL };
|
||||
let visibleSets = this.filterSetsBySearchText();
|
||||
filterSetsByActiveTerm(sets, terms, selectedTermID) {
|
||||
if (selectedTermID === 0) return sets;
|
||||
|
||||
const activeTerm = _.findWhere(terms, { id: selectedTermID });
|
||||
const setID = activeTerm.gradingPeriodGroupId;
|
||||
return _.where(sets, { id: setID.toString() });
|
||||
},
|
||||
|
||||
changeSelectedEnrollmentTerm(event) {
|
||||
this.setState({ selectedTermID: parseInt(event.target.value) });
|
||||
},
|
||||
|
||||
getVisibleSets() {
|
||||
let setsFilteredBySearchText = this.filterSetsBySearchText(this.state.sets, this.state.searchText);
|
||||
let filterByTermArgs = [setsFilteredBySearchText, this.state.enrollmentTerms, this.state.selectedTermID];
|
||||
return this.filterSetsByActiveTerm(...filterByTermArgs);
|
||||
},
|
||||
|
||||
renderSets() {
|
||||
const urls = { batchUpdateUrl: this.props.urls.gradingPeriodsUpdateURL };
|
||||
let visibleSets = this.getVisibleSets();
|
||||
return _.map(visibleSets, set => {
|
||||
return (
|
||||
<GradingPeriodSet key={set.id}
|
||||
|
@ -112,18 +156,20 @@ define([
|
|||
gradingPeriods={set.gradingPeriods}
|
||||
urls={urls}
|
||||
readOnly={this.props.readOnly}
|
||||
permissions={set.permissions} />
|
||||
permissions={set.permissions}
|
||||
terms={this.state.enrollmentTerms} />
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<div className="GradingPeriodSets__toolbar header-bar no-line">
|
||||
<EnrollmentTermsDropdown
|
||||
terms={this.state.enrollmentTerms}
|
||||
changeSelectedEnrollmentTerm={this.changeSelectedEnrollmentTerm} />
|
||||
<SearchGradingPeriodsField changeSearchText={this.changeSearchText} />
|
||||
<div className="header-bar-right">
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{this.renderSets()}
|
||||
|
|
|
@ -71,7 +71,7 @@ define([
|
|||
|
||||
replaceInputWithDate: function(dateType, dateElement) {
|
||||
var date = this.state[dateType];
|
||||
dateElement.val(DatesHelper.formatDateForDisplay(date));
|
||||
dateElement.val(DatesHelper.formatDatetimeForDisplay(date));
|
||||
},
|
||||
|
||||
render: function () {
|
||||
|
|
|
@ -112,13 +112,13 @@ define([
|
|||
ref="startDate"
|
||||
name="startDate"
|
||||
className="input-grading-period-date date_field"
|
||||
defaultValue={DatesHelper.formatDateForDisplay(this.props.startDate)}
|
||||
defaultValue={DatesHelper.formatDatetimeForDisplay(this.props.startDate)}
|
||||
disabled={this.props.disabled}/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div id={this.addIdToText("period_start_date_")} ref="startDate">
|
||||
{DatesHelper.formatDateForDisplay(this.props.startDate)}
|
||||
{DatesHelper.formatDatetimeForDisplay(this.props.startDate)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -131,13 +131,13 @@ define([
|
|||
className="input-grading-period-date date_field"
|
||||
ref="endDate"
|
||||
name="endDate"
|
||||
defaultValue={DatesHelper.formatDateForDisplay(this.props.endDate)}
|
||||
defaultValue={DatesHelper.formatDatetimeForDisplay(this.props.endDate)}
|
||||
disabled={this.props.disabled}/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div id={this.addIdToText("period_end_date_")} ref="endDate">
|
||||
{DatesHelper.formatDateForDisplay(this.props.endDate)}
|
||||
{DatesHelper.formatDatetimeForDisplay(this.props.endDate)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
@import "base/environment";
|
||||
|
||||
.EnrollmentTerms__dropdown {
|
||||
background-color: $lightBackground;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.EnrollmentTerms__list {
|
||||
margin-left: 24px;
|
||||
@include fontSize($ic-font-size--xsmall);
|
||||
}
|
|
@ -3,12 +3,23 @@
|
|||
.ItemGroup__header {
|
||||
background-color: $lightBackground;
|
||||
border: 1px solid $ic-border-light;
|
||||
display: flex;
|
||||
min-height: 30px;
|
||||
padding: $ic-sp;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ItemGroup__header__toggle {
|
||||
display: inline-block;
|
||||
flex: 0 0 auto;
|
||||
margin: auto;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.ItemGroup__header__details {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.ItemGroup__header__title {
|
||||
display: inline-block;
|
||||
flex: 1 1 auto;
|
||||
|
@ -20,7 +31,8 @@
|
|||
|
||||
.ItemGroup__header__admin {
|
||||
align-self: center;
|
||||
display: flex;
|
||||
float: right;
|
||||
display: inline-block;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
|
@ -43,6 +55,9 @@
|
|||
|
||||
.GradingPeriodSearchField {
|
||||
width: 250px;
|
||||
display: inline-block;
|
||||
padding-left: 12px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
// GRADING PERIOD SET
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<% js_bundle :account_grading_standards %>
|
||||
<% css_bundle :grading_standards, :grading_period_sets %>
|
||||
<% css_bundle :grading_standards, :grading_period_sets, :enrollment_terms %>
|
||||
<% content_for :page_title, t(:title, "Grading Standards") %>
|
||||
|
||||
<div id="react_grading_tabs"></div>
|
||||
|
|
|
@ -2,7 +2,7 @@ module Api::V1::EnrollmentTerm
|
|||
include Api::V1::Json
|
||||
|
||||
def enrollment_term_json(enrollment_term, user, session, enrollments=[], includes=[])
|
||||
api_json(enrollment_term, user, session, :only => %w(id name start_at end_at workflow_state)).tap do |hash|
|
||||
api_json(enrollment_term, user, session, :only => %w(id name start_at end_at workflow_state grading_period_group_id created_at)).tap do |hash|
|
||||
hash['sis_term_id'] = enrollment_term.sis_source_id if enrollment_term.root_account.grants_any_right?(user, :read_sis, :manage_sis)
|
||||
hash['start_at'], hash['end_at'] = enrollment_term.overridden_term_dates(enrollments) if enrollments.present?
|
||||
end
|
||||
|
|
|
@ -77,8 +77,8 @@ define [
|
|||
@gradingPeriod.onTitleChange(fakeEvent)
|
||||
ok @gradingPeriod.props.updateGradingPeriodCollection.calledOnce
|
||||
|
||||
test 'replaceInputWithDate calls formatDateForDisplay', ->
|
||||
formatDate = @stub(DatesHelper, 'formatDateForDisplay')
|
||||
test 'replaceInputWithDate calls formatDatetimeForDisplay', ->
|
||||
formatDatetime = @stub(DatesHelper, 'formatDatetimeForDisplay')
|
||||
fakeDateElement = { val: -> }
|
||||
@gradingPeriod.replaceInputWithDate("startDate", fakeDateElement)
|
||||
ok formatDate.calledOnce
|
||||
ok formatDatetime.calledOnce
|
||||
|
|
|
@ -42,7 +42,7 @@ define [
|
|||
ok _.isDate(assignment.created_at)
|
||||
ok _.isUndefined(assignment.undefined_due_at)
|
||||
|
||||
module 'DatesHelper#formatDateForDisplay',
|
||||
module 'DatesHelper#formatDatetimeForDisplay',
|
||||
setup: ->
|
||||
@snapshot = tz.snapshot()
|
||||
teardown: ->
|
||||
|
@ -51,13 +51,29 @@ define [
|
|||
test 'formats the date for display, adjusted for the timezone', ->
|
||||
assignment = defaultAssignment()
|
||||
tz.changeZone(detroit, 'America/Detroit')
|
||||
formattedDate = DatesHelper.formatDateForDisplay(assignment.due_at)
|
||||
formattedDate = DatesHelper.formatDatetimeForDisplay(assignment.due_at)
|
||||
equal formattedDate, "Jul 14, 2015 at 2:35pm"
|
||||
|
||||
tz.changeZone(juneau, 'America/Juneau')
|
||||
formattedDate = DatesHelper.formatDateForDisplay(assignment.due_at)
|
||||
formattedDate = DatesHelper.formatDatetimeForDisplay(assignment.due_at)
|
||||
equal formattedDate, "Jul 14, 2015 at 10:35am"
|
||||
|
||||
module 'DatesHelper#formatDateForDisplay',
|
||||
setup: ->
|
||||
@snapshot = tz.snapshot()
|
||||
teardown: ->
|
||||
tz.restore(@snapshot)
|
||||
|
||||
test 'formats the date for display, adjusted for the timezone, excluding the time', ->
|
||||
assignment = defaultAssignment()
|
||||
tz.changeZone(detroit, 'America/Detroit')
|
||||
formattedDate = DatesHelper.formatDateForDisplay(assignment.due_at)
|
||||
equal formattedDate, "Jul 14, 2015"
|
||||
|
||||
tz.changeZone(juneau, 'America/Juneau')
|
||||
formattedDate = DatesHelper.formatDateForDisplay(assignment.due_at)
|
||||
equal formattedDate, "Jul 14, 2015"
|
||||
|
||||
module 'DatesHelper#isMidnight',
|
||||
setup: ->
|
||||
@snapshot = tz.snapshot()
|
||||
|
|
|
@ -10,7 +10,7 @@ define([
|
|||
renderComponent: function(props={}) {
|
||||
const defaults = {
|
||||
readOnly: false,
|
||||
URLs: {
|
||||
urls: {
|
||||
gradingPeriodSetsURL: "api/v1/accounts/1/grading_period_sets",
|
||||
gradingPeriodsUpdateURL: "api/v1/grading_period_sets/{{ set_id }}/grading_periods/batch_update",
|
||||
enrollmentTermsURL: "api/v1/accounts/1/enrollment_terms"
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
define([
|
||||
'react',
|
||||
'underscore',
|
||||
'jsx/grading/EnrollmentTermsDropdown'
|
||||
], (React, _, Dropdown) => {
|
||||
const wrapper = document.getElementById('fixtures');
|
||||
const Simulate = React.addons.TestUtils.Simulate;
|
||||
|
||||
module('EnrollmentTermsDropdown', {
|
||||
renderComponent() {
|
||||
const props = {
|
||||
terms: this.terms(),
|
||||
changeSelectedEnrollmentTerm: this.spy()
|
||||
};
|
||||
const element = React.createElement(Dropdown, props);
|
||||
return React.render(element, wrapper);
|
||||
},
|
||||
|
||||
terms() {
|
||||
return [
|
||||
{
|
||||
id: 18,
|
||||
name: "Fall 2013 - Art",
|
||||
startAt: new Date("2013-08-03T02:57:42.000Z"),
|
||||
endAt: new Date("2013-11-03T02:57:53.000Z"),
|
||||
createdAt: new Date("2013-07-27T16:51:41.000Z"),
|
||||
gradingPeriodGroupId: 3,
|
||||
displayName: "Fall 2013 - Art"
|
||||
},
|
||||
{
|
||||
id: 21,
|
||||
name: "Winter 2013 - Art",
|
||||
startAt: new Date("2013-12-03T02:57:42.000Z"),
|
||||
endAt: new Date("2014-01-21T02:57:53.000Z"),
|
||||
createdAt: new Date("2013-08-27T16:51:41.000Z"),
|
||||
gradingPeriodGroupId: 3,
|
||||
displayName: "Winter 2013 - Art"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: null,
|
||||
startAt: null,
|
||||
endAt: new Date("2013-10-21T02:57:53.000Z"),
|
||||
createdAt: new Date("2013-08-22T16:51:41.000Z"),
|
||||
gradingPeriodGroupId: 2,
|
||||
displayName: "Term starting Sep 3, 2013"
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: null,
|
||||
startAt: null,
|
||||
endAt: null,
|
||||
createdAt: new Date("2013-08-23T16:51:41.000Z"),
|
||||
gradingPeriodGroupId: 2,
|
||||
displayName: "Term created Aug 23, 2013"
|
||||
},
|
||||
{
|
||||
id: 22,
|
||||
name: null,
|
||||
startAt: null,
|
||||
endAt: null,
|
||||
createdAt: new Date("2013-08-23T16:51:41.000Z"),
|
||||
gradingPeriodGroupId: null,
|
||||
displayName: "Term created Aug 23, 2013"
|
||||
}
|
||||
];
|
||||
},
|
||||
|
||||
teardown() {
|
||||
React.unmountComponentAtNode(wrapper);
|
||||
}
|
||||
});
|
||||
|
||||
test('includes "number of terms belonging to sets + 1" options', function () {
|
||||
let dropdown = this.renderComponent();
|
||||
let node = React.findDOMNode(dropdown.refs.termsDropdown);
|
||||
equal(node.length, 5);
|
||||
});
|
||||
|
||||
test('starts by showing all enrollment terms', function () {
|
||||
let dropdown = this.renderComponent();
|
||||
let node = React.findDOMNode(dropdown.refs.termsDropdown);
|
||||
const ALL_TERMS_ID = 0;
|
||||
equal(node.value, ALL_TERMS_ID);
|
||||
});
|
||||
|
||||
test("calls changeSelectedEnrollmentTerm when a selection is made", function() {
|
||||
let dropdown = this.renderComponent();
|
||||
let node = React.findDOMNode(dropdown.refs.termsDropdown);
|
||||
node.value = 3;
|
||||
Simulate.change(node);
|
||||
ok(dropdown.props.changeSelectedEnrollmentTerm.calledOnce);
|
||||
});
|
||||
|
||||
test("displays the terms in descending order by start date then created date if start date doesn't exist", function() {
|
||||
let dropdown = this.renderComponent();
|
||||
let node = React.findDOMNode(dropdown.refs.termsDropdown);
|
||||
let optionIDs = _.pluck(node.getElementsByTagName("OPTION"), "value");
|
||||
propEqual(optionIDs, ["0", "21", "18", "7", "2"]);
|
||||
});
|
||||
});
|
|
@ -10,23 +10,27 @@ define([
|
|||
module("GradingPeriodSetCollection", {
|
||||
renderComponent() {
|
||||
const props = {
|
||||
URLs: {
|
||||
gradingPeriodSetsURL: "api/v1/accounts/1/grading_period_sets"
|
||||
}
|
||||
urls: {
|
||||
gradingPeriodSetsURL: "api/v1/accounts/1/grading_period_sets",
|
||||
enrollmentTermsURL: "api/v1/accounts/1/terms",
|
||||
gradingPeriodsUpdateURL: "api/v1/accounts/1/grading_period_sets"
|
||||
},
|
||||
readOnly: false
|
||||
};
|
||||
|
||||
const element = React.createElement(SetCollection, props);
|
||||
return React.render(element, wrapper);
|
||||
},
|
||||
|
||||
successResponse() {
|
||||
setsResponse() {
|
||||
return {
|
||||
data: {
|
||||
grading_period_sets: [
|
||||
{
|
||||
id: "1",
|
||||
title: "Macarena",
|
||||
grading_periods: []
|
||||
grading_periods: [],
|
||||
permissions: { read: true, create: true, update: true, delete: true }
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
|
@ -34,15 +38,111 @@ define([
|
|||
grading_periods: [
|
||||
{ id: 9, title: "Febrero", start_date: "2014-06-08T15:44:25Z", end_date: "2014-07-08T15:44:25Z" },
|
||||
{ id: 11, title: "Marzo", start_date: "2014-08-08T15:44:25Z", end_date: "2014-09-08T15:44:25Z" }
|
||||
]
|
||||
],
|
||||
permissions: { read: true, create: true, update: true, delete: true }
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
stubAJAXSuccess() {
|
||||
const response = this.successResponse();
|
||||
deserializedSets() {
|
||||
return [
|
||||
{
|
||||
id: "1",
|
||||
title: "Macarena",
|
||||
gradingPeriods: [],
|
||||
permissions: { read: true, create: true, update: true, delete: true }
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
title: "Mambo Numero Cinco",
|
||||
gradingPeriods: [
|
||||
{
|
||||
id: "9",
|
||||
title: "Febrero",
|
||||
startDate: new Date("2014-06-08T15:44:25Z"),
|
||||
endDate: new Date("2014-07-08T15:44:25Z")
|
||||
},
|
||||
{
|
||||
id: "11",
|
||||
title: "Marzo",
|
||||
startDate: new Date("2014-08-08T15:44:25Z"),
|
||||
endDate: new Date("2014-09-08T15:44:25Z")
|
||||
}
|
||||
],
|
||||
permissions: { read: true, create: true, update: true, delete: true }
|
||||
}
|
||||
];
|
||||
},
|
||||
|
||||
termsResponse() {
|
||||
return {
|
||||
data: {
|
||||
enrollment_terms: [
|
||||
{
|
||||
id: 1,
|
||||
name: "Fall 2013 - Art",
|
||||
start_at: "2013-06-03T02:57:42Z",
|
||||
end_at: "2013-12-03T02:57:53Z",
|
||||
created_at: "2015-10-27T16:51:41Z",
|
||||
grading_period_group_id: 2
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: null,
|
||||
start_at: "2014-01-03T02:58:36Z",
|
||||
end_at: "2014-03-03T02:58:42Z",
|
||||
created_at: "2013-06-02T17:29:19Z",
|
||||
grading_period_group_id: 2
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: null,
|
||||
start_at: null,
|
||||
end_at: null,
|
||||
created_at: "2014-05-02T17:29:19Z",
|
||||
grading_period_group_id: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
deserializedTerms() {
|
||||
return [
|
||||
{
|
||||
id: 1,
|
||||
name: "Fall 2013 - Art",
|
||||
startAt: new Date("2013-06-03T02:57:42Z"),
|
||||
endAt: new Date("2013-12-03T02:57:53Z"),
|
||||
createdAt: new Date("2015-10-27T16:51:41Z"),
|
||||
gradingPeriodGroupId: 2,
|
||||
displayName: "Fall 2013 - Art"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: null,
|
||||
startAt: new Date("2014-01-03T02:58:36Z"),
|
||||
endAt: new Date("2014-03-03T02:58:42Z"),
|
||||
createdAt: new Date("2013-06-02T17:29:19Z"),
|
||||
gradingPeriodGroupId: 2,
|
||||
displayName: "Term starting Jan 3, 2014"
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: null,
|
||||
startAt: null,
|
||||
endAt: null,
|
||||
createdAt: new Date("2014-05-02T17:29:19Z"),
|
||||
gradingPeriodGroupId: 1,
|
||||
displayName: "Term created May 2, 2014"
|
||||
}
|
||||
];
|
||||
},
|
||||
|
||||
stubAJAXSuccess(opts={ type: "sets" }) {
|
||||
const response = opts.type === "sets" ? this.setsResponse() : this.termsResponse();
|
||||
const successPromise = new Promise(resolve => resolve(response));
|
||||
this.stub(axios, "get").returns(successPromise);
|
||||
return successPromise;
|
||||
|
@ -64,25 +164,7 @@ define([
|
|||
|
||||
asyncTest("deserializes sets and grading periods if the AJAX call is successful", function() {
|
||||
const success = this.stubAJAXSuccess();
|
||||
const deserializedSet = {
|
||||
id: "2",
|
||||
title: "Mambo Numero Cinco",
|
||||
gradingPeriods: [
|
||||
{
|
||||
id: "9",
|
||||
title: "Febrero",
|
||||
startDate: new Date("2014-06-08T15:44:25Z"),
|
||||
endDate: new Date("2014-07-08T15:44:25Z")
|
||||
},
|
||||
{
|
||||
id: "11",
|
||||
title: "Marzo",
|
||||
startDate: new Date("2014-08-08T15:44:25Z"),
|
||||
endDate: new Date("2014-09-08T15:44:25Z")
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const deserializedSet = this.deserializedSets()[1];
|
||||
let collection = this.renderComponent();
|
||||
|
||||
success.then(function() {
|
||||
|
@ -103,14 +185,14 @@ define([
|
|||
});
|
||||
|
||||
test("setAndGradingPeriodTitles returns an array of set and grading period title names", function() {
|
||||
let set = { title: "Set!", gradingPeriods: [{ title: "Grading Period 1" }, { title: "Grading Period 2" }] };
|
||||
const set = { title: "Set!", gradingPeriods: [{ title: "Grading Period 1" }, { title: "Grading Period 2" }] };
|
||||
let collection = this.renderComponent();
|
||||
let titles = collection.setAndGradingPeriodTitles(set);
|
||||
const titles = collection.setAndGradingPeriodTitles(set);
|
||||
propEqual(titles, ["Set!", "Grading Period 1", "Grading Period 2"]);
|
||||
});
|
||||
|
||||
test("setAndGradingPeriodTitles filters out empty, null, and undefined titles", function() {
|
||||
let set = {
|
||||
const set = {
|
||||
title: null,
|
||||
gradingPeriods: [
|
||||
{ title: "Grading Period 1" },
|
||||
|
@ -121,52 +203,126 @@ define([
|
|||
};
|
||||
|
||||
let collection = this.renderComponent();
|
||||
let titles = collection.setAndGradingPeriodTitles(set);
|
||||
const titles = collection.setAndGradingPeriodTitles(set);
|
||||
propEqual(titles, ["Grading Period 1", "Grading Period 2"]);
|
||||
});
|
||||
|
||||
test("changeSearchText calls setState if the new search text differs from the old search text", function() {
|
||||
const titles = ["hello world", "goodbye friend"];
|
||||
let collection = this.renderComponent();
|
||||
const setStateSpy = this.spy(collection, "setState");
|
||||
collection.changeSearchText("hello world");
|
||||
collection.changeSearchText("goodbye world");
|
||||
ok(setStateSpy.calledTwice)
|
||||
});
|
||||
|
||||
test("changeSearchText does not call setState if the new search text equals the old search text", function() {
|
||||
const titles = ["hello world", "goodbye friend"];
|
||||
let collection = this.renderComponent();
|
||||
const setStateSpy = this.spy(collection, "setState");
|
||||
collection.changeSearchText("hello world");
|
||||
collection.changeSearchText("hello world");
|
||||
ok(setStateSpy.calledOnce)
|
||||
});
|
||||
|
||||
test("searchTextMatchesTitles returns true if the search text exactly matches one of the titles", function() {
|
||||
let titles = ["hello world", "goodbye friend"];
|
||||
const titles = ["hello world", "goodbye friend"];
|
||||
let collection = this.renderComponent();
|
||||
collection.changeSearchText("hello world");
|
||||
equal(collection.searchTextMatchesTitles(titles), true)
|
||||
});
|
||||
|
||||
test("searchTextMatchesTitles returns true if the search text exactly matches one of the titles", function() {
|
||||
const titles = ["hello world", "goodbye friend"];
|
||||
let collection = this.renderComponent();
|
||||
collection.changeSearchText("hello world");
|
||||
equal(collection.searchTextMatchesTitles(titles), true)
|
||||
});
|
||||
|
||||
test("searchTextMatchesTitles returns true if the search text is a substring of one of the titles", function() {
|
||||
let titles = ["hello world", "goodbye friend"];
|
||||
const titles = ["hello world", "goodbye friend"];
|
||||
let collection = this.renderComponent();
|
||||
collection.changeSearchText("orl");
|
||||
equal(collection.searchTextMatchesTitles(titles), true)
|
||||
});
|
||||
|
||||
test("searchTextMatchesTitles returns false if the search text is a not a substring of any of the titles", function() {
|
||||
let titles = ["hello world", "goodbye friend"];
|
||||
const titles = ["hello world", "goodbye friend"];
|
||||
let collection = this.renderComponent();
|
||||
collection.changeSearchText("olr");
|
||||
equal(collection.searchTextMatchesTitles(titles), false)
|
||||
});
|
||||
|
||||
asyncTest("filterSetsBySearchText returns sets that match the search text", function() {
|
||||
asyncTest("getVisibleSets returns sets that match the search text", function() {
|
||||
const success = this.stubAJAXSuccess();
|
||||
let collection = this.renderComponent();
|
||||
|
||||
success.then(function() {
|
||||
collection.changeSearchText("ma");
|
||||
let filteredIDs = _.pluck(collection.filterSetsBySearchText(), "id");
|
||||
let filteredIDs = _.pluck(collection.getVisibleSets(), "id");
|
||||
propEqual(filteredIDs, ["1", "2"]);
|
||||
|
||||
collection.changeSearchText("rz");
|
||||
filteredIDs = _.pluck(collection.filterSetsBySearchText(), "id");
|
||||
filteredIDs = _.pluck(collection.getVisibleSets(), "id");
|
||||
propEqual(filteredIDs, ["2"]);
|
||||
|
||||
collection.changeSearchText("Mac");
|
||||
filteredIDs = _.pluck(collection.filterSetsBySearchText(), "id");
|
||||
filteredIDs = _.pluck(collection.getVisibleSets(), "id");
|
||||
propEqual(filteredIDs, ["1"]);
|
||||
|
||||
collection.changeSearchText("dora the explorer");
|
||||
filteredIDs = _.pluck(collection.filterSetsBySearchText(), "id");
|
||||
propEqual(collection.filterSetsBySearchText(), []);
|
||||
filteredIDs = _.pluck(collection.getVisibleSets(), "id");
|
||||
propEqual(collection.getVisibleSets(), []);
|
||||
start();
|
||||
});
|
||||
});
|
||||
|
||||
asyncTest("deserializes enrollment terms if the AJAX call is successful", function() {
|
||||
const success = this.stubAJAXSuccess({ type: "terms" });
|
||||
const deserializedTerm = this.deserializedTerms()[0];
|
||||
let collection = this.renderComponent();
|
||||
|
||||
success.then(function() {
|
||||
const term = collection.state.enrollmentTerms[0];
|
||||
propEqual(term, deserializedTerm);
|
||||
start();
|
||||
});
|
||||
});
|
||||
|
||||
asyncTest("uses the name, start date (if no name), or creation date (if no start) for the display name", function() {
|
||||
const success = this.stubAJAXSuccess({ type: "terms" });
|
||||
const expectedNames = _.pluck(this.deserializedTerms(), "displayName");
|
||||
let collection = this.renderComponent();
|
||||
|
||||
success.then(function() {
|
||||
const names = _.pluck(collection.state.enrollmentTerms, "displayName");
|
||||
propEqual(names, expectedNames);
|
||||
start();
|
||||
});
|
||||
});
|
||||
|
||||
test("filterSetsByActiveTerm returns all the sets if 'All Terms' is selected", function() {
|
||||
const ALL_TERMS_ID = 0;
|
||||
const sets = this.deserializedSets();
|
||||
const terms = this.deserializedTerms();
|
||||
const selectedTermID = ALL_TERMS_ID;
|
||||
let collection = this.renderComponent();
|
||||
const filteredSets = collection.filterSetsByActiveTerm(sets, terms, selectedTermID);
|
||||
propEqual(filteredSets, sets);
|
||||
});
|
||||
|
||||
test("filterSetsByActiveTerm filters to only show the set that the selected term belongs to", function() {
|
||||
const sets = this.deserializedSets();
|
||||
const terms = this.deserializedTerms();
|
||||
let selectedTermID = 3;
|
||||
let collection = this.renderComponent();
|
||||
let filteredSets = collection.filterSetsByActiveTerm(sets, terms, selectedTermID);
|
||||
let expectedSets = _.where(sets, { id: "2" });
|
||||
propEqual(filteredSets, expectedSets);
|
||||
|
||||
selectedTermID = 4;
|
||||
filteredSets = collection.filterSetsByActiveTerm(sets, terms, selectedTermID);
|
||||
expectedSets = _.where(sets, { id: "1" });
|
||||
propEqual(filteredSets, expectedSets);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -54,7 +54,8 @@ define([
|
|||
gradingPeriods: examplePeriods,
|
||||
readOnly: false,
|
||||
urls: urls,
|
||||
permissions: allPermissions
|
||||
permissions: allPermissions,
|
||||
terms: []
|
||||
};
|
||||
|
||||
module("GradingPeriodSet", {
|
||||
|
@ -101,7 +102,8 @@ define([
|
|||
gradingPeriods: [],
|
||||
urls: urls,
|
||||
permissions: _.defaults(permissions, allPermissions),
|
||||
readOnly: readOnly
|
||||
readOnly: readOnly,
|
||||
terms: []
|
||||
};
|
||||
const element = React.createElement(GradingPeriodSet, set);
|
||||
let component = React.render(element, wrapper);
|
||||
|
@ -157,7 +159,8 @@ define([
|
|||
gradingPeriods: [],
|
||||
urls: urls,
|
||||
readOnly: false,
|
||||
permissions: allPermissions
|
||||
permissions: allPermissions,
|
||||
terms: []
|
||||
};
|
||||
const element = React.createElement(GradingPeriodSet, set);
|
||||
let component = React.render(element, wrapper);
|
||||
|
|
Loading…
Reference in New Issue