Merge branch 'master' into production

This commit is contained in:
Nick Winter 2014-06-11 19:38:59 -07:00
commit c1948ab083
41 changed files with 299 additions and 80 deletions

View file

@ -15,8 +15,10 @@ before_script:
- "./node_modules/.bin/bower install"
- "gem install sass"
- "./node_modules/.bin/brunch b"
- "./bin/coco-mongodb fork"
- "mkdir mongo"
- "mongod --dbpath=./mongo --fork --logpath ./mongodb.log"
- "node index.js --unittest &"
- "sleep 5" # to give node a chance to start
script:
- "./node_modules/jasmine-node/bin/jasmine-node test/server/ --coffee --captureExceptions"

View file

@ -13,9 +13,6 @@ userPropsToSave =
module.exports = FacebookHandler = class FacebookHandler extends CocoClass
constructor: ->
super()
subscriptions:
'facebook-logged-in':'onFacebookLogin'
'facebook-logged-out': 'onFacebookLogout'
@ -42,22 +39,18 @@ module.exports = FacebookHandler = class FacebookHandler extends CocoClass
return
oldEmail = me.get('email')
patch = {}
patch.firstName = r.first_name if r.first_name
patch.lastName = r.last_name if r.last_name
patch.gender = r.gender if r.gender
patch.email = r.email if r.email
patch.facebookID = r.id if r.id
me.set(patch)
patch._id = me.id
me.set('firstName', r.first_name) if r.first_name
me.set('lastName', r.last_name) if r.last_name
me.set('gender', r.gender) if r.gender
me.set('email', r.email) if r.email
me.set('facebookID', r.id) if r.id
Backbone.Mediator.publish('logging-in-with-facebook')
window.tracker?.trackEvent 'Facebook Login'
window.tracker?.identify()
me.save(patch, {
patch: true
me.patch({
error: backboneFailure,
url: "/db/user?facebookID=#{r.id}&facebookAccessToken=#{@authResponse.accessToken}"
url: "/db/user/#{me.id}?facebookID=#{r.id}&facebookAccessToken=#{@authResponse.accessToken}"
success: (model) ->
window.location.reload() if model.get('email') isnt oldEmail
})

View file

@ -22,13 +22,13 @@ module.exports = class LevelBus extends Bus
'tome:spell-changed': 'onSpellChanged'
'tome:spell-created': 'onSpellCreated'
'application:idle-changed': 'onIdleChanged'
constructor: ->
super(arguments...)
@changedSessionProperties = {}
@saveSession = _.debounce(@saveSession, 1000, {maxWait: 5000})
@playerIsIdle = false
init: ->
super()
@fireScriptsRef = @fireRef?.child('scripts')
@ -36,7 +36,7 @@ module.exports = class LevelBus extends Bus
setSession: (@session) ->
@listenTo(@session, 'change:multiplayer', @onMultiplayerChanged)
@timerIntervalID = setInterval(@incrementSessionPlaytime, 1000)
onIdleChanged: (e) ->
@playerIsIdle = e.idle
@ -44,7 +44,7 @@ module.exports = class LevelBus extends Bus
if @playerIsIdle then return
@changedSessionProperties.playtime = true
@session.set("playtime",@session.get("playtime") + 1)
onPoint: ->
return true unless @session?.get('multiplayer')
super()
@ -224,7 +224,7 @@ module.exports = class LevelBus extends Bus
saveSession: ->
return if _.isEmpty @changedSessionProperties
# don't let peaking admins mess with the session accidentally
# don't let peeking admins mess with the session accidentally
return unless @session.get('multiplayer') or @session.get('creator') is me.id
Backbone.Mediator.publish 'level:session-will-save', session: @session
patch = {}

View file

@ -10,7 +10,7 @@ init = ->
if me and not me.get('testGroupNumber')?
# Assign testGroupNumber to returning visitors; new ones in server/routes/auth
me.set 'testGroupNumber', Math.floor(Math.random() * 256)
me.save()
me.patch()
Backbone.listenTo(me, 'sync', Backbone.Mediator.publish('me:synced', {me:me}))

View file

@ -1,5 +1,6 @@
module.exports.thangNames = thangNames =
"Soldier M": [
"Duke"
"William"
"Lucas"
"Marcus"
@ -66,6 +67,7 @@ module.exports.thangNames = thangNames =
"Coco"
"Buffy"
"Allankrita"
"Kay"
]
"Peasant M": [
"Yorik"
@ -355,6 +357,8 @@ module.exports.thangNames = thangNames =
"Hank"
"Jeph"
"Neville"
"Alphonse"
"Edward"
]
"Captain": [
"Anya"
@ -367,4 +371,5 @@ module.exports.thangNames = thangNames =
"Jane"
"Lia"
"Hardcastle"
"Leona"
]

View file

@ -294,6 +294,7 @@
project_picture_help: "Upload a 230x115px or larger image showing off the project."
project_link: "Link"
project_link_help: "Link to the project."
player_code: "Player Code"
employers:
want_to_hire_our_players: "Want to hire expert CodeCombat players?"
@ -867,6 +868,7 @@
source_document: "Source Document"
document: "Document" # note to diplomats: not a physical document, a document in MongoDB, ie a record in a database
sprite_sheet: "Sprite Sheet"
candidate_sessions: "Candidate Sessions"
delta:
added: "Added"

View file

@ -97,6 +97,22 @@ class CocoModel extends Backbone.Model
noty text: "#{errorMessage}: #{res.status} #{res.statusText}", layout: 'topCenter', type: 'error', killer: false, timeout: 10000
@trigger "save", @
return super attrs, options
patch: (options) ->
return false unless @_revertAttributes
options ?= {}
options.patch = true
attrs = {_id: @id}
keys = []
for key in _.keys @attributes
unless _.isEqual @attributes[key], @_revertAttributes[key]
attrs[key] = @attributes[key]
keys.push key
return unless keys.length
console.debug 'Patching', @get('name') or @, keys
@save(attrs, options)
fetch: ->
@jqxhr = super(arguments...)
@ -104,7 +120,6 @@ class CocoModel extends Backbone.Model
@jqxhr
markToRevert: ->
console.debug "Saving _revertAttributes for #{@constructor.className}: '#{@get('name')}'"
if @type() is 'ThangType'
@_revertAttributes = _.clone @attributes # No deep clones for these!
else

View file

@ -75,7 +75,7 @@
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif
color: #555
ul.links, ul.projects
ul.links, ul.projects, ul.sessions
margin: 0
padding: 0
@ -140,7 +140,7 @@
background-color: rgb(177, 55, 25)
padding: 15px
font-size: 20px
.middle-column
width: $middle-width - 2 * $middle-padding
padding-left: $middle-padding

View file

@ -170,6 +170,19 @@ block content
span(data-i18n="account_profile.contact") Contact
| #{profile.name.split(' ')[0]}
if !editing && sessions.length
h3(data-i18n="account_profile.player_code") Player Code
ul.sessions
each session in sessions
li
- var sessionLink = "/play/level/" + session.levelID + "?team=" + (session.team || 'humans') + "&session=" + session._id;
a(href=sessionLink)
span= session.levelName
if session.team
span #{session.team}
if session.codeLanguage != 'javascript'
span - #{{coffeescript: 'CoffeeScript', python: 'Python', lua: 'Lua', io: 'Io', clojure: 'Clojure'}[session.codeLanguage]}
.middle-column.full-height-column
.sub-column
#name-container.editable-section

View file

@ -57,9 +57,9 @@ block content
strong= act.count
|
br
span= moment(activity.login.first).fromNow()
span= moment(act.first).fromNow()
br
span= moment(activity.login.last).fromNow()
span= moment(act.last).fromNow()
else
td 0
td(data-employer-age=(new Date() - new Date(employer.get('signedEmployerAgreement').date)) / 86400 / 1000)= moment(employer.get('signedEmployerAgreement').date).fromNow()

View file

@ -1,11 +1,19 @@
View = require 'views/kinds/RootView'
template = require 'templates/account/profile'
User = require 'models/User'
LevelSession = require 'models/LevelSession'
CocoCollection = require 'collections/CocoCollection'
{me} = require 'lib/auth'
JobProfileContactView = require 'views/modal/job_profile_contact_modal'
JobProfileView = require 'views/account/job_profile_view'
forms = require 'lib/forms'
class LevelSessionsCollection extends CocoCollection
url: -> "/db/user/#{@userID}/level.sessions/employer"
model: LevelSession
constructor: (@userID) ->
super()
module.exports = class ProfileView extends View
id: "profile-view"
template: template
@ -42,9 +50,7 @@ module.exports = class ProfileView extends View
if User.isObjectID @userID
@finishInit()
else
console.log "getting", @userID
$.ajax "/db/user/#{@userID}/nameToID", success: (@userID) =>
console.log " got", @userID
@finishInit() unless @destroyed
@render()
@ -63,6 +69,7 @@ module.exports = class ProfileView extends View
$.post "/db/user/#{@userID}/track/viewed_by_employer" unless me.isAdmin()
else
@user = User.getByID(@userID)
@sessions = @supermodel.loadCollection(new LevelSessionsCollection(@userID), 'candidate_sessions').model
onLinkedInLoaded: =>
@linkedinLoaded = true
@ -80,11 +87,11 @@ module.exports = class ProfileView extends View
@renderLinkedInButton()
else
@waitingForLinkedIn = true
importLinkedIn: =>
overwriteConfirm = confirm("Importing LinkedIn data will overwrite your current work experience, skills, name, descriptions, and education. Continue?")
unless overwriteConfirm then return
application.linkedinHandler.getProfileData (err, profileData) =>
console.log profileData
@processLinkedInProfileData profileData
jobProfileSchema: -> @user.schema().properties.jobProfile.properties
@ -217,6 +224,8 @@ module.exports = class ProfileView extends View
links = ($.extend(true, {}, link) for link in links)
link.icon = @iconForLink link for link in links
context.profileLinks = _.sortBy links, (link) -> not link.icon # icons first
context.sessions = (s.attributes for s in @sessions.models when (s.get('submitted') or s.get('level-id') is 'gridmancer'))
context.sessions.sort (a, b) -> (b.playtime ? 0) - (a.playtime ? 0)
context
afterRender: ->
@ -303,7 +312,7 @@ module.exports = class ProfileView extends View
errors = @user.validate()
return @showErrors errors if errors
jobProfile = @user.get('jobProfile')
jobProfile.updated = (new Date()).toISOString()
jobProfile.updated = (new Date()).toISOString() if @user is me
@user.set 'jobProfile', jobProfile
return unless res = @user.save()
res.error =>

View file

@ -113,7 +113,7 @@ module.exports = class SettingsView extends View
return unless me.hasLocalChanges()
res = me.save()
res = me.patch()
return unless res
save = $('#save-button', @$el).text($.i18n.t('common.saving', defaultValue: 'Saving...'))
.removeClass('btn-danger').addClass('btn-success').show()

View file

@ -36,7 +36,7 @@ module.exports = class ContributeClassView extends View
subscription = el.attr('name')
me.setEmailSubscription subscription+'News', checked
me.save()
me.patch()
@openModalView new SignupModalView() if me.get 'anonymous'
el.parent().find('.saved-notification').finish().show('fast').delay(3000).fadeOut(2000)

View file

@ -52,6 +52,7 @@ module.exports = class EmployersView extends View
@listenToOnce @candidates, 'all', @renderCandidatesAndSetupScrolling
renderCandidatesAndSetupScrolling: =>
@render()
$(".nano").nanoScroller()
if window.history?.state?.lastViewedCandidateID

View file

@ -150,7 +150,7 @@ module.exports = class RootView extends CocoView
saveLanguage: (newLang) ->
me.set('preferredLanguage', newLang)
res = me.save()
res = me.patch()
return unless res
res.error ->
errors = JSON.parse(res.responseText)

View file

@ -12,7 +12,7 @@ module.exports = class DiplomatSuggestionView extends View
subscribeAsDiplomat: ->
me.setEmailSubscription 'diplomatNews', true
me.save()
me.patch()
$("#email_translator").prop("checked", 1)
@hide()
return

View file

@ -40,7 +40,7 @@ module.exports = class WizardSettingsModal extends View
forms.applyErrorsToForm(@$el, res)
return
res = me.save()
res = me.patch()
return unless res
save = $('#save-button', @$el).text($.i18n.t('common.saving', defaultValue: 'Saving...'))
.addClass('btn-info').show().removeClass('btn-danger')

View file

@ -10,14 +10,6 @@ ModelModal = require 'views/modal/model_modal'
HIGHEST_SCORE = 1000000
class LevelSessionsCollection extends CocoCollection
url: ''
model: LevelSession
constructor: (levelID) ->
super()
@url = "/db/level/#{levelID}/all_sessions"
module.exports = class LadderTabView extends CocoView
id: 'ladder-tab-view'
template: require 'templates/play/ladder/ladder_tab'

View file

@ -43,7 +43,7 @@ module.exports = class LadderView extends RootView
onLoaded: ->
@teams = teamDataFromLevel @level
@render()
super()
getRenderData: ->
ctx = super()

View file

@ -79,7 +79,7 @@ module.exports = class EditorConfigModal extends View
Backbone.Mediator.publish 'tome:change-config'
Backbone.Mediator.publish 'tome:change-language', language: newLanguage unless newLanguage is oldLanguage
@session.save() unless newLanguage is oldLanguage
me.save()
me.patch()
destroy: ->
super()

View file

@ -81,7 +81,7 @@ module.exports = class VictoryModal extends View
if enough and not me.get('hourOfCodeComplete')
$('body').append($("<img src='http://code.org/api/hour/finish_codecombat.png' style='visibility: hidden;'>"))
me.set 'hourOfCodeComplete', true
me.save()
me.patch()
window.tracker?.trackEvent 'Hour of Code Finish', {}
# Show the "I'm done" button if they get to the end, unless it's been over two hours
tooMuch = elapsed >= 120 * 60 * 1000

View file

@ -355,7 +355,7 @@ module.exports = class PlaybackView extends View
onToggleMusic: (e) ->
e?.preventDefault()
me.set('music', not me.get('music'))
me.save()
me.patch()
$(document.activeElement).blur()
destroy: ->

View file

@ -97,7 +97,7 @@ module.exports = class CastButtonView extends View
return unless delay
@autocastDelay = delay = parseInt delay
me.set('autocastDelay', delay)
me.save()
me.patch()
spell.view.setAutocastDelay delay for spellKey, spell of @spells
@castOptions.find('a').each ->
$(@).toggleClass('selected', parseInt($(@).attr('data-delay')) is delay)

View file

@ -24,7 +24,7 @@ module.exports = class Spell
@permissions = read: p.permissions?.read ? [], readwrite: p.permissions?.readwrite ? [] # teams
teamSpells = @session.get('teamSpells')
team = @session.get('team') ? 'humans'
@useTranspiledCode = @permissions.readwrite.length and ((teamSpells and not _.contains(teamSpells[team], @spellKey)) or (@session.get('creator') isnt me.id) or @spectateView)
@useTranspiledCode = @permissions.readwrite.length and ((teamSpells and not _.contains(teamSpells[team], @spellKey)) or (@session.get('creator') isnt me.id and not (me.isAdmin() or 'employer' in me.get('permissions'))) or @spectateView)
#console.log @spellKey, "using transpiled code?", @useTranspiledCode
@source = @originalSource = p.source
@parameters = p.parameters

View file

@ -93,7 +93,7 @@ module.exports = class PlayLevelView extends View
setUpHourOfCode: ->
me.set 'hourOfCode', true
me.save()
me.patch()
$('body').append($("<img src='http://code.org/api/hour/begin_codecombat.png' style='visibility: hidden;'>"))
application.tracker?.trackEvent 'Hour of Code Begin', {}

View file

@ -107,6 +107,7 @@ module.exports = TestView = class TestView extends CocoView
# TODO Stubbify more things
# * document.location
# * firebase
# * all the services that load in main.html
afterEach ->
# TODO Clean up more things

View file

@ -1,5 +1,4 @@
mongoose = require('mongoose')
plugins = require('../plugins/plugins')
jsonschema = require('../../app/schemas/models/achievement')
log = require 'winston'
@ -29,7 +28,9 @@ AchievementSchema.pre('save', (next) ->
next()
)
module.exports = Achievement = mongoose.model('Achievement', AchievementSchema)
plugins = require('../plugins/plugins')
AchievementSchema.plugin(plugins.NamedPlugin)
AchievementSchema.plugin(plugins.SearchablePlugin, {searchable: ['name']})
module.exports = Achievement = mongoose.model('Achievement', AchievementSchema)

View file

@ -9,7 +9,7 @@ LevelComponentHandler = class LevelComponentHandler extends Handler
'description'
'code'
'js'
'language'
'codeLanguage'
'dependencies'
'propertyDocumentation'
'configSchema'
@ -25,4 +25,4 @@ LevelComponentHandler = class LevelComponentHandler extends Handler
req.method is 'GET' or req.user?.isAdmin()
module.exports = new LevelComponentHandler()
module.exports = new LevelComponentHandler()

View file

@ -14,14 +14,14 @@ class LevelSessionHandler extends Handler
getByRelationship: (req, res, args...) ->
return @getActiveSessions req, res if args.length is 2 and args[1] is 'active'
super(arguments...)
formatEntity: (req, document) ->
documentObject = super(req, document)
if req.user.isAdmin() or req.user.id is document.creator
if req.user.isAdmin() or req.user.id is document.creator or ('employer' in req.user.get('permissions'))
return documentObject
else
return _.omit documentObject, ['submittedCode','code']
getActiveSessions: (req, res) ->
return @sendUnauthorizedError(res) unless req.user.isAdmin()
start = new Date()
@ -34,6 +34,7 @@ class LevelSessionHandler extends Handler
hasAccessToDocument: (req, document, method=null) ->
return true if req.method is 'GET' and document.get('totalScore')
return true if ('employer' in req.user.get('permissions')) and (method ? req.method).toLowerCase() is 'get'
super(arguments...)
module.exports = new LevelSessionHandler()

View file

@ -7,7 +7,7 @@ LevelSystemHandler = class LevelSystemHandler extends Handler
'description'
'code'
'js'
'language'
'codeLanguage'
'dependencies'
'propertyDocumentation'
'configSchema'

View file

@ -1,7 +1,6 @@
mongoose = require('mongoose')
Achievement = require('../achievements/Achievement')
EarnedAchievement = require '../achievements/EarnedAchievement'
User = require '../users/User'
LocalMongo = require '../../app/lib/LocalMongo'
util = require '../../app/lib/utils'
log = require 'winston'
@ -19,6 +18,8 @@ loadAchievements = ->
loadAchievements()
module.exports = AchievablePlugin = (schema, options) ->
User = require '../users/User'
checkForAchievement = (doc) ->
collectionName = doc.constructor.modelName

View file

@ -1,5 +1,4 @@
mongoose = require('mongoose')
User = require('../users/User')
textSearch = require('mongoose-text-search')
module.exports.MigrationPlugin = (schema, migrations) ->

View file

@ -62,7 +62,6 @@ module.exports.setup = (app) ->
req.logIn(user, (err) ->
return next(err) if (err)
activity = req.user.trackActivity 'login', 1
console.log "updating", activity
user.update {activity: activity}, (err) ->
return next(err) if (err)
res.send(UserHandler.formatEntity(req, req.user))

View file

@ -189,6 +189,7 @@ UserHandler = class UserHandler extends Handler
return @avatar(req, res, args[0]) if args[1] is 'avatar'
return @getNamesByIDs(req, res) if args[1] is 'names'
return @nameToID(req, res, args[0]) if args[1] is 'nameToID'
return @getLevelSessionsForEmployer(req, res, args[0]) if args[1] is 'level.sessions' and args[2] is 'employer'
return @getLevelSessions(req, res, args[0]) if args[1] is 'level.sessions'
return @getCandidates(req, res) if args[1] is 'candidates'
return @getEmployers(req, res) if args[1] is 'employers'
@ -227,9 +228,18 @@ UserHandler = class UserHandler extends Handler
res.redirect photoURL
res.end()
getLevelSessionsForEmployer: (req, res, userID) ->
return @sendUnauthorizedError(res) unless req.user._id+'' is userID or req.user.isAdmin() or ('employer' in req.user.get('permissions'))
query = creator: userID, levelID: {$in: ['gridmancer', 'greed', 'dungeon-arena', 'brawlwood', 'gold-rush']}
projection = 'levelName levelID team playtime codeLanguage submitted' # code totalScore
LevelSession.find(query).select(projection).exec (err, documents) =>
return @sendDatabaseError(res, err) if err
documents = (LevelSessionHandler.formatEntity(req, doc) for doc in documents)
@sendSuccess(res, documents)
getLevelSessions: (req, res, userID) ->
return @sendUnauthorizedError(res) unless req.user._id+'' is userID or req.user.isAdmin()
query = {'creator': userID}
query = creator: userID
projection = null
if req.query.project
projection = {}

View file

@ -93,7 +93,10 @@ sendMain = (req, res) ->
log.error "Error modifying main.html: #{err}" if err
# insert the user object directly into the html so the application can have it immediately. Sanitize </script>
data = data.replace('"userObjectTag"', JSON.stringify(UserHandler.formatEntity(req, req.user)).replace(/\//g, '\\/'))
res.send data
res.header "Cache-Control", "no-cache, no-store, must-revalidate"
res.header "Pragma", "no-cache"
res.header "Expires", 0
res.send 200, data
setupFacebookCrossDomainCommunicationRoute = (app) ->
app.get '/channel.html', (req, res) ->

View file

@ -0,0 +1,80 @@
FacebookHandler = require 'lib/FacebookHandler'
mockAuthEvent =
response:
authResponse:
accessToken: "aksdhjflkqjrj245234b52k345q344le4j4k5l45j45s4dkljvdaskl"
userID: "4301938"
expiresIn: 5138
signedRequest: "akjsdhfjkhea.3423nkfkdsejnfkd"
status: "connected"
# Whatev, it's all public info anyway
mockMe =
id: "4301938"
email: "scott@codecombat.com"
first_name: "Scott"
gender: "male"
last_name: "Erickson"
link: "https://www.facebook.com/scott.erickson.779"
locale: "en_US"
name: "Scott Erickson"
timezone: -7
updated_time: "2014-05-21T04:58:06+0000"
username: "scott.erickson.779"
verified: true
work: [
{
employer:
id: "167559910060759"
name: "CodeCombat"
location:
id: "114952118516947"
name: "San Francisco, California"
start_date: "2013-02-28"
}
{
end_date: "2013-01-31"
employer:
id: "39198748555"
name: "Skritter"
location:
id: "106109576086811"
name: "Oberlin, Ohio"
start_date: "2008-06-01"
}
]
window.FB ?= {
api: ->
}
describe 'lib/FacebookHandler.coffee', ->
it 'on facebook-logged-in, gets data from FB and sends a patch to the server', ->
me.clear({silent:true})
me.markToRevert()
me.set({_id: '12345'})
spyOn FB, 'api'
new FacebookHandler()
Backbone.Mediator.publish 'facebook-logged-in', mockAuthEvent
expect(FB.api).toHaveBeenCalled()
apiArgs = FB.api.calls.argsFor(0)
expect(apiArgs[0]).toBe('/me')
apiArgs[1](mockMe) # sending the 'response'
request = jasmine.Ajax.requests.mostRecent()
expect(request).toBeDefined()
params = JSON.parse request.params
expect(params.firstName).toBe(mockMe.first_name)
expect(params.lastName).toBe(mockMe.last_name)
expect(params.gender).toBe(mockMe.gender)
expect(params.email).toBe(mockMe.email)
expect(params.facebookID).toBe(mockMe.id)
expect(request.method).toBe('PATCH')
expect(_.string.startsWith(request.url, '/db/user/12345')).toBeTruthy()

View file

@ -0,0 +1,84 @@
CocoModel = require 'models/CocoModel'
class BlandClass extends CocoModel
@className: 'Bland'
@schema: {
type: 'object'
additionalProperties: false
properties:
number: {type: 'number'}
object: {type: 'object'}
string: {type: 'string'}
_id: {type: 'string'}
}
urlRoot: '/db/bland'
describe 'CocoModel', ->
describe 'save', ->
it 'saves to db/<urlRoot>', ->
b = new BlandClass({})
res = b.save()
request = jasmine.Ajax.requests.mostRecent()
expect(res).toBeDefined()
expect(request.url).toBe(b.urlRoot)
expect(request.method).toBe('POST')
it 'does not save if the data is invalid based on the schema', ->
b = new BlandClass({number: 'NaN'})
res = b.save()
expect(res).toBe(false)
request = jasmine.Ajax.requests.mostRecent()
expect(request).toBeUndefined()
it 'uses PUT when _id is included', ->
b = new BlandClass({_id: 'test'})
b.save()
request = jasmine.Ajax.requests.mostRecent()
expect(request.method).toBe('PUT')
describe 'patch', ->
it 'PATCHes only properties that have changed', ->
b = new BlandClass({_id: 'test', number:1})
b.loaded = true
b.set('string', 'string')
b.patch()
request = jasmine.Ajax.requests.mostRecent()
params = JSON.parse request.params
expect(params.string).toBeDefined()
expect(params.number).toBeUndefined()
it 'collates all changes made over several sets', ->
b = new BlandClass({_id: 'test', number:1})
b.loaded = true
b.set('string', 'string')
b.set('object', {4:5})
b.patch()
request = jasmine.Ajax.requests.mostRecent()
params = JSON.parse request.params
expect(params.string).toBeDefined()
expect(params.object).toBeDefined()
expect(params.number).toBeUndefined()
it 'does not include data from previous patches', ->
b = new BlandClass({_id: 'test', number:1})
b.loaded = true
b.set('object', {1:2})
b.patch()
request = jasmine.Ajax.requests.mostRecent()
attrs = JSON.stringify(b.attributes) # server responds with all
request.response({status: 200, responseText: attrs})
b.set('number', 3)
b.patch()
request = jasmine.Ajax.requests.mostRecent()
params = JSON.parse request.params
expect(params.object).toBeUndefined()
it 'does nothing when there\'s nothing to patch', ->
b = new BlandClass({_id: 'test', number:1})
b.loaded = true
b.set('number', 1)
b.patch()
request = jasmine.Ajax.requests.mostRecent()
expect(request).toBeUndefined()

View file

@ -9,6 +9,10 @@ jasmine.getEnv().addReporter(new jasmine.SpecReporter({
displaySuccessfulSpec: true,
displayFailedSpec: true
}))
rep = new jasmine.JsApiReporter()
jasmine.getEnv().addReporter(rep)
GLOBAL._ = require('lodash')
_.str = require('underscore.string')
_.mixin(_.str.exports())
@ -149,3 +153,13 @@ _drop = (done) ->
chunks = mongoose.connection.db.collection('media.chunks')
chunks.remove {}, ->
done()
tickInterval = null
tick = ->
# When you want jasmine-node to exit after running the tests,
# you have to close the connection first.
if rep.finished
mongoose.disconnect()
clearTimeout tickInterval
tickInterval = setInterval tick, 1000

View file

@ -6,9 +6,8 @@ urlLogin = getURL('/auth/login')
urlReset = getURL('/auth/reset')
describe '/auth/whoami', ->
http = require 'http'
it 'returns 200', (done) ->
http.get(getURL('/auth/whoami'), (response) ->
request.get(getURL('/auth/whoami'), (err, response) ->
expect(response).toBeDefined()
expect(response.statusCode).toBe(200)
done()

View file

@ -1,12 +1,16 @@
require '../common'
describe '/file', ->
# Doesn't work on Travis. Need to figure out why, probably by having the
# url not depend on some external resource.
xdescribe '/file', ->
url = getURL('/file')
files = []
options = {
uri:url
json: {
url: 'http://scotterickson.info/images/where-are-you.jpg'
# url: 'http://scotterickson.info/images/where-are-you.jpg'
url: 'http://fc07.deviantart.net/fs37/f/2008/283/5/1/Chu_Chu_Pikachu_by_angelishi.gif'
filename: 'where-are-you.jpg'
mimetype: 'image/jpeg'
description: 'None!'
@ -20,7 +24,8 @@ describe '/file', ->
filename: 'ittybitty.data'
mimetype: 'application/octet-stream'
description: 'rando-info'
my_buffer_url: 'http://scotterickson.info/images/where-are-you.jpg'
# my_buffer_url: 'http://scotterickson.info/images/where-are-you.jpg'
my_buffer_url: 'http://fc07.deviantart.net/fs37/f/2008/283/5/1/Chu_Chu_Pikachu_by_angelishi.gif'
}
it 'preparing test : deletes all the files first', (done) ->

View file

@ -18,13 +18,3 @@ describe 'Level', ->
level.save (err) ->
throw err if err
done()
it 'loads again after being saved', (done) ->
url = getURL('/db/level/'+level._id)
request.get url, (err, res, body) ->
expect(res.statusCode).toBe(200)
sameLevel = JSON.parse(body)
expect(sameLevel.name).toEqual(level.get 'name')
expect(sameLevel.description).toEqual(level.get 'description')
expect(sameLevel.permissions).toEqual(simplePermissions)
done()