[ACA-30333]FE - [Process-services] Create ADF task form. (#5611)

* [ACA-3033] FE - [Process-services] Create ADF task form.

* * Modifed task-from-template

* * Refactored details component with the task-form

* * Updated unit tests to the recent changes

* * Modified task-details component

* * Fixed failing tests

* * Fixed failing tests* Added doc

* * Fixed task-details-form e2e

* Fixed failing test on tas-details e2e

* * Fixed flaky process-services e2e

* * Fixed flaky e2e tests
This commit is contained in:
siva kumar 2020-04-22 12:58:03 +05:30 committed by GitHub
parent fde037498f
commit ea1454dde0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 1207 additions and 294 deletions

View File

@ -76,6 +76,7 @@
<adf-task-details #activitiDetails <adf-task-details #activitiDetails
[debugMode]="true" [debugMode]="true"
[taskId]="currentTaskId" [taskId]="currentTaskId"
[showFormTitle]="true"
[fieldValidators]="fieldValidators" [fieldValidators]="fieldValidators"
[showHeaderContent]="showHeaderContent" [showHeaderContent]="showHeaderContent"
(formCompleted)="onFormCompleted()" (formCompleted)="onFormCompleted()"

View File

@ -0,0 +1,54 @@
---
Title: Task Form component
Added: v2.0.0
Status: Active
Last reviewed: 2020-04-21
---
# [Task Form component](../../../lib/process-services/src/lib/task-list/components/task-form/task-form.component.ts "Defined in task-form.component.ts")
Shows a [`form`](../../../lib/process-services/src/lib/task-list/models/form.model.ts) for a task.
## Basic Usage
```html
<adf-task-form
[taskId]="taskId">
</adf-task-form>
```
## Class members
### Properties
| Name | Type | Default value | Description |
| ---- | ---- | ------------- | ----------- |
| fieldValidators | [`FormFieldValidator`](../../../lib/core/form/components/widgets/core/form-field-validator.ts)`[]` | \[] | Field validators for use with the form. |
| readOnlyForm | `boolean` | false | Toggles read-only state of the form. All form widgets render as read-only if enabled. |
| showFormCompleteButton | `boolean` | true | Toggles rendering of the `Complete` outcome button. |
| showFormRefreshButton | `boolean` | true | Toggles rendering of the `Refresh` button. |
| showFormSaveButton | `boolean` | true | Toggles rendering of the `Save` outcome button. |
| showFormTitle | `boolean` | false | Toggles rendering of the form title. |
| showCancelButton | `boolean` | true | Toggles rendering of the `Cancel` empty form button. |
| showFormValidationIcon | `boolean` | true | Toggle rendering of the validation icon
| taskId | `string` | | (**required**) The id of the task whose details we are asking for. |
### Events
| Name | Type | Description |
| ---- | ---- | ----------- |
| cancel | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<void>` | Emitted when the "Cancel" button is clicked. |
| completed | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<void>` | Emitted when the form associated with the task is completed. |
| error | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<any>` | Emitted when an error occurs. |
| executeOutcome | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`FormOutcomeEvent`](../../../lib/core/form/components/widgets/core/form-outcome-event.model.ts)`>` | Emitted when any outcome is executed. Default behaviour can be prevented via `event.preventDefault()`. |
| formCompleted | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`FormModel`](../../../lib/core/form/components/widgets/core/form.model.ts)`>` | Emitted when the form is submitted with the `Complete` outcome. |
| formContentClicked | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`ContentLinkModel`](../../../lib/core/form/components/widgets/core/content-link.model.ts)`>` | Emitted when the form field content is clicked. |
| formLoaded | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`FormModel`](../../../lib/core/form/components/widgets/core/form.model.ts)`>` | Emitted when the form is loaded or reloaded. |
| formError | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`FormFieldModel`](../../core/models/form-field.model.md)`[]>` | Emitted when the supplied form values have a validation error. |
| formSaved | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`FormModel`](../../../lib/core/form/components/widgets/core/form.model.ts)`>` | Emitted when the form is submitted with the `Save` or custom outcomes. |
## See also
- [Form component](./form.component.md)
- [Form field model](../../core/models/form-field.model.md)
- [Form service](../../core/services/form.service.md)

View File

@ -20,11 +20,11 @@ import { BrowserVisibility, BrowserActions, DropdownPage } from '@alfresco/adf-t
export class AttachFormPage { export class AttachFormPage {
noFormMessage: ElementFinder = element(by.id('adf-no-form-message')); noFormMessage: ElementFinder = element(by.css('.adf-empty-content__title'));
attachFormButton: ElementFinder = element(by.id('adf-no-form-attach-form-button')); attachFormButton: ElementFinder = element(by.id('adf-attach-form-attach-button'));
completeButton: ElementFinder = element(by.id('adf-no-form-complete-button')); completeButton: ElementFinder = element(by.id('adf-attach-form-complete-button'));
formDropdown: ElementFinder = element(by.id('form_id')); formDropdown: ElementFinder = element(by.id('form_id'));
cancelButton: ElementFinder = element(by.id('adf-no-form-cancel-button')); cancelButton: ElementFinder = element(by.id('adf-attach-form-cancel-button'));
defaultTitle: ElementFinder = element(by.css('mat-card-title[class="mat-card-title mat-card-title"]')); defaultTitle: ElementFinder = element(by.css('mat-card-title[class="mat-card-title mat-card-title"]'));
attachFormDropdown = new DropdownPage(element(by.css("div[class='adf-attach-form-row']"))); attachFormDropdown = new DropdownPage(element(by.css("div[class='adf-attach-form-row']")));
@ -66,6 +66,6 @@ export class AttachFormPage {
} }
async checkAttachFormButtonIsDisabled(): Promise<void> { async checkAttachFormButtonIsDisabled(): Promise<void> {
await BrowserVisibility.waitUntilElementIsVisible(element(by.css('button[id="adf-no-form-attach-form-button"][disabled]'))); await BrowserVisibility.waitUntilElementIsVisible(element(by.css('button[id="adf-attach-form-attach-button"][disabled]')));
} }
} }

View File

@ -55,10 +55,11 @@ export class TaskDetailsPage {
involvePeopleHeader: ElementFinder = element(by.css('div[class="adf-search-text-header"]')); involvePeopleHeader: ElementFinder = element(by.css('div[class="adf-search-text-header"]'));
removeInvolvedPeople: ElementFinder = element(by.css('button[data-automation-id="Remove"]')); removeInvolvedPeople: ElementFinder = element(by.css('button[data-automation-id="Remove"]'));
peopleTitle: ElementFinder = element(by.id('people-title')); peopleTitle: ElementFinder = element(by.id('people-title'));
noFormMessage: ElementFinder = element(by.css('span[id*="no-form-message"]'));
cancelAttachForm: ElementFinder = element(by.id('adf-no-form-cancel-button')); cancelAttachForm: ElementFinder = element(by.id('adf-no-form-cancel-button'));
attachFormButton: ElementFinder = element(by.id('adf-no-form-attach-form-button')); attachFormButton: ElementFinder = element(by.id('adf-no-form-attach-form-button'));
disabledAttachFormButton: ElementFinder = element(by.css('button[id="adf-no-form-attach-form-button"][disabled]')); disabledAttachFormButton: ElementFinder = element(by.css('button[id="adf-no-form-attach-form-button"][disabled]'));
removeAttachForm: ElementFinder = element(by.id('adf-no-form-remove-button')); removeAttachForm: ElementFinder = element(by.id('adf-attach-form-remove-button'));
attachFormName: ElementFinder = element(by.css('span[class="adf-form-title ng-star-inserted"]')); attachFormName: ElementFinder = element(by.css('span[class="adf-form-title ng-star-inserted"]'));
emptyTaskDetails: ElementFinder = element(by.css('adf-task-details > div > div')); emptyTaskDetails: ElementFinder = element(by.css('adf-task-details > div > div'));
priority: ElementFinder = element(by.css('span[data-automation-id*="priority"] span')); priority: ElementFinder = element(by.css('span[data-automation-id*="priority"] span'));
@ -163,6 +164,14 @@ export class TaskDetailsPage {
await BrowserActions.click(this.formNameField); await BrowserActions.click(this.formNameField);
} }
async checkStandaloneNoFormMessageIsDisplayed(): Promise<void> {
await BrowserVisibility.waitUntilElementIsVisible(this.noFormMessage);
}
async getNoFormMessage(): Promise<string> {
return BrowserActions.getText(this.noFormMessage);
}
getAssignee(): Promise<string> { getAssignee(): Promise<string> {
return BrowserActions.getText(this.assigneeField); return BrowserActions.getText(this.assigneeField);
} }

View File

@ -99,6 +99,7 @@ describe('Attach Folder', () => {
await contentNodeSelector.clickMoveCopyButton(); await contentNodeSelector.clickMoveCopyButton();
await widget.attachFolderWidget().checkFolderIsAttached(app.UPLOAD_FOLDER_FORM_CS.FIELD.widget_id, user.email); await widget.attachFolderWidget().checkFolderIsAttached(app.UPLOAD_FOLDER_FORM_CS.FIELD.widget_id, user.email);
await widget.attachFolderWidget().removeFolder(app.UPLOAD_FOLDER_FORM_CS.FIELD.widget_id, user.email); await widget.attachFolderWidget().removeFolder(app.UPLOAD_FOLDER_FORM_CS.FIELD.widget_id, user.email);
await taskPage.formFields().checkWidgetIsVisible(app.UPLOAD_FOLDER_FORM_CS.FIELD.widget_id);
await widget.attachFolderWidget().checkFolderIsNotAttached(app.UPLOAD_FOLDER_FORM_CS.FIELD.widget_id, user.email); await widget.attachFolderWidget().checkFolderIsNotAttached(app.UPLOAD_FOLDER_FORM_CS.FIELD.widget_id, user.email);
}); });
}); });

View File

@ -22,12 +22,14 @@ import { UsersActions } from '../actions/users.actions';
import { NavigationBarPage } from '../pages/adf/navigation-bar.page'; import { NavigationBarPage } from '../pages/adf/navigation-bar.page';
import { AttachFormPage } from '../pages/adf/process-services/attach-form.page'; import { AttachFormPage } from '../pages/adf/process-services/attach-form.page';
import { TasksPage } from '../pages/adf/process-services/tasks.page'; import { TasksPage } from '../pages/adf/process-services/tasks.page';
import { TaskDetailsPage } from '../pages/adf/process-services/task-details.page';
import CONSTANTS = require('../util/constants'); import CONSTANTS = require('../util/constants');
describe('Attach Form Component', () => { describe('Attach Form Component', () => {
const loginPage = new LoginPage(); const loginPage = new LoginPage();
const taskPage = new TasksPage(); const taskPage = new TasksPage();
const taskDetailsPage = new TaskDetailsPage();
const attachFormPage = new AttachFormPage(); const attachFormPage = new AttachFormPage();
const formFields = new FormFields(); const formFields = new FormFields();
const navigationBarPage = new NavigationBarPage(); const navigationBarPage = new NavigationBarPage();
@ -83,9 +85,9 @@ describe('Attach Form Component', () => {
await taskPage.filtersPage().goToFilter(CONSTANTS.TASK_FILTERS.MY_TASKS); await taskPage.filtersPage().goToFilter(CONSTANTS.TASK_FILTERS.MY_TASKS);
await taskPage.tasksListPage().selectRow(testNames.taskName); await taskPage.tasksListPage().selectRow(testNames.taskName);
await attachFormPage.checkNoFormMessageIsDisplayed(); await taskPage.taskDetails().checkStandaloneNoFormMessageIsDisplayed();
await attachFormPage.checkAttachFormButtonIsDisplayed(); await taskPage.taskDetails().checkAttachFormButtonIsDisplayed();
await attachFormPage.checkCompleteButtonIsDisplayed(); await taskPage.taskDetails().checkCompleteTaskButtonIsDisplayed();
}); });
it('[C280048] Should be able to view the attach-form component after clicking cancel button', async () => { it('[C280048] Should be able to view the attach-form component after clicking cancel button', async () => {
@ -94,7 +96,7 @@ describe('Attach Form Component', () => {
await taskPage.filtersPage().goToFilter(CONSTANTS.TASK_FILTERS.MY_TASKS); await taskPage.filtersPage().goToFilter(CONSTANTS.TASK_FILTERS.MY_TASKS);
await taskPage.tasksListPage().selectRow(testNames.taskName); await taskPage.tasksListPage().selectRow(testNames.taskName);
await attachFormPage.clickAttachFormButton(); await taskPage.taskDetails().clickAttachFormButton();
await attachFormPage.checkDefaultFormTitleIsDisplayed(testNames.formTitle); await attachFormPage.checkDefaultFormTitleIsDisplayed(testNames.formTitle);
await attachFormPage.checkFormDropdownIsDisplayed(); await attachFormPage.checkFormDropdownIsDisplayed();
await attachFormPage.checkCancelButtonIsDisplayed(); await attachFormPage.checkCancelButtonIsDisplayed();
@ -104,7 +106,7 @@ describe('Attach Form Component', () => {
await formFields.checkWidgetIsReadOnlyMode(testNames.widgetTitle); await formFields.checkWidgetIsReadOnlyMode(testNames.widgetTitle);
await attachFormPage.clickCancelButton(); await attachFormPage.clickCancelButton();
await attachFormPage.checkAttachFormButtonIsDisplayed(); await taskPage.taskDetails().checkAttachFormButtonIsDisplayed();
}); });
it('[C280017] Should be able to attach a form on a standalone task and complete', async () => { it('[C280017] Should be able to attach a form on a standalone task and complete', async () => {
@ -113,7 +115,7 @@ describe('Attach Form Component', () => {
await taskPage.filtersPage().goToFilter(CONSTANTS.TASK_FILTERS.MY_TASKS); await taskPage.filtersPage().goToFilter(CONSTANTS.TASK_FILTERS.MY_TASKS);
await taskPage.tasksListPage().selectRow(testNames.taskName); await taskPage.tasksListPage().selectRow(testNames.taskName);
await attachFormPage.clickAttachFormButton(); await taskDetailsPage.clickAttachFormButton();
await attachFormPage.selectAttachFormOption(testNames.formName); await attachFormPage.selectAttachFormOption(testNames.formName);
await attachFormPage.clickAttachFormButton(); await attachFormPage.clickAttachFormButton();

View File

@ -81,7 +81,7 @@ describe('Start Task - Task App', () => {
await taskPage.taskDetails().checkAttachFormButtonIsDisplayed(); await taskPage.taskDetails().checkAttachFormButtonIsDisplayed();
await taskPage.taskDetails().checkAttachFormButtonIsEnabled(); await taskPage.taskDetails().checkAttachFormButtonIsEnabled();
await expect(await taskPage.taskDetails().getFormName()).toEqual(CONSTANTS.TASK_DETAILS.NO_FORM); await expect(await taskPage.taskDetails().getFormName()).toEqual(CONSTANTS.TASK_DETAILS.NO_FORM);
await expect(await taskPage.formFields().getNoFormMessage()).toEqual(noFormMessage); await expect(await taskDetails.getNoFormMessage()).toEqual(noFormMessage);
}); });
it('[C268910] Should a standalone task be displayed in completed tasks when completing it', async () => { it('[C268910] Should a standalone task be displayed in completed tasks when completing it', async () => {
@ -107,17 +107,20 @@ describe('Start Task - Task App', () => {
await taskPage.tasksListPage().checkContentIsDisplayed(tasks[2]); await taskPage.tasksListPage().checkContentIsDisplayed(tasks[2]);
await taskPage.formFields().noFormIsDisplayed(); await taskPage.formFields().noFormIsDisplayed();
await taskPage.taskDetails().clickAttachFormButton();
const formFields = await taskPage.formFields(); const formFields = await taskPage.formFields();
await formFields.clickOnAttachFormButton();
await formFields.selectForm(app.formName); await formFields.selectForm(app.formName);
await formFields.clickOnAttachFormButton(); await formFields.clickOnAttachFormButton();
await taskPage.formFields().checkFormIsDisplayed();
await taskPage.taskDetails().checkCompleteFormButtonIsDisplayed();
await expect(await taskPage.taskDetails().getFormName()).toEqual(app.formName); await expect(await taskPage.taskDetails().getFormName()).toEqual(app.formName);
}); });
it('[C268912] Should a standalone task be displayed when removing the form from APS', async () => { it('[C268912] Should a standalone task be displayed when removing the form from APS', async () => {
const task = await taskPage.createNewTask(); const task = await taskPage.createNewTask();
const taskDetails = await taskPage.taskDetails();
await task.addName(tasks[3]); await task.addName(tasks[3]);
await task.selectForm(app.formName); await task.selectForm(app.formName);
await task.clickStartButton(); await task.clickStartButton();
@ -134,6 +137,6 @@ describe('Start Task - Task App', () => {
await taskPage.formFields().noFormIsDisplayed(); await taskPage.formFields().noFormIsDisplayed();
await expect(await taskPage.taskDetails().getFormName()).toEqual(CONSTANTS.TASK_DETAILS.NO_FORM); await expect(await taskPage.taskDetails().getFormName()).toEqual(CONSTANTS.TASK_DETAILS.NO_FORM);
await expect(await taskPage.formFields().getNoFormMessage()).toEqual(noFormMessage); await expect(await taskDetails.getNoFormMessage()).toEqual(noFormMessage);
}); });
}); });

View File

@ -27,11 +27,13 @@ import { TaskDetailsPage } from '../pages/adf/process-services/task-details.page
import { TasksListPage } from '../pages/adf/process-services/tasks-list.page'; import { TasksListPage } from '../pages/adf/process-services/tasks-list.page';
import CONSTANTS = require('../util/constants'); import CONSTANTS = require('../util/constants');
import { TasksPage } from '../pages/adf/process-services/tasks.page'; import { TasksPage } from '../pages/adf/process-services/tasks.page';
import { AttachFormPage } from '../pages/adf/process-services/attach-form.page';
describe('Task Details - Form', () => { describe('Task Details - Form', () => {
const loginPage = new LoginPage(); const loginPage = new LoginPage();
const tasksListPage = new TasksListPage(); const tasksListPage = new TasksListPage();
const taskDetailsPage = new TaskDetailsPage(); const taskDetailsPage = new TaskDetailsPage();
const attachFormPage = new AttachFormPage();
const taskPage = new TasksPage(); const taskPage = new TasksPage();
const filtersPage = new FiltersPage(); const filtersPage = new FiltersPage();
const widget = new Widget(); const widget = new Widget();
@ -101,25 +103,27 @@ describe('Task Details - Form', () => {
await tasksListPage.selectRow(task.name); await tasksListPage.selectRow(task.name);
await taskDetailsPage.clickForm(); await taskDetailsPage.clickForm();
await taskDetailsPage.checkAttachFormDropdownIsDisplayed(); await attachFormPage.checkFormDropdownIsDisplayed();
await taskDetailsPage.checkAttachFormButtonIsDisabled(); await attachFormPage.checkAttachFormButtonIsDisabled();
await taskDetailsPage.selectAttachFormOption(newForm.name); await attachFormPage.selectAttachFormOption(newForm.name);
await taskDetailsPage.checkSelectedForm(newForm.name); await taskDetailsPage.checkSelectedForm(newForm.name);
await taskDetailsPage.checkAttachFormButtonIsEnabled(); await attachFormPage.checkAttachFormButtonIsDisplayed();
await taskDetailsPage.checkCancelAttachFormIsDisplayed(); await attachFormPage.checkCancelButtonIsDisplayed();
await taskDetailsPage.clickCancelAttachForm(); await attachFormPage.clickCancelButton();
await taskDetailsPage.checkFormIsAttached(attachedForm.name); await taskDetailsPage.checkFormIsAttached(attachedForm.name);
await taskDetailsPage.clickForm(); await taskDetailsPage.clickForm();
await taskDetailsPage.checkAttachFormDropdownIsDisplayed(); await attachFormPage.checkFormDropdownIsDisplayed();
await taskDetailsPage.selectAttachFormOption(newForm.name); await attachFormPage.checkAttachFormButtonIsDisabled();
await attachFormPage.selectAttachFormOption(newForm.name);
await taskDetailsPage.checkSelectedForm(newForm.name);
await taskDetailsPage.checkAttachFormButtonIsDisplayed(); await attachFormPage.checkAttachFormButtonIsDisplayed();
await taskDetailsPage.clickAttachFormButton(); await attachFormPage.clickAttachFormButton();
await taskDetailsPage.checkFormIsAttached(newForm.name); await taskDetailsPage.checkFormIsAttached(newForm.name);
}); });

View File

@ -336,5 +336,20 @@
"CHOOSE_ITEM": "Choose {{ name }} to...", "CHOOSE_ITEM": "Choose {{ name }} to...",
"CHOOSE_IN": "Choose file in {{ name }}..." "CHOOSE_IN": "Choose file in {{ name }}..."
} }
},
"ADF_TASK_FORM": {
"EMPTY_FORM": {
"SUBTITLE": "Attach a form that can be viewed later",
"COMPLETE-TASK-MESSAGE": "Task {{taskName}} completed",
"COMPLETE-TASK-SUB-MESSAGE": "No forms to be added",
"BUTTONS": {
"COMPLETE": "COMPLETE",
"CANCEL": "CANCEL"
}
},
"COMPLETED_TASK": {
"TITLE": "Task {{taskName}} completed",
"SUBTITLE": "No forms to be added"
}
} }
} }

View File

@ -97,6 +97,46 @@ export let standaloneTaskWithoutForm = new TaskDetailsModel({
memberOfCandidateGroup: false memberOfCandidateGroup: false
}); });
export let completedStandaloneTaskWithoutForm = new TaskDetailsModel({
id: '200',
name: 'Standalone Task Without Form',
description: null,
category: null,
assignee: {
id: 1001,
firstName: 'Wilbur',
lastName: 'Adams',
email: 'wilbur@app.activiti.com'
},
created: '2016-11-03T15:25:42.749+0000',
dueDate: null,
endDate: new Date(),
duration: null,
priority: 50,
parentTaskId: null,
parentTaskName: null,
processInstanceId: null,
processInstanceName: null,
processDefinitionId: null,
processDefinitionName: null,
processDefinitionDescription: null,
processDefinitionKey: null,
processDefinitionCategory: null,
processDefinitionVersion: null,
processDefinitionDeploymentId: null,
formKey: null,
processInstanceStartUserId: null,
initiatorCanCompleteTask: false,
adhocTaskCanBeReassigned: false,
taskDefinitionKey: 'sid-DDECD9E4-0299-433F-9193-C3D905C3EEBE',
executionId: '86',
involvedGroups: [],
involvedPeople: [],
memberOfCandidateUsers: false,
managerOfCandidateGroup: false,
memberOfCandidateGroup: false
});
export let taskDetailsMock = new TaskDetailsModel({ export let taskDetailsMock = new TaskDetailsModel({
id: '91', id: '91',
name: 'Request translation', name: 'Request translation',
@ -137,6 +177,54 @@ export let taskDetailsMock = new TaskDetailsModel({
memberOfCandidateGroup: false memberOfCandidateGroup: false
}); });
export let initiatorCanCompleteTaskDetailsMock = new TaskDetailsModel({
id: '91',
name: 'Request translation',
description: null,
category: null,
assignee: { email: 'mock-user-email' },
created: '2016-11-03T15:25:42.749+0000',
dueDate: null,
endDate: null,
duration: null,
priority: 50,
parentTaskId: null,
parentTaskName: null,
processInstanceId: '86',
processInstanceName: null,
processDefinitionId: 'TranslationProcess:2:8',
processDefinitionName: 'Translation Process',
processDefinitionDescription: null,
processDefinitionKey: 'TranslationProcess',
processDefinitionCategory: 'http://www.activiti.org/processdef',
processDefinitionVersion: 2,
processDefinitionDeploymentId: '5',
formKey: '4',
processInstanceStartUserId: '1001',
initiatorCanCompleteTask: true,
adhocTaskCanBeReassigned: false,
taskDefinitionKey: 'sid-DDECD9E4-0299-433F-9193-C3D905C3EEBE',
executionId: '86',
involvedGroups: [],
involvedPeople: [
{
id: 1001,
firstName: 'Wilbur',
lastName: 'Adams',
email: 'wilbur@app.activiti.com'
},
{
id: 111,
firstName: 'fake-first-name',
lastName: 'fake-last-name',
email: 'fake@app.activiti.com'
}
],
memberOfCandidateUsers: false,
managerOfCandidateGroup: false,
memberOfCandidateGroup: false
});
export let taskDetailsWithOutAssigneeMock = new TaskDetailsModel({ export let taskDetailsWithOutAssigneeMock = new TaskDetailsModel({
id: '91', id: '91',
name: 'Request translation', name: 'Request translation',
@ -183,6 +271,7 @@ export let claimableTaskDetailsMock = new TaskDetailsModel({
endDate: null, endDate: null,
duration: null, duration: null,
priority: 50, priority: 50,
formKey: '4',
parentTaskId: null, parentTaskId: null,
parentTaskName: null, parentTaskName: null,
processInstanceId: '86', processInstanceId: '86',
@ -353,6 +442,36 @@ export let taskDetailsWithOutCandidateGroup = new TaskDetailsModel({
] ]
}); });
export let completedTaskWithFormMock = new TaskDetailsModel({
id: '91',
name: 'Request translation',
description: null,
category: null,
assignee: {
id: 1001,
firstName: 'Wilbur',
lastName: 'Adams',
email: 'wilbur@app.activiti.com'
},
created: '2016-11-03T15:25:42.749+0000',
dueDate: null,
endDate: new Date(),
duration: null,
priority: 50,
formKey: '91',
parentTaskId: null,
parentTaskName: null,
processInstanceId: '86',
processInstanceName: null,
processDefinitionId: 'TranslationProcess:2:8',
processDefinitionName: 'Translation Process',
involvedGroups: [],
involvedPeople: [],
managerOfCandidateGroup: true,
memberOfCandidateGroup: true,
memberOfCandidateUsers: false
});
export let completedTaskDetailsMock = new TaskDetailsModel({ export let completedTaskDetailsMock = new TaskDetailsModel({
id: '91', id: '91',
name: 'Request translation', name: 'Request translation',
@ -366,9 +485,10 @@ export let completedTaskDetailsMock = new TaskDetailsModel({
}, },
created: '2016-11-03T15:25:42.749+0000', created: '2016-11-03T15:25:42.749+0000',
dueDate: null, dueDate: null,
endDate: '2016-11-03T15:25:42.749+0000', endDate: new Date(),
duration: null, duration: null,
priority: 50, priority: 50,
formKey: null,
parentTaskId: null, parentTaskId: null,
parentTaskName: null, parentTaskName: null,
processInstanceId: '86', processInstanceId: '86',
@ -382,6 +502,40 @@ export let completedTaskDetailsMock = new TaskDetailsModel({
memberOfCandidateUsers: false memberOfCandidateUsers: false
}); });
export let taskDetailsWithOutFormMock = new TaskDetailsModel({
'id': '91',
'name': 'Request translation',
'description': 'fake description',
'category': null,
'assignee': {'id': 1001, 'firstName': 'Admin', 'lastName': 'Paul', 'email': 'my@mymail.com' },
'created': '2016-11-03T15:25:42.749+0000',
'dueDate': '2016-11-03T15:25:42.749+0000',
'endDate': null,
'duration': null,
'priority': 50,
'parentTaskId': null,
'parentTaskName': null,
'processInstanceId': '86',
'processInstanceName': null,
'processDefinitionId': 'TranslationProcess:2:8',
'processDefinitionName': 'Translation Process',
'processDefinitionDescription': null,
'processDefinitionKey': 'TranslationProcess',
'processDefinitionCategory': 'http://www.activiti.org/processdef',
'processDefinitionVersion': 2,
'processDefinitionDeploymentId': '5',
'formKey': null,
'processInstanceStartUserId': '1001',
'initiatorCanCompleteTask': false,
'adhocTaskCanBeReassigned': false,
'taskDefinitionKey': 'sid-DDECD9E4-0299-433F-9193-C3D905C3EEBE',
'executionId': '86',
'involvedPeople': [],
'memberOfCandidateUsers': false,
'managerOfCandidateGroup': false,
'memberOfCandidateGroup': false
});
export const taskFormMock = { export const taskFormMock = {
id: 4, id: 4,
name: 'Translation request', name: 'Translation request',

View File

@ -11,6 +11,7 @@
@import '../content-widget/attach-file-widget-dialog.component'; @import '../content-widget/attach-file-widget-dialog.component';
@import '../form/start-form.component'; @import '../form/start-form.component';
@import '../process-list/components/start-process.component'; @import '../process-list/components/start-process.component';
@import '../task-list/components/task-form/task-form.component';
@mixin adf-process-services-theme($theme) { @mixin adf-process-services-theme($theme) {
@include adf-process-filters-theme($theme); @include adf-process-filters-theme($theme);
@ -26,4 +27,5 @@
@include adf-attach-file-widget-dialog-component-theme($theme); @include adf-attach-file-widget-dialog-component-theme($theme);
@include adf-start-form-component-theme($theme); @include adf-start-form-component-theme($theme);
@include adf-process-services-create-theme($theme); @include adf-process-services-create-theme($theme);
@include adf-task-form-theme($theme);
} }

View File

@ -1,7 +1,7 @@
<div class="adf-attach-form"> <div class="adf-attach-form">
<mat-card> <mat-card>
<mat-card-content> <mat-card-content>
<div class="adf-no-form-message-container"> <div class="adf-attache-form-message-container">
<mat-card-title class="mat-card-title"> <mat-card-title class="mat-card-title">
<h4 class="adf-form-title">{{ 'ADF_TASK_LIST.ATTACH_FORM.SELECT_FORM' | translate }}</h4> <h4 class="adf-form-title">{{ 'ADF_TASK_LIST.ATTACH_FORM.SELECT_FORM' | translate }}</h4>
</mat-card-title> </mat-card-title>
@ -23,13 +23,13 @@
</div> </div>
</mat-card-content> </mat-card-content>
<mat-card-actions class="adf-no-form-mat-card-actions"> <mat-card-actions class="adf-attach-form-mat-card-actions">
<div> <div>
<button mat-button id="adf-no-form-remove-button" color="warn" *ngIf="formKey" (click)="onRemoveButtonClick()">{{ 'ADF_TASK_LIST.ATTACH_FORM.REMOVE_FORM' | translate }}</button> <button mat-button id="adf-attach-form-remove-button" color="warn" *ngIf="formKey" (click)="onRemoveButtonClick()">{{ 'ADF_TASK_LIST.ATTACH_FORM.REMOVE_FORM' | translate }}</button>
</div> </div>
<div> <div>
<button mat-button id="adf-no-form-cancel-button" (click)="onCancelButtonClick()">{{ 'ADF_TASK_LIST.START_TASK.FORM.ACTION.CANCEL' | translate }}</button> <button mat-button id="adf-attach-form-cancel-button" (click)="onCancelButtonClick()">{{ 'ADF_TASK_LIST.START_TASK.FORM.ACTION.CANCEL' | translate }}</button>
<button mat-button id="adf-no-form-attach-form-button" [disabled]="disableSubmit" color="primary" (click)="onAttachFormButtonClick()">{{ 'ADF_TASK_LIST.START_TASK.FORM.LABEL.ATTACHFORM' | translate }}</button> <button mat-button id="adf-attach-form-attach-button" [disabled]="disableSubmit" color="primary" (click)="onAttachFormButtonClick()">{{ 'ADF_TASK_LIST.START_TASK.FORM.LABEL.ATTACHFORM' | translate }}</button>
</div> </div>
</mat-card-actions> </mat-card-actions>
</mat-card> </mat-card>

View File

@ -9,7 +9,7 @@
margin: 20px 0; margin: 20px 0;
} }
.adf-no-form-mat-card-actions { .adf-attach-form-mat-card-actions {
justify-content: space-between; justify-content: space-between;
margin-top: 30px; margin-top: 30px;
text-align: right; text-align: right;

View File

@ -51,7 +51,7 @@ describe('AttachFormComponent', () => {
it('should show the attach button disabled', async(() => { it('should show the attach button disabled', async(() => {
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
const attachButton = fixture.debugElement.query(By.css('#adf-no-form-attach-form-button')); const attachButton = fixture.debugElement.query(By.css('#adf-attach-form-attach-button'));
expect(attachButton.nativeElement.disabled).toBeTruthy(); expect(attachButton.nativeElement.disabled).toBeTruthy();
}); });
})); }));
@ -60,7 +60,7 @@ describe('AttachFormComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
const emitSpy = spyOn(component.cancelAttachForm, 'emit'); const emitSpy = spyOn(component.cancelAttachForm, 'emit');
const el = fixture.nativeElement.querySelector('#adf-no-form-cancel-button'); const el = fixture.nativeElement.querySelector('#adf-attach-form-cancel-button');
el.click(); el.click();
expect(emitSpy).toHaveBeenCalled(); expect(emitSpy).toHaveBeenCalled();
}); });
@ -72,8 +72,8 @@ describe('AttachFormComponent', () => {
spyOn(taskService, 'attachFormToATask').and.returnValue(of(true)); spyOn(taskService, 'attachFormToATask').and.returnValue(of(true));
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
expect(element.querySelector('#adf-no-form-attach-form-button')).toBeDefined(); expect(element.querySelector('#adf-attach-form-attach-button')).toBeDefined();
const el = fixture.nativeElement.querySelector('#adf-no-form-attach-form-button'); const el = fixture.nativeElement.querySelector('#adf-attach-form-attach-button');
el.click(); el.click();
expect(taskService.attachFormToATask).toHaveBeenCalledWith(1, 2); expect(taskService.attachFormToATask).toHaveBeenCalledWith(1, 2);
}); });
@ -87,7 +87,7 @@ describe('AttachFormComponent', () => {
spyOn(taskService, 'attachFormToATask').and.returnValue(of(true)); spyOn(taskService, 'attachFormToATask').and.returnValue(of(true));
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
const attachButton = fixture.debugElement.query(By.css('#adf-no-form-attach-form-button')); const attachButton = fixture.debugElement.query(By.css('#adf-attach-form-attach-button'));
expect(attachButton.nativeElement.disabled).toBeFalsy(); expect(attachButton.nativeElement.disabled).toBeFalsy();
}); });
})); }));
@ -103,7 +103,7 @@ describe('AttachFormComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
component.attachFormControl.setValue(2); component.attachFormControl.setValue(2);
fixture.detectChanges(); fixture.detectChanges();
const attachButton = fixture.debugElement.query(By.css('#adf-no-form-attach-form-button')); const attachButton = fixture.debugElement.query(By.css('#adf-attach-form-attach-button'));
expect(attachButton.nativeElement.disabled).toBeTruthy(); expect(attachButton.nativeElement.disabled).toBeTruthy();
}); });
})); }));
@ -140,8 +140,8 @@ describe('AttachFormComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
fixture.detectChanges(); fixture.detectChanges();
expect(element.querySelector('#adf-no-form-remove-button')).toBeDefined(); expect(element.querySelector('#adf-attach-form-remove-button')).toBeDefined();
const el = fixture.nativeElement.querySelector('#adf-no-form-remove-button'); const el = fixture.nativeElement.querySelector('#adf-attach-form-remove-button');
el.click(); el.click();
expect(component.formId).toBeNull(); expect(component.formId).toBeNull();
}); });
@ -163,7 +163,7 @@ describe('AttachFormComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
const emitSpy = spyOn(component.success, 'emit'); const emitSpy = spyOn(component.success, 'emit');
const el = fixture.nativeElement.querySelector('#adf-no-form-attach-form-button'); const el = fixture.nativeElement.querySelector('#adf-attach-form-attach-button');
el.click(); el.click();
expect(emitSpy).toHaveBeenCalled(); expect(emitSpy).toHaveBeenCalled();
}); });

View File

@ -17,19 +17,14 @@
import { NoTaskDetailsTemplateDirective } from './no-task-detail-template.directive'; import { NoTaskDetailsTemplateDirective } from './no-task-detail-template.directive';
import { TaskDetailsComponent } from './task-details.component'; import { TaskDetailsComponent } from './task-details.component';
import { AuthenticationService } from '@alfresco/adf-core';
import { of } from 'rxjs';
describe('NoTaskDetailsTemplateDirective', () => { describe('NoTaskDetailsTemplateDirective', () => {
let component: NoTaskDetailsTemplateDirective; let component: NoTaskDetailsTemplateDirective;
let detailsComponent: TaskDetailsComponent; let detailsComponent: TaskDetailsComponent;
let authService: AuthenticationService;
beforeEach(() => { beforeEach(() => {
authService = new AuthenticationService(null, null, null, null, null); detailsComponent = new TaskDetailsComponent(null, null, null, null, null);
spyOn(authService, 'getBpmLoggedUser').and.returnValue(of({ email: 'fake-email' }));
detailsComponent = new TaskDetailsComponent(null, authService, null, null, null, null);
component = new NoTaskDetailsTemplateDirective(detailsComponent); component = new NoTaskDetailsTemplateDirective(detailsComponent);
}); });

View File

@ -22,51 +22,21 @@
<div class="adf-task-details-core-form"> <div class="adf-task-details-core-form">
<div *ngIf="isAssigned()"> <div *ngIf="isAssigned()">
<adf-form *ngIf="isFormComponentVisible()" #activitiForm <adf-task-form
[taskId]="taskDetails.id" [taskId]="taskDetails.id"
[showTitle]="showFormTitle" [showFormTitle]="showFormTitle"
[showRefreshButton]="showFormRefreshButton" [showFormRefreshButton]="showFormRefreshButton"
[showCompleteButton]="showFormCompleteButton" [showCancelButton]="true"
[disableCompleteButton]="!isCompleteButtonEnabled()"
[showSaveButton]="isSaveButtonVisible()"
[readOnly]="internalReadOnlyForm"
[fieldValidators]="fieldValidators" [fieldValidators]="fieldValidators"
(formSaved)='onFormSaved($event)' (formSaved)='onFormSaved($event)'
(formCompleted)='onFormCompleted($event)' (formCompleted)='onFormCompleted($event)'
(formContentClicked)='onFormContentClick($event)' (formContentClicked)='onFormContentClick($event)'
(formLoaded)='onFormLoaded($event)' (formLoaded)='onFormLoaded($event)'
(error)='onFormError($event)' (completed)="onComplete()"
(executeOutcome)='onFormExecuteOutcome($event)'> (showAttachForm)="onShowAttachForm()"
</adf-form> (executeOutcome)='onFormExecuteOutcome($event)'
<adf-task-standalone *ngIf="isTaskStandaloneComponentVisible()" (error)="onFormError($event)" #activitiTaskForm>
[taskName]="taskDetails.name" </adf-task-form>
[taskId]="taskDetails.id"
[isCompleted]="isCompletedTask()"
[hasCompletePermission]="isCompleteButtonEnabled()"
[hideCancelButton]="true"
(complete)="onComplete()"
(showAttachForm)="onShowAttachForm()">
</adf-task-standalone>
<mat-card class="adf-message-card" *ngIf="!isTaskStandaloneComponentVisible() && !isCompletedTask() && !isFormComponentVisible()" >
<mat-card-content>
<div class="adf-no-form-message-container">
<div class="adf-no-form-message-list">
<div *ngIf="!isCompletedTask()" class="adf-no-form-message">
<span id="adf-no-form-message">{{'ADF_TASK_LIST.STANDALONE_TASK.NO_FORM_MESSAGE' | translate}}</span>
</div>
</div>
</div>
</mat-card-content>
<mat-card-actions class="adf-no-form-mat-card-actions">
<div>
<button mat-button id="adf-no-form-complete-button" color="primary" (click)="onComplete()">{{ 'ADF_TASK_LIST.DETAILS.BUTTON.COMPLETE' | translate }}</button>
</div>
</mat-card-actions>
</mat-card>
<adf-attach-form *ngIf="isShowAttachForm()" <adf-attach-form *ngIf="isShowAttachForm()"
[taskId]="taskDetails.id" [taskId]="taskDetails.id"
[formKey]="taskDetails.formKey" [formKey]="taskDetails.formKey"

View File

@ -36,8 +36,6 @@ import { TaskDetailsModel } from '../models/task-details.model';
import { import {
noDataMock, noDataMock,
taskDetailsMock, taskDetailsMock,
standaloneTaskWithForm,
standaloneTaskWithoutForm,
taskFormMock, taskFormMock,
tasksMock, tasksMock,
taskDetailsWithOutAssigneeMock taskDetailsWithOutAssigneeMock
@ -64,7 +62,6 @@ describe('TaskDetailsComponent', () => {
let getTaskDetailsSpy: jasmine.Spy; let getTaskDetailsSpy: jasmine.Spy;
let getTasksSpy: jasmine.Spy; let getTasksSpy: jasmine.Spy;
let assignTaskSpy: jasmine.Spy; let assignTaskSpy: jasmine.Spy;
let completeTaskSpy: jasmine.Spy;
let logService: LogService; let logService: LogService;
let commentProcessService: CommentProcessService; let commentProcessService: CommentProcessService;
let peopleProcessService: PeopleProcessService; let peopleProcessService: PeopleProcessService;
@ -99,7 +96,6 @@ describe('TaskDetailsComponent', () => {
getTasksSpy = spyOn(service, 'getTasks').and.returnValue(of(tasksMock)); getTasksSpy = spyOn(service, 'getTasks').and.returnValue(of(tasksMock));
assignTaskSpy = spyOn(service, 'assignTask').and.returnValue(of(fakeUser)); assignTaskSpy = spyOn(service, 'assignTask').and.returnValue(of(fakeUser));
completeTaskSpy = spyOn(service, 'completeTask').and.returnValue(of({}));
commentProcessService = TestBed.get(CommentProcessService); commentProcessService = TestBed.get(CommentProcessService);
authService = TestBed.get(AuthenticationService); authService = TestBed.get(AuthenticationService);
@ -175,42 +171,6 @@ describe('TaskDetailsComponent', () => {
}); });
})); }));
it('should display task standalone component when the task does not have an associated form', async(() => {
component.taskId = '123';
getTaskDetailsSpy.and.returnValue(of(standaloneTaskWithoutForm));
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(component.isStandaloneTaskWithoutForm()).toBeTruthy();
expect(fixture.debugElement.query(By.css('adf-task-standalone'))).not.toBeNull();
});
}));
it('should not display task standalone component when the task has a form', async(() => {
component.taskId = '123';
getTaskDetailsSpy.and.returnValue(of(standaloneTaskWithForm));
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(component.isStandaloneTaskWithForm()).toBeTruthy();
expect(fixture.debugElement.query(By.css('adf-task-standalone'))).toBeDefined();
expect(fixture.debugElement.query(By.css('adf-task-standalone'))).not.toBeNull();
});
}));
it('should display the AttachFormComponent when standaloneTaskWithForm and click on attach button', async(() => {
component.taskId = '123';
getTaskDetailsSpy.and.returnValue(of(standaloneTaskWithForm));
fixture.detectChanges();
component.onShowAttachForm();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(component.isStandaloneTaskWithForm()).toBeTruthy();
expect(fixture.debugElement.query(By.css('adf-attach-form'))).toBeDefined();
});
}));
it('should display the claim message when the task is not assigned', async(() => { it('should display the claim message when the task is not assigned', async(() => {
component.taskDetails = taskDetailsWithOutAssigneeMock; component.taskDetails = taskDetailsWithOutAssigneeMock;
fixture.detectChanges(); fixture.detectChanges();
@ -229,86 +189,6 @@ describe('TaskDetailsComponent', () => {
}); });
})); }));
describe('and form with visiblity', () => {
beforeEach(async () => {
component.taskId = '123';
spyOn(formService, 'completeTaskForm').and.returnValue(of({}));
taskDetailsMock.formKey = '4';
getTaskDetailsSpy.and.returnValue(of(taskDetailsMock));
fixture.detectChanges();
await fixture.whenStable();
});
it('[C312410] - Should be possible to complete a task that has an invisible field on a form with a value', async (done) => {
component.formCompleted.subscribe((form: FormModel) => {
expect(form.id).toBe(taskFormMock.id);
done();
});
component.taskDetails.initiatorCanCompleteTask = true;
component.showNextTask = false;
fixture.detectChanges();
await fixture.whenStable();
const inputTextOne: HTMLInputElement = fixture.nativeElement.querySelector('#text1');
expect(inputTextOne).toBeDefined();
expect(inputTextOne).not.toBeNull();
const inputTextTwo: HTMLInputElement = fixture.nativeElement.querySelector('#text2');
expect(inputTextTwo).toBeDefined();
expect(inputTextTwo).not.toBeNull();
let inputTextThree: HTMLInputElement = fixture.nativeElement.querySelector('#text3');
expect(inputTextThree).toBeDefined();
expect(inputTextThree).not.toBeNull();
inputTextOne.value = 'a';
inputTextOne.dispatchEvent(new Event('input'));
inputTextTwo.value = 'a';
inputTextTwo.dispatchEvent(new Event('input'));
inputTextThree.value = 'a';
inputTextThree.dispatchEvent(new Event('input'));
fixture.detectChanges();
await fixture.whenStable();
inputTextThree = fixture.nativeElement.querySelector('#text3');
expect(inputTextThree).toBeDefined();
expect(inputTextThree).not.toBeNull();
inputTextOne.value = 'b';
inputTextOne.dispatchEvent(new Event('input'));
fixture.detectChanges();
await fixture.whenStable();
const inputThreeContainer = fixture.nativeElement.querySelector('#field-text3-container');
expect(inputThreeContainer.hidden).toBe(true);
const completeOutcomeButton: HTMLButtonElement = fixture.nativeElement.querySelector('#adf-form-complete');
expect(completeOutcomeButton.hidden).toBe(false);
completeOutcomeButton.click();
fixture.detectChanges();
});
it('[C277278] - Should show if the form is valid via the validation icon', async () => {
const numberInput: HTMLInputElement = fixture.nativeElement.querySelector('#numberField');
let validationForm = fixture.nativeElement.querySelector('#adf-valid-form-icon');
expect(numberInput).toBeDefined();
expect(numberInput).not.toBeNull();
expect(validationForm.textContent).toBe('check_circle');
numberInput.value = 'a';
numberInput.dispatchEvent(new Event('input'));
fixture.detectChanges();
await fixture.whenStable();
const invalidForm = fixture.nativeElement.querySelector('#adf-invalid-form-icon');
expect(invalidForm).not.toBeNull();
expect(invalidForm.textContent).toBe('error');
numberInput.value = '4';
numberInput.dispatchEvent(new Event('input'));
fixture.detectChanges();
await fixture.whenStable();
validationForm = fixture.nativeElement.querySelector('#adf-valid-form-icon');
expect(validationForm.textContent).toBe('check_circle');
});
});
describe('change detection', () => { describe('change detection', () => {
let change; let change;
@ -414,11 +294,6 @@ describe('TaskDetailsComponent', () => {
expect(getTasksSpy).not.toHaveBeenCalled(); expect(getTasksSpy).not.toHaveBeenCalled();
}); });
it('should call service to complete task when complete button clicked', () => {
component.onComplete();
expect(completeTaskSpy).toHaveBeenCalled();
});
it('should emit a complete event when complete button clicked and task completed', () => { it('should emit a complete event when complete button clicked and task completed', () => {
const emitSpy: jasmine.Spy = spyOn(component.formCompleted, 'emit'); const emitSpy: jasmine.Spy = spyOn(component.formCompleted, 'emit');
component.onComplete(); component.onComplete();

View File

@ -17,7 +17,6 @@
import { import {
PeopleProcessService, UserProcessModel, PeopleProcessService, UserProcessModel,
AuthenticationService,
CardViewUpdateService, CardViewUpdateService,
ClickNotification, ClickNotification,
LogService, LogService,
@ -42,8 +41,8 @@ import { Observable, Observer, of, Subject } from 'rxjs';
import { TaskQueryRequestRepresentationModel } from '../models/filter.model'; import { TaskQueryRequestRepresentationModel } from '../models/filter.model';
import { TaskDetailsModel } from '../models/task-details.model'; import { TaskDetailsModel } from '../models/task-details.model';
import { TaskListService } from './../services/tasklist.service'; import { TaskListService } from './../services/tasklist.service';
import { UserRepresentation } from '@alfresco/js-api';
import { catchError, share, takeUntil } from 'rxjs/operators'; import { catchError, share, takeUntil } from 'rxjs/operators';
import { TaskFormComponent } from './task-form/task-form.component';
@Component({ @Component({
selector: 'adf-task-details', selector: 'adf-task-details',
@ -61,6 +60,9 @@ export class TaskDetailsComponent implements OnInit, OnChanges, OnDestroy {
@ViewChild('errorDialog') @ViewChild('errorDialog')
errorDialog: TemplateRef<any>; errorDialog: TemplateRef<any>;
@ViewChild('activitiTaskForm')
taskFormComponent: TaskFormComponent;
/** Toggles debug mode. */ /** Toggles debug mode. */
@Input() @Input()
debugMode: boolean = false; debugMode: boolean = false;
@ -179,11 +181,9 @@ export class TaskDetailsComponent implements OnInit, OnChanges, OnDestroy {
peopleSearch: Observable<UserProcessModel[]>; peopleSearch: Observable<UserProcessModel[]>;
currentLoggedUser: UserRepresentation;
data: any; data: any;
constructor(private taskListService: TaskListService, constructor(private taskListService: TaskListService,
private authService: AuthenticationService,
private peopleProcessService: PeopleProcessService, private peopleProcessService: PeopleProcessService,
private logService: LogService, private logService: LogService,
private cardViewUpdateService: CardViewUpdateService, private cardViewUpdateService: CardViewUpdateService,
@ -192,9 +192,6 @@ export class TaskDetailsComponent implements OnInit, OnChanges, OnDestroy {
ngOnInit() { ngOnInit() {
this.peopleSearch = new Observable<UserProcessModel[]>((observer) => this.peopleSearchObserver = observer).pipe(share()); this.peopleSearch = new Observable<UserProcessModel[]>((observer) => this.peopleSearchObserver = observer).pipe(share());
this.authService.getBpmLoggedUser().subscribe(user => {
this.currentLoggedUser = user;
});
if (this.taskId) { if (this.taskId) {
this.loadDetails(this.taskId); this.loadDetails(this.taskId);
@ -225,26 +222,6 @@ export class TaskDetailsComponent implements OnInit, OnChanges, OnDestroy {
} }
} }
isStandaloneTask(): boolean {
return !(this.taskDetails && (!!this.taskDetails.processDefinitionId));
}
isStandaloneTaskWithForm(): boolean {
return this.isStandaloneTask() && this.hasFormKey();
}
isStandaloneTaskWithoutForm(): boolean {
return this.isStandaloneTask() && !this.hasFormKey();
}
isFormComponentVisible(): boolean {
return this.hasFormKey() && !this.isShowAttachForm();
}
isTaskStandaloneComponentVisible(): boolean {
return this.isStandaloneTaskWithoutForm() && !this.isShowAttachForm();
}
isShowAttachForm(): boolean { isShowAttachForm(): boolean {
return this.showAttachForm; return this.showAttachForm;
} }
@ -256,13 +233,6 @@ export class TaskDetailsComponent implements OnInit, OnChanges, OnDestroy {
this.taskDetails = null; this.taskDetails = null;
} }
/**
* Check if the task has a form
*/
hasFormKey(): boolean {
return (this.taskDetails && (!!this.taskDetails.formKey));
}
isTaskActive() { isTaskActive() {
return this.taskDetails && this.taskDetails.duration === null; return this.taskDetails && this.taskDetails.duration === null;
} }
@ -329,44 +299,6 @@ export class TaskDetailsComponent implements OnInit, OnChanges, OnDestroy {
return !!this.taskDetails.assignee; return !!this.taskDetails.assignee;
} }
private hasEmailAddress(): boolean {
return this.taskDetails.assignee.email ? true : false;
}
isAssignedToMe(): boolean {
return this.isAssigned() && this.hasEmailAddress() ?
this.isEmailEqual(this.taskDetails.assignee.email, this.currentLoggedUser.email) :
this.isExternalIdEqual(this.taskDetails.assignee.externalId, this.currentLoggedUser.externalId);
}
private isEmailEqual(assigneeMail: string, currentLoggedEmail: string): boolean {
return assigneeMail.toLocaleLowerCase() === currentLoggedEmail.toLocaleLowerCase();
}
private isExternalIdEqual(assigneeExternalId: string, currentUserExternalId: string): boolean {
return assigneeExternalId.toLocaleLowerCase() === currentUserExternalId.toLocaleLowerCase();
}
isCompleteButtonEnabled(): boolean {
return this.isAssignedToMe() || this.canInitiatorComplete();
}
isCompleteButtonVisible(): boolean {
return !this.hasFormKey() && this.isTaskActive() && this.isCompleteButtonEnabled();
}
canInitiatorComplete(): boolean {
return this.taskDetails.initiatorCanCompleteTask;
}
isSaveButtonVisible(): boolean {
return this.hasSaveButton() && (!this.canInitiatorComplete() || this.isAssignedToMe());
}
hasSaveButton(): boolean {
return this.showFormSaveButton;
}
/** /**
* Retrieve the next open task * Retrieve the next open task
* @param processInstanceId * @param processInstanceId
@ -395,9 +327,7 @@ export class TaskDetailsComponent implements OnInit, OnChanges, OnDestroy {
* Complete button clicked * Complete button clicked
*/ */
onComplete(): void { onComplete(): void {
this.taskListService this.onFormCompleted(null);
.completeTask(this.taskId)
.subscribe(() => this.onFormCompleted(null));
} }
onShowAttachForm() { onShowAttachForm() {
@ -410,6 +340,7 @@ export class TaskDetailsComponent implements OnInit, OnChanges, OnDestroy {
onCompleteAttachForm() { onCompleteAttachForm() {
this.showAttachForm = false; this.showAttachForm = false;
this.taskFormComponent.loadTask(this.taskId);
this.loadDetails(this.taskId); this.loadDetails(this.taskId);
} }
@ -464,10 +395,6 @@ export class TaskDetailsComponent implements OnInit, OnChanges, OnDestroy {
this.loadDetails(taskId); this.loadDetails(taskId);
} }
isCompletedTask(): boolean {
return this.taskDetails && this.taskDetails.endDate ? true : undefined;
}
searchUser(searchedWord: string) { searchUser(searchedWord: string) {
this.peopleProcessService.getWorkflowUsers(null, searchedWord) this.peopleProcessService.getWorkflowUsers(null, searchedWord)
.subscribe( .subscribe(

View File

@ -0,0 +1,74 @@
<ng-container *ngIf="!loading; else loadingTemplate">
<adf-form *ngIf="hasFormKey(); else withoutForm"
[taskId]="taskDetails?.id"
[showTitle]="showFormTitle"
[showValidationIcon]="showFormValidationIcon"
[showRefreshButton]="showFormRefreshButton"
[showCompleteButton]="showFormCompleteButton"
[disableCompleteButton]="!isCompleteButtonEnabled()"
[showSaveButton]="isSaveButtonVisible()"
[readOnly]="isReadOnlyForm()"
[fieldValidators]="fieldValidators"
(formSaved)='onFormSaved($event)'
(formCompleted)='onFormCompleted($event)'
(formContentClicked)='onFormContentClick($event)'
(formLoaded)='onFormLoaded($event)'
(formError)='onFormError($event)'
(error)='onError($event)'
(executeOutcome)='onFormExecuteOutcome($event)'>
</adf-form>
<ng-template #withoutForm>
<adf-task-standalone *ngIf="isStandaloneTask(); else emptyFormMessage"
[taskName]="taskDetails.name"
[taskId]="taskDetails.id"
[isCompleted]="isCompletedTask()"
[hasCompletePermission]="isCompleteButtonVisible()"
[hideCancelButton]="showCancelButton"
(complete)="onCompleteTask()"
(showAttachForm)="onShowAttachForm()">
</adf-task-standalone>
<ng-template #emptyFormMessage>
<mat-card class="adf-task-form-container">
<mat-card-header>
<mat-card-title>
<h4>
<span class="adf-form-title">
{{taskDetails.name}}
<ng-container *ngIf="!taskDetails.name">
{{'FORM.FORM_RENDERER.NAMELESS_TASK' | translate}}
</ng-container>
</span>
</h4>
</mat-card-title>
</mat-card-header>
<mat-card-content>
<adf-empty-content *ngIf="isCompletedTask(); else emptyFormTemplate"
[icon]="'description'"
[title]="getCompletedTaskTranslatedMessage() | async"
[subtitle]="'ADF_TASK_FORM.COMPLETED_TASK.SUBTITLE'">
</adf-empty-content>
<ng-template #emptyFormTemplate>
<adf-empty-content
[icon]="'description'"
[title]="'ADF_TASK_LIST.STANDALONE_TASK.NO_FORM_MESSAGE'"
[subtitle]="'ADF_TASK_FORM.EMPTY_FORM.SUBTITLE'">
</adf-empty-content>
</ng-template>
</mat-card-content>
<mat-card-actions class="adf-task-form-actions">
<button id="adf-no-form-cancel-button" mat-button *ngIf="showCancelButton" (click)="onCancel()">
{{'ADF_TASK_FORM.EMPTY_FORM.BUTTONS.CANCEL' | translate}}
</button>
<button mat-button *ngIf="!isCompletedTask()" color="primary" (click)="onCompleteTask()" id="adf-no-form-complete-button">
{{'ADF_TASK_FORM.EMPTY_FORM.BUTTONS.COMPLETE' | translate}}
</button>
</mat-card-actions>
</mat-card>
</ng-template>
</ng-template>
</ng-container>
<ng-template #loadingTemplate>
<div fxLayout="row" fxLayoutAlign="center stretch">
<mat-spinner></mat-spinner>
</div>
</ng-template>

View File

@ -0,0 +1,32 @@
@mixin adf-task-form-theme($theme) {
$config: mat-typography-config();
.adf-task-form {
&-container {
overflow: hidden;
}
&-actions {
float: right;
padding-bottom: 25px !important;
padding-right: 25px !important;
& .mat-button {
height: 36px;
border-radius: 5px;
}
& .mat-button-wrapper {
width: 58px;
height: 20px;
opacity: 0.54;
font-size: mat-font-size($config, body-2);
font-weight: bold;
}
}
}
}

View File

@ -0,0 +1,504 @@
/*!
* @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 { TaskFormComponent } from './task-form.component';
import {
setupTestBed,
TranslationService,
TranslationMock,
FormService,
AuthenticationService,
FormModel,
FormOutcomeEvent,
FormOutcomeModel
} from '@alfresco/adf-core';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { TaskListService } from '../../services/tasklist.service';
import { TranslateStore } from '@ngx-translate/core';
import { NO_ERRORS_SCHEMA, SimpleChange } from '@angular/core';
import { of, throwError } from 'rxjs';
import {
taskFormMock,
taskDetailsMock,
completedTaskDetailsMock,
taskDetailsWithOutFormMock,
standaloneTaskWithoutForm,
completedStandaloneTaskWithoutForm,
claimableTaskDetailsMock,
initiatorCanCompleteTaskDetailsMock
} from '../../../mock/task/task-details.mock';
import { TaskDetailsModel } from '../../models/task-details.model';
import { TaskListModule } from '../../task-list.module';
describe('TaskFormComponent', () => {
let component: TaskFormComponent;
let fixture: ComponentFixture<TaskFormComponent>;
let formService: FormService;
let taskListService: TaskListService;
let getTaskDetailsSpy: jasmine.Spy;
let completeTaskSpy: jasmine.Spy;
let element: HTMLElement;
let authService: AuthenticationService;
let getBpmLoggedUserSpy: jasmine.Spy;
setupTestBed({
imports: [
NoopAnimationsModule,
TaskListModule
],
providers: [
{ provide: TranslationService, useClass: TranslationMock },
TranslateStore],
schemas: [NO_ERRORS_SCHEMA]
});
beforeEach(() => {
fixture = TestBed.createComponent(TaskFormComponent);
component = fixture.componentInstance;
element = fixture.nativeElement;
taskListService = TestBed.get(TaskListService);
formService = TestBed.get(FormService);
getTaskDetailsSpy = spyOn(taskListService, 'getTaskDetails').and.returnValue(of(taskDetailsMock));
completeTaskSpy = spyOn(taskListService, 'completeTask').and.returnValue(of({}));
spyOn(formService, 'getTaskForm').and.returnValue(of(taskFormMock));
taskDetailsMock.processDefinitionId = null;
spyOn(formService, 'getTask').and.returnValue(of(taskDetailsMock));
authService = TestBed.get(AuthenticationService);
getBpmLoggedUserSpy = spyOn(authService, 'getBpmLoggedUser').and.returnValue(of({ email: 'fake-email' }));
});
afterEach(async() => {
await fixture.whenStable();
getTaskDetailsSpy.calls.reset();
fixture.destroy();
});
describe('Task with form', () => {
beforeEach(async() => {
await fixture.whenStable();
getTaskDetailsSpy.calls.reset();
});
it('Should be able to display task form', async () => {
component.taskId = '123';
taskDetailsMock.formKey = '4';
component.currentLoggedUser = taskDetailsMock.assignee;
getTaskDetailsSpy.and.returnValue(of(taskDetailsMock));
fixture.detectChanges();
await fixture.whenStable();
const activitFormSelector = element.querySelector('adf-form');
const inputFieldOne = fixture.debugElement.nativeElement.querySelector('#text1');
const inputFieldTwo = fixture.debugElement.nativeElement.querySelector('#text2');
const inputFieldThree = fixture.debugElement.nativeElement.querySelector('#text3');
expect(activitFormSelector).toBeDefined();
expect(inputFieldOne['disabled']).toEqual(false);
expect(inputFieldTwo['disabled']).toEqual(false);
expect(inputFieldThree['disabled']).toEqual(false);
});
it('Should be able to complete assigned task', async () => {
getBpmLoggedUserSpy.and.returnValue(of({ id: 1001, firstName: 'Wilbur', lastName: 'Adams', email: 'wilbur@app.activiti.com' }));
getTaskDetailsSpy.and.returnValue(of(taskDetailsMock));
const formCompletedSpy: jasmine.Spy = spyOn(component.formCompleted, 'emit');
const completeTaskFormSpy = spyOn(formService, 'completeTaskForm').and.returnValue(of({}));
component.taskId = '123';
component.ngOnInit();
fixture.detectChanges();
await fixture.whenStable();
const completeButton = fixture.debugElement.nativeElement.querySelector('#adf-form-complete');
expect(completeButton['disabled']).toEqual(false);
completeButton.click();
expect(completeTaskFormSpy).toHaveBeenCalled();
expect(formCompletedSpy).toHaveBeenCalled();
});
it('Should be able to complete the task as a process initiator', async () => {
const formCompletedSpy: jasmine.Spy = spyOn(component.formCompleted, 'emit');
const completeTaskFormSpy = spyOn(formService, 'completeTaskForm').and.returnValue(of({}));
getTaskDetailsSpy.and.returnValue(of(initiatorCanCompleteTaskDetailsMock));
component.taskId = '123';
fixture.detectChanges();
await fixture.whenStable();
const activitFormSelector = element.querySelector('adf-form');
const completeButton = fixture.debugElement.nativeElement.querySelector('#adf-form-complete');
expect(activitFormSelector).toBeDefined();
expect(completeButton['disabled']).toEqual(false);
completeButton.click();
expect(completeTaskFormSpy).toHaveBeenCalled();
expect(formCompletedSpy).toHaveBeenCalled();
});
it('Should emit error event in case form complete service fails', async () => {
const errorSpy: jasmine.Spy = spyOn(component.error, 'emit');
const completeTaskFormSpy = spyOn(formService, 'completeTaskForm').and.returnValue(throwError({message: 'servce failed'}));
getTaskDetailsSpy.and.returnValue(of(initiatorCanCompleteTaskDetailsMock));
component.taskId = '123';
fixture.detectChanges();
await fixture.whenStable();
const activitFormSelector = element.querySelector('adf-form');
const completeButton = fixture.debugElement.nativeElement.querySelector('#adf-form-complete');
expect(activitFormSelector).toBeDefined();
expect(completeButton['disabled']).toEqual(false);
completeButton.click();
expect(completeTaskFormSpy).toHaveBeenCalled();
expect(errorSpy).toHaveBeenCalled();
});
});
describe('change detection', () => {
beforeEach(async() => {
component.taskId = '123';
fixture.detectChanges();
await fixture.whenStable();
getTaskDetailsSpy.calls.reset();
});
it('should fetch new task details when taskId changed', () => {
const change = new SimpleChange('123', '456', true);
component.ngOnChanges({ 'taskId': change });
fixture.detectChanges();
expect(getTaskDetailsSpy).toHaveBeenCalledWith('123');
});
it('should NOT fetch new task details when taskId changed to null', async () => {
const nullChange = new SimpleChange('123', null, true);
component.ngOnChanges({ 'taskId': nullChange });
fixture.detectChanges();
await fixture.whenStable();
expect(getTaskDetailsSpy).not.toHaveBeenCalled();
});
});
describe('Task assigned to candidates', () => {
beforeEach(async() => {
component.taskId = '123';
fixture.detectChanges();
await fixture.whenStable();
getTaskDetailsSpy.calls.reset();
});
it('Should be able to display form in readonly mode if the task assigned to candidates', async() => {
getTaskDetailsSpy.and.returnValue(of(claimableTaskDetailsMock));
fixture.detectChanges();
await fixture.whenStable();
const activitFormSelector = element.querySelector('adf-form');
const inputFieldOne = fixture.debugElement.nativeElement.querySelector('#text1');
const inputFieldTwo = fixture.debugElement.nativeElement.querySelector('#text2');
const inputFieldThree = fixture.debugElement.nativeElement.querySelector('#text3');
expect(activitFormSelector).toBeDefined();
expect(inputFieldOne['disabled']).toEqual(true);
expect(inputFieldTwo['disabled']).toEqual(true);
expect(inputFieldThree['disabled']).toEqual(true);
});
});
describe('Form events', () => {
beforeEach(() => {
component.taskId = '123';
fixture.detectChanges();
});
it('Should emit a save event when form saved', () => {
const formSavedSpy: jasmine.Spy = spyOn(component.formSaved, 'emit');
component.onFormSaved(new FormModel());
expect(formSavedSpy).toHaveBeenCalled();
});
it('Should emit a outcome execution event when form outcome executed', () => {
const executeOutcomeSpy: jasmine.Spy = spyOn(component.executeOutcome, 'emit');
component.onFormExecuteOutcome(new FormOutcomeEvent(new FormOutcomeModel(new FormModel())));
expect(executeOutcomeSpy).toHaveBeenCalled();
});
it('Should emit a complete event when form completed', () => {
const formCompletedSpy: jasmine.Spy = spyOn(component.formCompleted, 'emit');
component.onFormCompleted(new FormModel());
expect(formCompletedSpy).toHaveBeenCalled();
});
it('Should call service to complete task when complete button clicked', () => {
component.onCompleteTask();
expect(completeTaskSpy).toHaveBeenCalled();
});
it('Should emit a complete event when complete button clicked and task completed', () => {
const completeSpy: jasmine.Spy = spyOn(component.completed, 'emit');
component.onCompleteTask();
expect(completeSpy).toHaveBeenCalled();
});
it('Should emit a load event when form loaded', () => {
const formLoadedSpy: jasmine.Spy = spyOn(component.formLoaded, 'emit');
component.onFormLoaded(new FormModel());
expect(formLoadedSpy).toHaveBeenCalled();
});
it('Should emit an error event when form error occurs', () => {
const formErrorSpy: jasmine.Spy = spyOn(component.formError, 'emit');
component.onFormError({});
expect(formErrorSpy).toHaveBeenCalled();
});
it('Should emit an error event when form services fails', () => {
const errorSpy: jasmine.Spy = spyOn(component.error, 'emit');
component.onError({});
expect(errorSpy).toHaveBeenCalled();
});
});
describe('Completed Process Task', () => {
beforeEach(async() => {
component.taskId = '123';
fixture.detectChanges();
await fixture.whenStable();
getTaskDetailsSpy.calls.reset();
});
it('Should be able to display form in readonly mode if the task completed', async() => {
getTaskDetailsSpy.and.returnValue(of(completedTaskDetailsMock));
fixture.detectChanges();
await fixture.whenStable();
const activitFormSelector = element.querySelector('adf-form');
const inputFieldOne = fixture.debugElement.nativeElement.querySelector('#text1');
const inputFieldTwo = fixture.debugElement.nativeElement.querySelector('#text2');
const inputFieldThree = fixture.debugElement.nativeElement.querySelector('#text3');
expect(activitFormSelector).toBeDefined();
expect(inputFieldOne['disabled']).toEqual(true);
expect(inputFieldTwo['disabled']).toEqual(true);
expect(inputFieldThree['disabled']).toEqual(true);
});
it('Should be able to show completed message and cancel button for the completed task without form', async () => {
completedTaskDetailsMock.formKey = null;
component.taskDetails = new TaskDetailsModel(completedTaskDetailsMock);
fixture.detectChanges();
await fixture.whenStable();
const completeButtonElement = fixture.debugElement.nativeElement.querySelector('#adf-no-form-complete-button');
const cancelButtonElement = fixture.debugElement.nativeElement.querySelector('#adf-no-form-cancel-button');
const completedFormMessage = fixture.debugElement.nativeElement.querySelector('.adf-empty-content__title');
const subMessage = fixture.debugElement.nativeElement.querySelector('.adf-empty-content__subtitle');
expect(completeButtonElement).toBeNull();
expect(cancelButtonElement).not.toBeNull();
expect(completedFormMessage.innerText).toContain('ADF_TASK_FORM.COMPLETED_TASK.TITLE');
expect(subMessage.innerText).toContain('ADF_TASK_FORM.COMPLETED_TASK.SUBTITLE');
});
it('Should not display complete button to the completed task without form', async () => {
completedTaskDetailsMock.formKey = null;
component.taskDetails = new TaskDetailsModel(completedTaskDetailsMock);
fixture.detectChanges();
await fixture.whenStable();
const completeButtonElement = fixture.debugElement.nativeElement.querySelector('#adf-no-form-complete-button');
expect(completeButtonElement).toBeNull();
});
});
describe('Process Task with no form', () => {
beforeEach(() => {
component.taskId = '123';
fixture.detectChanges();
});
it('Should be able to show no form message if the task does not attached a form', async () => {
component.taskDetails = new TaskDetailsModel(taskDetailsWithOutFormMock);
fixture.detectChanges();
await fixture.whenStable();
const completeButtonElement = fixture.debugElement.nativeElement.querySelector('#adf-no-form-complete-button');
const cancelButtonElement = fixture.debugElement.nativeElement.querySelector('#adf-no-form-cancel-button');
const completedFormMessage = fixture.debugElement.nativeElement.querySelector('.adf-empty-content__title');
const subMessage = fixture.debugElement.nativeElement.querySelector('.adf-empty-content__subtitle');
expect(completeButtonElement).not.toBeNull();
expect(cancelButtonElement).not.toBeNull();
expect(completedFormMessage.innerText).toContain('ADF_TASK_LIST.STANDALONE_TASK.NO_FORM_MESSAGE');
expect(subMessage.innerText).toContain('ADF_TASK_FORM.EMPTY_FORM.SUBTITLE');
});
it('Should be able display complete button to a task without form', async () => {
component.taskDetails = new TaskDetailsModel(taskDetailsWithOutFormMock);
fixture.detectChanges();
await fixture.whenStable();
const completeButtonElement = fixture.debugElement.nativeElement.querySelector('#adf-no-form-complete-button');
expect(completeButtonElement).not.toBeNull();
expect(completeButtonElement['disabled']).toEqual(false);
});
it('Should be able to complete a task with no form when complete button is clicked', async () => {
fixture.detectChanges();
component.taskDetails = new TaskDetailsModel(taskDetailsWithOutFormMock);
fixture.detectChanges();
await fixture.whenStable();
const completeButtonElement = fixture.debugElement.nativeElement.querySelector('#adf-no-form-complete-button');
completeButtonElement.click();
expect(completeTaskSpy).toHaveBeenCalledWith('91');
});
it('Should emit error event in case complete task service fails', async () => {
const errorSpy: jasmine.Spy = spyOn(component.error, 'emit');
completeTaskSpy.and.returnValue(throwError({message: 'servce failed'}));
component.taskDetails = new TaskDetailsModel(taskDetailsWithOutFormMock);
fixture.detectChanges();
await fixture.whenStable();
const activitFormSelector = element.querySelector('adf-form');
const completeButtonElement = fixture.debugElement.nativeElement.querySelector('#adf-no-form-complete-button');
expect(activitFormSelector).toBeDefined();
expect(completeButtonElement['disabled']).toEqual(false);
completeButtonElement.click();
expect(errorSpy).toHaveBeenCalled();
});
it('Should be able to emit cancel event on task with no-form when cancel button is clicked', async () => {
const cancelSpy = spyOn(component.cancel, 'emit');
getTaskDetailsSpy.and.returnValue(of(taskDetailsWithOutFormMock));
component.taskDetails = new TaskDetailsModel(taskDetailsWithOutFormMock);
fixture.detectChanges();
await fixture.whenStable();
const cancelButtonElement = fixture.debugElement.nativeElement.querySelector('#adf-no-form-cancel-button');
cancelButtonElement.click();
expect(cancelSpy).toHaveBeenCalled();
});
});
describe('Standalone Task with no form', () => {
beforeEach(() => {
component.taskId = '123';
fixture.detectChanges();
});
it('Should be able to display empty template in case standalone task does not attached a form', async () => {
component.taskDetails = new TaskDetailsModel(standaloneTaskWithoutForm);
fixture.detectChanges();
await fixture.whenStable();
const taskStandAlone = element.querySelector('adf-task-standalone');
const noFormMessage = fixture.debugElement.nativeElement.querySelector('#adf-no-form-message');
expect(taskStandAlone).not.toBeNull();
expect(noFormMessage.innerText).toContain('ADF_TASK_LIST.STANDALONE_TASK.NO_FORM_MESSAGE');
});
it('Should be able display attach form button for a standalone task without form', async() => {
const showAttachFormSpy = spyOn(component.showAttachForm, 'emit');
component.taskDetails = new TaskDetailsModel(standaloneTaskWithoutForm);
fixture.detectChanges();
await fixture.whenStable();
const attacheFormButton = fixture.debugElement.nativeElement.querySelector('#adf-no-form-attach-form-button');
expect(attacheFormButton).not.toBeNull();
attacheFormButton.click();
expect(showAttachFormSpy).toHaveBeenCalled();
});
it('Should be able to display completed template if standalone task completed', async() => {
component.taskDetails = completedStandaloneTaskWithoutForm;
fixture.detectChanges();
await fixture.whenStable();
const taskStandAlone = element.querySelector('adf-task-standalone');
const completedFormMessage = fixture.debugElement.nativeElement.querySelector('#adf-completed-form-message');
const subMessage = fixture.debugElement.nativeElement.querySelector('.adf-no-form-submessage');
expect(taskStandAlone).not.toBeNull();
expect(completedFormMessage.innerText).toContain('ADF_TASK_LIST.STANDALONE_TASK.COMPLETE_TASK_MESSAGE');
expect(subMessage.innerText).toContain('ADF_TASK_LIST.STANDALONE_TASK.COMPLETE_TASK_SUB_MESSAGE');
});
});
describe('Form with visiblity', () => {
beforeEach(async () => {
component.taskId = '123';
spyOn(formService, 'completeTaskForm').and.returnValue(of({}));
taskDetailsMock.formKey = '4';
getTaskDetailsSpy.and.returnValue(of(taskDetailsMock));
fixture.detectChanges();
await fixture.whenStable();
});
it('[C312410] - Should be possible to complete a task that has an invisible field on a form with a value', async () => {
component.formCompleted.subscribe((form: FormModel) => {
expect(form.id).toBe(taskFormMock.id);
});
component.taskDetails.initiatorCanCompleteTask = true;
fixture.detectChanges();
await fixture.whenStable();
const inputTextOne: HTMLInputElement = fixture.nativeElement.querySelector('#text1');
expect(inputTextOne).toBeDefined();
expect(inputTextOne).not.toBeNull();
const inputTextTwo: HTMLInputElement = fixture.nativeElement.querySelector('#text2');
expect(inputTextTwo).toBeDefined();
expect(inputTextTwo).not.toBeNull();
let inputTextThree: HTMLInputElement = fixture.nativeElement.querySelector('#text3');
expect(inputTextThree).toBeDefined();
expect(inputTextThree).not.toBeNull();
inputTextOne.value = 'a';
inputTextOne.dispatchEvent(new Event('input'));
inputTextTwo.value = 'a';
inputTextTwo.dispatchEvent(new Event('input'));
inputTextThree.value = 'a';
inputTextThree.dispatchEvent(new Event('input'));
fixture.detectChanges();
await fixture.whenStable();
inputTextThree = fixture.nativeElement.querySelector('#text3');
expect(inputTextThree).toBeDefined();
expect(inputTextThree).not.toBeNull();
inputTextOne.value = 'b';
inputTextOne.dispatchEvent(new Event('input'));
fixture.detectChanges();
await fixture.whenStable();
const inputThreeContainer = fixture.nativeElement.querySelector('#field-text3-container');
expect(inputThreeContainer.hidden).toBe(true);
const completeOutcomeButton: HTMLButtonElement = fixture.nativeElement.querySelector('#adf-form-complete');
expect(completeOutcomeButton.hidden).toBe(false);
completeOutcomeButton.click();
fixture.detectChanges();
});
it('[C277278] - Should show if the form is valid via the validation icon', async () => {
const numberInput: HTMLInputElement = fixture.nativeElement.querySelector('#numberField');
let validationForm = fixture.nativeElement.querySelector('#adf-valid-form-icon');
expect(numberInput).toBeDefined();
expect(numberInput).not.toBeNull();
expect(validationForm.textContent).toBe('check_circle');
numberInput.value = 'a';
numberInput.dispatchEvent(new Event('input'));
fixture.detectChanges();
await fixture.whenStable();
const invalidForm = fixture.nativeElement.querySelector('#adf-invalid-form-icon');
expect(invalidForm).not.toBeNull();
expect(invalidForm.textContent).toBe('error');
numberInput.value = '4';
numberInput.dispatchEvent(new Event('input'));
fixture.detectChanges();
await fixture.whenStable();
validationForm = fixture.nativeElement.querySelector('#adf-valid-form-icon');
expect(validationForm.textContent).toBe('check_circle');
});
});
});

View File

@ -0,0 +1,287 @@
/*!
* @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, OnInit, Input, Output, EventEmitter, SimpleChanges } from '@angular/core';
import {
FormModel,
ContentLinkModel,
FormRenderingService,
FormFieldValidator,
FormOutcomeEvent,
AuthenticationService,
TranslationService,
FormFieldModel
} from '@alfresco/adf-core';
import { TaskDetailsModel } from '../../models/task-details.model';
import { TaskListService } from '../../services/tasklist.service';
import { UserRepresentation } from '@alfresco/js-api';
import { AttachFileWidgetComponent } from '../../../content-widget/attach-file-widget.component';
import { AttachFolderWidgetComponent } from '../../../content-widget/attach-folder-widget.component';
import { Observable } from 'rxjs';
@Component({
selector: 'adf-task-form',
templateUrl: './task-form.component.html',
styleUrls: ['./task-form.component.scss']
})
export class TaskFormComponent implements OnInit {
/** (**required**) The id of the task whose details we are asking for. */
@Input()
taskId: string;
/** Toggles rendering of the form title. */
@Input()
showFormTitle: boolean = false;
/** Toggles rendering of the `Complete` outcome button. */
@Input()
showFormCompleteButton: boolean = true;
/** Toggles rendering of the `Save` outcome button. */
@Input()
showFormSaveButton: boolean = true;
/** Toggle rendering of the `Cancel` button. */
@Input()
showCancelButton: boolean = true;
/** Toggles read-only state of the form. All form widgets render as read-only
* if enabled.
*/
@Input()
readOnlyForm: boolean = false;
/** Toggles rendering of the `Refresh` button. */
@Input()
showFormRefreshButton: boolean = true;
/** Toggle rendering of the validation icon next to the form title. */
@Input()
showFormValidationIcon: boolean = true;
/** Field validators for use with the form. */
@Input()
fieldValidators: FormFieldValidator[] = [];
/** Emitted when the form is submitted with the `Save` or custom outcomes. */
@Output()
formSaved = new EventEmitter<FormModel>();
/** Emitted when the form is submitted with the `Complete` outcome. */
@Output()
formCompleted = new EventEmitter<FormModel>();
/** Emitted when the form field content is clicked. */
@Output()
formContentClicked = new EventEmitter<ContentLinkModel>();
/** Emitted when the form is loaded or reloaded. */
@Output()
formLoaded = new EventEmitter<FormModel>();
/** Emitted when the form associated with the form task is attached. */
@Output()
showAttachForm: EventEmitter<void> = new EventEmitter<void>();
/** Emitted when any outcome is executed. Default behaviour can be prevented
* via `event.preventDefault()`.
*/
@Output()
executeOutcome = new EventEmitter<FormOutcomeEvent>();
/** Emitted when the form associated with the task is completed. */
@Output()
completed = new EventEmitter<void>();
/** Emitted when the supplied form values have a validation error. */
@Output()
formError: EventEmitter<FormFieldModel[]> = new EventEmitter<FormFieldModel[]>();
/** Emitted when an error occurs. */
@Output()
error = new EventEmitter<any>();
/** Emitted when the "Cancel" button is clicked. */
@Output()
cancel = new EventEmitter<void>();
taskDetails: TaskDetailsModel;
currentLoggedUser: UserRepresentation;
loading: boolean = false;
completedTaskMessage: string;
internalReadOnlyForm: boolean = false;
constructor(
private taskListService: TaskListService,
private authService: AuthenticationService,
private formRenderingService: FormRenderingService,
private translationService: TranslationService
) {
this.formRenderingService.setComponentTypeResolver('upload', () => AttachFileWidgetComponent, true);
this.formRenderingService.setComponentTypeResolver('select-folder', () => AttachFolderWidgetComponent, true);
}
ngOnInit() {
this.authService.getBpmLoggedUser().subscribe(user => {
this.currentLoggedUser = user;
});
this.loadTask(this.taskId);
}
ngOnChanges(changes: SimpleChanges) {
const taskId = changes['taskId'];
if (taskId && taskId.currentValue) {
this.loadTask(this.taskId);
return;
}
}
loadTask(taskId: string) {
this.loading = true;
if (taskId) {
this.taskListService.getTaskDetails(taskId).subscribe(
(res: TaskDetailsModel) => {
this.taskDetails = res;
if (!this.taskDetails.name) {
this.taskDetails.name = 'No name';
}
const endDate: any = res.endDate;
if (endDate && !isNaN(endDate.getTime())) {
this.internalReadOnlyForm = true;
} else {
this.internalReadOnlyForm = this.readOnlyForm;
}
this.loading = false;
});
}
}
onFormSaved(savedForm: FormModel) {
this.formSaved.emit(savedForm);
}
onFormCompleted(form: FormModel) {
this.formCompleted.emit(form);
}
onFormLoaded(form: FormModel): void {
this.formLoaded.emit(form);
}
onFormContentClick(content: ContentLinkModel): void {
this.formContentClicked.emit(content);
}
onFormExecuteOutcome(outcome: FormOutcomeEvent) {
this.executeOutcome.emit(outcome);
}
onFormError(error: any) {
this.formError.emit(error);
}
onError(error: any) {
this.error.emit(error);
}
onCompleteTask() {
this.taskListService.completeTask(this.taskDetails.id).subscribe(
() => this.completed.emit(),
(error) => this.error.emit(error));
}
onCancel() {
this.cancel.emit();
}
onShowAttachForm() {
this.showAttachForm.emit();
}
hasFormKey(): boolean {
return (this.taskDetails && (!!this.taskDetails.formKey));
}
isStandaloneTask(): boolean {
return !(this.taskDetails && (!!this.taskDetails.processDefinitionId));
}
isTaskLoaded(): boolean {
return !!this.taskDetails;
}
isCompletedTask(): boolean {
return this.taskDetails && this.taskDetails.endDate !== undefined && this.taskDetails.endDate !== null;
}
isCompleteButtonVisible(): boolean {
return !this.hasFormKey() && this.isTaskActive() && this.isCompleteButtonEnabled();
}
isTaskActive() {
return this.taskDetails && this.taskDetails.duration === null;
}
isAssigned(): boolean {
return !!this.taskDetails.assignee;
}
private hasEmailAddress(): boolean {
return this.taskDetails.assignee.email ? true : false;
}
isAssignedToMe(): boolean {
return this.isAssigned() && this.hasEmailAddress() ?
this.isEmailEqual() :
this.isExternalIdEqual();
}
private isEmailEqual(): boolean {
return (this.taskDetails.assignee && this.currentLoggedUser) && ( this.taskDetails.assignee.email.toLocaleLowerCase() === this.currentLoggedUser.email.toLocaleLowerCase());
}
private isExternalIdEqual(): boolean {
return (this.taskDetails.assignee && this.currentLoggedUser) && (this.taskDetails.assignee.externalId === this.currentLoggedUser.externalId);
}
isCompleteButtonEnabled(): boolean {
return this.isAssignedToMe() || this.canInitiatorComplete();
}
canInitiatorComplete(): boolean {
return this.taskDetails.initiatorCanCompleteTask;
}
isReadOnlyForm(): boolean {
return this.internalReadOnlyForm || !(this.isAssignedToMe() || this.canInitiatorComplete());
}
isSaveButtonVisible(): boolean {
return this.showFormSaveButton && (!this.canInitiatorComplete() || this.isAssignedToMe());
}
canCompleteTask(): boolean {
return !this.isCompletedTask() && this.isAssignedToMe();
}
getCompletedTaskTranslatedMessage(): Observable<string> {
return this.translationService.get('ADF_TASK_FORM.COMPLETED_TASK.TITLE', { taskName: this.taskDetails.name });
}
}

View File

@ -20,6 +20,7 @@ export * from './components/checklist.component';
export * from './components/task-header.component'; export * from './components/task-header.component';
export * from './components/no-task-detail-template.directive'; export * from './components/no-task-detail-template.directive';
export * from './components/task-filters.component'; export * from './components/task-filters.component';
export * from './components/task-form/task-form.component';
export * from './components/task-details.component'; export * from './components/task-details.component';
export * from './components/task-audit.directive'; export * from './components/task-audit.directive';
export * from './components/start-task.component'; export * from './components/start-task.component';

View File

@ -31,6 +31,7 @@ import { NoTaskDetailsTemplateDirective } from './components/no-task-detail-temp
import { StartTaskComponent } from './components/start-task.component'; import { StartTaskComponent } from './components/start-task.component';
import { TaskAuditDirective } from './components/task-audit.directive'; import { TaskAuditDirective } from './components/task-audit.directive';
import { TaskDetailsComponent } from './components/task-details.component'; import { TaskDetailsComponent } from './components/task-details.component';
import { TaskFormComponent } from './components/task-form/task-form.component';
import { TaskFiltersComponent } from './components/task-filters.component'; import { TaskFiltersComponent } from './components/task-filters.component';
import { TaskHeaderComponent } from './components/task-header.component'; import { TaskHeaderComponent } from './components/task-header.component';
import { TaskListComponent } from './components/task-list.component'; import { TaskListComponent } from './components/task-list.component';
@ -56,6 +57,7 @@ import { FormModule } from '../form/form.module';
TaskFiltersComponent, TaskFiltersComponent,
TaskListComponent, TaskListComponent,
TaskDetailsComponent, TaskDetailsComponent,
TaskFormComponent,
TaskAuditDirective, TaskAuditDirective,
ChecklistComponent, ChecklistComponent,
TaskHeaderComponent, TaskHeaderComponent,
@ -68,6 +70,7 @@ import { FormModule } from '../form/form.module';
TaskFiltersComponent, TaskFiltersComponent,
TaskListComponent, TaskListComponent,
TaskDetailsComponent, TaskDetailsComponent,
TaskFormComponent,
TaskAuditDirective, TaskAuditDirective,
ChecklistComponent, ChecklistComponent,
TaskHeaderComponent, TaskHeaderComponent,

View File

@ -26,9 +26,9 @@ export class FormFields {
saveButton: ElementFinder = element(by.cssContainingText('mat-card-actions[class*="adf-for"] span', 'SAVE')); saveButton: ElementFinder = element(by.cssContainingText('mat-card-actions[class*="adf-for"] span', 'SAVE'));
valueLocator: Locator = by.css('input'); valueLocator: Locator = by.css('input');
labelLocator: Locator = by.css('label'); labelLocator: Locator = by.css('label');
noFormMessage: ElementFinder = element(by.css('span[id*="no-form-message"]')); noFormMessage: ElementFinder = element(by.css('.adf-empty-content__title'));
completedTaskNoFormMessage: ElementFinder = element(by.css('div[id*="completed-form-message"] p')); completedTaskNoFormMessage: ElementFinder = element(by.css('div[id*="completed-form-message"] p'));
attachFormButton: ElementFinder = element(by.id('adf-no-form-attach-form-button')); attachFormButton: ElementFinder = element(by.id('adf-attach-form-attach-button'));
completeButton: ElementFinder = element(by.id('adf-form-complete')); completeButton: ElementFinder = element(by.id('adf-form-complete'));
errorMessage: Locator = by.css('.adf-error-text-container .adf-error-text'); errorMessage: Locator = by.css('.adf-error-text-container .adf-error-text');

View File

@ -28,9 +28,9 @@ export class FormFieldsPage {
saveButton: ElementFinder = element(by.cssContainingText('mat-card-actions[class*="adf-for"] span', 'SAVE')); saveButton: ElementFinder = element(by.cssContainingText('mat-card-actions[class*="adf-for"] span', 'SAVE'));
valueLocator: Locator = by.css('input'); valueLocator: Locator = by.css('input');
labelLocator: Locator = by.css('label'); labelLocator: Locator = by.css('label');
noFormMessage: ElementFinder = element(by.css('span[id*="no-form-message"]')); noFormMessage: ElementFinder = element(by.css('.adf-empty-content__title'));
completedTaskNoFormMessage: ElementFinder = element(by.css('div[id*="completed-form-message"] p')); completedTaskNoFormMessage: ElementFinder = element(by.css('div[id*="completed-form-message"] p'));
attachFormButton: ElementFinder = element(by.id('adf-no-form-attach-form-button')); attachFormButton: ElementFinder = element(by.id('adf-attach-form-attach-button'));
completeButton: ElementFinder = element(by.id('adf-form-complete')); completeButton: ElementFinder = element(by.id('adf-form-complete'));
errorMessage: Locator = by.css('.adf-error-text-container .adf-error-text'); errorMessage: Locator = by.css('.adf-error-text-container .adf-error-text');