diff --git a/app/modules/discover/components/discover-home-order-by/discover-home-order-by.controller.coffee b/app/modules/discover/components/discover-home-order-by/discover-home-order-by.controller.coffee new file mode 100644 index 00000000..ebecd4ab --- /dev/null +++ b/app/modules/discover/components/discover-home-order-by/discover-home-order-by.controller.coffee @@ -0,0 +1,50 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# File: discover-home-order-by.controller.coffee +### + +class DiscoverHomeOrderByController + @.$inject = [ + '$translate' + ] + + constructor: (@translate) -> + @.is_open = false + + @.texts = { + week: @translate.instant('DISCOVER.FILTERS.WEEK'), + month: @translate.instant('DISCOVER.FILTERS.MONTH'), + year: @translate.instant('DISCOVER.FILTERS.YEAR'), + all: @translate.instant('DISCOVER.FILTERS.ALL_TIME') + } + + currentText: () -> + return @.texts[@.currentOrderBy] + + open: () -> + @.is_open = true + + close: () -> + @.is_open = false + + orderBy: (type) -> + @.currentOrderBy = type + @.is_open = false + + @.onChange({orderBy: @.currentOrderBy}) + +angular.module("taigaDiscover").controller("DiscoverHomeOrderBy", DiscoverHomeOrderByController) diff --git a/app/modules/discover/components/discover-home-order-by/discover-home-order-by.controller.spec.coffee b/app/modules/discover/components/discover-home-order-by/discover-home-order-by.controller.spec.coffee new file mode 100644 index 00000000..db71dbb9 --- /dev/null +++ b/app/modules/discover/components/discover-home-order-by/discover-home-order-by.controller.spec.coffee @@ -0,0 +1,96 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# File: discover-home-order-by.controller.spec.coffee +### + +describe "DiscoverHomeOrderBy", -> + $provide = null + $controller = null + mocks = {} + + _mockTranslate = -> + mocks.translate = { + instant: sinon.stub() + } + + $provide.value("$translate", mocks.translate) + + _mocks = -> + module (_$provide_) -> + $provide = _$provide_ + + _mockTranslate() + + return null + + _inject = -> + inject (_$controller_) -> + $controller = _$controller_ + + _setup = -> + _mocks() + _inject() + + beforeEach -> + module "taigaDiscover" + + _setup() + + it "get current search text", () -> + mocks.translate.instant.withArgs('DISCOVER.FILTERS.WEEK').returns('week') + mocks.translate.instant.withArgs('DISCOVER.FILTERS.MONTH').returns('month') + + ctrl = $controller("DiscoverHomeOrderBy") + + ctrl.currentOrderBy = 'week' + text = ctrl.currentText() + + expect(text).to.be.equal('week') + + ctrl.currentOrderBy = 'month' + text = ctrl.currentText() + + expect(text).to.be.equal('month') + + it "open", () -> + ctrl = $controller("DiscoverHomeOrderBy") + + ctrl.is_open = false + + ctrl.open() + + expect(ctrl.is_open).to.be.true + + it "close", () -> + ctrl = $controller("DiscoverHomeOrderBy") + + ctrl.is_open = true + + ctrl.close() + + expect(ctrl.is_open).to.be.false + + it "order by", () -> + ctrl = $controller("DiscoverHomeOrderBy") + ctrl.onChange = sinon.spy() + + ctrl.orderBy('week') + + + expect(ctrl.currentOrderBy).to.be.equal('week') + expect(ctrl.is_open).to.be.false + expect(ctrl.onChange).to.have.been.calledWith({orderBy: 'week'}) diff --git a/app/modules/discover/components/discover-home-order-by/discover-home-order-by.directive.coffee b/app/modules/discover/components/discover-home-order-by/discover-home-order-by.directive.coffee new file mode 100644 index 00000000..2def5e12 --- /dev/null +++ b/app/modules/discover/components/discover-home-order-by/discover-home-order-by.directive.coffee @@ -0,0 +1,37 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# File: discover-home-order-by.directive.coffee +### + +DiscoverHomeOrderByDirective = () -> + link = (scope, el, attrs) -> + + return { + controller: "DiscoverHomeOrderBy", + controllerAs: "vm", + bindToController: true, + templateUrl: "discover/components/discover-home-order-by/discover-home-order-by.html", + scope: { + currentOrderBy: "=orderBy", + onChange: "&" + }, + link: link + } + +DiscoverHomeOrderByDirective.$inject = [] + +angular.module("taigaDiscover").directive("tgDiscoverHomeOrderBy", DiscoverHomeOrderByDirective) diff --git a/app/modules/discover/components/discover-home-order-by/discover-home-order-by.jade b/app/modules/discover/components/discover-home-order-by/discover-home-order-by.jade new file mode 100644 index 00000000..fbaaa04c --- /dev/null +++ b/app/modules/discover/components/discover-home-order-by/discover-home-order-by.jade @@ -0,0 +1,12 @@ +.filter-highlighted(ng-mouseleave="vm.close()") + a.current-filter( + href="#" + ng-click="vm.open()" + ) {{vm.currentText()}} + span.icon-arrow-bottom + + ul.filter-list(ng-if="vm.is_open") + li(ng-click="vm.orderBy('week')") {{ 'DISCOVER.FILTERS.WEEK' | translate }} + li(ng-click="vm.orderBy('month')") {{ 'DISCOVER.FILTERS.MONTH' | translate }} + li(ng-click="vm.orderBy('year')") {{ 'DISCOVER.FILTERS.YEAR' | translate }} + li(ng-click="vm.orderBy('all')") {{ 'DISCOVER.FILTERS.ALL_TIME' | translate }} diff --git a/app/modules/discover/components/discover-search-bar/discover-search-bar.controller.coffee b/app/modules/discover/components/discover-search-bar/discover-search-bar.controller.coffee new file mode 100644 index 00000000..a7ca46c1 --- /dev/null +++ b/app/modules/discover/components/discover-search-bar/discover-search-bar.controller.coffee @@ -0,0 +1,36 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# File: discover-search-bar.controller.coffee +### + +class DiscoverSearchBarController + @.$inject = [ + 'tgDiscoverProjectsService' + ] + + constructor: (@discoverProjectsService) -> + taiga.defineImmutableProperty @, 'projects', () => return @discoverProjectsService.projectsCount + + @discoverProjectsService.fetchStats() + + selectFilter: (filter) -> + @.onChange({filter: filter, q: @.q}) + + submitFilter: -> + @.onChange({filter: @.filter, q: @.q}) + +angular.module("taigaDiscover").controller("DiscoverSearchBar", DiscoverSearchBarController) diff --git a/app/modules/discover/components/discover-search-bar/discover-search-bar.controller.spec.coffee b/app/modules/discover/components/discover-search-bar/discover-search-bar.controller.spec.coffee new file mode 100644 index 00000000..36b5dd4e --- /dev/null +++ b/app/modules/discover/components/discover-search-bar/discover-search-bar.controller.spec.coffee @@ -0,0 +1,72 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# File: doscover-search-bar.controller.spec.coffee +### + +describe "DiscoverSearchBarController", -> + $provide = null + $controller = null + mocks = {} + + _mockDiscoverProjectsService = -> + mocks.discoverProjectsService = { + fetchStats: sinon.spy() + } + + $provide.value('tgDiscoverProjectsService', mocks.discoverProjectsService) + + _inject = -> + inject (_$controller_) -> + $controller = _$controller_ + + _mocks = -> + module (_$provide_) -> + $provide = _$provide_ + + _mockDiscoverProjectsService() + + return null + + _setup = -> + _inject() + + beforeEach -> + module "taigaDiscover" + + _mocks() + _setup() + + it "select filter", () -> + ctrl = $controller("DiscoverSearchBar") + ctrl.onChange = sinon.spy() + ctrl.q = 'query' + + ctrl.selectFilter('text') + + expect(mocks.discoverProjectsService.fetchStats).to.have.been.called; + expect(ctrl.onChange).to.have.been.calledWith(sinon.match({filter: 'text', q: 'query'})); + + it "submit filter", () -> + ctrl = $controller("DiscoverSearchBar") + ctrl.filter = 'all' + ctrl.q = 'query' + ctrl.onChange = sinon.spy() + + ctrl.submitFilter() + + expect(mocks.discoverProjectsService.fetchStats).to.have.been.called; + expect(ctrl.onChange).to.have.been.calledWith(sinon.match({filter: 'all', q: 'query'})); diff --git a/app/modules/discover/components/discover-search-bar/discover-search-bar.directive.coffee b/app/modules/discover/components/discover-search-bar/discover-search-bar.directive.coffee new file mode 100644 index 00000000..9dab0fbc --- /dev/null +++ b/app/modules/discover/components/discover-search-bar/discover-search-bar.directive.coffee @@ -0,0 +1,38 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# File: discover-search.directive.coffee +### + +DiscoverSearchBarDirective = () -> + link = (scope, el, attrs, ctrl) -> + + return { + controller: "DiscoverSearchBar", + controllerAs: "vm" + templateUrl: 'discover/components/discover-search-bar/discover-search-bar.html', + bindToController: true, + scope: { + q: "=" + filter: "=", + onChange: "&" + }, + link: link + } + +DiscoverSearchBarDirective.$inject = [] + +angular.module('taigaDiscover').directive('tgDiscoverSearchBar', DiscoverSearchBarDirective) diff --git a/app/modules/discover/components/discover-search-bar/discover-search-bar.jade b/app/modules/discover/components/discover-search-bar/discover-search-bar.jade new file mode 100644 index 00000000..201c6bca --- /dev/null +++ b/app/modules/discover/components/discover-search-bar/discover-search-bar.jade @@ -0,0 +1,71 @@ +div.discover-header + div.discover-header-inner + + h1.title {{ 'DISCOVER.DISCOVER_TITLE' | translate }} + + p.project-number( + ng-if="vm.projects", + translate="DISCOVER.DISCOVER_SUBTITLE", + translate-values="{ projects: '{{vm.projects}}'}" + translate-interpolation="messageformat" + ) + + form(ng-submit="vm.submitFilter()") + div.searchbox + input( + name="search" + type="text" + placeholder="{{ 'DISCOVER.SEARCH.INPUT_PLACEHOLDER' | translate }}" + ng-model="vm.q" + ) + a.search-button( + ng-click="vm.submitFilter()" + href="#" + title="{{ 'DISCOVER.SEARCH.ACTION_TITLE' | translate }}" + ) + include ../../../../svg/search.svg + + fieldset.searchbox-filters(ng-if="vm.filter") + input( + type='radio' + id="filter-all" + name="filter-search" + ) + label( + for="filter-all" + ng-click="vm.selectFilter('all')" + ng-class="{active: vm.filter == 'all'}", + ) {{ 'DISCOVER.FILTERS.ALL' | translate }} + + input( + type='radio' + id="filter-kanban" + name="filter-search" + ) + label( + for="filter-kanban" + ng-class="{active: vm.filter == 'kanban'}", + ng-click="vm.selectFilter('kanban')" + ) {{ 'DISCOVER.FILTERS.KANBAN' | translate }} + + input( + type='radio' + id="filter-scrum" + name="filter-search" + ) + label( + for="filter-scrum" + ng-class="{active: vm.filter == 'scrum'}", + ng-click="vm.selectFilter('scrum')" + ) {{ 'DISCOVER.FILTERS.SCRUM' | translate }} + + input( + type='radio' + id="filter-people" + name="filter-search" + ) + label( + for="filter-people" + ng-class="{active: vm.filter == 'people'}", + ng-click="vm.selectFilter('people')" + ) {{ 'DISCOVER.FILTERS.PEOPLE' | translate }} diff --git a/app/modules/discover/components/discover-search-bar/discover-search-bar.scss b/app/modules/discover/components/discover-search-bar/discover-search-bar.scss new file mode 100644 index 00000000..a45705b2 --- /dev/null +++ b/app/modules/discover/components/discover-search-bar/discover-search-bar.scss @@ -0,0 +1,54 @@ +.discover-header { + background: url('../images/discover.png') repeat-x bottom left $whitish; + margin-bottom: 2.5rem; + padding: 1rem 1rem 2rem; + text-align: center; + .discover-header-inner { + @include centered; + margin: 0 auto; + } + .title { + @extend %xxlarge; + margin-bottom: 0; + } + .project-number { + @extend %light; + @extend %large; + color: $primary; + } + form { + margin: 0 30%; + position: relative; + @include breakpoint(tablet) { + margin: 0 .5rem; + } + } + input[type="text"] { + background: $white; + border: 0; + padding: 1rem; + width: 100%; + &:focus { + outline-color: $primary-light; + } + &:-webkit-autofill { + background: rgba($primary-dark, .5); + } + } + .search-button { + position: absolute; + right: 1rem; + top: 1rem; + &:hover { + svg { + fill: $primary; + } + } + } + svg { + fill: $gray-light; + height: 1.5rem; + transition: all .2; + width: 1.5rem; + } +} diff --git a/app/modules/discover/components/discover-search-list-header/discover-search-list-header.controller.coffee b/app/modules/discover/components/discover-search-list-header/discover-search-list-header.controller.coffee new file mode 100644 index 00000000..4c6e0c23 --- /dev/null +++ b/app/modules/discover/components/discover-search-list-header/discover-search-list-header.controller.coffee @@ -0,0 +1,46 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# File: discover-search-list-header.controller.coffee +### + +class DiscoverSearchListHeaderController + @.$inject = [] + + constructor: () -> + @.like_is_open = @.orderBy.indexOf('-total_fans') == 0 + @.activity_is_open = @.orderBy.indexOf('-total_activity') == 0 + + openLike: () -> + @.like_is_open = true + @.activity_is_open = false + + @.setOrderBy('-total_fans_last_week') + + openActivity: () -> + @.activity_is_open = true + @.like_is_open = false + + @.setOrderBy('-total_activity_last_week') + + setOrderBy: (type = '') -> + if !type + @.like_is_open = false + @.activity_is_open = false + + @.onChange({orderBy: type}) + +angular.module("taigaDiscover").controller("DiscoverSearchListHeader", DiscoverSearchListHeaderController) diff --git a/app/modules/discover/components/discover-search-list-header/discover-search-list-header.controller.spec.coffee b/app/modules/discover/components/discover-search-list-header/discover-search-list-header.controller.spec.coffee new file mode 100644 index 00000000..bda659f9 --- /dev/null +++ b/app/modules/discover/components/discover-search-list-header/discover-search-list-header.controller.spec.coffee @@ -0,0 +1,117 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# File: discover-search-list-header.controller.spec.coffee +### + +describe "DiscoverSearchListHeader", -> + $provide = null + $controller = null + scope = null + + _inject = -> + inject (_$controller_, $rootScope) -> + $controller = _$controller_ + scope = $rootScope.$new() + + _setup = -> + _inject() + + beforeEach -> + module "taigaDiscover" + + _setup() + + it "openLike", () -> + ctrl = $controller("DiscoverSearchListHeader", scope, { + orderBy: '' + }) + + ctrl.like_is_open = false + ctrl.activity_is_open = true + ctrl.setOrderBy = sinon.spy() + + ctrl.openLike() + + expect(ctrl.like_is_open).to.be.true + expect(ctrl.activity_is_open).to.be.false + expect(ctrl.setOrderBy).have.been.calledWith('-total_fans_last_week') + + it "openActivity", () -> + ctrl = $controller("DiscoverSearchListHeader", scope, { + orderBy: '' + }) + + ctrl.activity_is_open = false + ctrl.like_is_open = true + ctrl.setOrderBy = sinon.spy() + + ctrl.openActivity() + + expect(ctrl.activity_is_open).to.be.true + expect(ctrl.like_is_open).to.be.false + expect(ctrl.setOrderBy).have.been.calledWith('-total_activity_last_week') + + it "setOrderBy", () -> + ctrl = $controller("DiscoverSearchListHeader", scope, { + orderBy: '' + }) + + ctrl.onChange = sinon.spy() + + ctrl.setOrderBy("type1") + + expect(ctrl.onChange).to.have.been.calledWith(sinon.match({orderBy: "type1"})) + + it "setOrderBy falsy close the like or activity layer", () -> + ctrl = $controller("DiscoverSearchListHeader", scope, { + orderBy: '' + }) + + ctrl.like_is_open = true + ctrl.activity_is_open = true + + ctrl.onChange = sinon.spy() + + ctrl.setOrderBy() + + expect(ctrl.onChange).to.have.been.calledWith(sinon.match({orderBy: ''})) + expect(ctrl.like_is_open).to.be.false + expect(ctrl.activity_is_open).to.be.false + + it "closed like & activity", () -> + ctrl = $controller("DiscoverSearchListHeader", scope, { + orderBy: '' + }) + + expect(ctrl.like_is_open).to.be.false + expect(ctrl.activity_is_open).to.be.false + + it "open like", () -> + ctrl = $controller("DiscoverSearchListHeader", scope, { + orderBy: '-total_fans' + }) + + expect(ctrl.like_is_open).to.be.true + expect(ctrl.activity_is_open).to.be.false + + it "open activity", () -> + ctrl = $controller("DiscoverSearchListHeader", scope, { + orderBy: '-total_activity' + }) + + expect(ctrl.like_is_open).to.be.false + expect(ctrl.activity_is_open).to.be.true diff --git a/app/modules/discover/components/discover-search-list-header/discover-search-list-header.directive.coffee b/app/modules/discover/components/discover-search-list-header/discover-search-list-header.directive.coffee new file mode 100644 index 00000000..ce344946 --- /dev/null +++ b/app/modules/discover/components/discover-search-list-header/discover-search-list-header.directive.coffee @@ -0,0 +1,37 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# File: discover-search-list-header.directive.coffee +### + +DiscoverSearchListHeaderDirective = () -> + link = (scope, el, attrs) -> + + return { + controller: "DiscoverSearchListHeader", + controllerAs: "vm", + bindToController: true, + templateUrl: "discover/components/discover-search-list-header/discover-search-list-header.html", + scope: { + onChange: "&", + orderBy: "=" + }, + link: link + } + +DiscoverSearchListHeaderDirective.$inject = [] + +angular.module("taigaDiscover").directive("tgDiscoverSearchListHeader", DiscoverSearchListHeaderDirective) diff --git a/app/modules/discover/components/discover-search-list-header/discover-search-list-header.jade b/app/modules/discover/components/discover-search-list-header/discover-search-list-header.jade new file mode 100644 index 00000000..b480a619 --- /dev/null +++ b/app/modules/discover/components/discover-search-list-header/discover-search-list-header.jade @@ -0,0 +1,89 @@ +.discover-results-header + .discover-results-header-inner + .title + include ../../../../svg/search.svg + h2 {{ 'DISCOVER.SEARCH.RESULTS' | translate }} + + .filter-discover-search(ng-mouseleave="vm.toggleClose()") + a.discover-search-filter( + href="#" + ng-click="vm.openLike()" + ng-class="{active: vm.like_is_open}" + ) + include ../../../../svg/like.svg + span {{ 'DISCOVER.MOST_LIKED' | translate }} + a.discover-search-filter( + href="#" + ng-click="vm.openActivity()" + ng-class="{active: vm.activity_is_open}" + ) + include ../../../../svg/activity.svg + span {{ 'DISCOVER.MOST_ACTIVE' | translate }} + + .discover-search-subfilter.most-liked-subfilter(ng-if="vm.like_is_open") + a.results( + ng-if="vm.orderBy" + title="" + href="#", + ng-click="vm.setOrderBy()" + ) {{ 'DISCOVER.FILTERS.CLEAR' | translate }} + + ul.filter-list + li + a( + ng-class="{active: vm.orderBy == '-total_fans_last_week'}", + href="#", + ng-click="vm.setOrderBy('-total_fans_last_week')" + ) {{ 'DISCOVER.FILTERS.WEEK' | translate }} + li + a( + ng-class="{active: vm.orderBy == '-total_fans_last_month'}", + href="#", + ng-click="vm.setOrderBy('-total_fans_last_month')" + ) {{ 'DISCOVER.FILTERS.MONTH' | translate }} + li + a( + ng-class="{active: vm.orderBy == '-total_fans_last_year'}", + href="#", + ng-click="vm.setOrderBy('-total_fans_last_year')" + ) {{ 'DISCOVER.FILTERS.YEAR' | translate }} + li + a( + ng-class="{active: vm.orderBy == '-total_fans'}", + href="#", + ng-click="vm.setOrderBy('-total_fans')" + ) {{ 'DISCOVER.FILTERS.ALL_TIME' | translate }} + + .discover-search-subfilter.most-active-subfilter(ng-if="vm.activity_is_open") + a.results( + ng-if="vm.orderBy" + title="" + href="#", + ng-click="vm.setOrderBy()" + ) {{ 'DISCOVER.FILTERS.CLEAR' | translate }} + + ul.filter-list + li + a( + ng-class="{active: vm.orderBy == '-total_activity_last_week'}", + href="#", + ng-click="vm.setOrderBy('-total_activity_last_week')" + ) {{ 'DISCOVER.FILTERS.WEEK' | translate }} + li + a( + ng-class="{active: vm.orderBy == '-total_activity_last_month'}", + href="#", + ng-click="vm.setOrderBy('-total_activity_last_month')" + ) {{ 'DISCOVER.FILTERS.MONTH' | translate }} + li + a( + ng-class="{active: vm.orderBy == '-total_activity_last_year'}", + href="#", + ng-click="vm.setOrderBy('-total_activity_last_year')" + ) {{ 'DISCOVER.FILTERS.YEAR' | translate }} + li + a( + ng-class="{active: vm.orderBy == '-total_activity'}", + href="#", + ng-click="vm.setOrderBy('-total_activity')" + ) {{ 'DISCOVER.FILTERS.ALL_TIME' | translate }} diff --git a/app/modules/discover/components/discover-search-list-header/discover-search-list-header.scss b/app/modules/discover/components/discover-search-list-header/discover-search-list-header.scss new file mode 100644 index 00000000..d3112c54 --- /dev/null +++ b/app/modules/discover/components/discover-search-list-header/discover-search-list-header.scss @@ -0,0 +1,80 @@ +.discover-results-header { + .discover-results-header-inner { + align-items: center; + display: flex; + justify-content: space-between; + } + svg { + @include svg-size(1.1rem); + fill: $gray-light; + } + .title { + @extend %bold; + @extend %larger; + text-transform: uppercase; + } + h2 { + display: inline-block; + } +} + +.filter-discover-search { + .discover-search-filter { + margin-right: 1rem; + &.active { + color: $primary; + } + } +} + +.discover-search-subfilter { + @include arrow('bottom', $whitish, $whitish, 1, 8); + align-items: center; + background: $whitish; + display: flex; + justify-content: space-between; + position: relative; + &.most-liked-subfilter { + &::after, + &::before { + left: 85%; + } + } + &.most-active-subfilter { + &::after, + &::before { + left: 95%; + } + } + &.ng-enter { + animation: dropdownFade .2s; + } + .results { + @extend %small; + color: $red-light; + display: block; + padding: .5rem 1rem; + transition: all .2s; + &:hover { + color: $red; + } + } + .filter-list { + display: flex; + margin: 0; + margin-left: auto; + a { + display: block; + padding: .5rem 1rem; + transition: all .2s; + &:hover { + background: $gray-light; + color: currentColor; + } + &.active { + background: $primary-light; + color: $whitish; + } + } + } +} diff --git a/app/modules/discover/components/featured-projects/featured-projects.controller.coffee b/app/modules/discover/components/featured-projects/featured-projects.controller.coffee new file mode 100644 index 00000000..9175a524 --- /dev/null +++ b/app/modules/discover/components/featured-projects/featured-projects.controller.coffee @@ -0,0 +1,30 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# File: featured-projects.controller.coffee +### + +class FeaturedProjectsController + @.$inject = [ + "tgDiscoverProjectsService" + ] + + constructor: (@discoverProjectsService) -> + taiga.defineImmutableProperty @, "featured", () => return @discoverProjectsService.featured + + @discoverProjectsService.fetchFeatured() + +angular.module("taigaDiscover").controller("FeaturedProjects", FeaturedProjectsController) diff --git a/app/modules/discover/components/featured-projects/featured-projects.directive.coffee b/app/modules/discover/components/featured-projects/featured-projects.directive.coffee new file mode 100644 index 00000000..0ec079b5 --- /dev/null +++ b/app/modules/discover/components/featured-projects/featured-projects.directive.coffee @@ -0,0 +1,33 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# File: featured-projects.directive.coffee +### + +FeaturedProjectsDirective = () -> + link = (scope, el, attrs) -> + + return { + controller: "FeaturedProjects" + controllerAs: "vm", + templateUrl: "discover/components/featured-projects/featured-projects.html", + scope: {}, + link: link + } + +FeaturedProjectsDirective.$inject = [] + +angular.module("taigaDiscover").directive("tgFeaturedProjects", FeaturedProjectsDirective) diff --git a/app/modules/discover/components/featured-projects/featured-projects.jade b/app/modules/discover/components/featured-projects/featured-projects.jade new file mode 100644 index 00000000..ec1de366 --- /dev/null +++ b/app/modules/discover/components/featured-projects/featured-projects.jade @@ -0,0 +1,52 @@ +.featured-projects(ng-if="vm.featured.size") + h1.title {{ 'DISCOVER.FEATURED' | translate }} + + .featured-projects-inner + .featured-project(tg-repeat="project in vm.featured track by project.get('id')") + .tags-container + .project-tag( + style="background: {{tag.get('color')}}" + title="{{tag.get('name')}}" + tg-repeat="tag in project.get('colorized_tags') track by tag.get('name')" + ) + .project-card-inner + .project-card-header + a.project-card-logo( + href="#" + tg-nav="project:project=project.get('slug')" + title="{{::project.get('name')}}" + ) + img( + tg-project-logo-src="::project" + alt="{{::project.get('name')}}" + ) + h2.project-card-name + a( + href="#" + tg-nav="project:project=project.get('slug')" + title="{{::project.get('name')}}" + ) {{::project.get('name')}} + span.look-for-people( + ng-if="project.get('is_looking_for_people')" + title="{{ ::project.get('looking_for_people_note') }}" + ) + include ../../../../svg/recruit.svg + p.project-card-description {{ ::project.get('description') | limitTo:100 }}{{ ::project.get('description').length < 100 ? '' : '...'}} + .project-card-statistics + span.statistic( + ng-class="{'active': project.get('is_fan')}" + title="{{ 'PROJECT.FANS_COUNTER_TITLE'|translate:{total:project.get('total_fans')||0}:'messageformat' }}" + ) + include ../../../../svg/like.svg + span {{::project.get('total_fans')}} + span.statistic( + ng-class="{'active': project.get('is_watcher')}" + title="{{ 'PROJECT.WATCHERS_COUNTER_TITLE'|translate:{total:project.get('total_watchers')||0}:'messageformat' }}" + ) + include ../../../../svg/eye.svg + span {{::project.get('total_watchers')}} + span.statistic( + title="{{ 'PROJECT.MEMBERS_COUNTER_TITLE'|translate:{total:project.get('members').size||0}:'messageformat' }}" + ) + include ../../../../svg/team.svg + span.statistics-num {{ ::project.get('members').size }} diff --git a/app/modules/discover/components/featured-projects/featured-projects.scss b/app/modules/discover/components/featured-projects/featured-projects.scss new file mode 100644 index 00000000..17e4c703 --- /dev/null +++ b/app/modules/discover/components/featured-projects/featured-projects.scss @@ -0,0 +1,29 @@ +@import '../../../../styles/dependencies/mixins/project-card'; + +.featured-projects { + @include centered; + .title { + @extend %bold; + @extend %larger; + color: $grayer; + text-align: center; + } +} +.featured-projects-inner { + align-items: stretch; + display: flex; + flex-wrap: wrap; + justify-content: space-between; +} + +.featured-project { + @include project-card; + display: flex; + flex-basis: 23%; + @include breakpoint(tablet) { + flex-basis: 45%; + } + @include breakpoint(mobile) { + flex-basis: 100%; + } +} diff --git a/app/modules/discover/components/highlighted/highlighted.directive.coffee b/app/modules/discover/components/highlighted/highlighted.directive.coffee new file mode 100644 index 00000000..3fee80b4 --- /dev/null +++ b/app/modules/discover/components/highlighted/highlighted.directive.coffee @@ -0,0 +1,32 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# File: highlighted.directive.coffee +### + +HighlightedDirective = () -> + return { + templateUrl: "discover/components/highlighted/highlighted.html", + scope: { + loading: "=", + highlighted: "=", + orderBy: "=" + } + } + +HighlightedDirective.$inject = [] + +angular.module("taigaDiscover").directive("tgHighlighted", HighlightedDirective) diff --git a/app/modules/discover/components/highlighted/highlighted.jade b/app/modules/discover/components/highlighted/highlighted.jade new file mode 100644 index 00000000..a7606e77 --- /dev/null +++ b/app/modules/discover/components/highlighted/highlighted.jade @@ -0,0 +1,57 @@ +.highlighted-projects-container + .loading-container( + tg-loading="loading" + ng-show="loading" + ) + .highlighted-project( + tg-repeat="project in highlighted track by project.get('id')" + ng-if="!loading" + ) + a.project-logo( + href="#" + tg-nav="project:project=project.get('slug')" + title="{{::project.get('name')}}" + ) + img( + tg-project-logo-src="::project" + alt="{{::project.get('name')}}" + ) + .project-data-container + .single-project-header + h2.project-title + a( + href="#" + tg-nav="project:project=project.get('slug')" + title="{{::project.get('name')}}" + ) {{::project.get('name')}} + span.look-for-people( + ng-if="project.get('is_looking_for_people')" + title="{{ ::project.get('looking_for_people_note') }}" + ) + include ../../../../svg/recruit.svg + .project-statistics + span.statistic( + ng-class="{'active': project.get('is_fan')}" + title="{{ 'PROJECT.FANS_COUNTER_TITLE'|translate:{total:project.get('total_fans')||0}:'messageformat' }}" + ) + include ../../../../svg/like.svg + span {{::project.get('total_fans')}} + span.statistic( + ng-class="{'active': project.get('is_watcher')}" + title="{{ 'PROJECT.WATCHERS_COUNTER_TITLE'|translate:{total:project.get('total_watchers')||0}:'messageformat' }}" + ) + include ../../../../svg/eye.svg + span {{::project.get('total_watchers')}} + span.statistic( + title="{{ 'PROJECT.MEMBERS_COUNTER_TITLE'|translate:{total:project.get('members').size||0}:'messageformat' }}" + ) + include ../../../../svg/team.svg + span.statistics-num {{ ::project.get('members').size }} + p.project-description {{ ::project.get('description') | limitTo:150 }}{{ ::project.get('description').length < 150 ? '' : '...'}} + + a.view-more-projects.button-green( + ng-if="highlighted" + tg-nav="discover-search" + tg-nav-get-params="{\"order_by\": \"{{orderBy}}\"}" + href="#" + ) {{ 'DISCOVER.VIEW_MORE' | translate }} diff --git a/app/modules/discover/components/highlighted/highlighted.scss b/app/modules/discover/components/highlighted/highlighted.scss new file mode 100644 index 00000000..e1064406 --- /dev/null +++ b/app/modules/discover/components/highlighted/highlighted.scss @@ -0,0 +1,205 @@ +.highlighted { + @include centered; + display: flex; + justify-content: space-around; + margin-bottom: 4rem; + @include breakpoint(tablet) { + flex-direction: column; + tg-most-active { + margin-top: 4rem; + } + } + tg-most-liked, + tg-most-active { + align-content: stretch; + display: flex; + flex: 1; + } + tg-most-liked { + margin-right: 8%; + @include breakpoint(tablet) { + margin-right: 0; + } + } + .most-active, + .most-liked { + align-content: stretch; + display: flex; + flex: 1; + flex-direction: column; + } + .header { + align-items: center; + display: flex; + justify-content: space-between; + margin-bottom: 1rem; + svg { + @include svg-size(1.5rem); + fill: $gray-light; + margin: .5rem; + } + } + .title-wrapper { + align-items: center; + display: flex; + } + .title { + @extend %bold; + @extend %larger; + color: $grayer; + display: inline-block; + margin: 0; + } + tg-highlighted { + display: flex; + flex: 1; + } + .highlighted-projects-container { + display: flex; + flex: 1; + flex-direction: column; + justify-content: flex-start; + } + .loading-container { + margin-top: calc(50% - 1rem); + } + .loading-spinner { + display: block; + margin: 2rem auto; + max-height: 3rem; + max-width: 3rem; + } + .view-more-projects { + margin-top: auto; + width: 100%; + } + .empty-highlighted-project { + border: 2px dashed $whitish; + padding: 2rem; + text-align: center; + svg { + @include svg-size(2rem); + display: block; + fill: $gray-light; + margin: 1rem auto; + } + span { + @extend %light; + color: $gray; + display: block; + } + } +} + + +.filter-highlighted { + position: relative; + .current-filter { + padding: 1rem; + span { + margin-left: .2rem; + position: relative; + top: .2rem; + } + } + .filter-list { + background: $black; + position: absolute; + right: 0; + top: 1.5rem; + &.ng-enter { + animation: dropdownFade .2s ease-in; + } + &.ng-leave { + animation: dropdownFade .2s ease-in; + animation-direction: reverse; + } + } + li { + @extend %small; + color: $white; + cursor: pointer; + min-width: 8rem; + padding: .25rem .5rem; + &:hover { + background: rgba($primary-light, .4); + } + } +} + +.highlighted-project { + align-items: flex-start; + border-bottom: 1px solid $whitish; + display: flex; + flex-basis: 9rem; + min-height: 9rem; + padding: 1.5rem 0; + &:nth-last-child(-n+2) { + border-bottom: 0; + } + .project-logo { + flex-basis: 3rem; + height: auto; + margin-right: 1rem; + width: 3rem; + img { + width: 100%; + } + } + .project-data-container { + flex: 1; + } + .single-project-header { + align-content: center; + display: flex; + justify-content: space-between; + } + .project-title { + @extend %large; + @extend %text; + display: inline-block; + margin-bottom: .5rem; + a { + color: $primary; + &:hover { + color: $primary-light; + } + } + } + .look-for-people { + svg { + @include svg-size(); + fill: $gray-light; + margin-left: .5rem; + } + } + .project-description { + @extend %small; + color: $gray; + margin-bottom: 0; + } + .project-statistics { + display: flex; + flex-basis: 140px; + justify-content: flex-end; + svg { + @include svg-size(.8rem); + fill: $gray-light; + } + .svg-eye-closed { + display: none; + } + } + .statistic { + @extend %small; + color: $gray-light; + display: inline-block; + margin-right: .5rem; + &.active { + color: $primary; + svg { + fill: $primary; + } + } + } +} diff --git a/app/modules/discover/components/most-active/most-active.controller.coffee b/app/modules/discover/components/most-active/most-active.controller.coffee new file mode 100644 index 00000000..6c8676b7 --- /dev/null +++ b/app/modules/discover/components/most-active/most-active.controller.coffee @@ -0,0 +1,49 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# File: most-active.controller.coffee +### + +class MostActiveController + @.$inject = [ + "tgDiscoverProjectsService" + ] + + constructor: (@discoverProjectsService) -> + taiga.defineImmutableProperty @, "highlighted", () => return @discoverProjectsService.mostActive + + @.currentOrderBy = 'week' + @.order_by = @.getOrderBy() + + fetch: () -> + @.loading = true + @.order_by = @.getOrderBy() + + return @discoverProjectsService.fetchMostActive({order_by: @.order_by}).then () => + @.loading = false + + orderBy: (type) -> + @.currentOrderBy = type + + @.fetch() + + getOrderBy: (type) -> + if @.currentOrderBy == 'all' + return '-total_activity' + else + return '-total_activity_last_' + @.currentOrderBy + +angular.module("taigaDiscover").controller("MostActive", MostActiveController) diff --git a/app/modules/discover/components/most-active/most-active.controller.spec.coffee b/app/modules/discover/components/most-active/most-active.controller.spec.coffee new file mode 100644 index 00000000..da7258bc --- /dev/null +++ b/app/modules/discover/components/most-active/most-active.controller.spec.coffee @@ -0,0 +1,79 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# File: most-active.controller.spec.coffee +### + +describe "MostActive", -> + $provide = null + $controller = null + mocks = {} + + _mockDiscoverProjectsService = -> + mocks.discoverProjectsService = { + fetchMostActive: sinon.stub() + } + + $provide.value("tgDiscoverProjectsService", mocks.discoverProjectsService) + + _mocks = -> + module (_$provide_) -> + $provide = _$provide_ + + _mockDiscoverProjectsService() + + return null + + _inject = -> + inject (_$controller_) -> + $controller = _$controller_ + + _setup = -> + _mocks() + _inject() + + beforeEach -> + module "taigaDiscover" + + _setup() + + it "fetch", (done) -> + ctrl = $controller("MostActive") + + ctrl.getOrderBy = sinon.stub().returns('week') + + mockPromise = mocks.discoverProjectsService.fetchMostActive.withArgs(sinon.match({order_by: 'week'})).promise() + + promise = ctrl.fetch() + + expect(ctrl.loading).to.be.true + + mockPromise.resolve() + + promise.finally () -> + expect(ctrl.loading).to.be.false + done() + + + it "order by", () -> + ctrl = $controller("MostActive") + + ctrl.fetch = sinon.spy() + + ctrl.orderBy('month') + + expect(ctrl.fetch).to.have.been.called + expect(ctrl.currentOrderBy).to.be.equal('month') diff --git a/app/modules/discover/components/most-active/most-active.directive.coffee b/app/modules/discover/components/most-active/most-active.directive.coffee new file mode 100644 index 00000000..c3cf6b84 --- /dev/null +++ b/app/modules/discover/components/most-active/most-active.directive.coffee @@ -0,0 +1,34 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# File: most-active.directive.coffee +### + +MostActiveDirective = () -> + link = (scope, el, attrs, ctrl) -> + ctrl.fetch() + + return { + controller: "MostActive" + controllerAs: "vm", + templateUrl: "discover/components/most-active/most-active.html", + scope: {}, + link: link + } + +MostActiveDirective.$inject = [] + +angular.module("taigaDiscover").directive("tgMostActive", MostActiveDirective) diff --git a/app/modules/discover/components/most-active/most-active.jade b/app/modules/discover/components/most-active/most-active.jade new file mode 100644 index 00000000..37e58cdf --- /dev/null +++ b/app/modules/discover/components/most-active/most-active.jade @@ -0,0 +1,18 @@ +.most-active(ng-if="vm.highlighted.size") + .header + .title-wrapper + include ../../../../svg/activity.svg + h1.title {{ 'DISCOVER.MOST_ACTIVE' | translate }} + tg-discover-home-order-by(on-change="vm.orderBy(orderBy)", order-by="vm.currentOrderBy") + + tg-highlighted( + loading="vm.loading", + highlighted="vm.highlighted" + order-by="vm.order_by" + ) + +.empty-highlighted-project( + ng-if="!vm.highlighted.size" +) + include ../../../../svg/activity.svg + span {{ 'DISCOVER.MOST_ACTIVE_EMPTY' | translate }} diff --git a/app/modules/discover/components/most-liked/most-liked.controller.coffee b/app/modules/discover/components/most-liked/most-liked.controller.coffee new file mode 100644 index 00000000..b75e2c9a --- /dev/null +++ b/app/modules/discover/components/most-liked/most-liked.controller.coffee @@ -0,0 +1,49 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# File: msot-liked.controller.coffee +### + +class MostLikedController + @.$inject = [ + "tgDiscoverProjectsService" + ] + + constructor: (@discoverProjectsService) -> + taiga.defineImmutableProperty @, "highlighted", () => return @discoverProjectsService.mostLiked + + @.currentOrderBy = 'week' + @.order_by = @.getOrderBy() + + fetch: () -> + @.loading = true + @.order_by = @.getOrderBy() + + @discoverProjectsService.fetchMostLiked({order_by: @.order_by}).then () => + @.loading = false + + orderBy: (type) -> + @.currentOrderBy = type + + @.fetch() + + getOrderBy: () -> + if @.currentOrderBy == 'all' + return '-total_fans' + else + return '-total_fans_last_' + @.currentOrderBy + +angular.module("taigaDiscover").controller("MostLiked", MostLikedController) diff --git a/app/modules/discover/components/most-liked/most-liked.controller.spec.coffee b/app/modules/discover/components/most-liked/most-liked.controller.spec.coffee new file mode 100644 index 00000000..f00ee3ad --- /dev/null +++ b/app/modules/discover/components/most-liked/most-liked.controller.spec.coffee @@ -0,0 +1,79 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# File: most-liked.controller.spec.coffee +### + +describe "MostLiked", -> + $provide = null + $controller = null + mocks = {} + + _mockDiscoverProjectsService = -> + mocks.discoverProjectsService = { + fetchMostLiked: sinon.stub() + } + + $provide.value("tgDiscoverProjectsService", mocks.discoverProjectsService) + + _mocks = -> + module (_$provide_) -> + $provide = _$provide_ + + _mockDiscoverProjectsService() + + return null + + _inject = -> + inject (_$controller_) -> + $controller = _$controller_ + + _setup = -> + _mocks() + _inject() + + beforeEach -> + module "taigaDiscover" + + _setup() + + it "fetch", (done) -> + ctrl = $controller("MostLiked") + + ctrl.getOrderBy = sinon.stub().returns('week') + + mockPromise = mocks.discoverProjectsService.fetchMostLiked.withArgs(sinon.match({order_by: 'week'})).promise() + + promise = ctrl.fetch() + + expect(ctrl.loading).to.be.true + + mockPromise.resolve() + + promise.finally () -> + expect(ctrl.loading).to.be.false + done() + + + it "order by", () -> + ctrl = $controller("MostLiked") + + ctrl.fetch = sinon.spy() + + ctrl.orderBy('month') + + expect(ctrl.fetch).to.have.been.called + expect(ctrl.currentOrderBy).to.be.equal('month') diff --git a/app/modules/discover/components/most-liked/most-liked.directive.coffee b/app/modules/discover/components/most-liked/most-liked.directive.coffee new file mode 100644 index 00000000..06813cb9 --- /dev/null +++ b/app/modules/discover/components/most-liked/most-liked.directive.coffee @@ -0,0 +1,34 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# File: most-liked.directive.coffee +### + +MostLikedDirective = () -> + link = (scope, el, attrs, ctrl) -> + ctrl.fetch() + + return { + controller: "MostLiked" + controllerAs: "vm", + templateUrl: "discover/components/most-liked/most-liked.html", + scope: {}, + link: link + } + +MostLikedDirective.$inject = [] + +angular.module("taigaDiscover").directive("tgMostLiked", MostLikedDirective) diff --git a/app/modules/discover/components/most-liked/most-liked.jade b/app/modules/discover/components/most-liked/most-liked.jade new file mode 100644 index 00000000..e3967aa0 --- /dev/null +++ b/app/modules/discover/components/most-liked/most-liked.jade @@ -0,0 +1,17 @@ +.most-liked(ng-if="vm.highlighted.size") + .header + .title-wrapper + include ../../../../svg/like.svg + h1.title {{ 'DISCOVER.MOST_LIKED' | translate }} + tg-discover-home-order-by(on-change="vm.orderBy(orderBy)", order-by="vm.currentOrderBy") + tg-highlighted( + loading="vm.loading", + highlighted="vm.highlighted" + order-by="vm.order_by" + ) + +.empty-highlighted-project( + ng-if="!vm.highlighted.size" +) + include ../../../../svg/like.svg + span {{ 'DISCOVER.MOST_LIKED_EMPTY' | translate }} diff --git a/app/modules/discover/discover-home/discover-home.controller.coffee b/app/modules/discover/discover-home/discover-home.controller.coffee new file mode 100644 index 00000000..d9d249d6 --- /dev/null +++ b/app/modules/discover/discover-home/discover-home.controller.coffee @@ -0,0 +1,33 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# File: discover-home.controller.coffee +### + +class DiscoverHomeController + @.$inject = [ + '$tgLocation', + '$tgNavUrls' + ] + + constructor: (@location, @navUrls) -> + + onSubmit: (q) -> + url = @navUrls.resolve('discover-search') + + @location.search('text', q).path(url) + +angular.module("taigaDiscover").controller("DiscoverHome", DiscoverHomeController) diff --git a/app/modules/discover/discover-home/discover-home.controller.spec.coffee b/app/modules/discover/discover-home/discover-home.controller.spec.coffee new file mode 100644 index 00000000..0318c6ba --- /dev/null +++ b/app/modules/discover/discover-home/discover-home.controller.spec.coffee @@ -0,0 +1,71 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# File: doscover-home.controller.spec.coffee +### + +describe "DiscoverHomeController", -> + $provide = null + $controller = null + mocks = {} + + _mockLocation = -> + mocks.location = {} + + $provide.value('$tgLocation', mocks.location) + + _mockNavUrls = -> + mocks.navUrls = {} + + $provide.value('$tgNavUrls', mocks.navUrls) + + _inject = -> + inject (_$controller_) -> + $controller = _$controller_ + + _mocks = -> + module (_$provide_) -> + $provide = _$provide_ + + _mockLocation() + _mockNavUrls() + + return null + + _setup = -> + _inject() + + beforeEach -> + module "taigaDiscover" + + _mocks() + _setup() + + it "onSubmit redirect to discover search", () -> + mocks.navUrls.resolve = sinon.stub().withArgs('discover-search').returns('url') + + pathSpy = sinon.spy() + searchStub = { + path: pathSpy + } + + mocks.location.search = sinon.stub().withArgs('text', 'query').returns(searchStub) + + ctrl = $controller("DiscoverHome") + + ctrl.onSubmit('query') + + expect(pathSpy).to.have.been.calledWith('url'); diff --git a/app/modules/discover/discover-home/discover-home.jade b/app/modules/discover/discover-home/discover-home.jade new file mode 100644 index 00000000..56096cb8 --- /dev/null +++ b/app/modules/discover/discover-home/discover-home.jade @@ -0,0 +1,12 @@ +doctype html + +section.discover + header + tg-discover-search-bar(on-change="vm.onSubmit(q)") + + section.highlighted + tg-most-liked + tg-most-active + + section.featured-projects + tg-featured-projects diff --git a/app/modules/discover/discover-search/discover-search.controller.coffee b/app/modules/discover/discover-search/discover-search.controller.coffee new file mode 100644 index 00000000..0744a9ad --- /dev/null +++ b/app/modules/discover/discover-search/discover-search.controller.coffee @@ -0,0 +1,114 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# File: discover-search.controller.coffee +### + +class DiscoverSearchController + @.$inject = [ + '$routeParams', + 'tgDiscoverProjectsService', + '$route' + ] + + constructor: (@routeParams, @discoverProjectsService, @route) -> + @.page = 1 + + taiga.defineImmutableProperty @, "searchResult", () => return @discoverProjectsService.searchResult + taiga.defineImmutableProperty @, "nextSearchPage", () => return @discoverProjectsService.nextSearchPage + + @.q = @routeParams.text + @.filter = @routeParams.filter || 'all' + @.orderBy = @routeParams['order_by'] || '' + + @.loadingGlobal = false + @.loadingList = false + @.loadingPagination = false + + fetch: () -> + @.page = 1 + + @discoverProjectsService.resetSearchList() + + return @.search() + + fetchByGlobalSearch: () -> + return if @.loadingGlobal + + @.loadingGlobal = true + + @.fetch().then () => @.loadingGlobal = false + + fetchByOrderBy: () -> + return if @.loadingList + + @.loadingList = true + + @.fetch().then () => @.loadingList = false + + showMore: () -> + return if @.loadingPagination + + @.loadingPagination = true + + @.page++ + + return @.search().then () => @.loadingPagination = false + + search: () -> + filter = @.getFilter() + + params = { + page: @.page, + q: @.q, + order_by: @.orderBy + } + + _.assign(params, filter) + + return @discoverProjectsService.fetchSearch(params) + + getFilter: () -> + if @.filter == 'people' + return {is_looking_for_people: true} + else if @.filter == 'scrum' + return {is_backlog_activated: true} + else if @.filter == 'kanban' + return {is_kanban_activated: true} + + return {} + + onChangeFilter: (filter, q) -> + @.filter = filter + @.q = q + + @route.updateParams({ + filter: @.filter, + text: @.q + }) + + @.fetchByGlobalSearch() + + onChangeOrder: (orderBy) -> + @.orderBy = orderBy + + @route.updateParams({ + order_by: orderBy + }) + + @.fetchByOrderBy() + +angular.module("taigaDiscover").controller("DiscoverSearch", DiscoverSearchController) diff --git a/app/modules/discover/discover-search/discover-search.controller.spec.coffee b/app/modules/discover/discover-search/discover-search.controller.spec.coffee new file mode 100644 index 00000000..6c05d661 --- /dev/null +++ b/app/modules/discover/discover-search/discover-search.controller.spec.coffee @@ -0,0 +1,199 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# File: discover-search.controller.spec.coffee +### + +describe "DiscoverSearch", -> + $provide = null + $controller = null + mocks = {} + + _mockRouteParams = -> + mocks.routeParams = {} + + $provide.value("$routeParams", mocks.routeParams) + + _mockRoute = -> + mocks.route = {} + + $provide.value("$route", mocks.route) + + _mockDiscoverProjects = -> + mocks.discoverProjects = { + resetSearchList: sinon.spy(), + fetchSearch: sinon.stub() + } + + mocks.discoverProjects.fetchSearch.promise().resolve() + + $provide.value("tgDiscoverProjectsService", mocks.discoverProjects) + + _mocks = -> + module (_$provide_) -> + $provide = _$provide_ + + _mockRoute() + _mockRouteParams() + _mockDiscoverProjects() + + return null + + _inject = -> + inject (_$controller_) -> + $controller = _$controller_ + + _setup = -> + _mocks() + _inject() + + beforeEach -> + module "taigaDiscover" + + _setup() + + it "initialize search params", () -> + mocks.routeParams.text = 'text' + mocks.routeParams.filter = 'filter' + mocks.routeParams.order_by = 'order' + + ctrl = $controller('DiscoverSearch') + + expect(ctrl.q).to.be.equal('text') + expect(ctrl.filter).to.be.equal('filter') + expect(ctrl.orderBy).to.be.equal('order') + + it "fetch", () -> + ctrl = $controller('DiscoverSearch') + + ctrl.search = sinon.spy() + + ctrl.fetch() + + expect(mocks.discoverProjects.resetSearchList).to.have.been.called + expect(ctrl.search).to.have.been.called + expect(ctrl.page).to.be.equal(1) + + it "showMore", (done) -> + ctrl = $controller('DiscoverSearch') + + ctrl.search = sinon.stub().promise() + + ctrl.showMore().then () -> + expect(ctrl.loadingPagination).to.be.false + + done() + + expect(ctrl.loadingPagination).to.be.true + expect(ctrl.search).to.have.been.called + expect(ctrl.page).to.be.equal(2) + + ctrl.search.resolve() + + it "search", () -> + mocks.discoverProjects.fetchSearch = sinon.stub() + + filter = { + filter: '123' + } + + ctrl = $controller('DiscoverSearch') + + ctrl.page = 1 + ctrl.q = 'text' + ctrl.orderBy = 1 + + ctrl.getFilter = () -> return filter + + params = { + filter: '123', + page: 1, + q: 'text', + order_by: 1 + } + + ctrl.search() + + expect(mocks.discoverProjects.fetchSearch).have.been.calledWith(sinon.match(params)) + + it "get filter", () -> + ctrl = $controller('DiscoverSearch') + + ctrl.filter = 'people' + expect(ctrl.getFilter()).to.be.eql({is_looking_for_people: true}) + + ctrl.filter = 'scrum' + expect(ctrl.getFilter()).to.be.eql({is_backlog_activated: true}) + + ctrl.filter = 'kanban' + expect(ctrl.getFilter()).to.be.eql({is_kanban_activated: true}) + + it "onChangeFilter", () -> + ctrl = $controller('DiscoverSearch') + + mocks.route.updateParams = sinon.stub() + + ctrl.fetchByGlobalSearch = sinon.spy() + + ctrl.onChangeFilter('filter', 'query') + + expect(ctrl.filter).to.be.equal('filter') + expect(ctrl.q).to.be.equal('query') + expect(ctrl.fetchByGlobalSearch).to.have.been.called + expect(mocks.route.updateParams).to.have.been.calledWith(sinon.match({filter: 'filter', text: 'query'})) + + it "onChangeOrder", () -> + ctrl = $controller('DiscoverSearch') + + mocks.route.updateParams = sinon.stub() + + ctrl.fetchByOrderBy = sinon.spy() + + ctrl.onChangeOrder('order-by') + + expect(ctrl.orderBy).to.be.equal('order-by') + expect(ctrl.fetchByOrderBy).to.have.been.called + expect(mocks.route.updateParams).to.have.been.calledWith(sinon.match({order_by: 'order-by'})) + + it "fetchByGlobalSearch", (done) -> + ctrl = $controller('DiscoverSearch') + + ctrl.fetch = sinon.stub().promise() + + ctrl.fetchByGlobalSearch().then () -> + expect(ctrl.loadingGlobal).to.be.false + + done() + + expect(ctrl.loadingGlobal).to.be.true + expect(ctrl.fetch).to.have.been.called + + ctrl.fetch.resolve() + + it "fetchByOrderBy", (done) -> + ctrl = $controller('DiscoverSearch') + + ctrl.fetch = sinon.stub().promise() + + ctrl.fetchByOrderBy().then () -> + expect(ctrl.loadingList).to.be.false + + done() + + expect(ctrl.loadingList).to.be.true + expect(ctrl.fetch).to.have.been.called + + ctrl.fetch.resolve() diff --git a/app/modules/discover/discover-search/discover-search.directive.coffee b/app/modules/discover/discover-search/discover-search.directive.coffee new file mode 100644 index 00000000..5f66cbea --- /dev/null +++ b/app/modules/discover/discover-search/discover-search.directive.coffee @@ -0,0 +1,32 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# File: discover-search.directive.coffee +### + +DiscoverSearchDirective = () -> + link = (scope, element, attrs, ctrl) -> + ctrl.fetch() + + return { + controller: "DiscoverSearch", + controllerAs: "vm" + link: link + } + +DiscoverSearchDirective.$inject = [] + +angular.module("taigaDiscover").directive("tgDiscoverSearch", DiscoverSearchDirective) diff --git a/app/modules/discover/discover-search/discover-search.jade b/app/modules/discover/discover-search/discover-search.jade new file mode 100644 index 00000000..e7629e0b --- /dev/null +++ b/app/modules/discover/discover-search/discover-search.jade @@ -0,0 +1,77 @@ +div(tg-discover-search) + .discover-search + tg-discover-search-bar( + filter="vm.filter", + q="vm.q", + on-change="vm.onChangeFilter(filter, q)" + ) + + .empty-discover-results(ng-if="!vm.searchResult.size && !vm.loadingGlobal && !vm.loadingList") + img( + src="/#{v}/images/issues-empty.png", + alt="{{ DISCOVER.EMPTY | translate }}" + ) + p.title(translate="DISCOVER.EMPTY") + + .discover-results(ng-if="vm.searchResult.size || vm.loadingGlobal || vm.loadingList") + .spin(tg-loading="vm.loadingGlobal") + + .discover-results-inner(ng-if="!vm.loadingGlobal") + tg-discover-search-list-header( + on-change="vm.onChangeOrder(orderBy)", + order-by="vm.orderBy" + ) + + .spin(ng-show="vm.loadingList", tg-loading="vm.loadingList") + + ul.project-list(ng-if="!vm.loadingList && vm.searchResult.size") + li.list-itemtype-project(tg-repeat="project in vm.searchResult track by project.get('id')") + .list-itemtype-project-left + a.list-itemtype-project-image( + href="#" + tg-nav="project:project=project.get('slug')" + title="{{ ::project.get('name') }}" + ) + img( + tg-project-logo-src="::project" + alt="{{::project.get('name')}}" + ) + .list-itemtype-project-data + h2 + a( + href="#" + tg-nav="project:project=project.get('slug')" + title="{{ ::project.get('name') }}" + ) {{project.get('name')}} + span.look-for-people( + ng-if="project.get('is_looking_for_people')" + title="{{ ::project.get('looking_for_people_note') }}" + ) + include ../../../svg/recruit.svg + p {{ ::project.get('description') | limitTo:300 }} + span(ng-if="::project.get('description').length > 300") ... + .list-itemtype-project-right.project-statistics + span.statistic( + ng-class="{'active': project.get('is_fan')}" + title="{{ 'PROJECT.FANS_COUNTER_TITLE'|translate:{total:project.get('total_fans')||0}:'messageformat' }}" + ) + include ../../../svg/like.svg + span {{::project.get('total_fans')}} + span.statistic( + ng-class="{'active': project.get('is_watcher')}" + title="{{ 'PROJECT.WATCHERS_COUNTER_TITLE'|translate:{total:project.get('total_watchers')||0}:'messageformat' }}" + ) + include ../../../svg/eye.svg + span {{::project.get('total_watchers')}} + span.statistic( + title="{{ 'PROJECT.MEMBERS_COUNTER_TITLE'|translate:{total:project.get('members').size||0}:'messageformat' }}" + ) + include ../../../svg/team.svg + span.statistics-num {{ ::project.get('members').size }} + + a.button-green.more-results( + tg-loading="vm.loadingPagination" + href="#" + ng-click="vm.showMore()" + ng-if="vm.nextSearchPage" + ) {{ 'DISCOVER.VIEW_MORE' | translate }} diff --git a/app/modules/discover/discover-search/discover-search.scss b/app/modules/discover/discover-search/discover-search.scss new file mode 100644 index 00000000..f31bcdbd --- /dev/null +++ b/app/modules/discover/discover-search/discover-search.scss @@ -0,0 +1,131 @@ +.discover-search { + .discover-header { + form { + margin: 0 8rem; + position: relative; + } + + .search-button { + left: 1rem; + right: auto; + } + .searchbox { + input { + padding-left: 3.5rem; + padding-right: 23rem; + } + } + } + .searchbox-filters { + position: absolute; + right: 1rem; + top: .7rem; + width: auto; + input { + display: none; + } + label { + border-radius: 4px; + color: $gray-light; + cursor: pointer; + display: inline-block; + padding: .4rem .75rem; + transition: all .2s; + transition-delay: .2s; + &.active { + background: $primary-light; + color: $white; + } + &:hover { + background: $whitish; + color: $gray; + } + } + } +} + +.discover-results { + @include centered; + .discover-results-inner { + .spin { + margin-top: 4rem; + } + } + .list-itemtype-project { + border-bottom: 1px solid $gray-light; + display: flex; + padding: 1rem 0; + &:last-child { + border-bottom: 0; + } + } + .list-itemtype-project-left { + align-items: flex-start; + display: flex; + } + .list-itemtype-project-image { + flex-shrink: 0; + margin-right: 1rem; + } + .list-itemtype-project-data { + flex: 1; + vertical-align: middle; + } + .look-for-people { + margin-left: .5rem; + svg { + @include svg-size(1rem); + fill: $gray-light; + } + } + .project-statistics { + display: flex; + flex-basis: 140px; + justify-content: flex-end; + svg { + @include svg-size(.8rem); + fill: $gray-light; + } + .svg-eye-closed { + display: none; + } + } + .statistic { + @extend %small; + color: $gray-light; + display: inline-block; + margin-right: .5rem; + &.active { + color: $primary; + svg { + fill: $primary; + } + } + } + .more-results { + display: block; + margin: 0 20rem; + transition: inherit; + } + div[tg-loading] { + img { + display: block; + margin: 0 auto; + } + } +} + +.empty-discover-results { + @include centered; + margin-top: 4rem; + text-align: center; + img { + margin-bottom: 1rem; + } + .title { + @extend %large; + @extend %light; + margin: 0; + text-transform: uppercase; + } +} diff --git a/app/modules/discover/discover.module.coffee b/app/modules/discover/discover.module.coffee new file mode 100644 index 00000000..2c2bfb7f --- /dev/null +++ b/app/modules/discover/discover.module.coffee @@ -0,0 +1,20 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# File: discover.module.coffee +### + +module = angular.module("taigaDiscover", []) diff --git a/app/modules/discover/services/discover-projects.service.coffee b/app/modules/discover/services/discover-projects.service.coffee new file mode 100644 index 00000000..f53808e3 --- /dev/null +++ b/app/modules/discover/services/discover-projects.service.coffee @@ -0,0 +1,93 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# File: discover-projects.service.coffee +### + +taiga = @.taiga + +class DiscoverProjectsService extends taiga.Service + @.$inject = [ + "tgResources", + "tgProjectsService" + ] + + constructor: (@rs, @projectsService) -> + @._mostLiked = Immutable.List() + @._mostActive = Immutable.List() + @._featured = Immutable.List() + @._searchResult = Immutable.List() + @._projectsCount = 0 + + @.decorate = @projectsService._decorate.bind(@projectsService) + + taiga.defineImmutableProperty @, "mostLiked", () => return @._mostLiked + taiga.defineImmutableProperty @, "mostActive", () => return @._mostActive + taiga.defineImmutableProperty @, "featured", () => return @._featured + taiga.defineImmutableProperty @, "searchResult", () => return @._searchResult + taiga.defineImmutableProperty @, "nextSearchPage", () => return @._nextSearchPage + taiga.defineImmutableProperty @, "projectsCount", () => return @._projectsCount + + fetchMostLiked: (params) -> + return @rs.projects.getProjects(params, false) + .then (result) => + data = result.data.slice(0, 5) + + projects = Immutable.fromJS(data) + projects = projects.map(@.decorate) + + @._mostLiked = projects + + fetchMostActive: (params) -> + return @rs.projects.getProjects(params, false) + .then (result) => + data = result.data.slice(0, 5) + + projects = Immutable.fromJS(data) + projects = projects.map(@.decorate) + + @._mostActive = projects + + fetchFeatured: () -> + params = {is_featured: true} + + return @rs.projects.getProjects(params, false) + .then (result) => + data = result.data.slice(0, 4) + + projects = Immutable.fromJS(data) + projects = projects.map(@.decorate) + + @._featured = projects + + resetSearchList: () -> + @._searchResult = Immutable.List() + + fetchStats: () -> + return @rs.stats.discover().then (discover) => + @._projectsCount = discover.getIn(['projects', 'total']) + + fetchSearch: (params) -> + return @rs.projects.getProjects(params) + .then (result) => + @._nextSearchPage = !!result.headers('X-Pagination-Next') + + projects = Immutable.fromJS(result.data) + projects = projects.map(@.decorate) + + @._searchResult = @._searchResult.concat(projects) + +angular.module("taigaDiscover").service("tgDiscoverProjectsService", DiscoverProjectsService) diff --git a/app/modules/discover/services/discover-projects.service.spec.coffee b/app/modules/discover/services/discover-projects.service.spec.coffee new file mode 100644 index 00000000..97adbd0c --- /dev/null +++ b/app/modules/discover/services/discover-projects.service.spec.coffee @@ -0,0 +1,178 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# File: discover-projects.service.spec.coffee +### + +describe "tgDiscoverProjectsService", -> + discoverProjectsService = provide = null + mocks = {} + + _mockResources = () -> + mocks.resources = { + projects: { + getProjects: sinon.stub() + }, + stats: { + discover: sinon.stub() + } + } + + provide.value "tgResources", mocks.resources + + _mockProjectsService = () -> + mocks.projectsService = { + _decorate: (content) -> + return content.set('decorate', true) + } + + provide.value "tgProjectsService", mocks.projectsService + + _inject = (callback) -> + inject (_tgDiscoverProjectsService_) -> + discoverProjectsService = _tgDiscoverProjectsService_ + callback() if callback + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockResources() + _mockProjectsService() + return null + + _setup = -> + _mocks() + + beforeEach -> + module "taigaDiscover" + _setup() + _inject() + + it "fetch most liked", (done) -> + params = {test: 1} + + mocks.resources.projects.getProjects.withArgs(sinon.match(params), false).promise().resolve({ + data: [ + {id: 1}, + {id: 2}, + {id: 3}, + {id: 4}, + {id: 5}, + {id: 6}, + {id: 7} + ] + }) + + discoverProjectsService.fetchMostLiked(params).then () -> + result = discoverProjectsService._mostLiked.toJS() + + expect(result).to.have.length(5) + expect(result[0].decorate).to.be.ok; + + done() + + it "fetch most active", (done) -> + params = {test: 1} + + mocks.resources.projects.getProjects.withArgs(sinon.match(params), false).promise().resolve({ + data: [ + {id: 1}, + {id: 2}, + {id: 3}, + {id: 4}, + {id: 5}, + {id: 6}, + {id: 7} + ] + }) + + discoverProjectsService.fetchMostActive(params).then () -> + result = discoverProjectsService._mostActive.toJS() + + expect(result).to.have.length(5) + expect(result[0].decorate).to.be.ok; + + done() + + it "fetch featured", (done) -> + mocks.resources.projects.getProjects.withArgs(sinon.match({is_featured: true}), false).promise().resolve({ + data: [ + {id: 1}, + {id: 2}, + {id: 3}, + {id: 4}, + {id: 5}, + {id: 6}, + {id: 7} + ] + }) + + discoverProjectsService.fetchFeatured().then () -> + result = discoverProjectsService._featured.toJS() + + expect(result).to.have.length(4) + expect(result[0].decorate).to.be.ok; + + done() + + it "reset search list", () -> + discoverProjectsService._searchResult = 'xxx' + + discoverProjectsService.resetSearchList() + + expect(discoverProjectsService._searchResult.size).to.be.equal(0) + + it "fetch stats", (done) -> + mocks.resources.stats.discover.promise().resolve(Immutable.fromJS({ + projects: { + total: 3 + } + })) + + discoverProjectsService.fetchStats().then () -> + expect(discoverProjectsService._projectsCount).to.be.equal(3) + + done() + + it "fetch search", (done) -> + params = {test: 1} + + result = { + headers: sinon.stub(), + data: [ + {id: 1}, + {id: 2}, + {id: 3} + ] + } + + result.headers.withArgs('X-Pagination-Next').returns('next') + + mocks.resources.projects.getProjects.withArgs(sinon.match(params)).promise().resolve(result) + + discoverProjectsService._searchResult = Immutable.fromJS([ + {id: 4}, + {id: 5} + ]) + + discoverProjectsService.fetchSearch(params).then () -> + result = discoverProjectsService._searchResult.toJS() + + expect(result).to.have.length(5) + + expect(result[4].decorate).to.be.ok; + + done() diff --git a/app/modules/home/home-controller.spec.coffee b/app/modules/home/home-controller.spec.coffee new file mode 100644 index 00000000..2713181f --- /dev/null +++ b/app/modules/home/home-controller.spec.coffee @@ -0,0 +1,78 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# File: home.controller.spec.coffee +### + +describe "HomeController", -> + homeCtrl = null + provide = null + controller = null + mocks = {} + + _mockCurrentUserService = () -> + mocks.currentUserService = { + getUser: sinon.stub() + } + + provide.value "tgCurrentUserService", mocks.currentUserService + + _mockLocation = () -> + mocks.location = { + path: sinon.stub() + } + provide.value "$location", mocks.location + + _mockTgNavUrls = () -> + mocks.tgNavUrls = { + resolve: sinon.stub() + } + + provide.value "$tgNavUrls", mocks.tgNavUrls + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockCurrentUserService() + _mockLocation() + _mockTgNavUrls() + + return null + + beforeEach -> + module "taigaHome" + + _mocks() + + inject ($controller) -> + controller = $controller + + it "anonymous home", () -> + homeCtrl = controller "Home", + $scope: {} + + expect(mocks.tgNavUrls.resolve).to.be.calledWith("discover") + expect(mocks.location.path).to.be.calledOnce + + it "non anonymous home", () -> + mocks.currentUserService = { + getUser: Immutable.fromJS({ + id: 1 + }) + } + + expect(mocks.tgNavUrls.resolve).to.be.notCalled + expect(mocks.location.path).to.be.notCalled diff --git a/app/modules/home/home.controller.coffee b/app/modules/home/home.controller.coffee new file mode 100644 index 00000000..a3d9121e --- /dev/null +++ b/app/modules/home/home.controller.coffee @@ -0,0 +1,32 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# File: home.controller.coffee +### + +class HomeController + @.$inject = [ + "tgCurrentUserService", + "$location", + "$tgNavUrls" + ] + + constructor: (@currentUserService, @location, @navUrls) -> + if not @currentUserService.getUser() + @location.path(@navUrls.resolve("discover")) + + +angular.module("taigaHome").controller("Home", HomeController) diff --git a/app/modules/home/home.scss b/app/modules/home/home.scss index e605acea..9f488e10 100644 --- a/app/modules/home/home.scss +++ b/app/modules/home/home.scss @@ -11,7 +11,7 @@ display: block; } .title-bar { - @extend %title; + @extend %light; @extend %larger; align-content: center; background: $whitish; diff --git a/app/modules/home/projects/home-project-list.jade b/app/modules/home/projects/home-project-list.jade index 0841af36..53336204 100644 --- a/app/modules/home/projects/home-project-list.jade +++ b/app/modules/home/projects/home-project-list.jade @@ -1,13 +1,59 @@ section.home-project-list(ng-if="vm.projects.size") - ul - li.home-project-list-single(tg-bind-scope, tg-repeat="project in vm.projects") - a(href="#", tg-nav="project:project=project.get('slug')") - h2.home-project-list-single-title - span.project-name(title="{{ ::project.get('name') }}") {{::project.get('name')}} - span.private(ng-if="project.get('is_private')", title="{{'PROJECT.PRIVATE' | translate}}") - include ../../../svg/lock.svg - p {{ ::project.get('description') | limitTo:150 }} - span(ng-if="::project.get('description').size > 150") ... + + .home-project(tg-bind-scope, tg-repeat="project in vm.projects") + .tags-container + .project-tag( + style="background: {{tag.get('color')}}" + title="{{tag.get('name')}}" + tg-repeat="tag in project.get('colorized_tags') track by tag.get('name')" + ) + .project-card-inner(href="#", tg-nav="project:project=project.get('slug')") + .project-card-header + a.project-card-logo( + href="#" + tg-nav="project:project=project.get('slug')" + title="{{::project.get('name')}}" + ) + img( + tg-project-logo-src="::project" + alt="{{::project.get('name')}}" + ) + h2.project-card-name + a( + href="#" + tg-nav="project:project=project.get('slug')" + title="{{::project.get('name')}}" + ) {{::project.get('name')}} + span.look-for-people( + ng-if="project.get('is_looking_for_people')" + title="{{ ::project.get('looking_for_people_note') }}" + ) + include ../../../svg/recruit.svg + p.project-card-description {{::project.get('description')| limitTo:100 }} + span(ng-if="::project.get('description').length > 100") ... + .project-card-statistics + span.statistic( + ng-class="{'active': project.get('is_fan')}" + title="{{ 'PROJECT.FANS_COUNTER_TITLE'|translate:{total:project.get('total_fans')||0}:'messageformat' }}" + ) + include ../../../svg/like.svg + span {{::project.get('total_fans')}} + span.statistic( + ng-class="{'active': project.get('is_watcher')}" + title="{{ 'PROJECT.WATCHERS_COUNTER_TITLE'|translate:{total:project.get('total_watchers')||0}:'messageformat' }}" + ) + include ../../../svg/eye.svg + span {{::project.get('total_watchers')}} + span.statistic( + title="{{ 'PROJECT.MEMBERS_COUNTER_TITLE'|translate:{total:project.get('members').size||0}:'messageformat' }}" + ) + include ../../../svg/team.svg + span.statistics-num {{ ::project.get('members').size }} + span.statistic( + ng-if="::project.get('is_private')" + title="{{ 'PROJECT.PRIVATE' | translate }}" + ) + include ../../../svg/lock.svg a.see-more-projects-btn.button-gray( href="#", diff --git a/app/modules/home/projects/home-project-list.scss b/app/modules/home/projects/home-project-list.scss index 21d67b31..c73826b0 100644 --- a/app/modules/home/projects/home-project-list.scss +++ b/app/modules/home/projects/home-project-list.scss @@ -1,52 +1,13 @@ -.home-project-list { - li { - border: 1px solid lighten($gray-light, 15%); - border-radius: 3px; - cursor: pointer; - margin-bottom: .75rem; - padding: 1rem; - text-overflow: ellipsis; - &:hover { - border-color: $primary-light; - transition: all .3s linear; - p { - color: $gray; - transition: color .3s linear; - } - .private path { - fill: $gray; - transition: fill .3s linear; - } - } - a { - display: flex; - flex-direction: column; - min-height: 5rem; - } - } - h2 { - @extend %text; - color: $gray; - font-size: 1.5rem; - line-height: 1.3; - margin-bottom: .5rem; - text-transform: none; - .project-name { - display: inline-block; - max-width: 90%; - overflow: hidden; - text-overflow: ellipsis; - vertical-align: middle; - white-space: nowrap; - } - } - p { - @extend %text; - @extend %xsmall; - color: $gray-light; - line-height: 125%; - margin: 0; - word-wrap: break-word; +@import '../../../styles/dependencies/mixins/project-card'; + +.home-project { + @include project-card; + cursor: pointer; + margin-bottom: 1rem; + transition: .2s; + transition-delay: .1s; + &:hover { + border: 1px solid $primary-light; } } diff --git a/app/modules/resources/projects-resource.service.coffee b/app/modules/resources/projects-resource.service.coffee index 35de7d51..4385b90c 100644 --- a/app/modules/resources/projects-resource.service.coffee +++ b/app/modules/resources/projects-resource.service.coffee @@ -22,6 +22,20 @@ pagination = () -> Resource = (urlsService, http, paginateResponseService) -> service = {} + service.getProjects = (params = {}, pagination = true) -> + url = urlsService.resolve("projects") + + httpOptions = {} + + if !pagination + httpOptions = { + headers: { + "x-disable-pagination": "1" + } + } + + return http.get(url, params, httpOptions) + service.getProjectBySlug = (projectSlug) -> url = urlsService.resolve("projects") diff --git a/app/modules/resources/resources.coffee b/app/modules/resources/resources.coffee index b6e8a17f..e8a4c33a 100644 --- a/app/modules/resources/resources.coffee +++ b/app/modules/resources/resources.coffee @@ -25,7 +25,8 @@ services = [ "tgTasksResource", "tgIssuesResource", "tgExternalAppsResource", - "tgAttachmentsResource" + "tgAttachmentsResource", + "tgStatsResource" ] Resources = ($injector) -> diff --git a/app/modules/resources/stats-resource.service.coffee b/app/modules/resources/stats-resource.service.coffee new file mode 100644 index 00000000..2376ae4c --- /dev/null +++ b/app/modules/resources/stats-resource.service.coffee @@ -0,0 +1,34 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# File: stats-resource.service.coffee +### + +Resource = (urlsService, http) -> + service = {} + + service.discover = (applicationId, state) -> + url = urlsService.resolve("stats-discover") + return http.get(url).then (result) -> + Immutable.fromJS(result.data) + + return () -> + return {"stats": service} + +Resource.$inject = ["$tgUrls", "$tgHttp"] + +module = angular.module("taigaResources2") +module.factory("tgStatsResource", Resource) diff --git a/conf.e2e.js b/conf.e2e.js index e0b11b37..bca55590 100644 --- a/conf.e2e.js +++ b/conf.e2e.js @@ -40,7 +40,8 @@ exports.config = { kanban: "e2e/suites/kanban.e2e.js", projectHome: "e2e/suites/project-home.e2e.js", search: "e2e/suites/search.e2e.js", - team: "e2e/suites/team.e2e.js" + team: "e2e/suites/team.e2e.js", + discover: "e2e/suites/discover/*.e2e.js" }, onPrepare: function() { // track mouse movements diff --git a/e2e/helpers/discover-helper.js b/e2e/helpers/discover-helper.js new file mode 100644 index 00000000..2e6d8cfe --- /dev/null +++ b/e2e/helpers/discover-helper.js @@ -0,0 +1,87 @@ +var utils = require('../utils'); + +var helper = module.exports; + +helper.liked = function() { + return $('tg-most-liked'); +}; + +helper.active = function() { + return $('tg-most-active'); +}; + +helper.featured = function() { + return $('tg-featured-projects'); +}; + +helper.likedProjects = function() { + return helper.liked().$$('.highlighted-project'); +}; + +helper.activeProjects = function() { + return helper.active().$$('.highlighted-project'); +}; + +helper.featuredProjects = function() { + return helper.featured().$$('.featured-project'); +}; + +helper.rearrangeLike = function(index) { + helper.liked().$('.current-filter').click(); + + helper.liked().$$('.filter-list li').get(index).click(); +}; + +helper.getLikeFilterText = function(index) { + return helper.liked().$('.current-filter').getText(); +}; + +helper.rearrangeActive = function(index) { + helper.active().$('.current-filter').click(); + + helper.active().$$('.filter-list li').get(index).click(); +}; + +helper.getActiveFilterText = function(index) { + return helper.active().$('.current-filter').getText(); +}; + +helper.searchFilter = function(index) { + return $$('.searchbox-filters label').get(index).click(); +}; + +helper.searchProjectsList = function() { + return $('.project-list'); +}; + +helper.searchProjects = function() { + return helper.searchProjectsList().$$('li'); +}; + +helper.searchInput = function() { + return $('.searchbox input'); +}; + +helper.sendSearch = function() { + return $('.search-button').click(); +}; + +helper.mostLiked = function() { + $$('.discover-search-filter').get(0).click(); +}; + +helper.mostActived = function() { + $$('.discover-search-filter').get(1).click(); +}; + +helper.searchOrder = function(index) { + $$('.filter-list a').get(index).click(); +}; + +helper.orderSelectorWrapper = function() { + return $('.discover-search-subfilter'); +}; + +helper.clearOrder = function() { + helper.orderSelectorWrapper().$('.results a').click(); +}; diff --git a/e2e/helpers/project-detail-helper.js b/e2e/helpers/project-detail-helper.js new file mode 100644 index 00000000..096dab63 --- /dev/null +++ b/e2e/helpers/project-detail-helper.js @@ -0,0 +1,27 @@ +var utils = require('../utils'); + +var helper = module.exports; + +helper.lookingForPeople = function() { + return $$('.looking-for-people input').get(0); +}; + +helper.lookingForPeopleReason = function() { + return $$('.looking-for-people-reason input').get(0); +}; + +helper.toggleIsLookingForPeople = function() { + helper.lookingForPeople().click(); +}; + +helper.editLogo = function() { + let inputFile = $('#logo-field'); + + var fileToUpload = utils.common.uploadImagePath(); + + return utils.common.uploadFile(inputFile, fileToUpload); +}; + +helper.getLogoSrc = function() { + return $('.image-container .image'); +}; diff --git a/e2e/suites/admin/project/project-detail.e2e.js b/e2e/suites/admin/project/project-detail.e2e.js index 827b562c..2ebb94a1 100644 --- a/e2e/suites/admin/project/project-detail.e2e.js +++ b/e2e/suites/admin/project/project-detail.e2e.js @@ -6,6 +6,8 @@ var chaiAsPromised = require('chai-as-promised'); chai.use(chaiAsPromised); var expect = chai.expect; +var adminHelper = require('../../../helpers/project-detail-helper'); + describe('project detail', function() { before(async function(){ browser.get(browser.params.glob.host + 'project/project-0/admin/project-profile/details'); @@ -40,5 +42,40 @@ describe('project detail', function() { $('button[type="submit"]').click(); expect(utils.notifications.success.open()).to.be.eventually.equal(true); + + await utils.notifications.success.close(); + }); + + it('looking for people', async function() { + let checked = !! await adminHelper.lookingForPeople().getAttribute('checked'); + + if(checked) { + adminHelper.toggleIsLookingForPeople(); + } + + adminHelper.toggleIsLookingForPeople(); + + adminHelper.lookingForPeopleReason().sendKeys('looking for people reason'); + + $('button[type="submit"]').click(); + + checked = !! await adminHelper.lookingForPeople().getAttribute('checked'); + + expect(checked).to.be.true; + expect(utils.notifications.success.open()).to.be.eventually.equal(true); + }); + + it('edit logo', async function() { + let imageContainer = $('.image-container'); + + let htmlChanges = await utils.common.outerHtmlChanges(imageContainer); + + adminHelper.editLogo(); + + await htmlChanges(); + + let src = await adminHelper.getLogoSrc().getAttribute('src'); + + expect(src).to.contains('upload-image-test.png'); }); }); diff --git a/e2e/suites/discover/discover-home.e2e.js b/e2e/suites/discover/discover-home.e2e.js new file mode 100644 index 00000000..96f1900b --- /dev/null +++ b/e2e/suites/discover/discover-home.e2e.js @@ -0,0 +1,63 @@ +var utils = require('../../utils'); +var discoverHelper = require('../../helpers/discover-helper'); + +var chai = require('chai'); +var chaiAsPromised = require('chai-as-promised'); + +chai.use(chaiAsPromised); +var expect = chai.expect; + + +describe('discover', () => { + before(async () => { + browser.get(browser.params.glob.host + 'discover'); + await utils.common.waitLoader(); + }); + + it('screenshot', async () => { + await utils.common.takeScreenshot("discover", "discover-home"); + }); + + describe('most liked', () => { + it('has projects', () => { + let projects = discoverHelper.likedProjects(); + + expect(projects.count()).to.be.eventually.above(0); + }); + + it('rearrange', () => { + discoverHelper.rearrangeLike(3); + + let filterText = discoverHelper.getLikeFilterText(); + let projects = discoverHelper.likedProjects(); + + expect(filterText).to.be.eventually.equal('All time'); + expect(projects.count()).to.be.eventually.equal(5); + + }); + }); + + describe('most active', () => { + it('has projects', () => { + let projects = discoverHelper.activeProjects(); + + expect(projects.count()).to.be.eventually.above(0); + }); + + it('rearrange', () => { + discoverHelper.rearrangeActive(3); + + let filterText = discoverHelper.getActiveFilterText(); + let projects = discoverHelper.activeProjects(); + + expect(filterText).to.be.eventually.equal('All time'); + expect(projects.count()).to.be.eventually.equal(5); + }); + }); + + it('featured projects', () => { + let projects = discoverHelper.featuredProjects(); + + expect(projects.count()).to.be.eventually.above(0); + }); +}); diff --git a/e2e/suites/discover/discover-search.e2e.js b/e2e/suites/discover/discover-search.e2e.js new file mode 100644 index 00000000..b51f3a45 --- /dev/null +++ b/e2e/suites/discover/discover-search.e2e.js @@ -0,0 +1,123 @@ +var utils = require('../../utils'); +var discoverHelper = require('../../helpers/discover-helper'); + +var chai = require('chai'); +var chaiAsPromised = require('chai-as-promised'); + +chai.use(chaiAsPromised); +var expect = chai.expect; + + +describe('discover search', () => { + before(async () => { + browser.get(browser.params.glob.host + 'discover/search'); + await utils.common.waitLoader(); + }); + + it('screenshot', async () => { + await utils.common.takeScreenshot("discover", "discover-search"); + }); + + describe('top bar', async () => { + after(async () => { + browser.get(browser.params.glob.host + 'discover/search'); + await utils.common.waitLoader(); + }); + + it('filters', async () => { + let htmlChanges = await utils.common.outerHtmlChanges(discoverHelper.searchProjectsList()); + + discoverHelper.searchFilter(1); + + await htmlChanges(); + + let url = await browser.getCurrentUrl(); + + let projects = discoverHelper.searchProjects(); + + expect(projects.count()).to.be.eventually.above(0); + expect(url).to.be.equal(browser.params.glob.host + 'discover/search?filter=kanban'); + }); + + it('search by text', () => { + discoverHelper.searchInput().sendKeys('Project Example 0'); + + discoverHelper.sendSearch(); + + let projects = discoverHelper.searchProjects(); + expect(projects.count()).to.be.eventually.equal(1); + }); + }); + + describe('most liked', async () => { + after(async () => { + browser.get(browser.params.glob.host + 'discover/search'); + await utils.common.waitLoader(); + }); + + it('default', async () => { + discoverHelper.mostLiked(); + + utils.common.takeScreenshot("discover", "discover-search-filter"); + + let url = await browser.getCurrentUrl(); + + expect(url).to.be.equal(browser.params.glob.host + 'discover/search?order_by=-total_fans_last_week'); + }); + + it('filter', async () => { + discoverHelper.searchOrder(3); + + let projects = discoverHelper.searchProjects(); + + let url = await browser.getCurrentUrl(); + + expect(projects.count()).to.be.eventually.above(0); + expect(url).to.be.equal(browser.params.glob.host + 'discover/search?order_by=-total_fans'); + }); + + it('clear', () => { + discoverHelper.clearOrder(); + + let orderSelector = discoverHelper.orderSelectorWrapper(); + + expect(orderSelector.isPresent()).to.be.eventually.equal(false); + }); + }); + + describe('most active', async () => { + after(async () => { + browser.get(browser.params.glob.host + 'discover/search'); + await utils.common.waitLoader(); + }); + + it('default', async () => { + discoverHelper.mostActived(); + + utils.common.takeScreenshot("discover", "discover-search-filter"); + + let url = await browser.getCurrentUrl(); + + expect(url).to.be.equal(browser.params.glob.host + 'discover/search?order_by=-total_activity_last_week'); + }); + + it('filter', async () => { + discoverHelper.searchOrder(3); + + let projects = discoverHelper.searchProjects(); + + let url = await browser.getCurrentUrl(); + + expect(projects.count()).to.be.eventually.above(0); + expect(url).to.be.equal(browser.params.glob.host + 'discover/search?order_by=-total_activity'); + }); + + it('clear', () => { + discoverHelper.clearOrder(); + + let orderSelector = discoverHelper.orderSelectorWrapper(); + + expect(orderSelector.isPresent()).to.be.eventually.equal(false); + }); + }); +}); diff --git a/e2e/utils/common.js b/e2e/utils/common.js index a0f9511d..c1c4e468 100644 --- a/e2e/utils/common.js +++ b/e2e/utils/common.js @@ -163,7 +163,7 @@ common.prepare = function() { browser.get(browser.params.glob.host); return common.closeCookies(); -} +}; common.dragEnd = function(elm) { return browser.wait(async function() { diff --git a/run-e2e.js b/run-e2e.js index 0c2ae066..33af48d8 100644 --- a/run-e2e.js +++ b/run-e2e.js @@ -17,7 +17,8 @@ var suites = [ 'kanban', 'projectHome', 'search', - 'team' + 'team', + 'discover' ]; var lunchSuites = [];