Commit 0096c80a by Andy Armstrong Committed by Diana Huang

Refactor course search into openedx/features

parent 77212379
...@@ -2230,6 +2230,7 @@ INSTALLED_APPS = ( ...@@ -2230,6 +2230,7 @@ INSTALLED_APPS = (
# Features # Features
'openedx.features.course_bookmarks', 'openedx.features.course_bookmarks',
'openedx.features.course_experience', 'openedx.features.course_experience',
'openedx.features.course_search',
'openedx.features.enterprise_support', 'openedx.features.enterprise_support',
) )
......
../../openedx/features/course_search/static/course_search
\ No newline at end of file
(function(define) {
define([
'js/search/base/views/search_form'
], function(SearchForm) {
'use strict';
return SearchForm.extend({
el: '#courseware-search-bar'
});
});
})(define || RequireJS.define);
(function(define) {
define([
'js/search/base/views/search_item_view'
], function(SearchItemView) {
'use strict';
return SearchItemView.extend({
templateId: '#course_search_item-tpl'
});
});
})(define || RequireJS.define);
(function(define) {
define([
'js/search/base/views/search_form'
], function(SearchForm) {
'use strict';
return SearchForm.extend({
el: '#dashboard-search-bar'
});
});
})(define || RequireJS.define);
(function(define) {
define([
'js/search/base/views/search_item_view'
], function(SearchItemView) {
'use strict';
return SearchItemView.extend({
templateId: '#dashboard_search_item-tpl'
});
});
})(define || RequireJS.define);
...@@ -28,6 +28,7 @@ var options = { ...@@ -28,6 +28,7 @@ var options = {
sourceFiles: [ sourceFiles: [
{pattern: 'coffee/src/**/!(*spec).js'}, {pattern: 'coffee/src/**/!(*spec).js'},
{pattern: 'course_bookmarks/**/!(*spec).js'}, {pattern: 'course_bookmarks/**/!(*spec).js'},
{pattern: 'course_search/**/!(*spec).js'},
{pattern: 'discussion/js/**/!(*spec).js'}, {pattern: 'discussion/js/**/!(*spec).js'},
{pattern: 'js/**/!(*spec|djangojs).js'}, {pattern: 'js/**/!(*spec|djangojs).js'},
{pattern: 'lms/js/**/!(*spec).js'}, {pattern: 'lms/js/**/!(*spec).js'},
......
...@@ -19,6 +19,8 @@ ...@@ -19,6 +19,8 @@
*/ */
modules: getModulesList([ modules: getModulesList([
'course_bookmarks/js/course_bookmarks_factory', 'course_bookmarks/js/course_bookmarks_factory',
'course_search/js/course_search_factory',
'course_search/js/dashboard_search_factory',
'discussion/js/discussion_board_factory', 'discussion/js/discussion_board_factory',
'discussion/js/discussion_profile_page_factory', 'discussion/js/discussion_profile_page_factory',
'js/api_admin/catalog_preview_factory', 'js/api_admin/catalog_preview_factory',
...@@ -32,8 +34,6 @@ ...@@ -32,8 +34,6 @@
'js/header_factory', 'js/header_factory',
'js/learner_dashboard/program_details_factory', 'js/learner_dashboard/program_details_factory',
'js/learner_dashboard/program_list_factory', 'js/learner_dashboard/program_list_factory',
'js/search/course/course_search_factory',
'js/search/dashboard/dashboard_search_factory',
'js/student_account/logistration_factory', 'js/student_account/logistration_factory',
'js/student_account/views/account_settings_factory', 'js/student_account/views/account_settings_factory',
'js/student_account/views/finish_auth_factory', 'js/student_account/views/finish_auth_factory',
......
...@@ -676,6 +676,7 @@ ...@@ -676,6 +676,7 @@
'course_bookmarks/js/spec/bookmark_button_view_spec.js', 'course_bookmarks/js/spec/bookmark_button_view_spec.js',
'course_bookmarks/js/spec/bookmarks_list_view_spec.js', 'course_bookmarks/js/spec/bookmarks_list_view_spec.js',
'course_bookmarks/js/spec/course_bookmarks_factory_spec.js', 'course_bookmarks/js/spec/course_bookmarks_factory_spec.js',
'course_search/js/spec/search_spec.js',
'discussion/js/spec/discussion_board_factory_spec.js', 'discussion/js/spec/discussion_board_factory_spec.js',
'discussion/js/spec/discussion_profile_page_factory_spec.js', 'discussion/js/spec/discussion_profile_page_factory_spec.js',
'discussion/js/spec/discussion_board_view_spec.js', 'discussion/js/spec/discussion_board_view_spec.js',
...@@ -749,7 +750,6 @@ ...@@ -749,7 +750,6 @@
'js/spec/markdown_editor_spec.js', 'js/spec/markdown_editor_spec.js',
'js/spec/dateutil_factory_spec.js', 'js/spec/dateutil_factory_spec.js',
'js/spec/navigation_spec.js', 'js/spec/navigation_spec.js',
'js/spec/search/search_spec.js',
'js/spec/shoppingcart/shoppingcart_spec.js', 'js/spec/shoppingcart/shoppingcart_spec.js',
'js/spec/staff_debug_actions_spec.js', 'js/spec/staff_debug_actions_spec.js',
'js/spec/student_account/access_spec.js', 'js/spec/student_account/access_spec.js',
......
...@@ -37,14 +37,6 @@ from openedx.features.course_experience import course_home_page_title, UNIFIED_C ...@@ -37,14 +37,6 @@ from openedx.features.course_experience import course_home_page_title, UNIFIED_C
</script> </script>
% endfor % endfor
% if settings.FEATURES.get('ENABLE_COURSEWARE_SEARCH'):
% for template_name in ["course_search_item", "course_search_results", "search_loading", "search_error"]:
<script type="text/template" id="${template_name}-tpl">
<%static:include path="search/${template_name}.underscore" />
</script>
% endfor
% endif
% if include_special_exams is not UNDEFINED and include_special_exams: % if include_special_exams is not UNDEFINED and include_special_exams:
% for template_name in ["proctored-exam-status"]: % for template_name in ["proctored-exam-status"]:
<script type="text/template" id="${template_name}-tpl"> <script type="text/template" id="${template_name}-tpl">
...@@ -81,7 +73,7 @@ from openedx.features.course_experience import course_home_page_title, UNIFIED_C ...@@ -81,7 +73,7 @@ from openedx.features.course_experience import course_home_page_title, UNIFIED_C
<%include file="/mathjax_include.html" args="disable_fast_preview=True"/> <%include file="/mathjax_include.html" args="disable_fast_preview=True"/>
% if settings.FEATURES.get('ENABLE_COURSEWARE_SEARCH'): % if settings.FEATURES.get('ENABLE_COURSEWARE_SEARCH'):
<%static:require_module module_name="js/search/course/course_search_factory" class_name="CourseSearchFactory"> <%static:require_module module_name="course_search/js/course_search_factory" class_name="CourseSearchFactory">
var courseId = $('.courseware-results').data('courseId'); var courseId = $('.courseware-results').data('courseId');
CourseSearchFactory(courseId); CourseSearchFactory(courseId);
</%static:require_module> </%static:require_module>
......
...@@ -28,12 +28,6 @@ from openedx.core.djangolib.markup import HTML, Text ...@@ -28,12 +28,6 @@ from openedx.core.djangolib.markup import HTML, Text
<%static:include path="dashboard/${template_name}.underscore" /> <%static:include path="dashboard/${template_name}.underscore" />
</script> </script>
% endfor % endfor
% for template_name in ["dashboard_search_item", "dashboard_search_results", "search_loading", "search_error"]:
<script type="text/template" id="${template_name}-tpl">
<%static:include path="search/${template_name}.underscore" />
</script>
% endfor
</%block> </%block>
<%block name="js_extra"> <%block name="js_extra">
...@@ -49,7 +43,7 @@ from openedx.core.djangolib.markup import HTML, Text ...@@ -49,7 +43,7 @@ from openedx.core.djangolib.markup import HTML, Text
}); });
</script> </script>
% if settings.FEATURES.get('ENABLE_DASHBOARD_SEARCH'): % if settings.FEATURES.get('ENABLE_DASHBOARD_SEARCH'):
<%static:require_module module_name="js/search/dashboard/dashboard_search_factory" class_name="DashboardSearchFactory"> <%static:require_module module_name="course_search/js/dashboard/dashboard_search_factory" class_name="DashboardSearchFactory">
DashboardSearchFactory(); DashboardSearchFactory();
</%static:require_module> </%static:require_module>
% endif % endif
......
""" """
Defines URLs for the course experience. Defines URLs for course bookmarks.
""" """
from django.conf.urls import url from django.conf.urls import url
......
(function(define) { (function(define) {
'use strict';
define([ define([
'underscore',
'backbone', 'backbone',
'js/search/base/models/search_result' 'course_search/js/models/search_result'
], function(Backbone, SearchResult) { ], function(_, Backbone, SearchResult) {
'use strict';
return Backbone.Collection.extend({ return Backbone.Collection.extend({
model: SearchResult, model: SearchResult,
...@@ -26,7 +27,9 @@ ...@@ -26,7 +27,9 @@
}, },
performSearch: function(searchTerm) { performSearch: function(searchTerm) {
this.fetchXhr && this.fetchXhr.abort(); if (this.fetchXhr) {
this.fetchXhr.abort();
}
this.searchTerm = searchTerm || ''; this.searchTerm = searchTerm || '';
this.resetState(); this.resetState();
this.fetchXhr = this.fetch({ this.fetchXhr = this.fetch({
...@@ -36,17 +39,19 @@ ...@@ -36,17 +39,19 @@
page_index: 0 page_index: 0
}, },
type: 'POST', type: 'POST',
success: function(self, xhr) { success: function(self) {
self.trigger('search'); self.trigger('search');
}, },
error: function(self, xhr) { error: function(self) {
self.trigger('error'); self.trigger('error');
} }
}); });
}, },
loadNextPage: function() { loadNextPage: function() {
this.fetchXhr && this.fetchXhr.abort(); if (this.fetchXhr) {
this.fetchXhr.abort();
}
this.fetchXhr = this.fetch({ this.fetchXhr = this.fetch({
data: { data: {
search_string: this.searchTerm, search_string: this.searchTerm,
...@@ -54,11 +59,11 @@ ...@@ -54,11 +59,11 @@
page_index: this.page + 1 page_index: this.page + 1
}, },
type: 'POST', type: 'POST',
success: function(self, xhr) { success: function(self) {
self.page += 1; self.page += 1; // eslint-disable-line no-param-reassign
self.trigger('next'); self.trigger('next');
}, },
error: function(self, xhr) { error: function(self) {
self.trigger('error'); self.trigger('error');
}, },
add: true, add: true,
...@@ -68,7 +73,9 @@ ...@@ -68,7 +73,9 @@
}, },
cancelSearch: function() { cancelSearch: function() {
this.fetchXhr && this.fetchXhr.abort(); if (this.fetchXhr) {
this.fetchXhr.abort();
}
this.resetState(); this.resetState();
}, },
...@@ -101,4 +108,4 @@ ...@@ -101,4 +108,4 @@
}); });
}); });
})(define || RequireJS.define); }(define || RequireJS.define));
(function(define) { (function(define) {
'use strict'; 'use strict';
define(['backbone', 'js/search/base/routers/search_router', 'js/search/course/views/search_form', define([
'js/search/base/collections/search_collection', 'js/search/course/views/search_results_view'], 'underscore', 'backbone', 'course_search/js/search_router', 'course_search/js/views/search_form',
function(Backbone, SearchRouter, CourseSearchForm, SearchCollection, SearchResultsView) { 'course_search/js/collections/search_collection', 'course_search/js/views/course_search_results_view'
],
function(_, Backbone, SearchRouter, CourseSearchForm, SearchCollection, CourseSearchResultsView) {
return function(courseId) { return function(courseId) {
var router = new SearchRouter(); var router = new SearchRouter();
var form = new CourseSearchForm(); var form = new CourseSearchForm();
var collection = new SearchCollection([], {courseId: courseId}); var collection = new SearchCollection([], {courseId: courseId});
var results = new SearchResultsView({collection: collection}); var results = new CourseSearchResultsView({collection: collection});
var dispatcher = _.clone(Backbone.Events); var dispatcher = _.clone(Backbone.Events);
dispatcher.listenTo(router, 'search', function(query) { dispatcher.listenTo(router, 'search', function(query) {
...@@ -44,4 +46,4 @@ ...@@ -44,4 +46,4 @@
}); });
}; };
}); });
})(define || RequireJS.define); }(define || RequireJS.define));
(function(define) { (function(define) {
'use strict'; 'use strict';
define(['backbone', 'js/search/base/routers/search_router', 'js/search/dashboard/views/search_form', define([
'js/search/base/collections/search_collection', 'js/search/dashboard/views/search_results_view'], 'underscore', 'backbone', 'course_search/js/search_router', 'course_search/js/views/search_form',
function(Backbone, SearchRouter, SearchForm, SearchCollection, SearchListView) { 'course_search/js/collections/search_collection', 'course_search/js/views/dashboard_search_results_view'
],
function(_, Backbone, SearchRouter, SearchForm, SearchCollection, SearchListView) {
return function() { return function() {
var router = new SearchRouter(); var router = new SearchRouter();
var form = new SearchForm(); var form = new SearchForm({
el: $('#dashboard-search-bar')
});
var collection = new SearchCollection([]); var collection = new SearchCollection([]);
var results = new SearchListView({collection: collection}); var results = new SearchListView({collection: collection});
var dispatcher = _.clone(Backbone.Events); var dispatcher = _.clone(Backbone.Events);
...@@ -48,4 +52,4 @@ ...@@ -48,4 +52,4 @@
}); });
}; };
}); });
})(define || RequireJS.define); }(define || RequireJS.define));
(function(define) { (function(define) {
define(['backbone'], function(Backbone) { 'use strict';
'use strict';
define(['backbone'], function(Backbone) {
return Backbone.Model.extend({ return Backbone.Model.extend({
defaults: { defaults: {
location: [], location: [],
...@@ -11,4 +11,4 @@ ...@@ -11,4 +11,4 @@
} }
}); });
}); });
})(define || RequireJS.define); }(define || RequireJS.define));
(function(define) { (function(define) {
define(['backbone'], function(Backbone) { 'use strict';
'use strict';
define(['backbone'], function(Backbone) {
return Backbone.Router.extend({ return Backbone.Router.extend({
routes: { routes: {
'search/:query': 'search' 'search/:query': 'search'
...@@ -11,4 +11,4 @@ ...@@ -11,4 +11,4 @@
} }
}); });
}); });
})(define || RequireJS.define); }(define || RequireJS.define));
...@@ -5,17 +5,16 @@ define([ ...@@ -5,17 +5,16 @@ define([
'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers',
'common/js/spec_helpers/page_helpers', 'common/js/spec_helpers/page_helpers',
'common/js/spec_helpers/template_helpers', 'common/js/spec_helpers/template_helpers',
'js/search/base/models/search_result', 'course_search/js/models/search_result',
'js/search/base/collections/search_collection', 'course_search/js/collections/search_collection',
'js/search/base/routers/search_router', 'course_search/js/search_router',
'js/search/course/views/search_item_view', 'course_search/js/views/search_form',
'js/search/dashboard/views/search_item_view', 'course_search/js/views/search_item_view',
'js/search/course/views/search_form', 'course_search/js/views/course_search_results_view',
'js/search/dashboard/views/search_form', 'course_search/js/views/dashboard_search_results_view',
'js/search/course/views/search_results_view', 'course_search/js/course_search_factory',
'js/search/dashboard/views/search_results_view', 'course_search/js/dashboard_search_factory',
'js/search/course/course_search_factory', 'text!course_search/templates/course_search_item.underscore'
'js/search/dashboard/dashboard_search_factory'
], function( ], function(
$, $,
Backbone, Backbone,
...@@ -26,14 +25,13 @@ define([ ...@@ -26,14 +25,13 @@ define([
SearchResult, SearchResult,
SearchCollection, SearchCollection,
SearchRouter, SearchRouter,
CourseSearchItemView, SearchForm,
DashSearchItemView, SearchItemView,
CourseSearchForm,
DashSearchForm,
CourseSearchResultsView, CourseSearchResultsView,
DashSearchResultsView, DashSearchResultsView,
CourseSearchFactory, CourseSearchFactory,
DashboardSearchFactory DashboardSearchFactory,
courseSearchItemTemplate
) { ) {
'use strict'; 'use strict';
...@@ -86,7 +84,6 @@ define([ ...@@ -86,7 +84,6 @@ define([
it('sends a request and parses the json result', function() { it('sends a request and parses the json result', function() {
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this);
this.collection.performSearch('search string');
var response = { var response = {
total: 2, total: 2,
access_denied_count: 1, access_denied_count: 1,
...@@ -99,6 +96,7 @@ define([ ...@@ -99,6 +96,7 @@ define([
} }
}] }]
}; };
this.collection.performSearch('search string');
AjaxHelpers.respondWithJson(requests, response); AjaxHelpers.respondWithJson(requests, response);
expect(this.onSearch).toHaveBeenCalled(); expect(this.onSearch).toHaveBeenCalled();
...@@ -221,12 +219,7 @@ define([ ...@@ -221,12 +219,7 @@ define([
describe('SearchItemView', function() { describe('SearchItemView', function() {
function beforeEachHelper(SearchItemView) { beforeEach(function() {
TemplateHelpers.installTemplates([
'templates/search/course_search_item',
'templates/search/dashboard_search_item'
]);
this.model = new SearchResult({ this.model = new SearchResult({
location: ['section', 'subsection', 'unit'], location: ['section', 'subsection', 'unit'],
content_type: 'Video', content_type: 'Video',
...@@ -243,31 +236,37 @@ define([ ...@@ -243,31 +236,37 @@ define([
url: 'path/to/content' url: 'path/to/content'
}); });
this.item = new SearchItemView({model: this.model}); this.item = new SearchItemView({
model: this.model,
template: courseSearchItemTemplate
});
this.item.render(); this.item.render();
this.seqItem = new SearchItemView({model: this.seqModel}); this.seqItem = new SearchItemView({
model: this.seqModel,
template: courseSearchItemTemplate
});
this.seqItem.render(); this.seqItem.render();
} });
function rendersItem() { it('rendersItem', function() {
expect(this.item.$el).toHaveAttr('role', 'region'); expect(this.item.$el).toHaveAttr('role', 'region');
expect(this.item.$el).toHaveAttr('aria-label', 'search result'); expect(this.item.$el).toHaveAttr('aria-label', 'search result');
expect(this.item.$el).toContainElement('a[href="' + this.model.get('url') + '"]'); expect(this.item.$el).toContainElement('a[href="' + this.model.get('url') + '"]');
expect(this.item.$el.find('.result-type')).toContainHtml(this.model.get('content_type')); expect(this.item.$el.find('.result-type')).toContainHtml(this.model.get('content_type'));
expect(this.item.$el.find('.result-excerpt')).toContainHtml(this.model.get('excerpt')); expect(this.item.$el.find('.result-excerpt')).toContainHtml(this.model.get('excerpt'));
expect(this.item.$el.find('.result-location')).toContainHtml('section ▸ subsection ▸ unit'); expect(this.item.$el.find('.result-location')).toContainHtml('section ▸ subsection ▸ unit');
} });
function rendersSequentialItem() { it('rendersSequentialItem', function() {
expect(this.seqItem.$el).toHaveAttr('role', 'region'); expect(this.seqItem.$el).toHaveAttr('role', 'region');
expect(this.seqItem.$el).toHaveAttr('aria-label', 'search result'); expect(this.seqItem.$el).toHaveAttr('aria-label', 'search result');
expect(this.seqItem.$el).toContainElement('a[href="' + this.seqModel.get('url') + '"]'); expect(this.seqItem.$el).toContainElement('a[href="' + this.seqModel.get('url') + '"]');
expect(this.seqItem.$el.find('.result-type')).toBeEmpty(); expect(this.seqItem.$el.find('.result-type')).toBeEmpty();
expect(this.seqItem.$el.find('.result-excerpt')).toBeEmpty(); expect(this.seqItem.$el.find('.result-excerpt')).toBeEmpty();
expect(this.seqItem.$el.find('.result-location')).toContainHtml('section ▸ subsection'); expect(this.seqItem.$el.find('.result-location')).toContainHtml('section ▸ subsection');
} });
function logsSearchItemViewEvent() { it('logsSearchItemViewEvent', function() {
this.model.collection = new SearchCollection([this.model], {course_id: 'edx101'}); this.model.collection = new SearchCollection([this.model], {course_id: 'edx101'});
this.item.render(); this.item.render();
// Mock the redirect call // Mock the redirect call
...@@ -277,27 +276,6 @@ define([ ...@@ -277,27 +276,6 @@ define([
expect(this.item.redirect).toHaveBeenCalled(); expect(this.item.redirect).toHaveBeenCalled();
this.item.$el.trigger('click'); this.item.$el.trigger('click');
expect(this.item.redirect).toHaveBeenCalled(); expect(this.item.redirect).toHaveBeenCalled();
}
describe('CourseSearchItemView', function() {
beforeEach(function() {
beforeEachHelper.call(this, CourseSearchItemView);
});
it('renders items correctly', rendersItem);
it('renders Sequence items correctly', rendersSequentialItem);
it('logs view event', logsSearchItemViewEvent);
});
describe('DashSearchItemView', function() {
beforeEach(function() {
beforeEachHelper.call(this, DashSearchItemView);
});
it('renders items correctly', rendersItem);
it('renders Sequence items correctly', rendersSequentialItem);
it('displays course name in breadcrumbs', function() {
expect(this.seqItem.$el.find('.result-course-name')).toContainHtml(this.model.get('course_name'));
});
it('logs view event', logsSearchItemViewEvent);
}); });
}); });
...@@ -315,7 +293,7 @@ define([ ...@@ -315,7 +293,7 @@ define([
$('.search-field').val(term); $('.search-field').val(term);
this.form.doSearch(term); this.form.doSearch(term);
expect(this.onSearch).toHaveBeenCalledWith($.trim(term)); expect(this.onSearch).toHaveBeenCalledWith($.trim(term));
expect($('.search-field').val()).toEqual(term); expect($('.search-field').val()).toEqual(term.trim());
expect($('.search-field')).toHaveClass('is-active'); expect($('.search-field')).toHaveClass('is-active');
expect($('.search-button')).toBeHidden(); expect($('.search-button')).toBeHidden();
expect($('.cancel-button')).toBeVisible(); expect($('.cancel-button')).toBeVisible();
...@@ -350,26 +328,12 @@ define([ ...@@ -350,26 +328,12 @@ define([
expect($('.search-button')).toBeVisible(); expect($('.search-button')).toBeVisible();
} }
describe('CourseSearchForm', function() { describe('SearchForm', function() {
beforeEach(function() {
loadFixtures('js/fixtures/search/course_search_form.html');
this.form = new CourseSearchForm();
this.onClear = jasmine.createSpy('onClear');
this.onSearch = jasmine.createSpy('onSearch');
this.form.on('clear', this.onClear);
this.form.on('search', this.onSearch);
});
it('trims input string', trimsInputString);
it('handles calls to doSearch', doesSearch);
it('triggers a search event and changes to active state', triggersSearchEvent);
it('clears search when clicking on cancel button', clearsSearchOnCancel);
it('clears search when search box is empty', clearsSearchOnEmpty);
});
describe('DashSearchForm', function() {
beforeEach(function() { beforeEach(function() {
loadFixtures('js/fixtures/search/dashboard_search_form.html'); loadFixtures('course_search/fixtures/course_search_form.html');
this.form = new DashSearchForm(); this.form = new SearchForm({
el: '#courseware-search-bar'
});
this.onClear = jasmine.createSpy('onClear'); this.onClear = jasmine.createSpy('onClear');
this.onSearch = jasmine.createSpy('onSearch'); this.onSearch = jasmine.createSpy('onSearch');
this.form.on('clear', this.onClear); this.form.on('clear', this.onClear);
...@@ -401,7 +365,9 @@ define([ ...@@ -401,7 +365,9 @@ define([
function returnsToContent() { function returnsToContent() {
this.resultsView.clear(); this.resultsView.clear();
expect(this.resultsView.$contentElement).toHaveCss({'display': this.contentElementDisplayValue}); expect(this.resultsView.$contentElement).toHaveCss({
display: this.contentElementDisplayValue
});
expect(this.resultsView.$el).toBeHidden(); expect(this.resultsView.$el).toBeHidden();
expect(this.resultsView.$el).toBeEmpty(); expect(this.resultsView.$el).toBeEmpty();
} }
...@@ -467,28 +433,14 @@ define([ ...@@ -467,28 +433,14 @@ define([
expect(this.resultsView.$el.find('a.search-load-next .icon')).toBeHidden(); expect(this.resultsView.$el.find('a.search-load-next .icon')).toBeHidden();
this.resultsView.loadNext(); this.resultsView.loadNext();
// toBeVisible does not work with inline // toBeVisible does not work with inline
expect(this.resultsView.$el.find('a.search-load-next .icon')).toHaveCss({'display': 'inline'}); expect(this.resultsView.$el.find('a.search-load-next .icon')).toHaveCss({
display: 'inline'
});
this.resultsView.renderNext(); this.resultsView.renderNext();
expect(this.resultsView.$el.find('a.search-load-next .icon')).toBeHidden(); expect(this.resultsView.$el.find('a.search-load-next .icon')).toBeHidden();
} }
function beforeEachHelper(SearchResultsView) { function beforeEachHelper(SearchResultsView) {
appendSetFixtures(
'<div class="courseware-results"></div>' +
'<section id="course-content"></section>' +
'<section id="dashboard-search-results"></section>' +
'<section id="my-courses" tabindex="-1"></section>'
);
TemplateHelpers.installTemplates([
'templates/search/course_search_item',
'templates/search/dashboard_search_item',
'templates/search/course_search_results',
'templates/search/dashboard_search_results',
'templates/search/search_loading',
'templates/search/search_error'
]);
var MockCollection = Backbone.Collection.extend({ var MockCollection = Backbone.Collection.extend({
hasNextPage: function() {}, hasNextPage: function() {},
latestModelsCount: 0, latestModelsCount: 0,
...@@ -497,6 +449,14 @@ define([ ...@@ -497,6 +449,14 @@ define([
return SearchCollection.prototype.latestModels.apply(this, arguments); return SearchCollection.prototype.latestModels.apply(this, arguments);
} }
}); });
appendSetFixtures(
'<div class="courseware-results"></div>' +
'<section id="course-content"></section>' +
'<section id="dashboard-search-results"></section>' +
'<section id="my-courses" tabindex="-1"></section>'
);
this.collection = new MockCollection(); this.collection = new MockCollection();
this.resultsView = new SearchResultsView({collection: this.collection}); this.resultsView = new SearchResultsView({collection: this.collection});
} }
...@@ -599,13 +559,13 @@ define([ ...@@ -599,13 +559,13 @@ define([
$('.cancel-button').trigger('click'); $('.cancel-button').trigger('click');
AjaxHelpers.skipResetRequest(requests); AjaxHelpers.skipResetRequest(requests);
// there should be no results // there should be no results
expect(this.$contentElement).toHaveCss({'display': this.contentElementDisplayValue}); expect(this.$contentElement).toHaveCss({display: this.contentElementDisplayValue});
expect(this.$searchResults).toBeHidden(); expect(this.$searchResults).toBeHidden();
} }
function clearsResults() { function clearsResults() {
$('.cancel-button').trigger('click'); $('.cancel-button').trigger('click');
expect(this.$contentElement).toHaveCss({'display': this.contentElementDisplayValue}); expect(this.$contentElement).toHaveCss({display: this.contentElementDisplayValue});
expect(this.$searchResults).toBeHidden(); expect(this.$searchResults).toBeHidden();
} }
...@@ -624,13 +584,14 @@ define([ ...@@ -624,13 +584,14 @@ define([
} }
}] }]
}; };
var body;
$('.search-field').val('query'); $('.search-field').val('query');
$('.search-button').trigger('click'); $('.search-button').trigger('click');
AjaxHelpers.respondWithJson(requests, response); AjaxHelpers.respondWithJson(requests, response);
expect(this.$searchResults.find('li').length).toEqual(1); expect(this.$searchResults.find('li').length).toEqual(1);
expect($('.search-load-next')).toBeVisible(); expect($('.search-load-next')).toBeVisible();
$('.search-load-next').trigger('click'); $('.search-load-next').trigger('click');
var body = requests[1].requestBody; body = requests[1].requestBody;
expect(body).toContain('search_string=query'); expect(body).toContain('search_string=query');
expect(body).toContain('page_index=1'); expect(body).toContain('page_index=1');
AjaxHelpers.respondWithJson(requests, response); AjaxHelpers.respondWithJson(requests, response);
...@@ -644,27 +605,14 @@ define([ ...@@ -644,27 +605,14 @@ define([
expect(requests[0].requestBody).toContain('search_string=query'); expect(requests[0].requestBody).toContain('search_string=query');
} }
function loadTemplates() {
TemplateHelpers.installTemplates([
'templates/search/course_search_item',
'templates/search/dashboard_search_item',
'templates/search/search_loading',
'templates/search/search_error',
'templates/search/course_search_results',
'templates/search/dashboard_search_results'
]);
}
describe('CourseSearchApp', function() { describe('CourseSearchApp', function() {
beforeEach(function() { beforeEach(function() {
loadFixtures('js/fixtures/search/course_search_form.html'); var courseId = 'a/b/c';
loadFixtures('course_search/fixtures/course_search_form.html');
appendSetFixtures( appendSetFixtures(
'<div class="courseware-results"></div>' + '<div class="courseware-results"></div>' +
'<section id="course-content"></section>' '<section id="course-content"></section>'
); );
loadTemplates.call(this);
var courseId = 'a/b/c';
CourseSearchFactory(courseId); CourseSearchFactory(courseId);
spyOn(Backbone.history, 'navigate'); spyOn(Backbone.history, 'navigate');
this.$contentElement = $('#course-content'); this.$contentElement = $('#course-content');
...@@ -688,12 +636,11 @@ define([ ...@@ -688,12 +636,11 @@ define([
describe('DashSearchApp', function() { describe('DashSearchApp', function() {
beforeEach(function() { beforeEach(function() {
loadFixtures('js/fixtures/search/dashboard_search_form.html'); loadFixtures('course_search/fixtures/dashboard_search_form.html');
appendSetFixtures( appendSetFixtures(
'<section id="dashboard-search-results"></section>' + '<section id="dashboard-search-results"></section>' +
'<section id="my-courses" tabindex="-1"></section>' '<section id="my-courses" tabindex="-1"></section>'
); );
loadTemplates.call(this);
DashboardSearchFactory(); DashboardSearchFactory();
spyOn(Backbone.history, 'navigate'); spyOn(Backbone.history, 'navigate');
......
(function(define) { (function(define) {
define([ 'use strict';
'js/search/base/views/search_results_view',
'js/search/course/views/search_item_view'
], function(SearchResultsView, CourseSearchItemView) {
'use strict';
define([
'course_search/js/views/search_results_view',
'text!course_search/templates/course_search_results.underscore',
'text!course_search/templates/course_search_item.underscore'
], function(
SearchResultsView,
courseSearchResultsTemplate,
courseSearchItemTemplate
) {
return SearchResultsView.extend({ return SearchResultsView.extend({
el: '.courseware-results', el: '.courseware-results',
contentElement: '#course-content', contentElement: '#course-content',
coursewareResultsWrapperElement: '.courseware-results-wrapper', coursewareResultsWrapperElement: '.courseware-results-wrapper',
resultsTemplateId: '#course_search_results-tpl', resultsTemplate: courseSearchResultsTemplate,
loadingTemplateId: '#search_loading-tpl', itemTemplate: courseSearchItemTemplate,
errorTemplateId: '#search_error-tpl',
events: { events: {
'click .search-load-next': 'loadNext' 'click .search-load-next': 'loadNext'
}, },
SearchItemView: CourseSearchItemView,
clear: function() { clear: function() {
SearchResultsView.prototype.clear.call(this); SearchResultsView.prototype.clear.call(this);
...@@ -31,4 +34,4 @@ ...@@ -31,4 +34,4 @@
}); });
}); });
})(define || RequireJS.define); }(define || RequireJS.define));
(function(define) { (function(define) {
define([ 'use strict';
'js/search/base/views/search_results_view',
'js/search/dashboard/views/search_item_view'
], function(SearchResultsView, DashSearchItemView) {
'use strict';
define([
'course_search/js/views/search_results_view',
'text!course_search/templates/dashboard_search_results.underscore',
'text!course_search/templates/dashboard_search_item.underscore'
], function(
SearchResultsView,
dashboardSearchResultsTemplate,
dashboardSearchItemTemplate
) {
return SearchResultsView.extend({ return SearchResultsView.extend({
el: '#dashboard-search-results', el: '#dashboard-search-results',
contentElement: '#my-courses, #profile-sidebar', contentElement: '#my-courses, #profile-sidebar',
resultsTemplateId: '#dashboard_search_results-tpl', resultsTemplate: dashboardSearchResultsTemplate,
loadingTemplateId: '#search_loading-tpl', itemTemplate: dashboardSearchItemTemplate,
errorTemplateId: '#search_error-tpl',
events: { events: {
'click .search-load-next': 'loadNext', 'click .search-load-next': 'loadNext',
'click .search-back-to-courses': 'backToCourses' 'click .search-back-to-courses': 'backToCourses'
}, },
SearchItemView: DashSearchItemView,
backToCourses: function() { backToCourses: function() {
this.clear(); this.clear();
...@@ -26,4 +28,4 @@ ...@@ -26,4 +28,4 @@
}); });
}); });
})(define || RequireJS.define); }(define || RequireJS.define));
(function(define) { (function(define) {
define(['jquery', 'backbone'], function($, Backbone) { 'use strict';
'use strict';
define(['jquery', 'backbone'], function($, Backbone) {
return Backbone.View.extend({ return Backbone.View.extend({
el: '', el: '',
...@@ -22,19 +22,17 @@ ...@@ -22,19 +22,17 @@
}, },
doSearch: function(term) { doSearch: function(term) {
var trimmed;
if (term) { if (term) {
this.$searchField.val(term); trimmed = term.trim();
} this.$searchField.val(trimmed);
else { } else {
term = this.$searchField.val(); trimmed = this.$searchField.val().trim();
} }
var trimmed = $.trim(term);
if (trimmed) { if (trimmed) {
this.setActiveStyle(); this.setActiveStyle();
this.trigger('search', trimmed); this.trigger('search', trimmed);
} } else {
else {
this.clearSearch(); this.clearSearch();
} }
}, },
...@@ -63,4 +61,4 @@ ...@@ -63,4 +61,4 @@
}); });
}); });
})(define || RequireJS.define); }(define || RequireJS.define));
(function(define) { (function(define) {
'use strict';
define([ define([
'jquery', 'jquery',
'underscore', 'underscore',
'backbone', 'backbone',
'gettext', 'gettext',
'logger' 'logger',
], function($, _, Backbone, gettext, Logger) { 'edx-ui-toolkit/js/utils/html-utils'
'use strict'; ], function($, _, Backbone, gettext, Logger, HtmlUtils) {
return Backbone.View.extend({ return Backbone.View.extend({
tagName: 'li', tagName: 'li',
templateId: '',
className: 'search-results-item', className: 'search-results-item',
attributes: { attributes: {
'role': 'region', role: 'region',
'aria-label': 'search result' 'aria-label': 'search result'
}, },
events: { events: {
'click': 'logSearchItem' click: 'logSearchItem'
}, },
initialize: function() { initialize: function(options) {
this.tpl = _.template($(this.templateId).html()); this.template = options.template;
}, },
render: function() { render: function() {
var data = _.clone(this.model.attributes); var data = _.clone(this.model.attributes);
// Drop the preview text and result type if the search term is found
// in the title/location in the course hierarchy // Drop the preview text and result type if the search term is found
// in the title/location in the course hierarchy
if (this.model.get('content_type') === 'Sequence') { if (this.model.get('content_type') === 'Sequence') {
data.excerpt = ''; data.excerpt = '';
data.content_type = ''; data.content_type = '';
} }
this.$el.html(this.tpl(data)); HtmlUtils.setHtml(this.$el, HtmlUtils.template(this.template)(data));
return this; return this;
}, },
...@@ -47,7 +48,6 @@ ...@@ -47,7 +48,6 @@
}, },
logSearchItem: function(event) { logSearchItem: function(event) {
event.preventDefault();
var self = this; var self = this;
var target = this.model.id; var target = this.model.id;
var link = this.model.get('url'); var link = this.model.get('url');
...@@ -56,10 +56,13 @@ ...@@ -56,10 +56,13 @@
var pageSize = collection.pageSize; var pageSize = collection.pageSize;
var searchTerm = collection.searchTerm; var searchTerm = collection.searchTerm;
var index = collection.indexOf(this.model); var index = collection.indexOf(this.model);
event.preventDefault();
Logger.log('edx.course.search.result_selected', { Logger.log('edx.course.search.result_selected', {
'search_term': searchTerm, search_term: searchTerm,
'result_position': (page * pageSize + index), result_position: (page * pageSize) + index,
'result_link': target result_link: target
}).always(function() { }).always(function() {
self.redirect(link); self.redirect(link);
}); });
...@@ -67,4 +70,4 @@ ...@@ -67,4 +70,4 @@
}); });
}); });
})(define || RequireJS.define); }(define || RequireJS.define));
(function(define) { (function(define) {
'use strict';
define([ define([
'jquery', 'jquery',
'underscore', 'underscore',
'backbone', 'backbone',
'gettext' 'edx-ui-toolkit/js/utils/html-utils',
], function($, _, Backbone, gettext) { 'edx-ui-toolkit/js/utils/string-utils',
'use strict'; 'course_search/js/views/search_item_view',
'text!course_search/templates/search_loading.underscore',
'text!course_search/templates/search_error.underscore'
], function($, _, Backbone, HtmlUtils, StringUtils, SearchItemView, searchLoadingTemplate, searchErrorTemplate) {
return Backbone.View.extend({ return Backbone.View.extend({
// these should be defined by subclasses // these should be defined by subclasses
el: '', el: '',
contentElement: '', contentElement: '',
resultsTemplateId: '', resultsTemplate: null,
loadingTemplateId: '', itemTemplate: null,
errorTemplateId: '', loadingTemplate: searchLoadingTemplate,
errorTemplate: searchErrorTemplate,
events: {}, events: {},
spinner: '.search-load-next .icon', spinner: '.search-load-next .icon',
SearchItemView: function() {},
initialize: function() { initialize: function() {
this.$contentElement = $(this.contentElement); this.$contentElement = $(this.contentElement);
this.resultsTemplate = _.template($(this.resultsTemplateId).html());
this.loadingTemplate = _.template($(this.loadingTemplateId).html());
this.errorTemplate = _.template($(this.errorTemplateId).html());
}, },
render: function() { render: function() {
this.$el.html(this.resultsTemplate({ HtmlUtils.setHtml(this.$el, HtmlUtils.template(this.resultsTemplate)({
totalCount: this.collection.totalCount, totalCount: this.collection.totalCount,
totalCountMsg: this.totalCountMsg(), totalCountMsg: this.totalCountMsg(),
pageSize: this.collection.pageSize, pageSize: this.collection.pageSize,
...@@ -40,10 +41,10 @@ ...@@ -40,10 +41,10 @@
}, },
renderNext: function() { renderNext: function() {
// total count may have changed // total count may have changed
this.$el.find('.search-count').text(this.totalCountMsg()); this.$el.find('.search-count').text(this.totalCountMsg());
this.renderItems(); this.renderItems();
if (! this.collection.hasNextPage()) { if (!this.collection.hasNextPage()) {
this.$el.find('.search-load-next').remove(); this.$el.find('.search-load-next').remove();
} }
this.$el.find(this.spinner).hide(); this.$el.find(this.spinner).hide();
...@@ -52,15 +53,20 @@ ...@@ -52,15 +53,20 @@
renderItems: function() { renderItems: function() {
var latest = this.collection.latestModels(); var latest = this.collection.latestModels();
var items = latest.map(function(result) { var items = latest.map(function(result) {
var item = new this.SearchItemView({model: result}); var item = new SearchItemView({
model: result,
template: this.itemTemplate
});
return item.render().el; return item.render().el;
}, this); }, this);
this.$el.find('ol').append(items); this.$el.find('ol').append(items);
}, },
totalCountMsg: function() { totalCountMsg: function() {
var fmt = ngettext('%s result', '%s results', this.collection.totalCount); var fmt = ngettext('{total_results} result', '{total_results} results', this.collection.totalCount);
return interpolate(fmt, [this.collection.totalCount]); return StringUtils.interpolate(fmt, {
total_results: this.collection.totalCount
});
}, },
clear: function() { clear: function() {
...@@ -75,26 +81,28 @@ ...@@ -75,26 +81,28 @@
showLoadingMessage: function() { showLoadingMessage: function() {
this.doCleanup(); this.doCleanup();
this.$el.html(this.loadingTemplate()); HtmlUtils.setHtml(this.$el, HtmlUtils.template(this.loadingTemplate)());
this.showResults(); this.showResults();
}, },
showErrorMessage: function() { showErrorMessage: function() {
this.$el.html(this.errorTemplate()); HtmlUtils.setHtml(this.$el, HtmlUtils.template(this.errorTemplate)());
this.showResults(); this.showResults();
}, },
doCleanup: function() { doCleanup: function() {
// Empty any loading/error message and empty the el // Empty any loading/error message and empty the el
// Bookmarks share the same container element, So we are doing // Bookmarks share the same container element, So we are doing
// this to ensure that elements are in clean/initial state // this to ensure that elements are in clean/initial state
$('#loading-message').html(''); $('#loading-message').html('');
$('#error-message').html(''); $('#error-message').html('');
this.$el.html(''); this.$el.html('');
}, },
loadNext: function(event) { loadNext: function(event) {
event && event.preventDefault(); if (event) {
event.preventDefault();
}
this.$el.find(this.spinner).show(); this.$el.find(this.spinner).show();
this.trigger('next'); this.trigger('next');
return false; return false;
...@@ -102,4 +110,4 @@ ...@@ -102,4 +110,4 @@
}); });
}); });
})(define || RequireJS.define); }(define || RequireJS.define));
...@@ -29,12 +29,6 @@ from openedx.core.djangoapps.theming import helpers as theming_helpers ...@@ -29,12 +29,6 @@ from openedx.core.djangoapps.theming import helpers as theming_helpers
<%static:include path="dashboard/${template_name}.underscore" /> <%static:include path="dashboard/${template_name}.underscore" />
</script> </script>
% endfor % endfor
% for template_name in ["dashboard_search_item", "dashboard_search_results", "search_loading", "search_error"]:
<script type="text/template" id="${template_name}-tpl">
<%static:include path="search/${template_name}.underscore" />
</script>
% endfor
</%block> </%block>
<%block name="js_extra"> <%block name="js_extra">
...@@ -50,7 +44,7 @@ from openedx.core.djangoapps.theming import helpers as theming_helpers ...@@ -50,7 +44,7 @@ from openedx.core.djangoapps.theming import helpers as theming_helpers
}); });
</script> </script>
% if settings.FEATURES.get('ENABLE_DASHBOARD_SEARCH'): % if settings.FEATURES.get('ENABLE_DASHBOARD_SEARCH'):
<%static:require_module module_name="js/search/dashboard/dashboard_search_factory" class_name="DashboardSearchFactory"> <%static:require_module module_name="course_search/js/dashboard_search_factory" class_name="DashboardSearchFactory">
DashboardSearchFactory(); DashboardSearchFactory();
</%static:require_module> </%static:require_module>
% endif % endif
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment