BLD-1000: Download handout.

This commit is contained in:
polesye
2014-04-14 18:52:58 +03:00
parent f3842dcc01
commit 127bf7ed3f
24 changed files with 498 additions and 81 deletions

View File

@@ -47,7 +47,8 @@ var FileUpload = Backbone.Model.extend({
return RegExp(('(?:.+)\\.(' + formats.join('|') + ')$'), 'i');
};
return _.contains(attrs.mimeTypes, file.type) ||
return (attrs.mimeTypes.length === 0 && attrs.fileFormats.length === 0) ||
_.contains(attrs.mimeTypes, file.type) ||
getRegExp(attrs.fileFormats).test(file.name);
},
// Return strings for the valid file types and extensions this

View File

@@ -0,0 +1,176 @@
define(
[
'jquery', 'underscore', 'js/spec_helpers/create_sinon', 'squire'
],
function ($, _, create_sinon, Squire) {
'use strict';
describe('FileUploader', function () {
var FileUploaderTemplate = readFixtures(
'metadata-file-uploader-entry.underscore'
),
FileUploaderItemTemplate = readFixtures(
'metadata-file-uploader-item.underscore'
),
locator = 'locator',
feedbackTpl = readFixtures('system-feedback.underscore'),
modelStub = {
default_value: 'http://example.org/test_1',
display_name: 'File Upload',
explicitly_set: false,
field_name: 'file_upload',
help: 'Specifies the name for this component.',
type: 'FileUploader',
value: 'http://example.org/test_1'
},
self, injector;
var setValue = function (view, value) {
view.setValueInEditor(value);
view.updateModel();
};
var createPromptSpy = function (name) {
var spy = jasmine.createSpyObj(name, ['constructor', 'show', 'hide']);
spy.constructor.andReturn(spy);
spy.show.andReturn(spy);
spy.extend = jasmine.createSpy().andReturn(spy.constructor);
return spy;
};
beforeEach(function () {
self = this;
this.addMatchers({
assertValueInView: function(expected) {
var value = this.actual.getValueFromEditor();
return this.env.equals_(value, expected);
},
assertCanUpdateView: function (expected) {
var view = this.actual,
value;
view.setValueInEditor(expected);
value = view.getValueFromEditor();
return this.env.equals_(value, expected);
},
assertClear: function (modelValue) {
var env = this.env,
view = this.actual,
model = view.model;
return model.getValue() === null &&
env.equals_(model.getDisplayValue(), modelValue) &&
env.equals_(view.getValueFromEditor(), modelValue);
},
assertUpdateModel: function (originalValue, newValue) {
var env = this.env,
view = this.actual,
model = view.model,
expectOriginal;
view.setValueInEditor(newValue);
expectOriginal = env.equals_(model.getValue(), originalValue);
view.updateModel();
return expectOriginal &&
env.equals_(model.getValue(), newValue);
},
verifyButtons: function (upload, download, index) {
var view = this.actual,
uploadBtn = view.$('.upload-setting'),
downloadBtn = view.$('.download-setting');
upload = upload ? uploadBtn.length : !uploadBtn.length;
download = download ? downloadBtn.length : !downloadBtn.length;
return upload && download;
}
});
appendSetFixtures($('<script>', {
id: 'metadata-file-uploader-entry',
type: 'text/template'
}).text(FileUploaderTemplate));
appendSetFixtures($('<script>', {
id: 'metadata-file-uploader-item',
type: 'text/template'
}).text(FileUploaderItemTemplate));
this.uploadSpies = createPromptSpy('UploadDialog');
injector = new Squire();
injector.mock('js/views/uploads', function () {
return self.uploadSpies.constructor;
});
injector.mock('js/views/video/transcripts/metadata_videolist');
injector.mock('js/views/video/translations_editor');
runs(function() {
injector.require([
'js/models/metadata', 'js/views/metadata'
],
function(MetadataModel, MetadataView) {
var model = new MetadataModel($.extend(true, {}, modelStub));
self.view = new MetadataView.FileUploader({
model: model,
locator: locator
});
});
});
waitsFor(function() {
return self.view;
}, 'FileUploader was not created', 2000);
});
afterEach(function () {
injector.clean();
injector.remove();
});
it('returns the initial value upon initialization', function () {
expect(this.view).assertValueInView('http://example.org/test_1');
expect(this.view).verifyButtons(true, true);
});
it('updates its value correctly', function () {
expect(this.view).assertCanUpdateView('http://example.org/test_2');
});
it('upload works correctly', function () {
var options;
setValue(this.view, '');
expect(this.view).verifyButtons(true, false);
this.view.$el.find('.upload-setting').click();
expect(this.uploadSpies.constructor).toHaveBeenCalled();
expect(this.uploadSpies.show).toHaveBeenCalled();
options = this.uploadSpies.constructor.mostRecentCall.args[0];
options.onSuccess({
'asset': {
'url': 'http://example.org/test_3'
}
});
expect(this.view).verifyButtons(true, true);
expect(this.view.getValueFromEditor()).toEqual('http://example.org/test_3');
});
it('has a clear method to revert to the model default', function () {
setValue(this.view, 'http://example.org/test_5');
this.view.clear();
expect(this.view).assertClear('http://example.org/test_1');
});
it('has an update model method', function () {
expect(this.view).assertUpdateModel(null, 'http://example.org/test_6');
});
});
});

View File

@@ -212,43 +212,22 @@ function ($, _, create_sinon, Squire) {
});
});
describe('has a clear method to revert to the model default', function () {
it('w/ popup, if values were changed', function (){
var requests = create_sinon.requests(this),
options;
setValue(this.view, {
'fr': 'fr.srt',
'uk': 'uk.srt'
});
this.view.$el.find('.create-setting').click();
this.view.clear();
expect(this.view).assertClear({
'en': 'en.srt',
'ru': 'ru.srt'
});
expect(this.view.$el.find('.create-setting')).not.toHaveClass('is-disabled');
it('has a clear method to revert to the model default', function () {
setValue(this.view, {
'fr': 'en.srt',
'uk': 'ru.srt'
});
it('w/o popup, if just keys were changed', function (){
setValue(this.view, {
'fr': 'en.srt',
'uk': 'ru.srt'
});
this.view.$el.find('.create-setting').click();
this.view.$el.find('.create-setting').click();
this.view.clear();
this.view.clear();
expect(this.view).assertClear({
'en': 'en.srt',
'ru': 'ru.srt'
});
expect(this.view.$el.find('.create-setting')).not.toHaveClass('is-disabled');
expect(this.view).assertClear({
'en': 'en.srt',
'ru': 'ru.srt'
});
expect(this.view.$el.find('.create-setting')).not.toHaveClass('is-disabled');
});
it('has an update model method', function () {
@@ -261,26 +240,15 @@ function ($, _, create_sinon, Squire) {
expect(this.view.$el.find('select').length).toEqual(5);
});
describe('can remove an entry', function () {
it('w/ popup, if values were changed', function (){
var requests = create_sinon.requests(this),
options;
expect(_.keys(this.view.model.get('value')).length).toEqual(4);
this.view.$el.find('.remove-setting').last().click();
expect(_.keys(this.view.model.get('value')).length).toEqual(3);
});
it('w/o popup, if just keys were changed', function (){
setValue(this.view, {
'en': 'en.srt',
'ru': 'ru.srt',
'fr': ''
});
expect(_.keys(this.view.model.get('value')).length).toEqual(3);
this.view.$el.find('.remove-setting').last().click();
expect(_.keys(this.view.model.get('value')).length).toEqual(2);
it('can remove an entry', function () {
setValue(this.view, {
'en': 'en.srt',
'ru': 'ru.srt',
'fr': ''
});
expect(_.keys(this.view.model.get('value')).length).toEqual(3);
this.view.$el.find('.remove-setting').last().click();
expect(_.keys(this.view.model.get('value')).length).toEqual(2);
});
it('only allows one blank entry at a time', function () {

View File

@@ -8,12 +8,7 @@ define(["js/views/baseview", "underscore"], function(BaseView, _) {
var templateName = _.result(this, 'templateName');
// Backbone model cid is only unique within the collection.
this.uniqueId = _.uniqueId(templateName + "_");
var tpl = document.getElementById(templateName).text;
if(!tpl) {
console.error("Couldn't load template: " + templateName);
}
this.template = _.template(tpl);
this.template = this.loadTemplate(templateName);
this.$el.html(this.template({model: this.model, uniqueId: this.uniqueId}));
this.listenTo(this.model, 'change', this.render);
this.render();
@@ -85,6 +80,20 @@ define(["js/views/baseview", "underscore"], function(BaseView, _) {
}
return this;
},
/**
* Loads the named template from the page, or logs an error if it fails.
* @param name The name of the template.
* @returns The loaded template.
*/
loadTemplate: function(name) {
var templateSelector = "#" + name,
templateText = $(templateSelector).text();
if (!templateText) {
console.error("Failed to load " + name + " template");
}
return _.template(templateText);
}
});

View File

@@ -1,26 +1,29 @@
define(
[
"js/views/baseview", "underscore", "js/models/metadata", "js/views/abstract_editor",
"js/models/uploads", "js/views/uploads",
"js/views/video/transcripts/metadata_videolist",
"js/views/video/translations_editor"
],
function(BaseView, _, MetadataModel, AbstractEditor, VideoList, VideoTranslations) {
function(BaseView, _, MetadataModel, AbstractEditor, FileUpload, UploadDialog, VideoList, VideoTranslations) {
var Metadata = {};
Metadata.Editor = BaseView.extend({
// Model is CMS.Models.MetadataCollection,
initialize : function() {
var self = this,
counter = 0,
locator = self.$el.closest('[data-locator]').data('locator');
this.template = this.loadTemplate('metadata-editor');
this.$el.html(this.template({numEntries: this.collection.length}));
var counter = 0;
var self = this;
this.collection.each(
function (model) {
var data = {
el: self.$el.find('.metadata_entry')[counter++],
locator: locator,
model: model
},
conversions = {
@@ -85,7 +88,7 @@ function(BaseView, _, MetadataModel, AbstractEditor, VideoList, VideoTranslation
events : {
"change input" : "updateModel",
"keypress .setting-input" : "showClearButton" ,
"keypress .setting-input" : "showClearButton",
"click .setting-clear" : "clear"
},
@@ -487,5 +490,62 @@ function(BaseView, _, MetadataModel, AbstractEditor, VideoList, VideoTranslation
}
});
/**
* Provides convenient way to upload/download files in component edit.
* The editor uploads files directly to course assets and stores link
* to uploaded file.
*/
Metadata.FileUploader = AbstractEditor.extend({
events : {
"click .upload-setting" : "upload",
"click .setting-clear" : "clear"
},
templateName: "metadata-file-uploader-entry",
templateButtonsName: "metadata-file-uploader-item",
initialize: function () {
this.buttonTemplate = this.loadTemplate(this.templateButtonsName);
AbstractEditor.prototype.initialize.apply(this);
},
getValueFromEditor: function () {
return this.$('#' + this.uniqueId).val();
},
setValueInEditor: function (value) {
var html = this.buttonTemplate({
model: this.model,
uniqueId: this.uniqueId
});
this.$('#' + this.uniqueId).val(value);
this.$('.wrapper-uploader-actions').html(html);
},
upload: function (event) {
var self = this,
target = $(event.currentTarget),
url = /assets/ + this.options.locator,
model = new FileUpload({
title: gettext('Upload File'),
}),
view = new UploadDialog({
model: model,
url: url,
parentElement: target.closest('.xblock-editor'),
onSuccess: function (response) {
if (response['asset'] && response['asset']['url']) {
self.model.setValue(response['asset']['url']);
}
}
}).show();
event.preventDefault();
}
});
return Metadata;
});