Merge pull request #7540 from opf/merge/release_9_1_in_dev
Merge Release/9.1 into dev [ci skip]pull/7543/head
commit
18f7d5afb1
@ -0,0 +1,42 @@ |
||||
import {Injector} from '@angular/core'; |
||||
import {WorkPackageCardViewComponent} from "core-components/wp-card-view/wp-card-view.component"; |
||||
import {CardClickHandler} from "core-components/wp-card-view/event-handler/click-handler"; |
||||
import {CardDblClickHandler} from "core-components/wp-card-view/event-handler/double-click-handler"; |
||||
import {CardRightClickHandler} from "core-components/wp-card-view/event-handler/right-click-handler"; |
||||
|
||||
export interface CardEventHandler { |
||||
EVENT:string; |
||||
SELECTOR:string; |
||||
|
||||
handleEvent(card:WorkPackageCardViewComponent, evt:JQueryEventObject):void; |
||||
|
||||
eventScope(card:WorkPackageCardViewComponent):JQuery; |
||||
} |
||||
|
||||
export class CardViewHandlerRegistry { |
||||
|
||||
constructor(public readonly injector:Injector) { |
||||
} |
||||
|
||||
private eventHandlers:((c:WorkPackageCardViewComponent) => CardEventHandler)[] = [ |
||||
// Clicking on the card (not within a cell)
|
||||
c => new CardClickHandler(this.injector, c), |
||||
// Double Clicking on the row (not within a cell)
|
||||
c => new CardDblClickHandler(this.injector, c), |
||||
// Right clicking on cards
|
||||
t => new CardRightClickHandler(this.injector, t), |
||||
]; |
||||
|
||||
attachTo(card:WorkPackageCardViewComponent) { |
||||
this.eventHandlers.map(factory => { |
||||
let handler = factory(card); |
||||
let target = handler.eventScope(card); |
||||
|
||||
target.on(handler.EVENT, handler.SELECTOR, (evt:JQueryEventObject) => { |
||||
handler.handleEvent(card, evt); |
||||
}); |
||||
|
||||
return handler; |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,72 @@ |
||||
import {Injector} from '@angular/core'; |
||||
import {CardEventHandler} from "core-components/wp-card-view/event-handler/card-view-handler-registry"; |
||||
import {WorkPackageCardViewComponent} from "core-components/wp-card-view/wp-card-view.component"; |
||||
import {WorkPackageTableSelection} from "core-components/wp-fast-table/state/wp-table-selection.service"; |
||||
import {WorkPackageTableFocusService} from "core-components/wp-fast-table/state/wp-table-focus.service"; |
||||
import {WorkPackageCardViewService} from "core-components/wp-card-view/services/wp-card-view.service"; |
||||
|
||||
export class CardClickHandler implements CardEventHandler { |
||||
|
||||
// Injections
|
||||
public wpTableSelection:WorkPackageTableSelection = this.injector.get(WorkPackageTableSelection); |
||||
public wpTableFocus:WorkPackageTableFocusService = this.injector.get(WorkPackageTableFocusService); |
||||
public wpCardView:WorkPackageCardViewService = this.injector.get(WorkPackageCardViewService); |
||||
|
||||
constructor(public readonly injector:Injector, |
||||
card:WorkPackageCardViewComponent) { |
||||
} |
||||
|
||||
public get EVENT() { |
||||
return 'click.cardView.card'; |
||||
} |
||||
|
||||
public get SELECTOR() { |
||||
return `.wp-card`; |
||||
} |
||||
|
||||
public eventScope(card:WorkPackageCardViewComponent) { |
||||
return jQuery(card.container.nativeElement); |
||||
} |
||||
|
||||
public handleEvent(card:WorkPackageCardViewComponent, evt:JQueryEventObject) { |
||||
let target = jQuery(evt.target); |
||||
|
||||
// Ignore links
|
||||
if (target.is('a') || target.parent().is('a')) { |
||||
return true; |
||||
} |
||||
|
||||
// Locate the card from event
|
||||
let element = target.closest(this.SELECTOR); |
||||
let wpId = element.data('workPackageId'); |
||||
let classIdentifier = element.data('classIdentifier'); |
||||
|
||||
if (!wpId) { |
||||
return true; |
||||
} |
||||
|
||||
let index = this.wpCardView.findRenderedCard(classIdentifier); |
||||
|
||||
// Update single selection if no modifier present
|
||||
if (!(evt.ctrlKey || evt.metaKey || evt.shiftKey)) { |
||||
this.wpTableSelection.setSelection(wpId, index); |
||||
} |
||||
|
||||
// Multiple selection if shift present
|
||||
if (evt.shiftKey) { |
||||
this.wpTableSelection.setMultiSelectionFrom(this.wpCardView.renderedCards, wpId, index); |
||||
} |
||||
|
||||
// Single selection expansion if ctrl / cmd(mac)
|
||||
if (evt.ctrlKey || evt.metaKey) { |
||||
this.wpTableSelection.toggleRow(wpId); |
||||
} |
||||
|
||||
// The current card is the last selected work package
|
||||
// not matter what other card are (de-)selected below.
|
||||
// Thus save that card for the details view button.
|
||||
this.wpTableFocus.updateFocus(wpId); |
||||
return false; |
||||
} |
||||
} |
||||
|
@ -0,0 +1,52 @@ |
||||
import {Injector} from '@angular/core'; |
||||
import {CardEventHandler} from "core-components/wp-card-view/event-handler/card-view-handler-registry"; |
||||
import {WorkPackageCardViewComponent} from "core-components/wp-card-view/wp-card-view.component"; |
||||
import {WorkPackageTableSelection} from "core-components/wp-fast-table/state/wp-table-selection.service"; |
||||
import {StateService} from "@uirouter/core"; |
||||
|
||||
export class CardDblClickHandler implements CardEventHandler { |
||||
|
||||
// Injections
|
||||
public $state:StateService = this.injector.get(StateService); |
||||
public wpTableSelection:WorkPackageTableSelection = this.injector.get(WorkPackageTableSelection); |
||||
|
||||
constructor(public readonly injector:Injector, |
||||
card:WorkPackageCardViewComponent) { |
||||
} |
||||
|
||||
public get EVENT() { |
||||
return 'dblclick.cardView.card'; |
||||
} |
||||
|
||||
public get SELECTOR() { |
||||
return `.wp-card`; |
||||
} |
||||
|
||||
public eventScope(card:WorkPackageCardViewComponent) { |
||||
return jQuery(card.container.nativeElement); |
||||
} |
||||
|
||||
public handleEvent(card:WorkPackageCardViewComponent, evt:JQueryEventObject) { |
||||
let target = jQuery(evt.target); |
||||
|
||||
// Ignore links
|
||||
if (target.is('a') || target.parent().is('a')) { |
||||
return true; |
||||
} |
||||
|
||||
// Locate the row from event
|
||||
let element = target.closest(this.SELECTOR); |
||||
let wpId = element.data('workPackageId'); |
||||
|
||||
if (!wpId) { |
||||
return true; |
||||
} |
||||
|
||||
this.$state.go( |
||||
'work-packages.show', |
||||
{workPackageId: wpId} |
||||
); |
||||
return false; |
||||
} |
||||
} |
||||
|
@ -0,0 +1,68 @@ |
||||
import {Injector} from '@angular/core'; |
||||
import {CardEventHandler} from "core-components/wp-card-view/event-handler/card-view-handler-registry"; |
||||
import {WorkPackageCardViewComponent} from "core-components/wp-card-view/wp-card-view.component"; |
||||
import {WorkPackageTableSelection} from "core-components/wp-fast-table/state/wp-table-selection.service"; |
||||
import {uiStateLinkClass} from "core-components/wp-fast-table/builders/ui-state-link-builder"; |
||||
import {debugLog} from "core-app/helpers/debug_output"; |
||||
import {WorkPackageCardViewService} from "core-components/wp-card-view/services/wp-card-view.service"; |
||||
import {OpWorkPackageContextMenu} from "core-components/op-context-menu/wp-context-menu/wp-table-context-menu.directive"; |
||||
import {OPContextMenuService} from "core-components/op-context-menu/op-context-menu.service"; |
||||
|
||||
export class CardRightClickHandler implements CardEventHandler { |
||||
|
||||
// Injections
|
||||
public wpTableSelection:WorkPackageTableSelection = this.injector.get(WorkPackageTableSelection); |
||||
public wpCardView:WorkPackageCardViewService = this.injector.get(WorkPackageCardViewService); |
||||
public opContextMenu:OPContextMenuService = this.injector.get(OPContextMenuService); |
||||
|
||||
constructor(public readonly injector:Injector, |
||||
card:WorkPackageCardViewComponent) { |
||||
} |
||||
|
||||
public get EVENT() { |
||||
return 'contextmenu.cardView.rightclick'; |
||||
} |
||||
|
||||
public get SELECTOR() { |
||||
return `.wp-card`; |
||||
} |
||||
|
||||
public eventScope(card:WorkPackageCardViewComponent) { |
||||
return jQuery(card.container.nativeElement); |
||||
} |
||||
|
||||
public handleEvent(card:WorkPackageCardViewComponent, evt:JQueryEventObject) { |
||||
let target = jQuery(evt.target); |
||||
|
||||
// We want to keep the original context menu on hrefs
|
||||
// (currently, this is only the id)
|
||||
if (target.closest(`.${uiStateLinkClass}`).length) { |
||||
debugLog('Allowing original context menu on state link'); |
||||
return true; |
||||
} |
||||
|
||||
evt.preventDefault(); |
||||
evt.stopPropagation(); |
||||
|
||||
// Locate the card from event
|
||||
const element = target.closest(this.SELECTOR); |
||||
const wpId = element.data('workPackageId'); |
||||
|
||||
if (!wpId) { |
||||
return true; |
||||
} else { |
||||
let classIdentifier = element.data('classIdentifier'); |
||||
let index = this.wpCardView.findRenderedCard(classIdentifier); |
||||
|
||||
if (!this.wpTableSelection.isSelected(wpId)) { |
||||
this.wpTableSelection.setSelection(wpId, index); |
||||
} |
||||
|
||||
const handler = new OpWorkPackageContextMenu(this.injector, wpId, jQuery(evt.target) as JQuery, {}, undefined, card.showInfoButton); |
||||
this.opContextMenu.show(handler, evt); |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
} |
||||
|
@ -0,0 +1,200 @@ |
||||
import {Inject, Injectable, Injector} from '@angular/core'; |
||||
import {WorkPackageResource} from "core-app/modules/hal/resources/work-package-resource"; |
||||
import {WorkPackageTableOrderService} from "core-components/wp-fast-table/state/wp-table-order.service"; |
||||
import {States} from "core-components/states.service"; |
||||
import {WorkPackageChangeset} from "core-components/wp-edit-form/work-package-changeset"; |
||||
import {IWorkPackageCreateServiceToken} from "core-components/wp-new/wp-create.service.interface"; |
||||
import {WorkPackageCreateService} from "core-components/wp-new/wp-create.service"; |
||||
import {WorkPackageNotificationService} from "core-components/wp-edit/wp-notification.service"; |
||||
import {CurrentProjectService} from "core-components/projects/current-project.service"; |
||||
import {WorkPackageInlineCreateService} from "core-components/wp-inline-create/wp-inline-create.service"; |
||||
import {DragAndDropService} from "core-app/modules/common/drag-and-drop/drag-and-drop.service"; |
||||
import {DragAndDropHelpers} from "core-app/modules/common/drag-and-drop/drag-and-drop.helpers"; |
||||
import {WorkPackageCardViewComponent} from "core-components/wp-card-view/wp-card-view.component"; |
||||
|
||||
@Injectable() |
||||
export class WorkPackageCardDragAndDropService { |
||||
|
||||
private _workPackages:WorkPackageResource[]; |
||||
|
||||
/** Whether the card view has an active inline created wp */ |
||||
public activeInlineCreateWp?:WorkPackageResource; |
||||
|
||||
/** A reference to the component in use, to have access to the current input variables */ |
||||
public cardView:WorkPackageCardViewComponent; |
||||
|
||||
public readonly dragService = this.injector.get(DragAndDropService, null); |
||||
|
||||
public constructor(readonly states:States, |
||||
readonly injector:Injector, |
||||
readonly reorderService:WorkPackageTableOrderService, |
||||
@Inject(IWorkPackageCreateServiceToken) readonly wpCreate:WorkPackageCreateService, |
||||
readonly wpNotifications:WorkPackageNotificationService, |
||||
readonly currentProject:CurrentProjectService, |
||||
readonly wpInlineCreate:WorkPackageInlineCreateService) { |
||||
|
||||
} |
||||
|
||||
public init(componentRef:WorkPackageCardViewComponent) { |
||||
this.cardView = componentRef; |
||||
} |
||||
|
||||
public destroy() { |
||||
if (this.dragService !== null) { |
||||
this.dragService.remove(this.cardView.container.nativeElement); |
||||
} |
||||
} |
||||
|
||||
public registerDragAndDrop() { |
||||
// The DragService may not have been provided
|
||||
// in which case we do not provide drag and drop
|
||||
if (this.dragService === null) { |
||||
return; |
||||
} |
||||
|
||||
this.dragService.register({ |
||||
dragContainer: this.cardView.container.nativeElement, |
||||
scrollContainers: [this.cardView.container.nativeElement], |
||||
moves: (card:HTMLElement) => { |
||||
const wpId:string = card.dataset.workPackageId!; |
||||
const workPackage = this.states.workPackages.get(wpId).value!; |
||||
|
||||
return this.cardView.canDragOutOf(workPackage) && !card.dataset.isNew; |
||||
}, |
||||
accepts: () => this.cardView.dragInto, |
||||
onMoved: async (card:HTMLElement) => { |
||||
const wpId:string = card.dataset.workPackageId!; |
||||
const toIndex = DragAndDropHelpers.findIndex(card); |
||||
|
||||
const newOrder = await this.reorderService.move(this.currentOrder, wpId, toIndex); |
||||
this.updateOrder(newOrder); |
||||
|
||||
this.cardView.onMoved.emit(); |
||||
}, |
||||
onRemoved: (card:HTMLElement) => { |
||||
const wpId:string = card.dataset.workPackageId!; |
||||
|
||||
const newOrder = this.reorderService.remove(this.currentOrder, wpId); |
||||
this.updateOrder(newOrder); |
||||
}, |
||||
onAdded: async (card:HTMLElement) => { |
||||
const wpId:string = card.dataset.workPackageId!; |
||||
const toIndex = DragAndDropHelpers.findIndex(card); |
||||
|
||||
const workPackage = this.states.workPackages.get(wpId).value!; |
||||
const result = await this.addWorkPackageToQuery(workPackage, toIndex); |
||||
|
||||
card.parentElement!.removeChild(card); |
||||
|
||||
return result; |
||||
} |
||||
}); |
||||
} |
||||
|
||||
/** |
||||
* Get the current work packages |
||||
*/ |
||||
public get workPackages():WorkPackageResource[] { |
||||
return this._workPackages; |
||||
} |
||||
|
||||
/** |
||||
* Set work packages array, |
||||
* remembering to keep the active inline-create |
||||
*/ |
||||
public set workPackages(workPackages:WorkPackageResource[]) { |
||||
if (this.activeInlineCreateWp) { |
||||
let existingNewWp = this._workPackages.find(o => o.isNew); |
||||
|
||||
// If there is already a card for a new WP,
|
||||
// we have to replace this one by the new activeInlineCreateWp
|
||||
if (existingNewWp) { |
||||
let index = this._workPackages.indexOf(existingNewWp); |
||||
this._workPackages[index] = this.activeInlineCreateWp; |
||||
} else { |
||||
this._workPackages = [this.activeInlineCreateWp, ...workPackages]; |
||||
} |
||||
} else { |
||||
this._workPackages = [...workPackages]; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Get current order |
||||
*/ |
||||
private get currentOrder():string[] { |
||||
return this.workPackages |
||||
.filter(wp => wp && !wp.isNew) |
||||
.map(el => el.id!); |
||||
} |
||||
|
||||
/** |
||||
* Update current order |
||||
*/ |
||||
private updateOrder(newOrder:string[]) { |
||||
newOrder = _.uniq(newOrder); |
||||
|
||||
this.workPackages = newOrder.map(id => this.states.workPackages.get(id).value!); |
||||
this.cardView.cdRef.detectChanges(); |
||||
} |
||||
|
||||
/** |
||||
* Inline create a new card |
||||
*/ |
||||
public addNewCard() { |
||||
this.wpCreate |
||||
.createOrContinueWorkPackage(this.currentProject.identifier) |
||||
.then((changeset:WorkPackageChangeset) => { |
||||
this.activeInlineCreateWp = changeset.resource; |
||||
this.workPackages = this.workPackages; |
||||
this.cardView.cdRef.detectChanges(); |
||||
}); |
||||
} |
||||
|
||||
/** |
||||
* Add the given work package to the query |
||||
*/ |
||||
async addWorkPackageToQuery(workPackage:WorkPackageResource, toIndex:number = -1):Promise<boolean> { |
||||
try { |
||||
await this.cardView.workPackageAddedHandler(workPackage); |
||||
const newOrder = await this.reorderService.add(this.currentOrder, workPackage.id!, toIndex); |
||||
this.updateOrder(newOrder); |
||||
return true; |
||||
} catch (e) { |
||||
this.wpNotifications.handleRawError(e, workPackage); |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* Remove the new card |
||||
*/ |
||||
removeCard(wp:WorkPackageResource) { |
||||
const index = this.workPackages.indexOf(wp); |
||||
this.workPackages.splice(index, 1); |
||||
this.activeInlineCreateWp = undefined; |
||||
|
||||
if (!wp.isNew) { |
||||
const newOrder = this.reorderService.remove(this.currentOrder, wp.id!); |
||||
this.updateOrder(newOrder); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* On new card saved |
||||
*/ |
||||
async onCardSaved(wp:WorkPackageResource) { |
||||
if (this.activeInlineCreateWp && this.activeInlineCreateWp.__initialized_at === wp.__initialized_at) { |
||||
const index = this.workPackages.indexOf(this.activeInlineCreateWp); |
||||
this.activeInlineCreateWp = undefined; |
||||
|
||||
// Add this item to the results
|
||||
const newOrder = await this.reorderService.add(this.currentOrder, wp.id!, index); |
||||
this.updateOrder(newOrder); |
||||
|
||||
// Notify inline create service
|
||||
this.wpInlineCreate.newInlineWorkPackageCreated.next(wp.id!); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,37 @@ |
||||
import {IsolatedQuerySpace} from "core-app/modules/work_packages/query-space/isolated-query-space"; |
||||
import {Injectable} from '@angular/core'; |
||||
import {WorkPackageResource} from "core-app/modules/hal/resources/work-package-resource"; |
||||
import {RenderedRow} from "core-components/wp-fast-table/builders/primary-render-pass"; |
||||
|
||||
@Injectable() |
||||
export class WorkPackageCardViewService { |
||||
public constructor(readonly querySpace:IsolatedQuerySpace) { |
||||
} |
||||
|
||||
public classIdentifier(wp:WorkPackageResource) { |
||||
// The same class names are used for the proximity to the table representation.
|
||||
return `wp-row-${wp.id}`; |
||||
} |
||||
|
||||
public get renderedCards() { |
||||
return this.querySpace.rendered.getValueOr([]); |
||||
} |
||||
|
||||
public findRenderedCard(classIdentifier:string):number { |
||||
const index = _.findIndex(this.renderedCards, (card) => card.classIdentifier === classIdentifier); |
||||
|
||||
return index; |
||||
} |
||||
|
||||
public updateRenderedCardsValues(workPackages:WorkPackageResource[]) { |
||||
this.querySpace.rendered.putValue( |
||||
workPackages.map((wp) => { |
||||
return { |
||||
classIdentifier: this.classIdentifier(wp), |
||||
workPackageId: wp.id, |
||||
hidden: false |
||||
} as RenderedRow; |
||||
}) |
||||
) |
||||
} |
||||
} |
@ -0,0 +1,71 @@ |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2018 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 docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
require 'spec_helper' |
||||
|
||||
describe 'Select work package card', type: :feature, js: true, selenium: true do |
||||
let(:user) { FactoryBot.create(:admin) } |
||||
let(:project) { FactoryBot.create(:project) } |
||||
let(:work_package_1) { FactoryBot.create(:work_package, project: project) } |
||||
let(:work_package_2) { FactoryBot.create(:work_package, project: project) } |
||||
let(:wp_table) { ::Pages::WorkPackagesTable.new(project) } |
||||
let(:wp_card_view) { WorkPackageCards.new(project) } |
||||
|
||||
let(:display_representation) { ::Components::WorkPackages::DisplayRepresentation.new } |
||||
|
||||
before do |
||||
login_as(user) |
||||
|
||||
work_package_1 |
||||
work_package_2 |
||||
|
||||
wp_table.visit! |
||||
wp_table.expect_work_package_listed(work_package_1) |
||||
wp_table.expect_work_package_listed(work_package_2) |
||||
|
||||
display_representation.switch_to_card_layout |
||||
end |
||||
|
||||
describe 'opening' do |
||||
it 'the full screen view via double click' do |
||||
wp_card_view.open_full_screen_by_doubleclick(work_package_1) |
||||
expect(page).to have_selector('.work-packages--details--subject', |
||||
text: work_package_1.subject) |
||||
end |
||||
|
||||
it 'the split screen of the selected WP' do |
||||
wp_card_view.select_work_package(work_package_2) |
||||
find('#work-packages-details-view-button').click |
||||
split_wp = Pages::SplitWorkPackage.new(work_package_2) |
||||
split_wp.expect_attributes Subject: work_package_2.subject |
||||
|
||||
find('#work-packages-details-view-button').click |
||||
expect(page).to have_no_selector('.work-packages--details') |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,53 @@ |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2018 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 docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
require 'spec_helper' |
||||
|
||||
class WorkPackageCards |
||||
include Capybara::DSL |
||||
include RSpec::Matchers |
||||
attr_reader :project |
||||
|
||||
def initialize(project = nil) |
||||
@project = project |
||||
end |
||||
|
||||
def open_full_screen_by_doubleclick(work_package) |
||||
loading_indicator_saveguard |
||||
page.driver.browser.action.double_click(card(work_package).native).perform |
||||
|
||||
Pages::FullWorkPackage.new(work_package, project) |
||||
end |
||||
|
||||
def select_work_package(work_package) |
||||
card(work_package).click |
||||
end |
||||
|
||||
def card(work_package) |
||||
page.find(".wp-card-#{work_package.id}") |
||||
end |
||||
end |
Loading…
Reference in new issue