Course discovery UI improvements
This commit is contained in:
@@ -217,7 +217,7 @@ class IndexPageCourseCardsSortingTests(ModuleStoreTestCase):
|
||||
|
||||
# assert that the course discovery UI is not present
|
||||
self.assertNotIn('Search for a course', response.content)
|
||||
self.assertNotIn('<aside aria-label="Refine your search" class="search-facets phone-menu">', response.content)
|
||||
self.assertNotIn('<aside aria-label="Refine Your Search" class="search-facets phone-menu">', response.content)
|
||||
|
||||
# make sure we have the special css class on the section
|
||||
self.assertIn('<div class="courses no-course-discovery"', response.content)
|
||||
@@ -241,7 +241,7 @@ class IndexPageCourseCardsSortingTests(ModuleStoreTestCase):
|
||||
|
||||
# assert that the course discovery UI is present
|
||||
self.assertIn('Search for a course', response.content)
|
||||
self.assertIn('<aside aria-label="Refine your search" class="search-facets phone-menu">', response.content)
|
||||
self.assertIn('<aside aria-label="Refine Your Search" class="search-facets phone-menu">', response.content)
|
||||
self.assertIn('<div class="courses"', response.content)
|
||||
|
||||
@patch('student.views.render_to_response', RENDER_MOCK)
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
define([
|
||||
'backbone',
|
||||
'js/discovery/result'
|
||||
], function (Backbone, Result) {
|
||||
'js/discovery/models/course_card'
|
||||
], function (Backbone, CourseCard) {
|
||||
'use strict';
|
||||
|
||||
return Backbone.Collection.extend({
|
||||
|
||||
model: Result,
|
||||
model: CourseCard,
|
||||
pageSize: 20,
|
||||
totalCount: 0,
|
||||
latestModelsCount: 0,
|
||||
|
||||
18
lms/static/js/discovery/collections/filters.js
Normal file
18
lms/static/js/discovery/collections/filters.js
Normal file
@@ -0,0 +1,18 @@
|
||||
;(function (define) {
|
||||
|
||||
define(['backbone', 'js/discovery/models/filter'], function (Backbone, Filter) {
|
||||
'use strict';
|
||||
|
||||
return Backbone.Collection.extend({
|
||||
model: Filter,
|
||||
getTerms: function () {
|
||||
return this.reduce(function (terms, filter) {
|
||||
terms[filter.id] = filter.get('query');
|
||||
return terms;
|
||||
}, {});
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
})(define || RequireJS.define);
|
||||
@@ -1,71 +1,97 @@
|
||||
;(function (define) {
|
||||
'use strict';
|
||||
|
||||
define(['backbone', 'js/discovery/collection', 'js/discovery/form', 'js/discovery/result_list_view',
|
||||
'js/discovery/filter_bar_view', 'js/discovery/search_facets_view'],
|
||||
function(Backbone, Collection, Form, ResultListView, FilterBarView, FacetsBarView) {
|
||||
define(['backbone', 'js/discovery/models/search_state', 'js/discovery/collections/filters',
|
||||
'js/discovery/views/search_form', 'js/discovery/views/courses_listing',
|
||||
'js/discovery/views/filter_bar', 'js/discovery/views/refine_sidebar'],
|
||||
function(Backbone, SearchState, Filters, SearchForm, CoursesListing, FilterBar, RefineSidebar) {
|
||||
|
||||
return function (meanings, searchQuery) {
|
||||
//facet types configuration - set default display names
|
||||
var facetsTypes = meanings;
|
||||
|
||||
var collection = new Collection([]);
|
||||
var results = new ResultListView({ collection: collection });
|
||||
var dispatcher = _.clone(Backbone.Events);
|
||||
var form = new Form();
|
||||
var filters = new FilterBarView();
|
||||
var facetsBarView = new FacetsBarView(facetsTypes);
|
||||
var dispatcher = _.extend({}, Backbone.Events);
|
||||
var search = new SearchState();
|
||||
var filters = new Filters();
|
||||
var listing = new CoursesListing({ model: search.discovery });
|
||||
var form = new SearchForm();
|
||||
var filterBar = new FilterBar({ collection: filters });
|
||||
var refineSidebar = new RefineSidebar({
|
||||
collection: search.discovery.facetOptions,
|
||||
meanings: meanings
|
||||
});
|
||||
|
||||
dispatcher.listenTo(form, 'search', function (query) {
|
||||
filters.reset();
|
||||
form.showLoadingIndicator();
|
||||
filters.changeQueryFilter(query);
|
||||
search.performSearch(query, filters.getTerms());
|
||||
});
|
||||
|
||||
dispatcher.listenTo(filters, 'search', function (searchTerm, facets) {
|
||||
collection.performSearch(searchTerm, facets);
|
||||
dispatcher.listenTo(refineSidebar, 'selectOption', function (type, query, name) {
|
||||
form.showLoadingIndicator();
|
||||
});
|
||||
|
||||
dispatcher.listenTo(filters, 'clear', function () {
|
||||
form.clearSearch();
|
||||
collection.performSearch();
|
||||
filters.hideClearAllButton();
|
||||
});
|
||||
|
||||
dispatcher.listenTo(results, 'next', function () {
|
||||
collection.loadNextPage();
|
||||
form.showLoadingIndicator();
|
||||
});
|
||||
|
||||
dispatcher.listenTo(collection, 'search', function () {
|
||||
if (collection.length > 0) {
|
||||
form.showFoundMessage(collection.totalCount);
|
||||
results.render();
|
||||
if (filters.get(type)) {
|
||||
removeFilter(type);
|
||||
}
|
||||
else {
|
||||
form.showNotFoundMessage(collection.searchTerm);
|
||||
filters.add({type: type, query: query, name: name});
|
||||
search.refineSearch(filters.getTerms());
|
||||
}
|
||||
facetsBarView.renderFacets(collection.facets);
|
||||
form.hideLoadingIndicator();
|
||||
});
|
||||
|
||||
dispatcher.listenTo(collection, 'next', function () {
|
||||
results.renderNext();
|
||||
form.hideLoadingIndicator();
|
||||
dispatcher.listenTo(filterBar, 'clearFilter', removeFilter);
|
||||
|
||||
dispatcher.listenTo(filterBar, 'clearAll', function () {
|
||||
form.doSearch('');
|
||||
});
|
||||
|
||||
dispatcher.listenTo(collection, 'error', function () {
|
||||
dispatcher.listenTo(listing, 'next', function () {
|
||||
search.loadNextPage()
|
||||
});
|
||||
|
||||
dispatcher.listenTo(search, 'next', function () {
|
||||
listing.renderNext();
|
||||
});
|
||||
|
||||
dispatcher.listenTo(search, 'search', function (query, total) {
|
||||
if (total > 0) {
|
||||
form.showFoundMessage(total);
|
||||
if (query) {
|
||||
filters.add(
|
||||
{type: 'search_query', query: query, name: quote(query)},
|
||||
{merge: true}
|
||||
);
|
||||
}
|
||||
}
|
||||
else {
|
||||
form.showNotFoundMessage(query);
|
||||
filters.reset();
|
||||
}
|
||||
form.hideLoadingIndicator();
|
||||
listing.render();
|
||||
refineSidebar.render();
|
||||
});
|
||||
|
||||
dispatcher.listenTo(search, 'error', function () {
|
||||
form.showErrorMessage();
|
||||
form.hideLoadingIndicator();
|
||||
});
|
||||
|
||||
dispatcher.listenTo(facetsBarView, 'addFilter', function (data) {
|
||||
filters.addFilter(data);
|
||||
});
|
||||
|
||||
// kick off search on page refresh
|
||||
form.doSearch(searchQuery);
|
||||
|
||||
function removeFilter(type) {
|
||||
form.showLoadingIndicator();
|
||||
filters.remove(type);
|
||||
if (type === 'search_query') {
|
||||
form.doSearch('');
|
||||
}
|
||||
else {
|
||||
search.refineSearch(filters.getTerms());
|
||||
}
|
||||
}
|
||||
|
||||
function quote(string) {
|
||||
return '"'+string+'"';
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
;(function (define) {
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'underscore',
|
||||
'backbone',
|
||||
'gettext',
|
||||
'js/discovery/filters',
|
||||
'js/discovery/filter',
|
||||
'js/discovery/filter_view'
|
||||
], function ($, _, Backbone, gettext, FiltersCollection, Filter, FilterView) {
|
||||
'use strict';
|
||||
|
||||
return Backbone.View.extend({
|
||||
|
||||
el: '#filter-bar',
|
||||
|
||||
tagName: 'div',
|
||||
templateId: '#filter_bar-tpl',
|
||||
className: 'filters hidden',
|
||||
|
||||
events: {
|
||||
'click #clear-all-filters': 'clearAll',
|
||||
'click li .discovery-button': 'clearFilter'
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
this.collection = new FiltersCollection([]);
|
||||
this.tpl = _.template($(this.templateId).html());
|
||||
this.$el.html(this.tpl());
|
||||
this.hideClearAllButton();
|
||||
this.filtersList = this.$el.find('ul');
|
||||
},
|
||||
|
||||
render: function () {
|
||||
return this;
|
||||
},
|
||||
|
||||
changeQueryFilter: function(query) {
|
||||
var queryModel = this.collection.getQueryModel();
|
||||
if (typeof queryModel !== 'undefined') {
|
||||
this.collection.remove(queryModel);
|
||||
}
|
||||
|
||||
if (query) {
|
||||
var data = {query: query, type: 'search_string'};
|
||||
this.addFilter(data);
|
||||
}
|
||||
else {
|
||||
this.startSearch();
|
||||
}
|
||||
},
|
||||
|
||||
addFilter: function(data) {
|
||||
var currentfilter = this.collection.findWhere(data);
|
||||
if(typeof currentfilter === 'undefined') {
|
||||
var filter = new Filter(data);
|
||||
var filterView = new FilterView({model: filter});
|
||||
this.collection.add(filter);
|
||||
this.filtersList.append(filterView.render().el);
|
||||
this.trigger('search', this.getSearchTerm(), this.collection);
|
||||
if (this.$el.hasClass('hidden')) {
|
||||
this.showClearAllButton();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
clearFilter: function (event) {
|
||||
event.preventDefault();
|
||||
var $target = $(event.currentTarget);
|
||||
var clearModel = this.collection.findWhere({
|
||||
query: $target.data('value'),
|
||||
type: $target.data('type')
|
||||
});
|
||||
this.collection.remove(clearModel);
|
||||
this.startSearch();
|
||||
},
|
||||
|
||||
clearFilters: function() {
|
||||
this.collection.reset([]);
|
||||
this.filtersList.empty();
|
||||
},
|
||||
|
||||
clearAll: function(event) {
|
||||
event.preventDefault();
|
||||
this.clearFilters();
|
||||
this.trigger('clear');
|
||||
},
|
||||
|
||||
showClearAllButton: function () {
|
||||
this.$el.removeClass('hidden');
|
||||
},
|
||||
|
||||
hideClearAllButton: function() {
|
||||
this.$el.addClass('hidden');
|
||||
},
|
||||
|
||||
getSearchTerm: function() {
|
||||
var queryModel = this.collection.getQueryModel();
|
||||
if (typeof queryModel !== 'undefined') {
|
||||
return queryModel.get('query');
|
||||
}
|
||||
return '';
|
||||
},
|
||||
|
||||
startSearch: function() {
|
||||
if (this.collection.length === 0) {
|
||||
this.trigger('clear');
|
||||
}
|
||||
else {
|
||||
this.trigger('search', this.getSearchTerm(), this.collection);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
})(define || RequireJS.define);
|
||||
@@ -1,30 +0,0 @@
|
||||
;(function (define) {
|
||||
|
||||
define([
|
||||
'backbone',
|
||||
'js/discovery/filter'
|
||||
], function (Backbone, Filter) {
|
||||
'use strict';
|
||||
|
||||
return Backbone.Collection.extend({
|
||||
|
||||
model: Filter,
|
||||
url: '',
|
||||
|
||||
initialize: function () {
|
||||
this.bind('remove', this.onModelRemoved, this);
|
||||
},
|
||||
|
||||
onModelRemoved: function (model, collection, options) {
|
||||
model.cleanModelView();
|
||||
},
|
||||
|
||||
getQueryModel: function() {
|
||||
return this.findWhere({'type': 'search_string'});
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
})(define || RequireJS.define);
|
||||
65
lms/static/js/discovery/models/course_discovery.js
Normal file
65
lms/static/js/discovery/models/course_discovery.js
Normal file
@@ -0,0 +1,65 @@
|
||||
;(function (define) {
|
||||
|
||||
define([
|
||||
'underscore',
|
||||
'backbone',
|
||||
'js/discovery/models/course_card',
|
||||
'js/discovery/models/facet_option',
|
||||
], function (_, Backbone, CourseCard, FacetOption) {
|
||||
'use strict';
|
||||
|
||||
return Backbone.Model.extend({
|
||||
url: '/search/course_discovery/',
|
||||
jqhxr: null,
|
||||
|
||||
defaults: {
|
||||
totalCount: 0,
|
||||
latestCount: 0
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
this.courseCards = new Backbone.Collection([], { model: CourseCard });
|
||||
this.facetOptions = new Backbone.Collection([], { model: FacetOption });
|
||||
},
|
||||
|
||||
parse: function (response) {
|
||||
var courses = response.results || [];
|
||||
var facets = response.facets || {};
|
||||
this.courseCards.add(_.pluck(courses, 'data'));
|
||||
|
||||
this.set({
|
||||
totalCount: response.total,
|
||||
latestCount: courses.length
|
||||
});
|
||||
|
||||
var options = this.facetOptions;
|
||||
_(facets).each(function (obj, key) {
|
||||
_(obj.terms).each(function (count, term) {
|
||||
options.add({
|
||||
facet: key,
|
||||
term: term,
|
||||
count: count
|
||||
}, {merge: true});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
reset: function () {
|
||||
this.set({
|
||||
totalCount: 0,
|
||||
latestCount: 0
|
||||
});
|
||||
this.courseCards.reset();
|
||||
this.facetOptions.reset();
|
||||
},
|
||||
|
||||
latest: function () {
|
||||
return this.courseCards.last(this.get('latestCount'));
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
})(define || RequireJS.define);
|
||||
18
lms/static/js/discovery/models/facet_option.js
Normal file
18
lms/static/js/discovery/models/facet_option.js
Normal file
@@ -0,0 +1,18 @@
|
||||
;(function (define) {
|
||||
|
||||
define(['backbone'], function (Backbone) {
|
||||
'use strict';
|
||||
|
||||
return Backbone.Model.extend({
|
||||
idAttribute: 'term',
|
||||
defaults: {
|
||||
facet: '',
|
||||
term: '',
|
||||
count: 0,
|
||||
selected: false
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
})(define || RequireJS.define);
|
||||
@@ -4,13 +4,11 @@ define(['backbone'], function (Backbone) {
|
||||
'use strict';
|
||||
|
||||
return Backbone.Model.extend({
|
||||
idAttribute: 'type',
|
||||
defaults: {
|
||||
type: 'search_query',
|
||||
query: '',
|
||||
type: 'search_string'
|
||||
},
|
||||
|
||||
cleanModelView: function() {
|
||||
this.destroy();
|
||||
name: ''
|
||||
}
|
||||
});
|
||||
|
||||
151
lms/static/js/discovery/models/search_state.js
Normal file
151
lms/static/js/discovery/models/search_state.js
Normal file
@@ -0,0 +1,151 @@
|
||||
;(function (define) {
|
||||
|
||||
define([
|
||||
'underscore',
|
||||
'backbone',
|
||||
'js/discovery/models/course_discovery',
|
||||
'js/discovery/collections/filters'
|
||||
], function (_, Backbone, CourseDiscovery, Filters) {
|
||||
'use strict';
|
||||
|
||||
|
||||
return Backbone.Model.extend({
|
||||
|
||||
page: 0,
|
||||
pageSize: 20,
|
||||
searchTerm: '',
|
||||
terms: {},
|
||||
jqhxr: null,
|
||||
|
||||
initialize: function () {
|
||||
this.discovery = new CourseDiscovery();
|
||||
this.listenTo(this.discovery, 'sync', this.onSync, this);
|
||||
this.listenTo(this.discovery, 'error', this.onError, this);
|
||||
},
|
||||
|
||||
performSearch: function (searchTerm, otherTerms) {
|
||||
this.reset();
|
||||
this.searchTerm = searchTerm;
|
||||
if (otherTerms) {
|
||||
this.terms = otherTerms;
|
||||
}
|
||||
this.sendQuery(this.buildQuery(0));
|
||||
},
|
||||
|
||||
refineSearch: function (terms) {
|
||||
this.reset();
|
||||
this.terms = terms;
|
||||
this.sendQuery(this.buildQuery(0));
|
||||
},
|
||||
|
||||
loadNextPage: function () {
|
||||
if (this.hasNextPage()) {
|
||||
this.sendQuery(this.buildQuery(this.page+1));
|
||||
}
|
||||
},
|
||||
|
||||
// private
|
||||
|
||||
hasNextPage: function () {
|
||||
var total = this.discovery.get('totalCount');
|
||||
return total - ((this.page+1) * this.pageSize) > 0;
|
||||
},
|
||||
|
||||
sendQuery: function (data) {
|
||||
this.jqhxr && this.jqhxr.abort();
|
||||
this.jqhxr = this.discovery.fetch({
|
||||
type: 'POST',
|
||||
data: data
|
||||
});
|
||||
return this.jqhxr;
|
||||
},
|
||||
|
||||
buildQuery: function (pageIndex) {
|
||||
var data = {
|
||||
search_string: this.searchTerm,
|
||||
page_size: this.pageSize,
|
||||
page_index: pageIndex
|
||||
};
|
||||
_.extend(data, this.terms);
|
||||
return data;
|
||||
},
|
||||
|
||||
reset: function () {
|
||||
this.discovery.reset();
|
||||
this.page = 0;
|
||||
},
|
||||
|
||||
onError: function (collection, response, options) {
|
||||
if (response.statusText !== 'abort') {
|
||||
this.trigger('error');
|
||||
}
|
||||
},
|
||||
|
||||
onSync: function (collection, response, options) {
|
||||
var total = this.discovery.get('totalCount');
|
||||
var originalSearchTerm = this.searchTerm;
|
||||
if (options.data.page_index === 0) {
|
||||
if (total === 0) {
|
||||
// list all courses
|
||||
this.cachedDiscovery().done(function (cached) {
|
||||
this.discovery.courseCards.reset(cached.courseCards.toJSON());
|
||||
this.discovery.facetOptions.reset(cached.facetOptions.toJSON());
|
||||
this.discovery.set('latestCount', cached.get('latestCount'));
|
||||
this.trigger('search', originalSearchTerm, total);
|
||||
});
|
||||
this.searchTerm = '';
|
||||
this.terms = {};
|
||||
}
|
||||
else {
|
||||
_.each(this.terms, function (term, facet) {
|
||||
if (facet !== 'search_query') {
|
||||
var option = this.discovery.facetOptions.findWhere({
|
||||
facet: facet,
|
||||
term: term
|
||||
})
|
||||
if (option) {
|
||||
option.set('selected', true);
|
||||
}
|
||||
}
|
||||
}, this);
|
||||
this.trigger('search', this.searchTerm, total);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.page = options.data.page_index;
|
||||
this.trigger('next');
|
||||
}
|
||||
},
|
||||
|
||||
// lazy load
|
||||
cachedDiscovery: function () {
|
||||
var deferred = $.Deferred();
|
||||
var self = this;
|
||||
|
||||
if (this.cached) {
|
||||
deferred.resolveWith(this, [this.cached]);
|
||||
}
|
||||
else {
|
||||
this.cached = new CourseDiscovery();
|
||||
this.cached.fetch({
|
||||
type: 'POST',
|
||||
data: {
|
||||
search_string: '',
|
||||
page_size: this.pageSize,
|
||||
page_index: 0
|
||||
},
|
||||
success: function(model, response, options) {
|
||||
deferred.resolveWith(self, [model]);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
return deferred.promise();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
})(define || RequireJS.define);
|
||||
@@ -1,127 +0,0 @@
|
||||
;(function (define) {
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'underscore',
|
||||
'backbone',
|
||||
'gettext',
|
||||
'js/discovery/facets_view',
|
||||
'js/discovery/facet_view'
|
||||
], function ($, _, Backbone, gettext, FacetsView, FacetView) {
|
||||
'use strict';
|
||||
|
||||
return Backbone.View.extend({
|
||||
|
||||
el: '.search-facets',
|
||||
|
||||
tagName: 'div',
|
||||
templateId: '#search_facets_list-tpl',
|
||||
className: 'facets',
|
||||
facetsTypes: {},
|
||||
moreLessLinksTpl: '#more_less_links-tpl',
|
||||
|
||||
events: {
|
||||
'click li': 'addFacet',
|
||||
'click .show-less': 'collapse',
|
||||
'click .show-more': 'expand',
|
||||
},
|
||||
|
||||
initialize: function (facetsTypes) {
|
||||
if(facetsTypes) {
|
||||
this.facetsTypes = facetsTypes;
|
||||
}
|
||||
this.tpl = _.template($(this.templateId).html());
|
||||
this.moreLessTpl = _.template($(this.moreLessLinksTpl).html());
|
||||
this.$el.html(this.tpl());
|
||||
this.facetViews = [];
|
||||
this.$facetViewsEl = this.$el.find('.search-facets-lists');
|
||||
},
|
||||
|
||||
render: function () {
|
||||
return this;
|
||||
},
|
||||
|
||||
collapse: function(event) {
|
||||
var $el = $(event.currentTarget),
|
||||
$more = $el.siblings('.show-more'),
|
||||
$ul = $el.parent('div').siblings('ul');
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
$ul.addClass('collapse');
|
||||
$el.addClass('hidden');
|
||||
$more.removeClass('hidden');
|
||||
},
|
||||
|
||||
expand: function(event) {
|
||||
var $el = $(event.currentTarget),
|
||||
$ul = $el.parent('div').siblings('ul'),
|
||||
facets = $ul.find('li').length,
|
||||
itemHeight = 34;
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
$el.addClass('hidden');
|
||||
$ul.removeClass('collapse');
|
||||
$el.siblings('.show-less').removeClass('hidden');
|
||||
},
|
||||
|
||||
addFacet: function(event) {
|
||||
event.preventDefault();
|
||||
var $target = $(event.currentTarget);
|
||||
var value = $target.find('.facet-option').data('value');
|
||||
var name = $target.find('.facet-option').data('text');
|
||||
var data = {type: $target.data('facet'), query: value, name: name};
|
||||
this.trigger('addFilter', data);
|
||||
},
|
||||
|
||||
displayName: function(name, term){
|
||||
if(this.facetsTypes.hasOwnProperty(name)) {
|
||||
if(term) {
|
||||
if (typeof this.facetsTypes[name].terms !== 'undefined') {
|
||||
return this.facetsTypes[name].terms.hasOwnProperty(term) ? this.facetsTypes[name].terms[term] : term;
|
||||
}
|
||||
else {
|
||||
return term;
|
||||
}
|
||||
}
|
||||
else if(this.facetsTypes[name].hasOwnProperty('name')) {
|
||||
return this.facetsTypes[name]['name'];
|
||||
}
|
||||
else {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
else{
|
||||
return term ? term : name;
|
||||
}
|
||||
},
|
||||
|
||||
renderFacets: function(facets) {
|
||||
var self = this;
|
||||
// Remove old facets
|
||||
$.each(this.facetViews, function(key, facetsList) {
|
||||
facetsList.remove();
|
||||
});
|
||||
self.facetViews = [];
|
||||
// Render new facets
|
||||
$.each(facets, function(name, stats) {
|
||||
var facetsView = new FacetsView();
|
||||
self.facetViews.push(facetsView);
|
||||
self.$facetViewsEl.append(facetsView.render(name, self.displayName(name), stats).el);
|
||||
$.each(stats.terms, function(term, count) {
|
||||
var facetView = new FacetView();
|
||||
facetsView.$views.append(facetView.render(name, self.displayName(name, term), term, count).el);
|
||||
facetsView.list.push(facetView);
|
||||
});
|
||||
if(_.size(stats.terms) > 9) {
|
||||
facetsView.$el.append(self.moreLessTpl());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
})(define || RequireJS.define);
|
||||
@@ -28,7 +28,7 @@ define([
|
||||
return Backbone.View.extend({
|
||||
|
||||
tagName: 'li',
|
||||
templateId: '#result_item-tpl',
|
||||
templateId: '#course_card-tpl',
|
||||
className: 'courses-listing-item',
|
||||
|
||||
initialize: function () {
|
||||
@@ -5,8 +5,8 @@ define([
|
||||
'underscore',
|
||||
'backbone',
|
||||
'gettext',
|
||||
'js/discovery/result_item_view'
|
||||
], function ($, _, Backbone, gettext, ResultItemView) {
|
||||
'js/discovery/views/course_card'
|
||||
], function ($, _, Backbone, gettext, CourseCardView) {
|
||||
'use strict';
|
||||
|
||||
return Backbone.View.extend({
|
||||
@@ -32,48 +32,29 @@ define([
|
||||
},
|
||||
|
||||
renderItems: function () {
|
||||
var latest = this.collection.latestModels();
|
||||
var latest = this.model.latest();
|
||||
var items = latest.map(function (result) {
|
||||
var item = new ResultItemView({ model: result });
|
||||
var item = new CourseCardView({ model: result });
|
||||
return item.render().el;
|
||||
}, this);
|
||||
this.$list.append(items);
|
||||
},
|
||||
|
||||
attachScrollHandler: function () {
|
||||
this.nextScrollEvent = true;
|
||||
this.$window.on('scroll', this.scrollHandler.bind(this));
|
||||
this.$window.on('scroll', _.throttle(this.scrollHandler.bind(this), 400));
|
||||
},
|
||||
|
||||
scrollHandler: function () {
|
||||
if (this.nextScrollEvent) {
|
||||
setTimeout(this.throttledScrollHandler.bind(this), 400);
|
||||
this.nextScrollEvent = false;
|
||||
if (this.isNearBottom() && !this.isLoading) {
|
||||
this.trigger('next');
|
||||
this.isLoading = true;
|
||||
}
|
||||
},
|
||||
|
||||
throttledScrollHandler: function () {
|
||||
if (this.isNearBottom()) {
|
||||
this.scrolledToBottom();
|
||||
}
|
||||
this.nextScrollEvent = true;
|
||||
},
|
||||
|
||||
isNearBottom: function () {
|
||||
var scrollBottom = this.$window.scrollTop() + this.$window.height();
|
||||
var threshold = this.$document.height() - 200;
|
||||
return scrollBottom >= threshold;
|
||||
},
|
||||
|
||||
scrolledToBottom: function () {
|
||||
if (this.thereIsMore() && !this.isLoading) {
|
||||
this.trigger('next');
|
||||
this.isLoading = true;
|
||||
}
|
||||
},
|
||||
|
||||
thereIsMore: function () {
|
||||
return this.collection.hasNextPage();
|
||||
}
|
||||
|
||||
});
|
||||
78
lms/static/js/discovery/views/filter_bar.js
Normal file
78
lms/static/js/discovery/views/filter_bar.js
Normal file
@@ -0,0 +1,78 @@
|
||||
;(function (define) {
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'underscore',
|
||||
'backbone',
|
||||
'gettext',
|
||||
'js/discovery/models/filter',
|
||||
'js/discovery/views/filter_label'
|
||||
], function ($, _, Backbone, gettext, Filter, FilterLabel) {
|
||||
'use strict';
|
||||
|
||||
return Backbone.View.extend({
|
||||
|
||||
el: '#filter-bar',
|
||||
templateId: '#filter_bar-tpl',
|
||||
|
||||
events: {
|
||||
'click #clear-all-filters': 'clearAll',
|
||||
'click li .discovery-button': 'clearFilter'
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
this.tpl = _.template($(this.templateId).html());
|
||||
this.render();
|
||||
this.listenTo(this.collection, 'remove', this.hideIfEmpty);
|
||||
this.listenTo(this.collection, 'add', this.addFilter);
|
||||
this.listenTo(this.collection, 'reset', this.resetFilters);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.$el.html(this.tpl());
|
||||
this.$ul = this.$el.find('ul');
|
||||
this.$el.addClass('is-animated');
|
||||
return this;
|
||||
},
|
||||
|
||||
addFilter: function (filter) {
|
||||
var label = new FilterLabel({model: filter});
|
||||
this.$ul.append(label.render().el);
|
||||
this.show();
|
||||
},
|
||||
|
||||
hideIfEmpty: function () {
|
||||
if (this.collection.isEmpty()) {
|
||||
this.hide();
|
||||
}
|
||||
},
|
||||
|
||||
resetFilters: function () {
|
||||
this.$ul.empty();
|
||||
this.hide();
|
||||
},
|
||||
|
||||
clearFilter: function (event) {
|
||||
var $target = $(event.currentTarget);
|
||||
var filter = this.collection.get($target.data('type'));
|
||||
this.trigger('clearFilter', filter.id);
|
||||
},
|
||||
|
||||
clearAll: function (event) {
|
||||
this.trigger('clearAll');
|
||||
},
|
||||
|
||||
show: function () {
|
||||
this.$el.removeClass('is-collapsed');
|
||||
},
|
||||
|
||||
hide: function () {
|
||||
this.$ul.empty();
|
||||
this.$el.addClass('is-collapsed');
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
})(define || RequireJS.define);
|
||||
@@ -4,7 +4,7 @@ define([
|
||||
'jquery',
|
||||
'underscore',
|
||||
'backbone',
|
||||
'gettext',
|
||||
'gettext'
|
||||
], function ($, _, Backbone, gettext) {
|
||||
'use strict';
|
||||
|
||||
@@ -15,21 +15,17 @@ define([
|
||||
className: 'active-filter',
|
||||
|
||||
initialize: function () {
|
||||
this.tpl = _.template($(this.templateId).html());
|
||||
this.listenTo(this.model, 'destroy', this.remove);
|
||||
this.tpl = _.template($('#filter-tpl').html());
|
||||
this.listenTo(this.model, 'remove', this.remove);
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.className = this.model.get('type');
|
||||
var data = this.model.attributes;
|
||||
var data = _.clone(this.model.attributes);
|
||||
data.name = data.name || data.query;
|
||||
this.className = data.type;
|
||||
this.$el.html(this.tpl(data));
|
||||
return this;
|
||||
},
|
||||
|
||||
remove: function() {
|
||||
this.stopListening();
|
||||
this.$el.remove();
|
||||
}
|
||||
|
||||
});
|
||||
99
lms/static/js/discovery/views/refine_sidebar.js
Normal file
99
lms/static/js/discovery/views/refine_sidebar.js
Normal file
@@ -0,0 +1,99 @@
|
||||
;(function (define) {
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'underscore',
|
||||
'backbone',
|
||||
'gettext'
|
||||
], function ($, _, Backbone, gettext) {
|
||||
'use strict';
|
||||
|
||||
return Backbone.View.extend({
|
||||
|
||||
el: '.search-facets',
|
||||
events: {
|
||||
'click li button': 'selectOption',
|
||||
'click .show-less': 'collapse',
|
||||
'click .show-more': 'expand'
|
||||
},
|
||||
|
||||
initialize: function (options) {
|
||||
this.meanings = options.meanings || {}
|
||||
this.$container = this.$el.find('.search-facets-lists');
|
||||
this.facetTpl = _.template($('#facet-tpl').html());
|
||||
this.facetOptionTpl = _.template($('#facet_option-tpl').html());
|
||||
},
|
||||
|
||||
facetName: function (key) {
|
||||
return this.meanings[key] && this.meanings[key].name || key;
|
||||
},
|
||||
|
||||
termName: function (facetKey, termKey) {
|
||||
return this.meanings[facetKey] &&
|
||||
this.meanings[facetKey].terms &&
|
||||
this.meanings[facetKey].terms[termKey] || termKey;
|
||||
},
|
||||
|
||||
renderOptions: function (options) {
|
||||
var html = _.map(options, function(option) {
|
||||
var data = _.clone(option.attributes);
|
||||
data.name = this.termName(data.facet, data.term);
|
||||
return this.facetOptionTpl(data);
|
||||
}, this).join('');
|
||||
return html;
|
||||
},
|
||||
|
||||
renderFacet: function (facetKey, options) {
|
||||
return this.facetTpl({
|
||||
name: facetKey,
|
||||
displayName: this.facetName(facetKey),
|
||||
options: this.renderOptions(options),
|
||||
listIsHuge: (options.length > 9)
|
||||
});
|
||||
},
|
||||
|
||||
render: function () {
|
||||
var grouped = this.collection.groupBy('facet');
|
||||
var html = _.map(grouped, function(options, facetKey) {
|
||||
if (options.length > 0) {
|
||||
return this.renderFacet(facetKey, options);
|
||||
}
|
||||
}, this).join('');
|
||||
this.$container.html(html);
|
||||
return this;
|
||||
},
|
||||
|
||||
collapse: function (event) {
|
||||
var $el = $(event.currentTarget),
|
||||
$more = $el.siblings('.show-more'),
|
||||
$ul = $el.parent().siblings('ul');
|
||||
|
||||
$ul.addClass('collapse');
|
||||
$el.addClass('hidden');
|
||||
$more.removeClass('hidden');
|
||||
},
|
||||
|
||||
expand: function (event) {
|
||||
var $el = $(event.currentTarget),
|
||||
$ul = $el.parent('div').siblings('ul');
|
||||
|
||||
$el.addClass('hidden');
|
||||
$ul.removeClass('collapse');
|
||||
$el.siblings('.show-less').removeClass('hidden');
|
||||
},
|
||||
|
||||
selectOption: function (event) {
|
||||
var $target = $(event.currentTarget);
|
||||
this.trigger(
|
||||
'selectOption',
|
||||
$target.data('facet'),
|
||||
$target.data('value'),
|
||||
$target.data('text')
|
||||
);
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
})(define || RequireJS.define);
|
||||
@@ -1,13 +1,13 @@
|
||||
;(function (define) {
|
||||
|
||||
define(['jquery', 'backbone'], function ($, Backbone) {
|
||||
define(['jquery', 'backbone', 'gettext'], function ($, Backbone, gettext) {
|
||||
'use strict';
|
||||
|
||||
return Backbone.View.extend({
|
||||
|
||||
el: '#discovery-form',
|
||||
events: {
|
||||
'submit form': 'submitForm',
|
||||
'submit form': 'submitForm'
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
@@ -23,23 +23,20 @@ define(['jquery', 'backbone'], function ($, Backbone) {
|
||||
},
|
||||
|
||||
doSearch: function (term) {
|
||||
if (term) {
|
||||
if (term !== undefined) {
|
||||
this.$searchField.val(term);
|
||||
}
|
||||
else {
|
||||
term = this.$searchField.val();
|
||||
}
|
||||
this.trigger('search', $.trim(term));
|
||||
this.$message.empty();
|
||||
},
|
||||
|
||||
clearSearch: function () {
|
||||
this.$message.empty();
|
||||
this.$searchField.val('');
|
||||
},
|
||||
|
||||
showLoadingIndicator: function () {
|
||||
this.$message.empty();
|
||||
this.$loadingIndicator.removeClass('hidden');
|
||||
},
|
||||
|
||||
@@ -62,6 +59,7 @@ define(['jquery', 'backbone'], function ($, Backbone) {
|
||||
[_.escape(term)]
|
||||
);
|
||||
this.$message.html(msg);
|
||||
this.clearSearch();
|
||||
},
|
||||
|
||||
showErrorMessage: function () {
|
||||
@@ -2,26 +2,28 @@
|
||||
<div id="discovery-form" class="wrapper-search-context">
|
||||
<div id="discovery-message" class="search-status-label"></div>
|
||||
<form class="wrapper-search-input">
|
||||
<input class="discovery-input" placeholder="Search for a course" type="text"/><!-- removes spacing
|
||||
--><button type="submit" class="button postfix discovery-submit" aria-label="Search">
|
||||
<input class="discovery-input" placeholder="Search for a course" type="text"/>
|
||||
<button type="submit" class="button postfix discovery-submit" aria-label="Search">
|
||||
<i class="icon fa fa-search" aria-hidden="true"></i>
|
||||
<div aria-live="polite" aria-relevant="all">
|
||||
<div id="loading-indicator" class="loading-spinner hidden">
|
||||
<i class="icon fa fa-spinner fa-spin" aria-hidden="true"></i>
|
||||
<i class="icon fa fa-spinner fa-spin"></i>
|
||||
<span class="sr">Loading</span>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
|
||||
<div id="filter-bar" class="filters hide-phone">
|
||||
</div>
|
||||
|
||||
<div class="courses" role="region" aria-label="${_('List of Courses')}">
|
||||
<div class="courses" role="region" aria-label="List of Courses">
|
||||
<ul class="courses-listing"></ul>
|
||||
</div>
|
||||
|
||||
<aside aria-label="Refine your search" class="search-facets phone-menu">
|
||||
<h2 class="header-search-facets">Refine Your Search</h2>
|
||||
<section class="search-facets-lists"></section>
|
||||
</aside>
|
||||
|
||||
</section>
|
||||
|
||||
22
lms/static/js/spec/discovery/collections/filters_spec.js
Normal file
22
lms/static/js/spec/discovery/collections/filters_spec.js
Normal file
@@ -0,0 +1,22 @@
|
||||
define(['js/discovery/collections/filters'], function(Filters) {
|
||||
'use strict';
|
||||
|
||||
describe('discovery.collections.Filters', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
this.filters = new Filters([
|
||||
{ type: 'org', query: 'edX', name: 'edX'},
|
||||
{ type: 'language', query: 'en', name: 'English'}
|
||||
]);
|
||||
});
|
||||
|
||||
it('converts to a dictionary', function () {
|
||||
expect(this.filters.getTerms()).toEqual({
|
||||
org: 'edX',
|
||||
language: 'en'
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
195
lms/static/js/spec/discovery/discovery_factory_spec.js
Normal file
195
lms/static/js/spec/discovery/discovery_factory_spec.js
Normal file
@@ -0,0 +1,195 @@
|
||||
define([
|
||||
'jquery', 'common/js/spec_helpers/ajax_helpers','common/js/spec_helpers/template_helpers',
|
||||
'js/discovery/discovery_factory'
|
||||
], function($, AjaxHelpers, TemplateHelpers, DiscoveryFactory) {
|
||||
'use strict';
|
||||
|
||||
|
||||
var MEANINGS = {
|
||||
org: {
|
||||
name: 'Organization',
|
||||
terms: {
|
||||
edX1: "edX_1"
|
||||
}
|
||||
},
|
||||
modes: {
|
||||
name: 'Course Type',
|
||||
terms: {
|
||||
honor: 'Honor',
|
||||
verified: 'Verified'
|
||||
}
|
||||
},
|
||||
language: {
|
||||
terms: {
|
||||
en: 'English',
|
||||
hr: 'Croatian'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var JSON_RESPONSE = {
|
||||
"total": 365,
|
||||
"results": [
|
||||
{
|
||||
"data": {
|
||||
"modes": [
|
||||
"honor"
|
||||
],
|
||||
"course": "edX/DemoX/Demo_Course",
|
||||
"enrollment_start": "2015-04-21T00:00:00+00:00",
|
||||
"number": "DemoX",
|
||||
"content": {
|
||||
"overview": " About This Course Include your long course description here.",
|
||||
"display_name": "edX Demonstration Course",
|
||||
"number": "DemoX"
|
||||
},
|
||||
"start": "1970-01-01T05:00:00+00:00",
|
||||
"image_url": "/c4x/edX/DemoX/asset/images_course_image.jpg",
|
||||
"org": "edX",
|
||||
"id": "edX/DemoX/Demo_Course"
|
||||
}
|
||||
}
|
||||
],
|
||||
"facets": {
|
||||
"org": {
|
||||
"total": 26,
|
||||
"terms": {
|
||||
"edX1": 1,
|
||||
"edX2": 1,
|
||||
"edX3": 1,
|
||||
"edX4": 1,
|
||||
"edX5": 1,
|
||||
"edX6": 1,
|
||||
"edX7": 1,
|
||||
"edX8": 1,
|
||||
"edX9": 1,
|
||||
"edX10": 1,
|
||||
"edX11": 1,
|
||||
"edX12": 1,
|
||||
"edX13": 1,
|
||||
"edX14": 1,
|
||||
"edX15": 1,
|
||||
"edX16": 1,
|
||||
"edX17": 1,
|
||||
"edX18": 1,
|
||||
"edX19": 1,
|
||||
"edX20": 1,
|
||||
"edX21": 1,
|
||||
"edX22": 1,
|
||||
"edX23": 1,
|
||||
"edX24": 1,
|
||||
"edX25": 1,
|
||||
"edX26": 1
|
||||
},
|
||||
"other": 0
|
||||
},
|
||||
"modes": {
|
||||
"total": 1,
|
||||
"terms": {
|
||||
"honor": 1
|
||||
},
|
||||
"other": 0
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
describe('discovery.DiscoveryFactory', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
loadFixtures('js/fixtures/discovery.html');
|
||||
TemplateHelpers.installTemplates([
|
||||
'templates/discovery/course_card',
|
||||
'templates/discovery/facet',
|
||||
'templates/discovery/facet_option',
|
||||
'templates/discovery/filter',
|
||||
'templates/discovery/filter_bar'
|
||||
]);
|
||||
DiscoveryFactory(MEANINGS);
|
||||
});
|
||||
|
||||
it('does search', function () {
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
$('.discovery-input').val('test');
|
||||
$('.discovery-submit').trigger('click');
|
||||
AjaxHelpers.respondWithJson(requests, JSON_RESPONSE);
|
||||
expect($('.courses-listing article').length).toEqual(1);
|
||||
expect($('.courses-listing .course-title')).toContainHtml('edX Demonstration Course');
|
||||
expect($('.active-filter').length).toBe(1);
|
||||
});
|
||||
|
||||
it('loads more', function () {
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
jasmine.Clock.useMock();
|
||||
$('.discovery-input').val('test');
|
||||
$('.discovery-submit').trigger('click');
|
||||
AjaxHelpers.respondWithJson(requests, JSON_RESPONSE);
|
||||
expect($('.courses-listing article').length).toEqual(1);
|
||||
expect($('.courses-listing .course-title')).toContainHtml('edX Demonstration Course');
|
||||
window.scroll(0, $(document).height());
|
||||
$(window).trigger('scroll');
|
||||
jasmine.Clock.tick(500);
|
||||
AjaxHelpers.respondWithJson(requests, JSON_RESPONSE);
|
||||
expect($('.courses-listing article').length).toEqual(2);
|
||||
});
|
||||
|
||||
it('displays not found message', function () {
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
$('.discovery-input').val('asdfasdf');
|
||||
$('.discovery-submit').trigger('click');
|
||||
AjaxHelpers.respondWithJson(requests, {});
|
||||
expect($('.discovery-input').val()).toEqual('');
|
||||
expect($('#discovery-message')).not.toBeEmpty();
|
||||
expect($('.courses-listing')).toBeEmpty();
|
||||
});
|
||||
|
||||
it('displays error message', function () {
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
$('.discovery-input').val('asdfasdf');
|
||||
$('.discovery-submit').trigger('click');
|
||||
AjaxHelpers.respondWithError(requests, 404);
|
||||
expect($('#discovery-message')).not.toBeEmpty();
|
||||
expect($('.courses-listing')).toBeEmpty();
|
||||
});
|
||||
|
||||
it('check filters and bar removed on clear all', function () {
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
$('.discovery-input').val('test');
|
||||
$('.discovery-submit').trigger('click');
|
||||
AjaxHelpers.respondWithJson(requests, JSON_RESPONSE);
|
||||
expect($('.active-filter').length).toBe(1);
|
||||
expect($('#filter-bar')).not.toHaveClass('is-collapsed');
|
||||
$('#clear-all-filters').trigger('click');
|
||||
expect($('.active-filter').length).toBe(0);
|
||||
expect($('#filter-bar')).toHaveClass('is-collapsed');
|
||||
});
|
||||
|
||||
it('check filters and bar removed on last filter cleared', function () {
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
$('.discovery-input').val('test');
|
||||
$('.discovery-submit').trigger('click');
|
||||
AjaxHelpers.respondWithJson(requests, JSON_RESPONSE);
|
||||
expect($('.active-filter').length).toBe(1);
|
||||
var $filter = $('.active-filter');
|
||||
$filter.find('.discovery-button').trigger('click');
|
||||
expect($('.active-filter').length).toBe(0);
|
||||
});
|
||||
|
||||
it('filter results by named facet', function () {
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
$('.discovery-input').val('test');
|
||||
$('.discovery-submit').trigger('click');
|
||||
AjaxHelpers.respondWithJson(requests, JSON_RESPONSE);
|
||||
expect($('.active-filter').length).toBe(1);
|
||||
$('.search-facets li [data-value="edX1"]').trigger('click');
|
||||
expect($('.active-filter').length).toBe(2);
|
||||
expect($('.active-filter [data-value="edX1"]').length).toBe(1);
|
||||
$('.search-facets li [data-value="edX1"]').trigger('click');
|
||||
expect($('.active-filter [data-value="edX1"]').length).toBe(0);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
@@ -1,603 +0,0 @@
|
||||
define([
|
||||
'jquery',
|
||||
'backbone',
|
||||
'logger',
|
||||
'common/js/spec_helpers/ajax_helpers',
|
||||
'common/js/spec_helpers/template_helpers',
|
||||
'js/discovery/discovery_factory',
|
||||
'js/discovery/collection',
|
||||
'js/discovery/form',
|
||||
'js/discovery/result',
|
||||
'js/discovery/result_item_view',
|
||||
'js/discovery/result_list_view',
|
||||
'js/discovery/filter',
|
||||
'js/discovery/filters',
|
||||
'js/discovery/filter_bar_view',
|
||||
'js/discovery/filter_view',
|
||||
'js/discovery/search_facets_view'
|
||||
], function(
|
||||
$,
|
||||
Backbone,
|
||||
Logger,
|
||||
AjaxHelpers,
|
||||
TemplateHelpers,
|
||||
DiscoveryFactory,
|
||||
Collection,
|
||||
DiscoveryForm,
|
||||
ResultItem,
|
||||
ResultItemView,
|
||||
ResultListView,
|
||||
FilterModel,
|
||||
FiltersCollection,
|
||||
FiltersBarView,
|
||||
FilterView,
|
||||
SearchFacetView
|
||||
) {
|
||||
'use strict';
|
||||
|
||||
var JSON_RESPONSE = {
|
||||
"total": 365,
|
||||
"results": [
|
||||
{
|
||||
"data": {
|
||||
"modes": [
|
||||
"honor"
|
||||
],
|
||||
"course": "edX/DemoX/Demo_Course",
|
||||
"enrollment_start": "2015-04-21T00:00:00+00:00",
|
||||
"number": "DemoX",
|
||||
"content": {
|
||||
"overview": " About This Course Include your long course description here.",
|
||||
"display_name": "edX Demonstration Course",
|
||||
"number": "DemoX"
|
||||
},
|
||||
"start": "1970-01-01T05:00:00+00:00",
|
||||
"image_url": "/c4x/edX/DemoX/asset/images_course_image.jpg",
|
||||
"org": "edX",
|
||||
"id": "edX/DemoX/Demo_Course"
|
||||
}
|
||||
}
|
||||
],
|
||||
"facets": {
|
||||
"org": {
|
||||
"total": 26,
|
||||
"terms": {
|
||||
"edX1": 1,
|
||||
"edX2": 1,
|
||||
"edX3": 1,
|
||||
"edX4": 1,
|
||||
"edX5": 1,
|
||||
"edX6": 1,
|
||||
"edX7": 1,
|
||||
"edX8": 1,
|
||||
"edX9": 1,
|
||||
"edX10": 1,
|
||||
"edX11": 1,
|
||||
"edX12": 1,
|
||||
"edX13": 1,
|
||||
"edX14": 1,
|
||||
"edX15": 1,
|
||||
"edX16": 1,
|
||||
"edX17": 1,
|
||||
"edX18": 1,
|
||||
"edX19": 1,
|
||||
"edX20": 1,
|
||||
"edX21": 1,
|
||||
"edX22": 1,
|
||||
"edX23": 1,
|
||||
"edX24": 1,
|
||||
"edX25": 1,
|
||||
"edX26": 1
|
||||
},
|
||||
"other": 0
|
||||
},
|
||||
"modes": {
|
||||
"total": 1,
|
||||
"terms": {
|
||||
"honor": 1
|
||||
},
|
||||
"other": 0
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var FACET_LIST = [
|
||||
{"type": "example1", "query": "search1"},
|
||||
{"type": "example2", "query": "search2"}
|
||||
];
|
||||
|
||||
var SEARCH_FILTER = {"type": "search_string", "query": "search3"};
|
||||
|
||||
|
||||
describe('Course Discovery', function () {
|
||||
|
||||
describe('Collection', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
this.collection = new Collection();
|
||||
|
||||
this.onSearch = jasmine.createSpy('onSearch');
|
||||
this.collection.on('search', this.onSearch);
|
||||
|
||||
this.onNext = jasmine.createSpy('onNext');
|
||||
this.collection.on('next', this.onNext);
|
||||
|
||||
this.onError = jasmine.createSpy('onError');
|
||||
this.collection.on('error', this.onError);
|
||||
});
|
||||
|
||||
it('sends a request and parses the json result', function () {
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
this.collection.performSearch('search string');
|
||||
AjaxHelpers.respondWithJson(requests, JSON_RESPONSE);
|
||||
expect(this.onSearch).toHaveBeenCalled();
|
||||
expect(this.collection.totalCount).toEqual(365);
|
||||
expect(this.collection.latestModels()[0].attributes).toEqual(JSON_RESPONSE.results[0].data);
|
||||
expect(this.collection.page).toEqual(0);
|
||||
});
|
||||
|
||||
it('handles errors', function () {
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
this.collection.performSearch('search string');
|
||||
AjaxHelpers.respondWithError(requests);
|
||||
expect(this.onSearch).not.toHaveBeenCalled();
|
||||
expect(this.onError).toHaveBeenCalled();
|
||||
this.collection.loadNextPage();
|
||||
AjaxHelpers.respondWithError(requests);
|
||||
expect(this.onSearch).not.toHaveBeenCalled();
|
||||
expect(this.onError).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('loads next page', function () {
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
var response = { total: 35, results: [] };
|
||||
this.collection.loadNextPage();
|
||||
AjaxHelpers.respondWithJson(requests, response);
|
||||
expect(this.onNext).toHaveBeenCalled();
|
||||
expect(this.onError).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('sends correct paging parameters', function () {
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
var response = { total: 52, results: [] };
|
||||
this.collection.performSearch('search string');
|
||||
AjaxHelpers.respondWithJson(requests, response);
|
||||
this.collection.loadNextPage();
|
||||
AjaxHelpers.respondWithJson(requests, response);
|
||||
spyOn($, 'ajax');
|
||||
this.collection.loadNextPage();
|
||||
expect($.ajax.mostRecentCall.args[0].url).toEqual(this.collection.url);
|
||||
expect($.ajax.mostRecentCall.args[0].data).toEqual({
|
||||
search_string : 'search string',
|
||||
page_size : this.collection.pageSize,
|
||||
page_index : 2
|
||||
});
|
||||
});
|
||||
|
||||
it('has next page', function () {
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
var response = { total: 35, access_denied_count: 5, results: [] };
|
||||
this.collection.performSearch('search string');
|
||||
AjaxHelpers.respondWithJson(requests, response);
|
||||
expect(this.collection.hasNextPage()).toEqual(true);
|
||||
this.collection.loadNextPage();
|
||||
AjaxHelpers.respondWithJson(requests, response);
|
||||
expect(this.collection.hasNextPage()).toEqual(false);
|
||||
});
|
||||
|
||||
it('resets state when performing new search', function () {
|
||||
this.collection.add(new ResultItem());
|
||||
expect(this.collection.length).toEqual(1);
|
||||
this.collection.performSearch('search string');
|
||||
expect(this.collection.length).toEqual(0);
|
||||
expect(this.collection.page).toEqual(0);
|
||||
expect(this.collection.totalCount).toEqual(0);
|
||||
expect(this.collection.latestModelsCount).toEqual(0);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
describe('ResultItem', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
this.result = new ResultItem();
|
||||
});
|
||||
|
||||
it('has properties', function () {
|
||||
expect(this.result.get('modes')).toBeDefined();
|
||||
expect(this.result.get('course')).toBeDefined();
|
||||
expect(this.result.get('enrollment_start')).toBeDefined();
|
||||
expect(this.result.get('number')).toBeDefined();
|
||||
expect(this.result.get('content')).toEqual({
|
||||
display_name: '',
|
||||
number: '',
|
||||
overview: ''
|
||||
});
|
||||
expect(this.result.get('start')).toBeDefined();
|
||||
expect(this.result.get('image_url')).toBeDefined();
|
||||
expect(this.result.get('org')).toBeDefined();
|
||||
expect(this.result.get('id')).toBeDefined();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
describe('ResultItemView', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
TemplateHelpers.installTemplate('templates/discovery/result_item');
|
||||
this.item = new ResultItemView({
|
||||
model: new ResultItem(JSON_RESPONSE.results[0].data)
|
||||
});
|
||||
});
|
||||
|
||||
it('renders correctly', function () {
|
||||
var data = this.item.model.attributes;
|
||||
this.item.render();
|
||||
expect(this.item.$el).toContainHtml(data.content.display_name);
|
||||
expect(this.item.$el).toContain('a[href="/courses/' + data.course + '/about"]');
|
||||
expect(this.item.$el).toContain('img[src="' + data.image_url + '"]');
|
||||
expect(this.item.$el.find('.course-name')).toContainHtml(data.org);
|
||||
expect(this.item.$el.find('.course-name')).toContainHtml(data.content.number);
|
||||
expect(this.item.$el.find('.course-name')).toContainHtml(data.content.display_name);
|
||||
expect(this.item.$el.find('.course-date')).toContainHtml('Jan 01, 1970');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
describe('DiscoveryForm', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
loadFixtures('js/fixtures/discovery.html');
|
||||
this.form = new DiscoveryForm();
|
||||
this.onSearch = jasmine.createSpy('onSearch');
|
||||
this.form.on('search', this.onSearch);
|
||||
});
|
||||
|
||||
it('trims input string', function () {
|
||||
var term = ' search string ';
|
||||
$('.discovery-input').val(term);
|
||||
$('form').trigger('submit');
|
||||
expect(this.onSearch).toHaveBeenCalledWith($.trim(term));
|
||||
});
|
||||
|
||||
it('handles calls to doSearch', function () {
|
||||
var term = ' search string ';
|
||||
$('.discovery-input').val(term);
|
||||
this.form.doSearch(term);
|
||||
expect(this.onSearch).toHaveBeenCalledWith($.trim(term));
|
||||
expect($('.discovery-input').val()).toEqual(term);
|
||||
expect($('#discovery-message')).toBeEmpty();
|
||||
});
|
||||
|
||||
it('clears search', function () {
|
||||
$('.discovery-input').val('somethig');
|
||||
this.form.clearSearch();
|
||||
expect($('.discovery-input').val()).toEqual('');
|
||||
});
|
||||
|
||||
it('shows/hides loading indicator', function () {
|
||||
this.form.showLoadingIndicator();
|
||||
expect($('#loading-indicator')).not.toHaveClass('hidden');
|
||||
this.form.hideLoadingIndicator();
|
||||
expect($('#loading-indicator')).toHaveClass('hidden');
|
||||
});
|
||||
|
||||
it('shows messages', function () {
|
||||
this.form.showNotFoundMessage();
|
||||
expect($('#discovery-message')).not.toBeEmpty();
|
||||
this.form.showErrorMessage();
|
||||
expect($('#discovery-message')).not.toBeEmpty();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('FilterBarView', function () {
|
||||
beforeEach(function () {
|
||||
loadFixtures('js/fixtures/discovery.html');
|
||||
TemplateHelpers.installTemplates(
|
||||
['templates/discovery/filter_bar',
|
||||
'templates/discovery/filter']
|
||||
);
|
||||
this.filterBar = new FiltersBarView();
|
||||
this.onClear = jasmine.createSpy('onClear');
|
||||
this.filterBar.on('clear', this.onClear);
|
||||
});
|
||||
|
||||
it('view searches for sent facet object', function () {
|
||||
expect(this.filterBar.$el.length).toBe(1);
|
||||
this.filterBar.addFilter(FACET_LIST[0]);
|
||||
expect(this.filterBar.$el.find('#clear-all-filters')).toBeVisible();
|
||||
});
|
||||
|
||||
it('view searches for entered search string', function () {
|
||||
spyOn(this.filterBar, 'addFilter').andCallThrough();
|
||||
expect(this.filterBar.$el.length).toBe(1);
|
||||
this.filterBar.changeQueryFilter(SEARCH_FILTER.query);
|
||||
expect(this.filterBar.$el.find('#clear-all-filters')).toBeVisible();
|
||||
expect(this.filterBar.addFilter).toHaveBeenCalledWith(SEARCH_FILTER);
|
||||
});
|
||||
|
||||
it('model cleans view on destruction correctly', function () {
|
||||
this.filterBar.addFilter(SEARCH_FILTER);
|
||||
var model = this.filterBar.collection.findWhere(SEARCH_FILTER);
|
||||
expect(this.filterBar.$el.find('.active-filter').length).toBe(1);
|
||||
model.cleanModelView();
|
||||
expect(this.filterBar.$el.find('.active-filter').length).toBe(0);
|
||||
});
|
||||
|
||||
it('view removes all filters and hides bar if clear all', function () {
|
||||
spyOn(this.filterBar, 'clearAll').andCallThrough();
|
||||
this.filterBar.delegateEvents();
|
||||
this.filterBar.addFilter(SEARCH_FILTER);
|
||||
var clearAll = this.filterBar.$el.find('#clear-all-filters');
|
||||
expect(clearAll).toBeVisible();
|
||||
clearAll.trigger('click');
|
||||
expect(this.filterBar.clearAll).toHaveBeenCalled();
|
||||
expect(this.onClear).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('view hides bar if all filters removed', function () {
|
||||
spyOn(this.filterBar, 'clearFilter').andCallThrough();
|
||||
this.filterBar.delegateEvents();
|
||||
this.filterBar.addFilter(SEARCH_FILTER);
|
||||
var clearAll = this.filterBar.$el.find('#clear-all-filters');
|
||||
expect(clearAll).toBeVisible();
|
||||
var filter = this.filterBar.$el.find('li .discovery-button');
|
||||
filter.trigger('click');
|
||||
expect(this.filterBar.clearFilter).toHaveBeenCalled();
|
||||
expect(this.onClear).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('view changes query filter', function () {
|
||||
this.filterBar.addFilter(SEARCH_FILTER);
|
||||
var filter = $(this.filterBar.$el.find('li .discovery-button')[0]);
|
||||
expect(filter.text().trim()).toBe(SEARCH_FILTER.query);
|
||||
// Have to explicitly remove model because events not dispatched
|
||||
var model = this.filterBar.collection.findWhere(SEARCH_FILTER);
|
||||
model.cleanModelView();
|
||||
this.filterBar.changeQueryFilter(SEARCH_FILTER.query + '2');
|
||||
filter = $(this.filterBar.$el.find('li .discovery-button')[0]);
|
||||
expect(filter.text().trim()).toBe(SEARCH_FILTER.query + '2');
|
||||
});
|
||||
|
||||
it('view returns correct search term', function () {
|
||||
this.filterBar.addFilter(SEARCH_FILTER);
|
||||
expect(this.filterBar.getSearchTerm()).toBe(SEARCH_FILTER.query);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('SearchFacetView', function () {
|
||||
beforeEach(function () {
|
||||
loadFixtures('js/fixtures/discovery.html');
|
||||
TemplateHelpers.installTemplates([
|
||||
'templates/discovery/search_facet',
|
||||
'templates/discovery/search_facets_section',
|
||||
'templates/discovery/search_facets_list',
|
||||
'templates/discovery/more_less_links'
|
||||
]);
|
||||
var facetsTypes = {org: 'Organization', modes: 'Course Type'};
|
||||
this.searchFacetView = new SearchFacetView(facetsTypes);
|
||||
this.searchFacetView.renderFacets(JSON_RESPONSE.facets);
|
||||
this.onAddFilter = jasmine.createSpy('onAddFilter');
|
||||
this.searchFacetView.on('addFilter', this.onAddFilter);
|
||||
});
|
||||
|
||||
it('view expands more content on show more click', function () {
|
||||
var $showMore = this.searchFacetView.$el.find('.show-more');
|
||||
var $showLess = this.searchFacetView.$el.find('.show-less');
|
||||
var $ul = $showMore.parent('div').siblings('ul');
|
||||
expect($showMore).not.toHaveClass('hidden');
|
||||
expect($showLess).toHaveClass('hidden');
|
||||
expect($ul).toHaveClass('collapse');
|
||||
$showMore.trigger('click');
|
||||
expect($showMore).toHaveClass('hidden');
|
||||
expect($showLess).not.toHaveClass('hidden');
|
||||
expect($ul).not.toHaveClass('collapse');
|
||||
});
|
||||
|
||||
it('view collapses content on show less click', function () {
|
||||
var $showMore = this.searchFacetView.$el.find('.show-more');
|
||||
var $showLess = this.searchFacetView.$el.find('.show-less');
|
||||
var $ul = $showMore.parent('div').siblings('ul');
|
||||
$showMore.trigger('click');
|
||||
expect($showMore).toHaveClass('hidden');
|
||||
expect($showLess).not.toHaveClass('hidden');
|
||||
expect($ul).not.toHaveClass('collapse');
|
||||
$showLess.trigger('click');
|
||||
expect($showMore).not.toHaveClass('hidden');
|
||||
expect($showLess).toHaveClass('hidden');
|
||||
expect($ul).toHaveClass('collapse');
|
||||
});
|
||||
|
||||
it('view triggers addFilter event if facet is clicked', function () {
|
||||
this.searchFacetView.delegateEvents();
|
||||
var $facetLink = this.searchFacetView.$el.find('li [data-value="edX1"]');
|
||||
var $facet = $facetLink.parent('li');
|
||||
$facet.trigger('click');
|
||||
expect(this.onAddFilter).toHaveBeenCalledWith(
|
||||
{
|
||||
type: $facet.data('facet'),
|
||||
query: $facetLink.data('value'),
|
||||
name : $facetLink.data('text')
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('re-render facets on second click', function () {
|
||||
// First search
|
||||
this.searchFacetView.delegateEvents();
|
||||
this.searchFacetView.renderFacets(JSON_RESPONSE.facets);
|
||||
expect(this.searchFacetView.facetViews.length).toBe(2);
|
||||
// Setup spy
|
||||
var customView = this.searchFacetView.facetViews[0];
|
||||
spyOn(customView, 'remove').andCallThrough();
|
||||
// Second search
|
||||
this.searchFacetView.renderFacets(JSON_RESPONSE.facets);
|
||||
expect(this.searchFacetView.facetViews.length).toBe(2);
|
||||
expect(customView.remove).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('ResultListView', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
jasmine.Clock.useMock();
|
||||
loadFixtures('js/fixtures/discovery.html');
|
||||
TemplateHelpers.installTemplate('templates/discovery/result_item');
|
||||
var collection = new Collection([JSON_RESPONSE.results[0].data]);
|
||||
collection.latestModelsCount = 1;
|
||||
this.view = new ResultListView({ collection: collection });
|
||||
});
|
||||
|
||||
it('renders search results', function () {
|
||||
this.view.render();
|
||||
expect($('.courses-listing article').length).toEqual(1);
|
||||
expect($('.courses-listing .course-title')).toContainHtml('edX Demonstration Course');
|
||||
this.view.renderNext();
|
||||
expect($('.courses-listing article').length).toEqual(2);
|
||||
});
|
||||
|
||||
it('scrolling triggers an event for next page', function () {
|
||||
this.onNext = jasmine.createSpy('onNext');
|
||||
this.view.on('next', this.onNext);
|
||||
spyOn(this.view.collection, 'hasNextPage').andCallFake(function () {
|
||||
return true;
|
||||
});
|
||||
this.view.render();
|
||||
window.scroll(0, $(document).height());
|
||||
$(window).trigger('scroll');
|
||||
jasmine.Clock.tick(500);
|
||||
expect(this.onNext).toHaveBeenCalled();
|
||||
|
||||
// should not be triggered again (while it is loading)
|
||||
$(window).trigger('scroll');
|
||||
jasmine.Clock.tick(500);
|
||||
expect(this.onNext.calls.length).toEqual(1);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Discovery Factory', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
loadFixtures('js/fixtures/discovery.html');
|
||||
TemplateHelpers.installTemplates([
|
||||
'templates/discovery/result_item',
|
||||
'templates/discovery/filter',
|
||||
'templates/discovery/filter_bar',
|
||||
'templates/discovery/search_facet',
|
||||
'templates/discovery/search_facets_section',
|
||||
'templates/discovery/search_facets_list',
|
||||
'templates/discovery/more_less_links'
|
||||
]);
|
||||
|
||||
DiscoveryFactory(
|
||||
{
|
||||
org: {
|
||||
name: 'Organization',
|
||||
terms: {
|
||||
edX1: "edX_1"
|
||||
}
|
||||
},
|
||||
modes: {
|
||||
name: 'Course Type',
|
||||
terms: {
|
||||
honor: 'Honor',
|
||||
verified: 'Verified'
|
||||
}
|
||||
},
|
||||
language: {
|
||||
en: 'English',
|
||||
hr: 'Croatian'
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('performs search', function () {
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
$('.discovery-input').val('test');
|
||||
$('.discovery-submit').trigger('click');
|
||||
AjaxHelpers.respondWithJson(requests, JSON_RESPONSE);
|
||||
expect($('.courses-listing article').length).toEqual(1);
|
||||
expect($('.courses-listing .course-title')).toContainHtml('edX Demonstration Course');
|
||||
expect($('.active-filter').length).toBe(1);
|
||||
});
|
||||
|
||||
it('loads more', function () {
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
jasmine.Clock.useMock();
|
||||
$('.discovery-input').val('test');
|
||||
$('.discovery-submit').trigger('click');
|
||||
AjaxHelpers.respondWithJson(requests, JSON_RESPONSE);
|
||||
expect($('.courses-listing article').length).toEqual(1);
|
||||
expect($('.courses-listing .course-title')).toContainHtml('edX Demonstration Course');
|
||||
window.scroll(0, $(document).height());
|
||||
$(window).trigger('scroll');
|
||||
jasmine.Clock.tick(500);
|
||||
AjaxHelpers.respondWithJson(requests, JSON_RESPONSE);
|
||||
expect($('.courses-listing article').length).toEqual(2);
|
||||
});
|
||||
|
||||
it('displays not found message', function () {
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
$('.discovery-input').val('asdfasdf');
|
||||
$('.discovery-submit').trigger('click');
|
||||
AjaxHelpers.respondWithJson(requests, {});
|
||||
expect($('#discovery-message')).not.toBeEmpty();
|
||||
expect($('.courses-listing')).toBeEmpty();
|
||||
});
|
||||
|
||||
it('displays error message', function () {
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
$('.discovery-input').val('asdfasdf');
|
||||
$('.discovery-submit').trigger('click');
|
||||
AjaxHelpers.respondWithError(requests, 404);
|
||||
expect($('#discovery-message')).not.toBeEmpty();
|
||||
expect($('.courses-listing')).toBeEmpty();
|
||||
});
|
||||
|
||||
it('check filters and bar removed on clear all', function () {
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
$('.discovery-input').val('test');
|
||||
$('.discovery-submit').trigger('click');
|
||||
AjaxHelpers.respondWithJson(requests, JSON_RESPONSE);
|
||||
expect($('.active-filter').length).toBe(1);
|
||||
expect($('#filter-bar')).not.toHaveClass('hidden');
|
||||
$('#clear-all-filters').trigger('click');
|
||||
expect($('.active-filter').length).toBe(0);
|
||||
expect($('#filter-bar')).toHaveClass('hidden');
|
||||
});
|
||||
|
||||
it('check filters and bar removed on last filter cleared', function () {
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
$('.discovery-input').val('test');
|
||||
$('.discovery-submit').trigger('click');
|
||||
AjaxHelpers.respondWithJson(requests, JSON_RESPONSE);
|
||||
expect($('.active-filter').length).toBe(1);
|
||||
var $filter = $('.active-filter');
|
||||
$filter.find('.discovery-button').trigger('click');
|
||||
expect($('.active-filter').length).toBe(0);
|
||||
});
|
||||
|
||||
it('filter results by named facet', function () {
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
$('.discovery-input').val('test');
|
||||
$('.discovery-submit').trigger('click');
|
||||
AjaxHelpers.respondWithJson(requests, JSON_RESPONSE);
|
||||
expect($('.active-filter').length).toBe(1);
|
||||
var $facetLink = $('.search-facets li [data-value="edX1"]');
|
||||
var $facet = $facetLink.parent('li');
|
||||
$facet.trigger('click');
|
||||
expect($('.active-filter').length).toBe(2);
|
||||
expect($('.active-filter [data-value="edX1"]').length).toBe(1);
|
||||
expect($('.active-filter [data-value="edX1"]').text().trim()).toBe("edX_1");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
28
lms/static/js/spec/discovery/models/course_card_spec.js
Normal file
28
lms/static/js/spec/discovery/models/course_card_spec.js
Normal file
@@ -0,0 +1,28 @@
|
||||
define(['js/discovery/models/course_card'], function(CourseCard) {
|
||||
'use strict';
|
||||
|
||||
describe('discovery.models.CourseCard', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
this.card = new CourseCard();
|
||||
});
|
||||
|
||||
it('has properties', function () {
|
||||
expect(this.card.get('modes')).toBeDefined();
|
||||
expect(this.card.get('course')).toBeDefined();
|
||||
expect(this.card.get('enrollment_start')).toBeDefined();
|
||||
expect(this.card.get('number')).toBeDefined();
|
||||
expect(this.card.get('content')).toEqual({
|
||||
display_name: '',
|
||||
number: '',
|
||||
overview: ''
|
||||
});
|
||||
expect(this.card.get('start')).toBeDefined();
|
||||
expect(this.card.get('image_url')).toBeDefined();
|
||||
expect(this.card.get('org')).toBeDefined();
|
||||
expect(this.card.get('id')).toBeDefined();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
101
lms/static/js/spec/discovery/models/course_directory_spec.js
Normal file
101
lms/static/js/spec/discovery/models/course_directory_spec.js
Normal file
@@ -0,0 +1,101 @@
|
||||
define([
|
||||
'common/js/spec_helpers/ajax_helpers', 'js/discovery/models/course_discovery'
|
||||
], function(AjaxHelpers, CourseDiscovery) {
|
||||
'use strict';
|
||||
|
||||
|
||||
var JSON_RESPONSE = {
|
||||
"total": 365,
|
||||
"results": [
|
||||
{
|
||||
"data": {
|
||||
"modes": [
|
||||
"honor"
|
||||
],
|
||||
"course": "edX/DemoX/Demo_Course",
|
||||
"enrollment_start": "2015-04-21T00:00:00+00:00",
|
||||
"number": "DemoX",
|
||||
"content": {
|
||||
"overview": " About This Course Include your long course description here.",
|
||||
"display_name": "edX Demonstration Course",
|
||||
"number": "DemoX"
|
||||
},
|
||||
"start": "1970-01-01T05:00:00+00:00",
|
||||
"image_url": "/c4x/edX/DemoX/asset/images_course_image.jpg",
|
||||
"org": "edX",
|
||||
"id": "edX/DemoX/Demo_Course"
|
||||
}
|
||||
}
|
||||
],
|
||||
"facets": {
|
||||
"org": {
|
||||
"total": 26,
|
||||
"terms": {
|
||||
"edX1": 1,
|
||||
"edX2": 1,
|
||||
"edX3": 1,
|
||||
"edX4": 1,
|
||||
"edX5": 1,
|
||||
"edX6": 1,
|
||||
"edX7": 1,
|
||||
"edX8": 1,
|
||||
"edX9": 1,
|
||||
"edX10": 1,
|
||||
"edX11": 1,
|
||||
"edX12": 1,
|
||||
"edX13": 1,
|
||||
"edX14": 1,
|
||||
"edX15": 1,
|
||||
"edX16": 1,
|
||||
"edX17": 1,
|
||||
"edX18": 1,
|
||||
"edX19": 1,
|
||||
"edX20": 1,
|
||||
"edX21": 1,
|
||||
"edX22": 1,
|
||||
"edX23": 1,
|
||||
"edX24": 1,
|
||||
"edX25": 1,
|
||||
"edX26": 1
|
||||
},
|
||||
"other": 0
|
||||
},
|
||||
"modes": {
|
||||
"total": 1,
|
||||
"terms": {
|
||||
"honor": 1
|
||||
},
|
||||
"other": 0
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
describe('discovery.models.CourseDiscovery', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
this.discovery = new CourseDiscovery();
|
||||
this.discovery.fetch();
|
||||
AjaxHelpers.respondWithJson(requests, JSON_RESPONSE);
|
||||
});
|
||||
|
||||
it('parses server response', function () {
|
||||
expect(this.discovery.courseCards.length).toBe(1);
|
||||
expect(this.discovery.facetOptions.length).toBe(27);
|
||||
});
|
||||
|
||||
it('resets collections', function () {
|
||||
this.discovery.reset();
|
||||
expect(this.discovery.courseCards.length).toBe(0);
|
||||
expect(this.discovery.facetOptions.length).toBe(0);
|
||||
});
|
||||
|
||||
it('returns latest course cards', function () {
|
||||
var latest = this.discovery.latest();
|
||||
expect(latest.length).toBe(1);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
19
lms/static/js/spec/discovery/models/facet_option_spec.js
Normal file
19
lms/static/js/spec/discovery/models/facet_option_spec.js
Normal file
@@ -0,0 +1,19 @@
|
||||
define(['js/discovery/models/facet_option'], function(FacetOption) {
|
||||
'use strict';
|
||||
|
||||
describe('discovery.models.FacetOption', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
this.filter = new FacetOption();
|
||||
});
|
||||
|
||||
it('has properties', function () {
|
||||
expect(this.filter.get('facet')).toBeDefined();
|
||||
expect(this.filter.get('term')).toBeDefined();
|
||||
expect(this.filter.get('count')).toBeDefined();
|
||||
expect(this.filter.get('selected')).toBeDefined();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
16
lms/static/js/spec/discovery/models/filter_spec.js
Normal file
16
lms/static/js/spec/discovery/models/filter_spec.js
Normal file
@@ -0,0 +1,16 @@
|
||||
define(['js/discovery/models/filter'], function(Filter) {
|
||||
'use strict';
|
||||
|
||||
describe('discovery.models.Filter', function () {
|
||||
beforeEach(function () {
|
||||
this.filter = new Filter();
|
||||
});
|
||||
|
||||
it('has properties', function () {
|
||||
expect(this.filter.get('type')).toBeDefined();
|
||||
expect(this.filter.get('query')).toBeDefined();
|
||||
expect(this.filter.get('name')).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
90
lms/static/js/spec/discovery/models/search_state_spec.js
Normal file
90
lms/static/js/spec/discovery/models/search_state_spec.js
Normal file
@@ -0,0 +1,90 @@
|
||||
define([
|
||||
'common/js/spec_helpers/ajax_helpers', 'js/discovery/models/search_state'
|
||||
], function(AjaxHelpers, SearchState) {
|
||||
'use strict';
|
||||
|
||||
|
||||
var JSON_RESPONSE = {
|
||||
"total": 365,
|
||||
"results": [
|
||||
{
|
||||
"data": {
|
||||
"modes": [
|
||||
"honor"
|
||||
],
|
||||
"course": "edX/DemoX/Demo_Course",
|
||||
"enrollment_start": "2015-04-21T00:00:00+00:00",
|
||||
"number": "DemoX",
|
||||
"content": {
|
||||
"overview": " About This Course Include your long course description here.",
|
||||
"display_name": "edX Demonstration Course",
|
||||
"number": "DemoX"
|
||||
},
|
||||
"start": "1970-01-01T05:00:00+00:00",
|
||||
"image_url": "/c4x/edX/DemoX/asset/images_course_image.jpg",
|
||||
"org": "edX",
|
||||
"id": "edX/DemoX/Demo_Course"
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
describe('discovery.models.SearchState', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
this.search = new SearchState();
|
||||
this.onSearch = jasmine.createSpy('onSearch');
|
||||
this.onNext = jasmine.createSpy('onNext');
|
||||
this.onError = jasmine.createSpy('onError');
|
||||
this.search.on('search', this.onSearch);
|
||||
this.search.on('next', this.onNext);
|
||||
this.search.on('error', this.onError);
|
||||
});
|
||||
|
||||
it('perform search', function () {
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
this.search.performSearch('dummy');
|
||||
AjaxHelpers.respondWithJson(requests, JSON_RESPONSE);
|
||||
expect(this.onSearch).toHaveBeenCalledWith('dummy', 365);
|
||||
expect(this.search.discovery.courseCards.length).toBe(1);
|
||||
this.search.refineSearch({ modes: 'honor' });
|
||||
AjaxHelpers.respondWithJson(requests, JSON_RESPONSE);
|
||||
expect(this.onSearch).toHaveBeenCalledWith('dummy', 365);
|
||||
});
|
||||
|
||||
it('returns an error', function () {
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
this.search.performSearch('');
|
||||
AjaxHelpers.respondWithError(requests, 500);
|
||||
expect(this.onError).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('loads next page', function () {
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
this.search.performSearch('dummy');
|
||||
AjaxHelpers.respondWithJson(requests, JSON_RESPONSE);
|
||||
this.search.loadNextPage();
|
||||
AjaxHelpers.respondWithJson(requests, JSON_RESPONSE);
|
||||
expect(this.onNext).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('shows all results when there are none', function () {
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
this.search.performSearch('dummy', { modes: 'SomeOption' });
|
||||
// no results
|
||||
AjaxHelpers.respondWithJson(requests, { total: 0 });
|
||||
expect(this.onSearch).not.toHaveBeenCalled();
|
||||
// there should be another Ajax call to fetch all courses
|
||||
AjaxHelpers.respondWithJson(requests, JSON_RESPONSE);
|
||||
expect(this.onSearch).toHaveBeenCalledWith('dummy', 0);
|
||||
// new search
|
||||
this.search.performSearch('something');
|
||||
// no results
|
||||
AjaxHelpers.respondWithJson(requests, { total: 0 });
|
||||
// should load cached results
|
||||
expect(this.onSearch).toHaveBeenCalledWith('dummy', 0);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
56
lms/static/js/spec/discovery/views/course_card_spec.js
Normal file
56
lms/static/js/spec/discovery/views/course_card_spec.js
Normal file
@@ -0,0 +1,56 @@
|
||||
define([
|
||||
'jquery', 'common/js/spec_helpers/template_helpers', 'js/discovery/models/course_card',
|
||||
'js/discovery/views/course_card'
|
||||
], function($, TemplateHelpers, CourseCard, CourseCardView) {
|
||||
'use strict';
|
||||
|
||||
|
||||
var JSON_RESPONSE = {
|
||||
"total": 365,
|
||||
"results": [
|
||||
{
|
||||
"data": {
|
||||
"modes": [
|
||||
"honor"
|
||||
],
|
||||
"course": "edX/DemoX/Demo_Course",
|
||||
"enrollment_start": "2015-04-21T00:00:00+00:00",
|
||||
"number": "DemoX",
|
||||
"content": {
|
||||
"overview": " About This Course Include your long course description here.",
|
||||
"display_name": "edX Demonstration Course",
|
||||
"number": "DemoX"
|
||||
},
|
||||
"start": "1970-01-01T05:00:00+00:00",
|
||||
"image_url": "/c4x/edX/DemoX/asset/images_course_image.jpg",
|
||||
"org": "edX",
|
||||
"id": "edX/DemoX/Demo_Course"
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
describe('discovery.views.CourseCard', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
TemplateHelpers.installTemplate('templates/discovery/course_card');
|
||||
this.view = new CourseCardView({
|
||||
model: new CourseCard(JSON_RESPONSE.results[0].data)
|
||||
});
|
||||
this.view.render();
|
||||
});
|
||||
|
||||
it('renders', function () {
|
||||
var data = this.view.model.attributes;
|
||||
expect(this.view.$el).toContainHtml(data.content.display_name);
|
||||
expect(this.view.$el).toContain('a[href="/courses/' + data.course + '/about"]');
|
||||
expect(this.view.$el).toContain('img[src="' + data.image_url + '"]');
|
||||
expect(this.view.$el.find('.course-name')).toContainHtml(data.org);
|
||||
expect(this.view.$el.find('.course-name')).toContainHtml(data.content.number);
|
||||
expect(this.view.$el.find('.course-name')).toContainHtml(data.content.display_name);
|
||||
expect(this.view.$el.find('.course-date')).toContainHtml('Jan 01, 1970');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
76
lms/static/js/spec/discovery/views/courses_listing_spec.js
Normal file
76
lms/static/js/spec/discovery/views/courses_listing_spec.js
Normal file
@@ -0,0 +1,76 @@
|
||||
define([
|
||||
'jquery', 'backbone', 'common/js/spec_helpers/template_helpers',
|
||||
'js/discovery/models/course_card', 'js/discovery/views/courses_listing'
|
||||
], function($, Backbone, TemplateHelpers, CourseCard, CoursesListing) {
|
||||
'use strict';
|
||||
|
||||
|
||||
var JSON_RESPONSE = {
|
||||
"total": 365,
|
||||
"results": [
|
||||
{
|
||||
"data": {
|
||||
"modes": [
|
||||
"honor"
|
||||
],
|
||||
"course": "edX/DemoX/Demo_Course",
|
||||
"enrollment_start": "2015-04-21T00:00:00+00:00",
|
||||
"number": "DemoX",
|
||||
"content": {
|
||||
"overview": " About This Course Include your long course description here.",
|
||||
"display_name": "edX Demonstration Course",
|
||||
"number": "DemoX"
|
||||
},
|
||||
"start": "1970-01-01T05:00:00+00:00",
|
||||
"image_url": "/c4x/edX/DemoX/asset/images_course_image.jpg",
|
||||
"org": "edX",
|
||||
"id": "edX/DemoX/Demo_Course"
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
describe('discovery.views.CoursesListing', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
jasmine.Clock.useMock();
|
||||
loadFixtures('js/fixtures/discovery.html');
|
||||
TemplateHelpers.installTemplate('templates/discovery/course_card');
|
||||
var collection = new Backbone.Collection(
|
||||
[JSON_RESPONSE.results[0].data],
|
||||
{ model: CourseCard }
|
||||
);
|
||||
var mock = {
|
||||
collection: collection,
|
||||
latest: function () { return this.collection.last(20); }
|
||||
}
|
||||
this.view = new CoursesListing({ model: mock });
|
||||
});
|
||||
|
||||
it('renders search results', function () {
|
||||
this.view.render();
|
||||
expect($('.courses-listing article').length).toEqual(1);
|
||||
expect($('.courses-listing .course-title')).toContainHtml('edX Demonstration Course');
|
||||
this.view.renderNext();
|
||||
expect($('.courses-listing article').length).toEqual(2);
|
||||
});
|
||||
|
||||
it('scrolling triggers an event for next page', function () {
|
||||
this.onNext = jasmine.createSpy('onNext');
|
||||
this.view.on('next', this.onNext);
|
||||
this.view.render();
|
||||
window.scroll(0, $(document).height());
|
||||
$(window).trigger('scroll');
|
||||
jasmine.Clock.tick(500);
|
||||
expect(this.onNext).toHaveBeenCalled();
|
||||
|
||||
// should not be triggered again (while it is loading)
|
||||
$(window).trigger('scroll');
|
||||
jasmine.Clock.tick(500);
|
||||
expect(this.onNext.calls.length).toEqual(1);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
53
lms/static/js/spec/discovery/views/filter_bar_spec.js
Normal file
53
lms/static/js/spec/discovery/views/filter_bar_spec.js
Normal file
@@ -0,0 +1,53 @@
|
||||
define([
|
||||
'jquery', 'common/js/spec_helpers/template_helpers', 'js/discovery/collections/filters',
|
||||
'js/discovery/views/filter_bar'
|
||||
], function($, TemplateHelpers, Filters, FilterBar) {
|
||||
'use strict';
|
||||
|
||||
describe('discovery.views.FilterBar', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
loadFixtures('js/fixtures/discovery.html');
|
||||
TemplateHelpers.installTemplates([
|
||||
'templates/discovery/filter',
|
||||
'templates/discovery/filter_bar'
|
||||
]);
|
||||
this.filters = new Filters();
|
||||
this.filterBar = new FilterBar({ collection: this.filters });
|
||||
this.filters.add({
|
||||
type: 'org',
|
||||
query: 'edX',
|
||||
name: 'edX'
|
||||
});
|
||||
});
|
||||
|
||||
it('adds filter', function () {
|
||||
expect(this.filterBar.$el.find('button')).toHaveData('type', 'org');
|
||||
});
|
||||
|
||||
it('removes filter', function () {
|
||||
this.filters.remove('org');
|
||||
expect(this.filterBar.$el.find('ul')).toBeEmpty();
|
||||
expect(this.filterBar.$el).toHaveClass('is-collapsed');
|
||||
});
|
||||
|
||||
it('resets filters', function () {
|
||||
this.filters.reset();
|
||||
expect(this.filterBar.$el.find('ul')).toBeEmpty();
|
||||
expect(this.filterBar.$el).toHaveClass('is-collapsed');
|
||||
});
|
||||
|
||||
it('triggers events', function () {
|
||||
this.onClearFilter = jasmine.createSpy('onClearFilter');
|
||||
this.onClearAll = jasmine.createSpy('onClearAll');
|
||||
this.filterBar.on('clearFilter', this.onClearFilter);
|
||||
this.filterBar.on('clearAll', this.onClearAll);
|
||||
this.filterBar.$el.find('button').click();
|
||||
expect(this.onClearFilter).toHaveBeenCalledWith('org');
|
||||
this.filterBar.$el.find('#clear-all-filters').click();
|
||||
expect(this.onClearAll).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
40
lms/static/js/spec/discovery/views/filter_label_spec.js
Normal file
40
lms/static/js/spec/discovery/views/filter_label_spec.js
Normal file
@@ -0,0 +1,40 @@
|
||||
define([
|
||||
'jquery', 'common/js/spec_helpers/template_helpers', 'js/discovery/models/filter',
|
||||
'js/discovery/views/filter_label'
|
||||
], function($, TemplateHelpers, Filter, FilterLabel) {
|
||||
'use strict';
|
||||
|
||||
describe('discovery.views.FilterLabel', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
TemplateHelpers.installTemplate('templates/discovery/filter');
|
||||
var filter = new Filter({
|
||||
type: 'language',
|
||||
query: 'en',
|
||||
name: 'English'
|
||||
});
|
||||
this.view = new FilterLabel({model: filter});
|
||||
this.view.render();
|
||||
});
|
||||
|
||||
it('renders', function () {
|
||||
var data = this.view.model.attributes;
|
||||
expect(this.view.$el.find('button')).toHaveData('value', 'en');
|
||||
expect(this.view.$el.find('button')).toHaveData('type', 'language');
|
||||
expect(this.view.$el).toContainHtml(data.name);
|
||||
});
|
||||
|
||||
it('renders changes', function () {
|
||||
this.view.model.set('query', 'es');
|
||||
expect(this.view.$el.find('button')).toHaveData('value', 'es');
|
||||
});
|
||||
|
||||
it('removes itself', function () {
|
||||
// simulate removing from collection
|
||||
this.view.model.trigger('remove');
|
||||
expect(this.view.$el).not.toExist();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
77
lms/static/js/spec/discovery/views/refine_sidebar_spec.js
Normal file
77
lms/static/js/spec/discovery/views/refine_sidebar_spec.js
Normal file
@@ -0,0 +1,77 @@
|
||||
define([
|
||||
'jquery', 'common/js/spec_helpers/template_helpers', 'js/discovery/models/facet_option',
|
||||
'js/discovery/views/refine_sidebar'
|
||||
], function($, TemplateHelpers, FacetOption, RefineSidebar) {
|
||||
'use strict';
|
||||
|
||||
var MEANINGS = {
|
||||
org: {
|
||||
name: 'Organization',
|
||||
terms: {
|
||||
edX1: "edX_1"
|
||||
}
|
||||
},
|
||||
modes: {
|
||||
name: 'Course Type',
|
||||
terms: {
|
||||
honor: 'Honor',
|
||||
verified: 'Verified'
|
||||
}
|
||||
},
|
||||
language: {
|
||||
terms: {
|
||||
en: 'English',
|
||||
hr: 'Croatian'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
describe('discovery.views.RefineSidebar', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
loadFixtures('js/fixtures/discovery.html');
|
||||
TemplateHelpers.installTemplates([
|
||||
'templates/discovery/facet',
|
||||
'templates/discovery/facet_option'
|
||||
]);
|
||||
this.facetOptions = new Backbone.Collection([], { model: FacetOption });
|
||||
this.facetOptions.add([
|
||||
{ facet: 'language', term: 'es', count: 12 },
|
||||
{ facet: 'language', term: 'en', count: 10 },
|
||||
{ facet: 'modes', term: 'honor', count: 2, selected: true }
|
||||
]);
|
||||
this.sidebar = new RefineSidebar({ collection: this.facetOptions, meanings: MEANINGS });
|
||||
this.sidebar.render();
|
||||
|
||||
});
|
||||
|
||||
it('styles active filter', function () {
|
||||
expect(this.sidebar.$el.find('button.selected')).toHaveData('facet', 'modes');
|
||||
});
|
||||
|
||||
it('styles active filter', function () {
|
||||
this.onSelect = jasmine.createSpy('onSelect');
|
||||
this.sidebar.on('selectOption', this.onSelect);
|
||||
this.sidebar.$el.find('button[data-value="en"]').click();
|
||||
expect(this.onSelect).toHaveBeenCalledWith('language', 'en', 'English');
|
||||
});
|
||||
|
||||
it('expands and collapses facet', function () {
|
||||
var options = _.range(20).map(function (number) {
|
||||
return { facet: 'org', term: 'test' + number, count: 1 };
|
||||
});
|
||||
this.facetOptions.reset(options);
|
||||
this.sidebar.render();
|
||||
this.sidebar.$el.find('.show-more').click();
|
||||
expect(this.sidebar.$el.find('ul.facet-list')).not.toHaveClass('collapse');
|
||||
expect(this.sidebar.$el.find('.show-more')).toHaveClass('hidden');
|
||||
this.sidebar.$el.find('.show-less').click();
|
||||
expect(this.sidebar.$el.find('ul.facet-list')).toHaveClass('collapse');
|
||||
expect(this.sidebar.$el.find('.show-less')).toHaveClass('hidden');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
53
lms/static/js/spec/discovery/views/search_form_spec.js
Normal file
53
lms/static/js/spec/discovery/views/search_form_spec.js
Normal file
@@ -0,0 +1,53 @@
|
||||
define(['jquery', 'js/discovery/views/search_form'], function($, SearchForm) {
|
||||
'use strict';
|
||||
|
||||
describe('discovery.views.SearchForm', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
loadFixtures('js/fixtures/discovery.html');
|
||||
this.form = new SearchForm();
|
||||
this.onSearch = jasmine.createSpy('onSearch');
|
||||
this.form.on('search', this.onSearch);
|
||||
});
|
||||
|
||||
it('trims input string', function () {
|
||||
var term = ' search string ';
|
||||
$('.discovery-input').val(term);
|
||||
$('form').trigger('submit');
|
||||
expect(this.onSearch).toHaveBeenCalledWith($.trim(term));
|
||||
});
|
||||
|
||||
it('handles calls to doSearch', function () {
|
||||
var term = ' search string ';
|
||||
$('.discovery-input').val(term);
|
||||
this.form.doSearch(term);
|
||||
expect(this.onSearch).toHaveBeenCalledWith($.trim(term));
|
||||
expect($('.discovery-input').val()).toBe(term);
|
||||
expect($('#discovery-message')).toBeEmpty();
|
||||
});
|
||||
|
||||
it('clears search', function () {
|
||||
$('.discovery-input').val('somethig');
|
||||
this.form.clearSearch();
|
||||
expect($('.discovery-input').val()).toBe('');
|
||||
});
|
||||
|
||||
it('shows/hides loading indicator', function () {
|
||||
this.form.showLoadingIndicator();
|
||||
expect($('#loading-indicator')).not.toHaveClass('hidden');
|
||||
this.form.hideLoadingIndicator();
|
||||
expect($('#loading-indicator')).toHaveClass('hidden');
|
||||
});
|
||||
|
||||
it('shows messages', function () {
|
||||
this.form.showFoundMessage(123);
|
||||
expect($('#discovery-message')).toContainHtml(123);
|
||||
this.form.showNotFoundMessage();
|
||||
expect($('#discovery-message')).not.toBeEmpty();
|
||||
this.form.showErrorMessage();
|
||||
expect($('#discovery-message')).not.toBeEmpty();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -591,7 +591,18 @@
|
||||
'lms/include/js/spec/edxnotes/plugins/caret_navigation_spec.js',
|
||||
'lms/include/js/spec/edxnotes/collections/notes_spec.js',
|
||||
'lms/include/js/spec/search/search_spec.js',
|
||||
'lms/include/js/spec/discovery/discovery_spec.js',
|
||||
'lms/include/js/spec/discovery/collections/filters_spec.js',
|
||||
'lms/include/js/spec/discovery/models/course_card_spec.js',
|
||||
'lms/include/js/spec/discovery/models/course_directory_spec.js',
|
||||
'lms/include/js/spec/discovery/models/facet_option_spec.js',
|
||||
'lms/include/js/spec/discovery/models/filter_spec.js',
|
||||
'lms/include/js/spec/discovery/models/search_state_spec.js',
|
||||
'lms/include/js/spec/discovery/views/course_card_spec.js',
|
||||
'lms/include/js/spec/discovery/views/courses_listing_spec.js',
|
||||
'lms/include/js/spec/discovery/views/filter_bar_spec.js',
|
||||
'lms/include/js/spec/discovery/views/refine_sidebar_spec.js',
|
||||
'lms/include/js/spec/discovery/views/search_form_spec.js',
|
||||
'lms/include/js/spec/discovery/discovery_factory_spec.js',
|
||||
'lms/include/js/spec/ccx/schedule_spec.js'
|
||||
]);
|
||||
|
||||
|
||||
@@ -274,8 +274,8 @@ $facet-background-color: #007db8;
|
||||
@extend %t-demi-strong;
|
||||
@include border-radius(3px);
|
||||
@include padding-right(55px);
|
||||
width: 100%;
|
||||
border: 2px solid $gray-l3;
|
||||
width: 100%;
|
||||
height: $course-search-input-height;
|
||||
color: $black;
|
||||
font-style: normal;
|
||||
@@ -298,10 +298,10 @@ $facet-background-color: #007db8;
|
||||
top: 0;
|
||||
border: 2px solid $m-blue-d6;
|
||||
border-radius: ($baseline*0.1);
|
||||
box-shadow: none;
|
||||
background: $blue;
|
||||
padding: 0 ($baseline*0.7);
|
||||
height: $course-search-input-height;
|
||||
padding: 0 ($baseline*0.7);
|
||||
background: $blue;
|
||||
box-shadow: none;
|
||||
color: $white;
|
||||
text-shadow: none;
|
||||
|
||||
@@ -316,7 +316,7 @@ $facet-background-color: #007db8;
|
||||
background: $blue;
|
||||
position: absolute;
|
||||
top: ($baseline*0.7); // same as padding rule for .discovery-submit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// +Filters and Facets - Search
|
||||
@@ -326,11 +326,24 @@ $facet-background-color: #007db8;
|
||||
.filters {
|
||||
@include clearfix();
|
||||
margin-top: ($baseline/2);
|
||||
border-top: 2px solid $courseware-button-border-color;
|
||||
border-bottom: 2px solid $courseware-button-border-color;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-height: ($baseline*10);
|
||||
overflow: hidden;
|
||||
|
||||
&.is-animated {
|
||||
@include transition(max-height 0.3s);
|
||||
}
|
||||
|
||||
&.is-collapsed {
|
||||
max-height: 0;
|
||||
}
|
||||
|
||||
.filters-inner {
|
||||
@include clearfix();
|
||||
border-top: 2px solid $courseware-button-border-color;
|
||||
border-bottom: 2px solid $courseware-button-border-color;
|
||||
}
|
||||
|
||||
ul {
|
||||
@include padding-left(0);
|
||||
@@ -343,8 +356,8 @@ $facet-background-color: #007db8;
|
||||
@include margin(($baseline/2), $baseline, ($baseline/2), 0);
|
||||
position: relative;
|
||||
padding: ($baseline/2) ($baseline*0.75);
|
||||
background: $courseware-button-border-color;
|
||||
width: auto;
|
||||
background: $courseware-button-border-color;
|
||||
|
||||
.facet-option {
|
||||
@extend %t-strong;
|
||||
@@ -363,8 +376,9 @@ $facet-background-color: #007db8;
|
||||
@extend %t-strong;
|
||||
margin: ($baseline/2);
|
||||
width: auto;
|
||||
text-align: center;
|
||||
color: $m-blue-d5;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.flt-right {
|
||||
@@ -382,8 +396,8 @@ $facet-background-color: #007db8;
|
||||
box-shadow: 1px 2px 5px $black-t0;
|
||||
border-top: 1px solid $black;
|
||||
border-bottom: 2px solid $black;
|
||||
background-color: $white;
|
||||
max-height: ($baseline*100);
|
||||
background-color: $white;
|
||||
|
||||
@include media($bp-tiny) {
|
||||
@include fill-parent();
|
||||
@@ -426,28 +440,32 @@ $facet-background-color: #007db8;
|
||||
.header-search-facets, .header-facet {
|
||||
@extend %t-title6;
|
||||
@extend %t-strong;
|
||||
padding: ($baseline/2);
|
||||
margin: 0;
|
||||
padding: ($baseline/2);
|
||||
color: $facet-text-color;
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.header-facet {
|
||||
margin: 0 ($baseline/2);
|
||||
padding: ($baseline/2) 0;
|
||||
}
|
||||
|
||||
.search-facets-lists section {
|
||||
border-top: 1px solid $gray-l4;
|
||||
}
|
||||
|
||||
.facet-list {
|
||||
@extend %ui-no-list;
|
||||
@include clearfix();
|
||||
padding-bottom: ($baseline/2);
|
||||
padding-bottom: ($baseline/2);
|
||||
|
||||
&.collapse {
|
||||
max-height: ($baseline*14);
|
||||
max-height: ($baseline*1.5*9) - ($baseline/2);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
li {
|
||||
@include clearfix();
|
||||
position: relative;
|
||||
padding: 0;
|
||||
height: ($baseline*1.5);
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -457,25 +475,19 @@ $facet-background-color: #007db8;
|
||||
@include clearfix();
|
||||
@extend %t-action3;
|
||||
@extend %text-truncated;
|
||||
padding: ($baseline/4) ($baseline/2);
|
||||
border-radius: 0px;
|
||||
opacity: 1;
|
||||
border-radius: 0px;
|
||||
padding: ($baseline/4) ($baseline/2);
|
||||
width: 100%;
|
||||
color: $facet-text-color;
|
||||
|
||||
li {
|
||||
@include clearfix();
|
||||
position: relative;
|
||||
clear: both;
|
||||
padding: 0;
|
||||
height: ($baseline*1.5);
|
||||
}
|
||||
|
||||
.count {
|
||||
@include right($baseline*0.6);
|
||||
@include text-align(right);
|
||||
@include box-sizing(border-box);
|
||||
@include transition(all 0.2s ease-out);
|
||||
position: absolute;
|
||||
width: ($baseline*2);
|
||||
}
|
||||
|
||||
//STATE: hover, focus
|
||||
@@ -488,6 +500,28 @@ $facet-background-color: #007db8;
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
//STATE: selected (hover, focus)
|
||||
&.selected,
|
||||
&.selected:hover,
|
||||
&.selected:focus {
|
||||
background: $gray-l4;
|
||||
color: $facet-text-color;
|
||||
|
||||
.count {
|
||||
color: $gray-l4;
|
||||
}
|
||||
|
||||
.count:before {
|
||||
@include left($baseline*0.75);
|
||||
position: absolute;
|
||||
width: ($baseline*2);
|
||||
color: $gray-l1;
|
||||
font-family: FontAwesome;
|
||||
text-align: center;
|
||||
content: '\f00d'; // fa-times
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.toggle {
|
||||
@@ -496,7 +530,9 @@ $facet-background-color: #007db8;
|
||||
button {
|
||||
@extend %t-icon6;
|
||||
@extend %t-strong;
|
||||
padding: ($baseline/4) ($baseline/2);
|
||||
color: $facet-background-color;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
% if settings.FEATURES.get('ENABLE_COURSE_DISCOVERY'):
|
||||
<%block name="header_extras">
|
||||
% for template_name in ["result_item", "filter_bar", "filter", "search_facets_list", "search_facets_section", "search_facet", "more_less_links"]:
|
||||
% for template_name in ["course_card", "filter_bar", "filter", "facet", "facet_option"]:
|
||||
<script type="text/template" id="${template_name}-tpl">
|
||||
<%static:include path="discovery/${template_name}.underscore" />
|
||||
</script>
|
||||
@@ -68,7 +68,7 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="filter-bar" class="filters hide-phone">
|
||||
<div id="filter-bar" class="filters hide-phone is-collapsed">
|
||||
</div>
|
||||
% endif
|
||||
|
||||
@@ -84,7 +84,10 @@
|
||||
|
||||
|
||||
% if course_discovery_enabled:
|
||||
<aside aria-label="${_('Refine your search')}" class="search-facets phone-menu">
|
||||
<aside aria-label="${_('Refine Your Search')}" class="search-facets phone-menu">
|
||||
<h2 class="header-search-facets">${_('Refine Your Search')}</h2>
|
||||
<section class="search-facets-lists">
|
||||
</section>
|
||||
</aside>
|
||||
% endif
|
||||
|
||||
|
||||
16
lms/templates/discovery/facet.underscore
Normal file
16
lms/templates/discovery/facet.underscore
Normal file
@@ -0,0 +1,16 @@
|
||||
<h3 class="header-facet">
|
||||
<%= displayName %>
|
||||
</h3>
|
||||
<ul data-facet="<%= name %>" class="facet-list collapse">
|
||||
<%= options %>
|
||||
</ul>
|
||||
<% if (listIsHuge) { %>
|
||||
<div class="toggle ">
|
||||
<button class="show-more discovery-button">
|
||||
<%= gettext("More") %>
|
||||
</button>
|
||||
<button class="show-less hidden discovery-button">
|
||||
<%= gettext("Less") %>
|
||||
</button>
|
||||
</div>
|
||||
<% } %>
|
||||
8
lms/templates/discovery/facet_option.underscore
Normal file
8
lms/templates/discovery/facet_option.underscore
Normal file
@@ -0,0 +1,8 @@
|
||||
<li>
|
||||
<button data-facet="<%= facet %>" data-value="<%= term %>" data-text="<%= name %>" class="facet-option discovery-button <%= selected ? 'selected' : '' %>">
|
||||
<%= name %>
|
||||
<span class="count">
|
||||
<%= count %>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
@@ -1,5 +1,4 @@
|
||||
<ul class="active-filters facet-list">
|
||||
</ul>
|
||||
<span>
|
||||
<button id="clear-all-filters" class="clear-filters flt-right discovery-button"><%= gettext('CLEAR ALL') %></button>
|
||||
</span>
|
||||
<div class="filters-inner">
|
||||
<ul class="active-filters facet-list"></ul>
|
||||
<button id="clear-all-filters" class="clear-filters flt-right discovery-button"><%= gettext('Clear All') %></button>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
<div class="toggle ">
|
||||
<button class="show-more discovery-button">
|
||||
<%= gettext("MORE...") %>
|
||||
</button>
|
||||
<button class="show-less hidden discovery-button">
|
||||
<%= gettext("LESS...") %>
|
||||
</button>
|
||||
</div>
|
||||
@@ -1,6 +0,0 @@
|
||||
<button data-value="<%= term %>" data-text="<%= name %>" class="facet-option discovery-button">
|
||||
<%= name %>
|
||||
<span class="count">
|
||||
<%= count %>
|
||||
</span>
|
||||
</button>
|
||||
@@ -1,5 +0,0 @@
|
||||
<h2 class="header-search-facets">
|
||||
<%= gettext('Refine your search') %>
|
||||
</h2>
|
||||
<section class="search-facets-lists">
|
||||
</section>
|
||||
@@ -1,5 +0,0 @@
|
||||
<h3 class="header-facet">
|
||||
<%= displayName %>
|
||||
</h3>
|
||||
<ul data-facet="<%= name %>" class="facet-list collapse">
|
||||
</ul>
|
||||
Reference in New Issue
Block a user