add temporary conditional release popup to assignments

refs: CYOE-74

Test plan:
View conditional content on assignment, quiz, and discussion topic
edit pages.
- Button should be disabled before assignment has saved (though some
  assignments save before first edit)
- Button should be enabled after save
- Clicking button should bring up a modal (with an error page loaded
  in it)
- For discussion topics, saving here means "saved as a graded topic"

Change-Id: I3faed62567488c71067f94ff911b57ca71de9648
Reviewed-on: https://gerrit.instructure.com/77677
Tested-by: Jenkins
Reviewed-by: Matt Berns <mberns@instructure.com>
Reviewed-by: Christian Prescott <cprescott@instructure.com>
QA-Review: Jahnavi Yetukuri <jyetukuri@instructure.com>
Product-Review: Matt Goodwin <mattg@instructure.com>
This commit is contained in:
Michael Brewer-Davis 2016-04-21 16:29:17 -05:00 committed by Robert Lamb
parent 1cd05d560b
commit ca63fdc0e4
18 changed files with 496 additions and 53 deletions

View File

@ -17,11 +17,13 @@ define [
'compiled/fn/preventDefault'
'compiled/views/calendar/MissingDateDialogView'
'compiled/views/editor/KeyboardShortcuts'
'jsx/shared/conditional_release/ConditionalRelease'
'jquery.instructure_misc_helpers' # $.scrollSidebar
'compiled/jquery.rails_flash_notifications' #flashMessage
], (I18n, ValidatedFormView, AssignmentGroupSelector, GradingTypeSelector,
GroupCategorySelector, PeerReviewsSelector, PostToSisSelector, _, template, RichContentEditor,
htmlEscape, DiscussionTopic, Announcement, Assignment, $, preventDefault, MissingDateDialog, KeyboardShortcuts) ->
htmlEscape, DiscussionTopic, Announcement, Assignment, $, preventDefault, MissingDateDialog, KeyboardShortcuts,
ConditionalRelease) ->
RichContentEditor.preloadRemoteModule()
@ -42,14 +44,16 @@ htmlEscape, DiscussionTopic, Announcement, Assignment, $, preventDefault, Missin
'#discussion_point_change_warning' : '$discussionPointPossibleWarning'
'#discussion-edit-view' : '$discussionEditView'
'#discussion-details-tab' : '$discussionDetailsTab'
'#conditional-release-target' : '$conditionalReleaseTarget'
events: _.extend(@::events,
'click .removeAttachment' : 'removeAttachment'
'click .save_and_publish': 'saveAndPublish'
'click .cancel_button' : 'handleCancel'
'change #use_for_grading' : 'toggleAvailabilityOptions'
'change #use_for_grading' : 'updateTabView'
'change #use_for_grading' : 'toggleConditionalReleaseTab'
'change #discussion_topic_assignment_points_possible' : 'handlePointsChange'
'change' : 'onChange'
)
messages:
@ -88,7 +92,6 @@ htmlEscape, DiscussionTopic, Announcement, Assignment, $, preventDefault, Missin
canModerate: @permissions.CAN_MODERATE
isLargeRoster: ENV?.IS_LARGE_ROSTER || false
threaded: data.discussion_type is "threaded"
CONDITIONAL_RELEASE_SERVICE_ENABLED: ENV.CONDITIONAL_RELEASE_SERVICE_ENABLED
json.assignment = json.assignment.toView()
json
@ -130,6 +133,7 @@ htmlEscape, DiscussionTopic, Announcement, Assignment, $, preventDefault, Missin
_.defer(@watchUnload)
_.defer(@attachKeyboardShortcuts)
_.defer(@renderTabs) if ENV.CONDITIONAL_RELEASE_SERVICE_ENABLED
_.defer(@loadConditionalRelease) if ENV.CONDITIONAL_RELEASE_SERVICE_ENABLED
@$(".datetime_field").datetime_field()
@ -188,7 +192,15 @@ htmlEscape, DiscussionTopic, Announcement, Assignment, $, preventDefault, Missin
renderTabs: =>
@$discussionEditView.tabs()
@$discussionDetailsTab.show()
@updateTabView()
@toggleConditionalReleaseTab()
loadConditionalRelease: =>
if !ENV.CONDITIONAL_RELEASE_ENV
return # can happen during unit tests due to _.defer
@conditionalReleaseEditor = ConditionalRelease.attach(
@$conditionalReleaseTarget.get(0),
I18n.t('discussion topic'),
ENV.CONDITIONAL_RELEASE_ENV)
getFormData: ->
data = super
@ -322,10 +334,15 @@ htmlEscape, DiscussionTopic, Announcement, Assignment, $, preventDefault, Missin
else
@$availabilityOptions.show()
updateTabView: ->
toggleConditionalReleaseTab: ->
if ENV.CONDITIONAL_RELEASE_SERVICE_ENABLED
if @$useForGrading.is(':checked')
@$discussionEditView.tabs("option", "disabled", false)
else
@$discussionEditView.tabs("option", "disabled", [1])
@$discussionDetailsTab.show()
onChange: ->
if ENV.CONDITIONAL_RELEASE_SERVICE_ENABLED && !@assignmentDirty
@assignmentDirty = true
@conditionalReleaseEditor.setProps({ assignmentDirty: true })

View File

@ -2,9 +2,10 @@ define [
'i18n!assignments'
'Backbone'
'jquery'
'jsx/shared/conditional_release/ConditionalRelease'
'jst/assignments/EditHeaderView'
'jquery.disableWhileLoading'
], (I18n, Backbone, $, template) ->
], (I18n, Backbone, $, ConditionalRelease, template) ->
class EditHeaderView extends Backbone.View
@ -12,6 +13,8 @@ define [
events:
'click .delete_assignment_link': 'onDelete'
'change #grading_type_selector': 'onGradingTypeUpdate'
'change' : 'onChange'
messages:
confirm: I18n.t('confirms.delete_assignment', 'Are you sure you want to delete this assignment?')
@ -19,12 +22,20 @@ define [
els:
'#edit-assignment-header-tabs': '$headerTabs'
'#edit-assignment-header-cr-tabs': '$headerTabsCr'
'#conditional-release-target': '$conditionalReleaseTarget'
afterRender: ->
# doubled for conditional release
@$headerTabs.tabs()
@$headerTabsCr.tabs()
if ENV.CONDITIONAL_RELEASE_SERVICE_ENABLED
@toggleConditionalReleaseTab(@model.gradingType())
@conditionalReleaseEditor = ConditionalRelease.attach(
@$conditionalReleaseTarget.get(0),
I18n.t('assignment'),
ENV.CONDITIONAL_RELEASE_ENV)
onDelete: (e) =>
e.preventDefault()
@delete() if confirm(@messages.confirm)
@ -42,7 +53,23 @@ define [
onDeleteSuccess: ->
location.href = ENV.ASSIGNMENT_INDEX_URL
onGradingTypeUpdate: (e) =>
if ENV.CONDITIONAL_RELEASE_SERVICE_ENABLED
@toggleConditionalReleaseTab(e.target.value)
toggleConditionalReleaseTab: (gradingType) ->
if ENV.CONDITIONAL_RELEASE_SERVICE_ENABLED
if gradingType == 'not_graded'
@$headerTabsCr.tabs("option", "disabled", [1])
else
@$headerTabsCr.tabs("option", "disabled", false)
toJSON: ->
json = @model.toView()
json['CONDITIONAL_RELEASE_SERVICE_ENABLED'] = ENV.CONDITIONAL_RELEASE_SERVICE_ENABLED
json
onChange: ->
if ENV.CONDITIONAL_RELEASE_SERVICE_ENABLED && !@assignmentDirty
@assignmentDirty = true
@conditionalReleaseEditor.setProps({ assignmentDirty: true })

View File

@ -27,7 +27,7 @@ module ConditionalRelease
conditional_release_js_env
render locals: {
cr_app_url: ConditionalRelease::Service.configure_defaults_url,
cr_app_url: ConditionalRelease::Service.edit_rule_path,
}
end
end

View File

@ -0,0 +1,163 @@
define([
'jquery',
'react',
'react-modal',
'i18n!conditional_release',
], function($, React, Modal, I18n) {
// Conditional Release adds its form at the tail of the page
// because it is sometimes nested within a larger form (eg., discussion topics)
const HiddenForm = React.createClass({
propTypes: {
env: React.PropTypes.object.isRequired,
target: React.PropTypes.string.isRequired
},
render() {
return (
<form id="conditional-release-editor-form"
target={this.props.target}
method="POST"
action={this.props.env.edit_rule_url}>
<input type="hidden" name="env" value={JSON.stringify(this.props.env)} />
</form>
);
}
});
// Need to specify componentDidMount so that we can be sure the iframe exists before
// submitting a form targetting it
const ConditionalReleaseFrame = React.createClass({
render() {
return (
<div className="conditional-release-editor-body ReactModal__Body">
<iframe className="conditional-release-editor-frame" id={this.props.name} name={this.props.name} src={this.props.source}></iframe>
</div>
)
},
componentDidMount() {
this.props.onComponentDidMount();
}
});
const Editor = React.createClass({
displayName: 'ConditionalReleaseEditor',
propTypes: {
env: React.PropTypes.object.isRequired,
type: React.PropTypes.string.isRequired
},
getInitialState() {
return {
modalIsOpen: false,
hiddenContainer: null
};
},
enabled() {
return this.props.env &&
this.props.env.assignment &&
this.props.env.assignment.id &&
!this.props.assignmentDirty;
},
saveDataMessage() {
if (!this.enabled()) {
return (
<div ref="saveDataMessage" className="conditional-release-save-data">
{I18n.t('Save your %{type} to begin specifying conditional content.', { type: this.props.type })}
</div>
);
}
},
popupId() {
if (this.props.env.assignment) {
return "conditional_release_" + this.props.env.assignment.id;
} else {
return "conditional_release_no_assignment";
}
},
hiddenContainer() {
return this.state.hiddenContainer;
},
componentDidMount() {
const $hiddenContainer = $('<div id="conditional-release-hidden-form-container"></div>');
this.setState({ hiddenContainer: $hiddenContainer });
$('body').append($hiddenContainer);
React.render(
<HiddenForm target={this.popupId()} env={this.props.env}></HiddenForm>,
$hiddenContainer.get(0));
},
componentWillUnmount() {
React.unmountComponentAtNode(this.hiddenContainer().get(0));
this.hiddenContainer().remove();
},
onClick(event) {
event.preventDefault();
event.stopPropagation();
this.setState({ modalIsOpen: true });
},
closeModal() {
this.setState({ modalIsOpen: false });
this.refs.iframe.source = null;
},
submitForm() {
$("#conditional-release-editor-form").submit();
},
render () {
return (
<div className="conditional-release-editor">
<button ref="button" className="Button" disabled={!this.enabled()} onClick={this.onClick}>
{I18n.t('View conditional content settings')}
</button>
{ this.saveDataMessage() }
<Modal isOpen={this.state.modalIsOpen}
onAfterOpen={this.onModalOpen}
onRequestClose={this.closeModal}
className='conditional-release-editor-modal ReactModal__Content--canvas'
overlayClassName='ReactModal__Overlay--canvas'>
<div className="conditional-release-editor-layout ReactModal__Layout">
<div className="ReactModal__Header">
<div className="ReactModal__Header-Title">
<h4>{I18n.t('Conditional Content')}</h4>
</div>
<div className="ReactModal__Header-Actions">
<button className="Button Button--icon-action" type="button" onClick={this.closeModal}>
<i className="icon-x"></i>
<span className="screenreader-only">Close</span>
</button>
</div>
</div>
<ConditionalReleaseFrame ref="iframe" name={this.popupId()} onComponentDidMount={this.submitForm} />
</div>
</Modal>
</div>
)
}
});
const attach = function(element, type, env) {
const editor = (
<Editor env={env} type={type} />
);
return React.render(editor, element);
};
const ConditionalRelease = {
Editor: Editor,
attach: attach
};
return ConditionalRelease;
});

View File

@ -24,8 +24,8 @@ module ConditionalRelease
enabled: false, # required
host: nil, # required
protocol: nil, # defaults to Canvas
configure_defaults_app_path: 'javascripts/edit_defaults.js',
edit_object_score_ranges_path: 'javascripts/edit_object_score_ranges.js',
edit_rule_path: "api/editor",
create_account_path: 'api/account',
}.freeze
def self.env_for(context, user = nil, session: nil, assignment: nil, domain: nil, real_user: nil)
@ -35,9 +35,10 @@ module ConditionalRelease
}
if enabled && user
env.merge!({
CONDITIONAL_RELEASE_JWT: jwt_for(context, user, domain, session: session, real_user: real_user),
CONDITIONAL_RELEASE_ENV: {
assignment: assignment_attributes(assignment)
jwt: jwt_for(context, user, domain, session: session, real_user: real_user),
assignment: assignment_attributes(assignment),
edit_rule_url: edit_rule_url
}
})
end
@ -45,7 +46,7 @@ module ConditionalRelease
end
def self.jwt_for(context, user, domain, claims: {}, session: nil, real_user: nil)
return Canvas::Security::ServicesJwt.generate(
Canvas::Security::ServicesJwt.generate(
claims.merge({
sub: user.id.to_s,
account_id: Context.get_account(context).root_account.lti_guid.to_s,
@ -74,12 +75,12 @@ module ConditionalRelease
!!(configured? && context.feature_enabled?(:conditional_release))
end
def self.configure_defaults_url
build_url configure_defaults_app_path
def self.edit_rule_url
build_url edit_rule_path
end
def self.edit_object_score_ranges_url
build_url edit_object_score_ranges_path
def self.create_account_url
build_url create_account_path
end
def self.protocol
@ -90,12 +91,16 @@ module ConditionalRelease
config[:host]
end
def self.configure_defaults_app_path
config[:configure_defaults_app_path]
def self.unique_id
config[:unique_id] || "conditional-release-service@instructure.auth"
end
def self.edit_object_score_ranges_path
config[:edit_object_score_ranges_path]
def self.edit_rule_path
config[:edit_rule_path]
end
def self.create_account_path
config[:create_account_path]
end
class << self

View File

@ -3,4 +3,4 @@
@import "pages/shared/grading_standards.scss";
@import "pages/shared/mark_as_done.scss";
@import "vendor/embed_content.scss";
@import "components/conditional_release";

View File

@ -1,4 +1,5 @@
@import "base/_environment.scss";
@import "components/conditional_release";
.removeAttachment {
@include fontSize(20px);

View File

@ -2,3 +2,4 @@
@import "pages/quizzes/quizzes-mobile";
@import "pages/shared/message_students";
@import "components/ui.selectmenu";
@import "components/conditional_release";

View File

@ -0,0 +1,24 @@
.conditional-release-editor {
padding-top: 12px;
}
.conditional-release-editor-layout {
width: 90% !important;
height: 90% !important;
display: flex;
flex-direction: column;
}
.conditional-release-editor-body {
flex: 1 1 auto;
display: flex;
}
.conditional-release-editor-frame {
border: none;
flex: 1 1 auto;
}
.conditional-release-save-data {
padding: 12px 0px;
}

View File

@ -9,31 +9,19 @@
<div id="discussion-edit-view" class="ui-tabs-minimal">
{{#unless isAnnouncement}}
<div id="discussion-edit-header" class="discussion-edit-header row-fluid">
{{#if CONDITIONAL_RELEASE_SERVICE_ENABLED }}
{{#if ENV.CONDITIONAL_RELEASE_SERVICE_ENABLED }}
<ul id="discussion-edit-header-tabs">
<li><a href="#discussion-details-tab" id="details_link">{{#t}}Details{{/t}}</a></li>
<li><a href="#discussion-conditional-release-tab" id="conditional_release_link">{{#t}}Conditional Content{{/t}}</a></li>
<span id="discussion-edit-header-spacer"></span>
{{else}}
<div class="span4 offset8 text-right">
{{/if}}
{{#if published}}
<span id="topic-draft-state" class="published-status published">
<i class="icon-publish"></i>
{{#t "buttons.published"}}Published{{/t}}
</span>
{{else}}
<span id="topic-draft-state" class="published-status unpublished">
<i class="icon-unpublished"></i>
{{#t "buttons.not_published"}}Not Published{{/t}}
</span>
{{/if}}
{{#if CONDITIONAL_RELEASE_SERVICE_ENABLED }}
{{> DiscussionTopics/publishedButton }}
</ul>
{{else}}
<div class="span4 offset8 text-right">
{{> DiscussionTopics/publishedButton }}
</div>
</div>
{{/if}}
</div>
{{/unless}}
<div id="discussion-details-tab">
@ -300,10 +288,10 @@
</div>
{{/if}}
</div>
{{#if CONDITIONAL_RELEASE_SERVICE_ENABLED }}
{{#if ENV.CONDITIONAL_RELEASE_SERVICE_ENABLED }}
{{#unless isAnnouncement }}
<div id="discussion-conditional-release-tab">
Conditional release content goes here
<div id="conditional-release-target" />
</div>
{{/unless}}
{{/if}}

View File

@ -0,0 +1,11 @@
{{#if published}}
<span id="topic-draft-state" class="published-status published">
<i class="icon-publish"></i>
{{#t "buttons.published"}}Published{{/t}}
</span>
{{else}}
<span id="topic-draft-state" class="published-status unpublished">
<i class="icon-unpublished"></i>
{{#t "buttons.not_published"}}Not Published{{/t}}
</span>
{{/if}}

View File

@ -46,7 +46,9 @@
<form id="edit_assignment_form" class="form-horizontal bootstrap-form">
</form>
</div>
<div id="conditional_release_wrapper"></div>
<div id="conditional_release_wrapper">
<div id="conditional-release-target" />
</div>
</div>
</div>

View File

@ -1,3 +1,4 @@
<div id="conditional_release_tab">
Conditional release content goes here
<div id="conditional-release-target"></div>
</div>

View File

@ -41,6 +41,7 @@ define([
'INST', // safari sniffing for VO workarounds
'quiz_formula_solution',
'jsx/shared/rce/RichContentEditor',
'jsx/shared/conditional_release/ConditionalRelease',
'jquery.ajaxJSON' /* ajaxJSON */,
'jquery.instructure_date_and_time' /* time_field, datetime_field */,
'jquery.instructure_forms' /* formSubmit, fillFormData, getFormData, formErrors, errorBox */,
@ -60,7 +61,8 @@ define([
Handlebars, DueDateOverrideView, Quiz,
DueDateList, QuizRegradeView, SectionList,
MissingDateDialog,MultipleChoiceToggle,EditorToggle,TextHelper,
RCEKeyboardShortcuts, INST, QuizFormulaSolution, RichContentEditor){
RCEKeyboardShortcuts, INST, QuizFormulaSolution, RichContentEditor,
ConditionalRelease){
var dueDateList, overrideView, quizModel, sectionList, correctAnswerVisibility,
scoreValidation;
@ -1476,6 +1478,16 @@ define([
return result;
}
function toggleConditionalReleaseTab(quizType) {
if (ENV['CONDITIONAL_RELEASE_SERVICE_ENABLED']) {
if (quizType == "assignment") {
$('#quiz_tabs').tabs("option", "disabled", false);
} else {
$('#quiz_tabs').tabs("option", "disabled", [2]);
}
}
}
$(document).ready(function() {
quiz.init().updateDisplayComments();
correctAnswerVisibility.init();
@ -1879,6 +1891,10 @@ define([
var url = $("#quiz_urls .update_quiz_url").attr('href');
$("#quiz_title_input").val(quiz_title);
$("#quiz_title_text").text(quiz_title);
if (ENV['CONDITIONAL_RELEASE_SERVICE_ENABLED']) {
toggleConditionalReleaseTab(event.target.value);
}
}).change();
$(".question_form :input").keycodes("esc", function(event) {
@ -3703,6 +3719,20 @@ define([
success: function() { window.location.replace(ENV.QUIZZES_URL); }
});
});
if (ENV['CONDITIONAL_RELEASE_SERVICE_ENABLED']) {
this.conditionalReleaseEditor = ConditionalRelease.attach(
$('#conditional-release-target').get(0),
I18n.t('quiz'),
ENV['CONDITIONAL_RELEASE_ENV']);
$('div.form').on('change', function(event) {
if (!this.assignmentDirty) {
this.assignmentDirty = true
this.conditionalReleaseEditor.setProps({ assignmentDirty: true });
}
});
}
});
$.fn.multipleAnswerSetsQuestion = function() {
@ -4101,7 +4131,6 @@ define([
if(!showCorrectAnswersLastAttempt) {
$('#quiz_show_correct_answers_last_attempt').prop('checked', false);
}
}).triggerHandler('change');
});
});

View File

@ -85,6 +85,7 @@ GroupCategorySelector, fakeENV) ->
equal view.$el.find('#podcast_enabled').length, 1
equal view.$el.find('#podcast_has_student_posts_container').length, 0
conditionalReleaseEnv = { assignment: { id: 1 }, jwt: 'foo' }
test 'does not show conditional release tab when feature not enabled', ->
ENV.CONDITIONAL_RELEASE_SERVICE_ENABLED = false
view = @editView()
@ -93,23 +94,29 @@ GroupCategorySelector, fakeENV) ->
test 'shows disabled conditional release tab when feature enabled, but not assignment', ->
ENV.CONDITIONAL_RELEASE_SERVICE_ENABLED = true
ENV.CONDITIONAL_RELEASE_ENV = conditionalReleaseEnv
view = @editView()
view.renderTabs()
view.loadConditionalRelease()
equal view.$el.find('#discussion-conditional-release-tab').length, 1
equal view.$discussionEditView.hasClass('ui-tabs'), true
equal view.$discussionEditView.tabs("option", "disabled"), true
test 'shows enabled conditional release tab when feature enabled, and assignment', ->
ENV.CONDITIONAL_RELEASE_SERVICE_ENABLED = true
ENV.CONDITIONAL_RELEASE_ENV = conditionalReleaseEnv
view = @editView({ withAssignment: true })
view.renderTabs()
view.loadConditionalRelease()
equal view.$el.find('#discussion-conditional-release-tab').length, 1
equal view.$discussionEditView.hasClass('ui-tabs'), true
equal view.$discussionEditView.tabs("option", "disabled"), false
test 'enables conditional release tab when changed to assignment', ->
ENV.CONDITIONAL_RELEASE_SERVICE_ENABLED = true
ENV.CONDITIONAL_RELEASE_ENV = conditionalReleaseEnv
view = @editView()
view.loadConditionalRelease()
view.renderTabs()
equal view.$discussionEditView.tabs("option", "disabled"), true
@ -119,10 +126,38 @@ GroupCategorySelector, fakeENV) ->
test 'disables conditional release tab when changed from assignment', ->
ENV.CONDITIONAL_RELEASE_SERVICE_ENABLED = true
ENV.CONDITIONAL_RELEASE_ENV = conditionalReleaseEnv
view = @editView({ withAssignment: true })
view.loadConditionalRelease()
view.renderTabs()
equal view.$discussionEditView.tabs("option", "disabled"), false
view.$useForGrading.prop('checked', false)
view.$useForGrading.trigger('change')
equal view.$discussionEditView.tabs("option", "disabled"), true
test 'renders conditional release tab content', ->
ENV.CONDITIONAL_RELEASE_SERVICE_ENABLED = true
ENV.CONDITIONAL_RELEASE_ENV = conditionalReleaseEnv
view = @editView({ withAssignment: true })
view.loadConditionalRelease()
equal 1, view.$conditionalReleaseTarget.children().size()
test 'conditional release editor is disabled on change', ->
ENV.CONDITIONAL_RELEASE_SERVICE_ENABLED = true
ENV.CONDITIONAL_RELEASE_ENV = conditionalReleaseEnv
view = @editView({ withAssignment: true })
view.loadConditionalRelease()
view.onChange()
equal false, view.conditionalReleaseEditor.enabled()
test 'conditional release editor is disabled only once', ->
ENV.CONDITIONAL_RELEASE_SERVICE_ENABLED = true
ENV.CONDITIONAL_RELEASE_ENV = conditionalReleaseEnv
view = @editView({ withAssignment: true })
view.loadConditionalRelease()
debugger
stub = @stub(view.conditionalReleaseEditor, 'setProps')
view.onChange()
view.onChange()
ok stub.calledOnce

View File

@ -10,8 +10,9 @@ define [
name: 'Test Assignment'
assignment_overrides: []
editHeaderView = () ->
assignment = new Assignment defaultAssignmentOpts
editHeaderView = (assignment_opts = {}) ->
$.extend(assignment_opts, defaultAssignmentOpts)
assignment = new Assignment assignment_opts
app = new EditHeaderView
model: assignment
@ -34,3 +35,45 @@ define [
view.delete()
equal cb.called, true, 'onDeleteSuccess was called'
test 'attaches conditional release editor', ->
ENV.CONDITIONAL_RELEASE_SERVICE_ENABLED = true
ENV.CONDITIONAL_RELEASE_ENV = { assignment: { id: 1 }, jwt: 'foo' }
view = editHeaderView()
equal 1, view.$conditionalReleaseTarget.children().size()
test 'disables conditional release tab on load when grading type is not_graded', ->
ENV.CONDITIONAL_RELEASE_SERVICE_ENABLED = true
ENV.CONDITIONAL_RELEASE_ENV = { assignment: { id: 1 }, jwt: 'foo' }
view = editHeaderView({ grading_type: 'not_graded' })
equal true, view.$headerTabsCr.tabs('option', 'disabled')
test 'enables conditional release tab when grading type switched from not_graded', ->
ENV.CONDITIONAL_RELEASE_SERVICE_ENABLED = true
ENV.CONDITIONAL_RELEASE_ENV = { assignment: { id: 1 }, jwt: 'foo' }
view = editHeaderView({ grading_type: 'not_graded' })
view.onGradingTypeUpdate({target: { value: 'points' }})
equal false, view.$headerTabsCr.tabs('option', 'disabled')
test 'disables conditional release tab when grading type switched to not_graded', ->
ENV.CONDITIONAL_RELEASE_SERVICE_ENABLED = true
ENV.CONDITIONAL_RELEASE_ENV = { assignment: { id: 1 }, jwt: 'foo' }
view = editHeaderView({ grading_type: 'points' })
view.onGradingTypeUpdate({target: { value: 'not_graded' }})
equal true, view.$headerTabsCr.tabs('option', 'disabled')
test 'disables conditional release component on data change', ->
ENV.CONDITIONAL_RELEASE_SERVICE_ENABLED = true
ENV.CONDITIONAL_RELEASE_ENV = { assignment: { id: 1 }, jwt: 'foo' }
view = editHeaderView({ grading_type: 'points' })
view.onChange()
equal false, view.$headerTabsCr.tabs('option', 'disabled')
equal false, view.conditionalReleaseEditor.enabled()
test 'disables conditional release component once', ->
ENV.CONDITIONAL_RELEASE_SERVICE_ENABLED = true
ENV.CONDITIONAL_RELEASE_ENV = { assignment: { id: 1 }, jwt: 'foo' }
view = editHeaderView({ grading_type: 'points' })
stub = @stub(view.conditionalReleaseEditor, 'setProps')
view.onChange()
ok stub.calledOnce

View File

@ -0,0 +1,96 @@
define([
'react',
'jquery',
'jsx/shared/conditional_release/ConditionalRelease'
], (React, $, ConditionalRelease) => {
const TestUtils = React.addons.TestUtils;
let component = null;
module('Conditional Release component', {
teardown: () => {
if (component) {
const componentNode = React.findDOMNode(component);
if (componentNode) {
React.unmountComponentAtNode(componentNode.parentNode);
}
}
component = null;
}
});
const assignmentEnv = { assignment: { id: 1 }, edit_rule_url: 'about:blank', jwt: 'foo' }
const noAssignmentEnv = { edit_rule_url: 'about:blank', jwt: 'foo' }
const assignmentNoIdEnv = { assignment: { foo: 'bar' }, edit_rule_url: 'about:blank', jwt: 'foo' }
test('it disables its button when no assignment', () => {
component = TestUtils.renderIntoDocument(
<ConditionalRelease.Editor env={noAssignmentEnv} type='foo' />
);
const button = React.findDOMNode(component.refs.button);
equal(button.disabled, true)
});
test('it shows the help text when no assignment', () => {
component = TestUtils.renderIntoDocument(
<ConditionalRelease.Editor env={noAssignmentEnv} type='foo' />
);
ok(component.refs.saveDataMessage);
});
test('it shows the help text when an assignment but no id', () => {
component = TestUtils.renderIntoDocument(
<ConditionalRelease.Editor env={assignmentNoIdEnv} type='foo' />
);
ok(component.refs.saveDataMessage);
});
test('it enabled its button when there is an assignment', () => {
component = TestUtils.renderIntoDocument(
<ConditionalRelease.Editor env={assignmentEnv} type='foo' />
);
const button = React.findDOMNode(component.refs.button);
equal(button.disabled, false)
});
test('it does not show the help text when there is an assignment', () => {
component = TestUtils.renderIntoDocument(
<ConditionalRelease.Editor env={assignmentEnv} type='foo' />
);
notOk(component.refs.saveDataMessage);
});
test('it adds the hidden form when mounted', () => {
component = TestUtils.renderIntoDocument(
<ConditionalRelease.Editor env={assignmentEnv} type='foo' />
);
ok(document.contains(component.hiddenContainer().get(0)))
});
test('it removes the hidden form when unmounted', () => {
component = TestUtils.renderIntoDocument(
<ConditionalRelease.Editor env={assignmentEnv} type='foo' />
);
const removed = React.unmountComponentAtNode(React.findDOMNode(component).parentNode);
ok(removed)
notOk(document.contains(component.hiddenContainer().get(0)))
component = null; // we've already removed, skip teardown
});
test('it pops up the modal when the button is clicked', () => {
component = TestUtils.renderIntoDocument(
<ConditionalRelease.Editor env={assignmentEnv} type='foo' />
);
sinon.spy(component, 'submitForm');
TestUtils.Simulate.click(component.refs.button);
ok(component.submitForm.called);
});
test('it disables when data is dirty', () => {
component = TestUtils.renderIntoDocument(
<ConditionalRelease.Editor env={assignmentEnv} type='foo' assignmentDirty={true} />
);
notOk(component.enabled());
});
});

View File

@ -71,9 +71,9 @@ describe ConditionalRelease::Service do
it 'creates urls' do
stub_config({
protocol: 'foo', host: 'bar',
configure_defaults_app_path: 'some/path'
edit_rule_path: 'some/path'
})
expect(Service.configure_defaults_url).to eq 'foo://bar/some/path'
expect(Service.edit_rule_url).to eq 'foo://bar/some/path'
end
it 'requires feature flag to be enabled' do
@ -113,19 +113,19 @@ describe ConditionalRelease::Service do
Service.stubs(:enabled_in_context?).returns(false)
course_with_student_logged_in(active_all: true)
env = Service.env_for(@course, @student, domain: 'foo.bar')
expect(env).not_to have_key :CONDITIONAL_RELEASE_JWT
expect(env).not_to have_key :CONDITIONAL_RELEASE_ENV
end
it 'returns no jwt or env if user not specified' do
course_model
env = Service.env_for(@course)
expect(env).not_to have_key :CONDITIONAL_RELEASE_JWT
expect(env).not_to have_key :CONDITIONAL_RELEASE_ENV
end
it 'returns a jwt if everything enabled' do
it 'returns an env with jwt if everything enabled' do
course_with_student_logged_in(active_all: true)
env = Service.env_for(@course, @student, domain: 'foo.bar')
expect(env[:CONDITIONAL_RELEASE_JWT]).to eq :jwt
expect(env[:CONDITIONAL_RELEASE_ENV][:jwt]).to eq :jwt
end
it 'includes assignment data when an assignment is specified' do