commit
ee290a6c62
@ -0,0 +1,40 @@ |
||||
|
||||
// Style the hierarchy group indicator arrow |
||||
// default: open arrow down |
||||
// collapsed: left arrow |
||||
.wp-table--hierarchy-indicator |
||||
@include varprop(color, body-font-color) |
||||
|
||||
&:hover |
||||
text-decoration: none |
||||
|
||||
// Toggle the indicator accessibility texts |
||||
// accordingly |
||||
.wp-table--hierarchy-indicator-collapsed |
||||
display: none |
||||
|
||||
.-hierarchy-collapsed |
||||
.wp-table--hierarchy-indicator-expanded |
||||
display: none |
||||
.wp-table--hierarchy-indicator-collapsed |
||||
display: inline |
||||
|
||||
|
||||
.wp-table--hierarchy-indicator-icon |
||||
@include icon-common |
||||
@extend .icon-arrow-down2 |
||||
font-size: 0.75rem |
||||
|
||||
.-hierarchy-collapsed & |
||||
@extend .icon-arrow-right7 |
||||
|
||||
.wp-table--row[class*="__collapsed-group-"] |
||||
display: none |
||||
|
||||
.wp-table--hierarchy-td |
||||
min-width: 0px !important |
||||
|
||||
// Highlight the additional hierarchy |
||||
// so it becomes clear they're not part of the sort |
||||
.wp-table--hierarchy-aditional-row |
||||
@include varprop(background, gray-light) |
@ -0,0 +1,238 @@ |
||||
import {collapsedGroupClass, hierarchyGroupClass, hierarchyRootClass} from '../../helpers/wp-table-hierarchy-helpers'; |
||||
import {WorkPackageTableHierarchyService} from '../../state/wp-table-hierarchy.service'; |
||||
import {WorkPackageTableMetadata} from '../../wp-table-metadata'; |
||||
import {UiStateLinkBuilder} from '../ui-state-link-builder'; |
||||
import {WorkPackageResourceInterface} from '../../../api/api-v3/hal-resources/work-package-resource.service'; |
||||
import {HalResource} from '../../../api/api-v3/hal-resources/hal-resource.service'; |
||||
import {WorkPackageTableRow} from '../../wp-table.interfaces'; |
||||
import {PlainRowsBuilder} from './plain-rows-builder'; |
||||
import {RowsBuilder} from './rows-builder'; |
||||
import {States} from '../../../states.service'; |
||||
import {injectorBridge} from '../../../angular/angular-injector-bridge.functions'; |
||||
import {WorkPackageTableColumnsService} from '../../state/wp-table-columns.service'; |
||||
import {WorkPackageTable} from '../../wp-fast-table'; |
||||
import {SingleRowBuilder} from './single-row-builder'; |
||||
|
||||
export const indicatorCollapsedClass = '-hierarchy-collapsed'; |
||||
export const hierarchyCellClassName = 'wp-table--hierarchy-span'; |
||||
|
||||
export class HierarchyRowsBuilder extends PlainRowsBuilder { |
||||
// Injections
|
||||
public states:States; |
||||
public wpTableColumns:WorkPackageTableColumnsService; |
||||
public wpTableHierarchy:WorkPackageTableHierarchyService; |
||||
public I18n:op.I18n; |
||||
|
||||
public uiStateBuilder = new UiStateLinkBuilder(); |
||||
public text:{ |
||||
leaf:(level:number) => string; |
||||
expanded:(level:number) => string; |
||||
collapsed:(level:number) => string; |
||||
}; |
||||
|
||||
// The group expansion state
|
||||
constructor() { |
||||
super(); |
||||
injectorBridge(this); |
||||
|
||||
this.text = { |
||||
leaf: (level:number) => I18n.t('js.work_packages.hierarchy.leaf', { level: level }), |
||||
expanded: (level:number) => I18n.t('js.work_packages.hierarchy.children_expanded', { level: level }), |
||||
collapsed: (level:number) => I18n.t('js.work_packages.hierarchy.children_collapsed', { level: level }), |
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* The hierarchy builder is only applicable if the hierachy mode is active |
||||
*/ |
||||
public isApplicable(table:WorkPackageTable, metaData:WorkPackageTableMetadata) { |
||||
return this.wpTableHierarchy.isEnabled; |
||||
} |
||||
|
||||
/** |
||||
* Rebuild the entire grouped tbody from the given table |
||||
* @param table |
||||
*/ |
||||
public buildRows(table:WorkPackageTable):DocumentFragment { |
||||
// Remember all additional rows drawn for hierarchy
|
||||
const additional:{[workPackageId:string]: WorkPackageResourceInterface} = {}; |
||||
|
||||
const tbodyContent = document.createDocumentFragment(); |
||||
|
||||
table.rows.forEach((wpId:string) => { |
||||
let row:WorkPackageTableRow = table.rowIndex[wpId]; |
||||
|
||||
// If this row was already rendered in a hierarchy, ignore it here
|
||||
if (additional[row.workPackageId]) { |
||||
return; |
||||
} |
||||
|
||||
// If we have ancestors
|
||||
if (row.object.ancestors.length) { |
||||
this.buildWithHierarchy(table, tbodyContent, row, additional); |
||||
} else { |
||||
let tr = this.buildEmptyRow(row); |
||||
row.element = tr; |
||||
tbodyContent.appendChild(tr); |
||||
} |
||||
|
||||
additional[row.object.id] = row.object; |
||||
}); |
||||
|
||||
return tbodyContent; |
||||
} |
||||
|
||||
public get colspan():number { |
||||
return this.wpTableColumns.columnCount + 1; |
||||
} |
||||
|
||||
public buildEmptyRow(row:WorkPackageTableRow, table?:WorkPackageTable, level?:number) { |
||||
level = level || row.object.ancestors.length; |
||||
const element = this.rowBuilder.buildEmpty(row.object); |
||||
const hierarchyIndicator = this.buildHierarchyIndicator(row.object, level); |
||||
const state = this.wpTableHierarchy.currentState; |
||||
|
||||
row.object.ancestors.forEach((ancestor:WorkPackageResourceInterface) => { |
||||
element.classList.add(`__hierarchy-group-${ancestor.id}`); |
||||
|
||||
if (state.collapsed[ancestor.id]) { |
||||
element.classList.add(collapsedGroupClass(ancestor.id)); |
||||
} |
||||
}); |
||||
|
||||
element.classList.add(`__hierarchy-root-${row.object.id}`); |
||||
jQuery(element).find('td.subject').prepend(hierarchyIndicator); |
||||
return element; |
||||
} |
||||
|
||||
/** |
||||
* Build the hierarchy indicator at the given indentation level. |
||||
*/ |
||||
private buildHierarchyIndicator(workPackage:WorkPackageResourceInterface, level:number):HTMLElement { |
||||
const hierarchyIndicator = document.createElement('span'); |
||||
const collapsed = this.wpTableHierarchy.collapsed(workPackage.id); |
||||
hierarchyIndicator.classList.add(hierarchyCellClassName); |
||||
hierarchyIndicator.style.width = 10 + (10 * level) + 'px'; |
||||
hierarchyIndicator.style.paddingLeft = (20 * level) + 'px'; |
||||
|
||||
if (workPackage.$loaded && workPackage.isLeaf) { |
||||
hierarchyIndicator.innerHTML = ` |
||||
<span tabindex="0" class="wp-table--leaf-indicator"> |
||||
<span class="hidden-for-sighted">${this.text.leaf(level)}</span> |
||||
</span> |
||||
`;
|
||||
} else { |
||||
const className = collapsed ? indicatorCollapsedClass : ''; |
||||
hierarchyIndicator.innerHTML = ` |
||||
<a href tabindex="0" role="button" class="wp-table--hierarchy-indicator ${className}"> |
||||
<span class="wp-table--hierarchy-indicator-icon"></span> |
||||
<span class="wp-table--hierarchy-indicator-expanded hidden-for-sighted">${this.text.expanded(level)}</span> |
||||
<span class="wp-table--hierarchy-indicator-collapsed hidden-for-sighted">${this.text.collapsed(level)}</span> |
||||
</a> |
||||
`;
|
||||
} |
||||
return hierarchyIndicator; |
||||
} |
||||
|
||||
private buildWithHierarchy( |
||||
table:WorkPackageTable, |
||||
tbody:DocumentFragment, |
||||
row:WorkPackageTableRow, |
||||
additional:{[workPackageId:string]: WorkPackageResourceInterface}) { |
||||
|
||||
// Ancestor data [root, med, thisrow]
|
||||
const ancestors = row.object.ancestors; |
||||
const ancestorGroups:string[] = []; |
||||
ancestors.forEach((ancestor:WorkPackageResourceInterface, index:number) => { |
||||
if (!additional[ancestor.id]) { |
||||
let ancestorRow = this.buildAncestorRow(table, ancestor, ancestorGroups, index); |
||||
// special case, root without parent
|
||||
if (index === 0) { |
||||
// Simply append the root here
|
||||
tbody.appendChild(ancestorRow); |
||||
|
||||
} else { |
||||
// This ancestor must be inserted in the last position of its root
|
||||
const parent = ancestors[index-1]; |
||||
this.insertIntoHierarchy(tbody, ancestorRow, parent.id); |
||||
} |
||||
|
||||
additional[ancestor.id] = ancestor; |
||||
ancestorGroups.push(hierarchyGroupClass(ancestor.id)); |
||||
} |
||||
}); |
||||
|
||||
// Insert this row to parent
|
||||
const parent = _.last(ancestors); |
||||
const tr = this.buildEmptyRow(row); |
||||
row.element = tr; |
||||
this.insertIntoHierarchy(tbody, tr, parent.id); |
||||
} |
||||
|
||||
/** |
||||
* Append a row to the given parent hierarchy group. |
||||
*/ |
||||
private insertIntoHierarchy(tbody:DocumentFragment, tr:HTMLElement, parentId:string) { |
||||
// Either append to the hierarchy group root (= the parentID row itself)
|
||||
const hierarchyRoot = `.__hierarchy-root-${parentId}`; |
||||
// Or, if it has descendants, append to the LATEST of that set
|
||||
const hierarchyGroup = `.__hierarchy-group-${parentId}`; |
||||
jQuery(tbody).find(`${hierarchyRoot},${hierarchyGroup}`).last().after(tr); |
||||
} |
||||
|
||||
/** |
||||
* Append an additional ancestor row that is not yet loaded |
||||
*/ |
||||
private buildAncestorRow( |
||||
table:WorkPackageTable, |
||||
ancestor:WorkPackageResourceInterface, |
||||
ancestorGroups:string[], |
||||
index:number):HTMLElement { |
||||
|
||||
const loadedRow = table.rowIndex[ancestor.id]; |
||||
|
||||
if (loadedRow) { |
||||
const tr = this.buildEmptyRow(loadedRow, table, index); |
||||
tr.classList.add('wp-table--hierarchy-aditional-row'); |
||||
return tr; |
||||
} |
||||
|
||||
const tr = this.rowBuilder.createEmptyRow(ancestor); |
||||
const columns = this.wpTableColumns.currentState; |
||||
|
||||
tr.classList.add(`wp-table--hierarchy-aditional-row`, hierarchyRootClass(ancestor.id), ...ancestorGroups); |
||||
|
||||
// Set available information for ID and subject column
|
||||
// and print hierarchy indicator at subject field.
|
||||
columns.forEach((column:string, i:number) => { |
||||
const td = document.createElement('td'); |
||||
|
||||
if (column === 'subject') { |
||||
const textNode = document.createTextNode(ancestor.name); |
||||
td.appendChild(this.buildHierarchyIndicator(ancestor, index)); |
||||
td.appendChild(textNode); |
||||
} |
||||
|
||||
if (column === 'id') { |
||||
const link = this.uiStateBuilder.linkToShow( |
||||
ancestor.id, |
||||
ancestor.subject, |
||||
ancestor.id |
||||
); |
||||
|
||||
td.appendChild(link); |
||||
} |
||||
|
||||
tr.appendChild(td); |
||||
}); |
||||
|
||||
// Append details icon
|
||||
const td = document.createElement('td'); |
||||
tr.appendChild(td); |
||||
|
||||
return tr; |
||||
} |
||||
} |
||||
|
||||
|
||||
HierarchyRowsBuilder.$inject = ['wpTableColumns', 'wpTableHierarchy', 'states', 'I18n']; |
@ -0,0 +1,42 @@ |
||||
import {ClickOrEnterHandler} from '../click-or-enter-handler'; |
||||
import {WorkPackageTableHierarchyService} from '../../state/wp-table-hierarchy.service'; |
||||
import {injectorBridge} from '../../../angular/angular-injector-bridge.functions'; |
||||
import {WorkPackageTable} from '../../wp-fast-table'; |
||||
import {States} from '../../../states.service'; |
||||
import {TableEventHandler} from '../table-handler-registry'; |
||||
import {rowClassName} from '../../builders/rows/single-row-builder'; |
||||
|
||||
export class HierarchyClickHandler extends ClickOrEnterHandler { |
||||
// Injections
|
||||
public states:States; |
||||
public wpTableHierarchy:WorkPackageTableHierarchyService; |
||||
|
||||
constructor() { |
||||
super(); |
||||
injectorBridge(this); |
||||
} |
||||
|
||||
public get EVENT() { |
||||
return 'click.table.hierarchy'; |
||||
} |
||||
|
||||
public get SELECTOR() { |
||||
return `.${rowClassName} .wp-table--hierarchy-indicator `; |
||||
} |
||||
|
||||
public processEvent(table: WorkPackageTable, evt:JQueryEventObject):boolean { |
||||
let target = jQuery(evt.target); |
||||
|
||||
// Locate the row from event
|
||||
let element = target.closest(`.${rowClassName}`); |
||||
let wpId = element.data('workPackageId'); |
||||
|
||||
this.wpTableHierarchy.toggle(wpId); |
||||
|
||||
evt.stopImmediatePropagation(); |
||||
evt.preventDefault(); |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
HierarchyClickHandler.$inject = ['states', 'wpTableHierarchy']; |
@ -0,0 +1,56 @@ |
||||
import { |
||||
collapsedGroupClass, |
||||
hierarchyGroupClass, |
||||
hierarchyRootClass |
||||
} from '../../helpers/wp-table-hierarchy-helpers'; |
||||
import {indicatorCollapsedClass} from '../../builders/rows/hierarchy-rows-builder'; |
||||
import {WorkPackageTableHierarchyService} from '../../state/wp-table-hierarchy.service'; |
||||
import {injectorBridge} from '../../../angular/angular-injector-bridge.functions'; |
||||
import {WorkPackageTable} from '../../wp-fast-table'; |
||||
import {WPTableHierarchyState} from '../../wp-table.interfaces'; |
||||
import {States} from '../../../states.service'; |
||||
|
||||
export class HierarchyTransformer { |
||||
public wpTableHierarchy:WorkPackageTableHierarchyService; |
||||
public states:States; |
||||
|
||||
constructor(table:WorkPackageTable) { |
||||
injectorBridge(this); |
||||
let enabled = false; |
||||
|
||||
this.wpTableHierarchy.hierarchyState |
||||
.observeUntil(this.states.table.stopAllSubscriptions).subscribe((state:WPTableHierarchyState) => { |
||||
|
||||
if (enabled !== state.enabled) { |
||||
table.refreshBody(); |
||||
table.postRender(); |
||||
} else if (enabled) { |
||||
// No change in hierarchy mode
|
||||
// Refresh groups
|
||||
this.renderHierarchyState(state); |
||||
} |
||||
|
||||
enabled = state.enabled; |
||||
}); |
||||
} |
||||
|
||||
/** |
||||
* Update all currently visible rows to match the selection state. |
||||
*/ |
||||
private renderHierarchyState(state:WPTableHierarchyState) { |
||||
// Show all hierarchies
|
||||
jQuery('[class^="__hierarchy-group-"]').removeClass((i:number, classNames:string):string => { |
||||
return (classNames.match(/__collapsed-group-\d+/g) || []).join(' '); |
||||
}); |
||||
|
||||
// Hide all collapsed hierarchies
|
||||
_.each(state.collapsed, (isCollapsed:boolean, wpId:string) => { |
||||
// Hide/Show the descendants.
|
||||
jQuery(`.${hierarchyGroupClass(wpId)}`).toggleClass(collapsedGroupClass(wpId), isCollapsed); |
||||
// Toggle the root style
|
||||
jQuery(`.${hierarchyRootClass(wpId)} .wp-table--hierarchy-indicator`).toggleClass(indicatorCollapsedClass, isCollapsed); |
||||
}); |
||||
} |
||||
} |
||||
|
||||
HierarchyTransformer.$inject = ['wpTableHierarchy', 'states']; |
@ -0,0 +1,14 @@ |
||||
/** |
||||
* Returns the collapsed group class for the given ancestor id |
||||
*/ |
||||
export function collapsedGroupClass(ancestorId:string):string { |
||||
return `__collapsed-group-${ancestorId}`; |
||||
} |
||||
|
||||
export function hierarchyGroupClass(ancestorId:string):string { |
||||
return `__hierarchy-group-${ancestorId}`; |
||||
} |
||||
|
||||
export function hierarchyRootClass(ancestorId:string):string { |
||||
return `__hierarchy-root-${ancestorId}`; |
||||
} |
@ -0,0 +1,89 @@ |
||||
import {WorkPackageTableMetadata} from '../wp-table-metadata'; |
||||
import {States} from '../../states.service'; |
||||
import {opServicesModule} from '../../../angular-modules'; |
||||
import {State} from '../../../helpers/reactive-fassade'; |
||||
import {WPTableHierarchyState} from '../wp-table.interfaces'; |
||||
|
||||
export class WorkPackageTableHierarchyService { |
||||
|
||||
// The selected columns state of the current table instance
|
||||
public hierarchyState:State<WPTableHierarchyState>; |
||||
|
||||
constructor(public states: States, public QueryService:any) { |
||||
this.hierarchyState = states.table.hierarchies; |
||||
} |
||||
|
||||
/** |
||||
* Return whether the current hierarchy mode is active |
||||
*/ |
||||
public get isEnabled():boolean { |
||||
return this.currentState.enabled; |
||||
} |
||||
|
||||
public setEnabled(active:boolean = true) { |
||||
const state = this.currentState; |
||||
state.enabled = active; |
||||
|
||||
this.hierarchyState.put(state); |
||||
} |
||||
|
||||
/** |
||||
* Return whether the given wp ID is collapsed. |
||||
*/ |
||||
public collapsed(wpId:string):boolean { |
||||
return this.currentState.collapsed[wpId] === true; |
||||
} |
||||
|
||||
/** |
||||
* Collapse the hierarchy for this work package |
||||
*/ |
||||
public collapse(wpId:string):void { |
||||
this.setState(wpId, true); |
||||
} |
||||
|
||||
/** |
||||
* Expand the hierarchy for this work package |
||||
*/ |
||||
public expand(wpId:string):void { |
||||
this.setState(wpId, false); |
||||
} |
||||
|
||||
/** |
||||
* Toggle the hierarchy state |
||||
*/ |
||||
public toggle(wpId:string):void { |
||||
this.setState(wpId, !this.collapsed(wpId)); |
||||
} |
||||
|
||||
/** |
||||
* Set the collapse/expand state of the given work package id. |
||||
*/ |
||||
private setState(wpId:string, isCollapsed:boolean):void { |
||||
const state = this.currentState; |
||||
state.collapsed[wpId] = isCollapsed; |
||||
this.hierarchyState.put(state); |
||||
} |
||||
|
||||
/** |
||||
* Get current selection state. |
||||
*/ |
||||
public get currentState():WPTableHierarchyState { |
||||
const state = this.hierarchyState.getCurrentValue(); |
||||
|
||||
if (state == null) { |
||||
return this.initialState; |
||||
} |
||||
|
||||
return state; |
||||
} |
||||
|
||||
private get initialState():WPTableHierarchyState { |
||||
return { |
||||
enabled: false, |
||||
collapsed: {} |
||||
} as WPTableHierarchyState; |
||||
} |
||||
|
||||
} |
||||
|
||||
opServicesModule.service('wpTableHierarchy', WorkPackageTableHierarchyService); |
@ -0,0 +1,95 @@ |
||||
require 'spec_helper' |
||||
|
||||
describe 'Work Package table hierarchy', js: true do |
||||
let(:user) { FactoryGirl.create :admin } |
||||
let(:project) { FactoryGirl.create(:project) } |
||||
|
||||
let(:category) { FactoryGirl.create :category, project: project, name: 'Foo' } |
||||
|
||||
let!(:wp_root) { FactoryGirl.create(:work_package, project: project) } |
||||
let!(:wp_inter) { FactoryGirl.create(:work_package, project: project, parent: wp_root) } |
||||
let!(:wp_leaf) { FactoryGirl.create(:work_package, project: project, category: category, parent: wp_inter) } |
||||
let!(:wp_other) { FactoryGirl.create(:work_package, project: project) } |
||||
let(:wp_table) { Pages::WorkPackagesTable.new(project) } |
||||
let(:hierarchy) { ::Components::WorkPackages::Hierarchies.new } |
||||
|
||||
let!(:query) do |
||||
query = FactoryGirl.build(:query, user: user, project: project) |
||||
query.column_names = ['subject', 'category'] |
||||
query.filters.clear |
||||
query.add_filter('category_id', '=', [category.id]) |
||||
|
||||
query.save! |
||||
query |
||||
end |
||||
|
||||
def expect_listed(*wps) |
||||
wps.each do |wp| |
||||
wp_table.expect_work_package_listed(wp) |
||||
end |
||||
end |
||||
|
||||
def expect_hidden(*wps) |
||||
wps.each do |wp| |
||||
hierarchy.expect_hidden(wp) |
||||
end |
||||
end |
||||
|
||||
before do |
||||
login_as(user) |
||||
end |
||||
|
||||
it 'shows hierarchy correctly' do |
||||
wp_table.visit! |
||||
expect_listed(wp_root, wp_inter, wp_leaf, wp_other) |
||||
|
||||
hierarchy.expect_no_hierarchies |
||||
|
||||
# Hierarchy mode is disabled by default |
||||
hierarchy.enable_hierarchy |
||||
|
||||
hierarchy.expect_hierarchy_at(wp_root) |
||||
hierarchy.expect_hierarchy_at(wp_inter) |
||||
hierarchy.expect_leaf_at(wp_leaf) |
||||
hierarchy.expect_leaf_at(wp_other) |
||||
|
||||
# Toggling hierarchies hides the inner children |
||||
hierarchy.toggle_row(wp_root) |
||||
|
||||
# Root, other showing |
||||
expect_listed(wp_root, wp_other) |
||||
# Inter, Leaf hidden |
||||
expect_hidden(wp_inter, wp_leaf) |
||||
|
||||
# Show all again |
||||
hierarchy.toggle_row(wp_root) |
||||
expect_listed(wp_root, wp_other, wp_inter, wp_leaf) |
||||
|
||||
# Disable hierarchies |
||||
hierarchy.disable_hierarchy |
||||
hierarchy.expect_no_hierarchies |
||||
|
||||
# Now visiting the query for category |
||||
wp_table.visit_query(query) |
||||
|
||||
# Should only list the matching leaf |
||||
wp_table.expect_work_package_listed(wp_leaf) |
||||
|
||||
# When toggling hierarchies, shows root and intermediate node |
||||
# Hierarchy mode is disabled by default |
||||
hierarchy.enable_hierarchy |
||||
|
||||
hierarchy.expect_hierarchy_at(wp_root) |
||||
hierarchy.expect_hierarchy_at(wp_inter) |
||||
|
||||
hierarchy.toggle_row(wp_root) |
||||
expect_listed(wp_root) |
||||
expect_listed(wp_inter, wp_leaf) |
||||
|
||||
# Disabling hierarchy hides them again |
||||
hierarchy.disable_hierarchy |
||||
|
||||
expect(page).to have_no_selector("#wp-row-#{wp_root.id}") |
||||
expect(page).to have_no_selector("#wp-row-#{wp_inter.id}") |
||||
end |
||||
end |
@ -0,0 +1,75 @@ |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF) |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# 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 General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See doc/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
module Components |
||||
module WorkPackages |
||||
class Hierarchies |
||||
include Capybara::DSL |
||||
include RSpec::Matchers |
||||
|
||||
def enable_hierarchy |
||||
find('#work-packages-settings-button').click |
||||
page.find('#settingsDropdown a.menu-item', text: 'Display hierarchy').click |
||||
end |
||||
|
||||
def disable_hierarchy |
||||
find('#work-packages-settings-button').click |
||||
expect(page).to have_selector('#settingsDropdown .menu-item') |
||||
page.find('#settingsDropdown a.menu-item', text: 'Hide hierarchy').click |
||||
end |
||||
|
||||
def expect_no_hierarchies |
||||
expect(page).to have_no_selector('.wp-table--hierarchy-span') |
||||
end |
||||
|
||||
def expect_leaf_at(work_package) |
||||
expect(page).to have_selector("#wp-row-#{work_package.id} .wp-table--leaf-indicator") |
||||
end |
||||
|
||||
def expect_hierarchy_at(work_package, collapsed = false) |
||||
selector = "#wp-row-#{work_package.id} .wp-table--hierarchy-indicator" |
||||
collapsed_sel = ".-hierarchy-collapsed" |
||||
|
||||
if collapsed |
||||
expect(page).to have_selector("#{selector}#{collapsed_sel}") |
||||
else |
||||
expect(page).to have_selector(selector) |
||||
expect(page).to have_no_selector("#{selector}#{collapsed_sel}") |
||||
end |
||||
end |
||||
|
||||
def expect_hidden(work_package) |
||||
expect(page).to have_selector("#wp-row-#{work_package.id}", visible: :hidden) |
||||
end |
||||
|
||||
def toggle_row(work_package) |
||||
find("#wp-row-#{work_package.id} .wp-table--hierarchy-indicator").click |
||||
end |
||||
end |
||||
end |
||||
end |
Loading…
Reference in new issue