From 00079c72c3e30094a7037301cfbf74c21899b531 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathias=20H=C3=B6ld?= Date: Mon, 17 Jun 2019 11:19:11 +0200 Subject: [PATCH] api: Send workflow item currency with subproject details When the subproject details are fetched, the workflow item currency is now returned as well. Removed the action 'fetchWorkflowItems' as it is not used. Add E2E-test to check this bug. Closes #281 --- CHANGELOG.md | 4 +- api/src/subproject_view_details.ts | 2 + .../integration/workflowitem_edit_spec.js | 115 ++++++++++++++++++ frontend/src/pages/Common/Identifier.js | 4 +- frontend/src/pages/Common/TextInput.js | 5 +- frontend/src/pages/Workflows/Workflow.js | 2 +- .../src/pages/Workflows/WorkflowContainer.js | 2 - .../pages/Workflows/WorkflowDialogAmount.js | 7 +- frontend/src/pages/Workflows/WorkflowItem.js | 20 ++- frontend/src/pages/Workflows/actions.js | 10 -- 10 files changed, 149 insertions(+), 22 deletions(-) create mode 100644 e2e-test/cypress/integration/workflowitem_edit_spec.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 31c936a12..e43c435ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - +### Fixed + +- Display correct currency when editing workflow items [#281](https://github.com/openkfw/TruBudget/issues/281) diff --git a/api/src/subproject_view_details.ts b/api/src/subproject_view_details.ts index f5b2ca942..ace241a7e 100644 --- a/api/src/subproject_view_details.ts +++ b/api/src/subproject_view_details.ts @@ -133,6 +133,7 @@ interface ExposedWorkflowitem { data: { id: string; creationUnixTs: string; + currency: string | null | undefined; displayName: string | null; exchangeRate: string | undefined | null; billingDate: string | undefined | null; @@ -267,6 +268,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi creationUnixTs: toUnixTimestampStr(workflowitem.createdAt), displayName: workflowitem.displayName, exchangeRate: workflowitem.exchangeRate, + currency: workflowitem.currency, billingDate: workflowitem.billingDate, amountType: workflowitem.amountType, description: workflowitem.description, diff --git a/e2e-test/cypress/integration/workflowitem_edit_spec.js b/e2e-test/cypress/integration/workflowitem_edit_spec.js new file mode 100644 index 000000000..444a8be13 --- /dev/null +++ b/e2e-test/cypress/integration/workflowitem_edit_spec.js @@ -0,0 +1,115 @@ +describe("Workflowitem's history", function() { + let projectId; + let subprojectId; + + before(() => { + cy.login(); + + cy.createProject("workflowitem edit test project", "workflowitem edit test", []) + .then(({ id }) => { + projectId = id; + return cy.createSubproject(projectId, "workflowitem edit test", "EUR"); + }) + .then(({ id }) => { + subprojectId = id; + }); + }); + + beforeEach(function() { + cy.login(); + cy.visit(`/projects/${projectId}/${subprojectId}`); + }); + + it("After creating an allocated workflowitem, the currency is equal to the subproject's currency", function() { + // Create a workflow item + cy.get("[data-test=createWorkflowitem]").click(); + cy.get("[data-test=nameinput] input").type("Test"); + cy.get("[data-test=commentinput] textarea") + .last() + .type("Test"); + cy.get("[data-test=amount-type-allocated]").click(); + cy.get("[data-test=dropdown-currencies-click]").should("contain", "EUR"); + + // When the currency is equal to the currency of the subproject + // the exchange rate field is disabled + cy.get("[data-test=rateinput] input").should("be.disabled"); + }); + + it("After selecting another currencty, the exchange rate can be entered and is saved correctly", function() { + // Create a workflow item + cy.get("[data-test=createWorkflowitem]").click(); + cy.get("[data-test=nameinput] input").type("Test"); + cy.get("[data-test=commentinput] textarea") + .last() + .type("Test"); + cy.get("[data-test=amount-type-allocated]").click(); + + // Select a different currency than the subproject currency + cy.get("[data-test=dropdown-currencies-click]").click(); + cy.get("[data-value=USD]").click(); + + // Enter amount + cy.get("[data-test=amountinput] input").type("1234"); + + // The exchange rate field should be enabled because + // we selected a different currency + cy.get("[data-test=rateinput] input").should("be.enabled"); + cy.get("[data-test=rateinput] input").type("1.5"); + cy.get("[data-test=next]").click(); + cy.get("[data-test=submit]").click(); + + // The workflow item amount should be displayed in the + // subproject's currency + cy.get("[data-test=workflowitem-amount]") + .first() + .should("contain", "€"); + + // The information on the workflow item amount + // and exchange rate is displayed in a tooltip + cy.get("[data-test=amount-explanation]") + .first() + .should("have.attr", "title") + .should("contain", "$"); + }); + + it( + "When editing a workflow item with a different currency than the subproject currency, " + + "the selected currency is displayed", + function() { + // Create a workflow item and select a different currency + cy.get("[data-test=createWorkflowitem]").click(); + cy.get("[data-test=nameinput] input").type("Test"); + cy.get("[data-test=commentinput] textarea") + .last() + .type("Test"); + cy.get("[data-test=amount-type-allocated]").click(); + cy.get("[data-test=dropdown-currencies-click]").click(); + cy.get("[data-value=USD]").click(); + cy.get("[data-test=amountinput] input").type("1234"); + cy.get("[data-test=rateinput] input").should("be.enabled"); + cy.get("[data-test=rateinput] input").type("1.5"); + cy.get("[data-test=next]").click(); + cy.get("[data-test=submit]").click(); + + // Verify the selected values + cy.get("[data-test=workflowitem-amount]") + .first() + .should("contain", "€"); + cy.get("[data-test=amount-explanation]") + .first() + .should("have.attr", "title") + .should("contain", "$"); + + // Edit the workflow item and verify that the + // pre-selected currency is the one we selected + // when the workflow item was created + cy.get("[data-test=edit-workflowitem]") + .last() + .click(); + cy.get("[data-test=dropdown-currencies-click]").should("contain", "USD"); + + // Close the dialog + cy.get("[data-test=cancel]").click(); + } + ); +}); diff --git a/frontend/src/pages/Common/Identifier.js b/frontend/src/pages/Common/Identifier.js index bc0017531..da443f4f1 100644 --- a/frontend/src/pages/Common/Identifier.js +++ b/frontend/src/pages/Common/Identifier.js @@ -21,7 +21,7 @@ class Identifier extends Component { helperText={this.props.nameHintText} value={this.props.name} onChange={this.props.nameOnChange} - id="nameinput" + data-test={this.props.commentId || "nameinput"} /> ); diff --git a/frontend/src/pages/Common/TextInput.js b/frontend/src/pages/Common/TextInput.js index 7923d8c68..af1e60afb 100644 --- a/frontend/src/pages/Common/TextInput.js +++ b/frontend/src/pages/Common/TextInput.js @@ -20,7 +20,9 @@ const TextInput = ({ multiline = false, type = "text", disabled = false, - id + id, + // eslint-disable-next-line no-useless-computed-key + ["data-test"]: dataTest }) => ( onChange(event.target.value)} onBlur={onBlur} pattern={pattern} + data-test={dataTest} /> ); diff --git a/frontend/src/pages/Workflows/Workflow.js b/frontend/src/pages/Workflows/Workflow.js index 1147cafc8..646309f9b 100644 --- a/frontend/src/pages/Workflows/Workflow.js +++ b/frontend/src/pages/Workflows/Workflow.js @@ -38,7 +38,7 @@ const Workflow = props => { {/* Button is disabled either if the user is not allowed to edit or the user is in "sort" mode */} props.showCreateDialog()} style={{ diff --git a/frontend/src/pages/Workflows/WorkflowContainer.js b/frontend/src/pages/Workflows/WorkflowContainer.js index 1d543673a..63eedf38a 100644 --- a/frontend/src/pages/Workflows/WorkflowContainer.js +++ b/frontend/src/pages/Workflows/WorkflowContainer.js @@ -21,7 +21,6 @@ import { enableSubProjectBudgetEdit, enableWorkflowEdit, fetchAllSubprojectDetails, - fetchWorkflowItems, hideWorkflowDetails, hideWorkflowDialog, hideWorkflowitemAdditionalData, @@ -142,7 +141,6 @@ const mapDispatchToProps = (dispatch, ownProps) => { closeSubproject: (pId, sId) => dispatch(closeSubproject(pId, sId, true)), closeWorkflowItem: (pId, sId, wId) => dispatch(closeWorkflowItem(pId, sId, wId, true)), - fetchWorkflowItems: streamName => dispatch(fetchWorkflowItems(streamName)), setSelectedView: (id, section) => dispatch(setSelectedView(id, section)), showWorkflowItemPermissions: wId => dispatch(showWorkflowItemPermissions(wId)), diff --git a/frontend/src/pages/Workflows/WorkflowDialogAmount.js b/frontend/src/pages/Workflows/WorkflowDialogAmount.js index b8d2402df..e26809249 100644 --- a/frontend/src/pages/Workflows/WorkflowDialogAmount.js +++ b/frontend/src/pages/Workflows/WorkflowDialogAmount.js @@ -67,16 +67,19 @@ class WorkflowDialogAmount extends Component { value="N/A" control={} label={strings.workflow.workflow_budget_na} + data-test="amount-type-na" /> } label={strings.workflow.workflow_budget_allocated} + data-test="amount-type-allocated" /> } label={strings.workflow.workflow_budget_disbursed} + data-test="amount-type-disbursed" /> @@ -133,7 +136,7 @@ class WorkflowDialogAmount extends Component { type="text" multiline={false} aria-label="amount" - id="amountinput" + data-test="amountinput" style={{ width: "20%", paddingRight: 20 @@ -152,7 +155,7 @@ class WorkflowDialogAmount extends Component { }} type="text" aria-label="rate" - id="rateinput" + data-test="rateinput" disabled={workflowCurrency === subProjectCurrency} style={{ width: "20%", diff --git a/frontend/src/pages/Workflows/WorkflowItem.js b/frontend/src/pages/Workflows/WorkflowItem.js index ad1a5bd62..513d62918 100644 --- a/frontend/src/pages/Workflows/WorkflowItem.js +++ b/frontend/src/pages/Workflows/WorkflowItem.js @@ -257,7 +257,7 @@ const getAmountField = (amount, type, exchangeRate, sourceCurrency, targetCurren const amountExplanationTitle = toAmountString(amount, sourceCurrency) + " x " + exchangeRate; const amountExplaination = ( - + ); @@ -307,7 +307,17 @@ const getCardStyle = (workflowSortEnabled, status) => { return style; }; -const ActionButton = ({ notVisible, disabled, onClick, icon, title, workflowSortEnabled, status }) => { +const ActionButton = ({ + notVisible, + disabled, + onClick, + icon, + title, + workflowSortEnabled, + status, + // eslint-disable-next-line no-useless-computed-key + ["data-test"]: dataTest +}) => { return (
{icon} @@ -360,6 +371,7 @@ const renderActionButtons = ( title={additionalDataDisabled ? "" : strings.common.additional_data} workflowSortEnabled={workflowSortEnabled} status={status} + data-test="additional-data-icon" />
- + {amountType === "N/A" ? amountTypes(amountType) : getAmountField(amount, amountType, exchangeRate, sourceCurrency, targetCurrency)} diff --git a/frontend/src/pages/Workflows/actions.js b/frontend/src/pages/Workflows/actions.js index 4d2a83c01..0a9c9c7ff 100644 --- a/frontend/src/pages/Workflows/actions.js +++ b/frontend/src/pages/Workflows/actions.js @@ -1,6 +1,3 @@ -export const FETCH_WORKFLOW_ITEMS = "FETCH_WORKFLOW_ITEMS"; -export const FETCH_WORKFLOW_ITEMS_SUCCESS = "FETCH_WORKFLOW_ITEMS_SUCCESS"; - export const SHOW_WORKFLOW_CREATE = "SHOW_WORKFLOW_CREATE"; export const HIDE_WORKFLOW_DIALOG = "HIDE_WORKFLOW_DIALOG"; @@ -344,13 +341,6 @@ export function updateWorkflowOrderOnState(workflowItems) { }; } -export function fetchWorkflowItems(streamName) { - return { - type: FETCH_WORKFLOW_ITEMS, - streamName: streamName - }; -} - export function storeWorkflowActions(actions) { return { type: STORE_WORKFLOWACTIONS,