Remove accessible-by-keyboard a11y component (#9331)

* Replace accessible-by-keyboard

* Replace messed up inplace styles with work-package-comment bem block

The styles are not used anymore except for work package comments anyway

* Replace split view icons with buttons

* Make back-button its own bem block

* Replace button in hide-section component

* Replace activity buttons

* Replace notification icon

* Replace collapsible-section

* Replace config-menu

* Make attribute help text trigger a button

* Ensure all buttons are role=button

* Replace inplace save controls

* Replace relations buttons

* Replace relation toggle button

* Replace relation row buttons

* Replace breadcrumb parent

* Replace activities toggler

* Remove accessible-by-keyboard component

* Fix button role to button type

* Fix indent

* Bemmify media query

* Fix specs
This commit is contained in:
Oliver Günther
2021-05-31 16:18:14 +02:00
committed by GitHub
parent e246c4ef25
commit e363d2f2fd
51 changed files with 446 additions and 733 deletions
+1
View File
@@ -446,6 +446,7 @@ en:
label_quote_comment: "Quote this comment"
label_recent: "Recent"
label_reset: "Reset"
label_remove: "Remove"
label_remove_column: "Remove column"
label_remove_columns: "Remove selected columns"
label_remove_row: "Remove row"
+1 -2
View File
@@ -11,8 +11,7 @@ OpenProject is a hybrid application with most parts being Rails, along with some
The Angular frontend services and components can be tested with frontend specs. A good isolated example on how to set up an Angular TestBed to test components is `frontend/src/app/modules/a11y/accessible-by-keyboard.component.spec.ts`
The Angular frontend services and components can be tested with frontend specs.
If you want to test services that have no dependencies, a simple instantiation of that class is sufficient to test the service in isolation. A good example is ` frontend/src/app/components/projects/current-project.service.spec.ts`
@@ -1,87 +1,115 @@
<ng-container>
<!-- Name -->
<label for="operators-{{filter.id}}"
class="advanced-filters--filter-name"
[textContent]="filter.name"
[attr.title]="filter.name">
<label
for="operators-{{filter.id}}"
class="advanced-filters--filter-name"
[textContent]="filter.name"
[attr.title]="filter.name"
>
</label>
<!-- Operator -->
<div class="advanced-filters--filter-operator">
<label for="operators-{{filter.id}}"
class="hidden-for-sighted">
<label
for="operators-{{filter.id}}"
class="hidden-for-sighted"
>
{{ filter.name }}
{{ text.open_filter }}
</label>
<select required
*ngIf="valueType !== '[1]Boolean'"
class="advanced-filters--select"
id="operators-{{filter.id}}"
name="op[{{filter.id}}]"
[(ngModel)]="filter.operator"
(ngModelChange)="onFilterUpdated(filter)"
[compareWith]="compareByHref"
style="vertical-align: top;">
<option *ngFor="let operator of availableOperators"
[textContent]="operator.name"
[ngValue]="operator">
<select
required
*ngIf="valueType !== '[1]Boolean'"
class="advanced-filters--select"
id="operators-{{filter.id}}"
name="op[{{filter.id}}]"
[(ngModel)]="filter.operator"
(ngModelChange)="onFilterUpdated(filter)"
[compareWith]="compareByHref"
style="vertical-align: top;"
>
<option
*ngFor="let operator of availableOperators"
[textContent]="operator.name"
[ngValue]="operator"
>
</option>
</select>
<filter-boolean-value *ngIf="valueType == '[1]Boolean'"
(filterChanged)="onFilterUpdated($event)"
[shouldFocus]="shouldFocus"
[filter]="filter"></filter-boolean-value>
<filter-boolean-value
*ngIf="valueType == '[1]Boolean'"
(filterChanged)="onFilterUpdated($event)"
[shouldFocus]="shouldFocus"
[filter]="filter"
></filter-boolean-value>
</div>
<!-- Values -->
<ng-container *ngIf="showValuesInput && valueType">
<div class="advanced-filters--filter-value" [ngSwitch]="valueType">
<filter-integer-value *ngSwitchCase="'[1]Integer'"
(filterChanged)="onFilterUpdated($event)"
[shouldFocus]="shouldFocus"
[filter]="filter"></filter-integer-value>
<filter-integer-value
*ngSwitchCase="'[1]Integer'"
(filterChanged)="onFilterUpdated($event)"
[shouldFocus]="shouldFocus"
[filter]="filter"
></filter-integer-value>
<filter-date-value *ngSwitchCase="'[1]Date'"
(filterChanged)="onFilterUpdated($event)"
[shouldFocus]="shouldFocus"
[filter]="filter"></filter-date-value>
<filter-date-value
*ngSwitchCase="'[1]Date'"
(filterChanged)="onFilterUpdated($event)"
[shouldFocus]="shouldFocus"
[filter]="filter"
></filter-date-value>
<filter-dates-value *ngSwitchCase="'[2]Date'"
(filterChanged)="onFilterUpdated($event)"
[shouldFocus]="shouldFocus"
[filter]="filter"></filter-dates-value>
<filter-dates-value
*ngSwitchCase="'[2]Date'"
(filterChanged)="onFilterUpdated($event)"
[shouldFocus]="shouldFocus"
[filter]="filter"
></filter-dates-value>
<filter-date-time-value *ngSwitchCase="'[1]DateTime'"
(filterChanged)="onFilterUpdated($event)"
[shouldFocus]="shouldFocus"
[filter]="filter"></filter-date-time-value>
<filter-date-time-value
*ngSwitchCase="'[1]DateTime'"
(filterChanged)="onFilterUpdated($event)"
[shouldFocus]="shouldFocus"
[filter]="filter"
></filter-date-time-value>
<filter-date-times-value *ngSwitchCase="'[2]DateTime'"
[shouldFocus]="shouldFocus"
(filterChanged)="onFilterUpdated($event)"
[filter]="filter"></filter-date-times-value>
<filter-date-times-value
*ngSwitchCase="'[2]DateTime'"
[shouldFocus]="shouldFocus"
(filterChanged)="onFilterUpdated($event)"
[filter]="filter"
></filter-date-times-value>
<filter-string-value *ngSwitchCase="'[1]String'"
(filterChanged)="onFilterUpdated($event)"
[shouldFocus]="shouldFocus"
[filter]="filter"></filter-string-value>
<filter-string-value
*ngSwitchCase="'[1]String'"
(filterChanged)="onFilterUpdated($event)"
[shouldFocus]="shouldFocus"
[filter]="filter"
></filter-string-value>
<filter-string-value *ngSwitchCase="'[1]Float'"
(filterChanged)="onFilterUpdated($event)"
[shouldFocus]="shouldFocus"
[filter]="filter"></filter-string-value>
<filter-string-value
*ngSwitchCase="'[1]Float'"
(filterChanged)="onFilterUpdated($event)"
[shouldFocus]="shouldFocus"
[filter]="filter"
></filter-string-value>
<filter-searchable-multiselect-value *ngSwitchCase="'[]WorkPackage'"
(filterChanged)="onFilterUpdated($event)"
[shouldFocus]="shouldFocus"
[filter]="filter"></filter-searchable-multiselect-value>
<filter-searchable-multiselect-value
*ngSwitchCase="'[]WorkPackage'"
(filterChanged)="onFilterUpdated($event)"
[shouldFocus]="shouldFocus"
[filter]="filter"
></filter-searchable-multiselect-value>
<filter-toggled-multiselect-value *ngSwitchDefault
(filterChanged)="onFilterUpdated($event)"
[shouldFocus]="shouldFocus"
[filter]="filter"></filter-toggled-multiselect-value>
<filter-toggled-multiselect-value
*ngSwitchDefault
(filterChanged)="onFilterUpdated($event)"
[shouldFocus]="shouldFocus"
[filter]="filter"
></filter-toggled-multiselect-value>
</div>
</ng-container>
@@ -89,9 +117,13 @@
</div>
<div class="advanced-filters--remove-filter">
<accessible-by-keyboard (execute)="removeThisFilter()">
<op-icon icon-classes="icon-close advanced-filters--remove-filter-icon"
[icon-title]="text.button_delete"></op-icon>
</accessible-by-keyboard>
<button
type="button"
class="op-link"
[title]="text.button_delete"
(click)="removeThisFilter()"
>
<op-icon icon-classes="icon-close advanced-filters--remove-filter-icon"></op-icon>
</button>
</div>
</ng-container>
@@ -3,36 +3,42 @@
<ng-container *ngIf="!showAbove" [ngTemplateOutlet]="template"></ng-container>
<div class="work-package--details--long-field work-packages--activity--add-comment hide-when-print"
#commentContainer
*ngIf="canAddComment">
<div
class="work-package--details--long-field work-packages--activity--add-comment hide-when-print"
#commentContainer
*ngIf="canAddComment"
>
<div class="inline-edit--container inplace-edit">
<edit-form-portal *ngIf="active"
[schemaInput]="schema"
[changeInput]="change"
[editFieldHandler]="handler">
<edit-form-portal
*ngIf="active"
[schemaInput]="schema"
[changeInput]="change"
[editFieldHandler]="handler"
>
</edit-form-portal>
<div *ngIf="!active"
(dragover)="startDragOverActivation($event)"
class="inplace-edit--read">
<accessible-by-keyboard
class="inplace-editing--trigger-container"
spanClass="inplace-editing--container inline-edit--display-field"
linkClass="inplace-editing--trigger-link"
[linkAriaLabel]="text.editTitle"
(execute)="activate()">
<span class="inplace-editing--trigger-text inline-edit--formattable-display-text -default">
<span [textContent]="text.placeholder"></span>
<div
*ngIf="!active"
(dragover)="startDragOverActivation($event)"
>
<button
class="work-package-comment inline-edit--display-field"
[title]="text.editTitle"
(click)="activate()"
>
<span
class="work-package-comment--text"
[textContent]="text.placeholder"
>
</span>
<span class="inplace-editing--trigger-icon">
<op-icon icon-classes="icon-edit" [icon-title]="text.editTitle"></op-icon>
</span>
</accessible-by-keyboard>
<op-icon
class="work-package-comment--icon"
icon-classes="icon-edit" [icon-title]="text.editTitle"
></op-icon>
</button>
</div>
</div>
</div>
</div>
<ng-container *ngIf="showAbove" [ngTemplateOutlet]="template"></ng-container>
</div>
@@ -6,19 +6,25 @@
class="wp-breadcrumb-parent breadcrumb-project-title nocut">
<span [textContent]="parent.name"></span>
</a>
<accessible-by-keyboard
(execute)="open()"
*ngIf="canModifyParent()"
[linkTitle]="parent ? text.edit_parent : text.set_parent"
linkClass="wp-relation--parent-change -no-decoration hide-when-print icon-small {{ parent ? 'icon-edit icon5' : 'icon-add icon4' }}">
<button
*ngIf="canModifyParent()"
type="button"
class="op-link wp-relation--parent-change -no-decoration hide-when-print"
[title]="parent ? text.edit_parent : text.set_parent"
(click)="open()"
>
<span *ngIf="!parent" [textContent]="text.set_parent"></span>
</accessible-by-keyboard>
<accessible-by-keyboard
(execute)="updateParent(null)"
<op-icon icon-classes="icon-small {{ parent ? 'icon-edit icon5 icon-no-color' : 'icon-add icon4' }}"></op-icon>
</button>
<button
*ngIf="canModifyParent() && parent"
[linkTitle]="text.remove_parent"
linkClass="wp-relation--parent-remove hide-when-print -no-decoration icon-small icon-remove icon4">
</accessible-by-keyboard>
type="button"
class="op-link wp-relation--parent-remove hide-when-print -no-decoration"
[title]="text.remove_parent"
(click)="updateParent(null)"
>
<op-icon icon-classes="icon-small icon-no-color icon-remove icon4"></op-icon>
</button>
</ng-container>
<wp-relations-autocomplete
@@ -25,17 +25,25 @@
</activity-link>
<div class="comments-icons"
[hidden]="shouldHideIcons()">
<accessible-by-keyboard *ngIf="userCanQuote"
(execute)="quoteComment()"
[linkTitle]="text.quote_comment">
<op-icon icon-classes="action-icon icon-quote" [icon-title]="text.quote_comment"></op-icon>
</accessible-by-keyboard>
<accessible-by-keyboard *ngIf="userCanEdit"
(execute)="activate()"
[linkTitle]="text.edit_comment"
class="edit-activity--{{activityNo}}">
<op-icon icon-classes="action-icon icon-edit" [icon-title]="text.edit_comment"></op-icon>
</accessible-by-keyboard>
<button
type="button"
*ngIf="userCanQuote"
class="op-link"
[title]="text.quote_comment"
(click)="quoteComment()"
>
<op-icon icon-classes="action-icon icon-quote"></op-icon>
</button>
<button
*ngIf="userCanEdit"
type="button"
class="op-link"
[ngClass]="'edit-activity--' + activityNo"
[title]="text.edit_comment"
(click)="activate()"
>
<op-icon icon-classes="action-icon icon-edit"></op-icon>
</button>
</div>
</div>
<img *ngIf="bcfSnapshotUrl"
@@ -11,12 +11,13 @@
</wp-relations-autocomplete>
</div>
<div class="wp-relations-controls-section relation-row">
<accessible-by-keyboard
linkClass="wp-create-relation--cancel"
(execute)="cancel()"
aria-hidden="false">
<op-icon icon-classes="icon-remove" [icon-title]="text.abort"></op-icon>
</accessible-by-keyboard>
<button
type="button"
class="op-link wp-create-relation--cancel"
(click)="cancel()"
>
<op-icon icon-classes="icon-remove icon-no-color" [icon-title]="text.abort"></op-icon>
</button>
</div>
</div>
</div>
@@ -1,6 +1,6 @@
<div *ngIf="workPackage && relatedWorkPackage"
class="relation-row relation-row-{{ relatedWorkPackage.id }}"
focus-within="'.wp-relations-controls-section accessible-by-keyboard'">
focus-within="'.wp-relations-controls-section button'">
<div class="grid-block hierarchy-item">
<div class="grid-content medium-2 collapse">
@@ -49,22 +49,26 @@
<div class="grid-content medium-2 collapse wp-relations-controls-section"
ng-class="{'-expanded': userInputs.showRelationInfo }">
<accessible-by-keyboard [linkAriaLabel]="text.description_label"
[linkTitle]="text.description_label"
linkClass="wp-relations--description-btn"
[ngClass]="{'-visible': showDescriptionInfo }"
(execute)="userInputs.showRelationInfo = !userInputs.showRelationInfo">
<op-icon icon-classes="icon-info1 -padded wp-relations--icon wp-relations--description-icon"
icon-title="text.toggleDescription"></op-icon>
</accessible-by-keyboard>
<accessible-by-keyboard *ngIf="relation.delete"
(execute)="removeRelation(relation)"
aria-hidden="false"
[linkAriaLabel]="text.removeButton"
[linkTitle]="text.removeButton"
linkClass="relation-row--remove-btn">
<op-icon icon-classes="icon-remove -padded wp-relations--icon" [icon-title]="text.removeButton"></op-icon>
</accessible-by-keyboard>
<button
type="button"
class="op-link wp-relations--description-btn"
[ngClass]="{'-visible': showDescriptionInfo }"
[title]="text.description_label"
(click)="userInputs.showRelationInfo = !userInputs.showRelationInfo"
>
<op-icon icon-classes="icon-info1 icon-no-color -padded wp-relations--icon wp-relations--description-icon"
[icon-title]="text.toggleDescription"></op-icon>
</button>
<button
*ngIf="!!relation.delete"
type="button"
class="op-link relation-row--remove-btn"
[ngClass]="{'-visible': showDescriptionInfo }"
[title]="text.removeButton"
(click)="removeRelation()"
>
<op-icon icon-classes="icon-remove icon-no-color -padded wp-relations--icon" [icon-title]="text.removeButton"></op-icon>
</button>
</div>
</div>
@@ -42,12 +42,13 @@
</wp-relations-autocomplete>
</div>
<div class="grid-content medium-1 collapse wp-relations-controls-section relation-row">
<accessible-by-keyboard
(execute)="toggleRelationsCreateForm()"
linkClass="wp-create-relation--cancel"
aria-hidden="false">
<op-icon icon-classes="icon-remove -padded" [icon-title]="text.abort"></op-icon>
</accessible-by-keyboard>
<button
type="button"
class="op-link wp-create-relation--cancel"
(click)="toggleRelationsCreateForm()"
>
<op-icon icon-classes="icon-remove icon-no-color -padded" [icon-title]="text.abort"></op-icon>
</button>
</div>
</div>
</div>
@@ -7,13 +7,15 @@
</div>
<div class="attributes-group--header-toggle"
*ngIf="firstGroup">
<accessible-by-keyboard id="wp-relation-group-by-toggle"
#wpRelationGroupByToggler
(execute)="toggleButton()"
linkClass="button -small -transparent -with-icon icon-group-by icon-small hide-when-print"
[linkAriaLabel]="togglerText">
<button
#wpRelationGroupByToggler
id="wp-relation-group-by-toggle"
type="button"
class="button -small -transparent -with-icon icon-group-by icon-small hide-when-print"
(click)="toggleButton()"
>
<span [textContent]="togglerText"></span>
</accessible-by-keyboard>
</button>
</div>
</div>
@@ -8,12 +8,14 @@
[ngClass]="{'-with-toggler': first && showToggler}"
*ngIf="first || inf.isNextDate">
<span class="activity-date--label" [textContent]="inf.date"></span>
<accessible-by-keyboard (execute)="toggleComments()"
*ngIf="first && showToggler"
linkClass="activity-comments--toggler button -small -transparent -with-icon icon-filter icon-small hide-when-print"
[linkAriaLabel]="togglerText">
<span [textContent]="togglerText"></span>
</accessible-by-keyboard>
<button
*ngIf="first && showToggler"
type="button"
class="activity-comments--toggler button -small -transparent -with-icon icon-filter icon-small hide-when-print"
[textContent]="togglerText"
(click)="toggleComments()"
>
</button>
</h3>
<activity-entry [workPackage]="workPackage"
@@ -1,4 +1,4 @@
<div class="form--selected-value--container" focus-within="accessible-by-keyboard">
<div class="form--selected-value--container" focus-within="button">
<span class="form--selected-value">
<span [ngClass]="{'deleting': deleting }">
<a [attr.href]="watcher.showUser.href">
@@ -13,13 +13,14 @@
</a>
</span>
</span>
<accessible-by-keyboard (execute)="remove()"
[linkAriaLabel]="text.remove"
[linkTitle]="text.remove"
linkClass="form--selected-value--remover">
<op-icon icon-classes="icon-remove"
[icon-title]="text.remove"></op-icon>
</accessible-by-keyboard>
<button
type="button"
class="op-link form--selected-value--remover"
[title]="text.remove"
(click)="remove()"
>
<op-icon icon-classes="icon-remove"></op-icon>
</button>
</div>
@@ -1,7 +1,8 @@
<accessible-by-keyboard
(execute)="openTableConfigurationModal()"
linkClass="wp-table--columns-selection"
[linkTitle]="text.configureTable"
[linkAriaLabel]="text.configureTable">
<op-icon icon-classes="icon-button icon-small icon-settings"></op-icon>
</accessible-by-keyboard>
<button
type="button"
class="op-link wp-table--columns-selection"
[title]="text.configureTable"
(click)="openTableConfigurationModal()"
>
<op-icon icon-classes="icon-button icon-small icon-no-color icon-settings"></op-icon>
</button>
@@ -3,24 +3,24 @@
class="op-work-package-tabs">
<ng-container slot="actions" *ngIf="view === 'split'">
<li class="tab-icon">
<accessible-by-keyboard
(execute)="switchToFullscreen()"
[linkAriaLabel]="text.details.goToFullScreen"
[linkTitle]="text.details.goToFullScreen"
linkClass="work-packages--details-fullscreen-icon"
spanClass="icon-context icon-to-fullscreen"
<button
type="button"
class="op-link work-packages--details-fullscreen-icon"
[title]="text.details.goToFullScreen"
(click)="switchToFullscreen()"
>
</accessible-by-keyboard>
<op-icon icon-classes="icon-context icon-no-color icon-to-fullscreen"></op-icon>
</button>
</li>
<li class="tab-icon">
<accessible-by-keyboard
(execute)="close()"
[linkAriaLabel]="text.details.close"
[linkTitle]="text.details.close"
linkClass="work-packages--details-close-icon"
spanClass="icon-context icon-close"
<button
type="button"
class="op-link work-packages--details-close-icon"
[title]="text.details.close"
(click)="close()"
>
</accessible-by-keyboard>
<op-icon icon-classes="icon-context icon-no-color icon-close"></op-icon>
</button>
</li>
</ng-container>
</op-scrollable-tabs>
@@ -6,6 +6,7 @@ import { WpTabWrapperComponent } from "core-components/wp-tabs/components/wp-tab
import { DynamicModule } from "ng-dynamic-component";
import { OpenprojectAccessibilityModule } from "core-app/modules/a11y/openproject-a11y.module";
import { OpenprojectTabsModule } from "core-app/modules/common/tabs/openproject-tabs.module";
import { IconModule } from "core-app/modules/icon/icon.module";
@NgModule({
declarations: [
@@ -17,7 +18,8 @@ import { OpenprojectTabsModule } from "core-app/modules/common/tabs/openproject-
UIRouterModule,
DynamicModule,
OpenprojectAccessibilityModule,
OpenprojectTabsModule
OpenprojectTabsModule,
IconModule
],
exports: [
WpTabsComponent,
@@ -1,68 +0,0 @@
//-- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
// 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-2013 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.
//++
import { AccessibleByKeyboardComponent } from "core-app/modules/a11y/accessible-by-keyboard.component";
import { ComponentFixture, TestBed } from '@angular/core/testing';
describe('accessibleByKeyboard component', () => {
beforeEach((() => {
// noinspection JSIgnoredPromiseFromCall
TestBed.configureTestingModule({
declarations: [
AccessibleByKeyboardComponent
]
}).compileComponents();
}));
describe('inner element', function() {
let app:AccessibleByKeyboardComponent;
let fixture:ComponentFixture<AccessibleByKeyboardComponent>;
let element:HTMLElement;
it('should render an inner link with specified classes', function() {
fixture = TestBed.createComponent(AccessibleByKeyboardComponent);
app = fixture.debugElement.componentInstance;
element = fixture.elementRef.nativeElement;
app.linkClass = 'a-link-class';
app.spanClass = 'a-span-class';
fixture.detectChanges();
const link = element.querySelector('a')!;
const span = element.querySelector('a > span')!;
expect(link.classList.contains('a-link-class')).toBeTruthy();
expect(span.classList.contains('a-span-class')).toBeTruthy();
});
});
});
@@ -1,62 +0,0 @@
//-- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2021 the OpenProject GmbH
//
// 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-2013 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.
//++
import { Component, EventEmitter, Input, Output } from '@angular/core';
@Component({
selector: 'accessible-by-keyboard',
template: `
<a (accessibleClick)="handleClick($event)"
role="link"
[ngClass]="linkClass"
[attr.disabled]="isDisabled || undefined"
[attr.title]="linkTitle"
[attr.aria-label]="linkAriaLabel"
href>
<span [ngClass]="spanClass">
<ng-content></ng-content>
</span>
</a>
`
})
export class AccessibleByKeyboardComponent {
@Output() execute = new EventEmitter<JQuery.TriggeredEvent>();
@Input() isDisabled:boolean;
@Input() linkClass:string;
@Input() linkTitle:string;
@Input() spanClass:string;
@Input() linkAriaLabel:string;
public handleClick(event:JQuery.TriggeredEvent) {
if (!this.isDisabled) {
this.execute.emit(event);
}
return false;
}
}
@@ -31,7 +31,6 @@ import { NgModule } from "@angular/core";
import { FormsModule } from "@angular/forms";
import { CommonModule } from "@angular/common";
import { AccessibleClickDirective } from "core-app/modules/a11y/accessible-click.directive";
import { AccessibleByKeyboardComponent } from "core-app/modules/a11y/accessible-by-keyboard.component";
import { DoubleClickOrTapDirective } from "core-app/modules/a11y/double-click-or-tap.directive";
@NgModule({
@@ -42,12 +41,10 @@ import { DoubleClickOrTapDirective } from "core-app/modules/a11y/double-click-or
exports: [
AccessibleClickDirective,
DoubleClickOrTapDirective,
AccessibleByKeyboardComponent,
],
declarations: [
AccessibleClickDirective,
DoubleClickOrTapDirective,
AccessibleByKeyboardComponent,
]
})
export class OpenprojectAccessibilityModule {
@@ -1,7 +1,11 @@
<accessible-by-keyboard (execute)="handleClick()"
*ngIf="exists"
[linkAriaLabel]="text.open_dialog"
linkClass="icon help-text--entry help-text--for-{{ attribute }}">
<op-icon icon-classes="icon icon-help1"></op-icon>
{{ additionalLabel }}
</accessible-by-keyboard>
<button
type="button"
*ngIf="exists"
class="op-link help-text--entry"
[attr.data-qa-help-text-for]="attribute"
[title]="text.open_dialog"
(click)="handleClick($event)"
>
<span *ngIf="additionalLabel" [textContent]="additionalLabel"></span>
<op-icon icon-classes="icon icon-no-color icon-help1"></op-icon>
</button>
@@ -93,10 +93,12 @@ export class AttributeHelpTextComponent implements OnInit {
}
}
public handleClick() {
public handleClick(event:Event):void {
this.load().then((resource) => {
this.opModalService.show(AttributeHelpTextModal, this.injector, { helpText: resource });
});
event.preventDefault();
}
private load() {
@@ -91,11 +91,13 @@
<op-date-time [dateTimeValue]="board.createdAt"></op-date-time>
</td>
<td class="buttons">
<accessible-by-keyboard *ngIf="board.editable"
(execute)="destroyBoard(board)"
linkClass="icon icon-delete">
{{ text.delete }}
</accessible-by-keyboard>
<button class="op-link"
type="button"
(click)="destroyBoard(board)"
>
<op-icon icon-classes="icon icon-delete"></op-icon>
<span [textContent]="text.delete"></span>
</button>
</td>
</tr>
</tbody>
@@ -0,0 +1,8 @@
<button
class="op-back-button button hide-when-print"
[ngClass]="linkClass"
[title]="text.goBack"
(click)="goBack()"
>
<op-icon icon-classes="op-back-button--icon button--icon icon-back-up"></op-icon>
</button>
@@ -0,0 +1,18 @@
.op-back-button
padding: 0
line-height: 32px
margin: 0 1rem 0 0
width: 75px
height: 34px
&--icon
font-size: 1rem
line-height: 22px
@media only screen and (max-width: 679px)
// Move button in the current reverse order to front
position: absolute
top: 0
left: 0
z-index: 1
width: 100%
@@ -31,22 +31,14 @@ import { BackRoutingService } from "core-app/modules/common/back-routing/back-ro
import { I18nService } from "core-app/modules/common/i18n/i18n.service";
@Component({
template: `
<div class="wp-show--back-button hide-when-print">
<accessible-by-keyboard (execute)="goBack()"
[linkClass]="classes()"
[linkAriaLabel]="text.goBack"
[linkTitle]="text.goBack">
<op-icon icon-classes="button--icon icon-back-up"></op-icon>
</accessible-by-keyboard>
</div>
`,
templateUrl: './back-button.component.html',
styleUrls: ['./back-button.component.sass'],
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'back-button',
selector: 'op-back-button',
})
export class BackButtonComponent {
@Input() public linkClass:string;
@Input() public customBackMethod:Function;
@Input() public customBackMethod:() => unknown;
public text = {
goBack: this.I18n.t('js.button_back')
@@ -56,18 +48,11 @@ export class BackButtonComponent {
readonly I18n:I18nService) {
}
public goBack() {
public goBack():void {
if (this.customBackMethod) {
this.customBackMethod();
} else {
this.backRoutingService.goBack();
}
}
public classes():string {
let classes = 'button ';
classes += this.linkClass ? this.linkClass : '';
return classes;
}
}
@@ -57,7 +57,8 @@ export class CollapsibleSectionComponent implements OnInit {
target.removeAttribute('hidden');
}
public toggle() {
public toggle(event:Event) {
this.expanded = !this.expanded;
event.preventDefault();
}
}
@@ -1,12 +1,14 @@
<section class="collapsible-section"
[ngClass]="{ '-expanded': expanded }">
<accessible-by-keyboard (execute)="toggle()"
[linkAriaLabel]="sectionTitle"
linkClass="collapsible-section--toggle-link"
spanClass="collapsible-section--legend">
<span [textContent]="sectionTitle"></span>
</accessible-by-keyboard>
<button
class="collapsible-section--toggle-link"
(click)="toggle($event)"
>
<span class="collapsible-section--legend"
[textContent]="sectionTitle">
</span>
</button>
<div class="collapsible-section--body toggle-slide-animation"
#sectionBody
[hidden]="!expanded">
@@ -1,6 +1,8 @@
<accessible-by-keyboard
(execute)="hideSection()"
linkTitle="Remove"
linkAriaLabel="Remove">
<op-icon icon-classes="icon-close icon-small" icon-title="Remove"></op-icon>
</accessible-by-keyboard>
<button
type="button"
class="op-link"
[title]="text.remove"
(click)="hideSection()"
>
<op-icon icon-classes="icon-close icon-small"></op-icon>
</button>
@@ -28,6 +28,7 @@
import { Component, ElementRef, OnInit } from "@angular/core";
import { HideSectionService } from "core-app/modules/common/hide-section/hide-section.service";
import { I18nService } from "core-app/modules/common/i18n/i18n.service";
export const hideSectionLinkSelector = 'hide-section-link';
@@ -40,8 +41,13 @@ export class HideSectionLinkComponent implements OnInit {
public sectionName:string;
text = {
remove: this.I18n.t('js.label_remove')
}
constructor(protected elementRef:ElementRef,
protected hideSectionService:HideSectionService) {}
protected hideSectionService:HideSectionService,
protected I18n:I18nService) {}
ngOnInit():void {
this.sectionName = this.elementRef.nativeElement.dataset.sectionName;
@@ -16,4 +16,8 @@
&:focus
color: var(--content-link-hover-active-color)
// Ensure op-icon within a link gets inline-block
// to avoid it receiving any text-decoration on hover
.op-icon--wrapper
display: inline-block
@@ -39,9 +39,13 @@
</div>
</div>
</div>
<accessible-by-keyboard *ngIf="removable()"
(execute)="remove()"
[linkTitle]="text.close_popup"
linkClass="notification-box--close icon-context icon-close">
</accessible-by-keyboard>
<button
*ngIf="removable()"
type="button"
class="op-link notification-box--close"
[title]="text.close_popup"
(click)="remove()"
>
<op-icon icon-classes="icon-context icon-close icon-no-color"></op-icon>
</button>
</div>
@@ -1,15 +1,21 @@
<div class="inplace-edit--dashboard">
<div class="inplace-edit--controls">
<accessible-by-keyboard (execute)="save()"
[attr.disabled]="(field.required && field.isEmpty()) || undefined"
[linkTitle]="saveTitle"
class="inplace-edit--control inplace-edit--control--save">
<op-icon icon-classes="icon-checkmark" [icon-title]="saveTitle"></op-icon>
</accessible-by-keyboard>
<accessible-by-keyboard (execute)="cancel()"
[linkTitle]="cancelTitle"
class="inplace-edit--control inplace-edit--control--cancel">
<op-icon icon-classes="icon-close" [icon-title]="cancelTitle"></op-icon>
</accessible-by-keyboard>
<button
type="button"
class="inplace-edit--control inplace-edit--control--save"
[disabled]="(field.required && field.isEmpty()) || undefined"
[title]="saveTitle"
(click)="save()"
>
<op-icon icon-classes="icon-checkmark icon-no-color" [icon-title]="saveTitle"></op-icon>
</button>
<button
type="button"
class="inplace-edit--control inplace-edit--control--cancel"
[title]="cancelTitle"
(click)="cancel()"
>
<op-icon icon-classes="icon-close icon-no-color" [icon-title]="cancelTitle"></op-icon>
</button>
</div>
</div>
@@ -24,6 +24,7 @@
>
<b>{{ text.inviteUser }}</b> {{ principal.name }}
<button
type="button"
class="op-link"
(click)="principalControl?.setValue(null)"
>{{ text.changeUserSelection }}</button>
@@ -35,6 +36,7 @@
>
<b>{{ text.createNewPlaceholder }}</b> {{ principal.name }}
<button
type="button"
class="op-link"
(click)="principalControl?.setValue(null)"
>{{ text.changePlaceholderSelection }}</button>
@@ -2,10 +2,10 @@
[ngClass]="currentPartition">
<div class="toolbar-container -editable">
<div class="toolbar">
<back-button *ngIf="backButtonCallback"
<op-back-button *ngIf="backButtonCallback"
linkClass="back-button"
[customBackMethod]="backButtonCallback">
</back-button>
</op-back-button>
<editable-toolbar-title [title]="selectedTitle"
[inFlight]="toolbarDisabled"
@@ -9,7 +9,7 @@
<div id="toolbar">
<div class="wp-show--header-container">
<back-button linkClass="work-packages-back-button"></back-button>
<op-back-button linkClass="work-packages-back-button"></op-back-button>
<div class="subject-header">
<wp-subject></wp-subject>
@@ -10,10 +10,10 @@
<edit-form [resource]="workPackage">
<div class="wp-show--header-container">
<back-button *ngIf="showBackButton()"
<op-back-button *ngIf="showBackButton()"
linkClass="work-packages-back-button"
[customBackMethod]="backToList.bind(this)">
</back-button>
</op-back-button>
<div class="subject-header">
<wp-subject></wp-subject>
@@ -13,6 +13,10 @@
.collapsible-section--toggle-link
@include without-link-styling
background: none
border: none
width: 100%
text-align: left
display: block
cursor: pointer
color: var(--body-font-color)
@@ -17,201 +17,3 @@
</div>
</div>
```
## In place editing: Attributes
```
<div class="attributes-group">
<div class="attributes-group--header">
<div class="attributes-group--header-container">
<h3 class="attributes-group--header-text">
Details
</h3>
</div>
</div>
<div class="attributes-key-value">
<div class="attributes-key-value--key">
Custom Field
</div>
<div class="attributes-key-value--value-container">
<editable-attribute-field>
<div class="inline-edit--container -small">
<span title="Editable text field" class="inline-edit--display-field inplace-edit -editable">
Editable text field
</span>
</div>
</editable-attribute-field>
</div>
</div>
</div>
```
## In place editing: Single line of text
### Read mode
```
<div class="work-package--details--long-field work-packages--activity--add-comment">
<div class="inplace-edit--read">
<accessible-by-keyboard class="inplace-editing--trigger-container">
<a href="" class="inplace-editing--trigger-link">
<span class="inplace-editing--container inline-edit--display-field">
<span>Comment and type @ to notify other people</span>
<span class="inplace-editing--trigger-icon">
<op-icon class="op-icon--wrapper">
<i aria-hidden="true" class="icon-edit" title="Comment and type @ to notify other people"></i>
</op-icon>
</span>
</span>
</a>
</accessible-by-keyboard>
</div>
</div>
```
## In place editing: Multiple lines of text
### Read mode
```
<div class="attributes-group">
<div class="attributes-group--header">
<div class="attributes-group--header-container">
<div class="attributes-group--header-container">
<h3 class="attributes-group--header-text">Description</h3>
</div>
</div>
</div>
<editable-attribute-field>
<div class="inline-edit--container description -no-label">
<div>
<span class="inline-edit--display-field inplace-edit -editable">
<div class="read-value--html wiki highlight -multiline">
<p>
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor.
Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim
</p>
</div>
</span>
</div>
</div>
</editable-attribute-field>
</div>
```
### Edit mode
```
<div class="attributes-group ">
<div class="attributes-group--header">
<div class="attributes-group--header-container">
<h3 class="attributes-group--header-text ">
Description
</h3>
</div>
</div>
<div class="single-attribute wiki">
<span inplace-editor="" placeholder="Click to enter description..." autocomplete-path="/work_packages/auto_complete.json?project_id=undefined" >
<div class="inplace-edit type-wiki_textarea attribute-description textarea-wrapper" aria-busy="false" >
<div class="inplace-edit--write " >
<form class="inplace-edit--form " name="editForm" novalidate="">
<div class="inplace-edit--write-value">
<div class="jstElements">
<button type="button" class="jstb_strong" title="Strong">
<span>Strong</span>
</button>
<button type="button" class="jstb_em" title="Italic">
<span>Italic</span>
</button>
<button type="button" class="jstb_ins" title="Underline">
<span>Underline</span>
</button>
<button type="button" class="jstb_del" title="Deleted">
<span>Deleted</span></button><button type="button" class="jstb_code" title="Inline Code">
<span>Inline Code</span></button><span id="space1" class="jstSpacer">&nbsp;</span>
<button type="button" class="jstb_h1" title="Heading 1">
<span>Heading 1</span>
</button>
<button type="button" class="jstb_h2" title="Heading 2">
<span>Heading 2</span>
</button>
<button type="button" class="jstb_h3" title="Heading 3">
<span>Heading 3</span>
</button>
<span id="space2" class="jstSpacer">&nbsp;</span>
<button type="button" class="jstb_ul" title="Unordered List">
<span>Unordered List</span>
</button>
<button type="button" class="jstb_ol" title="Ordered List">
<span>Ordered List</span>
</button>
<span id="space3" class="jstSpacer">&nbsp;</span>
<button type="button" class="jstb_bq" title="Quote">
<span>Quote</span></button>
<button type="button" class="jstb_unbq" title="Unquote">
<span>Unquote</span>
</button>
<button type="button" class="jstb_pre" title="Preformatted Text">
<span>Preformatted Text</span></button><span id="space4" class="jstSpacer">&nbsp;</span>
<button type="button" class="jstb_link" title="Link to a Wiki page">
<span>Link to a Wiki page</span>
</button>
<button type="button" class="jstb_img" title="Image">
<span>Image</span>
</button>
<div class="jstb_help">
<a href="/help/wiki_syntax" title="Text formatting" class="icon icon-help1">
<span class="hidden-for-sighted">
Text formatting
</span>
</a>
</div>
<button class="jstb_preview icon-preview" type="button" title="Preview">
</button>
</div>
<div class="jstEditor">
<textarea wiki-toolbar="" class="focus-input inplace-edit--textarea " name="value" title="Description: Edit" aria-multiline="true" tabindex="0" aria-hidden="false" aria-disabled="false" aria-invalid="false" rows="5">
</textarea>
</div>
<div class="jstHandle"></div>
</div>
<div class="inplace-edit--dashboard">
<div class="inplace-edit--controls">
<accessible-by-keyboard class="inplace-edit--control inplace-edit--control--save">
<a href="" role="link" title="Description: Save">
<span>
<op-icon class="op-icon--wrapper">
<i class="icon-checkmark" title="Description: Save"></i>
<span class="hidden-for-sighted">Description: Save</span>
</op-icon>
</span>
</a>
</accessible-by-keyboard>
<accessible-by-keyboard class="inplace-edit--control inplace-edit--control--cancel">
<a href="" role="link" title="Description: Cancel">
<span>
<op-icon class="op-icon--wrapper">
<i class="icon-close" title="Description: Cancel"></i>
<span class="hidden-for-sighted">Description: Cancel</span>
</op-icon>
</span>
</a>
</accessible-by-keyboard>
</div>
</div>
</form>
</div>
</div>
</span>
</div>
</div>
```
@@ -47,11 +47,10 @@
// WP single view styles
@import work_packages/single_view/single_view
@import work_packages/single_view/attachments
@import work_packages/single_view/inplace_esque_fields
@import work_packages/single_view/work_package_comment
// WP fullscreen specific styles
@import work_packages/fullscreen/fullscreen
@import work_packages/fullscreen/back_button
// Tabs
@import work_packages/tabs/activities
@@ -1,24 +0,0 @@
.wp-show--back-button
min-width: 90px
height: 34px
a
padding: 0
line-height: 32px
margin: 0
width: 75px
height: 34px
.button--icon:before
font-size: 1rem
line-height: 22px
@media only screen and (max-width: 679px)
.router--work-packages-base &
// Move button in the current reverse order to front
position: absolute
top: 0
left: 0
z-index: 1
a
width: 100%
@@ -55,11 +55,12 @@
text-align: center
// Align controls via flex
display: flex
justify-content: space-evenly
align-items: center
float: right
// Disabled submit styles when not applicable
.inplace-edit--control--save[disabled] a,
.inplace-edit--control--send[disabled] a
.inplace-edit--control[disabled]
background-color: var(--inplace-edit--bg-color--disabled)
color: var(--inplace-edit--color--disabled)
cursor: not-allowed
@@ -67,19 +68,19 @@
// A single save/cancel control
.inplace-edit--control
font-size: 0.9rem
flex: 1
padding: 5px
a
// Center save/cancel links
width: 100%
line-height: 27px
border: 1px solid transparent
display: inline-block
color: var(--body-font-color)
text-decoration: none
width: 29px
height: 29px
background: none
&:hover, &:active
border-color: var(--inplace-edit--border-color)
// Center save/cancel links
line-height: 27px
border: 1px solid transparent
display: inline-block
color: var(--body-font-color)
text-decoration: none
&:hover, &:active
border-color: var(--inplace-edit--border-color)
.icon-context:before
padding: 0
@@ -1,76 +0,0 @@
// Add a border at all times to the inplace container
.inplace-editing--container
position: relative
// need to specify the a explicitly as otherwise
// the default class will win
a.inplace-editing--trigger-link,
.inplace-editing--trigger-link
&:hover,
&:focus,
&.-focus
text-decoration: none
color: var(--body-font-color)
&.-active
border-color: transparent
.inplace-editing--container
border-color: var(--inplace-edit--border-color)
.inplace-editing--trigger-icon
visibility: visible
// Do not hover trigger-link when element is read-only
.-read-only
.inplace-editing--trigger-link:hover .inplace-editing--container
border-color: transparent
// Explicit styles for input-esque trigger appearance
.work-packages--activity--add-comment,
.work-package--watchers-lookup
.inplace-editing--container
border: 1px solid #eee
// Explicit styling for the comment and watcher container
.work-packages--activity--add-comment,
.work-package--watchers-lookup
margin: 20px 0
// Styles the comment trigger link similar to attachment
// but without icon and actual link
a.inplace-editing--trigger-link
color: var(--body-font-color)
font-style: italic
// The trigger text left to the edit icon
.inplace-editing--trigger-text
width: calc(100% - 42px)
padding: 3px
line-height: 2
display: inline-block
// The edit icon to the right of the trigger text
.inplace-editing--trigger-icon
position: absolute
height: 100%
top: 0
right: 0
text-align: center
width: 32px
background: $gray-light
border-left: 1px solid var(--inplace-edit--border-color)
color: var(--body-font-color)
visibility: hidden
i
position: relative
// Position the icon in the middle
top: calc(50% - 0.5rem)
.icon-context:before
// HACK: overriding default padding here
padding-right: 0
&:hover
text-decoration: none
@@ -95,15 +95,6 @@ i
vertical-align: -2px
padding: 0 0 0 4px
.work-packages--details-close-icon span,
.work-packages--details-fullscreen-icon span
display: inline-block
padding-top: 2px
font-size: 0.9rem
&:before
color: var(--body-font-color)
// -------------------- Attribute groups --------------------
// Special styling for project context area
.attributes-group.-project-context
@@ -0,0 +1,41 @@
.work-package-comment
display: flex
align-items: center
width: 100%
color: var(--body-font-color)
padding: 0
margin: 0
background: none
border: 1px solid #eee
cursor: pointer
height: 42px
&:hover &,
&:focus &
border-color: var(--inplace-edit--border-color)
text-decoration: none
color: var(--body-font-color)
&--icon
visibility: visible
// The trigger text left to the edit icon
&--text
flex: 1
padding: 3px
// The edit icon to the right of the trigger text
&--icon
height: 100%
text-align: center
width: 32px
background: var(--gray-light)
border-left: 1px solid var(--inplace-edit--border-color)
color: var(--body-font-color)
visibility: hidden
// Align the icon centered
display: flex
align-items: center
justify-content: center
@@ -248,4 +248,4 @@
// Icon required for documents activity
.icon-document
@extend .icon-notes
@extend .icon-notes
@@ -52,7 +52,7 @@ module Components
def open!
SeleniumHubWaiter.wait
container.find(".help-text--for-#{help_text.attribute_name}").click
container.find("[data-qa-help-text-for='#{help_text.attribute_name}']").click
expect(page).to have_selector('.attribute-help-text--modal h1', text: help_text.attribute_caption)
end
@@ -52,7 +52,7 @@ describe 'Work package attribute help texts', type: :feature, js: true do
shared_examples 'allows to view help texts' do
it 'shows an indicator for whatever help text exists' do
expect(page).to have_selector('.work-package--single-view .help-text--for-status')
expect(page).to have_selector('.work-package--single-view [data-qa-help-text-for="status"]')
# Open help text modal
modal.open!
@@ -242,11 +242,11 @@ describe 'Work package relations tab', js: true, selenium: true do
textarea.set 'my description!'
# Save description
created_row.find('.inplace-edit--control--save a').click
created_row.find('.inplace-edit--control--save').click
loading_indicator_saveguard
# Wait for the relations table to be present
# Wait for the relations table to be presen
sleep 2
expect(page).to have_selector('.wp-relations--subject-field')
@@ -264,7 +264,7 @@ describe 'Work package relations tab', js: true, selenium: true do
text: 'my description!').click
# Cancel edition
created_row.find('.inplace-edit--control--cancel a').click
created_row.find('.inplace-edit--control--cancel').click
created_row.find('.wp-relation--description-read-value',
text: 'my description!').click
+10 -14
View File
@@ -78,14 +78,12 @@ describe 'Wysiwyg work package user mentions',
expect(page)
.to have_selector('a.mention', text: '@Foo Bar')
retry_block do
comment_field.submit_by_click if comment_field.active?
comment_field.submit_by_click if comment_field.active?
wp_page.expect_and_dismiss_notification message: "The comment was successfully added."
wp_page.expect_and_dismiss_notification message: "The comment was successfully added."
expect(page)
.to have_selector('a.user-mention', text: 'Foo Bar')
end
expect(page)
.to have_selector('a.user-mention', text: 'Foo Bar')
# Mentioning a group works
comment_field.activate!
@@ -99,21 +97,19 @@ describe 'Wysiwyg work package user mentions',
expect(page)
.to have_selector('a.mention', text: '@Foogroup')
retry_block do
comment_field.submit_by_click if comment_field.active?
comment_field.submit_by_click if comment_field.active?
wp_page.expect_and_dismiss_notification message: "The comment was successfully added."
wp_page.expect_and_dismiss_notification message: "The comment was successfully added."
expect(page)
.to have_selector('a.user-mention', text: 'Foogroup')
end
expect(page)
.to have_selector('a.user-mention', text: 'Foogroup')
# The mention is still displayed as such when reentering the comment field
find('#activity-1')
find('#activity-1 .op-user-activity')
.hover
within('#activity-1') do
click_link("Edit this comment")
click_button("Edit this comment")
end
expect(page)
+1 -1
View File
@@ -125,7 +125,7 @@ class EditField
end
def submit_by_dashboard
field_container.find('.inplace-edit--control--save a', wait: 5).click
field_container.find('.inplace-edit--control--save', wait: 5).click
end
##
@@ -42,7 +42,7 @@ class TextEditorField < EditField
end
def submit_by_click
target = field_container.find(control_link)
target = field_container.find(control_link, wait: 10)
scroll_to_element(target)
target.click
end
@@ -52,7 +52,7 @@ class TextEditorField < EditField
end
def cancel_by_click
target = field_container.find(control_link(:cancel))
target = field_container.find(control_link(:cancel), wait: 10)
scroll_to_element(target)
target.click
end
@@ -64,6 +64,6 @@ class TextEditorField < EditField
def control_link(action = :save)
raise 'Invalid link' unless %i[save cancel].include?(action)
".inplace-edit--control--#{action}"
".inplace-edit--control--#{action}:not([disabled])"
end
end
@@ -245,7 +245,7 @@ module Pages
end
def trigger_edit_comment
add_comment_container.find('.inplace-editing--trigger-link').click
add_comment_container.find('.work-package-comment').click
end
def update_comment(comment)
@@ -255,7 +255,7 @@ module Pages
def save_comment
label = 'Comment: Save'
add_comment_container.find(:xpath, "//a[@title='#{label}']").click
add_comment_container.find(:xpath, "//button[@title='#{label}']").click
end
def save!