mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-03-14 07:00:01 -04:00
Merge branch 'master' of github.com:codecombat/codecombat
This commit is contained in:
commit
8d4d617069
33 changed files with 5846 additions and 3633 deletions
|
@ -11,7 +11,7 @@
|
|||
learn_more: "Learn more"
|
||||
classroom_in_a_box: "A classroom in-a-box for teaching computer science."
|
||||
codecombat_is: "CodeCombat is a platform <strong>for students</strong> to learn computer science while playing through a real game."
|
||||
our_courses: "Our courses have been specifically playtested to <strong>excel in the classroom</strong>, even by teachers with little to no prior programming experience."
|
||||
our_courses: "Our courses have been specifically playtested <strong>to excel in the classroom</strong>, even by teachers with little to no prior programming experience."
|
||||
top_screenshots_hint: "Students write code and see their changes update in real-time"
|
||||
designed_with: "Designed with teachers in mind"
|
||||
real_code: "Real, typed code"
|
||||
|
@ -78,6 +78,7 @@
|
|||
home: "Home"
|
||||
contribute: "Contribute"
|
||||
legal: "Legal"
|
||||
privacy: "Privacy"
|
||||
about: "About"
|
||||
contact: "Contact"
|
||||
twitter_follow: "Follow"
|
||||
|
|
|
@ -48,6 +48,7 @@ module.exports =
|
|||
'hi': { nativeDescription: 'मानक हिन्दी', englishDescription: 'Hindi' }
|
||||
'ms': { nativeDescription: 'Bahasa Melayu', englishDescription: 'Bahasa Malaysia' }
|
||||
'my': { nativeDescription: 'မြန်မာစကား', englishDescription: 'Myanmar language' }
|
||||
'nl': { nativeDescription: 'Nederlands', englishDescription: 'Dutch' }
|
||||
'nl-BE': { nativeDescription: 'Nederlands (België)', englishDescription: 'Dutch (Belgium)' }
|
||||
'nl-NL': { nativeDescription: 'Nederlands (Nederland)', englishDescription: 'Dutch (Netherlands)' }
|
||||
'ja': { nativeDescription: '日本語', englishDescription: 'Japanese' }
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
2057
app/locale/nl.coffee
Normal file
2057
app/locale/nl.coffee
Normal file
File diff suppressed because it is too large
Load diff
|
@ -310,16 +310,8 @@ block content
|
|||
li.small(data-i18n="about.jobs_benefit_7")
|
||||
.col-sm-6.col-md-5.col-md-offset-1.col-lg-4.col-lg-offset-0
|
||||
.job-listing
|
||||
h5 Curriculum Specialist
|
||||
.text-center
|
||||
small.label
|
||||
| San Francisco/Remote • Contract
|
||||
p.small We're looking for an enthusiastic educator with hands-on experience teaching computer science who can help us expand our curriculum offerings.
|
||||
p.small This contractor will focus on empowering teachers who have little to no prior computer science knowledge with the tools to lead students through CodeCombat’s courses.
|
||||
a.job-link.btn.btn-lg.btn-navy(href="https://jobs.lever.co/codecombat/2d157b31-0d79-4878-a1d9-e5cb2d25d656" rel="external")
|
||||
span(data-i18n="about.learn_more")
|
||||
//- h5 (No Open Roles)
|
||||
//- p.small Check back later for updates on new positions at CodeCombat.
|
||||
h5 (No Open Roles)
|
||||
p.small Check back later for updates on new positions at CodeCombat.
|
||||
.col-sm-6.col-md-5.col-lg-4
|
||||
.job-listing
|
||||
h5(data-i18n="about.jobs_custom_title")
|
||||
|
|
|
@ -92,11 +92,13 @@ mixin accountLinks
|
|||
li
|
||||
a(href="/about", data-i18n="nav.about")
|
||||
li
|
||||
a(href="/Careers", data-i18n="nav.jobs")
|
||||
a(href="/about#jobs", data-i18n="nav.jobs")
|
||||
li
|
||||
a(href="http://blog.codecombat.com/", data-i18n="nav.blog")
|
||||
li
|
||||
a(href="/legal", data-i18n="nav.legal")
|
||||
li
|
||||
a(href="/privacy", data-i18n="nav.privacy")
|
||||
|
||||
.col-sm-3
|
||||
ul.list-unstyled
|
||||
|
@ -128,9 +130,7 @@ mixin accountLinks
|
|||
li
|
||||
strong(data-i18n="nav.support")
|
||||
li
|
||||
a(href="https://discourse.codecombat.com/t/faq-check-before-posting/1027", data-i18n="nav.faqs")
|
||||
li
|
||||
a(tabindex=-1, data-toggle="coco-modal", data-target="core/ContactModal", data-i18n="nav.contact")
|
||||
a.contact-modal(tabindex=-1, data-i18n="nav.contact")
|
||||
li
|
||||
a(href="https://www.facebook.com/codecombat", data-i18n="nav.facebook")
|
||||
li
|
||||
|
|
|
@ -63,7 +63,7 @@ block footer
|
|||
|
||||
#footer-links
|
||||
a(href="/about", data-i18n="nav.about")
|
||||
a(tabindex=-1, data-toggle="coco-modal", data-target="core/ContactModal", data-i18n="nav.contact")
|
||||
a.contact-modal(tabindex=-1, data-i18n="nav.contact")
|
||||
a(href='http://blog.codecombat.com/', data-i18n="nav.blog")
|
||||
a(href='https://jobs.lever.co/codecombat', tabindex=-1, data-i18n="nav.careers")
|
||||
a(href='/legal', tabindex=-1, data-i18n="nav.legal")
|
||||
|
|
|
@ -60,7 +60,7 @@
|
|||
li
|
||||
a(href=view.forumLink(), data-i18n="nav.forum")
|
||||
li
|
||||
a(tabindex=-1, data-toggle="coco-modal", data-target="core/ContactModal", data-i18n="nav.contact")
|
||||
a.contact-modal(tabindex=-1, data-i18n="nav.contact")
|
||||
li
|
||||
a(href='/community', data-i18n="nav.community")
|
||||
|
||||
|
|
|
@ -120,7 +120,7 @@ block header
|
|||
li
|
||||
a(href='http://discourse.codecombat.com/category/artisan', data-i18n="nav.forum", target="_blank") Forum
|
||||
li
|
||||
a(data-toggle="coco-modal", data-target="core/ContactModal", data-i18n="nav.contact") Email
|
||||
a.contact-modal(data-i18n="nav.contact")
|
||||
|
||||
block outer_content
|
||||
.outer-content
|
||||
|
|
|
@ -71,7 +71,7 @@ block header
|
|||
li
|
||||
a(href='http://discourse.codecombat.com/category/artisan', data-i18n="nav.forum", target="_blank") Forum
|
||||
li
|
||||
a(data-toggle="coco-modal", data-target="core/ContactModal", data-i18n="nav.contact") Email
|
||||
a.contact-modal(data-i18n="nav.contact")
|
||||
|
||||
block outer_content
|
||||
.outer-content
|
||||
|
|
|
@ -37,7 +37,7 @@ block header
|
|||
li
|
||||
a(href='http://discourse.codecombat.com/category/diplomat', data-i18n="nav.forum", target="_blank") Forum
|
||||
li
|
||||
a(data-toggle="coco-modal", data-target="core/ContactModal", data-i18n="nav.contact") Email
|
||||
a.contact-modal(data-i18n="nav.contact")
|
||||
|
||||
block outer_content
|
||||
.outer-content.container-fluid
|
||||
|
|
|
@ -70,5 +70,5 @@ if view.showAds()
|
|||
if !me.get('anonymous')
|
||||
#play-footer(class=me.isPremium() ? "premium" : "")
|
||||
p(class='footer-link-text').picoctf-hide
|
||||
a.contact-link(title='Send CodeCombat a message', tabindex=-1, data-i18n="nav.contact") Contact
|
||||
a.contact-link(title='Send CodeCombat a message', tabindex=-1, data-i18n="nav.contact")
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ require("locale/pt-PT")
|
|||
require("locale/pl")
|
||||
require("locale/it")
|
||||
require("locale/tr")
|
||||
require("locale/nl")
|
||||
require("locale/nl-BE")
|
||||
require("locale/nl-NL")
|
||||
require("locale/fa")
|
||||
|
|
|
@ -21,6 +21,7 @@ module.exports = class CocoView extends Backbone.View
|
|||
'click #loading-error .login-btn': 'onClickLoadingErrorLoginButton'
|
||||
'click #loading-error #create-account-btn': 'onClickLoadingErrorCreateAccountButton'
|
||||
'click #loading-error #logout-btn': 'onClickLoadingErrorLogoutButton'
|
||||
'click .contact-modal': 'onClickContactModal'
|
||||
|
||||
subscriptions: {}
|
||||
shortcuts: {}
|
||||
|
@ -176,6 +177,16 @@ module.exports = class CocoView extends Backbone.View
|
|||
msg = $.i18n.t 'loading_error.connection_failure', defaultValue: 'Connection failed.'
|
||||
noty text: msg, layout: 'center', type: 'error', killer: true, timeout: 3000
|
||||
|
||||
onClickContactModal: (e) ->
|
||||
if me.isTeacher()
|
||||
if application.isProduction()
|
||||
window.Intercom?('show')
|
||||
else
|
||||
alert('Teachers, Intercom widget only available in production.')
|
||||
else
|
||||
ContactModal = require 'views/core/ContactModal'
|
||||
@openModalView(new ContactModal())
|
||||
|
||||
onClickLoadingErrorLoginButton: (e) ->
|
||||
e.stopPropagation() # Backbone subviews and superviews will handle this call repeatedly otherwise
|
||||
AuthModal = require 'views/core/AuthModal'
|
||||
|
|
|
@ -63,7 +63,6 @@ module.exports = class CoursesView extends RootView
|
|||
@supermodel.loadModel(@hero, 'hero')
|
||||
@listenTo @hero, 'all', ->
|
||||
@render()
|
||||
window.tracker?.trackEvent 'Students Loaded', category: 'Students', ['Mixpanel']
|
||||
|
||||
afterInsert: ->
|
||||
super()
|
||||
|
|
|
@ -5,4 +5,4 @@ module.exports = class RestrictedToStudentsView extends RootView
|
|||
template: require 'templates/courses/restricted-to-students-view'
|
||||
|
||||
initialize: ->
|
||||
window.tracker?.trackEvent 'Restricted To Students Loaded', category: 'Students', ['Mixpanel']
|
||||
window.tracker?.trackEvent 'Restricted To Students Loaded', category: 'Students', []
|
||||
|
|
|
@ -58,14 +58,14 @@ module.exports = class HintsView extends CocoView
|
|||
@state.set({ hintsTitle, hint: @hintsState.getHint(index) })
|
||||
|
||||
onClickNextButton: ->
|
||||
window.tracker?.trackEvent 'Hints Next Clicked', category: 'Students', levelSlug: @level.get('slug'), hintCount: @hintsState.get('hints')?.length ? 0, hintCurrent: @state.get('hintIndex'), ['Mixpanel']
|
||||
window.tracker?.trackEvent 'Hints Next Clicked', category: 'Students', levelSlug: @level.get('slug'), hintCount: @hintsState.get('hints')?.length ? 0, hintCurrent: @state.get('hintIndex'), []
|
||||
max = @hintsState.get('total') - 1
|
||||
@state.set('hintIndex', Math.min(@state.get('hintIndex') + 1, max))
|
||||
@playSound 'menu-button-click'
|
||||
@updateHintTimer()
|
||||
|
||||
onClickPreviousButton: ->
|
||||
window.tracker?.trackEvent 'Hints Previous Clicked', category: 'Students', levelSlug: @level.get('slug'), hintCount: @hintsState.get('hints')?.length ? 0, hintCurrent: @state.get('hintIndex'), ['Mixpanel']
|
||||
window.tracker?.trackEvent 'Hints Previous Clicked', category: 'Students', levelSlug: @level.get('slug'), hintCount: @hintsState.get('hints')?.length ? 0, hintCurrent: @state.get('hintIndex'), []
|
||||
@state.set('hintIndex', Math.max(@state.get('hintIndex') - 1, 0))
|
||||
@playSound 'menu-button-click'
|
||||
@updateHintTimer()
|
||||
|
@ -89,7 +89,7 @@ module.exports = class HintsView extends CocoView
|
|||
hintsViewTime[hintIndex]++
|
||||
hintsUsed = @state.get('hintsUsed')
|
||||
if hintsViewTime[hintIndex] > @hintUsedThresholdSeconds and not hintsUsed[hintIndex]
|
||||
window.tracker?.trackEvent 'Hint Used', category: 'Students', levelSlug: @level.get('slug'), hintCount: @hintsState.get('hints')?.length ? 0, hintCurrent: hintIndex, ['Mixpanel']
|
||||
window.tracker?.trackEvent 'Hint Used', category: 'Students', levelSlug: @level.get('slug'), hintCount: @hintsState.get('hints')?.length ? 0, hintCurrent: hintIndex, []
|
||||
hintsUsed[hintIndex] = true
|
||||
@state.set('hintsUsed', hintsUsed)
|
||||
clearInterval(@timerIntervalID)
|
||||
|
|
|
@ -49,7 +49,7 @@ module.exports = class CourseVictoryModal extends ModalView
|
|||
@course = new Course()
|
||||
@supermodel.trackRequest @course.fetchForCourseInstance(@courseInstanceID)
|
||||
|
||||
window.tracker?.trackEvent 'Play Level Victory Modal Loaded', category: 'Students', levelSlug: @level.get('slug'), ['Mixpanel']
|
||||
window.tracker?.trackEvent 'Play Level Victory Modal Loaded', category: 'Students', levelSlug: @level.get('slug'), []
|
||||
|
||||
onResourceLoadFailed: (e) ->
|
||||
if e.resource.jqxhr is @nextLevelRequest
|
||||
|
@ -98,7 +98,7 @@ module.exports = class CourseVictoryModal extends ModalView
|
|||
@showView(@views[index+1])
|
||||
|
||||
onNextLevel: ->
|
||||
window.tracker?.trackEvent 'Play Level Victory Modal Next Level', category: 'Students', levelSlug: @level.get('slug'), nextLevelSlug: @nextLevel.get('slug'), ['Mixpanel']
|
||||
window.tracker?.trackEvent 'Play Level Victory Modal Next Level', category: 'Students', levelSlug: @level.get('slug'), nextLevelSlug: @nextLevel.get('slug'), []
|
||||
if me.isSessionless()
|
||||
link = "/play/level/#{@nextLevel.get('slug')}?course=#{@courseID}&codeLanguage=#{utils.getQueryVariable('codeLanguage', 'python')}"
|
||||
else
|
||||
|
@ -107,7 +107,7 @@ module.exports = class CourseVictoryModal extends ModalView
|
|||
application.router.navigate(link, {trigger: true})
|
||||
|
||||
onDone: ->
|
||||
window.tracker?.trackEvent 'Play Level Victory Modal Done', category: 'Students', levelSlug: @level.get('slug'), ['Mixpanel']
|
||||
window.tracker?.trackEvent 'Play Level Victory Modal Done', category: 'Students', levelSlug: @level.get('slug'), []
|
||||
if me.isSessionless()
|
||||
link = '/teachers/courses'
|
||||
else
|
||||
|
|
|
@ -50,7 +50,7 @@ module.exports = class SpellTopBarView extends CocoView
|
|||
onClickHintsButton: ->
|
||||
return unless @hintsState?
|
||||
@hintsState.set('hidden', not @hintsState.get('hidden'))
|
||||
window.tracker?.trackEvent 'Hints Clicked', category: 'Students', levelSlug: @options.level.get('slug'), hintCount: @hintsState.get('hints')?.length ? 0, ['Mixpanel']
|
||||
window.tracker?.trackEvent 'Hints Clicked', category: 'Students', levelSlug: @options.level.get('slug'), hintCount: @hintsState.get('hints')?.length ? 0, []
|
||||
|
||||
onCodeReload: (e) ->
|
||||
@openModalView new ReloadLevelModal()
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"src_folders": ["spec/smoke"],
|
||||
"src_folders": ["spec/smoke/tests"],
|
||||
"output_folder": "spec/smoke/reports",
|
||||
"custom_commands_path": "",
|
||||
"custom_commands_path": "spec/smoke/commands",
|
||||
"custom_assertions_path": "",
|
||||
"page_objects_path": "",
|
||||
"globals_path": "",
|
||||
|
|
25
scripts/consolidate-language-family.coffee
Normal file
25
scripts/consolidate-language-family.coffee
Normal file
|
@ -0,0 +1,25 @@
|
|||
fs = require 'fs'
|
||||
path = require 'path'
|
||||
en = require('../app/locale/en').translation
|
||||
|
||||
localeCodes =
|
||||
parent: 'nl'
|
||||
childA: 'nl-NL'
|
||||
childB: 'nl-BE'
|
||||
|
||||
localeSources = {}
|
||||
for kind, code of localeCodes
|
||||
localeSources[kind] = fs.readFileSync(path.join(__dirname, "../app/locale/#{code}.coffee"), encoding='utf8').split('\n')
|
||||
|
||||
for parentLine, index in localeSources.parent
|
||||
for childKey in ['childA', 'childB', 'childC']
|
||||
continue unless childLine = localeSources[childKey]?[index]
|
||||
if childLine is parentLine and childLine isnt ''
|
||||
childLine = '#' + parentLine
|
||||
localeSources[childKey][index] = childLine
|
||||
|
||||
for childKey in ['childA', 'childB', 'childC']
|
||||
continue unless childCode = localeCodes[childKey]
|
||||
childLines = localeSources[childKey]
|
||||
newContents = childLines.join('\n')
|
||||
fs.writeFileSync "app/locale/#{childCode}.coffee", newContents
|
|
@ -11,7 +11,7 @@ if (process.argv.length !== 7) {
|
|||
// TODO: 2nd follow up email activity does not handle paged activity results
|
||||
// TODO: sendMail copied from updateCloseIoLeads.js
|
||||
// TODO: template values copied from updateCloseIoLeads.js
|
||||
// TODO: status change is not related to specific lead contacts
|
||||
// TODO: status change is not related to specific lead contacts, e.g. lead_7fQAZKtX7tPYe352JpaUULVaVA99Ppq4HlsHXrRkpA9
|
||||
// TODO: update status after adding a call task
|
||||
|
||||
const createTeacherEmailTemplatesAuto1 = ['tmpl_i5bQ2dOlMdZTvZil21bhTx44JYoojPbFkciJ0F560mn', 'tmpl_CEZ9PuE1y4PRvlYiKB5kRbZAQcTIucxDvSeqvtQW57G'];
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -3,7 +3,7 @@ log = require 'winston'
|
|||
request = require 'request'
|
||||
|
||||
apiKey = config.closeIO?.apiKey
|
||||
defaultSalesContactUser = 'user_Fh0uLUkRIKMk2to61ISq8PneyQonuD2i7hes6RhZgDX'
|
||||
defaultSalesContactUserID = 'user_Fh0uLUkRIKMk2to61ISq8PneyQonuD2i7hes6RhZgDX'
|
||||
|
||||
module.exports =
|
||||
logError: (msg) ->
|
||||
|
@ -50,27 +50,57 @@ module.exports =
|
|||
return done('ERROR: multiple leads returned for ' + email + ' ' + leads.data.length)
|
||||
return done()
|
||||
|
||||
getSalesContactEmail: (email, done) ->
|
||||
getSalesContactEmail: (userEmail, done) ->
|
||||
# Sales contact email precedence: previous email to contact, previous email to lead, lead custom field, lead status default
|
||||
try
|
||||
# NOTE: does not work on + email addresses due to Close.io API bug
|
||||
uri = "https://#{apiKey}:X@app.close.io/api/v1/lead/?query=email_address:#{email}"
|
||||
uri = "https://#{apiKey}:X@app.close.io/api/v1/lead/?query=email_address:#{userEmail}"
|
||||
request.get uri, (error, response, body) =>
|
||||
return done(error) if error
|
||||
leads = JSON.parse(body)
|
||||
return done("Unexpected leads format: " + body) unless leads.data?
|
||||
return done("No existing Close.IO lead found for #{email}") unless leads.data?.length > 0
|
||||
return done("Unexpected Close leads format: " + body) unless leads.data?
|
||||
return done("No existing Close.IO lead found for #{userEmail}") unless leads.data?.length > 0
|
||||
lead = leads.data[0]
|
||||
uri = "https://#{apiKey}:X@app.close.io/api/v1/activity/?lead_id=#{lead.id}"
|
||||
request.get uri, (error, response, body) =>
|
||||
return done(error) if error
|
||||
activities = JSON.parse(body)
|
||||
return done("Unexpected activities format: " + body) unless activities.data?
|
||||
for activity in activities.data when activity._type is 'Email'
|
||||
if /@codecombat\.(?:com)|(?:nl)/ig.test(activity.sender) and not (activity.sender?.indexOf(config.mail.username) >= 0) and not (activity.sender?.indexOf('brian@codecombat.com') >= 0)
|
||||
return done(null, activity.sender, activity.user_id, lead.id)
|
||||
return done(null, config.mail.supportSchools, defaultSalesContactUser, lead.id)
|
||||
activityForThisContact = null
|
||||
activityForThisLead = null
|
||||
for activity in activities.data when activity?._type is 'Email'
|
||||
continue unless /@codecombat\.(?:com)|(?:nl)/ig.test(activity.sender)
|
||||
continue if activity.sender.indexOf('brian@codecombat.com') >= 0
|
||||
continue if activity.sender.indexOf(config.mail.username) >= 0
|
||||
activityForThisLead ?= activity
|
||||
for email in activity.to or [] when email?.toLowerCase() is userEmail?.toLowerCase()
|
||||
activityForThisContact ?= activity
|
||||
|
||||
if activityForThisContact
|
||||
return done(null, activityForThisContact.sender, activityForThisContact.user_id, lead.id)
|
||||
else if activityForThisLead
|
||||
return done(null, activityForThisLead.sender, activityForThisLead.user_id, lead.id)
|
||||
|
||||
if email = lead.custom?['auto_sales_email']
|
||||
# Have to lookup Close user Id if email from lead custom field
|
||||
uri = "https://#{apiKey}:X@app.close.io/api/v1/user/?_fields=id,email"
|
||||
request.get uri, (error, response, body) =>
|
||||
return done(error) if error
|
||||
users = JSON.parse(body)
|
||||
return done("Unexpected Close users format: " + body) unless users.data?
|
||||
userID = null
|
||||
for user in users.data or [] when user.email?.toLowerCase() is email.toLowerCase()
|
||||
userID = user.id
|
||||
break
|
||||
if userID
|
||||
return done(null, email, userID, lead.id)
|
||||
else
|
||||
@logError("No user found for leadID=#{lead.id} user=#{userEmail} auto_sales_email=#{lead.custom?['auto_sales_email']}")
|
||||
return done(null, config.mail.supportSchools, defaultSalesContactUserID, lead.id)
|
||||
else
|
||||
return done(null, config.mail.supportSchools, defaultSalesContactUserID, lead.id)
|
||||
catch error
|
||||
log.error("closeIO.getSalesContactEmail Error for #{email}: #{JSON.stringify(error)}")
|
||||
log.error("closeIO.getSalesContactEmail Error for #{userEmail}: #{JSON.stringify(error)}")
|
||||
return done(error)
|
||||
|
||||
sendMail: (fromAddress, subject, content, salesContactEmail, leadID, done) ->
|
||||
|
@ -106,7 +136,7 @@ module.exports =
|
|||
uri: "https://#{apiKey}:X@app.close.io/api/v1/lead/#{leadID}/"
|
||||
body: JSON.stringify(putData)
|
||||
request.put options, (error, response, body) =>
|
||||
return done(error) if error
|
||||
return done(error) if error
|
||||
result = JSON.parse(body)
|
||||
if result.errors or result['field-errors']
|
||||
errorMessage = "Update Close.io lead PUT error for #{teacherEmail} #{leadID}"
|
||||
|
@ -123,7 +153,7 @@ module.exports =
|
|||
uri: "https://#{apiKey}:X@app.close.io/api/v1/task/"
|
||||
body: JSON.stringify(postData)
|
||||
request.post options, (error, response, body) =>
|
||||
return done(error) if error
|
||||
return done(error) if error
|
||||
result = JSON.parse(body)
|
||||
if result.errors or result['field-errors']
|
||||
errorMessage = "Create Close.io call task POST error for #{teacherEmail} #{leadID}"
|
||||
|
@ -144,7 +174,7 @@ module.exports =
|
|||
uri: "https://#{apiKey}:X@app.close.io/api/v1/opportunity/"
|
||||
body: JSON.stringify(postData)
|
||||
request.post options, (error, response, body) =>
|
||||
return done(error) if error
|
||||
return done(error) if error
|
||||
result = JSON.parse(body)
|
||||
if result.errors or result['field-errors']
|
||||
errorMessage = "Create Close.io opportunity POST error for #{teacherEmail} #{leadID}"
|
||||
|
|
29
spec/smoke/commands/waitForApplicationLoaded.js
Normal file
29
spec/smoke/commands/waitForApplicationLoaded.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
constants = require('../constants');
|
||||
|
||||
exports.command = function(callback) {
|
||||
return this
|
||||
.timeoutsAsyncScript(constants.ASYNC_TIMEOUT * 4) // Make it extra long for production
|
||||
.executeAsync(function(done) {
|
||||
|
||||
waitForApplication = function () {
|
||||
if (window.application) { waitForCurrentView() }
|
||||
else { setTimeout(waitForApplication, 1) }
|
||||
}
|
||||
|
||||
waitForCurrentView = function () {
|
||||
if (window.currentView && window.currentView.supermodel) { waitForCurrentViewToLoad() }
|
||||
else { setTimeout(waitForCurrentView, 1) }
|
||||
}
|
||||
|
||||
waitForCurrentViewToLoad = function () {
|
||||
if (window.currentView.supermodel.finished()) { done() }
|
||||
else { setTimeout(waitForCurrentViewToLoad) }
|
||||
}
|
||||
|
||||
waitForApplication();
|
||||
}, [], function(result) {
|
||||
if(result.error)
|
||||
console.log('waitForApplicationLoaded error:', result.error, result)
|
||||
})
|
||||
.pause(constants.PAUSE_TIME)
|
||||
};
|
8
spec/smoke/commands/waitForElementVisibleAndClick.js
Normal file
8
spec/smoke/commands/waitForElementVisibleAndClick.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
constants = require('../constants');
|
||||
|
||||
exports.command = function(selector) {
|
||||
return this
|
||||
.waitForElementVisible(selector, constants.ASYNC_TIMEOUT)
|
||||
.pause(constants.PAUSE_TIME)
|
||||
.click(selector);
|
||||
};
|
21
spec/smoke/commands/waitForModalLoaded.js
Normal file
21
spec/smoke/commands/waitForModalLoaded.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
constants = require('../constants');
|
||||
|
||||
exports.command = function() {
|
||||
return this
|
||||
.timeoutsAsyncScript(constants.ASYNC_TIMEOUT)
|
||||
.executeAsync(function(done) {
|
||||
try {
|
||||
window.currentModal.supermodel.finishLoading()
|
||||
.then(function() { done(); })
|
||||
.catch(function(e) { console.error('Promise error', e); done(e); });
|
||||
}
|
||||
catch (e) {
|
||||
console.error('Caught error:', e);
|
||||
done(e);
|
||||
}
|
||||
}, [], function(result) {
|
||||
if(result.error)
|
||||
console.log('waitForModalLoaded error:', result.error)
|
||||
})
|
||||
.pause(constants.PAUSE_TIME)
|
||||
};
|
21
spec/smoke/commands/waitForViewLoaded.js
Normal file
21
spec/smoke/commands/waitForViewLoaded.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
constants = require('../constants');
|
||||
|
||||
exports.command = function() {
|
||||
return this
|
||||
.timeoutsAsyncScript(constants.ASYNC_TIMEOUT)
|
||||
.executeAsync(function(done) {
|
||||
try {
|
||||
window.currentView.supermodel.finishLoading()
|
||||
.then(function() { done(); })
|
||||
.catch(function(e) { console.error('Promise error', e); done(e); });
|
||||
}
|
||||
catch (e) {
|
||||
console.error('Caught error:', e);
|
||||
done(e);
|
||||
}
|
||||
}, [], function(result) {
|
||||
if(result.error)
|
||||
console.log('waitForViewLoaded error:', result.error)
|
||||
})
|
||||
.pause(constants.PAUSE_TIME);
|
||||
};
|
23
spec/smoke/constants.js
Normal file
23
spec/smoke/constants.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
switch (process.env.COCO_SMOKE_DOMAIN) {
|
||||
case "local":
|
||||
module.exports.DOMAIN = 'http://localhost:3000';
|
||||
break;
|
||||
case "next":
|
||||
module.exports.DOMAIN = 'http://next.codecombat.com';
|
||||
break;
|
||||
case "staging":
|
||||
module.exports.DOMAIN = 'http://staging.codecombat.com';
|
||||
break;
|
||||
case "prod":
|
||||
module.exports.DOMAIN = 'https://codecombat.com';
|
||||
break;
|
||||
default:
|
||||
module.exports.DOMAIN = 'http://localhost:3000';
|
||||
}
|
||||
|
||||
// General time to wait for elements to appear
|
||||
module.exports.ASYNC_TIMEOUT = 8000;
|
||||
|
||||
// Used after an element appears, before an action occurs, to give the UI time to catch up,
|
||||
// and to make the smoke test more 'watchable'.
|
||||
module.exports.PAUSE_TIME = 300;
|
|
@ -1,101 +0,0 @@
|
|||
WAIT_TIMEOUT = 8000;
|
||||
|
||||
// TODO: Refactor into shared file
|
||||
switch (process.env.COCO_SMOKE_DOMAIN) {
|
||||
case "local":
|
||||
DOMAIN = 'http://localhost:3000';
|
||||
break;
|
||||
case "next":
|
||||
DOMAIN = 'http://next.codecombat.com';
|
||||
break;
|
||||
case "staging":
|
||||
DOMAIN = 'http://staging.codecombat.com';
|
||||
break;
|
||||
case "prod":
|
||||
DOMAIN = 'https://codecombat.com';
|
||||
break;
|
||||
default:
|
||||
DOMAIN = 'http://localhost:3000';
|
||||
}
|
||||
|
||||
var timestamp = new Date().getTime(),
|
||||
email = `email${timestamp}@${timestamp}.com`,
|
||||
name = timestamp.toString(),
|
||||
password = timestamp.toString();
|
||||
|
||||
module.exports = {
|
||||
'Sign up': function (browser) {
|
||||
|
||||
browser
|
||||
// Go to home page
|
||||
.url(DOMAIN)
|
||||
.resizeWindow(1250, 900)
|
||||
|
||||
// Open login modal
|
||||
.executeAsync(function(done) { window.currentView.supermodel.finishLoading.then(done); })
|
||||
.click('#create-account-link')
|
||||
|
||||
// Sign up
|
||||
.waitForElementVisible('.individual-path-button', WAIT_TIMEOUT)
|
||||
.click('.individual-path-button')
|
||||
.waitForElementVisible('#birthday-month-input', WAIT_TIMEOUT)
|
||||
.setValue('#birthday-month-input', 'January')
|
||||
.setValue('#birthday-day-input', '1')
|
||||
.setValue('#birthday-year-input', '1999')
|
||||
.click('.next-button')
|
||||
.waitForElementVisible('input[name="email"]', WAIT_TIMEOUT)
|
||||
.setValue('input[name="email"]', email)
|
||||
.setValue('input[name="name"]', name)
|
||||
.setValue('input[name="password"]', password)
|
||||
.click('#subscribe-input')
|
||||
.pause(100) // Sometimes create account button does not get clicked
|
||||
.click('#create-account-btn')
|
||||
.waitForElementVisible('#start-btn', WAIT_TIMEOUT)
|
||||
.click('#start-btn')
|
||||
|
||||
// Confirm we went to campaign view, navigate back to home
|
||||
.waitForElementVisible('#logout-button', WAIT_TIMEOUT * 3) // takes particularly long
|
||||
.assert.urlContains('/play')
|
||||
},
|
||||
|
||||
'Logout': function (browser) {
|
||||
browser
|
||||
.url(DOMAIN)
|
||||
.executeAsync(function (done) {
|
||||
window.currentView.supermodel.finishLoading.then(done);
|
||||
})
|
||||
.click('.dropdown-toggle')
|
||||
.waitForElementVisible('.dropdown #logout-button', WAIT_TIMEOUT)
|
||||
.click('.dropdown #logout-button')
|
||||
},
|
||||
|
||||
'Log back in': function (browser) {
|
||||
browser
|
||||
// Log back in
|
||||
.waitForElementVisible('#login-link', WAIT_TIMEOUT)
|
||||
.click('#login-link')
|
||||
.waitForElementVisible('#login-btn', WAIT_TIMEOUT)
|
||||
.setValue('input#username-or-email-input', email)
|
||||
.setValue('input#password-input', password)
|
||||
.click('#login-btn')
|
||||
.pause(100)
|
||||
.waitForElementVisible('#main-nav', WAIT_TIMEOUT)
|
||||
},
|
||||
|
||||
'Delete account': function (browser) {
|
||||
browser
|
||||
// Delete account
|
||||
.url(`${DOMAIN}/account/settings`)
|
||||
.pause(100)
|
||||
.executeAsync(function(done) { window.currentView.supermodel.finishLoading.then(done); })
|
||||
.waitForElementVisible('#delete-account-email-or-username', WAIT_TIMEOUT)
|
||||
.setValue('#delete-account-email-or-username', email)
|
||||
.setValue('#delete-account-password', password)
|
||||
.click('#delete-account-btn')
|
||||
.waitForElementVisible('#confirm-button', WAIT_TIMEOUT)
|
||||
.click('#confirm-button')
|
||||
.end();
|
||||
}
|
||||
};
|
||||
|
||||
|
92
spec/smoke/tests/individual-account-lifecycle.js
Normal file
92
spec/smoke/tests/individual-account-lifecycle.js
Normal file
|
@ -0,0 +1,92 @@
|
|||
constants = require('../constants');
|
||||
|
||||
var timestamp = new Date().getTime(),
|
||||
email = `email${timestamp}@${timestamp}.com`,
|
||||
name = timestamp.toString(),
|
||||
password = timestamp.toString();
|
||||
|
||||
module.exports = {
|
||||
'Sign up': function (browser) {
|
||||
|
||||
browser
|
||||
// Go to home page
|
||||
.url(constants.DOMAIN)
|
||||
.resizeWindow(1250, 900)
|
||||
|
||||
// Open login modal
|
||||
.waitForApplicationLoaded()
|
||||
.click('#create-account-link')
|
||||
|
||||
// Sign up
|
||||
.waitForModalLoaded()
|
||||
.click('.individual-path-button')
|
||||
.waitForElementVisibleAndClick('#birthday-month-input')
|
||||
.setValue('#birthday-month-input', 'January')
|
||||
.setValue('#birthday-day-input', '1')
|
||||
.setValue('#birthday-year-input', '1999')
|
||||
.pause(constants.PAUSE_TIME)
|
||||
.click('.next-button')
|
||||
.waitForElementVisible('input[name="email"]', constants.ASYNC_TIMEOUT)
|
||||
.executeAsync(function(done) {
|
||||
// If G+ or FB load in the middle of execution, they re-render the modal. This code waits for both
|
||||
// to load before continuing. TODO: Refactor code so this is unnecessary.
|
||||
check = function() {
|
||||
if(currentModal.signupState.get('facebookEnabled') && currentModal.signupState.get('gplusEnabled')) {
|
||||
done()
|
||||
}
|
||||
}
|
||||
currentModal.signupState.on('change', check);
|
||||
check();
|
||||
}, [], function(res) { if(res.error) { console.error('G+/FB wait error:', res.error) } })
|
||||
.pause(constants.PAUSE_TIME)
|
||||
.setValue('input[name="email"]', email)
|
||||
.setValue('input[name="name"]', name)
|
||||
.setValue('input[name="password"]', password)
|
||||
.click('#subscribe-input')
|
||||
.pause(constants.PAUSE_TIME*2)
|
||||
.click('#create-account-btn')
|
||||
.waitForElementVisibleAndClick('#start-btn')
|
||||
|
||||
// Confirm we went to campaign view, navigate back to home
|
||||
.waitForElementVisible('#logout-button', constants.ASYNC_TIMEOUT * 3) // takes particularly long
|
||||
.assert.urlContains('/play')
|
||||
},
|
||||
|
||||
'Logout': function (browser) {
|
||||
browser
|
||||
.url(constants.DOMAIN)
|
||||
.waitForViewLoaded()
|
||||
.pause(constants.PAUSE_TIME)
|
||||
.click('.dropdown-toggle')
|
||||
.waitForElementVisibleAndClick('.dropdown #logout-button')
|
||||
},
|
||||
|
||||
'Log back in': function (browser) {
|
||||
browser
|
||||
.waitForElementVisibleAndClick('#login-link')
|
||||
.waitForElementVisible('#login-btn', constants.ASYNC_TIMEOUT)
|
||||
.setValue('input#username-or-email-input', email)
|
||||
.setValue('input#password-input', password)
|
||||
.pause(constants.PAUSE_TIME)
|
||||
.click('#login-btn')
|
||||
.waitForElementVisible('#main-nav', constants.ASYNC_TIMEOUT)
|
||||
.pause(constants.PAUSE_TIME)
|
||||
},
|
||||
|
||||
'Delete account': function (browser) {
|
||||
browser
|
||||
.url(`${constants.DOMAIN}/account/settings`)
|
||||
.pause(constants.PAUSE_TIME)
|
||||
.waitForViewLoaded()
|
||||
.waitForElementVisible('#delete-account-email-or-username', constants.ASYNC_TIMEOUT)
|
||||
.setValue('#delete-account-email-or-username', email)
|
||||
.setValue('#delete-account-password', password)
|
||||
.pause(constants.PAUSE_TIME)
|
||||
.click('#delete-account-btn')
|
||||
.waitForElementVisibleAndClick('#confirm-button')
|
||||
.pause(constants.PAUSE_TIME*2)
|
||||
.end();
|
||||
}
|
||||
};
|
||||
|
||||
|
45
spec/smoke/tests/play-as-anonymous.js
Normal file
45
spec/smoke/tests/play-as-anonymous.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
constants = require('../constants');
|
||||
|
||||
module.exports = {
|
||||
'Go to dungeon campaign': function (browser) {
|
||||
browser
|
||||
.url(constants.DOMAIN + '/play/dungeon')
|
||||
.resizeWindow(1250, 900)
|
||||
.waitForApplicationLoaded()
|
||||
},
|
||||
|
||||
'Go to level view for Dungeons of Kithgard': function (browser) {
|
||||
browser
|
||||
.click('a[data-level-slug="dungeons-of-kithgard"]')
|
||||
.pause(constants.PAUSE_TIME)
|
||||
.click('.start-level')
|
||||
.pause(constants.PAUSE_TIME)
|
||||
.waitForModalLoaded()
|
||||
.waitForElementVisibleAndClick('#confirm-button')
|
||||
.waitForElementVisibleAndClick('.btn.equip-item')
|
||||
.click('#play-level-button')
|
||||
.pause(constants.PAUSE_TIME)
|
||||
},
|
||||
|
||||
'Play Dungeons of Kithgard': function (browser) {
|
||||
browser
|
||||
.waitForElementVisibleAndClick('button.start-level-button')
|
||||
.keys([browser.Keys.ESCAPE])
|
||||
.pause(constants.PAUSE_TIME)
|
||||
.keys('hero.moveDown()\nhero.moveRight()\n')
|
||||
.pause(constants.PAUSE_TIME)
|
||||
.click('.cast-button')
|
||||
.pause(constants.PAUSE_TIME)
|
||||
.waitForElementVisibleAndClick('.done-button')
|
||||
},
|
||||
|
||||
'Go through victory modal, check that Gems in the Deep is unlocked': function (browser) {
|
||||
browser
|
||||
.waitForModalLoaded()
|
||||
.click('#continue-button')
|
||||
.pause(constants.PAUSE_TIME)
|
||||
.waitForElementVisible('a[data-level-slug="gems-in-the-deep"]', constants.ASYNC_TIMEOUT)
|
||||
.pause(constants.PAUSE_TIME)
|
||||
.end()
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue