Commit 0bcb1d35 authored by Douwe Maan's avatar Douwe Maan

Merge branch 'expand-backlog-closed-lists-issue-boards' into 'master'

Expand/collapse close & backlog lists in issue boards

Closes #23917

See merge request !11820
parents a5757c72 1633d3d7
...@@ -88,6 +88,8 @@ $(() => { ...@@ -88,6 +88,8 @@ $(() => {
if (list.type === 'closed') { if (list.type === 'closed') {
list.position = Infinity; list.position = Infinity;
list.label = { description: 'Shows all closed issues. Moving an issue to this list closes it' }; list.label = { description: 'Shows all closed issues. Moving an issue to this list closes it' };
} else if (list.type === 'backlog') {
list.position = -1;
} }
}); });
...@@ -128,7 +130,7 @@ $(() => { ...@@ -128,7 +130,7 @@ $(() => {
}, },
computed: { computed: {
disabled() { disabled() {
return !this.store.lists.filter(list => list.type !== 'blank' && list.type !== 'done').length; return !this.store.lists.filter(list => !list.preset).length;
}, },
tooltipTitle() { tooltipTitle() {
if (this.disabled) { if (this.disabled) {
......
/* eslint-disable comma-dangle, space-before-function-paren, one-var */ /* eslint-disable comma-dangle, space-before-function-paren, one-var */
/* global Sortable */ /* global Sortable */
import Vue from 'vue'; import Vue from 'vue';
import AccessorUtilities from '../../lib/utils/accessor';
import boardList from './board_list'; import boardList from './board_list';
import boardBlankState from './board_blank_state'; import boardBlankState from './board_blank_state';
import './board_delete'; import './board_delete';
...@@ -22,6 +23,10 @@ gl.issueBoards.Board = Vue.extend({ ...@@ -22,6 +23,10 @@ gl.issueBoards.Board = Vue.extend({
disabled: Boolean, disabled: Boolean,
issueLinkBase: String, issueLinkBase: String,
rootPath: String, rootPath: String,
boardId: {
type: String,
required: true,
},
}, },
data () { data () {
return { return {
...@@ -78,7 +83,16 @@ gl.issueBoards.Board = Vue.extend({ ...@@ -78,7 +83,16 @@ gl.issueBoards.Board = Vue.extend({
methods: { methods: {
showNewIssueForm() { showNewIssueForm() {
this.$refs['board-list'].showIssueForm = !this.$refs['board-list'].showIssueForm; this.$refs['board-list'].showIssueForm = !this.$refs['board-list'].showIssueForm;
} },
toggleExpanded(e) {
if (this.list.isExpandable && !e.target.classList.contains('js-no-trigger-collapse')) {
this.list.isExpanded = !this.list.isExpanded;
if (AccessorUtilities.isLocalStorageAccessSafe()) {
localStorage.setItem(`boards.${this.boardId}.${this.list.type}.expanded`, this.list.isExpanded);
}
}
},
}, },
mounted () { mounted () {
this.sortableOptions = gl.issueBoards.getBoardSortableDefaultOptions({ this.sortableOptions = gl.issueBoards.getBoardSortableDefaultOptions({
...@@ -102,4 +116,11 @@ gl.issueBoards.Board = Vue.extend({ ...@@ -102,4 +116,11 @@ gl.issueBoards.Board = Vue.extend({
this.sortable = Sortable.create(this.$el.parentNode, this.sortableOptions); this.sortable = Sortable.create(this.$el.parentNode, this.sortableOptions);
}, },
created() {
if (this.list.isExpandable && AccessorUtilities.isLocalStorageAccessSafe()) {
const isCollapsed = localStorage.getItem(`boards.${this.boardId}.${this.list.type}.expanded`) === 'false';
this.list.isExpanded = !isCollapsed;
}
},
}); });
...@@ -152,6 +152,7 @@ gl.issueBoards.IssueCardInner = Vue.extend({ ...@@ -152,6 +152,7 @@ gl.issueBoards.IssueCardInner = Vue.extend({
<div class="card-assignee"> <div class="card-assignee">
<user-avatar-link <user-avatar-link
v-for="(assignee, index) in issue.assignees" v-for="(assignee, index) in issue.assignees"
:key="assignee.id"
v-if="shouldRenderAssignee(index)" v-if="shouldRenderAssignee(index)"
class="js-no-trigger" class="js-no-trigger"
:link-href="assigneeUrl(assignee)" :link-href="assigneeUrl(assignee)"
......
...@@ -26,7 +26,8 @@ gl.issueBoards.ModalFooter = Vue.extend({ ...@@ -26,7 +26,8 @@ gl.issueBoards.ModalFooter = Vue.extend({
}, },
methods: { methods: {
addIssues() { addIssues() {
const list = this.modal.selectedList || this.state.lists[0]; const firstListIndex = 1;
const list = this.modal.selectedList || this.state.lists[firstListIndex];
const selectedIssues = ModalStore.getSelectedIssues(); const selectedIssues = ModalStore.getSelectedIssues();
const issueIds = selectedIssues.map(issue => issue.globalId); const issueIds = selectedIssues.map(issue => issue.globalId);
......
...@@ -11,7 +11,7 @@ gl.issueBoards.ModalFooterListsDropdown = Vue.extend({ ...@@ -11,7 +11,7 @@ gl.issueBoards.ModalFooterListsDropdown = Vue.extend({
}, },
computed: { computed: {
selected() { selected() {
return this.modal.selectedList || this.state.lists[0]; return this.modal.selectedList || this.state.lists[1];
}, },
}, },
destroyed() { destroyed() {
......
...@@ -12,7 +12,9 @@ class List { ...@@ -12,7 +12,9 @@ class List {
this.position = obj.position; this.position = obj.position;
this.title = obj.title; this.title = obj.title;
this.type = obj.list_type; this.type = obj.list_type;
this.preset = ['closed', 'blank'].indexOf(this.type) > -1; this.preset = ['backlog', 'closed', 'blank'].indexOf(this.type) > -1;
this.isExpandable = ['backlog', 'closed'].indexOf(this.type) > -1;
this.isExpanded = true;
this.page = 1; this.page = 1;
this.loading = true; this.loading = true;
this.loadingMore = false; this.loadingMore = false;
......
...@@ -32,10 +32,14 @@ gl.issueBoards.BoardsStore = { ...@@ -32,10 +32,14 @@ gl.issueBoards.BoardsStore = {
}, },
new (listObj) { new (listObj) {
const list = this.addList(listObj); const list = this.addList(listObj);
const backlogList = this.findList('type', 'backlog', 'backlog');
list list
.save() .save()
.then(() => { .then(() => {
// Remove any new issues from the backlog
// as they will be visible in the new list
list.issues.forEach(backlogList.removeIssue.bind(backlogList));
this.state.lists = _.sortBy(this.state.lists, 'position'); this.state.lists = _.sortBy(this.state.lists, 'position');
}) })
.catch(() => { .catch(() => {
...@@ -48,7 +52,7 @@ gl.issueBoards.BoardsStore = { ...@@ -48,7 +52,7 @@ gl.issueBoards.BoardsStore = {
}, },
shouldAddBlankState () { shouldAddBlankState () {
// Decide whether to add the blank state // Decide whether to add the blank state
return !(this.state.lists.filter(list => list.type !== 'closed')[0]); return !(this.state.lists.filter(list => list.type !== 'backlog' && list.type !== 'closed')[0]);
}, },
addBlankState () { addBlankState () {
if (!this.shouldAddBlankState() || this.welcomeIsHidden() || this.disabled) return; if (!this.shouldAddBlankState() || this.welcomeIsHidden() || this.disabled) return;
...@@ -101,7 +105,7 @@ gl.issueBoards.BoardsStore = { ...@@ -101,7 +105,7 @@ gl.issueBoards.BoardsStore = {
issueTo.removeLabel(listFrom.label); issueTo.removeLabel(listFrom.label);
} }
if (listTo.type === 'closed') { if (listTo.type === 'closed' && listFrom.type !== 'backlog') {
issueLists.forEach((list) => { issueLists.forEach((list) => {
list.removeIssue(issue); list.removeIssue(issue);
}); });
......
...@@ -96,9 +96,51 @@ ...@@ -96,9 +96,51 @@
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
width: 400px; width: 400px;
} }
&.is-expandable {
.board-header {
cursor: pointer;
}
}
&.is-collapsed {
width: 50px;
.board-header {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
.board-title {
position: initial;
padding: 0;
border-bottom: 0;
> span {
display: block;
transform: rotate(90deg) translate(25px, 0);
}
}
.board-title-expandable-toggle {
position: absolute;
top: 50%;
left: 50%;
margin-left: -10px;
}
.board-list-component,
.board-issue-count-holder {
display: none;
}
}
} }
.board-inner { .board-inner {
position: relative;
height: 100%; height: 100%;
font-size: $issue-boards-font-size; font-size: $issue-boards-font-size;
background: $gray-light; background: $gray-light;
......
...@@ -5,7 +5,9 @@ module Projects ...@@ -5,7 +5,9 @@ module Projects
before_action :authorize_read_list!, only: [:index] before_action :authorize_read_list!, only: [:index]
def index def index
render json: serialize_as_json(board.lists) lists = ::Boards::Lists::ListService.new(project, current_user).execute(board)
render json: serialize_as_json(lists)
end end
def create def create
......
...@@ -5,6 +5,10 @@ class Board < ActiveRecord::Base ...@@ -5,6 +5,10 @@ class Board < ActiveRecord::Base
validates :project, presence: true validates :project, presence: true
def backlog_list
lists.merge(List.backlog).take
end
def closed_list def closed_list
lists.merge(List.closed).take lists.merge(List.closed).take
end end
......
...@@ -2,7 +2,7 @@ class List < ActiveRecord::Base ...@@ -2,7 +2,7 @@ class List < ActiveRecord::Base
belongs_to :board belongs_to :board
belongs_to :label belongs_to :label
enum list_type: { label: 1, closed: 2 } enum list_type: { backlog: 0, label: 1, closed: 2 }
validates :board, :list_type, presence: true validates :board, :list_type, presence: true
validates :label, :position, presence: true, if: :label? validates :label, :position, presence: true, if: :label?
......
...@@ -12,6 +12,7 @@ module Boards ...@@ -12,6 +12,7 @@ module Boards
def create_board! def create_board!
board = project.boards.create board = project.boards.create
board.lists.create(list_type: :backlog)
board.lists.create(list_type: :closed) board.lists.create(list_type: :closed)
board board
......
...@@ -3,7 +3,7 @@ module Boards ...@@ -3,7 +3,7 @@ module Boards
class ListService < BaseService class ListService < BaseService
def execute def execute
issues = IssuesFinder.new(current_user, filter_params).execute issues = IssuesFinder.new(current_user, filter_params).execute
issues = without_board_labels(issues) unless list issues = without_board_labels(issues) unless movable_list?
issues = with_list_label(issues) if movable_list? issues = with_list_label(issues) if movable_list?
issues.order_by_position_and_priority issues.order_by_position_and_priority
end end
......
...@@ -2,6 +2,8 @@ module Boards ...@@ -2,6 +2,8 @@ module Boards
module Lists module Lists
class ListService < BaseService class ListService < BaseService
def execute(board) def execute(board)
board.lists.create(list_type: :backlog) unless board.lists.backlog.exists?
board.lists board.lists
end end
end end
......
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
":disabled" => "disabled", ":disabled" => "disabled",
":issue-link-base" => "issueLinkBase", ":issue-link-base" => "issueLinkBase",
":root-path" => "rootPath", ":root-path" => "rootPath",
":board-id" => "boardId",
":key" => "_uid" } ":key" => "_uid" }
= render "projects/boards/components/sidebar" = render "projects/boards/components/sidebar"
%board-add-issues-modal{ "blank-state-image" => render('shared/empty_states/icons/issues.svg'), %board-add-issues-modal{ "blank-state-image" => render('shared/empty_states/icons/issues.svg'),
......
.board{ ":class" => '{ "is-draggable": !list.preset }', .board{ ":class" => '{ "is-draggable": !list.preset, "is-expandable": list.isExpandable, "is-collapsed": !list.isExpanded }',
":data-id" => "list.id" } ":data-id" => "list.id" }
.board-inner .board-inner
%header.board-header{ ":class" => '{ "has-border": list.label }', ":style" => "{ borderTopColor: (list.label ? list.label.color : null) }" } %header.board-header{ ":class" => '{ "has-border": list.label && list.label.color }', ":style" => "{ borderTopColor: (list.label && list.label.color ? list.label.color : null) }", "@click" => "toggleExpanded($event)" }
%h3.board-title.js-board-handle{ ":class" => '{ "user-can-drag": (!disabled && !list.preset) }' } %h3.board-title.js-board-handle{ ":class" => '{ "user-can-drag": (!disabled && !list.preset) }' }
%i.fa.fa-fw.board-title-expandable-toggle{ "v-if": "list.isExpandable",
":class": "{ \"fa-caret-down\": list.isExpanded, \"fa-caret-right\": !list.isExpanded && list.position === -1, \"fa-caret-left\": !list.isExpanded && list.position !== -1 }",
"aria-hidden": "true" }
%span.has-tooltip{ ":title" => '(list.label ? list.label.description : "")', %span.has-tooltip{ ":title" => '(list.label ? list.label.description : "")',
data: { container: "body", placement: "bottom" } } data: { container: "body", placement: "bottom" } }
{{ list.title }} {{ list.title }}
...@@ -10,13 +13,13 @@ ...@@ -10,13 +13,13 @@
%span.board-issue-count.pull-left{ ":class" => '{ "has-btn": list.type !== "closed" && !disabled }' } %span.board-issue-count.pull-left{ ":class" => '{ "has-btn": list.type !== "closed" && !disabled }' }
{{ list.issuesSize }} {{ list.issuesSize }}
- if can?(current_user, :admin_issue, @project) - if can?(current_user, :admin_issue, @project)
%button.btn.btn-small.btn-default.pull-right.has-tooltip{ type: "button", %button.btn.btn-small.btn-default.pull-right.has-tooltip.js-no-trigger-collapse{ type: "button",
"@click" => "showNewIssueForm", "@click" => "showNewIssueForm",
"v-if" => 'list.type !== "closed"', "v-if" => 'list.type !== "closed"',
"aria-label" => "New issue", "aria-label" => "New issue",
"title" => "New issue", "title" => "New issue",
data: { placement: "top", container: "body" } } data: { placement: "top", container: "body" } }
= icon("plus") = icon("plus", class: "js-no-trigger-collapse")
- if can?(current_user, :admin_list, @project) - if can?(current_user, :admin_list, @project)
%board-delete{ "inline-template" => true, %board-delete{ "inline-template" => true,
":list" => "list", ":list" => "list",
......
---
title: Expand/collapse backlog & closed lists in issue boards
merge_request:
author:
...@@ -27,7 +27,7 @@ describe Projects::Boards::ListsController do ...@@ -27,7 +27,7 @@ describe Projects::Boards::ListsController do
parsed_response = JSON.parse(response.body) parsed_response = JSON.parse(response.body)
expect(response).to match_response_schema('lists') expect(response).to match_response_schema('lists')
expect(parsed_response.length).to eq 2 expect(parsed_response.length).to eq 3
end end
context 'with unauthorized user' do context 'with unauthorized user' do
......
...@@ -6,6 +6,12 @@ FactoryGirl.define do ...@@ -6,6 +6,12 @@ FactoryGirl.define do
sequence(:position) sequence(:position)
end end
factory :backlog_list, parent: :list do
list_type :backlog
label nil
position nil
end
factory :closed_list, parent: :list do factory :closed_list, parent: :list do
list_type :closed list_type :closed
label nil label nil
......
...@@ -231,7 +231,7 @@ describe 'Issue Boards add issue modal', :feature, :js do ...@@ -231,7 +231,7 @@ describe 'Issue Boards add issue modal', :feature, :js do
click_button 'Add 1 issue' click_button 'Add 1 issue'
end end
page.within(first('.board')) do page.within(find('.board:nth-child(2)')) do
expect(page).to have_selector('.card') expect(page).to have_selector('.card')
end end
end end
...@@ -247,7 +247,7 @@ describe 'Issue Boards add issue modal', :feature, :js do ...@@ -247,7 +247,7 @@ describe 'Issue Boards add issue modal', :feature, :js do
click_button 'Add 1 issue' click_button 'Add 1 issue'
end end
page.within(find('.board:nth-child(2)')) do page.within(find('.board:nth-child(3)')) do
expect(page).to have_selector('.card') expect(page).to have_selector('.card')
end end
end end
......
This diff is collapsed.
...@@ -25,11 +25,11 @@ describe 'Issue Boards', :feature, :js do ...@@ -25,11 +25,11 @@ describe 'Issue Boards', :feature, :js do
visit namespace_project_board_path(project.namespace, project, board) visit namespace_project_board_path(project.namespace, project, board)
wait_for_requests wait_for_requests
expect(page).to have_selector('.board', count: 2) expect(page).to have_selector('.board', count: 3)
end end
it 'has un-ordered issue as last issue' do it 'has un-ordered issue as last issue' do
page.within(first('.board')) do page.within(find('.board:nth-child(2)')) do
expect(all('.card').last).to have_content(issue4.title) expect(all('.card').last).to have_content(issue4.title)
end end
end end
...@@ -39,7 +39,7 @@ describe 'Issue Boards', :feature, :js do ...@@ -39,7 +39,7 @@ describe 'Issue Boards', :feature, :js do
wait_for_requests wait_for_requests
page.within(first('.board')) do page.within(find('.board:nth-child(2)')) do
expect(first('.card')).to have_content(issue4.title) expect(first('.card')).to have_content(issue4.title)
end end
end end
...@@ -50,7 +50,7 @@ describe 'Issue Boards', :feature, :js do ...@@ -50,7 +50,7 @@ describe 'Issue Boards', :feature, :js do
visit namespace_project_board_path(project.namespace, project, board) visit namespace_project_board_path(project.namespace, project, board)
wait_for_requests wait_for_requests
expect(page).to have_selector('.board', count: 2) expect(page).to have_selector('.board', count: 3)
end end
it 'moves from middle to top' do it 'moves from middle to top' do
...@@ -113,50 +113,50 @@ describe 'Issue Boards', :feature, :js do ...@@ -113,50 +113,50 @@ describe 'Issue Boards', :feature, :js do
visit namespace_project_board_path(project.namespace, project, board) visit namespace_project_board_path(project.namespace, project, board)
wait_for_requests wait_for_requests
expect(page).to have_selector('.board', count: 3) expect(page).to have_selector('.board', count: 4)
end end
it 'moves to top of another list' do it 'moves to top of another list' do
drag(list_from_index: 0, list_to_index: 1) drag(list_from_index: 1, list_to_index: 2)
wait_for_requests wait_for_requests
expect(first('.board')).to have_selector('.card', count: 2) expect(find('.board:nth-child(2)')).to have_selector('.card', count: 2)
expect(all('.board')[1]).to have_selector('.card', count: 4) expect(all('.board')[2]).to have_selector('.card', count: 4)
page.within(all('.board')[1]) do page.within(all('.board')[2]) do
expect(first('.card')).to have_content(issue3.title) expect(first('.card')).to have_content(issue3.title)
end end
end end
it 'moves to bottom of another list' do it 'moves to bottom of another list' do
drag(list_from_index: 0, list_to_index: 1, to_index: 2) drag(list_from_index: 1, list_to_index: 2, to_index: 2)
wait_for_requests wait_for_requests
expect(first('.board')).to have_selector('.card', count: 2) expect(find('.board:nth-child(2)')).to have_selector('.card', count: 2)
expect(all('.board')[1]).to have_selector('.card', count: 4) expect(all('.board')[2]).to have_selector('.card', count: 4)
page.within(all('.board')[1]) do page.within(all('.board')[2]) do
expect(all('.card').last).to have_content(issue3.title) expect(all('.card').last).to have_content(issue3.title)
end end
end end
it 'moves to index of another list' do it 'moves to index of another list' do
drag(list_from_index: 0, list_to_index: 1, to_index: 1) drag(list_from_index: 1, list_to_index: 2, to_index: 1)
wait_for_requests wait_for_requests
expect(first('.board')).to have_selector('.card', count: 2) expect(find('.board:nth-child(2)')).to have_selector('.card', count: 2)
expect(all('.board')[1]).to have_selector('.card', count: 4) expect(all('.board')[2]).to have_selector('.card', count: 4)
page.within(all('.board')[1]) do page.within(all('.board')[2]) do
expect(all('.card')[1]).to have_content(issue3.title) expect(all('.card')[1]).to have_content(issue3.title)
end end
end end
end end
def drag(selector: '.board-list', list_from_index: 0, from_index: 0, to_index: 0, list_to_index: 0) def drag(selector: '.board-list', list_from_index: 1, from_index: 0, to_index: 0, list_to_index: 1)
drag_to(selector: selector, drag_to(selector: selector,
scrollable: '#board-app', scrollable: '#board-app',
list_from_index: list_from_index, list_from_index: list_from_index,
......
...@@ -15,15 +15,15 @@ describe 'Issue Boards new issue', feature: true, js: true do ...@@ -15,15 +15,15 @@ describe 'Issue Boards new issue', feature: true, js: true do
visit namespace_project_board_path(project.namespace, project, board) visit namespace_project_board_path(project.namespace, project, board)
wait_for_requests wait_for_requests
expect(page).to have_selector('.board', count: 2) expect(page).to have_selector('.board', count: 3)
end end
it 'displays new issue button' do it 'displays new issue button' do
expect(page).to have_selector('.board-issue-count-holder .btn', count: 1) expect(first('.board')).to have_selector('.board-issue-count-holder .btn', count: 1)
end end
it 'does not display new issue button in closed list' do it 'does not display new issue button in closed list' do
page.within('.board:nth-child(2)') do page.within('.board:nth-child(3)') do
expect(page).not_to have_selector('.board-issue-count-holder .btn') expect(page).not_to have_selector('.board-issue-count-holder .btn')
end end
end end
......
...@@ -13,7 +13,7 @@ describe 'Issue Boards', feature: true, js: true do ...@@ -13,7 +13,7 @@ describe 'Issue Boards', feature: true, js: true do
let!(:issue2) { create(:labeled_issue, project: project, labels: [development, stretch], relative_position: 1) } let!(:issue2) { create(:labeled_issue, project: project, labels: [development, stretch], relative_position: 1) }
let(:board) { create(:board, project: project) } let(:board) { create(:board, project: project) }
let!(:list) { create(:list, board: board, label: development, position: 0) } let!(:list) { create(:list, board: board, label: development, position: 0) }
let(:card) { first('.board').first('.card') } let(:card) { find('.board:nth-child(2)').first('.card') }
before do before do
Timecop.freeze Timecop.freeze
...@@ -74,7 +74,7 @@ describe 'Issue Boards', feature: true, js: true do ...@@ -74,7 +74,7 @@ describe 'Issue Boards', feature: true, js: true do
wait_for_requests wait_for_requests
page.within(first('.board')) do page.within(find('.board:nth-child(2)')) do
expect(page).to have_selector('.card', count: 1) expect(page).to have_selector('.card', count: 1)
end end
end end
...@@ -101,7 +101,7 @@ describe 'Issue Boards', feature: true, js: true do ...@@ -101,7 +101,7 @@ describe 'Issue Boards', feature: true, js: true do
end end
it 'removes the assignee' do it 'removes the assignee' do
card_two = first('.board').find('.card:nth-child(2)') card_two = find('.board:nth-child(2)').find('.card:nth-child(2)')
click_card(card_two) click_card(card_two)
page.within('.assignee') do page.within('.assignee') do
...@@ -154,7 +154,7 @@ describe 'Issue Boards', feature: true, js: true do ...@@ -154,7 +154,7 @@ describe 'Issue Boards', feature: true, js: true do
expect(page).to have_content(user.name) expect(page).to have_content(user.name)
end end
page.within(first('.board')) do page.within(find('.board:nth-child(2)')) do
find('.card:nth-child(2)').trigger('click') find('.card:nth-child(2)').trigger('click')
end end
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
"id": { "type": "integer" }, "id": { "type": "integer" },
"list_type": { "list_type": {
"type": "string", "type": "string",
"enum": ["label", "closed"] "enum": ["backlog", "label", "closed"]
}, },
"label": { "label": {
"type": ["object", "null"], "type": ["object", "null"],
......
import Vue from 'vue';
import '~/boards/services/board_service';
import '~/boards/components/board';
import '~/boards/models/list';
describe('Board component', () => {
let vm;
let el;
beforeEach((done) => {
loadFixtures('boards/show.html.raw');
el = document.createElement('div');
document.body.appendChild(el);
// eslint-disable-next-line no-undef
gl.boardService = new BoardService('/', '/', 1);
vm = new gl.issueBoards.Board({
propsData: {
boardId: '1',
disabled: false,
issueLinkBase: '/',
rootPath: '/',
// eslint-disable-next-line no-undef
list: new List({
id: 1,
position: 0,
title: 'test',
list_type: 'backlog',
}),
},
}).$mount(el);
Vue.nextTick(done);
});
afterEach(() => {
vm.$destroy();
// remove the component from the DOM
document.querySelector('.board').remove();
localStorage.removeItem(`boards.${vm.boardId}.${vm.list.type}.expanded`);
});
it('board is expandable when list type is backlog', () => {
expect(
vm.$el.classList.contains('is-expandable'),
).toBe(true);
});
it('board is expandable when list type is closed', (done) => {
vm.list.type = 'closed';
Vue.nextTick(() => {
expect(
vm.$el.classList.contains('is-expandable'),
).toBe(true);
done();
});
});
it('board is not expandable when list type is label', (done) => {
vm.list.type = 'label';
vm.list.isExpandable = false;
Vue.nextTick(() => {
expect(
vm.$el.classList.contains('is-expandable'),
).toBe(false);
done();
});
});
it('collapses when clicking header', (done) => {
vm.$el.querySelector('.board-header').click();
Vue.nextTick(() => {
expect(
vm.$el.classList.contains('is-collapsed'),
).toBe(true);
done();
});
});
it('created sets isExpanded to true from localStorage', (done) => {
vm.$el.querySelector('.board-header').click();
return Vue.nextTick()
.then(() => {
expect(
vm.$el.classList.contains('is-collapsed'),
).toBe(true);
// call created manually
vm.$options.created[0].call(vm);
return Vue.nextTick();
})
.then(() => {
expect(
vm.$el.classList.contains('is-collapsed'),
).toBe(true);
done();
});
});
});
require 'spec_helper'
describe Projects::BoardsController, '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
let(:admin) { create(:admin) }
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project, :repository, namespace: namespace, path: 'boards-project') }
render_views
before(:all) do
clean_frontend_fixtures('boards/')
end
before(:each) do
sign_in(admin)
end
it 'boards/show.html.raw' do |example|
get(:index,
namespace_id: project.namespace,
project_id: project)
expect(response).to be_success
store_frontend_fixture(response, example.description)
end
end
...@@ -14,8 +14,9 @@ describe Boards::CreateService, services: true do ...@@ -14,8 +14,9 @@ describe Boards::CreateService, services: true do
it 'creates the default lists' do it 'creates the default lists' do
board = service.execute board = service.execute
expect(board.lists.size).to eq 1 expect(board.lists.size).to eq 2
expect(board.lists.first).to be_closed expect(board.lists.first).to be_backlog
expect(board.lists.last).to be_closed
end end
end end
......
...@@ -13,6 +13,7 @@ describe Boards::Issues::ListService, services: true do ...@@ -13,6 +13,7 @@ describe Boards::Issues::ListService, services: true do
let(:p2) { create(:label, title: 'P2', project: project, priority: 2) } let(:p2) { create(:label, title: 'P2', project: project, priority: 2) }
let(:p3) { create(:label, title: 'P3', project: project, priority: 3) } let(:p3) { create(:label, title: 'P3', project: project, priority: 3) }
let!(:backlog) { create(:backlog_list, board: board) }
let!(:list1) { create(:list, board: board, label: development, position: 0) } let!(:list1) { create(:list, board: board, label: development, position: 0) }
let!(:list2) { create(:list, board: board, label: testing, position: 1) } let!(:list2) { create(:list, board: board, label: testing, position: 1) }
let!(:closed) { create(:closed_list, board: board) } let!(:closed) { create(:closed_list, board: board) }
...@@ -53,12 +54,20 @@ describe Boards::Issues::ListService, services: true do ...@@ -53,12 +54,20 @@ describe Boards::Issues::ListService, services: true do
expect(issues).to eq [opened_issue2, reopened_issue1, opened_issue1] expect(issues).to eq [opened_issue2, reopened_issue1, opened_issue1]
end end
it 'returns opened issues when listing issues from Backlog' do
params = { board_id: board.id, id: backlog.id }
issues = described_class.new(project, user, params).execute
expect(issues).to eq [opened_issue2, reopened_issue1, opened_issue1]
end
it 'returns closed issues when listing issues from Closed' do it 'returns closed issues when listing issues from Closed' do
params = { board_id: board.id, id: closed.id } params = { board_id: board.id, id: closed.id }
issues = described_class.new(project, user, params).execute issues = described_class.new(project, user, params).execute
expect(issues).to eq [closed_issue4, closed_issue2, closed_issue5, closed_issue3, closed_issue1] expect(issues).to eq [closed_issue4, closed_issue2, closed_issue3, closed_issue1]
end end
it 'returns opened issues that have label list applied when listing issues from a label list' do it 'returns opened issues that have label list applied when listing issues from a label list' do
......
require 'spec_helper' require 'spec_helper'
describe Boards::Lists::ListService, services: true do describe Boards::Lists::ListService, services: true do
let(:project) { create(:empty_project) }
let(:board) { create(:board, project: project) }
let(:label) { create(:label, project: project) }
let!(:list) { create(:list, board: board, label: label) }
let(:service) { described_class.new(project, double) }
describe '#execute' do describe '#execute' do
it "returns board's lists" do context 'when the board has a backlog list' do
project = create(:empty_project) let!(:backlog_list) { create(:backlog_list, board: board) }
board = create(:board, project: project)
label = create(:label, project: project) it 'does not create a backlog list' do
list = create(:list, board: board, label: label) expect { service.execute(board) }.not_to change(board.lists, :count)
end
it "returns board's lists" do
expect(service.execute(board)).to eq [backlog_list, list, board.closed_list]
end
end
service = described_class.new(project, double) context 'when the board does not have a backlog list' do
it 'creates a backlog list' do
expect { service.execute(board) }.to change(board.lists, :count).by(1)
end
expect(service.execute(board)).to eq [list, board.closed_list] it "returns board's lists" do
expect(service.execute(board)).to eq [board.backlog_list, list, board.closed_list]
end
end end
end end
end end
...@@ -48,7 +48,7 @@ module JavaScriptFixturesHelpers ...@@ -48,7 +48,7 @@ module JavaScriptFixturesHelpers
link_tags = doc.css('link') link_tags = doc.css('link')
link_tags.remove link_tags.remove
scripts = doc.css("script:not([type='text/template'])") scripts = doc.css("script:not([type='text/template']):not([type='text/x-template'])")
scripts.remove scripts.remove
fixture = doc.to_html fixture = doc.to_html
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment