[AAE-6345] New start process page APA (#7521)

* [AAE-6345] New start process page APA

* [AAE-6345] Added test

* [AAE-6345] Update

* CR

* Revrite wrapper to component

* Quick fix to e2e

* Show errors as ng-content

* Add Inplace word

* Fix unit tests

* Fix e2e
This commit is contained in:
Bartosz Sekuła 2022-03-03 20:02:36 +01:00 committed by GitHub
parent 75f7360a35
commit 14a777c5d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 240 additions and 48 deletions

View File

@ -131,7 +131,8 @@
"processstring",
"typeahed",
"minmax",
"jsons"
"jsons",
"Inplace"
],
"dictionaries": [
"html",

View File

@ -0,0 +1,17 @@
<div
class="adf-inplace-input-container"
[ngClass]="{'adf-inplace-input-container-error': control.invalid}"
>
<mat-form-field class="adf-inplace-input-mat-form-field">
<input
matInput
[formControl]="control"
class="adf-inplace-input"
data-automation-id="adf-inplace-input"
>
<mat-error data-automation-id="adf-inplace-input-error">
<ng-content></ng-content>
</mat-error>
</mat-form-field>
</div>

View File

@ -0,0 +1,35 @@
.adf-inplace-input-container {
.mat-form-field-underline {
display: none;
}
.mat-form-field-infix {
display: flex;
border-top: 0;
}
&-error {
input {
border: 1px solid var(--theme-warn-color) !important;
}
}
.adf-inplace-input-mat-form-field {
width: 100%;
}
.adf-inplace-input {
padding: 7px;
border: 1px solid transparent;
border-radius: 4px;
&:focus {
border: 1px solid var(--theme-primary-color);
}
&:hover:not(:focus) {
border: 1px solid var(--theme-bg-hover-color);
background-color: var(--theme-bg-hover-color);
}
}
}

View File

@ -0,0 +1,72 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FormControl } from '@angular/forms';
import { TranslateModule } from '@ngx-translate/core';
import { ContentTestingModule } from 'content-services/src/lib/testing/content.testing.module';
import { InplaceFormInputComponent } from './inplace-form-input.component';
describe('InplaceFormInputComponent', () => {
let component: InplaceFormInputComponent;
let fixture: ComponentFixture<InplaceFormInputComponent>;
let formControl: FormControl;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
TranslateModule.forRoot(),
ContentTestingModule
],
declarations: [InplaceFormInputComponent]
}).compileComponents();
});
beforeEach(() => {
formControl = new FormControl('');
fixture = TestBed.createComponent(InplaceFormInputComponent);
component = fixture.componentInstance;
component.control = formControl;
});
it('should update form value', () => {
formControl.setValue('New Value');
fixture.detectChanges();
const input = fixture.nativeElement.querySelector(
'[data-automation-id="adf-inplace-input"]'
);
expect(input.value).toBe('New Value');
});
it('should show error', () => {
formControl.setValidators(() => ({ error: 'error' }));
formControl.setValue('value');
formControl.markAsTouched();
formControl.updateValueAndValidity();
fixture.detectChanges();
const error = fixture.nativeElement.querySelector(
'[data-automation-id="adf-inplace-input-error"]'
);
expect(error).toBeTruthy();
});
});

View File

@ -0,0 +1,34 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
Component,
Input,
ViewEncapsulation
} from '@angular/core';
import { FormControl } from '@angular/forms';
@Component({
selector: 'adf-inplace-form-input',
templateUrl: './inplace-form-input.component.html',
styleUrls: ['./inplace-form-input.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class InplaceFormInputComponent {
@Input()
control: FormControl;
}

View File

@ -40,6 +40,7 @@ import { EditJsonDialogModule } from '../dialogs/edit-json/edit-json.dialog.modu
import { A11yModule } from '@angular/cdk/a11y';
import { FlexLayoutModule } from '@angular/flex-layout';
import { ViewerModule } from '../viewer/viewer.module';
import { InplaceFormInputComponent } from './components/inplace-form-input/inplace-form-input.component';
@NgModule({
imports: [
@ -67,7 +68,8 @@ import { ViewerModule } from '../viewer/viewer.module';
StartFormCustomButtonDirective,
...WIDGET_DIRECTIVES,
...MASK_DIRECTIVE,
WidgetComponent
WidgetComponent,
InplaceFormInputComponent
],
exports: [
ContentWidgetComponent,
@ -75,6 +77,7 @@ import { ViewerModule } from '../viewer/viewer.module';
FormListComponent,
FormRendererComponent,
StartFormCustomButtonDirective,
InplaceFormInputComponent,
...WIDGET_DIRECTIVES
]
})

View File

@ -18,6 +18,7 @@
export * from './components/form-field/form-field.component';
export * from './components/form-base.component';
export * from './components/form-list.component';
export * from './components/inplace-form-input/inplace-form-input.component';
export * from './components/widgets/content/content.widget';
export * from './components/form-custom-button.directive';
export * from './components/form-renderer.component';

View File

@ -1,6 +1,8 @@
<mat-card class="adf-start-process">
<mat-card-title class="adf-title">{{'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.FORM.TITLE' | translate}}
<mat-card-title
*ngIf="showTitle"
class="adf-title">
{{'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.FORM.TITLE' | translate}}
</mat-card-title>
<mat-card-content>
@ -9,8 +11,13 @@
</mat-card-subtitle>
<div *ngIf="!isProcessDefinitionsEmpty(); else emptyProcessDefinitionsList">
<form [formGroup]="processForm">
<mat-form-field class="adf-process-input-container" [floatLabel]="'always'">
<form [formGroup]="processForm" class="adf-select-process-form">
<mat-form-field
class="adf-process-input-container"
floatLabel="always"
*ngIf="showSelectProcessDropdown"
data-automation-id="adf-select-cloud-process-dropdown"
>
<mat-label>{{ 'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.FORM.LABEL.TYPE' | translate }}</mat-label>
<input
#inputAutocomplete
@ -25,7 +32,7 @@
{{ getProcessDefinitionValue(processDef) }}
</mat-option>
</mat-autocomplete>
<button id="adf-select-process-dropdown" *ngIf="showSelectProcessDropdown" mat-icon-button (click)="displayDropdown($event)">
<button id="adf-select-process-dropdown" mat-icon-button (click)="displayDropdown($event)">
<mat-icon>arrow_drop_down</mat-icon>
</button>
</div>
@ -34,22 +41,17 @@
</mat-error>
</mat-form-field>
<mat-form-field class="adf-process-input-container" [floatLabel]="'always'">
<mat-label>{{ 'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.FORM.LABEL.NAME' | translate }}</mat-label>
<input
matInput
formControlName="processInstanceName"
id="processName">
<mat-error id="adf-start-process-required-error" *ngIf="processInstanceName.hasError('required')">
<adf-inplace-form-input [control]="processInstanceName">
<span *ngIf="processInstanceName.hasError('required')">
{{ 'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.ERROR.PROCESS_NAME_REQUIRED' | translate }}
</mat-error>
<mat-error id="adf-start-process-maxlength-error" *ngIf="processInstanceName.hasError('maxlength')">
</span>
<span id="adf-start-process-maxlength-error" *ngIf="processInstanceName.hasError('maxlength')">
{{ 'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.ERROR.MAXIMUM_LENGTH' | translate : { characters : maxNameLength } }}
</mat-error>
<mat-error *ngIf="processInstanceName.hasError('pattern')">
</span>
<span *ngIf="processInstanceName.hasError('pattern')">
{{ 'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.ERROR.SPACE_VALIDATOR' | translate }}
</mat-error>
</mat-form-field>
</span>
</adf-inplace-form-input>
</form>
<ng-container *ngIf="hasForm() else taskFormCloudButtons">

View File

@ -1,9 +1,5 @@
.adf {
&-start-process {
mat-form-field {
width: 100%;
}
.mat-form-field-label {
color: var(--theme-colors-mat-grey-dark);
}
@ -17,14 +13,17 @@
}
}
&-select-process-form {
display: flex;
flex-direction: column;
}
&-title {
padding-bottom: 1.25em;
}
&-process-input-container {
mat-form-field {
width: 100%;
}
margin: 0 7px;
}
&-process-input-autocomplete {
@ -37,7 +36,7 @@
}
}
&-start-form-container {
&-form-container {
.mat-card {
box-shadow: none !important;
padding: 0 !important;

View File

@ -165,7 +165,7 @@ describe('StartProcessCloudComponent', () => {
component.ngOnChanges({ appName: change });
fixture.detectChanges();
tick();
typeValueInto('#processName', 'OLE');
typeValueInto('[data-automation-id="adf-inplace-input"]', 'OLE');
typeValueInto('#processDefinitionName', 'processwithoutform2');
fixture.detectChanges();
tick(550);
@ -242,7 +242,7 @@ describe('StartProcessCloudComponent', () => {
component.ngOnChanges({ appName: change });
fixture.detectChanges();
tick();
typeValueInto('#processName', 'My new process with form');
typeValueInto('[data-automation-id="adf-inplace-input"]', 'My new process with form');
typeValueInto('#processDefinitionName', 'processwithform');
fixture.detectChanges();
tick(550);
@ -269,7 +269,7 @@ describe('StartProcessCloudComponent', () => {
fixture.detectChanges();
tick();
typeValueInto('#processName', 'My new process with form');
typeValueInto('[data-automation-id="adf-inplace-input"]', 'My new process with form');
typeValueInto('#processDefinitionName', 'processwithform');
fixture.detectChanges();
tick(550);
@ -297,7 +297,7 @@ describe('StartProcessCloudComponent', () => {
fixture.detectChanges();
tick();
typeValueInto('#processName', 'My new process with form');
typeValueInto('[data-automation-id="adf-inplace-input"]', 'My new process with form');
typeValueInto('#processDefinitionName', 'processwithform');
fixture.detectChanges();
tick(550);
@ -327,7 +327,7 @@ describe('StartProcessCloudComponent', () => {
fixture.detectChanges();
tick();
typeValueInto('#processName', 'My new process with form');
typeValueInto('[data-automation-id="adf-inplace-input"]', 'My new process with form');
typeValueInto('#processDefinitionName', 'processwithform');
fixture.detectChanges();
tick(4500);
@ -582,18 +582,6 @@ describe('StartProcessCloudComponent', () => {
fixture.detectChanges();
});
it('should have labels for process name and type', async () => {
component.appName = 'myApp';
component.processDefinitionName = 'NewProcess 2';
component.ngOnChanges({ appName: firstChange });
fixture.detectChanges();
await fixture.whenStable();
const inputLabelsNodes = document.querySelectorAll('.adf-start-process .adf-process-input-container mat-label');
expect(inputLabelsNodes.length).toBe(2);
});
it('should have floating labels for process name and type', async () => {
component.appName = 'myApp';
component.processDefinitionName = 'NewProcess 2';
@ -862,5 +850,39 @@ describe('StartProcessCloudComponent', () => {
expect(escapeKeyboardEvent.cancelBubble).toBe(true);
});
it('should hide title', () => {
component.showTitle = false;
fixture.detectChanges();
const title = fixture.debugElement.query(By.css('.adf-title'));
expect(title).toBeFalsy();
});
it('should show title', () => {
const title = fixture.debugElement.query(By.css('.adf-title'));
expect(title).toBeTruthy();
});
it('should show process definition dropdown', () => {
component.processDefinitionList = fakeProcessDefinitions;
fixture.detectChanges();
const processDropdown = fixture.debugElement.query(By.css('[data-automation-id="adf-select-cloud-process-dropdown"]'));
expect(processDropdown).toBeTruthy();
});
it('should hide process definition dropdown', () => {
component.processDefinitionList = fakeProcessDefinitions;
component.showSelectProcessDropdown = false;
fixture.detectChanges();
const processDropdown = fixture.debugElement.query(By.css('#processDefinitionName'));
expect(processDropdown).toBeFalsy();
});
});
});

View File

@ -74,6 +74,10 @@ export class StartProcessCloudComponent implements OnChanges, OnInit, OnDestroy
@Input()
showSelectProcessDropdown: boolean = true;
/** Show/hide title. */
@Input()
showTitle: boolean = true;
/** Emitted when the process is successfully started. */
@Output()
success = new EventEmitter<ProcessInstanceCloud>();
@ -369,8 +373,8 @@ export class StartProcessCloudComponent implements OnChanges, OnInit, OnDestroy
return !!process.name ? process.name : process.key;
}
get processInstanceName(): AbstractControl {
return this.processForm.get('processInstanceName');
get processInstanceName(): FormControl {
return this.processForm.get('processInstanceName') as FormControl;
}
get processDefinition(): AbstractControl {

View File

@ -23,7 +23,7 @@ import { FormFields } from '../../core/pages/form/form-fields';
export class StartProcessCloudPage {
defaultProcessName = $('input[id="processName"]');
processNameInput = $('#processName');
processNameInput = $('[data-automation-id="adf-inplace-input"]');
selectProcessDropdownArrow = $('button[id="adf-select-process-dropdown"]');
cancelProcessButton = $('#cancel_process');
formStartProcessButton = $('button[data-automation-id="adf-form-start process"]');
@ -89,6 +89,8 @@ export class StartProcessCloudPage {
async isStartProcessButtonEnabled(): Promise<boolean> {
await BrowserVisibility.waitUntilElementIsNotVisible(this.startProcessButtonDisabled);
await BrowserVisibility.waitUntilElementIsVisible(this.startProcessButton);
await BrowserVisibility.waitUntilElementIsClickable(this.startProcessButton);
return this.startProcessButton.isEnabled();
}