diff --git a/apps/hpc-ftsadmin-e2e/src/fixtures/add-flow.json b/apps/hpc-ftsadmin-e2e/src/fixtures/add-flow.json new file mode 100644 index 000000000..111ab8816 --- /dev/null +++ b/apps/hpc-ftsadmin-e2e/src/fixtures/add-flow.json @@ -0,0 +1,14 @@ +{ + "test1": { + "add-flow-source-organization-field": "Germany, Government of", + "add-flow-source-usage-year-field": "2022", + "add-flow-destination-project-field": "apts627", + "add-flow-amount-USD-field": "8994018977", + "add-flow-original-currency-funding-amount-field": "200000000000000", + "add-flow-exchange-rate-field": "22237", + "add-flow-description-field": "GV, WASH EDU", + "add-flow-flow-status-field": "Commitment", + "add-flow-earmarking-field": "Unearmarked", + "add-flow-report-channel-field": "Other" + } +} diff --git a/apps/hpc-ftsadmin-e2e/src/support/commands.ts b/apps/hpc-ftsadmin-e2e/src/support/commands.ts index 0ab74356a..2fb954eb2 100644 --- a/apps/hpc-ftsadmin-e2e/src/support/commands.ts +++ b/apps/hpc-ftsadmin-e2e/src/support/commands.ts @@ -17,6 +17,7 @@ declare global { interface Chainable { login(username: string): void; getDummyData(): DummyData; + typedGet(dataTest: DataTest): Cypress.Chainable>; } } } @@ -60,3 +61,65 @@ Cypress.Commands.add('login', (username: string) => { const dataWithUser = { ...parsedData, currentUser: userIds[0] }; localStorage.setItem(STORAGE_KEY, JSON.stringify(dataWithUser)); }); + +/* + * When removing data-test properties from code, + * please also remove it from here + */ +export type DataTest = + | 'flows-nav-button' + | 'flows-table' + | 'flows-table-checkbox' + | 'flows-table-id' + | 'flows-table-status' + | 'flows-table-updated' + | 'flows-table-external-reference' + | 'flows-table-amount-usd' + | 'flows-table-source-organization' + | 'flows-table-destination-organization' + | 'flows-table-plans' + | 'flows-table-locations' + | 'flows-table-years' + | 'flows-table-details' + | 'flows-table-newMoney' + | 'flows-table-decisionDate' + | 'flows-table-exchangeRate' + | 'flows-table-flowDate' + | 'flows-table-sourceSystemId' + | 'flows-table-reporterRefCode' + | 'flows-table-exchangeRate' + | 'flows-table-pagination' + | `flows-table-row-${number}v${number}` // flow.id v flow.versionID + | `flows-table-header-${string}` // TableHeadersProps.label as value + | 'pending-flows-nav-button' + | 'pending-flows-bulk-reject-button' + | 'keywords-nav-button' + | 'keywords-table-id' + | 'keywords-table-name' + | 'keywords-table-relatedFlows' + | 'keywords-table-public' + | `keywords-table-row-${number}` // keyword.id + | `keywords-table-header-${string}` // TableHeadersProps.label as value + | 'organizations-nav-button' + | 'organizations-table-id' + | 'organizations-table-name' + | 'organizations-table-abbreviation' + | 'organizations-table-type' + | 'organizations-table-subType' + | 'organizations-table-location' + | 'organizations-table-created-by' + | 'organizations-table-updated-by' + | `keywords-table-row-${number}` // organization.id + | `organizations-table-header-${string}` // TableHeadersProps.label as value + | 'organizations-table-pagination'; + +/* + * Command that makes the same as cy.get() + * but we use it for known elements. + * + * Ex:
test
+ * Our type for dataTest will define "test" as a possible value + */ +Cypress.Commands.add('typedGet', (dataTest: DataTest) => { + return cy.get(`[data-test="${dataTest}"]`); +}); diff --git a/apps/hpc-ftsadmin-e2e/src/support/select-component-utils.ts b/apps/hpc-ftsadmin-e2e/src/support/select-component-utils.ts new file mode 100644 index 000000000..ecb067b78 --- /dev/null +++ b/apps/hpc-ftsadmin-e2e/src/support/select-component-utils.ts @@ -0,0 +1,3 @@ +export const selectOption = (option: string) => { + cy.contains('li', option).click(); +}; diff --git a/apps/hpc-ftsadmin-e2e/src/tests/add-flow.cy.ts b/apps/hpc-ftsadmin-e2e/src/tests/add-flow.cy.ts new file mode 100644 index 000000000..36d03b5a6 --- /dev/null +++ b/apps/hpc-ftsadmin-e2e/src/tests/add-flow.cy.ts @@ -0,0 +1,151 @@ +import * as ADD_FLOW from '../fixtures/add-flow.json'; +import { selectOption } from '../support/select-component-utils'; + +describe('hpc-ftsadmin add-flow', () => { + beforeEach(() => { + cy.visit('/'); + cy.login('Admin User'); + cy.visit('/'); + cy.location('pathname').should('eq', '/flows'); + cy.typedGet('add-flow-nav-button').click(); + cy.location('pathname').should('eq', '/flow/add'); + }); + + it('bulkRejectPendingFlows workflow', () => { + cy.typedGet('add-flow-source-organization-field').type( + ADD_FLOW['test1']['add-flow-source-organization-field'] + ); + selectOption(ADD_FLOW['test1']['add-flow-source-organization-field']); + + cy.typedGet('add-flow-source-organization-field').should( + 'contain', + ADD_FLOW['test1']['add-flow-source-organization-field'] + ); + cy.typedGet('add-flow-source-location-field').should('contain', 'Germany'); + + cy.typedGet('add-flow-source-usage-year-field').type( + ADD_FLOW['test1']['add-flow-source-usage-year-field'] + ); + selectOption(ADD_FLOW['test1']['add-flow-source-usage-year-field']); + + cy.typedGet('add-flow-destination-project-field').type( + ADD_FLOW['test1']['add-flow-destination-project-field'] + ); + selectOption(ADD_FLOW['test1']['add-flow-destination-project-field']); + + cy.typedGet('add-flow-destination-plan-field').should( + 'contain', + 'Afghanistan 2002 (ITAP for the Afghan People)' + ); + cy.typedGet('add-flow-destination-global-cluster-field').should( + 'contain', + 'Multi-sector' + ); + cy.typedGet('add-flow-destination-usage-year-field').should( + 'contain', + '2022' + ); + cy.typedGet('add-flow-destination-location-field').should( + 'contain', + 'Afghanistan' + ); + cy.typedGet('add-flow-destination-organization-field').should( + 'contain', + "United Nations Children's Fund" + ); + + /* + * Original Currency related testing + */ + + cy.typedGet('add-flow-original-currency-dropdown').click(); + + cy.typedGet('add-flow-original-currency-funding-amount-field').type( + ADD_FLOW['test1']['add-flow-original-currency-funding-amount-field'] + ); + cy.typedGet('add-flow-original-currency-field').click(); + selectOption('LAK'); + + cy.typedGet('add-flow-exchange-rate-field').type( + ADD_FLOW['test1']['add-flow-exchange-rate-field'] + ); + + cy.typedGet('add-flow-original-currency-button').should( + 'contain.text', + 'Calculate the funding amount in USD' + ); + cy.typedGet('add-flow-original-currency-funding-amount-field').clear(); + + cy.typedGet('add-flow-amount-USD-field').type( + ADD_FLOW['test1']['add-flow-amount-USD-field'] + ); + + cy.typedGet('add-flow-original-currency-button').should( + 'contain.text', + 'Calculate funding amount in its original currency' + ); + + cy.typedGet('add-flow-exchange-rate-field').clear(); + cy.typedGet('add-flow-original-currency-funding-amount-field').type( + ADD_FLOW['test1']['add-flow-original-currency-funding-amount-field'] + ); + + cy.typedGet('add-flow-original-currency-button').should( + 'contain.text', + 'Calculate the exchange rate' + ); + + cy.typedGet('add-flow-original-currency-button').should( + 'contain.text', + 'Calculate the exchange rate' + ); + /* + * -------------------------------------------------------- + */ + + cy.typedGet('add-flow-description-field').type( + ADD_FLOW['test1']['add-flow-description-field'] + ); + + const date = new Date().toLocaleDateString('en-GB'); + cy.typedGet('add-flow-first-reported-field').type(date); + cy.typedGet('add-flow-decision-date-field').type(date); + + cy.typedGet('add-flow-flow-status-field').click(); + selectOption('Commitment'); + + cy.typedGet('add-flow-flow-date-field').type(date); + + // Properly write + cy.typedGet('add-flow-reported-by-organization-options').click(); + + cy.typedGet('add-flow-reported-channel-field').click(); + selectOption('Fax'); + + cy.typedGet('add-flow-date-reported-field').should('contain', date); + + cy.typedGet('add-flow-create-button').click(); + + cy.typedGet('add-flow-copy-button').click(); + + cy.typedGet('add-flow-title').should('contain', 'Copy'); + + cy.typedGet('add-flow-add-parent-flow-button').click(); + + // TODO: Get previous flow ID + cy.typedGet('add-flow-add-parent-flow-field').type('PREVIOUS FLOW ID'); + selectOption('PREVIOUS FLOW ID'); + + cy.typedGet('add-flow-add-parent-flow-submit-button').click(); + + cy.typedGet('add-flow-parent-flow-table').should('exist'); + }); + + /** + * TODO: Add more checking to the test once edit flow is in the app + */ + it('Enter pending-flow', () => { + cy.typedGet('flows-table-row-305776v1').click(); + cy.location('pathname').should('eq', '/flows/305776'); + }); +}); diff --git a/apps/hpc-ftsadmin-e2e/src/tests/test.cy.ts b/apps/hpc-ftsadmin-e2e/src/tests/login.cy.ts similarity index 100% rename from apps/hpc-ftsadmin-e2e/src/tests/test.cy.ts rename to apps/hpc-ftsadmin-e2e/src/tests/login.cy.ts diff --git a/apps/hpc-ftsadmin-e2e/src/tests/pending-flow.cy.ts b/apps/hpc-ftsadmin-e2e/src/tests/pending-flow.cy.ts new file mode 100644 index 000000000..60286d533 --- /dev/null +++ b/apps/hpc-ftsadmin-e2e/src/tests/pending-flow.cy.ts @@ -0,0 +1,30 @@ +describe('hpc-ftsadmin pending-flow', () => { + beforeEach(() => { + cy.visit('/'); + cy.login('Admin User'); + cy.visit('/'); + cy.location('pathname').should('eq', '/flows'); + cy.typedGet('pending-flows-nav-button').click(); + cy.location('pathname').should('eq', '/pending-flows'); + }); + + it('bulkRejectPendingFlows workflow', () => { + cy.typedGet('flows-table') + .find('input[type="checkbox"]') + .each(($checkbox) => { + cy.wrap($checkbox).click(); + }); + + cy.typedGet('pending-flows-bulk-reject-button').click(); + cy.contains('No results found'); + cy.typedGet('flows-table').should('not.exist'); + }); + + /** + * TODO: Add more checking to the test once edit flow is in the app + */ + it('Enter pending-flow', () => { + cy.typedGet('flows-table-row-305776v1').click(); + cy.location('pathname').should('eq', '/flows/305776'); + }); +}); diff --git a/apps/hpc-ftsadmin-e2e/tsconfig.e2e.json b/apps/hpc-ftsadmin-e2e/tsconfig.e2e.json index fdfc65e24..b5cd2a2fc 100644 --- a/apps/hpc-ftsadmin-e2e/tsconfig.e2e.json +++ b/apps/hpc-ftsadmin-e2e/tsconfig.e2e.json @@ -7,5 +7,10 @@ "allowJs": true, "composite": true }, - "include": ["**/*.ts", "**/*.js", "../../libs/hpc-dummy/src/index.ts"] + "include": [ + "**/*.ts", + "**/*.js", + "../../libs/hpc-dummy/src/index.ts", + "./src/fixtures/*.json" + ] } diff --git a/apps/hpc-ftsadmin/src/app/app.tsx b/apps/hpc-ftsadmin/src/app/app.tsx index 88b5efa40..895eadda0 100644 --- a/apps/hpc-ftsadmin/src/app/app.tsx +++ b/apps/hpc-ftsadmin/src/app/app.tsx @@ -127,18 +127,22 @@ export const App = () => { { label: t.t(lang, (s) => s.navigation.flows), path: paths.flows(), + dataTest: 'flows-nav-button', }, { label: t.t(lang, (s) => s.navigation.pendingFlows), path: paths.pendingFlows(), + dataTest: 'pending-flows-nav-button', }, { label: t.t(lang, (s) => s.navigation.organizations), path: paths.organizations(), + dataTest: 'organizations-nav-button', }, { label: t.t(lang, (s) => s.navigation.keywords), path: paths.keywords(), + dataTest: 'keywords-nav-button', }, ]} className={CLASSES.CONTAINER.FLUID} diff --git a/apps/hpc-ftsadmin/src/app/components/tables/flows-table.tsx b/apps/hpc-ftsadmin/src/app/components/tables/flows-table.tsx index 7a8b5935c..9d200f6d1 100644 --- a/apps/hpc-ftsadmin/src/app/components/tables/flows-table.tsx +++ b/apps/hpc-ftsadmin/src/app/components/tables/flows-table.tsx @@ -241,6 +241,7 @@ export default function FlowsTable(props: FlowsTableProps) { ? tw`bg-unocha-primary bg-opacity-10` : undefined, }} + data-test={`flows-table-row-${row.id}v${row.versionID}`} > {props.pending && ( - + @@ -750,6 +751,7 @@ export default function FlowsTable(props: FlowsTableProps) { (s) => s.components.flowsTable.rejectPendingFlows.button )} displayLoading={loading} + dataTest="pending-flows-bulk-reject-button" onClick={() => submitForm()} /> } diff --git a/apps/hpc-ftsadmin/src/app/components/tables/keywords-table.tsx b/apps/hpc-ftsadmin/src/app/components/tables/keywords-table.tsx index 837504c7d..8a7880736 100644 --- a/apps/hpc-ftsadmin/src/app/components/tables/keywords-table.tsx +++ b/apps/hpc-ftsadmin/src/app/components/tables/keywords-table.tsx @@ -300,7 +300,10 @@ export default function KeywordTable(props: KeywordTableProps) { ) ) .map((row) => ( - + {tableHeaders.map((column) => { if (!column.active) { return null; @@ -313,7 +316,7 @@ export default function KeywordTable(props: KeywordTableProps) { size="small" component="th" scope="row" - data-test="keyword-table-id" + data-test="keywords-table-id" > {row.id} @@ -325,7 +328,7 @@ export default function KeywordTable(props: KeywordTableProps) { component="th" size="small" scope="row" - data-test="keyword-table-name" + data-test="keywords-table-name" > {row.refCount} @@ -350,7 +353,7 @@ export default function KeywordTable(props: KeywordTableProps) { {row.description === 'public' ? : '--'} @@ -392,7 +395,7 @@ export default function KeywordTable(props: KeywordTableProps) { {tableHeaders.map((column) => { if (!column.active) { @@ -177,7 +178,7 @@ export default function OrganizationTable(props: OrganizationTableProps) { size="small" component="th" scope="row" - data-test="organization-table-id" + data-test="organizations-table-id" > {row.id} @@ -189,7 +190,7 @@ export default function OrganizationTable(props: OrganizationTableProps) { component="th" size="small" scope="row" - data-test="organization-table-name" + data-test="organizations-table-name" > {row.name} @@ -199,7 +200,7 @@ export default function OrganizationTable(props: OrganizationTableProps) { {row.abbreviation} @@ -209,7 +210,7 @@ export default function OrganizationTable(props: OrganizationTableProps) { {(() => { const res = row.categories.filter( @@ -226,7 +227,7 @@ export default function OrganizationTable(props: OrganizationTableProps) { {(() => { const res = row.categories.filter( @@ -243,7 +244,7 @@ export default function OrganizationTable(props: OrganizationTableProps) { {row.locations.length > 0 ? row.locations.map( @@ -260,7 +261,7 @@ export default function OrganizationTable(props: OrganizationTableProps) { {parseUpdatedCreatedBy(row.create, lang)} {} @@ -271,7 +272,7 @@ export default function OrganizationTable(props: OrganizationTableProps) { {parseUpdatedCreatedBy(row.update, lang)} @@ -314,7 +315,7 @@ export default function OrganizationTable(props: OrganizationTableProps) { { @@ -73,6 +74,7 @@ const BaseButton = (props: Props) => { active, condensed, autoFocus, + dataTest, } = props; const [focused, setFocused] = useState(false); @@ -108,6 +110,7 @@ const BaseButton = (props: Props) => { className={className} onClick={behaviour.onClick} ref={ref as React.RefObject} + data-test={dataTest} > {contents} @@ -117,6 +120,7 @@ const BaseButton = (props: Props) => { onClick={behaviour.onClick} type={behaviour.type} ref={ref as React.RefObject} + data-test={dataTest} > {contents} @@ -125,6 +129,7 @@ const BaseButton = (props: Props) => { className={className} to={behaviour.to} ref={ref as React.RefObject} + data-test={dataTest} > {contents} diff --git a/libs/hpc-ui/src/lib/components/main-navigation.tsx b/libs/hpc-ui/src/lib/components/main-navigation.tsx index c5d6fd871..5e619f745 100644 --- a/libs/hpc-ui/src/lib/components/main-navigation.tsx +++ b/libs/hpc-ui/src/lib/components/main-navigation.tsx @@ -29,6 +29,7 @@ interface Props { path: string; label: string; selected?: boolean; + dataTest?: string; } | null | undefined @@ -167,7 +168,11 @@ export default (props: Props) => { loc.pathname.startsWith(tab.path + '/') : tab.selected; return ( -
  • +
  • {tab.label}