Merge branch 'master' of github.com:codecombat/codecombat

This commit is contained in:
Rob 2016-09-21 14:43:51 -07:00
commit 8d4d617069
33 changed files with 5846 additions and 3633 deletions

View file

@ -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"

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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 CodeCombats 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")

View file

@ -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

View file

@ -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")

View file

@ -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")

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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")

View file

@ -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")

View file

@ -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'

View file

@ -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()

View file

@ -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', []

View file

@ -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)

View file

@ -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

View file

@ -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()

View file

@ -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": "",

View 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

View file

@ -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

View file

@ -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}"

View 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)
};

View 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);
};

View 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)
};

View 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
View 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;

View file

@ -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();
}
};

View 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();
}
};

View 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()
}
}