Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
5b9e0e9d
Commit
5b9e0e9d
authored
Dec 14, 2015
by
Andy Armstrong
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #10957 from edx/andya/studio-xblock-debugging
Support local debugging of XBlock JavaScript
parents
a6e83834
cc24ca55
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
386 additions
and
361 deletions
+386
-361
cms/static/coffee/spec/views/module_edit_spec.coffee
+5
-3
cms/static/js/spec/views/xblock_spec.js
+8
-8
cms/static/js/views/xblock.js
+13
-13
common/static/common/js/components/utils/view_utils.js
+261
-239
common/static/common/js/spec/components/view_utils_spec.js
+99
-98
No files found.
cms/static/coffee/spec/views/module_edit_spec.coffee
View file @
5b9e0e9d
define
[
"jquery"
,
"js/spec_helpers/edit_helpers"
,
"coffee/src/views/module_edit"
,
"js/models/module_info"
,
"xmodule"
],
(
$
,
edit_helpers
,
ModuleEdit
,
ModuleModel
)
->
define
[
"jquery"
,
"common/js/components/utils/view_utils"
,
"js/spec_helpers/edit_helpers"
,
"coffee/src/views/module_edit"
,
"js/models/module_info"
,
"xmodule"
],
(
$
,
ViewUtils
,
edit_helpers
,
ModuleEdit
,
ModuleModel
)
->
describe
"ModuleEdit"
,
->
describe
"ModuleEdit"
,
->
beforeEach
->
beforeEach
->
...
@@ -60,7 +62,7 @@ define ["jquery", "js/spec_helpers/edit_helpers", "coffee/src/views/module_edit"
...
@@ -60,7 +62,7 @@ define ["jquery", "js/spec_helpers/edit_helpers", "coffee/src/views/module_edit"
spyOn
(
@
moduleEdit
,
'loadDisplay'
)
spyOn
(
@
moduleEdit
,
'loadDisplay'
)
spyOn
(
@
moduleEdit
,
'delegateEvents'
)
spyOn
(
@
moduleEdit
,
'delegateEvents'
)
spyOn
(
$
.
fn
,
'append'
)
spyOn
(
$
.
fn
,
'append'
)
spyOn
(
$
,
'getScript'
).
andReturn
(
$
.
Deferred
().
resolve
().
promise
())
spyOn
(
ViewUtils
,
'loadJavaScript'
).
andReturn
(
$
.
Deferred
().
resolve
().
promise
());
window
.
MockXBlock
=
(
runtime
,
element
)
->
window
.
MockXBlock
=
(
runtime
,
element
)
->
return
{
}
return
{
}
...
@@ -150,7 +152,7 @@ define ["jquery", "js/spec_helpers/edit_helpers", "coffee/src/views/module_edit"
...
@@ -150,7 +152,7 @@ define ["jquery", "js/spec_helpers/edit_helpers", "coffee/src/views/module_edit"
expect
(
$
(
'head'
).
append
).
toHaveBeenCalledWith
(
"<script>inline-js</script>"
)
expect
(
$
(
'head'
).
append
).
toHaveBeenCalledWith
(
"<script>inline-js</script>"
)
it
"loads js urls from fragments"
,
->
it
"loads js urls from fragments"
,
->
expect
(
$
.
get
Script
).
toHaveBeenCalledWith
(
"js-url"
)
expect
(
ViewUtils
.
loadJava
Script
).
toHaveBeenCalledWith
(
"js-url"
)
it
"loads head html"
,
->
it
"loads head html"
,
->
expect
(
$
(
'head'
).
append
).
toHaveBeenCalledWith
(
"head-html"
)
expect
(
$
(
'head'
).
append
).
toHaveBeenCalledWith
(
"head-html"
)
...
...
cms/static/js/spec/views/xblock_spec.js
View file @
5b9e0e9d
define
([
"jquery"
,
"common/js/spec_helpers/ajax_helpers"
,
"URI"
,
"js/views/xblock"
,
"js/models/xblock_info
"
,
define
([
"jquery"
,
"URI"
,
"common/js/spec_helpers/ajax_helpers"
,
"common/js/components/utils/view_utils
"
,
"xmodule"
,
"coffee/src/main"
,
"xblock/cms.runtime.v1"
],
"js/views/xblock"
,
"js/models/xblock_info"
,
"xmodule"
,
"coffee/src/main"
,
"xblock/cms.runtime.v1"
],
function
(
$
,
AjaxHelpers
,
URI
,
XBlockView
,
XBlockInfo
)
{
function
(
$
,
URI
,
AjaxHelpers
,
ViewUtils
,
XBlockView
,
XBlockInfo
)
{
"use strict"
;
describe
(
"XBlockView"
,
function
()
{
describe
(
"XBlockView"
,
function
()
{
var
model
,
xblockView
,
mockXBlockHtml
;
var
model
,
xblockView
,
mockXBlockHtml
;
...
@@ -89,11 +89,11 @@ define([ "jquery", "common/js/spec_helpers/ajax_helpers", "URI", "js/views/xbloc
...
@@ -89,11 +89,11 @@ define([ "jquery", "common/js/spec_helpers/ajax_helpers", "URI", "js/views/xbloc
it
(
'aborts rendering when a dependent script fails to load'
,
function
()
{
it
(
'aborts rendering when a dependent script fails to load'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
),
var
requests
=
AjaxHelpers
.
requests
(
this
),
m
ockJavaScriptUrl
=
"mock
.js"
,
m
issingJavaScriptUrl
=
"no_such_file
.js"
,
promise
;
promise
;
spyOn
(
$
,
'get
Script'
).
andReturn
(
$
.
Deferred
().
reject
().
promise
());
spyOn
(
ViewUtils
,
'loadJava
Script'
).
andReturn
(
$
.
Deferred
().
reject
().
promise
());
promise
=
postXBlockRequest
(
requests
,
[
promise
=
postXBlockRequest
(
requests
,
[
[
"hash5"
,
{
mimetype
:
"application/javascript"
,
kind
:
"url"
,
data
:
m
ock
JavaScriptUrl
}]
[
"hash5"
,
{
mimetype
:
"application/javascript"
,
kind
:
"url"
,
data
:
m
issing
JavaScriptUrl
}]
]);
]);
expect
(
promise
.
isRejected
()).
toBe
(
true
);
expect
(
promise
.
isRejected
()).
toBe
(
true
);
});
});
...
@@ -104,7 +104,7 @@ define([ "jquery", "common/js/spec_helpers/ajax_helpers", "URI", "js/views/xbloc
...
@@ -104,7 +104,7 @@ define([ "jquery", "common/js/spec_helpers/ajax_helpers", "URI", "js/views/xbloc
postXBlockRequest
(
AjaxHelpers
.
requests
(
this
),
[]);
postXBlockRequest
(
AjaxHelpers
.
requests
(
this
),
[]);
xblockView
.
$el
.
find
(
".notification-action-button"
).
click
();
xblockView
.
$el
.
find
(
".notification-action-button"
).
click
();
expect
(
notifySpy
).
toHaveBeenCalledWith
(
"add-missing-groups"
,
model
.
get
(
"id"
));
expect
(
notifySpy
).
toHaveBeenCalledWith
(
"add-missing-groups"
,
model
.
get
(
"id"
));
})
})
;
});
});
});
});
});
});
cms/static/js/views/xblock.js
View file @
5b9e0e9d
define
([
"jquery"
,
"underscore"
,
"js/views/baseview"
,
"xblock/runtime.v1"
],
define
([
"jquery"
,
"underscore"
,
"common/js/components/utils/view_utils"
,
"js/views/baseview"
,
"xblock/runtime.v1"
],
function
(
$
,
_
,
BaseView
,
XBlock
)
{
function
(
$
,
_
,
ViewUtils
,
BaseView
,
XBlock
)
{
'use strict'
;
var
XBlockView
=
BaseView
.
extend
({
var
XBlockView
=
BaseView
.
extend
({
// takes XBlockInfo as a model
// takes XBlockInfo as a model
...
@@ -83,7 +84,7 @@ define(["jquery", "underscore", "js/views/baseview", "xblock/runtime.v1"],
...
@@ -83,7 +84,7 @@ define(["jquery", "underscore", "js/views/baseview", "xblock/runtime.v1"],
* may have thrown JavaScript errors after rendering in which case the xblock parameter
* may have thrown JavaScript errors after rendering in which case the xblock parameter
* will be null.
* will be null.
*/
*/
xblockReady
:
function
(
xblock
)
{
xblockReady
:
function
(
xblock
)
{
// jshint ignore:line
// Do nothing
// Do nothing
},
},
...
@@ -95,7 +96,7 @@ define(["jquery", "underscore", "js/views/baseview", "xblock/runtime.v1"],
...
@@ -95,7 +96,7 @@ define(["jquery", "underscore", "js/views/baseview", "xblock/runtime.v1"],
* represents this process.
* represents this process.
* @param fragment The fragment returned from the xblock_handler
* @param fragment The fragment returned from the xblock_handler
* @param element The element into which to render the fragment (defaults to this.$el)
* @param element The element into which to render the fragment (defaults to this.$el)
* @returns {
jQuery p
romise} A promise representing the rendering process
* @returns {
P
romise} A promise representing the rendering process
*/
*/
renderXBlockFragment
:
function
(
fragment
,
element
)
{
renderXBlockFragment
:
function
(
fragment
,
element
)
{
var
html
=
fragment
.
html
,
var
html
=
fragment
.
html
,
...
@@ -131,7 +132,7 @@ define(["jquery", "underscore", "js/views/baseview", "xblock/runtime.v1"],
...
@@ -131,7 +132,7 @@ define(["jquery", "underscore", "js/views/baseview", "xblock/runtime.v1"],
* Dynamically loads all of an XBlock's dependent resources. This is an asynchronous
* Dynamically loads all of an XBlock's dependent resources. This is an asynchronous
* process so a promise is returned.
* process so a promise is returned.
* @param resources The resources to be rendered
* @param resources The resources to be rendered
* @returns {
jQuery p
romise} A promise representing the rendering process
* @returns {
P
romise} A promise representing the rendering process
*/
*/
addXBlockFragmentResources
:
function
(
resources
)
{
addXBlockFragmentResources
:
function
(
resources
)
{
var
self
=
this
,
var
self
=
this
,
...
@@ -171,7 +172,7 @@ define(["jquery", "underscore", "js/views/baseview", "xblock/runtime.v1"],
...
@@ -171,7 +172,7 @@ define(["jquery", "underscore", "js/views/baseview", "xblock/runtime.v1"],
/**
/**
* Loads the specified resource into the page.
* Loads the specified resource into the page.
* @param resource The resource to be loaded.
* @param resource The resource to be loaded.
* @returns {
jQuery p
romise} A promise representing the loading of the resource.
* @returns {
P
romise} A promise representing the loading of the resource.
*/
*/
loadResource
:
function
(
resource
)
{
loadResource
:
function
(
resource
)
{
var
head
=
$
(
'head'
),
var
head
=
$
(
'head'
),
...
@@ -189,8 +190,7 @@ define(["jquery", "underscore", "js/views/baseview", "xblock/runtime.v1"],
...
@@ -189,8 +190,7 @@ define(["jquery", "underscore", "js/views/baseview", "xblock/runtime.v1"],
if
(
kind
===
"text"
)
{
if
(
kind
===
"text"
)
{
head
.
append
(
"<script>"
+
data
+
"</script>"
);
head
.
append
(
"<script>"
+
data
+
"</script>"
);
}
else
if
(
kind
===
"url"
)
{
}
else
if
(
kind
===
"url"
)
{
// Return a promise for the script resolution
return
ViewUtils
.
loadJavaScript
(
data
);
return
$
.
getScript
(
data
);
}
}
}
else
if
(
mimetype
===
"text/html"
)
{
}
else
if
(
mimetype
===
"text/html"
)
{
if
(
placement
===
"head"
)
{
if
(
placement
===
"head"
)
{
...
@@ -202,11 +202,11 @@ define(["jquery", "underscore", "js/views/baseview", "xblock/runtime.v1"],
...
@@ -202,11 +202,11 @@ define(["jquery", "underscore", "js/views/baseview", "xblock/runtime.v1"],
},
},
fireNotificationActionEvent
:
function
(
event
)
{
fireNotificationActionEvent
:
function
(
event
)
{
var
eventName
=
$
(
event
.
currentTarget
).
data
(
"notification-action"
);
var
eventName
=
$
(
event
.
currentTarget
).
data
(
"notification-action"
);
if
(
eventName
)
{
if
(
eventName
)
{
event
.
preventDefault
();
event
.
preventDefault
();
this
.
notifyRuntime
(
eventName
,
this
.
model
.
get
(
"id"
));
this
.
notifyRuntime
(
eventName
,
this
.
model
.
get
(
"id"
));
}
}
}
}
});
});
...
...
common/static/common/js/components/utils/view_utils.js
View file @
5b9e0e9d
/**
/**
* Provides useful utilities for views.
* Provides useful utilities for views.
*/
*/
;(
function
(
define
)
{
;(
function
(
define
,
require
)
{
'use strict'
;
'use strict'
;
define
([
"jquery"
,
"underscore"
,
"gettext"
,
"common/js/components/views/feedback_notification"
,
define
([
"jquery"
,
"underscore"
,
"gettext"
,
"common/js/components/views/feedback_notification"
,
"common/js/components/views/feedback_prompt"
],
"common/js/components/views/feedback_prompt"
],
function
(
$
,
_
,
gettext
,
NotificationView
,
PromptView
)
{
function
(
$
,
_
,
gettext
,
NotificationView
,
PromptView
)
{
var
toggleExpandCollapse
,
showLoadingIndicator
,
hideLoadingIndicator
,
confirmThenRunOperation
,
var
toggleExpandCollapse
,
showLoadingIndicator
,
hideLoadingIndicator
,
confirmThenRunOperation
,
runOperationShowingMessage
,
withDisabledElement
,
disableElementWhileRunning
,
runOperationShowingMessage
,
withDisabledElement
,
disableElementWhileRunning
,
getScrollOffset
,
setScrollOffset
,
setScrollTop
,
redirect
,
reload
,
hasChangedAttributes
,
deleteNotificationHandler
,
getScrollOffset
,
setScrollOffset
,
setScrollTop
,
redirect
,
reload
,
hasChangedAttributes
,
validateRequiredField
,
validateURLItemEncoding
,
validateTotalKeyLength
,
checkTotalKeyLengthViolations
;
deleteNotificationHandler
,
validateRequiredField
,
validateURLItemEncoding
,
validateTotalKeyLength
,
checkTotalKeyLengthViolations
,
loadJavaScript
;
// see https://openedx.atlassian.net/browse/TNL-889 for what is it and why it's 65
// see https://openedx.atlassian.net/browse/TNL-889 for what is it and why it's 65
var
MAX_SUM_KEY_LENGTH
=
65
;
var
MAX_SUM_KEY_LENGTH
=
65
;
/**
/**
* Toggles the expanded state of the current element.
* Toggles the expanded state of the current element.
*/
*/
toggleExpandCollapse
=
function
(
target
,
collapsedClass
)
{
toggleExpandCollapse
=
function
(
target
,
collapsedClass
)
{
// Support the old 'collapsed' option until fully switched over to is-collapsed
// Support the old 'collapsed' option until fully switched over to is-collapsed
if
(
!
collapsedClass
)
{
if
(
!
collapsedClass
)
{
collapsedClass
=
'collapsed'
;
collapsedClass
=
'collapsed'
;
}
}
target
.
closest
(
'.expand-collapse'
).
toggleClass
(
'expand collapse'
);
target
.
closest
(
'.expand-collapse'
).
toggleClass
(
'expand collapse'
);
target
.
closest
(
'.is-collapsible, .window'
).
toggleClass
(
collapsedClass
);
target
.
closest
(
'.is-collapsible, .window'
).
toggleClass
(
collapsedClass
);
target
.
closest
(
'.is-collapsible'
).
children
(
'article'
).
slideToggle
();
target
.
closest
(
'.is-collapsible'
).
children
(
'article'
).
slideToggle
();
};
};
/**
/**
* Show the page's loading indicator.
* Show the page's loading indicator.
*/
*/
showLoadingIndicator
=
function
()
{
showLoadingIndicator
=
function
()
{
$
(
'.ui-loading'
).
show
();
$
(
'.ui-loading'
).
show
();
};
};
/**
/**
* Hide the page's loading indicator.
* Hide the page's loading indicator.
*/
*/
hideLoadingIndicator
=
function
()
{
hideLoadingIndicator
=
function
()
{
$
(
'.ui-loading'
).
hide
();
$
(
'.ui-loading'
).
hide
();
};
};
/**
/**
* Confirms with the user whether to run an operation or not, and then runs it if desired.
* Confirms with the user whether to run an operation or not, and then runs it if desired.
*/
*/
confirmThenRunOperation
=
function
(
title
,
message
,
actionLabel
,
operation
,
onCancelCallback
)
{
confirmThenRunOperation
=
function
(
title
,
message
,
actionLabel
,
operation
,
onCancelCallback
)
{
return
new
PromptView
.
Warning
({
return
new
PromptView
.
Warning
({
title
:
title
,
title
:
title
,
message
:
message
,
message
:
message
,
actions
:
{
actions
:
{
primary
:
{
primary
:
{
text
:
actionLabel
,
text
:
actionLabel
,
click
:
function
(
prompt
)
{
click
:
function
(
prompt
)
{
prompt
.
hide
();
prompt
.
hide
();
operation
();
operation
();
}
}
},
},
secondary
:
{
secondary
:
{
text
:
gettext
(
'Cancel'
),
text
:
gettext
(
'Cancel'
),
click
:
function
(
prompt
)
{
click
:
function
(
prompt
)
{
if
(
onCancelCallback
)
{
if
(
onCancelCallback
)
{
onCancelCallback
();
onCancelCallback
();
}
return
prompt
.
hide
();
}
}
return
prompt
.
hide
();
}
}
}
}
}
}).
show
();
}).
show
();
};
};
/**
* Shows a progress message for the duration of an asynchronous operation.
* Note: this does not remove the notification upon failure because an error
* will be shown that shouldn't be removed.
* @param message The message to show.
* @param operation A function that returns a promise representing the operation.
*/
runOperationShowingMessage
=
function
(
message
,
operation
)
{
var
notificationView
;
notificationView
=
new
NotificationView
.
Mini
({
title
:
gettext
(
message
)
});
notificationView
.
show
();
return
operation
().
done
(
function
()
{
notificationView
.
hide
();
});
};
/**
/**
* Wraps a Backbone event callback to disable the event's target element
.
* Shows a progress message for the duration of an asynchronous operation
.
*
* Note: this does not remove the notification upon failure because an error
* This paradigm is designed to be used in Backbone event maps where
* will be shown that shouldn't be removed.
* multiple events firing simultaneously is not desired
.
* @param message The message to show
.
*
* @param operation A function that returns a promise representing the operation.
* @param functionName the function to execute, as a string.
*/
* The function must return a jQuery promise and be able to take an event
runOperationShowingMessage
=
function
(
message
,
operation
)
{
*/
var
notificationView
;
withDisabledElement
=
function
(
functionName
)
{
notificationView
=
new
NotificationView
.
Mini
(
{
return
function
(
event
)
{
title
:
gettext
(
message
)
var
view
=
this
;
})
;
disableElementWhileRunning
(
$
(
event
.
currentTarget
),
function
()
{
notificationView
.
show
();
//call view.functionName(event), with view as the current this
return
operation
().
done
(
function
()
{
return
view
[
functionName
].
apply
(
view
,
[
event
]
);
notificationView
.
hide
(
);
});
});
};
};
};
/**
* Disables a given element when a given operation is running.
* @param {jQuery} element the element to be disabled.
* @param operation the operation during whose duration the
* element should be disabled. The operation should return
* a JQuery promise.
*/
disableElementWhileRunning
=
function
(
element
,
operation
)
{
element
.
addClass
(
"is-disabled"
).
attr
(
'aria-disabled'
,
true
);
return
operation
().
always
(
function
()
{
element
.
removeClass
(
"is-disabled"
).
attr
(
'aria-disabled'
,
false
);
});
};
/**
/**
* Returns a handler that removes a notification, both dismissing it and deleting it from the database.
* Wraps a Backbone event callback to disable the event's target element.
* @param callback function to call when deletion succeeds
*
*/
* This paradigm is designed to be used in Backbone event maps where
deleteNotificationHandler
=
function
(
callback
)
{
* multiple events firing simultaneously is not desired.
return
function
(
event
)
{
*
event
.
preventDefault
();
* @param functionName the function to execute, as a string.
$
.
ajax
({
* The function must return a jQuery promise and be able to take an event
url
:
$
(
this
).
data
(
'dismiss-link'
),
*/
type
:
'DELETE'
,
withDisabledElement
=
function
(
functionName
)
{
success
:
callback
return
function
(
event
)
{
var
view
=
this
;
disableElementWhileRunning
(
$
(
event
.
currentTarget
),
function
()
{
//call view.functionName(event), with view as the current this
return
view
[
functionName
].
apply
(
view
,
[
event
]);
});
};
};
/**
* Disables a given element when a given operation is running.
* @param {jQuery} element the element to be disabled.
* @param operation the operation during whose duration the
* element should be disabled. The operation should return
* a JQuery promise.
*/
disableElementWhileRunning
=
function
(
element
,
operation
)
{
element
.
addClass
(
"is-disabled"
).
attr
(
'aria-disabled'
,
true
);
return
operation
().
always
(
function
()
{
element
.
removeClass
(
"is-disabled"
).
attr
(
'aria-disabled'
,
false
);
});
});
};
};
};
/**
/**
* Performs an animated scroll so that the window has the specified scroll top.
* Returns a handler that removes a notification, both dismissing it and deleting it from the database.
* @param scrollTop The desired scroll top for the window.
* @param callback function to call when deletion succeeds
*/
*/
setScrollTop
=
function
(
scrollTop
)
{
deleteNotificationHandler
=
function
(
callback
)
{
$
(
'html, body'
).
animate
({
return
function
(
event
)
{
scrollTop
:
scrollTop
event
.
preventDefault
();
},
500
);
$
.
ajax
({
};
url
:
$
(
this
).
data
(
'dismiss-link'
),
type
:
'DELETE'
,
success
:
callback
});
};
};
/**
/**
* Returns the relative position that the element is scrolled from the top of the view port.
* Performs an animated scroll so that the window has the specified scroll top.
* @param element The element in question.
* @param scrollTop The desired scroll top for the window.
*/
*/
getScrollOffset
=
function
(
element
)
{
setScrollTop
=
function
(
scrollTop
)
{
var
elementTop
=
element
.
offset
().
top
;
$
(
'html, body'
).
animate
({
return
elementTop
-
$
(
window
).
scrollTop
();
scrollTop
:
scrollTop
};
},
500
);
};
/**
* Returns the relative position that the element is scrolled from the top of the view port.
* @param element The element in question.
*/
getScrollOffset
=
function
(
element
)
{
var
elementTop
=
element
.
offset
().
top
;
return
elementTop
-
$
(
window
).
scrollTop
();
};
/**
/**
* Scrolls the window so that the element is scrolled down to the specified relative position
* Scrolls the window so that the element is scrolled down to the specified relative position
* from the top of the view port.
* from the top of the view port.
* @param element The element in question.
* @param element The element in question.
* @param offset The amount by which the element should be scrolled from the top of the view port.
* @param offset The amount by which the element should be scrolled from the top of the view port.
*/
*/
setScrollOffset
=
function
(
element
,
offset
)
{
setScrollOffset
=
function
(
element
,
offset
)
{
var
elementTop
=
element
.
offset
().
top
,
var
elementTop
=
element
.
offset
().
top
,
newScrollTop
=
elementTop
-
offset
;
newScrollTop
=
elementTop
-
offset
;
setScrollTop
(
newScrollTop
);
setScrollTop
(
newScrollTop
);
};
};
/**
/**
* Redirects to the specified URL. This is broken out as its own function for unit testing.
* Redirects to the specified URL. This is broken out as its own function for unit testing.
*/
*/
redirect
=
function
(
url
)
{
redirect
=
function
(
url
)
{
window
.
location
=
url
;
window
.
location
=
url
;
};
};
/**
/**
* Reloads the page. This is broken out as its own function for unit testing.
* Reloads the page. This is broken out as its own function for unit testing.
*/
*/
reload
=
function
()
{
reload
=
function
()
{
window
.
location
.
reload
();
window
.
location
.
reload
();
};
};
/**
/**
* Returns true if a model has changes to at least one of the specified attributes.
* Returns true if a model has changes to at least one of the specified attributes.
* @param model The model in question.
* @param model The model in question.
* @param attributes The list of attributes to be compared.
* @param attributes The list of attributes to be compared.
* @returns {boolean} Returns true if attribute changes are found.
* @returns {boolean} Returns true if attribute changes are found.
*/
*/
hasChangedAttributes
=
function
(
model
,
attributes
)
{
hasChangedAttributes
=
function
(
model
,
attributes
)
{
var
i
,
changedAttributes
=
model
.
changedAttributes
();
var
i
,
changedAttributes
=
model
.
changedAttributes
();
if
(
!
changedAttributes
)
{
if
(
!
changedAttributes
)
{
return
false
;
return
false
;
}
}
for
(
i
=
0
;
i
<
attributes
.
length
;
i
++
)
{
for
(
i
=
0
;
i
<
attributes
.
length
;
i
++
)
{
if
(
_
.
has
(
changedAttributes
,
attributes
[
i
]))
{
if
(
_
.
has
(
changedAttributes
,
attributes
[
i
]))
{
return
true
;
return
true
;
}
}
}
}
return
false
;
return
false
;
};
};
/**
/**
* Helper method for course/library creation - verifies a required field is not blank.
* Helper method for course/library creation - verifies a required field is not blank.
*/
*/
validateRequiredField
=
function
(
msg
)
{
validateRequiredField
=
function
(
msg
)
{
return
msg
.
length
===
0
?
gettext
(
'Required field.'
)
:
''
;
return
msg
.
length
===
0
?
gettext
(
'Required field.'
)
:
''
;
};
};
/**
/**
* Helper method for course/library creation.
* Helper method for course/library creation.
* Check that a course (org, number, run) doesn't use any special characters
* Check that a course (org, number, run) doesn't use any special characters
*/
*/
validateURLItemEncoding
=
function
(
item
,
allowUnicode
)
{
validateURLItemEncoding
=
function
(
item
,
allowUnicode
)
{
var
required
=
validateRequiredField
(
item
);
var
required
=
validateRequiredField
(
item
);
if
(
required
)
{
if
(
required
)
{
return
required
;
return
required
;
}
if
(
allowUnicode
)
{
if
(
/
\s
/g
.
test
(
item
))
{
return
gettext
(
'Please do not use any spaces in this field.'
);
}
}
}
if
(
allowUnicode
)
{
else
{
if
(
/
\s
/g
.
test
(
item
))
{
if
(
item
!==
encodeURIComponent
(
item
)
||
item
.
match
(
/
[
!'()*
]
/
))
{
return
gettext
(
'Please do not use any spaces in this field.'
);
return
gettext
(
'Please do not use any spaces or special characters in this field.'
);
}
}
}
}
else
{
return
''
;
if
(
item
!==
encodeURIComponent
(
item
)
||
item
.
match
(
/
[
!'()*
]
/
))
{
};
return
gettext
(
'Please do not use any spaces or special characters in this field.'
);
}
}
return
''
;
};
// Ensure that sum length of key field values <= ${MAX_SUM_KEY_LENGTH} chars.
// Ensure that sum length of key field values <= ${MAX_SUM_KEY_LENGTH} chars.
validateTotalKeyLength
=
function
(
key_field_selectors
)
{
validateTotalKeyLength
=
function
(
key_field_selectors
)
{
var
totalLength
=
_
.
reduce
(
var
totalLength
=
_
.
reduce
(
key_field_selectors
,
key_field_selectors
,
function
(
sum
,
ele
)
{
return
sum
+
$
(
ele
).
val
().
length
;},
function
(
sum
,
ele
)
{
return
sum
+
$
(
ele
).
val
().
length
;},
0
0
);
);
return
totalLength
<=
MAX_SUM_KEY_LENGTH
;
return
totalLength
<=
MAX_SUM_KEY_LENGTH
;
};
};
checkTotalKeyLengthViolations
=
function
(
selectors
,
classes
,
key_field_selectors
,
message_tpl
)
{
checkTotalKeyLengthViolations
=
function
(
selectors
,
classes
,
key_field_selectors
,
message_tpl
)
{
if
(
!
validateTotalKeyLength
(
key_field_selectors
))
{
if
(
!
validateTotalKeyLength
(
key_field_selectors
))
{
$
(
selectors
.
errorWrapper
).
addClass
(
classes
.
shown
).
removeClass
(
classes
.
hiding
);
$
(
selectors
.
errorWrapper
).
addClass
(
classes
.
shown
).
removeClass
(
classes
.
hiding
);
$
(
selectors
.
errorMessage
).
html
(
'<p>'
+
_
.
template
(
message_tpl
,
{
limit
:
MAX_SUM_KEY_LENGTH
})
+
'</p>'
);
$
(
selectors
.
errorMessage
).
html
(
$
(
selectors
.
save
).
addClass
(
classes
.
disabled
);
'<p>'
+
_
.
template
(
message_tpl
,
{
limit
:
MAX_SUM_KEY_LENGTH
})
+
'</p>'
}
else
{
);
$
(
selectors
.
errorWrapper
).
removeClass
(
classes
.
shown
).
addClass
(
classes
.
hiding
);
$
(
selectors
.
save
).
addClass
(
classes
.
disabled
);
}
}
else
{
};
$
(
selectors
.
errorWrapper
).
removeClass
(
classes
.
shown
).
addClass
(
classes
.
hiding
);
}
};
return
{
/**
'toggleExpandCollapse'
:
toggleExpandCollapse
,
* Dynamically loads the specified JavaScript file.
'showLoadingIndicator'
:
showLoadingIndicator
,
* @param url The URL to a JavaScript file.
'hideLoadingIndicator'
:
hideLoadingIndicator
,
* @returns {Promise} A promise indicating when the URL has been loaded.
'confirmThenRunOperation'
:
confirmThenRunOperation
,
*/
'runOperationShowingMessage'
:
runOperationShowingMessage
,
loadJavaScript
=
function
(
url
)
{
'withDisabledElement'
:
withDisabledElement
,
var
deferred
=
$
.
Deferred
();
'disableElementWhileRunning'
:
disableElementWhileRunning
,
require
([
url
],
'deleteNotificationHandler'
:
deleteNotificationHandler
,
function
()
{
'setScrollTop'
:
setScrollTop
,
deferred
.
resolve
();
'getScrollOffset'
:
getScrollOffset
,
},
'setScrollOffset'
:
setScrollOffset
,
function
()
{
'redirect'
:
redirect
,
deferred
.
reject
();
'reload'
:
reload
,
});
'hasChangedAttributes'
:
hasChangedAttributes
,
return
deferred
.
promise
();
'validateRequiredField'
:
validateRequiredField
,
};
'validateURLItemEncoding'
:
validateURLItemEncoding
,
'validateTotalKeyLength'
:
validateTotalKeyLength
,
return
{
'checkTotalKeyLengthViolations'
:
checkTotalKeyLengthViolations
'toggleExpandCollapse'
:
toggleExpandCollapse
,
};
'showLoadingIndicator'
:
showLoadingIndicator
,
});
'hideLoadingIndicator'
:
hideLoadingIndicator
,
}).
call
(
this
,
define
||
RequireJS
.
define
);
'confirmThenRunOperation'
:
confirmThenRunOperation
,
'runOperationShowingMessage'
:
runOperationShowingMessage
,
'withDisabledElement'
:
withDisabledElement
,
'disableElementWhileRunning'
:
disableElementWhileRunning
,
'deleteNotificationHandler'
:
deleteNotificationHandler
,
'setScrollTop'
:
setScrollTop
,
'getScrollOffset'
:
getScrollOffset
,
'setScrollOffset'
:
setScrollOffset
,
'redirect'
:
redirect
,
'reload'
:
reload
,
'hasChangedAttributes'
:
hasChangedAttributes
,
'validateRequiredField'
:
validateRequiredField
,
'validateURLItemEncoding'
:
validateURLItemEncoding
,
'validateTotalKeyLength'
:
validateTotalKeyLength
,
'checkTotalKeyLengthViolations'
:
checkTotalKeyLengthViolations
,
'loadJavaScript'
:
loadJavaScript
};
});
}).
call
(
this
,
define
||
RequireJS
.
define
,
require
||
RequireJS
.
require
);
common/static/common/js/spec/components/view_utils_spec.js
View file @
5b9e0e9d
;(
function
(
define
)
{
;(
function
(
define
)
{
'use strict'
;
'use strict'
;
define
([
"jquery"
,
"underscore"
,
"common/js/components/utils/view_utils"
,
"common/js/spec_helpers/view_helpers"
,
'jasmine-stealth'
],
define
([
"jquery"
,
"underscore"
,
"backbone"
,
"common/js/components/utils/view_utils"
,
function
(
$
,
_
,
ViewUtils
,
ViewHelpers
)
{
"common/js/spec_helpers/view_helpers"
,
"jasmine-stealth"
],
function
(
$
,
_
,
Backbone
,
ViewUtils
,
ViewHelpers
)
{
describe
(
"ViewUtils"
,
function
()
{
describe
(
"ViewUtils"
,
function
()
{
describe
(
"disabled element while running"
,
function
()
{
describe
(
"disabled element while running"
,
function
()
{
it
(
"adds 'is-disabled' class to element while action is running and removes it after"
,
function
()
{
it
(
"adds 'is-disabled' class to element while action is running and removes it after"
,
function
()
{
var
link
,
var
link
,
deferred
=
new
$
.
Deferred
(),
deferred
=
new
$
.
Deferred
(),
promise
=
deferred
.
promise
();
promise
=
deferred
.
promise
();
setFixtures
(
"<a href='#' id='link'>ripe apples drop about my head</a>"
);
setFixtures
(
"<a href='#' id='link'>ripe apples drop about my head</a>"
);
link
=
$
(
"#link"
);
link
=
$
(
"#link"
);
expect
(
link
).
not
.
toHaveClass
(
"is-disabled"
);
expect
(
link
).
not
.
toHaveClass
(
"is-disabled"
);
ViewUtils
.
disableElementWhileRunning
(
link
,
function
()
{
return
promise
;
});
ViewUtils
.
disableElementWhileRunning
(
link
,
function
()
{
return
promise
;
});
expect
(
link
).
toHaveClass
(
"is-disabled"
);
expect
(
link
).
toHaveClass
(
"is-disabled"
);
deferred
.
resolve
();
deferred
.
resolve
();
expect
(
link
).
not
.
toHaveClass
(
"is-disabled"
);
expect
(
link
).
not
.
toHaveClass
(
"is-disabled"
);
});
});
it
(
"uses withDisabledElement wrapper to disable element while running a Backbone event handler"
,
function
()
{
it
(
"disables elements within withDisabledElement"
,
function
()
{
var
link
,
var
link
,
eventCallback
,
eventCallback
,
event
,
event
,
deferred
=
new
$
.
Deferred
(),
deferred
=
new
$
.
Deferred
(),
promise
=
deferred
.
promise
(),
promise
=
deferred
.
promise
(),
MockView
=
Backbone
.
View
.
extend
({
MockView
=
Backbone
.
View
.
extend
({
testFunction
:
function
()
{
testFunction
:
function
()
{
return
promise
;
return
promise
;
}
}
}),
}),
testView
=
new
MockView
();
testView
=
new
MockView
();
setFixtures
(
"<a href='#' id='link'>ripe apples drop about my head</a>"
);
setFixtures
(
"<a href='#' id='link'>ripe apples drop about my head</a>"
);
link
=
$
(
"#link"
);
link
=
$
(
"#link"
);
expect
(
link
).
not
.
toHaveClass
(
"is-disabled"
);
expect
(
link
).
not
.
toHaveClass
(
"is-disabled"
);
eventCallback
=
ViewUtils
.
withDisabledElement
(
'testFunction'
);
eventCallback
=
ViewUtils
.
withDisabledElement
(
'testFunction'
);
event
=
{
currentTarget
:
link
};
event
=
{
currentTarget
:
link
};
eventCallback
.
apply
(
testView
,
[
event
]);
eventCallback
.
apply
(
testView
,
[
event
]);
expect
(
link
).
toHaveClass
(
"is-disabled"
);
expect
(
link
).
toHaveClass
(
"is-disabled"
);
deferred
.
resolve
();
deferred
.
resolve
();
expect
(
link
).
not
.
toHaveClass
(
"is-disabled"
);
expect
(
link
).
not
.
toHaveClass
(
"is-disabled"
);
});
});
});
});
describe
(
"progress notification"
,
function
()
{
describe
(
"progress notification"
,
function
()
{
it
(
"shows progress notification and removes it upon success"
,
function
()
{
it
(
"shows progress notification and removes it upon success"
,
function
()
{
var
testMessage
=
"Testing..."
,
var
testMessage
=
"Testing..."
,
deferred
=
new
$
.
Deferred
(),
deferred
=
new
$
.
Deferred
(),
promise
=
deferred
.
promise
(),
promise
=
deferred
.
promise
(),
notificationSpy
=
ViewHelpers
.
createNotificationSpy
();
notificationSpy
=
ViewHelpers
.
createNotificationSpy
();
ViewUtils
.
runOperationShowingMessage
(
testMessage
,
function
()
{
return
promise
;
});
ViewUtils
.
runOperationShowingMessage
(
testMessage
,
function
()
{
return
promise
;
});
ViewHelpers
.
verifyNotificationShowing
(
notificationSpy
,
/Testing/
);
ViewHelpers
.
verifyNotificationShowing
(
notificationSpy
,
/Testing/
);
deferred
.
resolve
();
deferred
.
resolve
();
ViewHelpers
.
verifyNotificationHidden
(
notificationSpy
);
ViewHelpers
.
verifyNotificationHidden
(
notificationSpy
);
});
});
it
(
"shows progress notification and leaves it showing upon failure"
,
function
()
{
it
(
"shows progress notification and leaves it showing upon failure"
,
function
()
{
var
testMessage
=
"Testing..."
,
var
testMessage
=
"Testing..."
,
deferred
=
new
$
.
Deferred
(),
deferred
=
new
$
.
Deferred
(),
promise
=
deferred
.
promise
(),
promise
=
deferred
.
promise
(),
notificationSpy
=
ViewHelpers
.
createNotificationSpy
();
notificationSpy
=
ViewHelpers
.
createNotificationSpy
();
ViewUtils
.
runOperationShowingMessage
(
testMessage
,
function
()
{
return
promise
;
});
ViewUtils
.
runOperationShowingMessage
(
testMessage
,
function
()
{
return
promise
;
});
ViewHelpers
.
verifyNotificationShowing
(
notificationSpy
,
/Testing/
);
ViewHelpers
.
verifyNotificationShowing
(
notificationSpy
,
/Testing/
);
deferred
.
fail
();
deferred
.
fail
();
ViewHelpers
.
verifyNotificationShowing
(
notificationSpy
,
/Testing/
);
ViewHelpers
.
verifyNotificationShowing
(
notificationSpy
,
/Testing/
);
});
});
});
});
describe
(
"course/library fields validation"
,
function
()
{
describe
(
"course/library fields validation"
,
function
()
{
describe
(
"without unicode support"
,
function
()
{
describe
(
"without unicode support"
,
function
()
{
it
(
"validates presence of field"
,
function
()
{
it
(
"validates presence of field"
,
function
()
{
var
error
=
ViewUtils
.
validateURLItemEncoding
(
''
,
false
);
var
error
=
ViewUtils
.
validateURLItemEncoding
(
''
,
false
);
expect
(
error
).
toBeTruthy
();
expect
(
error
).
toBeTruthy
();
});
});
it
(
"checks for presence of special characters in the field"
,
function
()
{
it
(
"checks for presence of special characters in the field"
,
function
()
{
var
error
;
var
error
;
// Special characters are not allowed.
// Special characters are not allowed.
error
=
ViewUtils
.
validateURLItemEncoding
(
'my+field'
,
false
);
error
=
ViewUtils
.
validateURLItemEncoding
(
'my+field'
,
false
);
expect
(
error
).
toBeTruthy
();
expect
(
error
).
toBeTruthy
();
error
=
ViewUtils
.
validateURLItemEncoding
(
'2014!'
,
false
);
error
=
ViewUtils
.
validateURLItemEncoding
(
'2014!'
,
false
);
expect
(
error
).
toBeTruthy
();
expect
(
error
).
toBeTruthy
();
error
=
ViewUtils
.
validateURLItemEncoding
(
'*field*'
,
false
);
error
=
ViewUtils
.
validateURLItemEncoding
(
'*field*'
,
false
);
expect
(
error
).
toBeTruthy
();
expect
(
error
).
toBeTruthy
();
// Spaces not allowed.
// Spaces not allowed.
error
=
ViewUtils
.
validateURLItemEncoding
(
'Jan 2014'
,
false
);
error
=
ViewUtils
.
validateURLItemEncoding
(
'Jan 2014'
,
false
);
expect
(
error
).
toBeTruthy
();
expect
(
error
).
toBeTruthy
();
// -_~. are allowed.
// -_~. are allowed.
error
=
ViewUtils
.
validateURLItemEncoding
(
'2015-Math_X1.0~'
,
false
);
error
=
ViewUtils
.
validateURLItemEncoding
(
'2015-Math_X1.0~'
,
false
);
expect
(
error
).
toBeFalsy
();
expect
(
error
).
toBeFalsy
();
});
});
it
(
"does not allow unicode characters"
,
function
()
{
it
(
"does not allow unicode characters"
,
function
()
{
var
error
=
ViewUtils
.
validateURLItemEncoding
(
'Field-
\
u010d'
,
false
);
var
error
=
ViewUtils
.
validateURLItemEncoding
(
'Field-
\
u010d'
,
false
);
expect
(
error
).
toBeTruthy
();
expect
(
error
).
toBeTruthy
();
});
});
});
});
describe
(
"with unicode support"
,
function
()
{
describe
(
"with unicode support"
,
function
()
{
it
(
"validates presence of field"
,
function
()
{
it
(
"validates presence of field"
,
function
()
{
var
error
=
ViewUtils
.
validateURLItemEncoding
(
''
,
true
);
var
error
=
ViewUtils
.
validateURLItemEncoding
(
''
,
true
);
expect
(
error
).
toBeTruthy
();
expect
(
error
).
toBeTruthy
();
});
});
it
(
"checks for presence of spaces"
,
function
()
{
it
(
"checks for presence of spaces"
,
function
()
{
var
error
=
ViewUtils
.
validateURLItemEncoding
(
'My Field'
,
true
);
var
error
=
ViewUtils
.
validateURLItemEncoding
(
'My Field'
,
true
);
expect
(
error
).
toBeTruthy
();
expect
(
error
).
toBeTruthy
();
});
});
it
(
"allows unicode characters"
,
function
()
{
it
(
"allows unicode characters"
,
function
()
{
var
error
=
ViewUtils
.
validateURLItemEncoding
(
'Field-
\
u010d'
,
true
);
var
error
=
ViewUtils
.
validateURLItemEncoding
(
'Field-
\
u010d'
,
true
);
expect
(
error
).
toBeFalsy
();
expect
(
error
).
toBeFalsy
();
});
});
});
});
});
});
});
});
});
});
}).
call
(
this
,
define
||
RequireJS
.
define
);
}).
call
(
this
,
define
||
RequireJS
.
define
);
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment