Files
edx-platform/lms/static/js/spec/search/search_spec.js
2016-08-05 15:29:20 -04:00

744 lines
34 KiB
JavaScript

define([
'jquery',
'backbone',
'logger',
'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers',
'common/js/spec_helpers/page_helpers',
'common/js/spec_helpers/template_helpers',
'js/search/base/models/search_result',
'js/search/base/collections/search_collection',
'js/search/base/routers/search_router',
'js/search/course/views/search_item_view',
'js/search/dashboard/views/search_item_view',
'js/search/course/views/search_form',
'js/search/dashboard/views/search_form',
'js/search/course/views/search_results_view',
'js/search/dashboard/views/search_results_view',
'js/search/course/course_search_factory',
'js/search/dashboard/dashboard_search_factory'
], function(
$,
Backbone,
Logger,
AjaxHelpers,
PageHelpers,
TemplateHelpers,
SearchResult,
SearchCollection,
SearchRouter,
CourseSearchItemView,
DashSearchItemView,
CourseSearchForm,
DashSearchForm,
CourseSearchResultsView,
DashSearchResultsView,
CourseSearchFactory,
DashboardSearchFactory
) {
'use strict';
describe('Search', function() {
beforeEach(function() {
PageHelpers.preventBackboneChangingUrl();
});
describe('SearchResult', function() {
beforeEach(function() {
this.result = new SearchResult();
});
it('has properties', function() {
expect(this.result.get('location')).toBeDefined();
expect(this.result.get('content_type')).toBeDefined();
expect(this.result.get('excerpt')).toBeDefined();
expect(this.result.get('url')).toBeDefined();
});
});
describe('SearchCollection', function() {
beforeEach(function() {
this.collection = new SearchCollection();
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 without a course ID', function() {
var collection = new SearchCollection([]);
spyOn($, 'ajax');
collection.performSearch('search string');
expect($.ajax.calls.mostRecent().args[0].url).toEqual('/search/');
});
it('sends a request with course ID', function() {
var collection = new SearchCollection([], {courseId: 'edx101'});
spyOn($, 'ajax');
collection.performSearch('search string');
expect($.ajax.calls.mostRecent().args[0].url).toEqual('/search/edx101');
});
it('sends a request and parses the json result', function() {
var requests = AjaxHelpers.requests(this);
this.collection.performSearch('search string');
var response = {
total: 2,
access_denied_count: 1,
results: [{
data: {
location: ['section', 'subsection', 'unit'],
url: '/some/url/to/content',
content_type: 'text',
excerpt: 'this is a short excerpt'
}
}]
};
AjaxHelpers.respondWithJson(requests, response);
expect(this.onSearch).toHaveBeenCalled();
expect(this.collection.totalCount).toEqual(1);
expect(this.collection.latestModelsCount).toEqual(1);
expect(this.collection.accessDeniedCount).toEqual(1);
expect(this.collection.page).toEqual(0);
expect(this.collection.first().attributes).toEqual(response.results[0].data);
});
it('handles errors', function() {
var requests = AjaxHelpers.requests(this);
this.collection.performSearch('search string');
AjaxHelpers.respondWithError(requests, 500);
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 searchString = 'search string';
var response = {total: 52, results: []};
this.collection.performSearch(searchString);
AjaxHelpers.respondWithJson(requests, response);
this.collection.loadNextPage();
AjaxHelpers.respondWithJson(requests, response);
spyOn($, 'ajax');
this.collection.loadNextPage();
expect($.ajax.calls.mostRecent().args[0].url).toEqual(this.collection.url);
expect($.ajax.calls.mostRecent().args[0].data.search_string).toEqual(searchString);
expect($.ajax.calls.mostRecent().args[0].data.page_size).toEqual(this.collection.pageSize);
expect($.ajax.calls.mostRecent().args[0].data.page_index).toEqual(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('aborts any previous request', function() {
var requests = AjaxHelpers.requests(this);
var response = {total: 35, results: []};
this.collection.performSearch('old search');
this.collection.performSearch('new search');
AjaxHelpers.skipResetRequest(requests);
AjaxHelpers.respondWithJson(requests, response);
expect(this.onSearch.calls.count()).toEqual(1);
this.collection.performSearch('old search');
this.collection.cancelSearch();
AjaxHelpers.skipResetRequest(requests);
expect(this.onSearch.calls.count()).toEqual(1);
this.collection.loadNextPage();
this.collection.loadNextPage();
AjaxHelpers.skipResetRequest(requests);
AjaxHelpers.respondWithJson(requests, response);
expect(this.onNext.calls.count()).toEqual(1);
});
describe('reset state', function() {
beforeEach(function() {
this.collection.page = 2;
this.collection.totalCount = 35;
this.collection.latestModelsCount = 5;
});
it('resets state when performing new search', function() {
this.collection.performSearch('search string');
expect(this.collection.models.length).toEqual(0);
expect(this.collection.page).toEqual(0);
expect(this.collection.totalCount).toEqual(0);
expect(this.collection.latestModelsCount).toEqual(0);
});
it('resets state when canceling a search', function() {
this.collection.cancelSearch();
expect(this.collection.models.length).toEqual(0);
expect(this.collection.page).toEqual(0);
expect(this.collection.totalCount).toEqual(0);
expect(this.collection.latestModelsCount).toEqual(0);
});
});
});
describe('SearchRouter', function() {
beforeEach(function() {
this.router = new SearchRouter();
this.onSearch = jasmine.createSpy('onSearch');
this.router.on('search', this.onSearch);
});
it('has a search route', function() {
expect(this.router.routes['search/:query']).toEqual('search');
});
it('triggers a search event', function() {
var query = 'mercury';
this.router.search(query);
expect(this.onSearch).toHaveBeenCalledWith(query);
});
});
describe('SearchItemView', function() {
function beforeEachHelper(SearchItemView) {
TemplateHelpers.installTemplates([
'templates/search/course_search_item',
'templates/search/dashboard_search_item'
]);
this.model = new SearchResult({
location: ['section', 'subsection', 'unit'],
content_type: 'Video',
course_name: 'Course Name',
excerpt: 'A short excerpt.',
url: 'path/to/content'
});
this.seqModel = new SearchResult({
location: ['section', 'subsection'],
content_type: 'Sequence',
course_name: 'Course Name',
excerpt: 'A short excerpt.',
url: 'path/to/content'
});
this.item = new SearchItemView({model: this.model});
this.item.render();
this.seqItem = new SearchItemView({model: this.seqModel});
this.seqItem.render();
}
function rendersItem() {
expect(this.item.$el).toHaveAttr('role', 'region');
expect(this.item.$el).toHaveAttr('aria-label', 'search result');
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-excerpt')).toContainHtml(this.model.get('excerpt'));
expect(this.item.$el.find('.result-location')).toContainHtml('section ▸ subsection ▸ unit');
}
function rendersSequentialItem() {
expect(this.seqItem.$el).toHaveAttr('role', 'region');
expect(this.seqItem.$el).toHaveAttr('aria-label', 'search result');
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-excerpt')).toBeEmpty();
expect(this.seqItem.$el.find('.result-location')).toContainHtml('section ▸ subsection');
}
function logsSearchItemViewEvent() {
this.model.collection = new SearchCollection([this.model], {course_id: 'edx101'});
this.item.render();
// Mock the redirect call
spyOn(this.item, 'redirect').and.callFake(function() {});
spyOn(Logger, 'log').and.returnValue($.Deferred().resolve());
this.item.$el.find('a').trigger('click');
expect(this.item.redirect).toHaveBeenCalled();
this.item.$el.trigger('click');
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);
});
});
describe('SearchForm', function() {
function trimsInputString() {
var term = ' search string ';
$('.search-field').val(term);
$('form').trigger('submit');
expect(this.onSearch).toHaveBeenCalledWith($.trim(term));
}
function doesSearch() {
var term = ' search string ';
$('.search-field').val(term);
this.form.doSearch(term);
expect(this.onSearch).toHaveBeenCalledWith($.trim(term));
expect($('.search-field').val()).toEqual(term);
expect($('.search-field')).toHaveClass('is-active');
expect($('.search-button')).toBeHidden();
expect($('.cancel-button')).toBeVisible();
}
function triggersSearchEvent() {
var term = 'search string';
$('.search-field').val(term);
$('form').trigger('submit');
expect(this.onSearch).toHaveBeenCalledWith(term);
expect($('.search-field')).toHaveClass('is-active');
expect($('.search-button')).toBeHidden();
expect($('.cancel-button')).toBeVisible();
}
function clearsSearchOnCancel() {
$('.search-field').val('search string');
$('.search-button').trigger('click');
$('.cancel-button').trigger('click');
expect($('.search-field')).not.toHaveClass('is-active');
expect($('.search-button')).toBeVisible();
expect($('.cancel-button')).toBeHidden();
expect($('.search-field')).toHaveValue('');
}
function clearsSearchOnEmpty() {
$('.search-field').val('');
$('form').trigger('submit');
expect(this.onClear).toHaveBeenCalled();
expect($('.search-field')).not.toHaveClass('is-active');
expect($('.cancel-button')).toBeHidden();
expect($('.search-button')).toBeVisible();
}
describe('CourseSearchForm', 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() {
loadFixtures('js/fixtures/search/dashboard_search_form.html');
this.form = new DashSearchForm();
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('SearchResultsView', function() {
function showsLoadingMessage() {
this.resultsView.showLoadingMessage();
expect(this.resultsView.$contentElement).toBeHidden();
expect(this.resultsView.$el).toBeVisible();
expect(this.resultsView.$el).not.toBeEmpty();
}
function showsErrorMessage() {
this.resultsView.showErrorMessage();
expect(this.resultsView.$contentElement).toBeHidden();
expect(this.resultsView.$el).toBeVisible();
expect(this.resultsView.$el).not.toBeEmpty();
}
function returnsToContent() {
this.resultsView.clear();
expect(this.resultsView.$contentElement).toHaveCss({'display': this.contentElementDisplayValue});
expect(this.resultsView.$el).toBeHidden();
expect(this.resultsView.$el).toBeEmpty();
}
function showsNoResultsMessage() {
this.collection.reset();
this.resultsView.render();
expect(this.resultsView.$el).toContainHtml('no results');
expect(this.resultsView.$el.find('ol')).not.toExist();
}
function rendersSearchResults() {
var searchResults = [{
location: ['section', 'subsection', 'unit'],
url: '/some/url/to/content',
content_type: 'text',
course_name: '',
excerpt: 'this is a short excerpt'
}];
this.collection.set(searchResults);
this.collection.latestModelsCount = 1;
this.collection.totalCount = 1;
this.resultsView.render();
expect(this.resultsView.$el.find('ol')[0]).toExist();
expect(this.resultsView.$el.find('li').length).toEqual(1);
expect(this.resultsView.$el).toContainHtml('Search Results');
expect(this.resultsView.$el).toContainHtml('this is a short excerpt');
this.collection.set(searchResults);
this.collection.totalCount = 2;
this.resultsView.renderNext();
expect(this.resultsView.$el.find('.search-count')).toContainHtml('2');
expect(this.resultsView.$el.find('li').length).toEqual(2);
}
function showsMoreResultsLink() {
this.collection.totalCount = 123;
this.collection.hasNextPage = function() { return true; };
this.resultsView.render();
expect(this.resultsView.$el.find('a.search-load-next')[0]).toExist();
this.collection.totalCount = 123;
this.collection.hasNextPage = function() { return false; };
this.resultsView.render();
expect(this.resultsView.$el.find('a.search-load-next')[0]).not.toExist();
}
function triggersNextPageEvent() {
var onNext = jasmine.createSpy('onNext');
this.resultsView.on('next', onNext);
this.collection.totalCount = 123;
this.collection.hasNextPage = function() { return true; };
this.resultsView.render();
this.resultsView.$el.find('a.search-load-next').click();
expect(onNext).toHaveBeenCalled();
}
function showsLoadMoreSpinner() {
this.collection.totalCount = 123;
this.collection.hasNextPage = function() { return true; };
this.resultsView.render();
expect(this.resultsView.$el.find('a.search-load-next .icon')).toBeHidden();
this.resultsView.loadNext();
// toBeVisible does not work with inline
expect(this.resultsView.$el.find('a.search-load-next .icon')).toHaveCss({'display': 'inline'});
this.resultsView.renderNext();
expect(this.resultsView.$el.find('a.search-load-next .icon')).toBeHidden();
}
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({
hasNextPage: function() {},
latestModelsCount: 0,
pageSize: 20,
latestModels: function() {
return SearchCollection.prototype.latestModels.apply(this, arguments);
}
});
this.collection = new MockCollection();
this.resultsView = new SearchResultsView({collection: this.collection});
}
describe('CourseSearchResultsView', function() {
beforeEach(function() {
beforeEachHelper.call(this, CourseSearchResultsView);
this.contentElementDisplayValue = 'table-cell';
});
it('shows loading message', showsLoadingMessage);
it('shows error message', showsErrorMessage);
it('returns to content', returnsToContent);
it('shows a message when there are no results', showsNoResultsMessage);
it('renders search results', rendersSearchResults);
it('shows a link to load more results', showsMoreResultsLink);
it('triggers an event for next page', triggersNextPageEvent);
it('shows a spinner when loading more results', showsLoadMoreSpinner);
});
describe('DashSearchResultsView', function() {
beforeEach(function() {
beforeEachHelper.call(this, DashSearchResultsView);
this.contentElementDisplayValue = 'block';
});
it('shows loading message', showsLoadingMessage);
it('shows error message', showsErrorMessage);
it('returns to content', returnsToContent);
it('shows a message when there are no results', showsNoResultsMessage);
it('renders search results', rendersSearchResults);
it('shows a link to load more results', showsMoreResultsLink);
it('triggers an event for next page', triggersNextPageEvent);
it('shows a spinner when loading more results', showsLoadMoreSpinner);
it('returns back to courses', function() {
var onReset = jasmine.createSpy('onReset');
this.resultsView.on('reset', onReset);
this.resultsView.render();
expect(this.resultsView.$el.find('a.search-back-to-courses')).toExist();
this.resultsView.$el.find('.search-back-to-courses').click();
expect(onReset).toHaveBeenCalled();
expect(this.resultsView.$contentElement).toBeVisible();
expect(this.resultsView.$el).toBeHidden();
});
});
});
describe('SearchApp', function() {
function showsLoadingMessage() {
$('.search-field').val('search string');
$('.search-button').trigger('click');
expect(this.$contentElement).toBeHidden();
expect(this.$searchResults).toBeVisible();
expect(this.$searchResults).not.toBeEmpty();
}
function performsSearch() {
var requests = AjaxHelpers.requests(this);
$('.search-field').val('search string');
$('.search-button').trigger('click');
AjaxHelpers.respondWithJson(requests, {
total: 1337,
access_denied_count: 12,
results: [{
data: {
location: ['section', 'subsection', 'unit'],
url: '/some/url/to/content',
content_type: 'text',
excerpt: 'this is a short excerpt',
course_name: ''
}
}]
});
expect($('.search-info')).toExist();
expect($('.search-result-list')).toBeVisible();
expect(this.$searchResults.find('li').length).toEqual(1);
}
function showsErrorMessage() {
var requests = AjaxHelpers.requests(this);
$('.search-field').val('search string');
$('.search-button').trigger('click');
AjaxHelpers.respondWithError(requests, 500, {});
expect(this.$searchResults).toContainHtml('There was an error');
}
function updatesNavigationHistory() {
$('.search-field').val('edx');
$('.search-button').trigger('click');
expect(Backbone.history.navigate.calls.mostRecent().args[0]).toContain('search/edx');
$('.cancel-button').trigger('click');
expect(Backbone.history.navigate.calls.argsFor(1)[0]).toBe('');
}
function cancelsSearchRequest() {
var requests = AjaxHelpers.requests(this);
// send search request to server
$('.search-field').val('search string');
$('.search-button').trigger('click');
// cancel the search and then skip the request that was marked as reset
$('.cancel-button').trigger('click');
AjaxHelpers.skipResetRequest(requests);
// there should be no results
expect(this.$contentElement).toHaveCss({'display': this.contentElementDisplayValue});
expect(this.$searchResults).toBeHidden();
}
function clearsResults() {
$('.cancel-button').trigger('click');
expect(this.$contentElement).toHaveCss({'display': this.contentElementDisplayValue});
expect(this.$searchResults).toBeHidden();
}
function loadsNextPage() {
var requests = AjaxHelpers.requests(this);
var response = {
total: 1337,
access_denied_count: 12,
results: [{
data: {
location: ['section', 'subsection', 'unit'],
url: '/some/url/to/content',
content_type: 'text',
excerpt: 'this is a short excerpt',
course_name: ''
}
}]
};
$('.search-field').val('query');
$('.search-button').trigger('click');
AjaxHelpers.respondWithJson(requests, response);
expect(this.$searchResults.find('li').length).toEqual(1);
expect($('.search-load-next')).toBeVisible();
$('.search-load-next').trigger('click');
var body = requests[1].requestBody;
expect(body).toContain('search_string=query');
expect(body).toContain('page_index=1');
AjaxHelpers.respondWithJson(requests, response);
expect(this.$searchResults.find('li').length).toEqual(2);
}
function navigatesToSearch() {
var requests = AjaxHelpers.requests(this);
Backbone.history.start();
Backbone.history.loadUrl('search/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() {
beforeEach(function() {
loadFixtures('js/fixtures/search/course_search_form.html');
appendSetFixtures(
'<div class="courseware-results"></div>' +
'<section id="course-content"></section>'
);
loadTemplates.call(this);
var courseId = 'a/b/c';
CourseSearchFactory(courseId);
spyOn(Backbone.history, 'navigate');
this.$contentElement = $('#course-content');
this.contentElementDisplayValue = 'table-cell';
this.$searchResults = $('.courseware-results');
});
afterEach(function() {
Backbone.history.stop();
});
it('shows loading message on search', showsLoadingMessage);
it('performs search', performsSearch);
it('shows an error message', showsErrorMessage);
it('updates navigation history', updatesNavigationHistory);
it('cancels search request', cancelsSearchRequest);
it('clears results', clearsResults);
it('loads next page', loadsNextPage);
it('navigates to search', navigatesToSearch);
});
describe('DashSearchApp', function() {
beforeEach(function() {
loadFixtures('js/fixtures/search/dashboard_search_form.html');
appendSetFixtures(
'<section id="dashboard-search-results"></section>' +
'<section id="my-courses" tabindex="-1"></section>'
);
loadTemplates.call(this);
DashboardSearchFactory();
spyOn(Backbone.history, 'navigate');
this.$contentElement = $('#my-courses');
this.contentElementDisplayValue = 'block';
this.$searchResults = $('#dashboard-search-results');
});
afterEach(function() {
Backbone.history.stop();
});
it('shows loading message on search', showsLoadingMessage);
it('performs search', performsSearch);
it('shows an error message', showsErrorMessage);
it('updates navigation history', updatesNavigationHistory);
it('cancels search request', cancelsSearchRequest);
it('clears results', clearsResults);
it('loads next page', loadsNextPage);
it('navigates to search', navigatesToSearch);
it('returns to course list', function() {
var requests = AjaxHelpers.requests(this);
$('.search-field').val('search string');
$('.search-button').trigger('click');
AjaxHelpers.respondWithJson(requests, {
total: 1337,
access_denied_count: 12,
results: [{
data: {
location: ['section', 'subsection', 'unit'],
url: '/some/url/to/content',
content_type: 'text',
excerpt: 'this is a short excerpt',
course_name: ''
}
}]
});
expect($('.search-back-to-courses')).toExist();
$('.search-back-to-courses').trigger('click');
expect(this.$contentElement).toBeVisible();
expect(this.$searchResults).toBeHidden();
expect(this.$searchResults).toBeEmpty();
});
});
});
});
});