[AAE-10773] Make Form core process agonostic (#8032)

* move form list in a component

* move things in the right place

* move last pice in the right place

* move things in the right place

* move people and group in the right place

* move radio and typehead
form service start remove responsibilities

* remove model service and editor service from formService

* move dropdwon in process-service
finish remove service from form service

* fix some wrong import

* move activiti

* fix double quote imports

* move dynamic table

* fix shell

* move unit test

* [ci:force] fix lint issues

* fix build and some unit test

* fix process spec type spy problems [ci:foce]

* fix

* fix broken tests

* fix lint issues

* fix cloud dropdown test

* cleanup process-service-cloud tests

* fix people process

* improve e2e test

Co-authored-by: Kasia Biernat <kasia.biernat@hyland.com>
This commit is contained in:
Eugenio Romano
2022-12-21 15:12:38 +00:00
committed by GitHub
parent eb27d38eba
commit a535af667b
180 changed files with 1971 additions and 3260 deletions

View File

@@ -0,0 +1,110 @@
/*!
* @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 {
FormModel, setupTestBed
} from '@alfresco/adf-core';
import {
Component,
DebugElement, ViewChild
} from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { TranslateModule } from '@ngx-translate/core';
import { ProcessServiceCloudTestingModule } from '../../testing/process-service-cloud.testing.module';
import { FormCloudComponent } from './form-cloud.component';
@Component({
selector: 'adf-cloud-form-with-custom-outcomes',
template: `
<adf-cloud-form #adfCloudForm>
<adf-cloud-form-custom-outcomes>
<button mat-button id="adf-custom-outcome-1" (click)="onCustomButtonOneClick()">
CUSTOM-BUTTON-1
</button>
<button mat-button id="adf-custom-outcome-2" (click)="onCustomButtonTwoClick()">
CUSTOM-BUTTON-2
</button>
</adf-cloud-form-custom-outcomes>
</adf-cloud-form>`
})
class FormCloudWithCustomOutComesComponent {
@ViewChild('adfCloudForm', { static: true })
adfCloudForm: FormCloudComponent;
onCustomButtonOneClick() {
}
onCustomButtonTwoClick() {
}
}
describe('FormCloudWithCustomOutComesComponent', () => {
let fixture: ComponentFixture<FormCloudWithCustomOutComesComponent>;
let customComponent: FormCloudWithCustomOutComesComponent;
let debugElement: DebugElement;
setupTestBed({
imports: [
TranslateModule.forRoot(),
ProcessServiceCloudTestingModule
],
declarations: [FormCloudWithCustomOutComesComponent]
});
beforeEach(() => {
fixture = TestBed.createComponent(FormCloudWithCustomOutComesComponent);
customComponent = fixture.componentInstance;
debugElement = fixture.debugElement;
const formRepresentation = {
fields: [
{ id: 'container1' }
],
outcomes: [
{ id: 'outcome-1', name: 'outcome 1' }
]
};
const form = new FormModel(formRepresentation);
customComponent.adfCloudForm.form = form;
fixture.detectChanges();
});
afterEach(() => {
fixture.destroy();
});
it('should be able to inject custom outcomes and click on custom outcomes', async () => {
fixture.detectChanges();
const onCustomButtonOneSpy = spyOn(customComponent, 'onCustomButtonOneClick').and.callThrough();
const buttonOneBtn = debugElement.query(By.css('#adf-custom-outcome-1'));
const buttonTwoBtn = debugElement.query(By.css('#adf-custom-outcome-2'));
expect(buttonOneBtn).not.toBeNull();
expect(buttonTwoBtn).not.toBeNull();
buttonOneBtn.nativeElement.click();
fixture.detectChanges();
await fixture.whenStable();
expect(onCustomButtonOneSpy).toHaveBeenCalled();
expect(buttonOneBtn.nativeElement.innerText).toBe('CUSTOM-BUTTON-1');
expect(buttonTwoBtn.nativeElement.innerText).toBe('CUSTOM-BUTTON-2');
});
});

View File

@@ -17,56 +17,42 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { VersionCompatibilityService } from '@alfresco/adf-content-services';
import {
Component,
DebugElement,
SimpleChange,
NgModule,
Injector,
ComponentFactoryResolver,
ViewChild
} from '@angular/core';
import { By } from '@angular/platform-browser';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Observable, of, throwError } from 'rxjs';
import {
CoreModule,
AlfrescoApiService, ContentLinkModel, CoreModule,
FormFieldModel,
FormFieldTypes,
FormModel,
FormOutcomeEvent,
FormOutcomeModel,
setupTestBed,
TRANSLATION_PROVIDER,
WidgetVisibilityService,
FormService,
UploadWidgetContentLinkModel,
ContentLinkModel,
AlfrescoApiService
FormOutcomeModel, FormRenderingService, FormService, setupTestBed,
TRANSLATION_PROVIDER, UploadWidgetContentLinkModel, WidgetVisibilityService
} from '@alfresco/adf-core';
import { VersionCompatibilityService } from '@alfresco/adf-content-services';
import { Node } from '@alfresco/js-api';
import { ESCAPE } from '@angular/cdk/keycodes';
import { HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import {
Component, ComponentFactoryResolver, Injector, SimpleChange
} from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MatDialog } from '@angular/material/dialog';
import { MatDialogHarness } from '@angular/material/dialog/testing';
import { By } from '@angular/platform-browser';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { Observable, of, throwError } from 'rxjs';
import { ProcessServiceCloudTestingModule } from '../../testing/process-service-cloud.testing.module';
import { FormCloudService } from '../services/form-cloud.service';
import { FormCloudComponent } from './form-cloud.component';
import { FormCloudModule } from '../form-cloud.module';
import {
cloudFormMock,
conditionalUploadWidgetsMock,
emptyFormRepresentationJSON,
fakeCloudForm,
multilingualForm,
fakeMetadataForm
fakeCloudForm, fakeMetadataForm, multilingualForm
} from '../mocks/cloud-form.mock';
import { FormCloudRepresentation } from '../models/form-cloud-representation.model';
import { FormCloudModule } from '../form-cloud.module';
import { TranslateService, TranslateModule } from '@ngx-translate/core';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { FormCloudService } from '../services/form-cloud.service';
import { CloudFormRenderingService } from './cloud-form-rendering.service';
import { Node } from '@alfresco/js-api';
import { ESCAPE } from '@angular/cdk/keycodes';
import { MatDialog } from '@angular/material/dialog';
import { HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { MatDialogHarness } from '@angular/material/dialog/testing';
import { FormCloudComponent } from './form-cloud.component';
const mockOauth2Auth: any = {
oauth2Auth: {
@@ -83,7 +69,6 @@ describe('FormCloudComponent', () => {
let matDialog: MatDialog;
let visibilityService: WidgetVisibilityService;
let formRenderingService: CloudFormRenderingService;
let translateService: TranslateService;
let documentRootLoader: HarnessLoader;
@Component({
@@ -95,13 +80,6 @@ describe('FormCloudComponent', () => {
typeId = 'CustomWidget';
}
@NgModule({
declarations: [CustomWidget],
exports: [CustomWidget]
})
class CustomUploadModule {
}
const buildWidget = (type: string, injector: Injector): any => {
const resolver = formRenderingService.getComponentTypeResolver(type);
const widgetType = resolver(null);
@@ -115,25 +93,14 @@ describe('FormCloudComponent', () => {
setupTestBed({
imports: [
NoopAnimationsModule,
TranslateModule.forRoot(),
CoreModule.forRoot(),
FormCloudModule,
CustomUploadModule
ProcessServiceCloudTestingModule
],
providers: [
{
provide: TRANSLATION_PROVIDER,
multi: true,
useValue: {
name: 'app',
source: 'resources'
}
},
{
provide: VersionCompatibilityService,
useValue: {}
}
},
{ provide: FormRenderingService, useClass: CloudFormRenderingService }
]
});
@@ -144,7 +111,6 @@ describe('FormCloudComponent', () => {
formRenderingService = TestBed.inject(CloudFormRenderingService);
formCloudService = TestBed.inject(FormCloudService);
translateService = TestBed.inject(TranslateService);
matDialog = TestBed.inject(MatDialog);
visibilityService = TestBed.inject(WidgetVisibilityService);
@@ -1100,121 +1066,77 @@ describe('FormCloudComponent', () => {
});
});
describe('Multilingual Form', () => {
it('should translate form labels on language change', async () => {
spyOn(formCloudService, 'getForm').and.returnValue(of(multilingualForm));
const formId = '123';
const appName = 'test-app';
formComponent.formId = formId;
formComponent.appVersion = 1;
formComponent.ngOnChanges({ appName: new SimpleChange(null, appName, true) });
expect(formCloudService.getForm).toHaveBeenCalledWith(appName, formId, 1);
fixture.detectChanges();
expect(getLabelValue('textField')).toEqual('Text field');
expect(getLabelValue('fildUploadField')).toEqual('File Upload');
expect(getLabelValue('dateField')).toEqual('Date field (D-M-YYYY)');
expect(getLabelValue('amountField')).toEqual('Amount field');
fixture.ngZone.run(() => translateService.use('fr'));
await fixture.whenStable();
fixture.detectChanges();
expect(getLabelValue('textField')).toEqual('Champ de texte');
expect(getLabelValue('fildUploadField')).toEqual('Téléchargement de fichiers');
expect(getLabelValue('dateField')).toEqual('Champ de date (D-M-YYYY)');
expect(getLabelValue('amountField')).toEqual('Champ Montant');
});
const getLabelValue = (containerId: string): string => {
const label = fixture.debugElement.nativeElement.querySelector(`[id="field-${containerId}-container"] label`);
return label.innerText;
};
});
});
@Component({
selector: 'adf-cloud-form-with-custom-outcomes',
template: `
<adf-cloud-form #adfCloudForm>
<adf-cloud-form-custom-outcomes>
<button mat-button id="adf-custom-outcome-1" (click)="onCustomButtonOneClick()">
CUSTOM-BUTTON-1
</button>
<button mat-button id="adf-custom-outcome-2" (click)="onCustomButtonTwoClick()">
CUSTOM-BUTTON-2
</button>
</adf-cloud-form-custom-outcomes>
</adf-cloud-form>`
})
class FormCloudWithCustomOutComesComponent {
@ViewChild('adfCloudForm', { static: true })
adfCloudForm: FormCloudComponent;
onCustomButtonOneClick() {
}
onCustomButtonTwoClick() {
}
}
describe('FormCloudWithCustomOutComesComponent', () => {
let fixture: ComponentFixture<FormCloudWithCustomOutComesComponent>;
let customComponent: FormCloudWithCustomOutComesComponent;
let debugElement: DebugElement;
describe('Multilingual Form', () => {
let translateService: TranslateService;
let formCloudService: FormCloudService;
let formComponent: FormCloudComponent;
let fixture: ComponentFixture<FormCloudComponent>;
setupTestBed({
imports: [
NoopAnimationsModule,
TranslateModule.forRoot(),
ProcessServiceCloudTestingModule
CoreModule.forRoot()
],
declarations: [FormCloudWithCustomOutComesComponent]
providers: [
{
provide: TRANSLATION_PROVIDER,
multi: true,
useValue: {
name: 'app',
source: 'resources'
}
}
]
});
beforeEach(() => {
fixture = TestBed.createComponent(FormCloudWithCustomOutComesComponent);
customComponent = fixture.componentInstance;
debugElement = fixture.debugElement;
const formRepresentation = {
fields: [
{ id: 'container1' }
],
outcomes: [
{ id: 'outcome-1', name: 'outcome 1' }
]
};
translateService = TestBed.inject(TranslateService);
formCloudService = TestBed.inject(FormCloudService);
const form = new FormModel(formRepresentation);
customComponent.adfCloudForm.form = form;
fixture = TestBed.createComponent(FormCloudComponent);
formComponent = fixture.componentInstance;
formComponent.form = formComponent.parseForm(fakeMetadataForm);
fixture.detectChanges();
});
afterEach(() => {
fixture.destroy();
});
it('should translate form labels on language change', async () => {
spyOn(formCloudService, 'getForm').and.returnValue(of(multilingualForm));
const formId = '123';
const appName = 'test-app';
formComponent.formId = formId;
formComponent.appVersion = 1;
it('should be able to inject custom outcomes and click on custom outcomes', async () => {
fixture.detectChanges();
formComponent.ngOnChanges({ appName: new SimpleChange(null, appName, true) });
expect(formCloudService.getForm).toHaveBeenCalledWith(appName, formId, 1);
const onCustomButtonOneSpy = spyOn(customComponent, 'onCustomButtonOneClick').and.callThrough();
const buttonOneBtn = debugElement.query(By.css('#adf-custom-outcome-1'));
const buttonTwoBtn = debugElement.query(By.css('#adf-custom-outcome-2'));
expect(buttonOneBtn).not.toBeNull();
expect(buttonTwoBtn).not.toBeNull();
fixture.ngZone.run(() => translateService.use('fr'));
buttonOneBtn.nativeElement.click();
fixture.detectChanges();
await fixture.whenStable();
fixture.detectChanges();
expect(onCustomButtonOneSpy).toHaveBeenCalled();
expect(buttonOneBtn.nativeElement.innerText).toBe('CUSTOM-BUTTON-1');
expect(buttonTwoBtn.nativeElement.innerText).toBe('CUSTOM-BUTTON-2');
expect(getLabelValue('textField')).toEqual('Champ de texte');
expect(getLabelValue('fildUploadField')).toEqual('Téléchargement de fichiers');
expect(getLabelValue('dateField')).toEqual('Champ de date (D-M-YYYY)');
expect(getLabelValue('amountField')).toEqual('Champ Montant');
fixture.ngZone.run(() => translateService.use('en'));
await fixture.whenStable();
fixture.detectChanges();
expect(getLabelValue('textField')).toEqual('Text field');
expect(getLabelValue('fildUploadField')).toEqual('File Upload');
expect(getLabelValue('dateField')).toEqual('Date field (D-M-YYYY)');
expect(getLabelValue('amountField')).toEqual('Amount field');
});
const getLabelValue = (containerId: string): string => {
const label = fixture.debugElement.nativeElement.querySelector(`[id="field-${containerId}-container"] label`);
return label.innerText;
};
});
describe('retrieve metadata on submit', () => {

View File

@@ -824,14 +824,6 @@ describe('AttachFileCloudWidgetComponent', () => {
expect(widget.field.params.fileSource.destinationFolderPath.value).toBe('mock-folder-id');
});
it('it should get a destination folder path value from a folder variable', () => {
createUploadWidgetField(form, 'attach-file-attach', [], mockAllFileSourceWithFolderVariablePathType);
fixture.detectChanges();
expect(widget.field.params.fileSource.destinationFolderPath.type).toBe('folder');
expect(widget.field.params.fileSource.destinationFolderPath.value).toBe('mock-folder-id');
});
it('it should set destination folder path value to undefined if mapped variable deleted/renamed', () => {
createUploadWidgetField(form, 'attach-file-attach', [], mockAllFileSourceWithRenamedFolderVariablePathType);
fixture.detectChanges();

View File

@@ -71,7 +71,6 @@ describe('DropdownCloudWidgetComponent', () => {
describe('Simple Dropdown', () => {
beforeEach(() => {
spyOn(formService, 'getRestFieldValues').and.callFake(() => of(fakeOptionList));
widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), {
id: 'dropdown-id',
name: 'date-name',
@@ -85,13 +84,14 @@ describe('DropdownCloudWidgetComponent', () => {
});
it('should require field with restUrl', () => {
spyOn(formCloudService, 'getRestWidgetData');
widget.field = new FormFieldModel(new FormModel());
widget.ngOnInit();
expect(formService.getRestFieldValues).not.toHaveBeenCalled();
expect(formCloudService.getRestWidgetData).not.toHaveBeenCalled();
widget.field = new FormFieldModel(null, { restUrl: null });
widget.ngOnInit();
expect(formService.getRestFieldValues).not.toHaveBeenCalled();
expect(formCloudService.getRestWidgetData).not.toHaveBeenCalled();
});
it('should select the default value when an option is chosen as default', async () => {
@@ -339,46 +339,13 @@ describe('DropdownCloudWidgetComponent', () => {
fixture.detectChanges();
await fixture.whenStable();
expect(element.querySelector('.adf-invalid')).toBeTruthy();
const requiredErrorElement = fixture.debugElement.query(By.css('.adf-dropdown-required-message .adf-error-text'));
expect(requiredErrorElement.nativeElement.innerText).toEqual('FORM.FIELD.REQUIRED');
});
});
describe('when is required', () => {
beforeEach(() => {
widget.field = new FormFieldModel( new FormModel({ taskId: '<id>' }), {
type: FormFieldTypes.DROPDOWN,
required: true
});
});
it('should be able to display label with asterisk', async () => {
fixture.detectChanges();
await fixture.whenStable();
const asterisk: HTMLElement = element.querySelector('.adf-asterisk');
expect(asterisk).toBeTruthy();
expect(asterisk.textContent).toEqual('*');
});
it('should be invalid if no default option after interaction', async () => {
fixture.detectChanges();
await fixture.whenStable();
expect(element.querySelector('.adf-invalid')).toBeFalsy();
const dropdownSelect = element.querySelector('.adf-select');
dropdownSelect.dispatchEvent(new Event('blur'));
fixture.detectChanges();
await fixture.whenStable();
expect(element.querySelector('.adf-invalid')).toBeTruthy();
});
});
describe('filter', () => {
beforeEach(() => {

View File

@@ -135,7 +135,7 @@ describe('RadioButtonsCloudWidgetComponent', () => {
expect(widget.field.isValid).toBe(true);
});
it('should be able to set a Radio Button widget as required', () => {
it('should set Radio Button as valid when required and not empty', () => {
widget.field = new FormFieldModel(new FormModel({}), {
id: 'radio-id',
name: 'radio-name-label',

View File

@@ -20,7 +20,6 @@ import {
AlfrescoApiService,
FormValues,
AppConfigService,
FormOutcomeModel,
FormModel,
FormFieldOption
} from '@alfresco/adf-core';
@@ -232,17 +231,7 @@ export class FormCloudService extends BaseCloudService implements FormCloudServi
formValues[variable.name] = variable.value;
});
const form = new FormModel(flattenForm, formValues, readOnly);
if (!json.fields) {
form.outcomes = [
new FormOutcomeModel(form, {
id: '$save',
name: FormOutcomeModel.SAVE_ACTION,
isSystem: true
})
];
}
return form;
return new FormModel(flattenForm, formValues, readOnly);
}
return null;
}

View File

@@ -306,7 +306,7 @@ describe('Task Cloud Service', () => {
});
});
it('should throw error if appName is not defined when querying by id', (done) => {
it('should throw error if appName is not defined when querying by id with update playload', (done) => {
const appName = null;
const taskId = '68d54a8f';
const updatePayload = { description: 'New description' };

View File

@@ -338,7 +338,7 @@ describe('TaskFormCloudComponent', () => {
component.onError({});
});
it('should emit taskCompleted when task is completed', () => {
it('should reload when task is completed', () => {
spyOn(taskCloudService, 'completeTask').and.returnValue(of({}));
const reloadSpy = spyOn(component, 'ngOnChanges').and.callThrough();
@@ -350,7 +350,7 @@ describe('TaskFormCloudComponent', () => {
expect(reloadSpy).toHaveBeenCalled();
});
it('should emit taskClaimed when task is claimed', () => {
it('should reload when task is claimed', () => {
spyOn(taskCloudService, 'claimTask').and.returnValue(of({}));
spyOn(component, 'hasCandidateUsers').and.returnValue(true);
const reloadSpy = spyOn(component, 'ngOnChanges').and.callThrough();

View File

@@ -168,18 +168,6 @@ describe('TaskHeaderCloudComponent', () => {
expect(valueEl.nativeElement.value).toBe('67c4z2a8f-01f3-11e9-8e36-0a58646002ad');
});
it('should display process instance id', async () => {
fixture.detectChanges();
await fixture.whenStable();
fixture.detectChanges();
const labelEl = fixture.debugElement.query(By.css('[data-automation-id="card-textitem-label-processInstanceId"]'));
const valueEl = fixture.debugElement.query(By.css('[data-automation-id="card-textitem-value-processInstanceId"]'));
expect(labelEl.nativeElement.textContent.trim()).toBe('ADF_CLOUD_TASK_HEADER.PROPERTIES.PROCESS_INSTANCE_ID');
expect(valueEl.nativeElement.value).toBe('67c4z2a8f-01f3-11e9-8e36-0a58646002ad');
});
it('should display placeholder if no due date', async () => {
component.taskDetails.dueDate = null;
component.refreshData();