Merge pull request #455 from edx/peter-fogg/remove-videoalpha-xml

Peter fogg/remove videoalpha xml
This commit is contained in:
Peter Fogg
2013-08-06 11:06:46 -07:00
28 changed files with 1064 additions and 266 deletions

View File

@@ -228,6 +228,26 @@ def i_created_a_video_component(step):
)
@step('I have created a Video Alpha component$')
def i_created_video_alpha(step):
step.given('I have enabled the videoalpha advanced module')
world.css_click('a.course-link')
step.given('I have added a new subsection')
step.given('I expand the first section')
world.css_click('a.new-unit-item')
world.css_click('.large-advanced-icon')
world.click_component_from_menu('videoalpha', None, '.xmodule_VideoAlphaModule')
@step('I have enabled the (.*) advanced module$')
def i_enabled_the_advanced_module(step, module):
step.given('I have opened a new course section in Studio')
world.css_click('.nav-course-settings')
world.css_click('.nav-course-settings-advanced')
type_in_codemirror(0, '["%s"]' % module)
press_the_notification_button(step, 'Save')
@step('I have clicked the new unit button')
def open_new_unit(step):
step.given('I have opened a new course section in Studio')
@@ -236,14 +256,14 @@ def open_new_unit(step):
world.css_click('a.new-unit-item')
@step('when I view the video it (.*) show the captions')
def shows_captions(step, show_captions):
@step('when I view the (video.*) it (.*) show the captions')
def shows_captions(_step, video_type, show_captions):
# Prevent cookies from overriding course settings
world.browser.cookies.delete('hide_captions')
if show_captions == 'does not':
assert world.css_has_class('.video', 'closed')
assert world.css_has_class('.%s' % video_type, 'closed')
else:
assert world.is_css_not_present('.video.closed')
assert world.is_css_not_present('.%s.closed' % video_type)
@step('the save button is disabled$')

View File

@@ -59,6 +59,12 @@ def edit_component_and_select_settings():
world.css_click('#settings-mode')
@world.absorb
def edit_component():
world.wait_for(lambda _driver: world.css_visible('a.edit-button'))
world.css_click('a.edit-button')
@world.absorb
def verify_setting_entry(setting, display_name, value, explicitly_set):
assert_equal(display_name, setting.find_by_css('.setting-label')[0].value)

View File

@@ -22,3 +22,19 @@ def set_show_captions(step, setting):
world.wait_for(lambda _driver: world.css_visible('a.save-button'))
world.browser.select('Show Captions', setting)
world.css_click('a.save-button')
@step('I see the correct videoalpha settings and default values$')
def correct_videoalpha_settings(_step):
world.verify_all_setting_entries([['Display Name', 'Video Alpha', False],
['Download Track', '', False],
['Download Video', '', False],
['End Time', '0', False],
['HTML5 Subtitles', '', False],
['Show Captions', 'True', False],
['Start Time', '0', False],
['Video Sources', '', False],
['Youtube ID', 'OEoXaMPEzfM', False],
['Youtube ID for .75x speed', '', False],
['Youtube ID for 1.25x speed', '', False],
['Youtube ID for 1.5x speed', '', False]])

View File

@@ -22,3 +22,28 @@ Feature: Video Component
Given I have created a Video component
And I have toggled captions
Then when I view the video it does show the captions
Scenario: Autoplay is disabled in Studio for Video Alpha
Given I have created a Video Alpha component
Then when I view the videoalpha it does not have autoplay enabled
Scenario: User can view Video Alpha metadata
Given I have created a Video Alpha component
And I edit the component
Then I see the correct videoalpha settings and default values
Scenario: User can modify Video Alpha display name
Given I have created a Video Alpha component
And I edit the component
Then I can modify the display name
And my videoalpha display name change is persisted on save
Scenario: Video Alpha captions are hidden when "show captions" is false
Given I have created a Video Alpha component
And I have set "show captions" to False
Then when I view the videoalpha it does not show the captions
Scenario: Video Alpha captions are shown when "show captions" is true
Given I have created a Video Alpha component
And I have set "show captions" to True
Then when I view the videoalpha it does show the captions

View File

@@ -1,13 +1,14 @@
#pylint: disable=C0111
from lettuce import world, step
from terrain.steps import reload_the_page
############### ACTIONS ####################
@step('when I view the video it does not have autoplay enabled')
def does_not_autoplay(_step):
assert world.css_find('.video')[0]['data-autoplay'] == 'False'
@step('when I view the (.*) it does not have autoplay enabled')
def does_not_autoplay(_step, video_type):
assert world.css_find('.%s' % video_type)[0]['data-autoplay'] == 'False'
assert world.css_has_class('.video_control', 'play')
@@ -31,3 +32,15 @@ def hide_or_show_captions(step, shown):
button = world.css_find(button_css)
button.mouse_out()
world.css_click(button_css)
@step('I edit the component')
def i_edit_the_component(_step):
world.edit_component()
@step('my videoalpha display name change is persisted on save')
def videoalpha_name_persisted(step):
world.css_click('a.save-button')
reload_the_page(step)
world.edit_component()
world.verify_setting_entry(world.get_setting_entry('Display Name'), 'Display Name', '3.4', True)

View File

@@ -0,0 +1 @@
../../../templates/js/metadata-list-entry.underscore

View File

@@ -3,12 +3,14 @@ describe "Test Metadata Editor", ->
numberEntryTemplate = readFixtures('metadata-number-entry.underscore')
stringEntryTemplate = readFixtures('metadata-string-entry.underscore')
optionEntryTemplate = readFixtures('metadata-option-entry.underscore')
listEntryTemplate = readFixtures('metadata-list-entry.underscore')
beforeEach ->
setFixtures($("<script>", {id: "metadata-editor-tpl", type: "text/template"}).text(editorTemplate))
appendSetFixtures($("<script>", {id: "metadata-number-entry", type: "text/template"}).text(numberEntryTemplate))
appendSetFixtures($("<script>", {id: "metadata-string-entry", type: "text/template"}).text(stringEntryTemplate))
appendSetFixtures($("<script>", {id: "metadata-option-entry", type: "text/template"}).text(optionEntryTemplate))
appendSetFixtures($("<script>", {id: "metadata-list-entry", type: "text/template"}).text(listEntryTemplate))
genericEntry = {
default_value: 'default value',
@@ -62,6 +64,18 @@ describe "Test Metadata Editor", ->
value: 10.2
}
listEntry = {
default_value: ["a thing", "another thing"],
display_name: "List",
explicitly_set: false,
field_name: "list",
help: "A list of things.",
inheritable: false,
options: [],
type: CMS.Models.Metadata.LIST_TYPE,
value: ["the first display value", "the second"]
}
# Test for the editor that creates the individual views.
describe "CMS.Views.Metadata.Editor creates editors for each field", ->
beforeEach ->
@@ -84,16 +98,18 @@ describe "Test Metadata Editor", ->
{"display_name": "Never", "value": "never"}],
type: "unknown type",
value: null
}
},
listEntry
]
)
it "creates child views on initialize, and sorts them alphabetically", ->
view = new CMS.Views.Metadata.Editor({collection: @model})
childModels = view.collection.models
expect(childModels.length).toBe(5)
childViews = view.$el.find('.setting-input')
expect(childViews.length).toBe(5)
expect(childModels.length).toBe(6)
# Be sure to check list view as well as other input types
childViews = view.$el.find('.setting-input, .list-settings')
expect(childViews.length).toBe(6)
verifyEntry = (index, display_name, type) ->
expect(childModels[index].get('display_name')).toBe(display_name)
@@ -101,9 +117,10 @@ describe "Test Metadata Editor", ->
verifyEntry(0, 'Display Name', 'text')
verifyEntry(1, 'Inputs', 'number')
verifyEntry(2, 'Show Answer', 'select-one')
verifyEntry(3, 'Unknown', 'text')
verifyEntry(4, 'Weight', 'number')
verifyEntry(2, 'List', '')
verifyEntry(3, 'Show Answer', 'select-one')
verifyEntry(4, 'Unknown', 'text')
verifyEntry(5, 'Weight', 'number')
it "returns its display name", ->
view = new CMS.Views.Metadata.Editor({collection: @model})
@@ -146,27 +163,27 @@ describe "Test Metadata Editor", ->
# Tests for individual views.
assertInputType = (view, expectedType) ->
input = view.$el.find('.setting-input')
expect(input.length).toBe(1)
expect(input[0].type).toBe(expectedType)
expect(input.length).toEqual(1)
expect(input[0].type).toEqual(expectedType)
assertValueInView = (view, expectedValue) ->
expect(view.getValueFromEditor()).toBe(expectedValue)
expect(view.getValueFromEditor()).toEqual(expectedValue)
assertCanUpdateView = (view, newValue) ->
view.setValueInEditor(newValue)
expect(view.getValueFromEditor()).toBe(newValue)
expect(view.getValueFromEditor()).toEqual(newValue)
assertClear = (view, modelValue, editorValue=modelValue) ->
view.clear()
expect(view.model.getValue()).toBe(null)
expect(view.model.getDisplayValue()).toBe(modelValue)
expect(view.getValueFromEditor()).toBe(editorValue)
expect(view.model.getDisplayValue()).toEqual(modelValue)
expect(view.getValueFromEditor()).toEqual(editorValue)
assertUpdateModel = (view, originalValue, newValue) ->
view.setValueInEditor(newValue)
expect(view.model.getValue()).toBe(originalValue)
expect(view.model.getValue()).toEqual(originalValue)
view.updateModel()
expect(view.model.getValue()).toBe(newValue)
expect(view.model.getValue()).toEqual(newValue)
describe "CMS.Views.Metadata.String is a basic string input with clear functionality", ->
beforeEach ->
@@ -298,3 +315,45 @@ describe "Test Metadata Editor", ->
verifyDisallowedChars(@integerView)
verifyDisallowedChars(@floatView)
describe "CMS.Views.Metadata.List allows the user to enter an ordered list of strings", ->
beforeEach ->
listModel = new CMS.Models.Metadata(listEntry)
@listView = new CMS.Views.Metadata.List({model: listModel})
@el = @listView.$el
it "returns the initial value upon initialization", ->
assertValueInView(@listView, ['the first display value', 'the second'])
it "updates its value correctly", ->
assertCanUpdateView(@listView, ['a new item', 'another new item', 'a third'])
it "has a clear method to revert to the model default", ->
assertClear(@listView, ['a thing', 'another thing'])
it "has an update model method", ->
assertUpdateModel(@listView, null, ['a new value'])
it "can add an entry", ->
expect(@listView.model.get('value').length).toEqual(2)
@el.find('.create-setting').click()
expect(@el.find('input.input').length).toEqual(3)
it "can remove an entry", ->
expect(@listView.model.get('value').length).toEqual(2)
@el.find('.remove-setting').first().click()
expect(@listView.model.get('value').length).toEqual(1)
it "only allows one blank entry at a time", ->
expect(@el.find('input').length).toEqual(2)
@el.find('.create-setting').click()
@el.find('.create-setting').click()
expect(@el.find('input').length).toEqual(3)
it "re-enables the add setting button after entering a new value", ->
expect(@el.find('input').length).toEqual(2)
@el.find('.create-setting').click()
expect(@el.find('.create-setting')).toHaveClass('is-disabled')
@el.find('input').last().val('third setting')
@el.find('input').last().trigger('input')
expect(@el.find('.create-setting')).not.toHaveClass('is-disabled')

View File

@@ -111,3 +111,4 @@ CMS.Models.Metadata.SELECT_TYPE = "Select";
CMS.Models.Metadata.INTEGER_TYPE = "Integer";
CMS.Models.Metadata.FLOAT_TYPE = "Float";
CMS.Models.Metadata.GENERIC_TYPE = "Generic";
CMS.Models.Metadata.LIST_TYPE = "List";

View File

@@ -27,6 +27,9 @@ CMS.Views.Metadata.Editor = Backbone.View.extend({
model.getType() === CMS.Models.Metadata.FLOAT_TYPE) {
new CMS.Views.Metadata.Number(data);
}
else if(model.getType() === CMS.Models.Metadata.LIST_TYPE) {
new CMS.Views.Metadata.List(data);
}
else {
// Everything else is treated as GENERIC_TYPE, which uses String editor.
new CMS.Views.Metadata.String(data);
@@ -310,3 +313,59 @@ CMS.Views.Metadata.Option = CMS.Views.Metadata.AbstractEditor.extend({
}).prop('selected', true);
}
});
CMS.Views.Metadata.List = CMS.Views.Metadata.AbstractEditor.extend({
events : {
"click .setting-clear" : "clear",
"keypress .setting-input" : "showClearButton",
"change input" : "updateModel",
"input input" : "enableAdd",
"click .create-setting" : "addEntry",
"click .remove-setting" : "removeEntry"
},
templateName: "metadata-list-entry",
getValueFromEditor: function () {
return _.map(
this.$el.find('li input'),
function (ele) { return ele.value.trim(); }
).filter(_.identity);
},
setValueInEditor: function (value) {
var list = this.$el.find('ol');
list.empty();
_.each(value, function(ele, index) {
var template = _.template(
'<li class="list-settings-item">' +
'<input type="text" class="input" value="<%= ele %>">' +
'<a href="#" class="remove-action remove-setting" data-index="<%= index %>"><i class="icon-remove-sign"></i><span class="sr">Remove</span></a>' +
'</li>'
);
list.append($(template({'ele': ele, 'index': index})));
});
},
addEntry: function(event) {
event.preventDefault();
// We don't call updateModel here since it's bound to the
// change event
var list = this.model.get('value') || [];
this.setValueInEditor(list.concat(['']))
this.$el.find('.create-setting').addClass('is-disabled');
},
removeEntry: function(event) {
event.preventDefault();
var entry = $(event.currentTarget).siblings().val();
this.setValueInEditor(_.without(this.model.get('value'), entry));
this.updateModel();
this.$el.find('.create-setting').removeClass('is-disabled');
},
enableAdd: function() {
this.$el.find('.create-setting').removeClass('is-disabled');
}
});

View File

@@ -148,6 +148,14 @@ body.course.textbooks {
padding: ($baseline*0.75) $baseline;
background: $gray-l6;
.action {
margin-right: ($baseline/4);
&:last-child {
margin-right: 0;
}
}
// add a chapter is below with chapters styling
.action-primary {

View File

@@ -449,12 +449,39 @@ body.course.unit {
// Module Actions, also used for Static Pages
.module-actions {
box-shadow: inset 0 1px 1px $shadow;
padding: 0 0 $baseline $baseline;
background-color: $gray-l6;
box-shadow: inset 0 1px 2px $shadow;
border-top: 1px solid $gray-l1;
padding: ($baseline*0.75) $baseline;
background: $gray-l6;
.save-button {
margin: ($baseline/2) 8px 0 0;
.action {
display: inline-block;
vertical-align: middle;
margin-right: ($baseline/4);
&:last-child {
margin-right: 0;
}
}
.action-primary {
@include blue-button;
@extend .t-action2;
@include transition(all .15s);
display: inline-block;
padding: ($baseline/5) $baseline;
font-weight: 600;
text-transform: uppercase;
}
.action-secondary {
@include grey-button;
@extend .t-action2;
@include transition(all .15s);
display: inline-block;
padding: ($baseline/5) $baseline;
font-weight: 600;
text-transform: uppercase;
}
}
}
@@ -599,26 +626,27 @@ body.course.unit {
}
}
.wrapper-comp-setting{
.wrapper-comp-setting {
display: inline-block;
min-width: 300px;
width: 45%;
width: 55%;
top: 0;
vertical-align: top;
margin-bottom:5px;
position: relative;
}
label.setting-label {
.setting-label {
@extend .t-copy-sub1;
@include transition(color $tmg-f2 ease-in-out 0s);
font-weight: 400;
vertical-align: middle;
display: inline-block;
position: relative;
left: 0;
width: 33%;
min-width: 100px;
width: 35%;
margin-right: ($baseline/2);
font-weight: 600;
&.is-focused {
color: $blue;
@@ -708,14 +736,98 @@ body.course.unit {
}
}
.tip.setting-help {
.setting-help {
@include font-size(12);
display: inline-block;
font-color: $gray-l6;
min-width: 260px;
width: 50%;
min-width: ($baseline*10);
width: 35%;
vertical-align: top;
}
// TYPE: enumerated lists of metadata sets
.metadata-list-enum {
* {
@include box-sizing(border-box);
}
// label
.setting-label {
vertical-align: top;
margin-top: ($baseline/2);
}
// inputs and labels
.wrapper-list-settings {
@include size(45%,100%);
display: inline-block;
min-width: ($baseline*5);
// enumerated fields
.list-settings {
margin: 0;
.list-settings-item {
margin-bottom: ($baseline/2);
}
// inputs
.input {
width: 80%;
margin-right: ($baseline/2);
vertical-align: middle;
}
}
}
// actions
.create-action, .remove-action, .setting-clear {
}
.setting-clear {
vertical-align: top;
margin-top: ($baseline/4);
}
.create-setting {
@extend .ui-btn-flat-outline;
@extend .t-action3;
display: block;
width: 100%;
padding: ($baseline/2);
font-weight: 600;
*[class^="icon-"] {
margin-right: ($baseline/4);
}
// STATE: disabled
&.is-disabled {
}
}
.remove-setting {
@include transition(color 0.25s ease-in-out);
@include font-size(20);
display: inline-block;
background: transparent;
color: $blue-l3;
&:hover {
color: $blue;
}
// STATE: disabled
&.is-disabled {
}
}
}
}
}
}

View File

@@ -26,8 +26,8 @@
</div>
</div>
<div class="row module-actions">
<a href="#" class="save-button">${_("Save")}</a>
<a href="#" class="cancel-button">${_("Cancel")}</a>
<a href="#" class="save-button action-primary action">${_("Save")}</a>
<a href="#" class="cancel-button action-secondary action">${_("Cancel")}</a>
</div> <!-- Module Actions-->
</div>
</div>

View File

@@ -1,6 +1,6 @@
<ul class="list-input settings-list">
<% _.each(_.range(numEntries), function() { %>
<li class="field comp-setting-entry metadata_entry" id="settings-listing">
<li class="field comp-setting-entry metadata_entry">
</li>
<% }) %>
</ul>

View File

@@ -0,0 +1,17 @@
<div class="wrapper-comp-setting metadata-list-enum">
<label class="label setting-label" for="<%= uniqueId %>"><%= model.get('display_name')%></label>
<div id="<%= uniqueId %>" class="wrapper-list-settings">
<ol class="list-settings">
</ol>
<a href="#" class="create-action create-setting">
<i class="icon-plus"></i><%= gettext("Add") %> <span class="sr"><%= model.get('display_name')%></span>
</a>
</div>
<button class="action setting-clear inactive" type="button" name="setting-clear" value="<%= gettext("Clear") %>" data-tooltip="<%= gettext("Clear") %>">
<i class="icon-undo"></i>
<span class="sr">"<%= gettext("Clear Value") %>"</span>
</button>
</div>
<span class="tip setting-help"><%= model.get('help') %></span>

View File

@@ -25,6 +25,10 @@
<%static:include path="js/metadata-option-entry.underscore" />
</script>
<script id="metadata-list-entry" type="text/template">
<%static:include path="js/metadata-list-entry.underscore" />
</script>
<% showHighLevelSource='source_code' in editable_metadata_fields and editable_metadata_fields['source_code']['explicitly_set'] %>
<% metadata_field_copy = copy.copy(editable_metadata_fields) %>
## Delete 'source_code' field (if it exists) so metadata editor view does not attempt to render it.
@@ -40,4 +44,4 @@
<%include file="source-edit.html" />
% endif
<div class="wrapper-comp-settings metadata_edit" id="settings-tab" data-metadata='${json.dumps(metadata_field_copy) | h}'/>
<div class="wrapper-comp-settings metadata_edit" id="settings-tab" data-metadata='${json.dumps(metadata_field_copy) | h}'/>

View File

@@ -9,7 +9,7 @@
% endfor
</ul>
</div>
<div class="${'tabs-wrapper' if (len(tabs) != 1) else 'editor-single-tab' }">
<div class="tabs-wrapper">
% 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']"/>

View File

@@ -20,5 +20,9 @@
<%static:include path="js/metadata-option-entry.underscore" />
</script>
<script id="metadata-list-entry" type="text/template">
<%static:include path="js/metadata-list-entry.underscore" />
</script>
<div class="wrapper-comp-settings metadata_edit" id="settings-tab" data-metadata='${json.dumps(editable_metadata_fields) | h}'/>