mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-02-17 00:40:56 -05:00
Make network error handling more generic
This commit is contained in:
parent
cef0ed0185
commit
29350bf1de
12 changed files with 315 additions and 89 deletions
|
@ -142,7 +142,7 @@ module.exports = class Tracker
|
|||
$.post("#{window.location.protocol or 'http:'}//analytics.codecombat.com/analytics", dataToSend).fail ->
|
||||
console.error "Analytics post failed!"
|
||||
else
|
||||
request = @supermodel.addRequestResource 'log_event', {
|
||||
request = @supermodel.addRequestResource {
|
||||
url: '/db/analytics.log.event/-/log_event'
|
||||
data: {event: event, properties: properties}
|
||||
method: 'POST'
|
||||
|
|
|
@ -34,6 +34,11 @@
|
|||
twitter_follow: "Follow"
|
||||
teachers: "Teachers"
|
||||
careers: "Careers"
|
||||
facebook: "Facebook"
|
||||
twitter: "Twitter"
|
||||
create_a_class: "Create a Class"
|
||||
other: "Other"
|
||||
learn_to_code: "Learn to Code!"
|
||||
|
||||
modal:
|
||||
close: "Close"
|
||||
|
@ -1352,17 +1357,22 @@
|
|||
|
||||
loading_error:
|
||||
could_not_load: "Error loading from server"
|
||||
connection_failure: "Connection failed."
|
||||
connection_failure: "Connection Failed" # {change}
|
||||
login_required: "Login Required"
|
||||
login_required_desc: " You need to be logged in to access this page."
|
||||
unauthorized: "You need to be signed in. Do you have cookies disabled?"
|
||||
forbidden: "You do not have the permissions."
|
||||
not_found: "Not found."
|
||||
forbidden: "Forbidden" # {change}
|
||||
forbidden_desc: "Oh no, there’s nothing we can show you here! Make sure you’re logged into the correct account, or visit one of the links below to get back to programming!"
|
||||
not_found: "Not Found" # {change}
|
||||
not_found_desc: "Hm, there’s nothing here. Visit one of the following links to get back to programming!"
|
||||
not_allowed: "Method not allowed."
|
||||
timeout: "Server timeout."
|
||||
timeout: "Server Timeout" # {change}
|
||||
conflict: "Resource conflict."
|
||||
bad_input: "Bad input."
|
||||
server_error: "Server error."
|
||||
unknown: "Unknown error."
|
||||
unknown: "Unknown Error" # {change}
|
||||
error: "ERROR"
|
||||
general_desc: "Something went wrong, and it’s probably our fault. Try waiting a bit and then refreshing the page, or visit one of the following links to get back to programming!"
|
||||
|
||||
resources:
|
||||
sessions: "Sessions"
|
||||
|
|
|
@ -28,6 +28,10 @@ module.exports = class SuperModel extends Backbone.Model
|
|||
unfinished
|
||||
|
||||
loadModel: (model, name, fetchOptions, value=1) ->
|
||||
# Deprecating name. Handle if name is not included
|
||||
value = fetchOptions if _.isNumber(fetchOptions)
|
||||
fetchOptions = name if _.isObject(name)
|
||||
|
||||
# hero-ladder levels need remote opponent_session for latest session data (e.g. code)
|
||||
# Can't apply to everything since other features rely on cached models being more recent (E.g. level_session)
|
||||
# E.g.#2 heroConfig isn't necessarily saved to db in world map inventory modal, so we need to load the cached session on level start
|
||||
|
@ -48,6 +52,10 @@ module.exports = class SuperModel extends Backbone.Model
|
|||
return res
|
||||
|
||||
loadCollection: (collection, name, fetchOptions, value=1) ->
|
||||
# Deprecating name. Handle if name is not included
|
||||
value = fetchOptions if _.isNumber(fetchOptions)
|
||||
fetchOptions = name if _.isObject(name)
|
||||
|
||||
url = collection.getURL()
|
||||
if cachedCollection = @collections[url]
|
||||
console.debug 'Collection cache hit', url, 'already loaded', cachedCollection.loaded
|
||||
|
@ -135,6 +143,10 @@ module.exports = class SuperModel extends Backbone.Model
|
|||
return @progress is 1.0 or not @denom
|
||||
|
||||
addModelResource: (modelOrCollection, name, fetchOptions, value=1) ->
|
||||
# Deprecating name. Handle if name is not included
|
||||
value = fetchOptions if _.isNumber(fetchOptions)
|
||||
fetchOptions = name if _.isObject(name)
|
||||
|
||||
modelOrCollection.saveBackups = modelOrCollection.saveBackups or @shouldSaveBackups(modelOrCollection)
|
||||
@checkName(name)
|
||||
res = new ModelResource(modelOrCollection, name, fetchOptions, value)
|
||||
|
@ -145,20 +157,25 @@ module.exports = class SuperModel extends Backbone.Model
|
|||
@removeResource _.find(@resources, (resource) -> resource?.model is modelOrCollection)
|
||||
|
||||
addRequestResource: (name, jqxhrOptions, value=1) ->
|
||||
# Deprecating name. Handle if name is not included
|
||||
value = jqxhrOptions if _.isNumber(jqxhrOptions)
|
||||
jqxhrOptions = name if _.isObject(name)
|
||||
|
||||
@checkName(name)
|
||||
res = new RequestResource(name, jqxhrOptions, value)
|
||||
@storeResource(res, value)
|
||||
return res
|
||||
|
||||
addSomethingResource: (name, value=1) ->
|
||||
value = name if _.isNumber(name)
|
||||
@checkName(name)
|
||||
res = new SomethingResource(name, value)
|
||||
@storeResource(res, value)
|
||||
return res
|
||||
|
||||
checkName: (name) ->
|
||||
if not name
|
||||
throw new Error('Resource name should not be empty.')
|
||||
if _.isString(name)
|
||||
console.warn("SuperModel name property deprecated. Remove '#{name}' from code.")
|
||||
|
||||
storeResource: (resource, value) ->
|
||||
@rid++
|
||||
|
|
15
app/styles/core/loading-error.sass
Normal file
15
app/styles/core/loading-error.sass
Normal file
|
@ -0,0 +1,15 @@
|
|||
#loading-error
|
||||
padding: 20px
|
||||
|
||||
.btn
|
||||
margin-top: 20px
|
||||
|
||||
.login-btn
|
||||
margin-right: 10px
|
||||
|
||||
#not-found-img
|
||||
max-width: 20%
|
||||
margin: 20px 0
|
||||
|
||||
#links-row
|
||||
margin-top: 50px
|
|
@ -39,7 +39,7 @@ block header
|
|||
li
|
||||
a(href="/account/prepaid", data-i18n="account.prepaid_codes") Prepaid Codes
|
||||
li
|
||||
a#logout-button(data-i18n="login.log_out")
|
||||
a#logout-button(data-i18n="login.log_out")
|
||||
|
||||
else
|
||||
button.btn.btn-sm.btn-primary.header-font.signup-button(data-i18n="login.sign_up")
|
||||
|
|
|
@ -1,28 +1,89 @@
|
|||
.alert.alert-danger.loading-error-alert
|
||||
span(data-i18n="loading_error.could_not_load") Error loading from server
|
||||
span (
|
||||
span(data-i18n="resources.#{name}")
|
||||
span )
|
||||
if !responseText
|
||||
strong(data-i18n="loading_error.connection_failure") Connection failed.
|
||||
else if status === 401
|
||||
strong(data-i18n="loading_error.unauthorized") You need to be signed in. Do you have cookies disabled?
|
||||
else if status === 403
|
||||
strong(data-i18n="loading_error.forbidden") You do not have the permissions.
|
||||
else if status === 404
|
||||
strong(data-i18n="loading_error.not_found") Not found.
|
||||
else if status === 405
|
||||
strong(data-i18n="loading_error.not_allowed") Method not allowed.
|
||||
else if status === 408
|
||||
strong(data-i18n="loading_error.timeout") Server timeout.
|
||||
else if status === 409
|
||||
strong(data-i18n="loading_error.conflict") Resource conflict.
|
||||
else if status === 422
|
||||
strong(data-i18n="loading_error.bad_input") Bad input.
|
||||
else if status >= 500
|
||||
strong(data-i18n="loading_error.server_error") Server error.
|
||||
#loading-error.text-center
|
||||
if jqxhr.status === 401
|
||||
h1
|
||||
span.spr 401:
|
||||
span(data-i18n="loading_error.login_required")
|
||||
p(data-i18n="loading_error.login_required_desc")
|
||||
button.login-btn.btn.btn-primary(data-i18n="login.log_in")
|
||||
button#create-account-btn.btn.btn-primary(data-i18n="login.sign_up")
|
||||
|
||||
// 402 currently not in use. TODO: Set it up
|
||||
else if jqxhr.status === 402
|
||||
h2 402: Payment Required
|
||||
|
||||
else if jqxhr.status === 403
|
||||
h1
|
||||
span.spr 403:
|
||||
span(data-i18n="loading_error.forbidden") Forbidden
|
||||
p(data-i18n="loading_error.forbidden_desc")
|
||||
|
||||
// this should make no diff... but sometimes the server returns 403 when it should return 401
|
||||
if !me.isAnonymous()
|
||||
button#logout-btn.btn.btn-primary(data-i18n="login.log_out")
|
||||
|
||||
else if jqxhr.status === 404
|
||||
h1
|
||||
span.spr 404:
|
||||
span(data-i18n="loading_error.not_found")
|
||||
- var num = Math.floor(Math.random() * 3) + 1;
|
||||
img#not-found-img(src="/images/pages/not_found/404_#{num}.png")
|
||||
p(data-i18n="loading_error.not_found_desc")
|
||||
|
||||
else if !jqxhr.status
|
||||
h1(data-i18n="loading_error.connection_failure")
|
||||
p It doesn’t look like you’re connected to the internet! Check your network connection and then reload this page.
|
||||
|
||||
else
|
||||
strong(data-i18n="loading_error.unknown") Unknown error.
|
||||
if jqxhr.status === 408
|
||||
h1
|
||||
span.spr 408:
|
||||
span(data-i18n="loading_error.timeout")
|
||||
else if jqxhr.status >= 500 && jqxhr.status <= 599
|
||||
h1
|
||||
span.spr #{jqxhr.status}:
|
||||
span(data-i18n="loading_error.server_error")
|
||||
else
|
||||
h1(data-i18n="loading_error.unknown")
|
||||
|
||||
p(data-i18n="loading_error.general_desc")
|
||||
|
||||
button.btn.btn-xs.retry-loading-resource(data-i18n="common.retry", data-resource-index=resourceIndex) Retry
|
||||
button.btn.btn-xs.skip-loading-resource(data-i18n="play_level.skip", data-resource-index=resourceIndex) Skip
|
||||
#links-row.row
|
||||
.col-sm-3
|
||||
strong(data-i18n="cmomon.help") Help
|
||||
br
|
||||
a(href="/", data-i18n="nav.home")
|
||||
br
|
||||
a(href=view.forumLink(), data-i18n="nav.forum")
|
||||
br
|
||||
a(tabindex=-1, data-toggle="coco-modal", data-target="core/ContactModal", data-i18n="nav.contact")
|
||||
br
|
||||
a(href='/community', data-i18n="nav.community")
|
||||
|
||||
.col-sm-3
|
||||
strong(data-i18n="courses.students")
|
||||
br
|
||||
a(href="/courses/students", data-i18n="nav.learn_to_code")
|
||||
if me.isAnonymous()
|
||||
br
|
||||
a.login-btn(data-i18n="login.log_in")
|
||||
br
|
||||
a(href="/courses", data-i18n="courses.join_class")
|
||||
|
||||
.col-sm-3
|
||||
strong(data-i18n="nav.teachers")
|
||||
br
|
||||
a(href="/schools", data-i18n="about.why_codecombat")
|
||||
if me.isAnonymous()
|
||||
br
|
||||
a.login-btn(data-i18n="login.log_in")
|
||||
br
|
||||
a(href="/courses/teachers", data-i18n="nav.create_a_class")
|
||||
|
||||
.col-sm-3
|
||||
strong(data-i18n="nav.other")
|
||||
br
|
||||
a(href="http://blog.codecombat.com/", data-i18n="nav.blog")
|
||||
br
|
||||
a(href="https://www.facebook.com/codecombat", data-i18n="nav.facebook")
|
||||
br
|
||||
a(href="https://twitter.com/codecombat", data-i18n="nav.twitter")
|
|
@ -6,7 +6,8 @@ ol.breadcrumb
|
|||
a(href=path.url)= path.name
|
||||
li.active= currentFolder
|
||||
|
||||
.well.pull-left#test-wrapper
|
||||
#test-wrapper.well.pull-left
|
||||
#demo-area
|
||||
#testing-area
|
||||
|
||||
.nav.nav-pills.nav-stacked.pull-right.well#test-nav
|
||||
|
|
|
@ -3,6 +3,7 @@ utils = require 'core/utils'
|
|||
CocoClass = require 'core/CocoClass'
|
||||
loadingScreenTemplate = require 'templates/core/loading'
|
||||
loadingErrorTemplate = require 'templates/core/loading-error'
|
||||
auth = require 'core/auth'
|
||||
|
||||
lastToggleModalCall = 0
|
||||
visibleModal = null
|
||||
|
@ -16,8 +17,9 @@ module.exports = class CocoView extends Backbone.View
|
|||
template: -> ''
|
||||
|
||||
events:
|
||||
'click .retry-loading-resource': 'onRetryResource'
|
||||
'click .skip-loading-resource': 'onSkipResource'
|
||||
'click #loading-error .login-btn': 'onClickLoadingErrorLoginButton'
|
||||
'click #loading-error #create-account-btn': 'onClickLoadingErrorCreateAccountButton'
|
||||
'click #loading-error #logout-btn': 'onClickLoadingErrorLogoutButton'
|
||||
|
||||
subscriptions: {}
|
||||
shortcuts: {}
|
||||
|
@ -157,43 +159,25 @@ module.exports = class CocoView extends Backbone.View
|
|||
onResourceLoadFailed: (e) ->
|
||||
r = e.resource
|
||||
return if r.jqxhr?.status is 402 # payment-required failures are handled separately
|
||||
if r.jqxhr?.status is 0
|
||||
r.retries ?= 0
|
||||
r.retries += 1
|
||||
if r.retries > 20
|
||||
msg = 'Your computer or our servers appear to be offline. Please try refreshing.'
|
||||
noty text: msg, layout: 'center', type: 'error', killer: true
|
||||
return
|
||||
else
|
||||
@warnConnectionError()
|
||||
return _.delay (=> r.load()), 3000
|
||||
|
||||
@$el.find('.loading-container .errors').append(loadingErrorTemplate({
|
||||
status: r.jqxhr?.status
|
||||
name: r.name
|
||||
resourceIndex: r.rid,
|
||||
responseText: r.jqxhr?.responseText
|
||||
})).i18n()
|
||||
@$el.find('.progress').hide()
|
||||
@showError(r.jqxhr)
|
||||
|
||||
warnConnectionError: ->
|
||||
msg = $.i18n.t 'loading_error.connection_failure', defaultValue: 'Connection failed.'
|
||||
noty text: msg, layout: 'center', type: 'error', killer: true, timeout: 3000
|
||||
|
||||
onRetryResource: (e) ->
|
||||
res = @supermodel.getResource($(e.target).data('resource-index'))
|
||||
# different views may respond to this call, and not all have the resource to reload
|
||||
return unless res and res.isFailed
|
||||
res.load()
|
||||
@$el.find('.progress').show()
|
||||
$(e.target).closest('.loading-error-alert').remove()
|
||||
|
||||
onSkipResource: (e) ->
|
||||
res = @supermodel.getResource($(e.target).data('resource-index'))
|
||||
return unless res and res.isFailed
|
||||
res.markLoaded()
|
||||
@$el.find('.progress').show()
|
||||
$(e.target).closest('.loading-error-alert').remove()
|
||||
onClickLoadingErrorLoginButton: (e) ->
|
||||
e.stopPropagation() # Backbone subviews and superviews will handle this call repeatedly otherwise
|
||||
AuthModal = require 'views/core/AuthModal'
|
||||
@openModalView(new AuthModal({mode: 'login'}))
|
||||
|
||||
onClickLoadingErrorCreateAccountButton: (e) ->
|
||||
e.stopPropagation()
|
||||
AuthModal = require 'views/core/AuthModal'
|
||||
@openModalView(new AuthModal({mode: 'signup'}))
|
||||
|
||||
onClickLoadingErrorLogoutButton: (e) ->
|
||||
e.stopPropagation()
|
||||
auth.logoutUser()
|
||||
|
||||
# Modals
|
||||
|
||||
|
@ -254,6 +238,23 @@ module.exports = class CocoView extends Backbone.View
|
|||
@_lastLoading.find('.loading-screen').remove()
|
||||
@_lastLoading.find('>').removeClass('hidden')
|
||||
@_lastLoading = null
|
||||
|
||||
showError: (jqxhr) ->
|
||||
return unless @_lastLoading?
|
||||
context = {
|
||||
jqxhr: jqxhr
|
||||
view: @
|
||||
me: me
|
||||
}
|
||||
@_lastLoading.find('.loading-screen').replaceWith((loadingErrorTemplate(context)))
|
||||
@_lastLoading.i18n()
|
||||
|
||||
forumLink: ->
|
||||
link = 'http://discourse.codecombat.com/'
|
||||
lang = (me.get('preferredLanguage') or 'en-US').split('-')[0]
|
||||
if lang in ['zh', 'ru', 'es', 'fr', 'pt', 'de', 'nl', 'lt']
|
||||
link += "c/other-languages/#{lang}"
|
||||
link
|
||||
|
||||
showReadOnly: ->
|
||||
return if me.isAdmin() or me.isArtisan()
|
||||
|
|
|
@ -100,13 +100,6 @@ module.exports = class RootView extends CocoView
|
|||
c.usesSocialMedia = @usesSocialMedia
|
||||
c
|
||||
|
||||
forumLink: ->
|
||||
link = 'http://discourse.codecombat.com/'
|
||||
lang = (me.get('preferredLanguage') or 'en-US').split('-')[0]
|
||||
if lang in ['zh', 'ru', 'es', 'fr', 'pt', 'de', 'nl', 'lt']
|
||||
link += "c/other-languages/#{lang}"
|
||||
link
|
||||
|
||||
afterRender: ->
|
||||
if @$el.find('#site-nav').length # hack...
|
||||
@$el.addClass('site-chrome')
|
||||
|
|
|
@ -32,23 +32,23 @@ module.exports = class ClassroomView extends RootView
|
|||
initialize: (options, classroomID) ->
|
||||
return if me.isAnonymous()
|
||||
@classroom = new Classroom({_id: classroomID})
|
||||
@supermodel.loadModel @classroom, 'classroom'
|
||||
@supermodel.loadModel @classroom
|
||||
@courses = new CocoCollection([], { url: "/db/course", model: Course})
|
||||
@courses.comparator = '_id'
|
||||
@supermodel.loadCollection(@courses, 'courses')
|
||||
@supermodel.loadCollection(@courses)
|
||||
@campaigns = new CocoCollection([], { url: "/db/campaign", model: Campaign })
|
||||
@courses.comparator = '_id'
|
||||
@supermodel.loadCollection(@campaigns, 'campaigns', { data: { type: 'course' }})
|
||||
@supermodel.loadCollection(@campaigns, { data: { type: 'course' }})
|
||||
@courseInstances = new CocoCollection([], { url: "/db/course_instance", model: CourseInstance})
|
||||
@courseInstances.comparator = 'courseID'
|
||||
@supermodel.loadCollection(@courseInstances, 'course_instances', { data: { classroomID: classroomID } })
|
||||
@supermodel.loadCollection(@courseInstances, { data: { classroomID: classroomID } })
|
||||
@prepaids = new Prepaids()
|
||||
@prepaids.comparator = '_id'
|
||||
@prepaids.fetchByCreator(me.id)
|
||||
@supermodel.loadCollection(@prepaids, 'prepaids')
|
||||
@supermodel.loadCollection(@prepaids)
|
||||
@users = new CocoCollection([], { url: "/db/classroom/#{classroomID}/members", model: User })
|
||||
@users.comparator = (user) => user.broadName().toLowerCase()
|
||||
@supermodel.loadCollection(@users, 'users')
|
||||
@supermodel.loadCollection(@users)
|
||||
@listenToOnce @courseInstances, 'sync', @onCourseInstancesSync
|
||||
@sessions = new CocoCollection([], { model: LevelSession })
|
||||
|
||||
|
@ -56,7 +56,7 @@ module.exports = class ClassroomView extends RootView
|
|||
@sessions = new CocoCollection([], { model: LevelSession })
|
||||
for courseInstance in @courseInstances.models
|
||||
sessions = new CocoCollection([], { url: "/db/course_instance/#{courseInstance.id}/level_sessions", model: LevelSession })
|
||||
@supermodel.loadCollection(sessions, 'sessions', { data: { project: ['level', 'playtime', 'creator', 'changed', 'state.complete'].join(' ') } })
|
||||
@supermodel.loadCollection(sessions, { data: { project: ['level', 'playtime', 'creator', 'changed', 'state.complete'].join(' ') } })
|
||||
courseInstance.sessions = sessions
|
||||
sessions.courseInstance = courseInstance
|
||||
courseInstance.sessionsByUser = {}
|
||||
|
|
|
@ -31,13 +31,13 @@ module.exports = class CoursesView extends RootView
|
|||
@courseInstances = new CocoCollection([], { url: "/db/user/#{me.id}/course_instances", model: CourseInstance})
|
||||
@courseInstances.comparator = (ci) -> return ci.get('classroomID') + ci.get('courseID')
|
||||
@listenToOnce @courseInstances, 'sync', @onCourseInstancesLoaded
|
||||
@supermodel.loadCollection(@courseInstances, 'course_instances')
|
||||
@supermodel.loadCollection(@courseInstances)
|
||||
@classrooms = new CocoCollection([], { url: "/db/classroom", model: Classroom })
|
||||
@supermodel.loadCollection(@classrooms, 'classrooms', { data: {memberID: me.id} })
|
||||
@supermodel.loadCollection(@classrooms, { data: {memberID: me.id} })
|
||||
@courses = new CocoCollection([], { url: "/db/course", model: Course})
|
||||
@supermodel.loadCollection(@courses, 'courses')
|
||||
@supermodel.loadCollection(@courses)
|
||||
@campaigns = new CocoCollection([], { url: "/db/campaign", model: Campaign })
|
||||
@supermodel.loadCollection(@campaigns, 'campaigns', { data: { type: 'course' }})
|
||||
@supermodel.loadCollection(@campaigns, { data: { type: 'course' }})
|
||||
|
||||
onCourseInstancesLoaded: ->
|
||||
map = {}
|
||||
|
@ -51,7 +51,7 @@ module.exports = class CoursesView extends RootView
|
|||
model: LevelSession
|
||||
})
|
||||
courseInstance.sessions.comparator = 'changed'
|
||||
@supermodel.loadCollection(courseInstance.sessions, 'sessions', { data: { project: 'state.complete level.original playtime changed' }})
|
||||
@supermodel.loadCollection(courseInstance.sessions, { data: { project: 'state.complete level.original playtime changed' }})
|
||||
|
||||
@hocCourseInstance = @courseInstances.findWhere({hourOfCode: true})
|
||||
if @hocCourseInstance
|
||||
|
|
128
test/app/views/core/CocoView.spec.coffee
Normal file
128
test/app/views/core/CocoView.spec.coffee
Normal file
|
@ -0,0 +1,128 @@
|
|||
CocoView = require 'views/core/CocoView'
|
||||
User = require 'models/User'
|
||||
|
||||
BlandView = class BlandView extends CocoView
|
||||
template: -> ''
|
||||
initialize: ->
|
||||
@user = new User()
|
||||
@supermodel.loadModel(@user)
|
||||
afterRender: ->
|
||||
@$el.css({
|
||||
'border': '2px solid black'
|
||||
'background': 'white'
|
||||
'padding': '20px'
|
||||
})
|
||||
|
||||
describe 'CocoView', ->
|
||||
describe 'network error handling', ->
|
||||
view = null
|
||||
respond = (code, json) ->
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
view.render()
|
||||
request.respondWith({status: code, responseText: JSON.stringify(json or {})})
|
||||
|
||||
beforeEach ->
|
||||
view = new BlandView()
|
||||
|
||||
|
||||
describe 'when the server returns 401', ->
|
||||
beforeEach -> respond(401)
|
||||
|
||||
it 'shows a login button which opens the AuthModal', ->
|
||||
button = view.$el.find('.login-btn')
|
||||
expect(button.length).toBe(1)
|
||||
spyOn(view, 'openModalView').and.callFake (modal) -> expect(modal.mode).toBe('login')
|
||||
button.click()
|
||||
expect(view.openModalView).toHaveBeenCalled()
|
||||
|
||||
it 'shows a create account button which opens the AuthModal', ->
|
||||
button = view.$el.find('#create-account-btn')
|
||||
expect(button.length).toBe(1)
|
||||
spyOn(view, 'openModalView').and.callFake (modal) -> expect(modal.mode).toBe('signup')
|
||||
button.click()
|
||||
expect(view.openModalView).toHaveBeenCalled()
|
||||
|
||||
it 'says "Login Required"', ->
|
||||
expect(view.$el.text().indexOf('Login Required')).toBeGreaterThan(-1)
|
||||
|
||||
it '(demo)', ->
|
||||
$('#demo-area').append(view.$el)
|
||||
|
||||
|
||||
describe 'when the server returns 402', ->
|
||||
|
||||
beforeEach -> respond(402)
|
||||
|
||||
it 'does nothing, because it is up to the view to handle payment next steps'
|
||||
|
||||
|
||||
describe 'when the server returns 403', ->
|
||||
|
||||
beforeEach -> respond(403)
|
||||
|
||||
it 'includes a logout button which logs out the account', ->
|
||||
button = view.$el.find('#logout-btn')
|
||||
expect(button.length).toBe(1)
|
||||
button.click()
|
||||
request = jasmine.Ajax.requests.mostRecent()
|
||||
expect(request.url).toBe('/auth/logout')
|
||||
|
||||
it '(demo)', ->
|
||||
$('#demo-area').append(view.$el)
|
||||
|
||||
|
||||
describe 'when the server returns 404', ->
|
||||
|
||||
beforeEach -> respond(404)
|
||||
|
||||
it 'includes one of the 404 images', ->
|
||||
img = view.$el.find('#not-found-img')
|
||||
expect(img.length).toBe(1)
|
||||
|
||||
it '(demo)', ->
|
||||
$('#demo-area').append(view.$el)
|
||||
|
||||
|
||||
describe 'when the server returns 408', ->
|
||||
|
||||
beforeEach -> respond(408)
|
||||
|
||||
it 'includes "Server Timeout" in the header', ->
|
||||
expect(view.$el.text().indexOf('Server Timeout')).toBeGreaterThan(-1)
|
||||
|
||||
it 'shows a message encouraging refreshing the page or following links', ->
|
||||
expect(view.$el.text().indexOf('refresh')).toBeGreaterThan(-1)
|
||||
|
||||
it '(demo)', ->
|
||||
$('#demo-area').append(view.$el)
|
||||
|
||||
|
||||
describe 'when no connection is made', ->
|
||||
|
||||
beforeEach ->
|
||||
respond()
|
||||
|
||||
it 'shows "Connection Failed"', ->
|
||||
expect(view.$el.text().indexOf('Connection Failed')).toBeGreaterThan(-1)
|
||||
|
||||
it '(demo)', ->
|
||||
$('#demo-area').append(view.$el)
|
||||
|
||||
|
||||
describe 'when the server returns any other number >= 400', ->
|
||||
|
||||
beforeEach -> respond(9001)
|
||||
|
||||
it 'includes "Unknown Error" in the header', ->
|
||||
expect(view.$el.text().indexOf('Unknown Error')).toBeGreaterThan(-1)
|
||||
|
||||
it 'shows a message encouraging refreshing the page or following links', ->
|
||||
expect(view.$el.text().indexOf('refresh')).toBeGreaterThan(-1)
|
||||
|
||||
it '(demo)', ->
|
||||
$('#demo-area').append(view.$el)
|
||||
|
||||
|
||||
|
||||
|
||||
|
Loading…
Reference in a new issue