diff --git a/src/components/task.vue b/src/components/task.vue index 777fae78b..12c3d78e1 100644 --- a/src/components/task.vue +++ b/src/components/task.vue @@ -284,19 +284,19 @@ export default { } this.prepareTask(); }, - closeTask() { + closeTask(parentRequestId = null) { if (this.hasErrors) { this.$emit('error', this.requestId); return; } if (this.task.process_request.status === 'COMPLETED') { - this.processCompleted(); + this.loadNextAssignedTask(parentRequestId); } else if (this.task.allow_interstitial) { this.task.interstitial_screen['_interstitial'] = true; this.screen = this.task.interstitial_screen; - this.loadNextAssignedTask(); + this.loadNextAssignedTask(parentRequestId); } else { this.$emit('closed', this.task.id); @@ -318,11 +318,18 @@ export default { } this.unsubscribeSocketListeners(); this.redirecting = task.process_request_id; - this.$emit('redirect', task); + this.$emit('redirect', task.id, true); return; + } else { + // Only emit completed after getting the subprocess tasks and there are no tasks and process is completed + if (requestId == this.task.process_request_id && this.parentRequest && this.task.process_request.status === 'COMPLETED') { + this.$emit('completed', this.parentRequest); + } } this.taskId = task.id; this.nodeId = task.element_id; + } else { + this.$emit('completed', (this.parentRequest ? this.parentRequest : requestId)); } }); }, @@ -366,9 +373,8 @@ export default { // This may no longer be needed }, processCompleted() { - if (this.parentRequest && this.task.allow_interstitial) { - // There could be another task in the parent, so don't emit completed - return; + if (this.parentRequest) { + this.$emit('completed', this.parentRequest); } this.$emit('completed', this.requestId); }, @@ -417,8 +423,13 @@ export default { `ProcessMaker.Models.ProcessRequest.${this.parentRequest}`, '.ProcessUpdated', (data) => { - if (['ACTIVITY_COMPLETED', 'ACTIVITY_ACTIVATED'].includes(data.event)) { - this.loadNextAssignedTask(this.parentRequest); + if (['ACTIVITY_ACTIVATED'].includes(data.event)) { + this.closeTask(this.parentRequest); + } + if (['ACTIVITY_COMPLETED'].includes(data.event)) { + if (this.task.process_request.status === 'COMPLETED') { + this.processCompleted(); + } } if (data.event === 'ACTIVITY_EXCEPTION') { this.$emit('error', this.requestId); diff --git a/src/main.js b/src/main.js index 69aa58e1f..816460b0f 100644 --- a/src/main.js +++ b/src/main.js @@ -152,7 +152,7 @@ window.ProcessMaker = { {value: 2, content: 'John'}, {value: 3, content: 'Mary'}, {value: 4, content: 'Patricia'}, - ], + ], }}); break; default: @@ -201,6 +201,16 @@ window.Echo = { }, 1000); }); }, + eventMocks(event, response) { + this.listeners.forEach((listener) => { + setTimeout(() => { + listener.callback({ + type: event, + response, + }); + }, 1000); + }); + }, private() { return { notification(callback) { diff --git a/tests/components/TaskRedirect.vue b/tests/components/TaskRedirect.vue new file mode 100644 index 000000000..328a782eb --- /dev/null +++ b/tests/components/TaskRedirect.vue @@ -0,0 +1,160 @@ + + + + + \ No newline at end of file diff --git a/tests/e2e/fixtures/single_line_input.json b/tests/e2e/fixtures/single_line_input.json new file mode 100644 index 000000000..b31a18574 --- /dev/null +++ b/tests/e2e/fixtures/single_line_input.json @@ -0,0 +1,573 @@ +{ + "type": "screen_package", + "version": "2", + "screens": [ + { + "id": 80, + "screen_category_id": "1", + "title": "FOUR-5159", + "description": "FOUR-5159", + "type": "FORM", + "config": [ + { + "name": "FOUR-5159", + "items": [ + { + "label": "Line Input", + "config": { + "icon": "far fa-square", + "name": "form_input_1", + "type": "text", + "label": "New Input", + "helper": null, + "dataFormat": "string", + "validation": null, + "placeholder": null + }, + "component": "FormInput", + "inspector": [ + { + "type": "FormInput", + "field": "name", + "config": { + "name": "Variable Name", + "label": "Variable Name", + "helper": "A variable name is a symbolic name to reference information.", + "validation": "regex:/^([a-zA-Z]([a-zA-Z0-9_]?)+\\.?)+(? Date ##/##/####
SSN ###-##-####
Phone (###) ###-####", + "validation": null + } + }, + { + "type": "FormInput", + "field": "customCssSelector", + "config": { + "label": "CSS Selector Name", + "helper": "Use this in your custom css rules", + "validation": "regex: [-?[_a-zA-Z]+[_-a-zA-Z0-9]*]" + } + }, + { + "type": "FormInput", + "field": "ariaLabel", + "config": { + "label": "Aria Label", + "helper": "Attribute designed to help assistive technology (e.g. screen readers) attach a label" + } + }, + { + "type": "FormInput", + "field": "tabindex", + "config": { + "label": "Tab Order", + "helper": "Order in which a user will move focus from one control to another by pressing the Tab key", + "validation": "regex: [0-9]*" + } + } + ], + "editor-control": "FormInput", + "editor-component": "FormInput" + }, + { + "label": "Submit Button", + "config": { + "icon": "fas fa-share-square", + "name": null, + "event": "submit", + "label": "New Submit", + "tooltip": [], + "variant": "primary", + "fieldValue": null, + "defaultSubmit": true + }, + "component": "FormButton", + "inspector": [ + { + "type": "FormInput", + "field": "label", + "config": { + "label": "Label", + "helper": "The label describes the button's text" + } + }, + { + "type": "FormInput", + "field": "name", + "config": { + "name": "Variable Name", + "label": "Variable Name", + "helper": "A variable name is a symbolic name to reference information.", + "validation": "regex:/^(?:[A-Za-z])(?:[0-9A-Z_.a-z])*(? Date ##/##/####
SSN ###-##-####
Phone (###) ###-####", + "validation": null + } + }, + { + "type": "FormInput", + "field": "customCssSelector", + "config": { + "label": "CSS Selector Name", + "helper": "Use this in your custom css rules", + "validation": "regex: [-?[_a-zA-Z]+[_-a-zA-Z0-9]*]" + } + }, + { + "type": "FormInput", + "field": "ariaLabel", + "config": { + "label": "Aria Label", + "helper": "Attribute designed to help assistive technology (e.g. screen readers) attach a label" + } + }, + { + "type": "FormInput", + "field": "tabindex", + "config": { + "label": "Tab Order", + "helper": "Order in which a user will move focus from one control to another by pressing the Tab key", + "validation": "regex: [0-9]*" + } + } + ], + "editor-control": "FormSubmit", + "editor-component": "FormButton" + } + ] + } + ], + "computed": [], + "custom_css": null, + "created_at": "2022-01-20T15:37:47+00:00", + "updated_at": "2022-01-20T15:37:57+00:00", + "status": "ACTIVE", + "key": null, + "watchers": [], + "categories": [ + { + "id": 1, + "name": "Uncategorized", + "status": "ACTIVE", + "is_system": 0, + "created_at": "2021-12-22T18:37:43+00:00", + "updated_at": "2021-12-22T18:37:43+00:00", + "pivot": { + "assignable_id": 80, + "category_id": 1, + "category_type": "ProcessMaker\\Models\\ScreenCategory" + } + } + ] + } + ], + "screen_categories": [], + "scripts": [] +} \ No newline at end of file diff --git a/tests/e2e/specs/Task.spec.js b/tests/e2e/specs/Task.spec.js index f1843f33b..b62978298 100644 --- a/tests/e2e/specs/Task.spec.js +++ b/tests/e2e/specs/Task.spec.js @@ -1,5 +1,6 @@ import moment from 'moment'; import Screens from '../fixtures/webentry.json'; +import SingleScreen from '../fixtures/single_line_input.json'; import InterstitialScreen from '../fixtures/interstitial_screen.json'; describe('Task component', () => { @@ -17,7 +18,7 @@ describe('Task component', () => { send(){}, })); win.Echo = { - + }; }, }); @@ -148,7 +149,7 @@ describe('Task component', () => { advanceStatus: 'completed', status: 'CLOSED', } - ); + ); cy.reload(); }); }); @@ -250,7 +251,7 @@ describe('Task component', () => { lastname: 'Doe', }, } - ); + ); cy.reload(); }); cy.wait(2000); @@ -316,4 +317,359 @@ describe('Task component', () => { }); cy.window().its('PM4ConfigOverrides.getScreenEndpoint').should('equal', 'tasks/1/screens'); }); + /* DNAT = Display Next Assigned Task + parentTask1 parentTask2 + \_______childTask1_______childTask2_______/ + (DNAT) + After childTask1 should redirect to childTask2 + */ + it('Task with display next assigned task checked with another pending task in same request should redirect to the next task of same request', () => { + cy.server(); + cy.route( + 'GET', + 'http://localhost:8080/api/1.0/tasks/1?include=data,user,requestor,processRequest,component,screen,requestData,bpmnTagName,interstitial,definition,nested', + { + id: 1, + advanceStatus: 'open', + component: 'task-screen', + screen: SingleScreen.screens[0], + process_request: { + id: 1, + status: 'ACTIVE', + }, + } + ); + + cy.visit('/?scenario=TaskRedirect', {}); + + cy.wait(2000); + cy.get('.form-group').find('button').click(); + + cy.route('PUT', 'http://localhost:8080/api/1.0/tasks/1').then(function() { + let responseDataTask1 = { + 'status': 'CLOSED', + 'process_request_id': 2, + 'id': 1, + 'screen': SingleScreen.screens[0], + 'allow_interstitial': true, + 'interstitial_screen': InterstitialScreen.screens[0], + }; + + getTask( + 'http://localhost:8080/api/1.0/tasks/'+responseDataTask1['id']+'?include=data,user,requestor,processRequest,component,screen,requestData,bpmnTagName,interstitial,definition,nested', + responseDataTask1 + ); + + let responseDataTasks = { + 'status': 'ACTIVE', + 'taskId': 2, + 'process_request_id': 2, + }; + + getTasks( + 'http://localhost:8080/api/1.0/tasks?user_id=1&status=ACTIVE&process_request_id=1&include_sub_tasks=1', + responseDataTasks + ); + + let responseDataTask2 = { + 'status': 'ACTIVE', + 'process_request_id': 2, + 'parent_request_id': 1, + 'taskId': 2, + 'screen': Screens.screens[0], + 'allow_interstitial': false, + 'interstitial_screen': null, + }; + + getTask( + 'http://localhost:8080/api/1.0/tasks/'+responseDataTask2['taskId']+'?include=data,user,requestor,processRequest,component,screen,requestData,bpmnTagName,interstitial,definition,nested', + responseDataTask2 + ); + + cy.wait(2000); + cy.reload(); + }); + + cy.url().should('eq', 'http://localhost:8080/tasks/2/edit'); + }); + + /* DNAT = Display Next Assigned Task + parentTask1 parentTask2 + \_______childTask1_______/ + (DNAT) + After childTask1 should redirect to parentTask2 + */ + it('Task with display next assigned task checked in different process request should redirect to the next task of parent request', () => { + cy.server(); + cy.route( + 'GET', + 'http://localhost:8080/api/1.0/tasks/1?include=data,user,requestor,processRequest,component,screen,requestData,bpmnTagName,interstitial,definition,nested', + { + id: 1, + advanceStatus: 'open', + component: 'task-screen', + screen: SingleScreen.screens[0], + process_request: { + id: 1, + status: 'ACTIVE', + }, + } + ); + + cy.visit('/?scenario=TaskRedirect', {}); + + cy.wait(2000); + cy.get('.form-group').find('button').click(); + + cy.route('PUT', 'http://localhost:8080/api/1.0/tasks/1').then(function() { + let responseDataTask1 = { + 'status': 'CLOSED', + 'process_request_id': 1, + 'id': 1, + 'screen': SingleScreen.screens[0], + 'allow_interstitial': true, + 'interstitial_screen': InterstitialScreen.screens[0], + }; + + getTask( + 'http://localhost:8080/api/1.0/tasks/'+responseDataTask1['id']+'?include=data,user,requestor,processRequest,component,screen,requestData,bpmnTagName,interstitial,definition,nested', + responseDataTask1 + ); + + let responseDataTasks = { + 'status': 'ACTIVE', + 'taskId': 2, + 'process_request_id': 2, + }; + + getTasks( + 'http://localhost:8080/api/1.0/tasks?user_id=1&status=ACTIVE&process_request_id=1&include_sub_tasks=1', + responseDataTasks + ); + + let responseDataTask2 = { + 'status': 'ACTIVE', + 'process_request_id': 1, + 'taskId': 2, + 'screen': Screens.screens[0], + 'allow_interstitial': false, + 'interstitial_screen': null, + }; + + getTask( + 'http://localhost:8080/api/1.0/tasks/'+responseDataTask2['taskId']+'?include=data,user,requestor,processRequest,component,screen,requestData,bpmnTagName,interstitial,definition,nested', + responseDataTask2 + ); + + cy.wait(2000); + cy.reload(); + }); + + cy.url().should('eq', 'http://localhost:8080/tasks/2/edit'); + }); + + /* DNAT = Display Next Assigned Task + parentTask1 parentTask2 + \_______childTask1_______/ + After childTask1 (Not DNAT) should redirect to tasks list + */ + it('Task with display next assigned task unchecked should redirect to tasks list', () => { + cy.server(); + cy.route( + 'GET', + 'http://localhost:8080/api/1.0/tasks/1?include=data,user,requestor,processRequest,component,screen,requestData,bpmnTagName,interstitial,definition,nested', + { + id: 1, + advanceStatus: 'open', + component: 'task-screen', + screen: SingleScreen.screens[0], + process_request: { + id: 1, + status: 'ACTIVE', + }, + } + ); + + cy.visit('/?scenario=TaskRedirect', {}); + + cy.wait(2000); + cy.get('.form-group').find('button').click(); + + cy.route('PUT', 'http://localhost:8080/api/1.0/tasks/1').then(function() { + let responseDataTask1 = { + 'status': 'CLOSED', + 'process_request_id': 1, + 'id': 1, + 'screen': SingleScreen.screens[0], + 'allow_interstitial': false, + 'interstitial_screen': null, + }; + + getTask( + 'http://localhost:8080/api/1.0/tasks/'+responseDataTask1['id']+'?include=data,user,requestor,processRequest,component,screen,requestData,bpmnTagName,interstitial,definition,nested', + responseDataTask1 + ); + + cy.wait(2000); + cy.reload(); + }); + + cy.url().should('eq', 'http://localhost:8080/tasks'); + }); + + /* DNAT = Display Next Assigned Task + parentTask1_____________________endEvent + (DNAT) + After parentTask1 and not pending tasks should redirect to same request + */ + it('Process without pending task should redirect to request', () => { + cy.server(); + cy.route( + 'GET', + 'http://localhost:8080/api/1.0/tasks/1?include=data,user,requestor,processRequest,component,screen,requestData,bpmnTagName,interstitial,definition,nested', + { + id: 1, + advanceStatus: 'open', + component: 'task-screen', + screen: SingleScreen.screens[0], + process_request: { + id: 1, + status: 'ACTIVE', + }, + } + ); + + cy.visit('/?scenario=TaskRedirect', {}); + + cy.wait(2000); + cy.get('.form-group').find('button').click(); + + cy.route('PUT', 'http://localhost:8080/api/1.0/tasks/1').then(function() { + let responseDataTask1 = { + 'status': 'CLOSED', + 'process_request_id': 1, + 'id': 1, + 'screen': SingleScreen.screens[0], + 'allow_interstitial': true, + 'interstitial_screen': InterstitialScreen.screens[0], + }; + + getTask( + 'http://localhost:8080/api/1.0/tasks/'+responseDataTask1['id']+'?include=data,user,requestor,processRequest,component,screen,requestData,bpmnTagName,interstitial,definition,nested', + responseDataTask1 + ); + + getTasks('http://localhost:8080/api/1.0/tasks?user_id=1&status=ACTIVE&process_request_id=1&include_sub_tasks=1'); + + cy.wait(2000); + cy.reload(); + }); + + cy.url().should('eq', 'http://localhost:8080/requests/1'); + }); + + /* DNAT = Display Next Assigned Task + parentTask1 endEvent + \_______childTask1_______/ + (DNAT) + After childTask1 and not pending tasks should redirect to parent Request + */ + it('Subprocess without pending task should redirect to parent request', () => { + cy.server(); + cy.route( + 'GET', + 'http://localhost:8080/api/1.0/tasks/1?include=data,user,requestor,processRequest,component,screen,requestData,bpmnTagName,interstitial,definition,nested', + { + id: 1, + advanceStatus: 'open', + component: 'task-screen', + screen: SingleScreen.screens[0], + process_request: { + id: 2, + status: 'ACTIVE', + parent_request_id: 1, + }, + } + ); + + cy.visit('/?scenario=TaskRedirect', {}); + + cy.wait(2000); + cy.get('.form-group').find('button').click(); + + cy.route('PUT', 'http://localhost:8080/api/1.0/tasks/1').then(function() { + let responseDataTask1 = { + 'status': 'CLOSED', + 'process_request_id': 2, + 'parent_request_id': 3, + 'id': 1, + 'screen': SingleScreen.screens[0], + 'allow_interstitial': true, + 'interstitial_screen': InterstitialScreen.screens[0], + }; + + getTask( + 'http://localhost:8080/api/1.0/tasks/'+responseDataTask1['id']+'?include=data,user,requestor,processRequest,component,screen,requestData,bpmnTagName,interstitial,definition,nested', + responseDataTask1 + ); + + getTasks('http://localhost:8080/api/1.0/tasks?user_id=1&status=ACTIVE&process_request_id=1&include_sub_tasks=1'); + + cy.wait(2000); + cy.reload(); + }); + + cy.url().should('eq', 'http://localhost:8080/requests/3'); + }); }); + +function getTask(url, responseData) { + cy.route( + 'GET', + url, + { + id: responseData['id'], + advanceStatus: 'completed', + component: 'task-screen', + status: responseData['status'], + allow_interstitial: responseData['allow_interstitial'], + interstitial_screen: responseData['interstitial_screen'], + screen: responseData['screen'], + process_request: { + id: 1, + parent_request_id: responseData['parent_request_id'], + status: responseData['status'], + }, + } + ); +} +function getTasks(url, responseData = null) { + if (responseData) { + cy.route( + 'GET', + url, + { + data: + [ + { + id: responseData['taskId'], + advanceStatus: 'open', + process_id: 1, + process_request_id: responseData['process_request_id'], + subprocess_request_id: 1, + status: responseData['status'], + completed_at: null, + due_at: moment().add(1, 'day').toISOString(), + due_notified: 0, + process_request: { + id: 1, + status: responseData['status'], + }, + }, + ], + } + ); + } else { + cy.route('GET', url, {data:[]}); + } +} \ No newline at end of file diff --git a/tests/e2e/support/commands.js b/tests/e2e/support/commands.js index 8c996a98b..8a9b1fa6d 100644 --- a/tests/e2e/support/commands.js +++ b/tests/e2e/support/commands.js @@ -33,6 +33,12 @@ Cypress.Commands.add('setVueComponentProperty', (selector, property, value) => { }); }); +Cypress.Commands.add('socketEvent', (event, body) => { + cy.window().then((win) => { + win.Echo.eventMocks(event, body); + }); +}); + /** * Converts Cypress fixtures, including JSON, to a Blob. All file types are * converted to base64 then converted to a Blob using Cypress