mirror of
https://github.com/opf/openproject.git
synced 2026-06-14 03:30:14 +00:00
[#64550] Port attribute-help-text to Primer dialog
Makes ng `attribute-help-text` interop with Primerized attribute help text dialogs.
This commit is contained in:
@@ -54,6 +54,10 @@ export class PathHelperService {
|
||||
return `${this.staticBase}/attachments/${attachmentIdentifier}/content`;
|
||||
}
|
||||
|
||||
public attributeHelpTextsShowDialogPath(id:string|number) {
|
||||
return `${this.staticBase}/attribute_help_texts/${id}/show_dialog`;
|
||||
}
|
||||
|
||||
public fileLinksPath():string {
|
||||
return `${this.api.v3.apiV3Base}/file_links`;
|
||||
}
|
||||
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
//-- copyright
|
||||
// OpenProject is an open source project management software.
|
||||
// Copyright (C) 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 COPYRIGHT and LICENSE files for more details.
|
||||
//++
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { TurboRequestsService } from 'core-app/core/turbo/turbo-requests.service';
|
||||
import { PathHelperService } from 'core-app/core/path-helper/path-helper.service';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AttributeHelpTextModalService {
|
||||
constructor(
|
||||
protected pathHelper:PathHelperService,
|
||||
protected turboRequests:TurboRequestsService,
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
public show(helpTextId:string):void {
|
||||
void this.turboRequests.requestStream(this.helpTextModalUrl(helpTextId));
|
||||
}
|
||||
|
||||
private helpTextModalUrl(helpTextId:string):string {
|
||||
return this.pathHelper.attributeHelpTextsShowDialogPath(helpTextId);
|
||||
}
|
||||
}
|
||||
+18
-7
@@ -1,14 +1,25 @@
|
||||
<div
|
||||
*ngIf="exists"
|
||||
<ng-container *ngIf="exists">
|
||||
<div
|
||||
class="spot-link help-text--entry"
|
||||
[attr.id]="buttonId"
|
||||
[attr.aria-labelledby]="tooltipId"
|
||||
[attr.data-qa-help-text-for]="attribute"
|
||||
[title]="text.open_dialog"
|
||||
(click)="handleClick($event)"
|
||||
(keydown.enter)="handleClick($event)"
|
||||
(keydown.space)="handleClick($event)"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<span *ngIf="additionalLabel" [textContent]="additionalLabel"></span>
|
||||
<svg question-icon size="xsmall"></svg>
|
||||
</div>
|
||||
>
|
||||
<span *ngIf="additionalLabel" [textContent]="additionalLabel"></span>
|
||||
<svg question-icon size="xsmall"></svg>
|
||||
</div>
|
||||
<tool-tip
|
||||
class="sr-only position-absolute"
|
||||
[attr.for]="buttonId"
|
||||
[attr.id]="tooltipId"
|
||||
popover="manual"
|
||||
data-direction="sw"
|
||||
data-type="label"
|
||||
[textContent]="text.open_dialog"
|
||||
/>
|
||||
</ng-container>
|
||||
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA, DebugElement } from '@angular/core';
|
||||
import { AttributeHelpTextComponent } from 'core-app/shared/components/attribute-help-texts/attribute-help-text.component';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { I18nService } from 'core-app/core/i18n/i18n.service';
|
||||
import { AttributeHelpTextsService } from './attribute-help-text.service';
|
||||
import { AttributeHelpTextModalService } from './attribute-help-text-modal.service';
|
||||
import { QuestionIconComponent } from '@openproject/octicons-angular';
|
||||
import { OpIconComponent } from '../icon/icon.component';
|
||||
|
||||
describe('AttributeHelpTextComponent', () => {
|
||||
let component:AttributeHelpTextComponent;
|
||||
let fixture:ComponentFixture<AttributeHelpTextComponent>;
|
||||
let element:DebugElement;
|
||||
|
||||
const serviceStub = {};
|
||||
let modalServiceStub:jasmine.SpyObj<AttributeHelpTextModalService>;
|
||||
const i18nStub = { t: (_scope:string|string[], _options?:{ [key:string]:any }) => 'Show help text' };
|
||||
|
||||
beforeEach(() => {
|
||||
modalServiceStub = jasmine.createSpyObj('AttributeHelpTextModalService', ['show']);
|
||||
|
||||
void TestBed
|
||||
.configureTestingModule({
|
||||
declarations: [
|
||||
AttributeHelpTextComponent,
|
||||
OpIconComponent,
|
||||
],
|
||||
providers: [
|
||||
{ provide: AttributeHelpTextsService, useValue: serviceStub },
|
||||
{ provide: AttributeHelpTextModalService, useValue: modalServiceStub },
|
||||
{ provide: I18nService, useValue: i18nStub },
|
||||
],
|
||||
imports: [
|
||||
QuestionIconComponent,
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AttributeHelpTextComponent);
|
||||
component = fixture.debugElement.componentInstance;
|
||||
component.helpTextId = 1;
|
||||
component.attribute = 'subject';
|
||||
component.attributeScope = 'Project';
|
||||
element = fixture.debugElement;
|
||||
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders a button', () => {
|
||||
const button = element.query(By.css("[role='button']"));
|
||||
|
||||
expect(button).toBeTruthy();
|
||||
expect(button.nativeElement).toHaveClass('spot-link');
|
||||
});
|
||||
|
||||
it('renders a tooltip', () => {
|
||||
const tooltip = element.query(By.css('tool-tip'));
|
||||
|
||||
expect(tooltip).toBeTruthy();
|
||||
expect(tooltip.nativeElement.textContent).toEqual('Show help text');
|
||||
expect(tooltip.nativeElement.getAttribute('for')).toMatch(/attribute-help-text-component-\d+/);
|
||||
expect(tooltip.nativeElement.popover).toEqual('manual');
|
||||
expect(tooltip.nativeElement.dataset.direction).toEqual('sw');
|
||||
expect(tooltip.nativeElement.dataset.type).toEqual('label');
|
||||
});
|
||||
|
||||
it('renders an icon', () => {
|
||||
const icon = element.query(By.directive(QuestionIconComponent));
|
||||
|
||||
expect(icon.nativeElement.getAttribute('size')).toEqual('xsmall');
|
||||
});
|
||||
|
||||
it('applies .help-text--entry class', () => {
|
||||
const button = element.query(By.css("[role='button']"));
|
||||
|
||||
expect(button.nativeElement).toHaveClass('help-text--entry');
|
||||
});
|
||||
|
||||
it('applies an ID', () => {
|
||||
const button = element.query(By.css("[role='button']"));
|
||||
|
||||
expect(button.nativeElement.id).toMatch(/attribute-help-text-component-\d+/);
|
||||
});
|
||||
|
||||
it('defines a data-qa-help-text-for attribute', () => {
|
||||
const button = element.query(By.css("[role='button']"));
|
||||
|
||||
expect(button.nativeElement.dataset.qaHelpTextFor).toEqual('subject');
|
||||
});
|
||||
|
||||
it('should call modalService on click', () => {
|
||||
const button = element.query(By.css("[role='button']"));
|
||||
button.nativeElement.click();
|
||||
|
||||
fixture.detectChanges();
|
||||
void fixture.whenStable().then(() => {
|
||||
expect(modalServiceStub.show).toHaveBeenCalledOnceWith('1');
|
||||
});
|
||||
});
|
||||
});
|
||||
+29
-22
@@ -36,10 +36,10 @@ import {
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
import { I18nService } from 'core-app/core/i18n/i18n.service';
|
||||
import { OpModalService } from 'core-app/shared/components/modal/modal.service';
|
||||
import { populateInputsFromDataset } from 'core-app/shared/components/dataset-inputs';
|
||||
import { AttributeHelpTextsService } from './attribute-help-text.service';
|
||||
import { AttributeHelpTextModalComponent } from './attribute-help-text.modal';
|
||||
import { AttributeHelpTextModalService } from './attribute-help-text-modal.service';
|
||||
import { uniqueId } from 'lodash';
|
||||
|
||||
export const attributeHelpTextSelector = 'attribute-help-text';
|
||||
|
||||
@@ -57,21 +57,19 @@ export class AttributeHelpTextComponent implements OnInit {
|
||||
// Scope to search for
|
||||
@Input() public attributeScope:string;
|
||||
|
||||
// Load single id entry if given
|
||||
// Use single id entry if given
|
||||
@Input() public helpTextId?:string|number;
|
||||
|
||||
public exists = false;
|
||||
readonly tooltipId = uniqueId('tooltip-');
|
||||
|
||||
readonly text = {
|
||||
open_dialog: this.I18n.t('js.help_texts.show_modal'),
|
||||
edit: this.I18n.t('js.button_edit'),
|
||||
close: this.I18n.t('js.button_close'),
|
||||
};
|
||||
|
||||
constructor(
|
||||
readonly elementRef:ElementRef,
|
||||
protected attributeHelpTexts:AttributeHelpTextsService,
|
||||
protected opModalService:OpModalService,
|
||||
protected attributeHelpTextModalService:AttributeHelpTextModalService,
|
||||
protected cdRef:ChangeDetectorRef,
|
||||
protected injector:Injector,
|
||||
protected I18n:I18nService,
|
||||
@@ -80,30 +78,39 @@ export class AttributeHelpTextComponent implements OnInit {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.helpTextId) {
|
||||
this.exists = true;
|
||||
} else {
|
||||
// Need to load the promise to find out if the attribute exists
|
||||
this.load().then((resource) => {
|
||||
this.exists = !!resource;
|
||||
// Need to load the promise to find out if the attribute exists
|
||||
this.getId()
|
||||
.then((id) => {
|
||||
this.helpTextId = id;
|
||||
this.cdRef.detectChanges();
|
||||
return resource;
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
public get exists() {
|
||||
return this.helpTextId != null;
|
||||
}
|
||||
|
||||
public get buttonId():string {
|
||||
return `attribute-help-text-component-${this.helpTextId}`;
|
||||
}
|
||||
|
||||
public handleClick(event:Event):void {
|
||||
this.load().then((resource) => {
|
||||
this.opModalService.show(AttributeHelpTextModalComponent, this.injector, { helpText: resource });
|
||||
});
|
||||
void this.getId().then((id) => this.attributeHelpTextModalService.show(id));
|
||||
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
private async getId() {
|
||||
if (this.exists) return this.helpTextId!.toString();
|
||||
|
||||
const resource = await this.load();
|
||||
const id = resource?.id;
|
||||
if (!id) return Promise.reject();
|
||||
return id;
|
||||
}
|
||||
|
||||
private load() {
|
||||
if (this.helpTextId) {
|
||||
return this.attributeHelpTexts.requireById(this.helpTextId);
|
||||
}
|
||||
return this.attributeHelpTexts.require(this.attribute, this.attributeScope);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
//-- copyright
|
||||
// OpenProject is an open source project management software.
|
||||
// Copyright (C) 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 COPYRIGHT and LICENSE files for more details.
|
||||
//++
|
||||
|
||||
import {
|
||||
ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, Inject, OnInit,
|
||||
} from '@angular/core';
|
||||
import { OpModalComponent } from 'core-app/shared/components/modal/modal.component';
|
||||
import { OpModalLocalsMap } from 'core-app/shared/components/modal/modal.types';
|
||||
import { OpModalLocalsToken } from 'core-app/shared/components/modal/modal.service';
|
||||
import { I18nService } from 'core-app/core/i18n/i18n.service';
|
||||
import { HelpTextResource } from 'core-app/features/hal/resources/help-text-resource';
|
||||
|
||||
@Component({
|
||||
templateUrl: './help-text.modal.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class AttributeHelpTextModalComponent extends OpModalComponent implements OnInit {
|
||||
readonly text = {
|
||||
attachments: this.I18n.t('js.label_attachments'),
|
||||
edit: this.I18n.t('js.button_edit'),
|
||||
close: this.I18n.t('js.button_close'),
|
||||
};
|
||||
|
||||
public helpText:HelpTextResource = this.locals.helpText!;
|
||||
|
||||
constructor(
|
||||
@Inject(OpModalLocalsToken) public locals:OpModalLocalsMap,
|
||||
readonly I18n:I18nService,
|
||||
readonly cdRef:ChangeDetectorRef,
|
||||
readonly elementRef:ElementRef,
|
||||
) {
|
||||
super(locals, cdRef, elementRef);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
super.ngOnInit();
|
||||
|
||||
// Load the attachments
|
||||
this
|
||||
.helpText
|
||||
.attachments
|
||||
.$load()
|
||||
.then(() => this.cdRef.detectChanges());
|
||||
}
|
||||
|
||||
public get helpTextLink() {
|
||||
if (this.helpText.editText) {
|
||||
return this.helpText.editText.$link.href;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
+2
-5
@@ -1,24 +1,20 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||
import { OpenprojectAttachmentsModule } from 'core-app/shared/components/attachments/openproject-attachments.module';
|
||||
import { IconModule } from 'core-app/shared/components/icon/icon.module';
|
||||
import { OpenprojectModalModule } from 'core-app/shared/components/modal/modal.module';
|
||||
|
||||
import { AttributeHelpTextComponent } from './attribute-help-text.component';
|
||||
import { AttributeHelpTextModalComponent } from './attribute-help-text.modal';
|
||||
import { StaticAttributeHelpTextComponent } from './static-attribute-help-text.component';
|
||||
import { StaticAttributeHelpTextModalComponent } from './static-attribute-help-text.modal';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
OpenprojectModalModule,
|
||||
OpenprojectAttachmentsModule,
|
||||
IconModule,
|
||||
],
|
||||
declarations: [
|
||||
AttributeHelpTextComponent,
|
||||
AttributeHelpTextModalComponent,
|
||||
StaticAttributeHelpTextComponent,
|
||||
StaticAttributeHelpTextModalComponent,
|
||||
],
|
||||
@@ -28,5 +24,6 @@ import { StaticAttributeHelpTextModalComponent } from './static-attribute-help-t
|
||||
AttributeHelpTextComponent,
|
||||
StaticAttributeHelpTextComponent,
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
})
|
||||
export class AttributeHelpTextModule {}
|
||||
|
||||
+15
-29
@@ -29,9 +29,8 @@
|
||||
import { input } from '@openproject/reactivestates';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service';
|
||||
import { take } from 'rxjs/operators';
|
||||
import { CollectionResource } from 'core-app/features/hal/resources/collection-resource';
|
||||
import { HelpTextResource } from 'core-app/features/hal/resources/help-text-resource';
|
||||
import { firstValueFrom, map } from 'rxjs';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AttributeHelpTextsService {
|
||||
@@ -49,43 +48,30 @@ export class AttributeHelpTextsService {
|
||||
public require(attribute:string, scope:string):Promise<HelpTextResource|undefined> {
|
||||
this.load();
|
||||
|
||||
return new Promise<HelpTextResource|undefined>((resolve, reject) => {
|
||||
this.helpTexts
|
||||
return new Promise((resolve) => {
|
||||
void this.helpTexts
|
||||
.valuesPromise()
|
||||
.then(() => resolve(this.find(attribute, scope)));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for a given attribute help text
|
||||
*
|
||||
*/
|
||||
public requireById(id:string|number):Promise<HelpTextResource|undefined> {
|
||||
this.load();
|
||||
|
||||
return this
|
||||
.helpTexts
|
||||
.values$()
|
||||
.pipe(
|
||||
take(1),
|
||||
)
|
||||
.toPromise()
|
||||
.then(() => {
|
||||
const value = this.helpTexts.getValueOr([]);
|
||||
return _.find(value, (element) => element.id?.toString() === id.toString());
|
||||
});
|
||||
private load():void {
|
||||
this.helpTexts
|
||||
.putFromPromiseIfPristine(() => firstValueFrom(this.loadUncached()));
|
||||
}
|
||||
|
||||
private load():void {
|
||||
this.helpTexts.putFromPromiseIfPristine(() => this.apiV3Service
|
||||
private loadUncached() {
|
||||
return this
|
||||
.apiV3Service
|
||||
.help_texts
|
||||
.get()
|
||||
.toPromise()
|
||||
.then((resources:CollectionResource<HelpTextResource>) => resources.elements));
|
||||
.pipe(
|
||||
map((collection) => collection.elements),
|
||||
);
|
||||
}
|
||||
|
||||
private find(attribute:string, scope:string):HelpTextResource|undefined {
|
||||
const value = this.helpTexts.getValueOr([]);
|
||||
return _.find(value, (element) => element.scope === scope && element.attribute === attribute);
|
||||
private find(attribute:string, scope:string) {
|
||||
const value = this.helpTexts.getValueOr<HelpTextResource[]>([]);
|
||||
return value.find((element) => element.scope === scope && element.attribute === attribute);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
<div
|
||||
class="spot-modal attribute-help-text--modal loading-indicator--location"
|
||||
data-indicator-name="modal"
|
||||
>
|
||||
<div
|
||||
class="spot-modal--header"
|
||||
data-test-selector="attribute-help-text--header"
|
||||
>
|
||||
<span class="spot-icon spot-icon_help1"></span>
|
||||
<span class="attribute-help-text--header-text"
|
||||
[textContent]="helpText.attributeCaption"></span>
|
||||
</div>
|
||||
|
||||
<div class="spot-modal--body spot-container">
|
||||
<div
|
||||
class="op-uc-container op-uc-container__no-permalinks"
|
||||
[innerHtml]="helpText.helpText.html"
|
||||
></div>
|
||||
|
||||
<fieldset class="form--fieldset" *ngIf="helpText && helpText.attachments.elements.length > 0">
|
||||
<legend class="form--fieldset-legend">
|
||||
{{ text.attachments }}
|
||||
</legend>
|
||||
<op-attachments
|
||||
[resource]="helpText"
|
||||
[allowUploading]="false"
|
||||
></op-attachments>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="spot-action-bar">
|
||||
<div class="spot-action-bar--right">
|
||||
<button
|
||||
type="button"
|
||||
class="button spot-action-bar--action spot-modal--cancel-button"
|
||||
(click)="closeMe()"
|
||||
>
|
||||
{{ text.close }}
|
||||
</button>
|
||||
<a
|
||||
class="help-text--edit-button button spot-action-bar--action"
|
||||
*ngIf="helpText.editText"
|
||||
[attr.href]="helpTextLink"
|
||||
[attr.title]="text.edit"
|
||||
>
|
||||
<op-icon icon-classes="button--icon icon-edit"></op-icon>
|
||||
<span class="button--text" [textContent]="text.edit"></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -31,18 +31,30 @@
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe "Project attribute help texts", :js do
|
||||
let(:project) { create(:project) }
|
||||
let!(:project) { create(:project) }
|
||||
|
||||
let(:instance) do
|
||||
create(:project_help_text,
|
||||
attribute_name: :name,
|
||||
help_text: "Some **help text** for name.")
|
||||
create(:project_help_text,
|
||||
attribute_name: :description,
|
||||
help_text: "Some **help text** for description.")
|
||||
create(:project_help_text,
|
||||
attribute_name: :status,
|
||||
help_text: "Some **help text** for status.")
|
||||
let!(:name_help_text) do
|
||||
create(
|
||||
:project_help_text,
|
||||
attribute_name: :name,
|
||||
help_text: "Some **help text** for name."
|
||||
)
|
||||
end
|
||||
|
||||
let!(:description_help_text) do
|
||||
create(
|
||||
:project_help_text,
|
||||
attribute_name: :description,
|
||||
help_text: "Some **help text** for description."
|
||||
)
|
||||
end
|
||||
|
||||
let!(:status_help_text) do
|
||||
create(
|
||||
:project_help_text,
|
||||
attribute_name: :status,
|
||||
help_text: "Some **help text** for status."
|
||||
)
|
||||
end
|
||||
|
||||
let(:grid) do
|
||||
@@ -56,17 +68,23 @@ RSpec.describe "Project attribute help texts", :js do
|
||||
end_column: 1)
|
||||
end
|
||||
|
||||
let(:modal) { Components::AttributeHelpTextModal.new(instance) }
|
||||
let(:wp_page) { Pages::FullWorkPackage.new work_package }
|
||||
|
||||
before do
|
||||
login_as user
|
||||
project
|
||||
instance
|
||||
end
|
||||
|
||||
shared_examples "allows to view help texts" do
|
||||
it "shows an indicator for whatever help text exists" do
|
||||
shared_examples "allows to view help texts" do |show_edit:|
|
||||
it "shows help text links" do
|
||||
visit project_path(project)
|
||||
|
||||
within "#menu-sidebar" do
|
||||
click_link_or_button "Overview"
|
||||
end
|
||||
|
||||
wait_for_network_idle
|
||||
expect(page).to have_css("#{test_selector('op-widget-box--header')} .help-text--entry", wait: 10, count: 2)
|
||||
end
|
||||
|
||||
it "shows help text modal on clicking help text link" do
|
||||
visit project_path(project)
|
||||
|
||||
within "#menu-sidebar" do
|
||||
@@ -77,18 +95,28 @@ RSpec.describe "Project attribute help texts", :js do
|
||||
expect(page).to have_css("#{test_selector('op-widget-box--header')} .help-text--entry", wait: 10)
|
||||
|
||||
# Open help text modal
|
||||
modal.open!
|
||||
expect(modal.modal_container).to have_css("strong", text: "help text")
|
||||
modal.expect_edit(editable: user.allowed_globally?(:edit_attribute_help_texts))
|
||||
page.find("[data-qa-help-text-for='description").click
|
||||
|
||||
modal.close!
|
||||
expect(page).to have_modal "Description"
|
||||
within_modal "Description" do
|
||||
expect(page).to have_css("strong", text: "help text")
|
||||
|
||||
expect(page).to have_button "Close"
|
||||
if show_edit
|
||||
expect(page).to have_link "Edit"
|
||||
end
|
||||
|
||||
click_on "Close"
|
||||
end
|
||||
|
||||
expect(page).to have_no_modal "Description"
|
||||
end
|
||||
end
|
||||
|
||||
describe "as admin" do
|
||||
let(:user) { create(:admin) }
|
||||
|
||||
it_behaves_like "allows to view help texts"
|
||||
it_behaves_like "allows to view help texts", show_edit: true
|
||||
|
||||
it "shows the help text on the project create form", :selenium do
|
||||
visit new_project_path
|
||||
@@ -119,6 +147,6 @@ RSpec.describe "Project attribute help texts", :js do
|
||||
create(:user, member_with_permissions: { project => [:view_project] })
|
||||
end
|
||||
|
||||
it_behaves_like "allows to view help texts"
|
||||
it_behaves_like "allows to view help texts", show_edit: false
|
||||
end
|
||||
end
|
||||
|
||||
@@ -40,7 +40,6 @@ RSpec.describe "Work package attribute help texts", :js do
|
||||
help_text: "Some **help text** for status.")
|
||||
end
|
||||
|
||||
let(:modal) { Components::AttributeHelpTextModal.new(instance) }
|
||||
let(:wp_page) { Pages::FullWorkPackage.new work_package }
|
||||
|
||||
before do
|
||||
@@ -52,23 +51,33 @@ RSpec.describe "Work package attribute help texts", :js do
|
||||
wp_page.ensure_page_loaded
|
||||
end
|
||||
|
||||
shared_examples "allows to view help texts" do
|
||||
shared_examples "allows to view help texts" do |show_edit:|
|
||||
it "shows an indicator for whatever help text exists" do
|
||||
expect(page).to have_css('.work-package--single-view [data-qa-help-text-for="status"]')
|
||||
|
||||
# Open help text modal
|
||||
modal.open!
|
||||
expect(modal.modal_container).to have_css("strong", text: "help text")
|
||||
modal.expect_edit(editable: user.allowed_globally?(:edit_attribute_help_texts))
|
||||
page.find("[data-qa-help-text-for='status']").click
|
||||
|
||||
modal.close!
|
||||
expect(page).to have_modal "Status"
|
||||
within_modal "Status" do
|
||||
expect(page).to have_css("strong", text: "help text")
|
||||
|
||||
expect(page).to have_button "Close"
|
||||
if show_edit
|
||||
expect(page).to have_link "Edit"
|
||||
end
|
||||
|
||||
click_on "Close"
|
||||
end
|
||||
|
||||
expect(page).to have_no_modal "Status"
|
||||
end
|
||||
end
|
||||
|
||||
describe "as admin" do
|
||||
let(:user) { create(:admin) }
|
||||
|
||||
it_behaves_like "allows to view help texts"
|
||||
it_behaves_like "allows to view help texts", show_edit: false
|
||||
end
|
||||
|
||||
describe "as regular user" do
|
||||
@@ -76,6 +85,6 @@ RSpec.describe "Work package attribute help texts", :js do
|
||||
create(:user, member_with_permissions: { project => [:view_work_packages] })
|
||||
end
|
||||
|
||||
it_behaves_like "allows to view help texts"
|
||||
it_behaves_like "allows to view help texts", show_edit: false
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user