diff --git a/apps/search/templates/search/personas.html b/apps/search/templates/search/personas.html index 6d906d64d2..72a18bbd6d 100644 --- a/apps/search/templates/search/personas.html +++ b/apps/search/templates/search/personas.html @@ -1,6 +1,12 @@ {% extends "base.html" %} {% from "browse/macros.html" import secondary_categories %} +{% block search_form %} + {% with skip_autofill=True %} + {% include "search.html" %} + {% endwith %} +{% endblock %} + {% block title %} {% if query %} {{ page_title(_('Personas Search Results for {0}')|f(query)) }} diff --git a/apps/search/tests/test_views.py b/apps/search/tests/test_views.py index c4d63853f8..9d9f56d38c 100644 --- a/apps/search/tests/test_views.py +++ b/apps/search/tests/test_views.py @@ -6,12 +6,12 @@ from django.http import QueryDict from django.test import client from mock import Mock -from nose import SkipTest from nose.tools import eq_, nottest from pyquery import PyQuery as pq import amo import amo.tests +from amo.helpers import locale_url from amo.urlresolvers import reverse from search.tests import SphinxTestCase from search import views @@ -266,15 +266,14 @@ class TestWebappSearch(amo.tests.ESTestCase): self.assertTemplateUsed(r, 'search/mobile/results.html') -@nottest class TestAjaxSearch(amo.tests.ESTestCase): @classmethod def setUpClass(cls): - super(TestSearchSuggestions, cls).setUpClass() + super(TestAjaxSearch, cls).setUpClass() cls.setUpIndex() - def assert_ajax_query(self, url, params, addons=[], + def search_addons(self, url, params, addons=[], types=amo.ADDON_SEARCH_TYPES): r = self.client.get('?'.join([url, params])) eq_(r.status_code, 200) @@ -299,85 +298,119 @@ class TestAjaxSearch(amo.tests.ESTestCase): class TestBaseAjaxSearch(TestAjaxSearch): - def assert_ajax_query(self, params, addons=[]): - super(TestBaseAjaxSearch, self).assert_ajax_query( + def search_addons(self, params, addons=[]): + self.refresh() + super(TestBaseAjaxSearch, self).search_addons( reverse('search.ajax'), params, addons) def test_ajax_search_by_id(self): addon = Addon.objects.get(id=4) - self.assert_ajax_query('q=4', [addon]) + self.search_addons('q=4', [addon]) def test_ajax_search_by_bad_id(self): - self.assert_ajax_query('q=999', []) + self.search_addons('q=999', []) def test_ajax_search_unreviewed_by_id(self): addon = Addon.objects.get(id=4) addon.update(status=amo.STATUS_UNREVIEWED) - self.assert_ajax_query('q=999', []) + self.search_addons('q=999', []) def test_ajax_search_lite_reviewed_by_id(self): addon = Addon.objects.get(id=4) addon.update(status=amo.STATUS_LITE) - self.assert_ajax_query('q=4', [addon]) + self.search_addons('q=4', [addon]) addon.update(status=amo.STATUS_LITE_AND_NOMINATED) - self.assert_ajax_query('q=4', [addon]) + self.search_addons('q=4', [addon]) def test_ajax_search_user_disabled_by_id(self): addon = Addon.objects.get(id=1) eq_(addon.disabled_by_user, True) - self.assert_ajax_query('q=1', []) + self.search_addons('q=1', []) def test_ajax_search_admin_disabled_by_id(self): addon = Addon.objects.get(id=2) eq_(addon.status, amo.STATUS_DISABLED) - self.assert_ajax_query('q=1', []) + self.search_addons('q=1', []) def test_ajax_search_personas_by_id(self): addon = Addon.objects.get(id=4) addon.update(type=amo.ADDON_PERSONA) Persona.objects.create(persona_id=4, addon_id=4) - self.assert_ajax_query('q=4', [addon]) + self.search_addons('q=4', [addon]) def test_ajax_search_char_limit(self): - self.assert_ajax_query('q=ad', []) + self.search_addons('q=ad', []) def test_ajax_search_by_name(self): + from nose import SkipTest raise SkipTest - # Exclude the following: - # 1 (user-disabled), 2 (admin-disabled), 3 (unreviewed). - self.assert_ajax_query('q=add', - list(Addon.objects.filter(id__in=[4, 5, 6]))) + self.search_addons('q=add', list(Addon.objects.reviewed())) def test_ajax_search_by_bad_name(self): - self.assert_ajax_query('q=some+filthy+bad+word', []) + self.search_addons('q=some+filthy+bad+word', []) class TestSearchSuggestions(TestAjaxSearch): def setUp(self): - super(TestSearchSuggestions, self).setUp() self.url = reverse('search.suggestions') - Addon.objects.get(id=4).update(type=amo.ADDON_WEBAPP) + amo.tests.addon_factory(name='addon webapp', type=amo.ADDON_WEBAPP) + amo.tests.addon_factory(name='addon persona', type=amo.ADDON_PERSONA) + amo.tests.addon_factory(name='addon persona', type=amo.ADDON_PERSONA, + disabled_by_user=True, status=amo.STATUS_NULL) self.refresh() - def assert_ajax_query(self, params, addons=[], - types=amo.ADDON_SEARCH_TYPES): - super(TestBaseAjaxSearch, self).assert_ajax_query( + def search_addons(self, params, addons=[], + types=views.AddonSuggestionsAjax.types): + super(TestSearchSuggestions, self).search_addons( self.url, params, addons, types) + def search_applications(self, params, apps=[]): + r = self.client.get('?'.join([self.url, params])) + eq_(r.status_code, 200) + data = json.loads(r.content) + + data = sorted(data, key=lambda x: x['id']) + apps = sorted(apps, key=lambda x: x.id) + + eq_(len(data), len(apps)) + for got, expected in zip(data, apps): + eq_(int(got['id']), expected.id) + eq_(got['name'], '%s Add-ons' % unicode(expected.pretty)) + eq_(got['url'], locale_url(expected.short)) + eq_(got['cls'], 'app ' + expected.short) + def test_get(self): r = self.client.get(self.url) eq_(r.status_code, 200) def test_addons(self): - addons = (Addon.objects.reviewed().exclude(type=amo.ADDON_WEBAPP) - .filter(disabled_by_user=False)) - self.assert_ajax_query('q=add', list(addons)) - self.assert_ajax_query('q=add&cat=all', list(addons)) + addons = (Addon.objects.reviewed() + .filter(disabled_by_user=False, + type__in=views.AddonSuggestionsAjax.types)) + self.search_addons('q=add', list(addons)) + self.search_addons('q=add&cat=all', list(addons)) + + def test_personas(self): + personas = (Addon.objects.reviewed() + .filter(type=amo.ADDON_PERSONA, disabled_by_user=False)) + self.search_addons('q=add&cat=personas', list(personas), + types=[amo.ADDON_PERSONA]) + self.search_addons('q=persona&cat=all', []) def test_webapps(self): - apps = (Addon.objects.reviewed().filter(type=amo.ADDON_WEBAPP) - .filter(disabled_by_user=False)) - self.assert_ajax_query('q=add&cat=apps', list(apps), - types=[amo.ADDON_WEBAPP]) + apps = (Addon.objects.reviewed() + .filter(type=amo.ADDON_WEBAPP, disabled_by_user=False)) + self.search_addons('q=add&cat=apps', list(apps), + types=[amo.ADDON_WEBAPP]) + + def test_applications(self): + self.search_applications('', []) + self.search_applications('q=firefox', [amo.FIREFOX]) + self.search_applications('q=thunder', [amo.THUNDERBIRD]) + self.search_applications('q=monkey', [amo.SEAMONKEY]) + self.search_applications('q=sun', [amo.SUNBIRD]) + self.search_applications('q=bird', [amo.THUNDERBIRD, amo.SUNBIRD]) + self.search_applications('q=mobile', [amo.MOBILE]) + self.search_applications('q=mozilla', []) diff --git a/apps/search/views.py b/apps/search/views.py index e23aaa7a25..8053fc8fde 100644 --- a/apps/search/views.py +++ b/apps/search/views.py @@ -323,6 +323,10 @@ class AddonSuggestionsAjax(BaseAjaxSearch): amo.ADDON_DICT, amo.ADDON_SEARCH, amo.ADDON_LPAPP] +class PersonaSuggestionsAjax(BaseAjaxSearch): + types = [amo.ADDON_PERSONA] + + class WebappSuggestionsAjax(BaseAjaxSearch): types = [amo.ADDON_WEBAPP] @@ -339,12 +343,13 @@ def ajax_search(request): @json_view def ajax_search_suggestions(request): - # TODO(cvan): Tests will come when I know this is what fligtar wants. results = [] q = request.GET.get('q') if q and (q.isdigit() or (not q.isdigit() and len(q) > 2)): q_ = q.lower() + cat = request.GET.get('cat', 'all') + # Applications. for a in amo.APP_USAGE: if q_ in unicode(a.pretty).lower(): @@ -358,8 +363,14 @@ def ajax_search_suggestions(request): # Categories. cats = (Category.objects .filter(Q(application=request.APP.id) | - Q(type=amo.ADDON_SEARCH)) - .exclude(type=amo.ADDON_WEBAPP)) + Q(type=amo.ADDON_SEARCH))) + if cat == 'personas': + cats = cats.filter(type=amo.ADDON_PERSONA) + elif cat == 'apps': + cats = cats.filter(type=amo.ADDON_WEBAPP) + else: + cats = cats.exclude(type__in=[amo.ADDON_PERSONA, amo.ADDON_WEBAPP]) + for c in cats: if not c.name: continue @@ -373,10 +384,13 @@ def ajax_search_suggestions(request): 'cls': 'cat' }) - if request.GET.get('cat') == 'apps': - results += WebappSuggestionsAjax(request).items - else: - results += AddonSuggestionsAjax(request).items + suggestions = { + 'all': AddonSuggestionsAjax, + 'personas': PersonaSuggestionsAjax, + 'apps': WebappSuggestionsAjax, + }.get(cat, AddonSuggestionsAjax) + + results += suggestions(request).items return results diff --git a/media/css/zamboni/amo_headerfooter.css b/media/css/zamboni/amo_headerfooter.css index 834c55861b..1e2753449b 100644 --- a/media/css/zamboni/amo_headerfooter.css +++ b/media/css/zamboni/amo_headerfooter.css @@ -103,6 +103,7 @@ body { /** Search */ div.header-search { + position: relative; margin: 0; width: 380px; clear: right; diff --git a/media/js/impala/ajaxcache.js b/media/js/impala/ajaxcache.js index 5e8cf2df2c..29c13b966f 100644 --- a/media/js/impala/ajaxcache.js +++ b/media/js/impala/ajaxcache.js @@ -31,6 +31,24 @@ $.ajaxCache = function(o) { ajaxFailure: $.noop, // Callback upon failure of Ajax request. }, o); + if (parseFloat(jQuery.fn.jquery) < 1.5) { + // jqXHR objects allow Deferred methods as of jQuery 1.5. Some of our + // old pages are stuck on jQuery 1.4, so hopefully this'll disappear + // sooner than later. + return $.ajax({ + url: o.url, + type: o.method, + data: o.data, + success: function(data) { + o.newItems(data, data); + o.ajaxSuccess(data, items); + }, + errors: function(data) { + o.ajaxFailure(data); + } + }); + } + var cache = z.AjaxCache(o.url + ':' + o.type), args = JSON.stringify(o.data), $self = this, @@ -55,12 +73,8 @@ $.ajaxCache = function(o) { if (!objEqual(data, cache.previous.data)) { items = data; } - if (o.newItems) { - o.newItems(data, items); - } - if (o.ajaxSuccess) { - o.ajaxSuccess(data, items); - } + o.newItems(data, items); + o.ajaxSuccess(data, items); }); // Optional failure callback. diff --git a/settings.py b/settings.py index 2da48bda19..4a9c0867b7 100644 --- a/settings.py +++ b/settings.py @@ -441,6 +441,7 @@ MINIFY_BUNDLES = { 'css/zamboni/tags.css', 'css/zamboni/tabs.css', 'css/impala/formset.less', + 'css/impala/suggestions.less', ), 'zamboni/impala': ( 'css/impala/base.css', @@ -582,7 +583,12 @@ MINIFY_BUNDLES = { 'js/global/menu.js', # Password length and strength - 'js/zamboni/password-strength.js' + 'js/zamboni/password-strength.js', + + # Search suggestions + 'js/impala/forms.js', + 'js/impala/ajaxcache.js', + 'js/impala/suggestions.js', ), 'impala': ( 'js/lib/jquery-1.6.4.js', diff --git a/templates/search.html b/templates/search.html index d158438493..b81a53d53b 100644 --- a/templates/search.html +++ b/templates/search.html @@ -2,13 +2,20 @@