From 6f08d5e42209bebeb6d0abbc5e0dfb89937ac03c Mon Sep 17 00:00:00 2001 From: Matt Lott Date: Fri, 5 Aug 2016 09:08:37 -0700 Subject: [PATCH] Add required district field to teacher trial request forms School now optional N/A placeholder on district field and don't save it if it's n/a Updating required field error UI a bit, and there is some larger refactoring needed here later. NCES phone number for district only entries will currently be a child school, will fix later. Closes #3818 --- app/core/forms.coffee | 2 +- app/locale/en.coffee | 6 +- app/styles/teachers/request-quote-view.sass | 96 ----- .../teachers/teacher-trial-requests.sass | 81 ++++ app/templates/admin/demo-requests.jade | 6 +- .../convert-to-teacher-account-view.jade | 51 +-- .../teachers/create-teacher-account-view.jade | 59 +-- .../teachers/request-quote-view.jade | 92 ++--- .../ConvertToTeacherAccountView.coffee | 62 ++- .../teachers/CreateTeacherAccountView.coffee | 74 ++-- app/views/teachers/RequestQuoteView.coffee | 59 ++- .../teachers/TeachersContactModal.coffee | 3 +- scripts/updateCloseIoLeads.js | 4 +- server/middleware/prepaids.coffee | 2 +- test/app/factories.coffee | 1 + .../ConvertToTeacherAccountView.spec.coffee | 46 +++ .../CreateTeacherAccountView.spec.coffee | 51 ++- .../teachers/RequestQuoteView.spec.coffee | 361 ++++++++++-------- 18 files changed, 630 insertions(+), 426 deletions(-) delete mode 100644 app/styles/teachers/request-quote-view.sass create mode 100644 app/styles/teachers/teacher-trial-requests.sass diff --git a/app/core/forms.coffee b/app/core/forms.coffee index b6c115ed4..c632e4beb 100644 --- a/app/core/forms.coffee +++ b/app/core/forms.coffee @@ -45,7 +45,7 @@ module.exports.applyErrorsToForm = (el, errors, warning=false) -> for error in errors if error.code is tv4.errorCodes.OBJECT_REQUIRED prop = _.last(_.string.words(error.message)) # hack - message = 'Required field' + message = $.i18n.t('common.required_field') else if error.dataPath prop = error.dataPath[1..] diff --git a/app/locale/en.coffee b/app/locale/en.coffee index 09b17cf3e..2d856e6d8 100644 --- a/app/locale/en.coffee +++ b/app/locale/en.coffee @@ -356,7 +356,7 @@ submit_patch: "Submit Patch" submit_changes: "Submit Changes" save_changes: "Save Changes" - required_field: "Required field" + required_field: "required" # {change} general: and: "and" @@ -885,7 +885,9 @@ evaluate_recommend: "Evaluate/Recommend" approve_funds: "Approve Funds" no_purchaser_role: "No role in purchase decisions" - organization_label: "Name of School/District" + district_label: "District" + district_na: "Enter N/A if not applicable" + organization_label: "School" # {change} city: "City" state: "State" country: "Country" diff --git a/app/styles/teachers/request-quote-view.sass b/app/styles/teachers/request-quote-view.sass deleted file mode 100644 index f4c61da92..000000000 --- a/app/styles/teachers/request-quote-view.sass +++ /dev/null @@ -1,96 +0,0 @@ -@import "app/styles/mixins" -@import "app/styles/bootstrap/variables" - -#request-quote-view - #site-content-area - margin: 50px 0 100px - .row - margin: 20px 0 - - #conversion-warning - margin-top: 20px - - .form-group - label - margin-bottom: 0 - - label.checkbox - font-weight: normal - - .help-block - margin: -4px 0 2px - - p - margin: 0 0 20px - - .checkbox, .checkbox-inline - input - margin-top: 8px - - #other-education-level-input - label - display: inline-block - display: inline-block - width: 200px - margin-left: 5px - - #submit-request-btn - margin-left: 10px - - // After submit (anonymous) - - h5 - margin-top: 50px - - #social-network-signups - margin: 20px 0 - button - margin-left: 10px - - .text-h1 - margin: 40px 0 30px - - -.algolia-autocomplete - width: 100%; - - .aa-input - width: 100% - - .aa-hint - color: #999 - width: 100% - - .aa-dropdown-menu - background-color: #fff - border: 1px solid #999 - border-top: none - width: 100% - - .aa-suggestion - cursor: pointer - padding: 5px 4px - border-top: 1px solid #ccc - - .school - font-family: Open Sans - font-size: 14px - line-height: 20px - font-weight: bold - - .district - font-family: Open Sans - font-size: 14px - line-height: 20px - - span - white-space: nowrap - - - .aa-suggestion.aa-cursor - background-color: #B2D7FF - - em - font-weight: bold - font-style: normal - diff --git a/app/styles/teachers/teacher-trial-requests.sass b/app/styles/teachers/teacher-trial-requests.sass new file mode 100644 index 000000000..b933f00e9 --- /dev/null +++ b/app/styles/teachers/teacher-trial-requests.sass @@ -0,0 +1,81 @@ +#create-teacher-account-view, #convert-to-teacher-account-view, #request-quote-view + .algolia-autocomplete + width: 100%; + + .aa-input + width: 100% + + .aa-hint + color: #999 + width: 100% + + .aa-dropdown-menu + background-color: #fff + border: 1px solid #999 + border-top: none + width: 100% + + .aa-suggestion + cursor: pointer + padding: 5px 4px + border-top: 1px solid #ccc + + .school + font-family: Open Sans + font-size: 14px + line-height: 20px + font-weight: bold + + .district + font-family: Open Sans + font-size: 14px + line-height: 20px + + span + white-space: nowrap + + + .aa-suggestion.aa-cursor + background-color: #B2D7FF + + em + font-weight: bold + font-style: normal + + // TODO: update form validation instead of overwriting these styles + + .control-label + font-weight: bold + width: 100% + + .error-help-block + margin-top: inherit + margin-bottom: 0px + float: right + font-size: 13px + font-style: italic + font-weight: normal + + .text-muted + float: right + font-size: 13px + font-style: italic + font-weight: normal + + .nullify-form-control + display: inherit + width: inherit + height: inherit + padding: inherit + font-size: inherit + line-height: inherit + color: inherit + vertical-align: inherit + background-color: inherit + background-image: inherit + border: inherit + border-radius: inherit + -webkit-box-shadow: inherit + box-shadow: inherit + -webkit-transition: inherit + transition: inherit diff --git a/app/templates/admin/demo-requests.jade b/app/templates/admin/demo-requests.jade index d072b64e6..ad4d251fa 100644 --- a/app/templates/admin/demo-requests.jade +++ b/app/templates/admin/demo-requests.jade @@ -30,8 +30,8 @@ block content thead tr th Created - th NCES District th School Name + th School District th.number NCES District Schools th.number NCES District Students th.number NCES School Students @@ -44,8 +44,8 @@ block content - continue; tr td.created= trialRequest.get('created').substring(0, 10) - td= trialRequest.get('properties').nces_district || '' - td= trialRequest.get('properties').organization || '' + td= trialRequest.get('properties').nces_name || trialRequest.get('properties').organization || '' + td= trialRequest.get('properties').nces_district || trialRequest.get('properties').district || '' td= trialRequest.get('properties').nces_district_schools || '' td= trialRequest.get('properties').nces_district_students || '' td= trialRequest.get('properties').nces_students || '' diff --git a/app/templates/teachers/convert-to-teacher-account-view.jade b/app/templates/teachers/convert-to-teacher-account-view.jade index 3feab53ac..ec754d4c8 100644 --- a/app/templates/teachers/convert-to-teacher-account-view.jade +++ b/app/templates/teachers/convert-to-teacher-account-view.jade @@ -28,40 +28,36 @@ block content .row.m-y-2 .col-md-offset-2.col-md-4.col-sm-6 .form-group - label.control-label(data-i18n="general.username") + span.control-label(data-i18n="general.username") input.form-control(disabled=true value=me.get('name')) .col-md-4.col-sm-6 #email-form-group.form-group - label.control-label(data-i18n="general.email") + span.control-label(data-i18n="general.email") input.form-control(name='email' disabled=true value=me.get('email')) .row.m-y-2 .col-md-offset-2.col-md-4.col-sm-6 .form-group - label.control-label(data-i18n="general.first_name") + span.control-label(data-i18n="general.first_name") input.form-control(name="firstName" value=me.get('firstName') || '') .col-md-4.col-sm-6 .form-group - label.control-label(data-i18n="general.last_name") + span.control-label(data-i18n="general.last_name") input.form-control(name="lastName" value=me.get('lastName') || '') .row.m-y-2 .col-md-offset-2.col-md-4.col-sm-6 .form-group - label.control-label + span.control-label span(data-i18n="teachers_quote.phone_number") span.spl.text-muted(data-i18n="signup.optional") - .help-block.small - em.text-info(data-i18n="teachers_quote.phone_number_help") - input.form-control(name="phoneNumber") + input.form-control(name="phoneNumber", data-i18n="[placeholder]teachers_quote.phone_number_help") .col-md-4.col-sm-6 .form-group - label.control-label(data-i18n="teachers_quote.primary_role_label") - .help-block.small - em.text-info(data-i18n="teachers_quote.role_help") + span.control-label(data-i18n="teachers_quote.primary_role_label") select.form-control(name="role") option(data-i18n="teachers_quote.role_default", , value='') option(data-i18n="courses.teacher", value="Teacher") @@ -75,30 +71,40 @@ block content .row.m-y-2 .col-md-offset-2.col-md-4.col-sm-6 .form-group - label.control-label(data-i18n="teachers_quote.organization_label") + span.control-label + span(data-i18n="teachers_quote.organization_label") + span.spl.text-muted(data-i18n="signup.optional") input.form-control#organization-control(name="organization") .col-md-4.col-sm-6 .form-group - label.control-label(data-i18n="teachers_quote.city") - input.form-control(name="city") + //- TODO: algolia and form errors both change form-control + //- TODO: District not red on validation error + span.control-label.form-control.nullify-form-control(data-i18n="teachers_quote.district_label") + input.form-control#district-control(name="district", data-i18n="[placeholder]teachers_quote.district_na") .row.m-y-2 .col-md-offset-2.col-md-4.col-sm-6 .form-group - label.control-label(data-i18n="teachers_quote.state") - input.form-control(name="state") + span.control-label(data-i18n="teachers_quote.city") + input.form-control(name="city") .col-md-4.col-sm-6 .form-group - label.control-labellabel.control-label(data-i18n="teachers_quote.country") + span.control-label(data-i18n="teachers_quote.state") + input.form-control(name="state") + + .row.m-y-2 + .col-md-offset-2.col-md-4.col-sm-6 + .form-group + span.control-labelspan.control-label(data-i18n="teachers_quote.country") input.form-control(name="country") #form-students-info .row.m-y-2 .col-md-offset-2.col-md-4 .form-group - label.control-label(data-i18n="courses.number_programming_students") + span.control-label(data-i18n="courses.number_programming_students") .help-block.small em.text-info(data-i18n="teachers_quote.num_students_help") select.form-control(name="numStudents") @@ -113,7 +119,7 @@ block content .col-md-4.col-sm-6 .form-group - label.control-label + span.control-label span(data-i18n="courses.number_total_students") span.spl.text-muted(data-i18n="signup.optional") select.form-control(name="numStudentsTotal") @@ -126,11 +132,10 @@ block content .form-group .row.m-y-2 - .col-md-offset-2.col-md-10 - label.control-label(data-i18n="teachers_quote.education_level_label") + .col-md-offset-2.col-md-4 + span.control-label(data-i18n="teachers_quote.education_level_label") .help-block.small em.text-info(data-i18n="teachers_quote.education_level_help") - .col-md-offset-2.col-md-5 .checkbox label input(type="checkbox" name="educationLevel" value="Elementary") @@ -156,7 +161,7 @@ block content #anything-else-row.row.m-y-2 .col-md-offset-2.col-md-8 - label.control-label + span.control-label span(data-i18n="teachers_quote.anything_else") span.spl.text-muted(data-i18n="signup.optional") textarea.form-control(rows=8, name="notes") diff --git a/app/templates/teachers/create-teacher-account-view.jade b/app/templates/teachers/create-teacher-account-view.jade index e7a4e940e..742b509cd 100644 --- a/app/templates/teachers/create-teacher-account-view.jade +++ b/app/templates/teachers/create-teacher-account-view.jade @@ -34,49 +34,47 @@ block content .row.m-y-2 .col-md-offset-2.col-md-4.col-sm-6 .form-group - label.control-label(data-i18n="general.username") + span.control-label(data-i18n="general.username") input.form-control(name="name") .col-md-4.col-sm-6 #email-form-group.form-group - label.control-label(data-i18n="general.email") + span.control-label(data-i18n="general.email") input.form-control(name="email") .row.m-y-2 .col-md-offset-2.col-md-4.col-sm-6 .form-group - label.control-label(data-i18n="general.first_name") + span.control-label(data-i18n="general.first_name") input.form-control(name="firstName") .col-md-4.col-sm-6 .form-group - label.control-label(data-i18n="general.last_name") + span.control-label(data-i18n="general.last_name") input.form-control(name="lastName") .row.m-y-2 .col-md-offset-2.col-md-4.col-sm-6 .form-group - label.control-label(data-i18n="general.password") + span.control-label(data-i18n="general.password") input.form-control(name="password1", type="password") .col-md-4.col-sm-6 .form-group - label.control-label(data-i18n="general.confirm_password") + span.control-label(data-i18n="general.confirm_password") input.form-control(name="password2", type="password") .row.m-y-2 .col-md-offset-2.col-md-4.col-sm-6 .form-group - label.control-label + span.control-label span(data-i18n="teachers_quote.phone_number") span.spl.text-muted(data-i18n="signup.optional") - .help-block.small - em.text-info(data-i18n="teachers_quote.phone_number_help") - input.form-control(name="phoneNumber") + input.form-control(name="phoneNumber", data-i18n="[placeholder]teachers_quote.phone_number_help") .col-md-4.col-sm-6 .form-group - label.control-label(data-i18n="teachers_quote.primary_role_label") + span.control-label(data-i18n="teachers_quote.primary_role_label") select.form-control(name="role") option(data-i18n="teachers_quote.role_default", , value='') option(data-i18n="courses.teacher", value="Teacher") @@ -90,30 +88,40 @@ block content .row.m-y-2 .col-md-offset-2.col-md-4.col-sm-6 .form-group - label.control-label(data-i18n="teachers_quote.organization_label") + span.control-label + span(data-i18n="teachers_quote.organization_label") + span.spl.text-muted(data-i18n="signup.optional") input.form-control#organization-control(name="organization") .col-md-4.col-sm-6 .form-group - label.control-label(data-i18n="teachers_quote.city") - input.form-control(name="city") + //- TODO: algolia and form errors both change form-control + //- TODO: District not red on validation error + span.control-label.form-control.nullify-form-control(data-i18n="teachers_quote.district_label") + input.form-control#district-control(name="district", data-i18n="[placeholder]teachers_quote.district_na") .row.m-y-2 .col-md-offset-2.col-md-4.col-sm-6 .form-group - label.control-label(data-i18n="teachers_quote.state") - input.form-control(name="state") + span.control-label(data-i18n="teachers_quote.city") + input.form-control(name="city") .col-md-4.col-sm-6 .form-group - label.control-labellabel.control-label(data-i18n="teachers_quote.country") + span.control-label(data-i18n="teachers_quote.state") + input.form-control(name="state") + + .row.m-y-2 + .col-md-offset-2.col-md-4.col-sm-6 + .form-group + span.control-label(data-i18n="teachers_quote.country") input.form-control(name="country") #form-students-info .row.m-y-2 .col-md-offset-2.col-md-4 .form-group - label.control-label(data-i18n="courses.number_programming_students") + span.control-label(data-i18n="courses.number_programming_students") .help-block.small em.text-info(data-i18n="teachers_quote.num_students_help") select.form-control(name="numStudents") @@ -128,7 +136,7 @@ block content .col-md-4.col-sm-6 .form-group - label.control-label + span.control-label span(data-i18n="courses.number_total_students") span.spl.text-muted(data-i18n="signup.optional") select.form-control(name="numStudentsTotal") @@ -140,14 +148,11 @@ block content option 10,000+ .form-group - .row.m-y-2 - .col-md-offset-2.col-md-10 - label.control-label(data-i18n="teachers_quote.education_level_label") - .help-block.small - em.text-info(data-i18n="teachers_quote.education_level_help") - - .col-md-offset-2.col-md-5 + .col-md-offset-2.col-md-4 + span.control-label(data-i18n="teachers_quote.education_level_label") + .help-block.small + em.text-info(data-i18n="teachers_quote.education_level_help") .checkbox label input(type="checkbox" name="educationLevel" value="Elementary") @@ -173,7 +178,7 @@ block content #anything-else-row.row.m-y-2 .col-md-offset-2.col-md-8 - label.control-label + span.control-label span(data-i18n="teachers_quote.anything_else") span.spl.text-muted(data-i18n="signup.optional") diff --git a/app/templates/teachers/request-quote-view.jade b/app/templates/teachers/request-quote-view.jade index f92a53fa3..0dffd6446 100644 --- a/app/templates/teachers/request-quote-view.jade +++ b/app/templates/teachers/request-quote-view.jade @@ -36,7 +36,7 @@ block content .row .col-md-offset-2.col-md-4.col-sm-6 .form-group - label.control-label + span.control-label span(data-i18n="general.username") span.spl.text-muted(data-i18n="signup.optional") - var name = me.get('name') || ''; @@ -44,20 +44,20 @@ block content .col-md-4.col-sm-6 .form-group - label.control-label(data-i18n="share_progress_modal.form_label") + span.control-label(data-i18n="share_progress_modal.form_label") - var email = me.get('email') || ''; input.form-control(name="email" value=email, disabled=!!email) .row .col-md-offset-2.col-md-4.col-sm-6 .form-group - label.control-label(data-i18n="general.first_name") + span.control-label(data-i18n="general.first_name") - var firstName = me.get('firstName') || ''; input.form-control(name="firstName" value=firstName, disabled=!!firstName) .col-md-4.col-sm-6 .form-group - label.control-label(data-i18n="general.last_name") + span.control-label(data-i18n="general.last_name") - var lastName = me.get('lastName') || ''; input.form-control(name="lastName" value=lastName, disabled=!!lastName) @@ -65,33 +65,30 @@ block content .row .col-md-offset-2.col-md-4.col-sm-6 .form-group - label.control-label - span(data-i18n="teachers_quote.phone_number") - .help-block.small - em.text-info(data-i18n="teachers_quote.phone_number_help") - input.form-control(name="phoneNumber") - + span.control-label(data-i18n="teachers_quote.phone_number") + input.form-control(name="phoneNumber", data-i18n="[placeholder]teachers_quote.phone_number_help") + if me.isAnonymous() .row .col-md-offset-2.col-md-4.col-sm-6 #email-form-group.form-group - label.control-label(data-i18n="general.email") + span.control-label(data-i18n="general.email") - var email = me.get('email') || ''; input.form-control(name="email" type="email", value=email, disabled=!!email) .col-md-4.col-sm-6 .form-group - label.control-label + span.control-label span(data-i18n="teachers_quote.phone_number") .help-block.small em.text-info(data-i18n="teachers_quote.phone_number_help") - input.form-control(name="phoneNumber") + input.form-control(name="phoneNumber") + - .row .col-md-offset-2.col-md-4.col-sm-6 .form-group - label.control-label(data-i18n="teachers_quote.primary_role_label") + span.control-label(data-i18n="teachers_quote.primary_role_label") select.form-control(name="role") option(data-i18n="teachers_quote.primary_role_default", , value='') option(data-i18n="courses.teacher", value="Teacher") @@ -100,45 +97,55 @@ block content option(data-i18n="teachers_quote.principal", value="Principal") option(data-i18n="teachers_quote.superintendent", value="Superintendent") option(data-i18n="teachers_quote.parent", value="Parent") - - .col-md-4.col-sm-6 + + .col-md-4.col-sm-6 .form-group - label.control-label(data-i18n="teachers_quote.purchaser_role_label") + span.control-label(data-i18n="teachers_quote.purchaser_role_label") select.form-control(name="purchaserRole") option(data-i18n="teachers_quote.purchaser_role_default", , value='') option(data-i18n="teachers_quote.influence_advocate", value="Influence/Advocate") option(data-i18n="teachers_quote.evaluate_recommend", value="Evaluate/Recommend") option(data-i18n="teachers_quote.approve_funds", value="Approve Funds") option(data-i18n="teachers_quote.no_purchaser_role", value="No role in purchase decisions") - + #form-school-info - .row + .row.m-y-2 .col-md-offset-2.col-md-4.col-sm-6 .form-group - label.control-label(data-i18n="teachers_quote.organization_label") + span.control-label + span(data-i18n="teachers_quote.organization_label") + span.spl.text-muted(data-i18n="signup.optional") input.form-control#organization-control(name="organization") .col-md-4.col-sm-6 .form-group - label.control-label(data-i18n="teachers_quote.city") - input.form-control(name="city") + //- TODO: algolia and form errors both change form-control + //- TODO: District not red on validation error + span.control-label.form-control.nullify-form-control(data-i18n="teachers_quote.district_label") + input.form-control#district-control(name="district", data-i18n="[placeholder]teachers_quote.district_na") - .row + .row.m-y-2 .col-md-offset-2.col-md-4.col-sm-6 .form-group - label.control-label(data-i18n="teachers_quote.state") - input.form-control(name="state") + span.control-label(data-i18n="teachers_quote.city") + input.form-control(name="city") .col-md-4.col-sm-6 .form-group - label.control-labellabel.control-label(data-i18n="teachers_quote.country") + span.control-label(data-i18n="teachers_quote.state") + input.form-control(name="state") + + .row.m-y-2 + .col-md-offset-2.col-md-4.col-sm-6 + .form-group + span.control-labelspan.control-label(data-i18n="teachers_quote.country") input.form-control(name="country") #form-students-info .row .col-md-offset-2.col-md-4 .form-group - label.control-label(data-i18n="courses.number_programming_students") + span.control-label(data-i18n="courses.number_programming_students") .help-block.small em.text-info(data-i18n="teachers_quote.num_students_help") select.form-control(name="numStudents") @@ -150,10 +157,10 @@ block content option 201-500 option 501-1000 option 1000+ - + .col-md-4.col-sm-6 .form-group - label.control-label(data-i18n="courses.number_total_students") + span.control-label(data-i18n="courses.number_total_students") select.form-control(name="numStudentsTotal") option(data-i18n="teachers_quote.num_students_default", value='') option 1-500 @@ -163,14 +170,11 @@ block content option 10,000+ .form-group - - .row - .col-md-offset-2.col-md-10 - label.control-label(data-i18n="teachers_quote.education_level_label") - .help-block.small - em.text-info(data-i18n="teachers_quote.education_level_help") - - .col-md-offset-2.col-md-5 + .row.m-y-2 + .col-md-offset-2.col-md-4 + span.control-label(data-i18n="teachers_quote.education_level_label") + .help-block.small + em.text-info(data-i18n="teachers_quote.education_level_help") .checkbox label input(type="checkbox" name="educationLevel" value="Elementary") @@ -196,7 +200,7 @@ block content #anything-else-row.row .col-md-offset-2.col-md-8 - label.control-label + span.control-label span(data-i18n="teachers_quote.anything_else") span.spl.text-muted(data-i18n="signup.optional") @@ -210,7 +214,7 @@ block content input(type="hidden" name="nces_students") input(type="hidden" name="nces_phone") - #buttons-row.row.text-center + #buttons-row.row.m-y-2.text-center input#submit-request-btn.btn.btn-lg.btn-primary(type="submit" data-i18n="[value]teachers_quote.title") #form-submit-success.text-center(class=showDone ? '' : 'hide') @@ -219,7 +223,7 @@ block content p span.spr(data-i18n="teachers_quote.thanks_p") a.spl(href="mailto:team@codecombat.com") team@codecombat.com - + unless me.isAnonymous() a.btn.btn-lg.btn-navy(href="/teachers/classes") span(data-i18n='teachers_quote.back_to_classes') @@ -244,17 +248,17 @@ block content .row .col-md-offset-2.col-md-4 .form-group - label.control-label(data-i18n="general.username") + span.control-label(data-i18n="general.username") input.form-control(name="name") .row .col-md-offset-2.col-md-4 .form-group - label.control-label(data-i18n="general.password") + span.control-label(data-i18n="general.password") input.form-control(name="password1", type="password") .col-md-4 .form-group - label.control-label(data-i18n="general.confirm_password") + span.control-label(data-i18n="general.confirm_password") input.form-control(name="password2", type="password") .text-center diff --git a/app/views/teachers/ConvertToTeacherAccountView.coffee b/app/views/teachers/ConvertToTeacherAccountView.coffee index ad3e3b7f5..79f0eb04a 100644 --- a/app/views/teachers/ConvertToTeacherAccountView.coffee +++ b/app/views/teachers/ConvertToTeacherAccountView.coffee @@ -7,9 +7,9 @@ errors = require 'core/errors' User = require 'models/User' ConfirmModal = require 'views/editor/modal/ConfirmModal' algolia = require 'core/services/algolia' -NCES_KEYS = ['id', 'name', 'district', 'district_id', 'district_schools', 'district_students', 'students', 'phone'] -FORM_KEY = 'request-quote-form' +DISTRICT_NCES_KEYS = ['district', 'district_id', 'district_schools', 'district_students', 'phone'] +SCHOOL_NCES_KEYS = DISTRICT_NCES_KEYS.concat(['id', 'name', 'students']) module.exports = class ConvertToTeacherAccountView extends RootView id: 'convert-to-teacher-account-view' @@ -40,13 +40,13 @@ module.exports = class ConvertToTeacherAccountView extends RootView return 'Your account has not been updated! If you continue, your changes will be lost.' invalidateNCES: -> - for key in NCES_KEYS + for key in SCHOOL_NCES_KEYS @$('input[name="nces_' + key + '"]').val '' onLoaded: -> if @trialRequests.size() and me.isTeacher() return application.router.navigate('/teachers', { trigger: true, replace: true }) - + super() afterRender: -> @@ -75,16 +75,34 @@ module.exports = class ConvertToTeacherAccountView extends RootView "
#{hr.name.value}
" + "
#{hr.district.value}, " + "#{hr.city?.value}, #{hr.state.value}
" - ]).on 'autocomplete:selected', (event, suggestion, dataset) => + @$('input[name="district"]').val suggestion.district @$('input[name="city"]').val suggestion.city @$('input[name="state"]').val suggestion.state - @$('input[name="district"]').val suggestion.district @$('input[name="country"]').val 'USA' - - for key in NCES_KEYS + for key in SCHOOL_NCES_KEYS @$('input[name="nces_' + key + '"]').val suggestion[key] + @onChangeForm() + $("#district-control").algolia_autocomplete({hint: false}, [ + source: (query, callback) -> + algolia.schoolsIndex.search(query, { hitsPerPage: 5, aroundLatLngViaIP: false }).then (answer) -> + callback answer.hits + , -> + callback [] + displayKey: 'district', + templates: + suggestion: (suggestion) -> + hr = suggestion._highlightResult + "
#{hr.district.value}, " + + "#{hr.city?.value}, #{hr.state.value}
" + ]).on 'autocomplete:selected', (event, suggestion, dataset) => + @$('input[name="organization"]').val '' # TODO: does not persist on tabbing: back to school, back to district + @$('input[name="city"]').val suggestion.city + @$('input[name="state"]').val suggestion.state + @$('input[name="country"]').val 'USA' + for key in DISTRICT_NCES_KEYS + @$('input[name="nces_' + key + '"]').val suggestion[key] @onChangeForm() onChangeForm: -> @@ -97,28 +115,35 @@ module.exports = class ConvertToTeacherAccountView extends RootView form = @$('form') attrs = forms.formToObject(form) - + trialRequestAttrs = _.cloneDeep(attrs) + + # Don't save n/a district entries, but do validate required district client-side + trialRequestAttrs = _.omit(trialRequestAttrs, 'district') if trialRequestAttrs.district?.replace(/\s/ig, '').match(/n\/a/ig) + if @$('#other-education-level-checkbox').is(':checked') val = @$('#other-education-level-input').val() - attrs.educationLevel.push(val) if val + trialRequestAttrs.educationLevel.push(val) if val forms.clearFormAlerts(form) - result = tv4.validateMultiple(attrs, formSchema) + result = tv4.validateMultiple(trialRequestAttrs, formSchema) error = false if not result.valid forms.applyErrorsToForm(form, result.errors) error = true - if not _.size(attrs.educationLevel) - forms.setErrorToProperty(form, 'educationLevel', 'Include at least one.') + if not _.size(trialRequestAttrs.educationLevel) + forms.setErrorToProperty(form, 'educationLevel', 'include at least one') + error = true + unless attrs.district + forms.setErrorToProperty(form, 'district', $.i18n.t('common.required_field')) error = true if error forms.scrollToFirstError() return - attrs['siteOrigin'] = 'convert teacher' + trialRequestAttrs['siteOrigin'] = 'convert teacher' @trialRequest = new TrialRequest({ type: 'course' - properties: attrs + properties: trialRequestAttrs }) if me.get('role') is 'student' and not me.isAnonymous() modal = new ConfirmModal({ @@ -151,15 +176,14 @@ module.exports = class ConvertToTeacherAccountView extends RootView formSchema = { type: 'object' - required: [ - 'firstName', 'lastName', 'organization', 'role', 'numStudents', 'city', 'state', 'country' - ] + required: ['firstName', 'lastName', 'role', 'numStudents', 'city', 'state', 'country'] properties: firstName: { type: 'string' } lastName: { type: 'string' } phoneNumber: { type: 'string' } role: { type: 'string' } organization: { type: 'string' } + district: { type: 'string' } city: { type: 'string' } state: { type: 'string' } country: { type: 'string' } @@ -172,5 +196,5 @@ formSchema = { notes: { type: 'string' } } -for key in NCES_KEYS +for key in SCHOOL_NCES_KEYS formSchema['nces_' + key] = type: 'string' diff --git a/app/views/teachers/CreateTeacherAccountView.coffee b/app/views/teachers/CreateTeacherAccountView.coffee index b0107e08c..8f1e103f7 100644 --- a/app/views/teachers/CreateTeacherAccountView.coffee +++ b/app/views/teachers/CreateTeacherAccountView.coffee @@ -7,9 +7,9 @@ errors = require 'core/errors' User = require 'models/User' algolia = require 'core/services/algolia' -FORM_KEY = 'request-quote-form' SIGNUP_REDIRECT = '/teachers/classes' -NCES_KEYS = ['id', 'name', 'district', 'district_id', 'district_schools', 'district_students', 'students', 'phone'] +DISTRICT_NCES_KEYS = ['district', 'district_id', 'district_schools', 'district_students', 'phone'] +SCHOOL_NCES_KEYS = DISTRICT_NCES_KEYS.concat(['id', 'name', 'students']) module.exports = class CreateTeacherAccountView extends RootView id: 'create-teacher-account-view' @@ -43,12 +43,12 @@ module.exports = class CreateTeacherAccountView extends RootView super() invalidateNCES: -> - for key in NCES_KEYS + for key in SCHOOL_NCES_KEYS @$('input[name="nces_' + key + '"]').val '' - + afterRender: -> super() - + # apply existing trial request on form properties = @trialRequest.get('properties') if properties @@ -58,7 +58,7 @@ module.exports = class CreateTeacherAccountView extends RootView otherLevel = _.first(_.difference(submittedLevels, commonLevels)) or '' @$('#other-education-level-checkbox').attr('checked', !!otherLevel) @$('#other-education-level-input').val(otherLevel) - + $("#organization-control").algolia_autocomplete({hint: false}, [ source: (query, callback) -> algolia.schoolsIndex.search(query, { hitsPerPage: 5, aroundLatLngViaIP: false }).then (answer) -> @@ -72,16 +72,34 @@ module.exports = class CreateTeacherAccountView extends RootView "
#{hr.name.value}
" + "
#{hr.district.value}, " + "#{hr.city?.value}, #{hr.state.value}
" - ]).on 'autocomplete:selected', (event, suggestion, dataset) => + @$('input[name="district"]').val suggestion.district @$('input[name="city"]').val suggestion.city @$('input[name="state"]').val suggestion.state - @$('input[name="district"]').val suggestion.district @$('input[name="country"]').val 'USA' - - for key in NCES_KEYS + for key in SCHOOL_NCES_KEYS @$('input[name="nces_' + key + '"]').val suggestion[key] + @onChangeForm() + $("#district-control").algolia_autocomplete({hint: false}, [ + source: (query, callback) -> + algolia.schoolsIndex.search(query, { hitsPerPage: 5, aroundLatLngViaIP: false }).then (answer) -> + callback answer.hits + , -> + callback [] + displayKey: 'district', + templates: + suggestion: (suggestion) -> + hr = suggestion._highlightResult + "
#{hr.district.value}, " + + "#{hr.city?.value}, #{hr.state.value}
" + ]).on 'autocomplete:selected', (event, suggestion, dataset) => + @$('input[name="organization"]').val '' # TODO: does not persist on tabbing: back to school, back to district + @$('input[name="city"]').val suggestion.city + @$('input[name="state"]').val suggestion.state + @$('input[name="country"]').val 'USA' + for key in DISTRICT_NCES_KEYS + @$('input[name="nces_' + key + '"]').val suggestion[key] @onChangeForm() onClickLoginLink: -> @@ -95,35 +113,41 @@ module.exports = class CreateTeacherAccountView extends RootView onSubmitForm: (e) -> e.preventDefault() - + # Creating Trial Request first, validate user attributes but do not use them form = @$('form') allAttrs = forms.formToObject(form) trialRequestAttrs = _.omit(allAttrs, 'name', 'password1', 'password2') - + + # Don't save n/a district entries, but do validate required district client-side + trialRequestAttrs = _.omit(trialRequestAttrs, 'district') if trialRequestAttrs.district?.replace(/\s/ig, '').match(/n\/a/ig) + if @$('#other-education-level-checkbox').is(':checked') val = @$('#other-education-level-input').val() trialRequestAttrs.educationLevel.push(val) if val - + forms.clearFormAlerts(form) - + result = tv4.validateMultiple(trialRequestAttrs, formSchema) error = false if not result.valid forms.applyErrorsToForm(form, result.errors) error = true - if not forms.validateEmail(trialRequestAttrs.email) - forms.setErrorToProperty(form, 'email', 'Invalid email.') + if not error and not forms.validateEmail(trialRequestAttrs.email) + forms.setErrorToProperty(form, 'email', 'invalid email') error = true if not _.size(trialRequestAttrs.educationLevel) - forms.setErrorToProperty(form, 'educationLevel', 'Include at least one.') + forms.setErrorToProperty(form, 'educationLevel', 'include at least one') + error = true + unless allAttrs.district + forms.setErrorToProperty(form, 'district', $.i18n.t('common.required_field')) error = true unless @gplusAttrs or @facebookAttrs if not allAttrs.password1 - forms.setErrorToProperty(form, 'password1', 'Required field') + forms.setErrorToProperty(form, 'password1', $.i18n.t('common.required_field')) error = true else if not allAttrs.password2 - forms.setErrorToProperty(form, 'password2', 'Required field') + forms.setErrorToProperty(form, 'password2', $.i18n.t('common.required_field')) error = true else if allAttrs.password1 isnt allAttrs.password2 forms.setErrorToProperty(form, 'password1', 'Password fields are not equivalent') @@ -151,7 +175,7 @@ module.exports = class CreateTeacherAccountView extends RootView .addClass('has-error') .append($("
#{userExists} ")) forms.scrollToFirstError() - else + else errors.showNotyNetworkError(arguments...) onClickEmailExistsLoginLink: -> @@ -225,7 +249,6 @@ module.exports = class CreateTeacherAccountView extends RootView @$('input[type="password"]').attr('disabled', true) @$('#gplus-logged-in-row, #social-network-signups').toggleClass('hide') - # Facebook signup onClickFacebookSignupButton: -> @@ -269,13 +292,9 @@ module.exports = class CreateTeacherAccountView extends RootView @$('#facebook-logged-in-row, #social-network-signups').toggleClass('hide') - formSchema = { type: 'object' - required: [ - 'firstName', 'lastName', 'email', 'organization', 'role', 'numStudents', 'city' - 'state', 'country' - ] + required: ['firstName', 'lastName', 'email', 'role', 'numStudents', 'city', 'state', 'country'] properties: password1: { type: 'string' } password2: { type: 'string' } @@ -286,6 +305,7 @@ formSchema = { phoneNumber: { type: 'string' } role: { type: 'string' } organization: { type: 'string' } + district: { type: 'string' } city: { type: 'string' } state: { type: 'string' } country: { type: 'string' } @@ -298,5 +318,5 @@ formSchema = { notes: { type: 'string' } } -for key in NCES_KEYS +for key in SCHOOL_NCES_KEYS formSchema['nces_' + key] = type: 'string' diff --git a/app/views/teachers/RequestQuoteView.coffee b/app/views/teachers/RequestQuoteView.coffee index b48a9d355..be34f5ad2 100644 --- a/app/views/teachers/RequestQuoteView.coffee +++ b/app/views/teachers/RequestQuoteView.coffee @@ -8,7 +8,8 @@ ConfirmModal = require 'views/editor/modal/ConfirmModal' algolia = require 'core/services/algolia' SIGNUP_REDIRECT = '/teachers' -NCES_KEYS = ['id', 'name', 'district', 'district_id', 'district_schools', 'district_students', 'students', 'phone'] +DISTRICT_NCES_KEYS = ['district', 'district_id', 'district_schools', 'district_students', 'phone'] +SCHOOL_NCES_KEYS = DISTRICT_NCES_KEYS.concat(['id', 'name', 'students']) module.exports = class RequestQuoteView extends RootView id: 'request-quote-view' @@ -46,7 +47,7 @@ module.exports = class RequestQuoteView extends RootView super() invalidateNCES: -> - for key in NCES_KEYS + for key in SCHOOL_NCES_KEYS @$('input[name="nces_' + key + '"]').val '' afterRender: -> @@ -75,16 +76,34 @@ module.exports = class RequestQuoteView extends RootView "
#{hr.name.value}
" + "
#{hr.district.value}, " + "#{hr.city?.value}, #{hr.state.value}
" - ]).on 'autocomplete:selected', (event, suggestion, dataset) => + @$('input[name="district"]').val suggestion.district @$('input[name="city"]').val suggestion.city @$('input[name="state"]').val suggestion.state - @$('input[name="district"]').val suggestion.district @$('input[name="country"]').val 'USA' - - for key in NCES_KEYS + for key in SCHOOL_NCES_KEYS @$('input[name="nces_' + key + '"]').val suggestion[key] + @onChangeRequestForm() + $("#district-control").algolia_autocomplete({hint: false}, [ + source: (query, callback) -> + algolia.schoolsIndex.search(query, { hitsPerPage: 5, aroundLatLngViaIP: false }).then (answer) -> + callback answer.hits + , -> + callback [] + displayKey: 'district', + templates: + suggestion: (suggestion) -> + hr = suggestion._highlightResult + "
#{hr.district.value}, " + + "#{hr.city?.value}, #{hr.state.value}
" + ]).on 'autocomplete:selected', (event, suggestion, dataset) => + @$('input[name="organization"]').val '' # TODO: does not persist on tabbing: back to school, back to district + @$('input[name="city"]').val suggestion.city + @$('input[name="state"]').val suggestion.state + @$('input[name="country"]').val 'USA' + for key in DISTRICT_NCES_KEYS + @$('input[name="nces_' + key + '"]').val suggestion[key] @onChangeRequestForm() onChangeRequestForm: -> @@ -96,32 +115,39 @@ module.exports = class RequestQuoteView extends RootView e.preventDefault() form = @$('#request-form') attrs = forms.formToObject(form) + trialRequestAttrs = _.cloneDeep(attrs) + + # Don't save n/a district entries, but do validate required district client-side + trialRequestAttrs = _.omit(trialRequestAttrs, 'district') if trialRequestAttrs.district?.replace(/\s/ig, '').match(/n\/a/ig) # custom other input logic if @$('#other-education-level-checkbox').is(':checked') val = @$('#other-education-level-input').val() - attrs.educationLevel.push(val) if val + trialRequestAttrs.educationLevel.push(val) if val forms.clearFormAlerts(form) requestFormSchema = if me.isAnonymous() then requestFormSchemaAnonymous else requestFormSchemaLoggedIn - result = tv4.validateMultiple(attrs, requestFormSchemaAnonymous) + result = tv4.validateMultiple(trialRequestAttrs, requestFormSchemaAnonymous) error = false if not result.valid forms.applyErrorsToForm(form, result.errors) error = true - if not forms.validateEmail(attrs.email) - forms.setErrorToProperty(form, 'email', 'Invalid email.') + if not error and not forms.validateEmail(trialRequestAttrs.email) + forms.setErrorToProperty(form, 'email', 'invalid email') error = true - if not _.size(attrs.educationLevel) - forms.setErrorToProperty(form, 'educationLevel', 'Include at least one.') + if not _.size(trialRequestAttrs.educationLevel) + forms.setErrorToProperty(form, 'educationLevel', 'include at least one') + error = true + unless attrs.district + forms.setErrorToProperty(form, 'district', $.i18n.t('common.required_field')) error = true if error forms.scrollToFirstError() return - attrs['siteOrigin'] = 'demo request' + trialRequestAttrs['siteOrigin'] = 'demo request' @trialRequest = new TrialRequest({ type: 'course' - properties: attrs + properties: trialRequestAttrs }) if me.get('role') is 'student' and not me.isAnonymous() modal = new ConfirmModal({ @@ -262,7 +288,7 @@ module.exports = class RequestQuoteView extends RootView requestFormSchemaAnonymous = { type: 'object' required: [ - 'firstName', 'lastName', 'email', 'organization', 'role', 'purchaserRole', 'numStudents', + 'firstName', 'lastName', 'email', 'role', 'purchaserRole', 'numStudents', 'numStudentsTotal', 'phoneNumber', 'city', 'state', 'country'] properties: firstName: { type: 'string' } @@ -273,6 +299,7 @@ requestFormSchemaAnonymous = { role: { type: 'string' } purchaserRole: { type: 'string' } organization: { type: 'string' } + district: { type: 'string' } city: { type: 'string' } state: { type: 'string' } country: { type: 'string' } @@ -285,7 +312,7 @@ requestFormSchemaAnonymous = { notes: { type: 'string' }, } -for key in NCES_KEYS +for key in SCHOOL_NCES_KEYS requestFormSchemaAnonymous['nces_' + key] = type: 'string' # same form, but add username input diff --git a/app/views/teachers/TeachersContactModal.coffee b/app/views/teachers/TeachersContactModal.coffee index 0d3dbb0f8..1f86a1cc9 100644 --- a/app/views/teachers/TeachersContactModal.coffee +++ b/app/views/teachers/TeachersContactModal.coffee @@ -34,7 +34,8 @@ module.exports = class TeachersContactModal extends ModalView message = """ Hi CodeCombat! I want to learn more about the Classroom experience and get licenses so that my students can access Computer Science 2 and on. - Name of School/District: #{props.organization or ''} + Name of School #{props.nces_name or props.organization or ''} + Name of District: #{props.nces_district or props.district or ''} Role: #{props.role or ''} Phone Number: #{props.phoneNumber or ''} """ diff --git a/scripts/updateCloseIoLeads.js b/scripts/updateCloseIoLeads.js index 1fcc5209a..6a1ca5d9f 100644 --- a/scripts/updateCloseIoLeads.js +++ b/scripts/updateCloseIoLeads.js @@ -16,7 +16,7 @@ if (process.argv.length !== 10) { // TODO: Cleanup country/status lookup code // Save as custom fields instead of user-specific lead notes (also saving nces_ props) -const commonTrialProperties = ['organization', 'city', 'state', 'country']; +const commonTrialProperties = ['organization', 'district', 'city', 'state', 'country']; // Old properties which are deprecated or moved const customFieldsToRemove = [ @@ -327,7 +327,7 @@ function findCocoLeads(done) { if (!trialRequest.properties || !trialRequest.properties.email) continue; const email = trialRequest.properties.email.toLowerCase(); emails.push(email); - const name = trialRequest.properties.nces_name || trialRequest.properties.organization || trialRequest.properties.school || email; + const name = trialRequest.properties.nces_name || trialRequest.properties.organization || trialRequest.properties.school || trialRequest.properties.district || trialRequest.properties.nces_district || email; if (!leads[name]) leads[name] = new CocoLead(name); leads[name].addTrialRequest(email, trialRequest); emailLeadMap[email] = leads[name]; diff --git a/server/middleware/prepaids.coffee b/server/middleware/prepaids.coffee index 11fc9bde2..4b8b625fc 100644 --- a/server/middleware/prepaids.coffee +++ b/server/middleware/prepaids.coffee @@ -138,7 +138,7 @@ module.exports = trialRequests = yield TrialRequest.find({$and: [{type: 'course'}, {applicant: {$in: userIDs}}]}, {applicant: 1, properties: 1}).lean() schoolPrepaidsMap = {} for trialRequest in trialRequests - school = trialRequest.properties?.organization ? trialRequest.properties?.school + school = trialRequest.properties?.nces_name ? trialRequest.properties?.organization ? trialRequest.properties?.school continue unless school if userPrepaidsMap[trialRequest.applicant.valueOf()]?.length > 0 schoolPrepaidsMap[school] ?= [] diff --git a/test/app/factories.coffee b/test/app/factories.coffee index e23bd0630..88c5a9dcb 100644 --- a/test/app/factories.coffee +++ b/test/app/factories.coffee @@ -184,6 +184,7 @@ module.exports = { email: 'an@email.com' phoneNumber: '555-555-5555' organization: 'Greendale' + district: 'Green District' } }, attrs) } diff --git a/test/app/views/teachers/ConvertToTeacherAccountView.spec.coffee b/test/app/views/teachers/ConvertToTeacherAccountView.spec.coffee index d853030c7..8a36b4782 100644 --- a/test/app/views/teachers/ConvertToTeacherAccountView.spec.coffee +++ b/test/app/views/teachers/ConvertToTeacherAccountView.spec.coffee @@ -31,6 +31,7 @@ describe 'ConvertToTeacherAccountView (/teachers/update-account)', -> phoneNumber: '555-555-5555' role: 'Teacher' organization: 'School' + district: 'District' city: 'Springfield' state: 'AA' country: 'asdf' @@ -168,3 +169,48 @@ describe 'ConvertToTeacherAccountView (/teachers/update-account)', -> }) expect(me.get('role')).toBe(successForm.role.toLowerCase()) + describe 'submitting the form without school', -> + beforeEach -> + view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented + form = view.$('form') + formData = _.omit(successForm, ['organization']) + forms.objectToForm(form, formData) + form.submit() + + it 'submits a trial request, which does not include school setting', -> + request = jasmine.Ajax.requests.mostRecent() + expect(request.url).toBe('/db/trial.request') + expect(request.method).toBe('POST') + attrs = JSON.parse(request.params) + expect(attrs.properties?.organization).toBeUndefined() + expect(attrs.properties?.district).toEqual('District') + + describe 'submitting the form without district', -> + beforeEach -> + view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented + form = view.$('form') + formData = _.omit(successForm, ['district']) + forms.objectToForm(form, formData) + form.submit() + + it 'displays a validation error on district and not school', -> + expect(view.$('#organization-control').parent().hasClass('has-error')).toEqual(false) + expect(view.$('#district-control').parent().hasClass('has-error')).toEqual(true) + + describe 'submitting the form district set to n/a', -> + beforeEach -> + view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented + form = view.$('form') + formData = _.omit(successForm, ['organization']) + formData.district = 'N/A' + forms.objectToForm(form, formData) + form.submit() + + it 'submits a trial request, which does not include district setting', -> + expect(view.$('#organization-control').parent().hasClass('has-error')).toEqual(false) + expect(view.$('#district-control').parent().hasClass('has-error')).toEqual(false) + request = jasmine.Ajax.requests.mostRecent() + expect(request.url).toBe('/db/trial.request') + expect(request.method).toBe('POST') + attrs = JSON.parse(request.params) + expect(attrs.properties?.district).toBeUndefined() diff --git a/test/app/views/teachers/CreateTeacherAccountView.spec.coffee b/test/app/views/teachers/CreateTeacherAccountView.spec.coffee index ee8903535..20d132651 100644 --- a/test/app/views/teachers/CreateTeacherAccountView.spec.coffee +++ b/test/app/views/teachers/CreateTeacherAccountView.spec.coffee @@ -33,6 +33,7 @@ describe 'CreateTeacherAccountView', -> phoneNumber: '555-555-5555' role: 'Teacher' organization: 'School' + district: 'District' city: 'Springfield' state: 'AA' country: 'asdf' @@ -219,6 +220,8 @@ describe 'CreateTeacherAccountView', -> expect(attrs.password2).toBeUndefined() expect(attrs.name).toBeUndefined() expect(attrs.properties?.siteOrigin).toBe('create teacher') + expect(attrs.properties?.organization).toEqual('School') + expect(attrs.properties?.district).toEqual('District') describe 'after saving the new trial request', -> beforeEach -> @@ -266,5 +269,49 @@ describe 'CreateTeacherAccountView', -> spyOn(view, 'openModalView') view.$('#email-form-group .login-link').click() expect(view.openModalView).toHaveBeenCalled() - - \ No newline at end of file + + describe 'submitting the form without school', -> + beforeEach -> + view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented + form = view.$('form') + formData = _.omit(successForm, ['organization']) + forms.objectToForm(form, formData) + form.submit() + + it 'submits a trial request, which does not include school setting', -> + request = jasmine.Ajax.requests.mostRecent() + expect(request.url).toBe('/db/trial.request') + expect(request.method).toBe('POST') + attrs = JSON.parse(request.params) + expect(attrs.properties?.organization).toBeUndefined() + expect(attrs.properties?.district).toEqual('District') + + describe 'submitting the form without district', -> + beforeEach -> + view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented + form = view.$('form') + formData = _.omit(successForm, ['district']) + forms.objectToForm(form, formData) + form.submit() + + it 'displays a validation error on district and not school', -> + expect(view.$('#organization-control').parent().hasClass('has-error')).toEqual(false) + expect(view.$('#district-control').parent().hasClass('has-error')).toEqual(true) + + describe 'submitting the form district set to n/a', -> + beforeEach -> + view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented + form = view.$('form') + formData = _.omit(successForm, ['organization']) + formData.district = 'N/A' + forms.objectToForm(form, formData) + form.submit() + + it 'submits a trial request, which does not include district setting', -> + expect(view.$('#organization-control').parent().hasClass('has-error')).toEqual(false) + expect(view.$('#district-control').parent().hasClass('has-error')).toEqual(false) + request = jasmine.Ajax.requests.mostRecent() + expect(request.url).toBe('/db/trial.request') + expect(request.method).toBe('POST') + attrs = JSON.parse(request.params) + expect(attrs.properties?.district).toBeUndefined() diff --git a/test/app/views/teachers/RequestQuoteView.spec.coffee b/test/app/views/teachers/RequestQuoteView.spec.coffee index 73d2c3386..060d50375 100644 --- a/test/app/views/teachers/RequestQuoteView.spec.coffee +++ b/test/app/views/teachers/RequestQuoteView.spec.coffee @@ -2,16 +2,17 @@ RequestQuoteView = require 'views/teachers/RequestQuoteView' forms = require 'core/forms' describe 'RequestQuoteView', -> - + view = null - - successFormValues = { + + successForm = { firstName: 'A' lastName: 'B' email: 'C@D.com' phoneNumber: '555-555-5555' role: 'Teacher' organization: 'School' + district: 'District' city: 'Springfield' state: 'AA' country: 'asdf' @@ -20,10 +21,10 @@ describe 'RequestQuoteView', -> purchaserRole: 'Approve Funds' educationLevel: ['Middle'] } - + isSubmitRequest = (r) -> _.string.startsWith(r.url, '/db/trial.request') and r.method is 'POST' - describe 'when user is anonymous and has an existing trial request', -> + describe 'when an anonymous user', -> beforeEach (done) -> me.clear() me.set('_id', '1234') @@ -32,184 +33,220 @@ describe 'RequestQuoteView', -> view = new RequestQuoteView() view.render() jasmine.demoEl(view.$el) - - request = jasmine.Ajax.requests.mostRecent() - request.respondWith({ - status: 200 - responseText: JSON.stringify([{ - _id: '1' - properties: { - firstName: 'First' - lastName: 'Last' - } - }]) - }) - _.defer done # Let SuperModel finish - - it 'shows request received', -> - expect(view.$('#request-form').hasClass('hide')).toBe(true) - expect(view.$('#form-submit-success').hasClass('hide')).toBe(false) - - - describe 'when user is signed in and has an existing trial request', -> - beforeEach (done) -> - me.clear() - me.set('_id', '1234') - me._revertAttributes = {} - spyOn(me, 'isAnonymous').and.returnValue(false) - view = new RequestQuoteView() - view.render() - jasmine.demoEl(view.$el) - - request = jasmine.Ajax.requests.mostRecent() - request.respondWith({ - status: 200 - responseText: JSON.stringify([{ - _id: '1' - properties: { - firstName: 'First' - lastName: 'Last' - } - }]) - }) _.defer done # Let SuperModel finish - it 'shows form with data from the most recent request', -> - expect(view.$('input[name="firstName"]').val()).toBe('First') - - describe 'when a user is anonymous and does NOT have an existing trial request', -> - beforeEach (done) -> - me.clear() - me.set('_id', '1234') - me._revertAttributes = {} - spyOn(me, 'isAnonymous').and.returnValue(true) - view = new RequestQuoteView() - view.render() - jasmine.demoEl(view.$el) - - request = jasmine.Ajax.requests.mostRecent() - request.respondWith({ - status: 200 - responseText: '[]' - }) - _.defer done # Let SuperModel finish - - describe 'when the form is unchanged', -> - it 'does not prevent navigating away', -> - expect(_.result(view, 'onLeaveMessage')).toBeFalsy() - - describe 'when the form has changed but is not submitted', -> - beforeEach -> - view.$el.find('#request-form').trigger('change') - - it 'prevents navigating away', -> - expect(_.result(view, 'onLeaveMessage')).toBeTruthy() - - describe 'on successful form submit', -> - beforeEach -> - view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented - forms.objectToForm(view.$el, successFormValues) - view.$('#request-form').submit() - @submitRequest = _.last(jasmine.Ajax.requests.filter(isSubmitRequest)) - @submitRequest.respondWith({ - status: 201 - responseText: JSON.stringify(_.extend({_id: 'a'}, successFormValues)) + describe 'has an existing trial request', -> + beforeEach (done) -> + request = jasmine.Ajax.requests.mostRecent() + request.respondWith({ + status: 200 + responseText: JSON.stringify([{ + _id: '1' + properties: { + firstName: 'First' + lastName: 'Last' + } + }]) }) + _.defer done # Let SuperModel finish - it 'does not prevent navigating away', -> - expect(_.result(view, 'onLeaveMessage')).toBeFalsy() - - it 'creates a new trial request', -> - expect(@submitRequest).toBeTruthy() - expect(@submitRequest.method).toBe('POST') - attrs = JSON.parse(@submitRequest.params) - expect(attrs.properties?.siteOrigin).toBe('demo request') - - it 'sets the user\'s role to the one they chose', -> - request = _.last(jasmine.Ajax.requests.filter((r) -> _.string.startsWith(r.url, '/db/user'))) - expect(request).toBeTruthy() - expect(request.method).toBe('PUT') - expect(JSON.parse(request.params).role).toBe('teacher') - - it 'shows a signup form', -> - expect(view.$('#form-submit-success').hasClass('hide')).toBe(false) + it 'shows request received', -> expect(view.$('#request-form').hasClass('hide')).toBe(true) - - describe 'signup form', -> + expect(view.$('#form-submit-success').hasClass('hide')).toBe(false) + + describe 'does NOT have an existing trial request', -> + beforeEach (done) -> + request = jasmine.Ajax.requests.mostRecent() + request.respondWith({ + status: 200 + responseText: '[]' + }) + _.defer done # Let SuperModel finish + + describe 'when the form is unchanged', -> + it 'does not prevent navigating away', -> + expect(_.result(view, 'onLeaveMessage')).toBeFalsy() + + describe 'when the form has changed but is not submitted', -> beforeEach -> - application.facebookHandler.fakeAPI() - application.gplusHandler.fakeAPI() - - it 'fills the username field with the given first and last names', -> - expect(view.$('input[name="name"]').val()).toBe('A B') - - it 'includes a facebook button which will sign them in immediately', -> - view.$('#facebook-signup-btn').click() - request = jasmine.Ajax.requests.mostRecent() - expect(request.method).toBe('PUT') - expect(request.url).toBe('/db/user?facebookID=abcd&facebookAccessToken=1234') - - it 'includes a gplus button which will sign them in immediately', -> - view.$('#gplus-signup-btn').click() - request = jasmine.Ajax.requests.mostRecent() - expect(request.method).toBe('PUT') - expect(request.url).toBe('/db/user?gplusID=abcd&gplusAccessToken=1234') - - it 'can sign them up with username and password', -> - form = view.$('#signup-form') - forms.objectToForm(form, { - password1: 'asdf' - password2: 'asdf' - name: 'some name' + view.$el.find('#request-form').trigger('change') + + it 'prevents navigating away', -> + expect(_.result(view, 'onLeaveMessage')).toBeTruthy() + + describe 'on successful form submit', -> + beforeEach -> + view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented + forms.objectToForm(view.$el, successForm) + view.$('#request-form').submit() + @submitRequest = _.last(jasmine.Ajax.requests.filter(isSubmitRequest)) + @submitRequest.respondWith({ + status: 201 + responseText: JSON.stringify(_.extend({_id: 'a'}, successForm)) }) - form.submit() - request = jasmine.Ajax.requests.mostRecent() + + it 'does not prevent navigating away', -> + expect(_.result(view, 'onLeaveMessage')).toBeFalsy() + + it 'creates a new trial request', -> + expect(@submitRequest).toBeTruthy() + expect(@submitRequest.method).toBe('POST') + attrs = JSON.parse(@submitRequest.params) + expect(attrs.properties?.siteOrigin).toBe('demo request') + + it 'sets the user\'s role to the one they chose', -> + request = _.last(jasmine.Ajax.requests.filter((r) -> _.string.startsWith(r.url, '/db/user'))) + expect(request).toBeTruthy() expect(request.method).toBe('PUT') - expect(request.url).toBe('/db/user/1234') - - describe 'when an anonymous user tries to submit a request with an existing user\'s email', -> - + expect(JSON.parse(request.params).role).toBe('teacher') + + it 'shows a signup form', -> + expect(view.$('#form-submit-success').hasClass('hide')).toBe(false) + expect(view.$('#request-form').hasClass('hide')).toBe(true) + + describe 'signup form', -> + beforeEach -> + application.facebookHandler.fakeAPI() + application.gplusHandler.fakeAPI() + + it 'fills the username field with the given first and last names', -> + expect(view.$('input[name="name"]').val()).toBe('A B') + + it 'includes a facebook button which will sign them in immediately', -> + view.$('#facebook-signup-btn').click() + request = jasmine.Ajax.requests.mostRecent() + expect(request.method).toBe('PUT') + expect(request.url).toBe('/db/user?facebookID=abcd&facebookAccessToken=1234') + + it 'includes a gplus button which will sign them in immediately', -> + view.$('#gplus-signup-btn').click() + request = jasmine.Ajax.requests.mostRecent() + expect(request.method).toBe('PUT') + expect(request.url).toBe('/db/user?gplusID=abcd&gplusAccessToken=1234') + + it 'can sign them up with username and password', -> + form = view.$('#signup-form') + forms.objectToForm(form, { + password1: 'asdf' + password2: 'asdf' + name: 'some name' + }) + form.submit() + request = jasmine.Ajax.requests.mostRecent() + expect(request.method).toBe('PUT') + expect(request.url).toBe('/db/user/1234') + + describe 'tries to submit a request with an existing user\'s email', -> beforeEach -> - forms.objectToForm(view.$el, successFormValues) + forms.objectToForm(view.$el, successForm) view.$('#request-form').submit() @submitRequest = _.last(jasmine.Ajax.requests.filter(isSubmitRequest)) @submitRequest.respondWith({ status: 409 responseText: '{}' }) - + it 'shows an error that the email already exists', -> expect(view.$('#email-form-group').hasClass('has-error')).toBe(true) expect(view.$('#email-form-group .error-help-block').length).toBe(1) - - describe 'when user is signed in and has role "student"', -> + + describe 'submits the form without school', -> + beforeEach -> + view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented + form = view.$('#request-form') + formData = _.omit(successForm, ['organization']) + forms.objectToForm(form, formData) + form.submit() + + it 'submits a trial request, which does not include school setting', -> + request = jasmine.Ajax.requests.mostRecent() + expect(request.url).toBe('/db/trial.request') + expect(request.method).toBe('POST') + attrs = JSON.parse(request.params) + expect(attrs.properties?.organization).toBeUndefined() + expect(attrs.properties?.district).toEqual('District') + + describe 'submits the form without district', -> + beforeEach -> + view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented + form = view.$('#request-form') + formData = _.omit(successForm, ['district']) + forms.objectToForm(form, formData) + form.submit() + + it 'displays a validation error on district and not school', -> + expect(view.$('#organization-control').parent().hasClass('has-error')).toEqual(false) + expect(view.$('#district-control').parent().hasClass('has-error')).toEqual(true) + + describe 'submits form with district set to n/a', -> + beforeEach -> + view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented + form = view.$('#request-form') + formData = _.omit(successForm, ['organization']) + formData.district = 'N/A' + forms.objectToForm(form, formData) + form.submit() + + it 'submits a trial request, which does not include district setting', -> + expect(view.$('#organization-control').parent().hasClass('has-error')).toEqual(false) + expect(view.$('#district-control').parent().hasClass('has-error')).toEqual(false) + request = jasmine.Ajax.requests.mostRecent() + expect(request.url).toBe('/db/trial.request') + expect(request.method).toBe('POST') + attrs = JSON.parse(request.params) + expect(attrs.properties?.district).toBeUndefined() + + describe 'when a signed in user', -> beforeEach (done) -> - me.set('role', 'student') - me.set('name', 'Some User') + me.clear() + me.set('_id', '1234') + me._revertAttributes = {} spyOn(me, 'isAnonymous').and.returnValue(false) view = new RequestQuoteView() view.render() jasmine.demoEl(view.$el) - - request = jasmine.Ajax.requests.mostRecent() - request.respondWith({ status: 200, responseText: '[]'}) _.defer done # Let SuperModel finish - - it 'shows a conversion warning', -> - expect(view.$('#conversion-warning').length).toBe(1) - - it 'requires confirmation to submit the form', -> - form = view.$('#request-form') - forms.objectToForm(form, successFormValues) - spyOn(view, 'openModalView') - form.submit() - expect(view.openModalView).toHaveBeenCalled() - - submitRequest = _.last(jasmine.Ajax.requests.filter(isSubmitRequest)) - expect(submitRequest).toBeFalsy() - confirmModal = view.openModalView.calls.argsFor(0)[0] - confirmModal.trigger 'confirm' - submitRequest = _.last(jasmine.Ajax.requests.filter(isSubmitRequest)) - expect(submitRequest).toBeTruthy() - - \ No newline at end of file + + describe 'has an existing trial request', -> + beforeEach (done) -> + request = jasmine.Ajax.requests.mostRecent() + request.respondWith({ + status: 200 + responseText: JSON.stringify([{ + _id: '1' + properties: { + firstName: 'First' + lastName: 'Last' + } + }]) + }) + _.defer done # Let SuperModel finish + + it 'shows form with data from the most recent request', -> + expect(view.$('input[name="firstName"]').val()).toBe('First') + + describe 'has role "student"', -> + beforeEach (done) -> + me.clear() + me.set('role', 'student') + me.set('name', 'Some User') + request = jasmine.Ajax.requests.mostRecent() + request.respondWith({ status: 200, responseText: '[]'}) + _.defer done # Let SuperModel finish + + it 'shows a conversion warning', -> + expect(view.$('#conversion-warning').length).toBe(1) + + it 'requires confirmation to submit the form', -> + form = view.$('#request-form') + forms.objectToForm(form, successForm) + spyOn(view, 'openModalView') + form.submit() + expect(view.openModalView).toHaveBeenCalled() + + submitRequest = _.last(jasmine.Ajax.requests.filter(isSubmitRequest)) + expect(submitRequest).toBeFalsy() + confirmModal = view.openModalView.calls.argsFor(0)[0] + confirmModal.trigger 'confirm' + submitRequest = _.last(jasmine.Ajax.requests.filter(isSubmitRequest)) + expect(submitRequest).toBeTruthy()