Add TabsEditingDescriptor for VideoAlpha
Updates comment Make Model descriptor class property. Fix tests
This commit is contained in:
33
cms/static/coffee/fixtures/tabs-edit.html
Normal file
33
cms/static/coffee/fixtures/tabs-edit.html
Normal file
@@ -0,0 +1,33 @@
|
||||
<div class="base_wrapper">
|
||||
<section class="editor-with-tabs">
|
||||
<div class="wrapper-comp-editor" id="editor-tab-id" data-html_id='test_id'>
|
||||
<div class="edit-header">
|
||||
<ul class="editor-tabs">
|
||||
<li class="inner_tab_wrap"><a href="#tab-0" class="tab">Tab 0 Editor</a></li>
|
||||
<li class="inner_tab_wrap"><a href="#tab-1" class="tab">Tab 1 Transcripts</a></li>
|
||||
<li class="inner_tab_wrap" id="settings"><a href="#tab-2" class="tab">Tab 2 Settings</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tabs-wrapper">
|
||||
<div class="component-tab" id="tab-0">
|
||||
<textarea name="" class="edit-box">XML Editor Text</textarea>
|
||||
</div>
|
||||
<div class="component-tab" id="tab-1">
|
||||
Transcripts
|
||||
</div>
|
||||
<div class="component-tab" id="tab-2">
|
||||
Subtitles
|
||||
</div>
|
||||
</div>
|
||||
<div class="wrapper-comp-settings">
|
||||
<ul>
|
||||
<li id="editor-mode"><a>Editor</a></li>
|
||||
<li id="settings-mode"><a>Settings</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="component-edit-header" style="display: block"/>
|
||||
</div>
|
||||
|
||||
95
cms/static/coffee/spec/tabs/edit.coffee
Normal file
95
cms/static/coffee/spec/tabs/edit.coffee
Normal file
@@ -0,0 +1,95 @@
|
||||
describe "TabsEditingDescriptor", ->
|
||||
beforeEach ->
|
||||
@isInactiveClass = "is-inactive"
|
||||
@isCurrent = "current"
|
||||
loadFixtures 'tabs-edit.html'
|
||||
@descriptor = new TabsEditingDescriptor($('.base_wrapper'))
|
||||
@html_id = 'test_id'
|
||||
@tab_0_switch = jasmine.createSpy('tab_0_switch');
|
||||
@tab_0_modelUpdate = jasmine.createSpy('tab_0_modelUpdate');
|
||||
@tab_1_switch = jasmine.createSpy('tab_1_switch');
|
||||
@tab_1_modelUpdate = jasmine.createSpy('tab_1_modelUpdate');
|
||||
TabsEditingDescriptor.Model.addModelUpdate(@html_id, 'Tab 0 Editor', @tab_0_modelUpdate)
|
||||
TabsEditingDescriptor.Model.addOnSwitch(@html_id, 'Tab 0 Editor', @tab_0_switch)
|
||||
TabsEditingDescriptor.Model.addModelUpdate(@html_id, 'Tab 1 Transcripts', @tab_1_modelUpdate)
|
||||
TabsEditingDescriptor.Model.addOnSwitch(@html_id, 'Tab 1 Transcripts', @tab_1_switch)
|
||||
|
||||
spyOn($.fn, 'hide').andCallThrough()
|
||||
spyOn($.fn, 'show').andCallThrough()
|
||||
spyOn(TabsEditingDescriptor.Model, 'initialize')
|
||||
spyOn(TabsEditingDescriptor.Model, 'updateValue')
|
||||
|
||||
afterEach ->
|
||||
TabsEditingDescriptor.Model.modules= {}
|
||||
|
||||
describe "constructor", ->
|
||||
it "first tab should be visible", ->
|
||||
expect(@descriptor.$tabs.first()).toHaveClass(@isCurrent)
|
||||
expect(@descriptor.$content.first()).not.toHaveClass(@isInactiveClass)
|
||||
|
||||
describe "onSwitchEditor", ->
|
||||
it "switching tabs changes styles", ->
|
||||
@descriptor.$tabs.eq(1).trigger("click")
|
||||
expect(@descriptor.$tabs.eq(0)).not.toHaveClass(@isCurrent)
|
||||
expect(@descriptor.$content.eq(0)).toHaveClass(@isInactiveClass)
|
||||
expect(@descriptor.$tabs.eq(1)).toHaveClass(@isCurrent)
|
||||
expect(@descriptor.$content.eq(1)).not.toHaveClass(@isInactiveClass)
|
||||
expect(@tab_1_switch).toHaveBeenCalled()
|
||||
|
||||
it "if click on current tab, nothing should happen", ->
|
||||
spyOn($.fn, 'trigger').andCallThrough()
|
||||
currentTab = @descriptor.$tabs.filter('.' + @isCurrent)
|
||||
@descriptor.$tabs.eq(0).trigger("click")
|
||||
expect(@descriptor.$tabs.filter('.' + @isCurrent)).toEqual(currentTab)
|
||||
expect($.fn.trigger.calls.length).toEqual(1)
|
||||
|
||||
it "onSwitch function call", ->
|
||||
@descriptor.$tabs.eq(1).trigger("click")
|
||||
expect(TabsEditingDescriptor.Model.updateValue).toHaveBeenCalled()
|
||||
expect(@tab_1_switch).toHaveBeenCalled()
|
||||
|
||||
describe "save", ->
|
||||
it "function for current tab should be called", ->
|
||||
@descriptor.$tabs.eq(1).trigger("click")
|
||||
data = @descriptor.save().data
|
||||
expect(@tab_1_modelUpdate).toHaveBeenCalled()
|
||||
|
||||
it "detach click event", ->
|
||||
spyOn($.fn, "off")
|
||||
@descriptor.save()
|
||||
expect($.fn.off).toHaveBeenCalledWith(
|
||||
'click',
|
||||
'.editor-tabs .tab',
|
||||
@descriptor.onSwitchEditor
|
||||
)
|
||||
|
||||
describe "editor/settings header", ->
|
||||
it "is hidden", ->
|
||||
expect(@descriptor.element.find(".component-edit-header").css('display')).toEqual('none')
|
||||
|
||||
describe "TabsEditingDescriptor special save cases", ->
|
||||
beforeEach ->
|
||||
@isInactiveClass = "is-inactive"
|
||||
@isCurrent = "current"
|
||||
loadFixtures 'tabs-edit.html'
|
||||
@descriptor = new window.TabsEditingDescriptor($('.base_wrapper'))
|
||||
@html_id = 'test_id'
|
||||
|
||||
describe "save", ->
|
||||
it "case: no init", ->
|
||||
data = @descriptor.save().data
|
||||
expect(data).toEqual(null)
|
||||
|
||||
it "case: no function in model update", ->
|
||||
TabsEditingDescriptor.Model.initialize(@html_id)
|
||||
data = @descriptor.save().data
|
||||
expect(data).toEqual(null)
|
||||
|
||||
it "case: no function in model update, but value presented", ->
|
||||
@tab_0_modelUpdate = jasmine.createSpy('tab_0_modelUpdate').andReturn(1)
|
||||
TabsEditingDescriptor.Model.addModelUpdate(@html_id, 'Tab 0 Editor', @tab_0_modelUpdate)
|
||||
@descriptor.$tabs.eq(1).trigger("click")
|
||||
expect(@tab_0_modelUpdate).toHaveBeenCalled()
|
||||
data = @descriptor.save().data
|
||||
expect(data).toEqual(1)
|
||||
|
||||
21
cms/templates/widgets/tabs-aggregator.html
Normal file
21
cms/templates/widgets/tabs-aggregator.html
Normal file
@@ -0,0 +1,21 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<div class="wrapper-comp-editor" id="editor-tab-${html_id}" data-html_id="${html_id}">
|
||||
<section class="editor-with-tabs">
|
||||
<div class="edit-header">
|
||||
<span class="component-name"></span>
|
||||
<ul class="${'editor-tabs' if (len(tabs) != 1) else 'editor-single-tab-name' }">
|
||||
% for tab in tabs:
|
||||
<li class="inner_tab_wrap"><a href="#tab-${html_id}-${loop.index}" class="tab ${'current' if tab.get('current', False) else ''}">${_(tab['name'])}</a></li>
|
||||
% endfor
|
||||
</ul>
|
||||
</div>
|
||||
<div class="${'tabs-wrapper' if (len(tabs) != 1) else 'editor-single-tab' }">
|
||||
% for tab in tabs:
|
||||
<div class="component-tab ${'is-inactive' if not tab.get('current', False) else ''}" id="tab-${html_id}-${loop.index}" >
|
||||
<%include file="${tab['template']}" args="tabName=tab['name']"/>
|
||||
</div>
|
||||
% endfor
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
24
cms/templates/widgets/tabs/metadata-edit-tab.html
Normal file
24
cms/templates/widgets/tabs/metadata-edit-tab.html
Normal file
@@ -0,0 +1,24 @@
|
||||
<%namespace name='static' file='../../static_content.html'/>
|
||||
<%
|
||||
import json
|
||||
%>
|
||||
|
||||
## js templates
|
||||
<script id="metadata-editor-tpl" type="text/template">
|
||||
<%static:include path="js/metadata-editor.underscore" />
|
||||
</script>
|
||||
|
||||
<script id="metadata-number-entry" type="text/template">
|
||||
<%static:include path="js/metadata-number-entry.underscore" />
|
||||
</script>
|
||||
|
||||
<script id="metadata-string-entry" type="text/template">
|
||||
<%static:include path="js/metadata-string-entry.underscore" />
|
||||
</script>
|
||||
|
||||
<script id="metadata-option-entry" type="text/template">
|
||||
<%static:include path="js/metadata-option-entry.underscore" />
|
||||
</script>
|
||||
|
||||
<div class="wrapper-comp-settings metadata_edit" id="settings-tab" data-metadata='${json.dumps(editable_metadata_fields) | h}'/>
|
||||
|
||||
33
cms/templates/widgets/videoalpha/codemirror-edit.html
Normal file
33
cms/templates/widgets/videoalpha/codemirror-edit.html
Normal file
@@ -0,0 +1,33 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%page args="tabName"/>
|
||||
<div>
|
||||
<textarea id="xml-${html_id}" class="edit-box">${data | h}</textarea>
|
||||
</div>
|
||||
|
||||
<script type='text/javascript'>
|
||||
$(document).ready(function(){
|
||||
## Init CodeMirror editor
|
||||
var el = $("#xml-${html_id}"),
|
||||
xml_editor = CodeMirror.fromTextArea(el.get(0), {
|
||||
mode: "application/xml",
|
||||
lineNumbers: true,
|
||||
lineWrapping: true
|
||||
});
|
||||
|
||||
TabsEditingDescriptor.Model.addModelUpdate(
|
||||
'${html_id}',
|
||||
'${tabName}',
|
||||
function() { return xml_editor.getValue(); })
|
||||
|
||||
TabsEditingDescriptor.Model.addOnSwitch(
|
||||
'${html_id}',
|
||||
'${tabName}',
|
||||
function(){
|
||||
## CodeMirror should get focus when tab is active
|
||||
xml_editor.refresh();
|
||||
xml_editor.focus();
|
||||
}
|
||||
)
|
||||
});
|
||||
</script>
|
||||
|
||||
0
cms/templates/widgets/videoalpha/subtitles.html
Normal file
0
cms/templates/widgets/videoalpha/subtitles.html
Normal file
@@ -0,0 +1,4 @@
|
||||
.supertestclass{
|
||||
color: red;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
.supertestclass{
|
||||
color: red;
|
||||
}
|
||||
|
||||
19
common/lib/xmodule/xmodule/css/tabs/codemirror.scss
Normal file
19
common/lib/xmodule/xmodule/css/tabs/codemirror.scss
Normal file
@@ -0,0 +1,19 @@
|
||||
.editor{
|
||||
@include clearfix();
|
||||
|
||||
.CodeMirror {
|
||||
@include box-sizing(border-box);
|
||||
width: 100%;
|
||||
position: relative;
|
||||
height: 379px;
|
||||
border: 1px solid #3c3c3c;
|
||||
border-top: 1px solid #8891a1;
|
||||
background: $white;
|
||||
color: #3c3c3c;
|
||||
}
|
||||
|
||||
.CodeMirror-scroll {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
137
common/lib/xmodule/xmodule/css/tabs/tabs.scss
Normal file
137
common/lib/xmodule/xmodule/css/tabs/tabs.scss
Normal file
@@ -0,0 +1,137 @@
|
||||
// styles duped from _unit.scss - Edit Header (Component Name, Mode-Editor, Mode-Settings)
|
||||
|
||||
|
||||
.tabs-wrapper{
|
||||
padding-top: 0;
|
||||
position: relative;
|
||||
|
||||
.wrapper-comp-settings {
|
||||
// set visibility to metadata editor
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.editor-single-tab-name {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
.editor-with-tabs {
|
||||
@include clearfix();
|
||||
position: relative;
|
||||
|
||||
|
||||
.edit-header {
|
||||
@include box-sizing(border-box);
|
||||
padding: 18px 0 18px $baseline;
|
||||
top: 0 !important; // ugly override for second level tab override
|
||||
right: 0;
|
||||
background-color: $blue;
|
||||
border-bottom: 1px solid $blue-d2;
|
||||
color: $white;
|
||||
|
||||
//Component Name
|
||||
.component-name {
|
||||
@extend .t-copy-sub1;
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 50%;
|
||||
color: $white;
|
||||
font-weight: 600;
|
||||
|
||||
|
||||
|
||||
em {
|
||||
display: inline-block;
|
||||
margin-right: ($baseline/4);
|
||||
font-weight: 400;
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
//Nav-Edit Modes
|
||||
.editor-tabs {
|
||||
list-style: none;
|
||||
right: 0;
|
||||
top: ($baseline/4);
|
||||
position: absolute;
|
||||
padding: 12px ($baseline*0.75);
|
||||
|
||||
.inner_tab_wrap {
|
||||
display: inline-block;
|
||||
margin-left: 8px;
|
||||
|
||||
a.tab {
|
||||
@include font-size(14);
|
||||
@include linear-gradient(top, rgba(255, 255, 255, .3), rgba(255, 255, 255, 0));
|
||||
border: 1px solid $blue-d1;
|
||||
border-radius: 3px;
|
||||
padding: ($baseline/4) ($baseline);
|
||||
background-color: $blue;
|
||||
font-weight: bold;
|
||||
color: $white;
|
||||
|
||||
&.current {
|
||||
@include linear-gradient($blue, $blue);
|
||||
color: $blue-d1;
|
||||
box-shadow: inset 0 1px 2px 1px $shadow-l1;
|
||||
background-color: $blue-d4;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: inset 0 1px 2px 1px $shadow;
|
||||
background-image: linear-gradient(#009FE6, #009FE6) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.is-inactive {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.comp-subtitles-entry {
|
||||
text-align: center;
|
||||
|
||||
.file-upload {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.comp-subtitles-import-list {
|
||||
> li {
|
||||
display: block;
|
||||
margin: $baseline/2 0px $baseline/2 0;
|
||||
}
|
||||
|
||||
.blue-button {
|
||||
font-size: 1em;
|
||||
display: block;
|
||||
width: 70%;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
.component-tab {
|
||||
background: $white;
|
||||
position: relative;
|
||||
border-top: 1px solid #8891a1;
|
||||
|
||||
&#advanced {
|
||||
padding: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.blue-button {
|
||||
@include blue-button;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"""Descriptors for XBlocks/Xmodules, that provide editing of atrributes"""
|
||||
|
||||
from pkg_resources import resource_string
|
||||
from xmodule.mako_module import MakoModuleDescriptor
|
||||
from xblock.core import Scope, String
|
||||
@@ -7,6 +9,7 @@ log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class EditingFields(object):
|
||||
"""Contains specific template information (the raw data body)"""
|
||||
data = String(scope=Scope.content, default='')
|
||||
|
||||
|
||||
@@ -29,6 +32,46 @@ class EditingDescriptor(EditingFields, MakoModuleDescriptor):
|
||||
return _context
|
||||
|
||||
|
||||
class TabsEditingDescriptor(EditingFields, MakoModuleDescriptor):
|
||||
"""
|
||||
Module that provides a raw editing view of its data and children. It does not
|
||||
perform any validation on its definition---just passes it along to the browser.
|
||||
|
||||
This class is intended to be used as a mixin.
|
||||
|
||||
Engine (module_edit.js) wants for metadata editor
|
||||
template to be always loaded, so don't forget to include
|
||||
settings tab in your module descriptor.
|
||||
"""
|
||||
mako_template = "widgets/tabs-aggregator.html"
|
||||
css = {'scss': [resource_string(__name__, 'css/tabs/tabs.scss')]}
|
||||
js = {'coffee': [resource_string(
|
||||
__name__, 'js/src/tabs/tabs-aggregator.coffee')]}
|
||||
js_module_name = "TabsEditingDescriptor"
|
||||
tabs = []
|
||||
|
||||
def get_context(self):
|
||||
_context = super(TabsEditingDescriptor, self).get_context()
|
||||
_context.update({
|
||||
'tabs': self.tabs,
|
||||
'html_id': self.location.html_id(), # element_id
|
||||
'data': self.data,
|
||||
})
|
||||
return _context
|
||||
|
||||
@classmethod
|
||||
def get_css(cls):
|
||||
# load every tab's css
|
||||
for tab in cls.tabs:
|
||||
tab_styles = tab.get('css', {})
|
||||
for css_type, css_content in tab_styles.items():
|
||||
if css_type in cls.css:
|
||||
cls.css[css_type].extend(css_content)
|
||||
else:
|
||||
cls.css[css_type] = css_content
|
||||
return cls.css
|
||||
|
||||
|
||||
class XMLEditingDescriptor(EditingDescriptor):
|
||||
"""
|
||||
Module that provides a raw editing view of its data as XML. It does not perform
|
||||
|
||||
125
common/lib/xmodule/xmodule/js/src/tabs/tabs-aggregator.coffee
Normal file
125
common/lib/xmodule/xmodule/js/src/tabs/tabs-aggregator.coffee
Normal file
@@ -0,0 +1,125 @@
|
||||
class @TabsEditingDescriptor
|
||||
@isInactiveClass : "is-inactive"
|
||||
|
||||
constructor: (element) ->
|
||||
@element = element;
|
||||
###
|
||||
Not tested on syncing of multiple editors of same type in tabs
|
||||
(Like many CodeMirrors).
|
||||
###
|
||||
|
||||
# hide editor/settings bar
|
||||
$('.component-edit-header').hide()
|
||||
|
||||
@$tabs = $(".tab", @element)
|
||||
@$content = $(".component-tab", @element)
|
||||
|
||||
@element.find('.editor-tabs .tab').each (index, value) =>
|
||||
$(value).on('click', @onSwitchEditor)
|
||||
|
||||
# If default visible tab is not setted or if were marked as current
|
||||
# more than 1 tab just first tab will be shown
|
||||
currentTab = @$tabs.filter('.current')
|
||||
currentTab = @$tabs.first() if currentTab.length isnt 1
|
||||
@html_id = @$tabs.closest('.wrapper-comp-editor').data('html_id')
|
||||
currentTab.trigger("click", [true, @html_id])
|
||||
|
||||
onSwitchEditor: (e, firstTime, html_id) =>
|
||||
e.preventDefault();
|
||||
|
||||
isInactiveClass = TabsEditingDescriptor.isInactiveClass
|
||||
$currentTarget = $(e.currentTarget)
|
||||
|
||||
if not $currentTarget.hasClass('current') or firstTime is true
|
||||
previousTab = null
|
||||
|
||||
@$tabs.each( (index, value) ->
|
||||
if $(value).hasClass('current')
|
||||
previousTab = $(value).html()
|
||||
)
|
||||
|
||||
# init and save data from previous tab
|
||||
TabsEditingDescriptor.Model.updateValue(@html_id, previousTab)
|
||||
|
||||
# Save data from editor in previous tab to editor in current tab here.
|
||||
# (to be implemented when there is a use case for this functionality)
|
||||
|
||||
# call onswitch
|
||||
onSwitchFunction = TabsEditingDescriptor.Model.modules[@html_id].tabSwitch[$currentTarget.text()]
|
||||
onSwitchFunction() if $.isFunction(onSwitchFunction)
|
||||
|
||||
@$tabs.removeClass('current')
|
||||
$currentTarget.addClass('current')
|
||||
|
||||
# Tabs are implemeted like anchors. Therefore we can use hash to find
|
||||
# corresponding content
|
||||
content_id = $currentTarget.attr('href')
|
||||
|
||||
@$content
|
||||
.addClass(isInactiveClass)
|
||||
.filter(content_id)
|
||||
.removeClass(isInactiveClass)
|
||||
|
||||
save: ->
|
||||
@element.off('click', '.editor-tabs .tab', @onSwitchEditor)
|
||||
current_tab = @$tabs.filter('.current').html()
|
||||
data: TabsEditingDescriptor.Model.getValue(@html_id, current_tab)
|
||||
|
||||
@Model :
|
||||
addModelUpdate : (id, tabName, modelUpdateFunction) ->
|
||||
###
|
||||
Function that registers 'modelUpdate' functions of every tab.
|
||||
These functions are used to update value, which will be returned
|
||||
by calling save on component.
|
||||
###
|
||||
@initialize(id)
|
||||
@modules[id].modelUpdate[tabName] = modelUpdateFunction
|
||||
|
||||
addOnSwitch : (id, tabName, onSwitchFunction) ->
|
||||
###
|
||||
Function that registers functions invoked when switching
|
||||
to particular tab.
|
||||
###
|
||||
@initialize(id)
|
||||
@modules[id].tabSwitch[tabName] = onSwitchFunction
|
||||
|
||||
updateValue : (id, tabName) ->
|
||||
###
|
||||
Function that invokes when switching tabs.
|
||||
It ensures that data from previous tab is stored.
|
||||
If new tab need this data, it should retrieve it from
|
||||
stored value.
|
||||
###
|
||||
@initialize(id)
|
||||
modelUpdateFunction = @modules[id]['modelUpdate'][tabName]
|
||||
@modules[id]['value'] = modelUpdateFunction() if $.isFunction(modelUpdateFunction)
|
||||
|
||||
getValue : (id, tabName) ->
|
||||
###
|
||||
Retrieves stored data on component save.
|
||||
1. When we switching tabs - previous tab data is always saved to @[id].value
|
||||
2. If current tab have registered 'modelUpdate' method, it should be invoked 1st.
|
||||
(If we have edited in 1st tab, then switched to 2nd, 2nd tab should
|
||||
care about getting data from @[id].value in onSwitch.)
|
||||
###
|
||||
if not @modules[id]
|
||||
return null
|
||||
if $.isFunction(@modules[id]['modelUpdate'][tabName])
|
||||
return @modules[id]['modelUpdate'][tabName]()
|
||||
else
|
||||
if typeof @modules[id]['value'] is 'undefined'
|
||||
return null
|
||||
else
|
||||
return @modules[id]['value']
|
||||
|
||||
# html_id's of descriptors will be stored in modules variable as
|
||||
# containers for callbacks.
|
||||
modules: {}
|
||||
|
||||
initialize : (id) ->
|
||||
###
|
||||
Initialize objects per id. Id is html_id of descriptor.
|
||||
###
|
||||
@modules[id] = @modules[id] or {}
|
||||
@modules[id].tabSwitch = @modules[id]['tabSwitch'] or {}
|
||||
@modules[id].modelUpdate = @modules[id]['modelUpdate'] or {}
|
||||
64
common/lib/xmodule/xmodule/tests/test_editing_module.py
Normal file
64
common/lib/xmodule/xmodule/tests/test_editing_module.py
Normal file
@@ -0,0 +1,64 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import unittest
|
||||
import os
|
||||
import logging
|
||||
|
||||
from mock import Mock
|
||||
from pkg_resources import resource_string
|
||||
from xmodule.editing_module import TabsEditingDescriptor
|
||||
|
||||
from .import get_test_system
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TabsEditingDescriptorTestCase(unittest.TestCase):
|
||||
""" Testing TabsEditingDescriptor"""
|
||||
|
||||
def setUp(self):
|
||||
super(TabsEditingDescriptorTestCase, self).setUp()
|
||||
system = get_test_system()
|
||||
system.render_template = Mock(return_value="<div>Test Template HTML</div>")
|
||||
self.tabs = [
|
||||
{
|
||||
'name': "Test_css",
|
||||
'template': "tabs/codemirror-edit.html",
|
||||
'current': True,
|
||||
'css': {
|
||||
'scss': [resource_string(__name__,
|
||||
'../../test_files/test_tabseditingdescriptor.scss')],
|
||||
'css': [resource_string(__name__,
|
||||
'../../test_files/test_tabseditingdescriptor.css')]
|
||||
}
|
||||
},
|
||||
{
|
||||
'name': "Subtitles",
|
||||
'template': "videoalpha/subtitles.html",
|
||||
},
|
||||
{
|
||||
'name': "Settings",
|
||||
'template': "tabs/video-metadata-edit-tab.html"
|
||||
}
|
||||
]
|
||||
|
||||
TabsEditingDescriptor.tabs = self.tabs
|
||||
self.descriptor = TabsEditingDescriptor(
|
||||
runtime=system,
|
||||
model_data={})
|
||||
|
||||
def test_get_css(self):
|
||||
"""test get_css"""
|
||||
css = self.descriptor.get_css()
|
||||
test_files_dir = os.path.dirname(__file__).replace('xmodule/tests', 'test_files')
|
||||
test_css_file = os.path.join(test_files_dir, 'test_tabseditingdescriptor.scss')
|
||||
with open(test_css_file) as new_css:
|
||||
added_css = new_css.read()
|
||||
self.assertEqual(css['scss'].pop(), added_css)
|
||||
self.assertEqual(css['css'].pop(), added_css)
|
||||
|
||||
def test_get_context(self):
|
||||
""""test get_context"""
|
||||
rendered_context = self.descriptor.get_context()
|
||||
self.assertListEqual(rendered_context['tabs'], self.tabs)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Test for Video Alpha Xmodule functional logic.
|
||||
These tests data readed from xml, not from mongo.
|
||||
These tests data readed from xml or from mongo.
|
||||
|
||||
we have a ModuleStoreTestCase class defined in
|
||||
common/lib/xmodule/xmodule/modulestore/tests/django_utils.py. You can
|
||||
@@ -12,10 +12,12 @@ in common/lib/xmodule/xmodule/modulestore/tests/factories.py to create
|
||||
the course, section, subsection, unit, etc.
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from xmodule.videoalpha_module import VideoAlphaDescriptor
|
||||
from . import LogicTest
|
||||
from lxml import etree
|
||||
|
||||
from pkg_resources import resource_string
|
||||
from .import get_test_system
|
||||
|
||||
class VideoAlphaModuleTest(LogicTest):
|
||||
"""Logic tests for VideoAlpha Xmodule."""
|
||||
@@ -49,3 +51,31 @@ class VideoAlphaModuleTest(LogicTest):
|
||||
)
|
||||
output = self.xmodule.get_timeframe(xmltree)
|
||||
self.assertEqual(output, (247, 47079))
|
||||
|
||||
|
||||
class VideoAlphaDescriptorTest(unittest.TestCase):
|
||||
"""Test for VideoAlphaDescriptor"""
|
||||
|
||||
def setUp(self):
|
||||
system = get_test_system()
|
||||
self.descriptor = VideoAlphaDescriptor(
|
||||
runtime=system,
|
||||
model_data={})
|
||||
|
||||
def test_get_context(self):
|
||||
""""test get_context"""
|
||||
correct_tabs = [
|
||||
{
|
||||
'name': "XML",
|
||||
'template': "videoalpha/codemirror-edit.html",
|
||||
'css': {'scss': [resource_string(__name__,
|
||||
'../css/tabs/codemirror.scss')]},
|
||||
'current': True,
|
||||
},
|
||||
{
|
||||
'name': "Settings",
|
||||
'template': "tabs/metadata-edit-tab.html"
|
||||
}
|
||||
]
|
||||
rendered_context = self.descriptor.get_context()
|
||||
self.assertListEqual(rendered_context['tabs'], correct_tabs)
|
||||
|
||||
@@ -20,7 +20,7 @@ from django.http import Http404
|
||||
from django.conf import settings
|
||||
|
||||
from xmodule.x_module import XModule
|
||||
from xmodule.raw_module import RawDescriptor
|
||||
from xmodule.editing_module import TabsEditingDescriptor
|
||||
from xmodule.modulestore.mongo import MongoModuleStore
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.contentstore.content import StaticContent
|
||||
@@ -187,6 +187,24 @@ class VideoAlphaModule(VideoAlphaFields, XModule):
|
||||
})
|
||||
|
||||
|
||||
class VideoAlphaDescriptor(VideoAlphaFields, RawDescriptor):
|
||||
class VideoAlphaDescriptor(VideoAlphaFields, TabsEditingDescriptor):
|
||||
"""Descriptor for `VideoAlphaModule`."""
|
||||
module_class = VideoAlphaModule
|
||||
template_dir_name = "videoalpha"
|
||||
|
||||
tabs = [
|
||||
{
|
||||
'name': "XML",
|
||||
'template': "videoalpha/codemirror-edit.html",
|
||||
'css': {'scss': [resource_string(__name__, 'css/tabs/codemirror.scss')]},
|
||||
'current': True,
|
||||
},
|
||||
# {
|
||||
# 'name': "Subtitles",
|
||||
# 'template': "videoalpha/subtitles.html",
|
||||
# },
|
||||
{
|
||||
'name': "Settings",
|
||||
'template': "tabs/metadata-edit-tab.html"
|
||||
}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user